use ms_codec::bch::{bch_create_checksum_regular, hrp_expand, polymod_run, MS_REGULAR_CONST};
use ms_codec::{decode_with_correction, encode, Error, Payload, Tag};
const ABC: &[u8; 32] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
const LENGTHS: [usize; 5] = [16, 20, 24, 28, 32];
fn sym(c: char) -> u8 {
ABC.iter().position(|&b| b == c as u8).unwrap() as u8
}
fn symbols(s: &str) -> Vec<u8> {
s.chars().skip(3).map(sym).collect()
}
fn corrupt_at(s: &str, pos: usize, mask: u8) -> String {
let mut c: Vec<char> = s.chars().collect();
let i = 3 + pos;
let v = sym(c[i].to_ascii_lowercase());
c[i] = ABC[((v ^ (mask & 0x1F)) & 0x1F) as usize] as char;
c.into_iter().collect()
}
fn clean(len: usize) -> (Vec<u8>, String) {
let e: Vec<u8> = (0..len as u8).collect();
let s = encode(Tag::ENTR, &Payload::Entr(e.clone())).unwrap();
(e, s)
}
#[test]
fn ms_regular_const_is_secretshare32_packed() {
let fe: [u128; 13] = [16, 25, 24, 3, 25, 11, 16, 23, 29, 3, 25, 17, 10];
let packed = fe
.iter()
.enumerate()
.fold(0u128, |a, (i, &v)| a | (v << (5 * (12 - i))));
assert_eq!(
MS_REGULAR_CONST, packed,
"MS_REGULAR_CONST must be SECRETSHARE32 big-endian packed"
);
assert_eq!(MS_REGULAR_CONST, 0x10ce0795c2fd1e62a);
assert_eq!(
(MS_REGULAR_CONST >> 64) & 1,
1,
"the codex32 target is a 65-bit value (bit 64 set)"
);
}
#[test]
fn polymod_lands_on_single_target_for_every_length() {
for len in LENGTHS {
let (_, s) = clean(len);
let mut input = hrp_expand("ms");
input.extend_from_slice(&symbols(&s));
assert_eq!(
polymod_run(&input),
MS_REGULAR_CONST,
"len={len}: a valid codeword's polymod residue must equal the single fixed target"
);
}
}
#[test]
fn handrolled_checksum_matches_codex32_encoded_tail_every_length() {
for len in LENGTHS {
let (_, s) = clean(len);
let syms = symbols(&s);
let dp = syms.len();
let recomputed = bch_create_checksum_regular("ms", &syms[..dp - 13]);
assert_eq!(
&recomputed[..],
&syms[dp - 13..],
"len={len}: hand-rolled checksum must equal the codex32-encoded checksum tail"
);
}
}
#[test]
fn clean_decode_with_correction_zero_corrections_every_length() {
for len in LENGTHS {
let (e, s) = clean(len);
let (t, p, corr) = decode_with_correction(&s)
.unwrap_or_else(|err| panic!("len={len} clean must decode: {err:?}"));
assert_eq!(t, Tag::ENTR);
assert_eq!(p, Payload::Entr(e));
assert!(
corr.is_empty(),
"len={len}: a clean string must report 0 corrections"
);
}
}
#[test]
fn corrects_1_to_4_errors_every_length() {
for len in LENGTHS {
let (e, s) = clean(len);
let dp = symbols(&s).len();
for k in 1..=4usize {
let positions: Vec<usize> = (0..k).map(|j| 1 + j * (dp / (k + 1)).max(1)).collect();
let mut bad = s.clone();
for &p in &positions {
bad = corrupt_at(&bad, p, 0x1F);
}
let (t, p2, corr) = decode_with_correction(&bad)
.unwrap_or_else(|err| panic!("len={len} k={k} must correct: {err:?}"));
assert_eq!(t, Tag::ENTR);
assert_eq!(p2, Payload::Entr(e.clone()), "len={len} k={k} payload");
let got: std::collections::BTreeSet<usize> = corr.iter().map(|c| c.position).collect();
let want: std::collections::BTreeSet<usize> = positions.iter().copied().collect();
assert_eq!(
got, want,
"len={len} k={k}: reported positions must equal injected"
);
}
}
}
#[test]
fn five_to_eight_errors_never_return_original_every_length() {
let mut x: u64 = 0x9E37_79B9_7F4A_7C15;
for len in LENGTHS {
let (e, s) = clean(len);
let original = Payload::Entr(e);
let dp = symbols(&s).len();
for _ in 0..80u32 {
for n_err in 5..=8usize {
let mut pos = std::collections::BTreeSet::new();
while pos.len() < n_err {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
pos.insert((x as usize) % dp);
}
let mut bad = s.clone();
let mask = ((x as u8) | 1) & 0x1F;
for &p in &pos {
bad = corrupt_at(&bad, p, mask);
}
if let Ok((_, p2, _)) = decode_with_correction(&bad) {
assert_ne!(
p2, original,
"len={len} n_err={n_err}: 5-8 errors silently returned the original"
);
}
}
}
}
}
#[test]
fn raw_wrong_length_fails_closed_every_length() {
for len in LENGTHS {
let (_, s) = clean(len);
let mut ins: Vec<char> = s.chars().collect();
ins.insert(3 + 5, 'p');
let inserted: String = ins.into_iter().collect();
assert!(
decode_with_correction(&inserted).is_err(),
"len={len}: an inserted symbol must fail closed"
);
let mut del: Vec<char> = s.chars().collect();
del.remove(3 + 5);
let deleted: String = del.into_iter().collect();
let r = decode_with_correction(&deleted);
assert!(
matches!(
&r,
Err(Error::TooManyErrors { .. }) | Err(Error::UnexpectedStringLength { .. })
),
"len={len}: a deleted symbol must fail closed (got {r:?})"
);
}
}