use viterbi::puncture::{DePuncturer, Puncturer};
use viterbi::{
CodeParams, CodeProfile, CodedBlock, ConfigError, HardHamming, ParamError, PunctureMatrix,
PuncturedRate, ViterbiDecoder, ViterbiEncoder,
};
fn hard_samples(cb: &CodedBlock) -> Vec<u8> {
(0..cb.nbits)
.map(|i| (cb.bytes[i / 8] >> (7 - (i % 8))) & 1)
.collect()
}
#[test]
fn k9_profile_resolves_to_valid_non_catastrophic_params() {
let p = CodeProfile::K9R1_2.params();
assert_eq!(p.k(), 9);
assert_eq!(p.n(), 2);
assert_eq!(p.generators(), &[0o561, 0o753]);
assert!(CodeParams::new(9, vec![0o561, 0o753], vec![false, false]).is_ok());
}
#[test]
fn k9_decoder_builds_at_s256() {
assert!(ViterbiDecoder::<256, HardHamming>::new(CodeProfile::K9R1_2.params(), 1_000).is_ok());
}
#[test]
fn k9_with_s64_is_state_count_mismatch() {
assert!(matches!(
ViterbiDecoder::<64, HardHamming>::new(CodeProfile::K9R1_2.params(), 1_000),
Err(ConfigError::Param(ParamError::StateCountMismatch {
states: 64,
k: 9
}))
));
}
#[test]
fn k9_clean_round_trip_recovers_payload() {
let enc = ViterbiEncoder::new(CodeProfile::K9R1_2.params()).unwrap();
let mut dec =
ViterbiDecoder::<256, HardHamming>::new(CodeProfile::K9R1_2.params(), 4096).unwrap();
let raw = b"K=9 generic path works!";
let coded = enc.encode(raw).unwrap();
let out = dec.decode_block(&coded).unwrap();
assert_eq!(&out.bytes, raw);
assert_eq!(out.nbits, raw.len() * 8);
}
#[test]
fn k9_encode_terminates_in_state_zero() {
let enc = ViterbiEncoder::new(CodeProfile::K9R1_2.params()).unwrap();
let mut dec =
ViterbiDecoder::<256, HardHamming>::new(CodeProfile::K9R1_2.params(), 4096).unwrap();
let raw = b"tail";
let nbits = raw.len() * 8;
let coded = enc.encode(raw).unwrap();
assert_eq!(
coded.nbits,
(nbits + 8) * 2,
"K=9 zero-tail must be m = k-1 = 8 stages"
);
let out = dec.decode_block(&coded).unwrap();
assert_eq!(&out.bytes, raw);
assert_eq!(
dec.last_terminal_metric(),
0,
"a clean K=9 decode must terminate in state 0 (metric 0)"
);
}
#[test]
fn k9_recovers_from_a_correctable_error() {
let enc = ViterbiEncoder::new(CodeProfile::K9R1_2.params()).unwrap();
let mut dec =
ViterbiDecoder::<256, HardHamming>::new(CodeProfile::K9R1_2.params(), 4096).unwrap();
let raw = b"correct me please";
let mut coded = enc.encode(raw).unwrap();
coded.bytes[0] ^= 0b0100_0000;
let out = dec.decode_block(&coded).unwrap();
assert_eq!(&out.bytes, raw);
}
#[test]
fn k9_with_puncturing_round_trips_and_tail_is_m_times_n() {
let params = CodeProfile::K9R1_2.params();
let enc = ViterbiEncoder::new(params.clone()).unwrap();
let matrix = PunctureMatrix::ccsds_rate(2, PuncturedRate::R2_3).unwrap();
let punc = Puncturer::new(matrix.clone(), ¶ms).unwrap();
let dp = DePuncturer::<HardHamming>::new(matrix.clone(), ¶ms).unwrap();
let raw = b"pk9"; let nbits = raw.len() * 8;
let coded = enc.encode(raw).unwrap();
assert_eq!(coded.nbits, (nbits + 8) * 2, "encode tail must use m = 8");
let punctured = punc.puncture(&coded, nbits).unwrap();
let m = 8usize;
let n = 2usize;
let kept_body = (nbits / matrix.period()) * matrix.kept_per_period(); assert_eq!(
punctured.nbits,
kept_body + m * n,
"punctured tail must be m·n = 16 verbatim samples (K=9)"
);
assert_eq!(
dp.expected_punctured_len(nbits),
Some(kept_body + m * n),
"expected_punctured_len must reflect m = 8, not M = 6"
);
let on_air = hard_samples(&punctured);
let full = dp.depuncture(&on_air, nbits).unwrap();
let mut dec = ViterbiDecoder::<256, HardHamming>::new(params, 4096).unwrap();
let out = dec.decode(&full, nbits).unwrap();
assert_eq!(&out.bytes, raw);
}