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; ...@@ -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,6 +141,9 @@ impl<'a> WhiskerIter<'a> { ...@@ -137,6 +141,9 @@ 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_type => {
...@@ -148,7 +155,7 @@ impl<'a> WhiskerIter<'a> { ...@@ -148,7 +155,7 @@ impl<'a> WhiskerIter<'a> {
} }
} }
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> {
...@@ -172,7 +179,7 @@ impl<'a> ValidatedWhiskerIter<'a> { ...@@ -172,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> {
...@@ -184,7 +191,10 @@ impl<'a> Iterator for ValidatedWhiskerIter<'a> { ...@@ -184,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::*;
...@@ -194,7 +204,7 @@ mod tests { ...@@ -194,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();
...@@ -238,7 +248,7 @@ mod tests { ...@@ -238,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]
...@@ -248,11 +258,58 @@ mod tests { ...@@ -248,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), 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] #[test]
...@@ -287,43 +344,47 @@ mod tests { ...@@ -287,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);
...@@ -332,13 +393,15 @@ mod tests { ...@@ -332,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();
...@@ -352,44 +415,70 @@ mod tests { ...@@ -352,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
); );
} }
...@@ -407,6 +496,15 @@ mod tests { ...@@ -407,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;
use crate::{error::AppendNodeError, identity::Identity};
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Route { pub struct Route {
pub max_hops: u8, pub max_hops: u8,
...@@ -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,7 +171,6 @@ impl Route { ...@@ -73,7 +171,6 @@ 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))?;
...@@ -85,57 +182,320 @@ impl Route { ...@@ -85,57 +182,320 @@ impl Route {
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());
} }
} }
const WHITE: [u8; 16] = [ const START_STATE: u16 = 0xE9CF;
0xe9, 0xcf, 0x67, 0x20, 0x19, 0x1a, 0x07, 0xdc, 0xc0, 0x72, 0x79, 0x97, 0x51, 0xf7, 0xdd, 0x93,
];
pub(crate) fn whiten(data: &mut [u8]) { pub(crate) fn whiten(data: &mut [u8]) {
for (i, d) in data.iter_mut().enumerate() { let mut state = START_STATE;
*d ^= WHITE[i % 15];
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)] #[cfg(test)]
...@@ -17,7 +41,7 @@ mod tests { ...@@ -17,7 +41,7 @@ mod tests {
let mut data = [0; 64]; let mut data = [0; 64];
data[0..57] data[0..57]
.clone_from_slice(&b"Hello world! The quick brown fox jumped over the lazy dog"[..]); .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); whiten(&mut data);
assert_ne!(orig, data); assert_ne!(orig, data);
...@@ -25,4 +49,54 @@ mod tests { ...@@ -25,4 +49,54 @@ mod tests {
whiten(&mut data); whiten(&mut data);
assert_eq!(orig, 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);
}
} }