use core::fmt;
pub const FEND: u8 = 0xC0;
pub const FESC: u8 = 0xDB;
pub const TFEND: u8 = 0xDC;
pub const TFESC: u8 = 0xDD;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KissError {
PortOutOfRange {
got: u8,
},
UnknownCommand {
got: u8,
},
BufferTooSmall {
needed: usize,
got: usize,
},
InvalidEscape {
got: u8,
},
FrameTooLarge {
capacity: usize,
},
}
impl fmt::Display for KissError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
KissError::PortOutOfRange { got } => {
write!(f, "TNC port {got} is out of range: must be within 0..=15")
}
KissError::UnknownCommand { got } => write!(
f,
"command nibble 0x{got:X} is unknown: must be 0..=6, or the whole byte 0xFF"
),
KissError::BufferTooSmall { needed, got } => write!(
f,
"output buffer of {got} bytes is too small: the encoded frame needs {needed} bytes"
),
KissError::InvalidEscape { got } => write!(
f,
"escape FESC followed by 0x{got:02X} is invalid: only TFEND (0xDC) or TFESC (0xDD) may follow"
),
KissError::FrameTooLarge { capacity } => write!(
f,
"received frame is too large: the buffer holds at most {capacity} bytes"
),
}
}
}
impl core::error::Error for KissError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct KissPort(u8);
impl KissPort {
pub const fn new(port: u8) -> Result<Self, KissError> {
if port <= 15 {
Ok(Self(port))
} else {
Err(KissError::PortOutOfRange { got: port })
}
}
#[must_use]
pub const fn get(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KissCommand {
Data,
TxDelay,
Persistence,
SlotTime,
TxTail,
FullDuplex,
SetHardware,
Return,
}
impl KissCommand {
#[must_use]
pub const fn to_byte(self, port: KissPort) -> u8 {
match self {
KissCommand::Data => port.0 << 4,
KissCommand::TxDelay => (port.0 << 4) | 1,
KissCommand::Persistence => (port.0 << 4) | 2,
KissCommand::SlotTime => (port.0 << 4) | 3,
KissCommand::TxTail => (port.0 << 4) | 4,
KissCommand::FullDuplex => (port.0 << 4) | 5,
KissCommand::SetHardware => (port.0 << 4) | 6,
KissCommand::Return => 0xFF,
}
}
pub const fn from_byte(byte: u8) -> Result<(Self, KissPort), KissError> {
if byte == 0xFF {
return Ok((KissCommand::Return, KissPort(0)));
}
let port = KissPort(byte >> 4);
let command = match byte & 0x0F {
0 => KissCommand::Data,
1 => KissCommand::TxDelay,
2 => KissCommand::Persistence,
3 => KissCommand::SlotTime,
4 => KissCommand::TxTail,
5 => KissCommand::FullDuplex,
6 => KissCommand::SetHardware,
nibble => return Err(KissError::UnknownCommand { got: nibble }),
};
Ok((command, port))
}
}
#[must_use]
pub fn encoded_len(port: KissPort, command: KissCommand, payload: &[u8]) -> usize {
let cmd = command.to_byte(port);
let escapes = payload
.iter()
.chain(core::iter::once(&cmd))
.filter(|&&b| b == FEND || b == FESC)
.count();
3 + payload.len() + escapes
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn encode_to_vec(port: KissPort, command: KissCommand, payload: &[u8]) -> alloc::vec::Vec<u8> {
let mut out = alloc::vec![0u8; encoded_len(port, command, payload)];
let n = encode_into(port, command, payload, &mut out)
.expect("a buffer of encoded_len() always fits");
out.truncate(n);
out
}
pub fn encode_into(
port: KissPort,
command: KissCommand,
payload: &[u8],
out: &mut [u8],
) -> Result<usize, KissError> {
let needed = encoded_len(port, command, payload);
if out.len() < needed {
return Err(KissError::BufferTooSmall {
needed,
got: out.len(),
});
}
let cmd = command.to_byte(port);
let mut pos = 0;
let mut put = |slot: &mut [u8], byte: u8| {
if let Some(cell) = slot.get_mut(pos) {
*cell = byte;
}
pos += 1;
};
put(out, FEND);
for &byte in core::iter::once(&cmd).chain(payload.iter()) {
match byte {
FEND => {
put(out, FESC);
put(out, TFEND);
}
FESC => {
put(out, FESC);
put(out, TFESC);
}
other => put(out, other),
}
}
put(out, FEND);
Ok(pos)
}
pub fn frame_iter(port: KissPort, command: KissCommand, payload: &[u8]) -> KissFrameIter<'_> {
KissFrameIter {
payload,
state: EncState::OpenFend,
cmd_byte: command.to_byte(port),
}
}
#[derive(Debug, Clone, Copy)]
enum EncState {
OpenFend,
Content {
pos: usize,
pending: Option<u8>,
},
Done,
}
#[derive(Debug, Clone)]
pub struct KissFrameIter<'a> {
payload: &'a [u8],
state: EncState,
cmd_byte: u8,
}
impl KissFrameIter<'_> {
fn content(&self, pos: usize) -> Option<u8> {
match pos.checked_sub(1) {
None => Some(self.cmd_byte),
Some(i) => self.payload.get(i).copied(),
}
}
}
impl Iterator for KissFrameIter<'_> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
match self.state {
EncState::OpenFend => {
self.state = EncState::Content {
pos: 0,
pending: None,
};
Some(FEND)
}
EncState::Content { pos, pending } => {
if let Some(byte) = pending {
self.state = EncState::Content {
pos: pos + 1,
pending: None,
};
return Some(byte);
}
match self.content(pos) {
Some(FEND) => {
self.state = EncState::Content {
pos,
pending: Some(TFEND),
};
Some(FESC)
}
Some(FESC) => {
self.state = EncState::Content {
pos,
pending: Some(TFESC),
};
Some(FESC)
}
Some(byte) => {
self.state = EncState::Content {
pos: pos + 1,
pending: None,
};
Some(byte)
}
None => {
self.state = EncState::Done;
Some(FEND)
}
}
}
EncState::Done => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KissFrame<'a> {
command: KissCommand,
port: KissPort,
payload: &'a [u8],
}
impl<'a> KissFrame<'a> {
#[must_use]
pub const fn command(&self) -> KissCommand {
self.command
}
#[must_use]
pub const fn port(&self) -> KissPort {
self.port
}
#[must_use]
pub const fn payload(&self) -> &'a [u8] {
self.payload
}
}
#[derive(Debug, Clone)]
pub struct KissDeframer<const N: usize> {
buf: [u8; N],
len: usize,
in_frame: bool,
escaping: bool,
overflowed: bool,
}
impl<const N: usize> KissDeframer<N> {
#[must_use]
pub const fn new() -> Self {
Self {
buf: [0; N],
len: 0,
in_frame: false,
escaping: false,
overflowed: false,
}
}
const fn reset_frame(&mut self) {
self.len = 0;
self.escaping = false;
self.overflowed = false;
}
pub fn push(&mut self, byte: u8) -> Option<Result<KissFrame<'_>, KissError>> {
if byte == FEND {
let close = self.in_frame && self.len > 0;
let escaping = self.escaping;
let overflowed = self.overflowed;
let len = self.len;
self.in_frame = true;
self.reset_frame();
if !close {
return None;
}
if escaping {
return Some(Err(KissError::InvalidEscape { got: FEND }));
}
if overflowed {
return Some(Err(KissError::FrameTooLarge { capacity: N }));
}
let bytes = self.buf.get(..len).unwrap_or(&[]);
let (&cmd_byte, payload) = bytes.split_first()?;
return Some(
KissCommand::from_byte(cmd_byte).map(|(command, port)| KissFrame {
command,
port,
payload,
}),
);
}
if !self.in_frame {
return None;
}
if self.escaping {
self.escaping = false;
match byte {
TFEND => self.store(FEND),
TFESC => self.store(FESC),
bad => {
self.in_frame = false;
self.reset_frame();
return Some(Err(KissError::InvalidEscape { got: bad }));
}
}
return None;
}
if byte == FESC {
self.escaping = true;
return None;
}
self.store(byte);
None
}
const fn store(&mut self, byte: u8) {
if self.len < N {
self.buf[self.len] = byte;
} else {
self.overflowed = true;
}
self.len = self.len.saturating_add(1);
}
}
impl<const N: usize> Default for KissDeframer<N> {
fn default() -> Self {
Self::new()
}
}