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
Select Git revision
  • master
1 result

Target

Select target project
No results found
Select Git revision
  • master
1 result
Show changes

Commits on Source 24

19 files
+ 1042
269
Compare changes
  • Side-by-side
  • Inline

Files

+2 −2
Original line number Original line Diff line number Diff line
[package]
[package]
name = "ham-cats"
name = "ham-cats"
version = "0.1.0"
version = "0.2.2"
edition = "2021"
edition = "2021"
license = "MIT"
license = "MIT"
description = "Reference implementations for CATS, the ham radio protocol"
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"
crc = "3.0.1"
encoding_rs = { version = "0.8.33", default-features = false }
encoding_rs = { version = "0.8.33", default-features = false }
half = { version = "2.3.1", default-features = false }
half = { version = "2.3.1", default-features = false }
labrador-ldpc = "1.1"
labrador-ldpc = "1.2.1"
paste = "1.0.14"
paste = "1.0.14"
snafu = { version = "0.7.5", default-features = false }
snafu = { version = "0.7.5", default-features = false }

LICENSE.md

0 → 100644
+26 −0
Original line number Original line Diff line number Diff line
The MIT License (MIT)
=====================

Copyright © 2023 CATS

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
+2 −4
Original line number Original line Diff line number Diff line
@@ -96,7 +96,7 @@ dependencies = [


[[package]]
[[package]]
name = "ham-cats"
name = "ham-cats"
version = "0.1.0"
version = "0.2.0"
dependencies = [
dependencies = [
 "arrayvec",
 "arrayvec",
 "bitvec",
 "bitvec",
@@ -133,9 +133,7 @@ dependencies = [


[[package]]
[[package]]
name = "labrador-ldpc"
name = "labrador-ldpc"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ff4a0568f6322b64e06b6a6a0810e2f64670aa325f6e9dca24084e99917f459"


[[package]]
[[package]]
name = "libc"
name = "libc"
+6 −1
Original line number Original line Diff line number Diff line
@@ -30,9 +30,14 @@ path = "fuzz_targets/fuzz_target_2.rs"
test = false
test = false
doc = false
doc = false



[[bin]]
[[bin]]
name = "fuzz_target_3"
name = "fuzz_target_3"
path = "fuzz_targets/fuzz_target_3.rs"
path = "fuzz_targets/fuzz_target_3.rs"
test = false
test = false
doc = false
doc = false

[[bin]]
name = "fuzz_target_4"
path = "fuzz_targets/fuzz_target_4.rs"
test = false
doc = false
+16 −0
Original line number Original line Diff line number Diff line
#![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);
});
+18 −18
Original line number Original line Diff line number Diff line
@@ -4,28 +4,28 @@ use core::ops::{Deref, DerefMut};
pub struct BufferOverflow;
pub struct BufferOverflow;


#[derive(Debug)]
#[derive(Debug)]
pub struct Buffer<'a, const N: usize> {
pub struct Buffer<'a, const N: usize, T = u8> {
    data: &'a mut [u8; N],
    data: &'a mut [T; N],
    i: usize,
    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`.
    /// Constructs a new `Buffer`.
    /// `data` is the backing array.
    /// `data` is the backing array.
    /// `i` is the number of elements in `data` that contain data (and should thus be exposed by `Buffer`)
    /// `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());
        assert!(i <= data.len());


        Self { data, i }
        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();
        let i = data.len();


        Self::new(data, i)
        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)
        Self::new(data, 0)
    }
    }


@@ -33,7 +33,7 @@ impl<'a, const N: usize> Buffer<'a, N> {
        N - self.i
        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 {
        if self.i == N {
            return Err(BufferOverflow);
            return Err(BufferOverflow);
        }
        }
@@ -44,11 +44,11 @@ impl<'a, const N: usize> Buffer<'a, N> {
        Ok(())
        Ok(())
    }
    }


    pub fn push(&mut self, v: u8) {
    pub fn push(&mut self, v: T) {
        self.try_push(v).unwrap();
        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() {
        if self.remaining_capacity() < other.len() {
            return Err(BufferOverflow);
            return Err(BufferOverflow);
        }
        }
@@ -59,11 +59,11 @@ impl<'a, const N: usize> Buffer<'a, N> {
        Ok(())
        Ok(())
    }
    }


    pub fn extend(&mut self, other: &[u8]) {
    pub fn extend(&mut self, other: &[T]) {
        self.try_extend_from_slice(other).unwrap();
        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 {
        if self.i == 0 {
            return None;
            return None;
        }
        }
@@ -92,7 +92,7 @@ impl<'a, const N: usize> Buffer<'a, N> {
        self.i -= delta;
        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);
        let mut out = Buffer::new_empty(buf);


        out.extend(self);
        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> {
impl<'a, const N: usize, T> From<&'a mut [T; N]> for Buffer<'a, N, T> {
    fn from(data: &'a mut [u8; N]) -> Self {
    fn from(data: &'a mut [T; N]) -> Self {
        Self { data, i: 0 }
        Self { data, i: 0 }
    }
    }
}
}


impl<'a, const N: usize> Deref for Buffer<'a, N> {
impl<const N: usize, T> Deref for Buffer<'_, N, T> {
    type Target = [u8];
    type Target = [T];


    fn deref(&self) -> &Self::Target {
    fn deref(&self) -> &Self::Target {
        &self.data[..self.i]
        &self.data[..self.i]
    }
    }
}
}


impl<'a, const N: usize> DerefMut for Buffer<'a, N> {
impl<const N: usize, T> DerefMut for Buffer<'_, N, T> {
    fn deref_mut(&mut self) -> &mut [u8] {
    fn deref_mut(&mut self) -> &mut [T] {
        &mut self.data[..self.i]
        &mut self.data[..self.i]
    }
    }
}
}

src/identity.rs

