diff --git a/embassy-at-cmux/src/frame.rs b/embassy-at-cmux/src/frame.rs index 372df9099..3267ecd1c 100644 --- a/embassy-at-cmux/src/frame.rs +++ b/embassy-at-cmux/src/frame.rs @@ -1,489 +1,161 @@ -//! ### Frame types +//! CMUX Frame Encoding and Decoding use bitfield_struct::bitfield; use crc::CRC_8_ROHC; -use embedded_io_async::Error as _; +use embedded_io_async::{BufRead, Error as IoError, ErrorKind as IoErrorKind, Write}; +// --- Constants --- const FLAG: u8 = 0xF9; const EA: u8 = 0x01; const CR: u8 = 0x02; const PF: u8 = 0x10; - -const FCS: crc::Crc = crc::Crc::::new(&CRC_8_ROHC); +const FCS_GENERATOR: crc::Crc = crc::Crc::::new(&CRC_8_ROHC); const GOOD_FCS: u8 = 0xCF; +// Maximum size of an Information field. +const MAX_INFO_LEN: usize = 254; +// --- Error Handling --- +#[derive(Debug, PartialEq)] +pub enum CmuxError { + Io(IoErrorKind), + CrcMismatch, + InvalidFrameFormat, + UnsupportedOperation, // For unimplemented features + InvalidInformationField, +} + +impl From for CmuxError { + fn from(err: E) -> Self { + CmuxError::Io(err.kind()) + } +} + +// --- Helper Functions --- +async fn write_ea_length(writer: &mut W, length: usize) -> Result<(), CmuxError> { + let mut len = length; + let mut bytes = [0u8; 4]; // Max EA length can fit in 4 bytes + let mut i = 3; + + while len > 0 { + bytes[i] = ((len as u8 & 0x7F) << 1) | EA; + len >>= 7; + i -= 1; + } + + Ok(writer.write_all(&bytes[i + 1..]).await?) +} + +async fn read_ea_length(reader: &mut R) -> Result { + let mut len = 0; + let mut shift = 0; + + loop { + let byte = reader.read_u8().await.map_err(|e| CmuxError::Io(e.kind()))?; + + len |= ((byte >> 1) as usize) << shift; + shift += 7; + + if byte & EA == EA { + break; + } + } + Ok(len) +} + +// --- CMUX Frame Components --- #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] -pub enum CR { +pub enum CommandResponse { Response = 0x00, Command = CR, } -impl From for CR { - fn from(value: u8) -> Self { - if (value & CR) == CR { - return Self::Command; - } - Self::Response - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] -pub enum PF { +pub enum PollFinal { Final = 0x00, Poll = PF, } -impl From for PF { - fn from(value: u8) -> Self { - if (value & PF) == PF { - return Self::Poll; - } - Self::Final - } -} - -fn read_ea_len(buf: &[u8]) -> (usize, usize) { - let mut len = 0; - let mut i = 0; - for b in buf { - len <<= 7; - len |= (b >> 1) as usize; - if (b & EA) == EA { - break; - } - i += 1; - } - i += 1; - - (i, len) -} - -fn read_ea(buf: &[u8]) -> &[u8] { - let (i, len) = read_ea_len(buf); - &buf[i..i + len] -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InformationType { - /// DLC parameter negotiation (PN) ParameterNegotiation = 0x80, - /// Power Saving Control (PSC) + // ... other InformationType variants ... PowerSavingControl = 0x40, - /// Multiplexer close down (CLD) MultiplexerCloseDown = 0xC0, - /// Test Command (Test) TestCommand = 0x20, - /// Flow Control On Command (FCon) FlowControlOnCommand = 0xA0, - /// Flow Control Off Command (FCoff) FlowControlOffCommand = 0x60, - /// Modem Status Command (MSC) ModemStatusCommand = 0xE0, - /// Non Supported Command Response (NSC) NonSupportedCommandResponse = 0x10, - /// Remote Port Negotiation Command (RPN) RemotePortNegotiationCommand = 0x90, - /// Remote Line Status Command(RLS) RemoteLineStatusCommand = 0x50, - /// Service Negotiation Command (SNC) ServiceNegotiationCommand = 0xD0, } -impl From for InformationType { - fn from(value: u8) -> Self { +impl TryFrom for InformationType { + type Error = CmuxError; + fn try_from(value: u8) -> Result { match value & !(CR | EA) { - 0x80 => Self::ParameterNegotiation, - 0x40 => Self::PowerSavingControl, - 0xC0 => Self::MultiplexerCloseDown, - 0x20 => Self::TestCommand, - 0xA0 => Self::FlowControlOnCommand, - 0x60 => Self::FlowControlOffCommand, - 0xE0 => Self::ModemStatusCommand, - 0x10 => Self::NonSupportedCommandResponse, - 0x90 => Self::RemotePortNegotiationCommand, - 0x50 => Self::RemoteLineStatusCommand, - 0xD0 => Self::ServiceNegotiationCommand, - n => panic!("Unknown information type {:#02x}", n), + 0x80 => Ok(Self::ParameterNegotiation), + // ... other InformationType matches ... + 0x40 => Ok(Self::PowerSavingControl), + 0xC0 => Ok(Self::MultiplexerCloseDown), + 0x20 => Ok(Self::TestCommand), + 0xA0 => Ok(Self::FlowControlOnCommand), + 0x60 => Ok(Self::FlowControlOffCommand), + 0xE0 => Ok(Self::ModemStatusCommand), + 0x10 => Ok(Self::NonSupportedCommandResponse), + 0x90 => Ok(Self::RemotePortNegotiationCommand), + 0x50 => Ok(Self::RemoteLineStatusCommand), + 0xD0 => Ok(Self::ServiceNegotiationCommand), + _ => Err(CmuxError::InvalidFrameFormat), } } } -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Information<'a> { - /// DLC parameter negotiation (PN) - ParameterNegotiation(ParameterNegotiation), - /// Power Saving Control (PSC) - PowerSavingControl, - /// Multiplexer close down (CLD) - MultiplexerCloseDown(MultiplexerCloseDown), - /// Test Command (Test) - TestCommand, - /// Flow Control On Command (FCon) - FlowControlOnCommand(FlowControlOnCommand), - /// Flow Control Off Command (FCoff) - FlowControlOffCommand(FlowControlOffCommand), - /// Modem Status Command (MSC) - ModemStatusCommand(ModemStatusCommand), - /// Non Supported Command Response (NSC) - NonSupportedCommandResponse(NonSupportedCommandResponse), - /// Remote Port Negotiation Command (RPN) - RemotePortNegotiationCommand, - /// Remote Line Status Command(RLS) - RemoteLineStatusCommand(RemoteLineStatusCommand), - /// Service Negotiation Command (SNC) - ServiceNegotiationCommand, - Data(&'a [u8]), -} - -impl<'a> Information<'a> { - pub fn is_command(&self) -> bool { - match self { - Information::ParameterNegotiation(i) => i.is_command(), - Information::FlowControlOnCommand(i) => i.is_command(), - Information::FlowControlOffCommand(i) => i.is_command(), - Information::ModemStatusCommand(i) => i.is_command(), - Information::NonSupportedCommandResponse(i) => i.is_command(), - Information::RemoteLineStatusCommand(i) => i.is_command(), - _ => true, - } - } - - fn wire_len(&self) -> usize { - match self { - Information::ParameterNegotiation(inner) => inner.wire_len(), - Information::PowerSavingControl => todo!(), - Information::MultiplexerCloseDown(inner) => inner.wire_len(), - Information::TestCommand => todo!(), - Information::FlowControlOnCommand(inner) => inner.wire_len(), - Information::FlowControlOffCommand(inner) => inner.wire_len(), - Information::ModemStatusCommand(inner) => inner.wire_len(), - Information::NonSupportedCommandResponse(inner) => inner.wire_len(), - Information::RemotePortNegotiationCommand => todo!(), - Information::RemoteLineStatusCommand(inner) => inner.wire_len(), - Information::ServiceNegotiationCommand => todo!(), - Information::Data(d) => d.len(), - } - } - - async fn write(&self, writer: &mut W) -> Result<(), Error> { - match self { - Information::ParameterNegotiation(inner) => inner.write(writer).await, - Information::FlowControlOnCommand(inner) => inner.write(writer).await, - Information::FlowControlOffCommand(inner) => inner.write(writer).await, - Information::ModemStatusCommand(inner) => inner.write(writer).await, - Information::NonSupportedCommandResponse(inner) => inner.write(writer).await, - Information::RemoteLineStatusCommand(inner) => inner.write(writer).await, - Information::Data(d) => writer.write_all(d).await.map_err(|e| Error::Write(e.kind())), - Information::RemotePortNegotiationCommand => todo!(), - Information::PowerSavingControl => todo!(), - Information::MultiplexerCloseDown(inner) => inner.write(writer).await, - Information::TestCommand => todo!(), - Information::ServiceNegotiationCommand => todo!(), - } - } - - pub fn parse(buf: &[u8]) -> Result { - let info_type = InformationType::from(buf[0]); - let cr = CR::from(buf[0]); - - // get length - let inner_data = read_ea(&buf[1..]); - - Ok(match info_type { - InformationType::ParameterNegotiation => Self::ParameterNegotiation(ParameterNegotiation { cr }), - InformationType::PowerSavingControl => Self::PowerSavingControl, - InformationType::MultiplexerCloseDown => Self::MultiplexerCloseDown(MultiplexerCloseDown { cr }), - InformationType::TestCommand => Self::TestCommand, - InformationType::FlowControlOnCommand => Self::FlowControlOnCommand(FlowControlOnCommand { cr }), - InformationType::FlowControlOffCommand => Self::FlowControlOffCommand(FlowControlOffCommand { cr }), - InformationType::ModemStatusCommand => { - let brk = if inner_data.len() == 3 { - Some(Break::from_bits(inner_data[2])) - } else { - None - }; - Self::ModemStatusCommand(ModemStatusCommand { - cr, - dlci: inner_data[0] >> 2, - control: Control::from_bits(inner_data[1]), - brk, - }) - } - InformationType::NonSupportedCommandResponse => { - Self::NonSupportedCommandResponse(NonSupportedCommandResponse { - cr, - command_type: InformationType::from(inner_data[0]), - }) - } - InformationType::RemotePortNegotiationCommand => Self::RemotePortNegotiationCommand, - InformationType::RemoteLineStatusCommand => Self::RemoteLineStatusCommand(RemoteLineStatusCommand { - cr, - dlci: inner_data[0] >> 2, - remote_line_status: RemoteLineStatus::from(inner_data[1]), - }), - InformationType::ServiceNegotiationCommand => Self::ServiceNegotiationCommand, - }) - } -} +// --- Information Field Implementations --- #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum FrameType { - /// Set Asynchronous Balanced Mode (SABM) command - Sabm = 0x2F, - /// Unnumbered Acknowledgement (UA) response - Ua = 0x63, - /// Disconnected mode (DM) - Dm = 0x0F, - /// Disconnect (DISC) - Disc = 0x43, - /// Unnumbered information with header check (UIH) command and response - Uih = 0xEF, - /// Unnumbered information (UI) command and response - Ui = 0x03, -} - -impl From for FrameType { - fn from(value: u8) -> Self { - match value & !PF { - 0x2F => Self::Sabm, - 0x63 => Self::Ua, - 0x0F => Self::Dm, - 0x43 => Self::Disc, - 0xEF => Self::Uih, - 0x03 => Self::Ui, - n => panic!("Unknown frame type {:#02x}", n), - } - } -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - Read(embedded_io_async::ErrorKind), - Write(embedded_io_async::ErrorKind), - Crc, - MalformedFrame, -} - -pub trait Info { - const INFORMATION_TYPE: InformationType; - - fn is_command(&self) -> bool; - - fn wire_len(&self) -> usize; - - async fn write(&self, writer: &mut W) -> Result<(), Error>; -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ParameterNegotiation { - cr: CR, + pub cr: CommandResponse, + // ... PN-specific fields ... } -impl Info for ParameterNegotiation { - const INFORMATION_TYPE: InformationType = InformationType::ParameterNegotiation; - - fn is_command(&self) -> bool { - self.cr == CR::Command - } - - fn wire_len(&self) -> usize { - todo!() - } - - async fn write(&self, writer: &mut W) -> Result<(), Error> { - let buf = [0u8; 8]; - - // TODO: Add Parameters! - - writer.write_all(&buf).await.map_err(|e| Error::Write(e.kind())) +impl ParameterNegotiation { + pub fn new(cr: CommandResponse) -> Self { + Self { cr } } } -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct MultiplexerCloseDown { - pub cr: CR, -} - -impl Info for MultiplexerCloseDown { - const INFORMATION_TYPE: InformationType = InformationType::MultiplexerCloseDown; - - fn is_command(&self) -> bool { - self.cr == CR::Command +impl InformationField for ParameterNegotiation { + fn information_type(&self) -> InformationType { + InformationType::ParameterNegotiation } - fn wire_len(&self) -> usize { - 1 - } - - async fn write(&self, writer: &mut W) -> Result<(), Error> { - writer - .write_all(&[Self::INFORMATION_TYPE as u8 | self.cr as u8 | EA]) - .await - .map_err(|e| Error::Write(e.kind())) - } -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct FlowControlOffCommand { - cr: CR, -} - -impl Info for FlowControlOffCommand { - const INFORMATION_TYPE: InformationType = InformationType::FlowControlOffCommand; - - fn is_command(&self) -> bool { - self.cr == CR::Command - } - - fn wire_len(&self) -> usize { - 1 - } - - async fn write(&self, writer: &mut W) -> Result<(), Error> { - writer - .write_all(&[Self::INFORMATION_TYPE as u8 | self.cr as u8 | EA]) - .await - .map_err(|e| Error::Write(e.kind())) - } -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct FlowControlOnCommand { - cr: CR, -} - -impl Info for FlowControlOnCommand { - const INFORMATION_TYPE: InformationType = InformationType::FlowControlOnCommand; - - fn is_command(&self) -> bool { - self.cr == CR::Command - } - - fn wire_len(&self) -> usize { - 1 - } - - async fn write(&self, writer: &mut W) -> Result<(), Error> { - writer - .write_all(&[Self::INFORMATION_TYPE as u8 | self.cr as u8 | EA]) - .await - .map_err(|e| Error::Write(e.kind())) - } -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ModemStatusCommand { - pub cr: CR, - pub dlci: u8, - pub control: Control, - pub brk: Option, -} - -impl Info for ModemStatusCommand { - const INFORMATION_TYPE: InformationType = InformationType::ModemStatusCommand; - - fn is_command(&self) -> bool { - self.cr == CR::Command - } - - fn wire_len(&self) -> usize { - self.brk.map_or(4, |_| 5) - } - - async fn write(&self, writer: &mut W) -> Result<(), Error> { - let len = self.wire_len() as u8 - 2; - - writer - .write_all(&[ - Self::INFORMATION_TYPE as u8 | self.cr as u8 | EA, - len << 1 | EA, - self.dlci << 2 | CR | EA, - self.control.with_ea(true).into_bits(), - ]) - .await - .map_err(|e| Error::Write(e.kind()))?; - - if let Some(brk) = self.brk { - writer - .write_all(&[brk.with_ea(true).into_bits()]) - .await - .map_err(|e| Error::Write(e.kind()))?; - } - + async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + // Write PN-specific data here Ok(()) } -} -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct NonSupportedCommandResponse { - pub cr: CR, - pub command_type: InformationType, -} - -impl Info for NonSupportedCommandResponse { - const INFORMATION_TYPE: InformationType = InformationType::NonSupportedCommandResponse; - - fn is_command(&self) -> bool { - self.cr == CR::Command + async fn read(reader: &mut R, len: usize) -> Result { + if len != 0 { + return Err(CmuxError::InvalidInformationField); + } + // Read and parse PN-specific data from 'reader' here + Ok(Self { + cr: CommandResponse::Command, + // ... parsed fields ... + }) } fn wire_len(&self) -> usize { - 2 - } - - async fn write(&self, writer: &mut W) -> Result<(), Error> { - writer - .write_all(&[ - Self::INFORMATION_TYPE as u8 | self.cr as u8 | EA, - self.command_type as u8 | self.cr as u8 | EA, - ]) - .await - .map_err(|e| Error::Write(e.kind())) - } -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct RemoteLineStatusCommand { - pub cr: CR, - pub dlci: u8, - pub remote_line_status: RemoteLineStatus, -} - -impl Info for RemoteLineStatusCommand { - const INFORMATION_TYPE: InformationType = InformationType::RemoteLineStatusCommand; - - fn is_command(&self) -> bool { - self.cr == CR::Command - } - - fn wire_len(&self) -> usize { - 3 - } - - async fn write(&self, writer: &mut W) -> Result<(), Error> { - writer - .write_all(&[ - Self::INFORMATION_TYPE as u8 | self.cr as u8 | EA, - self.dlci << 2 | CR | EA, - self.remote_line_status.into_bits(), - ]) - .await - .map_err(|e| Error::Write(e.kind())) + // Return the length of the serialized PN data + 0 } } @@ -554,282 +226,759 @@ pub struct Break { pub struct RemoteLineStatus { #[bits(4)] pub l: u8, - /// The res bits are set to zero for the sender and ignored by the reciever. + /// The res bits are set to zero for the sender and ignored by the receiver. #[bits(4, access = None)] reserved: u8, } -pub(crate) struct RxHeader<'a, R: embedded_io_async::BufRead> { - id: u8, - pub frame_type: FrameType, - pub len: usize, - fcs: crc::Digest<'a, u8>, - reader: &'a mut R, +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MultiplexerCloseDown { + pub cr: CommandResponse, } -#[cfg(feature = "defmt")] -impl<'a, R: embedded_io_async::BufRead> defmt::Format for RxHeader<'a, R> { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!( - fmt, - "RxHeader {{ id: {}, frame_type: {:?}, len: {}}}", - self.id, - self.frame_type, - self.len, - ) +impl MultiplexerCloseDown { + pub fn new(cr: CommandResponse) -> Self { + Self { cr } } } -impl<'a, R: embedded_io_async::BufRead> core::fmt::Debug for RxHeader<'a, R> { - fn fmt(&self, fmt: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { - write!( - fmt, - "RxHeader {{ id: {}, frame_type: {:?}, len: {}}}", - self.id, self.frame_type, self.len - ) +impl InformationField for MultiplexerCloseDown { + fn information_type(&self) -> InformationType { + InformationType::MultiplexerCloseDown } -} -impl<'a, R: embedded_io_async::BufRead> RxHeader<'a, R> { - pub(crate) async fn read(reader: &'a mut R) -> Result { - let mut fcs = FCS.digest(); + async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + // Write CLD-specific data here + Ok(()) + } - let mut header = [FLAG; 3]; - while header[0] == FLAG { - Self::read_exact(reader, &mut header[..1]).await?; + async fn read(reader: &mut R, len: usize) -> Result { + if len != 0 { + return Err(CmuxError::InvalidInformationField); } - Self::read_exact(reader, &mut header[1..]).await?; - - let id = header[0] >> 2; - let frame_type = FrameType::from(header[1]); - - fcs.update(&header); - - let mut len = (header[2] >> 1) as usize; - if (header[2] & EA) != EA { - let mut l2 = [0u8; 1]; - Self::read_exact(reader, &mut l2).await?; - fcs.update(&l2); - len |= (l2[0] as usize) << 7; - }; - Ok(Self { - id, - frame_type, - len, - reader, - fcs, + cr: CommandResponse::Command, + // ... parsed fields ... }) } - pub(crate) fn is_control(&self) -> bool { - self.id == 0 - } - - pub(crate) fn id(&self) -> u8 { - self.id - } - - async fn read_exact(r: &mut R, mut data: &mut [u8]) -> Result<(), Error> { - while !data.is_empty() { - let buf = r.fill_buf().await.map_err(|e| Error::Read(e.kind()))?; - if buf.is_empty() { - panic!("EOF"); - } - let n = buf.len().min(data.len()); - data[..n].copy_from_slice(&buf[..n]); - data = &mut data[n..]; - r.consume(n); - } - Ok(()) - } - - pub(crate) async fn read_information<'d>(mut self) -> Result, Error> { - assert!(self.len <= 24); - - let mut buf = [0u8; 24]; - Self::read_exact(self.reader, &mut buf[..self.len]).await?; - - if self.frame_type == FrameType::Ui { - self.fcs.update(&buf[..self.len]); - } - - let info = Information::parse(&buf[..self.len])?; - - // Make sure we cannot call this twice, or call `copy`, to over-read data - self.len = 0; - - self.finalize().await?; - - Ok(info) - } - - pub(crate) async fn copy(mut self, w: &mut W) -> Result<(), Error> { - while self.len != 0 { - let buf = self.reader.fill_buf().await.map_err(|e| Error::Read(e.kind()))?; - if buf.is_empty() { - panic!("EOF"); - } - let n = buf.len().min(self.len); - - // FIXME: This should be re-written in a way that allows us to set channel flowcontrol if `w` cannot receive more bytes - let n = w.write(&buf[..n]).await.map_err(|e| Error::Write(e.kind()))?; - self.reader.consume(n); - self.len -= n; - } - w.flush().await.map_err(|e| Error::Write(e.kind()))?; - self.finalize().await?; - - Ok(()) - } - - pub async fn finalize(mut self) -> Result<(), Error> { - while self.len > 0 { - // Discard any information here - let buf = self.reader.fill_buf().await.map_err(|e| Error::Read(e.kind()))?; - if buf.is_empty() { - panic!("EOF"); - } - let n = buf.len().min(self.len); - warn!("Discarding {} bytes of data in {:?}", n, self.frame_type); - self.reader.consume(n); - self.len -= n; - } - - let mut trailer = [0; 2]; - Self::read_exact(&mut self.reader, &mut trailer).await?; - - self.fcs.update(&[trailer[0]]); - let expected_fcs = self.fcs.finalize(); - - if trailer[1] != FLAG { - error!("Malformed packet! Expected {:#02x} but got {:#02x}", FLAG, trailer[1]); - return Err(Error::MalformedFrame); - } - - if expected_fcs != GOOD_FCS { - error!("bad crc! {:#02x} != {:#02x}", expected_fcs, GOOD_FCS); - return Err(Error::Crc); - } - - Ok(()) + fn wire_len(&self) -> usize { + // Return the length of the serialized CLD data + 0 } } -pub trait Frame { - const FRAME_TYPE: FrameType; +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FlowControlOffCommand { + pub cr: CommandResponse, +} - fn cr(&self) -> u8; - fn pf(&self) -> u8; +impl FlowControlOffCommand { + pub fn new(cr: CommandResponse) -> Self { + Self { cr } + } +} - fn id(&self) -> u8; - - fn information(&self) -> Option<&Information> { - None +impl InformationField for FlowControlOffCommand { + fn information_type(&self) -> InformationType { + InformationType::FlowControlOffCommand } - async fn write(&self, writer: &mut W) -> Result<(), Error> { - let information_len = self.information().map_or(0, |i| i.wire_len()); + async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + Ok(()) + } - trace!( - "TxHeader {{ id: {}, frame_type: {:?}, len: {}}}", - self.id(), - Self::FRAME_TYPE, - information_len - ); + async fn read(reader: &mut R, len: usize) -> Result { + if len != 0 { + return Err(CmuxError::InvalidInformationField); + } + Ok(Self { + cr: CommandResponse::Command, + }) + } - let fcs = if information_len < 128 { - let header = [ - FLAG, - self.id() << 2 | EA | self.cr(), - Self::FRAME_TYPE as u8 | self.pf(), - (information_len as u8) << 1 | EA, - ]; + fn wire_len(&self) -> usize { + 0 + } +} - writer.write_all(&header).await.map_err(|e| Error::Write(e.kind()))?; +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FlowControlOnCommand { + pub cr: CommandResponse, +} - 0xFF - FCS.checksum(&header[1..]) +impl FlowControlOnCommand { + pub fn new(cr: CommandResponse) -> Self { + Self { cr } + } +} + +impl InformationField for FlowControlOnCommand { + fn information_type(&self) -> InformationType { + InformationType::FlowControlOnCommand + } + + async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + // Write FCon-specific data here + Ok(()) + } + + async fn read(reader: &mut R, len: usize) -> Result { + if len != 0 { + return Err(CmuxError::InvalidInformationField); + } + Ok(Self { + cr: CommandResponse::Command, + }) + } + + fn wire_len(&self) -> usize { + // Return the length of the serialized FCon data + 0 + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ModemStatusCommand { + /// Command/Response flag for the frame. + pub cr: CommandResponse, + /// Data Link Connection Identifier (DLCI). + pub dlci: u8, + /// Control signals for the frame. + pub control: Control, + /// Optional break signal for the frame. + pub brk: Option, +} + +impl ModemStatusCommand { + pub fn new(cr: CommandResponse, dlci: u8, control: Control, brk: Option) -> Self { + Self { cr, dlci, control, brk } + } +} + +impl InformationField for ModemStatusCommand { + fn information_type(&self) -> InformationType { + InformationType::ModemStatusCommand + } + + async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + writer.write_all(&[(self.dlci << 2) | (self.cr as u8)]).await?; + writer.write_all(&[self.control.with_ea(true).into_bits()]).await?; + if let Some(brk) = self.brk { + writer.write_all(&[brk.with_ea(true).into_bits()]).await?; + } + Ok(()) + } + + async fn read(reader: &mut R, len: usize) -> Result { + if len < 2 || len > 3 { + return Err(CmuxError::InvalidInformationField); + } + let dlci_cr = reader.read_u8().await?; + let control = Control::from_bits(reader.read_u8().await?); + let brk = if len == 3 { + Some(Break::from_bits(reader.read_u8().await?)) } else { - let [b1, b2] = ((information_len as u16) << 1).to_le_bytes(); + None + }; + Ok(Self { + cr: (dlci_cr & 0x03).into(), + dlci: dlci_cr >> 2, + control, + brk, + }) + } - let header = [ - FLAG, - self.id() << 2 | EA | self.cr(), - Self::FRAME_TYPE as u8 | self.pf(), - b1, - b2, - ]; + fn wire_len(&self) -> usize { + // Return the length of the serialized FCon data + 2 + if self.brk.is_some() { 1 } else { 0 } + } +} - writer.write_all(&header).await.map_err(|e| Error::Write(e.kind()))?; +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct NonSupportedCommandResponse { + /// Command/Response flag for the frame. + pub cr: CommandResponse, + /// Type of the command that was not supported. + pub command_type: InformationType, +} - 0xFF - FCS.checksum(&header[1..]) +impl NonSupportedCommandResponse { + pub fn new(cr: CommandResponse, command_type: InformationType) -> Self { + Self { cr, command_type } + } +} + +impl InformationField for NonSupportedCommandResponse { + fn information_type(&self) -> InformationType { + InformationType::NonSupportedCommandResponse + } + + async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + writer.write_all(&[self.command_type as u8 | (self.cr as u8)]).await?; + Ok(()) + } + + async fn read(reader: &mut R, len: usize) -> Result { + if len != 1 { + return Err(CmuxError::InvalidInformationField); + } + let command_type_cr = reader.read_u8().await?; + Ok(Self { + cr: (command_type_cr & 0x03).into(), + command_type: (command_type_cr & !0x03).try_into()?, + }) + } + + fn wire_len(&self) -> usize { + 1 + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RemotePortNegotiationCommand { + pub cr: CommandResponse, + // pub port_parameters: Vec, // Add port parameters as needed +} + +impl RemotePortNegotiationCommand { + pub fn new(cr: CommandResponse) -> Self { + Self { cr } + } +} + +impl InformationField for RemotePortNegotiationCommand { + fn information_type(&self) -> InformationType { + InformationType::RemotePortNegotiationCommand + } + + async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + // Write RPN-specific data here, including port parameters + Ok(()) + } + + async fn read(reader: &mut R, len: usize) -> Result { + if len == 0 { + return Err(CmuxError::InvalidInformationField); + } + // Read and parse RPN-specific data from 'reader' here + Ok(Self { + cr: CommandResponse::Command, + }) + } + + fn wire_len(&self) -> usize { + // Return the length of the serialized RPN data, + // including the length of serialized port parameters + 0 + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RemoteLineStatusCommand { + /// Command/Response flag for the frame. + pub cr: CommandResponse, + /// Data Link Connection Identifier (DLCI). + pub dlci: u8, + /// Remote line status information. + pub remote_line_status: RemoteLineStatus, +} + +impl RemoteLineStatusCommand { + pub fn new(cr: CommandResponse, dlci: u8, remote_line_status: RemoteLineStatus) -> Self { + Self { + cr, + dlci, + remote_line_status, + } + } +} + +impl InformationField for RemoteLineStatusCommand { + fn information_type(&self) -> InformationType { + InformationType::RemoteLineStatusCommand + } + + async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + writer.write_all(&[(self.dlci << 2) | (self.cr as u8)]).await?; + writer.write_all(&[self.remote_line_status.into_bits()]).await?; + Ok(()) + } + + async fn read(reader: &mut R, len: usize) -> Result { + if len != 2 { + return Err(CmuxError::InvalidInformationField); + } + let dlci_cr = reader.read_u8().await?; + Ok(Self { + cr: (dlci_cr & 0x03).into(), + dlci: dlci_cr >> 2, + remote_line_status: RemoteLineStatus::from_bits(reader.read_u8().await?), + }) + } + + fn wire_len(&self) -> usize { + 2 + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ServiceNegotiationCommand { + pub cr: CommandResponse, + // pub services: Vec, // Add service definitions as needed +} + +impl ServiceNegotiationCommand { + pub fn new(cr: CommandResponse) -> Self { + Self { cr } + } +} + +impl InformationField for ServiceNegotiationCommand { + fn information_type(&self) -> InformationType { + InformationType::ServiceNegotiationCommand + } + + async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + // Write SNC-specific data here, including service definitions + Ok(()) + } + + async fn read(reader: &mut R, len: usize) -> Result { + if len == 0 { + return Err(CmuxError::InvalidInformationField); + } + // Read and parse SNC-specific data from 'reader' here, + // including service parameters. + Ok(Self { + cr: CommandResponse::Command, + }) + } + + fn wire_len(&self) -> usize { + // Return the length of the serialized SNC data, + // including the length of any serialized service parameters. + 0 + } +} + +// Trait for a consistent interface if desired +trait InformationField { + fn information_type(&self) -> InformationType; + async fn write(&self, writer: &mut W) -> Result<(), CmuxError>; + async fn read(reader: &mut R, len: usize) -> Result + where + Self: Sized; + fn wire_len(&self) -> usize; +} + +// --- CMUX Frame Types --- + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FrameType { + /// Set Asynchronous Balanced Mode (SABM) command + Sabm = 0x2F, + /// Unnumbered Acknowledgement (UA) response + Ua = 0x63, + /// Disconnected mode (DM) + Dm = 0x0F, + /// Disconnect (DISC) + Disc = 0x43, + /// Unnumbered information with header check (UIH) command and response + Uih = 0xEF, + /// Unnumbered information (UI) command and response + Ui = 0x03, +} + +impl TryFrom for FrameType { + type Error = CmuxError; + fn try_from(value: u8) -> Result { + match value & !PF { + 0x2F => Ok(Self::Sabm), + 0x63 => Ok(Self::Ua), + 0x0F => Ok(Self::Dm), + 0x43 => Ok(Self::Disc), + 0xEF => Ok(Self::Uih), + 0x03 => Ok(Self::Ui), + // ... other FrameType matches ... + _ => Err(CmuxError::InvalidFrameFormat), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum Frame { + Sabm(SabmFrame), + Uih(UihFrame), + // ... other Frame variants, potentially with associated data ... +} + +impl Frame { + pub async fn read(reader: &mut R) -> Result { + // 1. Find FLAG (handling potential byte stuffing) + loop { + let buf = reader.fill_buf().await?; + if !buf.is_empty() && buf[0] == FLAG { + reader.consume(1); // Consume the FLAG byte + break; + } else if !buf.is_empty() { + reader.consume(1); // Consume the non-FLAG byte + } else { + // Handle potential EOF or buffer underrun (if applicable) + // You might want to return a WouldBlock error or similar + // depending on your requirements + return Err(CmuxError::Io(IoErrorKind::BrokenPipe)); + } + } + + // Helper function to read a single byte + async fn read_byte(reader: &mut R) -> Result { + let buf = reader.fill_buf().await?; + if !buf.is_empty() { + reader.consume(1); + Ok(buf[0]) + } else { + Err(CmuxError::Io(IoErrorKind::BrokenPipe)) + } + } + + // 2. Read address, control, and length (with EA handling) + let address_control = read_byte(reader).await?; + let frame_type_byte = read_byte(reader).await?; + let mut length = read_byte(reader).await? as usize; + if length as u8 & EA == 0 { + length = (length << 8) | read_byte(reader).await? as usize; + length >>= 1; // Shift out the EA bit + } else { + length >>= 1; // Shift out the EA bit + } + + // 3. Calculate CRC on the fly + let mut fcs = FCS_GENERATOR.digest(); + fcs.update(&[ + address_control, + frame_type_byte, + (length >> 8) as u8, + (length & 0xFF) as u8, + ]); + + let id = address_control >> 2; + let cr = CommandResponse::from(address_control); + + let frame_type = FrameType::try_from(frame_type_byte)?; + // 4. Parse based on FrameType + let frame = match frame_type { + FrameType::Sabm => { + if length != 0 { + return Err(CmuxError::InvalidInformationField); + } + Frame::Sabm(SabmFrame { + id, + pf: (frame_type_byte & PF).into(), + }) + } + FrameType::Uih => { + if length > MAX_INFO_LEN { + return Err(CmuxError::InvalidFrameFormat); + } + let mut info_bytes = [0u8; MAX_INFO_LEN]; + + reader.read_exact(&mut info_bytes[0..length]).await?; + fcs.update(&info_bytes[0..length]); + let info_type = InformationType::try_from(info_bytes[0])?; + let information: I = match info_type { + InformationType::ParameterNegotiation => { + ParameterNegotiation::read(&mut &info_bytes[1..length], length - 1).await? + } + InformationType::MultiplexerCloseDown => { + MultiplexerCloseDown::read(&mut &info_bytes[1..length], length - 1).await? + } + InformationType::FlowControlOffCommand => { + FlowControlOffCommand::read(&mut &info_bytes[1..length], length - 1).await? + } + InformationType::FlowControlOnCommand => { + FlowControlOnCommand::read(&mut &info_bytes[1..length], length - 1).await? + } + InformationType::ModemStatusCommand => { + ModemStatusCommand::read(&mut &info_bytes[1..length], length - 1).await? + } + InformationType::NonSupportedCommandResponse => { + NonSupportedCommandResponse::read(&mut &info_bytes[1..length], length - 1).await? + } + InformationType::RemotePortNegotiationCommand => { + RemotePortNegotiationCommand::read(&mut &info_bytes[1..length], length - 1).await? + } + InformationType::RemoteLineStatusCommand => { + RemoteLineStatusCommand::read(&mut &info_bytes[1..length], length - 1).await? + } + InformationType::ServiceNegotiationCommand => { + ServiceNegotiationCommand::read(&mut &info_bytes[1..length], length - 1).await? + } + _ => return Err(CmuxError::UnsupportedOperation), // Or handle as needed + }; + + Frame::Uih(UihFrame { + id, + information, + cr, + pf: (frame_type_byte & PF).into(), + }) + } + _ => return Err(CmuxError::UnsupportedOperation), // Or handle as needed }; - if let Some(info) = self.information() { - info.write(writer).await?; + // 6. Finalize and check FCS + let received_fcs = read_byte(reader).await?; + if received_fcs != fcs.finalize() { + return Err(CmuxError::CrcMismatch); } - writer - .write_all(&[fcs, FLAG]) - .await - .map_err(|e| Error::Write(e.kind()))?; + // 7. Read trailing FLAG + if read_byte(reader).await? != FLAG { + return Err(CmuxError::InvalidFrameFormat); + } - writer.flush().await.map_err(|e| Error::Write(e.kind()))?; + Ok(frame) + } + + pub async fn write(&self, writer: &mut W) -> Result<(), CmuxError> { + // 1. Write FLAG + writer.write_all(&[FLAG]).await?; + + match self { + Frame::Sabm(frame) => { + // 2. Write address and control + writer.write_all(&[(frame.id << 2) | (frame.pf as u8)]).await?; + + // 3. Write frame type + writer.write_all(&[FrameType::Sabm as u8 | (frame.pf as u8)]).await?; + + // 4. Write length (using EA if necessary) + // No information field for SABM + + // 5. Write information field + // No information field for SABM + + // 6. Calculate and write FCS + let fcs = 0xFF + - FCS_GENERATOR.checksum(&[ + (frame.id << 2) | (frame.pf as u8), + FrameType::Sabm as u8 | (frame.pf as u8), + (0 as u8) << 1 | EA, + ]); + writer.write_all(&[fcs]).await?; + + // 7. Write trailing FLAG + writer.write_all(&[FLAG]).await?; + } + Frame::Uih(frame) => { + // 2. Write address and control + writer.write_all(&[(frame.id << 2) | (frame.cr as u8)]).await?; + + // 3. Write frame type + writer.write_all(&[FrameType::Uih as u8 | (frame.pf as u8)]).await?; + + // 4. Write length (using EA if necessary) + let info_len = frame.information.wire_len(); + write_ea_length(writer, info_len).await?; + + // 5. Write information field + frame.information.write(writer).await?; + + // 6. Calculate and write FCS + let mut fcs_buf = [0u8; 255]; + let mut fcs_writer = &mut fcs_buf[..]; + fcs_writer.write_all(&[(frame.id << 2) | (frame.cr as u8)]).await?; + fcs_writer.write_all(&[FrameType::Uih as u8 | (frame.pf as u8)]).await?; + + let info_len = frame.information.wire_len(); + if info_len > 127 { + // Extended length + fcs_writer.write_all(&[((info_len >> 7) as u8) << 1]).await?; + } + fcs_writer.write_all(&[((info_len as u8) << 1) | EA]).await?; + frame.information.write(fcs_writer)?; + + let fcs = 0xFF - FCS_GENERATOR.checksum(&fcs_buf[0..fcs_writer.len()]); + writer.write_all(&[fcs]).await?; + + // 7. Write trailing FLAG + writer.write_all(&[FLAG]).await?; + } // Handle other frame types... + } Ok(()) } } -/// Set Asynchronous Balanced Mode (SABM) command -pub struct Sabm { +#[derive(Debug, PartialEq)] +pub struct SabmFrame { pub id: u8, + pub pf: PollFinal, // Assuming SABM uses PF } -impl Frame for Sabm { - const FRAME_TYPE: FrameType = FrameType::Sabm; - - fn cr(&self) -> u8 { - CR::Command as u8 - } - - fn pf(&self) -> u8 { - PF::Poll as u8 - } - - fn id(&self) -> u8 { - self.id +impl SabmFrame { + pub fn new(id: u8, pf: PollFinal) -> Self { + Self { id, pf } } } -/// Unnumbered information with header check (UIH) command and response -pub struct Uih<'d> { +// ... Implementations for other frame types: UihFrame, etc. ... + +#[derive(Debug, PartialEq)] +pub struct UihFrame { pub id: u8, - pub information: Information<'d>, + pub information: I, + pub cr: CommandResponse, + pub pf: PollFinal, } -impl<'d> Frame for Uih<'d> { - const FRAME_TYPE: FrameType = FrameType::Uih; - - fn cr(&self) -> u8 { - CR::Command as u8 - } - - fn id(&self) -> u8 { - self.id - } - - fn pf(&self) -> u8 { - PF::Final as u8 - } - - fn information(&self) -> Option<&Information> { - Some(&self.information) +impl UihFrame { + pub fn new(id: u8, information: I, cr: CommandResponse, pf: PollFinal) -> Self { + Self { + id, + information, + cr, + pf, + } } } - #[cfg(test)] mod tests { use super::*; + // --- Test Utilities --- + // (These structs and implementations are helpful for testing) + + struct TestReader { + data: Vec, + pos: usize, + } + + impl TestReader { + fn new(data: Vec) -> Self { + Self { data, pos: 0 } + } + } + + impl BufRead for TestReader { + async fn fill_buf(&mut self) -> Result<&[u8], IoError> { + if self.pos >= self.data.len() { + Ok(&[]) + } else { + Ok(&self.data[self.pos..]) + } + } + + fn consume(&mut self, amt: usize) { + self.pos += amt; + } + } + + struct TestWriter { + data: Vec, + } + + impl TestWriter { + fn new() -> Self { + Self { data: Vec::new() } + } + + fn into_inner(self) -> Vec { + self.data + } + } + + impl Write for TestWriter { + async fn write(&mut self, buf: &[u8]) -> Result { + self.data.extend_from_slice(buf); + Ok(buf.len()) + } + + async fn flush(&mut self) -> Result<(), IoError> { + Ok(()) + } + } + + // --- Unit Tests --- + + #[tokio::test] + async fn test_sabm_frame() { + let mut writer = TestWriter::new(); + let frame = Frame::Sabm(SabmFrame::new(2, PollFinal::Poll)); + + frame.write(&mut writer).await.unwrap(); + let written_data = writer.into_inner(); + + assert_eq!( + written_data, + vec![0xF9, 0x0C, 0x3F, 0x9B, 0xF9] // Expected byte sequence for SABM + ); + + // Now, test reading + let mut reader = TestReader::new(written_data); + let read_frame = Frame::read(&mut reader).await.unwrap(); + + assert_eq!(frame, read_frame); + } + + #[tokio::test] + async fn test_uih_frame_with_parameter_negotiation() { + let mut writer = TestWriter::new(); + let info_field = ParameterNegotiation::new(CommandResponse::Command); + let frame = Frame::Uih(UihFrame::new( + 1, + &info_field, + CommandResponse::Command, + PollFinal::Final, + )); + + frame.write(&mut writer).await.unwrap(); + let written_data = writer.into_inner(); + + // Expected byte sequence for UIH with empty PN + let expected_data = vec![0xF9, 0x05, 0xEF, 0x01, 0x3E, 0xF9]; + assert_eq!(written_data, expected_data); + + let mut reader = TestReader::new(written_data); + let read_frame = Frame::read(&mut reader).await.unwrap(); + + assert_eq!(frame, read_frame); + } + + #[tokio::test] + async fn test_uih_frame_with_modem_status_command() { + let mut writer = TestWriter::new(); + let info_field = ModemStatusCommand::new(CommandResponse::Command, 2, Control::new(), Some(Break::new())); + let frame = Frame::Uih(UihFrame::new( + 1, + &info_field, + CommandResponse::Command, + PollFinal::Final, + )); + + frame.write(&mut writer).await.unwrap(); + let written_data = writer.into_inner(); + + // Expected byte sequence for UIH with empty PN + let expected_data = vec![0xF9, 0x05, 0xEF, 0x05, 0x0c, 0x01, 0x01, 0x0a, 0xF9]; + assert_eq!(written_data, expected_data); + + let mut reader = TestReader::new(written_data); + let read_frame = Frame::read(&mut reader).await.unwrap(); + + assert_eq!(frame, read_frame); + } + + // ... Add more tests for different frame types and information fields ... #[test] fn read_ea_test() {