1use super::cesr_code::CesrCode;
2use super::hard::{get_hard_size_from_byte, get_hard_size_from_sextet};
3use super::sealed::Sealed;
4use crate::b64::encode_binary;
5use crate::core::matter::{
6 MatterPart,
7 error::{ParsingError, ValidationError},
8 sizage::{Sizage, SizeType},
9};
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};
16use core::{num::NonZeroUsize, str::FromStr};
17use strum::{AsRefStr, Display, EnumIter, EnumString, IntoStaticStr, VariantNames};
18
19pub const SMALL_VAR_HEADER_SIZE: u8 = 3;
21pub const LARGE_VAR_HEADER_SIZE: u8 = 6;
23
24const SMALL_VAR_SIZES: [char; 3] = ['4', '5', '6'];
25const LARGE_VAR_SIZES: [char; 3] = ['7', '8', '9'];
26
27#[derive(
32 Debug,
33 Eq,
34 PartialEq,
35 Clone,
36 Copy,
37 Hash,
38 AsRefStr,
39 EnumString,
40 IntoStaticStr,
41 VariantNames,
42 Display,
43 EnumIter,
44)]
45#[allow(
46 non_camel_case_types,
47 reason = "CESR spec uses underscored code names like Blake3_256"
48)]
49pub enum MatterCode {
50 #[strum(serialize = "A")]
52 Ed25519Seed,
53 #[strum(serialize = "B")]
55 Ed25519N,
56 #[strum(serialize = "C")]
58 X25519,
59 #[strum(serialize = "D")]
61 Ed25519,
62 #[strum(serialize = "E")]
64 Blake3_256,
65 #[strum(serialize = "F")]
67 Blake2b_256,
68 #[strum(serialize = "G")]
70 Blake2s_256,
71 #[strum(serialize = "H")]
73 SHA3_256,
74 #[strum(serialize = "I")]
76 SHA2_256,
77 #[strum(serialize = "J")]
79 ECDSA256k1Seed,
80 #[strum(serialize = "K")]
82 Ed448Seed,
83 #[strum(serialize = "L")]
85 X448,
86 #[strum(serialize = "M")]
88 Short,
89 #[strum(serialize = "N")]
91 Big,
92 #[strum(serialize = "O")]
94 X25519Private,
95 #[strum(serialize = "P")]
97 X25519CipherSeed,
98 #[strum(serialize = "Q")]
100 ECDSA256r1Seed,
101 #[strum(serialize = "R")]
103 Tall,
104 #[strum(serialize = "S")]
106 Large,
107 #[strum(serialize = "T")]
109 Great,
110 #[strum(serialize = "U")]
112 Vast,
113 #[strum(serialize = "V")]
115 Label1,
116 #[strum(serialize = "W")]
118 Label2,
119 #[strum(serialize = "X")]
121 Tag3,
122 #[strum(serialize = "Y")]
124 Tag7,
125 #[strum(serialize = "Z")]
127 Tag11,
128 #[strum(serialize = "a")]
130 Salt256,
131 #[strum(serialize = "0A")]
133 Salt128,
134 #[strum(serialize = "0B")]
136 Ed25519Sig,
137 #[strum(serialize = "0C")]
139 ECDSA256k1Sig,
140 #[strum(serialize = "0D")]
142 Blake3_512,
143 #[strum(serialize = "0E")]
145 Blake2b_512,
146 #[strum(serialize = "0F")]
148 SHA3_512,
149 #[strum(serialize = "0G")]
151 SHA2_512,
152 #[strum(serialize = "0H")]
154 Long,
155 #[strum(serialize = "0I")]
157 ECDSA256r1Sig,
158 #[strum(serialize = "0J")]
160 Tag1,
161 #[strum(serialize = "0K")]
163 Tag2,
164 #[strum(serialize = "0L")]
166 Tag5,
167 #[strum(serialize = "0M")]
169 Tag6,
170 #[strum(serialize = "0N")]
172 Tag9,
173 #[strum(serialize = "0O")]
175 Tag10,
176 #[strum(serialize = "0P")]
178 GramHeadNeck,
179 #[strum(serialize = "0Q")]
181 GramHead,
182 #[strum(serialize = "0R")]
184 GramHeadAIDNeck,
185 #[strum(serialize = "0S")]
187 GramHeadAID,
188 #[strum(serialize = "1AAA")]
190 ECDSA256k1N,
191 #[strum(serialize = "1AAB")]
193 ECDSA256k1,
194 #[strum(serialize = "1AAC")]
196 Ed448N,
197 #[strum(serialize = "1AAD")]
199 Ed448,
200 #[strum(serialize = "1AAE")]
202 Ed448Sig,
203 #[strum(serialize = "1AAF")]
205 Tag4,
206 #[strum(serialize = "1AAG")]
208 DateTime,
209 #[strum(serialize = "1AAH")]
211 X25519CipherSalt,
212 #[strum(serialize = "1AAI")]
214 ECDSA256r1N,
215 #[strum(serialize = "1AAJ")]
217 ECDSA256r1,
218 #[strum(serialize = "1AAK")]
220 Null,
221 #[strum(serialize = "1AAL")]
223 No,
224 #[strum(serialize = "1AAM")]
226 Yes,
227 #[strum(serialize = "1AAN")]
229 Tag8,
230 #[strum(serialize = "1AAO")]
232 Escape,
233 #[strum(serialize = "1AAP")]
235 Empty,
236 #[strum(serialize = "1__-")]
238 TBD0S,
239 #[strum(serialize = "1___")]
241 TBD0,
242 #[strum(serialize = "2__-")]
244 TBD1S,
245 #[strum(serialize = "2___")]
247 TBD1,
248 #[strum(serialize = "3__-")]
250 TBD2S,
251 #[strum(serialize = "3___")]
253 TBD2,
254 #[strum(serialize = "4A")]
256 StrB64_L0,
257 #[strum(serialize = "5A")]
259 StrB64_L1,
260 #[strum(serialize = "6A")]
262 StrB64_L2,
263 #[strum(serialize = "7AAA")]
265 StrB64Big_L0,
266 #[strum(serialize = "8AAA")]
268 StrB64Big_L1,
269 #[strum(serialize = "9AAA")]
271 StrB64Big_L2,
272 #[strum(serialize = "4B")]
274 Bytes_L0,
275 #[strum(serialize = "5B")]
277 Bytes_L1,
278 #[strum(serialize = "6B")]
280 Bytes_L2,
281 #[strum(serialize = "7AAB")]
283 BytesBig_L0,
284 #[strum(serialize = "8AAB")]
286 BytesBig_L1,
287 #[strum(serialize = "9AAB")]
289 BytesBig_L2,
290 #[strum(serialize = "4C")]
292 X25519Cipher_L0,
293 #[strum(serialize = "5C")]
295 X25519Cipher_L1,
296 #[strum(serialize = "6C")]
298 X25519Cipher_L2,
299 #[strum(serialize = "7AAC")]
301 X25519CipherBig_L0,
302 #[strum(serialize = "8AAC")]
304 X25519CipherBig_L1,
305 #[strum(serialize = "9AAC")]
307 X25519CipherBig_L2,
308 #[strum(serialize = "4D")]
310 X25519CipherQB64_L0,
311 #[strum(serialize = "5D")]
313 X25519CipherQB64_L1,
314 #[strum(serialize = "6D")]
316 X25519CipherQB64_L2,
317 #[strum(serialize = "7AAD")]
319 X25519CipherQB64Big_L0,
320 #[strum(serialize = "8AAD")]
322 X25519CipherQB64Big_L1,
323 #[strum(serialize = "9AAD")]
325 X25519CipherQB64Big_L2,
326 #[strum(serialize = "4E")]
328 X25519CipherQB2_L0,
329 #[strum(serialize = "5E")]
331 X25519CipherQB2_L1,
332 #[strum(serialize = "6E")]
334 X25519CipherQB2_L2,
335 #[strum(serialize = "7AAE")]
337 X25519CipherQB2Big_L0,
338 #[strum(serialize = "8AAE")]
340 X25519CipherQB2Big_L1,
341 #[strum(serialize = "9AAE")]
343 X25519CipherQB2Big_L2,
344 #[strum(serialize = "4F")]
346 HPKEBaseCipher_L0,
347 #[strum(serialize = "5F")]
349 HPKEBaseCipher_L1,
350 #[strum(serialize = "6F")]
352 HPKEBaseCipher_L2,
353 #[strum(serialize = "7AAF")]
355 HPKEBaseCipherBig_L0,
356 #[strum(serialize = "8AAF")]
358 HPKEBaseCipherBig_L1,
359 #[strum(serialize = "9AAF")]
361 HPKEBaseCipherBig_L2,
362 #[strum(serialize = "4H")]
364 Decimal_L0,
365 #[strum(serialize = "5H")]
367 Decimal_L1,
368 #[strum(serialize = "6H")]
370 Decimal_L2,
371 #[strum(serialize = "7AAH")]
373 DecimalBig_L0,
374 #[strum(serialize = "8AAH")]
376 DecimalBig_L1,
377 #[strum(serialize = "9AAH")]
379 DecimalBig_L2,
380}
381
382impl MatterCode {
383 pub fn from_base64_stream(stream: &[u8]) -> Result<Self, ParsingError> {
390 let hs: usize = stream
391 .first()
392 .ok_or(ParsingError::EmptyStream)
393 .and_then(|b| {
394 get_hard_size_from_byte(*b).ok_or_else(|| ParsingError::MalformedCode {
395 part: MatterPart::Head,
396 found: b.to_string(),
397 })
398 })?
399 .into();
400 if stream.len() < hs {
401 return Err(ParsingError::StreamTooShort(MatterPart::Head));
402 }
403 let code_str = str::from_utf8(&stream[..hs]).map_err(ParsingError::InvalidUtf8)?;
404 let code = Self::from_str(code_str)
405 .map_err(|_| ParsingError::UnknownMatterCode(code_str.to_owned()))?;
406 Ok(code)
407 }
408
409 pub fn from_stream(stream: &[u8]) -> Result<Self, ParsingError> {
416 let hard_size: usize = stream
417 .first()
418 .ok_or(ParsingError::EmptyStream)
419 .map(|b| b >> 2)
420 .and_then(|b| {
421 get_hard_size_from_sextet(b).ok_or_else(|| ParsingError::MalformedCode {
422 part: MatterPart::Head,
423 found: b.to_string(),
424 })
425 })?
426 .into();
427
428 let hard_size_nz = NonZeroUsize::new(hard_size).ok_or(ParsingError::EmptyStream)?;
429 let bhs = (hard_size_nz.get() * 3).div_ceil(4);
430 if stream.len() < bhs {
431 return Err(ParsingError::StreamTooShort(MatterPart::Head));
432 }
433 let code_str = encode_binary(stream, hard_size_nz).map_err(ParsingError::Conversion)?;
434 let code = Self::from_str(&code_str)
435 .map_err(|_| ParsingError::UnknownMatterCode(code_str.clone()))?;
436 Ok(code)
437 }
438
439 #[must_use]
443 pub const fn is_base64_text_code(&self) -> bool {
444 matches!(
445 self,
446 Self::StrB64_L0
447 | Self::StrB64_L1
448 | Self::StrB64_L2
449 | Self::StrB64Big_L0
450 | Self::StrB64Big_L1
451 | Self::StrB64Big_L2
452 )
453 }
454
455 #[must_use]
459 pub const fn is_text_code(&self) -> bool {
460 matches!(
461 self,
462 Self::Bytes_L0
463 | Self::Bytes_L1
464 | Self::Bytes_L2
465 | Self::BytesBig_L0
466 | Self::BytesBig_L1
467 | Self::BytesBig_L2
468 )
469 }
470
471 #[must_use]
476 pub const fn is_base_64_decimal(&self) -> bool {
477 matches!(
478 self,
479 Self::Decimal_L0
480 | Self::Decimal_L1
481 | Self::Decimal_L2
482 | Self::DecimalBig_L0
483 | Self::DecimalBig_L1
484 | Self::DecimalBig_L2
485 )
486 }
487
488 #[must_use]
494 pub const fn is_digest(&self) -> bool {
495 matches!(
496 self,
497 Self::Blake3_256
498 | Self::Blake2b_256
499 | Self::Blake2s_256
500 | Self::SHA3_256
501 | Self::SHA2_256
502 | Self::Blake3_512
503 | Self::Blake2b_512
504 | Self::SHA3_512
505 | Self::SHA2_512
506 )
507 }
508
509 #[must_use]
514 pub const fn is_nonce(&self) -> bool {
515 matches!(
516 self,
517 Self::Empty
518 | Self::Salt128
519 | Self::Salt256
520 | Self::Blake3_256
521 | Self::Blake2b_256
522 | Self::Blake2s_256
523 | Self::SHA3_256
524 | Self::SHA2_256
525 | Self::Blake3_512
526 | Self::Blake2b_512
527 | Self::SHA3_512
528 | Self::SHA2_512
529 )
530 }
531
532 #[must_use]
537 pub const fn is_base64_num(&self) -> bool {
538 matches!(
539 self,
540 Self::Short
541 | Self::Long
542 | Self::Tall
543 | Self::Big
544 | Self::Large
545 | Self::Great
546 | Self::Vast
547 | Self::Salt128 )
549 }
550
551 #[must_use]
556 pub const fn is_base64_tag(&self) -> bool {
557 matches!(
558 self,
559 Self::Tag1
560 | Self::Tag2
561 | Self::Tag3
562 | Self::Tag4
563 | Self::Tag5
564 | Self::Tag6
565 | Self::Tag7
566 | Self::Tag8
567 | Self::Tag9
568 | Self::Tag10
569 | Self::Tag11
570 )
571 }
572
573 #[must_use]
578 pub const fn is_label(&self) -> bool {
579 matches!(
580 self,
581 Self::Empty
582 | Self::Tag1
583 | Self::Tag2
584 | Self::Tag3
585 | Self::Tag4
586 | Self::Tag5
587 | Self::Tag6
588 | Self::Tag7
589 | Self::Tag8
590 | Self::Tag9
591 | Self::Tag10
592 | Self::Tag11
593 | Self::StrB64_L0
594 | Self::StrB64_L1
595 | Self::StrB64_L2
596 | Self::StrB64Big_L0
597 | Self::StrB64Big_L1
598 | Self::StrB64Big_L2
599 | Self::Label1
600 | Self::Label2
601 | Self::Bytes_L0
602 | Self::Bytes_L1
603 | Self::Bytes_L2
604 | Self::BytesBig_L0
605 | Self::BytesBig_L1
606 | Self::BytesBig_L2
607 )
608 }
609
610 #[must_use]
615 pub const fn is_prefix_derivation(&self) -> bool {
616 matches!(
617 self,
618 Self::Ed25519N
619 | Self::Ed25519
620 | Self::Blake3_256
621 | Self::Blake2b_256
622 | Self::Blake2s_256
623 | Self::SHA3_256
624 | Self::SHA2_256
625 | Self::Blake3_512
626 | Self::Blake2b_512
627 | Self::SHA3_512
628 | Self::SHA2_512
629 | Self::ECDSA256k1N
630 | Self::ECDSA256k1
631 | Self::Ed448N
632 | Self::Ed448
633 | Self::Ed448Sig
634 | Self::ECDSA256r1N
635 | Self::ECDSA256r1
636 )
637 }
638
639 #[must_use]
643 pub const fn is_non_transferable(&self) -> bool {
644 matches!(
645 self,
646 Self::Ed25519N | Self::ECDSA256k1N | Self::Ed448N | Self::ECDSA256r1N
647 )
648 }
649
650 #[must_use]
654 pub const fn is_prefix_non_digestive(&self) -> bool {
655 matches!(
656 self,
657 Self::Ed25519N
658 | Self::Ed25519
659 | Self::ECDSA256k1N
660 | Self::ECDSA256k1
661 | Self::Ed448N
662 | Self::Ed448
663 | Self::ECDSA256r1N
664 | Self::ECDSA256r1
665 )
666 }
667
668 #[must_use]
670 #[allow(
671 clippy::too_many_lines,
672 reason = "exhaustive lookup table over 110 CESR code variants"
673 )]
674 pub const fn get_sizage(&self) -> Sizage {
675 match self {
676 Self::Ed25519Seed
677 | Self::Ed25519N
678 | Self::X25519
679 | Self::Ed25519
680 | Self::Blake3_256
681 | Self::Blake2b_256
682 | Self::Blake2s_256
683 | Self::SHA3_256
684 | Self::SHA2_256
685 | Self::ECDSA256k1Seed
686 | Self::X25519Private
687 | Self::ECDSA256r1Seed
688 | Self::Salt256 => Sizage::new(1, 0, 0, SizeType::Fixed(44), 0),
689 Self::Ed448Seed | Self::X448 => Sizage::new(1, 0, 0, SizeType::Fixed(76), 0),
690 Self::Short | Self::Label2 => Sizage::new(1, 0, 0, SizeType::Fixed(4), 0),
691 Self::Big => Sizage::new(1, 0, 0, SizeType::Fixed(12), 0),
692 Self::X25519CipherSeed => Sizage::new(1, 0, 0, SizeType::Fixed(124), 0),
693 Self::Tall => Sizage::new(1, 0, 0, SizeType::Fixed(8), 0),
694 Self::Large => Sizage::new(1, 0, 0, SizeType::Fixed(16), 0),
695 Self::Great => Sizage::new(1, 0, 0, SizeType::Fixed(20), 0),
696 Self::Vast => Sizage::new(1, 0, 0, SizeType::Fixed(24), 0),
697 Self::Label1 => Sizage::new(1, 0, 0, SizeType::Fixed(4), 1),
698 Self::Tag3 => Sizage::new(1, 3, 0, SizeType::Fixed(4), 0),
699 Self::Tag7 => Sizage::new(1, 7, 0, SizeType::Fixed(8), 0),
700 Self::Tag11 => Sizage::new(1, 11, 0, SizeType::Fixed(12), 0),
701 Self::Salt128 => Sizage::new(2, 0, 0, SizeType::Fixed(24), 0),
702 Self::Ed25519Sig
703 | Self::ECDSA256k1Sig
704 | Self::Blake3_512
705 | Self::Blake2b_512
706 | Self::SHA3_512
707 | Self::SHA2_512
708 | Self::ECDSA256r1Sig => Sizage::new(2, 0, 0, SizeType::Fixed(88), 0),
709 Self::Long => Sizage::new(2, 0, 0, SizeType::Fixed(8), 0),
710 Self::Tag1 => Sizage::new(2, 2, 1, SizeType::Fixed(4), 0),
711 Self::Tag2 => Sizage::new(2, 2, 0, SizeType::Fixed(4), 0),
712 Self::Tag5 => Sizage::new(2, 6, 1, SizeType::Fixed(8), 0),
713 Self::Tag6 => Sizage::new(2, 6, 0, SizeType::Fixed(8), 0),
714 Self::Tag9 => Sizage::new(2, 10, 1, SizeType::Fixed(12), 0),
715 Self::Tag10 => Sizage::new(2, 10, 0, SizeType::Fixed(12), 0),
716 Self::GramHeadNeck => Sizage::new(2, 22, 0, SizeType::Fixed(32), 0),
717 Self::GramHead => Sizage::new(2, 22, 0, SizeType::Fixed(28), 0),
718 Self::GramHeadAIDNeck => Sizage::new(2, 22, 0, SizeType::Fixed(76), 0),
719 Self::GramHeadAID => Sizage::new(2, 22, 0, SizeType::Fixed(72), 0),
720 Self::ECDSA256k1N | Self::ECDSA256k1 | Self::ECDSA256r1N | Self::ECDSA256r1 => {
721 Sizage::new(4, 0, 0, SizeType::Fixed(48), 0)
722 }
723 Self::Ed448N | Self::Ed448 => Sizage::new(4, 0, 0, SizeType::Fixed(80), 0),
724 Self::Ed448Sig => Sizage::new(4, 0, 0, SizeType::Fixed(156), 0),
725 Self::Tag4 => Sizage::new(4, 4, 0, SizeType::Fixed(8), 0),
726 Self::DateTime => Sizage::new(4, 0, 0, SizeType::Fixed(36), 0),
727 Self::X25519CipherSalt => Sizage::new(4, 0, 0, SizeType::Fixed(100), 0),
728 Self::Null | Self::No | Self::Yes | Self::Escape | Self::Empty => {
729 Sizage::new(4, 0, 0, SizeType::Fixed(4), 0)
730 }
731 Self::Tag8 => Sizage::new(4, 8, 0, SizeType::Fixed(12), 0),
732 Self::TBD0S => Sizage::new(4, 2, 0, SizeType::Fixed(12), 0),
733 Self::TBD0 => Sizage::new(4, 0, 0, SizeType::Fixed(8), 0),
734 Self::TBD1S => Sizage::new(4, 2, 1, SizeType::Fixed(12), 1),
735 Self::TBD1 => Sizage::new(4, 0, 0, SizeType::Fixed(8), 1),
736 Self::TBD2S => Sizage::new(4, 2, 0, SizeType::Fixed(12), 2),
737 Self::TBD2 => Sizage::new(4, 0, 0, SizeType::Fixed(8), 2),
738 Self::StrB64_L0
739 | Self::Bytes_L0
740 | Self::X25519Cipher_L0
741 | Self::X25519CipherQB64_L0
742 | Self::X25519CipherQB2_L0
743 | Self::HPKEBaseCipher_L0
744 | Self::Decimal_L0 => Sizage::new(2, 2, 0, SizeType::Small, 0),
745 Self::StrB64_L1
746 | Self::Bytes_L1
747 | Self::X25519Cipher_L1
748 | Self::X25519CipherQB64_L1
749 | Self::X25519CipherQB2_L1
750 | Self::HPKEBaseCipher_L1
751 | Self::Decimal_L1 => Sizage::new(2, 2, 0, SizeType::Small, 1),
752 Self::StrB64_L2
753 | Self::Bytes_L2
754 | Self::X25519Cipher_L2
755 | Self::X25519CipherQB64_L2
756 | Self::X25519CipherQB2_L2
757 | Self::HPKEBaseCipher_L2
758 | Self::Decimal_L2 => Sizage::new(2, 2, 0, SizeType::Small, 2),
759 Self::StrB64Big_L0
760 | Self::BytesBig_L0
761 | Self::X25519CipherBig_L0
762 | Self::X25519CipherQB64Big_L0
763 | Self::X25519CipherQB2Big_L0
764 | Self::HPKEBaseCipherBig_L0
765 | Self::DecimalBig_L0 => Sizage::new(4, 4, 0, SizeType::Large, 0),
766 Self::StrB64Big_L1
767 | Self::BytesBig_L1
768 | Self::X25519CipherBig_L1
769 | Self::X25519CipherQB64Big_L1
770 | Self::X25519CipherQB2Big_L1
771 | Self::HPKEBaseCipherBig_L1
772 | Self::DecimalBig_L1 => Sizage::new(4, 4, 0, SizeType::Large, 1),
773 Self::StrB64Big_L2
774 | Self::BytesBig_L2
775 | Self::X25519CipherBig_L2
776 | Self::X25519CipherQB64Big_L2
777 | Self::X25519CipherQB2Big_L2
778 | Self::HPKEBaseCipherBig_L2
779 | Self::DecimalBig_L2 => Sizage::new(4, 4, 0, SizeType::Large, 2),
780 }
781 }
782
783 pub fn promote(&self, size: usize, lead: usize) -> Result<Self, ValidationError> {
790 match self.get_sizage().fs() {
791 SizeType::Small if size < 64_usize.pow(2) => {
792 promote_same_size(*self, &SMALL_VAR_SIZES, lead, 2)
793 }
794 SizeType::Small if size < 64_usize.pow(4) => {
795 promote_small_to_large(*self, &LARGE_VAR_SIZES, lead, 4)
796 }
797 SizeType::Large if size < (64_usize.pow(4) - 1) => {
798 promote_same_size(*self, &LARGE_VAR_SIZES, lead, 4)
799 }
800 _ => Err(ValidationError::IncompatiblePromotion(self.to_string())),
801 }
802 }
803
804 pub fn raw_size(&self) -> Result<usize, ValidationError> {
810 let Sizage { hs, ss, fs, ls, .. } = self.get_sizage();
811 if let SizeType::Fixed(v) = fs {
812 let code_size = usize::from(hs) + usize::from(ss);
813 let raw_size = ((usize::from(v) - code_size) * 3 / 4) - usize::from(ls);
814 return Ok(raw_size);
815 }
816 Err(ValidationError::InvalidSizingOperation(self.to_string()))
817 }
818
819 #[must_use]
821 pub const fn is_special(&self) -> bool {
822 let Sizage { fs, ss, .. } = self.get_sizage();
823 matches!(fs, SizeType::Fixed(_)) && ss > 0
824 }
825}
826
827fn promote_same_size(
829 matter_code: MatterCode,
830 code_map: &[char],
831 lead: usize,
832 hs: usize,
833) -> Result<MatterCode, ValidationError> {
834 let s = code_map
835 .get(lead)
836 .ok_or_else(|| ValidationError::InvalidPromotionTarget {
837 code: matter_code.to_string(),
838 lead,
839 })?;
840 let code_str: &str = matter_code.as_ref();
841 let code = format!("{s}{}", &code_str[1..hs]);
842 MatterCode::try_from(code.as_str()).map_err(|_| ValidationError::InvalidPromotionResult {
843 from: code_str.to_owned(),
844 to: code,
845 })
846}
847
848fn promote_small_to_large(
849 matter_code: MatterCode,
850 code_map: &[char],
851 lead: usize,
852 hs: usize,
853) -> Result<MatterCode, ValidationError> {
854 let s = code_map
855 .get(lead)
856 .ok_or_else(|| ValidationError::InvalidPromotionTarget {
857 code: matter_code.to_string(),
858 lead,
859 })?;
860 let code_str: &str = matter_code.as_ref();
861 let code = format!("{s}{}{}", &"AAAA"[0..hs - 2], &code_str[1..2]);
862 MatterCode::try_from(code.as_str()).map_err(|_| ValidationError::InvalidPromotionResult {
863 from: code_str.to_owned(),
864 to: code,
865 })
866}
867
868impl Sealed for MatterCode {}
869
870impl CesrCode for MatterCode {
871 fn to_matter_code(&self) -> MatterCode {
872 *self
873 }
874
875 fn as_str(&self) -> &'static str {
876 self.into() }
878
879 }
881
882#[cfg(test)]
883#[allow(
884 clippy::panic,
885 clippy::too_many_arguments,
886 reason = "tests use panic for assertions; rstest parameterized tests exceed argument limit"
887)]
888mod tests {
889 use super::*;
890 use core::str::FromStr;
891 use rstest::rstest;
892 use strum::{IntoEnumIterator, ParseError};
893
894 macro_rules! with_payload {
895 ($bytes:expr) => {{
896 const PAYLOAD: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF];
897 let mut vec = Vec::from($bytes);
898 vec.extend_from_slice(PAYLOAD);
899 vec
900 }};
901 }
902
903 #[test]
904 fn test_codex_string_roundtrip() {
905 for variant in MatterCode::iter() {
906 let code_str: &'static str = variant.into();
907 let code_string = variant.to_string();
908 assert_eq!(code_str, code_string);
909
910 let parsed_variant = MatterCode::from_str(code_str).unwrap();
912 assert_eq!(variant, parsed_variant);
913
914 let code_as_ref: &str = variant.as_ref();
916 assert_eq!(code_str, code_as_ref);
917 }
918 }
919
920 #[test]
921 fn test_invalid_code_parsing() {
922 let result = MatterCode::from_str("!NOT_A_CODE!");
923 assert!(result.is_err());
924 let err = result.unwrap_err();
925 assert_eq!(err, ParseError::VariantNotFound);
926 }
927
928 #[test]
929 fn spot_check_codes() {
930 assert_eq!(MatterCode::Ed25519N.as_ref(), "B");
931 }
932
933 #[rstest]
934 #[case("A_payload", Ok(MatterCode::Ed25519Seed))]
935 #[case("B_payload", Ok(MatterCode::Ed25519N))]
936 #[case("C_payload", Ok(MatterCode::X25519))]
937 #[case("D_payload", Ok(MatterCode::Ed25519))]
938 #[case("E_payload", Ok(MatterCode::Blake3_256))]
939 #[case("F_payload", Ok(MatterCode::Blake2b_256))]
940 #[case("G_payload", Ok(MatterCode::Blake2s_256))]
941 #[case("H_payload", Ok(MatterCode::SHA3_256))]
942 #[case("I_payload", Ok(MatterCode::SHA2_256))]
943 #[case("J_payload", Ok(MatterCode::ECDSA256k1Seed))]
944 #[case("K_payload", Ok(MatterCode::Ed448Seed))]
945 #[case("L_payload", Ok(MatterCode::X448))]
946 #[case("M_payload", Ok(MatterCode::Short))]
947 #[case("N_payload", Ok(MatterCode::Big))]
948 #[case("O_payload", Ok(MatterCode::X25519Private))]
949 #[case("P_payload", Ok(MatterCode::X25519CipherSeed))]
950 #[case("Q_payload", Ok(MatterCode::ECDSA256r1Seed))]
951 #[case("R_payload", Ok(MatterCode::Tall))]
952 #[case("S_payload", Ok(MatterCode::Large))]
953 #[case("T_payload", Ok(MatterCode::Great))]
954 #[case("U_payload", Ok(MatterCode::Vast))]
955 #[case("V_payload", Ok(MatterCode::Label1))]
956 #[case("W_payload", Ok(MatterCode::Label2))]
957 #[case("X_payload", Ok(MatterCode::Tag3))]
958 #[case("Y_payload", Ok(MatterCode::Tag7))]
959 #[case("Z_payload", Ok(MatterCode::Tag11))]
960 #[case("a_payload", Ok(MatterCode::Salt256))]
961 #[case("0A_payload", Ok(MatterCode::Salt128))]
962 #[case("0B_payload", Ok(MatterCode::Ed25519Sig))]
963 #[case("0C_payload", Ok(MatterCode::ECDSA256k1Sig))]
964 #[case("0D_payload", Ok(MatterCode::Blake3_512))]
965 #[case("0E_payload", Ok(MatterCode::Blake2b_512))]
966 #[case("0F_payload", Ok(MatterCode::SHA3_512))]
967 #[case("0G_payload", Ok(MatterCode::SHA2_512))]
968 #[case("0H_payload", Ok(MatterCode::Long))]
969 #[case("0I_payload", Ok(MatterCode::ECDSA256r1Sig))]
970 #[case("0J_payload", Ok(MatterCode::Tag1))]
971 #[case("0K_payload", Ok(MatterCode::Tag2))]
972 #[case("0L_payload", Ok(MatterCode::Tag5))]
973 #[case("0M_payload", Ok(MatterCode::Tag6))]
974 #[case("0N_payload", Ok(MatterCode::Tag9))]
975 #[case("0O_payload", Ok(MatterCode::Tag10))]
976 #[case("0P_payload", Ok(MatterCode::GramHeadNeck))]
977 #[case("0Q_payload", Ok(MatterCode::GramHead))]
978 #[case("0R_payload", Ok(MatterCode::GramHeadAIDNeck))]
979 #[case("0S_payload", Ok(MatterCode::GramHeadAID))]
980 #[case("1AAA_payload", Ok(MatterCode::ECDSA256k1N))]
981 #[case("1AAB_payload", Ok(MatterCode::ECDSA256k1))]
982 #[case("1AAC_payload", Ok(MatterCode::Ed448N))]
983 #[case("1AAD_payload", Ok(MatterCode::Ed448))]
984 #[case("1AAE_payload", Ok(MatterCode::Ed448Sig))]
985 #[case("1AAF_payload", Ok(MatterCode::Tag4))]
986 #[case("1AAG_payload", Ok(MatterCode::DateTime))]
987 #[case("1AAH_payload", Ok(MatterCode::X25519CipherSalt))]
988 #[case("1AAI_payload", Ok(MatterCode::ECDSA256r1N))]
989 #[case("1AAJ_payload", Ok(MatterCode::ECDSA256r1))]
990 #[case("1AAK_payload", Ok(MatterCode::Null))]
991 #[case("1AAL_payload", Ok(MatterCode::No))]
992 #[case("1AAM_payload", Ok(MatterCode::Yes))]
993 #[case("1AAN_payload", Ok(MatterCode::Tag8))]
994 #[case("1AAO_payload", Ok(MatterCode::Escape))]
995 #[case("1AAP_payload", Ok(MatterCode::Empty))]
996 #[case("1__-_payload", Ok(MatterCode::TBD0S))]
997 #[case("1____payload", Ok(MatterCode::TBD0))]
998 #[case("2__-_payload", Ok(MatterCode::TBD1S))]
999 #[case("2____payload", Ok(MatterCode::TBD1))]
1000 #[case("3__-_payload", Ok(MatterCode::TBD2S))]
1001 #[case("3____payload", Ok(MatterCode::TBD2))]
1002 #[case("4A_payload", Ok(MatterCode::StrB64_L0))]
1003 #[case("5A_payload", Ok(MatterCode::StrB64_L1))]
1004 #[case("6A_payload", Ok(MatterCode::StrB64_L2))]
1005 #[case("7AAA_payload", Ok(MatterCode::StrB64Big_L0))]
1006 #[case("8AAA_payload", Ok(MatterCode::StrB64Big_L1))]
1007 #[case("9AAA_payload", Ok(MatterCode::StrB64Big_L2))]
1008 #[case("4B_payload", Ok(MatterCode::Bytes_L0))]
1009 #[case("5B_payload", Ok(MatterCode::Bytes_L1))]
1010 #[case("6B_payload", Ok(MatterCode::Bytes_L2))]
1011 #[case("7AAB_payload", Ok(MatterCode::BytesBig_L0))]
1012 #[case("8AAB_payload", Ok(MatterCode::BytesBig_L1))]
1013 #[case("9AAB_payload", Ok(MatterCode::BytesBig_L2))]
1014 #[case("4C_payload", Ok(MatterCode::X25519Cipher_L0))]
1015 #[case("5C_payload", Ok(MatterCode::X25519Cipher_L1))]
1016 #[case("6C_payload", Ok(MatterCode::X25519Cipher_L2))]
1017 #[case("7AAC_payload", Ok(MatterCode::X25519CipherBig_L0))]
1018 #[case("8AAC_payload", Ok(MatterCode::X25519CipherBig_L1))]
1019 #[case("9AAC_payload", Ok(MatterCode::X25519CipherBig_L2))]
1020 #[case("4D_payload", Ok(MatterCode::X25519CipherQB64_L0))]
1021 #[case("5D_payload", Ok(MatterCode::X25519CipherQB64_L1))]
1022 #[case("6D_payload", Ok(MatterCode::X25519CipherQB64_L2))]
1023 #[case("7AAD_payload", Ok(MatterCode::X25519CipherQB64Big_L0))]
1024 #[case("8AAD_payload", Ok(MatterCode::X25519CipherQB64Big_L1))]
1025 #[case("9AAD_payload", Ok(MatterCode::X25519CipherQB64Big_L2))]
1026 #[case("4E_payload", Ok(MatterCode::X25519CipherQB2_L0))]
1027 #[case("5E_payload", Ok(MatterCode::X25519CipherQB2_L1))]
1028 #[case("6E_payload", Ok(MatterCode::X25519CipherQB2_L2))]
1029 #[case("7AAE_payload", Ok(MatterCode::X25519CipherQB2Big_L0))]
1030 #[case("8AAE_payload", Ok(MatterCode::X25519CipherQB2Big_L1))]
1031 #[case("9AAE_payload", Ok(MatterCode::X25519CipherQB2Big_L2))]
1032 #[case("4F_payload", Ok(MatterCode::HPKEBaseCipher_L0))]
1033 #[case("5F_payload", Ok(MatterCode::HPKEBaseCipher_L1))]
1034 #[case("6F_payload", Ok(MatterCode::HPKEBaseCipher_L2))]
1035 #[case("7AAF_payload", Ok(MatterCode::HPKEBaseCipherBig_L0))]
1036 #[case("8AAF_payload", Ok(MatterCode::HPKEBaseCipherBig_L1))]
1037 #[case("9AAF_payload", Ok(MatterCode::HPKEBaseCipherBig_L2))]
1038 #[case("4H_payload", Ok(MatterCode::Decimal_L0))]
1039 #[case("5H_payload", Ok(MatterCode::Decimal_L1))]
1040 #[case("6H_payload", Ok(MatterCode::Decimal_L2))]
1041 #[case("7AAH_payload", Ok(MatterCode::DecimalBig_L0))]
1042 #[case("8AAH_payload", Ok(MatterCode::DecimalBig_L1))]
1043 #[case("9AAH_payload", Ok(MatterCode::DecimalBig_L2))]
1044 fn test_from_base64_stream_valid(
1045 #[case] input: &str,
1046 #[case] expected: Result<MatterCode, ParsingError>,
1047 ) {
1048 assert_eq!(
1049 MatterCode::from_base64_stream(input.as_bytes()).unwrap(),
1050 expected.unwrap()
1051 );
1052 }
1053
1054 #[rstest]
1055 #[case(with_payload!(&[0x00]), Ok(MatterCode::Ed25519Seed))]
1057 #[case(with_payload!(&[0x04]), Ok(MatterCode::Ed25519N))]
1058 #[case(with_payload!(&[0x08]), Ok(MatterCode::X25519))]
1059 #[case(with_payload!(&[0x0C]), Ok(MatterCode::Ed25519))]
1060 #[case(with_payload!(&[0x10]), Ok(MatterCode::Blake3_256))]
1061 #[case(with_payload!(&[0x14]), Ok(MatterCode::Blake2b_256))]
1062 #[case(with_payload!(&[0x18]), Ok(MatterCode::Blake2s_256))]
1063 #[case(with_payload!(&[0x1C]), Ok(MatterCode::SHA3_256))]
1064 #[case(with_payload!(&[0x20]), Ok(MatterCode::SHA2_256))]
1065 #[case(with_payload!(&[0x24]), Ok(MatterCode::ECDSA256k1Seed))]
1066 #[case(with_payload!(&[0x28]), Ok(MatterCode::Ed448Seed))]
1067 #[case(with_payload!(&[0x2C]), Ok(MatterCode::X448))]
1068 #[case(with_payload!(&[0x30]), Ok(MatterCode::Short))]
1069 #[case(with_payload!(&[0x34]), Ok(MatterCode::Big))]
1070 #[case(with_payload!(&[0x38]), Ok(MatterCode::X25519Private))]
1071 #[case(with_payload!(&[0x3C]), Ok(MatterCode::X25519CipherSeed))]
1072 #[case(with_payload!(&[0x40]), Ok(MatterCode::ECDSA256r1Seed))]
1073 #[case(with_payload!(&[0x44]), Ok(MatterCode::Tall))]
1074 #[case(with_payload!(&[0x48]), Ok(MatterCode::Large))]
1075 #[case(with_payload!(&[0x4C]), Ok(MatterCode::Great))]
1076 #[case(with_payload!(&[0x50]), Ok(MatterCode::Vast))]
1077 #[case(with_payload!(&[0x54]), Ok(MatterCode::Label1))]
1078 #[case(with_payload!(&[0x58]), Ok(MatterCode::Label2))]
1079 #[case(with_payload!(&[0x5C]), Ok(MatterCode::Tag3))]
1080 #[case(with_payload!(&[0x60]), Ok(MatterCode::Tag7))]
1081 #[case(with_payload!(&[0x64]), Ok(MatterCode::Tag11))]
1082 #[case(with_payload!(&[0x68]), Ok(MatterCode::Salt256))]
1083 #[case(with_payload!(&[0xD0, 0x08]), Ok(MatterCode::Salt128))]
1085 #[case(with_payload!(&[0xD0, 0x1F]), Ok(MatterCode::Ed25519Sig))]
1086 #[case(with_payload!(&[0xD0, 0x20]), Ok(MatterCode::ECDSA256k1Sig))]
1087 #[case(with_payload!(&[0xD0, 0x30]), Ok(MatterCode::Blake3_512))]
1088 #[case(with_payload!(&[0xD0, 0x40]), Ok(MatterCode::Blake2b_512))]
1089 #[case(with_payload!(&[0xD0, 0x50]), Ok(MatterCode::SHA3_512))]
1090 #[case(with_payload!(&[0xD0, 0x60]), Ok(MatterCode::SHA2_512))]
1091 #[case(with_payload!(&[0xD0, 0x70]), Ok(MatterCode::Long))]
1092 #[case(with_payload!(&[0xD0, 0x80]), Ok(MatterCode::ECDSA256r1Sig))]
1093 #[case(with_payload!(&[0xD0, 0x90]), Ok(MatterCode::Tag1))]
1094 #[case(with_payload!(&[0xD0, 0xA0]), Ok(MatterCode::Tag2))]
1095 #[case(with_payload!(&[0xD0, 0xB0]), Ok(MatterCode::Tag5))]
1096 #[case(with_payload!(&[0xD0, 0xC0]), Ok(MatterCode::Tag6))]
1097 #[case(with_payload!(&[0xD0, 0xD0]), Ok(MatterCode::Tag9))]
1098 #[case(with_payload!(&[0xD0, 0xE0]), Ok(MatterCode::Tag10))]
1099 #[case(with_payload!(&[0xD0, 0xF0]), Ok(MatterCode::GramHeadNeck))]
1100 #[case(with_payload!(&[0xD1, 0x00]), Ok(MatterCode::GramHead))]
1101 #[case(with_payload!(&[0xD1, 0x10]), Ok(MatterCode::GramHeadAIDNeck))]
1102 #[case(with_payload!(&[0xD1, 0x20]), Ok(MatterCode::GramHeadAID))]
1103 #[case(with_payload!(&[0xD4, 0x00, 0x00]), Ok(MatterCode::ECDSA256k1N))]
1105 #[case(with_payload!(&[0xD4, 0x00, 0x01]), Ok(MatterCode::ECDSA256k1))]
1106 #[case(with_payload!(&[0xD4, 0x00, 0x02]), Ok(MatterCode::Ed448N))]
1107 #[case(with_payload!(&[0xD4, 0x00, 0x03]), Ok(MatterCode::Ed448))]
1108 #[case(with_payload!(&[0xD4, 0x00, 0x04]), Ok(MatterCode::Ed448Sig))]
1109 #[case(with_payload!(&[0xD4, 0x00, 0x05]), Ok(MatterCode::Tag4))]
1110 #[case(with_payload!(&[0xD4, 0x00, 0x06]), Ok(MatterCode::DateTime))]
1111 #[case(with_payload!(&[0xD4, 0x00, 0x07]), Ok(MatterCode::X25519CipherSalt))]
1112 #[case(with_payload!(&[0xD4, 0x00, 0x08]), Ok(MatterCode::ECDSA256r1N))]
1113 #[case(with_payload!(&[0xD4, 0x00, 0x09]), Ok(MatterCode::ECDSA256r1))]
1114 #[case(with_payload!(&[0xD4, 0x00, 0x0A]), Ok(MatterCode::Null))]
1115 #[case(with_payload!(&[0xD4, 0x00, 0x0B]), Ok(MatterCode::No))]
1116 #[case(with_payload!(&[0xD4, 0x00, 0x0C]), Ok(MatterCode::Yes))]
1117 #[case(with_payload!(&[0xD4, 0x00, 0x0D]), Ok(MatterCode::Tag8))]
1118 #[case(with_payload!(&[0xD4, 0x00, 0x0E]), Ok(MatterCode::Escape))]
1119 #[case(with_payload!(&[0xD4, 0x00, 0x0F]), Ok(MatterCode::Empty))]
1120 #[case(with_payload!(&[0xD7, 0xFF, 0xFE]), Ok(MatterCode::TBD0S))]
1121 #[case(with_payload!(&[0xD7, 0xFF, 0xFF]), Ok(MatterCode::TBD0))]
1122 #[case(with_payload!(&[0xDB, 0xFF, 0xFE]), Ok(MatterCode::TBD1S))]
1123 #[case(with_payload!(&[0xDB, 0xFF, 0xFF]), Ok(MatterCode::TBD1))]
1124 #[case(with_payload!(&[0xDF, 0xFF, 0xFE]), Ok(MatterCode::TBD2S))]
1125 #[case(with_payload!(&[0xDF, 0xFF, 0xFF]), Ok(MatterCode::TBD2))]
1126 #[case(with_payload!(&[0xE0, 0x00]), Ok(MatterCode::StrB64_L0))]
1128 #[case(with_payload!(&[0xE4, 0x00]), Ok(MatterCode::StrB64_L1))]
1129 #[case(with_payload!(&[0xE8, 0x00]), Ok(MatterCode::StrB64_L2))]
1130 #[case(with_payload!(&[0xEC, 0x00, 0x00]), Ok(MatterCode::StrB64Big_L0))]
1131 #[case(with_payload!(&[0xF0, 0x00, 0x00]), Ok(MatterCode::StrB64Big_L1))]
1132 #[case(with_payload!(&[0xF4, 0x00, 0x00]), Ok(MatterCode::StrB64Big_L2))]
1133 #[case(with_payload!(&[0xE0, 0x10]), Ok(MatterCode::Bytes_L0))]
1134 #[case(with_payload!(&[0xE4, 0x10]), Ok(MatterCode::Bytes_L1))]
1135 #[case(with_payload!(&[0xE8, 0x10]), Ok(MatterCode::Bytes_L2))]
1136 #[case(with_payload!(&[0xEC, 0x00, 0x01]), Ok(MatterCode::BytesBig_L0))]
1137 #[case(with_payload!(&[0xF0, 0x00, 0x01]), Ok(MatterCode::BytesBig_L1))]
1138 #[case(with_payload!(&[0xF4, 0x00, 0x01]), Ok(MatterCode::BytesBig_L2))]
1139 #[case(with_payload!(&[0xE0, 0x20]), Ok(MatterCode::X25519Cipher_L0))]
1140 #[case(with_payload!(&[0xE4, 0x20]), Ok(MatterCode::X25519Cipher_L1))]
1141 #[case(with_payload!(&[0xE8, 0x20]), Ok(MatterCode::X25519Cipher_L2))]
1142 #[case(with_payload!(&[0xEC, 0x00, 0x02]), Ok(MatterCode::X25519CipherBig_L0))]
1143 #[case(with_payload!(&[0xF0, 0x00, 0x02]), Ok(MatterCode::X25519CipherBig_L1))]
1144 #[case(with_payload!(&[0xF4, 0x00, 0x02]), Ok(MatterCode::X25519CipherBig_L2))]
1145 #[case(with_payload!(&[0xE0, 0x30]), Ok(MatterCode::X25519CipherQB64_L0))]
1146 #[case(with_payload!(&[0xE4, 0x30]), Ok(MatterCode::X25519CipherQB64_L1))]
1147 #[case(with_payload!(&[0xE8, 0x30]), Ok(MatterCode::X25519CipherQB64_L2))]
1148 #[case(with_payload!(&[0xEC, 0x00, 0x03]), Ok(MatterCode::X25519CipherQB64Big_L0))]
1149 #[case(with_payload!(&[0xF0, 0x00, 0x03]), Ok(MatterCode::X25519CipherQB64Big_L1))]
1150 #[case(with_payload!(&[0xF4, 0x00, 0x03]), Ok(MatterCode::X25519CipherQB64Big_L2))]
1151 #[case(with_payload!(&[0xE0, 0x40]), Ok(MatterCode::X25519CipherQB2_L0))]
1152 #[case(with_payload!(&[0xE4, 0x40]), Ok(MatterCode::X25519CipherQB2_L1))]
1153 #[case(with_payload!(&[0xE8, 0x40]), Ok(MatterCode::X25519CipherQB2_L2))]
1154 #[case(with_payload!(&[0xEC, 0x00, 0x04]), Ok(MatterCode::X25519CipherQB2Big_L0))]
1155 #[case(with_payload!(&[0xF0, 0x00, 0x04]), Ok(MatterCode::X25519CipherQB2Big_L1))]
1156 #[case(with_payload!(&[0xF4, 0x00, 0x04]), Ok(MatterCode::X25519CipherQB2Big_L2))]
1157 #[case(with_payload!(&[0xE0, 0x50]), Ok(MatterCode::HPKEBaseCipher_L0))]
1158 #[case(with_payload!(&[0xE4, 0x50]), Ok(MatterCode::HPKEBaseCipher_L1))]
1159 #[case(with_payload!(&[0xE8, 0x50]), Ok(MatterCode::HPKEBaseCipher_L2))]
1160 #[case(with_payload!(&[0xEC, 0x00, 0x05]), Ok(MatterCode::HPKEBaseCipherBig_L0))]
1161 #[case(with_payload!(&[0xF0, 0x00, 0x05]), Ok(MatterCode::HPKEBaseCipherBig_L1))]
1162 #[case(with_payload!(&[0xF4, 0x00, 0x05]), Ok(MatterCode::HPKEBaseCipherBig_L2))]
1163 #[case(with_payload!(&[0xE0, 0x70]), Ok(MatterCode::Decimal_L0))]
1164 #[case(with_payload!(&[0xE4, 0x70]), Ok(MatterCode::Decimal_L1))]
1165 #[case(with_payload!(&[0xE8, 0x70]), Ok(MatterCode::Decimal_L2))]
1166 #[case(with_payload!(&[0xEC, 0x00, 0x07]), Ok(MatterCode::DecimalBig_L0))]
1167 #[case(with_payload!(&[0xF0, 0x00, 0x07]), Ok(MatterCode::DecimalBig_L1))]
1168 #[case(with_payload!(&[0xF4, 0x00, 0x07]), Ok(MatterCode::DecimalBig_L2))]
1169 fn test_all_matter_codes_from_byte_stream(
1170 #[case] input: Vec<u8>,
1171 #[case] expected: Result<MatterCode, ParsingError>,
1172 ) {
1173 assert_eq!(MatterCode::from_stream(&input), expected);
1174 }
1175
1176 #[rstest]
1177 #[case(MatterCode::StrB64_L0, 100, 1, Ok(MatterCode::StrB64_L1))]
1178 #[case(MatterCode::Bytes_L1, 200, 2, Ok(MatterCode::Bytes_L2))]
1179 #[case(MatterCode::Decimal_L2, 1, 0, Ok(MatterCode::Decimal_L0))]
1180 #[case(MatterCode::StrB64_L0, 5000, 0, Ok(MatterCode::StrB64Big_L0))] #[case(MatterCode::Bytes_L1, 5000, 2, Ok(MatterCode::BytesBig_L2))] #[case(MatterCode::Decimal_L2, 10000, 1, Ok(MatterCode::DecimalBig_L1))] #[case(MatterCode::StrB64Big_L0, 20000, 1, Ok(MatterCode::StrB64Big_L1))] #[case(MatterCode::BytesBig_L1, 20000, 0, Ok(MatterCode::BytesBig_L0))] #[case(MatterCode::DecimalBig_L2, 20000, 2, Ok(MatterCode::DecimalBig_L2))] #[case(
1187 MatterCode::Ed25519,
1188 100,
1189 0,
1190 Err(ValidationError::IncompatiblePromotion("D".to_owned()))
1191 )]
1192 #[case(
1193 MatterCode::StrB64_L0,
1194 64_usize.pow(4) + 1,
1195 0,
1196 Err(ValidationError::IncompatiblePromotion("4A".to_owned()))
1197 )]
1198 #[case(
1199 MatterCode::StrB64_L1,
1200 100,
1201 3,
1202 Err(ValidationError::InvalidPromotionTarget { code: "5A".to_owned(), lead: 3 })
1203 )]
1204 fn test_code_promotion(
1205 #[case] original: MatterCode,
1206 #[case] size: usize,
1207 #[case] lead: usize,
1208 #[case] expected: Result<MatterCode, ValidationError>,
1209 ) {
1210 assert_eq!(original.promote(size, lead), expected);
1211 }
1212
1213 #[test]
1214 fn test_codex_categorization() {
1215 assert!(MatterCode::Blake3_256.is_digest());
1216 assert!(MatterCode::Blake3_256.is_nonce());
1217 assert!(!MatterCode::Blake3_256.is_text_code());
1218 assert!(MatterCode::StrB64_L0.is_base64_text_code());
1219 assert!(MatterCode::StrB64_L0.is_label());
1220 assert!(!MatterCode::StrB64_L0.is_digest());
1221 assert!(MatterCode::Ed25519N.is_non_transferable());
1222 assert!(MatterCode::Ed25519N.is_prefix_derivation());
1223 assert!(MatterCode::Short.is_base64_num());
1224 assert!(!MatterCode::Short.is_base64_tag());
1225 assert!(MatterCode::Tag4.is_base64_tag());
1226 assert!(MatterCode::Tag4.is_label());
1227 }
1228
1229 #[rstest]
1230 #[case(MatterCode::Ed25519Seed, Ok(32))] #[case(MatterCode::Ed25519Sig, Ok(64))] #[case(MatterCode::Ed448Sig, Ok(114))] #[case(MatterCode::Label1, Ok(1))] #[case(
1235 MatterCode::StrB64_L0,
1236 Err(ValidationError::InvalidSizingOperation("4A".to_owned()))
1237 )]
1238 #[case(
1239 MatterCode::BytesBig_L1,
1240 Err(ValidationError::InvalidSizingOperation("8AAB".to_owned()))
1241 )]
1242 fn test_raw_size_calculation(
1243 #[case] code: MatterCode,
1244 #[case] expected: Result<usize, ValidationError>,
1245 ) {
1246 assert_eq!(code.raw_size(), expected);
1247 }
1248
1249 #[rstest]
1253 #[case(MatterCode::Ed25519Seed, 1, 0, 0, Some(44_u16), 0)]
1255 #[case(MatterCode::Ed25519N, 1, 0, 0, Some(44_u16), 0)]
1256 #[case(MatterCode::X25519, 1, 0, 0, Some(44_u16), 0)]
1257 #[case(MatterCode::Ed25519, 1, 0, 0, Some(44_u16), 0)]
1258 #[case(MatterCode::Blake3_256, 1, 0, 0, Some(44_u16), 0)]
1259 #[case(MatterCode::Blake2b_256, 1, 0, 0, Some(44_u16), 0)]
1260 #[case(MatterCode::Blake2s_256, 1, 0, 0, Some(44_u16), 0)]
1261 #[case(MatterCode::SHA3_256, 1, 0, 0, Some(44_u16), 0)]
1262 #[case(MatterCode::SHA2_256, 1, 0, 0, Some(44_u16), 0)]
1263 #[case(MatterCode::ECDSA256k1Seed, 1, 0, 0, Some(44_u16), 0)]
1264 #[case(MatterCode::Ed448Seed, 1, 0, 0, Some(76_u16), 0)]
1265 #[case(MatterCode::X448, 1, 0, 0, Some(76_u16), 0)]
1266 #[case(MatterCode::Short, 1, 0, 0, Some(4_u16), 0)]
1267 #[case(MatterCode::Big, 1, 0, 0, Some(12_u16), 0)]
1268 #[case(MatterCode::X25519Private, 1, 0, 0, Some(44_u16), 0)]
1269 #[case(MatterCode::X25519CipherSeed, 1, 0, 0, Some(124_u16), 0)]
1270 #[case(MatterCode::ECDSA256r1Seed, 1, 0, 0, Some(44_u16), 0)]
1271 #[case(MatterCode::Tall, 1, 0, 0, Some(8_u16), 0)]
1272 #[case(MatterCode::Large, 1, 0, 0, Some(16_u16), 0)]
1273 #[case(MatterCode::Great, 1, 0, 0, Some(20_u16), 0)]
1274 #[case(MatterCode::Vast, 1, 0, 0, Some(24_u16), 0)]
1275 #[case(MatterCode::Label1, 1, 0, 0, Some(4_u16), 1)]
1276 #[case(MatterCode::Label2, 1, 0, 0, Some(4_u16), 0)]
1277 #[case(MatterCode::Tag3, 1, 3, 0, Some(4_u16), 0)]
1279 #[case(MatterCode::Tag7, 1, 7, 0, Some(8_u16), 0)]
1280 #[case(MatterCode::Tag11, 1, 11, 0, Some(12_u16), 0)]
1281 #[case(MatterCode::Salt256, 1, 0, 0, Some(44_u16), 0)]
1283 #[case(MatterCode::Salt128, 2, 0, 0, Some(24_u16), 0)]
1285 #[case(MatterCode::Ed25519Sig, 2, 0, 0, Some(88_u16), 0)]
1286 #[case(MatterCode::ECDSA256k1Sig, 2, 0, 0, Some(88_u16), 0)]
1287 #[case(MatterCode::Blake3_512, 2, 0, 0, Some(88_u16), 0)]
1288 #[case(MatterCode::Blake2b_512, 2, 0, 0, Some(88_u16), 0)]
1289 #[case(MatterCode::SHA3_512, 2, 0, 0, Some(88_u16), 0)]
1290 #[case(MatterCode::SHA2_512, 2, 0, 0, Some(88_u16), 0)]
1291 #[case(MatterCode::Long, 2, 0, 0, Some(8_u16), 0)]
1292 #[case(MatterCode::ECDSA256r1Sig, 2, 0, 0, Some(88_u16), 0)]
1293 #[case(MatterCode::Tag1, 2, 2, 1, Some(4_u16), 0)]
1295 #[case(MatterCode::Tag2, 2, 2, 0, Some(4_u16), 0)]
1296 #[case(MatterCode::Tag5, 2, 6, 1, Some(8_u16), 0)]
1297 #[case(MatterCode::Tag6, 2, 6, 0, Some(8_u16), 0)]
1298 #[case(MatterCode::Tag9, 2, 10, 1, Some(12_u16), 0)]
1299 #[case(MatterCode::Tag10, 2, 10, 0, Some(12_u16), 0)]
1300 #[case(MatterCode::GramHeadNeck, 2, 22, 0, Some(32_u16), 0)]
1301 #[case(MatterCode::GramHead, 2, 22, 0, Some(28_u16), 0)]
1302 #[case(MatterCode::GramHeadAIDNeck, 2, 22, 0, Some(76_u16), 0)]
1303 #[case(MatterCode::GramHeadAID, 2, 22, 0, Some(72_u16), 0)]
1304 #[case(MatterCode::ECDSA256k1N, 4, 0, 0, Some(48_u16), 0)]
1306 #[case(MatterCode::ECDSA256k1, 4, 0, 0, Some(48_u16), 0)]
1307 #[case(MatterCode::Ed448N, 4, 0, 0, Some(80_u16), 0)]
1308 #[case(MatterCode::Ed448, 4, 0, 0, Some(80_u16), 0)]
1309 #[case(MatterCode::Ed448Sig, 4, 0, 0, Some(156_u16), 0)]
1310 #[case(MatterCode::DateTime, 4, 0, 0, Some(36_u16), 0)]
1311 #[case(MatterCode::X25519CipherSalt, 4, 0, 0, Some(100_u16), 0)]
1312 #[case(MatterCode::ECDSA256r1N, 4, 0, 0, Some(48_u16), 0)]
1313 #[case(MatterCode::ECDSA256r1, 4, 0, 0, Some(48_u16), 0)]
1314 #[case(MatterCode::Null, 4, 0, 0, Some(4_u16), 0)]
1315 #[case(MatterCode::No, 4, 0, 0, Some(4_u16), 0)]
1316 #[case(MatterCode::Yes, 4, 0, 0, Some(4_u16), 0)]
1317 #[case(MatterCode::Escape, 4, 0, 0, Some(4_u16), 0)]
1318 #[case(MatterCode::Empty, 4, 0, 0, Some(4_u16), 0)]
1319 #[case(MatterCode::Tag4, 4, 4, 0, Some(8_u16), 0)]
1321 #[case(MatterCode::Tag8, 4, 8, 0, Some(12_u16), 0)]
1322 #[case(MatterCode::TBD0S, 4, 2, 0, Some(12_u16), 0)]
1323 #[case(MatterCode::TBD0, 4, 0, 0, Some(8_u16), 0)]
1324 #[case(MatterCode::TBD1S, 4, 2, 1, Some(12_u16), 1)]
1325 #[case(MatterCode::TBD1, 4, 0, 0, Some(8_u16), 1)]
1326 #[case(MatterCode::TBD2S, 4, 2, 0, Some(12_u16), 2)]
1327 #[case(MatterCode::TBD2, 4, 0, 0, Some(8_u16), 2)]
1328 #[case(MatterCode::StrB64_L0, 2, 2, 0, None, 0)]
1330 #[case(MatterCode::StrB64_L1, 2, 2, 0, None, 1)]
1331 #[case(MatterCode::StrB64_L2, 2, 2, 0, None, 2)]
1332 #[case(MatterCode::Bytes_L0, 2, 2, 0, None, 0)]
1333 #[case(MatterCode::Bytes_L1, 2, 2, 0, None, 1)]
1334 #[case(MatterCode::Bytes_L2, 2, 2, 0, None, 2)]
1335 #[case(MatterCode::X25519Cipher_L0, 2, 2, 0, None, 0)]
1336 #[case(MatterCode::X25519Cipher_L1, 2, 2, 0, None, 1)]
1337 #[case(MatterCode::X25519Cipher_L2, 2, 2, 0, None, 2)]
1338 #[case(MatterCode::X25519CipherQB64_L0, 2, 2, 0, None, 0)]
1339 #[case(MatterCode::X25519CipherQB64_L1, 2, 2, 0, None, 1)]
1340 #[case(MatterCode::X25519CipherQB64_L2, 2, 2, 0, None, 2)]
1341 #[case(MatterCode::X25519CipherQB2_L0, 2, 2, 0, None, 0)]
1342 #[case(MatterCode::X25519CipherQB2_L1, 2, 2, 0, None, 1)]
1343 #[case(MatterCode::X25519CipherQB2_L2, 2, 2, 0, None, 2)]
1344 #[case(MatterCode::HPKEBaseCipher_L0, 2, 2, 0, None, 0)]
1345 #[case(MatterCode::HPKEBaseCipher_L1, 2, 2, 0, None, 1)]
1346 #[case(MatterCode::HPKEBaseCipher_L2, 2, 2, 0, None, 2)]
1347 #[case(MatterCode::Decimal_L0, 2, 2, 0, None, 0)]
1348 #[case(MatterCode::Decimal_L1, 2, 2, 0, None, 1)]
1349 #[case(MatterCode::Decimal_L2, 2, 2, 0, None, 2)]
1350 #[case(MatterCode::StrB64Big_L0, 4, 4, 0, None, 0)]
1352 #[case(MatterCode::StrB64Big_L1, 4, 4, 0, None, 1)]
1353 #[case(MatterCode::StrB64Big_L2, 4, 4, 0, None, 2)]
1354 #[case(MatterCode::BytesBig_L0, 4, 4, 0, None, 0)]
1355 #[case(MatterCode::BytesBig_L1, 4, 4, 0, None, 1)]
1356 #[case(MatterCode::BytesBig_L2, 4, 4, 0, None, 2)]
1357 #[case(MatterCode::X25519CipherBig_L0, 4, 4, 0, None, 0)]
1358 #[case(MatterCode::X25519CipherBig_L1, 4, 4, 0, None, 1)]
1359 #[case(MatterCode::X25519CipherBig_L2, 4, 4, 0, None, 2)]
1360 #[case(MatterCode::X25519CipherQB64Big_L0, 4, 4, 0, None, 0)]
1361 #[case(MatterCode::X25519CipherQB64Big_L1, 4, 4, 0, None, 1)]
1362 #[case(MatterCode::X25519CipherQB64Big_L2, 4, 4, 0, None, 2)]
1363 #[case(MatterCode::X25519CipherQB2Big_L0, 4, 4, 0, None, 0)]
1364 #[case(MatterCode::X25519CipherQB2Big_L1, 4, 4, 0, None, 1)]
1365 #[case(MatterCode::X25519CipherQB2Big_L2, 4, 4, 0, None, 2)]
1366 #[case(MatterCode::HPKEBaseCipherBig_L0, 4, 4, 0, None, 0)]
1367 #[case(MatterCode::HPKEBaseCipherBig_L1, 4, 4, 0, None, 1)]
1368 #[case(MatterCode::HPKEBaseCipherBig_L2, 4, 4, 0, None, 2)]
1369 #[case(MatterCode::DecimalBig_L0, 4, 4, 0, None, 0)]
1370 #[case(MatterCode::DecimalBig_L1, 4, 4, 0, None, 1)]
1371 #[case(MatterCode::DecimalBig_L2, 4, 4, 0, None, 2)]
1372 fn sizage_matches_keri_spec(
1373 #[case] code: MatterCode,
1374 #[case] expected_hs: usize,
1375 #[case] expected_ss: usize,
1376 #[case] expected_xs: usize,
1377 #[case] expected_fs: Option<u16>,
1378 #[case] expected_ls: usize,
1379 ) {
1380 let sizage = code.get_sizage();
1381 assert_eq!(sizage.hs(), expected_hs, "hs mismatch for {code:?}");
1382 assert_eq!(sizage.ss(), expected_ss, "ss mismatch for {code:?}");
1383 assert_eq!(sizage.xs(), expected_xs, "xs mismatch for {code:?}");
1384 assert_eq!(sizage.ls(), expected_ls, "ls mismatch for {code:?}");
1385 match (sizage.fs(), &expected_fs) {
1386 (SizeType::Fixed(a), Some(b)) => {
1387 assert_eq!(u16::from(*a), *b, "fs mismatch for {code:?}");
1388 }
1389 (SizeType::Small | SizeType::Large, None) => {}
1390 (SizeType::Fixed(_), None) | (SizeType::Small | SizeType::Large, Some(_)) => panic!(
1391 "SizeType mismatch for {code:?}: got {:?}, expected fs={expected_fs:?}",
1392 sizage.fs(),
1393 ),
1394 }
1395 }
1396
1397 #[rstest]
1398 #[case(&[], Err(ParsingError::EmptyStream))]
1399 #[case(
1400 &with_payload!(&[0xD4, 0x00, 0x00])[0..2],
1401 Err(ParsingError::StreamTooShort(MatterPart::Head))
1402 )]
1403 #[case(
1404 &with_payload!(&[0xD0, 0x1F])[0..1],
1405 Err(ParsingError::StreamTooShort(MatterPart::Head))
1406 )]
1407 #[case(&[0xFF], Err(ParsingError::MalformedCode { part: MatterPart::Head, found: "63".to_owned() }))]
1408 fn test_from_stream_errors(
1409 #[case] input: &[u8],
1410 #[case] expected: Result<MatterCode, ParsingError>,
1411 ) {
1412 assert_eq!(MatterCode::from_stream(input), expected);
1413 }
1414
1415 #[test]
1416 fn all_code_strings_are_unique() {
1417 use std::collections::HashSet;
1418 let mut seen = HashSet::new();
1419 for variant in MatterCode::iter() {
1420 let code_str: &'static str = variant.into();
1421 assert!(
1422 seen.insert(code_str),
1423 "Duplicate code string: {code_str} for {variant:?}"
1424 );
1425 }
1426 }
1427
1428 #[test]
1429 fn no_code_is_prefix_of_another() {
1430 let codes: Vec<&'static str> = MatterCode::iter()
1431 .map(|v| -> &'static str { v.into() })
1432 .collect();
1433 for (i, a) in codes.iter().enumerate() {
1434 for (j, b) in codes.iter().enumerate() {
1435 if i != j && a.len() < b.len() {
1436 assert!(
1437 !b.starts_with(a),
1438 "Code '{a}' is a prefix of '{b}' — ambiguous parsing"
1439 );
1440 }
1441 }
1442 }
1443 }
1444
1445 #[test]
1446 fn variant_count_matches_expected() {
1447 let count = MatterCode::iter().count();
1448 assert!(
1449 count >= 109,
1450 "Expected at least 109 MatterCode variants, found {count}"
1451 );
1452 }
1453
1454 #[test]
1460 fn all_fixed_codes_have_fs_divisible_by_4() {
1461 for code in MatterCode::iter() {
1464 let sizage = code.get_sizage();
1465 if let SizeType::Fixed(fs) = sizage.fs {
1466 assert_eq!(
1467 fs % 4,
1468 0,
1469 "Code {code:?} has fs={fs} which is not divisible by 4"
1470 );
1471 }
1472 }
1473 }
1474
1475 #[test]
1476 fn all_codes_cs_not_3_mod_4() {
1477 for code in MatterCode::iter() {
1480 let sizage = code.get_sizage();
1481 let cs = sizage.hs + sizage.ss;
1482 assert_ne!(
1483 cs % 4,
1484 3,
1485 "Code {code:?} has cs={cs} (hs={}, ss={}) which is 3 mod 4",
1486 sizage.hs,
1487 sizage.ss
1488 );
1489 }
1490 }
1491
1492 #[test]
1493 fn all_codes_hs_is_1_2_or_4() {
1494 for code in MatterCode::iter() {
1496 let sizage = code.get_sizage();
1497 assert!(
1498 sizage.hs == 1 || sizage.hs == 2 || sizage.hs == 4,
1499 "Code {code:?} has unexpected hs={}",
1500 sizage.hs
1501 );
1502 }
1503 }
1504
1505 #[test]
1506 fn fixed_non_special_codes_raw_size_consistency() {
1507 for code in MatterCode::iter() {
1510 let sizage = code.get_sizage();
1511 if let SizeType::Fixed(fs_val) = sizage.fs
1512 && sizage.ss == 0
1513 {
1514 let cs = usize::from(sizage.hs) + usize::from(sizage.ss);
1515 let rs = ((usize::from(fs_val) - cs) * 3 / 4) - usize::from(sizage.ls);
1516 let computed = code.raw_size().unwrap();
1517 assert_eq!(
1518 rs, computed,
1519 "Code {code:?}: formula gives rs={rs} but raw_size()={computed}"
1520 );
1521 }
1522 }
1523 }
1524
1525 #[test]
1526 fn fixed_codes_full_size_reconstructible() {
1527 for code in MatterCode::iter() {
1531 let sizage = code.get_sizage();
1532 if let SizeType::Fixed(fs_val) = sizage.fs
1533 && sizage.ss == 0
1534 {
1535 let cs = usize::from(sizage.hs) + usize::from(sizage.ss);
1536 let rs = code.raw_size().unwrap();
1537 let ls = usize::from(sizage.ls);
1538 let total_data_chars = usize::from(fs_val) - cs;
1539 let data_bytes = total_data_chars * 3 / 4;
1540 assert_eq!(
1541 data_bytes,
1542 rs + ls,
1543 "Code {code:?}: data_bytes={data_bytes} but rs+ls={}",
1544 rs + ls
1545 );
1546 }
1547 }
1548 }
1549
1550 #[test]
1551 fn variable_codes_have_correct_structure() {
1552 for code in MatterCode::iter() {
1555 let sizage = code.get_sizage();
1556 match sizage.fs {
1557 SizeType::Small => {
1558 assert_eq!(sizage.hs, 2, "Small code {code:?} should have hs=2");
1559 assert_eq!(sizage.ss, 2, "Small code {code:?} should have ss=2");
1560 assert!(sizage.ls <= 2, "Small code {code:?} has ls={}", sizage.ls);
1561 }
1562 SizeType::Large => {
1563 assert_eq!(sizage.hs, 4, "Large code {code:?} should have hs=4");
1564 assert_eq!(sizage.ss, 4, "Large code {code:?} should have ss=4");
1565 assert!(sizage.ls <= 2, "Large code {code:?} has ls={}", sizage.ls);
1566 }
1567 SizeType::Fixed(_) => {}
1568 }
1569 }
1570 }
1571
1572 #[test]
1573 fn x25519_cipher_qb64_big_l2_is_large() {
1574 let sizage = MatterCode::X25519CipherQB64Big_L2.get_sizage();
1575 assert!(
1576 matches!(sizage.fs, SizeType::Large),
1577 "X25519CipherQB64Big_L2 must be SizeType::Large (matching keripy)"
1578 );
1579 assert_eq!(sizage.hs, 4);
1580 assert_eq!(sizage.ss, 4);
1581 assert_eq!(sizage.ls, 2);
1582 }
1583
1584 #[test]
1585 fn lead_size_is_0_1_or_2() {
1586 for code in MatterCode::iter() {
1589 let sizage = code.get_sizage();
1590 assert!(
1591 sizage.ls <= 2,
1592 "Code {code:?} has ls={} which exceeds maximum of 2",
1593 sizage.ls
1594 );
1595 }
1596 }
1597
1598 #[test]
1599 fn variable_codes_come_in_l0_l1_l2_triplets() {
1600 let is_variable = |c: &MatterCode| {
1609 let s = c.get_sizage();
1610 matches!(s.fs, SizeType::Small | SizeType::Large)
1611 };
1612
1613 let small_codes: Vec<_> = MatterCode::iter()
1615 .filter(|c| is_variable(c) && c.get_sizage().hs == 2)
1616 .collect();
1617 let small_l0 = small_codes
1618 .iter()
1619 .filter(|c| c.get_sizage().ls == 0)
1620 .count();
1621 let small_l1 = small_codes
1622 .iter()
1623 .filter(|c| c.get_sizage().ls == 1)
1624 .count();
1625 let small_l2 = small_codes
1626 .iter()
1627 .filter(|c| c.get_sizage().ls == 2)
1628 .count();
1629 assert_eq!(
1630 small_l0, small_l1,
1631 "Small codes: L0 count ({small_l0}) != L1 count ({small_l1})"
1632 );
1633 assert_eq!(
1634 small_l1, small_l2,
1635 "Small codes: L1 count ({small_l1}) != L2 count ({small_l2})"
1636 );
1637
1638 let large_codes: Vec<_> = MatterCode::iter()
1640 .filter(|c| is_variable(c) && c.get_sizage().hs == 4)
1641 .collect();
1642 let large_l0 = large_codes
1643 .iter()
1644 .filter(|c| c.get_sizage().ls == 0)
1645 .count();
1646 let large_l1 = large_codes
1647 .iter()
1648 .filter(|c| c.get_sizage().ls == 1)
1649 .count();
1650 let large_l2 = large_codes
1651 .iter()
1652 .filter(|c| c.get_sizage().ls == 2)
1653 .count();
1654 assert_eq!(
1655 large_l0, large_l1,
1656 "Large codes: L0 count ({large_l0}) != L1 count ({large_l1})"
1657 );
1658 assert_eq!(
1659 large_l1, large_l2,
1660 "Large codes: L1 count ({large_l1}) != L2 count ({large_l2})"
1661 );
1662 }
1663
1664 #[test]
1665 fn code_string_length_matches_hs() {
1666 for code in MatterCode::iter() {
1669 let sizage = code.get_sizage();
1670 let code_str: &str = code.as_ref();
1671 assert_eq!(
1672 code_str.len(),
1673 usize::from(sizage.hs),
1674 "Code {code:?} has string '{code_str}' (len={}) but hs={}",
1675 code_str.len(),
1676 sizage.hs
1677 );
1678 }
1679 }
1680
1681 #[test]
1682 fn xs_only_nonzero_for_expected_codes() {
1683 for code in MatterCode::iter() {
1687 let sizage = code.get_sizage();
1688 if sizage.xs > 0 {
1689 assert!(
1690 matches!(
1691 code,
1692 MatterCode::Tag1 | MatterCode::Tag5 | MatterCode::Tag9 | MatterCode::TBD1S
1693 ),
1694 "Code {code:?} has xs={} but is not an expected odd-tag code",
1695 sizage.xs
1696 );
1697 assert_eq!(
1698 sizage.xs, 1,
1699 "Code {code:?} has xs={} but expected xs=1",
1700 sizage.xs
1701 );
1702 }
1703 }
1704 }
1705
1706 #[test]
1712 fn is_special_true_for_tag_codes() {
1713 let special_codes = [
1714 MatterCode::Tag1,
1715 MatterCode::Tag2,
1716 MatterCode::Tag3,
1717 MatterCode::Tag4,
1718 MatterCode::Tag5,
1719 MatterCode::Tag6,
1720 MatterCode::Tag7,
1721 MatterCode::Tag8,
1722 MatterCode::Tag9,
1723 MatterCode::Tag10,
1724 MatterCode::Tag11,
1725 ];
1726 for code in special_codes {
1727 assert!(code.is_special(), "{code:?} should be special");
1728 }
1729 }
1730
1731 #[test]
1732 fn is_special_true_for_gram_codes() {
1733 let gram_codes = [
1734 MatterCode::GramHeadNeck,
1735 MatterCode::GramHead,
1736 MatterCode::GramHeadAIDNeck,
1737 MatterCode::GramHeadAID,
1738 ];
1739 for code in gram_codes {
1740 assert!(code.is_special(), "{code:?} should be special");
1741 }
1742 }
1743
1744 #[test]
1745 fn is_special_true_for_tbd_s_codes() {
1746 let tbd_s_codes = [MatterCode::TBD0S, MatterCode::TBD1S, MatterCode::TBD2S];
1747 for code in tbd_s_codes {
1748 assert!(code.is_special(), "{code:?} should be special");
1749 }
1750 }
1751
1752 #[test]
1753 fn is_special_false_for_basic_crypto_codes() {
1754 let non_special = [
1755 MatterCode::Ed25519Seed,
1756 MatterCode::Ed25519N,
1757 MatterCode::Ed25519,
1758 MatterCode::Blake3_256,
1759 MatterCode::SHA2_256,
1760 MatterCode::Ed25519Sig,
1761 MatterCode::ECDSA256k1Sig,
1762 MatterCode::Short,
1763 MatterCode::Long,
1764 MatterCode::Salt128,
1765 ];
1766 for code in non_special {
1767 assert!(!code.is_special(), "{code:?} should NOT be special");
1768 }
1769 }
1770
1771 #[test]
1772 fn is_special_false_for_variable_codes() {
1773 let variable = [
1774 MatterCode::StrB64_L0,
1775 MatterCode::Bytes_L1,
1776 MatterCode::BytesBig_L2,
1777 ];
1778 for code in variable {
1779 assert!(
1780 !code.is_special(),
1781 "{code:?} should NOT be special (variable)"
1782 );
1783 }
1784 }
1785
1786 #[test]
1787 fn is_special_exhaustive_agrees_with_definition() {
1788 for code in MatterCode::iter() {
1791 let sizage = code.get_sizage();
1792 let expected = matches!(sizage.fs, SizeType::Fixed(_)) && sizage.ss > 0;
1793 assert_eq!(
1794 code.is_special(),
1795 expected,
1796 "Code {:?}: is_special()={} but expected={} (fs={:?}, ss={})",
1797 code,
1798 code.is_special(),
1799 expected,
1800 sizage.fs,
1801 sizage.ss
1802 );
1803 }
1804 }
1805}