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