use ms_codec::{CorrectionDetail, Error, Tag, decode_with_correction};
const CODEX32_ALPHABET: &[u8; 32] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
const VALID_MS1_12W: &str = "ms10entrsqqqqqqqqqqqqqqqqqqqqqqqqqqqqcj9sxraq34v7f";
fn corrupt_at(s: &str, pos: usize, xor_mask: u8) -> String {
let hrp_len = 3; let mut chars: Vec<char> = s.chars().collect();
let abs_idx = hrp_len + pos;
let original_char = chars[abs_idx];
let original_sym = CODEX32_ALPHABET
.iter()
.position(|&b| b == original_char.to_ascii_lowercase() as u8)
.expect("char in codex32 alphabet") as u8;
let new_sym = (original_sym ^ (xor_mask & 0x1F)) & 0x1F;
chars[abs_idx] = CODEX32_ALPHABET[new_sym as usize] as char;
chars.iter().collect()
}
fn data_part_len(s: &str) -> usize {
s.len() - 3 }
#[test]
fn zero_error_passthrough() {
let (tag, _payload, details) =
decode_with_correction(VALID_MS1_12W).expect("clean ms1 must decode");
assert_eq!(tag, Tag::ENTR);
assert!(details.is_empty(), "no corrections expected for clean input");
}
#[test]
fn one_error_at_position_0() {
let bad = corrupt_at(VALID_MS1_12W, 0, 0b10101);
assert_ne!(bad, VALID_MS1_12W, "corruption changed the string");
let (tag, _payload, details) =
decode_with_correction(&bad).expect("1-error decode must succeed");
assert_eq!(tag, Tag::ENTR);
assert_eq!(details.len(), 1, "exactly 1 correction reported");
assert_eq!(details[0].position, 0);
let original_char = VALID_MS1_12W.chars().nth(3).unwrap();
assert_eq!(
details[0].now, original_char,
"correction restores the original char"
);
assert_ne!(
details[0].was, details[0].now,
"correction changes the character"
);
}
#[test]
fn one_error_at_last_data_symbol() {
let dp_len = data_part_len(VALID_MS1_12W);
let last_data_pos = dp_len - 13 - 1;
let bad = corrupt_at(VALID_MS1_12W, last_data_pos, 0b01110);
let (tag, _payload, details) =
decode_with_correction(&bad).expect("1-error at last data position must decode");
assert_eq!(tag, Tag::ENTR);
assert_eq!(details.len(), 1);
assert_eq!(details[0].position, last_data_pos);
let original_char = VALID_MS1_12W.chars().nth(3 + last_data_pos).unwrap();
assert_eq!(details[0].now, original_char);
}
#[test]
fn four_error_t_boundary() {
let dp_len = data_part_len(VALID_MS1_12W);
let positions: [usize; 4] = [0, dp_len / 4, dp_len / 2, dp_len - 1];
let masks: [u8; 4] = [0b00001, 0b10000, 0b11111, 0b01010];
let mut bad = VALID_MS1_12W.to_string();
for (&p, &m) in positions.iter().zip(&masks) {
bad = corrupt_at(&bad, p, m);
}
let (tag, _payload, details) =
decode_with_correction(&bad).expect("4-error t-boundary must decode");
assert_eq!(tag, Tag::ENTR);
assert_eq!(details.len(), 4, "exactly 4 corrections reported");
let reported_positions: Vec<usize> = details.iter().map(|c| c.position).collect();
let mut expected_positions: Vec<usize> = positions.to_vec();
expected_positions.sort();
assert_eq!(reported_positions, expected_positions);
for det in &details {
assert_ne!(det.was, det.now, "correction changes the character");
}
}
#[test]
fn five_error_too_many() {
let dp_len = data_part_len(VALID_MS1_12W);
let positions: [usize; 5] = [0, dp_len / 5, 2 * dp_len / 5, 3 * dp_len / 5, dp_len - 1];
let masks: [u8; 5] = [0b00001, 0b00010, 0b00100, 0b01000, 0b10000];
let mut bad = VALID_MS1_12W.to_string();
for (&p, &m) in positions.iter().zip(&masks) {
bad = corrupt_at(&bad, p, m);
}
let err =
decode_with_correction(&bad).expect_err("5-error pattern must not decode successfully");
match err {
Error::TooManyErrors { bound } => {
assert_eq!(bound, 8, "BCH(93,80,8) singleton bound is 8");
}
other => panic!("expected TooManyErrors, got {other:?}"),
}
}
#[test]
fn corrupt_checksum_region() {
let dp_len = data_part_len(VALID_MS1_12W);
let checksum_pos = dp_len - 13 + 5; let bad = corrupt_at(VALID_MS1_12W, checksum_pos, 0b11001);
let (tag, _payload, details) =
decode_with_correction(&bad).expect("1-error in checksum tail must decode");
assert_eq!(tag, Tag::ENTR);
assert_eq!(details.len(), 1, "exactly 1 correction reported");
assert_eq!(
details[0].position, checksum_pos,
"correction position lies in the checksum tail"
);
let original_char = VALID_MS1_12W.chars().nth(3 + checksum_pos).unwrap();
assert_eq!(details[0].now, original_char);
let expected = CorrectionDetail {
position: checksum_pos,
was: details[0].was,
now: details[0].now,
};
assert_eq!(details[0], expected);
}