Skip to main content

cesr/core/matter/
builder.rs

1use super::{
2    MatterPart,
3    code::{CesrCode, MatterCode},
4    error::{MatterBuildError, ParsingError, ValidationError},
5    matter::Matter,
6    sizage::{Sizage, SizeType},
7};
8use crate::b64::{charset::is_b64_url_safe_charset, decode_int, encode_binary};
9use alloc::borrow::Cow;
10#[cfg(feature = "alloc")]
11#[allow(
12    unused_imports,
13    reason = "alloc prelude items; subset used per cfg/feature combination"
14)]
15use alloc::{borrow::ToOwned, format, string::String, string::ToString, vec, vec::Vec};
16use base64::{Engine, decoded_len_estimate, engine::general_purpose as b64};
17use core::num::NonZeroUsize;
18
19/// Marker trait for the type-state pattern used by [`MatterBuilder`].
20pub trait MatterBuilderState {}
21
22/// Initial state: no code, raw, or soft has been set.
23pub struct Start {}
24
25impl MatterBuilderState for Start {}
26
27/// State after setting a CESR code.
28pub struct WithCode<C: CesrCode> {
29    code: C,
30}
31
32impl<C: CesrCode> MatterBuilderState for WithCode<C> {}
33
34/// State after setting a code and soft value.
35pub struct WithSoft<'a, C: CesrCode> {
36    code: C,
37    soft: &'a str,
38}
39
40impl<C: CesrCode> MatterBuilderState for WithSoft<'_, C> {}
41
42/// State after setting a code and raw bytes.
43pub struct WithRaw<'a, C: CesrCode> {
44    code: C,
45    raw: Cow<'a, [u8]>,
46}
47
48impl<C: CesrCode> MatterBuilderState for WithRaw<'_, C> {}
49
50/// State after setting a code, raw bytes, and soft value.
51pub struct WithRawAndSoft<'a, C: CesrCode> {
52    code: C,
53    raw: Cow<'a, [u8]>,
54    soft: &'a str,
55}
56
57impl<C: CesrCode> MatterBuilderState for WithRawAndSoft<'_, C> {}
58
59/// A type-state builder for constructing [`Matter`] primitives.
60pub struct MatterBuilder<M>
61where
62    M: MatterBuilderState,
63{
64    state: M,
65}
66
67impl Default for MatterBuilder<Start> {
68    fn default() -> Self {
69        Self { state: Start {} }
70    }
71}
72
73impl MatterBuilder<Start> {
74    /// Creates a new `MatterBuilder` in the initial state.
75    #[must_use]
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    /// Sets the CESR code, advancing to the next builder state.
81    pub const fn with_code<C: CesrCode>(self, code: C) -> MatterBuilder<WithCode<C>> {
82        MatterBuilder {
83            state: WithCode { code },
84        }
85    }
86
87    /// Parses a [`Matter`] from a qualified Base64 (qb64) byte stream.
88    ///
89    /// # Errors
90    ///
91    /// Returns [`ParsingError`] or [`ValidationError`] if the stream is
92    /// malformed, truncated, or fails canonicality checks.
93    #[allow(
94        clippy::too_many_lines,
95        reason = "sequential parsing steps that are clearer together"
96    )]
97    pub fn from_qualified_base64<'a>(
98        self,
99        input: impl Into<Cow<'a, [u8]>>,
100    ) -> Result<Matter<'a, MatterCode>, MatterBuildError> {
101        let stream = input.into();
102        if stream.is_empty() {
103            return Err(MatterBuildError::from(ParsingError::EmptyStream));
104        }
105        let code = MatterCode::from_base64_stream(&stream)?;
106        // Resolve the descriptor ONCE. The code->Sizage match is the dominant
107        // lookup on this path; every field below derives from this single value
108        // instead of re-running the ~110-variant match five more times.
109        let sizage = code.get_sizage();
110        let hs = sizage.hs();
111        let ss = sizage.ss();
112        let cs = hs + ss;
113        if stream.len() < cs {
114            return Err(MatterBuildError::from(ParsingError::StreamTooShort(
115                MatterPart::Soft,
116            )));
117        }
118        let soft_full = &stream[hs..cs];
119        let xs = sizage.xs();
120        let xtra = &soft_full[..xs];
121        let soft_tail = &soft_full[xs..];
122        // xtra must be all PAD chars. Compare by byte instead of allocating a
123        // repeated String (`PAD.repeat(xs)`) on every decode.
124        if !xtra.iter().all(|&b| b == b'_') {
125            return Err(MatterBuildError::from(ParsingError::MalformedCode {
126                part: MatterPart::Xtra,
127                found: Matter::<MatterCode>::PAD.repeat(xs),
128            }));
129        }
130        // Validate the soft tail's UTF-8 once; the &str is reused for the
131        // variable-size fs computation below.
132        let soft_tail_str = str::from_utf8(soft_tail)
133            .map_err(|err| MatterBuildError::from(ParsingError::InvalidUtf8(err)))?;
134        // Inline frame_size_of: fixed codes return immediately; variable codes
135        // decode the already-validated soft tail. Avoids a second get_sizage()
136        // match and a redundant soft re-parse that frame_size_of would do.
137        let fs = match sizage.fs() {
138            SizeType::Fixed(fixed) => usize::from(*fixed),
139            SizeType::Small | SizeType::Large => {
140                let size: usize = decode_int(soft_tail_str)
141                    .map_err(|err| MatterBuildError::from(ParsingError::Conversion(err)))?;
142                compute_full_size(size, cs)?
143            }
144        };
145        if stream.len() < fs {
146            return Err(MatterBuildError::from(ValidationError::IncorrectRawSize {
147                code: code.to_string(),
148                expected: fs,
149                found: stream.len(),
150            }));
151        }
152        let trim = &stream[..fs];
153        let ps = cs % 4;
154
155        let paw = &trim[cs..];
156        let mut temp: Vec<u8> = Vec::with_capacity(ps + paw.len());
157        temp.resize(ps, b'A');
158        temp.extend_from_slice(paw);
159        let estimate = decoded_len_estimate(temp.len());
160        let mut buf: Vec<u8> = Vec::with_capacity(estimate);
161        b64::URL_SAFE
162            .decode_vec(temp, &mut buf)
163            .map_err(|err| MatterBuildError::from(ParsingError::Base64(err)))?;
164
165        if ps != 0 {
166            let pbs = 2 * ps;
167            let mask = (1u32 << pbs) - 1;
168            let mut pi: u32 = 0;
169            for &byte in buf.iter().take(ps) {
170                pi = (pi << 8) | u32::from(byte);
171            }
172            if (pi & mask) != 0 {
173                return Err(MatterBuildError::from(
174                    ValidationError::NonCanonicalEncoding(MatterPart::PadBits),
175                ));
176            }
177            let ls = sizage.ls();
178            let Some(lead_bytes) = buf.get(ps..(ps + ls)) else {
179                return Err(MatterBuildError::from(
180                    ValidationError::StructuralIntegrityError,
181                ));
182            };
183            if ls > 0 && lead_bytes.iter().any(|&b| b != 0) {
184                return Err(MatterBuildError::from(
185                    ValidationError::NonCanonicalEncoding(MatterPart::LeadBytes),
186                ));
187            }
188            buf.drain(..(ps + ls));
189        } else {
190            let ls = sizage.ls();
191            if ls > 0 {
192                let Some(lead_bytes) = buf.get(..ls) else {
193                    return Err(MatterBuildError::from(
194                        ValidationError::StructuralIntegrityError,
195                    ));
196                };
197                if lead_bytes.iter().any(|&b| b != 0) {
198                    return Err(MatterBuildError::from(
199                        ValidationError::NonCanonicalEncoding(MatterPart::LeadBytes),
200                    ));
201                }
202                buf.drain(..ls);
203            }
204        }
205        let raw: Cow<'a, [u8]> = Cow::Owned(buf);
206
207        let soft_start = hs + xs;
208        let soft_end = cs;
209
210        let soft: Cow<'a, str> = match &stream {
211            Cow::Borrowed(b) => {
212                let s = str::from_utf8(&b[soft_start..soft_end])
213                    .map_err(|err| MatterBuildError::from(ParsingError::InvalidUtf8(err)))?;
214                Cow::Borrowed(s)
215            }
216            Cow::Owned(v) => {
217                let s = str::from_utf8(&v[soft_start..soft_end])
218                    .map_err(|err| MatterBuildError::from(ParsingError::InvalidUtf8(err)))?;
219                Cow::Owned(s.to_owned())
220            }
221        };
222
223        Ok(Matter::new(code, raw, soft))
224    }
225
226    /// Parses a [`Matter`] from a qualified binary (qb2) byte stream.
227    ///
228    /// # Errors
229    ///
230    /// Returns [`ParsingError`] or [`ValidationError`] if the stream is
231    /// malformed, truncated, or fails canonicality checks.
232    #[allow(
233        clippy::too_many_lines,
234        reason = "sequential parsing steps that are clearer together"
235    )]
236    pub fn from_qualified_base2(
237        self,
238        stream: &[u8],
239    ) -> Result<Matter<'_, MatterCode>, MatterBuildError> {
240        if stream.is_empty() {
241            return Err(MatterBuildError::from(ParsingError::EmptyStream));
242        }
243        let code = MatterCode::from_stream(stream)?;
244        let hs = code.get_sizage().hs();
245        let ss = code.get_sizage().ss();
246        let cs = hs + ss;
247        let bcs = (cs * 3).div_ceil(4);
248        if stream.len() < bcs {
249            return Err(MatterBuildError::from(ParsingError::StreamTooShort(
250                MatterPart::Soft,
251            )));
252        }
253
254        let char_len = NonZeroUsize::new(cs)
255            .ok_or_else(|| MatterBuildError::from(ParsingError::EmptyStream))?;
256        let both = encode_binary(&stream[..bcs], char_len)
257            .map_err(|err| MatterBuildError::from(ParsingError::Conversion(err)))?;
258
259        let soft_full = &both[hs..cs];
260        let xs = code.get_sizage().xs();
261        let xtra = &soft_full[..xs];
262        let soft_tail = &soft_full[xs..];
263        if xtra != Matter::<MatterCode>::PAD.repeat(xs) {
264            return Err(MatterBuildError::from(ParsingError::MalformedCode {
265                part: MatterPart::Xtra,
266                found: Matter::<MatterCode>::PAD.repeat(xs),
267            }));
268        }
269        let fs = if let SizeType::Fixed(fixed) = code.get_sizage().fs() {
270            usize::from(*fixed)
271        } else {
272            let size: usize = decode_int(soft_tail)
273                .map_err(|err| MatterBuildError::from(ParsingError::Conversion(err)))?;
274            compute_full_size(size, cs)?
275        };
276        let bfs = compute_qb2_byte_size(fs)?;
277        if stream.len() < bfs {
278            return Err(MatterBuildError::from(ValidationError::IncorrectRawSize {
279                code: code.to_string(),
280                expected: bfs,
281                found: stream.len(),
282            }));
283        }
284        let trimmed = &stream[..bfs];
285        let ls = code.get_sizage().ls();
286        let lead_end = bcs
287            .checked_add(ls)
288            .ok_or_else(|| MatterBuildError::from(ValidationError::StructuralIntegrityError))?;
289        if lead_end > trimmed.len() {
290            return Err(MatterBuildError::from(ValidationError::IncorrectRawSize {
291                code: code.to_string(),
292                expected: lead_end,
293                found: trimmed.len(),
294            }));
295        }
296        let ps = cs % 4;
297        if ps != 0 {
298            #[allow(
299                clippy::as_conversions,
300                clippy::cast_possible_truncation,
301                reason = "2 * ps is always <= 6 which fits in u32"
302            )]
303            let pbs = (2 * ps) as u32;
304            let mut pi = trimmed[bcs - 1];
305            pi &= 2_u8.pow(pbs) - 1;
306            if pi != 0 {
307                return Err(MatterBuildError::from(
308                    ValidationError::NonCanonicalEncoding(MatterPart::PadBits),
309                ));
310            }
311        }
312        let li = &trimmed[bcs..lead_end];
313        if ls > 0 && li.iter().any(|&b| b != 0) {
314            return Err(MatterBuildError::from(
315                ValidationError::NonCanonicalEncoding(MatterPart::LeadBytes),
316            ));
317        }
318        let raw = &trimmed[lead_end..];
319
320        if raw.len() != trimmed.len() - lead_end {
321            return Err(MatterBuildError::from(
322                ValidationError::StructuralIntegrityError,
323            ));
324        }
325
326        Ok(Matter::new(
327            code,
328            Cow::Borrowed(raw),
329            Cow::Owned(soft_tail.to_owned()),
330        ))
331    }
332}
333
334impl<C: CesrCode> MatterBuilder<WithCode<C>> {
335    /// Provides the raw bytes for the primitive.
336    ///
337    /// # Errors
338    ///
339    /// Returns [`ParsingError::EmptyStream`] if `raw` is empty.
340    pub fn with_raw<'a>(
341        self,
342        raw: impl Into<Cow<'a, [u8]>>,
343    ) -> Result<MatterBuilder<WithRaw<'a, C>>, ParsingError> {
344        let raw_bytes = raw.into();
345        // Reject empty raw only for codes that actually carry a payload. Fixed
346        // zero-rawsize codes (e.g. `1AAP`) encode to just their code string, so
347        // empty raw is valid — keripy accepts it (differential-tested). A code
348        // whose raw size is unknown (variable) or non-zero still requires input.
349        if raw_bytes.is_empty() && !matches!(self.state.code.raw_size(), Ok(0)) {
350            return Err(ParsingError::EmptyStream);
351        }
352        Ok(MatterBuilder {
353            state: WithRaw {
354                code: self.state.code,
355                raw: raw_bytes,
356            },
357        })
358    }
359
360    /// Provides the soft value for the primitive.
361    ///
362    /// # Errors
363    ///
364    /// Returns [`ParsingError::EmptyStream`] if `soft` is empty.
365    pub const fn with_soft(
366        self,
367        soft: &str,
368    ) -> Result<MatterBuilder<WithSoft<'_, C>>, ParsingError> {
369        if soft.is_empty() {
370            return Err(ParsingError::EmptyStream);
371        }
372        let state = WithSoft {
373            code: self.state.code,
374            soft,
375        };
376        Ok(MatterBuilder { state })
377    }
378}
379
380impl<'a, C: CesrCode> MatterBuilder<WithRaw<'a, C>> {
381    /// Builds the [`Matter`] from raw bytes alone (no soft value).
382    ///
383    /// # Errors
384    ///
385    /// Returns [`ParsingError`] or [`ValidationError`] if the code requires
386    /// a soft value, or the raw size is incorrect.
387    pub fn build(self) -> Result<Matter<'a, C>, MatterBuildError> {
388        let WithRaw { code, raw: raw_val } = self.state;
389        let mc = code.to_matter_code();
390        let Sizage { ss, fs, .. } = mc.get_sizage();
391        match fs {
392            SizeType::Fixed(_) => {
393                if ss > 0 {
394                    return Err(MatterBuildError::from(ValidationError::MissingSoft {
395                        code: mc.to_string(),
396                    }));
397                }
398                let raw_size = mc.raw_size()?;
399                let trimmed = validate_and_trim_raw(mc, raw_val, raw_size)?;
400                Ok(Matter::new(code, trimmed, Cow::from("")))
401            }
402            _ => Err(MatterBuildError::from(
403                ValidationError::InvalidSizingOperation(mc.to_string()),
404            )),
405        }
406    }
407
408    /// Adds a soft value to the builder.
409    ///
410    /// # Errors
411    ///
412    /// Returns [`ParsingError::EmptyStream`] if `soft` is empty.
413    pub fn with_soft(
414        self,
415        soft: &'a str,
416    ) -> Result<MatterBuilder<WithRawAndSoft<'a, C>>, ParsingError> {
417        if soft.is_empty() {
418            return Err(ParsingError::EmptyStream);
419        }
420        let state = WithRawAndSoft {
421            code: self.state.code,
422            raw: self.state.raw,
423            soft,
424        };
425        Ok(MatterBuilder { state })
426    }
427}
428
429impl<'a, C: CesrCode> MatterBuilder<WithRawAndSoft<'a, C>> {
430    /// Builds the [`Matter`] from raw bytes and a soft value.
431    ///
432    /// # Errors
433    ///
434    /// Returns [`ParsingError`] or [`ValidationError`] if sizes are invalid
435    /// or the soft format is incorrect.
436    pub fn build(self) -> Result<Matter<'a, C>, MatterBuildError> {
437        let WithRawAndSoft {
438            soft,
439            code,
440            raw: raw_val,
441        } = self.state;
442        let mc = code.to_matter_code();
443        let Sizage { ss, fs, xs, .. } = mc.get_sizage();
444        match fs {
445            SizeType::Fixed(_) => {
446                if ss == 0 {
447                    let raw_size = mc.raw_size()?;
448                    let trimmed = validate_and_trim_raw(mc, raw_val, raw_size)?;
449                    return Ok(Matter::new(code, trimmed, Cow::from("")));
450                }
451                let final_soft = extract_soft(mc, ss, xs, soft)?;
452                let raw_size = mc.raw_size()?;
453                let trimmed = validate_and_trim_raw(mc, raw_val, raw_size)?;
454                Ok(Matter::new(code, trimmed, Cow::Borrowed(final_soft)))
455            }
456            _ => Err(MatterBuildError::from(
457                ValidationError::InvalidSizingOperation(mc.to_string()),
458            )),
459        }
460    }
461}
462
463impl<'a, C: CesrCode> MatterBuilder<WithSoft<'a, C>> {
464    /// Builds the [`Matter`] from a soft value alone (no raw bytes).
465    ///
466    /// # Errors
467    ///
468    /// Returns [`ValidationError`] if the code does not support soft-only
469    /// construction, or the soft format is invalid.
470    pub fn build(self) -> Result<Matter<'a, C>, ValidationError> {
471        let WithSoft { code, soft } = self.state;
472        let mc = code.to_matter_code();
473        let Sizage {
474            ss,
475            fs: size_type,
476            hs,
477            xs,
478            ..
479        } = mc.get_sizage();
480        match size_type {
481            SizeType::Fixed(fixed_size) if ss > 0 && fixed_size == (hs + ss) => {
482                let final_soft = extract_soft(mc, ss, xs, soft)?;
483                Ok(Matter::new(
484                    code,
485                    Cow::Borrowed(b""),
486                    Cow::Borrowed(final_soft),
487                ))
488            }
489            SizeType::Fixed(_) => Err(ValidationError::InvalidSoftFormat {
490                code: mc.to_string(),
491            }),
492            _ => Err(ValidationError::UnknownMatterCode(mc.to_string())),
493        }
494    }
495}
496
497#[inline]
498#[allow(dead_code, reason = "utility kept for future encoding use")]
499const fn get_lead_size(raw_len: usize) -> usize {
500    (3 - (raw_len % 3)) % 3
501}
502
503#[inline]
504#[allow(dead_code, reason = "utility kept for future encoding use")]
505const fn get_size(raw_len: usize, lead_len: usize) -> usize {
506    (raw_len + lead_len) / 3
507}
508
509/// Computes the full character size `fs` of a variable-size primitive from its
510/// decoded soft `size` (`fs = size * 4 + cs`).
511///
512/// `size` is decoded from the attacker-controlled soft field, so the arithmetic
513/// is checked: an overflow yields [`ValidationError::SizeOverflow`] rather than a
514/// debug panic or a silently-wrapped (truncated) frame.
515#[inline]
516fn compute_full_size(size: usize, cs: usize) -> Result<usize, ValidationError> {
517    size.checked_mul(4)
518        .and_then(|quad| quad.checked_add(cs))
519        .ok_or(ValidationError::SizeOverflow)
520}
521
522/// Computes the full binary (qb2) byte size `bfs` from the character size `fs`
523/// (`bfs = ceil(fs * 3 / 4)`).
524///
525/// `fs` derives from the attacker-controlled soft field via [`compute_full_size`],
526/// so the multiplication is checked; an overflow yields
527/// [`ValidationError::SizeOverflow`].
528#[inline]
529fn compute_qb2_byte_size(fs: usize) -> Result<usize, ValidationError> {
530    fs.checked_mul(3)
531        .map(|tripled| tripled.div_ceil(4))
532        .ok_or(ValidationError::SizeOverflow)
533}
534
535#[inline]
536fn extract_soft(code: MatterCode, ss: u8, xs: u8, soft: &str) -> Result<&str, ValidationError> {
537    let expected_len = usize::from(ss - xs);
538
539    if soft.len() < expected_len {
540        return Err(ValidationError::IncorrectSoftLength {
541            code: code.to_string(),
542            expected: expected_len,
543            found: soft.len(),
544        });
545    }
546
547    let final_soft = &soft[..expected_len];
548
549    if !is_b64_url_safe_charset(final_soft.as_bytes()) {
550        return Err(ValidationError::InvalidSoftFormat {
551            code: code.to_string(),
552        });
553    }
554
555    Ok(final_soft)
556}
557
558#[inline]
559fn validate_and_trim_raw(
560    code: MatterCode,
561    raw: Cow<'_, [u8]>,
562    raw_size: usize,
563) -> Result<Cow<'_, [u8]>, ValidationError> {
564    if raw.len() < raw_size {
565        return Err(ValidationError::IncorrectRawSize {
566            found: raw.len(),
567            expected: raw_size,
568            code: code.to_string(),
569        });
570    }
571    if raw.len() == raw_size {
572        return Ok(raw);
573    }
574    match raw {
575        Cow::Borrowed(s) => Ok(Cow::Borrowed(&s[..raw_size])),
576        Cow::Owned(mut v) => {
577            v.truncate(raw_size);
578            Ok(Cow::Owned(v))
579        }
580    }
581}
582
583impl MatterCode {
584    /// Full qb64 character size of the Matter primitive at the head of `stream`,
585    /// without decoding the raw body or validating pad/lead bits.
586    ///
587    /// # Errors
588    /// `MatterBuildError` on unknown code, short soft field, non-UTF-8 soft, or size overflow.
589    pub fn frame_size(stream: &[u8]) -> Result<usize, MatterBuildError> {
590        let code = Self::from_base64_stream(stream)?;
591        code.frame_size_of(stream)
592    }
593
594    /// `frame_size` for an already-known code — shared with `from_qualified_base64`
595    /// so there is exactly one size implementation.
596    pub(crate) fn frame_size_of(self, stream: &[u8]) -> Result<usize, MatterBuildError> {
597        let sizage = self.get_sizage();
598        if let SizeType::Fixed(fixed) = sizage.fs() {
599            return Ok(usize::from(*fixed));
600        }
601        let hs = sizage.hs();
602        let ss = sizage.ss();
603        let cs = hs + ss;
604        if stream.len() < cs {
605            return Err(MatterBuildError::from(ParsingError::StreamTooShort(
606                MatterPart::Soft,
607            )));
608        }
609        let xs = sizage.xs();
610        let soft_tail = str::from_utf8(&stream[hs + xs..cs])
611            .map_err(|err| MatterBuildError::from(ParsingError::InvalidUtf8(err)))?;
612        let size: usize = decode_int(soft_tail)
613            .map_err(|err| MatterBuildError::from(ParsingError::Conversion(err)))?;
614        compute_full_size(size, cs).map_err(MatterBuildError::from)
615    }
616}
617
618#[cfg(test)]
619#[allow(clippy::panic, reason = "tests use panic via unwrap/assert macros")]
620mod tests {
621    use super::{
622        MatterBuildError, MatterBuilder, Start, ValidationError, compute_full_size,
623        compute_qb2_byte_size, validate_and_trim_raw,
624    };
625    use crate::core::matter::code::MatterCode;
626    use std::{format, string::String, vec, vec::Vec};
627
628    #[test]
629    fn qb64_lead_bytes_slice_does_not_panic_on_short_buffer() {
630        // Regression (deep-fuzz `matter_from_qb64`): input `5BAA` panicked with
631        // "range end index 1 out of range for slice of length 0" — the lead-byte
632        // slice indexed past a decoded buffer shorter than the code's declared lead
633        // size. Parsing untrusted bytes must never panic; it must return a typed error.
634        let err = MatterBuilder::new()
635            .from_qualified_base64(b"5BAA".as_slice())
636            .expect_err("`5BAA` must be rejected, not accepted");
637        assert!(
638            matches!(
639                err,
640                MatterBuildError::Validation(ValidationError::StructuralIntegrityError)
641            ),
642            "expected StructuralIntegrityError, got {err:?}"
643        );
644    }
645
646    #[test]
647    fn matter_frame_size_fixed_and_truncated() {
648        // 'B' = Ed25519 non-transferable verkey, fixed fs = 44
649        let full = String::from("B") + &"A".repeat(43);
650        assert_eq!(MatterCode::frame_size(full.as_bytes()).unwrap(), 44);
651        assert!(MatterCode::frame_size(b"").is_err()); // empty -> error, no panic
652        assert!(MatterCode::frame_size(b"\x00\x00").is_err()); // unknown code -> error
653    }
654
655    #[test]
656    fn qb64_fixed_code_rejects_non_utf8_soft() {
657        // Regression for the Task 1 review gap: `frame_size_of` returns early for
658        // FIXED codes without validating soft UTF-8, so `from_qualified_base64`
659        // retains a bare `str::from_utf8(soft_tail)?` to keep rejecting non-UTF-8
660        // soft on fixed ss>0 codes. Tag3 ('X') is fixed: hs=1, ss=3, xs=0, fs=4,
661        // so the three soft bytes are validated. Non-UTF-8 soft must be a typed
662        // InvalidUtf8 error, never accepted or a panic.
663        let bytes: &[u8] = &[b'X', 0xFF, 0xFF, 0xFF];
664        let err = MatterBuilder::new()
665            .from_qualified_base64(bytes)
666            .expect_err("non-UTF-8 soft on fixed Tag3 must be rejected");
667        assert!(
668            matches!(
669                err,
670                MatterBuildError::Parsing(crate::core::matter::error::ParsingError::InvalidUtf8(_))
671            ),
672            "expected Parsing(InvalidUtf8), got {err:?}"
673        );
674    }
675
676    // ── Size-arithmetic overflow tests (#76) ────────────────────────────
677    // `size` is decoded from the attacker-controlled soft field. Computing the
678    // frame size with bare arithmetic panics on overflow (debug) or wraps to a
679    // small bogus size (release) that then slices a truncated frame as valid.
680    // These probe the checked helpers directly because the parse API caps the
681    // soft field at ss=4 (size <= 2^24-1), so overflow is unreachable through
682    // `from_qualified_base64`/`from_qualified_base2` today — it is latent, and
683    // must still be rejected as a typed error, never panic or wrap.
684
685    #[test]
686    fn compute_full_size_rejects_overflow() {
687        // size * 4 overflows usize.
688        let err = compute_full_size(usize::MAX / 2, 4)
689            .expect_err("size * 4 overflow must be a typed Err, not a panic or wrap");
690        assert_eq!(err, ValidationError::SizeOverflow);
691    }
692
693    #[test]
694    fn compute_full_size_rejects_add_overflow() {
695        // size * 4 fits but + cs overflows.
696        let err = compute_full_size(usize::MAX / 4, 8)
697            .expect_err("+ cs overflow must be a typed Err, not a panic or wrap");
698        assert_eq!(err, ValidationError::SizeOverflow);
699    }
700
701    #[test]
702    fn compute_full_size_in_range_is_exact() {
703        assert_eq!(compute_full_size(10, 4), Ok(44));
704        assert_eq!(compute_full_size(0, 2), Ok(2));
705        // Widest real variable code: ss=4 => size <= 2^24-1, must not overflow.
706        let max_real = 64_usize.pow(4) - 1;
707        assert_eq!(compute_full_size(max_real, 4), Ok((max_real * 4) + 4));
708    }
709
710    #[test]
711    fn compute_qb2_byte_size_rejects_overflow() {
712        // fs * 3 overflows usize.
713        let err = compute_qb2_byte_size(usize::MAX / 2)
714            .expect_err("fs * 3 overflow must be a typed Err, not a panic or wrap");
715        assert_eq!(err, ValidationError::SizeOverflow);
716    }
717
718    #[test]
719    fn compute_qb2_byte_size_in_range_is_exact() {
720        // ceil(44 * 3 / 4) = 33.
721        assert_eq!(compute_qb2_byte_size(44), Ok(33));
722        // ceil(2 * 3 / 4) = 2.
723        assert_eq!(compute_qb2_byte_size(2), Ok(2));
724    }
725
726    #[test]
727    fn should_extract_raw_size_based() {
728        use alloc::borrow::Cow;
729        let code = MatterCode::Ed25519;
730        let raw = b"18923yjkahds7612378612983189237669jyasgdutgjashgjg";
731        let result = validate_and_trim_raw(code, Cow::Borrowed(&raw[..]), 44);
732        assert!(result.is_ok());
733        let raw_result = result.unwrap();
734        assert_eq!(&*raw_result, &raw[..44]);
735    }
736
737    // TODO: write tests to extract soft
738    // TODO: write test to build matter  with only code and soft
739    // TODO: write test to build matter with qb64
740    // TODO: write test to build matter with qb2
741
742    #[test]
743    fn should_be_able_to_build_code_raw_matter() {
744        let code = MatterCode::Ed25519;
745        let raw = b"18923yjkahds7612378612983189237669jyasgdutgjashgjg";
746        let result = MatterBuilder::<Start>::new()
747            .with_code(code)
748            .with_raw(&raw[..])
749            .unwrap()
750            .build();
751
752        assert!(result.is_ok());
753        let matter = result.unwrap();
754        assert_eq!(matter.code(), &code);
755        assert_eq!(matter.raw(), &raw[..32]); // thats how much ed25519 raw length should be.
756    }
757
758    #[test]
759    fn should_build_typed_matter_from_code_and_raw() {
760        use crate::core::matter::code::VerKeyCode;
761        let code = VerKeyCode::Ed25519;
762        let raw = &[0u8; 32];
763        let result = MatterBuilder::<Start>::new()
764            .with_code(code)
765            .with_raw(&raw[..])
766            .unwrap()
767            .build();
768        assert!(result.is_ok());
769        let matter = result.unwrap();
770        assert_eq!(*matter.code(), VerKeyCode::Ed25519);
771    }
772
773    // ── ParsingError tests ─────────────────────────────────────────────
774
775    #[test]
776    fn qb64_empty_stream_returns_parsing_error() {
777        let result = MatterBuilder::new().from_qualified_base64(b"");
778        assert!(result.is_err());
779    }
780
781    #[test]
782    fn qb2_empty_stream_returns_parsing_error() {
783        let result = MatterBuilder::new().from_qualified_base2(b"");
784        assert!(result.is_err());
785    }
786
787    #[test]
788    fn builder_with_raw_rejects_empty_raw() {
789        let result = MatterBuilder::new()
790            .with_code(MatterCode::Ed25519)
791            .with_raw(b"");
792        assert!(result.is_err());
793        assert_eq!(
794            result.err().unwrap(),
795            crate::core::matter::error::ParsingError::EmptyStream
796        );
797    }
798
799    #[test]
800    fn builder_with_soft_rejects_empty_soft() {
801        let result = MatterBuilder::new()
802            .with_code(MatterCode::Tag3)
803            .with_soft("");
804        assert!(result.is_err());
805        assert_eq!(
806            result.err().unwrap(),
807            crate::core::matter::error::ParsingError::EmptyStream
808        );
809    }
810
811    // ── ValidationError tests ──────────────────────────────────────────
812
813    #[test]
814    fn builder_variable_code_in_with_raw_build_fails() {
815        // Variable-size codes should fail in WithRaw::build()
816        let result = MatterBuilder::new()
817            .with_code(MatterCode::Bytes_L0)
818            .with_raw(b"abcdef")
819            .unwrap()
820            .build();
821        assert!(result.is_err());
822    }
823
824    #[test]
825    fn builder_rejects_short_raw_for_ed25519() {
826        // Ed25519 needs 32 bytes
827        let result = MatterBuilder::new()
828            .with_code(MatterCode::Ed25519)
829            .with_raw(&[0u8; 3])
830            .unwrap()
831            .build();
832        assert!(result.is_err());
833    }
834
835    #[test]
836    fn builder_missing_soft_for_special_code() {
837        // Tag3 needs soft (ss=3) but only raw provided via WithRaw::build()
838        let result = MatterBuilder::new()
839            .with_code(MatterCode::Tag3)
840            .with_raw(b"abc")
841            .unwrap()
842            .build();
843        assert!(result.is_err());
844    }
845
846    // ── QB64 validation tests ──────────────────────────────────────────
847
848    #[test]
849    fn qb64_non_zero_pad_bits_rejected() {
850        // 'B' code (Ed25519N) has hs=1, ss=0, cs=1, ps=cs%4=1
851        // Prepending 'A' to payload and decoding: first payload char '_' (=63)
852        // yields first byte 0x03 with lower 2 bits set -> non-zero pad bits
853        let bad_qb64 = b"B_AAY2RlZmdoaWprbG1ub3BxcnN0dXYwMTIzNDU2Nzg5";
854        let result = MatterBuilder::new().from_qualified_base64(bad_qb64);
855        assert!(result.is_err());
856    }
857
858    #[test]
859    fn qb64_non_zero_lead_bytes_tbd1_rejected() {
860        // TBD1 (2___) has ls=1, cs=4, ps=0
861        // Payload '_2Fi' decodes to [0xFF, ...] -> non-zero lead byte
862        let bad_qb64 = b"2____2Fi";
863        let result = MatterBuilder::new().from_qualified_base64(bad_qb64);
864        assert!(result.is_err());
865    }
866
867    #[test]
868    fn qb64_non_zero_lead_bytes_tbd2_rejected() {
869        // TBD2 (3___) has ls=2, cs=4, ps=0
870        // Payload '__96' decodes to [0xFF, 0xFF, 0x7A] -> non-zero lead bytes
871        let bad_qb64 = b"3_____96";
872        let result = MatterBuilder::new().from_qualified_base64(bad_qb64);
873        assert!(result.is_err());
874    }
875
876    #[test]
877    fn qb2_stream_too_short_rejected() {
878        // Ed25519N needs bfs = ceil(44*3/4) = 33 bytes in QB2
879        // Providing only 3 bytes should fail
880        let truncated: &[u8] = &[0x04, 0x69, 0x4e];
881        let result = MatterBuilder::new().from_qualified_base2(truncated);
882        assert!(result.is_err());
883    }
884
885    #[test]
886    fn qb2_short_bfs_lead_does_not_panic() {
887        // Regression for #43: a variable-size code whose soft-encoded size
888        // decodes small enough that bfs < bcs + ls. The old code sliced
889        // trimmed[bcs..bcs+ls] (and trimmed[(bcs+ls)..]) without checking
890        // bcs + ls <= bfs, panicking with an out-of-bounds slice on crafted
891        // input. Parsing untrusted bytes must return a typed Err, never panic.
892        // Exact reproducing input captured from the matter_from_qb2 bolero
893        // fuzz target (#26).
894        let crafted: &[u8] = &[
895            0xe4, 0x70, 0x00, 0x21, 0x58, 0xff, 0xcd, 0x77, 0x4c, 0xa5, 0x50, 0x69, 0xd5, 0x8f,
896            0x3e, 0x87, 0xd4, 0x00, 0xb9, 0xff, 0xf8, 0xcd, 0x94, 0x81, 0xb2, 0xfe, 0x3e, 0x45,
897            0x2f, 0x40, 0x31, 0x6c, 0xb0, 0x69, 0xe4, 0x43, 0x40, 0x7b, 0x70, 0x9c, 0x38, 0x0f,
898            0x00, 0xc3, 0x67, 0x41, 0x21, 0xfb, 0xee, 0xe8, 0x58, 0xee, 0x9e, 0x8c, 0xff, 0x88,
899            0xbd, 0xb1, 0xcd, 0xd0, 0x67, 0x4a, 0x77, 0xe2, 0xea, 0x14, 0x4c, 0x64, 0xb3, 0x8b,
900            0xa5, 0x28, 0xe9, 0xd7, 0x50, 0xc2, 0x07, 0x3b, 0x69, 0xa7, 0xad, 0xc1, 0xd5, 0x25,
901            0xde, 0xc9, 0x8f, 0x58, 0xf3, 0xe4, 0x3e, 0xe4, 0x74, 0x03, 0x87, 0x24, 0x7c, 0xa6,
902            0xd4, 0xd4, 0x18, 0x8c, 0x00, 0x2e, 0xaf, 0x17, 0xb9, 0x1f, 0x79, 0x7d, 0xff, 0x0f,
903            0x35, 0xb0, 0xf8, 0x19, 0x1b, 0x14, 0xcd, 0x25, 0x88, 0xc8, 0x94, 0xf6, 0x80, 0x52,
904            0x81, 0xc3, 0x05, 0xfd, 0xb2, 0xe4, 0xf9, 0x0c, 0xfe, 0xcd, 0x7a, 0x23,
905        ];
906        let err = MatterBuilder::new()
907            .from_qualified_base2(crafted)
908            .expect_err("crafted qb2 with bfs < bcs + ls must be rejected, not parsed");
909        let crate::core::matter::error::MatterBuildError::Validation(validation) = err else {
910            panic!("expected Validation variant, got {err:?}");
911        };
912        assert!(
913            matches!(
914                validation,
915                crate::core::matter::error::ValidationError::IncorrectRawSize { .. }
916            ),
917            "expected IncorrectRawSize, got {validation:?}"
918        );
919    }
920
921    // ── Task 5: Builder raw+code fixed-size tests ────────────────────────
922
923    #[test]
924    fn builder_raw_code_fixed_vectors() {
925        use crate::core::matter::test_vectors::FIXED_VECTORS;
926        use core::str::FromStr;
927
928        for (i, vector) in FIXED_VECTORS.iter().enumerate() {
929            // Skip vectors with empty raw (e.g. Null, No, Yes, Escape, Empty)
930            // since with_raw() rejects empty slices.
931            if vector.raw.is_empty() {
932                continue;
933            }
934
935            let code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
936                panic!("FIXED_VECTORS[{i}]: unknown code_str '{}'", vector.code_str)
937            });
938
939            let matter = MatterBuilder::new()
940                .with_code(code)
941                .with_raw(vector.raw)
942                .unwrap_or_else(|e| {
943                    panic!(
944                        "FIXED_VECTORS[{i}] ({}): with_raw failed: {e:?}",
945                        vector.rust_variant
946                    )
947                })
948                .build()
949                .unwrap_or_else(|e| {
950                    panic!(
951                        "FIXED_VECTORS[{i}] ({}): build failed: {e:?}",
952                        vector.rust_variant
953                    )
954                });
955
956            assert_eq!(
957                matter.raw(),
958                vector.raw,
959                "FIXED_VECTORS[{i}] ({}): raw mismatch",
960                vector.rust_variant
961            );
962            assert_eq!(
963                matter.code(),
964                &code,
965                "FIXED_VECTORS[{i}] ({}): code mismatch",
966                vector.rust_variant
967            );
968        }
969    }
970
971    // ── Task 6: Builder code+soft special code tests ─────────────────────
972
973    #[test]
974    fn builder_soft_only_tag_codes() {
975        use crate::core::matter::test_vectors::SPECIAL_VECTORS;
976        use core::str::FromStr;
977
978        let soft_only_variants = [
979            "Tag3", "Tag7", "Tag11", "Tag1", "Tag2", "Tag4", "Tag5", "Tag6", "Tag8", "Tag9",
980            "Tag10",
981        ];
982
983        for vector in SPECIAL_VECTORS {
984            if !soft_only_variants.contains(&vector.rust_variant) {
985                continue;
986            }
987
988            let code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
989                panic!(
990                    "SPECIAL_VECTORS ({}): unknown code_str '{}'",
991                    vector.rust_variant, vector.code_str
992                )
993            });
994
995            let matter = MatterBuilder::new()
996                .with_code(code)
997                .with_soft(vector.soft)
998                .unwrap_or_else(|e| {
999                    panic!(
1000                        "SPECIAL_VECTORS ({}): with_soft failed: {e:?}",
1001                        vector.rust_variant
1002                    )
1003                })
1004                .build()
1005                .unwrap_or_else(|e| {
1006                    panic!(
1007                        "SPECIAL_VECTORS ({}): build failed: {e:?}",
1008                        vector.rust_variant
1009                    )
1010                });
1011
1012            assert_eq!(
1013                matter.soft(),
1014                vector.soft,
1015                "SPECIAL_VECTORS ({}): soft mismatch",
1016                vector.rust_variant
1017            );
1018            assert!(
1019                matter.raw().is_empty(),
1020                "SPECIAL_VECTORS ({}): expected empty raw for soft-only code",
1021                vector.rust_variant
1022            );
1023            assert_eq!(
1024                matter.code(),
1025                &code,
1026                "SPECIAL_VECTORS ({}): code mismatch",
1027                vector.rust_variant
1028            );
1029        }
1030    }
1031
1032    // ── Task 7: Builder raw+soft tests (GramHead*, TBD*S) ───────────────
1033
1034    #[test]
1035    fn builder_raw_and_soft_special_vectors() {
1036        use crate::core::matter::test_vectors::SPECIAL_VECTORS;
1037        use core::str::FromStr;
1038
1039        let raw_and_soft_variants = [
1040            "GramHeadNeck",
1041            "GramHead",
1042            "GramHeadAIDNeck",
1043            "GramHeadAID",
1044            "TBD0S",
1045            "TBD1S",
1046            "TBD2S",
1047        ];
1048
1049        for vector in SPECIAL_VECTORS {
1050            if !raw_and_soft_variants.contains(&vector.rust_variant) {
1051                continue;
1052            }
1053            assert!(
1054                !vector.raw.is_empty(),
1055                "SPECIAL_VECTORS ({}): expected non-empty raw for raw+soft code",
1056                vector.rust_variant
1057            );
1058
1059            let code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1060                panic!(
1061                    "SPECIAL_VECTORS ({}): unknown code_str '{}'",
1062                    vector.rust_variant, vector.code_str
1063                )
1064            });
1065
1066            let matter = MatterBuilder::new()
1067                .with_code(code)
1068                .with_raw(vector.raw)
1069                .unwrap_or_else(|e| {
1070                    panic!(
1071                        "SPECIAL_VECTORS ({}): with_raw failed: {e:?}",
1072                        vector.rust_variant
1073                    )
1074                })
1075                .with_soft(vector.soft)
1076                .unwrap_or_else(|e| {
1077                    panic!(
1078                        "SPECIAL_VECTORS ({}): with_soft failed: {e:?}",
1079                        vector.rust_variant
1080                    )
1081                })
1082                .build()
1083                .unwrap_or_else(|e| {
1084                    panic!(
1085                        "SPECIAL_VECTORS ({}): build failed: {e:?}",
1086                        vector.rust_variant
1087                    )
1088                });
1089
1090            assert_eq!(
1091                matter.raw(),
1092                vector.raw,
1093                "SPECIAL_VECTORS ({}): raw mismatch",
1094                vector.rust_variant
1095            );
1096            assert_eq!(
1097                matter.soft(),
1098                vector.soft,
1099                "SPECIAL_VECTORS ({}): soft mismatch",
1100                vector.rust_variant
1101            );
1102            assert_eq!(
1103                matter.code(),
1104                &code,
1105                "SPECIAL_VECTORS ({}): code mismatch",
1106                vector.rust_variant
1107            );
1108        }
1109    }
1110
1111    // ── Task 8: QB64 round-trip tests ────────────────────────────────────
1112
1113    #[test]
1114    fn qb64_round_trip_fixed_vectors() {
1115        use crate::core::matter::test_vectors::FIXED_VECTORS;
1116        use core::str::FromStr;
1117
1118        for (i, vector) in FIXED_VECTORS.iter().enumerate() {
1119            let matter = MatterBuilder::new()
1120                .from_qualified_base64(vector.qb64.as_bytes())
1121                .unwrap_or_else(|e| {
1122                    panic!(
1123                        "FIXED_VECTORS[{i}] ({}): QB64 parse failed: {e:?}",
1124                        vector.rust_variant
1125                    )
1126                });
1127
1128            assert_eq!(
1129                matter.raw(),
1130                vector.raw,
1131                "FIXED_VECTORS[{i}] ({}): raw mismatch from QB64",
1132                vector.rust_variant
1133            );
1134            assert_eq!(
1135                matter.soft(),
1136                vector.soft,
1137                "FIXED_VECTORS[{i}] ({}): soft mismatch from QB64",
1138                vector.rust_variant
1139            );
1140
1141            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1142                panic!("FIXED_VECTORS[{i}]: unknown code_str '{}'", vector.code_str)
1143            });
1144            assert_eq!(
1145                matter.code(),
1146                &expected_code,
1147                "FIXED_VECTORS[{i}] ({}): code mismatch from QB64",
1148                vector.rust_variant
1149            );
1150        }
1151    }
1152
1153    #[test]
1154    fn qb64_round_trip_special_vectors() {
1155        use crate::core::matter::test_vectors::SPECIAL_VECTORS;
1156        use core::str::FromStr;
1157
1158        for (i, vector) in SPECIAL_VECTORS.iter().enumerate() {
1159            let matter = MatterBuilder::new()
1160                .from_qualified_base64(vector.qb64.as_bytes())
1161                .unwrap_or_else(|e| {
1162                    panic!(
1163                        "SPECIAL_VECTORS[{i}] ({}): QB64 parse failed: {e:?}",
1164                        vector.rust_variant
1165                    )
1166                });
1167
1168            assert_eq!(
1169                matter.raw(),
1170                vector.raw,
1171                "SPECIAL_VECTORS[{i}] ({}): raw mismatch from QB64",
1172                vector.rust_variant
1173            );
1174            assert_eq!(
1175                matter.soft(),
1176                vector.soft,
1177                "SPECIAL_VECTORS[{i}] ({}): soft mismatch from QB64",
1178                vector.rust_variant
1179            );
1180
1181            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1182                panic!(
1183                    "SPECIAL_VECTORS[{i}]: unknown code_str '{}'",
1184                    vector.code_str
1185                )
1186            });
1187            assert_eq!(
1188                matter.code(),
1189                &expected_code,
1190                "SPECIAL_VECTORS[{i}] ({}): code mismatch from QB64",
1191                vector.rust_variant
1192            );
1193        }
1194    }
1195
1196    #[test]
1197    fn qb64_round_trip_variable_vectors() {
1198        use crate::core::matter::test_vectors::VARIABLE_VECTORS;
1199        use core::str::FromStr;
1200
1201        for (i, vector) in VARIABLE_VECTORS.iter().enumerate() {
1202            let matter = MatterBuilder::new()
1203                .from_qualified_base64(vector.qb64.as_bytes())
1204                .unwrap_or_else(|e| {
1205                    panic!(
1206                        "VARIABLE_VECTORS[{i}] ({}): QB64 parse failed: {e:?}",
1207                        vector.rust_variant
1208                    )
1209                });
1210
1211            assert_eq!(
1212                matter.raw(),
1213                vector.raw,
1214                "VARIABLE_VECTORS[{i}] ({}): raw mismatch from QB64",
1215                vector.rust_variant
1216            );
1217            assert_eq!(
1218                matter.soft(),
1219                vector.soft,
1220                "VARIABLE_VECTORS[{i}] ({}): soft mismatch from QB64",
1221                vector.rust_variant
1222            );
1223
1224            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1225                panic!(
1226                    "VARIABLE_VECTORS[{i}]: unknown code_str '{}'",
1227                    vector.code_str
1228                )
1229            });
1230            assert_eq!(
1231                matter.code(),
1232                &expected_code,
1233                "VARIABLE_VECTORS[{i}] ({}): code mismatch from QB64",
1234                vector.rust_variant
1235            );
1236        }
1237    }
1238
1239    // ── Task 9: QB2 round-trip tests ─────────────────────────────────────
1240
1241    #[test]
1242    fn qb2_round_trip_fixed_vectors() {
1243        use crate::core::matter::test_vectors::FIXED_VECTORS;
1244        use core::str::FromStr;
1245
1246        for (i, vector) in FIXED_VECTORS.iter().enumerate() {
1247            let matter = MatterBuilder::new()
1248                .from_qualified_base2(vector.qb2)
1249                .unwrap_or_else(|e| {
1250                    panic!(
1251                        "FIXED_VECTORS[{i}] ({}): QB2 parse failed: {e:?}",
1252                        vector.rust_variant
1253                    )
1254                });
1255
1256            assert_eq!(
1257                matter.raw(),
1258                vector.raw,
1259                "FIXED_VECTORS[{i}] ({}): raw mismatch from QB2",
1260                vector.rust_variant
1261            );
1262
1263            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1264                panic!("FIXED_VECTORS[{i}]: unknown code_str '{}'", vector.code_str)
1265            });
1266            assert_eq!(
1267                matter.code(),
1268                &expected_code,
1269                "FIXED_VECTORS[{i}] ({}): code mismatch from QB2",
1270                vector.rust_variant
1271            );
1272        }
1273    }
1274
1275    #[test]
1276    fn qb2_handles_ls_gt0_and_short_bfs_codes() {
1277        // Verifies that from_qualified_base2() correctly handles:
1278        // - codes with ls > 0 (e.g. Label1 ls=1, TBD1 ls=1, TBD2 ls=2)
1279        // - zero-payload 4-char codes where bfs < hs (Null, No, Yes, Escape, Empty)
1280        use crate::core::matter::test_vectors::FIXED_VECTORS;
1281        use core::str::FromStr;
1282
1283        // ls > 0 vectors
1284        let ls_gt0_vectors: Vec<_> = FIXED_VECTORS.iter().filter(|v| v.ls > 0).collect();
1285        assert!(
1286            !ls_gt0_vectors.is_empty(),
1287            "Expected at least one fixed vector with ls > 0"
1288        );
1289        for vector in &ls_gt0_vectors {
1290            let matter = MatterBuilder::new()
1291                .from_qualified_base2(vector.qb2)
1292                .unwrap_or_else(|e| {
1293                    panic!(
1294                        "FIXED ({}) with ls={}: QB2 parse failed: {e:?}",
1295                        vector.rust_variant, vector.ls
1296                    )
1297                });
1298            assert_eq!(
1299                matter.raw(),
1300                vector.raw,
1301                "FIXED ({}) with ls={}: raw mismatch",
1302                vector.rust_variant,
1303                vector.ls
1304            );
1305        }
1306
1307        // zero-payload 4-char codes where bfs < hs
1308        let short_qb2_vectors: Vec<_> = FIXED_VECTORS
1309            .iter()
1310            .filter(|v| v.ls == 0 && v.qb2.len() < usize::from(v.hs))
1311            .collect();
1312        assert!(
1313            !short_qb2_vectors.is_empty(),
1314            "Expected at least one fixed vector with bfs < hs"
1315        );
1316        for vector in &short_qb2_vectors {
1317            let matter = MatterBuilder::new()
1318                .from_qualified_base2(vector.qb2)
1319                .unwrap_or_else(|e| {
1320                    panic!(
1321                        "FIXED ({}) with bfs < hs: QB2 parse failed: {e:?}",
1322                        vector.rust_variant
1323                    )
1324                });
1325            let expected_code = MatterCode::from_str(vector.code_str).unwrap();
1326            assert_eq!(
1327                matter.code(),
1328                &expected_code,
1329                "FIXED ({}) with bfs < hs: code mismatch",
1330                vector.rust_variant
1331            );
1332        }
1333    }
1334
1335    #[test]
1336    fn qb2_round_trip_special_vectors() {
1337        use crate::core::matter::test_vectors::SPECIAL_VECTORS;
1338        use core::str::FromStr;
1339
1340        for (i, vector) in SPECIAL_VECTORS.iter().enumerate() {
1341            let matter = MatterBuilder::new()
1342                .from_qualified_base2(vector.qb2)
1343                .unwrap_or_else(|e| {
1344                    panic!(
1345                        "SPECIAL_VECTORS[{i}] ({}): QB2 parse failed: {e:?}",
1346                        vector.rust_variant
1347                    )
1348                });
1349
1350            assert_eq!(
1351                matter.raw(),
1352                vector.raw,
1353                "SPECIAL_VECTORS[{i}] ({}): raw mismatch from QB2",
1354                vector.rust_variant
1355            );
1356
1357            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1358                panic!(
1359                    "SPECIAL_VECTORS[{i}]: unknown code_str '{}'",
1360                    vector.code_str
1361                )
1362            });
1363            assert_eq!(
1364                matter.code(),
1365                &expected_code,
1366                "SPECIAL_VECTORS[{i}] ({}): code mismatch from QB2",
1367                vector.rust_variant
1368            );
1369        }
1370    }
1371
1372    #[test]
1373    fn qb2_round_trip_variable_vectors() {
1374        use crate::core::matter::test_vectors::VARIABLE_VECTORS;
1375        use core::str::FromStr;
1376
1377        for (i, vector) in VARIABLE_VECTORS.iter().enumerate() {
1378            let matter = MatterBuilder::new()
1379                .from_qualified_base2(vector.qb2)
1380                .unwrap_or_else(|e| {
1381                    panic!(
1382                        "VARIABLE_VECTORS[{i}] ({}): QB2 parse failed: {e:?}",
1383                        vector.rust_variant
1384                    )
1385                });
1386
1387            assert_eq!(
1388                matter.raw(),
1389                vector.raw,
1390                "VARIABLE_VECTORS[{i}] ({}): raw mismatch from QB2",
1391                vector.rust_variant
1392            );
1393
1394            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1395                panic!(
1396                    "VARIABLE_VECTORS[{i}]: unknown code_str '{}'",
1397                    vector.code_str
1398                )
1399            });
1400            assert_eq!(
1401                matter.code(),
1402                &expected_code,
1403                "VARIABLE_VECTORS[{i}] ({}): code mismatch from QB2",
1404                vector.rust_variant
1405            );
1406        }
1407    }
1408
1409    // ── Cross-format invariant tests (exhaustive over all vectors) ─────
1410
1411    #[test]
1412    fn all_fixed_vectors_qb64_and_qb2_produce_identical_matter() {
1413        use crate::core::matter::test_vectors::FIXED_VECTORS;
1414
1415        for (i, v) in FIXED_VECTORS.iter().enumerate() {
1416            let from_qb64 = MatterBuilder::new()
1417                .from_qualified_base64(v.qb64.as_bytes())
1418                .unwrap_or_else(|e| {
1419                    panic!("FIXED[{i}] ({}): QB64 parse failed: {e:?}", v.rust_variant)
1420                });
1421            let from_qb2 = MatterBuilder::new()
1422                .from_qualified_base2(v.qb2)
1423                .unwrap_or_else(|e| {
1424                    panic!("FIXED[{i}] ({}): QB2 parse failed: {e:?}", v.rust_variant)
1425                });
1426
1427            assert_eq!(
1428                from_qb64.code(),
1429                from_qb2.code(),
1430                "FIXED[{i}] ({}): code mismatch between QB64 and QB2",
1431                v.rust_variant
1432            );
1433            assert_eq!(
1434                from_qb64.raw(),
1435                from_qb2.raw(),
1436                "FIXED[{i}] ({}): raw mismatch between QB64 and QB2",
1437                v.rust_variant
1438            );
1439            assert_eq!(
1440                from_qb64.soft(),
1441                from_qb2.soft(),
1442                "FIXED[{i}] ({}): soft mismatch between QB64 and QB2",
1443                v.rust_variant
1444            );
1445        }
1446    }
1447
1448    #[test]
1449    fn all_special_vectors_qb64_and_qb2_produce_identical_matter() {
1450        use crate::core::matter::test_vectors::SPECIAL_VECTORS;
1451
1452        for (i, v) in SPECIAL_VECTORS.iter().enumerate() {
1453            let from_qb64 = MatterBuilder::new()
1454                .from_qualified_base64(v.qb64.as_bytes())
1455                .unwrap_or_else(|e| {
1456                    panic!(
1457                        "SPECIAL[{i}] ({}): QB64 parse failed: {e:?}",
1458                        v.rust_variant
1459                    )
1460                });
1461            let from_qb2 = MatterBuilder::new()
1462                .from_qualified_base2(v.qb2)
1463                .unwrap_or_else(|e| {
1464                    panic!("SPECIAL[{i}] ({}): QB2 parse failed: {e:?}", v.rust_variant)
1465                });
1466
1467            assert_eq!(
1468                from_qb64.code(),
1469                from_qb2.code(),
1470                "SPECIAL[{i}] ({}): code mismatch between QB64 and QB2",
1471                v.rust_variant
1472            );
1473            assert_eq!(
1474                from_qb64.raw(),
1475                from_qb2.raw(),
1476                "SPECIAL[{i}] ({}): raw mismatch between QB64 and QB2",
1477                v.rust_variant
1478            );
1479            assert_eq!(
1480                from_qb64.soft(),
1481                from_qb2.soft(),
1482                "SPECIAL[{i}] ({}): soft mismatch between QB64 and QB2",
1483                v.rust_variant
1484            );
1485        }
1486    }
1487
1488    #[test]
1489    fn all_variable_vectors_qb64_and_qb2_produce_identical_matter() {
1490        use crate::core::matter::test_vectors::VARIABLE_VECTORS;
1491
1492        for (i, v) in VARIABLE_VECTORS.iter().enumerate() {
1493            let from_qb64 = MatterBuilder::new()
1494                .from_qualified_base64(v.qb64.as_bytes())
1495                .unwrap_or_else(|e| {
1496                    panic!(
1497                        "VARIABLE[{i}] ({}): QB64 parse failed: {e:?}",
1498                        v.rust_variant
1499                    )
1500                });
1501            let from_qb2 = MatterBuilder::new()
1502                .from_qualified_base2(v.qb2)
1503                .unwrap_or_else(|e| {
1504                    panic!(
1505                        "VARIABLE[{i}] ({}): QB2 parse failed: {e:?}",
1506                        v.rust_variant
1507                    )
1508                });
1509
1510            assert_eq!(
1511                from_qb64.code(),
1512                from_qb2.code(),
1513                "VARIABLE[{i}] ({}): code mismatch between QB64 and QB2",
1514                v.rust_variant
1515            );
1516            assert_eq!(
1517                from_qb64.raw(),
1518                from_qb2.raw(),
1519                "VARIABLE[{i}] ({}): raw mismatch between QB64 and QB2",
1520                v.rust_variant
1521            );
1522            assert_eq!(
1523                from_qb64.soft(),
1524                from_qb2.soft(),
1525                "VARIABLE[{i}] ({}): soft mismatch between QB64 and QB2",
1526                v.rust_variant
1527            );
1528        }
1529    }
1530
1531    // ── Task 14: Variable-size code promotion tests ──────────────────────
1532
1533    #[test]
1534    fn variable_size_qb64_round_trip_preserves_soft_size_encoding() {
1535        use crate::core::matter::test_vectors::VARIABLE_VECTORS;
1536        use core::str::FromStr;
1537
1538        for (i, vector) in VARIABLE_VECTORS.iter().enumerate() {
1539            let matter = MatterBuilder::new()
1540                .from_qualified_base64(vector.qb64.as_bytes())
1541                .unwrap_or_else(|e| {
1542                    panic!(
1543                        "VARIABLE_VECTORS[{i}] ({}): QB64 parse failed: {e:?}",
1544                        vector.rust_variant
1545                    )
1546                });
1547
1548            // Verify raw round-trips correctly
1549            assert_eq!(
1550                matter.raw(),
1551                vector.raw,
1552                "VARIABLE_VECTORS[{i}] ({}): raw mismatch",
1553                vector.rust_variant
1554            );
1555
1556            // Verify the soft field contains the size encoding
1557            assert_eq!(
1558                matter.soft(),
1559                vector.soft,
1560                "VARIABLE_VECTORS[{i}] ({}): soft (size encoding) mismatch",
1561                vector.rust_variant
1562            );
1563
1564            // Verify the code was correctly identified/promoted
1565            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1566                panic!(
1567                    "VARIABLE_VECTORS[{i}]: unknown code_str '{}'",
1568                    vector.code_str
1569                )
1570            });
1571            assert_eq!(
1572                matter.code(),
1573                &expected_code,
1574                "VARIABLE_VECTORS[{i}] ({}): code mismatch after promotion",
1575                vector.rust_variant
1576            );
1577
1578            // Verify fs is None (variable-size) per the test vector
1579            assert!(
1580                vector.fs.is_none(),
1581                "VARIABLE_VECTORS[{i}] ({}): expected variable-size (fs=None)",
1582                vector.rust_variant
1583            );
1584        }
1585    }
1586
1587    #[test]
1588    fn variable_size_qb2_round_trip_preserves_raw_and_code() {
1589        use crate::core::matter::test_vectors::VARIABLE_VECTORS;
1590        use core::str::FromStr;
1591
1592        for (i, vector) in VARIABLE_VECTORS.iter().enumerate() {
1593            let matter = MatterBuilder::new()
1594                .from_qualified_base2(vector.qb2)
1595                .unwrap_or_else(|e| {
1596                    panic!(
1597                        "VARIABLE_VECTORS[{i}] ({}): QB2 parse failed: {e:?}",
1598                        vector.rust_variant
1599                    )
1600                });
1601
1602            assert_eq!(
1603                matter.raw(),
1604                vector.raw,
1605                "VARIABLE_VECTORS[{i}] ({}): raw mismatch from QB2",
1606                vector.rust_variant
1607            );
1608
1609            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1610                panic!(
1611                    "VARIABLE_VECTORS[{i}]: unknown code_str '{}'",
1612                    vector.code_str
1613                )
1614            });
1615            assert_eq!(
1616                matter.code(),
1617                &expected_code,
1618                "VARIABLE_VECTORS[{i}] ({}): code mismatch from QB2",
1619                vector.rust_variant
1620            );
1621        }
1622    }
1623
1624    // ── Boundary-size variable code tests (Small→Big promotion) ─────────
1625
1626    #[test]
1627    fn boundary_vectors_qb64_round_trip() {
1628        use crate::core::matter::test_vectors_boundary::BOUNDARY_VECTORS;
1629        use core::str::FromStr;
1630
1631        for (i, vector) in BOUNDARY_VECTORS.iter().enumerate() {
1632            let matter = MatterBuilder::new()
1633                .from_qualified_base64(vector.qb64.as_bytes())
1634                .unwrap_or_else(|e| {
1635                    panic!(
1636                        "BOUNDARY_VECTORS[{i}] ({}): QB64 parse failed: {e:?}",
1637                        vector.rust_variant
1638                    )
1639                });
1640
1641            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1642                panic!(
1643                    "BOUNDARY_VECTORS[{i}]: unknown code_str '{}'",
1644                    vector.code_str
1645                )
1646            });
1647            assert_eq!(
1648                matter.code(),
1649                &expected_code,
1650                "BOUNDARY_VECTORS[{i}] ({}): code mismatch from QB64",
1651                vector.rust_variant
1652            );
1653            assert_eq!(
1654                matter.raw(),
1655                vector.raw,
1656                "BOUNDARY_VECTORS[{i}] ({}): raw mismatch from QB64",
1657                vector.rust_variant
1658            );
1659            assert_eq!(
1660                matter.soft(),
1661                vector.soft,
1662                "BOUNDARY_VECTORS[{i}] ({}): soft mismatch from QB64",
1663                vector.rust_variant
1664            );
1665        }
1666    }
1667
1668    #[test]
1669    fn boundary_vectors_qb2_round_trip() {
1670        use crate::core::matter::test_vectors_boundary::BOUNDARY_VECTORS;
1671        use core::str::FromStr;
1672
1673        for (i, vector) in BOUNDARY_VECTORS.iter().enumerate() {
1674            let matter = MatterBuilder::new()
1675                .from_qualified_base2(vector.qb2)
1676                .unwrap_or_else(|e| {
1677                    panic!(
1678                        "BOUNDARY_VECTORS[{i}] ({}): QB2 parse failed: {e:?}",
1679                        vector.rust_variant
1680                    )
1681                });
1682
1683            let expected_code = MatterCode::from_str(vector.code_str).unwrap_or_else(|_| {
1684                panic!(
1685                    "BOUNDARY_VECTORS[{i}]: unknown code_str '{}'",
1686                    vector.code_str
1687                )
1688            });
1689            assert_eq!(
1690                matter.code(),
1691                &expected_code,
1692                "BOUNDARY_VECTORS[{i}] ({}): code mismatch from QB2",
1693                vector.rust_variant
1694            );
1695            assert_eq!(
1696                matter.raw(),
1697                vector.raw,
1698                "BOUNDARY_VECTORS[{i}] ({}): raw mismatch from QB2",
1699                vector.rust_variant
1700            );
1701        }
1702    }
1703
1704    #[test]
1705    fn boundary_vectors_qb64_and_qb2_produce_identical_matter() {
1706        use crate::core::matter::test_vectors_boundary::BOUNDARY_VECTORS;
1707
1708        for (i, vector) in BOUNDARY_VECTORS.iter().enumerate() {
1709            let from_qb64 = MatterBuilder::new()
1710                .from_qualified_base64(vector.qb64.as_bytes())
1711                .unwrap_or_else(|e| {
1712                    panic!(
1713                        "BOUNDARY_VECTORS[{i}] ({}): QB64 parse failed: {e:?}",
1714                        vector.rust_variant
1715                    )
1716                });
1717            let from_qb2 = MatterBuilder::new()
1718                .from_qualified_base2(vector.qb2)
1719                .unwrap_or_else(|e| {
1720                    panic!(
1721                        "BOUNDARY_VECTORS[{i}] ({}): QB2 parse failed: {e:?}",
1722                        vector.rust_variant
1723                    )
1724                });
1725
1726            assert_eq!(
1727                from_qb64.code(),
1728                from_qb2.code(),
1729                "BOUNDARY_VECTORS[{i}] ({}): code mismatch between QB64 and QB2",
1730                vector.rust_variant
1731            );
1732            assert_eq!(
1733                from_qb64.raw(),
1734                from_qb2.raw(),
1735                "BOUNDARY_VECTORS[{i}] ({}): raw mismatch between QB64 and QB2",
1736                vector.rust_variant
1737            );
1738            assert_eq!(
1739                from_qb64.soft(),
1740                from_qb2.soft(),
1741                "BOUNDARY_VECTORS[{i}] ({}): soft mismatch between QB64 and QB2",
1742                vector.rust_variant
1743            );
1744        }
1745    }
1746
1747    #[test]
1748    fn boundary_vector_code_promotion_is_correct() {
1749        use crate::core::matter::test_vectors_boundary::BOUNDARY_VECTORS;
1750
1751        // Vector 0: 12285 bytes (4095 triplets) — should stay Small (StrB64_L0, "4A")
1752        assert_eq!(
1753            BOUNDARY_VECTORS[0].code_str, "4A",
1754            "12285-byte vector should use Small code '4A'"
1755        );
1756        assert_eq!(BOUNDARY_VECTORS[0].ss, 2, "Small code should have ss=2");
1757
1758        // Vector 1: 12288 bytes (4096 triplets) — should promote to Big (StrB64Big_L0, "7AAA")
1759        assert_eq!(
1760            BOUNDARY_VECTORS[1].code_str, "7AAA",
1761            "12288-byte vector should promote to Big code '7AAA'"
1762        );
1763        assert_eq!(BOUNDARY_VECTORS[1].ss, 4, "Big code should have ss=4");
1764
1765        // Vector 2: 12291 bytes (4097 triplets) — should also be Big
1766        assert_eq!(
1767            BOUNDARY_VECTORS[2].code_str, "7AAA",
1768            "12291-byte vector should use Big code '7AAA'"
1769        );
1770        assert_eq!(BOUNDARY_VECTORS[2].ss, 4, "Big code should have ss=4");
1771    }
1772
1773    // ── QB2 parsing edge case tests ──────────────────────────────────────
1774    // These tests verify that the QB2 parser handles malformed, truncated,
1775    // and boundary-condition binary streams without panicking.
1776
1777    #[test]
1778    fn qb2_parsing_rejects_single_byte() {
1779        // A single byte is never enough for any CESR code (minimum 1-char
1780        // code still needs raw data bytes following the code byte).
1781        let result = MatterBuilder::new().from_qualified_base2(&[0x00]);
1782        assert!(result.is_err());
1783    }
1784
1785    #[test]
1786    fn qb2_parsing_rejects_two_bytes() {
1787        // Two bytes may parse a 1-char code header but will have insufficient
1788        // data for even the smallest fixed-size primitive (Short = 4 b64 chars
1789        // = 3 bytes in QB2).
1790        let result = MatterBuilder::new().from_qualified_base2(&[0x00, 0x00]);
1791        assert!(result.is_err());
1792    }
1793
1794    #[test]
1795    fn qb2_parsing_rejects_truncated_2char_code() {
1796        // A 2-char code needs 2 bytes for the header (hs=2 -> 2*6/8 rounded
1797        // up = 2 bytes). Providing only the first byte of a 2-char code
1798        // should fail with StreamTooShort.
1799        // 0xD0 is the first byte of Salt128 ("0A") in QB2.
1800        let result = MatterBuilder::new().from_qualified_base2(&[0xD0]);
1801        assert!(result.is_err());
1802    }
1803
1804    #[test]
1805    fn qb2_parsing_rejects_truncated_4char_code() {
1806        // A 4-char code needs 3 bytes for the header. Providing only 2 bytes
1807        // should fail.
1808        // 0xD4, 0x00 is the start of ECDSA256k1N ("1AAA") in QB2.
1809        let result = MatterBuilder::new().from_qualified_base2(&[0xD4, 0x00]);
1810        assert!(result.is_err());
1811    }
1812
1813    #[test]
1814    fn qb2_parsing_rejects_header_only_no_payload() {
1815        // Provide exactly the header bytes for Ed25519Seed (1-char code, "A")
1816        // but no payload data. The code needs 32 raw bytes.
1817        // In QB2: bhs = ceil(1*3/4) = 1 byte for the code, then 32 raw bytes.
1818        // Only providing the code byte should fail.
1819        let result = MatterBuilder::new().from_qualified_base2(&[0x00]);
1820        assert!(result.is_err());
1821    }
1822
1823    #[test]
1824    fn qb2_all_0xff_bytes_rejected() {
1825        // A stream of all 0xFF bytes maps to sextet value 63 in the first
1826        // position, which should not match any valid CESR code.
1827        let all_ff = [0xFF; 64];
1828        let result = MatterBuilder::new().from_qualified_base2(&all_ff);
1829        assert!(result.is_err());
1830    }
1831
1832    #[test]
1833    fn qb2_high_bit_patterns_do_not_panic() {
1834        // Various high-bit patterns that could trigger edge cases in the
1835        // sextet extraction logic.
1836        let patterns: Vec<Vec<u8>> = vec![
1837            vec![0x80; 4],
1838            vec![0xC0; 4],
1839            vec![0xE0; 4],
1840            vec![0xF0; 4],
1841            vec![0xFE; 4],
1842            vec![0x80, 0x00, 0x00, 0x00],
1843            vec![0xFF, 0x00, 0xFF, 0x00],
1844        ];
1845        for pattern in &patterns {
1846            // Should not panic -- errors are acceptable
1847            let _ = MatterBuilder::new().from_qualified_base2(pattern);
1848        }
1849    }
1850
1851    // ── proptest: Builder construction with random raw bytes ─────────────
1852
1853    mod prop {
1854        use super::*;
1855        use proptest::prelude::*;
1856
1857        // One proptest per representative code, each generating random raw bytes
1858        // of sufficient length and verifying builder construction succeeds.
1859
1860        macro_rules! proptest_builder_raw {
1861            ($name:ident, $code:expr, $raw_size:expr) => {
1862                proptest! {
1863                    #[test]
1864                    fn $name(
1865                        raw_bytes in proptest::collection::vec(any::<u8>(), $raw_size..=$raw_size + 64)
1866                    ) {
1867                        let code = $code;
1868                        let matter = MatterBuilder::new()
1869                            .with_code(code)
1870                            .with_raw(&raw_bytes)
1871                            .unwrap()
1872                            .build()
1873                            .unwrap();
1874
1875                        prop_assert_eq!(matter.raw(), &raw_bytes[..$raw_size]);
1876                        prop_assert_eq!(matter.code(), &code);
1877                    }
1878                }
1879            };
1880        }
1881
1882        // 1-char codes
1883        proptest_builder_raw!(random_raw_ed25519_seed, MatterCode::Ed25519Seed, 32);
1884        proptest_builder_raw!(random_raw_blake3_256, MatterCode::Blake3_256, 32);
1885        proptest_builder_raw!(random_raw_short, MatterCode::Short, 2);
1886
1887        // 2-char codes
1888        proptest_builder_raw!(random_raw_salt128, MatterCode::Salt128, 16);
1889        proptest_builder_raw!(random_raw_ed25519_sig, MatterCode::Ed25519Sig, 64);
1890        proptest_builder_raw!(random_raw_long, MatterCode::Long, 4);
1891
1892        // 4-char codes
1893        proptest_builder_raw!(random_raw_ed448_sig, MatterCode::Ed448Sig, 114);
1894        proptest_builder_raw!(random_raw_ecdsa256k1n, MatterCode::ECDSA256k1N, 33);
1895
1896        proptest! {
1897            #[test]
1898            fn qb2_parsing_never_panics_on_random_bytes(
1899                random_bytes in proptest::collection::vec(any::<u8>(), 0..256)
1900            ) {
1901                // The QB2 parser must never panic on arbitrary input.
1902                // It should either succeed or return an error.
1903                let _ = MatterBuilder::new().from_qualified_base2(&random_bytes);
1904            }
1905        }
1906
1907        proptest! {
1908            #[test]
1909            fn qb64_parsing_never_panics_on_random_payload(
1910                idx in 0usize..50,
1911                random_bytes in proptest::collection::vec(any::<u8>(), 0..256)
1912            ) {
1913                use crate::core::matter::test_vectors::FIXED_VECTORS;
1914
1915                let vector = &FIXED_VECTORS[idx % FIXED_VECTORS.len()];
1916
1917                // Build a QB64 stream with the correct code prefix but random payload
1918                let cs = usize::from(vector.hs) + usize::from(vector.ss);
1919                let prefix = &vector.qb64[..cs];
1920                let b64_chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1921                let payload: String = random_bytes.iter()
1922                    .map(|&b| char::from(b64_chars[usize::from(b) % 64]))
1923                    .collect();
1924
1925                // Pad to correct fs length if needed
1926                if let Some(fs) = vector.fs {
1927                    let needed = usize::from(fs) - cs;
1928                    if payload.len() >= needed {
1929                        let stream = format!("{prefix}{}", &payload[..needed]);
1930                        // Should not panic — errors are fine
1931                        let _ = MatterBuilder::new()
1932                            .from_qualified_base64(stream.as_bytes());
1933                    }
1934                }
1935            }
1936        }
1937    }
1938}