1use crate::{IsccError, IsccResult};
8use std::borrow::Cow;
9
10#[repr(u8)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17pub enum MainType {
18 Meta = 0,
19 Semantic = 1,
20 Content = 2,
21 Data = 3,
22 Instance = 4,
23 Iscc = 5,
24 Id = 6,
25 Flake = 7,
26}
27
28impl TryFrom<u8> for MainType {
29 type Error = IsccError;
30
31 fn try_from(value: u8) -> Result<Self, Self::Error> {
32 match value {
33 0 => Ok(Self::Meta),
34 1 => Ok(Self::Semantic),
35 2 => Ok(Self::Content),
36 3 => Ok(Self::Data),
37 4 => Ok(Self::Instance),
38 5 => Ok(Self::Iscc),
39 6 => Ok(Self::Id),
40 7 => Ok(Self::Flake),
41 _ => Err(IsccError::InvalidInput(format!(
42 "invalid MainType: {value}"
43 ))),
44 }
45 }
46}
47
48#[repr(u8)]
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum SubType {
56 None = 0,
58 Image = 1,
60 Audio = 2,
62 Video = 3,
64 Mixed = 4,
66 Sum = 5,
68 IsccNone = 6,
70 Wide = 7,
72}
73
74impl SubType {
75 pub const TEXT: Self = Self::None;
77}
78
79impl TryFrom<u8> for SubType {
80 type Error = IsccError;
81
82 fn try_from(value: u8) -> Result<Self, Self::Error> {
83 match value {
84 0 => Ok(Self::None),
85 1 => Ok(Self::Image),
86 2 => Ok(Self::Audio),
87 3 => Ok(Self::Video),
88 4 => Ok(Self::Mixed),
89 5 => Ok(Self::Sum),
90 6 => Ok(Self::IsccNone),
91 7 => Ok(Self::Wide),
92 _ => Err(IsccError::InvalidInput(format!("invalid SubType: {value}"))),
93 }
94 }
95}
96
97#[repr(u8)]
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104#[non_exhaustive]
105pub enum Version {
106 V0 = 0,
107 V1 = 1,
108}
109
110impl TryFrom<u8> for Version {
111 type Error = IsccError;
112
113 fn try_from(value: u8) -> Result<Self, Self::Error> {
114 match value {
115 0 => Ok(Self::V0),
116 1 => Ok(Self::V1),
117 _ => Err(IsccError::InvalidInput(format!("invalid Version: {value}"))),
118 }
119 }
120}
121
122fn validate_version(mtype: MainType, version: Version) -> IsccResult<()> {
129 match (mtype, version) {
130 (_, Version::V0) | (MainType::Id, Version::V1) => Ok(()),
131 (_, other) => Err(IsccError::InvalidInput(format!(
132 "invalid Version: {} for MainType {mtype:?}",
133 other as u8
134 ))),
135 }
136}
137
138fn get_bit(data: &[u8], bit_pos: usize) -> bool {
142 let byte_idx = bit_pos / 8;
143 let bit_idx = 7 - (bit_pos % 8);
144 (data[byte_idx] >> bit_idx) & 1 == 1
145}
146
147fn extract_bits(data: &[u8], bit_pos: usize, count: usize) -> u32 {
149 let mut value = 0u32;
150 for i in 0..count {
151 value = (value << 1) | u32::from(get_bit(data, bit_pos + i));
152 }
153 value
154}
155
156#[cfg(test)]
158fn bits_to_u32(bits: &[bool]) -> u32 {
159 bits.iter().fold(0u32, |acc, &b| (acc << 1) | u32::from(b))
160}
161
162#[cfg(test)]
164fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> {
165 bytes
166 .iter()
167 .flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1 == 1))
168 .collect()
169}
170
171fn bits_to_bytes(bits: &[bool]) -> Vec<u8> {
173 bits.chunks(8)
174 .map(|chunk| {
175 chunk.iter().enumerate().fold(
176 0u8,
177 |byte, (i, &bit)| if bit { byte | (1 << (7 - i)) } else { byte },
178 )
179 })
180 .collect()
181}
182
183fn encode_varnibble(value: u32) -> IsccResult<Vec<bool>> {
193 match value {
194 0..=7 => {
195 Ok((0..4).rev().map(|i| (value >> i) & 1 == 1).collect())
197 }
198 8..=71 => {
199 let v = value - 8;
201 let mut bits = vec![true, false];
202 bits.extend((0..6).rev().map(|i| (v >> i) & 1 == 1));
203 Ok(bits)
204 }
205 72..=583 => {
206 let v = value - 72;
208 let mut bits = vec![true, true, false];
209 bits.extend((0..9).rev().map(|i| (v >> i) & 1 == 1));
210 Ok(bits)
211 }
212 584..=4679 => {
213 let v = value - 584;
215 let mut bits = vec![true, true, true, false];
216 bits.extend((0..12).rev().map(|i| (v >> i) & 1 == 1));
217 Ok(bits)
218 }
219 _ => Err(IsccError::InvalidInput(format!(
220 "varnibble value out of range (0-4679): {value}"
221 ))),
222 }
223}
224
225fn decode_varnibble_from_bytes(data: &[u8], bit_pos: usize) -> IsccResult<(u32, usize)> {
231 let available = data.len() * 8 - bit_pos;
232 if available < 4 {
233 return Err(IsccError::InvalidInput(
234 "insufficient bits for varnibble".into(),
235 ));
236 }
237
238 if !get_bit(data, bit_pos) {
239 Ok((extract_bits(data, bit_pos, 4), 4))
241 } else if available >= 8 && !get_bit(data, bit_pos + 1) {
242 Ok((extract_bits(data, bit_pos + 2, 6) + 8, 8))
244 } else if available >= 12 && !get_bit(data, bit_pos + 2) {
245 Ok((extract_bits(data, bit_pos + 3, 9) + 72, 12))
247 } else if available >= 16 && !get_bit(data, bit_pos + 3) {
248 Ok((extract_bits(data, bit_pos + 4, 12) + 584, 16))
250 } else {
251 Err(IsccError::InvalidInput(
252 "invalid varnibble prefix or insufficient bits".into(),
253 ))
254 }
255}
256
257pub fn encode_header(
265 mtype: MainType,
266 stype: SubType,
267 version: Version,
268 length: u32,
269) -> IsccResult<Vec<u8>> {
270 validate_version(mtype, version)?;
271
272 let mut bits = Vec::new();
273 bits.extend(encode_varnibble(mtype as u32)?);
274 bits.extend(encode_varnibble(stype as u32)?);
275 bits.extend(encode_varnibble(version as u32)?);
276 bits.extend(encode_varnibble(length)?);
277
278 let remainder = bits.len() % 8;
280 if remainder != 0 {
281 bits.resize(bits.len() + (8 - remainder), false);
282 }
283
284 Ok(bits_to_bytes(&bits))
285}
286
287pub fn decode_header(data: &[u8]) -> IsccResult<(MainType, SubType, Version, u32, Vec<u8>)> {
294 let mut bit_pos = 0;
295
296 let (mtype_val, consumed) = decode_varnibble_from_bytes(data, bit_pos)?;
297 bit_pos += consumed;
298
299 let (stype_val, consumed) = decode_varnibble_from_bytes(data, bit_pos)?;
300 bit_pos += consumed;
301
302 let (version_val, consumed) = decode_varnibble_from_bytes(data, bit_pos)?;
303 bit_pos += consumed;
304
305 let (length, consumed) = decode_varnibble_from_bytes(data, bit_pos)?;
306 bit_pos += consumed;
307
308 if bit_pos % 8 != 0 && bit_pos + 4 <= data.len() * 8 && extract_bits(data, bit_pos, 4) == 0 {
311 bit_pos += 4;
312 }
313
314 let tail_byte_start = bit_pos.div_ceil(8);
316 let tail = if tail_byte_start < data.len() {
317 data[tail_byte_start..].to_vec()
318 } else {
319 vec![]
320 };
321
322 let mtype_u8 = u8::try_from(mtype_val)
325 .map_err(|_| IsccError::InvalidInput(format!("invalid MainType: {mtype_val}")))?;
326 let stype_u8 = u8::try_from(stype_val)
327 .map_err(|_| IsccError::InvalidInput(format!("invalid SubType: {stype_val}")))?;
328 let version_u8 = u8::try_from(version_val)
329 .map_err(|_| IsccError::InvalidInput(format!("invalid Version: {version_val}")))?;
330
331 let mtype = MainType::try_from(mtype_u8)?;
332 let stype = SubType::try_from(stype_u8)?;
333 let version = Version::try_from(version_u8)?;
334 validate_version(mtype, version)?;
335
336 Ok((mtype, stype, version, length, tail))
337}
338
339pub fn encode_length(mtype: MainType, length: u32) -> IsccResult<u32> {
348 match mtype {
349 MainType::Meta
350 | MainType::Semantic
351 | MainType::Content
352 | MainType::Data
353 | MainType::Instance
354 | MainType::Flake => {
355 if length >= 32 && length % 32 == 0 {
356 Ok(length / 32 - 1)
357 } else {
358 Err(IsccError::InvalidInput(format!(
359 "invalid length {length} for {mtype:?} (must be multiple of 32, >= 32)"
360 )))
361 }
362 }
363 MainType::Iscc => {
364 if length <= 7 {
365 Ok(length)
366 } else {
367 Err(IsccError::InvalidInput(format!(
368 "invalid length {length} for ISCC (must be 0-7)"
369 )))
370 }
371 }
372 MainType::Id => {
373 if (64..=96).contains(&length) && (length - 64) % 8 == 0 {
374 Ok((length - 64) / 8)
375 } else {
376 Err(IsccError::InvalidInput(format!(
377 "invalid length {length} for ID (must be 64-96, step 8)"
378 )))
379 }
380 }
381 }
382}
383
384pub fn decode_length(mtype: MainType, length: u32, stype: SubType) -> u32 {
392 match mtype {
393 MainType::Meta
394 | MainType::Semantic
395 | MainType::Content
396 | MainType::Data
397 | MainType::Instance
398 | MainType::Flake => (length + 1) * 32,
399 MainType::Iscc => {
400 if stype == SubType::Wide {
401 256
402 } else {
403 length.count_ones() * 64 + 128
404 }
405 }
406 MainType::Id => length * 8 + 64,
407 }
408}
409
410pub fn encode_units(main_types: &[MainType]) -> IsccResult<u32> {
419 let mut result = 0u32;
420 for &mt in main_types {
421 match mt {
422 MainType::Content => result |= 1,
423 MainType::Semantic => result |= 2,
424 MainType::Meta => result |= 4,
425 _ => {
426 return Err(IsccError::InvalidInput(format!(
427 "{mt:?} is not a valid optional unit type"
428 )));
429 }
430 }
431 }
432 Ok(result)
433}
434
435pub fn decode_units(unit_id: u32) -> IsccResult<Vec<MainType>> {
442 if unit_id > 7 {
443 return Err(IsccError::InvalidInput(format!(
444 "invalid unit_id: {unit_id} (must be 0-7)"
445 )));
446 }
447 let mut result = Vec::new();
448 if unit_id & 4 != 0 {
449 result.push(MainType::Meta);
450 }
451 if unit_id & 2 != 0 {
452 result.push(MainType::Semantic);
453 }
454 if unit_id & 1 != 0 {
455 result.push(MainType::Content);
456 }
457 Ok(result)
458}
459
460pub(crate) const PREFIXES: [&str; 26] = [
465 "AA", "CA", "CE", "CI", "CM", "CQ", "EA", "EE", "EI", "EM", "EQ", "GA", "IA", "KA", "KE", "KI", "KM", "KQ", "KU", "KY", "K4", "MA", "ME", "MI", "MM", "OA", ];
492
493pub fn encode_base32(data: &[u8]) -> String {
497 data_encoding::BASE32_NOPAD.encode(data)
498}
499
500pub fn decode_base32(code: &str) -> IsccResult<Vec<u8>> {
502 let upper = code.to_uppercase();
503 data_encoding::BASE32_NOPAD
504 .decode(upper.as_bytes())
505 .map_err(|e| IsccError::InvalidInput(format!("base32 decode error: {e}")))
506}
507
508pub fn encode_base64(data: &[u8]) -> String {
512 data_encoding::BASE64URL_NOPAD.encode(data)
513}
514
515pub fn encode_component(
525 mtype: MainType,
526 stype: SubType,
527 version: Version,
528 bit_length: u32,
529 digest: &[u8],
530) -> IsccResult<String> {
531 if mtype == MainType::Iscc {
532 return Err(IsccError::InvalidInput(
533 "ISCC MainType is not a unit; use gen_iscc_code_v0 instead".into(),
534 ));
535 }
536
537 let encoded_length = encode_length(mtype, bit_length)?;
538 let nbytes = (bit_length / 8) as usize;
539 let header = encode_header(mtype, stype, version, encoded_length)?;
540 let body = &digest[..nbytes.min(digest.len())];
541
542 let mut component = header;
543 component.extend_from_slice(body);
544
545 Ok(encode_base32(&component))
546}
547
548pub(crate) fn iscc_clean(iscc: &str) -> IsccResult<Cow<'_, str>> {
567 let trimmed = iscc.trim();
568 let cleaned: Cow<'_, str> = match trimmed.split_once(':') {
569 None => {
570 let is_multibase = matches!(
573 trimmed.as_bytes().first(),
574 Some(b'f' | b'b' | b'v' | b'z' | b'u')
575 );
576 if is_multibase || !trimmed.contains('-') {
577 Cow::Borrowed(trimmed)
578 } else {
579 Cow::Owned(trimmed.replace('-', ""))
580 }
581 }
582 Some((scheme, rest)) => {
583 let scheme = scheme.trim();
584 let code = rest.trim();
585 if code.contains(':') {
587 return Err(IsccError::InvalidInput(format!(
588 "Malformed ISCC string: {iscc}"
589 )));
590 }
591 if !scheme.eq_ignore_ascii_case("iscc") {
592 return Err(IsccError::InvalidInput(format!("Invalid scheme: {scheme}")));
593 }
594 if code.contains('-') {
595 Cow::Owned(code.replace('-', ""))
596 } else {
597 Cow::Borrowed(code)
598 }
599 }
600 };
601
602 if cleaned.is_empty() {
603 return Err(IsccError::InvalidInput("Empty ISCC string".to_string()));
604 }
605
606 Ok(cleaned)
607}
608
609pub fn iscc_decompose(iscc_code: &str) -> IsccResult<Vec<String>> {
616 let clean = iscc_clean(iscc_code)?;
617 let mut raw_code = decode_base32(&clean)?;
618 let mut components = Vec::new();
619
620 while !raw_code.is_empty() {
621 let (mt, st, vs, ln, body) = decode_header(&raw_code)?;
622
623 if mt != MainType::Iscc {
625 let ln_bits = decode_length(mt, ln, st);
626 let nbytes = (ln_bits / 8) as usize;
627 if body.len() < nbytes {
628 return Err(IsccError::InvalidInput(format!(
629 "truncated ISCC body: expected {nbytes} bytes, got {}",
630 body.len()
631 )));
632 }
633 let code = encode_component(mt, st, vs, ln_bits, &body[..nbytes])?;
634 components.push(code);
635 raw_code = body[nbytes..].to_vec();
636 continue;
637 }
638
639 let main_types = decode_units(ln)?;
641
642 if st == SubType::Wide {
644 if body.len() < 32 {
645 return Err(IsccError::InvalidInput(format!(
646 "truncated ISCC body: expected 32 bytes, got {}",
647 body.len()
648 )));
649 }
650 let data_code = encode_component(MainType::Data, SubType::None, vs, 128, &body[..16])?;
651 let instance_code =
652 encode_component(MainType::Instance, SubType::None, vs, 128, &body[16..32])?;
653 components.push(data_code);
654 components.push(instance_code);
655 break;
656 }
657
658 let expected_body = main_types.len() * 8 + 16;
660 if body.len() < expected_body {
661 return Err(IsccError::InvalidInput(format!(
662 "truncated ISCC body: expected {expected_body} bytes, got {}",
663 body.len()
664 )));
665 }
666
667 for (idx, &mtype) in main_types.iter().enumerate() {
669 let stype = if mtype == MainType::Meta {
670 SubType::None
671 } else {
672 st
673 };
674 let code = encode_component(mtype, stype, vs, 64, &body[idx * 8..])?;
675 components.push(code);
676 }
677
678 let data_code = encode_component(
680 MainType::Data,
681 SubType::None,
682 vs,
683 64,
684 &body[body.len() - 16..body.len() - 8],
685 )?;
686 let instance_code = encode_component(
687 MainType::Instance,
688 SubType::None,
689 vs,
690 64,
691 &body[body.len() - 8..],
692 )?;
693 components.push(data_code);
694 components.push(instance_code);
695 break;
696 }
697
698 Ok(components)
699}
700
701#[cfg(test)]
702mod tests {
703 use super::*;
704
705 #[test]
708 fn test_iscc_clean_strips_scheme_and_dashes() {
709 assert_eq!(
711 iscc_clean("ISCC:KACY-PXW4-45FT-YNJ3").unwrap(),
712 "KACYPXW445FTYNJ3"
713 );
714 }
715
716 #[test]
717 fn test_iscc_clean_case_insensitive_scheme() {
718 assert_eq!(
719 iscc_clean("iscc:KACYPXW445FTYNJ3").unwrap(),
720 "KACYPXW445FTYNJ3"
721 );
722 assert_eq!(
723 iscc_clean("Iscc:KACYPXW445FTYNJ3").unwrap(),
724 "KACYPXW445FTYNJ3"
725 );
726 }
727
728 #[test]
729 fn test_iscc_clean_trims_whitespace() {
730 assert_eq!(iscc_clean(" ISCC: KACY-PXW4 ").unwrap(), "KACYPXW4");
731 }
732
733 #[test]
734 fn test_iscc_clean_no_prefix() {
735 assert_eq!(
736 iscc_clean("KACY-PXW4-45FT-YNJ3").unwrap(),
737 "KACYPXW445FTYNJ3"
738 );
739 }
740
741 #[test]
742 fn test_iscc_clean_preserves_multibase_dashes() {
743 assert_eq!(iscc_clean("uABC-DEF").unwrap(), "uABC-DEF");
745 for prefix in ['f', 'b', 'v', 'z', 'u'] {
747 let input = format!("{prefix}AA-BB");
748 assert_eq!(iscc_clean(&input).unwrap(), input);
749 }
750 }
751
752 #[test]
753 fn test_iscc_clean_rejects_bad_scheme() {
754 assert!(matches!(
755 iscc_clean("http:KACYPXW445FTYNJ3"),
756 Err(IsccError::InvalidInput(_))
757 ));
758 }
759
760 #[test]
761 fn test_iscc_clean_rejects_extra_colon() {
762 assert!(matches!(
763 iscc_clean("ISCC:KACY:PXW4"),
764 Err(IsccError::InvalidInput(_))
765 ));
766 }
767
768 #[test]
771 fn test_varnibble_roundtrip() {
772 let test_values = [0, 1, 7, 8, 71, 72, 583, 584, 4679];
773 for &value in &test_values {
774 let bits = encode_varnibble(value).unwrap();
775 let bytes = bits_to_bytes(&bits);
776 let (decoded, consumed) = decode_varnibble_from_bytes(&bytes, 0).unwrap();
777 assert_eq!(decoded, value, "roundtrip failed for value {value}");
778 assert_eq!(consumed, bits.len(), "consumed mismatch for value {value}");
779 }
780 }
781
782 #[test]
783 fn test_varnibble_bit_lengths() {
784 assert_eq!(encode_varnibble(0).unwrap().len(), 4);
786 assert_eq!(encode_varnibble(7).unwrap().len(), 4);
787 assert_eq!(encode_varnibble(8).unwrap().len(), 8);
789 assert_eq!(encode_varnibble(71).unwrap().len(), 8);
790 assert_eq!(encode_varnibble(72).unwrap().len(), 12);
792 assert_eq!(encode_varnibble(583).unwrap().len(), 12);
793 assert_eq!(encode_varnibble(584).unwrap().len(), 16);
795 assert_eq!(encode_varnibble(4679).unwrap().len(), 16);
796 }
797
798 #[test]
799 fn test_varnibble_out_of_range() {
800 assert!(encode_varnibble(4680).is_err());
801 }
802
803 #[test]
804 fn test_varnibble_boundary_values() {
805 let bits_0 = encode_varnibble(0).unwrap();
807 assert_eq!(bits_0, vec![false, false, false, false]); let bits_7 = encode_varnibble(7).unwrap();
810 assert_eq!(bits_7, vec![false, true, true, true]); let bits_8 = encode_varnibble(8).unwrap();
813 assert_eq!(
814 bits_8,
815 vec![true, false, false, false, false, false, false, false]
816 ); }
818
819 #[test]
822 fn test_extract_bits_basic() {
823 let data = [0xA5u8];
825 assert_eq!(extract_bits(&data, 0, 4), 0b1010); assert_eq!(extract_bits(&data, 4, 4), 0b0101); assert_eq!(extract_bits(&data, 0, 8), 0xA5); assert_eq!(extract_bits(&data, 1, 3), 0b010); assert_eq!(extract_bits(&data, 0, 1), 1); assert_eq!(extract_bits(&data, 7, 1), 1); let data2 = [0xFF, 0x00];
834 assert_eq!(extract_bits(&data2, 0, 8), 0xFF);
835 assert_eq!(extract_bits(&data2, 8, 8), 0x00);
836 assert_eq!(extract_bits(&data2, 4, 8), 0xF0); assert_eq!(extract_bits(&data2, 6, 4), 0b1100); }
839
840 #[test]
841 fn test_decode_varnibble_from_bytes_boundary_values() {
842 let bits_3 = encode_varnibble(3).unwrap();
847 let bits_8 = encode_varnibble(8).unwrap();
848 let mut combined_bits = bits_3.clone();
849 combined_bits.extend(&bits_8);
850 let bytes = bits_to_bytes(&combined_bits);
851
852 let (val1, consumed1) = decode_varnibble_from_bytes(&bytes, 0).unwrap();
854 assert_eq!(val1, 3);
855 assert_eq!(consumed1, 4);
856
857 let (val2, consumed2) = decode_varnibble_from_bytes(&bytes, 4).unwrap();
859 assert_eq!(val2, 8);
860 assert_eq!(consumed2, 8);
861
862 let bits_0 = encode_varnibble(0).unwrap();
865 let bits_72 = encode_varnibble(72).unwrap();
866 let mut combined2 = bits_0;
867 combined2.extend(&bits_72);
868 let bytes2 = bits_to_bytes(&combined2);
869
870 let (val3, consumed3) = decode_varnibble_from_bytes(&bytes2, 4).unwrap();
871 assert_eq!(val3, 72);
872 assert_eq!(consumed3, 12);
873
874 let single_byte = [0x00u8];
876 let result = decode_varnibble_from_bytes(&single_byte, 6);
877 assert!(result.is_err(), "should fail with only 2 bits available");
878 }
879
880 #[test]
883 fn test_encode_header_meta_v0() {
884 let header = encode_header(MainType::Meta, SubType::None, Version::V0, 1).unwrap();
886 assert_eq!(header, vec![0x00, 0x01]);
887 }
888
889 #[test]
890 fn test_encode_header_with_padding() {
891 let header = encode_header(MainType::Meta, SubType::None, Version::V0, 8).unwrap();
895 assert_eq!(header.len(), 3);
896 assert_eq!(header, vec![0x00, 0x08, 0x00]);
899 }
900
901 #[test]
902 fn test_encode_header_data_type() {
903 let header = encode_header(MainType::Data, SubType::None, Version::V0, 1).unwrap();
905 assert_eq!(header, vec![0x30, 0x01]);
908 }
909
910 #[test]
911 fn test_encode_header_instance_type() {
912 let header = encode_header(MainType::Instance, SubType::None, Version::V0, 1).unwrap();
914 assert_eq!(header, vec![0x40, 0x01]);
917 }
918
919 #[test]
920 fn test_decode_header_roundtrip_all_main_types() {
921 let main_types = [
922 MainType::Meta,
923 MainType::Semantic,
924 MainType::Content,
925 MainType::Data,
926 MainType::Instance,
927 MainType::Iscc,
928 MainType::Id,
929 MainType::Flake,
930 ];
931
932 for &mtype in &main_types {
933 let header = encode_header(mtype, SubType::None, Version::V0, 1).unwrap();
934 let (dec_mtype, dec_stype, dec_version, dec_length, tail) =
935 decode_header(&header).unwrap();
936 assert_eq!(dec_mtype, mtype, "MainType mismatch for {mtype:?}");
937 assert_eq!(dec_stype, SubType::None);
938 assert_eq!(dec_version, Version::V0);
939 assert_eq!(dec_length, 1);
940 assert!(tail.is_empty(), "unexpected tail for {mtype:?}");
941 }
942 }
943
944 #[test]
945 fn test_decode_header_with_tail() {
946 let header = encode_header(MainType::Meta, SubType::None, Version::V0, 1).unwrap();
948 let body = vec![0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44];
949 let mut data = header;
950 data.extend_from_slice(&body);
951
952 let (mtype, stype, version, length, tail) = decode_header(&data).unwrap();
953 assert_eq!(mtype, MainType::Meta);
954 assert_eq!(stype, SubType::None);
955 assert_eq!(version, Version::V0);
956 assert_eq!(length, 1);
957 assert_eq!(tail, body);
958 }
959
960 #[test]
961 fn test_decode_header_with_padding_and_tail() {
962 let header = encode_header(MainType::Meta, SubType::None, Version::V0, 8).unwrap();
964 assert_eq!(header.len(), 3); let body = vec![0xFF, 0xEE];
967 let mut data = header;
968 data.extend_from_slice(&body);
969
970 let (mtype, _stype, _version, length, tail) = decode_header(&data).unwrap();
971 assert_eq!(mtype, MainType::Meta);
972 assert_eq!(length, 8);
973 assert_eq!(tail, body);
974 }
975
976 #[test]
977 fn test_decode_header_subtypes() {
978 let header = encode_header(MainType::Content, SubType::Image, Version::V0, 1).unwrap();
980 let (mtype, stype, version, length, _tail) = decode_header(&header).unwrap();
981 assert_eq!(mtype, MainType::Content);
982 assert_eq!(stype, SubType::Image);
983 assert_eq!(version, Version::V0);
984 assert_eq!(length, 1);
985 }
986
987 #[test]
990 fn test_encode_length_standard_types() {
991 assert_eq!(encode_length(MainType::Meta, 32).unwrap(), 0);
993 assert_eq!(encode_length(MainType::Meta, 64).unwrap(), 1);
994 assert_eq!(encode_length(MainType::Meta, 96).unwrap(), 2);
995 assert_eq!(encode_length(MainType::Meta, 128).unwrap(), 3);
996 assert_eq!(encode_length(MainType::Meta, 256).unwrap(), 7);
997 assert_eq!(encode_length(MainType::Data, 64).unwrap(), 1);
998 assert_eq!(encode_length(MainType::Instance, 64).unwrap(), 1);
999 }
1000
1001 #[test]
1002 fn test_encode_length_iscc() {
1003 for i in 0..=7 {
1005 assert_eq!(encode_length(MainType::Iscc, i).unwrap(), i);
1006 }
1007 assert!(encode_length(MainType::Iscc, 8).is_err());
1008 }
1009
1010 #[test]
1011 fn test_encode_length_id() {
1012 assert_eq!(encode_length(MainType::Id, 64).unwrap(), 0);
1014 assert_eq!(encode_length(MainType::Id, 72).unwrap(), 1);
1015 assert_eq!(encode_length(MainType::Id, 80).unwrap(), 2);
1016 assert_eq!(encode_length(MainType::Id, 96).unwrap(), 4);
1017 }
1018
1019 #[test]
1020 fn test_encode_length_invalid() {
1021 assert!(encode_length(MainType::Meta, 48).is_err());
1023 assert!(encode_length(MainType::Meta, 0).is_err());
1025 assert!(encode_length(MainType::Id, 63).is_err());
1027 assert!(encode_length(MainType::Id, 97).is_err());
1028 }
1029
1030 #[test]
1031 fn test_decode_length_standard_types() {
1032 assert_eq!(decode_length(MainType::Meta, 0, SubType::None), 32);
1034 assert_eq!(decode_length(MainType::Meta, 1, SubType::None), 64);
1035 assert_eq!(decode_length(MainType::Meta, 7, SubType::None), 256);
1036 assert_eq!(decode_length(MainType::Data, 1, SubType::None), 64);
1037 }
1038
1039 #[test]
1040 fn test_decode_length_iscc() {
1041 assert_eq!(decode_length(MainType::Iscc, 0, SubType::Wide), 256);
1043 assert_eq!(decode_length(MainType::Iscc, 0, SubType::Sum), 128); assert_eq!(decode_length(MainType::Iscc, 1, SubType::None), 192); assert_eq!(decode_length(MainType::Iscc, 3, SubType::None), 256); assert_eq!(decode_length(MainType::Iscc, 7, SubType::None), 320); }
1049
1050 #[test]
1051 fn test_decode_length_id() {
1052 assert_eq!(decode_length(MainType::Id, 0, SubType::None), 64);
1054 assert_eq!(decode_length(MainType::Id, 1, SubType::None), 72);
1055 assert_eq!(decode_length(MainType::Id, 4, SubType::None), 96);
1056 }
1057
1058 #[test]
1059 fn test_encode_decode_length_roundtrip() {
1060 for &mtype in &[
1061 MainType::Meta,
1062 MainType::Data,
1063 MainType::Instance,
1064 MainType::Content,
1065 ] {
1066 for bit_length in (32..=256).step_by(32) {
1067 let encoded = encode_length(mtype, bit_length).unwrap();
1068 let decoded = decode_length(mtype, encoded, SubType::None);
1069 assert_eq!(
1070 decoded, bit_length,
1071 "roundtrip failed for {mtype:?} bit_length={bit_length}"
1072 );
1073 }
1074 }
1075 }
1076
1077 #[test]
1080 fn test_base32_roundtrip() {
1081 let test_data: &[&[u8]] = &[
1082 &[0x00],
1083 &[0xFF],
1084 &[0x00, 0x01, 0x02, 0x03],
1085 &[0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE],
1086 &[0; 10],
1087 &[0xFF; 10],
1088 ];
1089
1090 for data in test_data {
1091 let encoded = encode_base32(data);
1092 let decoded = decode_base32(&encoded).unwrap();
1093 assert_eq!(&decoded, data, "base32 roundtrip failed for {data:?}");
1094 }
1095 }
1096
1097 #[test]
1098 fn test_base32_no_padding() {
1099 let encoded = encode_base32(&[0x00, 0x01]);
1100 assert!(!encoded.contains('='), "base32 should not contain padding");
1101 }
1102
1103 #[test]
1104 fn test_base32_case_insensitive_decode() {
1105 let data = vec![0xDE, 0xAD, 0xBE, 0xEF];
1106 let encoded = encode_base32(&data);
1107 let lower = encoded.to_lowercase();
1108 let decoded = decode_base32(&lower).unwrap();
1109 assert_eq!(decoded, data);
1110 }
1111
1112 #[test]
1115 fn test_encode_base64_empty() {
1116 assert_eq!(encode_base64(&[]), "");
1117 }
1118
1119 #[test]
1120 fn test_encode_base64_known_value() {
1121 assert_eq!(encode_base64(&[0, 1, 2, 3]), "AAECAw");
1123 }
1124
1125 #[test]
1126 fn test_encode_base64_roundtrip() {
1127 let data: &[&[u8]] = &[
1128 &[0xFF],
1129 &[0xDE, 0xAD, 0xBE, 0xEF],
1130 &[0; 10],
1131 &[0xFF; 10],
1132 b"Hello World",
1133 ];
1134 for input in data {
1135 let encoded = encode_base64(input);
1136 let decoded = data_encoding::BASE64URL_NOPAD
1137 .decode(encoded.as_bytes())
1138 .unwrap();
1139 assert_eq!(&decoded, input, "base64 roundtrip failed for {input:?}");
1140 }
1141 }
1142
1143 #[test]
1144 fn test_encode_base64_no_padding() {
1145 for len in 1..=10 {
1147 let data = vec![0xABu8; len];
1148 let encoded = encode_base64(&data);
1149 assert!(
1150 !encoded.contains('='),
1151 "base64 output must not contain padding for len={len}"
1152 );
1153 }
1154 }
1155
1156 #[test]
1159 fn test_encode_component_meta_known_vector() {
1160 let known_code = "AAAWKLHFPV6OPKDG";
1163 let raw = decode_base32(known_code).unwrap();
1164 assert_eq!(raw.len(), 10); let (mtype, stype, version, length, tail) = decode_header(&raw).unwrap();
1168 assert_eq!(mtype, MainType::Meta);
1169 assert_eq!(stype, SubType::None);
1170 assert_eq!(version, Version::V0);
1171 assert_eq!(length, 1); assert_eq!(tail.len(), 8); let result =
1176 encode_component(MainType::Meta, SubType::None, Version::V0, 64, &tail).unwrap();
1177 assert_eq!(result, known_code);
1178 }
1179
1180 #[test]
1181 fn test_encode_component_rejects_iscc_maintype() {
1182 assert!(
1183 encode_component(MainType::Iscc, SubType::Sum, Version::V0, 128, &[0; 16],).is_err()
1184 );
1185 }
1186
1187 #[test]
1188 fn test_encode_component_data_type() {
1189 let digest = [0xAA; 32];
1191 let code =
1192 encode_component(MainType::Data, SubType::None, Version::V0, 64, &digest).unwrap();
1193
1194 let raw = decode_base32(&code).unwrap();
1196 let (mtype, stype, version, length, tail) = decode_header(&raw).unwrap();
1197 assert_eq!(mtype, MainType::Data);
1198 assert_eq!(stype, SubType::None);
1199 assert_eq!(version, Version::V0);
1200 assert_eq!(length, 1); assert_eq!(tail, &digest[..8]); }
1203
1204 #[test]
1205 fn test_encode_component_content_image() {
1206 let digest = [0x55; 16];
1207 let code =
1208 encode_component(MainType::Content, SubType::Image, Version::V0, 128, &digest).unwrap();
1209
1210 let raw = decode_base32(&code).unwrap();
1211 let (mtype, stype, _version, length, tail) = decode_header(&raw).unwrap();
1212 assert_eq!(mtype, MainType::Content);
1213 assert_eq!(stype, SubType::Image);
1214 assert_eq!(length, 3); assert_eq!(tail, &digest[..]); }
1217
1218 #[test]
1221 fn test_maintype_try_from() {
1222 for v in 0..=7u8 {
1223 assert!(MainType::try_from(v).is_ok());
1224 }
1225 assert!(MainType::try_from(8).is_err());
1226 }
1227
1228 #[test]
1229 fn test_subtype_try_from() {
1230 for v in 0..=7u8 {
1231 assert!(SubType::try_from(v).is_ok());
1232 }
1233 assert!(SubType::try_from(8).is_err());
1234 }
1235
1236 #[test]
1237 fn test_version_try_from() {
1238 assert_eq!(Version::try_from(0).unwrap(), Version::V0);
1239 assert_eq!(Version::try_from(1).unwrap(), Version::V1);
1240 assert!(Version::try_from(2).is_err());
1241 }
1242
1243 #[test]
1246 fn test_iscc_decode_idv1_realm0() {
1247 let expected_body = vec![0x63, 0x94, 0x82, 0x4b, 0x1c, 0xf6, 0x20, 0x01];
1250 let with_prefix = crate::iscc_decode("ISCC:MAIGHFECJMOPMIAB").unwrap();
1251 assert_eq!(with_prefix, (6, 0, 1, 0, expected_body.clone()));
1252 let no_prefix = crate::iscc_decode("MAIGHFECJMOPMIAB").unwrap();
1254 assert_eq!(no_prefix, (6, 0, 1, 0, expected_body));
1255 }
1256
1257 #[test]
1258 fn test_decompose_idv1_accepts_version1() {
1259 let result = iscc_decompose("ISCC:MAIGHFECJMOPMIAB").unwrap();
1261 assert_eq!(result, vec!["MAIGHFECJMOPMIAB"]);
1262 }
1263
1264 #[test]
1265 fn test_decode_header_idv1_version1() {
1266 let raw = decode_base32("MAIGHFECJMOPMIAB").unwrap();
1268 let (mtype, stype, version, length, tail) = decode_header(&raw).unwrap();
1269 assert_eq!(mtype, MainType::Id);
1270 assert_eq!(stype, SubType::None); assert_eq!(version, Version::V1);
1272 assert_eq!(length, 0);
1273 assert_eq!(tail.len(), 8);
1274 }
1275
1276 #[test]
1277 fn test_encode_decode_header_idv1_roundtrip() {
1278 let header = encode_header(MainType::Id, SubType::Image, Version::V1, 0).unwrap();
1280 let (mtype, stype, version, length, _tail) = decode_header(&header).unwrap();
1281 assert_eq!(mtype, MainType::Id);
1282 assert_eq!(stype, SubType::Image);
1283 assert_eq!(version, Version::V1);
1284 assert_eq!(length, 0);
1285 }
1286
1287 #[test]
1288 fn test_encode_header_rejects_version1_for_non_id() {
1289 let result = encode_header(MainType::Meta, SubType::None, Version::V1, 1);
1291 assert!(result.is_err());
1292 assert!(result.unwrap_err().to_string().contains("invalid Version"));
1293 }
1294
1295 #[test]
1296 fn test_decode_header_rejects_version1_for_non_id() {
1297 let raw = [0x00u8, 0x11u8];
1301 let result = decode_header(&raw);
1302 assert!(result.is_err());
1303 assert!(result.unwrap_err().to_string().contains("invalid Version"));
1304 }
1305
1306 #[test]
1307 fn test_decode_header_rejects_truncated_varnibble_fields() {
1308 assert!(crate::iscc_decode("MDFZAAAAAAAAAAAAAA").is_err());
1312 assert!(iscc_decompose("MDFZAAAAAAAAAAAAAA").is_err());
1313 }
1314
1315 #[test]
1316 fn test_iscc_decode_rejects_version1_for_non_id() {
1317 let iscc = encode_base32(&[0x00u8, 0x11u8]);
1319 let result = crate::iscc_decode(&iscc);
1320 assert!(result.is_err());
1321 assert!(result.unwrap_err().to_string().contains("invalid Version"));
1322 }
1323
1324 #[test]
1325 fn test_subtype_text_alias() {
1326 assert_eq!(SubType::TEXT, SubType::None);
1327 assert_eq!(SubType::TEXT as u8, 0);
1328 }
1329
1330 #[test]
1333 fn test_bits_to_u32() {
1334 assert_eq!(bits_to_u32(&[false, false, false, false]), 0);
1335 assert_eq!(bits_to_u32(&[false, true, true, true]), 7);
1336 assert_eq!(bits_to_u32(&[true, false, false, false]), 8);
1337 assert_eq!(bits_to_u32(&[true, true, true, true]), 15);
1338 }
1339
1340 #[test]
1341 fn test_bytes_bits_roundtrip() {
1342 let data = vec![0x00, 0x01, 0xFF, 0xAB];
1343 let bits = bytes_to_bits(&data);
1344 assert_eq!(bits.len(), 32);
1345 let bytes = bits_to_bytes(&bits);
1346 assert_eq!(bytes, data);
1347 }
1348
1349 #[test]
1352 fn test_encode_units_empty() {
1353 assert_eq!(encode_units(&[]).unwrap(), 0);
1354 }
1355
1356 #[test]
1357 fn test_encode_units_content_only() {
1358 assert_eq!(encode_units(&[MainType::Content]).unwrap(), 1);
1359 }
1360
1361 #[test]
1362 fn test_encode_units_semantic_only() {
1363 assert_eq!(encode_units(&[MainType::Semantic]).unwrap(), 2);
1364 }
1365
1366 #[test]
1367 fn test_encode_units_semantic_content() {
1368 assert_eq!(
1369 encode_units(&[MainType::Semantic, MainType::Content]).unwrap(),
1370 3
1371 );
1372 }
1373
1374 #[test]
1375 fn test_encode_units_meta_only() {
1376 assert_eq!(encode_units(&[MainType::Meta]).unwrap(), 4);
1377 }
1378
1379 #[test]
1380 fn test_encode_units_meta_content() {
1381 assert_eq!(
1382 encode_units(&[MainType::Meta, MainType::Content]).unwrap(),
1383 5
1384 );
1385 }
1386
1387 #[test]
1388 fn test_encode_units_meta_semantic() {
1389 assert_eq!(
1390 encode_units(&[MainType::Meta, MainType::Semantic]).unwrap(),
1391 6
1392 );
1393 }
1394
1395 #[test]
1396 fn test_encode_units_all_optional() {
1397 assert_eq!(
1398 encode_units(&[MainType::Meta, MainType::Semantic, MainType::Content]).unwrap(),
1399 7
1400 );
1401 }
1402
1403 #[test]
1404 fn test_encode_units_rejects_data() {
1405 assert!(encode_units(&[MainType::Data]).is_err());
1406 }
1407
1408 #[test]
1409 fn test_encode_units_rejects_instance() {
1410 assert!(encode_units(&[MainType::Instance]).is_err());
1411 }
1412
1413 #[test]
1414 fn test_encode_units_rejects_iscc() {
1415 assert!(encode_units(&[MainType::Iscc]).is_err());
1416 }
1417
1418 #[test]
1421 fn test_decode_units_empty() {
1422 assert_eq!(decode_units(0).unwrap(), vec![]);
1423 }
1424
1425 #[test]
1426 fn test_decode_units_content() {
1427 assert_eq!(decode_units(1).unwrap(), vec![MainType::Content]);
1428 }
1429
1430 #[test]
1431 fn test_decode_units_semantic() {
1432 assert_eq!(decode_units(2).unwrap(), vec![MainType::Semantic]);
1433 }
1434
1435 #[test]
1436 fn test_decode_units_semantic_content() {
1437 assert_eq!(
1438 decode_units(3).unwrap(),
1439 vec![MainType::Semantic, MainType::Content]
1440 );
1441 }
1442
1443 #[test]
1444 fn test_decode_units_meta() {
1445 assert_eq!(decode_units(4).unwrap(), vec![MainType::Meta]);
1446 }
1447
1448 #[test]
1449 fn test_decode_units_meta_content() {
1450 assert_eq!(
1451 decode_units(5).unwrap(),
1452 vec![MainType::Meta, MainType::Content]
1453 );
1454 }
1455
1456 #[test]
1457 fn test_decode_units_meta_semantic() {
1458 assert_eq!(
1459 decode_units(6).unwrap(),
1460 vec![MainType::Meta, MainType::Semantic]
1461 );
1462 }
1463
1464 #[test]
1465 fn test_decode_units_all() {
1466 assert_eq!(
1467 decode_units(7).unwrap(),
1468 vec![MainType::Meta, MainType::Semantic, MainType::Content]
1469 );
1470 }
1471
1472 #[test]
1473 fn test_decode_units_invalid() {
1474 assert!(decode_units(8).is_err());
1475 assert!(decode_units(255).is_err());
1476 }
1477
1478 #[test]
1479 fn test_decode_units_roundtrip_with_encode_units() {
1480 for unit_id in 0..=7u32 {
1481 let types = decode_units(unit_id).unwrap();
1482 let encoded = encode_units(&types).unwrap();
1483 assert_eq!(encoded, unit_id, "roundtrip failed for unit_id={unit_id}");
1484 }
1485 }
1486
1487 #[test]
1490 fn test_decompose_single_meta_unit() {
1491 let result = iscc_decompose("AAAYPXW445FTYNJ3").unwrap();
1493 assert_eq!(result, vec!["AAAYPXW445FTYNJ3"]);
1494 }
1495
1496 #[test]
1497 fn test_decompose_single_unit_with_prefix() {
1498 let result = iscc_decompose("ISCC:AAAYPXW445FTYNJ3").unwrap();
1500 assert_eq!(result, vec!["AAAYPXW445FTYNJ3"]);
1501 }
1502
1503 #[test]
1504 fn test_decompose_single_unit_maintype() {
1505 let result = iscc_decompose("AAAYPXW445FTYNJ3").unwrap();
1507 assert_eq!(result.len(), 1);
1508 let raw = decode_base32(&result[0]).unwrap();
1509 let (mt, _, _, _, _) = decode_header(&raw).unwrap();
1510 assert_eq!(mt, MainType::Meta);
1511 }
1512
1513 #[test]
1514 fn test_decompose_standard_iscc_code() {
1515 let codes = [
1517 "AAAYPXW445FTYNJ3",
1518 "EAARMJLTQCUWAND2",
1519 "GABVVC5DMJJGYKZ4ZBYVNYABFFYXG",
1520 "IADWIK7A7JTUAQ2D6QARX7OBEIK3OOUAM42LOBLCZ4ZOGDLRHMDL6TQ",
1521 ];
1522 let composite = crate::gen_iscc_code_v0(
1523 &codes.iter().map(|s| *s as &str).collect::<Vec<&str>>(),
1524 false,
1525 )
1526 .unwrap();
1527
1528 let decomposed = iscc_decompose(&composite.iscc).unwrap();
1529
1530 assert_eq!(decomposed.len(), 4);
1532
1533 let main_types: Vec<MainType> = decomposed
1535 .iter()
1536 .map(|code| {
1537 let raw = decode_base32(code).unwrap();
1538 let (mt, _, _, _, _) = decode_header(&raw).unwrap();
1539 mt
1540 })
1541 .collect();
1542 assert_eq!(
1543 main_types,
1544 vec![
1545 MainType::Meta,
1546 MainType::Content,
1547 MainType::Data,
1548 MainType::Instance
1549 ]
1550 );
1551
1552 let raw_data = decode_base32(&decomposed[2]).unwrap();
1554 let (mt_d, _, _, _, _) = decode_header(&raw_data).unwrap();
1555 assert_eq!(mt_d, MainType::Data);
1556
1557 let raw_inst = decode_base32(&decomposed[3]).unwrap();
1558 let (mt_i, _, _, _, _) = decode_header(&raw_inst).unwrap();
1559 assert_eq!(mt_i, MainType::Instance);
1560 }
1561
1562 #[test]
1563 fn test_decompose_no_meta() {
1564 let codes = [
1566 "EAARMJLTQCUWAND2",
1567 "GABVVC5DMJJGYKZ4ZBYVNYABFFYXG",
1568 "IADWIK7A7JTUAQ2D6QARX7OBEIK3OOUAM42LOBLCZ4ZOGDLRHMDL6TQ",
1569 ];
1570 let composite = crate::gen_iscc_code_v0(
1571 &codes.iter().map(|s| *s as &str).collect::<Vec<&str>>(),
1572 false,
1573 )
1574 .unwrap();
1575
1576 let decomposed = iscc_decompose(&composite.iscc).unwrap();
1577
1578 assert_eq!(decomposed.len(), 3);
1580
1581 let main_types: Vec<MainType> = decomposed
1582 .iter()
1583 .map(|code| {
1584 let raw = decode_base32(code).unwrap();
1585 let (mt, _, _, _, _) = decode_header(&raw).unwrap();
1586 mt
1587 })
1588 .collect();
1589 assert_eq!(
1590 main_types,
1591 vec![MainType::Content, MainType::Data, MainType::Instance]
1592 );
1593 }
1594
1595 #[test]
1596 fn test_decompose_sum_only() {
1597 let codes = [
1599 "GABVVC5DMJJGYKZ4ZBYVNYABFFYXG",
1600 "IADWIK7A7JTUAQ2D6QARX7OBEIK3OOUAM42LOBLCZ4ZOGDLRHMDL6TQ",
1601 ];
1602 let composite = crate::gen_iscc_code_v0(
1603 &codes.iter().map(|s| *s as &str).collect::<Vec<&str>>(),
1604 false,
1605 )
1606 .unwrap();
1607
1608 let decomposed = iscc_decompose(&composite.iscc).unwrap();
1609
1610 assert_eq!(decomposed.len(), 2);
1612
1613 let main_types: Vec<MainType> = decomposed
1614 .iter()
1615 .map(|code| {
1616 let raw = decode_base32(code).unwrap();
1617 let (mt, _, _, _, _) = decode_header(&raw).unwrap();
1618 mt
1619 })
1620 .collect();
1621 assert_eq!(main_types, vec![MainType::Data, MainType::Instance]);
1622 }
1623
1624 #[test]
1625 fn test_decompose_conformance_roundtrip() {
1626 let json_str = include_str!("../tests/data.json");
1628 let data: serde_json::Value = serde_json::from_str(json_str).unwrap();
1629 let section = &data["gen_iscc_code_v0"];
1630 let cases = section.as_object().unwrap();
1631
1632 for (tc_name, tc) in cases {
1633 let expected_iscc = tc["outputs"]["iscc"].as_str().unwrap();
1634 let inputs = tc["inputs"].as_array().unwrap();
1635 let codes_json = inputs[0].as_array().unwrap();
1636 let input_codes: Vec<&str> = codes_json.iter().map(|v| v.as_str().unwrap()).collect();
1637
1638 let decomposed = iscc_decompose(expected_iscc).unwrap();
1639
1640 for code in &decomposed {
1642 let raw = decode_base32(code).unwrap();
1643 let (mt, _, _, _, _) = decode_header(&raw).unwrap();
1644 assert_ne!(
1645 mt,
1646 MainType::Iscc,
1647 "decomposed unit should not be ISCC in {tc_name}"
1648 );
1649 }
1650
1651 let last_two: Vec<MainType> = decomposed[decomposed.len() - 2..]
1653 .iter()
1654 .map(|code| {
1655 let raw = decode_base32(code).unwrap();
1656 let (mt, _, _, _, _) = decode_header(&raw).unwrap();
1657 mt
1658 })
1659 .collect();
1660 assert_eq!(
1661 last_two,
1662 vec![MainType::Data, MainType::Instance],
1663 "last two units must be Data+Instance in {tc_name}"
1664 );
1665
1666 assert_eq!(
1668 decomposed.len(),
1669 input_codes.len(),
1670 "decomposed unit count mismatch in {tc_name}"
1671 );
1672 }
1673 }
1674
1675 fn make_truncated_iscc(
1682 mtype: MainType,
1683 stype: SubType,
1684 length_field: u32,
1685 body_len: usize,
1686 ) -> String {
1687 let header = encode_header(mtype, stype, Version::V0, length_field).unwrap();
1688 let mut raw = header;
1689 raw.extend(vec![0xABu8; body_len]);
1690 encode_base32(&raw)
1691 }
1692
1693 #[test]
1694 fn test_decompose_truncated_standard_unit() {
1695 let length_field = encode_length(MainType::Meta, 64).unwrap();
1698 let iscc = make_truncated_iscc(MainType::Meta, SubType::None, length_field, 4);
1699 let result = iscc_decompose(&iscc);
1700 assert!(
1701 result.is_err(),
1702 "expected error for truncated standard unit"
1703 );
1704 let err = result.unwrap_err().to_string();
1705 assert!(
1706 err.contains("truncated ISCC body"),
1707 "error should mention truncation: {err}"
1708 );
1709 }
1710
1711 #[test]
1712 fn test_decompose_truncated_wide_mode() {
1713 let iscc = make_truncated_iscc(MainType::Iscc, SubType::Wide, 0, 16);
1716 let result = iscc_decompose(&iscc);
1717 assert!(result.is_err(), "expected error for truncated wide mode");
1718 let err = result.unwrap_err().to_string();
1719 assert!(
1720 err.contains("truncated ISCC body"),
1721 "error should mention truncation: {err}"
1722 );
1723 }
1724
1725 #[test]
1726 fn test_decompose_truncated_dynamic_units() {
1727 let unit_id = 5; let iscc = make_truncated_iscc(MainType::Iscc, SubType::None, unit_id, 8);
1732 let result = iscc_decompose(&iscc);
1733 assert!(
1734 result.is_err(),
1735 "expected error for truncated dynamic units"
1736 );
1737 let err = result.unwrap_err().to_string();
1738 assert!(
1739 err.contains("truncated ISCC body"),
1740 "error should mention truncation: {err}"
1741 );
1742 }
1743
1744 #[test]
1745 fn test_decompose_truncated_static_units() {
1746 let unit_id = 1; let iscc = make_truncated_iscc(MainType::Iscc, SubType::None, unit_id, 16);
1751 let result = iscc_decompose(&iscc);
1752 assert!(result.is_err(), "expected error for truncated static units");
1753 let err = result.unwrap_err().to_string();
1754 assert!(
1755 err.contains("truncated ISCC body"),
1756 "error should mention truncation: {err}"
1757 );
1758 }
1759
1760 #[test]
1761 fn test_decompose_empty_body() {
1762 let length_field = encode_length(MainType::Meta, 64).unwrap();
1764 let iscc = make_truncated_iscc(MainType::Meta, SubType::None, length_field, 0);
1765 let result = iscc_decompose(&iscc);
1766 assert!(result.is_err(), "expected error for empty body");
1767 let err = result.unwrap_err().to_string();
1768 assert!(
1769 err.contains("truncated ISCC body"),
1770 "error should mention truncation: {err}"
1771 );
1772 }
1773
1774 #[test]
1775 fn test_decompose_valid_still_works() {
1776 let meta_body = [0x11u8; 8];
1779 let content_body = [0x22u8; 8];
1780 let data_body = [0x33u8; 8];
1781 let instance_body = [0x44u8; 8];
1782
1783 let meta_code =
1784 encode_component(MainType::Meta, SubType::None, Version::V0, 64, &meta_body).unwrap();
1785 let content_code = encode_component(
1786 MainType::Content,
1787 SubType::None,
1788 Version::V0,
1789 64,
1790 &content_body,
1791 )
1792 .unwrap();
1793 let data_code =
1794 encode_component(MainType::Data, SubType::None, Version::V0, 64, &data_body).unwrap();
1795 let instance_code = encode_component(
1796 MainType::Instance,
1797 SubType::None,
1798 Version::V0,
1799 64,
1800 &instance_body,
1801 )
1802 .unwrap();
1803
1804 let sequence = format!("{meta_code}{content_code}{data_code}{instance_code}");
1806 let raw = decode_base32(&sequence).unwrap();
1807 let full_iscc = encode_base32(&raw);
1808
1809 let result = iscc_decompose(&full_iscc);
1810 assert!(
1811 result.is_ok(),
1812 "valid ISCC sequence should decompose: {result:?}"
1813 );
1814 let units = result.unwrap();
1815 assert_eq!(units.len(), 4, "should decompose into 4 units");
1816 }
1817}