use super::{DecoderScratch, ExtrinsicResult, QraCode, QraCodeType, pdmath};
const CRC6_GEN_POL: i32 = 0x30;
const CRC12_GEN_POL: i32 = 0xF01;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Q65DecodeError {
NotConverged,
CrcMismatch,
}
pub struct Q65Codec {
code: &'static QraCode,
scratch: DecoderScratch,
px: Vec<i32>,
py: Vec<i32>,
ix: Vec<f32>,
ex: Vec<f32>,
}
impl Q65Codec {
pub fn new(code: &'static QraCode) -> Self {
Self {
scratch: DecoderScratch::for_code(code),
px: vec![0; code.K],
py: vec![0; code.N],
ix: vec![0.0; code.M * code.N],
ex: vec![0.0; code.M * code.N],
code,
}
}
pub fn msg_len(&self) -> usize {
match self.code.code_type {
QraCodeType::Normal => self.code.K,
QraCodeType::Crc | QraCodeType::CrcPunctured => self.code.K - 1,
QraCodeType::CrcPunctured2 => self.code.K - 2,
}
}
pub fn channel_len(&self) -> usize {
match self.code.code_type {
QraCodeType::Normal | QraCodeType::Crc => self.code.N,
QraCodeType::CrcPunctured => self.code.N - 1,
QraCodeType::CrcPunctured2 => self.code.N - 2,
}
}
pub fn encode(&mut self, msg: &[i32], out: &mut [i32]) {
let n_k_user = self.msg_len();
let n_chan = self.channel_len();
assert_eq!(msg.len(), n_k_user, "encode: msg.len() != msg_len()");
assert_eq!(out.len(), n_chan, "encode: out.len() != channel_len()");
self.px[..n_k_user].copy_from_slice(msg);
match self.code.code_type {
QraCodeType::Normal => {}
QraCodeType::Crc | QraCodeType::CrcPunctured => {
self.px[n_k_user] = crc6(&self.px[..n_k_user]);
}
QraCodeType::CrcPunctured2 => {
let crc = crc12(&self.px[..n_k_user]);
self.px[n_k_user] = crc[0];
self.px[n_k_user + 1] = crc[1];
}
}
self.code.encode(&self.px, &mut self.py);
match self.code.code_type {
QraCodeType::Normal | QraCodeType::Crc => {
out.copy_from_slice(&self.py);
}
QraCodeType::CrcPunctured => {
out[..n_k_user].copy_from_slice(&self.py[..n_k_user]);
out[n_k_user..].copy_from_slice(&self.py[n_k_user + 1..]);
}
QraCodeType::CrcPunctured2 => {
out[..n_k_user].copy_from_slice(&self.py[..n_k_user]);
out[n_k_user..].copy_from_slice(&self.py[n_k_user + 2..]);
}
}
}
pub fn decode(
&mut self,
intrinsics: &[f32],
msg: &mut [i32],
max_iters: u32,
) -> Result<u32, Q65DecodeError> {
self.decode_inner(intrinsics, msg, max_iters, None)
}
pub fn decode_with_ap(
&mut self,
intrinsics: &[f32],
msg: &mut [i32],
max_iters: u32,
ap_mask: &[i32],
ap_symbols: &[i32],
) -> Result<u32, Q65DecodeError> {
assert_eq!(
ap_mask.len(),
self.msg_len(),
"decode_with_ap: ap_mask length"
);
assert_eq!(
ap_symbols.len(),
self.msg_len(),
"decode_with_ap: ap_symbols length"
);
self.decode_inner(intrinsics, msg, max_iters, Some((ap_mask, ap_symbols)))
}
fn decode_inner(
&mut self,
intrinsics: &[f32],
msg: &mut [i32],
max_iters: u32,
ap: Option<(&[i32], &[i32])>,
) -> Result<u32, Q65DecodeError> {
let n_k_user = self.msg_len();
let n_chan = self.channel_len();
let big_m = self.code.M;
assert_eq!(msg.len(), n_k_user, "decode: msg.len() != msg_len()");
assert_eq!(
intrinsics.len(),
big_m * n_chan,
"decode: intrinsics length"
);
let uniform = pdmath::uniform(big_m);
match self.code.code_type {
QraCodeType::Normal | QraCodeType::Crc => {
self.ix.copy_from_slice(intrinsics);
}
QraCodeType::CrcPunctured => {
let info_bytes = big_m * n_k_user;
self.ix[..info_bytes].copy_from_slice(&intrinsics[..info_bytes]);
self.ix[info_bytes..info_bytes + big_m].copy_from_slice(uniform);
self.ix[info_bytes + big_m..].copy_from_slice(&intrinsics[info_bytes..]);
}
QraCodeType::CrcPunctured2 => {
let info_bytes = big_m * n_k_user;
self.ix[..info_bytes].copy_from_slice(&intrinsics[..info_bytes]);
self.ix[info_bytes..info_bytes + big_m].copy_from_slice(uniform);
self.ix[info_bytes + big_m..info_bytes + 2 * big_m].copy_from_slice(uniform);
self.ix[info_bytes + 2 * big_m..].copy_from_slice(&intrinsics[info_bytes..]);
}
}
if let Some((ap_mask, ap_symbols)) = ap {
for k in 0..n_k_user {
let smask = ap_mask[k];
if smask == 0 {
continue;
}
let xk = ap_symbols[k];
let row = &mut self.ix[big_m * k..big_m * (k + 1)];
for kk in 0..big_m {
if (kk as i32 ^ xk) & smask != 0 {
row[kk] = 0.0;
}
}
pdmath::norm(row);
}
}
let rc = self
.code
.extrinsic(&mut self.ex, &self.ix, max_iters, &mut self.scratch);
let iterations = match rc {
ExtrinsicResult::Converged { iterations } => iterations,
ExtrinsicResult::NotConverged | ExtrinsicResult::BadCodeTables => {
return Err(Q65DecodeError::NotConverged);
}
};
self.code.map_decode(&mut self.ex, &self.ix, &mut self.px);
match self.code.code_type {
QraCodeType::Normal => {}
QraCodeType::Crc | QraCodeType::CrcPunctured => {
let crc = crc6(&self.px[..n_k_user]);
if crc != self.px[n_k_user] {
return Err(Q65DecodeError::CrcMismatch);
}
}
QraCodeType::CrcPunctured2 => {
let crc = crc12(&self.px[..n_k_user]);
if crc[0] != self.px[n_k_user] || crc[1] != self.px[n_k_user + 1] {
return Err(Q65DecodeError::CrcMismatch);
}
}
}
msg.copy_from_slice(&self.px[..n_k_user]);
Ok(iterations)
}
}
pub const Q65_LLH_THRESHOLD: f32 = -260.0;
impl Q65Codec {
pub fn check_codeword_llh(&self, intrinsics: &[f32], codeword: &[i32]) -> f32 {
let big_m = self.code.M;
let n_chan = self.channel_len();
assert_eq!(
intrinsics.len(),
big_m * n_chan,
"check_codeword_llh: intrinsics length"
);
assert_eq!(
codeword.len(),
n_chan,
"check_codeword_llh: codeword length"
);
let mut t = 0.0_f32;
for k in 0..n_chan {
let sym = codeword[k] as usize;
debug_assert!(sym < big_m, "codeword[{k}] = {sym} out of range");
let mut x = intrinsics[big_m * k + sym];
if x < 1.0e-36 {
x = 1.0e-36;
}
t += x.ln();
}
t
}
pub fn decode_with_codeword_list(
&self,
intrinsics: &[f32],
candidates: &[[i32; 63]],
) -> Option<(usize, [i32; 13])> {
if candidates.is_empty() {
return None;
}
let n_chan = self.channel_len();
debug_assert_eq!(
n_chan, 63,
"list-decode helper currently assumes Q65 channel length 63"
);
let n_user = self.msg_len();
debug_assert_eq!(n_user, 13, "list-decode assumes Q65 msg_len == 13");
let threshold = Q65_LLH_THRESHOLD + (candidates.len() as f32 / 3.0).ln();
let mut best_llh = threshold;
let mut best_idx: Option<usize> = None;
for (i, cw) in candidates.iter().enumerate() {
let llh = self.check_codeword_llh(intrinsics, cw);
if llh > best_llh {
best_llh = llh;
best_idx = Some(i);
}
}
let idx = best_idx?;
let cw = &candidates[idx];
let mut info = [0_i32; 13];
info.copy_from_slice(&cw[..n_user]);
Some((idx, info))
}
}
pub fn crc6(x: &[i32]) -> i32 {
let mut sr: i32 = 0;
for &symbol in x {
let mut t = symbol;
for _ in 0..6 {
if (t ^ sr) & 0x01 != 0 {
sr = (sr >> 1) ^ CRC6_GEN_POL;
} else {
sr >>= 1;
}
t >>= 1;
}
}
sr
}
pub fn crc12(x: &[i32]) -> [i32; 2] {
let mut sr: i32 = 0;
for &symbol in x {
let mut t = symbol;
for _ in 0..6 {
if (t ^ sr) & 0x01 != 0 {
sr = (sr >> 1) ^ CRC12_GEN_POL;
} else {
sr >>= 1;
}
t >>= 1;
}
}
[sr & 0x3F, sr >> 6]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fec::qra15_65_64::QRA15_65_64_IRR_E23;
fn perfect_intrinsics(channel: &[i32], m: usize) -> Vec<f32> {
let mut ix = vec![0.0_f32; m * channel.len()];
for (k, &sym) in channel.iter().enumerate() {
ix[m * k + sym as usize] = 1.0;
}
ix
}
#[test]
fn shape_for_q65() {
let codec = Q65Codec::new(&QRA15_65_64_IRR_E23);
assert_eq!(codec.msg_len(), 13, "Q65 carries 13 user info symbols");
assert_eq!(codec.channel_len(), 63, "Q65 transmits 63 channel symbols");
}
#[test]
fn crc12_returns_two_six_bit_symbols() {
let crc = crc12(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
assert!(crc[0] < 64, "low half = {} not a 6-bit symbol", crc[0]);
assert!(crc[1] < 64, "high half = {} not a 6-bit symbol", crc[1]);
}
#[test]
fn crc12_zero_input_yields_zero() {
let crc = crc12(&[0_i32; 13]);
assert_eq!(crc, [0, 0]);
}
#[test]
fn crc6_zero_input_yields_zero() {
assert_eq!(crc6(&[0_i32; 14]), 0);
}
#[test]
fn encode_then_decode_clean_recovers_message() {
let mut codec = Q65Codec::new(&QRA15_65_64_IRR_E23);
let msg: Vec<i32> = (0..13).map(|i| (i * 11 + 7) % 64).collect();
let mut channel = vec![0_i32; 63];
codec.encode(&msg, &mut channel);
let intrinsics = perfect_intrinsics(&channel, 64);
let mut decoded = vec![0_i32; 13];
let iters = codec
.decode(&intrinsics, &mut decoded, 50)
.expect("clean decode must succeed");
assert_eq!(decoded, msg);
assert!(iters < 50, "took too many iterations: {iters}");
}
#[test]
fn encode_then_decode_all_zero_message() {
let mut codec = Q65Codec::new(&QRA15_65_64_IRR_E23);
let msg = vec![0_i32; 13];
let mut channel = vec![0_i32; 63];
codec.encode(&msg, &mut channel);
assert!(channel.iter().all(|&s| s == 0));
let intrinsics = perfect_intrinsics(&channel, 64);
let mut decoded = vec![0_i32; 13];
codec
.decode(&intrinsics, &mut decoded, 50)
.expect("clean zero decode must succeed");
assert_eq!(decoded, msg);
}
#[test]
fn decode_with_ap_zero_mask_matches_plain_decode() {
let mut codec_a = Q65Codec::new(&QRA15_65_64_IRR_E23);
let mut codec_b = Q65Codec::new(&QRA15_65_64_IRR_E23);
let msg: Vec<i32> = (0..13).map(|i| (i * 7 + 11) % 64).collect();
let mut channel = vec![0_i32; 63];
codec_a.encode(&msg, &mut channel);
let intrinsics = perfect_intrinsics(&channel, 64);
let mut decoded_plain = vec![0_i32; 13];
let mut decoded_ap = vec![0_i32; 13];
let zero_mask = [0_i32; 13];
let zero_syms = [0_i32; 13];
codec_a.decode(&intrinsics, &mut decoded_plain, 50).unwrap();
codec_b
.decode_with_ap(&intrinsics, &mut decoded_ap, 50, &zero_mask, &zero_syms)
.unwrap();
assert_eq!(decoded_plain, decoded_ap);
assert_eq!(decoded_plain, msg);
}
#[test]
fn decode_with_ap_full_mask_locks_to_known_message() {
let mut codec = Q65Codec::new(&QRA15_65_64_IRR_E23);
let msg: Vec<i32> = (0..13).map(|i| (i * 13 + 5) % 64).collect();
let mut channel = vec![0_i32; 63];
codec.encode(&msg, &mut channel);
let intrinsics = perfect_intrinsics(&channel, 64);
let full_mask = [0x3F_i32; 13];
let mut decoded = vec![0_i32; 13];
let iters = codec
.decode_with_ap(&intrinsics, &mut decoded, 50, &full_mask, &msg)
.expect("full-mask decode must succeed");
assert_eq!(decoded, msg);
assert!(iters < 5, "fully-locked decode should converge fast");
}
#[test]
fn decode_with_wrong_ap_never_yields_wrong_message() {
let mut codec = Q65Codec::new(&QRA15_65_64_IRR_E23);
let msg: Vec<i32> = (0..13).map(|i| (i * 11 + 1) % 64).collect();
let mut channel = vec![0_i32; 63];
codec.encode(&msg, &mut channel);
let intrinsics = perfect_intrinsics(&channel, 64);
let mask = [0x3F_i32; 13];
let mut wrong_syms = msg.clone();
wrong_syms[0] = (msg[0] + 1) % 64;
let mut decoded = vec![0_i32; 13];
let result = codec.decode_with_ap(&intrinsics, &mut decoded, 50, &mask, &wrong_syms);
match result {
Err(_) => {}
Ok(_) => assert_eq!(
decoded, msg,
"wrong AP must not produce a wrong-message Ok decode"
),
}
}
#[test]
fn decode_with_wrong_crc_returns_an_error() {
let code = &QRA15_65_64_IRR_E23;
let mut codec = Q65Codec::new(code);
let mut info = vec![0_i32; code.K];
for i in 0..code.K {
info[i] = (i as i32 * 5 + 1) % 64;
}
let mut full_codeword = vec![0_i32; code.N];
code.encode(&info, &mut full_codeword);
let n_k_user = codec.msg_len();
let mut channel = vec![0_i32; codec.channel_len()];
channel[..n_k_user].copy_from_slice(&full_codeword[..n_k_user]);
channel[n_k_user..].copy_from_slice(&full_codeword[n_k_user + 2..]);
let intrinsics = perfect_intrinsics(&channel, 64);
let mut decoded = vec![0_i32; n_k_user];
let err = codec
.decode(&intrinsics, &mut decoded, 50)
.expect_err("invalid frame must be rejected");
assert!(
matches!(
err,
Q65DecodeError::CrcMismatch | Q65DecodeError::NotConverged
),
"got unexpected error variant: {err:?}"
);
}
}