#![allow(dead_code)]
use std::collections::HashMap;
pub(crate) const FNC1: i32 = -1;
pub(crate) const LATCH_NUMERIC: i32 = -2;
pub(crate) const LATCH_ALPHANUMERIC: i32 = -3;
pub(crate) const LATCH_ISO646: i32 = -4;
pub(crate) fn build_alpha_map() -> HashMap<i32, (u32, u8)> {
let mut m = HashMap::new();
for c in b'A'..=b'Z' {
m.insert(c as i32, ((c - b'A') as u32, 5));
}
for c in b'0'..=b'9' {
m.insert(c as i32, ((c + 4) as u32, 6));
}
m.insert(FNC1, (31, 5));
m
}
pub(crate) fn build_numeric_map() -> HashMap<[u8; 2], (u32, u8)> {
let mut m = HashMap::new();
for p in 0u32..=119 {
let hi = p / 11;
let lo = p % 11;
let c0 = if hi < 10 { b'0' + hi as u8 } else { b'^' };
let c1 = if lo < 10 { b'0' + lo as u8 } else { b'^' };
m.insert([c0, c1], (p + 8, 7));
}
m
}
pub(crate) fn push_bits(out: &mut Vec<bool>, value: u32, width: u8) {
for k in (0..width).rev() {
out.push((value >> k) & 1 == 1);
}
}
pub(crate) fn build_alphanumeric_map() -> HashMap<i32, (u32, u8)> {
let mut m = HashMap::new();
for c in b'0'..=b'9' {
m.insert(c as i32, ((c - 43) as u32, 5));
}
m.insert(FNC1, (0b01111, 5));
for c in b'A'..=b'Z' {
m.insert(c as i32, ((c - 33) as u32, 6));
}
m.insert(b'*' as i32, (0b111010, 6));
for c in b','..=b'/' {
m.insert(c as i32, ((c + 15) as u32, 6));
}
m.insert(LATCH_NUMERIC, (0b000, 3));
m.insert(LATCH_ISO646, (0b00100, 5));
m
}
pub(crate) fn build_iso646_map() -> HashMap<i32, (u32, u8)> {
let mut m = HashMap::new();
for c in b'0'..=b'9' {
m.insert(c as i32, ((c - 43) as u32, 5));
}
m.insert(FNC1, (0b01111, 5));
for c in b'A'..=b'Z' {
m.insert(c as i32, ((c - 1) as u32, 7));
}
for c in b'a'..=b'z' {
m.insert(c as i32, ((c - 7) as u32, 7));
}
m.insert(b'!' as i32, (0b11101000, 8));
m.insert(b'"' as i32, (0b11101001, 8));
for c in b'%'..=b'/' {
m.insert(c as i32, ((c + 197) as u32, 8));
}
for c in b':'..=b'?' {
m.insert(c as i32, ((c + 187) as u32, 8));
}
m.insert(b'_' as i32, (0b11111011, 8));
m.insert(b' ' as i32, (0b11111100, 8));
m.insert(LATCH_NUMERIC, (0b000, 3));
m.insert(LATCH_ALPHANUMERIC, (0b00100, 5));
m
}
pub(crate) fn default_cc_columns(lintype: &str) -> Option<u8> {
Some(match lintype {
"ean13"
| "upca"
| "gs1-128"
| "databaromni"
| "databartruncated"
| "databarexpanded"
| "databarexpandedstacked" => 4,
"ean8" | "databarlimited" => 3,
"upce" | "databarstacked" | "databarstackedomni" => 2,
_ => return None,
})
}
pub(crate) const BITCAPS_A: [&[u32]; 3] = [
&[167, 138, 118, 108, 88, 78, 59],
&[167, 138, 118, 98, 78],
&[197, 167, 138, 108, 78],
];
pub(crate) const BITCAPS_B: [&[u32]; 3] = [
&[336, 296, 256, 208, 160, 104, 56],
&[768, 648, 536, 416, 304, 208, 152, 112, 72, 32],
&[1184, 1016, 840, 672, 496, 352, 264, 208, 152, 96, 56],
];
pub(crate) const FILLPAT: [u8; 5] = [0, 0, 1, 0, 0];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CcVersion {
A,
B,
C,
}
impl CcVersion {
pub(crate) fn as_str(self) -> &'static str {
match self {
CcVersion::A => "a",
CcVersion::B => "b",
CcVersion::C => "c",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct GpfBuild {
pub cdf: Vec<bool>,
pub gpf: Vec<i32>,
}
pub(crate) fn build_gpf_with_method(gs1_input: &str) -> Result<GpfBuild, crate::error::Error> {
let elements = crate::util::gs1::parse(gs1_input)
.map_err(|e| crate::error::Error::InvalidData(format!("gs1_cc: {e}")))?;
if elements.is_empty() {
return Err(crate::error::Error::InvalidData(
"gs1_cc: empty GS1 input".into(),
));
}
let first_ai = elements[0].ai.as_str();
let first_value = elements[0].data.as_str();
let is_method_10 = matches!(first_ai, "10" | "11" | "17");
let (cdf, mut gpf): (Vec<bool>, Vec<i32>) = if is_method_10 {
let cdf = vec![true, false, true, true];
let mut gpf = Vec::new();
if first_ai == "10" {
gpf.extend(first_value.bytes().map(|b| b as i32));
} else {
gpf.push(FNC1);
}
if elements.len() > 1 {
gpf.push(FNC1);
}
(cdf, gpf)
} else {
(vec![false], Vec::new())
};
let start = if is_method_10 { 1 } else { 0 };
let total = elements.len();
for (i, e) in elements.iter().enumerate().skip(start) {
gpf.extend(e.ai.bytes().map(|b| b as i32));
gpf.extend(e.data.bytes().map(|b| b as i32));
if i + 1 < total && crate::util::gs1::ai_is_variable_length(&e.ai).unwrap_or(false) {
gpf.push(FNC1);
}
}
Ok(GpfBuild { cdf, gpf })
}
pub(crate) fn build_gpf(gs1_input: &str) -> Result<Vec<i32>, crate::util::gs1::ParseError> {
let elements = crate::util::gs1::parse(gs1_input)?;
let mut gpf: Vec<i32> = Vec::new();
for (i, e) in elements.iter().enumerate() {
gpf.extend(e.ai.bytes().map(|b| b as i32));
gpf.extend(e.data.bytes().map(|b| b as i32));
if i + 1 < elements.len() && crate::util::gs1::ai_is_variable_length(&e.ai).unwrap_or(false)
{
gpf.push(FNC1);
}
}
Ok(gpf)
}
pub(crate) fn select_cca_ccb_size(
payload_bits: usize,
cccolumns: u8,
) -> Option<(CcVersion, usize, u32)> {
let col_idx = (cccolumns as usize).checked_sub(2)?;
if col_idx >= BITCAPS_A.len() {
return None;
}
let caps_a = BITCAPS_A[col_idx];
let mut best: Option<(usize, u32)> = None;
for (idx, &cap) in caps_a.iter().enumerate() {
if cap as usize >= payload_bits {
best = Some((idx, cap));
}
}
if let Some((idx, cap)) = best {
return Some((CcVersion::A, idx, cap));
}
let caps_b = BITCAPS_B[col_idx];
let mut best: Option<(usize, u32)> = None;
for (idx, &cap) in caps_b.iter().enumerate() {
if cap as usize >= payload_bits {
best = Some((idx, cap));
}
}
best.map(|(idx, cap)| (CcVersion::B, idx, cap))
}
#[derive(Debug, Clone)]
pub(crate) struct CcEncoded {
pub version: CcVersion,
pub columns: u8,
pub codewords: Vec<u32>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CcCSize {
pub columns: u8,
pub eclevel: u8,
pub byte_count: u32,
}
pub(crate) fn select_ccc_size(payload_bits: usize, linwidth: u32) -> CcCSize {
let m_bytes = payload_bits.div_ceil(8);
let m_pre = (m_bytes / 6) * 5 + (m_bytes % 6);
let eccws: u32 = if m_pre <= 40 {
8
} else if m_pre <= 160 {
16
} else if m_pre <= 320 {
32
} else if m_pre <= 833 {
64
} else {
32
};
let m_total = m_pre + eccws as usize + 3;
let mut cccolumns = ((linwidth as i64 - 52) / 17).max(1) as usize;
if cccolumns > 30 {
cccolumns = 30;
}
while m_total.div_ceil(cccolumns) > 30 && cccolumns < 30 {
cccolumns += 1;
}
let r = m_total.div_ceil(cccolumns).max(3);
let data_slots = (cccolumns * r) as i64 - eccws as i64 - 3;
let data_slots = data_slots.max(0) as u32;
let byte_count = (data_slots / 5) * 6 + (data_slots % 5);
let eclevel = (eccws as f32).log2() as u8 - 1;
CcCSize {
columns: cccolumns as u8,
eclevel,
byte_count,
}
}
pub(crate) fn encode_cc_force_c(
gs1_input: &str,
linwidth: u32,
) -> Result<(Vec<u8>, CcCSize), crate::error::Error> {
let GpfBuild { cdf, gpf } = build_gpf_with_method(gs1_input)?;
let payload = encode_payload(&gpf);
let final_mode = final_mode_for(&gpf);
let total_payload_bits = cdf.len() + payload.len();
let size = select_ccc_size(total_payload_bits, linwidth);
let cap_bits = size.byte_count * 8;
let mut full: Vec<bool> = Vec::with_capacity(cap_bits as usize);
full.extend_from_slice(&cdf);
full.extend_from_slice(&payload);
let padded = pad_with_fillpat(&full, cap_bits, final_mode);
Ok((pack_ccb_bytes(&padded), size))
}
pub(crate) fn encode_cc(gs1_input: &str, columns: u8) -> Result<CcEncoded, crate::error::Error> {
let GpfBuild { cdf, gpf } = build_gpf_with_method(gs1_input)?;
let payload = encode_payload(&gpf);
let final_mode = final_mode_for(&gpf);
let total_payload_bits = cdf.len() + payload.len();
let (version, _row_idx, cap_bits) =
select_cca_ccb_size(total_payload_bits, columns).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"gs1_cc: payload ({total_payload_bits} bits) exceeds CC-A/CC-B capacity at {columns} columns",
))
})?;
let mut full: Vec<bool> = Vec::with_capacity(cap_bits as usize);
full.extend_from_slice(&cdf);
full.extend_from_slice(&payload);
let padded = pad_with_fillpat(&full, cap_bits, final_mode);
let codewords: Vec<u32> = match version {
CcVersion::A => pack_cca_codewords(&padded),
CcVersion::B => pack_ccb_bytes(&padded)
.into_iter()
.map(|b| b as u32)
.collect(),
CcVersion::C => {
return Err(crate::error::Error::InvalidData(
"gs1_cc: CC-C selection happens at the higher (linear) level".into(),
));
}
};
Ok(CcEncoded {
version,
columns,
codewords,
})
}
fn final_mode_for(gpf: &[i32]) -> CcMode {
let numeric = build_numeric_map();
let alphanumeric = build_alphanumeric_map();
let iso646 = build_iso646_map();
let (numeric_runs, alphanumeric_runs, next_iso646_only) =
compute_runs(gpf, &numeric, &alphanumeric, &iso646);
let mut mode = CcMode::Numeric;
let mut i = 0;
while i < gpf.len() {
match mode {
CcMode::Numeric => {
if i + 1 < gpf.len() {
let pair = [pair_byte(gpf[i]), pair_byte(gpf[i + 1])];
if numeric.contains_key(&pair) {
i += 2;
} else {
mode = CcMode::Alphanumeric;
}
} else {
let c = gpf[i];
if !(b'0' as i32..=b'9' as i32).contains(&c) {
mode = CcMode::Alphanumeric;
} else {
i += 1;
}
}
}
CcMode::Alphanumeric => {
let c = gpf[i];
if c == FNC1 {
mode = CcMode::Numeric;
i += 1;
} else if iso646.contains_key(&c) && !alphanumeric.contains_key(&c) {
mode = CcMode::Iso646;
} else if numeric_runs[i] >= 6
|| (numeric_runs[i] >= 4 && i + numeric_runs[i] as usize == gpf.len())
{
mode = CcMode::Numeric;
} else {
i += 1;
}
}
CcMode::Iso646 => {
let c = gpf[i];
if c == FNC1 {
mode = CcMode::Numeric;
i += 1;
} else if numeric_runs[i] >= 4 && next_iso646_only[i] >= 10 {
mode = CcMode::Numeric;
} else if alphanumeric_runs[i] >= 5 && next_iso646_only[i] >= 10 {
mode = CcMode::Alphanumeric;
} else {
i += 1;
}
}
}
}
mode
}
pub(crate) fn build_pwr928() -> [[u32; 7]; 69] {
let mut t = [[0u32; 7]; 69];
t[0][6] = 1;
for j in 1..69 {
let mut carry: u32 = 0;
for k in (0..7).rev() {
let v = t[j - 1][k] * 2 + carry;
t[j][k] = v % 928;
carry = v / 928;
}
}
t
}
pub(crate) fn pack_cca_codewords(bits: &[bool]) -> Vec<u32> {
let pwr928 = build_pwr928();
let mut out: Vec<u32> = Vec::new();
let mut b = 0;
while b < bits.len() {
let bsl = (bits.len() - b).min(69);
let chunk = &bits[b..b + bsl];
let csl = bsl / 10 + 1;
let mut cs = [0u32; 7];
for i in 0..bsl {
if chunk[bsl - i - 1] {
for k in 0..csl {
cs[k] += pwr928[i][k + 7 - csl];
}
}
}
for k in (1..csl).rev() {
cs[k - 1] += cs[k] / 928;
cs[k] %= 928;
}
out.extend_from_slice(&cs[..csl]);
b += bsl;
}
out
}
pub(crate) fn pack_ccb_bytes(bits: &[bool]) -> Vec<u8> {
let n = bits.len() / 8;
let mut out = Vec::with_capacity(n);
for chunk in bits.chunks_exact(8) {
let mut v: u8 = 0;
for &b in chunk {
v = (v << 1) | (b as u8);
}
out.push(v);
}
out
}
pub(crate) fn pad_with_fillpat(payload: &[bool], cap_bits: u32, final_mode: CcMode) -> Vec<bool> {
let cap = cap_bits as usize;
let mut out = Vec::with_capacity(cap);
out.extend_from_slice(payload);
if payload.len() >= cap {
return out;
}
let pad_len = cap - payload.len();
let mut pad: Vec<bool> = Vec::with_capacity(pad_len);
if final_mode == CcMode::Numeric {
pad.extend(std::iter::repeat_n(false, 4));
}
while pad.len() < pad_len {
for &b in &FILLPAT {
if pad.len() >= pad_len {
break;
}
pad.push(b == 1);
}
}
pad.truncate(pad_len);
out.extend(pad);
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CcMode {
Numeric,
Alphanumeric,
Iso646,
}
const NUMERIC_FNC1: u8 = b'^';
fn pair_byte(v: i32) -> u8 {
if v == FNC1 {
NUMERIC_FNC1
} else {
v as u8
}
}
pub(crate) fn encode_payload(gpf: &[i32]) -> Vec<bool> {
let numeric = build_numeric_map();
let alphanumeric = build_alphanumeric_map();
let iso646 = build_iso646_map();
let (numeric_runs, alphanumeric_runs, next_iso646_only) =
compute_runs(gpf, &numeric, &alphanumeric, &iso646);
let mut bits = Vec::new();
let mut mode = CcMode::Numeric;
let mut i = 0;
while i < gpf.len() {
match mode {
CcMode::Numeric => {
if i + 1 < gpf.len() {
let pair = [pair_byte(gpf[i]), pair_byte(gpf[i + 1])];
if let Some(&(v, w)) = numeric.get(&pair) {
push_bits(&mut bits, v, w);
i += 2;
} else {
push_bits(&mut bits, 0, 4);
mode = CcMode::Alphanumeric;
}
} else {
let c = gpf[i];
if c < b'0' as i32 || c > b'9' as i32 {
push_bits(&mut bits, 0, 4);
mode = CcMode::Alphanumeric;
} else {
let pair = [c as u8, NUMERIC_FNC1];
if let Some(&(v, w)) = numeric.get(&pair) {
push_bits(&mut bits, v, w);
i += 1;
} else {
push_bits(&mut bits, 0, 4);
mode = CcMode::Alphanumeric;
}
}
}
}
CcMode::Alphanumeric => {
let c = gpf[i];
if c == FNC1 {
let &(v, w) = alphanumeric.get(&FNC1).unwrap();
push_bits(&mut bits, v, w);
mode = CcMode::Numeric;
i += 1;
} else if iso646.contains_key(&c) && !alphanumeric.contains_key(&c) {
push_bits(&mut bits, 0b00100, 5);
mode = CcMode::Iso646;
} else if numeric_runs[i] >= 6 {
push_bits(&mut bits, 0, 3);
mode = CcMode::Numeric;
} else if numeric_runs[i] >= 4 && i + numeric_runs[i] as usize == gpf.len() {
push_bits(&mut bits, 0, 3);
mode = CcMode::Numeric;
} else if let Some(&(v, w)) = alphanumeric.get(&c) {
push_bits(&mut bits, v, w);
i += 1;
} else {
push_bits(&mut bits, 0b00100, 5);
mode = CcMode::Iso646;
}
}
CcMode::Iso646 => {
let c = gpf[i];
if c == FNC1 {
let &(v, w) = iso646.get(&FNC1).unwrap();
push_bits(&mut bits, v, w);
mode = CcMode::Numeric;
i += 1;
} else if numeric_runs[i] >= 4 && next_iso646_only[i] >= 10 {
push_bits(&mut bits, 0, 3);
mode = CcMode::Numeric;
} else if alphanumeric_runs[i] >= 5 && next_iso646_only[i] >= 10 {
push_bits(&mut bits, 0b00100, 5);
mode = CcMode::Alphanumeric;
} else if let Some(&(v, w)) = iso646.get(&c) {
push_bits(&mut bits, v, w);
i += 1;
} else {
i += 1;
}
}
}
}
bits
}
pub(crate) fn compute_runs(
gpf: &[i32],
numeric: &HashMap<[u8; 2], (u32, u8)>,
alphanumeric: &HashMap<i32, (u32, u8)>,
iso646: &HashMap<i32, (u32, u8)>,
) -> (Vec<u32>, Vec<u32>, Vec<u32>) {
let n = gpf.len();
let mut numeric_runs = vec![0u32; n + 2];
let mut alphanumeric_runs = vec![0u32; n + 1];
let mut next_iso646_only = vec![0u32; n + 1];
next_iso646_only[n] = 9999;
for i in (0..n).rev() {
let to_byte = |v: i32| -> u8 {
if v == FNC1 {
b'^'
} else {
v as u8
}
};
let c0 = to_byte(gpf[i]);
let c1 = if i + 1 < n {
to_byte(gpf[i + 1])
} else {
b'\0'
};
let pair = [c0, c1];
if i + 1 < n && numeric.contains_key(&pair) {
numeric_runs[i] = numeric_runs[i + 2] + 2;
} else {
numeric_runs[i] = 0;
}
if alphanumeric.contains_key(&gpf[i]) {
alphanumeric_runs[i] = alphanumeric_runs[i + 1] + 1;
} else {
alphanumeric_runs[i] = 0;
}
if iso646.contains_key(&gpf[i]) && !alphanumeric.contains_key(&gpf[i]) {
next_iso646_only[i] = 0;
} else {
next_iso646_only[i] = next_iso646_only[i + 1].saturating_add(1);
}
}
numeric_runs.truncate(n);
alphanumeric_runs.truncate(n);
next_iso646_only.truncate(n);
(numeric_runs, alphanumeric_runs, next_iso646_only)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_gpf_with_method_method_10_date_ai_no_longer_errors() {
let payload = "(17)250520(10)BATCH";
let got = build_gpf_with_method(payload).expect("AI 17 + YYMMDD must not error");
assert_eq!(
&got.cdf[0..4],
&[true, false, true, true],
"method-10 CDF prefix"
);
assert!(!got.gpf.is_empty(), "GPF must carry the AIs");
}
#[test]
fn build_gpf_with_method_method_11_ai_90_no_longer_errors() {
let payload = "(90)ABC123";
let got = build_gpf_with_method(payload).expect("AI 90 must not error");
assert!(!got.cdf[0], "AI 90 CDF leading bit is 0");
assert!(!got.gpf.is_empty(), "GPF must carry the AI-90 payload");
}
#[test]
fn alpha_map_letter_codes() {
let m = build_alpha_map();
assert_eq!(m.get(&(b'A' as i32)), Some(&(0, 5)));
assert_eq!(m.get(&(b'Z' as i32)), Some(&(25, 5)));
}
#[test]
fn alpha_map_digit_codes() {
let m = build_alpha_map();
assert_eq!(m.get(&(b'0' as i32)), Some(&(52, 6)));
assert_eq!(m.get(&(b'9' as i32)), Some(&(61, 6)));
}
#[test]
fn alpha_map_fnc1() {
let m = build_alpha_map();
assert_eq!(m.get(&FNC1), Some(&(31, 5)));
}
#[test]
fn numeric_map_two_digit_pair() {
let m = build_numeric_map();
assert_eq!(m.get(b"00"), Some(&(8, 7)));
assert_eq!(m.get(b"10"), Some(&(19, 7)));
assert_eq!(m.get(b"99"), Some(&(116, 7)));
}
#[test]
fn push_bits_msb_and_default_cc_columns_dispatch() {
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0, 0);
assert!(out.is_empty(), "width=0 → no-op");
push_bits(&mut out, 0b1010, 4);
assert_eq!(out, vec![true, false, true, false]);
push_bits(&mut out, 0, 3);
assert_eq!(out, vec![true, false, true, false, false, false, false]);
let mut buf: Vec<bool> = Vec::new();
push_bits(&mut buf, 0xFF, 8);
assert_eq!(buf, vec![true; 8]);
assert_eq!(default_cc_columns("ean13"), Some(4));
assert_eq!(default_cc_columns("upca"), Some(4));
assert_eq!(default_cc_columns("gs1-128"), Some(4));
assert_eq!(default_cc_columns("databaromni"), Some(4));
assert_eq!(default_cc_columns("databartruncated"), Some(4));
assert_eq!(default_cc_columns("databarexpanded"), Some(4));
assert_eq!(default_cc_columns("databarexpandedstacked"), Some(4));
assert_eq!(default_cc_columns("ean8"), Some(3));
assert_eq!(default_cc_columns("databarlimited"), Some(3));
assert_eq!(default_cc_columns("upce"), Some(2));
assert_eq!(default_cc_columns("databarstacked"), Some(2));
assert_eq!(default_cc_columns("databarstackedomni"), Some(2));
assert_eq!(default_cc_columns("nonexistent"), None);
assert_eq!(default_cc_columns(""), None);
}
#[test]
fn numeric_map_fnc1_pairs() {
let m = build_numeric_map();
assert_eq!(m.get(b"0^"), Some(&(18, 7)));
assert_eq!(m.get(b"^0"), Some(&(118, 7)));
assert_eq!(m.get(b"^^"), None);
}
#[test]
fn numeric_map_has_120_entries() {
let m = build_numeric_map();
assert_eq!(m.len(), 120);
}
#[test]
fn push_bits_msb_first() {
let mut bits = Vec::new();
push_bits(&mut bits, 0b10110, 5);
assert_eq!(bits, vec![true, false, true, true, false]);
}
#[test]
fn default_cc_columns_known() {
assert_eq!(default_cc_columns("gs1-128"), Some(4));
assert_eq!(default_cc_columns("upce"), Some(2));
assert_eq!(default_cc_columns("ean13"), Some(4));
assert_eq!(default_cc_columns("unknown"), None);
}
#[test]
fn bitcaps_shape() {
assert_eq!(BITCAPS_A.len(), 3);
assert_eq!(BITCAPS_A[0].len(), 7);
assert_eq!(BITCAPS_B.len(), 3);
assert_eq!(BITCAPS_B[2][0], 1184);
}
#[test]
fn fillpat_shape() {
assert_eq!(FILLPAT, [0, 0, 1, 0, 0]);
}
#[test]
fn alphanumeric_map_digits_and_letters() {
let m = build_alphanumeric_map();
assert_eq!(m.get(&(b'0' as i32)), Some(&(5, 5)));
assert_eq!(m.get(&(b'9' as i32)), Some(&(14, 5)));
assert_eq!(m.get(&FNC1), Some(&(15, 5)));
assert_eq!(m.get(&(b'A' as i32)), Some(&(32, 6)));
assert_eq!(m.get(&(b'Z' as i32)), Some(&(57, 6)));
assert_eq!(m.get(&(b'*' as i32)), Some(&(58, 6)));
assert_eq!(m.get(&(b',' as i32)), Some(&(59, 6)));
assert_eq!(m.get(&(b'/' as i32)), Some(&(62, 6)));
assert_eq!(m.get(&LATCH_NUMERIC), Some(&(0, 3)));
assert_eq!(m.get(&LATCH_ISO646), Some(&(4, 5)));
}
#[test]
fn runs_pure_digits() {
let gpf: Vec<i32> = b"12345".iter().map(|&b| b as i32).collect();
let numeric = build_numeric_map();
let alphanumeric = build_alphanumeric_map();
let iso646 = build_iso646_map();
let (n, an, _) = compute_runs(&gpf, &numeric, &alphanumeric, &iso646);
assert_eq!(n, vec![4, 4, 2, 2, 0]);
assert_eq!(an, vec![5, 4, 3, 2, 1]);
}
#[test]
fn runs_mixed_letters_digits() {
let gpf: Vec<i32> = b"A1B2".iter().map(|&b| b as i32).collect();
let numeric = build_numeric_map();
let alphanumeric = build_alphanumeric_map();
let iso646 = build_iso646_map();
let (n, an, _) = compute_runs(&gpf, &numeric, &alphanumeric, &iso646);
assert_eq!(n, vec![0, 0, 0, 0]);
assert_eq!(an, vec![4, 3, 2, 1]);
}
#[test]
fn encode_cc_matches_bwip_js_oracle_gtin() {
let r = encode_cc("(01)12345678901231", 4).unwrap();
assert_eq!(r.version, CcVersion::A);
assert_eq!(r.codewords, vec![33, 88, 99, 513, 522, 898, 225, 16]);
}
#[test]
fn encode_cc_matches_bwip_js_oracle_gtin_plus_batch() {
let r = encode_cc("(01)12345678901231(10)BATCH-1234567890", 4).unwrap();
assert_eq!(r.version, CcVersion::A);
assert_eq!(
r.codewords,
vec![33, 88, 99, 513, 522, 898, 801, 43, 637, 404, 662, 465, 402, 430, 428, 843, 296,],
);
}
#[test]
fn encode_cc_matches_bwip_js_oracle_ccb_gtin() {
let gpf = GpfBuild {
cdf: vec![false],
gpf: build_gpf("(01)12345678901231").unwrap(),
};
let payload = encode_payload(&gpf.gpf);
let cap_bits = 96u32;
let final_mode = final_mode_for(&gpf.gpf);
let mut full = Vec::with_capacity(cap_bits as usize);
full.extend_from_slice(&gpf.cdf);
full.extend_from_slice(&payload);
let padded = pad_with_fillpat(&full, cap_bits, final_mode);
let bytes = pack_ccb_bytes(&padded);
assert_eq!(bytes, vec![9, 42, 182, 45, 221, 101, 85, 1, 8, 66, 16, 132],);
}
#[test]
fn encode_cc_matches_bwip_js_oracle_method_10_alpha_value() {
let r = encode_cc("(10)ABCDEFGHIJ", 4).unwrap();
assert_eq!(r.version, CcVersion::A);
assert_eq!(r.codewords, vec![637, 227, 712, 316, 547, 779, 114, 132],);
}
#[test]
fn encode_cc_basic_gtin_picks_cca() {
let r = encode_cc("(01)12345678901231", 4).unwrap();
assert_eq!(r.version, CcVersion::A);
assert_eq!(r.columns, 4);
for cw in &r.codewords {
assert!(*cw < 928, "CC-A codeword {cw} ≥ 928");
}
}
#[test]
fn encode_cc_rejects_malformed_ai() {
let msg = match encode_cc("(0X)123", 4).unwrap_err() {
crate::error::Error::InvalidData(m) => m,
err => panic!(
"encode_cc(\"(0X)123\", 4) must reject as InvalidData (via gs1_cc wrapper of ParseError::InvalidAi); got {err:?}"
),
};
assert!(
msg.contains("gs1_cc:"),
"missing `gs1_cc:` wrapper prefix: {msg:?}"
);
assert!(
msg.contains("invalid AI"),
"missing `invalid AI` predicate (ParseError::InvalidAi Display): {msg:?}"
);
assert!(
msg.contains("\"0X\""),
"missing `\"0X\"` offending-AI Debug echo: {msg:?}"
);
assert!(
!msg.contains("empty GS1 input"),
"wrong arm — empty-input diagnostic leaked into malformed-AI reject: {msg:?}"
);
assert!(
!msg.contains("CC-A/CC-B capacity") && !msg.contains("CC-C selection"),
"wrong arm — CC-size diagnostic leaked into malformed-AI reject: {msg:?}"
);
}
#[test]
fn pwr928_row_0_is_unit() {
let t = build_pwr928();
assert_eq!(t[0], [0, 0, 0, 0, 0, 0, 1]);
}
#[test]
fn pwr928_row_1_is_two() {
let t = build_pwr928();
assert_eq!(t[1], [0, 0, 0, 0, 0, 0, 2]);
}
#[test]
fn pwr928_row_10_is_1024() {
let t = build_pwr928();
assert_eq!(t[10], [0, 0, 0, 0, 0, 1, 96]);
}
#[test]
fn select_ccc_size_known_anchors() {
let sz = select_ccc_size(80, 70);
assert_eq!(sz.columns, 1, "small payload → 1 column");
assert_eq!(sz.eclevel, 2, "log2(8) - 1 = 2");
assert_eq!(sz.byte_count, 10);
let sz = select_ccc_size(480, 200);
assert_eq!(sz.columns, 8, "linwidth=200 → 8 columns");
assert_eq!(sz.eclevel, 3, "log2(16) - 1 = 3");
assert_eq!(sz.byte_count, 63);
}
#[test]
fn select_ccc_size_eccws_32_and_64_brackets() {
let sz = select_ccc_size(1600, 400);
assert_eq!(
sz.eclevel, 4,
"m_pre=167 lands in 161..=320 → eccws=32 → log2(32)-1=4"
);
assert!(sz.columns >= 1);
assert!(sz.byte_count > 0);
let sz = select_ccc_size(3200, 600);
assert_eq!(
sz.eclevel, 5,
"m_pre=334 lands in 321..=833 → eccws=64 → log2(64)-1=5"
);
let sz = select_ccc_size(8160, 600);
assert_eq!(
sz.eclevel, 4,
"m_pre>833 fallback → eccws=32 → eclevel=4 (same as in-bracket eccws=32)"
);
}
#[test]
fn pack_ccb_bytes_simple() {
let bits = vec![
true, false, true, true, false, false, true, false, true, true, true, true, false,
false, false, false,
];
let bytes = pack_ccb_bytes(&bits);
assert_eq!(bytes, vec![0xB2, 0xF0]);
}
#[test]
fn pack_ccb_bytes_truncates_partial() {
let bits = vec![true; 12];
let bytes = pack_ccb_bytes(&bits);
assert_eq!(bytes.len(), 1);
assert_eq!(bytes[0], 0xFF);
}
#[test]
fn pack_cca_chunk_69_bits() {
let bits = vec![true; 69];
let cws = pack_cca_codewords(&bits);
assert_eq!(cws.len(), 7);
for cw in &cws {
assert!(*cw < 928, "codeword {cw} >= 928");
}
let mut total: u128 = 0;
for &cw in &cws {
total = total * 928 + cw as u128;
}
assert_eq!(total, (1u128 << 69) - 1);
}
#[test]
fn pack_cca_chunk_small_29_bits() {
let bits = vec![true; 29];
let cws = pack_cca_codewords(&bits);
assert_eq!(cws.len(), 3);
let mut total: u128 = 0;
for &cw in &cws {
total = total * 928 + cw as u128;
}
assert_eq!(total, (1u128 << 29) - 1);
}
#[test]
fn pack_cca_codewords_msb_first_bit_walk_direction() {
assert_eq!(pack_cca_codewords(&[]), Vec::<u32>::new());
assert_eq!(pack_cca_codewords(&[true]), vec![1u32]);
assert_eq!(pack_cca_codewords(&[false]), vec![0u32]);
assert_eq!(
pack_cca_codewords(&[true, false]),
vec![2u32],
"[true, false] MSB-first → 2"
);
assert_eq!(
pack_cca_codewords(&[false, true]),
vec![1u32],
"[false, true] MSB-first → 1"
);
assert_eq!(
pack_cca_codewords(&[true, false, false]),
vec![4u32],
"[true, false, false] MSB-first must give 4; direction-swap \
mutant would give 1 (=pwr928[0])"
);
assert_eq!(
pack_cca_codewords(&[false, false, true]),
vec![1u32],
"[false, false, true] MSB-first must give 1; direction-swap \
mutant would give 4 (=pwr928[2])"
);
assert_eq!(
pack_cca_codewords(&[true, true, false]),
vec![6u32],
"[true, true, false] = 6; direction-swap mutant → 3"
);
let a = pack_cca_codewords(&[true, false, false])[0];
let b = pack_cca_codewords(&[false, false, true])[0];
assert_ne!(a, b, "asymmetric inputs must give distinct codewords");
}
#[test]
fn pad_no_op_when_already_full() {
let payload = vec![true; 59];
let out = pad_with_fillpat(&payload, 59, CcMode::Numeric);
assert_eq!(out.len(), 59);
assert_eq!(out, payload);
}
#[test]
fn pad_appends_numeric_terminator_and_fill() {
let payload = vec![true, false, true, true];
let out = pad_with_fillpat(&payload, 16, CcMode::Numeric);
assert_eq!(out.len(), 16);
assert_eq!(&out[..4], &[true, false, true, true]);
assert_eq!(&out[4..8], &[false; 4]);
assert_eq!(&out[8..13], &[false, false, true, false, false]);
assert_eq!(&out[13..16], &[false, false, true]);
}
#[test]
fn pad_alphanumeric_mode_skips_terminator() {
let payload = vec![true, false];
let out = pad_with_fillpat(&payload, 12, CcMode::Alphanumeric);
assert_eq!(out.len(), 12);
assert_eq!(&out[..2], &[true, false]);
assert_eq!(&out[2..7], &[false, false, true, false, false]);
}
#[test]
fn build_gpf_single_ai() {
let gpf = build_gpf("(01)12345678901231").unwrap();
assert_eq!(gpf.len(), 16);
assert!(
!gpf.contains(&FNC1),
"fixed-length AI should not append FNC1"
);
assert_eq!(gpf[0], b'0' as i32);
assert_eq!(gpf[1], b'1' as i32);
assert_eq!(gpf[2], b'1' as i32);
assert_eq!(gpf[15], b'1' as i32);
}
#[test]
fn build_gpf_variable_then_fixed() {
let gpf = build_gpf("(10)BATCH1(01)12345678901231").unwrap();
assert_eq!(gpf.len(), 25);
assert_eq!(gpf[8], FNC1);
}
#[test]
fn select_size_short_payload() {
let (v, idx, cap) = select_cca_ccb_size(50, 2).unwrap();
assert_eq!(v, CcVersion::A);
assert_eq!(cap, 59);
assert_eq!(idx, 6); }
#[test]
fn select_size_falls_through_to_b() {
let (v, _, cap) = select_cca_ccb_size(250, 2).unwrap();
assert_eq!(v, CcVersion::B);
assert_eq!(cap, 256); }
#[test]
fn select_size_returns_none_when_too_big() {
assert!(select_cca_ccb_size(1500, 4).is_none());
}
#[test]
fn select_cca_ccb_size_per_column_count() {
let (v, _idx, cap) = select_cca_ccb_size(100, 3).unwrap();
assert_eq!(v, CcVersion::A, "100 bits in 3-col → CC-A");
assert!(cap >= 100, "cap {cap} must be ≥ payload 100");
let (v, _idx, cap) = select_cca_ccb_size(50, 4).unwrap();
assert_eq!(v, CcVersion::A, "50 bits in 4-col → CC-A");
assert!(cap >= 50);
let (v, _idx, cap) = select_cca_ccb_size(500, 4).unwrap();
assert_eq!(v, CcVersion::B, "500 bits in 4-col → CC-B");
assert!(cap >= 500);
assert!(
select_cca_ccb_size(50, 1).is_none(),
"cccolumns=1 < 2 must return None via checked_sub"
);
assert!(
select_cca_ccb_size(50, 0).is_none(),
"cccolumns=0 must return None"
);
assert!(
select_cca_ccb_size(50, 5).is_none(),
"cccolumns=5 (col_idx=3) exceeds BITCAPS_A[0..3] length → None"
);
}
#[test]
fn encode_payload_pure_digit_pair() {
let gpf: Vec<i32> = b"12".iter().map(|&b| b as i32).collect();
let bits = encode_payload(&gpf);
assert_eq!(bits.len(), 7);
assert_eq!(bits, vec![false, false, true, false, true, false, true],);
}
#[test]
fn encode_payload_letter_in_alphanumeric() {
let gpf: Vec<i32> = b"A".iter().map(|&b| b as i32).collect();
let bits = encode_payload(&gpf);
assert_eq!(bits.len(), 10);
assert_eq!(&bits[..4], &[false; 4]);
assert_eq!(&bits[4..10], &[true, false, false, false, false, false],);
}
#[test]
fn encode_payload_fnc1_separator() {
let gpf: Vec<i32> = vec![b'1' as i32, FNC1, b'A' as i32];
let bits = encode_payload(&gpf);
assert_eq!(bits.len(), 17);
}
#[test]
fn runs_with_iso646_only() {
let gpf: Vec<i32> = b"Ab".iter().map(|&b| b as i32).collect();
let numeric = build_numeric_map();
let alphanumeric = build_alphanumeric_map();
let iso646 = build_iso646_map();
let (_, an, niso) = compute_runs(&gpf, &numeric, &alphanumeric, &iso646);
assert_eq!(an, vec![1, 0]);
assert_eq!(niso, vec![1, 0]);
}
#[test]
fn runs_fnc1_in_pair_and_sentinel_propagation() {
let numeric = build_numeric_map();
let alphanumeric = build_alphanumeric_map();
let iso646 = build_iso646_map();
let gpf_fnc1: Vec<i32> = vec![FNC1, b'0' as i32, FNC1];
let (n_fnc1, an_fnc1, _) = compute_runs(&gpf_fnc1, &numeric, &alphanumeric, &iso646);
assert_eq!(
n_fnc1,
vec![2, 2, 0],
"FNC1 must convert to b'^' for numeric-pair lookups"
);
assert_eq!(
an_fnc1,
vec![3, 2, 1],
"FNC1 + digit + FNC1 alphanumeric chain"
);
let gpf_abc: Vec<i32> = b"ABC".iter().map(|&b| b as i32).collect();
let (_, _, niso_abc) = compute_runs(&gpf_abc, &numeric, &alphanumeric, &iso646);
assert_eq!(
niso_abc,
vec![10002, 10001, 10000],
"pure alphanumeric chain: sentinel 9999 propagates +1 per position"
);
let gpf_12a: Vec<i32> = b"12A".iter().map(|&b| b as i32).collect();
let (n_12a, _, _) = compute_runs(&gpf_12a, &numeric, &alphanumeric, &iso646);
assert_eq!(
n_12a,
vec![2, 0, 0],
"trailing non-numeric: numeric_runs falls to 0 mid-chain"
);
let (n_empty, an_empty, niso_empty) = compute_runs(&[], &numeric, &alphanumeric, &iso646);
assert_eq!(n_empty, Vec::<u32>::new(), "empty gpf → empty numeric_runs");
assert_eq!(
an_empty,
Vec::<u32>::new(),
"empty gpf → empty alphanumeric_runs"
);
assert_eq!(
niso_empty,
Vec::<u32>::new(),
"empty gpf → empty next_iso646_only"
);
}
#[test]
fn iso646_map_digits_letters_punct() {
let m = build_iso646_map();
assert_eq!(m.get(&(b'0' as i32)), Some(&(5, 5)));
assert_eq!(m.get(&(b'A' as i32)), Some(&(64, 7)));
assert_eq!(m.get(&(b'Z' as i32)), Some(&(89, 7)));
assert_eq!(m.get(&(b'a' as i32)), Some(&(90, 7)));
assert_eq!(m.get(&(b'z' as i32)), Some(&(115, 7)));
assert_eq!(m.get(&(b'!' as i32)), Some(&(232, 8)));
assert_eq!(m.get(&(b'"' as i32)), Some(&(233, 8)));
assert_eq!(m.get(&(b'%' as i32)), Some(&(234, 8)));
assert_eq!(m.get(&(b'/' as i32)), Some(&(244, 8)));
assert_eq!(m.get(&(b':' as i32)), Some(&(245, 8)));
assert_eq!(m.get(&(b'?' as i32)), Some(&(250, 8)));
assert_eq!(m.get(&(b'_' as i32)), Some(&(251, 8)));
assert_eq!(m.get(&(b' ' as i32)), Some(&(252, 8)));
assert_eq!(m.get(&LATCH_NUMERIC), Some(&(0, 3)));
assert_eq!(m.get(&LATCH_ALPHANUMERIC), Some(&(4, 5)));
}
#[test]
fn push_bits_msb_first_full_boundaries() {
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0, 4);
assert_eq!(out, vec![false; 4]);
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0xFF, 8);
assert_eq!(out, vec![true; 8]);
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0x80, 8);
assert_eq!(out, [&[true][..], &[false; 7][..]].concat());
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0b11_1010, 4);
assert_eq!(out, vec![true, false, true, false]);
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 1, 1);
assert_eq!(out, vec![true]);
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0, 1);
assert_eq!(out, vec![false]);
}
#[test]
fn default_cc_columns_covers_every_match_arm() {
for t in [
"ean13",
"upca",
"gs1-128",
"databaromni",
"databartruncated",
"databarexpanded",
"databarexpandedstacked",
] {
assert_eq!(default_cc_columns(t), Some(4), "{t}");
}
assert_eq!(default_cc_columns("ean8"), Some(3));
assert_eq!(default_cc_columns("databarlimited"), Some(3));
assert_eq!(default_cc_columns("upce"), Some(2));
assert_eq!(default_cc_columns("databarstacked"), Some(2));
assert_eq!(default_cc_columns("databarstackedomni"), Some(2));
assert_eq!(default_cc_columns("not-a-symbology"), None);
assert_eq!(default_cc_columns(""), None);
}
#[test]
fn pack_ccb_bytes_msb_first_8bit_packing() {
assert_eq!(pack_ccb_bytes(&[]), Vec::<u8>::new());
assert_eq!(pack_ccb_bytes(&[true; 8]), vec![0xFFu8]);
assert_eq!(pack_ccb_bytes(&[false; 8]), vec![0x00u8]);
let msb_only = [true, false, false, false, false, false, false, false];
assert_eq!(pack_ccb_bytes(&msb_only), vec![0x80u8]);
let lsb_only = [false, false, false, false, false, false, false, true];
assert_eq!(pack_ccb_bytes(&lsb_only), vec![0x01u8]);
let two_bytes = [
true, true, false, false, true, false, true, false, false, true, false, true, false, false, true, true, ];
assert_eq!(pack_ccb_bytes(&two_bytes), vec![0xCAu8, 0x53]);
let nine_bits = [true; 9];
assert_eq!(
pack_ccb_bytes(&nine_bits).len(),
1,
"9 bits → 1 byte (trailing bit dropped by chunks_exact)"
);
assert_eq!(pack_ccb_bytes(&nine_bits), vec![0xFFu8]);
assert_eq!(pack_ccb_bytes(&[true; 7]), Vec::<u8>::new());
}
#[test]
fn pair_byte_maps_fnc1_to_numeric_sentinel() {
assert_eq!(pair_byte(FNC1), NUMERIC_FNC1);
assert_eq!(pair_byte(b'0' as i32), b'0');
assert_eq!(pair_byte(b'9' as i32), b'9');
assert_eq!(pair_byte(0), 0);
}
#[test]
fn pad_with_fillpat_terminator_and_fillpat_layout() {
assert_eq!(
pad_with_fillpat(&[], 0, CcMode::Numeric),
Vec::<bool>::new(),
"cap=0 → empty output"
);
let four_true = vec![true, true, true, true];
assert_eq!(
pad_with_fillpat(&four_true, 4, CcMode::Numeric),
four_true,
"payload.len() == cap → early return"
);
assert_eq!(
pad_with_fillpat(&[], 4, CcMode::Numeric),
vec![false, false, false, false],
"cap=4 Numeric: 4 zeros (terminator); no FILLPAT room"
);
assert_eq!(
pad_with_fillpat(&[], 8, CcMode::Numeric),
vec![false, false, false, false, false, false, true, false],
"cap=8 Numeric: 4 terminator zeros + first 4 of FILLPAT [0,0,1,0]"
);
let two_true = vec![true, true];
assert_eq!(
pad_with_fillpat(&two_true, 7, CcMode::Numeric),
vec![true, true, false, false, false, false, false],
"cap=7 Numeric, 2-bit payload: payload + terminator + 1 FILLPAT bit"
);
let one_true = vec![true];
assert_eq!(
pad_with_fillpat(&one_true, 6, CcMode::Alphanumeric),
vec![true, false, false, true, false, false],
"cap=6 Alphanumeric: payload + 5 FILLPAT bits (no terminator)"
);
assert_eq!(
pad_with_fillpat(&one_true, 6, CcMode::Iso646),
vec![true, false, false, true, false, false],
"cap=6 Iso646: same as Alphanumeric (no Numeric terminator)"
);
for payload_len in 0..=10 {
for cap_bits in (payload_len as u32)..=20 {
let payload = vec![false; payload_len];
let out = pad_with_fillpat(&payload, cap_bits, CcMode::Numeric);
assert_eq!(
out.len(),
cap_bits as usize,
"payload_len={payload_len}, cap_bits={cap_bits}: output must be exactly cap_bits long"
);
}
}
}
#[test]
fn pair_byte_fnc1_sentinel_vs_regular_byte() {
assert_eq!(pair_byte(FNC1), b'^', "FNC1 (-1) → '^' (NUMERIC_FNC1)");
assert_eq!(
pair_byte(FNC1),
94,
"FNC1 sentinel numeric value is exactly 94"
);
for d in b'0'..=b'9' {
assert_eq!(
pair_byte(d as i32),
d,
"digit '{}' passes through unchanged",
d as char
);
}
assert_eq!(pair_byte(b'A' as i32), b'A');
assert_eq!(pair_byte(b'z' as i32), b'z');
assert_eq!(pair_byte(0), 0, "NUL passes through (and ≠ FNC1)");
assert_eq!(pair_byte(b'!' as i32), b'!');
assert_eq!(pair_byte(b'~' as i32), b'~');
assert_eq!(pair_byte(127), 127, "DEL passes through");
assert_eq!(pair_byte(255), 255, "0xFF passes through");
assert_eq!(pair_byte(94), 94, "literal 94 ≠ FNC1 path");
assert_eq!(pair_byte(FNC1), 94, "FNC1 → 94 via NUMERIC_FNC1");
assert_eq!(
pair_byte(-2),
-2_i32 as u8,
"non-FNC1 negative wraps via `as u8`"
);
assert_eq!(pair_byte(-2), 254);
}
#[test]
fn final_mode_for_terminal_state_per_input_class() {
assert_eq!(final_mode_for(&[]), CcMode::Numeric);
assert_eq!(
final_mode_for(&[b'1' as i32, b'2' as i32]),
CcMode::Numeric,
"digit pair must keep terminal mode at Numeric"
);
assert_eq!(
final_mode_for(&[b'5' as i32]),
CcMode::Numeric,
"single trailing digit keeps Numeric"
);
assert_eq!(
final_mode_for(&[b'A' as i32]),
CcMode::Alphanumeric,
"single trailing non-digit must terminate Alphanumeric; \
a mutant that swapped the single-char digit guard would \
keep Numeric"
);
assert_eq!(
final_mode_for(&[b'a' as i32]),
CcMode::Iso646,
"lowercase must terminate Iso646; a mutant that swapped \
the iso646-only condition (e.g. dropped the `!alphanumeric \
.contains(&c)` guard) would terminate in Alphanumeric"
);
assert_eq!(
final_mode_for(&[FNC1]),
CcMode::Numeric,
"FNC1 in Alphanumeric must reset to Numeric; a mutant that \
dropped the FNC1 arm would leave the terminal state as \
Alphanumeric"
);
assert_eq!(
final_mode_for(&[b'A' as i32, b'B' as i32]),
CcMode::Alphanumeric,
"two uppercase letters must terminate Alphanumeric, not \
Iso646; mutant dropping the `&& !alphanumeric.contains` \
guard would route into Iso646"
);
}
#[test]
fn encode_payload_state_machine_fingerprint_pinned() {
fn fp(bits: &[bool]) -> (usize, u64) {
let mut s: u64 = 0;
for (i, &b) in bits.iter().enumerate() {
let v = u64::from(b);
s = s.wrapping_add(
v.wrapping_mul((i as u64).wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
(bits.len(), s)
}
let cases: &[(&str, Vec<i32>, (usize, u64))] = &[
("empty", vec![], FP_EMPTY),
(
"num12",
b"123456789012".iter().map(|&b| b as i32).collect(),
FP_NUM12,
),
("digit_tail", vec![b'5' as i32], FP_DIGIT_TAIL),
(
"pair_then_letter",
b"12A".iter().map(|&b| b as i32).collect(),
FP_PAIR_LETTER,
),
(
"fnc1_mid_alpha",
vec![b'A' as i32, FNC1, b'B' as i32],
FP_FNC1_MID,
),
("iso_lower", vec![b'a' as i32], FP_ISO_LOWER),
(
"iso_long",
b"aaaaaaaa".iter().map(|&b| b as i32).collect(),
FP_ISO_LONG,
),
(
"alpha_then_num6",
b"A123456".iter().map(|&b| b as i32).collect(),
FP_ALPHA_NUM6,
),
(
"alpha_4digit_tail",
b"A1234".iter().map(|&b| b as i32).collect(),
FP_ALPHA_4TAIL,
),
(
"iso_then_num",
b"a1111111111".iter().map(|&b| b as i32).collect(),
FP_ISO_NUM,
),
(
"iso_back_alpha",
b"aABCDEFGHIJ".iter().map(|&b| b as i32).collect(),
FP_ISO_BACK_ALPHA,
),
(
"fnc1_iso",
vec![b'a' as i32, FNC1, b'b' as i32],
FP_FNC1_ISO,
),
];
for (tag, gpf, want) in cases {
let bits = encode_payload(gpf);
let got = fp(&bits);
eprintln!("CAP encode_payload/{tag} -> {got:?}");
assert_eq!(got, *want, "fingerprint changed for {tag}");
}
}
const FP_EMPTY: (usize, u64) = (0, 0);
const FP_NUM12: (usize, u64) = (42, 1382961031481);
const FP_DIGIT_TAIL: (usize, u64) = (7, 31853229132);
const FP_PAIR_LETTER: (usize, u64) = (17, 71669765547);
const FP_FNC1_MID: (usize, u64) = (25, 276061319144);
const FP_ISO_LOWER: (usize, u64) = (16, 151302838377);
const FP_ISO_LONG: (usize, u64) = (65, 3161432991351);
const FP_ALPHA_NUM6: (usize, u64) = (34, 666263376011);
const FP_ALPHA_4TAIL: (usize, u64) = (27, 416746414477);
const FP_ISO_NUM: (usize, u64) = (54, 1133444069947);
const FP_ISO_BACK_ALPHA: (usize, u64) = (81, 3835659674645);
const FP_FNC1_ISO: (usize, u64) = (37, 886581544174);
#[test]
fn final_mode_for_state_machine_fingerprint_pinned() {
fn mode_code(m: CcMode) -> u64 {
match m {
CcMode::Numeric => 1,
CcMode::Alphanumeric => 2,
CcMode::Iso646 => 3,
}
}
fn fp(gpf: &[i32]) -> (usize, u64) {
let m = mode_code(final_mode_for(gpf));
let mut s: u64 = 0;
let v = m;
s = s.wrapping_add(v.wrapping_mul((0_u64).wrapping_add(1).wrapping_mul(2_654_435_761)));
(gpf.len(), s)
}
let cases: &[(&str, Vec<i32>, (usize, u64))] = &[
("empty", vec![], FM_EMPTY),
(
"num3pair",
b"123456".iter().map(|&b| b as i32).collect(),
FM_NUM3PAIR,
),
(
"num_pair_break",
b"1A2345".iter().map(|&b| b as i32).collect(),
FM_NUM_PAIR_BREAK,
),
(
"alpha_run6",
b"A123456".iter().map(|&b| b as i32).collect(),
FM_ALPHA_RUN6,
),
(
"alpha_run5_no_switch",
b"A12345B".iter().map(|&b| b as i32).collect(),
FM_ALPHA_RUN5_NO_SWITCH,
),
(
"alpha_tail4",
b"A1234".iter().map(|&b| b as i32).collect(),
FM_ALPHA_TAIL4,
),
(
"alpha_tail3_no_switch",
b"A123".iter().map(|&b| b as i32).collect(),
FM_ALPHA_TAIL3_NO_SWITCH,
),
("alpha_fnc1", vec![b'A' as i32, FNC1], FM_ALPHA_FNC1),
(
"iso_chain",
b"abc".iter().map(|&b| b as i32).collect(),
FM_ISO_CHAIN,
),
("iso_fnc1", vec![b'a' as i32, FNC1], FM_ISO_FNC1),
(
"iso_to_num",
b"a1234567890123".iter().map(|&b| b as i32).collect(),
FM_ISO_TO_NUM,
),
(
"iso_to_alpha",
b"aABCDEFGHIJ".iter().map(|&b| b as i32).collect(),
FM_ISO_TO_ALPHA,
),
("single_digit", vec![b'5' as i32], FM_SINGLE_DIGIT),
];
for (tag, gpf, want) in cases {
let got = fp(gpf);
eprintln!("CAP final_mode_for/{tag} -> {got:?}");
assert_eq!(got, *want, "fingerprint changed for {tag}");
}
}
const FM_EMPTY: (usize, u64) = (0, 2654435761);
const FM_NUM3PAIR: (usize, u64) = (6, 2654435761);
const FM_NUM_PAIR_BREAK: (usize, u64) = (6, 2654435761);
const FM_ALPHA_RUN6: (usize, u64) = (7, 2654435761);
const FM_ALPHA_RUN5_NO_SWITCH: (usize, u64) = (7, 5308871522);
const FM_ALPHA_TAIL4: (usize, u64) = (5, 2654435761);
const FM_ALPHA_TAIL3_NO_SWITCH: (usize, u64) = (4, 5308871522);
const FM_ALPHA_FNC1: (usize, u64) = (2, 2654435761);
const FM_ISO_CHAIN: (usize, u64) = (3, 7963307283);
const FM_ISO_FNC1: (usize, u64) = (2, 2654435761);
const FM_ISO_TO_NUM: (usize, u64) = (14, 2654435761);
const FM_ISO_TO_ALPHA: (usize, u64) = (11, 5308871522);
const FM_SINGLE_DIGIT: (usize, u64) = (1, 2654435761);
#[test]
fn gs1_cc_sentinel_consts_pinned() {
assert_eq!(FNC1, -1, "FNC1 sentinel (BWIPP gs1_cc_fnc1) must remain -1");
assert_eq!(LATCH_NUMERIC, -2, "LATCH_NUMERIC sentinel must remain -2");
assert_eq!(
LATCH_ALPHANUMERIC, -3,
"LATCH_ALPHANUMERIC sentinel must remain -3"
);
assert_eq!(LATCH_ISO646, -4, "LATCH_ISO646 sentinel must remain -4");
}
#[test]
fn select_ccc_size_boundary_pinned() {
let sz = select_ccc_size(80, 200);
assert_eq!(
sz.columns, 8,
"(1) linwidth=200 → raw 8, no L453 clamp; `< 30` mutant would force 30"
);
let sz = select_ccc_size(80, 562);
assert_eq!(sz.columns, 30, "(2) linwidth=562 → raw 30 stays 30");
assert_eq!(sz.eclevel, 2);
assert_eq!(sz.byte_count, 94);
let sz = select_ccc_size(80, 579);
assert_eq!(
sz.columns, 30,
"(3) linwidth=579 → raw 31 MUST clamp to 30; `==` mutant leaves 31"
);
assert_eq!(sz.eclevel, 2);
assert_eq!(
sz.byte_count, 94,
"(3) byte_count must match (2) — `==` mutant on L453 yields 98 instead"
);
let sz = select_ccc_size(2544, 200);
assert_eq!(
sz.columns, 10,
"(4) loop must exit at cccolumns=10 where ceil(300/10)=30 exactly; \
`> 30` → `>= 30` mutant bumps to 11"
);
assert_eq!(sz.eclevel, 4);
assert_eq!(
sz.byte_count, 318,
"(4) byte_count 318 confirms cccolumns=10; `>=` mutant yields 327"
);
let sz = select_ccc_size(8640, 200);
assert_eq!(
sz.columns, 30,
"(5) loop MUST stop at cccolumns=30; `< 30` → `<= 30` mutant bumps to 31"
);
assert_eq!(sz.eclevel, 4);
assert_eq!(
sz.byte_count, 1110,
"(5) byte_count 1110 confirms cccolumns=30 cap held"
);
}
#[test]
fn cc_version_as_str_exact() {
assert_eq!(CcVersion::A.as_str(), "a", "CC-A version string");
assert_eq!(CcVersion::B.as_str(), "b", "CC-B version string");
assert_eq!(CcVersion::C.as_str(), "c", "CC-C version string");
assert_ne!(CcVersion::A.as_str(), CcVersion::B.as_str());
assert_ne!(CcVersion::B.as_str(), CcVersion::C.as_str());
}
#[test]
fn build_gpf_trailing_variable_ai_no_fnc1() {
let gpf = build_gpf("(01)12345678901231(10)BATCH1").unwrap();
assert_eq!(gpf.len(), 24, "no FNC1 anywhere → 16 + 8 = 24 bytes");
assert!(
!gpf.contains(&FNC1),
"last element is variable but has no follower → no trailing FNC1; \
`<=` / `i*1` mutants add one, `||` adds two"
);
assert_eq!(&gpf[0..2], &[b'0' as i32, b'1' as i32], "leading AI 01");
assert_eq!(
&gpf[16..18],
&[b'1' as i32, b'0' as i32],
"AI 10 must follow the GTIN immediately (no `||`-inserted FNC1)"
);
}
#[test]
fn final_mode_for_residual_boundary_killers() {
assert_eq!(
final_mode_for(&[b'0' as i32, b'A' as i32]),
CcMode::Alphanumeric,
"(554) pair ['0','A'] is not numeric → Alpha; `gpf[i*1]` mutant \
reads ['0','0'] (numeric) → would stay Numeric"
);
assert_eq!(
final_mode_for(&[b'a' as i32, b'0' as i32]),
CcMode::Iso646,
"(589 && / first >=) 'a' then single '0' stays Iso646; `||` or \
`numeric_runs<4` mutant switches to Numeric"
);
assert_eq!(
final_mode_for(&[
b'a' as i32,
b'0' as i32,
b'0' as i32,
b'0' as i32,
b'0' as i32
]),
CcMode::Numeric,
"(589 second >=) 'a'+4 digits switches Iso646→Numeric; \
`next_iso646_only<10` mutant stays Iso646"
);
assert_eq!(
final_mode_for(&[b'a' as i32, b'0' as i32]),
CcMode::Iso646,
"(591 && / first >=) 'a'+'0' stays Iso646; `||` or \
`alphanumeric_runs<5` mutant back-latches to Alphanumeric"
);
}
#[test]
fn final_mode_for_equivalence_notes_553_588() {
assert_eq!(final_mode_for(&[b'1' as i32, b'2' as i32]), CcMode::Numeric);
assert_eq!(
final_mode_for(&[b'1' as i32, b'A' as i32, b'2' as i32, b'3' as i32]),
CcMode::Alphanumeric
);
assert_eq!(final_mode_for(&[FNC1, b'1' as i32]), CcMode::Numeric);
assert_eq!(final_mode_for(&[b'a' as i32, FNC1]), CcMode::Numeric);
assert_eq!(
final_mode_for(&[b'a' as i32, FNC1, b'1' as i32, b'1' as i32]),
CcMode::Numeric
);
}
#[test]
fn encode_payload_residual_bit_killers() {
let zero = encode_payload(&[b'0' as i32]);
assert_eq!(
zero,
vec![false, false, true, false, false, true, false],
"(781 `<`) digit '0' → 7-bit single-digit pair; `==`/`<=` mutant \
treats '0' as non-digit → 9-bit latch+alpha"
);
let nine = encode_payload(&[b'9' as i32]);
assert_eq!(
nine,
vec![true, true, true, false, true, false, true],
"(781 `>`) digit '9' → 7-bit single-digit pair; `==`/`>=` mutant \
treats '9' as non-digit → 9-bit latch+alpha"
);
let a0f = encode_payload(&[b'A' as i32, b'0' as i32, FNC1]);
assert_eq!(
a0f,
vec![
false, false, false, false, true, false, false, false, false, false, false, false,
true, false, true, false, true, true, true, true,
],
"(814 `&&`) exact 20-bit stream for \"A0\"+FNC1; `||` mutant \
produces a different tail"
);
let a0 = encode_payload(&[b'a' as i32, b'0' as i32]);
assert_eq!(
a0.len(),
21,
"(838 `&&`) \"a0\" stays Iso646 → 21 bits; `||` mutant back-latches \
to Alphanumeric → 26 bits"
);
assert_eq!(
a0,
vec![
false, false, false, false, false, false, true, false, false, true, false, true,
true, false, true, false, false, false, true, false, true,
],
"(838 `&&`) exact 21-bit stream for \"a0\""
);
let hash = encode_payload(&[b'#' as i32]);
assert_eq!(
hash,
vec![false, false, false, false, false, false, true, false, false],
"(847 `+=`) '#' reaches the Iso646 unencodable fallback and the \
real `i += 1` produces a 9-bit stream; `-=`/`*=` mutants \
panic/loop"
);
}
#[test]
fn encode_payload_pack_runs_equivalence_notes() {
assert_eq!(
encode_payload(&[b'A' as i32]),
vec![false, false, false, false, true, false, false, false, false, false],
"'A' single tail → 4-bit latch + 6-bit alpha; `&&` mutant routes \
through the (missing) ['A','^'] pair and lands on the same latch"
);
assert_eq!(
encode_payload(&[FNC1]),
{
let mut v = vec![false, false, false, false];
push_bits(&mut v, 15, 5);
v
},
"FNC1 single tail latches the same way under the `&&` mutant"
);
assert_eq!(
pack_ccb_bytes(&[true, true, false, false, true, false, true, false]),
vec![0xCAu8],
"MSB-first pack of 0xCA; OR vs XOR with a cleared low bit is identical"
);
let numeric = build_numeric_map();
let alphanumeric = build_alphanumeric_map();
let iso646 = build_iso646_map();
let (n_runs, _, _) = compute_runs(
&[b'1' as i32, b'2' as i32, b'3' as i32],
&numeric,
&alphanumeric,
&iso646,
);
assert_eq!(
n_runs,
vec![2, 2, 0],
"trailing position numeric_runs is 0 (pair second byte is NUL); \
the `<=`/`i*1` mutants cannot make it non-zero"
);
}
#[test]
fn select_ccc_size_453_ge_equivalence_note() {
let sz = select_ccc_size(80, 562);
assert_eq!(
sz.columns, 30,
"raw=30 stays 30 under both `>` and the `>=` mutant (clamp 30→30 is a no-op)"
);
let sz = select_ccc_size(80, 579);
assert_eq!(sz.columns, 30, "raw=31 clamps to 30 under both operators");
}
}