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 40

24 files
+ 1856
149
Compare changes
  • Side-by-side
  • Inline

Files

.gitlab-ci.yml

0 → 100644
+11 −0
Original line number Original line Diff line number Diff line
image: "rust:latest"

before_script:
  - rustup component add rustfmt
  - rustup component add clippy

test:
  script:
    - cargo fmt -- --check
    - cargo clippy --all-targets --all-features -- -D warnings
    - cargo test
+6 −4
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"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
repository = "https://gitlab.scd31.com/cats/ham_cats"
homepage = "https://cats.radio"
categories = ["embedded", "encoding", "no-std", "no-std::no-alloc"]


[dependencies]
[dependencies]
arrayvec = { version = "0.7.4", default-features = false }
arrayvec = { version = "0.7.4", default-features = false }
@@ -12,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
Original line number Original line Diff line number Diff line
@@ -4,5 +4,7 @@ use libfuzzer_sys::fuzz_target;
use ham_cats::packet::Packet;
use ham_cats::packet::Packet;


fuzz_target!(|data: &[u8]| {
fuzz_target!(|data: &[u8]| {
    let _ = Packet::<1024>::fully_decode(data);
    let mut buf = [0; 1024];

    let _ = Packet::<1024>::fully_decode(data, &mut buf);
});
});
Original line number Original line Diff line number Diff line
#![no_main]
#![no_main]
use libfuzzer_sys::fuzz_target;
use libfuzzer_sys::fuzz_target;


use ham_cats::packet::Packet;
use ham_cats::{buffer::Buffer, packet::Packet};
use std::convert::TryInto;


fuzz_target!(|data: &[u8]| {
fuzz_target!(|data: &[u8]| {
    if let Ok(data) = data.try_into() {
    let mut buf = [0; 1024];
        let _ = Packet::<1024>::semi_decode(data);
    let mut buf = Buffer::new_empty(&mut buf);
    if buf.try_extend_from_slice(data).is_ok() {
        let _ = Packet::<1024>::semi_decode(buf);
    }
    }
});
});
Original line number Original line Diff line number Diff line
#![no_main]
#![no_main]
use libfuzzer_sys::fuzz_target;
use libfuzzer_sys::fuzz_target;


use ham_cats::packet::Packet;
use ham_cats::{buffer::Buffer, packet::Packet};
use std::convert::TryInto;


fuzz_target!(|data: &[u8]| {
fuzz_target!(|data: &[u8]| {
    if let Ok(data) = data.try_into() {
    let mut buf = [0; 1024];
        let _ = Packet::<1024>::decode(data);
    let mut buf = Buffer::new_empty(&mut buf);
    if buf.try_extend_from_slice(data).is_ok() {
        let _ = Packet::<1024>::decode(buf);
    }
    }
});
});
+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]
    }
    }
}
}
+45 −0
Original line number Original line Diff line number Diff line
@@ -41,3 +41,48 @@ pub enum CommentError {
    #[snafu(display("Given buffer too small for comment data"))]
    #[snafu(display("Given buffer too small for comment data"))]
    BufferOverflow,
    BufferOverflow,
}
}

#[derive(Debug, Snafu)]
pub enum DigipeatError {
    #[snafu(display("No route"))]
    NoRoute,

    #[snafu(display("Identification is us"))]
    Us,

    #[snafu(display("Max hops hit"))]
    MaxHops,

    #[snafu(display("Already digipeated by this node"))]
    AlreadyDigipeated,

    #[snafu(display("Future hop(s) are already set"))]
    SetDestiny,
}

#[derive(Debug, Snafu, PartialEq, Eq)]
pub enum AppendNodeError {
    #[snafu(display("Future hop(s) already set to a different node"))]
    SetFuture,

    #[snafu(display("Max hops hit"))]
    HopsOverflow,

    #[snafu(display("Node already in route"))]
    DuplicateNode,

    #[snafu(display("Given data causes the route whisker to overflow"))]
    RouteOverflow,
}

#[derive(Debug, Snafu, PartialEq, Eq)]
pub enum PacketRouteAppendError {
    #[snafu(display("No route whisker on packet"))]
    NoRouteWhisker,

    #[snafu(display("Route error"))]
    Route { error: AppendNodeError },

    #[snafu(display("CATS packet overflow"))]
    PacketOverflow,
}

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);
    }
}
}
+4 −2
Original line number Original line Diff line number Diff line
#![no_std]
#![cfg_attr(not(test), no_std)]


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;


mod buffer;
mod utf8;
mod utf8;