1#[cfg(not(feature = "std"))]
16#[allow(unused_imports)]
17use alloc::vec::Vec;
18
19use core::cmp::min;
20use core::fmt::{Display, Error, Formatter};
21
22use crate::QrCode;
23use crate::bits::{self, Bits};
24use crate::optimize::{Optimizer, Parser, Segment, total_encoded_len};
25use crate::types::{EcLevel, QrError, QrResult, Version};
26
27#[non_exhaustive]
35#[derive(Debug, Clone, Copy)]
36pub struct StructuredAppend<'a> {
37 symbols: u8,
39 payload: &'a [u8],
41 parity: u8,
44}
45
46impl<'a> StructuredAppend<'a> {
47 pub fn new(symbols: u8, payload: &'a [u8]) -> QrResult<Self> {
68 if !(2..=16).contains(&symbols) {
69 return Err(QrError::InvalidStructuredAppend { value: symbols });
70 }
71 let parity = payload.iter().fold(0u8, |acc, &byte| acc ^ byte);
72 Ok(Self { symbols, payload, parity })
73 }
74
75 #[must_use]
77 pub const fn symbols(&self) -> u8 {
78 self.symbols
79 }
80
81 #[must_use]
85 pub const fn parity(&self) -> u8 {
86 self.parity
87 }
88
89 #[must_use]
91 pub fn payload(&self) -> &'a [u8] {
92 self.payload
93 }
94
95 pub fn encode(&self, ec: EcLevel) -> QrResult<Vec<QrCode>> {
108 let n = usize::from(self.symbols);
109 let chunk = self.payload.len().div_ceil(n);
110 let mut codes = Vec::with_capacity(n);
111 for i in 0..n {
112 let start = min(i * chunk, self.payload.len());
115 let end = min((i + 1) * chunk, self.payload.len());
116 let piece = &self.payload[start..end];
117 let code = encode_one_symbol(piece, i as u8 + 1, self.symbols, self.parity, ec)?;
118 codes.push(code);
119 }
120 Ok(codes)
121 }
122}
123
124fn encode_one_symbol(data: &[u8], position: u8, total: u8, parity: u8, ec: EcLevel) -> QrResult<QrCode> {
135 let segments = Parser::new(data).collect::<Vec<Segment>>();
136 for &checkpoint in &[Version::Normal(9), Version::Normal(26), Version::Normal(40)] {
137 let opt = Optimizer::new(segments.iter().copied(), checkpoint).collect::<Vec<_>>();
138 let total_len = total_encoded_len(&opt, checkpoint) + 20;
141 if total_len <= bits::data_capacity_bits(checkpoint, ec)? {
142 let version = bits::find_min_version(total_len, ec);
143 let mut bits = Bits::new(version);
144 bits.reserve(total_len);
145 bits.push_structured_append_header(position, total, parity)?;
146 bits.push_segments(data, opt.into_iter())?;
147 bits.push_terminator(ec)?;
148 return QrCode::with_bits(bits, ec);
149 }
150 }
151 Err(QrError::DataTooLong)
152}
153
154#[non_exhaustive]
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum SaError {
162 Incomplete,
164 DuplicatePosition(u8),
166 CountMismatch,
168 ParityMismatch,
170 OutOfRange(u8),
173 NotStructuredAppend,
177 MalformedStream,
180}
181
182impl Display for SaError {
183 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
184 match self {
185 Self::Incomplete => f.write_str("incomplete Structured Append sequence (symbols missing)"),
186 Self::DuplicatePosition(position) => {
187 write!(f, "duplicate Structured Append position {position}")
188 }
189 Self::CountMismatch => f.write_str("Structured Append symbols disagree on the total count"),
190 Self::ParityMismatch => f.write_str("Structured Append symbols disagree on the parity byte"),
191 Self::OutOfRange(value) => {
192 write!(f, "Structured Append value {value} out of range (total 2..=16, position 1..=total)")
193 }
194 Self::NotStructuredAppend => f.write_str("not a Structured Append symbol (no `0011` mode indicator)"),
195 Self::MalformedStream => f.write_str("malformed Structured Append bit stream"),
196 }
197 }
198}
199
200impl ::core::error::Error for SaError {}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub struct SaSymbol<'a> {
212 pub position: u8,
214 pub total: u8,
216 pub parity: u8,
218 pub data: &'a [u8],
220}
221
222pub fn reassemble(parts: &[SaSymbol<'_>]) -> Result<Vec<u8>, SaError> {
238 let Some(first) = parts.first() else { return Err(SaError::Incomplete) };
239 if !(2..=16).contains(&first.total) {
240 return Err(SaError::OutOfRange(first.total));
241 }
242 let total = first.total;
243 let parity = first.parity;
244 for p in parts {
245 if p.total != total {
246 return Err(SaError::CountMismatch);
247 }
248 if p.parity != parity {
249 return Err(SaError::ParityMismatch);
250 }
251 if !(1..=total).contains(&p.position) {
252 return Err(SaError::OutOfRange(p.position));
253 }
254 }
255 if parts.len() != usize::from(total) {
256 return Err(SaError::Incomplete);
257 }
258 let mut seen = [false; 16];
259 for p in parts {
260 let idx = usize::from(p.position - 1);
261 if seen[idx] {
262 return Err(SaError::DuplicatePosition(p.position));
263 }
264 seen[idx] = true;
265 }
266 let mut ordered: Vec<&SaSymbol<'_>> = parts.iter().collect();
267 ordered.sort_by_key(|s| s.position);
268 let mut out = Vec::new();
269 for s in ordered {
270 out.extend_from_slice(s.data);
271 }
272 Ok(out)
273}
274
275#[cfg(test)]
276mod tests {
277 use super::StructuredAppend;
278 use crate::types::{EcLevel, QrError};
279 use alloc::{vec, vec::Vec};
280
281 #[test]
282 fn test_new_rejects_out_of_range() {
283 assert_eq!(StructuredAppend::new(1, b"x").err(), Some(QrError::InvalidStructuredAppend { value: 1 }));
284 assert_eq!(StructuredAppend::new(17, b"x").err(), Some(QrError::InvalidStructuredAppend { value: 17 }));
285 }
286
287 #[test]
288 fn test_new_accepts_bounds() {
289 assert!(StructuredAppend::new(2, b"x").is_ok());
290 assert!(StructuredAppend::new(16, b"x").is_ok());
291 }
292
293 #[test]
294 fn test_parity_xor() {
295 assert_eq!(StructuredAppend::new(3, &[0x01, 0x02, 0x03]).unwrap().parity(), 0x00);
296 assert_eq!(StructuredAppend::new(2, &[0xff, 0x0f]).unwrap().parity(), 0xf0);
297 let bytes: Vec<u8> = (0u8..=255).collect();
299 assert_eq!(StructuredAppend::new(2, &bytes).unwrap().parity(), 0);
300 }
301
302 #[test]
303 fn test_parity_empty() {
304 assert_eq!(StructuredAppend::new(2, b"").unwrap().parity(), 0);
305 }
306
307 #[test]
308 fn test_encode_count_and_versions() {
309 let payload = b"Split this payload across several QR symbols for resilience.";
310 let codes = StructuredAppend::new(3, payload).unwrap().encode(EcLevel::M).unwrap();
311 assert_eq!(codes.len(), 3);
312 for code in &codes {
313 assert!(!code.info().version().is_micro(), "Structured Append must be Normal QR");
314 }
315 }
316
317 #[test]
318 fn test_encode_all_normal_across_counts() {
319 let payload = b"the quick brown fox jumps over the lazy dog";
320 for n in 2..=16u8 {
321 let codes = StructuredAppend::new(n, payload).unwrap().encode(EcLevel::L).unwrap();
322 assert_eq!(codes.len(), usize::from(n));
323 assert!(codes.iter().all(|c| !c.info().version().is_micro()), "n={n} produced a Micro QR");
324 }
325 }
326
327 #[test]
328 fn test_encode_empty_payload() {
329 let codes = StructuredAppend::new(2, b"").unwrap().encode(EcLevel::M).unwrap();
331 assert_eq!(codes.len(), 2);
332 assert!(codes.iter().all(|c| !c.info().version().is_micro()));
333 }
334
335 #[test]
336 fn test_encode_deterministic() {
337 let payload = b"deterministic encoding";
338 let a = StructuredAppend::new(3, payload).unwrap().encode(EcLevel::M).unwrap();
339 let b = StructuredAppend::new(3, payload).unwrap().encode(EcLevel::M).unwrap();
340 for (a, b) in a.iter().zip(b.iter()) {
342 assert_eq!(a.to_colors(), b.to_colors());
343 }
344 }
345
346 #[test]
347 fn test_encode_too_long() {
348 let payload = vec![0u8; 16 * 4000];
350 let result = StructuredAppend::new(16, &payload).unwrap().encode(EcLevel::H);
351 assert_eq!(result.err(), Some(QrError::DataTooLong));
352 }
353}
354
355#[cfg(test)]
356mod reassemble_tests {
357 use super::{SaError, SaSymbol, reassemble};
358 use alloc::vec::Vec;
359
360 fn sym(position: u8, total: u8, parity: u8, data: &[u8]) -> SaSymbol<'_> {
361 SaSymbol { position, total, parity, data }
362 }
363
364 #[test]
365 fn test_reassemble_ok() {
366 let parts = [sym(1, 3, 0x5a, b"hel"), sym(2, 3, 0x5a, b"lo "), sym(3, 3, 0x5a, b"world")];
367 assert_eq!(reassemble(&parts).unwrap(), b"hello world");
368 }
369
370 #[test]
371 fn test_reassemble_out_of_order() {
372 let parts = [sym(3, 3, 0x5a, b"wor"), sym(1, 3, 0x5a, b"hel"), sym(2, 3, 0x5a, b"lo")];
374 assert_eq!(reassemble(&parts).unwrap(), b"hellowor");
375 }
376
377 #[test]
378 fn test_reassemble_empty() {
379 assert_eq!(reassemble(&[]), Err(SaError::Incomplete));
380 }
381
382 #[test]
383 fn test_reassemble_incomplete() {
384 let parts = [sym(1, 3, 0x5a, b"a"), sym(2, 3, 0x5a, b"b")];
385 assert_eq!(reassemble(&parts), Err(SaError::Incomplete));
386 }
387
388 #[test]
389 fn test_reassemble_duplicate() {
390 let parts = [sym(1, 3, 0x5a, b"a"), sym(1, 3, 0x5a, b"b"), sym(3, 3, 0x5a, b"c")];
391 assert_eq!(reassemble(&parts), Err(SaError::DuplicatePosition(1)));
392 }
393
394 #[test]
395 fn test_reassemble_count_mismatch() {
396 let parts = [sym(1, 3, 0x5a, b"a"), sym(2, 4, 0x5a, b"b")];
397 assert_eq!(reassemble(&parts), Err(SaError::CountMismatch));
398 }
399
400 #[test]
401 fn test_reassemble_parity_mismatch() {
402 let parts = [sym(1, 2, 0x5a, b"a"), sym(2, 2, 0x5b, b"b")];
403 assert_eq!(reassemble(&parts), Err(SaError::ParityMismatch));
404 }
405
406 #[test]
407 fn test_reassemble_out_of_range_total() {
408 assert_eq!(reassemble(&[sym(1, 1, 0, b"a")]), Err(SaError::OutOfRange(1)));
409 assert_eq!(reassemble(&[sym(1, 17, 0, b"a")]), Err(SaError::OutOfRange(17)));
410 }
411
412 #[test]
413 fn test_reassemble_out_of_range_position() {
414 let parts = [sym(0, 2, 0x5a, b"a"), sym(2, 2, 0x5a, b"b")];
415 assert_eq!(reassemble(&parts), Err(SaError::OutOfRange(0)));
416 let parts = [sym(1, 2, 0x5a, b"a"), sym(3, 2, 0x5a, b"b")];
417 assert_eq!(reassemble(&parts), Err(SaError::OutOfRange(3)));
418 }
419
420 #[test]
421 fn test_reassemble_max_sequence() {
422 let bytes: Vec<u8> = (1u8..=16).collect();
424 let parts: Vec<SaSymbol<'_>> = bytes
425 .iter()
426 .enumerate()
427 .map(|(i, _)| SaSymbol {
428 position: u8::try_from(i + 1).unwrap(),
429 total: 16,
430 parity: 0xff,
431 data: &bytes[i..=i],
432 })
433 .collect();
434 assert_eq!(reassemble(&parts).unwrap(), bytes);
435 }
436}