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