#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use alloc::vec::Vec;
use core::cmp::min;
use core::fmt::{Display, Error, Formatter};
use crate::QrCode;
use crate::bits::{self, Bits};
use crate::optimize::{Optimizer, Parser, Segment, total_encoded_len};
use crate::types::{EcLevel, QrError, QrResult, Version};
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub struct StructuredAppend<'a> {
symbols: u8,
payload: &'a [u8],
parity: u8,
}
impl<'a> StructuredAppend<'a> {
pub fn new(symbols: u8, payload: &'a [u8]) -> QrResult<Self> {
if !(2..=16).contains(&symbols) {
return Err(QrError::InvalidStructuredAppend { value: symbols });
}
let parity = payload.iter().fold(0u8, |acc, &byte| acc ^ byte);
Ok(Self { symbols, payload, parity })
}
#[must_use]
pub const fn symbols(&self) -> u8 {
self.symbols
}
#[must_use]
pub const fn parity(&self) -> u8 {
self.parity
}
#[must_use]
pub fn payload(&self) -> &'a [u8] {
self.payload
}
pub fn encode(&self, ec: EcLevel) -> QrResult<Vec<QrCode>> {
let n = usize::from(self.symbols);
let chunk = self.payload.len().div_ceil(n);
let mut codes = Vec::with_capacity(n);
for i in 0..n {
let start = min(i * chunk, self.payload.len());
let end = min((i + 1) * chunk, self.payload.len());
let piece = &self.payload[start..end];
let code = encode_one_symbol(piece, i as u8 + 1, self.symbols, self.parity, ec)?;
codes.push(code);
}
Ok(codes)
}
}
fn encode_one_symbol(data: &[u8], position: u8, total: u8, parity: u8, ec: EcLevel) -> QrResult<QrCode> {
let segments = Parser::new(data).collect::<Vec<Segment>>();
for &checkpoint in &[Version::Normal(9), Version::Normal(26), Version::Normal(40)] {
let opt = Optimizer::new(segments.iter().copied(), checkpoint).collect::<Vec<_>>();
let total_len = total_encoded_len(&opt, checkpoint) + 20;
if total_len <= bits::data_capacity_bits(checkpoint, ec)? {
let version = bits::find_min_version(total_len, ec);
let mut bits = Bits::new(version);
bits.reserve(total_len);
bits.push_structured_append_header(position, total, parity)?;
bits.push_segments(data, opt.into_iter())?;
bits.push_terminator(ec)?;
return QrCode::with_bits(bits, ec);
}
}
Err(QrError::DataTooLong)
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SaError {
Incomplete,
DuplicatePosition(u8),
CountMismatch,
ParityMismatch,
OutOfRange(u8),
NotStructuredAppend,
MalformedStream,
}
impl Display for SaError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
Self::Incomplete => f.write_str("incomplete Structured Append sequence (symbols missing)"),
Self::DuplicatePosition(position) => {
write!(f, "duplicate Structured Append position {position}")
}
Self::CountMismatch => f.write_str("Structured Append symbols disagree on the total count"),
Self::ParityMismatch => f.write_str("Structured Append symbols disagree on the parity byte"),
Self::OutOfRange(value) => {
write!(f, "Structured Append value {value} out of range (total 2..=16, position 1..=total)")
}
Self::NotStructuredAppend => f.write_str("not a Structured Append symbol (no `0011` mode indicator)"),
Self::MalformedStream => f.write_str("malformed Structured Append bit stream"),
}
}
}
impl ::core::error::Error for SaError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SaSymbol<'a> {
pub position: u8,
pub total: u8,
pub parity: u8,
pub data: &'a [u8],
}
pub fn reassemble(parts: &[SaSymbol<'_>]) -> Result<Vec<u8>, SaError> {
let Some(first) = parts.first() else { return Err(SaError::Incomplete) };
if !(2..=16).contains(&first.total) {
return Err(SaError::OutOfRange(first.total));
}
let total = first.total;
let parity = first.parity;
for p in parts {
if p.total != total {
return Err(SaError::CountMismatch);
}
if p.parity != parity {
return Err(SaError::ParityMismatch);
}
if !(1..=total).contains(&p.position) {
return Err(SaError::OutOfRange(p.position));
}
}
if parts.len() != usize::from(total) {
return Err(SaError::Incomplete);
}
let mut seen = [false; 16];
for p in parts {
let idx = usize::from(p.position - 1);
if seen[idx] {
return Err(SaError::DuplicatePosition(p.position));
}
seen[idx] = true;
}
let mut ordered: Vec<&SaSymbol<'_>> = parts.iter().collect();
ordered.sort_by_key(|s| s.position);
let mut out = Vec::new();
for s in ordered {
out.extend_from_slice(s.data);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::StructuredAppend;
use crate::types::{EcLevel, QrError};
use alloc::{vec, vec::Vec};
#[test]
fn test_new_rejects_out_of_range() {
assert_eq!(StructuredAppend::new(1, b"x").err(), Some(QrError::InvalidStructuredAppend { value: 1 }));
assert_eq!(StructuredAppend::new(17, b"x").err(), Some(QrError::InvalidStructuredAppend { value: 17 }));
}
#[test]
fn test_new_accepts_bounds() {
assert!(StructuredAppend::new(2, b"x").is_ok());
assert!(StructuredAppend::new(16, b"x").is_ok());
}
#[test]
fn test_parity_xor() {
assert_eq!(StructuredAppend::new(3, &[0x01, 0x02, 0x03]).unwrap().parity(), 0x00);
assert_eq!(StructuredAppend::new(2, &[0xff, 0x0f]).unwrap().parity(), 0xf0);
let bytes: Vec<u8> = (0u8..=255).collect();
assert_eq!(StructuredAppend::new(2, &bytes).unwrap().parity(), 0);
}
#[test]
fn test_parity_empty() {
assert_eq!(StructuredAppend::new(2, b"").unwrap().parity(), 0);
}
#[test]
fn test_encode_count_and_versions() {
let payload = b"Split this payload across several QR symbols for resilience.";
let codes = StructuredAppend::new(3, payload).unwrap().encode(EcLevel::M).unwrap();
assert_eq!(codes.len(), 3);
for code in &codes {
assert!(!code.info().version().is_micro(), "Structured Append must be Normal QR");
}
}
#[test]
fn test_encode_all_normal_across_counts() {
let payload = b"the quick brown fox jumps over the lazy dog";
for n in 2..=16u8 {
let codes = StructuredAppend::new(n, payload).unwrap().encode(EcLevel::L).unwrap();
assert_eq!(codes.len(), usize::from(n));
assert!(codes.iter().all(|c| !c.info().version().is_micro()), "n={n} produced a Micro QR");
}
}
#[test]
fn test_encode_empty_payload() {
let codes = StructuredAppend::new(2, b"").unwrap().encode(EcLevel::M).unwrap();
assert_eq!(codes.len(), 2);
assert!(codes.iter().all(|c| !c.info().version().is_micro()));
}
#[test]
fn test_encode_deterministic() {
let payload = b"deterministic encoding";
let a = StructuredAppend::new(3, payload).unwrap().encode(EcLevel::M).unwrap();
let b = StructuredAppend::new(3, payload).unwrap().encode(EcLevel::M).unwrap();
for (a, b) in a.iter().zip(b.iter()) {
assert_eq!(a.to_colors(), b.to_colors());
}
}
#[test]
fn test_encode_too_long() {
let payload = vec![0u8; 16 * 4000];
let result = StructuredAppend::new(16, &payload).unwrap().encode(EcLevel::H);
assert_eq!(result.err(), Some(QrError::DataTooLong));
}
}
#[cfg(test)]
mod reassemble_tests {
use super::{SaError, SaSymbol, reassemble};
use alloc::vec::Vec;
fn sym(position: u8, total: u8, parity: u8, data: &[u8]) -> SaSymbol<'_> {
SaSymbol { position, total, parity, data }
}
#[test]
fn test_reassemble_ok() {
let parts = [sym(1, 3, 0x5a, b"hel"), sym(2, 3, 0x5a, b"lo "), sym(3, 3, 0x5a, b"world")];
assert_eq!(reassemble(&parts).unwrap(), b"hello world");
}
#[test]
fn test_reassemble_out_of_order() {
let parts = [sym(3, 3, 0x5a, b"wor"), sym(1, 3, 0x5a, b"hel"), sym(2, 3, 0x5a, b"lo")];
assert_eq!(reassemble(&parts).unwrap(), b"hellowor");
}
#[test]
fn test_reassemble_empty() {
assert_eq!(reassemble(&[]), Err(SaError::Incomplete));
}
#[test]
fn test_reassemble_incomplete() {
let parts = [sym(1, 3, 0x5a, b"a"), sym(2, 3, 0x5a, b"b")];
assert_eq!(reassemble(&parts), Err(SaError::Incomplete));
}
#[test]
fn test_reassemble_duplicate() {
let parts = [sym(1, 3, 0x5a, b"a"), sym(1, 3, 0x5a, b"b"), sym(3, 3, 0x5a, b"c")];
assert_eq!(reassemble(&parts), Err(SaError::DuplicatePosition(1)));
}
#[test]
fn test_reassemble_count_mismatch() {
let parts = [sym(1, 3, 0x5a, b"a"), sym(2, 4, 0x5a, b"b")];
assert_eq!(reassemble(&parts), Err(SaError::CountMismatch));
}
#[test]
fn test_reassemble_parity_mismatch() {
let parts = [sym(1, 2, 0x5a, b"a"), sym(2, 2, 0x5b, b"b")];
assert_eq!(reassemble(&parts), Err(SaError::ParityMismatch));
}
#[test]
fn test_reassemble_out_of_range_total() {
assert_eq!(reassemble(&[sym(1, 1, 0, b"a")]), Err(SaError::OutOfRange(1)));
assert_eq!(reassemble(&[sym(1, 17, 0, b"a")]), Err(SaError::OutOfRange(17)));
}
#[test]
fn test_reassemble_out_of_range_position() {
let parts = [sym(0, 2, 0x5a, b"a"), sym(2, 2, 0x5a, b"b")];
assert_eq!(reassemble(&parts), Err(SaError::OutOfRange(0)));
let parts = [sym(1, 2, 0x5a, b"a"), sym(3, 2, 0x5a, b"b")];
assert_eq!(reassemble(&parts), Err(SaError::OutOfRange(3)));
}
#[test]
fn test_reassemble_max_sequence() {
let bytes: Vec<u8> = (1u8..=16).collect();
let parts: Vec<SaSymbol<'_>> = bytes
.iter()
.enumerate()
.map(|(i, _)| SaSymbol {
position: u8::try_from(i + 1).unwrap(),
total: 16,
parity: 0xff,
data: &bytes[i..=i],
})
.collect();
assert_eq!(reassemble(&parts).unwrap(), bytes);
}
}