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