libmudtelnet_rs/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2#![warn(clippy::pedantic)]
3#![allow(
4 clippy::module_name_repetitions,
5 clippy::fn_params_excessive_bools,
6 clippy::struct_excessive_bools
7)]
8#![forbid(unsafe_code)]
9#![cfg_attr(docsrs, feature(doc_cfg))]
10
11//! libmudtelnet-rs — Robust, event‑driven Telnet (RFC 854) for MUD clients
12//!
13//! libmudtelnet-rs turns a raw Telnet byte stream into a sequence of
14//! strongly-typed events you can act on. It prioritizes correctness,
15//! compatibility with real MUD servers, and performance in hot paths.
16//!
17//! - Minimal allocations in hot paths using `bytes::Bytes/BytesMut`.
18//! - Defensive parsing of malformed and truncated inputs (no panics on input).
19//! - Event semantics compatible with `libtelnet-rs` where practical.
20//! - Optional `no_std` support (disable default features).
21//!
22//! When to use this crate
23//! - You are building a MUD client or tool and need reliable Telnet parsing.
24//! - You want clean events for negotiation, subnegotiation (GMCP/MSDP/etc.),
25//! and data, without dealing with byte-level edge cases.
26//!
27//! Core concepts
28//! - [`Parser`]: Stateful Telnet parser. Feed it bytes; it returns `Vec<TelnetEvents>`.
29//! - [`events`]: Defines [`TelnetEvents`], the event enum your code matches on.
30//! - [`telnet`]: Constants for Telnet commands (`op_command`) and options (`op_option`).
31//! - [`compatibility`]: Negotiation support/state table used by the parser.
32//!
33//! Quickstart
34//! ```rust no_run
35//! use libmudtelnet_rs::{Parser, events::TelnetEvents};
36//!
37//! let mut parser = Parser::new();
38//! let events = parser.receive(b"hello \xff\xff world\r\n");
39//!
40//! for ev in events {
41//! match ev {
42//! TelnetEvents::DataReceive(buf) => {
43//! // Application text/data from the server
44//! let _bytes = &buf[..];
45//! }
46//! TelnetEvents::Negotiation(n) => {
47//! // WILL/WONT/DO/DONT notifications (state is tracked internally)
48//! let _cmd = n.command;
49//! let _opt = n.option;
50//! }
51//! TelnetEvents::Subnegotiation(sub) => {
52//! // Protocol payload (e.g., GMCP/MSDP)
53//! let _which = sub.option;
54//! let _payload = &sub.buffer[..];
55//! }
56//! TelnetEvents::IAC(_)
57//! | TelnetEvents::DataSend(_)
58//! | TelnetEvents::DecompressImmediate(_) => {}
59//! _ => {}
60//! }
61//! }
62//!
63//! // Sending text (IAC bytes escaped for you):
64//! let _send = parser.send_text("look\r\n");
65//! ```
66//!
67//! Negotiation and subnegotiation
68//! - Use [`Parser::_will`], [`Parser::_wont`], [`Parser::_do`], [`Parser::_dont`] to drive
69//! option state changes for options you support (see [`compatibility`]).
70//! - Use [`Parser::subnegotiation`] to send payloads; the parser wraps bytes with
71//! `IAC SB <option> ... IAC SE` and escapes `IAC` inside the body.
72//! - GMCP/MSDP interop: Once the server offers `WILL GMCP|MSDP` and the client
73//! responds with `DO`, both sides may send subnegotiations. The parser treats
74//! GMCP and MSDP as bidirectional after `WILL/DO`: it will accept their
75//! subnegotiations when either side is active, and it will allow the client to
76//! send them when the remote side is active, even if the client never sent
77//! `WILL`. This matches common MUD server behavior and avoids handshake
78//! deadlocks.
79//!
80//! MCCP decompression boundary
81//! - For MCCP2/3, when a compression turn‑on is received and supported, the parser
82//! emits a [`TelnetEvents::Subnegotiation`] followed by [`TelnetEvents::DecompressImmediate`]
83//! containing the bytes that must be decompressed before feeding back into
84//! [`Parser::receive`].
85//!
86//! Example: handling MCCP2/3 boundaries
87//! ```rust no_run
88//! use libmudtelnet_rs::{Parser, events::TelnetEvents};
89//!
90//! fn decompress_identity(data: &[u8]) -> Vec<u8> { data.to_vec() }
91//!
92//! let mut parser = Parser::new();
93//! for ev in parser.receive(&[]) {
94//! match ev {
95//! TelnetEvents::DecompressImmediate(data) => {
96//! let decompressed = decompress_identity(&data);
97//! let more = parser.receive(&decompressed);
98//! // handle `more` like any other events
99//! drop(more);
100//! }
101//! _ => {}
102//! }
103//! }
104//! ```
105//!
106//! `no_std`
107//! - Disable default features to build in `no_std` environments:
108//! `libmudtelnet-rs = { version = "*", default-features = false }`
109//! - In `no_std`, APIs behave the same; internal buffers use `bytes`.
110//!
111//! Tips
112//! - Always write out `TelnetEvents::DataSend` exactly as provided.
113//! - Treat `TelnetEvents::DataReceive` as application data; it has already had
114//! any doubled IACs unescaped.
115//! - Negotiate only options you actually support (via [`compatibility`]).
116//! - Prefer working with `&[u8]`/`Bytes` payloads; avoid `String` unless you
117//! know the server’s encoding.
118//!
119//! Common recipes
120//! - NAWS (window size) send
121//! ```rust
122//! # use bytes::{BufMut, BytesMut};
123//! # use libmudtelnet_rs::{Parser, events::TelnetEvents};
124//! # use libmudtelnet_rs::telnet::op_option::NAWS;
125//! let mut parser = Parser::new();
126//! let mut payload = BytesMut::with_capacity(4);
127//! payload.put_u16(120); // width
128//! payload.put_u16(40); // height
129//! if let Some(TelnetEvents::DataSend(buf)) = parser.subnegotiation(NAWS, payload.freeze()) {
130//! /* write buf to socket */
131//! }
132//! ```
133//! - TTYPE (terminal type) identify
134//! ```rust
135//! # use libmudtelnet_rs::{Parser, events::TelnetEvents};
136//! # use libmudtelnet_rs::telnet::{op_option::TTYPE, op_command::{IAC, IS}};
137//! let mut parser = Parser::new();
138//! // IAC SB TTYPE IS "xterm-256color" IAC SE
139//! let mut body = Vec::new();
140//! body.extend([IAC, IS]);
141//! body.extend(b"xterm-256color");
142//! if let Some(TelnetEvents::DataSend(buf)) = parser.subnegotiation(TTYPE, body) {
143//! /* write buf to socket */
144//! }
145//! ```
146//! - GMCP send (JSON text)
147//! ```rust
148//! # use libmudtelnet_rs::{Parser, events::TelnetEvents};
149//! # use libmudtelnet_rs::telnet::op_option::GMCP;
150//! let mut parser = Parser::new();
151//! let json = r#"{\"Core.Supports.Add\":[\"Room 1\"]}"#;
152//! if let Some(TelnetEvents::DataSend(buf)) = parser.subnegotiation_text(GMCP, json) {
153//! /* write buf to socket */
154//! }
155//! ```
156//! - Escape/unescape IAC when handling raw buffers
157//! ```rust
158//! # use libmudtelnet_rs::Parser;
159//! # use libmudtelnet_rs::telnet::op_command::IAC;
160//! let escaped = Parser::escape_iac(vec![IAC, 1, 2]);
161//! let roundtrip = Parser::unescape_iac(escaped);
162//! assert_eq!(&roundtrip[..], [IAC, 1, 2]);
163//! ```
164//!
165//! Feature flags
166//! - `std` (default): Enables standard library usage. Disable for `no_std`.
167//! - `arbitrary`: Implements `arbitrary::Arbitrary` for fuzzing/dev.
168//!
169//! FAQ
170//! - “Why does `send_text` return an event?” To unify I/O: everything that must
171//! go to the socket is surfaced as `TelnetEvents::DataSend(Bytes)`.
172//! - “Do I need to escape IAC myself?” No when using `send_text` and
173//! `subnegotiation[_text]`. Yes only if you craft raw buffers yourself.
174//! - “Where is TCP handled?” Out of scope; this crate is protocol parsing only.
175
176#[cfg(not(feature = "std"))]
177extern crate alloc;
178#[cfg(feature = "std")]
179extern crate std as alloc;
180
181use alloc::{format, vec, vec::Vec};
182
183use bytes::{BufMut, Bytes, BytesMut};
184
185pub use bytes;
186pub mod compatibility;
187pub mod events;
188pub mod telnet;
189
190use compatibility::{CompatibilityEntry, CompatibilityTable};
191use events::{TelnetEvents, TelnetIAC, TelnetNegotiation, TelnetSubnegotiation};
192use telnet::op_command::{DO, DONT, EOR, GA, IAC, NOP, SB, SE, WILL, WONT};
193
194enum EventType {
195 None(Bytes),
196 Iac(Bytes),
197 SubNegotiation(Bytes, Option<Bytes>),
198 Neg(Bytes),
199}
200
201#[deprecated(
202 since = "0.2.1",
203 note = "Use `Bytes::copy_from_slice` directly instead."
204)]
205#[macro_export]
206/// Macro for calling `Bytes::copy_from_slice()`
207macro_rules! vbytes {
208 ($slice:expr) => {
209 Bytes::copy_from_slice($slice)
210 };
211}
212
213/// Stateful, event‑driven Telnet parser.
214///
215/// Feed incoming bytes with [`Parser::receive`] and iterate the returned
216/// [`events::TelnetEvents`] to handle application data, negotiations, and
217/// subnegotiations. The parser minimizes copies by slicing into an internal
218/// buffer and returning `bytes::Bytes` views where possible.
219pub struct Parser {
220 pub options: CompatibilityTable,
221 buffer: BytesMut,
222}
223
224impl Default for Parser {
225 fn default() -> Self {
226 Parser::with_capacity(128)
227 }
228}
229
230impl Parser {
231 /// Create a default, empty Parser with an internal buffer capacity of 128 bytes.
232 #[must_use]
233 pub fn new() -> Self {
234 Self::default()
235 }
236
237 /// Create an empty parser, setting the initial internal buffer capcity.
238 #[must_use]
239 pub fn with_capacity(size: usize) -> Self {
240 Self::with_support_and_capacity(size, CompatibilityTable::default())
241 }
242
243 /// Create a parser, directly supplying a `CompatibilityTable`.
244 ///
245 /// Uses the default initial buffer capacity of 128 bytes.
246 #[must_use]
247 pub fn with_support(table: CompatibilityTable) -> Self {
248 Self::with_support_and_capacity(128, table)
249 }
250
251 /// Create an parser, setting the initial internal buffer capacity and directly supplying a `CompatibilityTable`.
252 // TODO(@cpu): 'table' should be first arg to match name.
253 #[must_use]
254 pub fn with_support_and_capacity(size: usize, table: CompatibilityTable) -> Self {
255 Self {
256 options: table,
257 buffer: BytesMut::with_capacity(size),
258 }
259 }
260
261 /// Receive bytes into the internal buffer.
262 ///
263 /// # Arguments
264 ///
265 /// * `data` - The bytes to be received. This should be sourced from the remote side of a connection.
266 ///
267 /// # Returns
268 ///
269 /// `Vec<TelnetEvents>` - Any events parsed from the internal buffer with the new bytes.
270 ///
271 pub fn receive(&mut self, data: &[u8]) -> Vec<TelnetEvents> {
272 self.buffer.put(data);
273 self.process()
274 }
275
276 /// Get whether the remote end supports and is using linemode.
277 #[must_use]
278 pub fn linemode_enabled(&self) -> bool {
279 matches!(
280 self.options.get_option(telnet::op_option::LINEMODE),
281 CompatibilityEntry {
282 remote: true,
283 remote_state: true,
284 ..
285 }
286 )
287 }
288
289 /// Escape IAC bytes in data that is to be transmitted and treated as a non-IAC sequence.
290 ///
291 /// # Example
292 /// `[255, 1, 6, 2]` -> `[255, 255, 1, 6, 2]`
293 ///
294 /// ```
295 /// use libmudtelnet_rs::Parser;
296 /// use libmudtelnet_rs::telnet::op_command::IAC;
297 /// let out = Parser::escape_iac(vec![IAC, 1, 6, 2]);
298 /// assert_eq!(&out[..], [IAC, IAC, 1, 6, 2]);
299 /// ```
300 pub fn escape_iac<T>(data: T) -> Bytes
301 where
302 Bytes: From<T>,
303 {
304 let data = Bytes::from(data);
305 // Fast path: no IAC bytes to escape
306 if !data.contains(&IAC) {
307 return data;
308 }
309
310 // Reserve exact capacity needed
311 #[allow(clippy::naive_bytecount)]
312 let iac_count = data.iter().filter(|&&b| b == IAC).count();
313 let mut res = BytesMut::with_capacity(data.len() + iac_count);
314
315 for &byte in &data {
316 res.put_u8(byte);
317 if byte == IAC {
318 res.put_u8(IAC);
319 }
320 }
321 res.freeze()
322 }
323
324 /// Reverse escaped IAC bytes for non-IAC sequences and data.
325 ///
326 /// # Example
327 /// `[255, 255, 1, 6, 2]` -> `[255, 1, 6, 2]`
328 ///
329 /// ```
330 /// use libmudtelnet_rs::Parser;
331 /// use libmudtelnet_rs::telnet::op_command::IAC;
332 /// let out = Parser::unescape_iac(vec![IAC, IAC, 1, 6, 2]);
333 /// assert_eq!(&out[..], [IAC, 1, 6, 2]);
334 /// ```
335 pub fn unescape_iac<T>(data: T) -> Bytes
336 where
337 Bytes: From<T>,
338 {
339 #[derive(Debug, Clone, Copy)]
340 enum States {
341 Normal,
342 Iac,
343 }
344
345 let data = Bytes::from(data);
346 // Fast path: no IAC-IAC sequences to unescape
347 if !data.windows(2).any(|w| w == [IAC, IAC]) {
348 return data;
349 }
350
351 let mut res = BytesMut::with_capacity(data.len());
352
353 let mut state = States::Normal;
354 let mut out_val;
355 for val in data {
356 (state, out_val) = match (state, val) {
357 (States::Normal, IAC) => (States::Iac, Some(val)),
358 (States::Iac, IAC) => (States::Normal, None),
359 (States::Normal | States::Iac, _) => (States::Normal, Some(val)),
360 };
361 if let Some(val) = out_val {
362 res.put_u8(val);
363 }
364 }
365
366 res.freeze()
367 }
368
369 /// Negotiate an option.
370 ///
371 /// # Arguments
372 ///
373 /// `command` - A `u8` representing the telnet command code to be negotiated with. Example: WILL (251), WONT (252), DO (253), DONT (254)
374 ///
375 /// `option` - A `u8` representing the telnet option code that is being negotiated.
376 ///
377 /// # Returns
378 ///
379 /// `TelnetEvents::DataSend` - A `DataSend` event to be processed.
380 ///
381 /// # Usage
382 ///
383 /// This and other methods meant for sending data to the remote end will generate a `TelnetEvents::Send(DataEvent)` event.
384 ///
385 /// These Send events contain a buffer that should be sent directly to the remote end, as it will have already been encoded properly.
386 pub fn negotiate(&mut self, command: u8, option: u8) -> TelnetEvents {
387 TelnetEvents::DataSend(TelnetNegotiation::new(command, option).to_bytes())
388 }
389
390 /// Indicate to the other side that you are able and wanting to utilize an option.
391 ///
392 /// # Arguments
393 ///
394 /// `option` - A `u8` representing the telnet option code that you want to enable locally.
395 ///
396 /// # Returns
397 ///
398 /// `Option<TelnetEvents::DataSend>` - The `DataSend` event to be processed, or None if not supported.
399 ///
400 /// # Notes
401 ///
402 /// This method will do nothing if the option is not "supported" locally via the `CompatibilityTable`.
403 pub fn _will(&mut self, option: u8) -> Option<TelnetEvents> {
404 match self.options.get_option(option) {
405 mut opt @ CompatibilityEntry {
406 local: true,
407 local_state: false,
408 ..
409 } => {
410 opt.local_state = true;
411 self.options.set_option(option, opt);
412 Some(self.negotiate(WILL, option))
413 }
414 _ => None,
415 }
416 }
417
418 /// Indicate to the other side that you are not wanting to utilize an option.
419 ///
420 /// # Arguments
421 ///
422 /// `option` - A `u8` representing the telnet option code that you want to disable locally.
423 ///
424 /// # Returns
425 ///
426 /// `Option<TelnetEvents::DataSend>` - A `DataSend` event to be processed, or None if the option is already disabled.
427 ///
428 pub fn _wont(&mut self, option: u8) -> Option<TelnetEvents> {
429 match self.options.get_option(option) {
430 mut opt @ CompatibilityEntry {
431 local_state: true, ..
432 } => {
433 opt.local_state = false;
434 self.options.set_option(option, opt);
435 Some(self.negotiate(WONT, option))
436 }
437 _ => None,
438 }
439 }
440
441 /// Indicate to the other side that you would like them to utilize an option.
442 ///
443 /// # Arguments
444 ///
445 /// `option` - A `u8` representing the telnet option code that you want to enable remotely.
446 ///
447 /// # Returns
448 ///
449 /// `Option<TelnetEvents::DataSend>` - A `DataSend` event to be processed, or None if the option is not supported or already enabled.
450 ///
451 /// # Notes
452 ///
453 /// This method will do nothing if the option is not "supported" remotely via the `CompatibilityTable`.
454 pub fn _do(&mut self, option: u8) -> Option<TelnetEvents> {
455 match self.options.get_option(option) {
456 CompatibilityEntry {
457 remote: true,
458 remote_state: false,
459 ..
460 } => Some(self.negotiate(DO, option)),
461 _ => None,
462 }
463 }
464
465 /// Indicate to the other side that you would like them to stop utilizing an option.
466 ///
467 /// # Arguments
468 ///
469 /// `option` - A `u8` representing the telnet option code that you want to disable remotely.
470 ///
471 /// # Returns
472 ///
473 /// `Option<TelnetEvents::DataSend>` - A `DataSend` event to be processed, or None if the option is already disabled.
474 ///
475 pub fn _dont(&mut self, option: u8) -> Option<TelnetEvents> {
476 match self.options.get_option(option) {
477 CompatibilityEntry {
478 remote_state: true, ..
479 } => Some(self.negotiate(DONT, option)),
480 _ => None,
481 }
482 }
483
484 /// Send a subnegotiation for a locally supported option.
485 ///
486 /// # Arguments
487 ///
488 /// `option` - A `u8` representing the telnet option code for the negotiation.
489 ///
490 /// `data` - A `Bytes` containing the data to be sent in the subnegotiation. This data will have all IAC (255) byte values escaped.
491 ///
492 /// # Returns
493 ///
494 /// `Option<TelnetEvents::DataSend>` - A `DataSend` event to be processed, or None if the option is not supported or is currently disabled.
495 ///
496 /// # Notes
497 ///
498 /// This method will do nothing if the option is not supported/enabled. For
499 /// most options, the client must be the performer (local + `local_state`). For
500 /// GMCP/MSDP specifically, the parser also allows sending when the remote
501 /// side has enabled the option (remote + `remote_state`), reflecting the
502 /// de‑facto bidirectional semantics after a `WILL/DO` handshake.
503 pub fn subnegotiation<T>(&mut self, option: u8, data: T) -> Option<TelnetEvents>
504 where
505 Bytes: From<T>,
506 {
507 let entry = self.options.get_option(option);
508 // Allow sends when we are the performer (local+state). For GMCP/MSDP, treat
509 // the capability as bidirectional after WILL/DO: allow send when the remote
510 // side has enabled it too (remote+state).
511 let may_send = match option {
512 telnet::op_option::GMCP | telnet::op_option::MSDP => {
513 (entry.local && entry.local_state) || (entry.remote && entry.remote_state)
514 }
515 _ => entry.local && entry.local_state,
516 };
517
518 if may_send {
519 Some(TelnetEvents::DataSend(
520 TelnetSubnegotiation::new(option, Bytes::from(data)).to_bytes(),
521 ))
522 } else {
523 None
524 }
525 }
526
527 /// Send a subnegotiation for a locally supported option, using a string instead of raw byte values.
528 ///
529 /// # Arguments
530 ///
531 /// `option` - A `u8` representing the telnet option code for the negotiation.
532 ///
533 /// `text` - A `&str` representing the text to be sent in the subnegotation. This data will have all IAC (255) byte values escaped.
534 ///
535 /// # Returns
536 ///
537 /// `Option<TelnetEvents::DataSend>` - A `DataSend` event to be processed, or None if the option is not supported or is currently disabled.
538 ///
539 /// # Notes
540 ///
541 /// This method will do nothing if the option is not "supported" locally via the `CompatibilityTable`.
542 pub fn subnegotiation_text(&mut self, option: u8, text: &str) -> Option<TelnetEvents> {
543 self.subnegotiation(option, Bytes::copy_from_slice(text.as_bytes()))
544 }
545
546 /// Directly send a string, with appended `\r\n`, to the remote end, along with an `IAC (255) GOAHEAD (249)` sequence.
547 ///
548 /// # Returns
549 ///
550 /// `TelnetEvents::DataSend` - A `DataSend` event to be processed.
551 ///
552 /// # Notes
553 ///
554 /// The string will have IAC (255) bytes escaped before being sent.
555 pub fn send_text(&mut self, text: &str) -> TelnetEvents {
556 TelnetEvents::DataSend(Parser::escape_iac(format!("{text}\r\n")))
557 }
558
559 /// Extract sub-buffers from the current buffer
560 fn extract_event_data(&mut self) -> Vec<EventType> {
561 #[derive(Copy, Clone)]
562 enum State {
563 Normal,
564 Iac,
565 Neg,
566 Sub,
567 SubOpt { opt: u8 },
568 SubIac { opt: u8 },
569 }
570
571 let mut events = Vec::with_capacity(4);
572 let mut iter_state = State::Normal;
573 let mut cmd_begin = 0;
574
575 // Empty self.buffer into an immutable Bytes we can process.
576 // We'll create views of this buffer to pass to the events using 'buf.slice'.
577 // Splitting is O(1) and doesn't copy the data. Freezing is zero-cost. Taking a slice is O(1).
578 let buf = self.buffer.split().freeze();
579 for (index, &val) in buf.iter().enumerate() {
580 (iter_state, cmd_begin) = match (&iter_state, val) {
581 (State::Normal, IAC) => {
582 if cmd_begin != index {
583 events.push(EventType::None(buf.slice(cmd_begin..index)));
584 }
585 (State::Iac, index)
586 }
587 (State::Iac, IAC) => (State::Normal, cmd_begin), // Double IAC, ignore,
588 (State::Iac, b) if b == GA || b == EOR || b == NOP => {
589 events.push(EventType::Iac(buf.slice(cmd_begin..=index)));
590 (State::Normal, index + 1)
591 }
592 (State::Iac, SB) => (State::Sub, cmd_begin),
593 (State::Iac, _) => (State::Neg, cmd_begin), // WILL | WONT | DO | DONT | IS | SEND
594 (State::Neg, _) => {
595 events.push(EventType::Neg(buf.slice(cmd_begin..=index)));
596 (State::Normal, index + 1)
597 }
598 // Edge case: truncated subnegotiation like `IAC SB IAC SE`.
599 // If the first byte after SB is IAC, treat it as the start of the
600 // terminating IAC SE sequence rather than as the option byte.
601 // This ensures we emit the completed (empty) subnegotiation and leave
602 // any following bytes (e.g., a trailing NUL) as normal data.
603 (State::Sub, IAC) => (State::SubIac { opt: 0 }, cmd_begin),
604 (State::Sub, opt) => (State::SubOpt { opt }, cmd_begin),
605 (State::SubOpt { opt } | State::SubIac { opt }, IAC) => {
606 (State::SubIac { opt: *opt }, cmd_begin)
607 }
608 (State::SubIac { opt }, SE)
609 if *opt == telnet::op_option::MCCP2 || *opt == telnet::op_option::MCCP3 =>
610 {
611 // MCCP2/MCCP3 MUST DECOMPRESS DATA AFTER THIS!
612 events.push(EventType::SubNegotiation(
613 buf.slice(cmd_begin..=index),
614 Some(buf.slice(index + 1..)),
615 ));
616 cmd_begin = buf.len();
617 break;
618 }
619 (State::SubIac { .. }, SE) => {
620 events.push(EventType::SubNegotiation(
621 buf.slice(cmd_begin..=index),
622 None,
623 ));
624 (State::Normal, index + 1)
625 }
626 (State::SubIac { opt }, _) => (State::SubOpt { opt: *opt }, cmd_begin),
627 (cur_state, _) => (*cur_state, cmd_begin),
628 };
629 }
630
631 if cmd_begin < buf.len() {
632 match iter_state {
633 State::Sub | State::SubOpt { .. } | State::SubIac { .. } => {
634 events.push(EventType::SubNegotiation(buf.slice(cmd_begin..), None));
635 }
636 _ => events.push(EventType::None(buf.slice(cmd_begin..))),
637 }
638 }
639
640 events
641 }
642
643 /// The internal parser method that takes the current buffer and generates the corresponding events.
644 fn process(&mut self) -> Vec<TelnetEvents> {
645 let mut event_list = Vec::with_capacity(2);
646 let events = self.extract_event_data();
647 for event in events {
648 match event {
649 EventType::None(buffer) | EventType::Iac(buffer) | EventType::Neg(buffer) => {
650 match (buffer.first(), buffer.get(1), buffer.get(2)) {
651 (Some(&IAC), Some(command), None) if *command != SE => {
652 // IAC command
653 event_list.push(TelnetEvents::IAC(TelnetIAC::new(*command)));
654 }
655 (Some(&IAC), Some(command), Some(opt)) => {
656 // Negotiation command
657 event_list.extend(self.process_negotiation(*command, *opt));
658 }
659 (Some(c), _, _) if *c != IAC => {
660 // Not an iac sequence, it's data!
661 event_list.push(TelnetEvents::DataReceive(buffer));
662 }
663 _ => {}
664 }
665 }
666 EventType::SubNegotiation(buffer, remaining) => {
667 let len = buffer.len();
668 if buffer[len - 2] == IAC && buffer[len - 1] == SE {
669 // Valid ending
670 let opt_entry = self.options.get_option(buffer[2]);
671 // Check appropriate direction based on option type
672 // - MCCP2: strictly server->client (remote)
673 // - GMCP/MSDP: bidirectional once negotiated (WILL/DO), accept if either
674 // side is active. This matches common MUD behavior and avoids requiring
675 // the client to also send WILL to be able to process server subnegs.
676 // - Others: default to local performer only.
677 let should_process = match buffer[2] {
678 telnet::op_option::MCCP2 => opt_entry.remote && opt_entry.remote_state,
679 telnet::op_option::GMCP | telnet::op_option::MSDP => {
680 (opt_entry.remote && opt_entry.remote_state)
681 || (opt_entry.local && opt_entry.local_state)
682 }
683 _ => opt_entry.local && opt_entry.local_state,
684 };
685 if should_process && len >= 4 {
686 let body = if len > 4 { &buffer[3..len - 2] } else { &[] };
687 event_list.push(TelnetEvents::Subnegotiation(TelnetSubnegotiation::new(
688 buffer[2],
689 Bytes::copy_from_slice(body),
690 )));
691 if let Some(rbuf) = remaining {
692 event_list.push(TelnetEvents::DecompressImmediate(rbuf));
693 }
694 }
695 } else {
696 // Missing the rest
697 self.buffer.put(&buffer[..]);
698 }
699 }
700 }
701 }
702 event_list
703 }
704
705 fn process_negotiation(&mut self, command: u8, opt: u8) -> Vec<TelnetEvents> {
706 let event = TelnetNegotiation::new(command, opt);
707 match (command, self.options.get_option(opt)) {
708 (
709 WILL,
710 mut entry @ CompatibilityEntry {
711 remote: true,
712 remote_state: false,
713 ..
714 },
715 ) => {
716 entry.remote_state = true;
717 self.options.set_option(opt, entry);
718 vec![
719 TelnetEvents::DataSend(Bytes::copy_from_slice(&[IAC, DO, opt])),
720 TelnetEvents::Negotiation(event),
721 ]
722 }
723 (WILL, CompatibilityEntry { remote: false, .. }) => {
724 vec![TelnetEvents::DataSend(Bytes::copy_from_slice(&[
725 IAC, DONT, opt,
726 ]))]
727 }
728 (
729 WONT,
730 mut entry @ CompatibilityEntry {
731 remote_state: true, ..
732 },
733 ) => {
734 entry.remote_state = false;
735 self.options.set_option(opt, entry);
736 vec![
737 TelnetEvents::DataSend(Bytes::copy_from_slice(&[IAC, DONT, opt])),
738 TelnetEvents::Negotiation(event),
739 ]
740 }
741 (
742 DO,
743 mut entry @ CompatibilityEntry {
744 local: true,
745 local_state: false,
746 ..
747 },
748 ) => {
749 entry.local_state = true;
750 entry.remote_state = true;
751 self.options.set_option(opt, entry);
752 vec![
753 TelnetEvents::DataSend(Bytes::copy_from_slice(&[IAC, WILL, opt])),
754 TelnetEvents::Negotiation(event),
755 ]
756 }
757 (
758 DO,
759 CompatibilityEntry {
760 local_state: false, ..
761 }
762 | CompatibilityEntry { local: false, .. },
763 ) => {
764 vec![TelnetEvents::DataSend(Bytes::copy_from_slice(&[
765 IAC, WONT, opt,
766 ]))]
767 }
768 (
769 DONT,
770 mut entry @ CompatibilityEntry {
771 local_state: true, ..
772 },
773 ) => {
774 entry.local_state = false;
775 self.options.set_option(opt, entry);
776 vec![
777 TelnetEvents::DataSend(Bytes::copy_from_slice(&[IAC, WONT, opt])),
778 TelnetEvents::Negotiation(event),
779 ]
780 }
781 (_, CompatibilityEntry { .. }) if command == DONT || command == WONT => {
782 vec![TelnetEvents::Negotiation(event)]
783 }
784 _ => Vec::default(),
785 }
786 }
787}