diff --git a/src/buffer.rs b/src/buffer.rs index 13db9eedd91d8c6ff8974cf0d2cf96f5d257c5b4..f2bc8eab4b8b206a66a4ba16f2f5fdd169add3f4 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -4,28 +4,28 @@ use core::ops::{Deref, DerefMut}; pub struct BufferOverflow; #[derive(Debug)] -pub struct Buffer<'a, const N: usize> { - data: &'a mut [u8; N], +pub struct Buffer<'a, const N: usize, T = u8> { + data: &'a mut [T; N], i: usize, } -impl<'a, const N: usize> Buffer<'a, N> { +impl<'a, const N: usize, T: Copy> Buffer<'a, N, T> { /// Constructs a new `Buffer`. /// `data` is the backing array. /// `i` is the number of elements in `data` that contain data (and should thus be exposed by `Buffer`) - pub fn new(data: &'a mut [u8; N], i: usize) -> Self { + pub fn new(data: &'a mut [T; N], i: usize) -> Self { assert!(i <= data.len()); Self { data, i } } - pub fn new_full(data: &'a mut [u8; N]) -> Self { + pub fn new_full(data: &'a mut [T; N]) -> Self { let i = data.len(); Self::new(data, i) } - pub fn new_empty(data: &'a mut [u8; N]) -> Self { + pub fn new_empty(data: &'a mut [T; N]) -> Self { Self::new(data, 0) } @@ -33,7 +33,7 @@ impl<'a, const N: usize> Buffer<'a, N> { N - self.i } - pub fn try_push(&mut self, v: u8) -> Result<(), BufferOverflow> { + pub fn try_push(&mut self, v: T) -> Result<(), BufferOverflow> { if self.i == N { return Err(BufferOverflow); } @@ -44,11 +44,11 @@ impl<'a, const N: usize> Buffer<'a, N> { Ok(()) } - pub fn push(&mut self, v: u8) { + pub fn push(&mut self, v: T) { self.try_push(v).unwrap(); } - pub fn try_extend_from_slice(&mut self, other: &[u8]) -> Result<(), BufferOverflow> { + pub fn try_extend_from_slice(&mut self, other: &[T]) -> Result<(), BufferOverflow> { if self.remaining_capacity() < other.len() { return Err(BufferOverflow); } @@ -59,11 +59,11 @@ impl<'a, const N: usize> Buffer<'a, N> { Ok(()) } - pub fn extend(&mut self, other: &[u8]) { + pub fn extend(&mut self, other: &[T]) { self.try_extend_from_slice(other).unwrap(); } - pub fn pop(&mut self) -> Option<u8> { + pub fn pop(&mut self) -> Option<T> { if self.i == 0 { return None; } @@ -92,7 +92,7 @@ impl<'a, const N: usize> Buffer<'a, N> { self.i -= delta; } - pub fn clone_backing<'b>(&self, buf: &'b mut [u8; N]) -> Buffer<'b, N> { + pub fn clone_backing<'b>(&self, buf: &'b mut [T; N]) -> Buffer<'b, N, T> { let mut out = Buffer::new_empty(buf); out.extend(self); @@ -101,22 +101,22 @@ impl<'a, const N: usize> Buffer<'a, N> { } } -impl<'a, const N: usize> From<&'a mut [u8; N]> for Buffer<'a, N> { - fn from(data: &'a mut [u8; N]) -> Self { +impl<'a, const N: usize, T> From<&'a mut [T; N]> for Buffer<'a, N, T> { + fn from(data: &'a mut [T; N]) -> Self { Self { data, i: 0 } } } -impl<'a, const N: usize> Deref for Buffer<'a, N> { - type Target = [u8]; +impl<'a, const N: usize, T> Deref for Buffer<'a, N, T> { + type Target = [T]; fn deref(&self) -> &Self::Target { &self.data[..self.i] } } -impl<'a, const N: usize> DerefMut for Buffer<'a, N> { - fn deref_mut(&mut self) -> &mut [u8] { +impl<'a, const N: usize, T> DerefMut for Buffer<'a, N, T> { + fn deref_mut(&mut self) -> &mut [T] { &mut self.data[..self.i] } } diff --git a/src/interleaver.rs b/src/interleaver.rs index 5efeaaf7a3fdded2468749e22dd88dfe01383ff0..e6386968ca7868cc31b24fe4b018dd427b1db715 100644 --- a/src/interleaver.rs +++ b/src/interleaver.rs @@ -1,4 +1,5 @@ use bitvec::prelude::*; +use labrador_ldpc::decoder::DecodeFrom; use crate::{ buffer::{Buffer, BufferOverflow}, @@ -60,8 +61,34 @@ pub(crate) fn uninterleave<const N: usize>( Ok(()) } +pub(crate) fn uninterleave_soft<const N: usize, T: DecodeFrom>( + data: &[T], + out: &mut Buffer<N, T>, +) -> Result<(), BufferOverflow> { + for _ in 0..data.len() { + out.try_push(T::zero())?; + } + + let mut out_i = 0; + for i in 0..32 { + for j in (0..data.len()).step_by(32) { + if i + j >= data.len() { + continue; + } + + out[i + j] = data[out_i]; + + out_i += 1; + } + } + + Ok(()) +} + #[cfg(test)] mod tests { + use crate::soft_bit::SoftBit; + use super::*; #[test] @@ -84,4 +111,34 @@ mod tests { assert_eq!(*orig, *uninterleaved); } + + #[test] + fn hard_interleave_soft_uninterleave() { + let mut data = [0x84, 0x73, 0x12, 0xA3, 0xFF, 0x00, 0xC2, 0x1B, 0x77]; + let orig = Buffer::new_full(&mut data); + + let mut interleaved = [0; 10]; + let mut interleaved = Buffer::new(&mut interleaved, 0); + interleaved.push(b'H'); + + interleave(&orig, &mut interleaved).unwrap(); + + let mut soft_interleaved = [0.0; 10 * 8]; + for (i, b) in interleaved.iter().enumerate() { + for j in 0..8 { + soft_interleaved[8 * i + j] = f32::from_hard_bit(b & (1 << (7 - j)) > 0); + } + } + + let mut uninterleaved = [0.0; 10 * 8]; + let mut uninterleaved = Buffer::new(&mut uninterleaved, 0); + uninterleave_soft(&soft_interleaved[8..], &mut uninterleaved).unwrap(); + + assert_eq!(orig.len() * 8, uninterleaved.len()); + for (i, b) in orig.iter().enumerate() { + for j in 0..8 { + assert_eq!(uninterleaved[8 * i + j].hard_bit(), *b & (1 << (7 - j)) > 0); + } + } + } } diff --git a/src/ldpc.rs b/src/ldpc.rs index a3cda75036ccfb1cadf6a4159707913c59198dc5..2c00c1b1f974aa86121396b2174e6822eb02dce8 100644 --- a/src/ldpc.rs +++ b/src/ldpc.rs @@ -1,6 +1,6 @@ -use labrador_ldpc::LDPCCode; +use labrador_ldpc::{decoder::DecodeFrom, LDPCCode}; -use crate::{buffer::Buffer, error::EncodeError}; +use crate::{buffer::Buffer, error::EncodeError, soft_bit::SoftBit}; macro_rules! enc_chunk { ($d:ident, $i:ident, $t:ident, $n:literal) => { @@ -36,6 +36,27 @@ macro_rules! dec_chunk { }; } +macro_rules! dec_chunk_soft { + ($d:ident, $p:ident, $w:ident, $w_u8:ident, $out:ident, $t:ident, $n:literal) => { + ::paste::paste! { + let code_data = &mut $d[..$n]; + let code_parity = &$p.get_mut(..$n)?; + + let mut input = [T::zero(); $n * 2]; + input[..$n].copy_from_slice(code_data); + input[$n..].copy_from_slice(code_parity); + const CODE: LDPCCode = LDPCCode::[<$t>]; + + let mut out_tmp = [0; CODE.output_len()]; + CODE.decode_ms(&input, &mut out_tmp, &mut $w[..CODE.decode_ms_working_len()], &mut $w_u8[..CODE.decode_ms_working_u8_len()], 16); + $out.try_extend_from_slice(&out_tmp[..$n/8]).ok()?; + + $d = &mut $d[$n..]; + $p = &mut $p[$n..]; + } + }; +} + // On failure this still modifies the data array! pub(crate) fn encode<const N: usize>(data: &mut Buffer<N>) -> Result<(), EncodeError> { let mut i = 0; @@ -153,8 +174,110 @@ pub(crate) fn decode<const N: usize>(data_av: &mut Buffer<N>) -> Option<()> { Some(()) } +pub(crate) fn decode_soft<const N: usize, const M: usize, T: DecodeFrom>( + data_av: &mut Buffer<N, T>, + out: &mut Buffer<M>, +) -> Option<()> { + if data_av.len() % 8 != 0 { + return None; + } + + if data_av.len() < 16 { + return None; + } + + let len: usize = len_from_soft(data_av[(data_av.len() - 16)..].try_into().unwrap()).into(); + + if len >= data_av.len() { + return None; + } + + let (mut data, parity) = data_av.split_at_mut(len * 8); + let mut parity = parity.get_mut(..parity.len().checked_sub(16)?)?; + + let mut working = [T::zero(); LDPCCode::TM8192.decode_ms_working_len()]; + let mut working_u8 = [0; LDPCCode::TM8192.decode_ms_working_u8_len()]; + + loop { + match data.len() { + 4096.. => { + dec_chunk_soft!(data, parity, working, working_u8, out, TM8192, 4096); + } + + 1024.. => { + dec_chunk_soft!(data, parity, working, working_u8, out, TM2048, 1024); + } + + 256.. => { + dec_chunk_soft!(data, parity, working, working_u8, out, TC512, 256); + } + + 128.. => { + dec_chunk_soft!(data, parity, working, working_u8, out, TC256, 128); + } + + 64.. => { + dec_chunk_soft!(data, parity, working, working_u8, out, TC128, 64); + } + + 0 => break, + + _ => { + // Extra bits are padded with 0xAA + // We need to tell the soft decoder that these bits can't have flipped + // So we use T::maxval + let mut code_data = [ + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + ] + .map(|x| if x > 0 { -T::maxval() } else { T::maxval() }); + code_data[..data.len()].copy_from_slice(data); + let code_parity = &parity.get_mut(..64)?; + + let mut input = [T::zero(); 128]; + input[..64].copy_from_slice(&code_data); + input[64..].copy_from_slice(code_parity); + let mut tmp_out = [0; LDPCCode::TC128.output_len()]; + LDPCCode::TC128.decode_ms( + &input, + &mut tmp_out, + &mut working[..LDPCCode::TC128.decode_ms_working_len()], + &mut working_u8[..LDPCCode::TC128.decode_ms_working_u8_len()], + 16, + ); + out.try_extend_from_slice(&tmp_out[..(data.len() / 8)]) + .ok()?; + + data = &mut data[..0]; + parity = &mut parity[..0]; + } + } + } + + Some(()) +} + +fn len_from_soft<T: DecodeFrom>(bits: &[T; 16]) -> u16 { + let mut upper = 0; + for b in &bits[0..8] { + upper <<= 1; + upper |= u8::from(b.hard_bit()); + } + + let mut lower = 0; + for b in &bits[8..] { + lower <<= 1; + lower |= u8::from(b.hard_bit()); + } + + u16::from_le_bytes([upper, lower]) +} + #[cfg(test)] mod tests { + use bitvec::{order::Msb0, view::BitView}; + use super::*; #[test] @@ -230,4 +353,30 @@ mod tests { assert_eq!(*orig, *data); } + + #[test] + fn basic_encode_decode_soft() { + let mut buf = [0; 8191]; + let mut buf2 = [0; 8191]; + let mut data = Buffer::new_empty(&mut buf); + for _ in 0..50 { + data.extend(b"This is a test packet. jsalksjd093809324JASLD:LKD*#$)(*#@)"); + } + let orig = data.clone_backing(&mut buf2); + + encode(&mut data).unwrap(); + assert_ne!(*orig, *data); + + let mut soft = [0.0; 8191 * 8]; + let mut soft = Buffer::new_empty(&mut soft); + for b in data.view_bits::<Msb0>() { + soft.push(f32::from_hard_bit(*b)); + } + + let mut out = [0; 8191]; + let mut out = Buffer::new_empty(&mut out); + decode_soft(&mut soft, &mut out).unwrap(); + + assert_eq!(*orig, *out); + } } diff --git a/src/lib.rs b/src/lib.rs index 949884264dbbf964753d7be43ab73792fc86c3aa..e2939177b9799de4a9299ae6548214d5d3720482 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub mod identity; pub mod interleaver; pub mod ldpc; pub mod packet; +pub mod soft_bit; pub mod whisker; pub mod whitener; diff --git a/src/packet.rs b/src/packet.rs index 66f05704ed3ddad9d82bdcb95ab83f3d720d83bf..13f9db32c460e1c3f5074d6bd59364f40db0ff34 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,6 +1,7 @@ use core::fmt::Debug; use crc::{Crc, CRC_16_IBM_SDLC}; +use labrador_ldpc::decoder::DecodeFrom; use crate::{ buffer::{Buffer, BufferOverflow}, @@ -183,7 +184,7 @@ impl<'a, const N: usize> Packet<'a, N> { /// Decodes packet that was received over the air. /// Packet shouldn't have preamble, sync word, or data langth L. - /// Expects bytes in the `buf` + /// Expects bytes in `data` /// `buf` is used as the backing buffer for the Packet pub fn fully_decode(data: &[u8], buf: &'a mut [u8; N]) -> Result<Self, DecodeError> { let mut buf = Buffer::new_empty(buf); @@ -194,6 +195,22 @@ impl<'a, const N: usize> Packet<'a, N> { Self::semi_decode(buf) } + /// Expects soft bits in `data`. Bits should be LLR, with positive numbers more likely to be 0. + /// Returns `DecodeError::Overflow` if `M` is less than `data.len()`. + pub fn fully_decode_soft<const M: usize, T: DecodeFrom>( + data: &mut [T], + buf: &'a mut [u8; N], + ) -> Result<Self, DecodeError> { + let mut out = [T::zero(); M]; + let mut out = Buffer::new_empty(&mut out); + interleaver::uninterleave_soft(data, &mut out).map_err(|_| DecodeError::Overflow)?; + let mut buf = Buffer::new_empty(buf); + ldpc::decode_soft(&mut out, &mut buf).ok_or(DecodeError::LdpcError)?; + whitener::whiten(&mut buf); + + Self::semi_decode(buf) + } + pub fn iter(&self) -> ValidatedWhiskerIter { ValidatedWhiskerIter::new(&self.buf) } @@ -391,8 +408,9 @@ fn try_lock<'a, const N: usize, T, E, F: Fn(&mut Buffer<'a, N>) -> Result<T, E>> #[cfg(test)] mod tests { use arrayvec::ArrayString; + use bitvec::{order::Msb0, view::BitView}; - use crate::whisker::NodeInfoBuilder; + use crate::{soft_bit::SoftBit, whisker::NodeInfoBuilder}; use super::*; @@ -519,6 +537,60 @@ mod tests { assert_eq!(comment, packet2.comment(&mut buf).unwrap()); } + #[test] + fn full_e2e_soft_decode() { + let comment = "Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker."; + + let mut buf = [0; 4096]; + let mut packet = Packet::new(&mut buf); + packet + .add_identification(Identification { + icon: 123, + callsign: ArrayString::from("ABCXYZ_LONG_CALL").unwrap(), + ssid: 43, + }) + .unwrap(); + + packet.add_comment(comment).unwrap(); + + let res = packet.add_identification(Identification { + icon: 456, + callsign: ArrayString::from("NOPE").unwrap(), + ssid: 0, + }); + assert!(matches!(res, Err(EncodeError::DuplicateData))); + + let mut buf2 = [0; 4096]; + let mut fully = Buffer::new_empty(&mut buf2); + packet.fully_encode(&mut fully).unwrap(); + + fully[40] ^= 0x55; + fully[844] ^= 0x7B; + + let mut soft = [0.0; 8191 * 8]; + let mut soft = Buffer::new_empty(&mut soft); + for b in fully.view_bits::<Msb0>().iter() { + soft.push(f32::from_hard_bit(*b)); + } + let soft = &mut soft[16..]; + + let mut buf3 = [0; 8191]; + // exclude length + let packet2: Packet<8191> = + Packet::fully_decode_soft::<{ 8191 * 8 }, _>(soft, &mut buf3).unwrap(); + assert_eq!( + Identification { + icon: 123, + callsign: ArrayString::from("ABCXYZ_LONG_CALL").unwrap(), + ssid: 43, + }, + packet2.identification().unwrap() + ); + + let mut buf = [0; 1024]; + assert_eq!(comment, packet2.comment(&mut buf).unwrap()); + } + #[test] fn node_info_e2e() { let mut buf = [0; 4096]; diff --git a/src/soft_bit.rs b/src/soft_bit.rs new file mode 100644 index 0000000000000000000000000000000000000000..a7ed66861c5159a57a6601dc3a692e8b306a8958 --- /dev/null +++ b/src/soft_bit.rs @@ -0,0 +1,45 @@ +use labrador_ldpc::decoder::DecodeFrom; + +// Less than zero = 1 bit +// Greater than zero = 0 bit +pub trait SoftBit { + fn from_hard_bit(bit: bool) -> Self; + fn hard_bit(&self) -> bool; +} + +impl<T: DecodeFrom> SoftBit for T { + fn from_hard_bit(bit: bool) -> Self { + if bit { + -Self::one() + } else { + Self::one() + } + } + + fn hard_bit(&self) -> bool { + self < &T::zero() + } +} + +#[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()); + } +} diff --git a/src/whitener.rs b/src/whitener.rs index 0c66dc54825bd0d2415ae3baaada447aab8a0b01..3b10ca0ac7de1067e994bafd9acd255174b8349f 100644 --- a/src/whitener.rs +++ b/src/whitener.rs @@ -1,5 +1,7 @@ +const START_STATE: u16 = 0xE9CF; + pub(crate) fn whiten(data: &mut [u8]) { - let mut state = 0xE9CF; + let mut state = START_STATE; for d in data.iter_mut() { let b;