use crate::block::{BlockCode, container_traits, expect_len};
use crate::rs::ReedSolomon;
use comnum_core::signal::hard_decisions;
use comnum_core::{Result, ensure};
mod ldpc;
mod turbo;
pub use ldpc::{Ar4jaRate, CcsdsLdpc};
pub use turbo::{CcsdsTurbo, TURBO_LENGTHS, TurboRate, turbo_permutation};
const DUAL_BASIS: [u8; 8] = [0x8D, 0xEF, 0xEC, 0x86, 0xFA, 0x99, 0xAF, 0x7B];
const DUAL_BASIS_INV: [u8; 8] = [0xC5, 0x42, 0x2E, 0xFD, 0xF0, 0x79, 0xAC, 0xCC];
const RS_FIELD: u32 = 0x187;
pub const RS_DEPTHS: [usize; 6] = [1, 2, 3, 4, 5, 8];
pub fn to_dual_basis(u: u8) -> u8 {
(0..8).filter(|r| (u >> (7 - r)) & 1 == 1).fold(0, |z, r| z ^ DUAL_BASIS[r])
}
pub fn from_dual_basis(z: u8) -> u8 {
(0..8).filter(|r| (z >> (7 - r)) & 1 == 1).fold(0, |u, r| u ^ DUAL_BASIS_INV[r])
}
#[derive(Clone, Debug, PartialEq)]
pub struct CcsdsRs {
rs: ReedSolomon,
depth: usize,
fill: usize,
}
impl CcsdsRs {
pub fn new(e: usize, depth: usize, fill: usize) -> Result<CcsdsRs> {
ensure(e == 8 || e == 16, || format!("the CCSDS Reed-Solomon codes correct E = 8 or 16 symbols, got {e}"))?;
ensure(RS_DEPTHS.contains(&depth), || {
format!("the interleaving depth must be 1, 2, 3, 4, 5 or 8, got {depth}")
})?;
ensure(fill < 255 - 2 * e, || format!("a virtual fill of {fill} symbols leaves no information"))?;
let rs = ReedSolomon::with_field(8, RS_FIELD, 128 - e, 11, e, 255 - fill)?;
Ok(CcsdsRs { rs, depth, fill })
}
pub fn e(&self) -> usize {
self.rs.t()
}
pub fn depth(&self) -> usize {
self.depth
}
pub fn fill(&self) -> usize {
self.fill
}
pub fn code(&self) -> &ReedSolomon {
&self.rs
}
pub fn frame_len(&self) -> usize {
self.rs.k_symbols() * self.depth
}
pub fn codeblock_len(&self) -> usize {
self.rs.n_symbols() * self.depth
}
pub fn encode_bytes(&self, frame: &[u8]) -> Result<Vec<u8>> {
expect_len(frame.len(), self.frame_len(), "octets")?;
let (k, i_depth) = (self.rs.k_symbols(), self.depth);
let parity = 2 * self.e();
let mut out = frame.to_vec();
out.resize(self.codeblock_len(), 0);
for i in 0..i_depth {
let msg: Vec<u16> = (0..k).map(|t| u16::from(from_dual_basis(frame[t * i_depth + i]))).collect();
let word = self.rs.encode_symbols(&msg)?;
for (t, &s) in word[k..].iter().enumerate() {
out[(k + t) * i_depth + i] = to_dual_basis(s as u8);
}
debug_assert_eq!(word.len(), k + parity);
}
Ok(out)
}
pub fn decode_bytes(&self, block: &[u8]) -> Result<(Vec<u8>, Option<usize>)> {
expect_len(block.len(), self.codeblock_len(), "octets")?;
let (n, k, i_depth) = (self.rs.n_symbols(), self.rs.k_symbols(), self.depth);
let mut frame = block[..self.frame_len()].to_vec();
let mut total = Some(0);
for i in 0..i_depth {
let r: Vec<u16> = (0..n).map(|t| u16::from(from_dual_basis(block[t * i_depth + i]))).collect();
let (msg, fixed) = self.rs.decode_symbols(&r)?;
match fixed {
Some(c) => {
total = total.map(|t| t + c);
for (t, &s) in msg.iter().enumerate().take(k) {
frame[t * i_depth + i] = to_dual_basis(s as u8);
}
}
None => total = None,
}
}
Ok((frame, total))
}
}
fn bits_to_bytes(bits: &[u8]) -> Vec<u8> {
bits.chunks(8).map(|b| b.iter().fold(0u8, |acc, &v| (acc << 1) | (v & 1))).collect()
}
fn bytes_to_bits(bytes: &[u8]) -> Vec<u8> {
bytes.iter().flat_map(|&b| (0..8).rev().map(move |k| (b >> k) & 1)).collect()
}
impl BlockCode for CcsdsRs {
fn k(&self) -> usize {
8 * self.frame_len()
}
fn n(&self) -> usize {
8 * self.codeblock_len()
}
fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
expect_len(info.len(), self.k(), "bits")?;
Ok(bytes_to_bits(&self.encode_bytes(&bits_to_bytes(info))?))
}
fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
expect_len(llr.len(), self.n(), "LLRs")?;
let (frame, _) = self.decode_bytes(&bits_to_bytes(&hard_decisions(llr)))?;
Ok(bytes_to_bits(&frame))
}
}
container_traits!(CcsdsRs);
#[cfg(test)]
mod tests {
use super::ldpc::ar4ja_pi;
use super::*;
use comnum_core::rng::Rng;
use comnum_math::gf2m::Field;
fn random_bits(n: usize, rng: &mut Rng) -> Vec<u8> {
(0..n).map(|_| rng.bit()).collect()
}
fn bpsk(c: &[u8], amplitude: f64) -> Vec<f64> {
c.iter().map(|&b| if b == 0 { amplitude } else { -amplitude }).collect()
}
#[test]
fn the_dual_basis_matrices_are_inverse_and_reproduce_annex_f() {
for u in 0..=255u8 {
assert_eq!(from_dual_basis(to_dual_basis(u)), u);
}
assert_eq!(from_dual_basis(0b1011_1001), 0b0010_1010);
let f = Field::new(8, RS_FIELD).unwrap();
assert_eq!(f.pow(213), 0b0010_1010);
assert_eq!(f.pow(152), 0b0101_1001);
assert_eq!(to_dual_basis(0b0101_1001), 0b1110_1000);
}
#[test]
fn the_generators_are_those_of_annex_g() {
let f = Field::new(8, RS_FIELD).unwrap();
let g16 = [0, 249, 59, 66, 4, 43, 126, 251, 97, 30, 3, 213, 50, 66, 170, 5, 24];
let g8 = [0, 30, 230, 49, 235, 129, 81, 76, 173];
for (e, half) in [(16, &g16[..]), (8, &g8[..])] {
let rs = CcsdsRs::new(e, 1, 0).unwrap();
let g = rs.code().generator();
assert_eq!(g.len(), 2 * e + 1);
for (i, &x) in half.iter().enumerate() {
assert_eq!(u32::from(g[i]), f.pow(x), "E = {e}, G{i}");
assert_eq!(g[i], g[2 * e - i], "E = {e}, G{i} = G{}", 2 * e - i);
}
}
}
#[test]
fn a_codeblock_corrects_e_symbols_per_codeword() {
let mut rng = Rng::new(1);
for (e, depth, fill) in [(16, 5, 0), (8, 2, 11), (16, 8, 3)] {
let rs = CcsdsRs::new(e, depth, fill).unwrap();
assert_eq!(rs.codeblock_len() - rs.frame_len(), 2 * e * depth);
let frame: Vec<u8> = (0..rs.frame_len()).map(|_| rng.below(256) as u8).collect();
let mut block = rs.encode_bytes(&frame).unwrap();
assert_eq!(&block[..frame.len()], &frame[..], "systematic");
for i in 0..depth {
for t in 0..e {
block[(3 * t + 1) * depth + i] ^= 0x5A;
}
}
let (out, fixed) = rs.decode_bytes(&block).unwrap();
assert_eq!(out, frame, "E = {e}, I = {depth}, q = {fill}");
assert_eq!(fixed, Some(e * depth));
}
assert!(CcsdsRs::new(16, 6, 0).is_err() && CcsdsRs::new(12, 1, 0).is_err());
}
#[test]
fn the_rate_7_8_code_is_that_of_table_c1() {
let code = CcsdsLdpc::c2().unwrap();
assert_eq!((code.k(), code.n()), (7136, 8160));
let mut rng = Rng::new(2);
let info = random_bits(7136, &mut rng);
assert!(code.ldpc().is_codeword(&code.full_codeword(&info).unwrap()));
let c = code.encode_block(&info).unwrap();
assert_eq!((c.len(), c[..7136] == info[..], &c[8158..]), (8160, true, &[0u8, 0][..]));
let llr: Vec<f64> = bpsk(&c, 1.0).iter().map(|v| 2.0 / 0.16 * (v + 0.4 * rng.gaussian())).collect();
assert_eq!(code.decode_block(&llr).unwrap(), info);
}
#[test]
fn the_ar4ja_codes_have_the_lengths_of_table_7_5_and_encode_codewords() {
let mut rng = Rng::new(3);
for (k, rate, n) in
[(1024, Ar4jaRate::R1_2, 2048), (1024, Ar4jaRate::R2_3, 1536), (1024, Ar4jaRate::R4_5, 1280)]
{
let code = CcsdsLdpc::ar4ja(k, rate).unwrap();
assert_eq!((code.k(), code.n()), (k, n), "rate {}", rate.name());
let info = random_bits(k, &mut rng);
assert!(code.ldpc().is_codeword(&code.full_codeword(&info).unwrap()), "rate {}", rate.name());
let c = code.encode_block(&info).unwrap();
assert_eq!(&c[..k], &info[..]);
let llr: Vec<f64> = bpsk(&c, 1.0).iter().map(|v| 2.0 / 0.16 * (v + 0.4 * rng.gaussian())).collect();
assert_eq!(code.decode_block(&llr).unwrap(), info, "rate {}", rate.name());
}
assert!(CcsdsLdpc::ar4ja(2048, Ar4jaRate::R1_2).is_err());
}
#[test]
fn the_permutations_of_the_ar4ja_codes_are_permutations() {
for m in [128, 256, 512, 1024, 2048, 4096, 8192] {
for k in 1..=26 {
let mut seen = vec![false; m];
for i in 0..m {
seen[ar4ja_pi(k, i, m)] = true;
}
assert!(seen.iter().all(|&s| s), "Pi_{k}, M = {m}");
}
}
}
#[test]
fn the_turbo_codes_have_the_lengths_of_table_6_2() {
let table = [
(1784, [3576, 5364, 7152, 10728]),
(3568, [7144, 10716, 14288, 21432]),
(7136, [14280, 21420, 28560, 42840]),
(8920, [17848, 26772, 35696, 53544]),
];
for (k, ns) in table {
for (r, n) in [TurboRate::R1_2, TurboRate::R1_3, TurboRate::R1_4, TurboRate::R1_6].into_iter().zip(ns) {
assert_eq!(CcsdsTurbo::new(k, r).unwrap().n(), n, "k = {k}, rate {}", r.name());
}
}
assert!(CcsdsTurbo::new(1000, TurboRate::R1_2).is_err());
}
#[test]
fn the_turbo_permutation_starts_as_computed_from_sec_6_3_g() {
for k in TURBO_LENGTHS {
let p = turbo_permutation(k).unwrap();
let first: Vec<usize> = p.perm()[..10].iter().map(|v| v + 1).collect();
assert_eq!(first, [4, 171, 300, 467, 596, 763, 892, 1059, 1188, 1355], "k = {k}");
}
}
#[test]
fn the_turbo_codes_terminate_and_decode() {
let mut rng = Rng::new(4);
for rate in [TurboRate::R1_2, TurboRate::R1_3, TurboRate::R1_4, TurboRate::R1_6] {
let code = CcsdsTurbo::new(1784, rate).unwrap();
let info = random_bits(1784, &mut rng);
let c = code.encode_block(&info).unwrap();
assert_eq!(c.len(), code.n());
let per = c.len() / (1784 + 4);
if rate != TurboRate::R1_2 {
let sys: Vec<u8> = (0..1784).map(|t| c[t * per]).collect();
assert_eq!(sys, info);
}
let llr: Vec<f64> = bpsk(&c, 1.0).iter().map(|v| 2.0 * (v + 0.5 * rng.gaussian())).collect();
assert_eq!(code.decode_block(&llr).unwrap(), info, "rate {}", rate.name());
}
}
}