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}