#![allow(dead_code)]
pub(crate) const PAD_CODE: [u8; 5] = [33, 33, 0, 0, 28];
pub(crate) const LATCH_LEN: [[u8; 5]; 5] = [
[0, 1, 1, 1, 1],
[1, 0, 1, 1, 1],
[2, 2, 0, 2, 2],
[2, 2, 2, 0, 2],
[2, 2, 2, 2, 0],
];
pub(crate) const LATCH_SEQ: [[&[u8]; 5]; 5] = [
[&[], &[63], &[58], &[58], &[58]],
[&[63], &[], &[63], &[63], &[63]],
[&[60, 60], &[60, 60], &[], &[60, 60], &[60, 60]],
[&[61, 61], &[61, 61], &[61, 61], &[], &[61, 61]],
[&[62, 62], &[62, 62], &[62, 62], &[62, 62], &[]],
];
pub(crate) const PAD_CODEWORD: u8 = 33;
pub(crate) fn encode_set_a_only(bytes: &[u8]) -> Result<Vec<u8>, crate::error::Error> {
let mut digit_run = 0usize;
for &b in bytes {
if b.is_ascii_digit() {
digit_run += 1;
if digit_run >= 9 {
return Err(crate::error::Error::InvalidData(
"MaxiCode encode_set_a_only: 9+ digit run requires the NS \
optimization (not in this path)"
.to_string(),
));
}
} else {
digit_run = 0;
}
}
let mut out = Vec::with_capacity(bytes.len());
for &b in bytes {
let cw = seta_codeword(b).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"MaxiCode encode_set_a_only: byte 0x{b:02x} not in set A \
(would need set-B/C/D/E shift/latch)",
))
})?;
out.push(cw);
}
Ok(out)
}
pub(crate) const SB_CODEWORD: u8 = 59; pub(crate) const LB_CODEWORD: u8 = 63; pub(crate) const SA_CODEWORD: u8 = 59; pub(crate) const SA2_CODEWORD: u8 = 56; pub(crate) const SA3_CODEWORD: u8 = 57; pub(crate) const LA_CODEWORD: u8 = 63;
pub(crate) const SC_CODEWORD: u8 = 60;
pub(crate) const SD_CODEWORD: u8 = 61;
pub(crate) const SE_CODEWORD: u8 = 62;
pub(crate) fn encode_secondary_a_b(bytes: &[u8]) -> Result<Vec<u8>, crate::error::Error> {
for &b in bytes {
if seta_codeword(b).is_none() && setb_codeword(b).is_none() {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode encode_secondary_a_b: byte 0x{b:02x} needs set C/D/E; \
use encode_secondary_a_b_with_ns instead (it supports the full \
set-A/B/C/D/E shift + latch + intra-latch dispatcher)",
)));
}
}
let n = bytes.len();
let mut out: Vec<u8> = Vec::with_capacity(n + 4);
let mut state_b = false; let mut i = 0;
while i < n {
let in_current_set = |b: u8| {
if state_b {
setb_codeword(b).is_some()
} else {
seta_codeword(b).is_some()
}
};
if in_current_set(bytes[i]) {
let cw = if state_b {
setb_codeword(bytes[i]).unwrap()
} else {
seta_codeword(bytes[i]).unwrap()
};
out.push(cw);
i += 1;
continue;
}
let other_start = i;
let mut j = i;
while j < n && !in_current_set(bytes[j]) {
j += 1;
}
let run_len = j - other_start;
let trailing_current_set = j < n;
if state_b {
match run_len {
1 => {
out.push(SA_CODEWORD);
out.push(seta_codeword(bytes[other_start]).unwrap());
}
2 => {
out.push(SA2_CODEWORD);
out.push(seta_codeword(bytes[other_start]).unwrap());
out.push(seta_codeword(bytes[other_start + 1]).unwrap());
}
3 => {
out.push(SA3_CODEWORD);
out.push(seta_codeword(bytes[other_start]).unwrap());
out.push(seta_codeword(bytes[other_start + 1]).unwrap());
out.push(seta_codeword(bytes[other_start + 2]).unwrap());
}
_ => {
out.push(LA_CODEWORD);
for &b in &bytes[other_start..j] {
out.push(seta_codeword(b).unwrap());
}
state_b = false;
if trailing_current_set {
out.push(LB_CODEWORD);
state_b = true;
}
}
}
} else {
match run_len {
1 => {
out.push(SB_CODEWORD);
out.push(setb_codeword(bytes[other_start]).unwrap());
}
2 if trailing_current_set => {
out.push(SB_CODEWORD);
out.push(setb_codeword(bytes[other_start]).unwrap());
out.push(SB_CODEWORD);
out.push(setb_codeword(bytes[other_start + 1]).unwrap());
}
_ => {
out.push(LB_CODEWORD);
for &b in &bytes[other_start..j] {
out.push(setb_codeword(b).unwrap());
}
state_b = true;
if trailing_current_set {
out.push(LA_CODEWORD);
state_b = false;
}
}
}
}
i = j;
}
Ok(out)
}
pub(crate) fn encode_secondary_a_b_with_ns(bytes: &[u8]) -> Result<Vec<u8>, crate::error::Error> {
for &b in bytes {
if seta_codeword(b).is_none()
&& setb_codeword(b).is_none()
&& setc_codeword(b).is_none()
&& setd_codeword(b).is_none()
&& sete_codeword(b).is_none()
{
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode encode_secondary_a_b_with_ns: byte 0x{b:02x} not in any \
of the five charsets (A/B/C/D/E)",
)));
}
}
let n = bytes.len();
let mut nseq = vec![0usize; n + 1];
for i in (0..n).rev() {
if bytes[i].is_ascii_digit() {
nseq[i] = nseq[i + 1] + 1;
}
}
let mut out: Vec<u8> = Vec::with_capacity(n + 4);
let mut state_b = false;
let mut i = 0;
while i < n {
if nseq[i] >= 9 {
let (lead, ns_start) = if !state_b {
let lead = nseq[i] - 9;
(lead, i + lead)
} else {
(0, i)
};
for k in 0..lead {
out.push(seta_codeword(bytes[i + k]).unwrap());
}
let ns_chunk = encode_ns_run(&bytes[ns_start..ns_start + 9]).unwrap();
out.extend_from_slice(&ns_chunk);
i = ns_start + 9;
continue;
}
let in_current = if state_b {
setb_codeword(bytes[i]).is_some()
} else {
seta_codeword(bytes[i]).is_some()
};
if in_current {
let cw = if state_b {
setb_codeword(bytes[i]).unwrap()
} else {
seta_codeword(bytes[i]).unwrap()
};
out.push(cw);
i += 1;
continue;
}
let cde_set = if setc_codeword(bytes[i]).is_some() {
Some(2u8) } else if setd_codeword(bytes[i]).is_some() {
Some(3u8) } else if sete_codeword(bytes[i]).is_some() {
Some(4u8) } else {
None
};
if let Some(set_idx) = cde_set {
let lookup: fn(u8) -> Option<u8> = match set_idx {
2 => setc_codeword,
3 => setd_codeword,
4 => sete_codeword,
_ => unreachable!(),
};
let shift = match set_idx {
2 => SC_CODEWORD,
3 => SD_CODEWORD,
4 => SE_CODEWORD,
_ => unreachable!(),
};
let mut k = i;
let mut body: Vec<u8> = Vec::with_capacity(n - i);
let mut primary_count: usize = 0;
while k < n {
let in_current_ab = if state_b {
setb_codeword(bytes[k]).is_some()
} else {
seta_codeword(bytes[k]).is_some()
};
if in_current_ab {
break;
}
if let Some(cw) = lookup(bytes[k]) {
body.push(cw);
primary_count += 1;
k += 1;
continue;
}
let (target_idx, target_lookup): (u8, fn(u8) -> Option<u8>) =
if setc_codeword(bytes[k]).is_some() {
(2, setc_codeword)
} else if setd_codeword(bytes[k]).is_some() {
(3, setd_codeword)
} else if sete_codeword(bytes[k]).is_some() {
(4, sete_codeword)
} else {
break;
};
let next_is_primary = k + 1 < n && lookup(bytes[k + 1]).is_some();
let committed = primary_count >= 3;
if !next_is_primary && !committed {
break;
}
let intra_shift = match target_idx {
2 => SC_CODEWORD,
3 => SD_CODEWORD,
4 => SE_CODEWORD,
_ => unreachable!(),
};
body.push(intra_shift);
body.push(target_lookup(bytes[k]).unwrap());
k += 1;
}
let run_len = k - i;
if run_len >= 3 {
out.push(shift);
out.push(shift);
out.extend_from_slice(&body);
let omit_back_latch_at_eom = k >= n && set_idx == 4;
if omit_back_latch_at_eom {
} else {
let back_latch = if k >= n {
58u8 } else if state_b {
if setb_codeword(bytes[k]).is_some() {
63u8 } else {
58u8 }
} else {
if seta_codeword(bytes[k]).is_some() {
58u8 } else if setb_codeword(bytes[k]).is_some() {
63u8 } else {
58u8 }
};
out.push(back_latch);
state_b = back_latch == 63;
}
i = k;
continue;
}
for &b in &bytes[i..k] {
let cw = lookup(b).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"MaxiCode encode_secondary_a_b_with_ns: byte 0x{b:02x} \
in the shift-path run has no codeword in the picked \
primary set (set_idx={set_idx}); cross-set mixing \
under run_len < 3 isn't supported",
))
})?;
out.push(shift);
out.push(cw);
}
i = k;
continue;
}
let mut j = i;
while j < n {
let in_other = if state_b {
seta_codeword(bytes[j]).is_some() && setb_codeword(bytes[j]).is_none()
} else {
setb_codeword(bytes[j]).is_some() && seta_codeword(bytes[j]).is_none()
};
if !in_other {
break;
}
j += 1;
}
let run_len = j - i;
let trailing = j < n;
if state_b {
match run_len {
1 => {
out.push(SA_CODEWORD);
out.push(seta_codeword(bytes[i]).unwrap());
}
2 => {
out.push(SA2_CODEWORD);
out.push(seta_codeword(bytes[i]).unwrap());
out.push(seta_codeword(bytes[i + 1]).unwrap());
}
3 => {
out.push(SA3_CODEWORD);
out.push(seta_codeword(bytes[i]).unwrap());
out.push(seta_codeword(bytes[i + 1]).unwrap());
out.push(seta_codeword(bytes[i + 2]).unwrap());
}
_ => {
out.push(LA_CODEWORD);
for &b in &bytes[i..j] {
out.push(seta_codeword(b).unwrap());
}
state_b = false;
if trailing {
out.push(LB_CODEWORD);
state_b = true;
}
}
}
} else {
match run_len {
1 => {
out.push(SB_CODEWORD);
out.push(setb_codeword(bytes[i]).unwrap());
}
2 if trailing => {
out.push(SB_CODEWORD);
out.push(setb_codeword(bytes[i]).unwrap());
out.push(SB_CODEWORD);
out.push(setb_codeword(bytes[i + 1]).unwrap());
}
_ => {
out.push(LB_CODEWORD);
for &b in &bytes[i..j] {
out.push(setb_codeword(b).unwrap());
}
state_b = true;
let next_is_cde = trailing
&& seta_codeword(bytes[j]).is_none()
&& setb_codeword(bytes[j]).is_none()
&& (setc_codeword(bytes[j]).is_some()
|| setd_codeword(bytes[j]).is_some()
|| sete_codeword(bytes[j]).is_some());
if trailing && nseq[j] < 9 && !next_is_cde {
out.push(LA_CODEWORD);
state_b = false;
}
}
}
}
i = j;
}
Ok(out)
}
pub(crate) fn apply_rs_ecc(pri: &[u8; 10], sec: &[u8]) -> Result<[u8; 144], crate::error::Error> {
let scodes = match sec.len() {
84 => 20,
68 => 28,
other => {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode RS-ECC: secondary length must be 84 (modes 2/3/4/6) or 68 (mode 5), got {other}",
)));
}
};
let seco: Vec<u8> = sec.iter().step_by(2).copied().collect();
let sece: Vec<u8> = sec.iter().skip(1).step_by(2).copied().collect();
let mut secochk = crate::util::rs_gf64::encode_k(&seco, scodes);
secochk.reverse();
let mut secechk = crate::util::rs_gf64::encode_k(&sece, scodes);
secechk.reverse();
let mut secchk = Vec::with_capacity(scodes * 2);
for i in 0..scodes {
secchk.push(secochk[i]);
secchk.push(secechk[i]);
}
let mut prichk = crate::util::rs_gf64::encode_k(pri, 10);
prichk.reverse();
let mut codewords = [0u8; 144];
codewords[..10].copy_from_slice(pri);
codewords[10..20].copy_from_slice(&prichk);
codewords[20..20 + sec.len()].copy_from_slice(sec);
codewords[20 + sec.len()..].copy_from_slice(&secchk);
Ok(codewords)
}
pub(crate) fn codewords_to_mods(codewords: &[u8; 144]) -> [bool; 864] {
let mut mods = [false; 864];
for (i, &cw) in codewords.iter().enumerate() {
for bit in 0..6 {
let value = (cw >> (5 - bit)) & 1;
mods[i * 6 + bit] = value == 1;
}
}
mods
}
#[derive(Debug, Clone)]
pub struct MaxiCodeSymbol {
cells: [[bool; COLS]; ROWS],
}
impl MaxiCodeSymbol {
pub fn cols(&self) -> usize {
COLS
}
pub fn rows(&self) -> usize {
ROWS
}
pub fn is_on(&self, row: usize, col: usize) -> bool {
if row >= ROWS || col >= COLS {
return false;
}
self.cells[row][col]
}
pub fn row_is_offset(row: usize) -> bool {
row % 2 == 1
}
}
pub(crate) const FIXED_BLACK_POSITIONS: &[u16] = &[
28, 29, 280, 281, 311, 457, 488, 500, 530, 670, 700, 677, 707,
];
pub(crate) fn lay_out_codewords(codewords: &[u8; 144]) -> Vec<u16> {
let mods = codewords_to_mods(codewords);
let mut pixs: Vec<u16> = Vec::with_capacity(864 + FIXED_BLACK_POSITIONS.len());
for (i, &bit) in mods.iter().enumerate() {
if bit {
pixs.push(MODMAP[i]);
}
}
pixs.extend_from_slice(FIXED_BLACK_POSITIONS);
pixs
}
pub(crate) fn build_grid(pixs: &[u16]) -> [[bool; COLS]; ROWS] {
let mut grid = [[false; COLS]; ROWS];
for &p in pixs {
let row = (p as usize) / COLS;
let col = (p as usize) % COLS;
if row < ROWS && col < COLS {
grid[row][col] = true;
}
}
grid
}
pub fn build_symbol(pri: &[u8; 10], sec: &[u8]) -> Result<MaxiCodeSymbol, crate::error::Error> {
let cws = apply_rs_ecc(pri, sec)?;
let pixs = lay_out_codewords(&cws);
Ok(MaxiCodeSymbol {
cells: build_grid(&pixs),
})
}
pub fn encode_mode_4(data: &[u8]) -> Result<MaxiCodeSymbol, crate::error::Error> {
let encmsg = encode_secondary_a_b_with_ns(data)?;
if encmsg.len() > 93 {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 4: encoded message ({} codewords) exceeds 93-cw capacity",
encmsg.len(),
)));
}
let mut cws = [PAD_CODE[0]; 94];
cws[0] = 4;
cws[1..1 + encmsg.len()].copy_from_slice(&encmsg);
let mut pri = [0u8; 10];
pri.copy_from_slice(&cws[..10]);
build_symbol(&pri, &cws[10..])
}
pub fn encode_mode_5(data: &[u8]) -> Result<MaxiCodeSymbol, crate::error::Error> {
let encmsg = encode_secondary_a_b_with_ns(data)?;
if encmsg.len() > 77 {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 5: encoded message ({} codewords) exceeds 77-cw capacity",
encmsg.len(),
)));
}
let mut cws = [PAD_CODE[0]; 78];
cws[0] = 5;
cws[1..1 + encmsg.len()].copy_from_slice(&encmsg);
let mut pri = [0u8; 10];
pri.copy_from_slice(&cws[..10]);
build_symbol(&pri, &cws[10..])
}
pub fn encode_mode_6(data: &[u8]) -> Result<MaxiCodeSymbol, crate::error::Error> {
let encmsg = encode_secondary_a_b_with_ns(data)?;
if encmsg.len() > 93 {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 6: encoded message ({} codewords) exceeds 93-cw capacity",
encmsg.len(),
)));
}
let mut cws = [PAD_CODE[0]; 94];
cws[0] = 6;
cws[1..1 + encmsg.len()].copy_from_slice(&encmsg);
let mut pri = [0u8; 10];
pri.copy_from_slice(&cws[..10]);
build_symbol(&pri, &cws[10..])
}
#[allow(clippy::type_complexity)]
pub(crate) fn parse_mode_2_or_3_input(
data: &[u8],
) -> Result<(&[u8], &[u8], &[u8], &[u8]), crate::error::Error> {
let body = if data.len() >= 9
&& &data[0..7] == b"\x5b\x29\x3e\x1e\x30\x31\x1d"
&& data[7].is_ascii_digit()
&& data[8].is_ascii_digit()
{
&data[9..]
} else {
data
};
let parts: Vec<&[u8]> = body.splitn(4, |&b| b == 0x1d).collect();
if parts.len() != 4 {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 2/3: expected 4 GS-separated fields \
(postcode, country, service, secondary); got {}",
parts.len(),
)));
}
Ok((parts[0], parts[1], parts[2], parts[3]))
}
pub fn encode_mode_2(data: &[u8]) -> Result<MaxiCodeSymbol, crate::error::Error> {
let (postcode, country, service, secondary) = parse_mode_2_or_3_input(data)?;
let pcode_str = std::str::from_utf8(postcode).map_err(|_| {
crate::error::Error::InvalidData("MaxiCode mode 2: postcode must be ASCII".into())
})?;
let ccode_str = std::str::from_utf8(country).map_err(|_| {
crate::error::Error::InvalidData("MaxiCode mode 2: country must be ASCII".into())
})?;
let scode_str = std::str::from_utf8(service).map_err(|_| {
crate::error::Error::InvalidData("MaxiCode mode 2: service must be ASCII".into())
})?;
let pri = pack_mode_2_primary(pcode_str, ccode_str, scode_str)?;
let encmsg = encode_secondary_a_b_with_ns(secondary)?;
if encmsg.len() > 84 {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 2: secondary message ({} cws) exceeds 84-cw capacity",
encmsg.len(),
)));
}
let mut sec = [PAD_CODE[0]; 84];
sec[..encmsg.len()].copy_from_slice(&encmsg);
build_symbol(&pri, &sec)
}
pub fn encode_mode_3(data: &[u8]) -> Result<MaxiCodeSymbol, crate::error::Error> {
let (postcode, country, service, secondary) = parse_mode_2_or_3_input(data)?;
let pcode_str = std::str::from_utf8(postcode).map_err(|_| {
crate::error::Error::InvalidData("MaxiCode mode 3: postcode must be ASCII".into())
})?;
let ccode_str = std::str::from_utf8(country).map_err(|_| {
crate::error::Error::InvalidData("MaxiCode mode 3: country must be ASCII".into())
})?;
let scode_str = std::str::from_utf8(service).map_err(|_| {
crate::error::Error::InvalidData("MaxiCode mode 3: service must be ASCII".into())
})?;
let pri = pack_mode_3_primary(pcode_str, ccode_str, scode_str)?;
let encmsg = encode_secondary_a_b_with_ns(secondary)?;
if encmsg.len() > 84 {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 3: secondary message ({} cws) exceeds 84-cw capacity",
encmsg.len(),
)));
}
let mut sec = [PAD_CODE[0]; 84];
sec[..encmsg.len()].copy_from_slice(&encmsg);
build_symbol(&pri, &sec)
}
pub(crate) const NS_CODEWORD: u8 = 31;
pub(crate) fn encode_ns_run(digits: &[u8]) -> Option<[u8; 6]> {
if digits.len() != 9 || !digits.iter().all(|b| b.is_ascii_digit()) {
return None;
}
let value: u32 = std::str::from_utf8(digits).unwrap().parse().unwrap();
debug_assert!(value < (1 << 30));
Some([
NS_CODEWORD,
((value >> 24) & 63) as u8,
((value >> 18) & 63) as u8,
((value >> 12) & 63) as u8,
((value >> 6) & 63) as u8,
(value & 63) as u8,
])
}
pub(crate) fn encode_set_a_with_ns(bytes: &[u8]) -> Result<Vec<u8>, crate::error::Error> {
let n = bytes.len();
let mut nseq = vec![0usize; n + 1];
for i in (0..n).rev() {
if bytes[i].is_ascii_digit() {
nseq[i] = nseq[i + 1] + 1;
}
}
let mut out: Vec<u8> = Vec::with_capacity(n);
let mut i = 0;
while i < n {
if nseq[i] >= 9 {
let run_len = nseq[i];
let lead = run_len - 9;
for &b in &bytes[i..i + lead] {
out.push(seta_codeword(b).unwrap());
}
let ns_start = i + lead;
let ns_chunk = encode_ns_run(&bytes[ns_start..ns_start + 9]).unwrap();
out.extend_from_slice(&ns_chunk);
i = ns_start + 9;
} else {
let b = bytes[i];
let cw = seta_codeword(b).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"MaxiCode encode_set_a_with_ns: byte 0x{b:02x} not in set A",
))
})?;
out.push(cw);
i += 1;
}
}
Ok(out)
}
pub(crate) fn setb_codeword(byte: u8) -> Option<u8> {
match byte {
b'`' => Some(0),
b'a'..=b'z' => Some(byte - b'`'),
28..=30 => Some(byte),
b'{' => Some(32),
b'}' => Some(34),
b'~' => Some(35),
127 => Some(36),
b';' => Some(37),
b'<' => Some(38),
b'=' => Some(39),
b'>' => Some(40),
b'?' => Some(41),
b'[' => Some(42),
b'\\' => Some(43),
b']' => Some(44),
b'^' => Some(45),
b'_' => Some(46),
_ => None,
}
}
pub(crate) fn setc_codeword(byte: u8) -> Option<u8> {
match byte {
192..=218 => Some(byte - 192),
28..=30 => Some(byte),
219..=223 => Some(byte - 219 + 32),
170 => Some(37),
172 => Some(38),
177 => Some(39),
178 => Some(40),
179 => Some(41),
181 => Some(42),
185 => Some(43),
186 => Some(44),
188 => Some(45),
189 => Some(46),
190 => Some(47),
128..=137 => Some(byte - 128 + 48),
32 => Some(59),
_ => None,
}
}
pub(crate) fn setd_codeword(byte: u8) -> Option<u8> {
match byte {
224..=250 => Some(byte - 224),
28..=30 => Some(byte),
251..=255 => Some(byte - 251 + 32),
161 => Some(37),
168 => Some(38),
171 => Some(39),
175 => Some(40),
176 => Some(41),
180 => Some(42),
183 => Some(43),
184 => Some(44),
187 => Some(45),
191 => Some(46),
138..=148 => Some(byte - 138 + 47),
32 => Some(59),
_ => None,
}
}
pub(crate) fn sete_codeword(byte: u8) -> Option<u8> {
match byte {
0..=26 => Some(byte),
27 => Some(30),
28..=31 => Some(byte - 28 + 32),
159 => Some(36),
160 => Some(37),
162 => Some(38),
163 => Some(39),
164 => Some(40),
165 => Some(41),
166 => Some(42),
167 => Some(43),
169 => Some(44),
173 => Some(45),
174 => Some(46),
182 => Some(47),
149..=158 => Some(byte - 149 + 48),
32 => Some(59),
_ => None,
}
}
pub(crate) fn seta_codeword(byte: u8) -> Option<u8> {
match byte {
b'\r' => Some(0),
b'A'..=b'Z' => Some(byte - b'@'),
28..=30 => Some(byte),
b' ' => Some(32),
34..=58 => Some(byte),
_ => None,
}
}
pub(crate) const MODMAP: [u16; 864] = [
469, 529, 286, 316, 347, 346, 673, 672, 703, 702, 647, 676, 283, 282, 313, 312, 370, 610, 618,
379, 378, 409, 408, 439, 705, 704, 559, 589, 588, 619, 458, 518, 640, 701, 675, 674, 285, 284,
315, 314, 310, 340, 531, 289, 288, 319, 349, 348, 456, 486, 517, 516, 471, 470, 369, 368, 399,
398, 429, 428, 549, 548, 579, 578, 609, 608, 649, 648, 679, 678, 709, 708, 639, 638, 669, 668,
699, 698, 279, 278, 309, 308, 339, 338, 381, 380, 411, 410, 441, 440, 561, 560, 591, 590, 621,
620, 547, 546, 577, 576, 607, 606, 367, 366, 397, 396, 427, 426, 291, 290, 321, 320, 351, 350,
651, 650, 681, 680, 711, 710, 1, 0, 31, 30, 61, 60, 3, 2, 33, 32, 63, 62, 5, 4, 35, 34, 65, 64,
7, 6, 37, 36, 67, 66, 9, 8, 39, 38, 69, 68, 11, 10, 41, 40, 71, 70, 13, 12, 43, 42, 73, 72, 15,
14, 45, 44, 75, 74, 17, 16, 47, 46, 77, 76, 19, 18, 49, 48, 79, 78, 21, 20, 51, 50, 81, 80, 23,
22, 53, 52, 83, 82, 25, 24, 55, 54, 85, 84, 27, 26, 57, 56, 87, 86, 117, 116, 147, 146, 177,
176, 115, 114, 145, 144, 175, 174, 113, 112, 143, 142, 173, 172, 111, 110, 141, 140, 171, 170,
109, 108, 139, 138, 169, 168, 107, 106, 137, 136, 167, 166, 105, 104, 135, 134, 165, 164, 103,
102, 133, 132, 163, 162, 101, 100, 131, 130, 161, 160, 99, 98, 129, 128, 159, 158, 97, 96, 127,
126, 157, 156, 95, 94, 125, 124, 155, 154, 93, 92, 123, 122, 153, 152, 91, 90, 121, 120, 151,
150, 181, 180, 211, 210, 241, 240, 183, 182, 213, 212, 243, 242, 185, 184, 215, 214, 245, 244,
187, 186, 217, 216, 247, 246, 189, 188, 219, 218, 249, 248, 191, 190, 221, 220, 251, 250, 193,
192, 223, 222, 253, 252, 195, 194, 225, 224, 255, 254, 197, 196, 227, 226, 257, 256, 199, 198,
229, 228, 259, 258, 201, 200, 231, 230, 261, 260, 203, 202, 233, 232, 263, 262, 205, 204, 235,
234, 265, 264, 207, 206, 237, 236, 267, 266, 297, 296, 327, 326, 357, 356, 295, 294, 325, 324,
355, 354, 293, 292, 323, 322, 353, 352, 277, 276, 307, 306, 337, 336, 275, 274, 305, 304, 335,
334, 273, 272, 303, 302, 333, 332, 271, 270, 301, 300, 331, 330, 361, 360, 391, 390, 421, 420,
363, 362, 393, 392, 423, 422, 365, 364, 395, 394, 425, 424, 383, 382, 413, 412, 443, 442, 385,
384, 415, 414, 445, 444, 387, 386, 417, 416, 447, 446, 477, 476, 507, 506, 537, 536, 475, 474,
505, 504, 535, 534, 473, 472, 503, 502, 533, 532, 455, 454, 485, 484, 515, 514, 453, 452, 483,
482, 513, 512, 451, 450, 481, 480, 511, 510, 541, 540, 571, 570, 601, 600, 543, 542, 573, 572,
603, 602, 545, 544, 575, 574, 605, 604, 563, 562, 593, 592, 623, 622, 565, 564, 595, 594, 625,
624, 567, 566, 597, 596, 627, 626, 657, 656, 687, 686, 717, 716, 655, 654, 685, 684, 715, 714,
653, 652, 683, 682, 713, 712, 637, 636, 667, 666, 697, 696, 635, 634, 665, 664, 695, 694, 633,
632, 663, 662, 693, 692, 631, 630, 661, 660, 691, 690, 721, 720, 751, 750, 781, 780, 723, 722,
753, 752, 783, 782, 725, 724, 755, 754, 785, 784, 727, 726, 757, 756, 787, 786, 729, 728, 759,
758, 789, 788, 731, 730, 761, 760, 791, 790, 733, 732, 763, 762, 793, 792, 735, 734, 765, 764,
795, 794, 737, 736, 767, 766, 797, 796, 739, 738, 769, 768, 799, 798, 741, 740, 771, 770, 801,
800, 743, 742, 773, 772, 803, 802, 745, 744, 775, 774, 805, 804, 747, 746, 777, 776, 807, 806,
837, 836, 867, 866, 897, 896, 835, 834, 865, 864, 895, 894, 833, 832, 863, 862, 893, 892, 831,
830, 861, 860, 891, 890, 829, 828, 859, 858, 889, 888, 827, 826, 857, 856, 887, 886, 825, 824,
855, 854, 885, 884, 823, 822, 853, 852, 883, 882, 821, 820, 851, 850, 881, 880, 819, 818, 849,
848, 879, 878, 817, 816, 847, 846, 877, 876, 815, 814, 845, 844, 875, 874, 813, 812, 843, 842,
873, 872, 811, 810, 841, 840, 871, 870, 901, 900, 931, 930, 961, 960, 903, 902, 933, 932, 963,
962, 905, 904, 935, 934, 965, 964, 907, 906, 937, 936, 967, 966, 909, 908, 939, 938, 969, 968,
911, 910, 941, 940, 971, 970, 913, 912, 943, 942, 973, 972, 915, 914, 945, 944, 975, 974, 917,
916, 947, 946, 977, 976, 919, 918, 949, 948, 979, 978, 921, 920, 951, 950, 981, 980, 923, 922,
953, 952, 983, 982, 925, 924, 955, 954, 985, 984, 927, 926, 957, 956, 987, 986, 58, 89, 88,
118, 149, 148, 178, 209, 208, 238, 269, 268, 298, 329, 328, 358, 389, 388, 418, 449, 448, 478,
509, 508, 538, 569, 568, 598, 629, 628, 658, 689, 688, 718, 749, 748, 778, 809, 808, 838, 869,
868, 898, 929, 928, 958, 989, 988,
];
pub(crate) const ROWS: usize = 33;
pub(crate) const COLS: usize = 30;
pub(crate) const TOTAL_CELLS: usize = ROWS * COLS;
pub(crate) const SENTINEL_ECI: i32 = -1;
pub(crate) const SENTINEL_PAD: i32 = -2;
pub(crate) const SENTINEL_NS: i32 = -3;
pub(crate) const SENTINEL_LA: i32 = -4;
pub(crate) const SENTINEL_LB: i32 = -5;
pub(crate) const SENTINEL_SA: i32 = -6;
pub(crate) const SENTINEL_SB: i32 = -7;
pub(crate) const SENTINEL_SC: i32 = -8;
pub(crate) const SENTINEL_SD: i32 = -9;
pub(crate) const SENTINEL_SE: i32 = -10;
fn to_bin(value: u64, width: usize) -> Option<String> {
if width < 64 && value >= (1u64 << width) {
return None;
}
let mut s = String::with_capacity(width);
for k in (0..width).rev() {
s.push(if (value >> k) & 1 == 1 { '1' } else { '0' });
}
Some(s)
}
pub(crate) fn pack_mode_2_primary(
postcode: &str,
country: &str,
service: &str,
) -> Result<[u8; 10], crate::error::Error> {
let postcode = postcode.trim_end();
if postcode.is_empty() || postcode.len() > 9 || !postcode.bytes().all(|b| b.is_ascii_digit()) {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 2: postcode must be 1-9 ASCII digits, got {postcode:?}",
)));
}
if country.len() != 3 || !country.bytes().all(|b| b.is_ascii_digit()) {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 2: country must be 3 ASCII digits, got {country:?}",
)));
}
if service.len() != 3 || !service.bytes().all(|b| b.is_ascii_digit()) {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 2: service must be 3 ASCII digits, got {service:?}",
)));
}
let pcode_string;
let pcode = if country == "840" && postcode.len() == 5 {
pcode_string = format!("{postcode}0000");
pcode_string.as_str()
} else {
postcode
};
let mdb = to_bin(2, 4).unwrap();
let ccode_n: u64 = country.parse().unwrap();
let scode_n: u64 = service.parse().unwrap();
let ccb = to_bin(ccode_n, 10).unwrap();
let scb = to_bin(scode_n, 10).unwrap();
let pcode_n: u64 = pcode.parse().unwrap();
let pcode_len_bits = to_bin(pcode.len() as u64, 6).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"MaxiCode mode 2: postcode length {} doesn't fit in 6 bits",
pcode.len(),
))
})?;
let pcode_value_bits = to_bin(pcode_n, 30).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"MaxiCode mode 2: numeric postcode {pcode_n} doesn't fit in 30 bits",
))
})?;
let mut pcb = String::with_capacity(36);
pcb.push_str(&pcode_len_bits);
pcb.push_str(&pcode_value_bits);
assert_eq!(pcb.len(), 36);
let mut scm = [b'0'; 60];
let copy = |scm: &mut [u8; 60], dst: usize, src: &str| {
scm[dst..dst + src.len()].copy_from_slice(src.as_bytes());
};
copy(&mut scm, 2, &mdb);
copy(&mut scm, 38, &pcb[0..4]);
copy(&mut scm, 30, &pcb[4..10]);
copy(&mut scm, 24, &pcb[10..16]);
copy(&mut scm, 18, &pcb[16..22]);
copy(&mut scm, 12, &pcb[22..28]);
copy(&mut scm, 6, &pcb[28..34]);
copy(&mut scm, 0, &pcb[34..36]);
copy(&mut scm, 52, &ccb[0..2]);
copy(&mut scm, 42, &ccb[2..8]);
copy(&mut scm, 36, &ccb[8..10]);
copy(&mut scm, 54, &scb[0..6]);
copy(&mut scm, 48, &scb[6..10]);
Ok(slice_scm_to_codewords(&scm))
}
fn seta_value_mode3(c: u8) -> Option<u8> {
match c {
32 => Some(32),
34..=58 => Some(c),
65..=90 => Some(c - 64),
_ => None,
}
}
fn slice_scm_to_codewords(scm: &[u8; 60]) -> [u8; 10] {
let mut pri = [0u8; 10];
for (chunk_idx, slot) in pri.iter_mut().enumerate() {
let mut value = 0u8;
for bit_idx in 0..6 {
let bit = scm[chunk_idx * 6 + bit_idx] - b'0';
value = (value << 1) | bit;
}
*slot = value;
}
pri
}
pub(crate) fn pack_mode_3_primary(
postcode: &str,
country: &str,
service: &str,
) -> Result<[u8; 10], crate::error::Error> {
if postcode.is_empty() || postcode.len() > 6 {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 3: postcode must be 1-6 characters, got {} chars",
postcode.len(),
)));
}
for &b in postcode.as_bytes() {
if seta_value_mode3(b).is_none() {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 3: postcode contains invalid byte 0x{b:02x} (allowed: space, '\"'..':', 'A'..'Z')",
)));
}
}
if country.len() != 3 || !country.bytes().all(|b| b.is_ascii_digit()) {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 3: country must be 3 ASCII digits, got {country:?}",
)));
}
if service.len() != 3 || !service.bytes().all(|b| b.is_ascii_digit()) {
return Err(crate::error::Error::InvalidData(format!(
"MaxiCode mode 3: service must be 3 ASCII digits, got {service:?}",
)));
}
let mut padded = [b' '; 6];
padded[..postcode.len()].copy_from_slice(postcode.as_bytes());
let mdb = to_bin(3, 4).unwrap();
let ccode_n: u64 = country.parse().unwrap();
let scode_n: u64 = service.parse().unwrap();
let ccb = to_bin(ccode_n, 10).unwrap();
let scb = to_bin(scode_n, 10).unwrap();
let mut pcb = String::with_capacity(36);
for &b in &padded {
let val = seta_value_mode3(b).unwrap();
pcb.push_str(&to_bin(u64::from(val), 6).unwrap());
}
let mut scm = [b'0'; 60];
let copy = |scm: &mut [u8; 60], dst: usize, src: &str| {
scm[dst..dst + src.len()].copy_from_slice(src.as_bytes());
};
copy(&mut scm, 2, &mdb);
copy(&mut scm, 38, &pcb[0..4]);
copy(&mut scm, 30, &pcb[4..10]);
copy(&mut scm, 24, &pcb[10..16]);
copy(&mut scm, 18, &pcb[16..22]);
copy(&mut scm, 12, &pcb[22..28]);
copy(&mut scm, 6, &pcb[28..34]);
copy(&mut scm, 0, &pcb[34..36]);
copy(&mut scm, 52, &ccb[0..2]);
copy(&mut scm, 42, &ccb[2..8]);
copy(&mut scm, 36, &ccb[8..10]);
copy(&mut scm, 54, &scb[0..6]);
copy(&mut scm, 48, &scb[6..10]);
Ok(slice_scm_to_codewords(&scm))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pad_code_shape() {
assert_eq!(PAD_CODE, [33, 33, 0, 0, 28]);
}
#[test]
fn seta_value_mode3_arms_and_boundaries() {
assert_eq!(seta_value_mode3(32), Some(32));
assert_eq!(seta_value_mode3(31), None);
assert_eq!(
seta_value_mode3(33),
None,
"'!' is between 32 (space) and 34 ('\"')"
);
assert_eq!(seta_value_mode3(34), Some(34), "'\"' start of range");
assert_eq!(seta_value_mode3(45), Some(45), "'-' mid-range");
assert_eq!(seta_value_mode3(58), Some(58), "':' end of range");
assert_eq!(
seta_value_mode3(59),
None,
"';' is between ':' (58) and 'A' (65)"
);
assert_eq!(seta_value_mode3(64), None, "'@' is one before 'A'");
assert_eq!(seta_value_mode3(65), Some(1), "'A' → 1");
assert_eq!(seta_value_mode3(77), Some(13), "'M' → 13");
assert_eq!(seta_value_mode3(90), Some(26), "'Z' → 26");
assert_eq!(seta_value_mode3(91), None, "'[' just after 'Z'");
assert_eq!(seta_value_mode3(0), None);
assert_eq!(seta_value_mode3(97), None, "lowercase 'a' rejected");
assert_eq!(seta_value_mode3(122), None, "lowercase 'z' rejected");
assert_eq!(seta_value_mode3(255), None);
}
#[test]
fn slice_scm_to_codewords_msb_packing() {
let scm = [b'0'; 60];
assert_eq!(slice_scm_to_codewords(&scm), [0u8; 10]);
let scm = [b'1'; 60];
assert_eq!(slice_scm_to_codewords(&scm), [0x3F; 10]);
let mut scm = [b'0'; 60];
for chunk in 0..10 {
scm[chunk * 6 + 5] = b'1';
}
assert_eq!(
slice_scm_to_codewords(&scm),
[1u8; 10],
"MSB-first: only LSB of each 6-bit chunk set → codeword = 1"
);
let mut scm = [b'0'; 60];
for chunk in 0..10 {
scm[chunk * 6] = b'1';
}
assert_eq!(
slice_scm_to_codewords(&scm),
[32u8; 10],
"MSB-first: only MSB set → codeword = 32"
);
let mut scm = [b'0'; 60];
for chunk in 0..10 {
scm[chunk * 6 + 1] = b'1';
scm[chunk * 6 + 3] = b'1';
scm[chunk * 6 + 5] = b'1';
}
assert_eq!(
slice_scm_to_codewords(&scm),
[21u8; 10],
"MSB-first alternating: 010101 = 0x15 = 21"
);
let mut scm = [b'0'; 60];
for chunk in 0..10u8 {
for bit_idx in 0..6 {
let bit = (chunk >> (5 - bit_idx)) & 1;
scm[chunk as usize * 6 + bit_idx] = b'0' + bit;
}
}
let expected: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
assert_eq!(
slice_scm_to_codewords(&scm),
expected,
"per-chunk indexing: each chunk encodes its index"
);
}
#[test]
fn to_bin_msb_first_with_width_guard() {
assert_eq!(to_bin(0, 4), Some("0000".to_string()));
assert_eq!(to_bin(5, 4), Some("0101".to_string()));
assert_eq!(to_bin(15, 4), Some("1111".to_string()), "max-fit value");
assert_eq!(to_bin(16, 4), None, "value=16 > 4 bits → None");
assert_eq!(to_bin(0, 0), Some(String::new()));
assert_eq!(to_bin(1, 0), None);
assert_eq!(to_bin(0, 1), Some("0".to_string()));
assert_eq!(to_bin(1, 1), Some("1".to_string()));
assert_eq!(to_bin(2, 1), None);
assert_eq!(to_bin(0xAA, 8), Some("10101010".to_string()), "MSB first");
assert_eq!(to_bin(0xFF, 8), Some("11111111".to_string()));
assert_eq!(to_bin(0x100, 8), None, "256 doesn't fit in 8 bits");
assert_eq!(
to_bin(0, 64),
Some("0".repeat(64)),
"width=64: 0 → 64 zeros"
);
assert_eq!(
to_bin(u64::MAX, 64),
Some("1".repeat(64)),
"width=64: u64::MAX → 64 ones (no overflow check)"
);
let high_bit_64 = 1u64 << 63;
let mut expected_high = String::from("1");
expected_high.push_str(&"0".repeat(63));
assert_eq!(
to_bin(high_bit_64, 64),
Some(expected_high),
"width=64: top bit set, rest zero"
);
assert_eq!(to_bin(0, 63), Some("0".repeat(63)));
let max_63 = (1u64 << 63) - 1;
assert_eq!(
to_bin(max_63, 63),
Some("1".repeat(63)),
"max fit in 63 bits"
);
assert_eq!(to_bin(1u64 << 63, 63), None, "1<<63 doesn't fit in 63 bits");
}
#[test]
fn latch_len_shape() {
for (from, row) in LATCH_LEN.iter().enumerate() {
assert_eq!(row[from], 0, "identity latch should be 0");
}
assert_eq!(LATCH_LEN[0], [0, 1, 1, 1, 1]);
assert_eq!(LATCH_LEN[4], [2, 2, 2, 2, 0]);
}
#[test]
fn modmap_covers_all_grid_cells() {
assert_eq!(MODMAP.len(), 864);
for (i, &pos) in MODMAP.iter().enumerate() {
assert!(
(pos as usize) < TOTAL_CELLS,
"MODMAP[{i}] = {pos} ≥ {TOTAL_CELLS}",
);
}
assert_eq!(MODMAP[0], 469);
assert_eq!(MODMAP[5], 346);
assert_eq!(MODMAP[863], 988);
}
#[test]
fn modmap_entries_are_unique() {
let mut seen = [false; TOTAL_CELLS];
let mut dupes = 0;
for &pos in MODMAP.iter() {
if seen[pos as usize] {
dupes += 1;
}
seen[pos as usize] = true;
}
assert_eq!(dupes, 0, "MODMAP has {dupes} duplicate cells");
let used = seen.iter().filter(|&&b| b).count();
assert_eq!(used, 864);
}
#[test]
fn encode_set_a_only_matches_oracle() {
assert_eq!(
encode_set_a_only(b"TEST1234").unwrap(),
vec![20, 5, 19, 20, 49, 50, 51, 52],
);
assert_eq!(encode_set_a_only(b"ABC").unwrap(), vec![1, 2, 3]);
assert_eq!(
encode_set_a_only(b"12345678").unwrap(),
vec![49, 50, 51, 52, 53, 54, 55, 56],
);
}
#[test]
fn encode_set_a_with_ns_matches_oracle_corpus() {
let cases: &[(&[u8], &[u8])] = &[
(b"TEST1234", &[20, 5, 19, 20, 49, 50, 51, 52]),
(b"ABC", &[1, 2, 3]),
(b"12345678", &[49, 50, 51, 52, 53, 54, 55, 56]),
(b"012345678", &[31, 0, 47, 6, 5, 14]),
(b"123456789", &[31, 7, 22, 60, 52, 21]),
(b"0123456789", &[48, 31, 7, 22, 60, 52, 21]),
(b"01234567890", &[48, 49, 31, 13, 62, 51, 35, 18]),
(b"012345678901", &[48, 49, 50, 31, 20, 38, 42, 16, 53]),
(b"X123456789Y", &[24, 31, 7, 22, 60, 52, 21, 25]),
(b"X012345678Y", &[24, 31, 0, 47, 6, 5, 14, 25]),
(b"X12345678901Y", &[24, 49, 50, 31, 20, 38, 42, 16, 53, 25]),
(
b"ABC123456789DEF",
&[1, 2, 3, 31, 7, 22, 60, 52, 21, 4, 5, 6],
),
];
for (input, want) in cases {
let input_echo = std::str::from_utf8(input).unwrap_or("<non-utf8>");
let got = encode_set_a_with_ns(input).unwrap_or_else(|e| {
panic!(
"encode_set_a_with_ns({input_echo:?}) (MaxiCode set-A + NS-run digit-pair compaction corpus) must succeed: {e:?}",
)
});
assert_eq!(
got,
*want,
"encode_set_a_with_ns mismatch for {:?}",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
);
}
}
#[test]
fn encode_ns_run_9_digit_matches_oracle() {
assert_eq!(
encode_ns_run(b"123456789").unwrap(),
[31, 7, 22, 60, 52, 21],
);
assert_eq!(encode_ns_run(b"012345678").unwrap(), [31, 0, 47, 6, 5, 14],);
assert_eq!(encode_ns_run(b"000000000").unwrap(), [31, 0, 0, 0, 0, 0]);
assert_eq!(
encode_ns_run(b"999999999").unwrap(),
[31, 59, 38, 44, 39, 63],
);
}
#[test]
fn encode_ns_run_rejects_non_9_digit_input() {
assert!(encode_ns_run(b"12345678").is_none()); assert!(encode_ns_run(b"1234567890").is_none()); assert!(encode_ns_run(b"12345678A").is_none()); }
#[test]
fn encode_set_a_only_rejects_non_seta() {
let err = encode_set_a_only(b"abc").expect_err("'abc' must reject");
let crate::error::Error::InvalidData(msg) = err else {
panic!("'abc' must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("MaxiCode encode_set_a_only:")
&& msg.contains("0x61")
&& msg.contains("not in set A"),
"lowercase 'a' (0x61) must pin set-A-membership diagnostic + byte echo; got {msg:?}"
);
assert!(
!msg.contains("NS optimization"),
"set-A-membership diagnostic must not leak the NS-run arm; got {msg:?}"
);
let err = encode_set_a_only(&[0xC3]).expect_err("0xC3 must reject");
let crate::error::Error::InvalidData(msg) = err else {
panic!("0xC3 must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("MaxiCode encode_set_a_only:"),
"missing `MaxiCode encode_set_a_only:` arm-route prefix: {msg:?}"
);
assert!(
msg.contains("0xc3") && msg.contains("not in set A"),
"high-bit byte must pin 0xc3 echo + set-A-membership text; got {msg:?}"
);
assert!(
!msg.contains("NS optimization"),
"cross-arm contamination: high-bit-byte reject mentions `NS optimization`: {msg:?}"
);
let err = encode_set_a_only(b"123456789").expect_err("9-digit run must reject");
let crate::error::Error::InvalidData(msg) = err else {
panic!("9-digit run must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("9+ digit run") && msg.contains("NS optimization"),
"digit-run diagnostic must call out the 9+ run + NS optimization; got {msg:?}"
);
assert!(
!msg.contains("not in set A"),
"NS-run diagnostic must not leak the set-A-membership arm; got {msg:?}"
);
}
#[test]
fn latch_seq_shape_matches_latch_len() {
for from in 0..5 {
for to in 0..5 {
assert_eq!(
LATCH_SEQ[from][to].len(),
LATCH_LEN[from][to] as usize,
"LATCH_SEQ[{from}][{to}] length mismatch",
);
}
}
}
#[test]
fn encode_secondary_a_b_matches_oracle_corpus() {
let cases: &[(&[u8], &[u8])] = &[
(b"ABC", &[1, 2, 3]),
(b"abc", &[63, 1, 2, 3]),
(b"Aa", &[1, 59, 1]),
(b"Ab", &[1, 59, 2]),
(b"Aab", &[1, 63, 1, 2]),
(b"Aabc", &[1, 63, 1, 2, 3]),
(b"AaB", &[1, 59, 1, 2]),
(b"AabB", &[1, 59, 1, 59, 2, 2]),
(b"AabBC", &[1, 59, 1, 59, 2, 2, 3]),
(b"AabcB", &[1, 63, 1, 2, 3, 63, 2]),
(b"AabcBC", &[1, 63, 1, 2, 3, 63, 2, 3]),
(b"AabcdB", &[1, 63, 1, 2, 3, 4, 63, 2]),
(b"TEST1234", &[20, 5, 19, 20, 49, 50, 51, 52]),
(b"ABCabc", &[1, 2, 3, 63, 1, 2, 3]),
(b"abcDEF", &[63, 1, 2, 3, 63, 4, 5, 6]),
];
for (input, want) in cases {
let got = encode_secondary_a_b(input).unwrap();
assert_eq!(
got,
*want,
"encode_secondary_a_b mismatch for {:?}",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
);
}
}
#[test]
fn apply_rs_ecc_matches_oracle_for_mode_4_x() {
let pri: [u8; 10] = [4, 24, 33, 33, 33, 33, 33, 33, 33, 33];
let sec = [33u8; 84];
let cws = apply_rs_ecc(&pri, &sec).expect(
"apply_rs_ecc(mode 4 X pri+sec) (MaxiCode mode-4 RS-ECC path: GF(64) RS over pri/sec; 10+10+84+40=144 cw total) must succeed",
);
assert_eq!(&cws[..10], pri.as_slice(), "pri segment mismatch");
let want_prichk: [u8; 10] = [39, 10, 13, 32, 7, 26, 45, 16, 13, 6];
assert_eq!(&cws[10..20], want_prichk.as_slice(), "prichk mismatch");
assert_eq!(&cws[20..104], sec.as_slice(), "sec segment mismatch");
let want_secchk: [u8; 40] = [
60, 60, 40, 40, 9, 9, 43, 43, 14, 14, 50, 50, 12, 12, 53, 53, 57, 57, 58, 58, 36, 36,
28, 28, 10, 10, 53, 53, 37, 37, 30, 30, 14, 14, 5, 5, 31, 31, 40, 40,
];
assert_eq!(&cws[104..], want_secchk.as_slice(), "secchk mismatch");
}
#[test]
fn apply_rs_ecc_rejects_invalid_secondary_length() {
let pri = [0u8; 10];
let err_empty = apply_rs_ecc(&pri, &[]).expect_err("empty sec must error");
let crate::error::Error::InvalidData(msg) = err_empty else {
panic!("empty-sec error must be InvalidData; got {err_empty:?}");
};
assert!(msg.contains("MaxiCode RS-ECC"), "diagnostic prefix: {msg}");
assert!(msg.contains("84"), "mention valid length 84: {msg}");
assert!(msg.contains("68"), "mention valid length 68: {msg}");
assert!(msg.contains(" 0"), "echo the wrong length 0: {msg}");
let short = [0u8; 67];
let err_67 = apply_rs_ecc(&pri, &short).expect_err("67-byte sec must error");
let crate::error::Error::InvalidData(msg) = err_67 else {
panic!("67-byte sec error must be InvalidData; got {err_67:?}");
};
assert!(msg.contains("67"), "echo the wrong length 67: {msg}");
let mid = [0u8; 70];
let err_70 = apply_rs_ecc(&pri, &mid).expect_err("70-byte sec must error");
let crate::error::Error::InvalidData(msg) = err_70 else {
panic!("70-byte sec error must be InvalidData; got {err_70:?}");
};
assert!(msg.contains("70"), "echo the wrong length 70: {msg}");
let just_over = [0u8; 85];
let err_85 = apply_rs_ecc(&pri, &just_over).expect_err("85-byte sec must error");
let crate::error::Error::InvalidData(msg) = err_85 else {
panic!("85-byte sec error must be InvalidData; got {err_85:?}");
};
assert!(msg.contains("85"), "echo the wrong length 85: {msg}");
let big = [0u8; 100];
let err_100 = apply_rs_ecc(&pri, &big).expect_err("100-byte sec must error");
let crate::error::Error::InvalidData(msg) = err_100 else {
panic!("100-byte sec error must be InvalidData; got {err_100:?}");
};
assert!(msg.contains("100"), "echo the wrong length 100: {msg}");
}
#[test]
fn lay_out_codewords_matches_oracle_for_mode_4_x() {
let pri: [u8; 10] = [4, 24, 33, 33, 33, 33, 33, 33, 33, 33];
let sec = [33u8; 84];
let cws = apply_rs_ecc(&pri, &sec).expect(
"apply_rs_ecc(mode 4 X pri+sec) (MaxiCode RS-ECC reference for lay_out_codewords pixs path) must succeed",
);
let pixs = lay_out_codewords(&cws);
assert_eq!(pixs.len(), 350, "pixs length mismatch");
let want_first: &[u16] = &[
316, 672, 703, 283, 610, 618, 439, 705, 619, 458, 674, 285, 340, 531, 348, 456, 470,
369, 428, 549, 578, 609, 608, 679, 709, 669, 668, 698, 279, 410,
];
assert_eq!(&pixs[..30], want_first, "pixs first 30 mismatch");
assert_eq!(&pixs[pixs.len() - 13..], FIXED_BLACK_POSITIONS);
}
#[test]
fn build_symbol_end_to_end_mode_4() {
let pri: [u8; 10] = [4, 24, 33, 33, 33, 33, 33, 33, 33, 33];
let sec = [33u8; 84];
let sym = build_symbol(&pri, &sec).unwrap();
assert_eq!(sym.rows(), 33);
assert_eq!(sym.cols(), 30);
for &p in FIXED_BLACK_POSITIONS {
let row = (p as usize) / 30;
let col = (p as usize) % 30;
assert!(
sym.is_on(row, col),
"fixed pos {p} (row {row} col {col}) should be on",
);
}
assert!(!sym.is_on(33, 0));
assert!(!sym.is_on(0, 30));
}
#[test]
fn maxicode_symbol_row_offset() {
assert!(!MaxiCodeSymbol::row_is_offset(0));
assert!(MaxiCodeSymbol::row_is_offset(1));
assert!(!MaxiCodeSymbol::row_is_offset(2));
assert!(MaxiCodeSymbol::row_is_offset(31));
assert!(!MaxiCodeSymbol::row_is_offset(32));
}
#[test]
fn build_grid_populates_correct_rows_cols() {
let pixs: &[u16] = &[0, 29, 30, 989]; let grid = build_grid(pixs);
assert!(grid[0][0]);
assert!(grid[0][29]);
assert!(grid[1][0]);
assert!(grid[32][29]);
assert!(!grid[15][15]);
}
#[test]
fn codewords_to_mods_extracts_msb_first() {
let mut cws = [0u8; 144];
cws[0] = 63;
cws[1] = 1; let mods = codewords_to_mods(&cws);
assert_eq!(&mods[..6], &[true; 6]);
assert_eq!(&mods[6..12], &[false, false, false, false, false, true]);
}
#[test]
fn codewords_to_mods_pins_mid_array_and_last_codeword_positions() {
let mut cws = [0u8; 144];
cws[50] = 0b10_0000; cws[100] = 0b01_0001; cws[143] = 0b11_1111;
let mods = codewords_to_mods(&cws);
assert_eq!(mods.len(), 864);
assert!(mods[300], "cws[50] bit 0 (MSB-first) = 1");
for b in 1..6 {
assert!(!mods[300 + b], "cws[50] bit {b} should be 0");
}
assert_eq!(
&mods[600..606],
&[false, true, false, false, false, true],
"cws[100]=17 (0b010001) MSB-first → [F,T,F,F,F,T]"
);
for b in 0..6 {
assert!(mods[858 + b], "cws[143] (LAST codeword) bit {b} = 1");
}
for i in 0..864 {
let in_set =
(300..306).contains(&i) || (600..606).contains(&i) || (858..864).contains(&i);
if !in_set {
assert!(
!mods[i],
"mods[{i}] should be false (no codeword sets this position)"
);
}
}
}
#[test]
fn encode_secondary_a_b_with_ns_matches_oracle_corpus() {
let cases: &[(&[u8], &[u8])] = &[
(
b"ABC123456789DEF",
&[1, 2, 3, 31, 7, 22, 60, 52, 21, 4, 5, 6],
),
(
b"TEST1234abc",
&[20, 5, 19, 20, 49, 50, 51, 52, 63, 1, 2, 3],
),
(b"Aa123456789", &[1, 59, 1, 31, 7, 22, 60, 52, 21]),
(b"abc012345678", &[63, 1, 2, 3, 31, 0, 47, 6, 5, 14]),
(
b"X123456789Yabcdef",
&[24, 31, 7, 22, 60, 52, 21, 25, 63, 1, 2, 3, 4, 5, 6],
),
];
for (input, want) in cases {
let got = encode_secondary_a_b_with_ns(input).unwrap();
assert_eq!(
got,
*want,
"encode_secondary_a_b_with_ns mismatch for {:?}",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
);
}
}
#[test]
fn encode_secondary_a_b_rejects_set_cde_bytes() {
for (input, want_byte) in [(&[0xC3u8] as &[u8], "0xc3"), (b"abc\xff", "0xff")] {
let err = encode_secondary_a_b(input).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("encode_secondary_a_b({input:?}) must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("MaxiCode encode_secondary_a_b:")
&& msg.contains(want_byte)
&& msg.contains("needs set C/D/E")
&& msg.contains("encode_secondary_a_b_with_ns"),
"{input:?} diagnostic must pin function name + {want_byte:?} byte + C/D/E
hint + remediation function; got {msg:?}"
);
}
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_single_byte_shift() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 0]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_d_single_byte_shift() {
let got = encode_secondary_a_b_with_ns(b"TEST\xE0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 61, 0]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_e_single_byte_shift() {
let got = encode_secondary_a_b_with_ns(b"TEST\xA0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 62, 37]);
}
#[test]
fn encode_secondary_a_b_with_ns_charset_preference_order() {
assert_eq!(
encode_secondary_a_b_with_ns(b"TEST\x80").unwrap(),
vec![20, 5, 19, 20, 60, 48]
);
assert_eq!(
encode_secondary_a_b_with_ns(b"TEST\x8A").unwrap(),
vec![20, 5, 19, 20, 61, 47]
);
assert_eq!(
encode_secondary_a_b_with_ns(b"TEST\x9B").unwrap(),
vec![20, 5, 19, 20, 62, 54]
);
assert_eq!(
encode_secondary_a_b_with_ns(b"TEST\xB2").unwrap(),
vec![20, 5, 19, 20, 60, 40]
);
assert_eq!(
encode_secondary_a_b_with_ns(b"TEST\xB7").unwrap(),
vec![20, 5, 19, 20, 61, 43]
);
}
#[test]
fn encode_secondary_a_b_with_ns_set_cde_prefix() {
let got = encode_secondary_a_b_with_ns(b"\xC0TEST").unwrap();
assert_eq!(got, vec![60, 0, 20, 5, 19, 20]);
}
#[test]
fn encode_secondary_a_b_with_ns_two_set_c_bytes() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC1").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 0, 60, 1]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_latch_3_bytes() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC1\xC2").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 1, 2, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_latch_4_bytes_eom() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC1\xC2\xC3").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 1, 2, 3, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_latch_5_bytes_eom() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC1\xC2\xC3\xC4").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 1, 2, 3, 4, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_latch_back_to_set_b() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC1\xC2\xC3abc").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 1, 2, 3, 63, 1, 2, 3]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_latch_back_to_set_a() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC1\xC2\xC3XYZ").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 1, 2, 3, 58, 24, 25, 26]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_latch_from_set_b_run() {
let got = encode_secondary_a_b_with_ns(b"abcd\xC0\xC1\xC2\xC3").unwrap();
assert_eq!(got, vec![63, 1, 2, 3, 4, 60, 60, 0, 1, 2, 3, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_d_latch_4_bytes() {
let got = encode_secondary_a_b_with_ns(b"TEST\xE0\xE1\xE2\xE3").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 61, 61, 0, 1, 2, 3, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_long_run_8_bytes() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC1\xC2\xC3\xC0\xC1\xC2\xC3").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 1, 2, 3, 0, 1, 2, 3, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_eight_bytes_start() {
let got = encode_secondary_a_b_with_ns(b"\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7").unwrap();
assert_eq!(got, vec![60, 60, 0, 1, 2, 3, 4, 5, 6, 7, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_intra_latch_d_shift() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC0\xE0\xC0\xC0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 0, 61, 0, 0, 0, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_intra_latch_d_shift_3plus3() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC0\xC0\xE0\xC0\xC0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 0, 0, 61, 0, 0, 0, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_intra_latch_trailing_d() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC0\xC0\xE0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 0, 0, 61, 0, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_intra_latch_consecutive_d() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC0\xC0\xE0\xE0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 0, 0, 61, 0, 61, 0, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_leading_d_uses_single_shift() {
let got = encode_secondary_a_b_with_ns(b"TEST\xE0\xC0\xC0\xC0\xC0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 61, 0, 60, 60, 0, 0, 0, 0, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_e_latch_eom_no_back_latch() {
let got = encode_secondary_a_b_with_ns(b"TEST\xA0\xA2\xA3\xA4").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 62, 62, 37, 38, 39, 40]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_e_latch_3byte_eom_no_back_latch() {
let got = encode_secondary_a_b_with_ns(b"TEST\xA0\xA2\xA3").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 62, 62, 37, 38, 39]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_e_latch_with_intra_c_shift_eom_no_back_latch() {
let got = encode_secondary_a_b_with_ns(b"TEST\xA0\xA2\xA3\xC0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 62, 62, 37, 38, 39, 60, 0]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_c_latch_eom_keeps_back_latch() {
let got = encode_secondary_a_b_with_ns(b"TEST\xC0\xC0\xC0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 60, 60, 0, 0, 0, 58]);
}
#[test]
fn encode_secondary_a_b_with_ns_set_d_latch_eom_keeps_back_latch() {
let got = encode_secondary_a_b_with_ns(b"TEST\xE0\xE0\xE0").unwrap();
assert_eq!(got, vec![20, 5, 19, 20, 61, 61, 0, 0, 0, 58]);
}
#[test]
fn setc_codeword_known_values() {
assert_eq!(setc_codeword(192), Some(0));
assert_eq!(setc_codeword(218), Some(26));
assert_eq!(setc_codeword(219), Some(32));
assert_eq!(setc_codeword(223), Some(36));
assert_eq!(setc_codeword(128), Some(48));
assert_eq!(setc_codeword(137), Some(57));
assert_eq!(setc_codeword(170), Some(37)); assert_eq!(setc_codeword(172), Some(38)); assert_eq!(setc_codeword(190), Some(47)); assert_eq!(setc_codeword(b' '), Some(59));
assert_eq!(setc_codeword(b'A'), None);
assert_eq!(setc_codeword(b'a'), None);
assert_eq!(setc_codeword(b'0'), None);
assert_eq!(setc_codeword(224), None); }
#[test]
fn setd_codeword_known_values() {
assert_eq!(setd_codeword(224), Some(0)); assert_eq!(setd_codeword(250), Some(26)); assert_eq!(setd_codeword(251), Some(32)); assert_eq!(setd_codeword(255), Some(36)); assert_eq!(setd_codeword(161), Some(37)); assert_eq!(setd_codeword(191), Some(46)); assert_eq!(setd_codeword(138), Some(47));
assert_eq!(setd_codeword(148), Some(57));
assert_eq!(setd_codeword(192), None); assert_eq!(setd_codeword(b'A'), None);
}
#[test]
fn sete_codeword_known_values() {
assert_eq!(sete_codeword(0), Some(0));
assert_eq!(sete_codeword(26), Some(26));
assert_eq!(sete_codeword(27), Some(30)); assert_eq!(sete_codeword(28), Some(32));
assert_eq!(sete_codeword(31), Some(35));
assert_eq!(sete_codeword(159), Some(36));
assert_eq!(sete_codeword(160), Some(37)); assert_eq!(sete_codeword(182), Some(47)); assert_eq!(sete_codeword(149), Some(48));
assert_eq!(sete_codeword(158), Some(57));
assert_eq!(sete_codeword(b'A'), None);
assert_eq!(sete_codeword(b'a'), None);
assert_eq!(sete_codeword(192), None);
}
#[test]
fn setb_codeword_known_values() {
assert_eq!(setb_codeword(b'a'), Some(1));
assert_eq!(setb_codeword(b'z'), Some(26));
assert_eq!(setb_codeword(b'`'), Some(0));
assert_eq!(setb_codeword(b';'), Some(37));
assert_eq!(setb_codeword(b'<'), Some(38));
assert_eq!(setb_codeword(b'='), Some(39));
assert_eq!(setb_codeword(b'>'), Some(40));
assert_eq!(setb_codeword(b'?'), Some(41));
assert_eq!(setb_codeword(b'['), Some(42));
assert_eq!(setb_codeword(b']'), Some(44));
assert_eq!(setb_codeword(b'_'), Some(46));
assert_eq!(setb_codeword(b'A'), None);
assert_eq!(setb_codeword(b'0'), None);
assert_eq!(setb_codeword(b'9'), None);
assert_eq!(setb_codeword(b' '), None);
}
#[test]
fn seta_codeword_known_values() {
assert_eq!(seta_codeword(b'A'), Some(1));
assert_eq!(seta_codeword(b'Z'), Some(26));
assert_eq!(seta_codeword(b'0'), Some(48));
assert_eq!(seta_codeword(b'9'), Some(57));
assert_eq!(seta_codeword(b' '), Some(32));
assert_eq!(seta_codeword(b'"'), Some(34));
assert_eq!(seta_codeword(b':'), Some(58));
assert_eq!(seta_codeword(b'/'), Some(47));
assert_eq!(seta_codeword(b'\r'), Some(0));
assert_eq!(seta_codeword(b'!'), None);
assert_eq!(seta_codeword(b'a'), None);
assert_eq!(seta_codeword(0x80), None);
}
fn setx_fp<F: Fn(u8) -> Option<u8>>(f: F) -> u64 {
let mut s: u64 = 0;
for b in 0u8..=255 {
let v = f(b).map(|x| x as u64 + 1).unwrap_or(0);
s = s.wrapping_add(
v.wrapping_mul((b as u64).wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
s
}
#[test]
fn setbcde_codeword_exhaustive_fingerprints_pinned() {
assert_eq!(
setx_fp(setb_codeword),
SETB_FP,
"setb_codeword fingerprint changed"
);
assert_eq!(
setx_fp(setc_codeword),
SETC_FP,
"setc_codeword fingerprint changed"
);
assert_eq!(
setx_fp(setd_codeword),
SETD_FP,
"setd_codeword fingerprint changed"
);
assert_eq!(
setx_fp(sete_codeword),
SETE_FP,
"sete_codeword fingerprint changed"
);
}
const SETB_FP: u64 = 258276599545300;
const SETC_FP: u64 = 745986699656874;
const SETD_FP: u64 = 798876332194799;
const SETE_FP: u64 = 484859236104260;
#[test]
fn build_grid_indexing_and_oob_guard() {
let pixs: [u16; 6] = [
0, 29, 30, 31, 989, 990, ];
let grid = build_grid(&pixs);
assert!(grid[0][0], "p=0 → (0,0)");
assert!(grid[0][29], "p=29 → (0,29) pins col = p % COLS");
assert!(grid[1][0], "p=30 → (1,0) pins row = p / COLS");
assert!(grid[1][1], "p=31 → (1,1)");
assert!(grid[32][29], "p=989 → (32,29) last valid cell");
assert!(!grid[1][29], "(1,29) untouched — pin row wrap");
assert!(!grid[0][1], "(0,1) untouched");
assert!(!grid[32][0], "(32,0) untouched — pin col = p % COLS");
assert!(!grid[2][0], "(2,0) untouched");
let total: usize = grid.iter().flat_map(|r| r.iter()).filter(|&&b| b).count();
assert_eq!(total, 5, "p=990 must be dropped by row<ROWS guard");
}
#[test]
fn pack_mode_2_primary_matches_oracle_us_zip5() {
let pri = pack_mode_2_primary("12345", "840", "001").unwrap();
assert_eq!(pri, [2, 36, 50, 46, 53, 17, 2, 18, 7, 0]);
}
#[test]
fn pack_mode_2_primary_matches_oracle_max_values() {
let pri = pack_mode_2_primary("999999999", "752", "100").unwrap();
assert_eq!(pri, [50, 63, 9, 43, 57, 30, 2, 60, 18, 6]);
}
#[test]
fn pack_mode_2_primary_handles_9_digit_postcode() {
let pri = pack_mode_2_primary("123450000", "840", "001").unwrap();
assert_eq!(pri, [2, 36, 50, 46, 53, 17, 2, 18, 7, 0]);
}
#[test]
fn pack_mode_3_primary_matches_oracle_uk_postcode() {
let pri = pack_mode_3_primary("SW1A1A", "826", "001").unwrap();
assert_eq!(pri, [19, 16, 28, 16, 60, 53, 36, 14, 7, 0]);
}
#[test]
fn pack_mode_3_primary_pads_short_postcode_with_spaces() {
let pri = pack_mode_3_primary("AB", "826", "001").unwrap();
assert_eq!(pri, [3, 8, 8, 8, 40, 16, 32, 14, 7, 0]);
}
#[test]
fn pack_mode_3_primary_rejects_malformed() {
for (postcode, want_len) in [("ABCDEFG", "7 chars"), ("", "0 chars")] {
let err = pack_mode_3_primary(postcode, "826", "001").unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("{postcode:?} must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("MaxiCode mode 3:")
&& msg.contains("postcode must be 1-6 characters")
&& msg.contains(want_len),
"{postcode:?} length arm must pin diagnostic + {want_len:?}; got {msg:?}"
);
}
for (postcode, want_byte) in [("abc", "0x61"), ("AB!CD", "0x21")] {
let err = pack_mode_3_primary(postcode, "826", "001").unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("{postcode:?} must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("postcode contains invalid byte") && msg.contains(want_byte),
"{postcode:?} invalid-byte arm must pin diagnostic + {want_byte:?}; got {msg:?}"
);
}
for (country, want_echo) in [("82", "\"82\""), ("8260", "\"8260\""), ("ABC", "\"ABC\"")] {
let err = pack_mode_3_primary("AB", country, "001").unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("country={country:?} must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("country must be 3 ASCII digits") && msg.contains(want_echo),
"country={country:?} must pin diagnostic + {want_echo:?}; got {msg:?}"
);
}
for (service, want_echo) in [("0001", "\"0001\""), ("01", "\"01\""), ("ABC", "\"ABC\"")] {
let err = pack_mode_3_primary("AB", "826", service).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("service={service:?} must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("service must be 3 ASCII digits") && msg.contains(want_echo),
"service={service:?} must pin diagnostic + {want_echo:?}; got {msg:?}"
);
}
}
#[test]
fn pack_mode_2_primary_rejects_malformed() {
for (postcode, want_echo) in [
("", "\"\""),
("1234567890", "\"1234567890\""),
("12345A", "\"12345A\""),
] {
let err = pack_mode_2_primary(postcode, "840", "001").unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("postcode={postcode:?} must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("MaxiCode mode 2:")
&& msg.contains("postcode must be 1-9 ASCII digits")
&& msg.contains(want_echo),
"postcode={postcode:?} must pin diagnostic + {want_echo:?}; got {msg:?}"
);
}
for (country, want_echo) in [("84", "\"84\""), ("8400", "\"8400\""), ("ABC", "\"ABC\"")] {
let err = pack_mode_2_primary("12345", country, "001").unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("country={country:?} must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("country must be 3 ASCII digits") && msg.contains(want_echo),
"country={country:?} must pin diagnostic + {want_echo:?}; got {msg:?}"
);
}
for (service, want_echo) in [("0001", "\"0001\""), ("01", "\"01\""), ("ABC", "\"ABC\"")] {
let err = pack_mode_2_primary("12345", "840", service).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("service={service:?} must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("service must be 3 ASCII digits") && msg.contains(want_echo),
"service={service:?} must pin diagnostic + {want_echo:?}; got {msg:?}"
);
}
}
#[test]
fn parse_mode_2_or_3_input_strips_fid_prefix() {
let data = b"\x5b\x29\x3e\x1e\x30\x31\x1d\x39\x39\
12345\x1d840\x1d001\x1dhello";
let (pc, cc, sc, sec) = parse_mode_2_or_3_input(data).unwrap();
assert_eq!(pc, b"12345");
assert_eq!(cc, b"840");
assert_eq!(sc, b"001");
assert_eq!(sec, b"hello");
}
#[test]
fn parse_mode_2_or_3_input_without_fid_prefix() {
let data = b"12345\x1d840\x1d001\x1dABC";
let (pc, cc, sc, sec) = parse_mode_2_or_3_input(data).unwrap();
assert_eq!(pc, b"12345");
assert_eq!(cc, b"840");
assert_eq!(sc, b"001");
assert_eq!(sec, b"ABC");
}
#[test]
fn parse_mode_2_or_3_input_rejects_too_few_fields() {
let data = b"12345\x1d840\x1d001";
let err = parse_mode_2_or_3_input(data).unwrap_err();
match err {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("MaxiCode mode 2/3"),
"rejection diagnostic must carry mode-2/3 prefix; got {msg}"
);
assert!(
msg.contains("expected 4 GS-separated fields"),
"diagnostic must carry the 4-field predicate; got {msg}"
);
assert!(
msg.contains("(postcode, country, service, secondary)"),
"diagnostic must enumerate the 4 field names; got {msg}"
);
assert!(
msg.contains("got 3"),
"diagnostic must echo actual field count (3) — kills `{{}}` drop; got {msg}"
);
}
other => panic!("expected InvalidData, got {other:?}"),
}
}
#[test]
fn encode_mode_2_smoke() {
let sym = encode_mode_2(b"12345\x1d840\x1d001\x1dhello").unwrap();
assert_eq!(sym.cols(), 30);
assert_eq!(sym.rows(), 33);
}
#[test]
fn encode_mode_3_smoke() {
let sym = encode_mode_3(b"K1A0B1\x1d124\x1d999\x1dhello").unwrap();
assert_eq!(sym.cols(), 30);
assert_eq!(sym.rows(), 33);
}
#[test]
fn encode_mode_2_rejects_bad_postcode() {
let err = encode_mode_2(b"ABC\x1d840\x1d001\x1dhello").unwrap_err();
match err {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("MaxiCode mode 2:"),
"mode-2 postcode rejection must carry mode-2 prefix; got {msg}"
);
assert!(
msg.contains("postcode must be 1-9 ASCII digits"),
"mode-2 rejection must carry digit-only predicate; got {msg}"
);
assert!(
msg.contains("\"ABC\""),
"mode-2 rejection must Debug-echo the raw postcode; got {msg}"
);
assert!(
!msg.contains("1-6 characters"),
"mode-2 rejection must NOT leak the mode-3 predicate; got {msg}"
);
}
other => panic!("expected InvalidData, got {other:?}"),
}
}
#[test]
fn encode_mode_2_rejects_oversized_secondary() {
let mut payload = b"12345\x1d840\x1d001\x1d".to_vec();
payload.extend(std::iter::repeat_n(b'A', 200));
match encode_mode_2(&payload).unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("MaxiCode mode 2:"),
"mode-2 capacity diagnostic must carry mode-2 prefix; got {msg}"
);
assert!(
msg.contains("exceeds 84-cw capacity"),
"mode-2 capacity diagnostic must carry the 84-cw cap; got {msg}"
);
assert!(
msg.contains("(200 cws)"),
"mode-2 capacity diagnostic must echo encmsg.len() as (200 cws); got {msg}"
);
assert!(
!msg.contains("93-cw") && !msg.contains("77-cw"),
"mode-2 diagnostic must NOT leak other modes' caps; got {msg}"
);
}
other => panic!("mode_2 must reject 200-byte secondary, got {other:?}"),
}
}
#[test]
fn encode_mode_3_rejects_oversized_secondary() {
let mut payload = b"AB12CD\x1d124\x1d999\x1d".to_vec();
payload.extend(std::iter::repeat_n(b'A', 200));
match encode_mode_3(&payload).unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("MaxiCode mode 3:"),
"mode-3 capacity diagnostic must carry mode-3 prefix; got {msg}"
);
assert!(
msg.contains("exceeds 84-cw capacity"),
"mode-3 capacity diagnostic must carry the 84-cw cap; got {msg}"
);
assert!(
msg.contains("(200 cws)"),
"mode-3 capacity diagnostic must echo encmsg.len() as (200 cws); got {msg}"
);
assert!(
!msg.contains("93-cw") && !msg.contains("77-cw"),
"mode-3 diagnostic must NOT leak other modes' caps; got {msg}"
);
assert!(
!msg.contains("MaxiCode mode 2:"),
"mode-3 diagnostic must NOT leak mode-2 prefix; got {msg}"
);
}
other => panic!("mode_3 must reject 200-byte secondary, got {other:?}"),
}
}
#[test]
fn encode_mode_4_smoke() {
let sym = encode_mode_4(b"hello").unwrap();
assert_eq!(sym.cols(), 30, "MaxiCode is always 30 cols");
assert_eq!(sym.rows(), 33, "MaxiCode is always 33 rows");
}
#[test]
fn encode_mode_4_rejects_overlong_payload() {
let big = vec![b'A'; 200];
match encode_mode_4(&big).unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("MaxiCode mode 4:"),
"mode-4 capacity diagnostic must carry mode-4 prefix; got {msg}"
);
assert!(
msg.contains("exceeds 93-cw capacity"),
"mode-4 capacity diagnostic must carry the 93-cw cap; got {msg}"
);
assert!(
msg.contains("200 codewords"),
"mode-4 capacity diagnostic must echo encmsg.len() (200); got {msg}"
);
assert!(
!msg.contains("84-cw") && !msg.contains("77-cw"),
"mode-4 diagnostic must NOT leak other modes' caps; got {msg}"
);
}
other => panic!("mode_4 must reject overlong payload, got {other:?}"),
}
}
#[test]
fn symbology_maxicode_mode_2_dispatches_correctly() {
use crate::{Encoded, Options, Symbology};
let mut opts = Options::default();
opts.extras.push(("mode".into(), "2".into()));
let enc = Symbology::Maxicode
.encode("12345\x1d840\x1d001\x1dhello", &opts)
.unwrap();
match enc {
Encoded::Hex(sym) => {
assert_eq!(
sym.cols(),
30,
"MaxiCode mode 2 must produce 30-col hex grid; got {} cols",
sym.cols()
);
assert_eq!(
sym.rows(),
33,
"MaxiCode mode 2 must produce 33-row hex grid; got {} rows",
sym.rows()
);
}
other => panic!(
"MaxiCode mode 2 must return Encoded::Hex (hexagonal grid family); got non-Hex variant {other:?}"
),
}
}
#[test]
fn encode_mode_5_codewords_match_bwip_js_test() {
let encmsg = encode_secondary_a_b_with_ns(b"TEST").unwrap();
assert_eq!(encmsg.len(), 4);
let mut cws = [PAD_CODE[0]; 78];
cws[0] = 5;
cws[1..5].copy_from_slice(&encmsg);
let mut pri = [0u8; 10];
pri.copy_from_slice(&cws[..10]);
let final_cws = apply_rs_ecc(&pri, &cws[10..]).unwrap();
assert_eq!(final_cws[..10], [5, 20, 5, 19, 20, 33, 33, 33, 33, 33]);
assert_eq!(final_cws[10..20], [50, 22, 36, 1, 17, 6, 35, 47, 16, 41]);
assert!(
final_cws[20..88].iter().all(|&b| b == 33),
"mode 5 sec=pads",
);
assert_eq!(final_cws[88..96], [5, 5, 39, 39, 9, 9, 62, 62]);
let sym = encode_mode_5(b"TEST").unwrap();
assert_eq!(sym.cols(), 30);
assert_eq!(sym.rows(), 33);
}
#[test]
fn encode_mode_6_codewords_match_bwip_js_test() {
let encmsg = encode_secondary_a_b_with_ns(b"TEST").unwrap();
let mut cws = [PAD_CODE[0]; 94];
cws[0] = 6;
cws[1..5].copy_from_slice(&encmsg);
let mut pri = [0u8; 10];
pri.copy_from_slice(&cws[..10]);
let final_cws = apply_rs_ecc(&pri, &cws[10..]).unwrap();
assert_eq!(final_cws[..10], [6, 20, 5, 19, 20, 33, 33, 33, 33, 33]);
assert_eq!(final_cws[10..20], [11, 53, 20, 26, 25, 29, 28, 16, 59, 54],);
assert!(
final_cws[20..104].iter().all(|&b| b == 33),
"mode 6 sec=pads",
);
assert_eq!(final_cws[104..112], [60, 60, 40, 40, 9, 9, 43, 43]);
let sym = encode_mode_6(b"TEST").unwrap();
assert_eq!(sym.cols(), 30);
assert_eq!(sym.rows(), 33);
}
#[test]
fn encode_mode_5_rejects_oversized_message() {
let oversized = vec![b'A'; 100];
let err = encode_mode_5(&oversized).unwrap_err();
match err {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("MaxiCode mode 5:"),
"mode-5 capacity diagnostic must carry the prefix; got {msg}"
);
assert!(
msg.contains("exceeds 77-cw capacity"),
"mode-5 capacity diagnostic must carry the 77-cw cap; got {msg}"
);
assert!(
msg.contains("100 codewords"),
"mode-5 capacity diagnostic must echo encmsg.len() (100); got {msg}"
);
assert!(
!msg.contains("84-cw") && !msg.contains("93-cw"),
"mode-5 diagnostic must NOT leak other modes' caps; got {msg}"
);
}
other => panic!("expected InvalidData, got {other:?}"),
}
}
#[test]
fn encode_mode_6_rejects_overlong_payload() {
let big = vec![b'A'; 200];
match encode_mode_6(&big).unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("MaxiCode mode 6:"),
"mode-6 capacity diagnostic must carry mode-6 prefix; got {msg}"
);
assert!(
msg.contains("exceeds 93-cw capacity"),
"mode-6 capacity diagnostic must carry the 93-cw cap; got {msg}"
);
assert!(
msg.contains("200 codewords"),
"mode-6 capacity diagnostic must echo encmsg.len() (200); got {msg}"
);
assert!(
!msg.contains("84-cw") && !msg.contains("77-cw"),
"mode-6 diagnostic must NOT leak other modes' caps; got {msg}"
);
}
other => panic!("mode_6 must reject overlong payload, got {other:?}"),
}
}
#[test]
fn symbology_maxicode_rejects_unsupported_mode() {
use crate::{Options, Symbology};
let mut opts = Options::default();
opts.extras.push(("mode".into(), "99".into()));
let r = Symbology::Maxicode.encode("hello", &opts);
let err = r.unwrap_err();
match err {
crate::error::Error::InvalidOption(msg) => {
assert!(
msg.contains("mode=99"),
"diagnostic must echo the parsed mode value (99); got {msg}"
);
assert!(
msg.contains("not supported"),
"diagnostic must carry the 'not supported' predicate; got {msg}"
);
assert!(
msg.contains("2..=6"),
"diagnostic must carry the implemented-range hint (2..=6); got {msg}"
);
assert!(
!msg.contains("must be a number"),
"range-arm diagnostic must NOT leak parse-fail wording; got {msg}"
);
}
other => panic!("expected InvalidOption, got {other:?}"),
}
}
#[test]
fn symbology_maxicode_mode_dispatcher_all_rejection_arms() {
use crate::error::Error;
use crate::{Options, Symbology};
let opts = Options::default().with("mode", "abc");
match Symbology::Maxicode.encode("hello", &opts) {
Err(Error::InvalidOption(msg)) => {
assert!(
msg.contains("maxicode:"),
"parse-fail diagnostic must carry the maxicode prefix; got {msg}"
);
assert!(
msg.contains("must be a number"),
"parse-fail diagnostic must carry the predicate; got {msg}"
);
assert!(
msg.contains("2..=6"),
"parse-fail diagnostic must carry the 2..=6 range hint; got {msg}"
);
assert!(
msg.contains("defaults to 4"),
"parse-fail diagnostic must carry the default-4 hint; got {msg}"
);
}
other => panic!("mode=abc should reject as parse-fail, got {other:?}"),
}
let opts = Options::default().with("mode", "0");
match Symbology::Maxicode.encode("hello", &opts) {
Err(Error::InvalidOption(msg)) => assert!(
msg.contains("mode=0") && msg.contains("not supported"),
"expected mode=0 unsupported diagnostic, got: {msg}"
),
other => panic!("mode=0 should reject, got {other:?}"),
}
let opts = Options::default().with("mode", "1");
assert!(
matches!(
Symbology::Maxicode.encode("hello", &opts),
Err(Error::InvalidOption(_))
),
"mode=1 should reject (below 2..=6)"
);
let opts = Options::default().with("mode", "7");
match Symbology::Maxicode.encode("hello", &opts) {
Err(Error::InvalidOption(msg)) => assert!(
msg.contains("mode=7") && msg.contains("not supported"),
"expected mode=7 unsupported diagnostic, got: {msg}"
),
other => panic!("mode=7 should reject (above 2..=6), got {other:?}"),
}
let opts = Options::default().with("mode", "");
assert!(
matches!(
Symbology::Maxicode.encode("hello", &opts),
Err(Error::InvalidOption(_))
),
"mode='' should reject as parse-fail"
);
let opts = Options::default().with("mode", "255");
assert!(
matches!(
Symbology::Maxicode.encode("hello", &opts),
Err(Error::InvalidOption(_))
),
"mode=255 should reject"
);
assert!(
Symbology::Maxicode
.encode("hello", &Options::default())
.is_ok(),
"default should encode in mode 4"
);
for &m in &[4u8, 5, 6] {
let opts = Options::default().with("mode", m.to_string());
assert!(
Symbology::Maxicode.encode("hello", &opts).is_ok(),
"mode={m} should encode 'hello'"
);
}
for &m in &[2u8, 3] {
let opts = Options::default().with("mode", m.to_string());
match Symbology::Maxicode.encode("hello", &opts) {
Err(Error::InvalidData(msg)) => assert!(
!msg.contains("not supported"),
"mode={m} dispatched correctly; downstream rejection \
should not mention 'not supported', got: {msg}"
),
Err(Error::InvalidOption(msg)) => assert!(
!msg.contains("not supported"),
"mode={m} surfaced wrong dispatcher arm: {msg}"
),
other => {
panic!("mode={m} 'hello' should reject via downstream parser, got {other:?}")
}
}
}
}
#[test]
fn sentinels_are_distinct() {
let s = [
SENTINEL_ECI,
SENTINEL_PAD,
SENTINEL_NS,
SENTINEL_LA,
SENTINEL_LB,
SENTINEL_SA,
SENTINEL_SB,
SENTINEL_SC,
SENTINEL_SD,
SENTINEL_SE,
];
let mut sorted = s;
sorted.sort_unstable();
for i in 1..sorted.len() {
assert_ne!(sorted[i], sorted[i - 1], "duplicate sentinel at {i}");
}
}
#[test]
fn encode_ns_run_packs_nine_digits() {
let result = encode_ns_run(b"000000001").unwrap();
assert_eq!(result[0], NS_CODEWORD);
assert_eq!(result[1..], [0, 0, 0, 0, 1]);
let result = encode_ns_run(b"000000063").unwrap();
assert_eq!(result[1..], [0, 0, 0, 0, 63]);
let result = encode_ns_run(b"000000064").unwrap();
assert_eq!(result[1..], [0, 0, 0, 1, 0]);
let result = encode_ns_run(b"000000000").unwrap();
assert_eq!(result[1..], [0, 0, 0, 0, 0]);
assert_eq!(encode_ns_run(b"12345678"), None); assert_eq!(encode_ns_run(b"1234567890"), None); assert_eq!(encode_ns_run(b"12345678A"), None); assert_eq!(encode_ns_run(b""), None); }
#[test]
fn to_bin_msb_first_width_clamped_with_overflow_guard() {
assert_eq!(to_bin(0, 0), Some(String::new()));
assert_eq!(to_bin(0, 4), Some("0000".to_string()));
assert_eq!(to_bin(15, 4), Some("1111".to_string()));
assert_eq!(
to_bin(5, 4),
Some("0101".to_string()),
"MSB-first: removing .rev() would give \"1010\""
);
assert_eq!(to_bin(7, 3), Some("111".to_string()));
assert_eq!(
to_bin(130, 8),
Some("10000010".to_string()),
"MSB-first ordering — reverse would give \"01000001\""
);
assert_eq!(to_bin(16, 4), None, "16 = 1<<4 doesn't fit in 4 bits");
assert!(to_bin(15, 4).is_some(), "15 fits in 4 bits");
assert_eq!(to_bin(u64::MAX, 63), None);
let s = to_bin(u64::MAX, 64).expect("width=64 always fits");
assert_eq!(s.len(), 64);
assert!(s.chars().all(|c| c == '1'));
let s = to_bin(0, 64).expect("width=64 always fits");
assert_eq!(s, "0".repeat(64));
for w in 0..=12 {
for v in 0..=7u64 {
if let Some(s) = to_bin(v, w) {
assert_eq!(s.len(), w, "to_bin({v}, {w}) length should be {w}");
}
}
}
}
#[test]
fn maxicode_const_table_sign_pinned() {
assert_eq!(SENTINEL_ECI, -1, "SENTINEL_ECI must remain -1");
assert_eq!(SENTINEL_PAD, -2, "SENTINEL_PAD must remain -2");
assert_eq!(SENTINEL_NS, -3, "SENTINEL_NS must remain -3");
assert_eq!(SENTINEL_LA, -4, "SENTINEL_LA (latch-A) must remain -4");
assert_eq!(SENTINEL_LB, -5, "SENTINEL_LB (latch-B) must remain -5");
assert_eq!(SENTINEL_SA, -6, "SENTINEL_SA (shift-A) must remain -6");
assert_eq!(SENTINEL_SB, -7, "SENTINEL_SB (shift-B) must remain -7");
assert_eq!(SENTINEL_SC, -8, "SENTINEL_SC (shift-C) must remain -8");
assert_eq!(SENTINEL_SD, -9, "SENTINEL_SD (shift-D) must remain -9");
assert_eq!(SENTINEL_SE, -10, "SENTINEL_SE (shift-E) must remain -10");
}
#[test]
fn encode_mode_boundary_pinned() {
let sym4 = encode_mode_4(&[b'A'; 93])
.expect("mode 4 at-boundary (93 cws) must succeed under `>`; `>=` mutant rejects");
assert_eq!(sym4.cols(), 30, "mode 4 boundary symbol must be 30 cols");
assert_eq!(sym4.rows(), 33, "mode 4 boundary symbol must be 33 rows");
let sym5 = encode_mode_5(&[b'A'; 77])
.expect("mode 5 at-boundary (77 cws) must succeed under `>`; `>=` mutant rejects");
assert_eq!(sym5.cols(), 30, "mode 5 boundary symbol must be 30 cols");
assert_eq!(sym5.rows(), 33, "mode 5 boundary symbol must be 33 rows");
let sym6 = encode_mode_6(&[b'A'; 93])
.expect("mode 6 at-boundary (93 cws) must succeed under `>`; `>=` mutant rejects");
assert_eq!(sym6.cols(), 30, "mode 6 boundary symbol must be 30 cols");
assert_eq!(sym6.rows(), 33, "mode 6 boundary symbol must be 33 rows");
let mut payload2 = b"12345\x1d840\x1d001\x1d".to_vec();
payload2.extend(std::iter::repeat_n(b'A', 84));
let sym2 = encode_mode_2(&payload2)
.expect("mode 2 at-boundary (84 sec cws) must succeed under `>`; `>=` mutant rejects");
assert_eq!(sym2.cols(), 30, "mode 2 boundary symbol must be 30 cols");
assert_eq!(sym2.rows(), 33, "mode 2 boundary symbol must be 33 rows");
let mut payload3 = b"K1A0B1\x1d124\x1d999\x1d".to_vec();
payload3.extend(std::iter::repeat_n(b'A', 84));
let sym3 = encode_mode_3(&payload3)
.expect("mode 3 at-boundary (84 sec cws) must succeed under `>`; `>=` mutant rejects");
assert_eq!(sym3.cols(), 30, "mode 3 boundary symbol must be 30 cols");
assert_eq!(sym3.rows(), 33, "mode 3 boundary symbol must be 33 rows");
}
#[test]
fn encode_secondary_a_b_state_machine_fingerprint_pinned() {
fn fp(out: &[u8]) -> (usize, u64) {
let mut s: u64 = 0;
for (i, &v) in out.iter().enumerate() {
s = s.wrapping_add(
(v as u64).wrapping_mul((i as u64).wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
(out.len(), s)
}
let cases: &[(&str, &[u8], (usize, u64))] = &[
("setA_only", b"A", FP_AB_SETA_ONLY),
("setB_solo", b"a", FP_AB_SETB_SOLO),
("setA_SB1", b"Aa", FP_AB_SETA_SB1),
("setB1_setA", b"aA", FP_AB_SETB1_SETA),
("setB2_eom", b"Aab", FP_AB_SETB2_EOM),
("setB2_mid", b"AabA", FP_AB_SETB2_MID),
("setB4_run", b"AabcdA", FP_AB_SETB4_RUN),
("SA3_arm", b"aaaaABCabcd", FP_AB_SA3_ARM),
("SA2_arm", b"aaaaABabcd", FP_AB_SA2_ARM),
];
for (tag, input, want) in cases {
let got = fp(&encode_secondary_a_b(input).unwrap_or_else(|e| {
panic!("encode_secondary_a_b({tag}) must succeed; got Err: {e:?}")
}));
eprintln!("CAP encode_secondary_a_b/{tag} -> {got:?}");
assert_eq!(got, *want, "fingerprint changed for {tag}");
}
}
const FP_AB_SETA_ONLY: (usize, u64) = (1, 2654435761);
const FP_AB_SETB_SOLO: (usize, u64) = (2, 161920581421);
const FP_AB_SETA_SB1: (usize, u64) = (3, 323841162842);
const FP_AB_SETB1_SETA: (usize, u64) = (3, 169883888704);
const FP_AB_SETB2_EOM: (usize, u64) = (4, 366312135018);
const FP_AB_SETB2_MID: (usize, u64) = (6, 992758974614);
const FP_AB_SETB4_RUN: (usize, u64) = (8, 1661676786386);
const FP_AB_SA3_ARM: (usize, u64) = (14, 3357861237665);
const FP_AB_SA2_ARM: (usize, u64) = (13, 3092417661565);
#[test]
fn encode_secondary_a_b_with_ns_state_machine_fingerprint_pinned() {
fn fp(out: &[u8]) -> (usize, u64) {
let mut s: u64 = 0;
for (i, &v) in out.iter().enumerate() {
s = s.wrapping_add(
(v as u64).wrapping_mul((i as u64).wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
(out.len(), s)
}
let cases: &[(&str, &[u8], (usize, u64))] = &[
("setA_only", b"A", FP_ABNS_SETA_ONLY),
("ns9_pure", b"123456789", FP_ABNS_NS9_PURE),
("ns9_lead3", b"123456789012", FP_ABNS_NS9_LEAD3),
("setB_then_ns9", b"abc123456789", FP_ABNS_SETB_NS9),
("setC_shift1", b"TEST\xC0", FP_ABNS_SETC_SHIFT1),
("setC_latch3", b"TEST\xC0\xC0\xC0", FP_ABNS_SETC_LATCH3),
("setC_only5", b"\xC0\xC0\xC0\xC0\xC0", FP_ABNS_SETC_ONLY5),
("setE_only3", b"\xA0\xA0\xA0", FP_ABNS_SETE_ONLY3),
("setB_solo", b"a", FP_ABNS_SETB_SOLO),
("setB2_eom", b"Aab", FP_ABNS_SETB2_EOM),
("setB2_mid", b"AabA", FP_ABNS_SETB2_MID),
("setB4_then_ns9", b"Aabcd123456789", FP_ABNS_SETB4_NS9),
("setB4_then_setC", b"Aabcd\xC0", FP_ABNS_SETB4_SETC),
];
for (tag, input, want) in cases {
let got = fp(&encode_secondary_a_b_with_ns(input).unwrap_or_else(|e| {
panic!("encode_secondary_a_b_with_ns({tag}) must succeed; got Err: {e:?}")
}));
eprintln!("CAP encode_secondary_a_b_with_ns/{tag} -> {got:?}");
assert_eq!(got, *want, "fingerprint changed for {tag}");
}
}
const FP_ABNS_SETA_ONLY: (usize, u64) = (1, 2654435761);
const FP_ABNS_NS9_PURE: (usize, u64) = (6, 1956319155857);
const FP_ABNS_NS9_LEAD3: (usize, u64) = (9, 3803806445513);
const FP_ABNS_SETB_NS9: (usize, u64) = (10, 4225861731512);
const FP_ABNS_SETC_SHIFT1: (usize, u64) = (6, 1239621500387);
const FP_ABNS_SETC_LATCH3: (usize, u64) = (10, 3734791115727);
const FP_ABNS_SETC_ONLY5: (usize, u64) = (8, 1709456630084);
const FP_ABNS_SETE_ONLY3: (usize, u64) = (5, 1672294529430);
const FP_ABNS_SETB_SOLO: (usize, u64) = (2, 161920581421);
const FP_ABNS_SETB2_EOM: (usize, u64) = (4, 366312135018);
const FP_ABNS_SETB2_MID: (usize, u64) = (6, 992758974614);
const FP_ABNS_SETB4_NS9: (usize, u64) = (12, 5499990896792);
const FP_ABNS_SETB4_SETC: (usize, u64) = (8, 1584698149317);
#[test]
fn encode_secondary_a_b_with_ns_set_a_shift_arms_reached_via_cde_back_latch() {
assert_eq!(
encode_secondary_a_b_with_ns(b"\xC0\xC0\xC0aBa").unwrap(),
vec![60, 60, 0, 0, 0, 63, 1, 59, 2, 1],
"L556 SA arm: SC-latch + LB + 'a' + SA(59) + 'B'(2) + 'a'(1)",
);
assert_eq!(
encode_secondary_a_b_with_ns(b"\xC0\xC0\xC0aBCa").unwrap(),
vec![60, 60, 0, 0, 0, 63, 1, 56, 2, 3, 1],
"L560 SA2 arm: SA2(56) + 'B'(2,L562) + 'C'(3,L563 i+1) + 'a'",
);
assert_eq!(
encode_secondary_a_b_with_ns(b"\xC0\xC0\xC0aBCDa").unwrap(),
vec![60, 60, 0, 0, 0, 63, 1, 57, 2, 3, 4, 1],
"L565 SA3 arm: SA3(57) + 'B'(2,L567) + 'C'(3,L568 i+1) + 'D'(4,L569 i+2)",
);
}
#[test]
fn encode_secondary_a_b_with_ns_setb_run_back_latch_boundaries() {
assert_eq!(
encode_secondary_a_b_with_ns(b"abcd\xA0").unwrap(),
vec![63, 1, 2, 3, 4, 62, 37],
"L616 ||→&&: set-E byte must keep next_is_cde=true (no LA)",
);
assert_eq!(
encode_secondary_a_b_with_ns(b"abcdE").unwrap(),
vec![63, 1, 2, 3, 4, 63, 5],
"L617 <→>: nseq[j]=0 < 9 true → LA(63) before 'E'(5)",
);
}
#[test]
fn seta_codeword_fs_gs_rs_controls() {
assert_eq!(seta_codeword(28), Some(28), "FS");
assert_eq!(seta_codeword(29), Some(29), "GS");
assert_eq!(seta_codeword(30), Some(30), "RS");
assert_eq!(seta_codeword(27), None, "27 below the 28..=30 arm");
assert_eq!(seta_codeword(31), None, "31 above the 28..=30 arm");
}
#[test]
fn build_grid_row_eq_rows_dropped() {
let grid = build_grid(&[0u16, 990u16]);
assert!(grid[0][0], "p=0 → (0,0) set");
let total: usize = grid.iter().flat_map(|r| r.iter()).filter(|&&b| b).count();
assert_eq!(
total, 1,
"p=990 (row==ROWS) must be dropped by `row < ROWS`; \
the <= mutant would panic before reaching here",
);
}
#[test]
fn maxicode_equivalence_notes() {
for acc in 0u8..=0x1F {
for bit in 0u8..=1 {
let shifted = acc << 1;
assert_eq!(
shifted | bit,
shifted ^ bit,
"|/^ identical when (acc<<1) has bit0==0 and bit∈{{0,1}}",
);
}
}
let pri = pack_mode_2_primary("12345", "840", "001").unwrap();
assert_eq!(pri, [2, 36, 50, 46, 53, 17, 2, 18, 7, 0]);
for p in 0usize..(ROWS * COLS) {
assert!(p % COLS < COLS, "col index always < COLS");
}
const SA2: u8 = 56;
const SA3: u8 = 57;
let alphabet: [u8; 4] = [b'A', b'B', b'a', b'b']; let mut buf = Vec::new();
for len in 1..=4usize {
let mut idx = vec![0usize; len];
loop {
buf.clear();
for &k in &idx {
buf.push(alphabet[k]);
}
let out = encode_secondary_a_b(&buf).unwrap();
assert!(
!out.contains(&SA2) && !out.contains(&SA3),
"encode_secondary_a_b should never reach SA2/SA3 arms; \
input {buf:?} produced {out:?}",
);
let mut p = len;
loop {
if p == 0 {
break;
}
p -= 1;
idx[p] += 1;
if idx[p] < alphabet.len() {
break;
}
idx[p] = 0;
}
if p == 0 && idx[0] == 0 {
break;
}
}
}
}
}