Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • cats/ham-cats
  • sam/ham-cats
  • Quantum_P/ham-cats
  • Reed/ham-cats
4 results
Show changes
use arrayvec::ArrayVec;
#[derive(Debug, Clone)]
pub struct Arbitrary(pub ArrayVec<u8, 255>);
impl Arbitrary {
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)
}
}
use arrayvec::ArrayVec;
#[derive(Debug)]
pub struct Comment(ArrayVec<u8, 255>);
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 internal_data(&self) -> &ArrayVec<u8, 255> {
&self.0
}
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)
}
}
use core::str::FromStr;
use arrayvec::ArrayString;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Destination {
ack: u8,
callsign: ArrayString<253>,
ssid: u8,
}
impl Destination {
/// Returns none if the ack number is > 127
/// Returns none if the dest callsign is too long
/// Returns none is is_ack is true and ack_num is 0
pub fn new(is_ack: bool, ack_num: u8, dest_callsign: &str, dest_ssid: u8) -> Option<Self> {
if ack_num > 127 {
return None;
}
if is_ack && ack_num == 0 {
return None;
}
let ack = if is_ack { (1 << 7) | ack_num } else { ack_num };
let callsign = ArrayString::from_str(dest_callsign).ok()?;
let ssid = dest_ssid;
Some(Self {
ack,
callsign,
ssid,
})
}
pub fn is_ack(&self) -> bool {
self.ack & (1 << 7) > 0
}
pub fn ack_num(&self) -> u8 {
self.ack & !(1 << 7)
}
pub fn callsign(&self) -> &str {
self.callsign.as_str()
}
pub fn ssid(&self) -> u8 {
self.ssid
}
pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
let n = self.callsign.len() + 2;
*buf.get_mut(0)? = n.try_into().unwrap();
*buf.get_mut(1)? = self.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 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,
callsign,
ssid,
})
}
}
use core::fmt::{Debug, Display};
use half::f16;
#[derive(PartialEq, Clone)]
pub struct Gps {
latitude: i32,
longitude: i32,
pub altitude: f16,
pub max_error: u8,
heading: u8,
pub speed: f16,
}
impl Gps {
/// 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 = if heading >= 0.0 {
heading % 360.0
} else {
// slightly hacky no-std floor
let factor = (-heading / 360.0) as u32 as f64;
360.0 * (1.0 + factor) + heading
};
let heading = round(heading * 128.0 / 180.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 * 180.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,
})
}
}
impl Debug for Gps {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("Gps")
.field("latitude", &DebugUnits(self.latitude(), "°"))
.field("longitude", &DebugUnits(self.longitude(), "°"))
.field("altitude", &DebugUnits(self.altitude, " m"))
.field("max_error", &DebugUnits(self.max_error, " m"))
.field("heading", &DebugUnits(self.heading(), "°"))
.field("speed", &DebugUnits(self.speed, " m/s"))
.finish()
}
}
struct DebugUnits<'a, T>(T, &'a str);
impl<T: Display> Debug for DebugUnits<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}{}", self.0, self.1)
}
}
// no-std and it's not worth bringing in a library for this
fn round(v: f64) -> u32 {
let floor = v as u32;
let delta = v - floor as f64;
if delta <= 0.5 {
floor
} else {
floor + 1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn heading_is_correct() {
let gps = Gps::new(0.0, 0.0, f16::from_f32(0.0), 0, 359.0, f16::from_f32(0.0));
assert_eq!(gps.heading, 255);
let gps = Gps::new(0.0, 0.0, f16::from_f32(0.0), 0, 0.0, f16::from_f32(0.0));
assert_eq!(gps.heading, 0);
let gps = Gps::new(0.0, 0.0, f16::from_f32(0.0), 0, -20.0, f16::from_f32(0.0));
assert_eq!(gps.heading, 242);
let gps = Gps::new(0.0, 0.0, f16::from_f32(0.0), 0, 719.0, f16::from_f32(0.0));
assert_eq!(gps.heading, 255);
let gps = Gps::new(0.0, 0.0, f16::from_f32(0.0), 0, 180.0, f16::from_f32(0.0));
assert_eq!(gps.heading, 128);
let gps = Gps::new(0.0, 0.0, f16::from_f32(0.0), 0, 540.0, f16::from_f32(0.0));
assert_eq!(gps.heading, 128);
}
#[test]
fn debug_printing() {
let gps = Gps::new(0.0, 0.0, f16::from_f32(0.0), 0, 359.0, f16::from_f32(0.0));
let x = format!("{gps:?}");
assert_eq!(x,"Gps { latitude: 0°, longitude: 0°, altitude: 0 m, max_error: 0 m, heading: 358.59375°, speed: 0 m/s }")
}
}
use arrayvec::ArrayString;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Identification {
pub callsign: ArrayString<252>,
pub ssid: u8,
pub icon: u16,
}
impl Identification {
pub fn new(call: &str, ssid: u8, icon: u16) -> Option<Self> {
let callsign = ArrayString::from(call).ok()?;
Some(Self {
icon,
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 <= 252
let len: u8 = self.callsign.as_bytes().len().try_into().unwrap();
*buf.get_mut(0)? = len + 3;
buf.get_mut(1..3)?.copy_from_slice(&self.icon.to_le_bytes());
let mut len = 3;
for (i, b) in self.callsign.as_bytes().iter().enumerate() {
*buf.get_mut(i + 3)? = *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 icon = u16::from_le_bytes([*data.get(1)?, *data.get(2)?]);
let callsign = core::str::from_utf8(data.get(3..len)?).ok()?;
let callsign = ArrayString::from(callsign).ok()?;
let ssid = *data.get(len)?;
Some(Self {
icon,
callsign,
ssid,
})
}
}
This diff is collapsed.
#[derive(Copy, Clone, Debug)]
pub struct NodeInfo {
hardware_id: Option<u16>,
software_id: Option<u8>,
uptime: Option<u32>,
antenna_height: Option<u8>,
antenna_gain: Option<u8>,
tx_power: Option<u8>,
voltage: Option<u8>,
xcvr_temperature: Option<i8>,
battery_charge: Option<u8>,
altitude: Option<f32>,
balloon: bool,
ambient_temperature: Option<i8>,
ambient_humidity: Option<u8>,
ambient_pressure: Option<u16>,
}
impl NodeInfo {
pub fn builder() -> NodeInfoBuilder {
NodeInfoBuilder::default()
}
pub fn hardware_id(&self) -> Option<u16> {
self.hardware_id
}
pub fn software_id(&self) -> Option<u8> {
self.software_id
}
/// Seconds
pub fn uptime(&self) -> Option<u32> {
self.uptime
}
/// Meters
pub fn antenna_height(&self) -> Option<u8> {
self.antenna_height
}
/// dBi
pub fn antenna_gain(&self) -> Option<f64> {
self.antenna_gain.map(|g| g as f64 / 4.0)
}
/// dBm
pub fn tx_power(&self) -> Option<f64> {
self.tx_power.map(|p| p as f64 / 4.0)
}
/// Volts
pub fn voltage(&self) -> Option<f64> {
self.voltage.map(|v| v as f64 / 10.0)
}
/// Degrees C
pub fn xcvr_temperature(&self) -> Option<i8> {
self.xcvr_temperature
}
/// Percent
pub fn battery_charge(&self) -> Option<f64> {
self.battery_charge.map(|b| b as f64 / 2.55)
}
/// Higher precision altitude than what's available in the GPS whisker. Takes precedent if set. Useful for high altitude balloons.
/// Meters
pub fn altitude(&self) -> Option<f32> {
self.altitude
}
/// If true, this whisker was emitted by a high altitude balloon payload
pub fn is_balloon(&self) -> bool {
self.balloon
}
/// Degrees C
pub fn ambient_temperature(&self) -> Option<i8> {
self.ambient_temperature
}
/// Relative humidity, percent
pub fn ambient_humidity(&self) -> Option<f64> {
self.ambient_humidity.map(|x| x as f64 / 2.55)
}
/// Decapascal (daPa)
pub fn ambient_pressure(&self) -> Option<u16> {
self.ambient_pressure
}
pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
let mut bitmask: u32 = 0;
let mut i = 4;
// TODO these could be macros
if let Some(x) = self.hardware_id {
bitmask |= 1;
buf.get_mut(i..(i + 2))?.copy_from_slice(&x.to_le_bytes());
i += 2;
}
if let Some(x) = self.software_id {
bitmask |= 2;
*buf.get_mut(i)? = x;
i += 1;
}
if let Some(x) = self.uptime {
bitmask |= 4;
buf.get_mut(i..(i + 4))?.copy_from_slice(&x.to_le_bytes());
i += 4;
}
if let Some(x) = self.antenna_height {
bitmask |= 8;
*buf.get_mut(i)? = x;
i += 1;
}
if let Some(x) = self.antenna_gain {
bitmask |= 16;
*buf.get_mut(i)? = x;
i += 1;
}
if let Some(x) = self.tx_power {
bitmask |= 32;
*buf.get_mut(i)? = x;
i += 1;
}
if let Some(x) = self.voltage {
bitmask |= 64;
*buf.get_mut(i)? = x;
i += 1;
}
if let Some(x) = self.xcvr_temperature {
bitmask |= 128;
*buf.get_mut(i)? = x.to_le_bytes()[0];
i += 1;
}
if let Some(x) = self.battery_charge {
bitmask |= 256;
*buf.get_mut(i)? = x;
i += 1;
}
if let Some(x) = self.altitude {
bitmask |= 512;
buf.get_mut(i..(i + 4))?.copy_from_slice(&x.to_le_bytes());
i += 4;
}
if self.balloon {
bitmask |= 1024;
}
if let Some(x) = self.ambient_temperature {
bitmask |= 2048;
*buf.get_mut(i)? = x.to_le_bytes()[0];
i += 1;
}
if let Some(x) = self.ambient_humidity {
bitmask |= 4096;
*buf.get_mut(i)? = x;
i += 1;
}
if let Some(x) = self.ambient_pressure {
bitmask |= 8192;
buf.get_mut(i..(i + 2))?.copy_from_slice(&x.to_le_bytes());
i += 2;
}
buf[0] = (i - 1).try_into().ok()?;
buf[1..4].copy_from_slice(&bitmask.to_be_bytes()[1..]);
Some(&buf[..i])
}
pub fn decode(data: &[u8]) -> Option<Self> {
let bitmask = u32::from_be_bytes([0, *data.get(1)?, *data.get(2)?, *data.get(3)?]);
let mut builder = NodeInfoBuilder::default();
let mut i = 4;
if bitmask & 1 > 0 {
builder.hardware_id = Some(u16::from_le_bytes([*data.get(i)?, *data.get(i + 1)?]));
i += 2;
}
if bitmask & 2 > 0 {
builder.software_id = Some(*data.get(i)?);
i += 1;
}
if bitmask & 4 > 0 {
builder.uptime = Some(u32::from_le_bytes([
*data.get(i)?,
*data.get(i + 1)?,
*data.get(i + 2)?,
*data.get(i + 3)?,
]));
i += 4;
}
if bitmask & 8 > 0 {
builder.antenna_height = Some(*data.get(i)?);
i += 1;
}
if bitmask & 16 > 0 {
builder.antenna_gain = Some(*data.get(i)?);
i += 1;
}
if bitmask & 32 > 0 {
builder.tx_power = Some(*data.get(i)?);
i += 1;
}
if bitmask & 64 > 0 {
builder.voltage = Some(*data.get(i)?);
i += 1;
}
if bitmask & 128 > 0 {
builder.xcvr_temperature = Some(i8::from_le_bytes([*data.get(i)?]));
i += 1;
}
if bitmask & 256 > 0 {
builder.battery_charge = Some(*data.get(i)?);
i += 1;
}
if bitmask & 512 > 0 {
builder.altitude = Some(f32::from_le_bytes(
data.get(i..(i + 4))?.try_into().unwrap(),
));
i += 4;
}
builder.balloon = bitmask & 1024 > 0;
if bitmask & 2048 > 0 {
builder.ambient_temperature = Some(i8::from_le_bytes([*data.get(i)?]));
i += 1;
}
if bitmask & 4096 > 0 {
builder.ambient_humidity = Some(*data.get(i)?);
i += 1;
}
if bitmask & 8192 > 0 {
builder.ambient_pressure = Some(u16::from_le_bytes(
data.get(i..(i + 2))?.try_into().unwrap(),
));
i += 2;
}
// prevent unused variable warning
let _ = i;
Some(builder.build())
}
}
#[derive(Default, Copy, Clone)]
pub struct NodeInfoBuilder {
hardware_id: Option<u16>,
software_id: Option<u8>,
uptime: Option<u32>,
antenna_height: Option<u8>,
antenna_gain: Option<u8>,
tx_power: Option<u8>,
voltage: Option<u8>,
xcvr_temperature: Option<i8>,
battery_charge: Option<u8>,
altitude: Option<f32>,
balloon: bool,
ambient_temperature: Option<i8>,
ambient_humidity: Option<u8>,
ambient_pressure: Option<u16>,
}
impl NodeInfoBuilder {
pub fn build(self) -> NodeInfo {
let NodeInfoBuilder {
hardware_id,
software_id,
uptime,
antenna_height,
antenna_gain,
tx_power,
voltage,
xcvr_temperature,
battery_charge,
altitude,
balloon,
ambient_temperature,
ambient_humidity,
ambient_pressure,
} = self;
NodeInfo {
hardware_id,
software_id,
uptime,
antenna_height,
antenna_gain,
tx_power,
voltage,
xcvr_temperature,
battery_charge,
altitude,
balloon,
ambient_temperature,
ambient_humidity,
ambient_pressure,
}
}
pub fn hardware_id(mut self, val: u16) -> Self {
self.hardware_id = Some(val);
self
}
pub fn software_id(mut self, val: u8) -> Self {
self.software_id = Some(val);
self
}
/// Seconds
pub fn uptime(mut self, val: u32) -> Self {
self.uptime = Some(val);
self
}
/// Meters
pub fn antenna_height(mut self, val: u8) -> Self {
self.antenna_height = Some(val);
self
}
/// dBi
pub fn antenna_gain(mut self, val: f64) -> Self {
self.antenna_gain = Some((val * 4.0).min(255.0) as u8);
self
}
/// dBm
pub fn tx_power(mut self, val: f64) -> Self {
self.tx_power = Some((val * 4.0).min(255.0) as u8);
self
}
/// Volts
pub fn voltage(mut self, val: f64) -> Self {
self.voltage = Some((val * 10.0).min(255.0) as u8);
self
}
/// Degrees C
pub fn xcvr_temperature(mut self, val: i8) -> Self {
self.xcvr_temperature = Some(val);
self
}
/// Percent
pub fn battery_charge(mut self, val: f64) -> Self {
self.battery_charge = Some((val * 2.55).min(255.0) as u8);
self
}
/// Higher precision altitude than what's available in the GPS whisker. Takes precedent if set. Useful for high altitude balloons.
/// Meters
pub fn altitude(mut self, val: f32) -> Self {
self.altitude = Some(val);
self
}
/// If true, this whisker was emitted by a high altitude balloon payload
/// Please only set this on actual balloon payloads. Otherwise it can cause issues for downstream users!
pub fn set_balloon(mut self) -> Self {
self.balloon = true;
self
}
/// Degrees C
pub fn ambient_temperature(mut self, val: i8) -> Self {
self.ambient_temperature = Some(val);
self
}
/// Relative humidity, percent
pub fn ambient_humidity(mut self, val: f64) -> Self {
self.ambient_humidity = Some((val * 2.55).min(255.0) as u8);
self
}
/// Decapascal (daPa)
pub fn ambient_pressure(mut self, val: u16) -> Self {
self.ambient_pressure = Some(val);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
// verify examples in the standard doc
#[test]
fn node_info_doc_examples() {
let node_info = NodeInfoBuilder::default()
.hardware_id(7408)
.uptime(98)
.tx_power(30.0)
.voltage(12.8)
.build();
let mut buf = [0; 255];
let encoded = node_info.encode(&mut buf).unwrap();
assert_eq!(
[0x0B, 0x00, 0x00, 0x65, 0xF0, 0x1C, 0x62, 0x00, 0x00, 0x00, 0x78, 0x80],
encoded
);
}
}
use arrayvec::ArrayVec;
use crate::{error::AppendNodeError, identity::Identity};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Route {
pub max_hops: u8,
path: ArrayVec<u8, 254>,
has_future: bool,
}
impl Route {
pub fn new(max_hops: u8) -> Self {
let path = ArrayVec::new();
Self {
max_hops,
path,
has_future: false,
}
}
/// Returns `None` if there isn't enough space for the callsign
/// Returns `None` if attempting to push past after future hop
/// See the specs for more information
#[must_use]
pub fn push_hop(&mut self, hop: RouteHop) -> Option<()> {
match hop {
RouteHop::Internet => self.push_internet(),
RouteHop::Past(past_hop) => self.push_past(past_hop),
RouteHop::Future(ident) => self.push_future(ident),
}
}
/// Returns `None` if there isn't enough space for the callsign
/// Returns `None` if attempting to push after future hop
/// See the specs for more information
#[must_use]
pub fn push_past(&mut self, past_hop: PastHop) -> Option<()> {
let ident = past_hop.identity();
let len = ident.callsign().as_bytes().len() + 3;
let free_space = self.path.capacity() - self.path.len();
if len > free_space {
return None;
}
if self.has_future {
return None;
}
// safe to unwrap since we already did a length check
self.path
.try_extend_from_slice(ident.callsign().as_bytes())
.unwrap();
self.path.push(0xFF);
self.path.push(ident.ssid());
self.path.push(past_hop.rssi);
Some(())
}
/// Returns `None` if there isn't enough space for the callsign
#[must_use]
pub fn push_future(&mut self, ident: Identity) -> Option<()> {
let len = ident.callsign().as_bytes().len() + 2;
let free_space = self.path.capacity() - self.path.len();
if len > free_space {
return None;
}
self.has_future = true;
// safe to unwrap since we already did a length check
self.path
.try_extend_from_slice(ident.callsign().as_bytes())
.unwrap();
self.path.push(0xFD);
self.path.push(ident.ssid());
Some(())
}
/// Returns `None` if there isn't enough space
pub fn push_internet(&mut self) -> Option<()> {
let free_space = self.path.capacity() - self.path.len();
if free_space < 1 {
return None;
}
self.path.push(0xFE);
Some(())
}
/// Append a callsign/ssid pair to the route, intelligently.
/// I.e. replace future node if possible
/// Returns an Err if the route is out of space, or appending the node doesn't make logical sense
/// (there's a future node that doesn't match)
/// This only appends past hops.
pub fn append_hop(&mut self, new_hop: PastHop) -> Result<(), AppendNodeError> {
let mut new_route = Route::new(self.max_hops);
let mut already_inserted = false;
for rn in self.iter() {
match rn {
RouteHop::Internet => new_route
.push_internet()
.ok_or(AppendNodeError::RouteOverflow)?,
RouteHop::Past(prev_past_hop) => {
let us = prev_past_hop.identity() == new_hop.identity();
if us {
return Err(AppendNodeError::DuplicateNode);
} else {
new_route
.push_past(prev_past_hop)
.ok_or(AppendNodeError::RouteOverflow)?;
}
}
RouteHop::Future(prev_ident) => {
let us = prev_ident == new_hop.identity();
if us {
if already_inserted {
return Err(AppendNodeError::DuplicateNode);
} else {
new_route
.push_past(new_hop)
.ok_or(AppendNodeError::RouteOverflow)?;
already_inserted = true;
}
} else if already_inserted {
new_route
.push_future(prev_ident)
.ok_or(AppendNodeError::RouteOverflow)?;
} else {
return Err(AppendNodeError::SetFuture);
}
}
}
}
if !already_inserted {
new_route
.push_past(new_hop)
.ok_or(AppendNodeError::RouteOverflow)?;
}
if new_route.iter().count() > usize::from(new_route.max_hops) {
return Err(AppendNodeError::HopsOverflow);
}
*self = new_route;
Ok(())
}
pub fn iter(&'_ self) -> RouteIter<'_> {
RouteIter::new(self)
}
pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
let packet_len = self.path.len() + 1;
let buf = buf.get_mut(0..(packet_len + 1))?;
buf[0] = packet_len.try_into().unwrap();
buf[1] = self.max_hops;
buf[2..(packet_len + 1)].copy_from_slice(&self.path);
Some(buf)
}
pub fn decode(data: &[u8]) -> Option<Self> {
let len: usize = (*data.first()?).into();
let data = data.get(1..(len + 1))?;
let max_hops = *data.first()?;
let mut path = ArrayVec::new();
path.try_extend_from_slice(&data[1..]).unwrap();
let has_future = data[1..].iter().any(|x| *x == 0xFD);
let s = Self {
max_hops,
path,
has_future,
};
if UntrustedRouteIter::new(&s).any(|v| v.is_err()) {
return None;
}
Some(s)
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum RouteHop<'a> {
Internet,
Past(PastHop<'a>),
Future(Identity<'a>),
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct PastHop<'a> {
identity: Identity<'a>,
rssi: u8,
}
impl<'a> PastHop<'a> {
pub fn new(identity: Identity<'a>, rssi: Option<f64>) -> Self {
let rssi = match rssi {
Some(rssi) => (rssi * 1.5 + 240.0).max(1.0) as u8,
None => 0,
};
Self { identity, rssi }
}
pub fn identity(&self) -> Identity {
self.identity
}
pub fn rssi(&self) -> Option<f64> {
if self.rssi == 0 {
None
} else {
Some(((self.rssi as f64) - 240.0) / 1.5)
}
}
}
#[derive(Debug, Clone)]
struct UntrustedRouteIter<'a> {
route: &'a Route,
i: usize,
seen_future: bool,
dead: bool,
}
impl<'a> UntrustedRouteIter<'a> {
fn new(route: &'a Route) -> Self {
Self {
route,
i: 0,
seen_future: false,
dead: false,
}
}
// Returns Err(()) if invalid
fn maybe_next(&mut self) -> Result<RouteHop<'a>, ()> {
let i_start = self.i;
self.i += 1;
if *self.route.path.get(i_start).ok_or(())? == 0xFE {
return Ok(RouteHop::Internet);
}
while *self.route.path.get(self.i).ok_or(())? != 0xFD && self.route.path[self.i] != 0xFF {
self.i += 1;
}
let callsign = core::str::from_utf8(self.route.path.get(i_start..self.i).ok_or(())?)
.map_err(|_| ())?;
let is_future = *self.route.path.get(self.i).ok_or(())? == 0xFD;
if self.seen_future && !is_future {
// past after future - not allowed
return Err(());
}
self.seen_future |= is_future;
let ssid = *self.route.path.get(self.i + 1).ok_or(())?;
self.i += 2;
if is_future {
Ok(RouteHop::Future(Identity::new(callsign, ssid)))
} else {
let rssi = *self.route.path.get(self.i).ok_or(())?;
self.i += 1;
Ok(RouteHop::Past(PastHop {
identity: Identity::new(callsign, ssid),
rssi,
}))
}
}
}
impl<'a> Iterator for UntrustedRouteIter<'a> {
type Item = Result<RouteHop<'a>, ()>;
fn next(&mut self) -> Option<Self::Item> {
if self.dead {
return None;
}
if self.i == self.route.path.len() {
return None;
}
let r = self.maybe_next();
if r.is_err() {
self.dead = true;
}
Some(r)
}
}
#[derive(Clone)]
pub struct RouteIter<'a> {
iter: UntrustedRouteIter<'a>,
}
impl<'a> RouteIter<'a> {
fn new(route: &'a Route) -> Self {
Self {
iter: UntrustedRouteIter::new(route),
}
}
}
impl<'a> Iterator for RouteIter<'a> {
type Item = RouteHop<'a>;
fn next(&mut self) -> Option<Self::Item> {
Some(self.iter.next()?.unwrap())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_rssi() {
let x = -66.0;
let r = PastHop::new(Identity::new("C0", 23), Some(x));
assert_eq!(x, r.rssi().unwrap());
let x = 10.0;
let r = PastHop::new(Identity::new("C0", 23), Some(x));
assert_eq!(x, r.rssi().unwrap());
let x = -158.0;
let r = PastHop::new(Identity::new("C0", 23), Some(x));
assert_eq!(x, r.rssi().unwrap());
let r = PastHop::new(Identity::new("C0", 23), None);
assert_eq!(None, r.rssi());
}
#[test]
fn append_fails_when_existing_future() {
let mut r = Route::new(5);
r.push_past(PastHop::new(Identity::new("C1", 0), None))
.unwrap();
r.push_past(PastHop::new(Identity::new("C2", 0), None))
.unwrap();
r.push_future(Identity::new("C3", 0)).unwrap();
assert_eq!(
AppendNodeError::SetFuture,
r.append_hop(PastHop::new(Identity::new("C3", 1), None))
.unwrap_err()
);
assert_eq!(
AppendNodeError::SetFuture,
r.append_hop(PastHop::new(Identity::new("C4", 0), None))
.unwrap_err()
);
}
#[test]
fn append_fails_when_would_exceed_max_hops() {
let mut r = Route::new(3);
r.append_hop(PastHop::new(Identity::new("C1", 0), None))
.unwrap();
r.append_hop(PastHop::new(Identity::new("C2", 0), None))
.unwrap();
r.append_hop(PastHop::new(Identity::new("C2", 1), None))
.unwrap();
assert_eq!(
AppendNodeError::HopsOverflow,
r.append_hop(PastHop::new(Identity::new("C4", 0), None))
.unwrap_err()
);
}
#[test]
fn append_fails_when_already_in_route() {
let mut r = Route::new(3);
r.append_hop(PastHop::new(Identity::new("C1", 0), None))
.unwrap();
assert_eq!(
AppendNodeError::DuplicateNode,
r.append_hop(PastHop::new(Identity::new("C1", 0), None))
.unwrap_err()
);
}
#[test]
fn append_overwrites_future() {
let mut r = Route::new(5);
r.push_past(PastHop::new(Identity::new("C1", 0), None))
.unwrap();
r.push_past(PastHop::new(Identity::new("C2", 0), None))
.unwrap();
r.push_future(Identity::new("C3", 0)).unwrap();
r.push_future(Identity::new("C4", 0)).unwrap();
r.append_hop(PastHop::new(Identity::new("C3", 0), None))
.unwrap();
let mut iter = r.iter();
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C1", 0), None))),
iter.next()
);
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C2", 0), None))),
iter.next()
);
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C3", 0), None))),
iter.next()
);
assert_eq!(Some(RouteHop::Future(Identity::new("C4", 0))), iter.next());
assert_eq!(None, iter.next());
}
#[test]
fn append_overwrites_future_even_when_at_hop_limit() {
let mut r = Route::new(4);
r.push_past(PastHop::new(Identity::new("C1", 0), None))
.unwrap();
r.push_past(PastHop::new(Identity::new("C2", 0), None))
.unwrap();
r.push_future(Identity::new("C3", 0)).unwrap();
r.push_future(Identity::new("C4", 0)).unwrap();
r.append_hop(PastHop::new(Identity::new("C3", 0), None))
.unwrap();
let mut iter = r.iter();
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C1", 0), None))),
iter.next()
);
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C2", 0), None))),
iter.next()
);
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C3", 0), None))),
iter.next()
);
assert_eq!(Some(RouteHop::Future(Identity::new("C4", 0,))), iter.next());
assert_eq!(None, iter.next());
}
#[test]
fn append_appends_when_no_future() {
let mut r = Route::new(5);
r.push_past(PastHop::new(Identity::new("C1", 0), None))
.unwrap();
r.push_past(PastHop::new(Identity::new("C2", 0), None))
.unwrap();
r.push_past(PastHop::new(Identity::new("C3", 0), None))
.unwrap();
r.append_hop(PastHop::new(Identity::new("C4", 0), None))
.unwrap();
let mut iter = r.iter();
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C1", 0), None))),
iter.next()
);
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C2", 0), None))),
iter.next()
);
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C3", 0), None))),
iter.next()
);
assert_eq!(
Some(RouteHop::Past(PastHop::new(Identity::new("C4", 0), None))),
iter.next()
);
assert_eq!(None, iter.next());
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
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)))
}
}
use arrayvec::ArrayVec;
#[derive(Debug)]
pub struct Unknown {
whisker_type: u8,
data: ArrayVec<u8, 255>,
}
impl Unknown {
pub(crate) fn new(whisker_type: u8, data: ArrayVec<u8, 255>) -> Self {
Self { whisker_type, data }
}
pub fn whisker_type(&self) -> u8 {
self.whisker_type
}
pub fn data(&self) -> &[u8] {
&self.data
}
}
const START_STATE: u16 = 0xE9CF;
pub(crate) fn whiten(data: &mut [u8]) {
let mut state = START_STATE;
for d in data.iter_mut() {
let b;
(b, state) = lfsr_byte(state);
*d ^= b;
}
}
// (byte, state)
fn lfsr_byte(mut state: u16) -> (u8, u16) {
let mut out = 0;
for i in (0..8).rev() {
out |= u8::try_from(state & 1).unwrap() << i;
state = lfsr(state);
}
(out, state)
}
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register#Galois_LFSRs
fn lfsr(mut state: u16) -> u16 {
let lsb = state & 1;
state >>= 1;
if lsb > 0 {
state ^= 0xB400; // apply toggle mask
}
state
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic() {
let mut data = [0; 64];
data[0..57]
.clone_from_slice(&b"Hello world! The quick brown fox jumped over the lazy dog"[..]);
let orig = data;
whiten(&mut data);
assert_ne!(orig, data);
whiten(&mut data);
assert_eq!(orig, data);
}
#[test]
fn test_lfsr() {
let start = 0xACE1;
let end_expected = 0xE270;
let state = lfsr(start);
assert_eq!(end_expected, state);
}
#[test]
fn test_lfsr_byte() {
let start = 0xE9CF;
let (out, state) = lfsr_byte(start);
assert_eq!(0xF3, out);
assert_eq!(0xE3B1, state);
}
#[test]
fn test_doc_example() {
let start = 0xE9CF;
let expected_out = [
0xF3, 0x8D, 0xD0, 0x6E, 0x1F, 0x65, 0x75, 0x75, 0xA5, 0xBA, 0xA9, 0xD0, 0x7A, 0x1D,
0x1, 0x21,
];
let mut actual_out = [0; 16];
let mut state = start;
for a in &mut actual_out {
let (out, ns) = lfsr_byte(state);
state = ns;
*a = out;
}
assert_eq!(expected_out, actual_out);
}
#[test]
fn test_doc_example_through_whitener() {
let expected_out = [
0xF3, 0x8D, 0xD0, 0x6E, 0x1F, 0x65, 0x75, 0x75, 0xA5, 0xBA, 0xA9, 0xD0, 0x7A, 0x1D,
0x1, 0x21,
];
let mut actual_out = [0; 16];
whiten(&mut actual_out);
assert_eq!(expected_out, actual_out);
}
}