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)]
209pub struct SaSymbol<'a> {
210 pub position: u8,
212 pub total: u8,
214 pub parity: u8,
216 pub data: &'a [u8],
218}
219
220pub fn reassemble(parts: &[SaSymbol<'_>]) -> Result<Vec<u8>, SaError> {
236 let Some(first) = parts.first() else { return Err(SaError::Incomplete) };
237 if !(2..=16).contains(&first.total) {
238 return Err(SaError::OutOfRange(first.total));
239 }
240 let total = first.total;
241 let parity = first.parity;
242 for p in parts {
243 if p.total != total {
244 return Err(SaError::CountMismatch);
245 }
246 if p.parity != parity {
247 return Err(SaError::ParityMismatch);
248 }
249 if !(1..=total).contains(&p.position) {
250 return Err(SaError::OutOfRange(p.position));
251 }
252 }
253 if parts.len() != usize::from(total) {
254 return Err(SaError::Incomplete);
255 }
256 let mut seen = [false; 16];
257 for p in parts {
258 let idx = usize::from(p.position - 1);
259 if seen[idx] {
260 return Err(SaError::DuplicatePosition(p.position));
261 }
262 seen[idx] = true;
263 }
264 let mut ordered: Vec<&SaSymbol<'_>> = parts.iter().collect();
265 ordered.sort_by_key(|s| s.position);
266 let mut out = Vec::new();
267 for s in ordered {
268 out.extend_from_slice(s.data);
269 }
270 Ok(out)
271}
272
273#[cfg(test)]
274mod tests {
275 use super::StructuredAppend;
276 use crate::types::{EcLevel, QrError};
277 use alloc::{vec, vec::Vec};
278
279 #[test]
280 fn test_new_rejects_out_of_range() {
281 assert_eq!(StructuredAppend::new(1, b"x").err(), Some(QrError::InvalidStructuredAppend { value: 1 }));
282 assert_eq!(StructuredAppend::new(17, b"x").err(), Some(QrError::InvalidStructuredAppend { value: 17 }));
283 }
284
285 #[test]
286 fn test_new_accepts_bounds() {
287 assert!(StructuredAppend::new(2, b"x").is_ok());
288 assert!(StructuredAppend::new(16, b"x").is_ok());
289 }
290
291 #[test]
292 fn test_parity_xor() {
293 assert_eq!(StructuredAppend::new(3, &[0x01, 0x02, 0x03]).unwrap().parity(), 0x00);
294 assert_eq!(StructuredAppend::new(2, &[0xff, 0x0f]).unwrap().parity(), 0xf0);
295 let bytes: Vec<u8> = (0u8..=255).collect();
297 assert_eq!(StructuredAppend::new(2, &bytes).unwrap().parity(), 0);
298 }
299
300 #[test]
301 fn test_parity_empty() {
302 assert_eq!(StructuredAppend::new(2, b"").unwrap().parity(), 0);
303 }
304
305 #[test]
306 fn test_encode_count_and_versions() {
307 let payload = b"Split this payload across several QR symbols for resilience.";
308 let codes = StructuredAppend::new(3, payload).unwrap().encode(EcLevel::M).unwrap();
309 assert_eq!(codes.len(), 3);
310 for code in &codes {
311 assert!(!code.info().version().is_micro(), "Structured Append must be Normal QR");
312 }
313 }
314
315 #[test]
316 fn test_encode_all_normal_across_counts() {
317 let payload = b"the quick brown fox jumps over the lazy dog";
318 for n in 2..=16u8 {
319 let codes = StructuredAppend::new(n, payload).unwrap().encode(EcLevel::L).unwrap();
320 assert_eq!(codes.len(), usize::from(n));
321 assert!(codes.iter().all(|c| !c.info().version().is_micro()), "n={n} produced a Micro QR");
322 }
323 }
324
325 #[test]
326 fn test_encode_empty_payload() {
327 let codes = StructuredAppend::new(2, b"").unwrap().encode(EcLevel::M).unwrap();
329 assert_eq!(codes.len(), 2);
330 assert!(codes.iter().all(|c| !c.info().version().is_micro()));
331 }
332
333 #[test]
334 fn test_encode_deterministic() {
335 let payload = b"deterministic encoding";
336 let a = StructuredAppend::new(3, payload).unwrap().encode(EcLevel::M).unwrap();
337 let b = StructuredAppend::new(3, payload).unwrap().encode(EcLevel::M).unwrap();
338 for (a, b) in a.iter().zip(b.iter()) {
340 assert_eq!(a.to_colors(), b.to_colors());
341 }
342 }
343
344 #[test]
345 fn test_encode_too_long() {
346 let payload = vec![0u8; 16 * 4000];
348 let result = StructuredAppend::new(16, &payload).unwrap().encode(EcLevel::H);
349 assert_eq!(result.err(), Some(QrError::DataTooLong));
350 }
351}
352
353#[cfg(test)]
354mod reassemble_tests {
355 use super::{SaError, SaSymbol, reassemble};
356 use alloc::vec::Vec;
357
358 fn sym(position: u8, total: u8, parity: u8, data: &[u8]) -> SaSymbol<'_> {
359 SaSymbol { position, total, parity, data }
360 }
361
362 #[test]
363 fn test_reassemble_ok() {
364 let parts = [sym(1, 3, 0x5a, b"hel"), sym(2, 3, 0x5a, b"lo "), sym(3, 3, 0x5a, b"world")];
365 assert_eq!(reassemble(&parts).unwrap(), b"hello world");
366 }
367
368 #[test]
369 fn test_reassemble_out_of_order() {
370 let parts = [sym(3, 3, 0x5a, b"wor"), sym(1, 3, 0x5a, b"hel"), sym(2, 3, 0x5a, b"lo")];
372 assert_eq!(reassemble(&parts).unwrap(), b"hellowor");
373 }
374
375 #[test]
376 fn test_reassemble_empty() {
377 assert_eq!(reassemble(&[]), Err(SaError::Incomplete));
378 }
379
380 #[test]
381 fn test_reassemble_incomplete() {
382 let parts = [sym(1, 3, 0x5a, b"a"), sym(2, 3, 0x5a, b"b")];
383 assert_eq!(reassemble(&parts), Err(SaError::Incomplete));
384 }
385
386 #[test]
387 fn test_reassemble_duplicate() {
388 let parts = [sym(1, 3, 0x5a, b"a"), sym(1, 3, 0x5a, b"b"), sym(3, 3, 0x5a, b"c")];
389 assert_eq!(reassemble(&parts), Err(SaError::DuplicatePosition(1)));
390 }
391
392 #[test]
393 fn test_reassemble_count_mismatch() {
394 let parts = [sym(1, 3, 0x5a, b"a"), sym(2, 4, 0x5a, b"b")];
395 assert_eq!(reassemble(&parts), Err(SaError::CountMismatch));
396 }
397
398 #[test]
399 fn test_reassemble_parity_mismatch() {
400 let parts = [sym(1, 2, 0x5a, b"a"), sym(2, 2, 0x5b, b"b")];
401 assert_eq!(reassemble(&parts), Err(SaError::ParityMismatch));
402 }
403
404 #[test]
405 fn test_reassemble_out_of_range_total() {
406 assert_eq!(reassemble(&[sym(1, 1, 0, b"a")]), Err(SaError::OutOfRange(1)));
407 assert_eq!(reassemble(&[sym(1, 17, 0, b"a")]), Err(SaError::OutOfRange(17)));
408 }
409
410 #[test]
411 fn test_reassemble_out_of_range_position() {
412 let parts = [sym(0, 2, 0x5a, b"a"), sym(2, 2, 0x5a, b"b")];
413 assert_eq!(reassemble(&parts), Err(SaError::OutOfRange(0)));
414 let parts = [sym(1, 2, 0x5a, b"a"), sym(3, 2, 0x5a, b"b")];
415 assert_eq!(reassemble(&parts), Err(SaError::OutOfRange(3)));
416 }
417
418 #[test]
419 fn test_reassemble_max_sequence() {
420 let bytes: Vec<u8> = (1u8..=16).collect();
422 let parts: Vec<SaSymbol<'_>> = bytes
423 .iter()
424 .enumerate()
425 .map(|(i, _)| SaSymbol {
426 position: u8::try_from(i + 1).unwrap(),
427 total: 16,
428 parity: 0xff,
429 data: &bytes[i..=i],
430 })
431 .collect();
432 assert_eq!(reassemble(&parts).unwrap(), bytes);
433 }
434}