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
......@@ -3,6 +3,7 @@ mod comment;
mod destination;
mod gps;
mod identification;
mod node_info;
mod route;
mod timestamp;
mod unknown;
......@@ -15,7 +16,8 @@ pub use self::{
destination::Destination,
gps::Gps,
identification::Identification,
route::{Route, RouteIter, RouteNode},
node_info::{NodeInfo, NodeInfoBuilder},
route::{PastHop, Route, RouteHop, RouteIter},
timestamp::Timestamp,
unknown::Unknown,
};
......@@ -27,6 +29,7 @@ pub(crate) const COMMENT_TYPE: u8 = 0x03;
pub(crate) const ROUTE_TYPE: u8 = 0x04;
pub(crate) const DESTINATION_TYPE: u8 = 0x05;
pub(crate) const ARBITRARY_TYPE: u8 = 0x06;
pub(crate) const NODE_INFO_TYPE: u8 = 0x09;
#[derive(Debug)]
pub enum Whisker {
......@@ -37,6 +40,7 @@ pub enum Whisker {
Route(Route),
Destination(Destination),
Arbitrary(Arbitrary),
NodeInfo(NodeInfo),
Unknown(Unknown),
}
......@@ -137,6 +141,9 @@ impl<'a> WhiskerIter<'a> {
ARBITRARY_TYPE => Whisker::Arbitrary(
Arbitrary::decode(data).ok_or(DecodeError::MalformedWhisker { position })?,
),
NODE_INFO_TYPE => Whisker::NodeInfo(
NodeInfo::decode(data).ok_or(DecodeError::MalformedWhisker { position })?,
),
// safe to unwrap because we know len has to be 255 or less, since it's a u8
whisker_type => {
......@@ -148,7 +155,7 @@ impl<'a> WhiskerIter<'a> {
}
}
impl<'a> Iterator for WhiskerIter<'a> {
impl Iterator for WhiskerIter<'_> {
type Item = Result<Whisker, DecodeError>;
fn next(&mut self) -> Option<Self::Item> {
......@@ -172,7 +179,7 @@ impl<'a> ValidatedWhiskerIter<'a> {
}
}
impl<'a> Iterator for ValidatedWhiskerIter<'a> {
impl Iterator for ValidatedWhiskerIter<'_> {
type Item = Whisker;
fn next(&mut self) -> Option<Self::Item> {
......@@ -184,7 +191,10 @@ impl<'a> Iterator for ValidatedWhiskerIter<'a> {
mod tests {
use half::f16;
use crate::whisker::route::RouteNode;
use crate::{
identity::Identity,
whisker::route::{PastHop, RouteHop},
};
use super::*;
......@@ -194,7 +204,7 @@ mod tests {
let call = "VE9ABCDEFGZZ4839-???";
let ssid = 17;
let ident = Identification::new(icon, call, ssid).unwrap();
let ident = Identification::new(call, ssid, icon).unwrap();
let mut buf = [0; 256];
let encoded = ident.encode(&mut buf).unwrap();
......@@ -238,7 +248,7 @@ mod tests {
assert_eq!(89.99899999704212, gps.latitude());
assert_eq!(-179.9989999551326, gps.longitude());
assert_eq!(120.9375, gps.heading());
assert_eq!(123.75, gps.heading());
}
#[test]
......@@ -248,11 +258,58 @@ mod tests {
23.45,
f16::from_f32(45.02),
24,
360.0,
359.0,
f16::from_f32(12.3),
);
assert_eq!(358.59375, gps.heading());
let gps = Gps::new(
4.0,
23.45,
f16::from_f32(45.02),
24,
957.47,
f16::from_f32(12.3),
);
assert_eq!(357.1875, gps.heading());
assert_eq!(957.65625 - 360.0 * 2.0, gps.heading());
}
#[test]
fn gps_min_heading() {
let gps = Gps::new(
4.0,
23.45,
f16::from_f32(45.02),
24,
0.0,
f16::from_f32(12.3),
);
assert_eq!(0.0, gps.heading());
let gps = Gps::new(
4.0,
23.45,
f16::from_f32(45.02),
24,
-22.0,
f16::from_f32(12.3),
);
assert_eq!(337.5, gps.heading());
let gps = Gps::new(
4.0,
23.45,
f16::from_f32(45.02),
24,
-1206.0,
f16::from_f32(12.3),
);
assert_eq!(233.4375, gps.heading());
}
#[test]
......@@ -287,43 +344,47 @@ mod tests {
#[test]
fn route_push_and_iter() {
let mut route = Route::new(34);
route.push_callsign("VE2XYZ", 23, false).unwrap();
route
.push_past(PastHop::new(Identity::new("VE2XYZ", 23), None))
.unwrap();
route.push_internet().unwrap();
route.push_internet().unwrap();
route.push_internet().unwrap();
route.push_callsign("VE9AAAAA", 94, true).unwrap();
assert!(route.push_callsign("VE9AAAAA", 94, false).is_none());
route.push_future(Identity::new("VE9AAAAA", 94)).unwrap();
// past after future - not allowed
assert!(route
.push_past(PastHop::new(Identity::new("VE9AAAAA", 94), None))
.is_none());
// too long
assert!(route
.push_callsign(
.push_future(Identity::new(
"lsdfjslkdfjlksdjflksfjsdklfjsdklfjsdklfjsdklfjsklfsef;jklsdfjkl;sdf;klsdf;klsjdfJSDJFSKL:DFJDSL:KFskldfj;slkdfjsdkl;fjdskl;fjsdfl;kjsdfl;ksdjfkl;ssdfl;kjsdfl;ksdjf;sdklsd;lfkjsdlfk;jsdl;fkjsd;klfjsd;fljsf;oidfjgwper0tujdfgndfjkl;gjnergjol;kehfgo;dijge;oghdfkl;gjdfkl;gjdeior;lgjedr;ioghjdorighndeklo;grjiop[",
20,
true,
).is_none());
)).is_none());
route
.push_callsign("This is the last callsign", 0, true)
.push_future(Identity::new("This is the last callsign", 0))
.unwrap();
route.push_internet().unwrap();
let mut iter = route.iter();
assert_eq!(
RouteNode::Identity("VE2XYZ", 23, false),
RouteHop::Past(PastHop::new(Identity::new("VE2XYZ", 23), None)),
iter.next().unwrap()
);
assert_eq!(RouteNode::Internet, iter.next().unwrap());
assert_eq!(RouteNode::Internet, iter.next().unwrap());
assert_eq!(RouteNode::Internet, iter.next().unwrap());
assert_eq!(RouteHop::Internet, iter.next().unwrap());
assert_eq!(RouteHop::Internet, iter.next().unwrap());
assert_eq!(RouteHop::Internet, iter.next().unwrap());
assert_eq!(
RouteNode::Identity("VE9AAAAA", 94, true),
RouteHop::Future(Identity::new("VE9AAAAA", 94)),
iter.next().unwrap()
);
assert_eq!(
RouteNode::Identity("This is the last callsign", 0, true),
RouteHop::Future(Identity::new("This is the last callsign", 0)),
iter.next().unwrap()
);
assert_eq!(RouteNode::Internet, iter.next().unwrap());
assert_eq!(RouteHop::Internet, iter.next().unwrap());
assert_eq!(None, iter.next());
assert_eq!(34, route.max_hops);
......@@ -332,13 +393,15 @@ mod tests {
#[test]
fn route_e2e() {
let mut route = Route::new(34);
route.push_callsign("VE2XYZ", 23, false).unwrap();
route
.push_past(PastHop::new(Identity::new("VE2XYZ", 23), Some(0.0)))
.unwrap();
route.push_internet().unwrap();
route.push_internet().unwrap();
route.push_internet().unwrap();
route.push_callsign("VE9AAAAA", 94, true).unwrap();
route.push_future(Identity::new("VE9AAAAA", 94)).unwrap();
route
.push_callsign("This is the last callsign", 0, true)
.push_future(Identity::new("This is the last callsign", 0))
.unwrap();
route.push_internet().unwrap();
......@@ -352,44 +415,70 @@ mod tests {
#[test]
fn route_doc_examples() {
let mut ex1 = Route::new(4);
ex1.push_callsign("VE1ABC", 0, false);
ex1.push_callsign("VE2DEF", 234, false);
ex1.push_callsign("VE3XYZ", 14, false);
ex1.push_past(PastHop::new(Identity::new("VE1ABC", 0), Some(-96.0)))
.unwrap();
ex1.push_past(PastHop::new(Identity::new("VE2DEF", 234), Some(-13.0)))
.unwrap();
ex1.push_past(PastHop::new(Identity::new("VE3XYZ", 14), Some(-106.0)))
.unwrap();
let mut buf = [0; 256];
let encoded = ex1.encode(&mut buf).unwrap();
assert_eq!(
&[
0x19, 0x04, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0x56, 0x45, 0x32, 0x44,
0x45, 0x46, 0xFF, 0xEA, 0x56, 0x45, 0x33, 0x58, 0x59, 0x5A, 0xFF, 0x0E
0x1C, 0x04, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0x60, 0x56, 0x45, 0x32,
0x44, 0x45, 0x46, 0xFF, 0xEA, 0xDC, 0x56, 0x45, 0x33, 0x58, 0x59, 0x5A, 0xFF, 0x0E,
0x51
],
&encoded
);
let mut ex1_5 = Route::new(4);
ex1_5
.push_past(PastHop::new(Identity::new("VE1ABC", 0), Some(-96.0)))
.unwrap();
ex1_5.push_future(Identity::new("VE2DEF", 234)).unwrap();
ex1_5.push_future(Identity::new("VE3XYZ", 14)).unwrap();
let mut buf = [0; 256];
let encoded = ex1_5.encode(&mut buf).unwrap();
assert_eq!(
&[
0x1A, 0x04, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0x60, 0x56, 0x45, 0x32,
0x44, 0x45, 0x46, 0xFD, 0xEA, 0x56, 0x45, 0x33, 0x58, 0x59, 0x5A, 0xFD, 0x0E,
],
&encoded
);
let mut ex2 = Route::new(3);
ex2.push_callsign("VE1ABC", 0, false);
ex2.push_past(PastHop::new(Identity::new("VE1ABC", 0), None))
.unwrap();
ex2.push_internet();
ex2.push_callsign("VE2DEF", 234, false);
ex2.push_past(PastHop::new(Identity::new("VE2DEF", 234), Some(-86.5)))
.unwrap();
ex2.push_internet();
ex2.push_callsign("VE3XYZ", 14, false);
ex2.push_past(PastHop::new(Identity::new("VE3XYZ", 14), Some(-65.0)))
.unwrap();
let encoded = ex2.encode(&mut buf).unwrap();
assert_eq!(
&[
0x1B, 0x03, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0xFE, 0x56, 0x45, 0x32,
0x44, 0x45, 0x46, 0xFF, 0xEA, 0xFE, 0x56, 0x45, 0x33, 0x58, 0x59, 0x5A, 0xFF, 0x0E
0x1E, 0x03, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0x00, 0xFE, 0x56, 0x45,
0x32, 0x44, 0x45, 0x46, 0xFF, 0xEA, 0x6E, 0xFE, 0x56, 0x45, 0x33, 0x58, 0x59, 0x5A,
0xFF, 0x0E, 0x8E
],
&encoded
);
let mut ex3 = Route::new(0);
ex3.push_callsign("VE1ABC", 0, false);
ex3.push_past(PastHop::new(Identity::new("VE1ABC", 0), Some(-42.5)))
.unwrap();
ex3.push_internet();
ex3.push_internet();
let encoded = ex3.encode(&mut buf).unwrap();
assert_eq!(
&[0x0B, 0x00, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0xFE, 0xFE],
&[0x0C, 0x00, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0xB0, 0xFE, 0xFE],
&encoded
);
}
......@@ -407,6 +496,15 @@ mod tests {
assert_eq!(dest, decoded);
}
#[test]
fn dest_ack() {
let dest = Destination::new(true, 84, "abc", 17).unwrap();
assert_eq!(84, dest.ack_num());
assert!(dest.is_ack());
assert_eq!("abc", dest.callsign());
assert_eq!(17, dest.ssid());
}
#[test]
fn arbitrary_e2e() {
let data = b"Hello world! This is an example comment";
......
#[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,
......@@ -19,29 +21,63 @@ impl Route {
}
/// Returns `None` if there isn't enough space for the callsign
/// Returns `None` if attempting to pushed is_used=true after an existing is_used=false
/// is_future=false when this callsign has digipeated
/// is_future=true when this callsign is expected to be part of the route, but isn't yet
/// see the specs for more information
pub fn push_callsign(&mut self, callsign: &str, ssid: u8, is_future: bool) -> Option<()> {
let len = callsign.as_bytes().len() + 2;
/// 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;
}
self.has_future = self.has_future || is_future;
if self.has_future && !is_future {
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(callsign.as_bytes())
.try_extend_from_slice(ident.callsign().as_bytes())
.unwrap();
self.path.push(if is_future { 0xFD } else { 0xFF });
self.path.push(ssid);
self.path.push(0xFD);
self.path.push(ident.ssid());
Some(())
}
......@@ -59,6 +95,68 @@ impl Route {
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)
}
......@@ -73,7 +171,6 @@ impl Route {
Some(buf)
}
// TODO reject packets that have a 0xFF anywhere after an FD
pub fn decode(data: &[u8]) -> Option<Self> {
let len: usize = (*data.first()?).into();
let data = data.get(1..(len + 1))?;
......@@ -85,57 +182,320 @@ impl Route {
let has_future = data[1..].iter().any(|x| *x == 0xFD);
Some(Self {
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)]
pub enum RouteNode<'a> {
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum RouteHop<'a> {
Internet,
Identity(&'a str, u8, bool),
Past(PastHop<'a>),
Future(Identity<'a>),
}
#[derive(Debug)]
pub struct RouteIter<'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> RouteIter<'a> {
impl<'a> UntrustedRouteIter<'a> {
fn new(route: &'a Route) -> Self {
Self { route, i: 0 }
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 RouteIter<'a> {
type Item = RouteNode<'a>;
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 i_start = self.i;
self.i += 1;
let r = self.maybe_next();
if self.route.path[i_start] == 0xFE {
return Some(RouteNode::Internet);
if r.is_err() {
self.dead = true;
}
while self.route.path[self.i] != 0xFD && self.route.path[self.i] != 0xFF {
self.i += 1;
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),
}
}
}
let callsign = core::str::from_utf8(&self.route.path[i_start..self.i]).unwrap();
let is_future = self.route.path[self.i] == 0xFD;
self.i += 1;
let ssid = self.route.path[self.i];
self.i += 1;
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();
Some(RouteNode::Identity(callsign, ssid, is_future))
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());
}
}
const WHITE: [u8; 16] = [
0xe9, 0xcf, 0x67, 0x20, 0x19, 0x1a, 0x07, 0xdc, 0xc0, 0x72, 0x79, 0x97, 0x51, 0xf7, 0xdd, 0x93,
];
const START_STATE: u16 = 0xE9CF;
pub(crate) fn whiten(data: &mut [u8]) {
for (i, d) in data.iter_mut().enumerate() {
*d ^= WHITE[i % 15];
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)]
......@@ -17,7 +41,7 @@ mod tests {
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.clone();
let orig = data;
whiten(&mut data);
assert_ne!(orig, data);
......@@ -25,4 +49,54 @@ mod tests {
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);
}
}