use core::fmt::Debug; use arrayvec::{ArrayVec, CapacityError}; use crc::{Crc, CRC_16_IBM_SDLC}; use crate::{ error::{CommentError, DecodeError, EncodeError}, utf8, whisker::{ Arbitrary, Comment, Destination, Gps, Identification, Route, Timestamp, ValidatedWhiskerIter, Whisker, WhiskerIter, COMMENT_TYPE, }, }; const X25: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_SDLC); macro_rules! uniq_whisker { ($t:meta) => { ::paste::paste! { pub fn [<$t:lower>](&self) -> Option<$t> { self.iter().find_map(|w| match w { Whisker::$t(x) => Some(x), _ => None, }) } pub fn [<add_ $t:lower>](&mut self, w: $t) -> Result<(), EncodeError> { if self.[<$t:lower>]().is_some() { return Err(EncodeError::DuplicateData); } try_lock(&mut self.data, |data| { let mut buf = [0; 256]; // safe to unwrap since we know we have enough space let out = w.encode(&mut buf).unwrap(); data.try_push(crate::whisker::[<$t:upper _TYPE>]).ok().ok_or(EncodeError::CatsOverflow)?; data.try_extend_from_slice(out).ok().ok_or(EncodeError::CatsOverflow)?; Ok(()) })?; Ok(()) } } }; } /// N is the maximum packet size we can handle /// Panics if N >= 8191 #[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> { assert!(N <= 8191); // validate the data for w in WhiskerIter::new(&data) { w?; } let comment_iter = WhiskerIter::new(&data) .filter_map(|w| match w.unwrap() { Whisker::Comment(c) => Some(c.internal_data().clone()), _ => None, }) .flatten(); if !utf8::validate(comment_iter) { return Err(DecodeError::InvalidComment); } 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) } uniq_whisker!(Identification); uniq_whisker!(Timestamp); uniq_whisker!(Gps); uniq_whisker!(Route); // TODO dest, arbitrary pub fn comment<'a>(&self, buf: &'a mut [u8]) -> Result<&'a str, CommentError> { let iter = self.iter().filter_map(|w| match w { Whisker::Comment(c) => Some(c), _ => None, }); let mut i = 0; let mut has_comment = false; for c in iter { has_comment = true; let data = c.internal_data(); buf.get_mut(i..(i + data.len())) .ok_or(CommentError::BufferOverflow)? .copy_from_slice(data); i += data.len(); } if !has_comment { return Err(CommentError::NoComment); } // Safe to unwrap since the comment was pre-validated Ok(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()); } }