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 core::fmt::{Debug, Display};
use half::f16; use half::f16;
#[derive(Debug, PartialEq)] #[derive(PartialEq, Clone)]
pub struct Gps { pub struct Gps {
latitude: i32, latitude: i32,
longitude: i32, longitude: i32,
...@@ -33,8 +34,14 @@ impl Gps { ...@@ -33,8 +34,14 @@ impl Gps {
let longitude = longitude.clamp(-179.999, 179.999); let longitude = longitude.clamp(-179.999, 179.999);
let longitude = (longitude * ((1u32 << 31) as f64) / 180.0) as i32; let longitude = (longitude * ((1u32 << 31) as f64) / 180.0) as i32;
let heading = heading.clamp(0.0, 359.999); let heading = if heading >= 0.0 {
let heading = (heading * 128.0 / 360.0) as u8; 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 { Self {
latitude, latitude,
...@@ -55,7 +62,7 @@ impl Gps { ...@@ -55,7 +62,7 @@ impl Gps {
} }
pub fn heading(&self) -> f64 { pub fn heading(&self) -> f64 {
self.heading as f64 / 128.0 * 360.0 self.heading as f64 / 128.0 * 180.0
} }
pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> { pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
...@@ -93,3 +100,68 @@ impl Gps { ...@@ -93,3 +100,68 @@ impl Gps {
}) })
} }
} }
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; use arrayvec::ArrayString;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Identification { pub struct Identification {
pub icon: u16,
pub callsign: ArrayString<252>, pub callsign: ArrayString<252>,
pub ssid: u8, pub ssid: u8,
pub icon: u16,
} }
impl Identification { impl Identification {
pub fn new(icon: u16, call: &str, ssid: u8) -> Option<Self> { pub fn new(call: &str, ssid: u8, icon: u16) -> Option<Self> {
let callsign = ArrayString::from(call).ok()?; let callsign = ArrayString::from(call).ok()?;
Some(Self { Some(Self {
......
...@@ -3,6 +3,7 @@ mod comment; ...@@ -3,6 +3,7 @@ mod comment;
mod destination; mod destination;
mod gps; mod gps;
mod identification; mod identification;
mod node_info;
mod route; mod route;
mod timestamp; mod timestamp;
mod unknown; mod unknown;
...@@ -15,7 +16,8 @@ pub use self::{ ...@@ -15,7 +16,8 @@ pub use self::{
destination::Destination, destination::Destination,
gps::Gps, gps::Gps,
identification::Identification, identification::Identification,
route::{Route, RouteIter, RouteNode}, node_info::{NodeInfo, NodeInfoBuilder},
route::{PastHop, Route, RouteHop, RouteIter},
timestamp::Timestamp, timestamp::Timestamp,
unknown::Unknown, unknown::Unknown,
}; };
...@@ -27,6 +29,7 @@ pub(crate) const COMMENT_TYPE: u8 = 0x03; ...@@ -27,6 +29,7 @@ pub(crate) const COMMENT_TYPE: u8 = 0x03;
pub(crate) const ROUTE_TYPE: u8 = 0x04; pub(crate) const ROUTE_TYPE: u8 = 0x04;
pub(crate) const DESTINATION_TYPE: u8 = 0x05; pub(crate) const DESTINATION_TYPE: u8 = 0x05;
pub(crate) const ARBITRARY_TYPE: u8 = 0x06; pub(crate) const ARBITRARY_TYPE: u8 = 0x06;
pub(crate) const NODE_INFO_TYPE: u8 = 0x09;
#[derive(Debug)] #[derive(Debug)]
pub enum Whisker { pub enum Whisker {
...@@ -37,6 +40,7 @@ pub enum Whisker { ...@@ -37,6 +40,7 @@ pub enum Whisker {
Route(Route), Route(Route),
Destination(Destination), Destination(Destination),
Arbitrary(Arbitrary), Arbitrary(Arbitrary),
NodeInfo(NodeInfo),
Unknown(Unknown), Unknown(Unknown),
} }
...@@ -137,16 +141,21 @@ impl<'a> WhiskerIter<'a> { ...@@ -137,16 +141,21 @@ impl<'a> WhiskerIter<'a> {
ARBITRARY_TYPE => Whisker::Arbitrary( ARBITRARY_TYPE => Whisker::Arbitrary(
Arbitrary::decode(data).ok_or(DecodeError::MalformedWhisker { position })?, 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 // safe to unwrap because we know len has to be 255 or less, since it's a u8
whisker_type => Whisker::Unknown(Unknown::new(whisker_type, data.try_into().unwrap())), whisker_type => {
Whisker::Unknown(Unknown::new(whisker_type, data[1..].try_into().unwrap()))
}
}; };
Ok(out) Ok(out)
} }
} }
impl<'a> Iterator for WhiskerIter<'a> { impl Iterator for WhiskerIter<'_> {
type Item = Result<Whisker, DecodeError>; type Item = Result<Whisker, DecodeError>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
...@@ -170,7 +179,7 @@ impl<'a> ValidatedWhiskerIter<'a> { ...@@ -170,7 +179,7 @@ impl<'a> ValidatedWhiskerIter<'a> {
} }
} }
impl<'a> Iterator for ValidatedWhiskerIter<'a> { impl Iterator for ValidatedWhiskerIter<'_> {
type Item = Whisker; type Item = Whisker;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
...@@ -182,7 +191,10 @@ impl<'a> Iterator for ValidatedWhiskerIter<'a> { ...@@ -182,7 +191,10 @@ impl<'a> Iterator for ValidatedWhiskerIter<'a> {
mod tests { mod tests {
use half::f16; use half::f16;
use crate::whisker::route::RouteNode; use crate::{
identity::Identity,
whisker::route::{PastHop, RouteHop},
};
use super::*; use super::*;
...@@ -192,7 +204,7 @@ mod tests { ...@@ -192,7 +204,7 @@ mod tests {
let call = "VE9ABCDEFGZZ4839-???"; let call = "VE9ABCDEFGZZ4839-???";
let ssid = 17; 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 mut buf = [0; 256];
let encoded = ident.encode(&mut buf).unwrap(); let encoded = ident.encode(&mut buf).unwrap();
...@@ -236,7 +248,7 @@ mod tests { ...@@ -236,7 +248,7 @@ mod tests {
assert_eq!(89.99899999704212, gps.latitude()); assert_eq!(89.99899999704212, gps.latitude());
assert_eq!(-179.9989999551326, gps.longitude()); assert_eq!(-179.9989999551326, gps.longitude());
assert_eq!(120.9375, gps.heading()); assert_eq!(123.75, gps.heading());
} }
#[test] #[test]
...@@ -246,11 +258,58 @@ mod tests { ...@@ -246,11 +258,58 @@ mod tests {
23.45, 23.45,
f16::from_f32(45.02), f16::from_f32(45.02),
24, 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!(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), f16::from_f32(12.3),
); );
assert_eq!(357.1875, gps.heading()); assert_eq!(233.4375, gps.heading());
} }
#[test] #[test]
...@@ -285,43 +344,47 @@ mod tests { ...@@ -285,43 +344,47 @@ mod tests {
#[test] #[test]
fn route_push_and_iter() { fn route_push_and_iter() {
let mut route = Route::new(34); 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_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();
assert!(route.push_callsign("VE9AAAAA", 94, false).is_none()); // past after future - not allowed
assert!(route
.push_past(PastHop::new(Identity::new("VE9AAAAA", 94), None))
.is_none());
// too long // too long
assert!(route 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[", "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, 20,
true, )).is_none());
).is_none());
route route
.push_callsign("This is the last callsign", 0, true) .push_future(Identity::new("This is the last callsign", 0))
.unwrap(); .unwrap();
route.push_internet().unwrap(); route.push_internet().unwrap();
let mut iter = route.iter(); let mut iter = route.iter();
assert_eq!( assert_eq!(
RouteNode::Identity("VE2XYZ", 23, false), RouteHop::Past(PastHop::new(Identity::new("VE2XYZ", 23), None)),
iter.next().unwrap() iter.next().unwrap()
); );
assert_eq!(RouteNode::Internet, iter.next().unwrap()); assert_eq!(RouteHop::Internet, iter.next().unwrap());
assert_eq!(RouteNode::Internet, iter.next().unwrap()); assert_eq!(RouteHop::Internet, iter.next().unwrap());
assert_eq!(RouteNode::Internet, iter.next().unwrap()); assert_eq!(RouteHop::Internet, iter.next().unwrap());
assert_eq!( assert_eq!(
RouteNode::Identity("VE9AAAAA", 94, true), RouteHop::Future(Identity::new("VE9AAAAA", 94)),
iter.next().unwrap() iter.next().unwrap()
); );
assert_eq!( assert_eq!(
RouteNode::Identity("This is the last callsign", 0, true), RouteHop::Future(Identity::new("This is the last callsign", 0)),
iter.next().unwrap() iter.next().unwrap()
); );
assert_eq!(RouteNode::Internet, iter.next().unwrap()); assert_eq!(RouteHop::Internet, iter.next().unwrap());
assert_eq!(None, iter.next()); assert_eq!(None, iter.next());
assert_eq!(34, route.max_hops); assert_eq!(34, route.max_hops);
...@@ -330,13 +393,15 @@ mod tests { ...@@ -330,13 +393,15 @@ mod tests {
#[test] #[test]
fn route_e2e() { fn route_e2e() {
let mut route = Route::new(34); 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_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 route
.push_callsign("This is the last callsign", 0, true) .push_future(Identity::new("This is the last callsign", 0))
.unwrap(); .unwrap();
route.push_internet().unwrap(); route.push_internet().unwrap();
...@@ -350,44 +415,70 @@ mod tests { ...@@ -350,44 +415,70 @@ mod tests {
#[test] #[test]
fn route_doc_examples() { fn route_doc_examples() {
let mut ex1 = Route::new(4); let mut ex1 = Route::new(4);
ex1.push_callsign("VE1ABC", 0, false); ex1.push_past(PastHop::new(Identity::new("VE1ABC", 0), Some(-96.0)))
ex1.push_callsign("VE2DEF", 234, false); .unwrap();
ex1.push_callsign("VE3XYZ", 14, false); 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 mut buf = [0; 256];
let encoded = ex1.encode(&mut buf).unwrap(); let encoded = ex1.encode(&mut buf).unwrap();
assert_eq!( assert_eq!(
&[ &[
0x19, 0x04, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0x56, 0x45, 0x32, 0x44, 0x1C, 0x04, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0x60, 0x56, 0x45, 0x32,
0x45, 0x46, 0xFF, 0xEA, 0x56, 0x45, 0x33, 0x58, 0x59, 0x5A, 0xFF, 0x0E 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 &encoded
); );
let mut ex2 = Route::new(3); 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_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_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(); let encoded = ex2.encode(&mut buf).unwrap();
assert_eq!( assert_eq!(
&[ &[
0x1B, 0x03, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0xFE, 0x56, 0x45, 0x32, 0x1E, 0x03, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0x00, 0xFE, 0x56, 0x45,
0x44, 0x45, 0x46, 0xFF, 0xEA, 0xFE, 0x56, 0x45, 0x33, 0x58, 0x59, 0x5A, 0xFF, 0x0E 0x32, 0x44, 0x45, 0x46, 0xFF, 0xEA, 0x6E, 0xFE, 0x56, 0x45, 0x33, 0x58, 0x59, 0x5A,
0xFF, 0x0E, 0x8E
], ],
&encoded &encoded
); );
let mut ex3 = Route::new(0); 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();
ex3.push_internet(); ex3.push_internet();
let encoded = ex3.encode(&mut buf).unwrap(); let encoded = ex3.encode(&mut buf).unwrap();
assert_eq!( 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 &encoded
); );
} }
...@@ -405,6 +496,15 @@ mod tests { ...@@ -405,6 +496,15 @@ mod tests {
assert_eq!(dest, decoded); 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] #[test]
fn arbitrary_e2e() { fn arbitrary_e2e() {
let data = b"Hello world! This is an example comment"; 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 arrayvec::ArrayVec;
#[derive(Debug, PartialEq, Eq)] use crate::{error::AppendNodeError, identity::Identity};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Route { pub struct Route {
pub max_hops: u8, pub max_hops: u8,
path: ArrayVec<u8, 254>, path: ArrayVec<u8, 254>,
...@@ -19,29 +21,63 @@ impl Route { ...@@ -19,29 +21,63 @@ impl Route {
} }
/// Returns `None` if there isn't enough space for the callsign /// 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 /// Returns `None` if attempting to push past after future hop
/// is_future=false when this callsign has digipeated /// See the specs for more information
/// is_future=true when this callsign is expected to be part of the route, but isn't yet #[must_use]
/// see the specs for more information pub fn push_hop(&mut self, hop: RouteHop) -> Option<()> {
pub fn push_callsign(&mut self, callsign: &str, ssid: u8, is_future: bool) -> Option<()> { match hop {
let len = callsign.as_bytes().len() + 2; 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(); let free_space = self.path.capacity() - self.path.len();
if len > free_space { if len > free_space {
return None; return None;
} }
self.has_future = self.has_future || is_future; if self.has_future {
if self.has_future && !is_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; return None;
} }
self.has_future = true;
// safe to unwrap since we already did a length check // safe to unwrap since we already did a length check
self.path self.path
.try_extend_from_slice(callsign.as_bytes()) .try_extend_from_slice(ident.callsign().as_bytes())
.unwrap(); .unwrap();
self.path.push(if is_future { 0xFD } else { 0xFF }); self.path.push(0xFD);
self.path.push(ssid); self.path.push(ident.ssid());
Some(()) Some(())
} }
...@@ -59,6 +95,68 @@ impl Route { ...@@ -59,6 +95,68 @@ impl Route {
Some(()) 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<'_> { pub fn iter(&'_ self) -> RouteIter<'_> {
RouteIter::new(self) RouteIter::new(self)
} }
...@@ -73,69 +171,331 @@ impl Route { ...@@ -73,69 +171,331 @@ impl Route {
Some(buf) Some(buf)
} }
// TODO reject packets that have a 0xFF anywhere after an FD
pub fn decode(data: &[u8]) -> Option<Self> { pub fn decode(data: &[u8]) -> Option<Self> {
let len: usize = (*data.first()?).into(); let len: usize = (*data.first()?).into();
let data = data.get(1..(len + 1))?; let data = data.get(1..(len + 1))?;
let max_hops = data[0]; let max_hops = *data.first()?;
let mut path = ArrayVec::new(); let mut path = ArrayVec::new();
path.try_extend_from_slice(&data[1..]).unwrap(); path.try_extend_from_slice(&data[1..]).unwrap();
let has_future = data[1..].iter().any(|x| *x == 0xFD); let has_future = data[1..].iter().any(|x| *x == 0xFD);
Some(Self { let s = Self {
max_hops, max_hops,
path, path,
has_future, has_future,
}) };
if UntrustedRouteIter::new(&s).any(|v| v.is_err()) {
return None;
}
Some(s)
} }
} }
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum RouteNode<'a> { pub enum RouteHop<'a> {
Internet, Internet,
Identity(&'a str, u8, bool), Past(PastHop<'a>),
Future(Identity<'a>),
} }
#[derive(Debug)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct RouteIter<'a> { 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, route: &'a Route,
i: usize, i: usize,
seen_future: bool,
dead: bool,
} }
impl<'a> RouteIter<'a> { impl<'a> UntrustedRouteIter<'a> {
fn new(route: &'a Route) -> Self { 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> { impl<'a> Iterator for UntrustedRouteIter<'a> {
type Item = RouteNode<'a>; type Item = Result<RouteHop<'a>, ()>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.dead {
return None;
}
if self.i == self.route.path.len() { if self.i == self.route.path.len() {
return None; return None;
} }
let i_start = self.i; let r = self.maybe_next();
self.i += 1;
if self.route.path[i_start] == 0xFE { if r.is_err() {
return Some(RouteNode::Internet); self.dead = true;
} }
while self.route.path[self.i] != 0xFD && self.route.path[self.i] != 0xFF { Some(r)
self.i += 1; }
}
#[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(); impl<'a> Iterator for RouteIter<'a> {
let is_future = self.route.path[self.i] == 0xFD; type Item = RouteHop<'a>;
self.i += 1;
let ssid = self.route.path[self.i]; fn next(&mut self) -> Option<Self::Item> {
self.i += 1; 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());
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Timestamp(u64); pub struct Timestamp(u64);
impl Timestamp { impl Timestamp {
......
...@@ -10,4 +10,12 @@ impl Unknown { ...@@ -10,4 +10,12 @@ impl Unknown {
pub(crate) fn new(whisker_type: u8, data: ArrayVec<u8, 255>) -> Self { pub(crate) fn new(whisker_type: u8, data: ArrayVec<u8, 255>) -> Self {
Self { whisker_type, data } 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);
}
}