libmudtelnet_rs/
events.rs

1//! Event types emitted by the Telnet parser.
2//!
3//! The parser transforms incoming bytes into a small set of structured events
4//! that are easy to handle in your application loop:
5//!
6//! - [`TelnetEvents::DataReceive`]: Application data from the server. Any
7//!   doubled IAC bytes are already unescaped. The payload is a zero‑copy
8//!   `bytes::Bytes` slice referencing the parser’s buffer at parse time.
9//! - [`TelnetEvents::DataSend`]: Bytes your app must send to the socket as‑is.
10//!   These are already fully encoded (negotiation frames, escaped IAC, etc.).
11//! - [`TelnetEvents::Negotiation`]: A WILL/WONT/DO/DONT triplet.
12//! - [`TelnetEvents::Subnegotiation`]: `IAC SB <option> <data> IAC SE` payload.
13//!   Inside `<data>`, embedded IAC bytes have been escaped on send and are
14//!   presented as‑is on receive.
15//! - [`TelnetEvents::IAC`]: A two‑byte IAC command such as GA/EOR/NOP.
16//! - [`TelnetEvents::DecompressImmediate`]: MCCP2/3 boundary signal. Decompress
17//!   this payload immediately and feed the result back into `Parser::receive`.
18//!
19//! Converting back to bytes
20//! ```
21//! use libmudtelnet_rs::events::{TelnetEvents, TelnetIAC};
22//! use libmudtelnet_rs::telnet::op_command::{IAC, NOP};
23//! let ev = TelnetEvents::IAC(TelnetIAC::new(NOP));
24//! let bytes = ev.to_bytes();
25//! assert_eq!(&bytes[..], [IAC, NOP]);
26//! ```
27
28use alloc::vec::Vec;
29
30use bytes::{BufMut, Bytes, BytesMut};
31
32use crate::telnet::op_command::{IAC, SB, SE};
33use crate::Parser;
34
35/// A struct representing a 2‑byte `IAC <command>` sequence.
36#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
37#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
38pub struct TelnetIAC {
39  pub command: u8,
40}
41
42impl TelnetIAC {
43  #[must_use]
44  pub fn new(command: u8) -> Self {
45    Self { command }
46  }
47
48  /// Consume the sequence struct and return the encoded bytes.
49  #[must_use]
50  pub fn to_bytes(self) -> Bytes {
51    Bytes::copy_from_slice(&[IAC, self.command])
52  }
53
54  #[must_use]
55  #[deprecated(since = "0.2.1", note = "Use `to_bytes` instead.")]
56  pub fn into_bytes(self) -> Bytes {
57    self.to_bytes()
58  }
59}
60
61/// A struct representing a 3‑byte `IAC <command> <option>` negotiation.
62#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
63#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
64pub struct TelnetNegotiation {
65  pub command: u8,
66  pub option: u8,
67}
68
69impl TelnetNegotiation {
70  #[must_use]
71  pub fn new(command: u8, option: u8) -> Self {
72    Self { command, option }
73  }
74
75  /// Consume the sequence struct and return the encoded bytes.
76  #[must_use]
77  pub fn to_bytes(self) -> Bytes {
78    Bytes::copy_from_slice(&[IAC, self.command, self.option])
79  }
80
81  #[must_use]
82  #[deprecated(since = "0.2.1", note = "Use `to_bytes` instead.")]
83  pub fn into_bytes(self) -> Bytes {
84    self.to_bytes()
85  }
86}
87
88/// A struct representing `IAC SB <option> <data> IAC SE`.
89#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
90pub struct TelnetSubnegotiation {
91  pub option: u8,
92  pub buffer: Bytes,
93}
94
95#[cfg(feature = "arbitrary")]
96impl<'a> arbitrary::Arbitrary<'a> for TelnetSubnegotiation {
97  fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
98    let option = u.arbitrary()?;
99    let buffer: Vec<u8> = u.arbitrary()?;
100    Ok(Self {
101      option,
102      buffer: Bytes::from(buffer),
103    })
104  }
105}
106
107impl TelnetSubnegotiation {
108  pub fn new(option: u8, buffer: Bytes) -> Self {
109    Self { option, buffer }
110  }
111
112  #[must_use]
113  pub fn to_bytes(self) -> Bytes {
114    let head = [IAC, SB, self.option];
115    let parsed = &Parser::escape_iac(self.buffer)[..];
116    let tail = [IAC, SE];
117    let mut buf = BytesMut::with_capacity(head.len() + parsed.len() + tail.len());
118    buf.put(&head[..]);
119    buf.put(parsed);
120    buf.put(&tail[..]);
121    buf.freeze()
122  }
123
124  #[must_use]
125  #[deprecated(since = "0.2.1", note = "Use `to_bytes` instead.")]
126  pub fn into_bytes(self) -> Bytes {
127    self.to_bytes()
128  }
129}
130
131/// The set of events produced by the parser.
132#[derive(Clone, Debug, Eq, PartialEq)]
133#[non_exhaustive]
134pub enum TelnetEvents {
135  /// A 2‑byte IAC command sequence (e.g., GA/EOR/NOP).
136  IAC(TelnetIAC),
137  /// A WILL/WONT/DO/DONT negotiation (`IAC <cmd> <opt>`).
138  Negotiation(TelnetNegotiation),
139  /// A subnegotiation payload (`IAC SB <opt> <data> IAC SE`).
140  Subnegotiation(TelnetSubnegotiation),
141  /// Application data received from the remote end.
142  DataReceive(Bytes),
143  /// Fully encoded data to send to the remote end.
144  DataSend(Bytes),
145  /// MCCP2/3 boundary. Decompress this data before feeding back into the parser.
146  DecompressImmediate(Bytes),
147}
148
149impl From<TelnetIAC> for TelnetEvents {
150  fn from(iac: TelnetIAC) -> Self {
151    TelnetEvents::IAC(iac)
152  }
153}
154
155impl From<TelnetNegotiation> for TelnetEvents {
156  fn from(neg: TelnetNegotiation) -> Self {
157    TelnetEvents::Negotiation(neg)
158  }
159}
160
161impl From<TelnetSubnegotiation> for TelnetEvents {
162  fn from(sub: TelnetSubnegotiation) -> Self {
163    TelnetEvents::Subnegotiation(sub)
164  }
165}
166
167impl TelnetEvents {
168  /// Helper method to generate a `TelnetEvents::DataSend`.
169  #[deprecated(since = "0.2.1", note = "Construct enum variant directly or use into.")]
170  pub fn build_send(buffer: Bytes) -> Self {
171    TelnetEvents::DataSend(buffer)
172  }
173
174  /// Helper method to generate a `TelnetEvents::DataReceive`.
175  #[deprecated(since = "0.2.1", note = "Construct enum variant directly or use into.")]
176  pub fn build_receive(buffer: Bytes) -> Self {
177    TelnetEvents::DataReceive(buffer)
178  }
179
180  /// Helper method to generate a `TelnetEvents::IAC`.
181  #[must_use]
182  #[deprecated(since = "0.2.1", note = "Construct enum variant directly or use into.")]
183  pub fn build_iac(command: u8) -> TelnetEvents {
184    TelnetEvents::IAC(TelnetIAC::new(command))
185  }
186
187  /// Helper method to generate a `TelnetEvents::Negotiation`.
188  #[must_use]
189  #[deprecated(since = "0.2.1", note = "Construct enum variant directly or use into.")]
190  pub fn build_negotiation(command: u8, option: u8) -> Self {
191    TelnetEvents::Negotiation(TelnetNegotiation::new(command, option))
192  }
193
194  /// Helper method to generate a `TelnetEvents::Subnegotiation`.
195  #[deprecated(since = "0.2.1", note = "Construct enum variant directly or use into.")]
196  pub fn build_subnegotiation(option: u8, buffer: Bytes) -> Self {
197    TelnetEvents::Subnegotiation(TelnetSubnegotiation::new(option, buffer))
198  }
199
200  #[must_use]
201  pub fn to_bytes(self) -> Bytes {
202    match self {
203      TelnetEvents::IAC(iac) => iac.to_bytes(),
204      TelnetEvents::Negotiation(neg) => neg.to_bytes(),
205      TelnetEvents::Subnegotiation(sub) => sub.to_bytes(),
206      TelnetEvents::DataReceive(data)
207      | TelnetEvents::DataSend(data)
208      | TelnetEvents::DecompressImmediate(data) => data,
209    }
210  }
211}
212
213/*
214TODO(@cpu): remove/retool this stuff in breaking release.
215*/
216#[allow(clippy::from_over_into)]
217impl Into<Bytes> for TelnetIAC {
218  fn into(self) -> Bytes {
219    self.to_bytes()
220  }
221}
222
223#[allow(clippy::from_over_into)]
224impl Into<Vec<u8>> for TelnetIAC {
225  fn into(self) -> Vec<u8> {
226    self.to_bytes().into()
227  }
228}
229
230#[allow(clippy::from_over_into)]
231impl Into<Bytes> for TelnetNegotiation {
232  fn into(self) -> Bytes {
233    self.to_bytes()
234  }
235}
236
237#[allow(clippy::from_over_into)]
238impl Into<Vec<u8>> for TelnetNegotiation {
239  fn into(self) -> Vec<u8> {
240    self.to_bytes().into()
241  }
242}
243
244#[allow(clippy::from_over_into)]
245impl Into<Bytes> for TelnetSubnegotiation {
246  fn into(self) -> Bytes {
247    self.to_bytes()
248  }
249}
250
251#[allow(clippy::from_over_into)]
252impl Into<Vec<u8>> for TelnetSubnegotiation {
253  fn into(self) -> Vec<u8> {
254    self.to_bytes().into()
255  }
256}