Skip to content
Snippets Groups Projects
whisker.rs 3.73 KiB
Newer Older
Stephen D's avatar
wip
Stephen D committed
use arrayvec::{ArrayString, ArrayVec};

pub enum Whisker {
    Identification(Identification),
    Timestamp(Timestamp),
    Gps(Gps),
    Comment(Comment),
    Route(Route),
    Destination(Destination),
}

#[derive(Debug, PartialEq, Eq)]
pub struct Identification {
    pub callsign: ArrayString<254>,
    pub ssid: u8,
}

impl Identification {
    pub fn new(call: &str, ssid: u8) -> Option<Self> {
        let callsign = ArrayString::from(call).ok()?;

        Some(Self { callsign, ssid })
    }

    /// Returns none if there is not enough space in the buffer
    pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
        // safe to unwrap because we know the length is <= 254
        let len: u8 = self.callsign.as_bytes().len().try_into().unwrap();
        *buf.get_mut(0)? = len + 1;

        let mut len = 1;
        for (i, b) in self.callsign.as_bytes().iter().enumerate() {
            *buf.get_mut(i + 1)? = *b;
            len += 1
        }

        *buf.get_mut(len)? = self.ssid;
        len += 1;

        Some(&buf[0..len])
    }

    pub fn decode(data: &[u8]) -> Option<Self> {
        let len = (*data.first()?).into();

        let callsign = core::str::from_utf8(data.get(1..len)?).ok()?;
        let callsign = ArrayString::from(callsign).ok()?;
        let ssid = *data.get(len)?;

        Some(Self { callsign, ssid })
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct Timestamp(u64);

impl Timestamp {
    /// Returns none if the given unix timestamp doesn't fit in 5 bytes
    pub fn new(unix_time: u64) -> Option<Self> {
        // must fit in 5 bytes
        if unix_time > (1 << (5 * 8)) - 1 {
            return None;
        }

        Some(Self(unix_time))
    }

    pub fn unix_time(&self) -> u64 {
        self.0
    }

    /// Returns none if there is not enough space in the buffer
    pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
        if buf.len() < 6 {
            return None;
        }

        buf[0] = 5;
        buf[1..6].copy_from_slice(&self.0.to_le_bytes()[0..5]);

        Some(&buf[0..6])
    }

    pub fn decode(data: &[u8]) -> Option<Self> {
        if data.len() < 6 {
            return None;
        }
        if data[0] != 5 {
            return None;
        }

        let mut extended_data = [0; 8];
        extended_data[0..5].copy_from_slice(&data[1..6]);

        Some(Self(u64::from_le_bytes(extended_data)))
    }
}

pub struct Gps {
    latitude: i32,
    longitude: i32,
    altitude: u16,
    max_error: u8,
    heading: u8,
    speed: u16,
}

impl Gps {
    //
}

pub struct Comment(ArrayVec<u8, 255>);

pub struct Route {
    max_hops: u8,
    path: ArrayVec<u8, 255>,
}

pub struct Destination {
    ack: u8,
    callsign: ArrayString<255>,
    ssid: u8,
}

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

    #[test]
    fn ident_e2e() {
        let call = "VE9ABCDEFGZZ4839-???";
        let ssid = 17;

        let ident = Identification::new(call, ssid).unwrap();
        let mut buf = [0; 256];

        let encoded = ident.encode(&mut buf).unwrap();
        let decoded = Identification::decode(encoded).unwrap();
        assert_eq!(ident, decoded);
        assert_eq!(call, &decoded.callsign);
        assert_eq!(ssid, decoded.ssid);
    }

    #[test]
    fn new_timestamp() {
        let t = 34894;
        assert_eq!(t, Timestamp::new(t).unwrap().unix_time());

        let t = 1 << (5 * 8);
        assert_eq!(None, Timestamp::new(t));
    }

    #[test]
    fn timestamp_e2e() {
        let t = 34894;
        let timestamp = Timestamp::new(t).unwrap();

        let mut buf = [0; 256];
        let encoded = timestamp.encode(&mut buf).unwrap();
        let decoded = Timestamp::decode(encoded).unwrap();
        assert_eq!(timestamp, decoded);
        assert_eq!(t, timestamp.unix_time());
    }
}