#![allow(dead_code)]
use crate::encoding::BitMatrix;
use crate::error::Error;
pub(crate) const FNC1: i16 = -1;
pub(crate) const FNC3: i16 = -2;
pub(crate) const LC: i16 = -5;
pub(crate) const LB: i16 = -6;
pub(crate) const LX: i16 = -7;
pub(crate) const LT: i16 = -8;
pub(crate) const LD: i16 = -9;
pub(crate) const UNL: i16 = -10;
pub(crate) const FNC2: i16 = -11;
pub(crate) const FNC4: i16 = -12;
pub(crate) const SFT1: i16 = -13;
pub(crate) const SFT2: i16 = -14;
pub(crate) const SFT3: i16 = -15;
pub(crate) const ECI: i16 = -16;
pub(crate) const PAD: i16 = -17;
pub(crate) const FNC1LD: i16 = -18;
pub(crate) const UNLCW: u16 = 255;
pub(crate) const MODE_A: u8 = 0;
pub(crate) const MODE_C: u8 = 1;
pub(crate) const MODE_T: u8 = 2;
pub(crate) const MODE_X: u8 = 3;
pub(crate) const MODE_D: u8 = 4;
pub(crate) const MODE_B: u8 = 5;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Metric {
pub id: &'static str,
pub rows: u16,
pub cols: u16,
pub dcol: u16,
pub dcw: u16,
pub rscw: u16,
pub rsbl: u16,
pub riso: u16,
pub risi: u16,
pub risl: u16,
}
pub(crate) const NA: u16 = 99;
#[rustfmt::skip]
pub(crate) const METRICS_NONSTYPE: [Metric; 11] = [
Metric { id: "A", rows: 16, cols: 18, dcol: 16, dcw: 10, rscw: 10, rsbl: 1, riso: 4, risi: NA, risl: 6 },
Metric { id: "B", rows: 22, cols: 22, dcol: 20, dcw: 19, rscw: 16, rsbl: 1, riso: 4, risi: NA, risl: 8 },
Metric { id: "C", rows: 28, cols: 32, dcol: 28, dcw: 44, rscw: 26, rsbl: 1, riso: 4, risi: 22, risl: 11 },
Metric { id: "D", rows: 40, cols: 42, dcol: 36, dcw: 91, rscw: 44, rsbl: 1, riso: 4, risi: 16, risl: 16 },
Metric { id: "E", rows: 52, cols: 54, dcol: 48, dcw: 182, rscw: 70, rsbl: 1, riso: 4, risi: 22, risl: 22 },
Metric { id: "F", rows: 70, cols: 76, dcol: 68, dcw: 370, rscw: 140, rsbl: 2, riso: 4, risi: 22, risl: 31 },
Metric { id: "G", rows: 104, cols: 98, dcol: 88, dcw: 732, rscw: 280, rsbl: 4, riso: 6, risi: 21, risl: 47 },
Metric { id: "H", rows: 148, cols: 134, dcol: 120, dcw: 1480, rscw: 560, rsbl: 8, riso: 6, risi: 20, risl: 69 },
Metric { id: "T-16", rows: 16, cols: 17, dcol: 16, dcw: 10, rscw: 10, rsbl: 1, riso: NA, risi: NA, risl: NA },
Metric { id: "T-32", rows: 16, cols: 33, dcol: 32, dcw: 24, rscw: 16, rsbl: 1, riso: NA, risi: NA, risl: NA },
Metric { id: "T-48", rows: 16, cols: 49, dcol: 48, dcw: 38, rscw: 22, rsbl: 1, riso: NA, risi: NA, risl: NA },
];
#[rustfmt::skip]
pub(crate) const METRICS_STYPE: [Metric; 3] = [
Metric { id: "S-10", rows: 8, cols: 11, dcol: 10, dcw: 4, rscw: 4, rsbl: 1, riso: NA, risi: NA, risl: NA },
Metric { id: "S-20", rows: 8, cols: 21, dcol: 20, dcw: 8, rscw: 8, rsbl: 1, riso: NA, risi: NA, risl: NA },
Metric { id: "S-30", rows: 8, cols: 31, dcol: 30, dcw: 12, rscw: 12, rsbl: 1, riso: NA, risi: NA, risl: NA },
];
pub(crate) const CPATMAP: [(char, &str); 10] = [
('A', "121343"),
('B', "12134343"),
('C', "12121343"),
('D', "1213434343"),
('E', "1212134343"),
('F', "1212121343"),
('G', "121213434343"),
('H', "121212134343"),
('S', "56661278"),
('T', "5666666666127878"),
];
pub(crate) fn cpatmap_for(version_first_letter: char) -> Option<&'static str> {
CPATMAP
.iter()
.find(|&&(c, _)| c == version_first_letter)
.map(|&(_, p)| p)
}
pub(crate) const BLACKDOTMAP: &[(&str, &[(u16, u16)])] = &[
("A", &[(12, 5)]),
("B", &[(16, 7)]),
("C", &[(26, 12)]),
("D", &[]),
("E", &[(26, 23)]),
("F", &[(26, 32), (70, 32), (26, 34), (70, 34)]),
("G", &[(27, 48), (69, 48)]),
(
"H",
&[(26, 70), (66, 70), (106, 70), (26, 72), (66, 72), (106, 72)],
),
("S-10", &[]),
("S-20", &[(10, 4)]),
("S-30", &[(15, 4), (15, 6)]),
("T-16", &[(8, 10)]),
("T-32", &[(16, 10), (16, 12)]),
("T-48", &[(24, 10), (24, 12), (24, 14)]),
];
pub(crate) fn blackdotmap_for(version_id: &str) -> Option<&'static [(u16, u16)]> {
BLACKDOTMAP
.iter()
.find(|&&(id, _)| id == version_id)
.map(|&(_, dots)| dots)
}
pub(crate) const STYPEVALS: [&str; 18] = [
"1",
"1010",
"1100100",
"1111101000",
"10011100010000",
"11000011010100000",
"11110100001001000000",
"100110001001011010000000",
"101111101011110000100000000",
"111011100110101100101000000000",
"1001010100000010111110010000000000",
"1011101001000011101101110100000000000",
"1110100011010100101001010001000000000000",
"10010001100001001110011100101010000000000000",
"10110101111001100010000011110100100000000000000",
"11100011010111111010100100110001101000000000000000",
"100011100001101111001001101111110000010000000000000000",
"101100011010001010111100001011101100010100000000000000000",
];
pub(crate) const RSPARAMS: [Option<(u16, u16)>; 9] = [
None,
None,
None,
None,
None,
Some((32, 37)),
None,
None,
Some((256, 301)),
];
pub(crate) const RS_GF_S: (u16, u16) = (32, 37);
pub(crate) const RS_GF_MATRIX: (u16, u16) = (256, 301);
pub(crate) fn avals_byte(b: u8) -> Option<u16> {
if b <= 128 {
Some(u16::from(b) + 1)
} else {
None
}
}
pub(crate) fn avals_digit_pair(d1: u8, d2: u8) -> Option<u16> {
if d1.is_ascii_digit() && d2.is_ascii_digit() {
let n = (d1 - b'0') * 10 + (d2 - b'0');
Some(130 + u16::from(n))
} else {
None
}
}
pub(crate) fn avals_marker(marker: i16) -> Option<u16> {
Some(match marker {
PAD => 129,
LC => 230,
LB => 231,
FNC1 => 232,
FNC2 => 233,
FNC3 => 234,
FNC4 => 235,
FNC1LD => 236,
LX => 238,
LT => 239,
_ => return None,
})
}
pub(crate) const MODE_B_MAX_LEN: usize = 1480;
pub(crate) fn encode_mode_b_run(
bytes: &[u8],
omit_prefix_if_exact: bool,
) -> Result<Vec<u16>, Error> {
if bytes.len() > MODE_B_MAX_LEN {
return Err(Error::InvalidData(format!(
"code one mode B: payload of {} bytes exceeds max {}",
bytes.len(),
MODE_B_MAX_LEN
)));
}
let p = bytes.len();
let mut cws: Vec<u16> = Vec::with_capacity(p + 2);
if !omit_prefix_if_exact {
if p < 250 {
cws.push(p as u16);
} else {
cws.push((p / 250) as u16 + 249);
cws.push((p % 250) as u16);
}
}
for &b in bytes {
cws.push(u16::from(b));
}
Ok(cws)
}
pub(crate) fn is_d(c: i16) -> bool {
(b'0' as i16..=b'9' as i16).contains(&c)
}
pub(crate) fn is_c(c: i16) -> bool {
c == SFT1
|| c == SFT2
|| c == SFT3
|| c == 32
|| is_d(c)
|| (b'A' as i16..=b'Z' as i16).contains(&c)
}
pub(crate) fn is_t(c: i16) -> bool {
c == SFT1
|| c == SFT2
|| c == SFT3
|| c == 32
|| is_d(c)
|| (b'a' as i16..=b'z' as i16).contains(&c)
}
pub(crate) fn is_x(c: i16) -> bool {
c == 13 || c == 42 || c == 62 || c == 32 || is_d(c) || (b'A' as i16..=b'Z' as i16).contains(&c)
}
pub(crate) fn is_ea(c: i16) -> bool {
c > 127
}
pub(crate) fn is_fn(c: i16) -> bool {
c < 0
}
pub(crate) fn compute_next_xterm(msg: &[i16]) -> Vec<u32> {
let n = msg.len();
let mut next = vec![0u32; n + 1];
next[n] = 9999;
for i in (0..n).rev() {
let c = msg[i];
if c == 13 || c == 42 || c == 62 {
next[i] = 0;
} else {
next[i] = next[i + 1].saturating_add(1);
}
}
for v in &mut next {
if *v > 10000 {
*v = 10000;
}
}
next
}
pub(crate) fn compute_next_non_x(msg: &[i16]) -> Vec<u32> {
let n = msg.len();
let mut next = vec![0u32; n + 1];
next[n] = 9999;
for i in (0..n).rev() {
if !is_x(msg[i]) {
next[i] = 0;
} else {
next[i] = next[i + 1].saturating_add(1);
}
}
for v in &mut next {
if *v > 10000 {
*v = 10000;
}
}
next
}
pub(crate) fn xterm_first(next_xterm: &[u32], next_non_x: &[u32], i: usize) -> bool {
next_xterm[i] < next_non_x[i]
}
#[inline]
fn ff(v: f64) -> f64 {
if (v as i64 as f64) == v {
v
} else {
f64::from(v as f32)
}
}
pub(crate) fn lookup(
msg: &[i16],
start_i: usize,
mode: u8,
next_xterm: &[u32],
next_non_x: &[u32],
) -> u8 {
let mut ac: f64 = 1.0;
let mut cc: f64 = 2.0;
let mut tc: f64 = 2.0;
let mut xc: f64 = 2.0;
let mut bc: f64 = 3.0;
if mode == MODE_A {
ac = 0.0;
cc = 1.0;
tc = 1.0;
xc = 1.0;
bc = 2.0;
}
if mode == MODE_C {
cc = 0.0;
}
if mode == MODE_T {
tc = 0.0;
}
if mode == MODE_X {
xc = 0.0;
}
if mode == MODE_B {
bc = 0.0;
}
let msglen = msg.len();
let mut k: usize = 0;
loop {
if start_i + k == msglen {
if bc <= ac.ceil() && bc <= cc.ceil() && bc <= tc.ceil() && bc <= xc.ceil() {
return MODE_B;
}
if ac <= cc.ceil() && ac <= tc.ceil() && ac <= xc.ceil() && ac <= bc.ceil() {
return MODE_A;
}
if cc.ceil() <= tc.ceil() && cc.ceil() <= xc.ceil() {
return MODE_C;
}
if tc.ceil() <= xc.ceil() {
return MODE_T;
}
return MODE_X;
}
let ch = msg[start_i + k];
if is_d(ch) {
ac = ff(ac + 0.5);
} else if is_ea(ch) {
ac = ac.ceil() + 2.0;
} else {
ac = ac.ceil() + 1.0;
}
if is_c(ch) {
cc = ff(cc + 0.6666667);
} else if is_ea(ch) {
cc = ff(cc + 2.6666667);
} else {
cc = ff(cc + 1.3333334);
}
if is_t(ch) {
tc = ff(tc + 0.6666667);
} else if is_ea(ch) {
tc = ff(tc + 2.6666667);
} else {
tc = ff(tc + 1.3333334);
}
if is_x(ch) {
xc = ff(xc + 0.6666667);
} else if is_ea(ch) {
xc = ff(xc + 4.3333334);
} else {
xc = ff(xc + 3.3333334);
}
if is_fn(ch) {
bc += 3.0;
} else {
bc += 1.0;
}
if k >= 3 {
if (bc + 1.0) <= ac.ceil()
&& (bc + 1.0) <= cc.ceil()
&& (bc + 1.0) <= tc.ceil()
&& (bc + 1.0) <= xc.ceil()
{
return MODE_B;
}
if (ac + 1.0) <= cc.ceil()
&& (ac + 1.0) <= tc.ceil()
&& (ac + 1.0) <= xc.ceil()
&& (ac + 1.0) <= bc.ceil()
{
return MODE_A;
}
if (tc.ceil() + 1.0) <= ac.ceil()
&& (tc.ceil() + 1.0) <= cc.ceil()
&& (tc.ceil() + 1.0) <= xc.ceil()
&& (tc.ceil() + 1.0) <= bc.ceil()
{
return MODE_T;
}
if (cc.ceil() + 1.0) <= ac.ceil() && (cc.ceil() + 1.0) <= tc.ceil() {
if cc.ceil() < xc.ceil() {
return MODE_C;
}
#[allow(clippy::float_cmp)]
if cc == xc {
if xterm_first(next_xterm, next_non_x, start_i + k + 1) {
return MODE_X;
} else {
return MODE_C;
}
}
}
if (xc.ceil() + 1.0) <= ac.ceil()
&& (xc.ceil() + 1.0) <= cc.ceil()
&& (xc.ceil() + 1.0) <= tc.ceil()
&& (xc.ceil() + 1.0) <= bc.ceil()
{
return MODE_X;
}
}
k += 1;
}
}
pub(crate) fn cnvals_lookup(c: i16) -> Option<u8> {
match c {
SFT1 => Some(0),
SFT2 => Some(1),
SFT3 => Some(2),
32 => Some(3),
48..=57 => Some((c - 44) as u8), 65..=90 => Some((c - 51) as u8), _ => None,
}
}
pub(crate) fn tnvals_lookup(c: i16) -> Option<u8> {
match c {
SFT1 => Some(0),
SFT2 => Some(1),
SFT3 => Some(2),
32 => Some(3),
48..=57 => Some((c - 44) as u8), 97..=122 => Some((c - 83) as u8), _ => None,
}
}
pub(crate) fn xvals_lookup(c: i16) -> Option<u8> {
match c {
13 => Some(0),
42 => Some(1),
62 => Some(2),
32 => Some(3),
48..=57 => Some((c - 44) as u8), 65..=90 => Some((c - 51) as u8), _ => None,
}
}
pub(crate) fn ctx_base_value(mode: u8, c: i16) -> Option<u8> {
match mode {
MODE_C => cnvals_lookup(c),
MODE_T => tnvals_lookup(c),
MODE_X => xvals_lookup(c),
_ => None,
}
}
pub(crate) fn ctxvals_to_cws(values: &[u8]) -> Vec<u16> {
debug_assert_eq!(
values.len() % 3,
0,
"ctxvals_to_cws expects a multiple of 3 values, got {}",
values.len()
);
let mut cws: Vec<u16> = Vec::with_capacity(values.len() / 3 * 2);
for chunk in values.chunks_exact(3) {
let packed: u32 =
u32::from(chunk[0]) * 1600 + u32::from(chunk[1]) * 40 + u32::from(chunk[2]) + 1;
cws.push((packed / 256) as u16);
cws.push((packed % 256) as u16);
}
cws
}
#[derive(Debug, Default)]
struct CtxBuf {
vals: Vec<u8>,
consumed_chars: Vec<usize>,
}
pub(crate) fn encode_ctx_run(ctx: &mut Ctx) -> Result<(), Error> {
debug_assert!(matches!(ctx.mode, MODE_C | MODE_T | MODE_X));
let mode_at_start = ctx.mode;
let mut buf = CtxBuf::default();
loop {
if ctx.at_end() {
break;
}
if buf.vals.len() % 3 == 0 {
let numd_i = ctx.numd[ctx.i];
if numd_i >= 12 {
return Err(Error::InvalidData(
"code one Mode CTX: digit-run >= 12 (Mode D entry) deferred to Stage 7"
.to_string(),
));
}
if numd_i >= 8 && ctx.i + numd_i == ctx.msg.len() {
return Err(Error::InvalidData(
"code one Mode CTX: digit-run >= 8 at EOM (Mode D entry) deferred to Stage 7"
.to_string(),
));
}
if ctx.mode == MODE_X && xvals_lookup(ctx.msg[ctx.i]).is_none() {
return Err(Error::InvalidData(
"code one Mode X: out-of-set char unlatch deferred to Stage 6b".to_string(),
));
}
if ctx.mode != MODE_X {
let newmode = lookup(&ctx.msg, ctx.i, ctx.mode, &ctx.next_xterm, &ctx.next_non_x);
if newmode != ctx.mode {
if newmode != MODE_A {
return Err(Error::InvalidData(format!(
"code one Mode CTX: lookup wants switch to mode {newmode}, \
only back-to-A is supported in Stage 6"
)));
}
break;
}
}
}
let c = ctx.msg[ctx.i];
let v = ctx_base_value(ctx.mode, c).ok_or_else(|| {
Error::InvalidData(format!(
"code one Mode CTX: char 0x{c:04x} requires shift-extended cvals \
(Stage 6b); base-set chars only in Stage 6"
))
})?;
buf.vals.push(v);
buf.consumed_chars.push(1);
ctx.i += 1;
}
if ctx.mode != mode_at_start {
unreachable!("CTX run shouldn't mutate ctx.mode internally");
}
let mut p = buf.vals.len();
while p % 3 != 0 {
p -= 1;
let cnt = buf.consumed_chars.pop().expect("partial triplet rewind");
ctx.i -= cnt;
buf.vals.pop();
}
let packed = ctxvals_to_cws(&buf.vals);
ctx.cws.extend_from_slice(&packed);
ctx.cws.push(UNLCW);
ctx.mode = MODE_A;
if !ctx.at_end() {
if ctx.numd[ctx.i] >= 2 {
let d1 = ctx.msg[ctx.i] as u8;
let d2 = ctx.msg[ctx.i + 1] as u8;
let cw = avals_digit_pair(d1, d2).ok_or_else(|| {
Error::InvalidData("code one CTX cleanup: digit-pair lookup failed".to_string())
})?;
ctx.cws.push(cw);
ctx.i += 2;
} else {
let b = ctx.msg[ctx.i];
if b < 0 {
return Err(Error::InvalidData(format!(
"code one CTX cleanup: trailing marker {b} handling deferred to Stage 7"
)));
}
let cw = avals_byte(b as u8).ok_or_else(|| {
Error::InvalidData("code one CTX cleanup: high-byte handling Stage 7".to_string())
})?;
ctx.cws.push(cw);
ctx.i += 1;
}
}
Ok(())
}
pub(crate) fn pick_symbol_size(cws_len: usize) -> Result<Metric, Error> {
for m in METRICS_NONSTYPE.iter() {
if usize::from(m.dcw) >= cws_len {
return Ok(*m);
}
}
Err(Error::InvalidData(format!(
"code one: cws of length {cws_len} exceeds the largest symbol (H, dcws=1480)"
)))
}
struct Gf256Tables {
alog: [u16; 256],
log: [u16; 256],
}
impl Gf256Tables {
fn new() -> Self {
let gf: u32 = 256;
let poly: u32 = 301;
let mut alog = [0u16; 256];
let mut log = [0u16; 256];
let mut x: u32 = 1;
for slot in alog.iter_mut().take((gf - 1) as usize) {
*slot = x as u16;
x *= 2;
if x >= gf {
x ^= poly;
}
}
for (i, &a) in alog.iter().enumerate().take((gf - 1) as usize).skip(1) {
log[a as usize] = i as u16;
}
Self { alog, log }
}
fn mul(&self, a: u16, b: u16) -> u16 {
if a == 0 || b == 0 {
return 0;
}
let l = (u32::from(self.log[a as usize]) + u32::from(self.log[b as usize])) % 255;
self.alog[l as usize]
}
}
static GF256: std::sync::OnceLock<Gf256Tables> = std::sync::OnceLock::new();
fn gf256() -> &'static Gf256Tables {
GF256.get_or_init(Gf256Tables::new)
}
pub(crate) fn gf256_gen_coeffs(ecpb: usize) -> Vec<u16> {
let gf = gf256();
let mut coeffs: Vec<u16> = vec![0u16; ecpb + 1];
coeffs[0] = 1;
for i in 0..ecpb {
coeffs[i + 1] = coeffs[i];
let alog_i = gf.alog[i];
for j in (1..=i).rev() {
let prod = gf.mul(coeffs[j], alog_i);
coeffs[j] = prod ^ coeffs[j - 1];
}
coeffs[0] = gf.mul(coeffs[0], alog_i);
}
coeffs.truncate(ecpb);
coeffs
}
pub(crate) fn gf256_rs_block_ecc(data: &[u16], coeffs: &[u16]) -> Vec<u16> {
let gf = gf256();
let nc = coeffs.len();
let mut lfsr: Vec<u16> = vec![0u16; nc];
for &d in data {
let val = d ^ lfsr[0];
for j in 0..nc - 1 {
let prod = gf.mul(coeffs[nc - j - 1], val);
lfsr[j] = lfsr[j + 1] ^ prod;
}
lfsr[nc - 1] = gf.mul(coeffs[0], val);
}
lfsr
}
pub(crate) fn pad_cws_matrix(cws: &mut Vec<u16>, dcws: usize) {
while cws.len() < dcws {
cws.push(129);
}
}
pub(crate) fn apply_reed_solomon(cws: &mut Vec<u16>, metric: &Metric) -> Result<(), Error> {
let dcws = usize::from(metric.dcw);
let rscw = usize::from(metric.rscw);
let rsbl = usize::from(metric.rsbl);
if cws.len() != dcws {
return Err(Error::InvalidData(format!(
"code one RS: cws.len()={} but dcws={dcws}",
cws.len()
)));
}
let dcpb = dcws / rsbl;
let ecpb = rscw / rsbl;
let coeffs = gf256_gen_coeffs(ecpb);
let mut ecbs: Vec<Vec<u16>> = Vec::with_capacity(rsbl);
for blk in 0..rsbl {
let mut data: Vec<u16> = Vec::with_capacity(dcpb);
for k in 0..dcpb {
data.push(cws[k * rsbl + blk]);
}
ecbs.push(gf256_rs_block_ecc(&data, &coeffs));
}
cws.reserve(rscw);
for i in 0..rscw {
let blk = i % rsbl;
let pos = i / rsbl;
cws.push(ecbs[blk][pos]);
}
Ok(())
}
pub(crate) fn encode_with_ecc(input: &[u8]) -> Result<(Vec<u16>, Metric), Error> {
let mut cws = encode_message(input)?;
let metric = pick_symbol_size(cws.len())?;
pad_cws_matrix(&mut cws, usize::from(metric.dcw));
apply_reed_solomon(&mut cws, &metric)?;
Ok((cws, metric))
}
const SENTINEL: i8 = -1;
fn cw_nibbles_8(cw: u16) -> ([u8; 4], [u8; 4]) {
let v = cw as u8;
let mut top = [0u8; 4];
let mut bot = [0u8; 4];
for i in 0..4 {
top[i] = (v >> (7 - i)) & 1;
bot[i] = (v >> (3 - i)) & 1;
}
(top, bot)
}
pub(crate) fn cws_to_mmat(cws: &[u16], metric: &Metric) -> Vec<u8> {
let total = usize::from(metric.dcw) + usize::from(metric.rscw);
debug_assert_eq!(cws.len(), total, "cws_to_mmat: cws.len() != dcws+rscw");
let dcol = usize::from(metric.dcol);
let nibbles_per_cw = 8usize;
let mut mmat: Vec<u8> = vec![0u8; total * nibbles_per_cw];
let mut r: usize = 0;
let mut c: usize = 0;
for &cw in cws {
let (top, bot) = cw_nibbles_8(cw);
mmat[r * dcol + c..r * dcol + c + 4].copy_from_slice(&top);
mmat[(r + 1) * dcol + c..(r + 1) * dcol + c + 4].copy_from_slice(&bot);
c += 4;
if c == dcol {
c = 0;
r += 2;
}
}
mmat
}
fn artifact_row(art_index: u8, cols: usize) -> Vec<i8> {
let mut row: Vec<i8> = Vec::with_capacity(cols);
let half = (cols - 1) / 2;
match art_index {
0 => row.extend(std::iter::repeat_n(0i8, cols)), 1 => row.extend(std::iter::repeat_n(1i8, cols)), 2 => {
row.push(0);
row.extend(std::iter::repeat_n(1i8, cols - 2));
row.push(0);
}
3 => {
row.push(0);
row.push(1);
row.extend(std::iter::repeat_n(0i8, cols - 4));
row.push(1);
row.push(0);
}
4 => {
row.extend(std::iter::repeat_n(SENTINEL, half));
row.push(1);
row.extend(std::iter::repeat_n(SENTINEL, half));
}
5 => {
row.extend(std::iter::repeat_n(SENTINEL, half));
row.push(0);
row.extend(std::iter::repeat_n(SENTINEL, half));
}
6 => {
row.push(1);
row.extend(std::iter::repeat_n(0i8, cols - 2));
row.push(1);
}
7 => {
row.push(1);
row.push(0);
row.extend(std::iter::repeat_n(1i8, cols - 4));
row.push(0);
row.push(1);
}
_ => row.extend(std::iter::repeat_n(0i8, cols)),
}
debug_assert_eq!(row.len(), cols);
row
}
pub(crate) fn compose_pixs(mmat: &[u8], metric: &Metric) -> Result<Vec<u8>, Error> {
let rows = usize::from(metric.rows);
let cols = usize::from(metric.cols);
let mut pixs: Vec<i8> = vec![SENTINEL; rows * cols];
let vers_letter =
metric.id.chars().next().ok_or_else(|| {
Error::InvalidData(format!("code one: empty metric id {:?}", metric.id))
})?;
let cpat = cpatmap_for(vers_letter).ok_or_else(|| {
Error::InvalidData(format!(
"code one: no CPATMAP entry for version {vers_letter:?}"
))
})?;
let band_start_row = (rows - cpat.len()) / 2;
let band_start_off = band_start_row * cols;
let mut band: Vec<i8> = Vec::with_capacity(cpat.len() * cols);
for ch in cpat.bytes() {
let art_idx = ch - b'1'; band.extend(artifact_row(art_idx, cols));
}
pixs[band_start_off..band_start_off + band.len()].copy_from_slice(&band);
let risl = usize::from(metric.risl);
let risi = usize::from(metric.risi);
let riso = usize::from(metric.riso);
if risl != usize::from(NA) {
for i in 0..risl {
let mut j = riso;
while j < cols {
let bit2 = if i % 12 == 0 { 1i8 } else { 0i8 };
let off = i * cols + j;
if off + 1 < pixs.len() {
pixs[off] = 1;
pixs[off + 1] = bit2;
}
if i != risl - 1 {
let mr = rows - i - 1;
let mc = cols - j - 2;
let moff = mr * cols + mc;
if moff + 1 < pixs.len() {
pixs[moff] = 1;
pixs[moff + 1] = bit2;
}
}
if risi == usize::from(NA) {
break;
}
j += risi;
}
}
}
if let Some(dots) = blackdotmap_for(metric.id) {
for &(c, r) in dots {
let off = usize::from(r) * cols + usize::from(c);
if off < pixs.len() {
pixs[off] = 1;
}
}
}
let mut mmat_idx = 0;
for cell in pixs.iter_mut() {
if *cell == SENTINEL {
if mmat_idx >= mmat.len() {
return Err(Error::InvalidData(format!(
"code one placement: pixs needs more mmat cells than available \
({}); metric={}",
mmat.len(),
metric.id
)));
}
*cell = mmat[mmat_idx] as i8;
mmat_idx += 1;
}
}
Ok(pixs.into_iter().map(|c| c as u8).collect())
}
pub(crate) fn encode_to_bit_matrix(input: &[u8]) -> Result<BitMatrix, Error> {
let (cws, metric) = encode_with_ecc(input)?;
let mmat = cws_to_mmat(&cws, &metric);
let pixs = compose_pixs(&mmat, &metric)?;
let cols = usize::from(metric.cols);
let rows = usize::from(metric.rows);
let mut bm = BitMatrix::new(cols, rows);
for r in 0..rows {
for c in 0..cols {
if pixs[r * cols + c] != 0 {
bm.set(c, r, true);
}
}
}
Ok(bm)
}
pub(crate) fn append_dbits(dbitsbuf: &mut Vec<u8>, dbitslen: &mut usize, val: u32, n_bits: usize) {
for shift in (0..n_bits).rev() {
let bit = ((val >> shift) & 1) as u8;
dbitsbuf.push(bit);
*dbitslen += 1;
}
}
pub(crate) fn flush_dbits_to_cws(dbitsbuf: &[u8], dbitslen: usize) -> Vec<u16> {
let mut bytes = Vec::with_capacity(dbitslen.div_ceil(8));
let mut idx = 0;
while idx + 8 <= dbitslen {
let mut b: u16 = 0;
for k in 0..8 {
b = b * 2 + u16::from(dbitsbuf[idx + k]);
}
bytes.push(b);
idx += 8;
}
bytes
}
fn numremcws_table() -> &'static [u16; 1480] {
use std::sync::OnceLock;
static TABLE: OnceLock<[u16; 1480]> = OnceLock::new();
TABLE.get_or_init(|| {
let mut t = [10000_u16; 1480];
for m in METRICS_NONSTYPE.iter() {
if m.id.len() == 1 {
let dcws = m.dcw as usize;
if (1..=1480).contains(&dcws) {
t[dcws - 1] = 1;
}
}
}
for i in (0..1479).rev() {
if t[i] != 1 {
t[i] = t[i + 1] + 1;
}
}
t
})
}
pub(crate) fn getnumremcws(j: usize) -> Option<u16> {
let table = numremcws_table();
if j >= table.len() {
return None;
}
Some(table[j])
}
#[allow(dead_code)]
pub(crate) const MODE_D_TERMINATOR_BITS: u32 = 0b111111;
pub(crate) fn encode_d_step(ctx: &mut Ctx) -> Result<(), Error> {
let msglen = ctx.msg.len();
let j_start = ctx.cws.len();
loop {
let i = ctx.i;
let numd_i = ctx.numd.get(i).copied().unwrap_or(0);
if numd_i >= 3 {
let d0 = (ctx.msg[i] & 0xff) as u32 - 48;
let d1 = (ctx.msg[i + 1] & 0xff) as u32 - 48;
let d2 = (ctx.msg[i + 2] & 0xff) as u32 - 48;
let val = d0 * 100 + d1 * 10 + d2 + 1;
append_dbits(&mut ctx.dbitsbuf, &mut ctx.dbitslen, val, 10);
ctx.i += 3;
continue;
}
let drem = (8 - ctx.dbitslen % 8) % 8;
let dbits_bytes = ctx.dbitslen / 8;
let remcws = getnumremcws(j_start + dbits_bytes).ok_or_else(|| {
Error::InvalidData(format!(
"code one Mode D: payload exceeds 1480-codeword cap (j={})",
j_start + dbits_bytes
))
})?;
let remcws_check = if dbits_bytes >= 1 {
getnumremcws(j_start + dbits_bytes - 1).ok_or_else(|| {
Error::InvalidData("code one Mode D: remcws_check overflow".to_string())
})?
} else {
1
};
let cond_a = (remcws_check == 1) && (drem == 0);
let cond_b = (remcws == 1) && (drem != 0);
if i == msglen && (cond_a || cond_b) {
if drem == 4 || drem == 6 {
append_dbits(&mut ctx.dbitsbuf, &mut ctx.dbitslen, 0b1111, 4);
}
if drem == 2 || drem == 6 {
append_dbits(&mut ctx.dbitsbuf, &mut ctx.dbitslen, 0b01, 2);
}
break;
}
let pos_one_left = (i == msglen.wrapping_sub(1)) && numd_i == 1;
let pos_two_left = msglen >= 2 && (i == msglen - 2) && numd_i == 2;
if (pos_one_left || pos_two_left) && remcws == 1 && drem == 0 {
break;
}
let skip_terminator =
(i == msglen.wrapping_sub(1)) && numd_i == 1 && remcws == 1 && (drem == 4 || drem == 6);
if !skip_terminator {
append_dbits(&mut ctx.dbitsbuf, &mut ctx.dbitslen, 0b111111, 6);
}
let drem2 = (8 - ctx.dbitslen % 8) % 8;
let mut drem3 = drem2;
if drem3 == 4 || drem3 == 6 {
if numd_i >= 1 {
let val = (ctx.msg[i] & 0xff) as u32 - 48 + 1;
append_dbits(&mut ctx.dbitsbuf, &mut ctx.dbitslen, val, 4);
ctx.i += 1;
} else {
append_dbits(&mut ctx.dbitsbuf, &mut ctx.dbitslen, 0b1111, 4);
}
drem3 -= 4;
}
if drem3 == 2 {
append_dbits(&mut ctx.dbitsbuf, &mut ctx.dbitslen, 0b01, 2);
}
break;
}
let bytes = flush_dbits_to_cws(&ctx.dbitsbuf, ctx.dbitslen);
ctx.cws.extend_from_slice(&bytes);
ctx.dbitsbuf.clear();
ctx.dbitslen = 0;
ctx.mode = MODE_A;
Ok(())
}
pub(crate) fn encode_message(input: &[u8]) -> Result<Vec<u16>, Error> {
let mut ctx = Ctx::from_bytes(input);
while !ctx.at_end() {
match ctx.mode {
MODE_A => encode_a_step(&mut ctx)?,
MODE_C | MODE_T | MODE_X => encode_ctx_run(&mut ctx)?,
MODE_D => encode_d_step(&mut ctx)?,
MODE_B => encode_b_step(&mut ctx)?,
_ => {
return Err(Error::InvalidData(format!(
"code one internal: unknown mode {}",
ctx.mode
)));
}
}
}
if matches!(ctx.mode, MODE_C | MODE_T | MODE_X) {
ctx.cws.push(UNLCW);
ctx.mode = MODE_A;
}
Ok(ctx.cws)
}
pub(crate) fn compute_numd(msg: &[i16]) -> Vec<usize> {
let mut numd = vec![0usize; msg.len() + 1];
for i in (0..msg.len()).rev() {
let c = msg[i];
if (b'0' as i16..=b'9' as i16).contains(&c) {
numd[i] = numd[i + 1] + 1;
} else {
numd[i] = 0;
}
}
numd
}
#[derive(Debug, Clone)]
pub(crate) struct Ctx {
pub msg: Vec<i16>,
pub i: usize,
pub cws: Vec<u16>,
pub mode: u8,
pub numd: Vec<usize>,
pub next_xterm: Vec<u32>,
pub next_non_x: Vec<u32>,
pub dbitsbuf: Vec<u8>,
pub dbitslen: usize,
}
impl Ctx {
pub(crate) fn from_bytes(input: &[u8]) -> Self {
let msg: Vec<i16> = input.iter().map(|&b| i16::from(b)).collect();
Self::from_markers(msg)
}
pub(crate) fn from_markers(msg: Vec<i16>) -> Self {
let numd = compute_numd(&msg);
let next_xterm = compute_next_xterm(&msg);
let next_non_x = compute_next_non_x(&msg);
Ctx {
msg,
i: 0,
cws: Vec::new(),
mode: MODE_A,
numd,
next_xterm,
next_non_x,
dbitsbuf: Vec::new(),
dbitslen: 0,
}
}
fn at_end(&self) -> bool {
self.i >= self.msg.len()
}
}
pub(crate) fn encode_a_step(ctx: &mut Ctx) -> Result<(), Error> {
if ctx.at_end() {
return Ok(());
}
debug_assert_eq!(ctx.mode, MODE_A);
let i = ctx.i;
let msglen = ctx.msg.len();
let numd_i = ctx.numd[i];
if numd_i >= 21 || (numd_i >= 13 && i + numd_i == msglen) {
ctx.dbitsbuf.clear();
ctx.dbitslen = 0;
append_dbits(&mut ctx.dbitsbuf, &mut ctx.dbitslen, 0b1111, 4);
ctx.mode = MODE_D;
return Ok(());
}
if numd_i >= 2 {
let d1 = ctx.msg[i] as u8;
let d2 = ctx.msg[i + 1] as u8;
let cw = avals_digit_pair(d1, d2).ok_or_else(|| {
Error::InvalidData(format!(
"code one Mode A internal: digit-pair lookup failed for ({d1}, {d2})"
))
})?;
ctx.cws.push(cw);
ctx.i += 2;
return Ok(());
}
if ctx.msg[i] == FNC1 {
return Err(Error::InvalidData(
"code one Mode A: FNC1 leader handling deferred to Stage 6+".to_string(),
));
}
let newmode = lookup(&ctx.msg, ctx.i, ctx.mode, &ctx.next_xterm, &ctx.next_non_x);
if newmode != ctx.mode {
let latch_marker = match newmode {
MODE_C => LC,
MODE_T => LT,
MODE_X => LX,
MODE_D => LD,
MODE_B => LB,
_ => {
return Err(Error::InvalidData(format!(
"code one Mode A: lookup returned unexpected mode {newmode}"
)));
}
};
if newmode == MODE_D {
ctx.dbitsbuf.clear();
ctx.dbitslen = 0;
append_dbits(&mut ctx.dbitsbuf, &mut ctx.dbitslen, 0b1111, 4);
ctx.mode = MODE_D;
return Ok(());
}
let latch_cw = avals_marker(latch_marker).ok_or_else(|| {
Error::InvalidData(format!(
"code one Mode A internal: avals_marker({latch_marker}) failed"
))
})?;
ctx.cws.push(latch_cw);
ctx.mode = newmode;
return Ok(());
}
let byte = ctx.msg[i];
if byte < 0 {
return Err(Error::InvalidData(format!(
"code one Mode A: marker byte {byte} handling deferred to Stage 6+"
)));
}
let cw = avals_byte(byte as u8).ok_or_else(|| {
Error::InvalidData(format!(
"code one Mode A: byte 0x{byte:02x} > 128 — use FNC4 (Stage 6+) or Mode B"
))
})?;
ctx.cws.push(cw);
ctx.i += 1;
Ok(())
}
pub(crate) fn encode_b_step(ctx: &mut Ctx) -> Result<(), Error> {
debug_assert_eq!(ctx.mode, MODE_B);
if ctx.at_end() {
return Ok(());
}
let mut bytes: Vec<u8> = Vec::with_capacity(ctx.msg.len() - ctx.i);
for &cell in &ctx.msg[ctx.i..] {
if !(0..=255).contains(&cell) {
return Err(Error::InvalidData(format!(
"code one Mode B: marker / ECI cell {cell} cannot be encoded as a raw byte \
(FNC and ECI paths inside Mode B are extension paths — see PORT_STATUS)"
)));
}
bytes.push(cell as u8);
}
let cws = encode_mode_b_run(&bytes, false)?;
ctx.cws.extend(cws);
ctx.i = ctx.msg.len();
Ok(())
}
pub(crate) fn encode_mode_a_only(input: &[u8]) -> Result<Vec<u16>, Error> {
let mut ctx = Ctx::from_bytes(input);
while !ctx.at_end() {
encode_a_step(&mut ctx)?;
if ctx.mode != MODE_A {
return Err(Error::InvalidData(
"code one Mode A-only: encoder switched out of Mode A — caller \
should use the full mode-selector (Stage 5+)"
.to_string(),
));
}
}
Ok(ctx.cws)
}
pub fn encode(input: &[u8]) -> Result<BitMatrix, Error> {
encode_to_bit_matrix(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gf256_gen_coeffs_small_polynomials() {
assert_eq!(gf256_gen_coeffs(0), Vec::<u16>::new());
assert_eq!(gf256_gen_coeffs(1), vec![1u16]);
assert_eq!(gf256_gen_coeffs(2), vec![2u16, 3]);
assert_eq!(gf256_gen_coeffs(10).len(), 10);
assert_eq!(gf256_gen_coeffs(44).len(), 44);
assert_eq!(gf256_gen_coeffs(5), gf256_gen_coeffs(5));
}
#[test]
fn gf256_mul_zero_identity_commutativity_and_poly_anchor() {
let gf = gf256();
for &x in &[1u16, 5, 42, 128, 255] {
assert_eq!(
gf.mul(0, x),
0,
"mul(0, {x}) must be 0 (kills `|| ` → `&&`)"
);
assert_eq!(
gf.mul(x, 0),
0,
"mul({x}, 0) must be 0 (kills `|| ` → `&&` on second arg)"
);
}
assert_eq!(gf.mul(0, 0), 0);
for &x in &[1u16, 2, 5, 17, 99, 128, 255] {
assert_eq!(gf.mul(1, x), x, "mul(1, {x}) must equal {x}");
assert_eq!(gf.mul(x, 1), x, "mul({x}, 1) must equal {x}");
}
let pairs: &[(u16, u16)] = &[(2, 3), (5, 7), (17, 42), (128, 64), (200, 31), (3, 255)];
for &(a, b) in pairs {
assert_eq!(
gf.mul(a, b),
gf.mul(b, a),
"mul not commutative at ({a}, {b})"
);
}
assert_eq!(gf.mul(2, 2), 4, "alog[1+1]=alog[2]=4");
assert_eq!(gf.mul(2, 4), 8, "alog[1+2]=alog[3]=8");
assert_eq!(gf.mul(2, 64), 128, "alog[1+6]=alog[7]=128");
assert_eq!(
gf.mul(2, 128),
45,
"mul(2, 128) wraps via poly 301: alog[8] = 256 ^ 301 = 45 \
(kills `% 255` mutants and antilog table drift)"
);
assert_eq!(gf.mul(128, 2), 45);
}
#[test]
fn gf256_rs_block_ecc_base_cases() {
assert_eq!(
gf256_rs_block_ecc(&[], &[1, 1]),
vec![0u16, 0],
"empty data → zero lfsr"
);
assert_eq!(
gf256_rs_block_ecc(&[], &[1, 1, 1]),
vec![0u16, 0, 0],
"empty data, longer lfsr"
);
assert_eq!(
gf256_rs_block_ecc(&[0, 0, 0], &[1, 1, 1]),
vec![0u16, 0, 0],
"zero data → zero lfsr"
);
assert_eq!(
gf256_rs_block_ecc(&[1], &[1, 1]),
vec![1u16, 1],
"one d=1, unit coeffs → lfsr [1, 1]"
);
assert_eq!(
gf256_rs_block_ecc(&[1, 1], &[1, 1]),
vec![1u16, 0],
"[1,1] data, unit coeffs → lfsr [1, 0]"
);
}
#[test]
fn cw_nibbles_8_msb_split_with_u8_truncation() {
let (top, bot) = cw_nibbles_8(0xA5);
assert_eq!(top, [1, 0, 1, 0]);
assert_eq!(bot, [0, 1, 0, 1]);
assert_eq!(cw_nibbles_8(0), ([0u8; 4], [0u8; 4]));
assert_eq!(cw_nibbles_8(255), ([1u8; 4], [1u8; 4]));
let (top, bot) = cw_nibbles_8(128);
assert_eq!(top, [1, 0, 0, 0]);
assert_eq!(bot, [0, 0, 0, 0]);
let (top, bot) = cw_nibbles_8(1);
assert_eq!(top, [0, 0, 0, 0]);
assert_eq!(bot, [0, 0, 0, 1]);
let (top, bot) = cw_nibbles_8(16);
assert_eq!(top, [0, 0, 0, 1]);
assert_eq!(bot, [0, 0, 0, 0]);
assert_eq!(cw_nibbles_8(256), ([0u8; 4], [0u8; 4]));
let (top, bot) = cw_nibbles_8(257);
assert_eq!(top, [0, 0, 0, 0]);
assert_eq!(bot, [0, 0, 0, 1]);
}
#[test]
fn ff_preserves_integer_path_and_f32_truncates_fractional_path() {
assert_eq!(ff(0.0), 0.0);
assert_eq!(ff(1.0), 1.0);
assert_eq!(ff(-3.0), -3.0);
assert_eq!(ff(1234567.0), 1234567.0);
let v = 16_777_217.0_f64;
assert_eq!(v as i64 as f64, v, "precondition: 16777217 is i64-exact");
assert_ne!(
v as f32 as f64, v,
"precondition: 16777217 loses precision via f32 round-trip"
);
assert_eq!(
ff(v),
v,
"ff(16777217.0) must take integer arm and return unmodified \
(kills `==` → `!=` mutant: the swap would round to 16777216.0)"
);
let frac = 0.6666667_f64;
assert_ne!(
frac as i64 as f64, frac,
"precondition: 0.6666667 not exact int"
);
assert_eq!(
ff(frac),
frac as f32 as f64,
"fractional arm must round-trip through f32"
);
let f = 1.3333334_f64;
assert_eq!(ff(f), f as f32 as f64);
let f = 4.3333334_f64;
assert_eq!(ff(f), f as f32 as f64);
}
#[test]
fn pick_symbol_size_walks_metrics_with_inclusive_boundary() {
assert_eq!(pick_symbol_size(0).unwrap().id, "A");
assert_eq!(pick_symbol_size(1).unwrap().id, "A");
assert_eq!(pick_symbol_size(10).unwrap().id, "A", "exact match `>=`");
assert_eq!(pick_symbol_size(11).unwrap().id, "B");
assert_eq!(pick_symbol_size(19).unwrap().id, "B");
assert_eq!(pick_symbol_size(20).unwrap().id, "C");
assert_eq!(pick_symbol_size(45).unwrap().id, "D");
assert_eq!(pick_symbol_size(1480).unwrap().id, "H");
let err = pick_symbol_size(1481).unwrap_err();
assert!(
matches!(err, Error::InvalidData(ref m) if m.contains("exceeds the largest symbol")),
"got {err:?}"
);
}
#[test]
fn pad_cws_matrix_extends_with_129_to_dcws() {
let mut cws: Vec<u16> = Vec::new();
pad_cws_matrix(&mut cws, 3);
assert_eq!(cws, vec![129u16, 129, 129]);
let mut cws = vec![1u16, 2];
pad_cws_matrix(&mut cws, 5);
assert_eq!(cws, vec![1u16, 2, 129, 129, 129]);
let mut cws = vec![1u16, 2, 3];
pad_cws_matrix(&mut cws, 3);
assert_eq!(cws, vec![1u16, 2, 3]);
let mut cws = vec![1u16, 2, 3];
pad_cws_matrix(&mut cws, 2);
assert_eq!(cws, vec![1u16, 2, 3]);
let mut cws: Vec<u16> = Vec::new();
pad_cws_matrix(&mut cws, 0);
assert!(
cws.is_empty(),
"pad_cws_matrix(empty, dcws=0) must be a no-op (no spurious padding inserted); got len={}",
cws.len()
);
}
#[test]
fn getnumremcws_boundary_and_symbol_edges() {
assert_eq!(
getnumremcws(0),
Some(10),
"pos 0 → 10 remaining (Version A)"
);
assert_eq!(getnumremcws(9), Some(1), "pos 9 → last cw of Version A");
assert_eq!(
getnumremcws(10),
Some(9),
"pos 10 → 9 remaining in Version B"
);
assert_eq!(getnumremcws(18), Some(1), "pos 18 → last cw of Version B");
assert_eq!(getnumremcws(43), Some(1), "pos 43 → last cw of Version C");
assert_eq!(
getnumremcws(1479),
Some(1),
"pos 1479 → last cw of Version H"
);
assert_eq!(getnumremcws(1480), None, "pos 1480 past largest → None");
assert_eq!(getnumremcws(usize::MAX), None, "u-MAX → None");
}
#[test]
fn append_dbits_and_flush_dbits_to_cws() {
let mut buf: Vec<u8> = Vec::new();
let mut len: usize = 0;
append_dbits(&mut buf, &mut len, 0b1010, 4);
assert_eq!(buf, vec![1u8, 0, 1, 0]);
assert_eq!(len, 4);
append_dbits(&mut buf, &mut len, 0, 3);
assert_eq!(buf, vec![1u8, 0, 1, 0, 0, 0, 0]);
assert_eq!(len, 7);
let mut buf: Vec<u8> = Vec::new();
let mut len: usize = 0;
append_dbits(&mut buf, &mut len, 0xFF, 8);
assert_eq!(buf, vec![1u8; 8]);
assert_eq!(len, 8);
assert_eq!(
flush_dbits_to_cws(&[1u8, 0, 1, 0, 0, 1, 1, 0], 8),
vec![166u16]
);
assert_eq!(
flush_dbits_to_cws(&[1u8, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 16),
vec![240u16, 15]
);
assert_eq!(
flush_dbits_to_cws(&[1u8, 0, 1, 0, 1, 0, 1, 0, 1], 9),
vec![170u16]
);
assert_eq!(flush_dbits_to_cws(&[], 0), Vec::<u16>::new());
assert_eq!(
flush_dbits_to_cws(&[1u8, 0, 1, 0, 1, 0, 1], 7),
Vec::<u16>::new()
);
}
#[test]
fn ctxvals_to_cws_and_ctx_base_value_dispatch() {
assert_eq!(ctxvals_to_cws(&[0, 0, 0]), vec![0u16, 1]);
assert_eq!(ctxvals_to_cws(&[1, 0, 0]), vec![6u16, 65]);
assert_eq!(ctxvals_to_cws(&[0, 1, 0]), vec![0u16, 41]);
assert_eq!(ctxvals_to_cws(&[0, 0, 1]), vec![0u16, 2]);
assert_eq!(ctxvals_to_cws(&[39, 39, 39]), vec![250u16, 0]);
assert_eq!(
ctxvals_to_cws(&[1, 2, 3, 4, 5, 6]),
vec![6u16, 148, 25, 207]
);
assert_eq!(ctxvals_to_cws(&[]), Vec::<u16>::new());
assert_eq!(
ctx_base_value(MODE_C, b'A' as i16),
cnvals_lookup(b'A' as i16),
"MODE_C → cnvals_lookup"
);
assert_eq!(
ctx_base_value(MODE_T, b'a' as i16),
tnvals_lookup(b'a' as i16),
"MODE_T → tnvals_lookup"
);
assert_eq!(
ctx_base_value(MODE_X, 13),
xvals_lookup(13),
"MODE_X → xvals_lookup"
);
assert_eq!(ctx_base_value(MODE_A, b'A' as i16), None);
assert_eq!(ctx_base_value(MODE_B, b'A' as i16), None);
assert_eq!(ctx_base_value(MODE_D, b'A' as i16), None);
assert_eq!(ctx_base_value(99, b'A' as i16), None);
assert_eq!(ctx_base_value(MODE_C, b'a' as i16), None);
assert_eq!(ctx_base_value(MODE_X, b'a' as i16), None);
}
#[test]
fn codeone_predicate_set_membership_pins() {
assert!(is_d(b'0' as i16));
assert!(is_d(b'5' as i16));
assert!(is_d(b'9' as i16));
assert!(!is_d(b'/' as i16));
assert!(!is_d(b':' as i16));
assert!(!is_d(b'A' as i16));
assert!(is_c(SFT1));
assert!(is_c(SFT2));
assert!(is_c(SFT3));
assert!(is_c(32)); assert!(is_c(b'5' as i16)); assert!(is_c(b'M' as i16)); assert!(!is_c(b'a' as i16)); assert!(!is_c(b'!' as i16));
assert!(!is_c(13));
assert!(is_t(SFT1));
assert!(is_t(SFT2));
assert!(is_t(SFT3));
assert!(is_t(32));
assert!(is_t(b'5' as i16));
assert!(is_t(b'm' as i16)); assert!(!is_t(b'A' as i16)); assert!(!is_t(13));
assert!(is_x(13)); assert!(is_x(42)); assert!(is_x(62)); assert!(is_x(32));
assert!(is_x(b'5' as i16));
assert!(is_x(b'Z' as i16));
assert!(!is_x(b'a' as i16));
assert!(!is_x(SFT1)); assert!(!is_x(b'!' as i16));
assert!(!is_ea(127));
assert!(is_ea(128)); assert!(is_ea(200));
assert!(is_ea(255));
assert!(!is_ea(-1));
assert!(is_fn(-1));
assert!(is_fn(SFT1)); assert!(is_fn(PAD));
assert!(!is_fn(0)); assert!(!is_fn(1));
assert!(!is_fn(127));
}
#[test]
fn avals_byte_digit_pair_marker_arithmetic_and_dispatch() {
assert_eq!(avals_byte(0), Some(1));
assert_eq!(avals_byte(b'A'), Some(66)); assert_eq!(avals_byte(128), Some(129));
assert_eq!(avals_byte(129), None);
assert_eq!(avals_byte(255), None);
assert_eq!(avals_digit_pair(b'0', b'0'), Some(130));
assert_eq!(avals_digit_pair(b'0', b'9'), Some(139));
assert_eq!(avals_digit_pair(b'1', b'0'), Some(140));
assert_eq!(avals_digit_pair(b'9', b'9'), Some(229));
assert_eq!(avals_digit_pair(b'4', b'2'), Some(172)); assert_eq!(avals_digit_pair(b'A', b'0'), None);
assert_eq!(avals_digit_pair(b'0', b'A'), None);
assert_eq!(avals_digit_pair(b'/', b'0'), None);
assert_eq!(avals_digit_pair(b':', b'9'), None);
assert_eq!(avals_marker(PAD), Some(129));
assert_eq!(avals_marker(LC), Some(230));
assert_eq!(avals_marker(LB), Some(231));
assert_eq!(avals_marker(FNC1), Some(232));
assert_eq!(avals_marker(FNC2), Some(233));
assert_eq!(avals_marker(FNC3), Some(234));
assert_eq!(avals_marker(FNC4), Some(235));
assert_eq!(avals_marker(FNC1LD), Some(236));
assert_eq!(avals_marker(LX), Some(238));
assert_eq!(avals_marker(LT), Some(239));
assert_eq!(avals_marker(-99), None);
assert_eq!(avals_marker(0), None);
assert_eq!(avals_marker(100), None);
}
#[test]
fn compute_next_non_x_walks_with_clamp() {
let msg: Vec<i16> = b"ABaCD".iter().map(|&b| i16::from(b)).collect();
let next = compute_next_non_x(&msg);
assert_eq!(next, vec![2u32, 1, 0, 10000, 10000, 9999]);
let msg: Vec<i16> = b"A!B".iter().map(|&b| i16::from(b)).collect();
let next = compute_next_non_x(&msg);
assert_eq!(next, vec![1u32, 0, 10000, 9999]);
assert_eq!(compute_next_non_x(&[]), vec![9999u32]);
assert_eq!(compute_next_non_x(&[b'a' as i16]), vec![0u32, 9999]);
assert_eq!(compute_next_non_x(&[b'A' as i16]), vec![10000u32, 9999]);
let msg: Vec<i16> = b" 12>*\r".iter().map(|&b| i16::from(b)).collect();
let next = compute_next_non_x(&msg);
assert_eq!(
next,
vec![10000u32, 10000, 10000, 10000, 10000, 10000, 9999]
);
}
#[test]
fn compute_next_xterm_walks_with_clamp() {
let msg: Vec<i16> = b"AB\rCD".iter().map(|&b| i16::from(b)).collect();
let next = compute_next_xterm(&msg);
assert_eq!(next, vec![2u32, 1, 0, 10000, 10000, 9999]);
let msg: Vec<i16> = b"X*X".iter().map(|&b| i16::from(b)).collect();
let next = compute_next_xterm(&msg);
assert_eq!(next, vec![1u32, 0, 10000, 9999]);
let msg: Vec<i16> = b">A".iter().map(|&b| i16::from(b)).collect();
let next = compute_next_xterm(&msg);
assert_eq!(next, vec![0u32, 10000, 9999]);
assert_eq!(compute_next_xterm(&[]), vec![9999u32]);
assert_eq!(compute_next_xterm(&[13i16, 13]), vec![0u32, 0, 9999]);
}
#[test]
fn public_encode_works_for_supported_inputs() {
for input in [b"A".as_ref(), b"Hello".as_ref(), b"12345".as_ref()] {
let bm = encode(input).unwrap_or_else(|e| {
panic!(
"encode({:?}) failed: {e:?}",
std::str::from_utf8(input).unwrap_or("<non-utf8>")
)
});
assert_eq!(
bm.width(),
18,
"encode({:?}) width",
std::str::from_utf8(input).unwrap_or("<non-utf8>")
);
assert_eq!(
bm.height(),
16,
"encode({:?}) height",
std::str::from_utf8(input).unwrap_or("<non-utf8>")
);
}
}
#[test]
fn marker_constants() {
let markers = [
FNC1, FNC3, LC, LB, LX, LT, LD, UNL, FNC2, FNC4, SFT1, SFT2, SFT3, ECI, PAD, FNC1LD,
];
for m in markers {
assert!(m < 0, "marker {m} should be a negative sentinel");
}
let mut sorted = markers;
sorted.sort_unstable();
for w in sorted.windows(2) {
assert_ne!(w[0], w[1], "duplicate marker constant");
}
assert_eq!(FNC1, -1);
assert_eq!(FNC3, -2);
assert_eq!(LC, -5);
assert_eq!(LB, -6);
assert_eq!(LX, -7);
assert_eq!(LT, -8);
assert_eq!(LD, -9);
assert_eq!(UNL, -10);
assert_eq!(FNC2, -11);
assert_eq!(FNC4, -12);
assert_eq!(SFT1, -13);
assert_eq!(SFT2, -14);
assert_eq!(SFT3, -15);
assert_eq!(ECI, -16);
assert_eq!(PAD, -17);
assert_eq!(FNC1LD, -18);
assert_eq!(UNLCW, 255);
}
#[test]
fn mode_constants() {
assert_eq!(MODE_A, 0);
assert_eq!(MODE_C, 1);
assert_eq!(MODE_T, 2);
assert_eq!(MODE_X, 3);
assert_eq!(MODE_D, 4);
assert_eq!(MODE_B, 5);
}
#[test]
fn metrics_shapes_and_anchors() {
assert_eq!(METRICS_NONSTYPE.len(), 11);
assert_eq!(METRICS_STYPE.len(), 3);
let a = METRICS_NONSTYPE[0];
assert_eq!(a.id, "A");
assert_eq!(a.rows, 16);
assert_eq!(a.cols, 18);
assert_eq!(a.dcol, 16);
assert_eq!(a.dcw, 10);
assert_eq!(a.rscw, 10);
assert_eq!(a.rsbl, 1);
assert_eq!(a.riso, 4);
assert_eq!(a.risi, NA);
assert_eq!(a.risl, 6);
let h = METRICS_NONSTYPE[7];
assert_eq!(h.id, "H");
assert_eq!(h.rows, 148);
assert_eq!(h.cols, 134);
assert_eq!(h.dcw, 1480);
assert_eq!(h.rscw, 560);
assert_eq!(h.rsbl, 8);
assert_eq!(h.riso, 6);
assert_eq!(h.risi, 20);
assert_eq!(h.risl, 69);
let t16 = METRICS_NONSTYPE[8];
assert_eq!(t16.id, "T-16");
assert_eq!(t16.dcw, 10);
assert_eq!(t16.rsbl, 1);
assert_eq!(t16.risl, NA);
let s30 = METRICS_STYPE[2];
assert_eq!(s30.id, "S-30");
assert_eq!(s30.rows, 8);
assert_eq!(s30.cols, 31);
assert_eq!(s30.dcw, 12);
assert_eq!(s30.rscw, 12);
assert_eq!(s30.risl, NA);
}
#[test]
fn cpatmap_shape_and_lookup() {
assert_eq!(CPATMAP.len(), 10);
assert_eq!(cpatmap_for('A'), Some("121343"));
assert_eq!(cpatmap_for('H'), Some("121212134343"));
assert_eq!(cpatmap_for('S'), Some("56661278"));
assert_eq!(cpatmap_for('T'), Some("5666666666127878"));
assert_eq!(cpatmap_for('Z'), None);
for &(letter, pat) in &CPATMAP {
assert!(
letter.is_ascii_uppercase(),
"CPATMAP letter must be uppercase ASCII"
);
for c in pat.chars() {
assert!(
matches!(c, '1'..='8'),
"CPATMAP[{letter}] = {pat:?} has invalid artifact digit {c:?}"
);
}
}
}
#[test]
fn blackdotmap_shape_and_lookup() {
assert_eq!(BLACKDOTMAP.len(), 14);
assert_eq!(blackdotmap_for("D"), Some(&[][..]));
assert_eq!(blackdotmap_for("S-10"), Some(&[][..]));
assert_eq!(blackdotmap_for("A"), Some(&[(12, 5)][..]));
assert_eq!(blackdotmap_for("B"), Some(&[(16, 7)][..]));
assert_eq!(
blackdotmap_for("F"),
Some(&[(26, 32), (70, 32), (26, 34), (70, 34)][..])
);
assert_eq!(
blackdotmap_for("H"),
Some(&[(26, 70), (66, 70), (106, 70), (26, 72), (66, 72), (106, 72)][..])
);
assert_eq!(blackdotmap_for("S-30"), Some(&[(15, 4), (15, 6)][..]));
assert_eq!(
blackdotmap_for("T-48"),
Some(&[(24, 10), (24, 12), (24, 14)][..])
);
assert_eq!(blackdotmap_for("Q"), None);
}
#[test]
fn stypevals_shape_and_anchors() {
assert_eq!(STYPEVALS.len(), 18);
assert_eq!(STYPEVALS[0], "1");
assert_eq!(STYPEVALS[1], "1010");
assert_eq!(STYPEVALS[2], "1100100");
assert_eq!(STYPEVALS[3], "1111101000");
for (i, &s) in STYPEVALS.iter().enumerate().take(10) {
let parsed = u64::from_str_radix(s, 2).expect("STYPEVALS entry parses as binary");
assert_eq!(
parsed,
10u64.pow(i as u32),
"STYPEVALS[{i}] should equal 10^{i}"
);
}
for (i, &s) in STYPEVALS.iter().enumerate() {
for c in s.chars() {
assert!(
c == '0' || c == '1',
"STYPEVALS[{i}] = {s:?} has non-binary char {c:?}"
);
}
}
}
#[test]
fn rsparams_shape_and_anchors() {
assert_eq!(RSPARAMS.len(), 9);
assert_eq!(RSPARAMS[5], Some((32, 37)));
assert_eq!(RSPARAMS[8], Some((256, 301)));
for (i, slot) in RSPARAMS.iter().enumerate() {
if i != 5 && i != 8 {
assert_eq!(slot, &None, "RSPARAMS[{i}] should be None (placeholder)");
}
}
assert_eq!(RS_GF_S, (32, 37));
assert_eq!(RS_GF_MATRIX, (256, 301));
}
#[test]
fn avals_lookups() {
assert_eq!(avals_byte(0), Some(1));
assert_eq!(avals_byte(1), Some(2));
assert_eq!(avals_byte(b'A'), Some(66)); assert_eq!(avals_byte(127), Some(128));
assert_eq!(avals_byte(128), Some(129));
assert_eq!(avals_byte(129), None);
assert_eq!(avals_byte(255), None);
assert_eq!(avals_digit_pair(b'0', b'0'), Some(130));
assert_eq!(avals_digit_pair(b'0', b'1'), Some(131));
assert_eq!(avals_digit_pair(b'1', b'0'), Some(140));
assert_eq!(avals_digit_pair(b'9', b'9'), Some(229));
assert_eq!(avals_digit_pair(b'A', b'0'), None);
assert_eq!(avals_digit_pair(b'0', b'A'), None);
assert_eq!(avals_marker(PAD), Some(129));
assert_eq!(avals_marker(LC), Some(230));
assert_eq!(avals_marker(LB), Some(231));
assert_eq!(avals_marker(FNC1), Some(232));
assert_eq!(avals_marker(FNC2), Some(233));
assert_eq!(avals_marker(FNC3), Some(234));
assert_eq!(avals_marker(FNC4), Some(235));
assert_eq!(avals_marker(FNC1LD), Some(236));
assert_eq!(avals_marker(LX), Some(238)); assert_eq!(avals_marker(LT), Some(239));
assert_eq!(avals_marker(LD), None); assert_eq!(avals_marker(UNL), None);
assert_eq!(avals_marker(SFT1), None);
assert_eq!(avals_marker(ECI), None);
}
#[test]
fn encode_mode_b_run_basic() {
let cws = encode_mode_b_run(b"", false)
.expect("encode_mode_b_run(b\"\", omit=false) (empty run → 1-cw length prefix [0]) must succeed");
assert_eq!(cws, vec![0]);
let cws = encode_mode_b_run(b"hello", false).expect(
"encode_mode_b_run(b\"hello\", omit=false) (5-byte run < 249 → 1-cw prefix=5 + 5 byte cws) must succeed",
);
assert_eq!(cws, vec![5, 104, 101, 108, 108, 111]);
let cws = encode_mode_b_run(&[0xFF], false).expect(
"encode_mode_b_run(b\"\\xFF\", omit=false) (1-byte run < 249 → 1-cw prefix=1 + 0xFF) must succeed",
);
assert_eq!(cws, vec![1, 255]);
let payload: Vec<u8> = (0..249).map(|i| i as u8).collect();
let cws = encode_mode_b_run(&payload, false).expect(
"encode_mode_b_run(249-byte payload, omit=false) (exactly-at 1-cw-prefix boundary: 249 → prefix=249) must succeed",
);
assert_eq!(cws.len(), 250); assert_eq!(cws[0], 249);
for (i, &cw) in cws[1..].iter().enumerate() {
assert_eq!(cw, i as u16);
}
let payload250: Vec<u8> = (0..250).map(|i| (i % 256) as u8).collect();
let cws = encode_mode_b_run(&payload250, false).expect(
"encode_mode_b_run(250-byte payload, omit=false) (one-past-1-cw-prefix → 2-cw prefix: (p/250)+249, p%250 = (250, 0)) must succeed",
);
assert_eq!(cws.len(), 252); assert_eq!(cws[0], 250); assert_eq!(cws[1], 0);
let payload500: Vec<u8> = (0..500).map(|i| (i % 256) as u8).collect();
let cws = encode_mode_b_run(&payload500, false).expect(
"encode_mode_b_run(500-byte payload, omit=false) (2-cw prefix mid-range: (500/250)+249, 500%250 = (251, 0)) must succeed",
);
assert_eq!(cws[0], 251);
assert_eq!(cws[1], 0);
assert_eq!(cws.len(), 502);
let cws = encode_mode_b_run(b"hi", true).expect(
"encode_mode_b_run(b\"hi\", omit=true) (omit_prefix_if_exact path → 2 byte cws, no prefix) must succeed",
);
assert_eq!(cws, vec![104, 105]);
}
#[test]
fn encode_mode_b_run_rejects_overlong() {
let too_big = vec![0u8; MODE_B_MAX_LEN + 1];
match encode_mode_b_run(&too_big, false).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("code one mode B:"),
"missing `code one mode B:` prefix: {msg}"
);
assert!(
msg.contains("payload of 1481 bytes"),
"missing `payload of 1481 bytes` actual-length echo: {msg}"
);
assert!(
msg.contains("exceeds max"),
"missing `exceeds max` predicate: {msg}"
);
assert!(
msg.contains("max 1480"),
"missing `max 1480` MODE_B_MAX_LEN echo (proves the cap constant is exposed): {msg}"
);
}
other => panic!(
"encode_mode_b_run({} bytes) should reject as InvalidData, got {other:?}",
MODE_B_MAX_LEN + 1
),
}
}
#[test]
fn encode_message_routes_high_bytes_through_mode_b() {
let input = [0xC8_u8, 0xC9, 0xCA];
let cws = encode_message(&input).expect(
"encode_message([0xC8, 0xC9, 0xCA]) (3 high bytes >127 → Mode A lookup picks Mode B at i=0 because Mode A cost FNC4+payload=2cws/byte > Mode B 1cw/byte) must succeed",
);
let lb = avals_marker(LB).expect("LB marker resolves");
assert_eq!(
cws,
vec![lb, 3, 0xC8, 0xC9, 0xCA],
"Mode B latch + length prefix + raw bytes"
);
}
#[test]
fn encode_message_mode_b_accepts_high_byte_range() {
let input: Vec<u8> = (128..=255).collect();
assert_eq!(input.len(), 128);
let cws = encode_message(&input).expect(
"encode_message(128..=255) (full 0x80..=0xFF high-byte range, 128 bytes → Mode B latch LB + encB 1-cw length prefix (128 < 250) + 128 raw byte cws) must succeed",
);
let lb = avals_marker(LB).expect("LB marker resolves");
assert_eq!(cws[0], lb);
assert_eq!(cws[1], 128); assert_eq!(cws.len(), 1 + 1 + 128);
for (i, expected) in (128u16..=255).enumerate() {
assert_eq!(cws[2 + i], expected, "byte at index {i}");
}
}
#[test]
fn encode_b_step_rejects_negative_cells() {
let mut ctx = Ctx::from_bytes(b"AB");
ctx.msg[1] = -1;
ctx.mode = MODE_B;
let err = encode_b_step(&mut ctx).unwrap_err();
let msg = format!("{err:?}");
assert!(
msg.contains("code one Mode B:"),
"diagnostic must carry the full code one Mode B prefix; got {msg}"
);
assert!(
msg.contains("cell -1"),
"diagnostic must echo the offending cell value -1; got {msg}"
);
assert!(
msg.contains("cannot be encoded as a raw byte"),
"diagnostic must carry the predicate; got {msg}"
);
assert!(
msg.contains("extension paths"),
"diagnostic must name the extension-paths remediation; got {msg}"
);
assert!(
msg.contains("PORT_STATUS"),
"diagnostic must point callers at PORT_STATUS; got {msg}"
);
}
#[test]
fn compute_numd_lookahead() {
assert_eq!(compute_numd(b"A".map(i16::from).as_slice()), vec![0, 0]);
assert_eq!(
compute_numd(b"Hello".map(i16::from).as_slice()),
vec![0, 0, 0, 0, 0, 0]
);
assert_eq!(
compute_numd(b"12345".map(i16::from).as_slice()),
vec![5, 4, 3, 2, 1, 0]
);
assert_eq!(
compute_numd(b"A12B3".map(i16::from).as_slice()),
vec![0, 2, 1, 0, 1, 0]
);
let msg = vec![FNC1, b'1' as i16, b'2' as i16];
assert_eq!(compute_numd(&msg), vec![0, 2, 1, 0]);
assert_eq!(compute_numd(&[]), vec![0]);
}
#[test]
fn encode_mode_a_only_matches_bwip_js_goldens() {
let cases: &[(&[u8], &[u16])] = &[
(b"A", &[66]),
(b"AB", &[66, 67]),
(b"1", &[50]),
(b"12", &[142]),
(b"123", &[142, 52]),
(b"Hello", &[73, 102, 109, 109, 112]),
];
for &(input, expected) in cases {
let cws = encode_mode_a_only(input).unwrap_or_else(|e| {
panic!(
"encode_mode_a_only({:?}) failed: {e:?}",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
)
});
assert_eq!(
cws,
expected,
"encode_mode_a_only({:?}) cws",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
);
}
}
#[test]
fn encode_mode_a_only_high_byte_still_rejected() {
let high_byte = [200u8];
let err = encode_mode_a_only(&high_byte).unwrap_err();
let msg = format!("{err:?}");
assert!(
msg.contains("code one Mode A:"),
"high-byte diagnostic must carry code one Mode A prefix; got {msg}"
);
assert!(
msg.contains("byte 0xc8"),
"high-byte diagnostic must echo byte=0xc8 (200 in hex); got {msg}"
);
assert!(
msg.contains("> 128"),
"high-byte diagnostic must carry the > 128 bound; got {msg}"
);
assert!(
msg.contains("FNC4") && msg.contains("Mode B"),
"high-byte diagnostic must name BOTH remediation paths (FNC4, Mode B); got {msg}"
);
}
#[test]
fn ctx_from_bytes() {
let ctx = Ctx::from_bytes(b"A1");
assert_eq!(ctx.msg, vec![65, 49]);
assert_eq!(ctx.numd, vec![0, 1, 0]);
assert_eq!(ctx.i, 0);
assert_eq!(ctx.mode, MODE_A);
assert!(
ctx.cws.is_empty(),
"Ctx::from_bytes(b\"A1\") must initialize cws to empty (no spurious initial codewords); got len={}",
ctx.cws.len()
);
assert_eq!(ctx.next_xterm.len(), 3);
assert_eq!(ctx.next_non_x.len(), 3);
}
#[test]
fn ctx_at_end_strict_ge_predicate() {
let ctx = Ctx::from_bytes(b"");
assert!(ctx.at_end(), "empty msg + i=0 must be at_end (i >= 0)");
let mut ctx = Ctx::from_bytes(b"A");
assert!(!ctx.at_end(), "single byte + i=0 not at_end");
ctx.i = 1;
assert!(
ctx.at_end(),
"single byte + i=1 (== len) at_end (kills `>=` → `>`)"
);
ctx.i = 99;
assert!(ctx.at_end(), "i > len still at_end (kills `>=` → `==`)");
let mut ctx = Ctx::from_bytes(b"AB1");
for cursor in 0..3 {
ctx.i = cursor;
assert!(!ctx.at_end(), "msg.len()=3 + i={cursor} not at_end");
}
ctx.i = 3;
assert!(ctx.at_end(), "msg.len()=3 + i=3 (boundary) at_end");
let mut ctx = Ctx::from_bytes(b"AB");
ctx.cws = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
ctx.i = 1;
assert!(
!ctx.at_end(),
"i=1 < msg.len()=2 not at_end regardless of cws length"
);
ctx.i = 2;
assert!(
ctx.at_end(),
"i=2 == msg.len()=2 at_end regardless of cws length"
);
}
#[test]
fn mode_predicates() {
assert!(is_d(b'0' as i16));
assert!(is_d(b'5' as i16));
assert!(is_d(b'9' as i16));
assert!(!is_d(b'/' as i16));
assert!(!is_d(b':' as i16));
assert!(!is_d(b'A' as i16));
assert!(!is_d(FNC1));
assert!(is_c(SFT1));
assert!(is_c(SFT2));
assert!(is_c(SFT3));
assert!(is_c(32)); assert!(is_c(b'0' as i16));
assert!(is_c(b'A' as i16));
assert!(is_c(b'Z' as i16));
assert!(!is_c(b'a' as i16));
assert!(!is_c(b'!' as i16));
assert!(!is_c(FNC1));
assert!(is_t(SFT1));
assert!(is_t(32));
assert!(is_t(b'5' as i16));
assert!(is_t(b'a' as i16));
assert!(is_t(b'z' as i16));
assert!(!is_t(b'A' as i16));
assert!(!is_t(b'!' as i16));
assert!(is_x(13));
assert!(is_x(42));
assert!(is_x(62));
assert!(is_x(32));
assert!(is_x(b'9' as i16));
assert!(is_x(b'A' as i16));
assert!(!is_x(b'a' as i16));
assert!(!is_x(b'!' as i16));
assert!(is_ea(128));
assert!(is_ea(200));
assert!(is_ea(255));
assert!(!is_ea(127));
assert!(!is_ea(0));
assert!(!is_ea(-1));
assert!(is_fn(-1));
assert!(is_fn(FNC1));
assert!(is_fn(PAD));
assert!(!is_fn(0));
assert!(!is_fn(127));
}
#[test]
fn lookahead_arrays() {
let msg: Vec<i16> = b"ABCDE".iter().map(|&b| i16::from(b)).collect();
let nxt = compute_next_xterm(&msg);
let nnx = compute_next_non_x(&msg);
assert_eq!(nxt.last().copied().unwrap(), 9999);
for (i, &v) in nxt.iter().take(5).enumerate() {
assert!(v >= 5 - i as u32, "next_xterm[{i}]={v}");
}
for &v in nnx.iter().take(5) {
assert!(v >= 1);
}
let msg: Vec<i16> = b"AB*DE".iter().map(|&b| i16::from(b)).collect();
let nxt = compute_next_xterm(&msg);
assert_eq!(nxt[0], 2);
assert_eq!(nxt[1], 1);
assert_eq!(nxt[2], 0);
assert!(nxt[3] >= 3);
let msg: Vec<i16> = b"AB!DE".iter().map(|&b| i16::from(b)).collect();
let nnx = compute_next_non_x(&msg);
assert_eq!(nnx[0], 2);
assert_eq!(nnx[1], 1);
assert_eq!(nnx[2], 0);
}
#[test]
fn xterm_first_strictly_less() {
let xterm = [2u32];
let non_x = [5u32];
assert!(xterm_first(&xterm, &non_x, 0));
let xterm = [7u32];
let non_x = [3u32];
assert!(!xterm_first(&xterm, &non_x, 0));
let xterm = [4u32];
let non_x = [4u32];
assert!(
!xterm_first(&xterm, &non_x, 0),
"equal distances must NOT count as xterm-first (rejects `<=`)"
);
let xterm = [9999u32];
let non_x = [1u32];
assert!(!xterm_first(&xterm, &non_x, 0));
let xterm = [99, 2, 100];
let non_x = [99, 5, 1];
assert!(xterm_first(&xterm, &non_x, 1), "i=1: 2 < 5 → true");
assert!(
!xterm_first(&xterm, &non_x, 2),
"i=2: 100 < 1 is false (rejects index swap)"
);
assert!(
!xterm_first(&xterm, &non_x, 0),
"i=0: 99 < 99 is false (rejects `<=`)"
);
}
#[test]
fn ff_truncates_fractions_but_passes_integers() {
assert_eq!(ff(0.0), 0.0);
assert_eq!(ff(1.0), 1.0);
assert_eq!(ff(-1.0), -1.0);
assert_eq!(ff(5.0), 5.0);
assert_eq!(ff(255.0), 255.0);
let big = 1e15_f64;
assert_eq!(
ff(big),
big,
"large integer passes through (rejects integer-branch removal)"
);
assert_ne!(
f64::from(big as f32),
big,
"sanity: 1e15 is NOT f32-exact (mutation would observe drift)"
);
let big_neg = -3_000_000_000_f64;
assert_eq!(ff(big_neg), big_neg);
let expected_one_tenth = f64::from(0.1_f32);
assert_eq!(ff(0.1), expected_one_tenth);
assert_ne!(
ff(0.1),
0.1,
"ff(0.1) must differ from f64 0.1 (f32 truncation observable)"
);
let two_thirds = 2.0_f64 / 3.0;
let two_thirds_f32 = f64::from(2.0_f32 / 3.0);
assert_eq!(ff(two_thirds), two_thirds_f32);
let abcdef_trigger = 5.000_000_2_f64;
assert_eq!(
ff(abcdef_trigger),
5.0,
"5.0000002 → f32 → 5.0 (lookup() relies on this collapse)"
);
}
#[test]
fn lookup_matches_bwip_js_initial_mode() {
let cases: &[(&[u8], u8)] = &[
(b"A", MODE_A),
(b"AB", MODE_A),
(b"ABC", MODE_A),
(b"ABCDE", MODE_A),
(b"ABCDEFG", MODE_C),
(b"ABCDEFGHIJ", MODE_C),
(b"abc", MODE_A),
(b"abcdef", MODE_T),
(b"ABCabc", MODE_A),
(b"Hello", MODE_A),
(b"HelloWorld", MODE_A),
];
for &(input, expected) in cases {
let ctx = Ctx::from_bytes(input);
let got = lookup(&ctx.msg, 0, MODE_A, &ctx.next_xterm, &ctx.next_non_x);
assert_eq!(
got,
expected,
"lookup({:?}) = {got}, want {expected}",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
);
}
}
#[test]
fn encode_mode_a_only_emits_latch_then_defers_to_ctx() {
let err = encode_mode_a_only(b"ABCDEFG").unwrap_err();
let msg = format!("{err:?}");
assert!(
msg.contains("code one Mode A-only:"),
"diagnostic must carry the code one Mode A-only prefix; got {msg}"
);
assert!(
msg.contains("switched out of Mode A"),
"diagnostic must explain WHY (encoder switched out); got {msg}"
);
assert!(
msg.contains("full mode-selector"),
"diagnostic must name the remediation (full mode-selector); got {msg}"
);
assert!(
msg.contains("(Stage 5+)"),
"diagnostic must carry the (Stage 5+) milestone hint; got {msg}"
);
}
#[test]
fn lookup_at_nonzero_position() {
let ctx = Ctx::from_bytes(b"ABCabcdef");
let got = lookup(&ctx.msg, 3, MODE_A, &ctx.next_xterm, &ctx.next_non_x);
assert_eq!(
got, MODE_T,
"lookup(\"ABCabcdef\", i=3) = {got}, want T (2)"
);
let ctx = Ctx::from_bytes(b"abcd1234");
for start in 0..=4 {
let got = lookup(&ctx.msg, start, MODE_A, &ctx.next_xterm, &ctx.next_non_x);
assert_eq!(
got, MODE_A,
"lookup(\"abcd1234\", i={start}) = {got}, want A"
);
}
}
#[test]
fn ctx_base_value_lookups() {
assert_eq!(cnvals_lookup(SFT1), Some(0));
assert_eq!(cnvals_lookup(SFT2), Some(1));
assert_eq!(cnvals_lookup(SFT3), Some(2));
assert_eq!(cnvals_lookup(32), Some(3));
assert_eq!(cnvals_lookup(b'0' as i16), Some(4));
assert_eq!(cnvals_lookup(b'9' as i16), Some(13));
assert_eq!(cnvals_lookup(b'A' as i16), Some(14));
assert_eq!(cnvals_lookup(b'Z' as i16), Some(39));
assert_eq!(cnvals_lookup(b'a' as i16), None);
assert_eq!(cnvals_lookup(b'!' as i16), None);
assert_eq!(tnvals_lookup(b'a' as i16), Some(14));
assert_eq!(tnvals_lookup(b'z' as i16), Some(39));
assert_eq!(tnvals_lookup(b'A' as i16), None);
assert_eq!(xvals_lookup(13), Some(0));
assert_eq!(xvals_lookup(42), Some(1));
assert_eq!(xvals_lookup(62), Some(2));
assert_eq!(xvals_lookup(b'A' as i16), Some(14));
assert_eq!(xvals_lookup(b'a' as i16), None);
}
#[test]
fn ctxvals_to_cws_pack_triplets() {
assert_eq!(ctxvals_to_cws(&[14, 15, 16]), vec![89, 233]);
assert_eq!(ctxvals_to_cws(&[17, 18, 19]), vec![109, 36]);
assert_eq!(
ctxvals_to_cws(&[14, 15, 16, 17, 18, 19]),
vec![89, 233, 109, 36]
);
assert_eq!(ctxvals_to_cws(&[]), Vec::<u16>::new());
}
#[test]
fn pick_symbol_size_picks_smallest_fit() {
for n in 0..=10 {
let m = pick_symbol_size(n).unwrap_or_else(|e| {
panic!(
"pick_symbol_size({n}) (sizes 0..=10 must fit version A, dcws=10) failed: {e}"
)
});
assert_eq!(m.id, "A", "size {n} should pick A");
assert_eq!(m.dcw, 10);
}
let m = pick_symbol_size(11).expect(
"pick_symbol_size(11) (11 cws > A.dcws=10 → smallest-fit is B, dcws=19) must succeed",
);
assert_eq!(m.id, "B");
assert_eq!(m.dcw, 19);
let m = pick_symbol_size(20).expect(
"pick_symbol_size(20) (20 cws > B.dcws=19 → smallest-fit is C, dcws=44) must succeed",
);
assert_eq!(m.id, "C");
assert_eq!(m.dcw, 44);
let m = pick_symbol_size(1480).expect(
"pick_symbol_size(1480) (exact-fit to largest version-H ceiling: H.dcws=1480) must succeed",
);
assert_eq!(m.id, "H");
let err = pick_symbol_size(1481).unwrap_err();
match err {
Error::InvalidData(msg) => {
assert!(
msg.contains("code one:"),
"ceiling diagnostic must carry the symbology prefix; got {msg}"
);
assert!(
msg.contains("cws of length 1481"),
"ceiling diagnostic must echo cws_len (1481); got {msg}"
);
assert!(
msg.contains("exceeds the largest symbol"),
"ceiling diagnostic must carry the predicate; got {msg}"
);
assert!(
msg.contains("(H, dcws=1480)"),
"ceiling diagnostic must name the H-symbol cap verbatim; got {msg}"
);
}
other => panic!("expected InvalidData, got {other:?}"),
}
}
#[test]
fn gf256_gen_coeffs_ecpb10_matches_bwip_js() {
let coeffs = gf256_gen_coeffs(10);
assert_eq!(coeffs, vec![52, 31, 182, 85, 43, 218, 76, 113, 141, 136]);
}
#[test]
fn gf256_rs_block_ecc_matches_bwip_js_for_letter_a() {
let coeffs = gf256_gen_coeffs(10);
let data: Vec<u16> = std::iter::once(66u16)
.chain(std::iter::repeat_n(129u16, 9))
.collect();
let ecc = gf256_rs_block_ecc(&data, &coeffs);
assert_eq!(ecc, vec![42, 216, 108, 200, 178, 200, 247, 148, 106, 230]);
}
#[test]
fn pad_cws_matrix_fills_with_129() {
let mut cws = vec![66u16];
pad_cws_matrix(&mut cws, 10);
assert_eq!(cws, vec![66, 129, 129, 129, 129, 129, 129, 129, 129, 129]);
let mut cws = vec![1u16; 5];
pad_cws_matrix(&mut cws, 5);
assert_eq!(cws.len(), 5);
}
#[test]
fn encode_with_ecc_matches_bwip_js_goldens() {
let cases: &[(&[u8], &[u16])] = &[
(
b"A",
&[
66, 129, 129, 129, 129, 129, 129, 129, 129, 129, 42, 216, 108, 200, 178, 200, 247, 148, 106, 230, ],
),
(
b"Hello",
&[
73, 102, 109, 109, 112, 129, 129, 129, 129, 129, 248, 151, 38, 190, 182, 51, 253, 251, 119, 221, ],
),
(
b"ABCDEFG",
&[
230, 89, 233, 109, 36, 255, 72, 129, 129, 129, 27, 137, 20, 221, 125, 242, 234, 77, 135, 221, ],
),
(
b"ABCDEFGHIJ",
&[
230, 89, 233, 109, 36, 128, 95, 255, 75, 129, 232, 138, 60, 238, 88, 10, 139, 73, 13, 216, ],
),
(
b"abcdef",
&[
239, 89, 233, 109, 36, 255, 129, 129, 129, 129, 76, 10, 136, 227, 193, 252, 200, 187, 220, 86, ],
),
];
for &(input, expected) in cases {
let (cws, metric) = encode_with_ecc(input).unwrap_or_else(|e| {
panic!(
"encode_with_ecc({:?}) failed: {e:?}",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
)
});
assert_eq!(metric.id, "A", "all Stage-7 goldens land in version A");
assert_eq!(
cws,
expected,
"encode_with_ecc({:?}) cws",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
);
}
}
#[test]
fn cw_nibbles_8_anchors() {
let (top, bot) = cw_nibbles_8(66);
assert_eq!(top, [0, 1, 0, 0]);
assert_eq!(bot, [0, 0, 1, 0]);
let (top, bot) = cw_nibbles_8(129);
assert_eq!(top, [1, 0, 0, 0]);
assert_eq!(bot, [0, 0, 0, 1]);
}
#[test]
fn cws_to_mmat_matches_bwip_js_golden_for_a() {
let metric = pick_symbol_size(1).unwrap();
let (cws, m2) = encode_with_ecc(b"A").unwrap();
assert_eq!(metric.id, m2.id);
let mmat = cws_to_mmat(&cws, &m2);
let expected: [u8; 160] = [
0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0,
1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0,
1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0,
];
assert_eq!(mmat, expected.as_slice());
}
#[test]
fn compose_pixs_matches_bwip_js_golden_for_a() {
let (cws, metric) = encode_with_ecc(b"A").unwrap();
let mmat = cws_to_mmat(&cws, &metric);
let pixs = compose_pixs(&mmat, &metric).unwrap();
let expected: [u8; 288] = [
0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0,
0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0,
1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0,
];
assert_eq!(pixs.as_slice(), expected.as_slice());
}
#[test]
fn encode_to_bit_matrix_produces_16x18_for_a() {
let bm = encode_to_bit_matrix(b"A").unwrap();
assert_eq!(bm.width(), 18);
assert_eq!(bm.height(), 16);
assert!(!bm.get(0, 0));
assert!(bm.get(1, 0));
for x in 0..18 {
assert!(bm.get(x, 6), "pixs[6][{x}] should be 1");
}
}
#[test]
fn compose_pixs_matches_bwip_js_golden_for_hello() {
let (cws, metric) = encode_with_ecc(b"Hello").unwrap();
let mmat = cws_to_mmat(&cws, &metric);
let pixs = compose_pixs(&mmat, &metric).unwrap();
let expected: [u8; 288] = [
0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1,
1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0,
0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1,
];
assert_eq!(pixs.as_slice(), expected.as_slice());
}
#[test]
fn compose_pixs_matches_bwip_js_golden_for_abc() {
let (cws, metric) = encode_with_ecc(b"ABC").unwrap();
let mmat = cws_to_mmat(&cws, &metric);
let pixs = compose_pixs(&mmat, &metric).unwrap();
let expected: [u8; 288] = [
0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0,
1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0,
0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0,
0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1,
];
assert_eq!(pixs.as_slice(), expected.as_slice());
}
#[test]
fn compose_pixs_matches_bwip_js_golden_for_abcdefg() {
let (cws, metric) = encode_with_ecc(b"ABCDEFG").unwrap();
let mmat = cws_to_mmat(&cws, &metric);
let pixs = compose_pixs(&mmat, &metric).unwrap();
let expected: [u8; 288] = [
1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1,
0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0,
0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1,
1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1,
0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1,
];
assert_eq!(pixs.as_slice(), expected.as_slice());
}
#[test]
fn encode_message_matches_bwip_js_goldens() {
let cases: &[(&[u8], &[u16])] = &[
(b"A", &[66]),
(b"Hello", &[73, 102, 109, 109, 112]),
(b"ABCDEFG", &[230, 89, 233, 109, 36, 255, 72]),
(b"abcdef", &[239, 89, 233, 109, 36, 255]),
(b"ABCDEFGHIJ", &[230, 89, 233, 109, 36, 128, 95, 255, 75]),
(b"ABCabcdef", &[66, 67, 68, 239, 89, 233, 109, 36, 255]),
];
for &(input, expected) in cases {
let cws = encode_message(input).unwrap_or_else(|e| {
panic!(
"encode_message({:?}) failed: {e:?}",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
)
});
assert_eq!(
cws,
expected,
"encode_message({:?}) cws",
std::str::from_utf8(input).unwrap_or("<non-utf8>"),
);
}
}
#[test]
fn mode_d_thirteen_digits_at_eom_matches_oracle() {
let cws = encode_message(b"1234567890123").unwrap();
assert_eq!(cws, vec![241, 241, 201, 197, 128, 223, 209]);
}
#[test]
fn mode_d_twenty_digits_at_eom_matches_oracle() {
let cws = encode_message(b"12345678901234567890").unwrap();
assert_eq!(cws, vec![241, 241, 201, 197, 128, 213, 106, 167, 253, 220]);
}
#[test]
fn mode_d_after_mode_a_prefix_matches_oracle() {
let cws = encode_message(b"A1234567890123").unwrap();
assert_eq!(cws, vec![66, 241, 241, 201, 197, 128, 223, 209]);
}
#[test]
fn mode_d_twentyone_digit_trigger_matches_oracle() {
let cws = encode_message(b"123456789012345678901").unwrap();
assert_eq!(cws, vec![241, 241, 201, 197, 128, 213, 106, 167, 225, 189]);
}
#[test]
fn mode_d_with_mode_a_tail_matches_oracle() {
let cws = encode_message(b"123456789012345678901ABC").unwrap();
assert_eq!(
cws,
vec![241, 241, 201, 197, 128, 213, 106, 167, 225, 191, 66, 67, 68]
);
}
#[test]
fn mode_d_sandwiched_between_mode_a_matches_oracle() {
let cws = encode_message(b"ABC123456789012345678901DEF").unwrap();
assert_eq!(
cws,
vec![66, 67, 68, 241, 241, 201, 197, 128, 213, 106, 167, 225, 191, 69, 70, 71]
);
}
#[test]
fn mode_d_sixteen_digits_one_trailing_matches_oracle() {
let cws = encode_message(b"1234567890123456").unwrap();
assert_eq!(cws, vec![241, 241, 201, 197, 128, 213, 107, 247]);
}
#[test]
fn mode_d_fourteen_digits_two_trailing_matches_oracle() {
let cws = encode_message(b"12345678901234").unwrap();
assert_eq!(cws, vec![241, 241, 201, 197, 128, 223, 209, 53]);
}
#[test]
fn mode_d_fifteen_digits_clean_termination_matches_oracle() {
let cws = encode_message(b"123456789012345").unwrap();
assert_eq!(cws, vec![241, 241, 201, 197, 128, 213, 107, 255]);
}
#[test]
fn getnumremcws_table_anchors() {
assert_eq!(getnumremcws(0), Some(10));
assert_eq!(getnumremcws(9), Some(1));
assert_eq!(getnumremcws(10), Some(9));
assert_eq!(getnumremcws(18), Some(1));
assert_eq!(getnumremcws(19), Some(25));
assert_eq!(getnumremcws(43), Some(1));
assert_eq!(getnumremcws(1479), Some(1));
assert_eq!(getnumremcws(1480), None);
}
#[test]
fn append_dbits_round_trip() {
let mut buf: Vec<u8> = Vec::new();
let mut len: usize = 0;
append_dbits(&mut buf, &mut len, 0b1111, 4);
append_dbits(&mut buf, &mut len, 124, 10);
assert_eq!(len, 14);
let bytes = flush_dbits_to_cws(&buf, len);
assert_eq!(bytes, vec![241]);
}
#[test]
fn predicate_classifiers_cover_every_set() {
assert!(is_d(b'0' as i16));
assert!(is_d(b'9' as i16));
assert!(!is_d(b'/' as i16)); assert!(!is_d(b':' as i16)); assert!(!is_d(b'A' as i16));
assert!(!is_d(b'a' as i16));
assert!(is_c(32)); assert!(is_c(b'A' as i16));
assert!(is_c(b'Z' as i16));
assert!(is_c(b'0' as i16));
assert!(!is_c(b'a' as i16)); assert!(!is_c(b'@' as i16)); assert!(!is_c(b'[' as i16));
assert!(is_t(32));
assert!(is_t(b'a' as i16));
assert!(is_t(b'z' as i16));
assert!(is_t(b'0' as i16));
assert!(!is_t(b'A' as i16)); assert!(!is_t(b'`' as i16)); assert!(!is_t(b'{' as i16));
assert!(is_x(13)); assert!(is_x(42)); assert!(is_x(62)); assert!(is_x(32)); assert!(is_x(b'A' as i16));
assert!(is_x(b'Z' as i16));
assert!(!is_x(b'a' as i16));
assert!(!is_x(b'+' as i16));
assert!(is_ea(128));
assert!(is_ea(255));
assert!(!is_ea(127)); assert!(!is_ea(0));
assert!(is_fn(-1));
assert!(is_fn(-128));
assert!(!is_fn(0));
assert!(!is_fn(1));
}
#[test]
fn artifact_row_per_index_known_layouts_cols_9() {
let cols: usize = 9;
let sentinel: i8 = -1;
assert_eq!(artifact_row(0, cols), vec![0i8; 9]);
assert_eq!(artifact_row(1, cols), vec![1i8; 9]);
assert_eq!(artifact_row(2, cols), vec![0, 1, 1, 1, 1, 1, 1, 1, 0]);
assert_eq!(artifact_row(3, cols), vec![0, 1, 0, 0, 0, 0, 0, 1, 0]);
assert_eq!(
artifact_row(4, cols),
vec![sentinel, sentinel, sentinel, sentinel, 1, sentinel, sentinel, sentinel, sentinel]
);
assert_eq!(
artifact_row(5, cols),
vec![sentinel, sentinel, sentinel, sentinel, 0, sentinel, sentinel, sentinel, sentinel]
);
assert_eq!(artifact_row(6, cols), vec![1, 0, 0, 0, 0, 0, 0, 0, 1]);
assert_eq!(artifact_row(7, cols), vec![1, 0, 1, 1, 1, 1, 1, 0, 1]);
assert_eq!(artifact_row(8, cols), vec![0i8; 9]);
assert_eq!(artifact_row(255, cols), vec![0i8; 9]);
}
#[test]
fn codeone_char_class_predicates_per_arm() {
for c in b'0'..=b'9' {
assert!(is_d(c as i16), "'{}' must be d", c as char);
}
assert!(!is_d(b'/' as i16), "'/' (47) just below '0'");
assert!(!is_d(b':' as i16), "':' (58) just above '9'");
assert!(!is_d(b'A' as i16), "'A' is not d");
assert!(!is_d(-1), "negative not d");
assert!(!is_d(200), "high byte not d");
assert!(is_c(SFT1), "SFT1 is c");
assert!(is_c(SFT2), "SFT2 is c");
assert!(is_c(SFT3), "SFT3 is c");
assert!(is_c(32), "space is c");
for c in b'0'..=b'9' {
assert!(is_c(c as i16), "'{}' (digit) is c", c as char);
}
for c in b'A'..=b'Z' {
assert!(is_c(c as i16), "'{}' uppercase is c", c as char);
}
assert!(!is_c(b'a' as i16), "'a' lowercase NOT in C");
assert!(!is_c(b'z' as i16), "'z' lowercase NOT in C");
assert!(!is_c(b'!' as i16), "'!' NOT in C");
assert!(!is_c(b'@' as i16), "'@' NOT in C");
assert!(is_t(SFT1));
assert!(is_t(32));
for c in b'0'..=b'9' {
assert!(is_t(c as i16), "'{}' (digit) is t", c as char);
}
for c in b'a'..=b'z' {
assert!(is_t(c as i16), "'{}' lowercase is t", c as char);
}
assert!(!is_t(b'A' as i16), "'A' uppercase NOT in T");
assert!(!is_t(b'Z' as i16), "'Z' uppercase NOT in T");
assert!(is_x(13), "CR (13) is x");
assert!(is_x(42), "'*' (42) is x");
assert!(is_x(62), "'>' (62) is x");
assert!(is_x(32), "space (32) is x");
for c in b'0'..=b'9' {
assert!(is_x(c as i16));
}
for c in b'A'..=b'Z' {
assert!(is_x(c as i16));
}
assert!(!is_x(SFT1), "SFT1 NOT in X12");
assert!(!is_x(b'a' as i16), "'a' NOT in X12");
assert!(!is_x(12), "12 (^L) NOT in X12");
assert!(!is_x(14), "14 (^N) NOT in X12");
assert!(!is_x(41), "41 ('(') NOT in X12");
assert!(!is_x(43), "43 ('+') NOT in X12");
assert!(!is_x(61), "61 ('=') NOT in X12");
assert!(!is_x(63), "63 ('?') NOT in X12");
assert!(!is_ea(127), "127 (DEL) NOT extended-ASCII (>127 only)");
assert!(is_ea(128), "128 IS extended-ASCII");
assert!(is_ea(255), "255 IS extended-ASCII");
assert!(is_ea(32767), "32767 IS extended-ASCII (max i16)");
assert!(!is_ea(0));
assert!(!is_ea(-1), "negative NOT extended-ASCII");
assert!(is_fn(-1), "-1 IS FN");
assert!(is_fn(SFT1), "SFT1 (-13) IS FN");
assert!(is_fn(SFT2));
assert!(is_fn(SFT3));
assert!(is_fn(-32768), "min i16 IS FN");
assert!(!is_fn(0), "0 NOT FN (catches `<= 0` mutant)");
assert!(!is_fn(1));
assert!(!is_fn(127));
assert!(!is_fn(255));
assert!(!is_fn(i16::MAX));
assert!(is_c(b'A' as i16) && is_x(b'A' as i16) && !is_t(b'A' as i16));
assert!(is_t(b'a' as i16) && !is_c(b'a' as i16) && !is_x(b'a' as i16));
}
#[test]
fn compute_next_xterm_distance_to_terminator_with_clamp() {
assert_eq!(
compute_next_xterm(&[]),
vec![9999u32],
"empty msg → [9999] sentinel only"
);
assert_eq!(
compute_next_xterm(&[13]),
vec![0, 9999],
"CR (13) at pos 0 → [0, 9999]"
);
assert_eq!(
compute_next_xterm(&[42]),
vec![0, 9999],
"'*' (42) at pos 0 → [0, 9999]"
);
assert_eq!(
compute_next_xterm(&[62]),
vec![0, 9999],
"'>' (62) at pos 0 → [0, 9999]"
);
assert_eq!(
compute_next_xterm(&[5]),
vec![10000, 9999],
"single non-term: 9999+1 = 10000 (clamped)"
);
assert_eq!(compute_next_xterm(&[5, 5]), vec![10000, 10000, 9999]);
assert_eq!(
compute_next_xterm(&[5, 13]),
vec![1, 0, 9999],
"[5, CR]: distance from 0 to CR is 1"
);
assert_eq!(compute_next_xterm(&[5, 5, 13]), vec![2, 1, 0, 9999]);
assert_eq!(
compute_next_xterm(&[5, 13, 5]),
vec![1, 0, 10000, 9999],
"[5, CR, 5]: trailing non-term clamps to 10000"
);
assert_eq!(compute_next_xterm(&[13, 13]), vec![0, 0, 9999]);
assert_eq!(
compute_next_xterm(&[42, 62, 13]),
vec![0, 0, 0, 9999],
"all 3 terminators → all 0"
);
assert_eq!(
compute_next_xterm(&[5, 42, 5, 62]),
vec![1, 0, 1, 0, 9999],
"exercises all 3 terminator values"
);
assert_eq!(
compute_next_xterm(&[12]),
vec![10000, 9999],
"12 NOT a terminator (catches `<= 13` mutant)"
);
assert_eq!(
compute_next_xterm(&[14]),
vec![10000, 9999],
"14 NOT a terminator"
);
assert_eq!(compute_next_xterm(&[41]), vec![10000, 9999]);
assert_eq!(compute_next_xterm(&[43]), vec![10000, 9999]);
assert_eq!(compute_next_xterm(&[61]), vec![10000, 9999]);
assert_eq!(compute_next_xterm(&[63]), vec![10000, 9999]);
for n in 0..10 {
let msg = vec![5i16; n];
assert_eq!(
compute_next_xterm(&msg).len(),
n + 1,
"len(msg)={n} → output len {n}+1"
);
}
}
#[test]
fn pad_cws_matrix_pads_to_dcws_with_constant_129() {
let mut cws = vec![];
pad_cws_matrix(&mut cws, 0);
assert!(cws.is_empty(), "empty target → no padding");
let mut cws = vec![];
pad_cws_matrix(&mut cws, 3);
assert_eq!(
cws,
vec![129, 129, 129],
"empty cws + dcws=3 → 3 pad codewords (129)"
);
let mut cws = vec![];
pad_cws_matrix(&mut cws, 1);
assert_eq!(cws, vec![129], "single pad");
let mut cws = vec![1, 2];
pad_cws_matrix(&mut cws, 3);
assert_eq!(
cws,
vec![1, 2, 129],
"[1, 2] + dcws=3 → append 1 pad (preserving prefix)"
);
let mut cws = vec![1, 2, 3];
pad_cws_matrix(&mut cws, 3);
assert_eq!(
cws,
vec![1, 2, 3],
"len == dcws: no change (catches `<= dcws` mutant that would add 1 extra)"
);
let mut cws = vec![1, 2, 3, 4, 5];
pad_cws_matrix(&mut cws, 3);
assert_eq!(
cws,
vec![1, 2, 3, 4, 5],
"len > dcws: no change (catches truncate mutant)"
);
let mut cws = vec![10, 20];
pad_cws_matrix(&mut cws, 5);
assert_eq!(
cws,
vec![10, 20, 129, 129, 129],
"[10, 20] + dcws=5 → 3 pads"
);
let mut cws = vec![];
pad_cws_matrix(&mut cws, 1);
assert_ne!(cws[0], 0, "pad is NOT 0");
assert_ne!(cws[0], 128, "pad is NOT 128 (catches off-by-one mutant)");
assert_ne!(cws[0], 130, "pad is NOT 130");
assert_eq!(cws[0], 129, "pad is exactly 129");
}
#[test]
fn ff_truncates_non_integer_to_f32_precision() {
assert_eq!(ff(0.0), 0.0);
assert_eq!(ff(1.0), 1.0);
assert_eq!(ff(-1.0), -1.0);
assert_eq!(ff(2.0), 2.0);
assert_eq!(ff(5.0), 5.0);
assert_eq!(ff(100.0), 100.0);
assert_eq!(ff(-100.0), -100.0);
assert_eq!(ff(123_456_789.0), 123_456_789.0);
assert_eq!(ff(0.5), 0.5, "0.5 is exact in f32; round-trip preserves");
assert_eq!(ff(0.25), 0.25);
assert_eq!(ff(0.125), 0.125);
let v_f64 = 2.0_f64 / 3.0_f64;
let v_f32_via_f64 = f64::from(v_f64 as f32);
let truncated = ff(v_f64);
assert_eq!(
truncated, v_f32_via_f64,
"2/3 in f64 must be truncated to f32 precision"
);
assert_ne!(
truncated, v_f64,
"f32 truncation of 2/3 must differ from f64 value (else-arm mutant catch)"
);
let v = 0.1_f64;
let want = f64::from(v as f32);
assert_eq!(ff(v), want, "0.1 truncated to f32 precision");
assert_ne!(ff(v), v, "0.1 in f64 ≠ 0.1 in f32 (round-trip)");
assert_eq!(ff(ff(7.0)), ff(7.0));
let once = ff(0.1);
let twice = ff(once);
assert_eq!(twice, once, "ff is idempotent");
let two_thirds = 2.0_f64 / 3.0_f64;
assert!(
ff(two_thirds) != two_thirds,
"ff(2/3) MUST differ from plain f64 2/3 (f32 truncation is the whole point)"
);
let neg = -0.3_f64;
let want_neg = f64::from(neg as f32);
assert_eq!(ff(neg), want_neg, "-0.3 truncated to f32");
}
#[test]
fn encode_a_step_digit_pair_and_at_end() {
let mut ctx = Ctx::from_bytes(b"12");
assert_eq!(ctx.mode, MODE_A);
assert_eq!(ctx.i, 0);
encode_a_step(&mut ctx).unwrap();
assert_eq!(
ctx.cws,
vec![142u16],
"digit pair '12' must emit avals_digit_pair = 130 + 12 = 142"
);
assert_eq!(ctx.i, 2, "i must advance by 2 after digit-pair consumption");
assert_eq!(ctx.mode, MODE_A);
let mut ctx = Ctx::from_bytes(b"AB");
ctx.i = 2; ctx.mode = MODE_A;
let cws_before = ctx.cws.clone();
let i_before = ctx.i;
let mode_before = ctx.mode;
encode_a_step(&mut ctx).unwrap();
assert_eq!(ctx.cws, cws_before, "at-end must not push codewords");
assert_eq!(ctx.i, i_before, "at-end must not advance i");
assert_eq!(ctx.mode, mode_before, "at-end must not change mode");
let mut ctx = Ctx::from_bytes(b"00");
encode_a_step(&mut ctx).unwrap();
assert_eq!(ctx.cws, vec![130u16], "'00' → 130 + 0 = 130");
let mut ctx = Ctx::from_bytes(b"99");
encode_a_step(&mut ctx).unwrap();
assert_eq!(ctx.cws, vec![229u16], "'99' → 130 + 99 = 229");
let mut ctx12 = Ctx::from_bytes(b"12");
encode_a_step(&mut ctx12).unwrap();
let mut ctx21 = Ctx::from_bytes(b"21");
encode_a_step(&mut ctx21).unwrap();
assert_ne!(
ctx12.cws[0], ctx21.cws[0],
"'12' and '21' must produce distinct codewords; equality means d1/d2 \
are summed symmetrically"
);
assert_eq!(ctx21.cws[0], 130 + 21, "'21' → 130 + 21 = 151");
}
#[test]
fn encode_b_step_happy_path_consumes_remaining_msg() {
let mut ctx = Ctx::from_bytes(b"AB");
ctx.mode = MODE_B;
ctx.i = 0;
encode_b_step(&mut ctx).unwrap();
assert_eq!(
ctx.cws,
vec![2u16, 65, 66],
"Mode B encodes [prefix=len, bytes...] → [2, 65, 66] for \"AB\""
);
assert_eq!(
ctx.i,
ctx.msg.len(),
"encode_b_step must advance i to msg.len() (consumes all)"
);
assert_eq!(ctx.mode, MODE_B);
let mut ctx = Ctx::from_bytes(b"hello");
ctx.mode = MODE_B;
ctx.i = 5; encode_b_step(&mut ctx).unwrap();
assert!(
ctx.cws.is_empty(),
"at-end Mode B must not emit any codewords"
);
let mut ctx = Ctx::from_bytes(b"Z");
ctx.mode = MODE_B;
encode_b_step(&mut ctx).unwrap();
assert_eq!(ctx.cws, vec![1u16, 90], "single 'Z' → [1, 90]");
assert_eq!(ctx.i, 1);
let mut ctx = Ctx::from_bytes(b"hello");
ctx.mode = MODE_B;
ctx.i = 2; encode_b_step(&mut ctx).unwrap();
assert_eq!(
ctx.cws,
vec![3u16, b'l' as u16, b'l' as u16, b'o' as u16],
"starting at i=2 must encode only 'llo' (3 bytes, prefix=3)"
);
assert_eq!(ctx.i, 5);
}
#[test]
fn nvals_lookup_arithmetic_per_arm_anchors() {
assert_eq!(cnvals_lookup(SFT1), Some(0));
assert_eq!(cnvals_lookup(SFT2), Some(1));
assert_eq!(cnvals_lookup(SFT3), Some(2));
assert_eq!(cnvals_lookup(32), Some(3));
assert_eq!(cnvals_lookup(b'0' as i16), Some(4));
assert_eq!(cnvals_lookup(b'5' as i16), Some(9));
assert_eq!(cnvals_lookup(b'9' as i16), Some(13));
assert_eq!(cnvals_lookup(b'A' as i16), Some(14));
assert_eq!(cnvals_lookup(b'M' as i16), Some(26));
assert_eq!(cnvals_lookup(b'Z' as i16), Some(39));
assert_eq!(cnvals_lookup(b'a' as i16), None);
assert_eq!(cnvals_lookup(b'!' as i16), None);
assert_eq!(cnvals_lookup(127), None);
assert_eq!(tnvals_lookup(SFT1), Some(0));
assert_eq!(tnvals_lookup(SFT2), Some(1));
assert_eq!(tnvals_lookup(SFT3), Some(2));
assert_eq!(tnvals_lookup(32), Some(3));
assert_eq!(tnvals_lookup(b'0' as i16), Some(4));
assert_eq!(tnvals_lookup(b'9' as i16), Some(13));
assert_eq!(tnvals_lookup(b'a' as i16), Some(14));
assert_eq!(tnvals_lookup(b'm' as i16), Some(26));
assert_eq!(tnvals_lookup(b'z' as i16), Some(39));
assert_eq!(tnvals_lookup(b'A' as i16), None);
assert_eq!(tnvals_lookup(b'Z' as i16), None);
assert_eq!(xvals_lookup(13), Some(0));
assert_eq!(xvals_lookup(42), Some(1));
assert_eq!(xvals_lookup(62), Some(2));
assert_eq!(xvals_lookup(32), Some(3));
assert_eq!(xvals_lookup(b'0' as i16), Some(4));
assert_eq!(xvals_lookup(b'9' as i16), Some(13));
assert_eq!(xvals_lookup(b'A' as i16), Some(14));
assert_eq!(xvals_lookup(b'Z' as i16), Some(39));
assert_eq!(xvals_lookup(b'a' as i16), None);
assert_eq!(xvals_lookup(b'+' as i16), None);
assert_eq!(cnvals_lookup(b'a' as i16), None, "C40 rejects lowercase");
assert_eq!(xvals_lookup(b'a' as i16), None, "X12 rejects lowercase");
assert!(
tnvals_lookup(b'a' as i16).is_some(),
"Text accepts lowercase"
);
assert_eq!(cnvals_lookup(b':' as i16), None);
assert_eq!(tnvals_lookup(b':' as i16), None);
assert_eq!(xvals_lookup(b':' as i16), None);
assert_eq!(cnvals_lookup(b'/' as i16), None);
assert_eq!(tnvals_lookup(b'/' as i16), None);
assert_eq!(xvals_lookup(b'/' as i16), None);
}
}