Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • cats/ham-cats
  • sam/ham-cats
  • Quantum_P/ham-cats
  • Reed/ham-cats
4 results
Show changes
Commits on Source (46)
Showing with 1085 additions and 136 deletions
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
[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 } ...@@ -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 = "0.7.5" snafu = { version = "0.7.5", default-features = false }
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.
...@@ -96,7 +96,7 @@ dependencies = [ ...@@ -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 = [ ...@@ -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"
......
...@@ -30,9 +30,14 @@ path = "fuzz_targets/fuzz_target_2.rs" ...@@ -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
...@@ -4,5 +4,7 @@ use libfuzzer_sys::fuzz_target; ...@@ -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);
}); });
#![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);
} }
}); });
#![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);
} }
}); });
#![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);
});
use core::ops::{Deref, DerefMut};
#[derive(Debug)]
pub struct BufferOverflow;
#[derive(Debug)]
pub struct Buffer<'a, const N: usize, T = u8> {
data: &'a mut [T; N],
i: usize,
}
impl<'a, const N: usize, T: Copy> Buffer<'a, N, T> {
/// Constructs a new `Buffer`.
/// `data` is the backing array.
/// `i` is the number of elements in `data` that contain data (and should thus be exposed by `Buffer`)
pub fn new(data: &'a mut [T; N], i: usize) -> Self {
assert!(i <= data.len());
Self { data, i }
}
pub fn new_full(data: &'a mut [T; N]) -> Self {
let i = data.len();
Self::new(data, i)
}
pub fn new_empty(data: &'a mut [T; N]) -> Self {
Self::new(data, 0)
}
pub const fn remaining_capacity(&self) -> usize {
N - self.i
}
pub fn try_push(&mut self, v: T) -> Result<(), BufferOverflow> {
if self.i == N {
return Err(BufferOverflow);
}
self.data[self.i] = v;
self.i += 1;
Ok(())
}
pub fn push(&mut self, v: T) {
self.try_push(v).unwrap();
}
pub fn try_extend_from_slice(&mut self, other: &[T]) -> Result<(), BufferOverflow> {
if self.remaining_capacity() < other.len() {
return Err(BufferOverflow);
}
self.data[self.i..(self.i + other.len())].copy_from_slice(other);
self.i += other.len();
Ok(())
}
pub fn extend(&mut self, other: &[T]) {
self.try_extend_from_slice(other).unwrap();
}
pub fn pop(&mut self) -> Option<T> {
if self.i == 0 {
return None;
}
self.i -= 1;
Some(self.data[self.i])
}
pub fn truncate(&mut self, new_len: usize) {
assert!(self.i >= new_len);
self.i = new_len;
}
pub fn drain(&mut self, start: usize, end: usize) {
assert!(end >= start);
assert!(end <= self.i);
let delta = end - start;
let surplus = self.len() - end;
for i in start..(start + surplus) {
self[i] = self[i + delta];
}
self.i -= delta;
}
pub fn clone_backing<'b>(&self, buf: &'b mut [T; N]) -> Buffer<'b, N, T> {
let mut out = Buffer::new_empty(buf);
out.extend(self);
out
}
}
impl<'a, const N: usize, T> From<&'a mut [T; N]> for Buffer<'a, N, T> {
fn from(data: &'a mut [T; N]) -> Self {
Self { data, i: 0 }
}
}
impl<const N: usize, T> Deref for Buffer<'_, N, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
&self.data[..self.i]
}
}
impl<const N: usize, T> DerefMut for Buffer<'_, N, T> {
fn deref_mut(&mut self) -> &mut [T] {
&mut self.data[..self.i]
}
}
...@@ -41,3 +41,48 @@ pub enum CommentError { ...@@ -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,
}
#[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
}
}
use arrayvec::{ArrayVec, CapacityError};
use bitvec::prelude::*; use bitvec::prelude::*;
use labrador_ldpc::decoder::DecodeFrom;
use crate::error::EncodeError; use crate::{
buffer::{Buffer, BufferOverflow},
error::EncodeError,
};
pub(crate) fn interleave<const N: usize>( pub(crate) fn interleave<const N: usize>(
data: &[u8], data: &[u8],
out: &mut ArrayVec<u8, N>, out: &mut Buffer<N>,
) -> Result<(), EncodeError> { ) -> Result<(), EncodeError> {
let bv = data.view_bits::<Msb0>(); let bv = data.view_bits::<Msb0>();
...@@ -33,10 +36,10 @@ pub(crate) fn interleave<const N: usize>( ...@@ -33,10 +36,10 @@ pub(crate) fn interleave<const N: usize>(
pub(crate) fn uninterleave<const N: usize>( pub(crate) fn uninterleave<const N: usize>(
data: &[u8], data: &[u8],
) -> Result<ArrayVec<u8, N>, CapacityError<u8>> { out: &mut Buffer<N>,
) -> Result<(), BufferOverflow> {
let bv = data.view_bits::<Msb0>(); let bv = data.view_bits::<Msb0>();
let mut out: ArrayVec<u8, N> = ArrayVec::new();
for _ in 0..data.len() { for _ in 0..data.len() {
out.try_push(0)?; out.try_push(0)?;
} }
...@@ -55,26 +58,86 @@ pub(crate) fn uninterleave<const N: usize>( ...@@ -55,26 +58,86 @@ pub(crate) fn uninterleave<const N: usize>(
} }
} }
Ok(out) 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() {
let orig = let mut data = [0x84, 0x73, 0x12, 0xA3, 0xFF, 0x00, 0xC2, 0x1B, 0x77];
ArrayVec::try_from([0x84, 0x73, 0x12, 0xA3, 0xFF, 0x00, 0xC2, 0x1B, 0x77]).unwrap(); let orig = Buffer::new_full(&mut data);
let mut interleaved: ArrayVec<u8, 10> = ArrayVec::new();
let mut interleaved = [0; 10];
let mut interleaved = Buffer::new(&mut interleaved, 0);
interleaved.push(b'H'); interleaved.push(b'H');
interleave(&orig, &mut interleaved).unwrap(); interleave(&orig, &mut interleaved).unwrap();
let expected = [b'H', 0xCD, 0xB5, 0xDB, 0x2A, 0x0A, 0x52, 0x0C, 0x89, 0x4F]; let expected = [b'H', 0xCD, 0xB5, 0xDB, 0x2A, 0x0A, 0x52, 0x0C, 0x89, 0x4F];
assert_eq!(expected, interleaved[..]); assert_eq!(expected, interleaved[..]);
let uninterleaved = uninterleave(&interleaved[1..]).unwrap(); let mut uninterleaved = [0; 10];
let mut uninterleaved = Buffer::new(&mut uninterleaved, 0);
uninterleave(&interleaved[1..], &mut uninterleaved).unwrap();
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, uninterleaved); assert_eq!(orig.len() * 8, uninterleaved.len());
for (i, b) in orig.iter().enumerate() {
for j in 0..8 {
assert_eq!(uninterleaved[8 * i + j].hard_bit(), *b & (1 << (7 - j)) > 0);
}
}
} }
} }
use arrayvec::ArrayVec; use labrador_ldpc::{decoder::DecodeFrom, LDPCCode};
use labrador_ldpc::LDPCCode;
use crate::error::EncodeError; 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) => {
...@@ -37,8 +36,29 @@ macro_rules! dec_chunk { ...@@ -37,8 +36,29 @@ 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 ArrayVec<u8, N>) -> Result<(), EncodeError> { pub(crate) fn encode<const N: usize>(data: &mut Buffer<N>) -> Result<(), EncodeError> {
let mut i = 0; let mut i = 0;
let n = data.len(); let n = data.len();
...@@ -84,7 +104,7 @@ pub(crate) fn encode<const N: usize>(data: &mut ArrayVec<u8, N>) -> Result<(), E ...@@ -84,7 +104,7 @@ pub(crate) fn encode<const N: usize>(data: &mut ArrayVec<u8, N>) -> Result<(), E
Ok(()) Ok(())
} }
pub(crate) fn decode<const N: usize>(data_av: &mut ArrayVec<u8, N>) -> Option<()> { pub(crate) fn decode<const N: usize>(data_av: &mut Buffer<N>) -> Option<()> {
if data_av.len() < 2 { if data_av.len() < 2 {
return None; return None;
} }
...@@ -154,28 +174,125 @@ pub(crate) fn decode<const N: usize>(data_av: &mut ArrayVec<u8, N>) -> Option<() ...@@ -154,28 +174,125 @@ pub(crate) fn decode<const N: usize>(data_av: &mut ArrayVec<u8, 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 arrayvec::ArrayVec; use crate::soft_bit::FromHardBit;
use bitvec::{order::Msb0, view::BitView};
#[test] #[test]
fn len_test() { fn len_test() {
// from the example in the docs // from the example in the docs
let mut data: ArrayVec<u8, 8191> = ArrayVec::new(); let mut buf = [0; 8191];
let mut data = Buffer::new_empty(&mut buf);
for _ in 0..41 { for _ in 0..41 {
data.extend( data.extend(b"Example packet data wueirpqwerwrywqoeiruy29346129384761");
b"Example packet data wueirpqwerwrywqoeiruy29346129384761"
.iter()
.cloned(),
);
} }
data.extend( data.extend(b"Example packet data wueirpqwerwrywqoeiru346129384761");
b"Example packet data wueirpqwerwrywqoeiru346129384761"
.iter()
.cloned(),
);
assert_eq!(2349, data.len()); assert_eq!(2349, data.len());
encode(&mut data).unwrap(); encode(&mut data).unwrap();
...@@ -185,51 +302,50 @@ mod tests { ...@@ -185,51 +302,50 @@ mod tests {
#[test] #[test]
fn basic_encode_decode_short() { fn basic_encode_decode_short() {
let mut data: ArrayVec<u8, 32> = ArrayVec::new(); let mut buf = [0; 32];
let mut buf2 = [0; 32];
let mut data = Buffer::new_empty(&mut buf);
data.try_extend_from_slice(b"Hello world!").unwrap(); data.try_extend_from_slice(b"Hello world!").unwrap();
let orig = data.clone(); let orig = data.clone_backing(&mut buf2);
encode(&mut data).unwrap(); encode(&mut data).unwrap();
decode(&mut data).unwrap(); decode(&mut data).unwrap();
assert_eq!(orig, data); assert_eq!(*orig, *data);
} }
#[test] #[test]
fn basic_encode_decode() { fn basic_encode_decode() {
let mut data: ArrayVec<u8, 8191> = ArrayVec::new(); let mut buf = [0; 8191];
let mut buf2 = [0; 8191];
let mut data = Buffer::new_empty(&mut buf);
for _ in 0..50 { for _ in 0..50 {
data.extend( data.extend(b"This is a test packet. jsalksjd093809324JASLD:LKD*#$)(*#@)");
b"This is a test packet. jsalksjd093809324JASLD:LKD*#$)(*#@)"
.iter()
.cloned(),
);
} }
let orig = data.clone(); let orig = data.clone_backing(&mut buf2);
encode(&mut data).unwrap(); encode(&mut data).unwrap();
assert_ne!(orig, data); assert_ne!(*orig, *data);
decode(&mut data).unwrap(); decode(&mut data).unwrap();
assert_eq!(orig, data); assert_eq!(*orig, *data);
} }
#[test] #[test]
fn encode_decode_with_bit_flips() { fn encode_decode_with_bit_flips() {
let mut data: ArrayVec<u8, 8191> = ArrayVec::new(); let mut buf = [0; 8191];
let mut buf2 = [0; 8191];
let mut data = Buffer::new_empty(&mut buf);
for _ in 0..50 { for _ in 0..50 {
data.extend( data.extend(b"jsalksjd093809324JASLD:LKD*#$)(*#@) Another test packet");
b"jsalksjd093809324JASLD:LKD*#$)(*#@) Another test packet"
.iter()
.cloned(),
);
} }
let orig = data.clone(); let orig = data.clone_backing(&mut buf2);
encode(&mut data).unwrap(); encode(&mut data).unwrap();
assert_ne!(orig, data); assert_ne!(*orig, *data);
data[234] ^= 0x55; data[234] ^= 0x55;
data[0] ^= 0xAA; data[0] ^= 0xAA;
...@@ -237,6 +353,32 @@ mod tests { ...@@ -237,6 +353,32 @@ mod tests {
decode(&mut data).unwrap(); decode(&mut data).unwrap();
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);
} }
} }
#![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;
......
use core::fmt::Debug; use core::fmt::Debug;
use arrayvec::{ArrayVec, CapacityError};
use crc::{Crc, CRC_16_IBM_SDLC}; use crc::{Crc, CRC_16_IBM_SDLC};
use labrador_ldpc::decoder::DecodeFrom;
use crate::{ use crate::{
error::{CommentError, DecodeError, EncodeError}, buffer::{Buffer, BufferOverflow},
error::{CommentError, DecodeError, DigipeatError, EncodeError, PacketRouteAppendError},
identity::Identity,
interleaver, ldpc, utf8, interleaver, ldpc, utf8,
whisker::{ whisker::{
Arbitrary, Comment, Destination, Gps, Identification, Route, Timestamp, Arbitrary, Comment, Destination, Gps, Identification, NodeInfo, PastHop, Route, RouteHop,
ValidatedWhiskerIter, Whisker, WhiskerIter, COMMENT_TYPE, Timestamp, ValidatedWhiskerIter, Whisker, WhiskerIter, COMMENT_TYPE,
}, },
whitener, whitener,
}; };
...@@ -18,24 +20,24 @@ const X25: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_SDLC); ...@@ -18,24 +20,24 @@ const X25: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_SDLC);
macro_rules! uniq_whisker { macro_rules! uniq_whisker {
($t:meta) => { ($t:meta) => {
::paste::paste! { ::paste::paste! {
pub fn [<$t:lower>](&self) -> Option<$t> { pub fn [<$t:snake:lower>](&self) -> Option<$t> {
self.iter().find_map(|w| match w { self.iter().find_map(|w| match w {
Whisker::$t(x) => Some(x), Whisker::$t(x) => Some(x),
_ => None, _ => None,
}) })
} }
pub fn [<add_ $t:lower>](&mut self, w: $t) -> Result<(), EncodeError> { pub fn [<add_ $t:snake:lower>](&mut self, w: $t) -> Result<(), EncodeError> {
if self.[<$t:lower>]().is_some() { if self.[<$t:snake:lower>]().is_some() {
return Err(EncodeError::DuplicateData); return Err(EncodeError::DuplicateData);
} }
try_lock(&mut self.data, |data| { try_lock(&mut self.buf, |data| {
let mut buf = [0; 256]; let mut buf = [0; 256];
// safe to unwrap since we know we have enough space // safe to unwrap since we know we have enough space
let out = w.encode(&mut buf).unwrap(); let out = w.encode(&mut buf).unwrap();
data.try_push(crate::whisker::[<$t:upper _TYPE>]).ok().ok_or(EncodeError::CatsOverflow)?; data.try_push(crate::whisker::[<$t:snake:upper _TYPE>]).ok().ok_or(EncodeError::CatsOverflow)?;
data.try_extend_from_slice(out).ok().ok_or(EncodeError::CatsOverflow)?; data.try_extend_from_slice(out).ok().ok_or(EncodeError::CatsOverflow)?;
Ok(()) Ok(())
...@@ -43,28 +45,78 @@ macro_rules! uniq_whisker { ...@@ -43,28 +45,78 @@ macro_rules! uniq_whisker {
Ok(()) Ok(())
} }
pub fn [<clear_ $t:snake:lower>](&mut self) {
self.clear_by_type(crate::whisker::[<$t:snake:upper _TYPE>], false);
}
}
};
}
macro_rules! poly_whisker {
($t: meta) => {
::paste::paste! {
pub fn [<$t:lower _iter>](&self) -> core::iter::FilterMap<ValidatedWhiskerIter, fn(Whisker) -> Option<$t>> {
fn filt(w: Whisker) -> Option<$t> {
match w {
Whisker::$t(x) => Some(x),
_ => None
}
}
self.iter().filter_map(filt)
}
pub fn [<add_ $t:lower>](&mut self, w: $t) -> Result<(), EncodeError> {
try_lock(&mut self.buf, |data| {
let mut buf = [0; 256];
// safe to unwrap since we know we have enough space
let out = w.encode(&mut buf).unwrap();
data.try_push(crate::whisker::[<$t:upper _TYPE>]).ok().ok_or(EncodeError::CatsOverflow)?;
data.try_extend_from_slice(out).ok().ok_or(EncodeError::CatsOverflow)?;
Ok(())
})?;
Ok(())
}
pub fn [<clear_ $t:lower>](&mut self) {
self.clear_by_type(crate::whisker::[<$t:upper _TYPE>], true);
}
} }
}; };
} }
/// N is the maximum packet size we can handle /// N is the maximum packet size we can handle
/// Panics if N >= 8191 /// Panics if N >= 8191
#[derive(Default, Clone)] pub struct Packet<'a, const N: usize> {
pub struct Packet<const N: usize> { buf: Buffer<'a, N>,
data: ArrayVec<u8, N>,
} }
// TODO need to have methods for removing whiskers from the packet impl<'a, const N: usize> Packet<'a, N> {
impl<const N: usize> Packet<N> { pub fn new(buf: &'a mut [u8; N]) -> Self {
pub fn decode(data: ArrayVec<u8, N>) -> Result<Self, DecodeError> { Self { buf: buf.into() }
}
pub fn clone_backing<'b>(&self, buf: &'b mut [u8; N]) -> Packet<'b, N> {
Packet {
buf: self.buf.clone_backing(buf),
}
}
/// Expects bytes in the `buf`
/// `buf` is used as the backing buffer for the Packet
pub fn decode(buf: Buffer<'a, N>) -> Result<Self, DecodeError> {
assert!(N <= 8191); assert!(N <= 8191);
// validate the data // validate the data
for w in WhiskerIter::new(&data) { for w in WhiskerIter::new(&buf) {
w?; w?;
} }
let comment_iter = WhiskerIter::new(&data) let comment_iter = WhiskerIter::new(&buf)
.filter_map(|w| match w.unwrap() { .filter_map(|w| match w.unwrap() {
Whisker::Comment(c) => Some(c.internal_data().clone()), Whisker::Comment(c) => Some(c.internal_data().clone()),
_ => None, _ => None,
...@@ -75,18 +127,18 @@ impl<const N: usize> Packet<N> { ...@@ -75,18 +127,18 @@ impl<const N: usize> Packet<N> {
return Err(DecodeError::InvalidComment); return Err(DecodeError::InvalidComment);
} }
Ok(Self { data }) Ok(Self { buf })
} }
pub fn encode(&self) -> &[u8] { pub fn encode(&self) -> &[u8] {
&self.data &self.buf
} }
/// Directly after the CRC block in The Pipeline /// Directly after the CRC block in The Pipeline
pub fn semi_encode(mut self) -> Result<ArrayVec<u8, N>, (EncodeError, Self)> { pub fn semi_encode(mut self) -> Result<Buffer<'a, N>, (EncodeError, Self)> {
let crc = X25.checksum(&self.data).to_le_bytes(); let crc = X25.checksum(&self.buf).to_le_bytes();
let res: Result<(), CapacityError<u8>> = try_lock(&mut self.data, |data| { let res: Result<(), BufferOverflow> = try_lock(&mut self.buf, |data| {
data.try_push(crc[0])?; data.try_push(crc[0])?;
data.try_push(crc[1])?; data.try_push(crc[1])?;
...@@ -94,61 +146,84 @@ impl<const N: usize> Packet<N> { ...@@ -94,61 +146,84 @@ impl<const N: usize> Packet<N> {
}); });
match res { match res {
Ok(()) => Ok(self.data), Ok(()) => Ok(self.buf),
Err(_) => Err((EncodeError::CatsOverflow, self)), Err(_) => Err((EncodeError::CatsOverflow, self)),
} }
} }
/// Directly after the CRC block in The Pipeline /// Directly after the CRC block in The Pipeline
pub fn semi_decode(mut data: ArrayVec<u8, N>) -> Result<Self, DecodeError> { /// Expects bytes in the `buf`
let crc1 = data.pop().ok_or(DecodeError::UnexpectedEndOfInput)?; /// `buf` is used as the backing buffer for the Packet
let crc0 = data.pop().ok_or(DecodeError::UnexpectedEndOfInput)?; pub fn semi_decode(mut buf: Buffer<'a, N>) -> Result<Self, DecodeError> {
let crc1 = buf.pop().ok_or(DecodeError::UnexpectedEndOfInput)?;
let crc0 = buf.pop().ok_or(DecodeError::UnexpectedEndOfInput)?;
let crc_expected = u16::from_le_bytes([crc0, crc1]); let crc_expected = u16::from_le_bytes([crc0, crc1]);
let crc_actual = X25.checksum(&data); let crc_actual = X25.checksum(&buf);
if crc_expected != crc_actual { if crc_expected != crc_actual {
return Err(DecodeError::CrcMismatch); return Err(DecodeError::CrcMismatch);
} }
Self::decode(data) Self::decode(buf)
} }
/// 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) -> Result<ArrayVec<u8, N>, 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)?;
let mut out: ArrayVec<u8, N> = ArrayVec::new();
// safe to unwrap - length must be below 8191 // safe to unwrap - length must be below 8191
out.try_extend_from_slice(&u16::try_from(data.len()).unwrap().to_le_bytes()) out.try_extend_from_slice(&u16::try_from(data.len()).unwrap().to_le_bytes())
.map_err(|_| EncodeError::CatsOverflow)?; .map_err(|_| EncodeError::CatsOverflow)?;
interleaver::interleave(&data, &mut out)?; interleaver::interleave(&data, out)?;
Ok(out) Ok(())
} }
/// 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.
pub fn fully_decode(data: &[u8]) -> Result<Self, DecodeError> { /// Expects bytes in `data`
let mut data = interleaver::uninterleave(data).map_err(|_| DecodeError::Overflow)?; /// `buf` is used as the backing buffer for the Packet
ldpc::decode(&mut data).ok_or(DecodeError::LdpcError)?; pub fn fully_decode(data: &[u8], buf: &'a mut [u8; N]) -> Result<Self, DecodeError> {
whitener::whiten(&mut data); let mut buf = Buffer::new_empty(buf);
interleaver::uninterleave(data, &mut buf).map_err(|_| DecodeError::Overflow)?;
ldpc::decode(&mut buf).ok_or(DecodeError::LdpcError)?;
whitener::whiten(&mut buf);
Self::semi_decode(buf)
}
Self::semi_decode(data) /// 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.data) ValidatedWhiskerIter::new(&self.buf)
} }
uniq_whisker!(Identification); uniq_whisker!(Identification);
uniq_whisker!(Timestamp); uniq_whisker!(Timestamp);
uniq_whisker!(Gps); uniq_whisker!(Gps);
uniq_whisker!(Route); uniq_whisker!(Route);
// TODO dest, arbitrary uniq_whisker!(NodeInfo);
poly_whisker!(Destination);
poly_whisker!(Arbitrary);
pub fn comment<'a>(&self, buf: &'a mut [u8]) -> Result<&'a str, CommentError> { pub fn comment<'b>(&self, buf: &'b mut [u8]) -> Result<&'b str, CommentError> {
let iter = self.iter().filter_map(|w| match w { let iter = self.iter().filter_map(|w| match w {
Whisker::Comment(c) => Some(c), Whisker::Comment(c) => Some(c),
_ => None, _ => None,
...@@ -180,7 +255,7 @@ impl<const N: usize> Packet<N> { ...@@ -180,7 +255,7 @@ impl<const N: usize> Packet<N> {
return Err(EncodeError::DuplicateData); return Err(EncodeError::DuplicateData);
} }
try_lock(&mut self.data, |data| { try_lock(&mut self.buf, |data| {
let mut comment = comment.as_bytes(); let mut comment = comment.as_bytes();
while comment.len() > 255 { while comment.len() > 255 {
...@@ -210,28 +285,120 @@ impl<const N: usize> Packet<N> { ...@@ -210,28 +285,120 @@ impl<const N: usize> Packet<N> {
Ok(()) Ok(())
} }
pub fn clear_comment(&mut self) {
self.clear_by_type(COMMENT_TYPE, true);
}
/// 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.
pub fn should_digipeat(&self, identity: Identity) -> Result<(), DigipeatError> {
let route = match self.route() {
Some(x) => x,
None => {
return Err(DigipeatError::NoRoute);
}
};
if let Some(ident) = self.identification() {
if &ident.callsign == identity.callsign() && ident.ssid == identity.ssid() {
return Err(DigipeatError::Us);
}
}
let max_hops: usize = route.max_hops.into();
let cur_hops = route
.iter()
.filter(|r| match r {
RouteHop::Internet => false,
RouteHop::Past(_) => true,
RouteHop::Future(_) => false,
})
.count();
if max_hops <= cur_hops {
return Err(DigipeatError::MaxHops);
}
let already_digipeated = route.iter().any(|r| match r {
RouteHop::Internet => false,
RouteHop::Past(past_hop) => past_hop.identity() == identity,
RouteHop::Future(_) => false,
});
if already_digipeated {
return Err(DigipeatError::AlreadyDigipeated);
}
let next_node = route.iter().find_map(|r| match r {
RouteHop::Future(x) => Some(x),
_ => None,
});
match next_node {
Some(ident) if ident != identity => Err(DigipeatError::SetDestiny),
_ => Ok(()),
}
}
/// Note that if this fails due to a CATS overflow, it will wipe the route off of the packet
pub fn append_to_route(
&mut self,
callsign: &str,
ssid: u8,
rssi: Option<f64>,
) -> Result<(), PacketRouteAppendError> {
let mut route = self.route().ok_or(PacketRouteAppendError::NoRouteWhisker)?;
route
.append_hop(PastHop::new(Identity::new(callsign, ssid), rssi))
.map_err(|error| PacketRouteAppendError::Route { error })?;
self.clear_route();
self.add_route(route)
.map_err(|_| PacketRouteAppendError::PacketOverflow)?;
Ok(())
}
fn clear_by_type(&mut self, whisker_type: u8, all: bool) {
let mut i = 0;
while i < self.buf.len() {
let t = self.buf[i];
let step = usize::from(self.buf[i + 1]) + 2;
if t == whisker_type {
self.buf.drain(i, i + step);
if !all {
return;
}
} else {
i += step;
}
}
}
} }
impl<const N: usize> Debug for Packet<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.data)) .entries(ValidatedWhiskerIter::new(&self.buf))
.finish() .finish()
} }
} }
// if the function returns an error, we roll back the array // if the function returns an error, we roll back the array
// by rolling it back, we really just pop bytes off the end until the length matches // by rolling it back, we really just pop bytes off the end until the length matches
fn try_lock<const N: usize, T, E, F: Fn(&mut ArrayVec<u8, N>) -> Result<T, E>>( fn try_lock<'a, const N: usize, T, E, F: Fn(&mut Buffer<'a, N>) -> Result<T, E>>(
arr: &mut ArrayVec<u8, N>, buf: &mut Buffer<'a, N>,
f: F, f: F,
) -> Result<T, E> { ) -> Result<T, E> {
let len = arr.len(); let len = buf.len();
match f(arr) { match f(buf) {
Ok(x) => Ok(x), Ok(x) => Ok(x),
Err(e) => { Err(e) => {
arr.truncate(len); buf.truncate(len);
Err(e) Err(e)
} }
...@@ -240,15 +407,56 @@ fn try_lock<const N: usize, T, E, F: Fn(&mut ArrayVec<u8, N>) -> Result<T, E>>( ...@@ -240,15 +407,56 @@ fn try_lock<const N: usize, T, E, F: Fn(&mut ArrayVec<u8, 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 super::*; #[test]
fn dest() {
let d1 = Destination::new(false, 7, "CALL1", 23).unwrap();
let d2 = Destination::new(true, 23, "CALL2", 2).unwrap();
let mut buf = [0; 1024];
let mut packet: Packet<1024> = Packet::new(&mut buf);
packet.add_destination(d1.clone()).unwrap();
packet.add_destination(d2.clone()).unwrap();
let mut dests = packet.destination_iter();
assert_eq!(d1, dests.next().unwrap());
assert_eq!(d2, dests.next().unwrap());
assert_eq!(None, dests.next());
}
#[test]
fn route_clear() {
let mut buf = [0; 1024];
let mut p: Packet<1024> = Packet::new(&mut buf);
p.add_identification(Identification::new("call", 43, 123).unwrap())
.unwrap();
let mut r = Route::new(8);
r.push_internet().unwrap();
p.add_route(r).unwrap();
p.add_comment("This is a comment").unwrap();
p.clear_route();
assert_eq!(
Identification::new("call", 43, 123).unwrap(),
p.identification().unwrap()
);
assert_eq!(None, p.route());
let mut buf = [0; 32];
assert_eq!("This is a comment", p.comment(&mut buf).unwrap());
}
#[test] #[test]
fn semi_e2e() { fn semi_e2e() {
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 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 packet = Packet::<2048>::default(); let mut buf = [0; 2048];
let mut packet = Packet::new(&mut buf);
packet packet
.add_identification(Identification { .add_identification(Identification {
icon: 123, icon: 123,
...@@ -286,7 +494,8 @@ mod tests { ...@@ -286,7 +494,8 @@ mod tests {
fn full_e2e() { fn full_e2e() {
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 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 packet = Packet::<4096>::default(); let mut buf = [0; 4096];
let mut packet = Packet::new(&mut buf);
packet packet
.add_identification(Identification { .add_identification(Identification {
icon: 123, icon: 123,
...@@ -304,13 +513,16 @@ mod tests { ...@@ -304,13 +513,16 @@ mod tests {
}); });
assert!(matches!(res, Err(EncodeError::DuplicateData))); assert!(matches!(res, Err(EncodeError::DuplicateData)));
let mut fully = packet.fully_encode().unwrap(); let mut buf2 = [0; 4096];
let mut fully = Buffer::new_empty(&mut buf2);
packet.fully_encode(&mut fully).unwrap();
fully[40] ^= 0x55; fully[40] ^= 0x55;
fully[844] ^= 0x7B; fully[844] ^= 0x7B;
// exclude length // exclude length
let packet2: Packet<8191> = Packet::fully_decode(&fully[2..]).unwrap(); let mut buf3 = [0; 8191];
let packet2: Packet<8191> = Packet::fully_decode(&fully[2..], &mut buf3).unwrap();
assert_eq!( assert_eq!(
Identification { Identification {
icon: 123, icon: 123,
...@@ -324,6 +536,134 @@ mod tests { ...@@ -324,6 +536,134 @@ 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]
fn node_info_e2e() {
let mut buf = [0; 4096];
let mut packet = Packet::new(&mut buf);
packet
.add_node_info(
NodeInfoBuilder::default()
.hardware_id(0xBEEF)
.software_id(0xBC)
.uptime(2304)
.antenna_height(5)
.antenna_gain(3.0)
.tx_power(30.0)
.voltage(12.6)
.xcvr_temperature(-15)
.battery_charge(65.0)
.build(),
)
.unwrap();
let mut buf2 = [0; 4096];
let mut encoded = Buffer::new_empty(&mut buf2);
packet.fully_encode(&mut encoded).unwrap();
let mut buf3 = [0; 4096];
let packet2 = Packet::fully_decode(&encoded[2..], &mut buf3).unwrap();
let node_info = packet2.node_info().unwrap();
assert_eq!(0xBEEF, node_info.hardware_id().unwrap());
assert_eq!(0xBC, node_info.software_id().unwrap());
assert_eq!(2304, node_info.uptime().unwrap());
assert_eq!(5, node_info.antenna_height().unwrap());
assert_eq!(3.0, node_info.antenna_gain().unwrap());
assert_eq!(30.0, node_info.tx_power().unwrap());
assert_eq!(12.6, node_info.voltage().unwrap());
assert_eq!(-15, node_info.xcvr_temperature().unwrap());
assert_eq!(64.70588235294117, node_info.battery_charge().unwrap());
}
#[test]
fn node_info_e2e_some_unpopulated() {
let mut buf = [0; 4096];
let mut packet = Packet::new(&mut buf);
packet
.add_node_info(
NodeInfoBuilder::default()
.software_id(0xBC)
.uptime(2304)
.antenna_gain(3.0)
.voltage(12.6)
.xcvr_temperature(-15)
.build(),
)
.unwrap();
let mut buf2 = [0; 4096];
let mut encoded = Buffer::new_empty(&mut buf2);
packet.fully_encode(&mut encoded).unwrap();
let mut buf3 = [0; 4096];
let packet2 = Packet::fully_decode(&encoded[2..], &mut buf3).unwrap();
let node_info = packet2.node_info().unwrap();
assert_eq!(None, node_info.hardware_id());
assert_eq!(0xBC, node_info.software_id().unwrap());
assert_eq!(2304, node_info.uptime().unwrap());
assert_eq!(None, node_info.antenna_height());
assert_eq!(3.0, node_info.antenna_gain().unwrap());
assert_eq!(None, node_info.tx_power());
assert_eq!(12.6, node_info.voltage().unwrap());
assert_eq!(-15, node_info.xcvr_temperature().unwrap());
assert_eq!(None, node_info.battery_charge());
}
#[test] #[test]
fn fully_decode_fuzz_tests() { fn fully_decode_fuzz_tests() {
let data = [ let data = [
...@@ -377,12 +717,13 @@ mod tests { ...@@ -377,12 +717,13 @@ mod tests {
118, 118, 118, 118, 118, 118, 118, 0, 0, 0, 0, 96, 96, 118, 118, 118, 118, 118, 118, 118, 0, 0, 0, 0, 96, 96,
]; ];
let _ = Packet::<1024>::fully_decode(&data); let mut buf = [0; 1024];
let _ = Packet::<1024>::fully_decode(&data, &mut buf);
} }
#[test] #[test]
fn semi_decode_fuzz_tests() { fn semi_decode_fuzz_tests() {
let data: ArrayVec<u8, 1024> = ArrayVec::try_from( let cases = [
&[ &[
42, 64, 64, 64, 229, 40, 64, 64, 0, 0, 173, 173, 173, 173, 173, 173, 173, 173, 64, 42, 64, 64, 64, 229, 40, 64, 64, 0, 0, 173, 173, 173, 173, 173, 173, 173, 173, 64,
64, 0, 0, 173, 187, 187, 187, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 64, 0, 0, 173, 187, 187, 187, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173,
...@@ -422,16 +763,56 @@ mod tests { ...@@ -422,16 +763,56 @@ mod tests {
187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187,
187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 126, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 126,
][..], ][..],
) &[
.unwrap(); 48, 5, 4, 255, 5, 5, 5, 5, 5, 5, 5, 37, 5, 7, 5, 5, 35, 5, 5, 5, 5, 4, 5, 7, 5, 5,
5, 5, 5, 5, 5, 5, 11, 126, 3, 101, 5, 3, 3, 96, 192, 128, 192, 192,
][..],
];
let _ = Packet::<1024>::semi_decode(data); for data in cases {
let mut buf = [0; 1024];
let mut buf = Buffer::new_empty(&mut buf);
buf.extend(data);
let _ = Packet::<1024>::semi_decode(buf);
}
} }
#[test] #[test]
fn decode_fuzz_tests() { fn decode_fuzz_tests() {
let data = ArrayVec::try_from(&[4, 0, 0, 0][..]).unwrap(); let cases = [&[4, 0, 0, 0][..], &[4, 5, 0, 0, 0, 10, 255, 255, 0, 0][..]];
let _ = Packet::<1024>::decode(data); for data in cases {
let mut buf = [0; 1024];
let mut buf = Buffer::new_empty(&mut buf);
buf.extend(data);
let _ = Packet::<1024>::decode(buf);
}
}
#[test]
fn fully_decode_soft_fuzz_tests() {
// When adding to this, don't forget to do the u8 -> i8 conversion
let cases = [
&mut [
-39, -39, -39, -118, -58, -58, -58, -58, -89, -39, -118, -58, -58, -58, 34, 34, 34,
34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, -58, -58,
127, 81, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127, -86, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, -58, -58, 127,
81, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 34, 34, 34, 34, 34, 34, 34,
34, 34, 34, 34, -58, -58, 127, 81, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, -128, -128, -128, -128, -128, -128, -128, -128, -128,
-128, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, -128, -128,
][..],
&mut [
-73, -73, -73, -73, -75, -76, -73, -73, -73, -73, -73, -73, -73, -73, 73, 72, 72,
72, 72, 72, 72, 62, -73, -118, 120, 127, 127, 121, 127, 112, 127, 127,
],
];
for data in cases {
let mut buf = [0; 1024];
let _ = Packet::<1024>::fully_decode_soft::<8192, i8>(data, &mut buf);
}
} }
} }
pub use labrador_ldpc::decoder::DecodeFrom;
// Less than zero = 1 bit
// Greater than zero = 0 bit
pub trait FromHardBit {
fn from_hard_bit(bit: bool) -> Self;
}
impl<T: DecodeFrom> FromHardBit for T {
fn from_hard_bit(bit: bool) -> Self {
if bit {
-Self::one()
} else {
Self::one()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn e2e() {
assert!(f32::from_hard_bit(true).hard_bit());
assert!(!f32::from_hard_bit(false).hard_bit());
assert!(f64::from_hard_bit(true).hard_bit());
assert!(!f64::from_hard_bit(false).hard_bit());
assert!(i8::from_hard_bit(true).hard_bit());
assert!(!i8::from_hard_bit(false).hard_bit());
assert!(i16::from_hard_bit(true).hard_bit());
assert!(!i16::from_hard_bit(false).hard_bit());
assert!(i32::from_hard_bit(true).hard_bit());
assert!(!i32::from_hard_bit(false).hard_bit());
}
}
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Arbitrary(pub ArrayVec<u8, 255>); pub struct Arbitrary(pub ArrayVec<u8, 255>);
impl Arbitrary { impl Arbitrary {
......
...@@ -2,7 +2,7 @@ use core::str::FromStr; ...@@ -2,7 +2,7 @@ use core::str::FromStr;
use arrayvec::ArrayString; use arrayvec::ArrayString;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Destination { pub struct Destination {
ack: u8, ack: u8,
callsign: ArrayString<253>, callsign: ArrayString<253>,
...@@ -36,7 +36,7 @@ impl Destination { ...@@ -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 {
......
use core::fmt::{Debug, Display};
use half::f16; use half::f16;
#[derive(Debug, PartialEq)] #[derive(PartialEq, Clone)]
pub struct Gps { pub struct Gps {
latitude: i32, latitude: i32,
longitude: i32, longitude: i32,
...@@ -33,8 +34,14 @@ impl Gps { ...@@ -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 { ...@@ -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 { ...@@ -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 }")
}
}