prns-interfaces-embassy 0.3.4

Embassy embedded interface implementations for the Personal Reticulum engine
Documentation
use prns_core::interfaces::lora::LORA_MAX_PAYLOAD;

const PACKET_LENGTH_BYTES: usize = size_of::<u16>();
const MAX_RECORD_BYTES: usize = PACKET_LENGTH_BYTES + LORA_MAX_PAYLOAD;

#[derive(Debug, PartialEq, Eq)]
pub(super) enum TransmitQueueError {
    Full,
    PacketTooLarge,
}

pub(super) struct TransmitQueue<'a> {
    storage: &'a mut [u8],
    head: usize,
    used: usize,
}

impl<'a> TransmitQueue<'a> {
    pub(super) fn new(storage: &'a mut [u8]) -> Self {
        Self {
            storage,
            head: 0,
            used: 0,
        }
    }

    pub(super) const fn is_empty(&self) -> bool {
        self.used == 0
    }

    pub(super) const fn can_push_max_packet(&self) -> bool {
        self.storage.len().saturating_sub(self.used) >= MAX_RECORD_BYTES
    }

    pub(super) fn push(&mut self, packet: &[u8]) -> Result<(), TransmitQueueError> {
        if packet.len() > LORA_MAX_PAYLOAD {
            return Err(TransmitQueueError::PacketTooLarge);
        }
        let record_bytes = PACKET_LENGTH_BYTES + packet.len();
        if record_bytes > self.storage.len().saturating_sub(self.used) {
            return Err(TransmitQueueError::Full);
        }

        let write = (self.head + self.used) % self.storage.len();
        let packet_length = u16::try_from(packet.len())
            .map_err(|_| TransmitQueueError::PacketTooLarge)?
            .to_le_bytes();
        Self::copy_into_ring(self.storage, write, &packet_length);
        Self::copy_into_ring(
            self.storage,
            (write + PACKET_LENGTH_BYTES) % self.storage.len(),
            packet,
        );
        self.used += record_bytes;
        Ok(())
    }

    pub(super) fn pop(&mut self, packet: &mut [u8; LORA_MAX_PAYLOAD]) -> Option<usize> {
        if self.is_empty() {
            return None;
        }

        let mut encoded_length = [0u8; PACKET_LENGTH_BYTES];
        Self::copy_from_ring(self.storage, self.head, &mut encoded_length);
        let packet_length = usize::from(u16::from_le_bytes(encoded_length));
        let record_bytes = PACKET_LENGTH_BYTES + packet_length;
        assert!(packet_length <= LORA_MAX_PAYLOAD);
        assert!(record_bytes <= self.used);
        Self::copy_from_ring(
            self.storage,
            (self.head + PACKET_LENGTH_BYTES) % self.storage.len(),
            &mut packet[..packet_length],
        );
        self.head = (self.head + record_bytes) % self.storage.len();
        self.used -= record_bytes;
        Some(packet_length)
    }

    fn copy_into_ring(storage: &mut [u8], start: usize, bytes: &[u8]) {
        let first_len = bytes.len().min(storage.len() - start);
        storage[start..start + first_len].copy_from_slice(&bytes[..first_len]);
        storage[..bytes.len() - first_len].copy_from_slice(&bytes[first_len..]);
    }

