anpp 2.0.0

A Rust implementation of the Advanced Navigation Packet Protocol
Documentation
use crate::{
    errors::{DecodeError, InsufficientCapacity},
    packet::{Header, Packet},
    utils,
};
#[cfg(not(feature = "std"))]
use arrayvec::ArrayVec;
use core::ops::Range;
#[cfg(feature = "std")]
use std::io::{self, Write};

/// Decoder for the *Advanced Navigation Packet Protocol*.
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Decoder {
    #[cfg(feature = "std")]
    buffer: Vec<u8>,
    #[cfg(not(feature = "std"))]
    buffer: ArrayVec<[u8; Decoder::DEFAULT_DECODER_BUFFER_SIZE]>,
}

impl Decoder {
    /// The buffer size used when not compiled with the `std` feature flag.
    pub const DEFAULT_DECODER_BUFFER_SIZE: usize = 512;

    /// Create a new `Decoder`.
    pub fn new() -> Decoder { Decoder::default() }

    /// Create a new `Decoder` and make sure its internal buffer is
    /// pre-allocated with at least `capacity` bytes.
    #[cfg(feature = "std")]
    pub fn with_capacity(capacity: usize) -> Decoder {
        Decoder {
            buffer: Vec::with_capacity(capacity),
        }
    }

    /// How many bytes are in the `Decoder`'s internal buffer?
    pub fn bytes_in_buffer(&self) -> usize { self.buffer.len() }

    /// The amount of data that can be added to the `Decoder`'s internal buffer
    /// before it will become full.
    ///
    /// # Note
    ///
    /// When compiled with the `std` feature the buffer is considered
    /// effectively infinite.
    #[inline]
    pub fn remaining_capacity(&self) -> usize { self._remaining_capacity() }

    #[cfg(feature = "std")]
    fn _remaining_capacity(&self) -> usize {
        // Assume the decoder's buffer is effectively infinite
        usize::max_value()
    }

    #[cfg(not(feature = "std"))]
    fn _remaining_capacity(&self) -> usize {
        self.buffer.capacity() - self.buffer.len()
    }

    /// Clear the `Decoder`'s internal buffer.
    pub fn clear(&mut self) { self.buffer.clear(); }

    /// Try to add some more raw data to the `Decoder`'s internal buffer.
    pub fn push_data(
        &mut self,
        data: &[u8],
    ) -> Result<(), InsufficientCapacity> {
        self._push_data(data)
    }

    #[cfg(feature = "std")]
    fn _push_data(&mut self, data: &[u8]) -> Result<(), InsufficientCapacity> {
        self.buffer.extend_from_slice(data);
        Ok(())
    }

    #[cfg(not(feature = "std"))]
    fn _push_data(&mut self, data: &[u8]) -> Result<(), InsufficientCapacity> {
        if self.remaining_capacity() < data.len() {
            return Err(InsufficientCapacity {
                required: data.len(),
                actual: self.remaining_capacity(),
            });
        }

        self.buffer.extend(data.iter().cloned());
        Ok(())
    }

    /// Retrieve the next `Packet` from the `Decoder`'s internal buffer.
    pub fn decode(&mut self) -> Result<Packet, DecodeError> {
        let pkt = find_potential_packet(&self.buffer)?;

        let result = if pkt.crc_is_valid(&self.buffer) {
            Ok(Packet::with_data(pkt.header.id, pkt.slice(&self.buffer))
                .expect("Already validated"))
        } else {
            Err(DecodeError::InvalidCRC)
        };

        self.remove(pkt.contents.end);

        result
    }

    fn remove(&mut self, amount: usize) { let _ = self.buffer.drain(..amount); }
}

#[cfg(feature = "std")]
impl Write for Decoder {
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        match self.push_data(data) {
            Ok(_) => Ok(data.len()),
            Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
        }
    }

    fn flush(&mut self) -> io::Result<()> { Ok(()) }
}

fn valid_header_locations<'a>(
    data: &'a [u8],
) -> impl Iterator<Item = usize> + 'a {
    data.windows(Header::LEN)
        .enumerate()
        .filter(|&(_, window)| Header::is_valid(window))
        .map(|(i, _window)| i)
}

