Expand description
libmudtelnet-rs — Robust, event‑driven Telnet (RFC 854) for MUD clients
libmudtelnet-rs turns a raw Telnet byte stream into a sequence of strongly-typed events you can act on. It prioritizes correctness, compatibility with real MUD servers, and performance in hot paths.
- Minimal allocations in hot paths using
bytes::Bytes/BytesMut. - Defensive parsing of malformed and truncated inputs (no panics on input).
- Event semantics compatible with
libtelnet-rswhere practical. - Optional
no_stdsupport (disable default features).
When to use this crate
- You are building a MUD client or tool and need reliable Telnet parsing.
- You want clean events for negotiation, subnegotiation (GMCP/MSDP/etc.), and data, without dealing with byte-level edge cases.
Core concepts
Parser: Stateful Telnet parser. Feed it bytes; it returnsVec<TelnetEvents>.events: DefinesTelnetEvents, the event enum your code matches on.telnet: Constants for Telnet commands (op_command) and options (op_option).compatibility: Negotiation support/state table used by the parser.
Quickstart
use libmudtelnet_rs::{Parser, events::TelnetEvents};
let mut parser = Parser::new();
let events = parser.receive(b"hello \xff\xff world\r\n");
for ev in events {
match ev {
TelnetEvents::DataReceive(buf) => {
// Application text/data from the server
let _bytes = &buf[..];
}
TelnetEvents::Negotiation(n) => {
// WILL/WONT/DO/DONT notifications (state is tracked internally)
let _cmd = n.command;
let _opt = n.option;
}
TelnetEvents::Subnegotiation(sub) => {
// Protocol payload (e.g., GMCP/MSDP)
let _which = sub.option;
let _payload = &sub.buffer[..];
}
TelnetEvents::IAC(_)
| TelnetEvents::DataSend(_)
| TelnetEvents::DecompressImmediate(_) => {}
_ => {}
}
}
// Sending text (IAC bytes escaped for you):
let _send = parser.send_text("look\r\n");Negotiation and subnegotiation
- Use
Parser::_will,Parser::_wont,Parser::_do,Parser::_dontto drive option state changes for options you support (seecompatibility). - Use
Parser::subnegotiationto send payloads; the parser wraps bytes withIAC SB <option> ... IAC SEand escapesIACinside the body.
MCCP decompression boundary
- For MCCP2/3, when a compression turn‑on is received and supported, the parser
emits a
TelnetEvents::Subnegotiationfollowed byTelnetEvents::DecompressImmediatecontaining the bytes that must be decompressed before feeding back intoParser::receive.
Example: handling MCCP2/3 boundaries
use libmudtelnet_rs::{Parser, events::TelnetEvents};
fn decompress_identity(data: &[u8]) -> Vec<u8> { data.to_vec() }
let mut parser = Parser::new();
for ev in parser.receive(&[]) {
match ev {
TelnetEvents::DecompressImmediate(data) => {
let decompressed = decompress_identity(&data);
let more = parser.receive(&decompressed);
// handle `more` like any other events
drop(more);
}
_ => {}
}
}no_std
- Disable default features to build in
no_stdenvironments:libmudtelnet-rs = { version = "*", default-features = false } - In
no_std, APIs behave the same; internal buffers usebytes.
Tips
- Always write out
TelnetEvents::DataSendexactly as provided. - Treat
TelnetEvents::DataReceiveas application data; it has already had any doubled IACs unescaped. - Negotiate only options you actually support (via
compatibility). - Prefer working with
&[u8]/Bytespayloads; avoidStringunless you know the server’s encoding.
Common recipes
- NAWS (window size) send
let mut parser = Parser::new(); let mut payload = BytesMut::with_capacity(4); payload.put_u16(120); // width payload.put_u16(40); // height if let Some(TelnetEvents::DataSend(buf)) = parser.subnegotiation(NAWS, payload.freeze()) { /* write buf to socket */ } - TTYPE (terminal type) identify
let mut parser = Parser::new(); // IAC SB TTYPE IS "xterm-256color" IAC SE let mut body = Vec::new(); body.extend([IAC, IS]); body.extend(b"xterm-256color"); if let Some(TelnetEvents::DataSend(buf)) = parser.subnegotiation(TTYPE, body) { /* write buf to socket */ } - GMCP send (JSON text)
let mut parser = Parser::new(); let json = r#"{\"Core.Supports.Add\":[\"Room 1\"]}"#; if let Some(TelnetEvents::DataSend(buf)) = parser.subnegotiation_text(GMCP, json) { /* write buf to socket */ } - Escape/unescape IAC when handling raw buffers
let escaped = Parser::escape_iac(vec![IAC, 1, 2]); let roundtrip = Parser::unescape_iac(escaped); assert_eq!(&roundtrip[..], [IAC, 1, 2]);
Feature flags
std(default): Enables standard library usage. Disable forno_std.arbitrary: Implementsarbitrary::Arbitraryfor fuzzing/dev.
FAQ
- “Why does
send_textreturn an event?” To unify I/O: everything that must go to the socket is surfaced asTelnetEvents::DataSend(Bytes). - “Do I need to escape IAC myself?” No when using
send_textandsubnegotiation[_text]. Yes only if you craft raw buffers yourself. - “Where is TCP handled?” Out of scope; this crate is protocol parsing only.
Re-exports§
pub use bytes;
Modules§
- compatibility
- Negotiation support and state table.
- events
- Event types emitted by the Telnet parser.
- telnet
- Telnet command and option constants.
Macros§
- vbytes
Deprecated - Macro for calling
Bytes::copy_from_slice()
Structs§
- Parser
- Stateful, event‑driven Telnet parser.