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

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)))
    }
}

Stephen D's avatar
Stephen D committed
#[derive(Debug, PartialEq)]
Stephen D's avatar
wip
Stephen D committed
pub struct Gps {
    latitude: i32,
    longitude: i32,
Stephen D's avatar
Stephen D committed
    pub altitude: f16,
    pub max_error: u8,
Stephen D's avatar
wip
Stephen D committed
    heading: u8,
Stephen D's avatar
Stephen D committed
    pub speed: f16,
Stephen D's avatar
wip
Stephen D committed
}

impl Gps {
Stephen D's avatar
Stephen D committed
    /// Constructs a new GPS whisker.
    ///
    /// latitude: degrees
    /// longitude: degrees
    /// altitude: meters
    /// max_error: maximum error distance from specified lat/lon/alt, meters
    /// heading: direction relative to north, degrees
    /// speed: meters per second
    pub fn new(
        latitude: f64,
        longitude: f64,
        altitude: f16,
        max_error: u8,
        heading: f64,
        speed: f16,
    ) -> Self {
        let latitude = latitude.clamp(-89.999, 89.999);
        let latitude = (latitude * ((1u32 << 31) as f64) / 90.0) as i32;

        let longitude = longitude.clamp(-179.999, 179.999);
        let longitude = (longitude * ((1u32 << 31) as f64) / 180.0) as i32;

        let heading = heading.clamp(-359.999, 359.999);
        let heading = (heading * 128.0 / 360.0) as u8;

        Self {
            latitude,
            longitude,
            altitude,
            max_error,
            heading,
            speed,
        }
    }

    pub fn latitude(&self) -> f64 {
        (self.latitude as f64) / (1u32 << 31) as f64 * 90.0
    }

    pub fn longitude(&self) -> f64 {
        (self.longitude as f64) / (1u32 << 31) as f64 * 180.0
    }

    pub fn heading(&self) -> f64 {
        self.heading as f64 / 128.0 * 360.0
    }

    pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
        let buf = buf.get_mut(0..15)?;
        buf[0] = 14;
        buf[1..5].copy_from_slice(&self.latitude.to_le_bytes());
        buf[5..9].copy_from_slice(&self.longitude.to_le_bytes());
        buf[9..11].copy_from_slice(&self.altitude.to_le_bytes());
        buf[11] = self.max_error;
        buf[12] = self.heading;
        buf[13..15].copy_from_slice(&self.speed.to_le_bytes());

        Some(buf)
    }

    pub fn decode(data: &[u8]) -> Option<Self> {
        let data = data.get(0..15)?;
        if data[0] != 14 {
            return None;
        }
        let latitude = i32::from_le_bytes(data[1..5].try_into().unwrap());
        let longitude = i32::from_le_bytes(data[5..9].try_into().unwrap());
        let altitude = f16::from_le_bytes(data[9..11].try_into().unwrap());
        let max_error = data[11];
        let heading = data[12];
        let speed = f16::from_le_bytes(data[13..15].try_into().unwrap());

        Some(Self {
            latitude,
            longitude,
            altitude,
            max_error,
            heading,
            speed,
        })
    }
Stephen D's avatar
wip
Stephen D committed
}

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

Stephen D's avatar
Stephen D committed
impl Comment {
    pub fn new(data: &[u8]) -> Option<Self> {
        let mut av = ArrayVec::new();
        av.try_extend_from_slice(data).ok()?;

        Some(Self(av))
    }

    pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
        let len = self.0.len();

        // length must be <= 255, so this is safe
        *buf.get_mut(0)? = len.try_into().unwrap();
        buf.get_mut(1..(len + 1))?.copy_from_slice(&self.0);

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

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

        Self::new(content)
    }
}
Stephen D's avatar
Stephen D committed

Stephen D's avatar
wip
Stephen D committed
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());
    }
Stephen D's avatar
Stephen D committed

    #[test]
    fn new_gps() {
        let gps = Gps::new(
            90.0,
            -180.0,
            f16::from_f32(45.02),
            24,
            123.45,
            f16::from_f32(12.3),
        );

        assert_eq!(89.99899999704212, gps.latitude());
        assert_eq!(-179.9989999551326, gps.longitude());
        assert_eq!(120.9375, gps.heading());
    }

    #[test]
    fn gps_e2e() {
        let gps = Gps::new(
            90.0,
            -180.0,
            f16::from_f32(45.02),
            24,
            123.45,
            f16::from_f32(12.3),
        );

        let mut buf = [0; 256];
        let encoded = gps.encode(&mut buf).unwrap();
        let decoded = Gps::decode(encoded).unwrap();
        assert_eq!(gps, decoded);
    }
Stephen D's avatar
Stephen D committed

    #[test]
    fn comment_e2e() {
        let data = b"Hello world! This is an example comment";
        let comment = Comment::new(data).unwrap();
        let mut buf = [0; 256];
        let encoded = comment.encode(&mut buf).unwrap();
        let decoded = Comment::decode(encoded).unwrap();
        let decoded2 = Comment::decode(&buf).unwrap();
        assert_eq!(data[..], decoded.0[..]);
        assert_eq!(data[..], decoded2.0[..]);
    }
Stephen D's avatar
wip
Stephen D committed
}