#![cfg_attr(not(feature = "std"), no_std)]
#![warn(clippy::pedantic)]
#![allow(
clippy::module_name_repetitions,
clippy::fn_params_excessive_bools,
clippy::struct_excessive_bools
)]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
use alloc::{format, vec, vec::Vec};
use bytes::{BufMut, Bytes, BytesMut};
pub use bytes;
pub mod compatibility;
pub mod events;
pub mod telnet;
use compatibility::{CompatibilityEntry, CompatibilityTable};
use events::{TelnetEvents, TelnetIAC, TelnetNegotiation, TelnetSubnegotiation};
use telnet::op_command::{DO, DONT, EOR, GA, IAC, NOP, SB, SE, WILL, WONT};
enum EventType {
None(Bytes),
Iac(Bytes),
SubNegotiation(Bytes, Option<Bytes>),
Neg(Bytes),
}
#[deprecated(
since = "0.2.1",
note = "Use `Bytes::copy_from_slice` directly instead."
)]
#[macro_export]
macro_rules! vbytes {
($slice:expr) => {
Bytes::copy_from_slice($slice)
};
}
pub struct Parser {
pub options: CompatibilityTable,
buffer: BytesMut,
}
impl Default for Parser {
fn default() -> Self {
Parser::with_capacity(128)
}
}
impl Parser {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_capacity(size: usize) -> Self {
Self::with_support_and_capacity(size, CompatibilityTable::default())
}
#[must_use]
pub fn with_support(table: CompatibilityTable) -> Self {
Self::with_support_and_capacity(128, table)
}
#[must_use]
pub fn with_support_and_capacity(size: usize, table: CompatibilityTable) -> Self {
Self {
options: table,
buffer: BytesMut::with_capacity(size),
}
}
pub fn receive(&mut self, data: &[u8]) -> Vec<TelnetEvents> {
self.buffer.put(data);
self.process()
}
#[must_use]
pub fn linemode_enabled(&self) -> bool {
matches!(
self.options.get_option(telnet::op_option::LINEMODE),
CompatibilityEntry {
remote: true,
remote_state: true,
..
}
)
}
pub fn escape_iac<T>(data: T) -> Bytes
where
Bytes: From<T>,
{
let data = Bytes::from(data);
if !data.contains(&IAC) {
return data;
}
#[allow(clippy::naive_bytecount)]
let iac_count = data.iter().filter(|&&b| b == IAC).count();
let mut res = BytesMut::with_capacity(data.len() + iac_count);
for &byte in &data {
res.put_u8(byte);
if byte == IAC {
res.put_u8(IAC);
}
}
res.freeze()
}
pub fn unescape_iac<T>(data: T) -> Bytes
where
Bytes: From<T>,
{
#[derive(Debug, Clone, Copy)]
enum States {
Normal,
Iac,
}
let data = Bytes::from(data);
if !data.windows(2).any(|w| w == [IAC, IAC]) {
return data;
}
let mut res = BytesMut::with_capacity(data.len());
let mut state = States::Normal;
let mut out_val;
for val in data {
(state, out_val) = match (state, val) {
(States::Normal, IAC) => (States::Iac, Some(val)),
(States::Iac, IAC) => (States::Normal, None),
(States::Normal | States::Iac, _) => (States::Normal, Some(val)),
};
if let Some(val) = out_val {
res.put_u8(val);
}
}
res.freeze()
}
pub fn negotiate(&mut self, command: u8, option: u8) -> TelnetEvents {
TelnetEvents::DataSend(TelnetNegotiation::new(command, option).to_bytes())
}
pub fn _will(&mut self, option: u8) -> Option<TelnetEvents> {
match self.options.get_option(option) {
mut opt @ CompatibilityEntry {
local: true,
local_state: false,
..
} => {
opt.local_state = true;
self.options.set_option(option, opt);
Some(self.negotiate(WILL, option))
}
_ => None,
}
}
pub fn _wont(&mut self, option: u8) -> Option<TelnetEvents> {
match self.options.get_option(option) {
mut opt @ CompatibilityEntry {
local_state: true, ..
} => {
opt.local_state = false;
self.options.set_option(option, opt);
Some(self.negotiate(WONT, option))
}
_ => None,
}
}
pub fn _do(&mut self, option: u8) -> Option<TelnetEvents> {
match self.options.get_option(option) {
CompatibilityEntry {
remote: true,
remote_state: false,
..
} => Some(self.negotiate(DO, option)),
_ => None,
}
}
pub fn _dont(&mut self, option: u8) -> Option<TelnetEvents> {
match self.options.get_option(option) {
CompatibilityEntry {
remote_state: true, ..
} => Some(self.negotiate(DONT, option)),
_ => None,
}
}
pub fn subnegotiation<T>(&mut self, option: u8, data: T) -> Option<TelnetEvents>
where
Bytes: From<T>,
{
let entry = self.options.get_option(option);
let may_send = match option {
telnet::op_option::GMCP | telnet::op_option::MSDP => {
(entry.local && entry.local_state) || (entry.remote && entry.remote_state)
}
_ => entry.local && entry.local_state,
};
if may_send {
Some(TelnetEvents::DataSend(
TelnetSubnegotiation::new(option, Bytes::from(data)).to_bytes(),
))
} else {
None
}
}
pub fn subnegotiation_text(&mut self, option: u8, text: &str) -> Option<TelnetEvents> {
self.subnegotiation(option, Bytes::copy_from_slice(text.as_bytes()))
}
pub fn send_text(&mut self, text: &str) -> TelnetEvents {
TelnetEvents::DataSend(Parser::escape_iac(format!("{text}\r\n")))
}
fn extract_event_data(&mut self) -> Vec<EventType> {
#[derive(Copy, Clone)]
enum State {
Normal,
Iac,
Neg,
Sub,
SubOpt { opt: u8 },
SubIac { opt: u8 },
}
let mut events = Vec::with_capacity(4);
let mut iter_state = State::Normal;
let mut cmd_begin = 0;
let buf = self.buffer.split().freeze();
for (index, &val) in buf.iter().enumerate() {
(iter_state, cmd_begin) = match (&iter_state, val) {
(State::Normal, IAC) => {
if cmd_begin != index {
events.push(EventType::None(buf.slice(cmd_begin..index)));
}
(State::Iac, index)
}
(State::Iac, IAC) => (State::Normal, cmd_begin), (State::Iac, b) if b == GA || b == EOR || b == NOP => {
events.push(EventType::Iac(buf.slice(cmd_begin..=index)));
(State::Normal, index + 1)
}
(State::Iac, SB) => (State::Sub, cmd_begin),
(State::Iac, _) => (State::Neg, cmd_begin), (State::Neg, _) => {
events.push(EventType::Neg(buf.slice(cmd_begin..=index)));
(State::Normal, index + 1)
}
(State::Sub, IAC) => (State::SubIac { opt: 0 }, cmd_begin),
(State::Sub, opt) => (State::SubOpt { opt }, cmd_begin),
(State::SubOpt { opt } | State::SubIac { opt }, IAC) => {
(State::SubIac { opt: *opt }, cmd_begin)
}
(State::SubIac { opt }, SE)
if *opt == telnet::op_option::MCCP2 || *opt == telnet::op_option::MCCP3 =>
{
events.push(EventType::SubNegotiation(
buf.slice(cmd_begin..=index),
Some(buf.slice(index + 1..)),
));
cmd_begin = buf.len();
break;
}
(State::SubIac { .. }, SE) => {
events.push(EventType::SubNegotiation(
buf.slice(cmd_begin..=index),
None,
));
(State::Normal, index + 1)
}
(State::SubIac { opt }, _) => (State::SubOpt { opt: *opt }, cmd_begin),
(cur_state, _) => (*cur_state, cmd_begin),
};
}
if cmd_begin < buf.len() {
match iter_state {
State::Sub | State::SubOpt { .. } | State::SubIac { .. } => {
events.push(EventType::SubNegotiation(buf.slice(cmd_begin..), None));
}
_ => events.push(EventType::None(buf.slice(cmd_begin..))),
}
}
events
}
fn process(&mut self) -> Vec<TelnetEvents> {
let mut event_list = Vec::with_capacity(2);
let events = self.extract_event_data();
for event in events {
match event {
EventType::None(buffer) | EventType::Iac(buffer) | EventType::Neg(buffer) => {
match (buffer.first(), buffer.get(1), buffer.get(2)) {
(Some(&IAC), Some(command), None) if *command != SE => {
event_list.push(TelnetEvents::IAC(TelnetIAC::new(*command)));
}
(Some(&IAC), Some(command), Some(opt)) => {
event_list.extend(self.process_negotiation(*command, *opt));
}
(Some(c), _, _) if *c != IAC => {
event_list.push(TelnetEvents::DataReceive(buffer));
}
_ => {}
}
}
EventType::SubNegotiation(buffer, remaining) => {
let len = buffer.len();
if buffer[len - 2] == IAC && buffer[len - 1] == SE {
let opt_entry = self.options.get_option(buffer[2]);
let should_process = match buffer[2] {
telnet::op_option::MCCP2 => opt_entry.remote && opt_entry.remote_state,
telnet::op_option::GMCP | telnet::op_option::MSDP => {
(opt_entry.remote && opt_entry.remote_state)
|| (opt_entry.local && opt_entry.local_state)
}
_ => opt_entry.local && opt_entry.local_state,
};
if should_process && len >= 4 {
let body = if len > 4 { &buffer[3..len - 2] } else { &[] };
event_list.push(TelnetEvents::Subnegotiation(TelnetSubnegotiation::new(
buffer[2],
Bytes::copy_from_slice(body),
)));
if let Some(rbuf) = remaining {
event_list.push(TelnetEvents::DecompressImmediate(rbuf));
}
}
} else {
self.buffer.put(&buffer[..]);
}
}
}
}
event_list
}
fn process_negotiation(&mut self, command: u8, opt: u8) -> Vec<TelnetEvents> {
let event = TelnetNegotiation::new(command, opt);
match (command, self.options.get_option(opt)) {
(
WILL,
mut entry @ CompatibilityEntry {
remote: true,
remote_state: false,
..
},
) => {
entry.remote_state = true;
self.options.set_option(opt, entry);
vec![
TelnetEvents::DataSend(Bytes::copy_from_slice(&[IAC, DO, opt])),
TelnetEvents::Negotiation(event),
]
}
(WILL, CompatibilityEntry { remote: false, .. }) => {
vec![TelnetEvents::DataSend(Bytes::copy_from_slice(&[
IAC, DONT, opt,
]))]
}
(
WONT,
mut entry @ CompatibilityEntry {
remote_state: true, ..
},
) => {
entry.remote_state = false;
self.options.set_option(opt, entry);
vec![
TelnetEvents::DataSend(Bytes::copy_from_slice(&[IAC, DONT, opt])),
TelnetEvents::Negotiation(event),
]
}
(
DO,
mut entry @ CompatibilityEntry {
local: true,
local_state: false,
..
},
) => {
entry.local_state = true;
entry.remote_state = true;
self.options.set_option(opt, entry);
vec![
TelnetEvents::DataSend(Bytes::copy_from_slice(&[IAC, WILL, opt])),
TelnetEvents::Negotiation(event),
]
}
(
DO,
CompatibilityEntry {
local_state: false, ..
}
| CompatibilityEntry { local: false, .. },
) => {
vec![TelnetEvents::DataSend(Bytes::copy_from_slice(&[
IAC, WONT, opt,
]))]
}
(
DONT,
mut entry @ CompatibilityEntry {
local_state: true, ..
},
) => {
entry.local_state = false;
self.options.set_option(opt, entry);
vec![
TelnetEvents::DataSend(Bytes::copy_from_slice(&[IAC, WONT, opt])),
TelnetEvents::Negotiation(event),
]
}
(_, CompatibilityEntry { .. }) if command == DONT || command == WONT => {
vec![TelnetEvents::Negotiation(event)]
}
_ => Vec::default(),
}
}
}