use ms_codec::{decode, decode_with_correction, encode, Payload, PayloadKind, Tag};
#[test]
fn mnem_encode_decode_round_trip_16b_japanese() {
let entropy: Vec<u8> = (0u8..16).collect();
let p = Payload::Mnem { language: 1, entropy: entropy.clone() };
let s = encode(Tag::ENTR, &p).expect("encode Mnem should succeed");
assert_eq!(s.len(), 51, "mnem 16-byte entropy -> ms1 len 51, got {}", s.len());
let (tag, recovered) = decode(&s).expect("decode mnem should succeed");
assert_eq!(tag, Tag::ENTR);
assert!(
matches!(recovered, Payload::Mnem { language: 1, .. }),
"expected Payload::Mnem{{language:1, ..}}, got {:?}",
recovered
);
assert_eq!(recovered.as_bytes(), entropy.as_slice());
}
#[test]
fn entr_still_decodes_to_entr_payload() {
let entropy = vec![0xAAu8; 16];
let p = Payload::Entr(entropy.clone());
let s = encode(Tag::ENTR, &p).expect("encode Entr should succeed");
let (tag, recovered) = decode(&s).expect("decode Entr should succeed");
assert_eq!(tag, Tag::ENTR);
assert_eq!(recovered.kind(), PayloadKind::Entr);
assert_eq!(recovered, Payload::Entr(entropy));
}
#[test]
fn mnem_decode_with_correction_clean_passes() {
let entropy: Vec<u8> = vec![0x55u8; 16];
let p = Payload::Mnem { language: 0, entropy: entropy.clone() };
let s = encode(Tag::ENTR, &p).expect("encode mnem");
let (tag, recovered, corrections) =
ms_codec::decode_with_correction(&s).expect("decode_with_correction on clean mnem");
assert_eq!(tag, Tag::ENTR);
assert!(corrections.is_empty(), "no corrections expected for clean input");
assert_eq!(recovered.as_bytes(), entropy.as_slice());
assert!(
matches!(recovered, Payload::Mnem { language: 0, .. }),
"expected Mnem language=0"
);
}
const ABC: &[u8; 32] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
fn sym(c: char) -> u8 {
ABC.iter().position(|&b| b == c as u8).unwrap() as u8
}
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 data_part_len(s: &str) -> usize {
s.len() - 3 }
#[test]
fn mnem_decode_with_correction_recovers_from_corruption() {
for &n in &[16usize, 20, 24, 28, 32] {
let entropy = vec![0xABu8; n];
let p = Payload::Mnem { language: 2, entropy: entropy.clone() };
let s = encode(Tag::ENTR, &p)
.unwrap_or_else(|e| panic!("encode n={n} failed: {e:?}"));
let dp = data_part_len(&s);
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 &pos in &positions {
bad = corrupt_at(&bad, pos, 0x1F);
}
let (tag, recovered, corr) = decode_with_correction(&bad)
.unwrap_or_else(|e| panic!("n={n} k={k} correction failed: {e:?}"));
assert_eq!(tag, Tag::ENTR, "n={n} k={k}: tag must be ENTR");
assert_eq!(
recovered,
Payload::Mnem { language: 2, entropy: entropy.clone() },
"n={n} k={k}: recovered payload must match original (language=2, entropy=[0xAB;{n}])"
);
let got_positions: std::collections::BTreeSet<usize> =
corr.iter().map(|d| d.position).collect();
let want_positions: std::collections::BTreeSet<usize> =
positions.iter().copied().collect();
assert_eq!(
got_positions, want_positions,
"n={n} k={k}: reported correction positions must equal injected positions"
);
}
}
}
#[test]
fn golden_mnem_english_16b_wire_vector() {
let entropy: Vec<u8> = vec![
0x0c, 0x1e, 0x24, 0xe5, 0x91, 0x75, 0x44, 0xd6,
0x66, 0xc3, 0x42, 0x99, 0x2a, 0xcf, 0xda, 0x1b,
];
let p = Payload::Mnem { language: 0, entropy: entropy.clone() };
let s = encode(Tag::ENTR, &p).expect("encode mnem golden");
assert_eq!(
s,
"ms10entrsqgqqc83yukgh23xkvmp59xf2eldpk4cdrq2y4h82yz",
"mnem wire encoding drifted from golden vector"
);
assert_eq!(s.len(), 51);
let (tag, recovered) = decode(&s).expect("decode golden");
assert_eq!(tag, Tag::ENTR);
assert_eq!(recovered, Payload::Mnem { language: 0, entropy });
}