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
Commits on Source (17)
[package]
name = "ham-cats"
version = "0.2.0"
version = "0.2.2"
edition = "2021"
license = "MIT"
description = "Reference implementations for CATS, the ham radio protocol"
......@@ -14,6 +14,6 @@ bitvec = { version = "1.0.1", default-features = false }
crc = "3.0.1"
encoding_rs = { version = "0.8.33", default-features = false }
half = { version = "2.3.1", default-features = false }
labrador-ldpc = "1.1"
labrador-ldpc = "1.2.1"
paste = "1.0.14"
snafu = { version = "0.7.5", default-features = false }
......@@ -96,7 +96,7 @@ dependencies = [
[[package]]
name = "ham-cats"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"arrayvec",
"bitvec",
......@@ -133,9 +133,7 @@ dependencies = [
[[package]]
name = "labrador-ldpc"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ff4a0568f6322b64e06b6a6a0810e2f64670aa325f6e9dca24084e99917f459"
version = "1.1.1"
[[package]]
name = "libc"
......
......@@ -30,9 +30,14 @@ path = "fuzz_targets/fuzz_target_2.rs"
test = false
doc = false
[[bin]]
name = "fuzz_target_3"
path = "fuzz_targets/fuzz_target_3.rs"
test = false
doc = false
[[bin]]
name = "fuzz_target_4"
path = "fuzz_targets/fuzz_target_4.rs"
test = false
doc = false
#![no_main]
use libfuzzer_sys::fuzz_target;
use ham_cats::packet::Packet;
use std::convert::TryInto;
fuzz_target!(|data: &[u8]| {
// [u8] -> [i8]
let mut data: Vec<_> = data
.iter()
.map(|x| (u16::from(*x) as i16 - 128).try_into().unwrap())
.collect();
let mut buf = [0; 1024];
let _ = Packet::<1024>::fully_decode_soft::<8192, i8>(&mut data, &mut buf);
});
......@@ -4,28 +4,28 @@ use core::ops::{Deref, DerefMut};
pub struct BufferOverflow;
#[derive(Debug)]
pub struct Buffer<'a, const N: usize> {
data: &'a mut [u8; N],
pub struct Buffer<'a, const N: usize, T = u8> {
data: &'a mut [T; N],
i: usize,
}
impl<'a, const N: usize> Buffer<'a, N> {
impl<'a, const N: usize, T: Copy> Buffer<'a, N, T> {
/// Constructs a new `Buffer`.
/// `data` is the backing array.
/// `i` is the number of elements in `data` that contain data (and should thus be exposed by `Buffer`)
pub fn new(data: &'a mut [u8; N], i: usize) -> Self {
pub fn new(data: &'a mut [T; N], i: usize) -> Self {
assert!(i <= data.len());
Self { data, i }
}
pub fn new_full(data: &'a mut [u8; N]) -> Self {
pub fn new_full(data: &'a mut [T; N]) -> Self {
let i = data.len();
Self::new(data, i)
}
pub fn new_empty(data: &'a mut [u8; N]) -> Self {
pub fn new_empty(data: &'a mut [T; N]) -> Self {
Self::new(data, 0)
}
......@@ -33,7 +33,7 @@ impl<'a, const N: usize> Buffer<'a, N> {
N - self.i
}
pub fn try_push(&mut self, v: u8) -> Result<(), BufferOverflow> {
pub fn try_push(&mut self, v: T) -> Result<(), BufferOverflow> {
if self.i == N {
return Err(BufferOverflow);
}
......@@ -44,11 +44,11 @@ impl<'a, const N: usize> Buffer<'a, N> {
Ok(())
}
pub fn push(&mut self, v: u8) {
pub fn push(&mut self, v: T) {
self.try_push(v).unwrap();
}
pub fn try_extend_from_slice(&mut self, other: &[u8]) -> Result<(), BufferOverflow> {
pub fn try_extend_from_slice(&mut self, other: &[T]) -> Result<(), BufferOverflow> {
if self.remaining_capacity() < other.len() {
return Err(BufferOverflow);
}
......@@ -59,11 +59,11 @@ impl<'a, const N: usize> Buffer<'a, N> {
Ok(())
}
pub fn extend(&mut self, other: &[u8]) {
pub fn extend(&mut self, other: &[T]) {
self.try_extend_from_slice(other).unwrap();
}
pub fn pop(&mut self) -> Option<u8> {
pub fn pop(&mut self) -> Option<T> {
if self.i == 0 {
return None;
}
......@@ -92,7 +92,7 @@ impl<'a, const N: usize> Buffer<'a, N> {
self.i -= delta;
}
pub fn clone_backing<'b>(&self, buf: &'b mut [u8; N]) -> Buffer<'b, N> {
pub fn clone_backing<'b>(&self, buf: &'b mut [T; N]) -> Buffer<'b, N, T> {
let mut out = Buffer::new_empty(buf);
out.extend(self);
......@@ -101,22 +101,22 @@ impl<'a, const N: usize> Buffer<'a, N> {
}
}
impl<'a, const N: usize> From<&'a mut [u8; N]> for Buffer<'a, N> {
fn from(data: &'a mut [u8; N]) -> Self {
impl<'a, const N: usize, T> From<&'a mut [T; N]> for Buffer<'a, N, T> {
fn from(data: &'a mut [T; N]) -> Self {
Self { data, i: 0 }
}
}
impl<'a, const N: usize> Deref for Buffer<'a, N> {
type Target = [u8];
impl<const N: usize, T> Deref for Buffer<'_, N, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
&self.data[..self.i]
}
}
impl<'a, const N: usize> DerefMut for Buffer<'a, N> {
fn deref_mut(&mut self) -> &mut [u8] {
impl<const N: usize, T> DerefMut for Buffer<'_, N, T> {
fn deref_mut(&mut self) -> &mut [T] {
&mut self.data[..self.i]
}
}
use bitvec::prelude::*;
use labrador_ldpc::decoder::DecodeFrom;
use crate::{
buffer::{Buffer, BufferOverflow},
......@@ -60,9 +61,34 @@ pub(crate) fn uninterleave<const N: usize>(
Ok(())
}
pub(crate) fn uninterleave_soft<const N: usize, T: DecodeFrom>(
data: &[T],
out: &mut Buffer<N, T>,
) -> Result<(), BufferOverflow> {
for _ in 0..data.len() {
out.try_push(T::zero())?;
}
let mut out_i = 0;
for i in 0..32 {
for j in (0..data.len()).step_by(32) {
if i + j >= data.len() {
continue;
}
out[i + j] = data[out_i];
out_i += 1;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::soft_bit::FromHardBit;
#[test]
fn interleaver_works() {
......@@ -84,4 +110,34 @@ mod tests {
assert_eq!(*orig, *uninterleaved);
}
#[test]
fn hard_interleave_soft_uninterleave() {
let mut data = [0x84, 0x73, 0x12, 0xA3, 0xFF, 0x00, 0xC2, 0x1B, 0x77];
let orig = Buffer::new_full(&mut data);
let mut interleaved = [0; 10];
let mut interleaved = Buffer::new(&mut interleaved, 0);
interleaved.push(b'H');
interleave(&orig, &mut interleaved).unwrap();
let mut soft_interleaved = [0.0; 10 * 8];
for (i, b) in interleaved.iter().enumerate() {
for j in 0..8 {
soft_interleaved[8 * i + j] = f32::from_hard_bit(b & (1 << (7 - j)) > 0);
}
}
let mut uninterleaved = [0.0; 10 * 8];
let mut uninterleaved = Buffer::new(&mut uninterleaved, 0);
uninterleave_soft(&soft_interleaved[8..], &mut uninterleaved).unwrap();
assert_eq!(orig.len() * 8, uninterleaved.len());
for (i, b) in orig.iter().enumerate() {
for j in 0..8 {
assert_eq!(uninterleaved[8 * i + j].hard_bit(), *b & (1 << (7 - j)) > 0);
}
}
}
}
use labrador_ldpc::LDPCCode;
use labrador_ldpc::{decoder::DecodeFrom, LDPCCode};
use crate::{buffer::Buffer, error::EncodeError};
......@@ -36,6 +36,27 @@ macro_rules! dec_chunk {
};
}
macro_rules! dec_chunk_soft {
($d:ident, $p:ident, $w:ident, $w_u8:ident, $out:ident, $t:ident, $n:literal) => {
::paste::paste! {
let code_data = &mut $d[..$n];
let code_parity = &$p.get_mut(..$n)?;
let mut input = [T::zero(); $n * 2];
input[..$n].copy_from_slice(code_data);
input[$n..].copy_from_slice(code_parity);
const CODE: LDPCCode = LDPCCode::[<$t>];
let mut out_tmp = [0; CODE.output_len()];
CODE.decode_ms(&input, &mut out_tmp, &mut $w[..CODE.decode_ms_working_len()], &mut $w_u8[..CODE.decode_ms_working_u8_len()], 16);
$out.try_extend_from_slice(&out_tmp[..$n/8]).ok()?;
$d = &mut $d[$n..];
$p = &mut $p[$n..];
}
};
}
// On failure this still modifies the data array!
pub(crate) fn encode<const N: usize>(data: &mut Buffer<N>) -> Result<(), EncodeError> {
let mut i = 0;
......@@ -153,9 +174,113 @@ pub(crate) fn decode<const N: usize>(data_av: &mut Buffer<N>) -> Option<()> {
Some(())
}
pub(crate) fn decode_soft<const N: usize, const M: usize, T: DecodeFrom>(
data_av: &mut Buffer<N, T>,
out: &mut Buffer<M>,
) -> Option<()> {
if data_av.len() % 8 != 0 {
return None;
}
if data_av.len() < 16 {
return None;
}
let len: usize = len_from_soft(data_av[(data_av.len() - 16)..].try_into().unwrap()).into();
if len * 8 + 16 >= data_av.len() {
return None;
}
let data_len = data_av.len().checked_sub(16)?;
let data_av = data_av.get_mut(..data_len)?;
let (mut data, mut parity) = data_av.split_at_mut(len * 8);
let mut working = [T::zero(); LDPCCode::TM8192.decode_ms_working_len()];
let mut working_u8 = [0; LDPCCode::TM8192.decode_ms_working_u8_len()];
loop {
match data.len() {
4096.. => {
dec_chunk_soft!(data, parity, working, working_u8, out, TM8192, 4096);
}
1024.. => {
dec_chunk_soft!(data, parity, working, working_u8, out, TM2048, 1024);
}
256.. => {
dec_chunk_soft!(data, parity, working, working_u8, out, TC512, 256);
}
128.. => {
dec_chunk_soft!(data, parity, working, working_u8, out, TC256, 128);
}
64.. => {
dec_chunk_soft!(data, parity, working, working_u8, out, TC128, 64);
}
0 => break,
_ => {
// Extra bits are padded with 0xAA
// We need to tell the soft decoder that these bits can't have flipped
// So we use T::maxval
let mut code_data = [
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
]
.map(|x| if x > 0 { -T::maxval() } else { T::maxval() });
code_data[..data.len()].copy_from_slice(data);
let code_parity = &parity.get_mut(..64)?;
let mut input = [T::zero(); 128];
input[..64].copy_from_slice(&code_data);
input[64..].copy_from_slice(code_parity);
let mut tmp_out = [0; LDPCCode::TC128.output_len()];
LDPCCode::TC128.decode_ms(
&input,
&mut tmp_out,
&mut working[..LDPCCode::TC128.decode_ms_working_len()],
&mut working_u8[..LDPCCode::TC128.decode_ms_working_u8_len()],
16,
);
out.try_extend_from_slice(&tmp_out[..(data.len() / 8)])
.ok()?;
data = &mut data[..0];
parity = &mut parity[..0];
}
}
}
Some(())
}
fn len_from_soft<T: DecodeFrom>(bits: &[T; 16]) -> u16 {
let mut upper = 0;
for b in &bits[0..8] {
upper <<= 1;
upper |= u8::from(b.hard_bit());
}
let mut lower = 0;
for b in &bits[8..] {
lower <<= 1;
lower |= u8::from(b.hard_bit());
}
u16::from_le_bytes([upper, lower])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::soft_bit::FromHardBit;
use bitvec::{order::Msb0, view::BitView};
#[test]
fn len_test() {
......@@ -230,4 +355,30 @@ mod tests {
assert_eq!(*orig, *data);
}
#[test]
fn basic_encode_decode_soft() {
let mut buf = [0; 8191];
let mut buf2 = [0; 8191];
let mut data = Buffer::new_empty(&mut buf);
for _ in 0..50 {
data.extend(b"This is a test packet. jsalksjd093809324JASLD:LKD*#$)(*#@)");
}
let orig = data.clone_backing(&mut buf2);
encode(&mut data).unwrap();
assert_ne!(*orig, *data);
let mut soft = [0.0; 8191 * 8];
let mut soft = Buffer::new_empty(&mut soft);
for b in data.view_bits::<Msb0>() {
soft.push(f32::from_hard_bit(*b));
}
let mut out = [0; 8191];
let mut out = Buffer::new_empty(&mut out);
decode_soft(&mut soft, &mut out).unwrap();
assert_eq!(*orig, *out);
}
}
#![no_std]
#![cfg_attr(not(test), no_std)]
pub mod buffer;
pub mod error;
......@@ -6,6 +6,7 @@ pub mod identity;
pub mod interleaver;
pub mod ldpc;
pub mod packet;
pub mod soft_bit;
pub mod whisker;
pub mod whitener;
......
use core::fmt::Debug;
use crc::{Crc, CRC_16_IBM_SDLC};
use labrador_ldpc::decoder::DecodeFrom;
use crate::{
buffer::{Buffer, BufferOverflow},
......@@ -168,7 +169,7 @@ impl<'a, const N: usize> Packet<'a, N> {
/// Encodes packet for transmission on the air.
/// Includes the data length L, but does not include the preamble or sync word.
pub fn fully_encode(self, out: &mut Buffer<N>) -> Result<(), EncodeError> {
pub fn fully_encode<const M: usize>(self, out: &mut Buffer<M>) -> Result<(), EncodeError> {
let mut data = self.semi_encode().map_err(|(err, _)| err)?;
whitener::whiten(&mut data);
ldpc::encode(&mut data)?;
......@@ -183,7 +184,7 @@ impl<'a, const N: usize> Packet<'a, N> {
/// Decodes packet that was received over the air.
/// Packet shouldn't have preamble, sync word, or data langth L.
/// Expects bytes in the `buf`
/// Expects bytes in `data`
/// `buf` is used as the backing buffer for the Packet
pub fn fully_decode(data: &[u8], buf: &'a mut [u8; N]) -> Result<Self, DecodeError> {
let mut buf = Buffer::new_empty(buf);
......@@ -194,6 +195,22 @@ impl<'a, const N: usize> Packet<'a, N> {
Self::semi_decode(buf)
}
/// Expects soft bits in `data`. Bits should be LLR, with positive numbers more likely to be 0.
/// Returns `DecodeError::Overflow` if `M` is less than `data.len()`.
pub fn fully_decode_soft<const M: usize, T: DecodeFrom>(
data: &mut [T],
buf: &'a mut [u8; N],
) -> Result<Self, DecodeError> {
let mut out = [T::zero(); M];
let mut out = Buffer::new_empty(&mut out);
interleaver::uninterleave_soft(data, &mut out).map_err(|_| DecodeError::Overflow)?;
let mut buf = Buffer::new_empty(buf);
ldpc::decode_soft(&mut out, &mut buf).ok_or(DecodeError::LdpcError)?;
whitener::whiten(&mut buf);
Self::semi_decode(buf)
}
pub fn iter(&self) -> ValidatedWhiskerIter {
ValidatedWhiskerIter::new(&self.buf)
}
......@@ -362,7 +379,7 @@ impl<'a, const N: usize> Packet<'a, N> {
}
}
impl<'a, const N: usize> Debug for Packet<'a, N> {
impl<const N: usize> Debug for Packet<'_, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_list()
.entries(ValidatedWhiskerIter::new(&self.buf))
......@@ -390,11 +407,11 @@ fn try_lock<'a, const N: usize, T, E, F: Fn(&mut Buffer<'a, N>) -> Result<T, E>>
#[cfg(test)]
mod tests {
use arrayvec::ArrayString;
use crate::whisker::NodeInfoBuilder;
use super::*;
use crate::soft_bit::FromHardBit;
use crate::whisker::NodeInfoBuilder;
use arrayvec::ArrayString;
use bitvec::{order::Msb0, view::BitView};
#[test]
fn dest() {
......@@ -519,6 +536,60 @@ mod tests {
assert_eq!(comment, packet2.comment(&mut buf).unwrap());
}
#[test]
fn full_e2e_soft_decode() {
let comment = "Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.";
let mut buf = [0; 4096];
let mut packet = Packet::new(&mut buf);
packet
.add_identification(Identification {
icon: 123,
callsign: ArrayString::from("ABCXYZ_LONG_CALL").unwrap(),
ssid: 43,
})
.unwrap();
packet.add_comment(comment).unwrap();
let res = packet.add_identification(Identification {
icon: 456,
callsign: ArrayString::from("NOPE").unwrap(),
ssid: 0,
});
assert!(matches!(res, Err(EncodeError::DuplicateData)));
let mut buf2 = [0; 4096];
let mut fully = Buffer::new_empty(&mut buf2);
packet.fully_encode(&mut fully).unwrap();
fully[40] ^= 0x55;
fully[844] ^= 0x7B;
let mut soft = [0.0; 8191 * 8];
let mut soft = Buffer::new_empty(&mut soft);
for b in fully.view_bits::<Msb0>().iter() {
soft.push(f32::from_hard_bit(*b));
}
let soft = &mut soft[16..];
let mut buf3 = [0; 8191];
// exclude length
let packet2: Packet<8191> =
Packet::fully_decode_soft::<{ 8191 * 8 }, _>(soft, &mut buf3).unwrap();
assert_eq!(
Identification {
icon: 123,
callsign: ArrayString::from("ABCXYZ_LONG_CALL").unwrap(),
ssid: 43,
},
packet2.identification().unwrap()
);
let mut buf = [0; 1024];
assert_eq!(comment, packet2.comment(&mut buf).unwrap());
}
#[test]
fn node_info_e2e() {
let mut buf = [0; 4096];
......@@ -717,4 +788,31 @@ mod tests {
let _ = Packet::<1024>::decode(buf);
}
}
#[test]
fn fully_decode_soft_fuzz_tests() {
// When adding to this, don't forget to do the u8 -> i8 conversion
let cases = [
&mut [
-39, -39, -39, -118, -58, -58, -58, -58, -89, -39, -118, -58, -58, -58, 34, 34, 34,
34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, -58, -58,
127, 81, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127, -86, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, -58, -58, 127,
81, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 34, 34, 34, 34, 34, 34, 34,
34, 34, 34, 34, -58, -58, 127, 81, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, -128, -128, -128, -128, -128, -128, -128, -128, -128,
-128, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, -128, -128,
][..],
&mut [
-73, -73, -73, -73, -75, -76, -73, -73, -73, -73, -73, -73, -73, -73, 73, 72, 72,
72, 72, 72, 72, 62, -73, -118, 120, 127, 127, 121, 127, 112, 127, 127,
],
];
for data in cases {
let mut buf = [0; 1024];
let _ = Packet::<1024>::fully_decode_soft::<8192, i8>(data, &mut buf);
}
}
}
pub use labrador_ldpc::decoder::DecodeFrom;
// Less than zero = 1 bit
// Greater than zero = 0 bit
pub trait FromHardBit {
fn from_hard_bit(bit: bool) -> Self;
}
impl<T: DecodeFrom> FromHardBit for T {
fn from_hard_bit(bit: bool) -> Self {
if bit {
-Self::one()
} else {
Self::one()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn e2e() {
assert!(f32::from_hard_bit(true).hard_bit());
assert!(!f32::from_hard_bit(false).hard_bit());
assert!(f64::from_hard_bit(true).hard_bit());
assert!(!f64::from_hard_bit(false).hard_bit());
assert!(i8::from_hard_bit(true).hard_bit());
assert!(!i8::from_hard_bit(false).hard_bit());
assert!(i16::from_hard_bit(true).hard_bit());
assert!(!i16::from_hard_bit(false).hard_bit());
assert!(i32::from_hard_bit(true).hard_bit());
assert!(!i32::from_hard_bit(false).hard_bit());
}
}
......@@ -36,7 +36,7 @@ impl Destination {
}
pub fn ack_num(&self) -> u8 {
self.ack
self.ack & !(1 << 7)
}
pub fn callsign(&self) -> &str {
......
use core::fmt::{Debug, Display};
use half::f16;
#[derive(Debug, PartialEq, Clone)]
#[derive(PartialEq, Clone)]
pub struct Gps {
latitude: i32,
longitude: i32,
......@@ -100,6 +101,27 @@ 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;
......@@ -135,4 +157,11 @@ mod tests {
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 }")
}
}
......@@ -155,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> {
......@@ -179,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> {
......@@ -496,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";
......
......@@ -9,6 +9,11 @@ pub struct NodeInfo {
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 {
......@@ -59,6 +64,32 @@ impl NodeInfo {
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;
......@@ -128,6 +159,38 @@ impl NodeInfo {
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..]);
......@@ -141,17 +204,17 @@ impl NodeInfo {
let mut i = 4;
if bitmask & 1 > 0 {
builder = builder.hardware_id(u16::from_le_bytes([*data.get(i)?, *data.get(i + 1)?]));
builder.hardware_id = Some(u16::from_le_bytes([*data.get(i)?, *data.get(i + 1)?]));
i += 2;
}
if bitmask & 2 > 0 {
builder = builder.software_id(*data.get(i)?);
builder.software_id = Some(*data.get(i)?);
i += 1;
}
if bitmask & 4 > 0 {
builder = builder.uptime(u32::from_le_bytes([
builder.uptime = Some(u32::from_le_bytes([
*data.get(i)?,
*data.get(i + 1)?,
*data.get(i + 2)?,
......@@ -162,7 +225,7 @@ impl NodeInfo {
}
if bitmask & 8 > 0 {
builder = builder.antenna_height(*data.get(i)?);
builder.antenna_height = Some(*data.get(i)?);
i += 1;
}
......@@ -182,14 +245,44 @@ impl NodeInfo {
}
if bitmask & 128 > 0 {
builder = builder.xcvr_temperature(i8::from_le_bytes([*data.get(i)?]));
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())
}
}
......@@ -205,6 +298,11 @@ pub struct NodeInfoBuilder {
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 {
......@@ -219,6 +317,11 @@ impl NodeInfoBuilder {
voltage,
xcvr_temperature,
battery_charge,
altitude,
balloon,
ambient_temperature,
ambient_humidity,
ambient_pressure,
} = self;
NodeInfo {
......@@ -231,6 +334,11 @@ impl NodeInfoBuilder {
voltage,
xcvr_temperature,
battery_charge,
altitude,
balloon,
ambient_temperature,
ambient_humidity,
ambient_pressure,
}
}
......@@ -294,6 +402,43 @@ impl NodeInfoBuilder {
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)]
......
......@@ -232,7 +232,7 @@ impl<'a> PastHop<'a> {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
struct UntrustedRouteIter<'a> {
route: &'a Route,
i: usize,
......@@ -312,6 +312,7 @@ impl<'a> Iterator for UntrustedRouteIter<'a> {
}
}
#[derive(Clone)]
pub struct RouteIter<'a> {
iter: UntrustedRouteIter<'a>,
}
......
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)]
......@@ -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);
}
}