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 11

15 files
+ 267
42
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.2.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 }
+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);
});
+2 −2
Original line number Original line Diff line number Diff line
@@ -107,7 +107,7 @@ impl<'a, const N: usize, T> From<&'a mut [T; N]> for Buffer<'a, N, T> {
    }
    }
}
}


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


    fn deref(&self) -> &Self::Target {
    fn deref(&self) -> &Self::Target {
@@ -115,7 +115,7 @@ impl<'a, const N: usize, T> Deref for Buffer<'a, N, T> {
    }
    }
}
}


impl<'a, const N: usize, T> DerefMut for Buffer<'a, N, T> {
impl<const N: usize, T> DerefMut for Buffer<'_, N, T> {
    fn deref_mut(&mut self) -> &mut [T] {
    fn deref_mut(&mut self) -> &mut [T] {
        &mut self.data[..self.i]
        &mut self.data[..self.i]
    }
    }
Original line number Original line Diff line number Diff line
@@ -87,9 +87,8 @@ pub(crate) fn uninterleave_soft<const N: usize, T: DecodeFrom>(


#[cfg(test)]
#[cfg(test)]
mod tests {
mod tests {
    use crate::soft_bit::SoftBit;

    use super::*;
    use super::*;
    use crate::soft_bit::FromHardBit;


    #[test]
    #[test]
    fn interleaver_works() {
    fn interleaver_works() {
+8 −6
Original line number Original line Diff line number Diff line
use labrador_ldpc::{decoder::DecodeFrom, LDPCCode};
use labrador_ldpc::{decoder::DecodeFrom, LDPCCode};


use crate::{buffer::Buffer, error::EncodeError, soft_bit::SoftBit};
use crate::{buffer::Buffer, error::EncodeError};


macro_rules! enc_chunk {
macro_rules! enc_chunk {
    ($d:ident, $i:ident, $t:ident, $n:literal) => {
    ($d:ident, $i:ident, $t:ident, $n:literal) => {
@@ -188,12 +188,14 @@ pub(crate) fn decode_soft<const N: usize, const M: usize, T: DecodeFrom>(


    let len: usize = len_from_soft(data_av[(data_av.len() - 16)..].try_into().unwrap()).into();
    let len: usize = len_from_soft(data_av[(data_av.len() - 16)..].try_into().unwrap()).into();


    if len >= data_av.len() {
    if len * 8 + 16 >= data_av.len() {
        return None;
        return None;
    }
    }


    let (mut data, parity) = data_av.split_at_mut(len * 8);
    let data_len = data_av.len().checked_sub(16)?;
    let mut parity = parity.get_mut(..parity.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 = [T::zero(); LDPCCode::TM8192.decode_ms_working_len()];
    let mut working_u8 = [0; LDPCCode::TM8192.decode_ms_working_u8_len()];
    let mut working_u8 = [0; LDPCCode::TM8192.decode_ms_working_u8_len()];
@@ -276,9 +278,9 @@ fn len_from_soft<T: DecodeFrom>(bits: &[T; 16]) -> u16 {


#[cfg(test)]
#[cfg(test)]
mod tests {
mod tests {
    use bitvec::{order::Msb0, view::BitView};

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


    #[test]
    #[test]
    fn len_test() {
    fn len_test() {
+1 −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;
+32 −6
Original line number Original line Diff line number Diff line
@@ -169,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)?;
@@ -379,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))
@@ -407,13 +407,12 @@ 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 super::*;
    use crate::soft_bit::FromHardBit;
    use crate::whisker::NodeInfoBuilder;
    use arrayvec::ArrayString;
    use arrayvec::ArrayString;
    use bitvec::{order::Msb0, view::BitView};
    use bitvec::{order::Msb0, view::BitView};


    use crate::{soft_bit::SoftBit, whisker::NodeInfoBuilder};

    use super::*;

    #[test]
    #[test]
    fn dest() {
    fn dest() {
        let d1 = Destination::new(false, 7, "CALL1", 23).unwrap();
        let d1 = Destination::new(false, 7, "CALL1", 23).unwrap();
@@ -789,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);
        }
    }
}
}
+3 −8
Original line number Original line Diff line number Diff line
use labrador_ldpc::decoder::DecodeFrom;
pub use labrador_ldpc::decoder::DecodeFrom;


// Less than zero = 1 bit
// Less than zero = 1 bit
// Greater than zero = 0 bit
// Greater than zero = 0 bit
pub trait SoftBit {
pub trait FromHardBit {
    fn from_hard_bit(bit: bool) -> Self;
    fn from_hard_bit(bit: bool) -> Self;
    fn hard_bit(&self) -> bool;
}
}


impl<T: DecodeFrom> SoftBit for T {
impl<T: DecodeFrom> FromHardBit for T {
    fn from_hard_bit(bit: bool) -> Self {
    fn from_hard_bit(bit: bool) -> Self {
        if bit {
        if bit {
            -Self::one()
            -Self::one()
@@ -15,10 +14,6 @@ impl<T: DecodeFrom> SoftBit for T {
            Self::one()
            Self::one()
        }
        }
    }
    }

    fn hard_bit(&self) -> bool {
        self < &T::zero()
    }
}
}


#[cfg(test)]
#[cfg(test)]
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,
@@ -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
// no-std and it's not worth bringing in a library for this
fn round(v: f64) -> u32 {
fn round(v: f64) -> u32 {
    let floor = v as 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));
        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);
        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
@@ -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> {
@@ -496,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)]
Original line number Original line Diff line number Diff line
@@ -232,7 +232,7 @@ impl<'a> PastHop<'a> {
    }
    }
}
}


#[derive(Debug)]
#[derive(Debug, Clone)]
struct UntrustedRouteIter<'a> {
struct UntrustedRouteIter<'a> {
    route: &'a Route,
    route: &'a Route,
    i: usize,
    i: usize,
@@ -312,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>,
}
}