Crate libmudtelnet_rs

Crate libmudtelnet_rs 

Source
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-rs where practical.
  • Optional no_std support (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 returns Vec<TelnetEvents>.
  • events: Defines TelnetEvents, 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

MCCP decompression boundary

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_std environments: libmudtelnet-rs = { version = "*", default-features = false }
  • In no_std, APIs behave the same; internal buffers use bytes.

Tips

  • Always write out TelnetEvents::DataSend exactly as provided.
  • Treat TelnetEvents::DataReceive as application data; it has already had any doubled IACs unescaped.
  • Negotiate only options you actually support (via compatibility).
  • Prefer working with &[u8]/Bytes payloads; avoid String unless 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 for no_std.
  • arbitrary: Implements arbitrary::Arbitrary for fuzzing/dev.

FAQ

  • “Why does send_text return an event?” To unify I/O: everything that must go to the socket is surfaced as TelnetEvents::DataSend(Bytes).
  • “Do I need to escape IAC myself?” No when using send_text and subnegotiation[_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§

vbytesDeprecated
Macro for calling Bytes::copy_from_slice()

Structs§

Parser
Stateful, event‑driven Telnet parser.