ham-cats 0.3.1

Reference implementations for CATS, the ham radio protocol
Documentation
use core::str::FromStr;

use arrayvec::ArrayString;

use crate::identity::Identity;

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct AckData {
    number: u8,
    pub is_ack: bool,
}

impl AckData {
    /// Panics if the given ack number is invalid (i.e. not between 1-127, inclusive)
    pub fn new(number: u8, is_ack: bool) -> Self {
        Self::try_new(number, is_ack).expect("Invalid ack number given")
    }

    /// Returns `None` if the given ack number is invalid (i.e. not between 1-127, inclusive)
    pub fn try_new(number: u8, is_ack: bool) -> Option<Self> {
        if (1..=127).contains(&number) {
            Some(Self { number, is_ack })
        } else {
            None
        }
    }

    pub fn number(self) -> u8 {
        self.number
    }

    pub fn set_ack(&mut self) {
        self.is_ack = true;
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Destination {
    ack_data: Option<AckData>,
    callsign: ArrayString<253>,
    ssid: u8,
}

impl Destination {
    /// Returns none if the dest callsign is too long
    pub fn new(dest_callsign: &str, dest_ssid: u8, ack_data: Option<AckData>) -> Option<Self> {
        let callsign = ArrayString::from_str(dest_callsign).ok()?;
        let ssid = dest_ssid;

        Some(Self {
            ack_data,
            callsign,
            ssid,
        })
    }

    pub fn ack_data(&self) -> Option<AckData> {
        self.ack_data
    }

    pub fn callsign(&self) -> &str {
        self.callsign.as_str()
    }

    pub fn ssid(&self) -> u8 {
        self.ssid
    }

    pub fn as_identity(&self) -> Identity<'_> {
        Identity::new(&self.callsign, self.ssid)
    }

    pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
        let n = self.callsign.len() + 2;
        let ack = match self.ack_data {
            Some(ack_data) => {
                if ack_data.is_ack {
                    (1 << 7) | ack_data.number
                } else {
                    ack_data.number
                }
            }
            None => 0,
        };

        *buf.get_mut(0)? = n.try_into().unwrap();
        *buf.get_mut(1)? = ack;
        buf.get_mut(2..n)?.copy_from_slice(self.callsign.as_bytes());
        *buf.get_mut(n)? = self.ssid;

        Some(&buf[0..(n + 1)])
    }

    pub fn decode(data: &[u8]) -> Option<Self> {
        let n = data.first()?;
        let call_length: usize = n.checked_sub(2)?.into();

        let ack = *data.get(1)?;
        if ack == 0b10000000 {
            // is_ack is true and # is 0
            return None;
        }
        let ack_data = if ack == 0 {
            None
        } else {
            Some(AckData::new(ack & 0x7F, ack & 0x80 > 0))
        };

        let callsign = core::str::from_utf8(data.get(2..(call_length + 2))?).ok()?;
        let callsign = ArrayString::from_str(callsign).ok()?;

        let ssid = *data.get(call_length + 2)?;

        Some(Self {
            ack_data,
            callsign,
            ssid,
        })
    }
}