packet.rs 7.43 KiB
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());
}
}