const A0: u8 = Rs63_12::NN as u8;
#[derive(Clone, Debug)]
pub struct Rs63_12 {
alpha_to: [u8; 64],
index_of: [u8; 64],
genpoly: [u8; Rs63_12::NROOTS + 1],
}
impl Default for Rs63_12 {
fn default() -> Self {
Self::new()
}
}
impl Rs63_12 {
pub const NN: usize = 63;
pub const MM: u32 = 6;
pub const FCR: u32 = 3;
pub const PRIM: u32 = 1;
pub const NROOTS: usize = 51;
pub const K_SYMBOLS: usize = Self::NN - Self::NROOTS;
pub const N_SYMBOLS: usize = Self::NN;
const GFPOLY: u32 = 0x43;
pub fn new() -> Self {
let mut alpha_to = [0u8; 64];
let mut index_of = [0u8; 64];
index_of[0] = A0; alpha_to[A0 as usize] = 0; let mut sr: u32 = 1;
for i in 0..Self::NN {
index_of[sr as usize] = i as u8;
alpha_to[i] = sr as u8;
sr <<= 1;
if sr & (1 << Self::MM) != 0 {
sr ^= Self::GFPOLY;
}
sr &= Self::NN as u32;
}
debug_assert_eq!(sr, 1, "gfpoly must be primitive");
let mut genpoly = [0u8; Self::NROOTS + 1];
genpoly[0] = 1;
for i in 0..Self::NROOTS {
let root = Self::FCR * Self::PRIM + i as u32 * Self::PRIM;
genpoly[i + 1] = 1;
for j in (1..=i).rev() {
if genpoly[j] != 0 {
let idx = Self::modnn(index_of[genpoly[j] as usize] as u32 + root);
genpoly[j] = genpoly[j - 1] ^ alpha_to[idx as usize];
} else {
genpoly[j] = genpoly[j - 1];
}
}
let idx = Self::modnn(index_of[genpoly[0] as usize] as u32 + root);
genpoly[0] = alpha_to[idx as usize];
}
for g in genpoly.iter_mut() {
*g = index_of[*g as usize];
}
Self {
alpha_to,
index_of,
genpoly,
}
}
#[inline]
fn modnn(mut x: u32) -> u32 {
while x >= Self::NN as u32 {
x -= Self::NN as u32;
x = (x >> Self::MM) + (x & Self::NN as u32);
}
x
}
pub fn encode_native(&self, info: &[u8; Self::K_SYMBOLS]) -> [u8; Self::N_SYMBOLS] {
let mut bb = [0u8; Self::NROOTS];
for i in 0..Self::K_SYMBOLS {
let feedback = self.index_of[(info[i] ^ bb[0]) as usize];
if feedback != A0 {
for j in 1..Self::NROOTS {
bb[j] ^= self.alpha_to[Self::modnn(
feedback as u32 + self.genpoly[Self::NROOTS - j] as u32,
) as usize];
}
}
for j in 0..Self::NROOTS - 1 {
bb[j] = bb[j + 1];
}
bb[Self::NROOTS - 1] = if feedback != A0 {
self.alpha_to[Self::modnn(feedback as u32 + self.genpoly[0] as u32) as usize]
} else {
0
};
}
let mut out = [0u8; Self::N_SYMBOLS];
out[..Self::K_SYMBOLS].copy_from_slice(info);
out[Self::K_SYMBOLS..].copy_from_slice(&bb);
out
}
pub fn decode_native(
&self,
data: &[u8; Self::N_SYMBOLS],
) -> Option<([u8; Self::K_SYMBOLS], u32)> {
self.decode_native_erasures(data, &[])
}
pub fn decode_native_erasures(
&self,
data: &[u8; Self::N_SYMBOLS],
eras_pos: &[u32],
) -> Option<([u8; Self::K_SYMBOLS], u32)> {
let no_eras = eras_pos.len();
if no_eras > Self::NROOTS {
return None; }
let mut recd = *data;
let mut s = [0u8; Self::NROOTS];
for i in 0..Self::NROOTS {
s[i] = recd[0];
}
for j in 1..Self::NN {
for i in 0..Self::NROOTS {
if s[i] == 0 {
s[i] = recd[j];
} else {
let sidx =
self.index_of[s[i] as usize] as u32 + (Self::FCR + i as u32) * Self::PRIM;
s[i] = recd[j] ^ self.alpha_to[Self::modnn(sidx) as usize];
}
}
}
let mut syn_error: u8 = 0;
for i in 0..Self::NROOTS {
syn_error |= s[i];
s[i] = self.index_of[s[i] as usize];
}
if syn_error == 0 {
let mut info = [0u8; Self::K_SYMBOLS];
info.copy_from_slice(&recd[..Self::K_SYMBOLS]);
return Some((info, 0));
}
let mut lambda = [0u8; Self::NROOTS + 1];
lambda[0] = 1;
let mut b = [0u8; Self::NROOTS + 1];
let mut t = [0u8; Self::NROOTS + 1];
if no_eras > 0 {
for &pos in eras_pos {
if pos as usize >= Self::NN {
return None;
}
}
let e0 = Self::modnn(Self::PRIM * (Self::NN as u32 - 1 - eras_pos[0]));
lambda[1] = self.alpha_to[e0 as usize];
for i in 1..no_eras {
let u = Self::modnn(Self::PRIM * (Self::NN as u32 - 1 - eras_pos[i]));
for j in (1..=i + 1).rev() {
let tmp = self.index_of[lambda[j - 1] as usize];
if tmp != A0 {
lambda[j] ^= self.alpha_to[Self::modnn(u + tmp as u32) as usize];
}
}
}
}
for i in 0..Self::NROOTS + 1 {
b[i] = self.index_of[lambda[i] as usize];
}
let mut el: i32 = no_eras as i32;
for r in (no_eras + 1)..=Self::NROOTS {
let mut discr_r: u8 = 0;
for i in 0..r {
if lambda[i] != 0 && s[r - i - 1] != A0 {
let idx = self.index_of[lambda[i] as usize] as u32 + s[r - i - 1] as u32;
discr_r ^= self.alpha_to[Self::modnn(idx) as usize];
}
}
let discr_idx = self.index_of[discr_r as usize];
if discr_idx == A0 {
for j in (1..=Self::NROOTS).rev() {
b[j] = b[j - 1];
}
b[0] = A0;
} else {
t[0] = lambda[0];
for i in 0..Self::NROOTS {
if b[i] != A0 {
t[i + 1] = lambda[i + 1]
^ self.alpha_to[Self::modnn(discr_idx as u32 + b[i] as u32) as usize];
} else {
t[i + 1] = lambda[i + 1];
}
}
if 2 * el < r as i32 + no_eras as i32 {
el = r as i32 + no_eras as i32 - el;
for i in 0..=Self::NROOTS {
b[i] = if lambda[i] == 0 {
A0
} else {
Self::modnn(
self.index_of[lambda[i] as usize] as u32 + Self::NN as u32
- discr_idx as u32,
) as u8
};
}
} else {
for j in (1..=Self::NROOTS).rev() {
b[j] = b[j - 1];
}
b[0] = A0;
}
lambda.copy_from_slice(&t);
}
}
let mut deg_lambda = 0usize;
let mut lambda_idx = [0u8; Self::NROOTS + 1];
for i in 0..Self::NROOTS + 1 {
lambda_idx[i] = self.index_of[lambda[i] as usize];
if lambda_idx[i] != A0 {
deg_lambda = i;
}
}
let iprim: u32 = Self::find_iprim();
let mut reg = [0u8; Self::NROOTS + 1];
reg[1..].copy_from_slice(&lambda_idx[1..]);
let mut root = [0u32; Self::NROOTS];
let mut loc = [0u32; Self::NROOTS];
let mut count = 0usize;
let mut k_idx: u32 = Self::modnn(iprim + Self::NN as u32 - 1);
for i in 1..=Self::NN as u32 {
let mut q: u8 = 1;
for j in (1..=deg_lambda).rev() {
if reg[j] != A0 {
reg[j] = Self::modnn(reg[j] as u32 + j as u32) as u8;
q ^= self.alpha_to[reg[j] as usize];
}
}
if q != 0 {
k_idx = Self::modnn(k_idx + iprim);
continue;
}
root[count] = i;
loc[count] = k_idx;
count += 1;
if count == deg_lambda {
break;
}
k_idx = Self::modnn(k_idx + iprim);
}
if deg_lambda != count {
return None; }
let deg_omega = deg_lambda.saturating_sub(1);
let mut omega = [0u8; Self::NROOTS + 1];
for i in 0..=deg_omega {
let mut tmp: u8 = 0;
for j in 0..=i {
if s[i - j] != A0 && lambda_idx[j] != A0 {
tmp ^=
self.alpha_to[Self::modnn(s[i - j] as u32 + lambda_idx[j] as u32) as usize];
}
}
omega[i] = self.index_of[tmp as usize];
}
for j in (0..count).rev() {
let mut num1: u8 = 0;
for i in (0..=deg_omega).rev() {
if omega[i] != A0 {
num1 ^=
self.alpha_to[Self::modnn(omega[i] as u32 + (i as u32) * root[j]) as usize];
}
}
let num2 =
self.alpha_to[Self::modnn(root[j] * (Self::FCR - 1) + Self::NN as u32) as usize];
let mut den: u8 = 0;
let end = deg_lambda.min(Self::NROOTS - 1) & !1;
let mut i = end as i32;
while i >= 0 {
if lambda_idx[i as usize + 1] != A0 {
den ^= self.alpha_to[Self::modnn(
lambda_idx[i as usize + 1] as u32 + (i as u32) * root[j],
) as usize];
}
i -= 2;
}
if den == 0 {
return None;
}
if num1 != 0 {
let err = self.alpha_to[Self::modnn(
self.index_of[num1 as usize] as u32
+ self.index_of[num2 as usize] as u32
+ Self::NN as u32
- self.index_of[den as usize] as u32,
) as usize];
let pos = loc[j] as usize;
if pos < Self::NN {
recd[pos] ^= err;
}
}
}
let mut info = [0u8; Self::K_SYMBOLS];
info.copy_from_slice(&recd[..Self::K_SYMBOLS]);
Some((info, count as u32))
}
fn find_iprim() -> u32 {
let mut iprim: u32 = 1;
while !iprim.is_multiple_of(Self::PRIM) {
iprim += Self::NN as u32;
}
iprim / Self::PRIM
}
pub fn encode_jt65(&self, info: &[u8; Self::K_SYMBOLS]) -> [u8; Self::N_SYMBOLS] {
let mut dat1 = [0u8; Self::K_SYMBOLS];
for i in 0..Self::K_SYMBOLS {
dat1[i] = info[Self::K_SYMBOLS - 1 - i];
}
let cw = self.encode_native(&dat1);
let mut sent = [0u8; Self::N_SYMBOLS];
for i in 0..Self::NROOTS {
sent[Self::NROOTS - 1 - i] = cw[Self::K_SYMBOLS + i];
}
for i in 0..Self::K_SYMBOLS {
sent[Self::NROOTS + i] = dat1[Self::K_SYMBOLS - 1 - i];
}
sent
}
pub fn decode_jt65(
&self,
recd0: &[u8; Self::N_SYMBOLS],
) -> Option<([u8; Self::K_SYMBOLS], u32)> {
self.decode_jt65_erasures(recd0, &[])
}
pub fn decode_jt65_erasures(
&self,
recd0: &[u8; Self::N_SYMBOLS],
eras_pos_wsjt: &[u32],
) -> Option<([u8; Self::K_SYMBOLS], u32)> {
let mut recd = [0u8; Self::N_SYMBOLS];
for i in 0..Self::K_SYMBOLS {
recd[i] = recd0[Self::NN - 1 - i];
}
for i in 0..Self::NROOTS {
recd[Self::K_SYMBOLS + i] = recd0[Self::NROOTS - 1 - i];
}
let eras_native: Vec<u32> = eras_pos_wsjt
.iter()
.filter(|&&p| (p as usize) < Self::NN)
.map(|&p| (Self::NN as u32 - 1) - p)
.collect();
let (info_native, nerr) = self.decode_native_erasures(&recd, &eras_native)?;
let mut info = [0u8; Self::K_SYMBOLS];
for i in 0..Self::K_SYMBOLS {
info[i] = info_native[Self::K_SYMBOLS - 1 - i];
}
Some((info, nerr))
}
}
use crate::core::{FecOpts, FecResult};
impl super::FecCodec for Rs63_12 {
const N: usize = Rs63_12::N_SYMBOLS * 6; const K: usize = Rs63_12::K_SYMBOLS * 6;
fn encode(&self, info: &[u8], codeword: &mut [u8]) {
assert_eq!(info.len(), Self::K);
assert_eq!(codeword.len(), Self::N);
let mut info_syms = [0u8; Rs63_12::K_SYMBOLS];
for (i, slot) in info_syms.iter_mut().enumerate() {
let mut w = 0u8;
for b in 0..6 {
w = (w << 1) | (info[6 * i + b] & 1);
}
*slot = w;
}
let sent = self.encode_jt65(&info_syms);
for (i, &sym) in sent.iter().enumerate() {
for b in 0..6 {
codeword[6 * i + b] = (sym >> (5 - b)) & 1;
}
}
}
fn decode_soft(&self, _llr: &[f32], _opts: &FecOpts) -> Option<FecResult> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tables_are_primitive() {
let rs = Rs63_12::new();
for x in 1u8..=62 {
let lg = rs.index_of[x as usize];
assert_ne!(lg, A0, "log of {x} is A0");
assert_eq!(
rs.alpha_to[lg as usize], x,
"alpha_to[index_of[{x}]] != {x}"
);
}
}
#[test]
fn encode_clean_codeword_has_zero_syndrome() {
let rs = Rs63_12::new();
let info: [u8; 12] = [0, 1, 2, 3, 4, 5, 62, 61, 7, 8, 33, 44];
let cw = rs.encode_native(&info);
let (decoded, nerr) = rs.decode_native(&cw).expect("clean decode");
assert_eq!(decoded, info);
assert_eq!(nerr, 0);
}
#[test]
fn corrects_single_error() {
let rs = Rs63_12::new();
let info: [u8; 12] = [12, 34, 56, 7, 8, 9, 10, 11, 42, 21, 0, 63 & 0x3f];
let mut cw = rs.encode_native(&info);
cw[17] ^= 0x2a; let (decoded, nerr) = rs.decode_native(&cw).expect("1 error correctable");
assert_eq!(decoded, info);
assert_eq!(nerr, 1);
}
#[test]
fn corrects_max_errors() {
let rs = Rs63_12::new();
let info: [u8; 12] = [5, 17, 29, 41, 53, 62, 1, 13, 25, 37, 49, 61];
let mut cw = rs.encode_native(&info);
let positions = [
0, 3, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59, 62, 1,
4, 7,
];
for (i, &p) in positions.iter().enumerate() {
let delta = ((i as u8 * 7 + 1) & 0x1f) | 1;
cw[p] ^= delta;
debug_assert!(delta != 0);
}
let (decoded, nerr) = rs.decode_native(&cw).expect("25 errors correctable");
assert_eq!(decoded, info);
assert_eq!(nerr, 25);
}
#[test]
fn rejects_26_errors() {
let rs = Rs63_12::new();
let info: [u8; 12] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
let mut cw = rs.encode_native(&info);
for p in 0..26 {
cw[p] ^= 0x15;
}
match rs.decode_native(&cw) {
None => {} Some((decoded, _)) => {
assert_ne!(decoded, info, "must not decode to original beyond bound");
}
}
}
#[test]
fn erasures_only_all_51_parity() {
let rs = Rs63_12::new();
let info: [u8; 12] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 42, 21];
let mut cw = rs.encode_native(&info);
let mut eras = Vec::new();
for p in 12..63u32 {
cw[p as usize] = 0;
eras.push(p);
}
let (decoded, nerr) = rs
.decode_native_erasures(&cw, &eras)
.expect("51-erasure decode");
assert_eq!(decoded, info);
assert_eq!(nerr, 51);
}
#[test]
fn erasures_let_us_correct_beyond_25_errors() {
let rs = Rs63_12::new();
let info: [u8; 12] = [1, 13, 25, 37, 49, 61, 5, 17, 29, 41, 53, 62];
let mut cw = rs.encode_native(&info);
let positions: Vec<usize> = {
let mut s = Vec::with_capacity(30);
let mut used = [false; 63];
for i in 0..63 {
let p = (i * 2) % 63;
if !used[p] {
used[p] = true;
s.push(p);
if s.len() == 30 {
break;
}
}
}
s
};
for (i, &p) in positions.iter().enumerate() {
let delta = ((i as u8 * 7 + 1) & 0x1f) | 1;
cw[p] ^= delta;
}
let eras: Vec<u32> = positions.iter().take(20).map(|&p| p as u32).collect();
let (decoded, _nerr) = rs
.decode_native_erasures(&cw, &eras)
.expect("20 erasures + 10 errors must decode");
assert_eq!(decoded, info);
}
#[test]
fn jt65_wrapper_roundtrip() {
let rs = Rs63_12::new();
let info: [u8; 12] = [0, 1, 2, 3, 4, 5, 62, 61, 7, 8, 33, 44];
let sent = rs.encode_jt65(&info);
let (decoded, nerr) = rs.decode_jt65(&sent).expect("jt65 clean roundtrip");
assert_eq!(decoded, info);
assert_eq!(nerr, 0);
}
}