0 → 100644
+19 −0
Original line number Original line Diff line number Diff line
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct Identity<'a> {
    callsign: &'a str,
    ssid: u8,
}

impl<'a> Identity<'a> {
    pub fn new(callsign: &'a str, ssid: u8) -> Self {
        Self { callsign, ssid }
    }

    pub fn callsign(&self) -> &'a str {
        self.callsign
    }

    pub fn ssid(&self) -> u8 {
        self.ssid
    }
}
Original line number Original line Diff line number Diff line
use bitvec::prelude::*;
use bitvec::prelude::*;
use labrador_ldpc::decoder::DecodeFrom;


use crate::{
use crate::{
    buffer::{Buffer, BufferOverflow},
    buffer::{Buffer, BufferOverflow},
@@ -60,9 +61,34 @@ pub(crate) fn uninterleave<const N: usize>(
    Ok(())
    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)]
#[cfg(test)]
mod tests {
mod tests {
    use super::*;
    use super::*;
    use crate::soft_bit::FromHardBit;


    #[test]
    #[test]
    fn interleaver_works() {
    fn interleaver_works() {
@@ -84,4 +110,34 @@ mod tests {


        assert_eq!(*orig, *uninterleaved);
        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);
            }
        }
    }
}
}
+152 −1
Original line number Original line Diff line number Diff line
use labrador_ldpc::LDPCCode;
use labrador_ldpc::{decoder::DecodeFrom, LDPCCode};


