#[allow(dead_code)]
pub mod codes {
pub const IAC: u8 = 0xFF;
pub const DONT: u8 = 0xFE;
pub const DO: u8 = 0xFD;
pub const WONT: u8 = 0xFC;
pub const WILL: u8 = 0xFB;
pub const SB: u8 = 0xFA;
pub const SE: u8 = 0xF0;
pub const TELOPT_ECHO: u8 = 0x01;
pub const TELOPT_SGA: u8 = 0x03; pub const TELOPT_LINEMODE: u8 = 0x22;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TelnetEvent {
Data(Vec<u8>),
Reply(Vec<u8>),
}
#[derive(Debug, Default)]
pub struct TelnetParser {
state: ParseState,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
enum ParseState {
#[default]
Data,
Iac,
Negotiate(u8), Subneg,
SubnegIac,
}
impl TelnetParser {
pub fn new() -> Self {
Self::default()
}
pub fn feed(&mut self, input: &[u8]) -> Vec<TelnetEvent> {
let mut events = Vec::new();
let mut data_buf = Vec::with_capacity(input.len());
let mut reply_buf = Vec::new();
for &b in input {
self.state = match self.state {
ParseState::Data => {
if b == codes::IAC {
ParseState::Iac
} else {
data_buf.push(b);
ParseState::Data
}
}
ParseState::Iac => match b {
codes::IAC => {
data_buf.push(0xFF);
ParseState::Data
}
codes::WILL | codes::WONT | codes::DO | codes::DONT => ParseState::Negotiate(b),
codes::SB => ParseState::Subneg,
_ => {
ParseState::Data
}
},
ParseState::Negotiate(cmd) => {
let response = match cmd {
codes::WILL => codes::DONT, codes::DO => codes::WONT, _ => 0,
};
if response != 0 {
reply_buf.extend_from_slice(&[codes::IAC, response, b]);
}
ParseState::Data
}
ParseState::Subneg => {
if b == codes::IAC {
ParseState::SubnegIac
} else {
ParseState::Subneg
}
}
ParseState::SubnegIac => match b {
codes::SE => ParseState::Data,
codes::IAC => ParseState::Subneg,
_ => ParseState::Subneg,
},
};
}
if !data_buf.is_empty() {
events.push(TelnetEvent::Data(data_buf));
}
if !reply_buf.is_empty() {
events.push(TelnetEvent::Reply(reply_buf));
}
events
}
}
pub fn initial_negotiation() -> Vec<u8> {
vec![
codes::IAC,
codes::WILL,
codes::TELOPT_ECHO,
codes::IAC,
codes::DO,
codes::TELOPT_LINEMODE,
]
}
pub fn iac_escape(data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(data.len());
for &b in data {
if b == codes::IAC {
out.push(codes::IAC);
out.push(codes::IAC);
} else {
out.push(b);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn passes_plain_data_through() {
let mut p = TelnetParser::new();
let evs = p.feed(b"hello");
assert_eq!(evs, vec![TelnetEvent::Data(b"hello".to_vec())]);
}
#[test]
fn unescapes_iac_iac() {
let mut p = TelnetParser::new();
let evs = p.feed(&[b'a', codes::IAC, codes::IAC, b'b']);
assert_eq!(evs, vec![TelnetEvent::Data(vec![b'a', 0xFF, b'b'])]);
}
#[test]
fn refuses_unknown_will() {
let mut p = TelnetParser::new();
let evs = p.feed(&[codes::IAC, codes::WILL, codes::TELOPT_ECHO]);
assert_eq!(
evs,
vec![TelnetEvent::Reply(vec![
codes::IAC,
codes::DONT,
codes::TELOPT_ECHO,
])]
);
}
#[test]
fn skips_subnegotiation_block() {
let mut p = TelnetParser::new();
let evs = p.feed(&[
b'a',
codes::IAC,
codes::SB,
0x18,
0x01,
0x02,
codes::IAC,
codes::SE,
b'b',
]);
assert_eq!(evs, vec![TelnetEvent::Data(vec![b'a', b'b'])]);
}
#[test]
fn iac_escape_doubles_ff() {
assert_eq!(iac_escape(&[1, 0xFF, 2]), vec![1, 0xFF, 0xFF, 2]);
}
}