use core::fmt::Debug; use arrayvec::{ArrayVec, CapacityError}; use crc::{Crc, CRC_16_IBM_SDLC}; use crate::{ error::{DecodeError, EncodeError}, whisker::{ Comment, Identification, ValidatedWhiskerIter, Whisker, WhiskerIter, COMMENT_TYPE, IDENT_TYPE, }, }; const X25: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_SDLC); // N is the maximum packet size we can handle // TODO limit this to 8191 at compile time #[derive(Default, Clone)] pub struct Packet<const N: usize> { data: ArrayVec<u8, N>, } // TODO need to have methods for removing whiskers from the packet impl<const N: usize> Packet<N> { pub fn decode(data: ArrayVec<u8, N>) -> Result<Self, DecodeError> { // validate the data // TODO validate comment whiskers for w in WhiskerIter::new(&data) { w?; } Ok(Self { data }) } pub fn encode(&self) -> &[u8] { &self.data } /// Directly after the CRC block in The Pipeline pub fn semi_encode(mut self) -> Result<ArrayVec<u8, N>, (EncodeError, Self)> { let crc = X25.checksum(&self.data).to_le_bytes(); let res: Result<(), CapacityError<u8>> = try_lock(&mut self.data, |data| { data.try_push(crc[0])?; data.try_push(crc[1])?; Ok(()) }); match res { Ok(()) => Ok(self.data), Err(_) => Err((EncodeError::CatsOverflow, self)), } } /// Directly after the CRC block in The Pipeline pub fn semi_decode(mut data: ArrayVec<u8, N>) -> Result<Self, DecodeError> { let crc1 = data.pop().ok_or(DecodeError::UnexpectedEndOfInput)?; let crc0 = data.pop().ok_or(DecodeError::UnexpectedEndOfInput)?; let crc_expected = u16::from_le_bytes([crc0, crc1]); let crc_actual = X25.checksum(&data); if crc_expected != crc_actual { return Err(DecodeError::CrcMismatch); } Self::decode(data) } pub fn iter(&self) -> ValidatedWhiskerIter { ValidatedWhiskerIter::new(&self.data) } pub fn identification(&self) -> Option<Identification> { self.iter().find_map(|w| match w { Whisker::Identification(x) => Some(x), _ => None, }) } pub fn add_identification( &mut self, identification: Identification, ) -> Result<(), EncodeError> { if self.identification().is_some() { return Err(EncodeError::DuplicateData); } let mut buf = [0; 256]; // safe to unwrap since we know we have enough space let out = identification.encode(&mut buf).unwrap(); try_lock(&mut self.data, |data| { data.try_push(IDENT_TYPE) .map_err(|_| EncodeError::CatsOverflow)?; data.try_extend_from_slice(out) .map_err(|_| EncodeError::CatsOverflow)?; Ok(()) })?; Ok(()) } /// Returns None if given buffer is not big enough pub fn comment<'a>(&self, buf: &'a mut [u8]) -> Option<&'a str> { let iter = self.iter().filter_map(|w| match w { Whisker::Comment(c) => Some(c), _ => None, }); let mut i = 0; for c in iter { let data = c.internal_data(); buf.get_mut(i..(i + data.len()))?.copy_from_slice(data); i += data.len(); } // Safe to unwrap since the comment was pre-validated (TODO) Some(core::str::from_utf8(&buf[0..i]).unwrap()) } pub fn add_comment(&mut self, comment: &str) -> Result<(), EncodeError> { if self.iter().any(|w| matches!(w, Whisker::Comment(_))) { // we already have a comment return Err(EncodeError::DuplicateData); } try_lock(&mut self.data, |data| { let mut comment = comment.as_bytes(); while comment.len() > 255 { let c = Comment::new(&comment[0..255]).unwrap(); let mut buf = [0; 256]; let out = c.encode(&mut buf).unwrap(); data.try_push(COMMENT_TYPE) .map_err(|_| EncodeError::CatsOverflow)?; data.try_extend_from_slice(out) .map_err(|_| EncodeError::CatsOverflow)?; comment = &comment[255..]; } let c = Comment::new(comment).unwrap(); let mut buf = [0; 256]; let out = c.encode(&mut buf).unwrap(); data.try_push(COMMENT_TYPE) .map_err(|_| EncodeError::CatsOverflow)?; data.try_extend_from_slice(out) .map_err(|_| EncodeError::CatsOverflow)?; Ok(()) })?; Ok(()) } } impl<const N: usize> Debug for Packet<N> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_list() .entries(ValidatedWhiskerIter::new(&self.data)) .finish() } } // if the function returns an error, we roll back the array // by rolling it back, we really just pop bytes off the end until the length matches fn try_lock<const N: usize, T, E, F: Fn(&mut ArrayVec<u8, N>) -> Result<T, E>>( arr: &mut ArrayVec<u8, N>, f: F, ) -> Result<T, E> { let len = arr.len(); match f(arr) { Ok(x) => Ok(x), Err(e) => { arr.truncate(len); Err(e) } } } #[cfg(test)] mod tests { use arrayvec::ArrayString; use super::*; #[test] fn semi_e2e() { let comment = "Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker."; let mut packet = Packet::<2048>::default(); packet .add_identification(Identification { icon: 123, callsign: ArrayString::from("ABCXYZ_LONG_CALL").unwrap(), ssid: 43, }) .unwrap(); packet.add_comment(comment).unwrap(); let res = packet.add_identification(Identification { icon: 456, callsign: ArrayString::from("NOPE").unwrap(), ssid: 0, }); assert!(matches!(res, Err(EncodeError::DuplicateData))); let semi = packet.semi_encode().unwrap(); let packet2 = Packet::semi_decode(semi).unwrap(); assert_eq!( Identification { icon: 123, callsign: ArrayString::from("ABCXYZ_LONG_CALL").unwrap(), ssid: 43, }, packet2.identification().unwrap() ); let mut buf = [0; 1024]; assert_eq!(comment, packet2.comment(&mut buf).unwrap()); } }