Skip to content
Snippets Groups Projects
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());
    }
}