libmudtelnet-rs
Robust, event‑driven Telnet (RFC 854) parsing for MUD clients in Rust — with minimal allocations, strong real‑world compatibility, and clean, typed events.
Standing on the Shoulders of Giants
libmudtelnet-rs is a fork of libmudtelnet by the Blightmud team, which powers the Blightmud MUD client. The original libmudtelnet was itself forked from libtelnet-rs by envis10n, which is inspired by the robust C library libtelnet by Sean Middleditch. We are deeply grateful for their foundational work.
This project continues the specialization for the unique and demanding world of MUDs. Our focus is on adding comprehensive MUD protocol support, fixing critical edge-case bugs encountered in the wild, and relentlessly pursuing the correctness and performance that modern MUD clients deserve.
Why MUD clients need this
You're building a MUD client. You need to handle Telnet negotiation, GMCP data streams, MCCP compression, and MSDP variables. Your parser must be robust against malformed sequences from decades-old servers while maintaining zero-allocation performance in hot paths.
Standard Telnet parsers expect compliance; MUD servers offer chaos. A stray SE byte without IAC, truncated subnegotiations, or multiple escaped IAC sequences can crash naive implementations. libmudtelnet handles these realities gracefully while delivering clean, structured events for your application logic.
Use libmudtelnet‑rs so you can focus on building triggers, mappers, and UIs instead of debugging protocol edge cases.
Install
Add to your Cargo.toml:
[]
= "2"
- MSRV: Rust 1.66+
- License: MIT
Protocol support
MUD‑specific (fully supported)
-
GMCP (201) - Generic MUD Communication Protocol
- Most widely adopted for JSON game data exchange
- Complete negotiation and subnegotiation with payloads
- Clean event delivery for your JSON parser
-
MSDP (69) - MUD Server Data Protocol
- Structured data with VAR/VAL/TABLE/ARRAY tags
- Complete tag definitions for robust parsing
- State tracking for complex data structures
-
MCCP2/MCCP3 (86/87) - MUD Client Compression Protocol
- Compression negotiation with proper signaling
- Special
DecompressImmediateevents for boundary handling - Supports both server-to-client and bidirectional compression
-
MXP (91) - MUD eXtension Protocol
- Markup and hyperlink protocol negotiation
- Complete subnegotiation data delivery
-
MSSP (70) - MUD Server Status Protocol
- Server information and capability exchange
- Structured data parsing support
-
ZMP (93) - Zenith MUD Protocol
- Package and module system negotiation
- Extensible protocol framework support
-
ATCP (200) - Achaea Telnet Client Protocol
- Legacy IRE MUD protocol support
- Backward compatibility for older systems
Standard Telnet options (negotiable)
- NAWS (31) - Negotiate About Window Size
- TTYPE (24) - Terminal Type negotiation
- CHARSET (42) - Character set negotiation (RFC 2066)
- ECHO (1) - Echo control
- SGA (3) - Suppress Go Ahead
- BINARY (0) - Binary transmission mode
- EOR (25) - End of Record markers
- TSPEED (32) - Terminal speed negotiation
- ENVIRON/NEWENVIRON (36/39) - Environment variables
- LINEMODE (34) - Line-at-a-time input mode
- Plus 30+ additional standard options with full state tracking
Quickstart
Basic parser loop
use ;
use op_option;
let mut parser = new;
// Feed bytes from your socket
let events = parser.receive;
for ev in events
// Send a line (IACs escaped for you, "\r\n" appended)
let to_send = parser.send_text;
if let DataSend = to_send
Tokio integration (async)
If your app uses Tokio, integrate the parser in your read loop and write any DataSend bytes back to the socket as-is. Example skeleton:
use ;
use ;
use TcpStream;
async
See a full template in examples/tokio_client.rs.
Protocol negotiation
use ;
use ;
use ;
let mut parser = new;
// Announce that we WILL use GMCP locally
let will_gmcp = parser.negotiate;
if let DataSend = will_gmcp
// Ask the server to DO NAWS (report window size)
let do_naws = parser.negotiate;
if let DataSend = do_naws
Interop note: GMCP/MSDP bidirectional after DO
Many MUD servers expect GMCP/MSDP to be bidirectional once the server sends
IAC WILL GMCP|MSDP and the client responds IAC DO. libmudtelnet‑rs follows
this behavior:
- It accepts GMCP/MSDP subnegotiations after a server
WILL/DOhandshake even if the client never sentWILL. - It also allows the client to send GMCP/MSDP subnegotiations once the remote
side is active, preventing deadlocks with servers that do not echo
DOto a clientWILL.
Other options retain standard Telnet semantics.
MSDP data handling
use msdp;
Event‑driven architecture
libmudtelnet-rs transforms the Telnet byte stream into a clean sequence of structured events:
- DataReceive: Text and data from the MUD server
- DataSend: Bytes your application must write to the socket
- Negotiation: WILL/WONT/DO/DONT option negotiations
- Subnegotiation: Protocol payloads (GMCP, MSDP, etc.)
- IAC: Low-level Telnet commands
- DecompressImmediate: MCCP compression boundary signals
This separation keeps your network I/O simple and your protocol handling clean.
Battle-Tested Robustness
Edge‑case handling
libmudtelnet-rs has been hardened against real-world protocol violations:
- Unescaped SE bytes: A
SEbyte without precedingIACduring subnegotiation is handled gracefully - Truncated subnegotiations: Malformed sequences like
IAC SB IAC SEwon't cause panics - Multiple IAC escaping: Complex escape sequences (
IAC IAC IAC IAC) are correctly unescaped - Option 0xFF handling: Negotiation of option 255 with truncated data is handled safely
Testing and validation
- Fuzz Testing: Continuous fuzzing with
cargo-fuzzbombards the parser with malformed inputs - Compatibility Tests: Validates behavior against libtelnet-rs test cases
- Edge Case Tests: Specific tests for each documented bug fix
- Property Tests: Round-trip and invariant validation
- Production Heritage: Serves as the foundation for Blightmud's Telnet implementation
Performance and memory
- Zero-allocation hot paths: Uses
bytes::BytesMutto avoid copies in parsing loops - Zero-copy events: Protocol payloads delivered as
bytes::Bytesslices - Efficient state tracking: Minimal memory footprint for negotiation states
- no_std compatible: Works in embedded environments (disable default features)
API stability
libmudtelnet-rs maintains API compatibility with libtelnet-rs where practical. The event semantics are stable - breaking changes follow semver and include migration guides.
Contributing
We welcome contributions from the MUD and Rust communities! Whether you've found a bug, want to add protocol support, or improve documentation, your help is appreciated.
Getting started
# Build and test
# Code quality
# Fuzz testing (requires cargo-fuzz)
&&
# Benchmarks
&&
Ways to contribute
-
Report Bugs: Found a MUD server that sends data the parser doesn't handle? Please open an issue with details
-
Add Protocol Support: Want support for a new MUD protocol? Let's discuss implementation approach
-
Improve Tests: Additional fuzz targets, edge cases, or property tests are always valuable
-
Documentation: Code examples, protocol explanations, or usage guides
-
Good first issues: check the good first issue label
-
Examples: see
examples/basic.rs,examples/tokio_client.rs, anddocs/API_EXAMPLES.md
This project follows the Rust Code of Conduct. We're committed to providing a welcoming environment for all contributors.
Compatibility
libmudtelnet-rs has been tested for API compatibility with libtelnet-rs. While much of the implementation has been rewritten for improved correctness and performance, the public API remains familiar to ease migration.
See CHANGELOG.md for detailed information about fixes and enhancements.
Credits
Many thanks to:
- The Blightmud team for their work on libmudtelnet, which
libmudtelnet-rsis forked from. - envis10n for his work on libtelnet-rs, which
libmudtelnetwas originally forked from. - Sean Middleditch for his work on libtelnet, which inspired
libtelnet-rs.