use crate::{buffer::Buffer, error::EncodeError};
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!
// On failure this still modifies the data array!
pub(crate) fn encode<const N: usize>(data: &mut Buffer<N>) -> Result<(), EncodeError> {
pub(crate) fn encode<const N: usize>(data: &mut Buffer<N>) -> Result<(), EncodeError> {
    let mut i = 0;
    let mut i = 0;
@@ -153,9 +174,113 @@ pub(crate) fn decode<const N: usize>(data_av: &mut Buffer<N>) -> Option<()> {
    Some(())
    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)]
#[cfg(test)]
mod tests {
mod tests {
    use super::*;
    use super::*;
    use crate::soft_bit::FromHardBit;
    use bitvec::{order::Msb0, view::BitView};


    #[test]
    #[test]
    fn len_test() {
    fn len_test() {
@@ -230,4 +355,30 @@ mod tests {


        assert_eq!(*orig, *data);
        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);
    }
}
}
+3 −1
Original line number Original line Diff line number Diff line
#![no_std]
#![cfg_attr(not(test), no_std)]


pub mod buffer;
pub mod buffer;
pub mod error;
pub mod error;
pub mod identity;
pub mod interleaver;
pub mod interleaver;
pub mod ldpc;
pub mod ldpc;
pub mod packet;
pub mod packet;
pub mod soft_bit;
pub mod whisker;
pub mod whisker;
pub mod whitener;
pub mod whitener;


+120 −23
Original line number Original line Diff line number Diff line
use core::fmt::Debug;
use core::fmt::Debug;


use crc::{Crc, CRC_16_IBM_SDLC};
use crc::{Crc, CRC_16_IBM_SDLC};
use labrador_ldpc::decoder::DecodeFrom;


use crate::{
use crate::{
    buffer::{Buffer, BufferOverflow},
    buffer::{Buffer, BufferOverflow},
    error::{CommentError, DecodeError, DigipeatError, EncodeError, PacketRouteAppendError},
    error::{CommentError, DecodeError, DigipeatError, EncodeError, PacketRouteAppendError},
    identity::Identity,
    interleaver, ldpc, utf8,
    interleaver, ldpc, utf8,
    whisker::{
    whisker::{
        Arbitrary, Comment, Destination, Gps, Identification, NodeInfo, Route, RouteNode,
        Arbitrary, Comment, Destination, Gps, Identification, NodeInfo, PastHop, Route, RouteHop,
        Timestamp, ValidatedWhiskerIter, Whisker, WhiskerIter, COMMENT_TYPE,
        Timestamp, ValidatedWhiskerIter, Whisker, WhiskerIter, COMMENT_TYPE,
    },
    },
    whitener,
    whitener,
@@ -167,7 +169,7 @@ impl<'a, const N: usize> Packet<'a, N> {


    /// Encodes packet for transmission on the air.
    /// Encodes packet for transmission on the air.
    /// Includes the data length L, but does not include the preamble or sync word.
    /// 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)?;
        let mut data = self.semi_encode().map_err(|(err, _)| err)?;
        whitener::whiten(&mut data);
        whitener::whiten(&mut data);
        ldpc::encode(&mut data)?;
        ldpc::encode(&mut data)?;
@@ -182,7 +184,7 @@ impl<'a, const N: usize> Packet<'a, N> {


    /// Decodes packet that was received over the air.
    /// Decodes packet that was received over the air.
    /// Packet shouldn't have preamble, sync word, or data langth L.
    /// 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
    /// `buf` is used as the backing buffer for the Packet
    pub fn fully_decode(data: &[u8], buf: &'a mut [u8; N]) -> Result<Self, DecodeError> {
    pub fn fully_decode(data: &[u8], buf: &'a mut [u8; N]) -> Result<Self, DecodeError> {
        let mut buf = Buffer::new_empty(buf);
        let mut buf = Buffer::new_empty(buf);
@@ -193,6 +195,22 @@ impl<'a, const N: usize> Packet<'a, N> {
        Self::semi_decode(buf)
        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 {
    pub fn iter(&self) -> ValidatedWhiskerIter {
        ValidatedWhiskerIter::new(&self.buf)
        ValidatedWhiskerIter::new(&self.buf)
    }
    }
@@ -274,7 +292,7 @@ impl<'a, const N: usize> Packet<'a, N> {


    /// Given the callsign and ssid of a node, should it digipeat this packet?
    /// Given the callsign and ssid of a node, should it digipeat this packet?
    /// Takes into account things such as if we've digipeated it already, the max hops, etc.
    /// Takes into account things such as if we've digipeated it already, the max hops, etc.
    pub fn should_digipeat(&self, callsign: &str, ssid: u8) -> Result<(), DigipeatError> {
    pub fn should_digipeat(&self, identity: Identity) -> Result<(), DigipeatError> {
        let route = match self.route() {
        let route = match self.route() {
            Some(x) => x,
            Some(x) => x,
            None => {
            None => {
@@ -283,7 +301,7 @@ impl<'a, const N: usize> Packet<'a, N> {
        };
        };


        if let Some(ident) = self.identification() {
        if let Some(ident) = self.identification() {
            if &ident.callsign == callsign && ident.ssid == ssid {
            if &ident.callsign == identity.callsign() && ident.ssid == identity.ssid() {
                return Err(DigipeatError::Us);
                return Err(DigipeatError::Us);
            }
            }
        }
        }
@@ -292,8 +310,9 @@ impl<'a, const N: usize> Packet<'a, N> {
        let cur_hops = route
        let cur_hops = route
            .iter()
            .iter()
            .filter(|r| match r {
            .filter(|r| match r {
                RouteNode::Internet => false,
                RouteHop::Internet => false,
                RouteNode::Identity(ident) => !ident.is_future(),
                RouteHop::Past(_) => true,
                RouteHop::Future(_) => false,
            })
            })
            .count();
            .count();


@@ -302,10 +321,9 @@ impl<'a, const N: usize> Packet<'a, N> {
        }
        }


        let already_digipeated = route.iter().any(|r| match r {
        let already_digipeated = route.iter().any(|r| match r {
            RouteNode::Internet => false,
            RouteHop::Internet => false,
            RouteNode::Identity(ident) => {
            RouteHop::Past(past_hop) => past_hop.identity() == identity,
                ident.callsign() == callsign && ident.ssid() == ssid && !ident.is_future()
            RouteHop::Future(_) => false,
            }
        });
        });


        if already_digipeated {
        if already_digipeated {
@@ -313,14 +331,12 @@ impl<'a, const N: usize> Packet<'a, N> {
        }
        }


        let next_node = route.iter().find_map(|r| match r {
        let next_node = route.iter().find_map(|r| match r {
            RouteNode::Identity(ident) if ident.is_future() => {
            RouteHop::Future(x) => Some(x),
                Some((ident.callsign(), ident.ssid()))
            }
            _ => None,
            _ => None,
        });
        });


        match next_node {
        match next_node {
            Some((rc, rs)) if rc != callsign || rs != ssid => Err(DigipeatError::SetDestiny),
            Some(ident) if ident != identity => Err(DigipeatError::SetDestiny),
            _ => Ok(()),
            _ => Ok(()),
        }
        }
    }
    }
@@ -334,7 +350,7 @@ impl<'a, const N: usize> Packet<'a, N> {
    ) -> Result<(), PacketRouteAppendError> {
    ) -> Result<(), PacketRouteAppendError> {
        let mut route = self.route().ok_or(PacketRouteAppendError::NoRouteWhisker)?;
        let mut route = self.route().ok_or(PacketRouteAppendError::NoRouteWhisker)?;
        route
        route
            .append_node(callsign, ssid, rssi)
            .append_hop(PastHop::new(Identity::new(callsign, ssid), rssi))
            .map_err(|error| PacketRouteAppendError::Route { error })?;
            .map_err(|error| PacketRouteAppendError::Route { error })?;
        self.clear_route();
        self.clear_route();


@@ -363,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 {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_list()
        f.debug_list()
            .entries(ValidatedWhiskerIter::new(&self.buf))
            .entries(ValidatedWhiskerIter::new(&self.buf))
@@ -391,11 +407,11 @@ fn try_lock<'a, const N: usize, T, E, F: Fn(&mut Buffer<'a, N>) -> Result<T, E>>


#[cfg(test)]
#[cfg(test)]
mod tests {
mod tests {
    use arrayvec::ArrayString;

    use crate::whisker::NodeInfoBuilder;

    use super::*;
    use super::*;
    use crate::soft_bit::FromHardBit;
    use crate::whisker::NodeInfoBuilder;
    use arrayvec::ArrayString;
    use bitvec::{order::Msb0, view::BitView};


    #[test]
    #[test]
    fn dest() {
    fn dest() {
@@ -417,7 +433,7 @@ mod tests {
    fn route_clear() {
    fn route_clear() {
        let mut buf = [0; 1024];
        let mut buf = [0; 1024];
        let mut p: Packet<1024> = Packet::new(&mut buf);
        let mut p: Packet<1024> = Packet::new(&mut buf);
        p.add_identification(Identification::new(123, "call", 43).unwrap())
        p.add_identification(Identification::new("call", 43, 123).unwrap())
            .unwrap();
            .unwrap();
        let mut r = Route::new(8);
        let mut r = Route::new(8);
        r.push_internet().unwrap();
        r.push_internet().unwrap();
@@ -427,7 +443,7 @@ mod tests {
        p.clear_route();
        p.clear_route();


        assert_eq!(
        assert_eq!(
            Identification::new(123, "call", 43).unwrap(),
            Identification::new("call", 43, 123).unwrap(),
            p.identification().unwrap()
            p.identification().unwrap()
        );
        );
        assert_eq!(None, p.route());
        assert_eq!(None, p.route());
@@ -520,6 +536,60 @@ mod tests {
        assert_eq!(comment, packet2.comment(&mut buf).unwrap());
        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]
    #[test]
    fn node_info_e2e() {
    fn node_info_e2e() {
        let mut buf = [0; 4096];
        let mut buf = [0; 4096];
@@ -718,4 +788,31 @@ mod tests {
            let _ = Packet::<1024>::decode(buf);
            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);
        }
    }
}
}

src/soft_bit.rs

0 → 100644
+40 −0
Original line number Original line Diff line number Diff line
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());
    }
}
Original line number Original line Diff line number Diff line
@@ -36,7 +36,7 @@ impl Destination {
    }
    }


    pub fn ack_num(&self) -> u8 {
    pub fn ack_num(&self) -> u8 {
        self.ack
        self.ack & !(1 << 7)
    }
    }


    pub fn callsign(&self) -> &str {
    pub fn callsign(&self) -> &str {
Original line number Original line Diff line number Diff line
use core::fmt::{Debug, Display};
use half::f16;
use half::f16;


#[derive(Debug, PartialEq, Clone)]
#[derive(PartialEq, Clone)]
pub struct Gps {
pub struct Gps {
    latitude: i32,
    latitude: i32,
    longitude: i32,
    longitude: i32,
@@ -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 {
    }
    }


    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 {
        })
        })
    }
    }
}
}

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 }")
    }
}
Original line number Original line Diff line number Diff line
@@ -2,13 +2,13 @@ use arrayvec::ArrayString;


#[derive(Debug, PartialEq, Eq, Clone)]
#[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 {
+113 −51
Original line number Original line Diff line number Diff line
@@ -17,7 +17,7 @@ pub use self::{
    gps::Gps,
    gps::Gps,
    identification::Identification,
    identification::Identification,
    node_info::{NodeInfo, NodeInfoBuilder},
    node_info::{NodeInfo, NodeInfoBuilder},
    route::{Route, RouteIdentity, RouteIter, RouteNode},
    route::{PastHop, Route, RouteHop, RouteIter},
    timestamp::Timestamp,
    timestamp::Timestamp,
    unknown::Unknown,
    unknown::Unknown,
};
};
@@ -155,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> {
@@ -179,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> {
@@ -191,7 +191,10 @@ impl<'a> Iterator for ValidatedWhiskerIter<'a> {
mod tests {
mod tests {
    use half::f16;
    use half::f16;


    use crate::whisker::route::{RouteIdentity, RouteNode};
    use crate::{
        identity::Identity,
        whisker::route::{PastHop, RouteHop},
    };


    use super::*;
    use super::*;


@@ -201,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();
@@ -245,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]
@@ -255,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]
@@ -295,60 +345,46 @@ mod tests {
    fn route_push_and_iter() {
    fn route_push_and_iter() {
        let mut route = Route::new(34);
        let mut route = Route::new(34);
        route
        route
            .push_callsign(RouteIdentity::new("VE2XYZ", 23, None, false))
            .push_past(PastHop::new(Identity::new("VE2XYZ", 23), None))
            .unwrap();
            .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
        route.push_future(Identity::new("VE9AAAAA", 94)).unwrap();
            .push_callsign(RouteIdentity::new("VE9AAAAA", 94, None, true))
            .unwrap();
        // past after future - not allowed
        // past after future - not allowed
        assert!(route
        assert!(route
            .push_callsign(RouteIdentity::new("VE9AAAAA", 94, None, false))
            .push_past(PastHop::new(Identity::new("VE9AAAAA", 94), None))
            .is_none());
            .is_none());


        // too long
        // too long
        assert!(route
        assert!(route
            .push_callsign(RouteIdentity::new(
            .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,
				None,
                true,
            )).is_none());
            )).is_none());


        route
        route
            .push_callsign(RouteIdentity::new(
            .push_future(Identity::new("This is the last callsign", 0))
                "This is the last callsign",
                0,
                None,
                true,
            ))
            .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(RouteIdentity::new("VE2XYZ", 23, None, 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(RouteIdentity::new("VE9AAAAA", 94, None, true)),
            RouteHop::Future(Identity::new("VE9AAAAA", 94)),
            iter.next().unwrap()
            iter.next().unwrap()
        );
        );
        assert_eq!(
        assert_eq!(
            RouteNode::Identity(RouteIdentity::new(
            RouteHop::Future(Identity::new("This is the last callsign", 0)),
                "This is the last callsign",
                0,
                None,
                true
            )),
            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);
@@ -358,21 +394,14 @@ mod tests {
    fn route_e2e() {
    fn route_e2e() {
        let mut route = Route::new(34);
        let mut route = Route::new(34);
        route
        route
            .push_callsign(RouteIdentity::new("VE2XYZ", 23, Some(0.0), false))
            .push_past(PastHop::new(Identity::new("VE2XYZ", 23), Some(0.0)))
            .unwrap();
            .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_future(Identity::new("VE9AAAAA", 94)).unwrap();
        route
        route
            .push_callsign(RouteIdentity::new("VE9AAAAA", 94, Some(10.0), true))
            .push_future(Identity::new("This is the last callsign", 0))
            .unwrap();
        route
            .push_callsign(RouteIdentity::new(
                "This is the last callsign",
                0,
                Some(-159.0),
                true,
            ))
            .unwrap();
            .unwrap();
        route.push_internet().unwrap();
        route.push_internet().unwrap();


@@ -386,9 +415,12 @@ 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(RouteIdentity::new("VE1ABC", 0, Some(-96.0), false));
        ex1.push_past(PastHop::new(Identity::new("VE1ABC", 0), Some(-96.0)))
        ex1.push_callsign(RouteIdentity::new("VE2DEF", 234, Some(-13.0), false));
            .unwrap();
        ex1.push_callsign(RouteIdentity::new("VE3XYZ", 14, Some(-106.0), 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();
@@ -401,12 +433,32 @@ mod tests {
            &encoded
            &encoded
        );
        );


        let mut ex1_5 = Route::new(4);
        ex1_5
            .push_past(PastHop::new(Identity::new("VE1ABC", 0), Some(-96.0)))
            .unwrap();
        ex1_5.push_future(Identity::new("VE2DEF", 234)).unwrap();
        ex1_5.push_future(Identity::new("VE3XYZ", 14)).unwrap();

        let mut buf = [0; 256];
        let encoded = ex1_5.encode(&mut buf).unwrap();
        assert_eq!(
            &[
                0x1A, 0x04, 0x56, 0x45, 0x31, 0x41, 0x42, 0x43, 0xFF, 0x00, 0x60, 0x56, 0x45, 0x32,
                0x44, 0x45, 0x46, 0xFD, 0xEA, 0x56, 0x45, 0x33, 0x58, 0x59, 0x5A, 0xFD, 0x0E,
            ],
            &encoded
        );

        let mut ex2 = Route::new(3);
        let mut ex2 = Route::new(3);
        ex2.push_callsign(RouteIdentity::new("VE1ABC", 0, None, false));
        ex2.push_past(PastHop::new(Identity::new("VE1ABC", 0), None))
            .unwrap();
        ex2.push_internet();
        ex2.push_internet();
        ex2.push_callsign(RouteIdentity::new("VE2DEF", 234, Some(-86.5), false));
        ex2.push_past(PastHop::new(Identity::new("VE2DEF", 234), Some(-86.5)))
            .unwrap();
        ex2.push_internet();
        ex2.push_internet();
        ex2.push_callsign(RouteIdentity::new("VE3XYZ", 14, Some(-65.0), 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!(
@@ -419,7 +471,8 @@ mod tests {
        );
        );


        let mut ex3 = Route::new(0);
        let mut ex3 = Route::new(0);
        ex3.push_callsign(RouteIdentity::new("VE1ABC", 0, Some(-42.5), 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();


@@ -443,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";
Original line number Original line Diff line number Diff line
@@ -9,6 +9,11 @@ pub struct NodeInfo {
    voltage: Option<u8>,
    voltage: Option<u8>,
    xcvr_temperature: Option<i8>,
    xcvr_temperature: Option<i8>,
    battery_charge: Option<u8>,
    battery_charge: Option<u8>,
    altitude: Option<f32>,
    balloon: bool,
    ambient_temperature: Option<i8>,
    ambient_humidity: Option<u8>,
    ambient_pressure: Option<u16>,
}
}


impl NodeInfo {
impl NodeInfo {
@@ -59,6 +64,32 @@ impl NodeInfo {
        self.battery_charge.map(|b| b as f64 / 2.55)
        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]> {
    pub fn encode<'a>(&self, buf: &'a mut [u8]) -> Option<&'a [u8]> {
        let mut bitmask: u32 = 0;
        let mut bitmask: u32 = 0;


@@ -128,6 +159,38 @@ impl NodeInfo {
            i += 1;
            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[0] = (i - 1).try_into().ok()?;
        buf[1..4].copy_from_slice(&bitmask.to_be_bytes()[1..]);
        buf[1..4].copy_from_slice(&bitmask.to_be_bytes()[1..]);


@@ -141,17 +204,17 @@ impl NodeInfo {
        let mut i = 4;
        let mut i = 4;


        if bitmask & 1 > 0 {
        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;
            i += 2;
        }
        }


        if bitmask & 2 > 0 {
        if bitmask & 2 > 0 {
            builder = builder.software_id(*data.get(i)?);
            builder.software_id = Some(*data.get(i)?);
            i += 1;
            i += 1;
        }
        }


        if bitmask & 4 > 0 {
        if bitmask & 4 > 0 {
            builder = builder.uptime(u32::from_le_bytes([
            builder.uptime = Some(u32::from_le_bytes([
                *data.get(i)?,
                *data.get(i)?,
                *data.get(i + 1)?,
                *data.get(i + 1)?,
                *data.get(i + 2)?,
                *data.get(i + 2)?,
@@ -162,7 +225,7 @@ impl NodeInfo {
        }
        }


        if bitmask & 8 > 0 {
        if bitmask & 8 > 0 {
            builder = builder.antenna_height(*data.get(i)?);
            builder.antenna_height = Some(*data.get(i)?);
            i += 1;
            i += 1;
        }
        }


@@ -182,14 +245,44 @@ impl NodeInfo {
        }
        }


        if bitmask & 128 > 0 {
        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;
            i += 1;
        }
        }


        if bitmask & 256 > 0 {
        if bitmask & 256 > 0 {
            builder.battery_charge = Some(*data.get(i)?);
            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())
        Some(builder.build())
    }
    }
}
}
@@ -205,6 +298,11 @@ pub struct NodeInfoBuilder {
    voltage: Option<u8>,
    voltage: Option<u8>,
    xcvr_temperature: Option<i8>,
    xcvr_temperature: Option<i8>,
    battery_charge: Option<u8>,
    battery_charge: Option<u8>,
    altitude: Option<f32>,
    balloon: bool,
    ambient_temperature: Option<i8>,
    ambient_humidity: Option<u8>,
    ambient_pressure: Option<u16>,
}
}


impl NodeInfoBuilder {
impl NodeInfoBuilder {
@@ -219,6 +317,11 @@ impl NodeInfoBuilder {
            voltage,
            voltage,
            xcvr_temperature,
            xcvr_temperature,
            battery_charge,
            battery_charge,
            altitude,
            balloon,
            ambient_temperature,
            ambient_humidity,
            ambient_pressure,
        } = self;
        } = self;


        NodeInfo {
        NodeInfo {
@@ -231,6 +334,11 @@ impl NodeInfoBuilder {
            voltage,
            voltage,
            xcvr_temperature,
            xcvr_temperature,
            battery_charge,
            battery_charge,
            altitude,
            balloon,
            ambient_temperature,
            ambient_humidity,
            ambient_pressure,
        }
        }
    }
    }


@@ -294,6 +402,43 @@ impl NodeInfoBuilder {


        self
        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)]
#[cfg(test)]
+161 −151
Original line number Original line Diff line number Diff line
use arrayvec::ArrayVec;
use arrayvec::ArrayVec;


use crate::error::AppendNodeError;
use crate::{error::AppendNodeError, identity::Identity};


#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Route {
pub struct Route {
@@ -21,30 +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, ident: RouteIdentity) -> Option<()> {
        match hop {
        let len = ident.callsign.as_bytes().len() + 3;
            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 || ident.is_future;
        if self.has_future {
        if self.has_future && !ident.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(ident.callsign.as_bytes())
            .try_extend_from_slice(ident.callsign().as_bytes())
            .unwrap();
            .unwrap();
        self.path.push(if ident.is_future { 0xFD } else { 0xFF });
        self.path.push(0xFD);
        self.path.push(ident.ssid);
        self.path.push(ident.ssid());
        self.path.push(ident.rssi);


        Some(())
        Some(())
    }
    }
@@ -66,48 +99,54 @@ impl Route {
    /// I.e. replace future node if possible
    /// 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
    /// 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)
    /// (there's a future node that doesn't match)
    pub fn append_node(
    /// This only appends past hops.
        &mut self,
    pub fn append_hop(&mut self, new_hop: PastHop) -> Result<(), AppendNodeError> {
        callsign: &str,
        ssid: u8,
        rssi: Option<f64>,
    ) -> Result<(), AppendNodeError> {
        let mut new_route = Route::new(self.max_hops);
        let mut new_route = Route::new(self.max_hops);


        let mut already_inserted = false;
        let mut already_inserted = false;
        for rn in self.iter() {
        for rn in self.iter() {
            match rn {
            match rn {
                RouteNode::Internet => new_route
                RouteHop::Internet => new_route
                    .push_internet()
                    .push_internet()
                    .ok_or(AppendNodeError::RouteOverflow)?,
                    .ok_or(AppendNodeError::RouteOverflow)?,
                RouteNode::Identity(ident) => {
                RouteHop::Past(prev_past_hop) => {
                    let us = ident.callsign() == callsign && ident.ssid() == ssid;
                    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 ident.is_future() {
                    if us {
                    if us {
                        if already_inserted {
                        if already_inserted {
                            return Err(AppendNodeError::DuplicateNode);
                            return Err(AppendNodeError::DuplicateNode);
                        } else {
                        } else {
                            new_route
                            new_route
                                    .push_callsign(RouteIdentity::new(callsign, ssid, rssi, false));
                                .push_past(new_hop)
                                .ok_or(AppendNodeError::RouteOverflow)?;
                            already_inserted = true;
                            already_inserted = true;
                        }
                        }
                    } else if already_inserted {
                    } else if already_inserted {
                            new_route.push_callsign(ident);
                        new_route
                            .push_future(prev_ident)
                            .ok_or(AppendNodeError::RouteOverflow)?;
                    } else {
                    } else {
                        return Err(AppendNodeError::SetFuture);
                        return Err(AppendNodeError::SetFuture);
                    }
                    }
                    } else if us {
                        return Err(AppendNodeError::DuplicateNode);
                    } else {
                        new_route.push_callsign(ident);
                    }
                }
                }
            }
            }
        }
        }


        if !already_inserted {
        if !already_inserted {
            new_route.push_callsign(RouteIdentity::new(callsign, ssid, rssi, false));
            new_route
                .push_past(new_hop)
                .ok_or(AppendNodeError::RouteOverflow)?;
        }
        }


        if new_route.iter().count() > usize::from(new_route.max_hops) {
        if new_route.iter().count() > usize::from(new_route.max_hops) {
@@ -157,41 +196,31 @@ impl Route {
    }
    }
}
}


#[derive(Debug, Eq, PartialEq)]
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum RouteNode<'a> {
pub enum RouteHop<'a> {
    Internet,
    Internet,
    Identity(RouteIdentity<'a>),
    Past(PastHop<'a>),
    Future(Identity<'a>),
}
}


#[derive(Debug, Eq, PartialEq)]
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct RouteIdentity<'a> {
pub struct PastHop<'a> {
    callsign: &'a str,
    identity: Identity<'a>,
    ssid: u8,
    rssi: u8,
    rssi: u8,
    is_future: bool,
}
}


impl<'a> RouteIdentity<'a> {
impl<'a> PastHop<'a> {
    pub fn new(callsign: &'a str, ssid: u8, rssi: Option<f64>, is_future: bool) -> Self {
    pub fn new(identity: Identity<'a>, rssi: Option<f64>) -> Self {
        let rssi = match rssi {
        let rssi = match rssi {
            Some(rssi) => (rssi * 1.5 + 240.0).max(1.0) as u8,
            Some(rssi) => (rssi * 1.5 + 240.0).max(1.0) as u8,
            None => 0,
            None => 0,
        };
        };


        Self {
        Self { identity, rssi }
            callsign,
            ssid,
            rssi,
            is_future,
        }
    }

    pub fn callsign(&self) -> &'a str {
        self.callsign
    }
    }


    pub fn ssid(&self) -> u8 {
    pub fn identity(&self) -> Identity {
        self.ssid
        self.identity
    }
    }


    pub fn rssi(&self) -> Option<f64> {
    pub fn rssi(&self) -> Option<f64> {
@@ -201,13 +230,9 @@ impl<'a> RouteIdentity<'a> {
            Some(((self.rssi as f64) - 240.0) / 1.5)
            Some(((self.rssi as f64) - 240.0) / 1.5)
        }
        }
    }
    }

    pub fn is_future(&self) -> bool {
        self.is_future
    }
}
}


#[derive(Debug)]
#[derive(Debug, Clone)]
struct UntrustedRouteIter<'a> {
struct UntrustedRouteIter<'a> {
    route: &'a Route,
    route: &'a Route,
    i: usize,
    i: usize,
@@ -226,12 +251,12 @@ impl<'a> UntrustedRouteIter<'a> {
    }
    }


    // Returns Err(()) if invalid
    // Returns Err(()) if invalid
    fn maybe_next(&mut self) -> Result<RouteNode<'a>, ()> {
    fn maybe_next(&mut self) -> Result<RouteHop<'a>, ()> {
        let i_start = self.i;
        let i_start = self.i;
        self.i += 1;
        self.i += 1;


        if *self.route.path.get(i_start).ok_or(())? == 0xFE {
        if *self.route.path.get(i_start).ok_or(())? == 0xFE {
            return Ok(RouteNode::Internet);
            return Ok(RouteHop::Internet);
        }
        }


        while *self.route.path.get(self.i).ok_or(())? != 0xFD && self.route.path[self.i] != 0xFF {
        while *self.route.path.get(self.i).ok_or(())? != 0xFD && self.route.path[self.i] != 0xFF {
@@ -249,20 +274,24 @@ impl<'a> UntrustedRouteIter<'a> {
        self.seen_future |= is_future;
        self.seen_future |= is_future;


        let ssid = *self.route.path.get(self.i + 1).ok_or(())?;
        let ssid = *self.route.path.get(self.i + 1).ok_or(())?;
        let rssi = *self.route.path.get(self.i + 2).ok_or(())?;
        self.i += 2;
        self.i += 3;


        Ok(RouteNode::Identity(RouteIdentity {
        if is_future {
            callsign,
            Ok(RouteHop::Future(Identity::new(callsign, ssid)))
            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,
                rssi,
            is_future,
            }))
            }))
        }
        }
    }
    }
}


impl<'a> Iterator for UntrustedRouteIter<'a> {
impl<'a> Iterator for UntrustedRouteIter<'a> {
    type Item = Result<RouteNode<'a>, ()>;
    type Item = Result<RouteHop<'a>, ()>;


    fn next(&mut self) -> Option<Self::Item> {
    fn next(&mut self) -> Option<Self::Item> {
        if self.dead {
        if self.dead {
@@ -283,6 +312,7 @@ impl<'a> Iterator for UntrustedRouteIter<'a> {
    }
    }
}
}


#[derive(Clone)]
pub struct RouteIter<'a> {
pub struct RouteIter<'a> {
    iter: UntrustedRouteIter<'a>,
    iter: UntrustedRouteIter<'a>,
}
}
@@ -296,7 +326,7 @@ impl<'a> RouteIter<'a> {
}
}


impl<'a> Iterator for RouteIter<'a> {
impl<'a> Iterator for RouteIter<'a> {
    type Item = RouteNode<'a>;
    type Item = RouteHop<'a>;


    fn next(&mut self) -> Option<Self::Item> {
    fn next(&mut self) -> Option<Self::Item> {
        Some(self.iter.next()?.unwrap())
        Some(self.iter.next()?.unwrap())
@@ -310,180 +340,160 @@ mod tests {
    #[test]
    #[test]
    fn route_rssi() {
    fn route_rssi() {
        let x = -66.0;
        let x = -66.0;
        let r = RouteIdentity::new("C0", 23, Some(x), false);
        let r = PastHop::new(Identity::new("C0", 23), Some(x));
        assert_eq!(x, r.rssi().unwrap());
        assert_eq!(x, r.rssi().unwrap());


        let x = 10.0;
        let x = 10.0;
        let r = RouteIdentity::new("C0", 23, Some(x), false);
        let r = PastHop::new(Identity::new("C0", 23), Some(x));
        assert_eq!(x, r.rssi().unwrap());
        assert_eq!(x, r.rssi().unwrap());


        let x = -158.0;
        let x = -158.0;
        let r = RouteIdentity::new("C0", 23, Some(x), false);
        let r = PastHop::new(Identity::new("C0", 23), Some(x));
        assert_eq!(x, r.rssi().unwrap());
        assert_eq!(x, r.rssi().unwrap());


        let r = RouteIdentity::new("C0", 23, None, false);
        let r = PastHop::new(Identity::new("C0", 23), None);
        assert_eq!(None, r.rssi());
        assert_eq!(None, r.rssi());
    }
    }


    #[test]
    #[test]
    fn append_fails_when_existing_future() {
    fn append_fails_when_existing_future() {
        let mut r = Route::new(5);
        let mut r = Route::new(5);
        r.push_callsign(RouteIdentity::new("C1", 0, None, false))
        r.push_past(PastHop::new(Identity::new("C1", 0), None))
            .unwrap();
            .unwrap();
        r.push_callsign(RouteIdentity::new("C2", 0, None, false))
        r.push_past(PastHop::new(Identity::new("C2", 0), None))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C3", 0, None, true))
            .unwrap();
            .unwrap();
        r.push_future(Identity::new("C3", 0)).unwrap();


        assert_eq!(
        assert_eq!(
            AppendNodeError::SetFuture,
            AppendNodeError::SetFuture,
            r.append_node("C3", 1, None).unwrap_err()
            r.append_hop(PastHop::new(Identity::new("C3", 1), None))
                .unwrap_err()
        );
        );


        assert_eq!(
        assert_eq!(
            AppendNodeError::SetFuture,
            AppendNodeError::SetFuture,
            r.append_node("C4", 0, None).unwrap_err()
            r.append_hop(PastHop::new(Identity::new("C4", 0), None))
                .unwrap_err()
        );
        );
    }
    }


    #[test]
    #[test]
    fn append_fails_when_would_exceed_max_hops() {
    fn append_fails_when_would_exceed_max_hops() {
        let mut r = Route::new(3);
        let mut r = Route::new(3);
        r.append_node("C1", 0, None).unwrap();
        r.append_hop(PastHop::new(Identity::new("C1", 0), None))
        r.append_node("C2", 0, None).unwrap();
            .unwrap();
        r.append_node("C2", 1, 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!(
        assert_eq!(
            AppendNodeError::HopsOverflow,
            AppendNodeError::HopsOverflow,
            r.append_node("C4", 0, None).unwrap_err()
            r.append_hop(PastHop::new(Identity::new("C4", 0), None))
                .unwrap_err()
        );
        );
    }
    }


    #[test]
    #[test]
    fn append_fails_when_already_in_route() {
    fn append_fails_when_already_in_route() {
        let mut r = Route::new(3);
        let mut r = Route::new(3);
        r.append_node("C1", 0, None).unwrap();
        r.append_hop(PastHop::new(Identity::new("C1", 0), None))
            .unwrap();


        assert_eq!(
        assert_eq!(
            AppendNodeError::DuplicateNode,
            AppendNodeError::DuplicateNode,
            r.append_node("C1", 0, None).unwrap_err()
            r.append_hop(PastHop::new(Identity::new("C1", 0), None))
                .unwrap_err()
        );
        );
    }
    }


    #[test]
    #[test]
    fn append_overwrites_future() {
    fn append_overwrites_future() {
        let mut r = Route::new(5);
        let mut r = Route::new(5);
        r.push_callsign(RouteIdentity::new("C1", 0, None, false))
        r.push_past(PastHop::new(Identity::new("C1", 0), None))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C2", 0, None, false))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C3", 0, None, true))
            .unwrap();
            .unwrap();
        r.push_callsign(RouteIdentity::new("C4", 0, None, true))
        r.push_past(PastHop::new(Identity::new("C2", 0), None))
            .unwrap();
            .unwrap();
        r.push_future(Identity::new("C3", 0)).unwrap();
        r.push_future(Identity::new("C4", 0)).unwrap();


        r.append_node("C3", 0, None).unwrap();
        r.append_hop(PastHop::new(Identity::new("C3", 0), None))
            .unwrap();


        let mut iter = r.iter();
        let mut iter = r.iter();
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
            Some(RouteHop::Past(PastHop::new(Identity::new("C1", 0), None))),
                "C1", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
                "C2", 0, None, false
            ))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
            Some(RouteHop::Past(PastHop::new(Identity::new("C2", 0), None))),
                "C3", 0, None, false
            ))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new("C4", 0, None, true))),
            Some(RouteHop::Past(PastHop::new(Identity::new("C3", 0), None))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(Some(RouteHop::Future(Identity::new("C4", 0))), iter.next());
        assert_eq!(None, iter.next());
        assert_eq!(None, iter.next());
    }
    }


    #[test]
    #[test]
    fn append_overwrites_future_even_when_at_hop_limit() {
    fn append_overwrites_future_even_when_at_hop_limit() {
        let mut r = Route::new(4);
        let mut r = Route::new(4);
        r.push_callsign(RouteIdentity::new("C1", 0, None, false))
        r.push_past(PastHop::new(Identity::new("C1", 0), None))
            .unwrap();
            .unwrap();
        r.push_callsign(RouteIdentity::new("C2", 0, None, false))
        r.push_past(PastHop::new(Identity::new("C2", 0), None))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C3", 0, None, true))
            .unwrap();
        r.push_callsign(RouteIdentity::new("C4", 0, None, true))
            .unwrap();
            .unwrap();
        r.push_future(Identity::new("C3", 0)).unwrap();
        r.push_future(Identity::new("C4", 0)).unwrap();


        r.append_node("C3", 0, None).unwrap();
        r.append_hop(PastHop::new(Identity::new("C3", 0), None))
            .unwrap();


        let mut iter = r.iter();
        let mut iter = r.iter();
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
            Some(RouteHop::Past(PastHop::new(Identity::new("C1", 0), None))),
                "C1", 0, None, false
            ))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
            Some(RouteHop::Past(PastHop::new(Identity::new("C2", 0), None))),
                "C2", 0, None, false
            ))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
            Some(RouteHop::Past(PastHop::new(Identity::new("C3", 0), None))),
                "C3", 0, None, false
            ))),
            iter.next()
        );
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new("C4", 0, None, true))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(Some(RouteHop::Future(Identity::new("C4", 0,))), iter.next());
        assert_eq!(None, iter.next());
        assert_eq!(None, iter.next());
    }
    }


    #[test]
    #[test]
    fn append_appends_when_no_future() {
    fn append_appends_when_no_future() {
        let mut r = Route::new(5);
        let mut r = Route::new(5);
        r.push_callsign(RouteIdentity::new("C1", 0, None, false))
        r.push_past(PastHop::new(Identity::new("C1", 0), None))
            .unwrap();
            .unwrap();
        r.push_callsign(RouteIdentity::new("C2", 0, None, false))
        r.push_past(PastHop::new(Identity::new("C2", 0), None))
            .unwrap();
            .unwrap();
        r.push_callsign(RouteIdentity::new("C3", 0, None, false))
        r.push_past(PastHop::new(Identity::new("C3", 0), None))
            .unwrap();
            .unwrap();


        r.append_node("C4", 0, None).unwrap();
        r.append_hop(PastHop::new(Identity::new("C4", 0), None))
            .unwrap();


        let mut iter = r.iter();
        let mut iter = r.iter();
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
            Some(RouteHop::Past(PastHop::new(Identity::new("C1", 0), None))),
                "C1", 0, None, false
            ))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
            Some(RouteHop::Past(PastHop::new(Identity::new("C2", 0), None))),
                "C2", 0, None, false
            ))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
            Some(RouteHop::Past(PastHop::new(Identity::new("C3", 0), None))),
                "C3", 0, None, false
            ))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(
        assert_eq!(
            Some(RouteNode::Identity(RouteIdentity::new(
            Some(RouteHop::Past(PastHop::new(Identity::new("C4", 0), None))),
                "C4", 0, None, false
            ))),
            iter.next()
            iter.next()
        );
        );
        assert_eq!(None, iter.next());
        assert_eq!(None, iter.next());
+79 −5
Original line number Original line Diff line number Diff line
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)]
@@ -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);
    }
}
}