fn find_potential_packet(
    buffer: &[u8],
) -> Result<PotentialPacket, DecodeError> {
    let header_ix = valid_header_locations(buffer)
        .next()
        .ok_or(DecodeError::RequiresMoreData)?;

    let header =
        Header::from_bytes(&buffer[header_ix..]).expect("Already verified");

    let content_start = header_ix + Header::LEN;
    let content_end = content_start + header.len as usize;

    if content_end > buffer.len() {
        Err(DecodeError::RequiresMoreData)
    } else {
        Ok(PotentialPacket {
            header,
            contents: content_start..content_end,
        })
    }
}

#[derive(Debug, Clone, PartialEq)]
struct PotentialPacket {
    header: Header,
    contents: Range<usize>,
}

impl PotentialPacket {
    fn crc_is_valid(&self, original_buffer: &[u8]) -> bool {
        utils::calculate_crc16(self.slice(original_buffer)) == self.header.crc
    }

    fn slice<'a>(&self, buffer: &'a [u8]) -> &'a [u8] {
        &buffer[self.contents.clone()]
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::prelude::v1::*;

    #[test]
    fn fill_the_decoder_buffer() {
        let mut decoder = Decoder::new();
        assert_eq!(decoder.bytes_in_buffer(), 0);

        let random_data = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

        decoder.push_data(random_data).unwrap();

        assert_eq!(decoder.bytes_in_buffer(), random_data.len());
        assert_eq!(&decoder.buffer[..], random_data);

        if cfg!(not(feature = "std")) {
            let got = decoder.remaining_capacity();
            let should_be =
                Decoder::DEFAULT_DECODER_BUFFER_SIZE - random_data.len();
            assert_eq!(got, should_be);
        }
    }

    #[test]
    #[cfg(not(feature = "std"))]
    fn overflow_the_buffer() {
        let mut decoder = Decoder::new();

        let data = [0; Decoder::DEFAULT_DECODER_BUFFER_SIZE + 1];
        let should_be = InsufficientCapacity {
            required: data.len(),
            actual: Decoder::DEFAULT_DECODER_BUFFER_SIZE,
        };

        let err = decoder.push_data(&data).unwrap_err();
        assert_eq!(err, should_be);
    }

    fn write_packet_to_decoder(decoder: &mut Decoder, pkt: &Packet) {
        let mut buffer = vec![0; pkt.total_length()];
        pkt.write_to_buffer(&mut buffer).unwrap();
        decoder.push_data(&buffer).unwrap();
    }

    #[test]
    fn round_trip_a_packet() {
        let mut decoder = Decoder::new();
        let pkt =
            Packet::with_data(42, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap();

        write_packet_to_decoder(&mut decoder, &pkt);

        let got = decoder.decode().unwrap();

        assert_eq!(got, pkt);
        assert_eq!(decoder.bytes_in_buffer(), 0);
    }

    fn generate_garbage(size: usize) -> Vec<u8> {
        let mut buffer = Vec::with_capacity(size);
        let mut counter: usize = 123;

        while buffer.len() < size {
            let byte = counter * counter % 256 + buffer.len();
            counter += byte;
            buffer.push(byte as u8);
        }

        buffer
    }

    #[test]
    fn find_all_valid_headers() {
        let should_be = vec![0, 25, 73, 188, 222];

        // Try to generate a bunch of garbage bytes interspersed with valid
        // packets
        let mut buffer = generate_garbage(512);
        for &ix in &should_be {
            let body_length = ix % 15 + 1;
            let body = generate_garbage(body_length);
            let pkt = Packet::with_data(ix as u8, &body).unwrap();
            pkt.write_to_buffer(&mut buffer[ix..]).unwrap();
        }

        // then make sure we can find them all again
        let got: Vec<usize> = valid_header_locations(&buffer).collect();
        assert_eq!(got, should_be);
    }

    #[test]
    fn find_a_potential_packet() {
        let start = 123;
        let pkt = Packet::with_data(42, &[1, 2, 3, 4, 5, 6]).unwrap();
        let mut buffer = generate_garbage(512);
        pkt.write_to_buffer(&mut buffer[start..]).unwrap();

        let should_be = PotentialPacket {
            header: pkt.header(),
            contents: start + Header::LEN..start + Header::LEN + pkt.len(),
        };

        let got = find_potential_packet(&buffer).unwrap();
        assert_eq!(got, should_be);
    }
}