    fn copy_from_ring(storage: &[u8], start: usize, bytes: &mut [u8]) {
        let bytes_len = bytes.len();
        let first_len = bytes_len.min(storage.len() - start);
        bytes[..first_len].copy_from_slice(&storage[start..start + first_len]);
        bytes[first_len..].copy_from_slice(&storage[..bytes_len - first_len]);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::lora::LORA_TX_QUEUE_BYTES;

    fn storage() -> [u8; LORA_TX_QUEUE_BYTES] {
        [0; LORA_TX_QUEUE_BYTES]
    }

    fn packet(length: usize, value: u8) -> std::vec::Vec<u8> {
        std::vec![value; length]
    }

    #[test]
    fn caller_sized_storage_sets_the_bounded_capacity() {
        let mut storage = [0; MAX_RECORD_BYTES];
        let mut queue = TransmitQueue::new(&mut storage);
        let packet = packet(LORA_MAX_PAYLOAD, 0xA5);

        assert!(queue.can_push_max_packet());
        queue.push(&packet).unwrap();
        assert!(!queue.can_push_max_packet());
        assert_eq!(queue.push(&[0x01]), Err(TransmitQueueError::Full));

        let mut output = [0; LORA_MAX_PAYLOAD];
        assert_eq!(queue.pop(&mut output), Some(LORA_MAX_PAYLOAD));
        assert_eq!(&output[..], packet.as_slice());
        assert!(queue.can_push_max_packet());
    }

    #[test]
    fn packets_leave_in_insertion_order_across_mixed_sizes() {
        let mut storage = storage();
        let mut queue = TransmitQueue::new(&mut storage);
        let expected = [packet(1, 1), packet(250, 2), packet(508, 3), packet(17, 4)];

        for packet in &expected {
            queue.push(packet).unwrap();
        }

        let mut output = [0; LORA_MAX_PAYLOAD];
        for packet in expected {
            let length = queue.pop(&mut output).unwrap();
            assert_eq!(&output[..length], packet);
        }
        assert!(queue.pop(&mut output).is_none());
    }

    #[test]
    fn packet_and_length_prefix_wrap_across_the_storage_end() {
        let mut storage = storage();
        let mut queue = TransmitQueue::new(&mut storage);
        for value in 0..12 {
            queue.push(&packet(LORA_MAX_PAYLOAD, value)).unwrap();
        }
        queue.push(&packet(21, 12)).unwrap();

        let mut output = [0; LORA_MAX_PAYLOAD];
        for _ in 0..13 {
            queue.pop(&mut output).unwrap();
        }
        assert_eq!(queue.head, LORA_TX_QUEUE_BYTES - 1);

        let wrapped = packet(508, 0xA5);
        queue.push(&wrapped).unwrap();
        assert_eq!(queue.storage[LORA_TX_QUEUE_BYTES - 1], 0xFC);
        assert_eq!(queue.storage[0], 0x01);
        let length = queue.pop(&mut output).unwrap();
        assert_eq!(&output[..length], wrapped);
    }

    #[test]
    fn exact_capacity_is_usable_and_one_more_record_is_rejected_without_mutation() {
        let mut storage = storage();
        let mut queue = TransmitQueue::new(&mut storage);
        for value in 0..12 {
            queue.push(&packet(LORA_MAX_PAYLOAD, value)).unwrap();
        }
        queue.push(&packet(22, 12)).unwrap();
        assert_eq!(queue.used, LORA_TX_QUEUE_BYTES);
        let before = queue.storage.to_vec();
        let before_head = queue.head;
        let before_used = queue.used;

        assert_eq!(queue.push(&[0xFF]), Err(TransmitQueueError::Full));
        assert_eq!(queue.storage, before.as_slice());
        assert_eq!((queue.head, queue.used), (before_head, before_used));
    }

    #[test]
    fn dequeue_reuses_capacity_after_wraparound() {
        let mut storage = storage();
        let mut queue = TransmitQueue::new(&mut storage);
        let mut output = [0; LORA_MAX_PAYLOAD];

        for value in 0..12 {
            queue.push(&packet(LORA_MAX_PAYLOAD, value)).unwrap();
        }
        for value in 0..6 {
            let length = queue.pop(&mut output).unwrap();
            assert_eq!(&output[..length], packet(LORA_MAX_PAYLOAD, value));
        }
        for value in 12..18 {
            queue.push(&packet(LORA_MAX_PAYLOAD, value)).unwrap();
        }
        for value in 6..18 {
            let length = queue.pop(&mut output).unwrap();
            assert_eq!(&output[..length], packet(LORA_MAX_PAYLOAD, value));
        }
        assert!(queue.is_empty());
    }

    #[test]
    fn twelve_maximum_packets_fit_but_thirteen_do_not() {
        let mut storage = storage();
        let mut queue = TransmitQueue::new(&mut storage);
        let maximum = [0x5A; LORA_MAX_PAYLOAD];

        for _ in 0..12 {
            queue.push(&maximum).unwrap();
        }
        assert_eq!(queue.push(&maximum), Err(TransmitQueueError::Full));
        assert!(!queue.can_push_max_packet());

        let mut output = [0; LORA_MAX_PAYLOAD];
        assert_eq!(queue.pop(&mut output), Some(LORA_MAX_PAYLOAD));
        assert!(queue.can_push_max_packet());
        assert_eq!(queue.push(&maximum), Ok(()));
    }

    #[test]
    fn twenty_four_250_byte_packets_fit() {
        let mut storage = storage();
        let mut queue = TransmitQueue::new(&mut storage);
        let packet = [0xA5; 250];

        for _ in 0..24 {
            assert_eq!(queue.push(&packet), Ok(()));
        }
        assert_eq!(queue.used, 24 * (PACKET_LENGTH_BYTES + packet.len()));
    }
}