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}