Skip to main content

iscc_lib/
conformance.rs

1//! Conformance selftest for ISO 24138:2024 (ISCC).
2//!
3//! Runs all 9 gen functions against vendored conformance vectors from `data.json`
4//! and reports pass/fail. An application that claims ISCC conformance MUST pass
5//! all tests in this suite.
6
7use crate::{
8    gen_audio_code_v0, gen_data_code_v0, gen_image_code_v0, gen_instance_code_v0, gen_iscc_code_v0,
9    gen_meta_code_v0, gen_mixed_code_v0, gen_text_code_v0, gen_video_code_v0,
10};
11
12/// Embedded conformance test vectors (compile-time).
13const TEST_DATA: &str = include_str!("../tests/data.json");
14
15/// Run all conformance tests against vendored test vectors.
16///
17/// Iterates through all 9 `gen_*_v0` function sections in the conformance data,
18/// calls each function with the specified inputs, and compares the `.iscc` field
19/// of the result against expected output. Returns `true` if all tests pass,
20/// `false` if any mismatch or error occurs. Does not panic — logs failures via
21/// `eprintln!` and continues through all test cases.
22pub fn conformance_selftest() -> bool {
23    let data: serde_json::Value = match serde_json::from_str(TEST_DATA) {
24        Ok(v) => v,
25        Err(e) => {
26            eprintln!("FAILED: could not parse conformance data: {e}");
27            return false;
28        }
29    };
30
31    let mut passed = true;
32
33    passed &= run_meta_tests(&data);
34    passed &= run_text_tests(&data);
35    passed &= run_image_tests(&data);
36    passed &= run_audio_tests(&data);
37    passed &= run_video_tests(&data);
38    passed &= run_mixed_tests(&data);
39    passed &= run_data_tests(&data);
40    passed &= run_instance_tests(&data);
41    passed &= run_iscc_tests(&data);
42
43    passed
44}
45
46/// Run conformance tests for `gen_meta_code_v0`.
47fn run_meta_tests(data: &serde_json::Value) -> bool {
48    let mut passed = true;
49    let section = &data["gen_meta_code_v0"];
50    let cases = match section.as_object() {
51        Some(c) => c,
52        None => {
53            eprintln!("FAILED: gen_meta_code_v0 section missing from conformance data");
54            return false;
55        }
56    };
57
58    for (tc_name, tc) in cases {
59        let func_name = "gen_meta_code_v0";
60        let result = (|| {
61            let inputs = tc["inputs"].as_array()?;
62            let name = inputs[0].as_str()?;
63            let desc_str = inputs[1].as_str()?;
64            let meta_val = &inputs[2];
65            let bits = inputs[3].as_u64()? as u32;
66
67            let meta_arg: Option<String> = match meta_val {
68                serde_json::Value::Null => None,
69                serde_json::Value::String(s) => Some(s.clone()),
70                serde_json::Value::Object(_) => serde_json::to_string(meta_val).ok(),
71                _ => None,
72            };
73
74            let desc = if desc_str.is_empty() {
75                None
76            } else {
77                Some(desc_str)
78            };
79
80            let expected_iscc = tc["outputs"]["iscc"].as_str()?;
81            match gen_meta_code_v0(name, desc, meta_arg.as_deref(), bits) {
82                Ok(result) if result.iscc == expected_iscc => Some(true),
83                Ok(result) => {
84                    eprintln!(
85                        "FAILED: {func_name}.{tc_name} — expected {expected_iscc}, got {}",
86                        result.iscc
87                    );
88                    Some(false)
89                }
90                Err(e) => {
91                    eprintln!("FAILED: {func_name}.{tc_name} — error: {e}");
92                    Some(false)
93                }
94            }
95        })();
96
97        match result {
98            Some(true) => {}
99            Some(false) => passed = false,
100            None => {
101                eprintln!("FAILED: {func_name}.{tc_name} — could not parse test inputs");
102                passed = false;
103            }
104        }
105    }
106    passed
107}
108
109/// Run conformance tests for `gen_text_code_v0`.
110fn run_text_tests(data: &serde_json::Value) -> bool {
111    let mut passed = true;
112    let section = &data["gen_text_code_v0"];
113    let cases = match section.as_object() {
114        Some(c) => c,
115        None => {
116            eprintln!("FAILED: gen_text_code_v0 section missing from conformance data");
117            return false;
118        }
119    };
120
121    for (tc_name, tc) in cases {
122        let func_name = "gen_text_code_v0";
123        let result = (|| {
124            let inputs = tc["inputs"].as_array()?;
125            let text = inputs[0].as_str()?;
126            let bits = inputs[1].as_u64()? as u32;
127            let expected_iscc = tc["outputs"]["iscc"].as_str()?;
128
129            match gen_text_code_v0(text, bits) {
130                Ok(result) if result.iscc == expected_iscc => Some(true),
131                Ok(result) => {
132                    eprintln!(
133                        "FAILED: {func_name}.{tc_name} — expected {expected_iscc}, got {}",
134                        result.iscc
135                    );
136                    Some(false)
137                }
138                Err(e) => {
139                    eprintln!("FAILED: {func_name}.{tc_name} — error: {e}");
140                    Some(false)
141                }
142            }
143        })();
144
145        match result {
146            Some(true) => {}
147            Some(false) => passed = false,
148            None => {
149                eprintln!("FAILED: {func_name}.{tc_name} — could not parse test inputs");
150                passed = false;
151            }
152        }
153    }
154    passed
155}
156
157/// Run conformance tests for `gen_image_code_v0`.
158fn run_image_tests(data: &serde_json::Value) -> bool {
159    let mut passed = true;
160    let section = &data["gen_image_code_v0"];
161    let cases = match section.as_object() {
162        Some(c) => c,
163        None => {
164            eprintln!("FAILED: gen_image_code_v0 section missing from conformance data");
165            return false;
166        }
167    };
168
169    for (tc_name, tc) in cases {
170        let func_name = "gen_image_code_v0";
171        let result = (|| {
172            let inputs = tc["inputs"].as_array()?;
173            let pixels_json = inputs[0].as_array()?;
174            let bits = inputs[1].as_u64()? as u32;
175            let expected_iscc = tc["outputs"]["iscc"].as_str()?;
176
177            let pixels: Vec<u8> = pixels_json
178                .iter()
179                .map(|v| v.as_u64().map(|n| n as u8))
180                .collect::<Option<Vec<u8>>>()?;
181
182            match gen_image_code_v0(&pixels, bits) {
183                Ok(result) if result.iscc == expected_iscc => Some(true),
184                Ok(result) => {
185                    eprintln!(
186                        "FAILED: {func_name}.{tc_name} — expected {expected_iscc}, got {}",
187                        result.iscc
188                    );
189                    Some(false)
190                }
191                Err(e) => {
192                    eprintln!("FAILED: {func_name}.{tc_name} — error: {e}");
193                    Some(false)
194                }
195            }
196        })();
197
198        match result {
199            Some(true) => {}
200            Some(false) => passed = false,
201            None => {
202                eprintln!("FAILED: {func_name}.{tc_name} — could not parse test inputs");
203                passed = false;
204            }
205        }
206    }
207    passed
208}
209
210/// Run conformance tests for `gen_audio_code_v0`.
211fn run_audio_tests(data: &serde_json::Value) -> bool {
212    let mut passed = true;
213    let section = &data["gen_audio_code_v0"];
214    let cases = match section.as_object() {
215        Some(c) => c,
216        None => {
217            eprintln!("FAILED: gen_audio_code_v0 section missing from conformance data");
218            return false;
219        }
220    };
221
222    for (tc_name, tc) in cases {
223        let func_name = "gen_audio_code_v0";
224        let result = (|| {
225            let inputs = tc["inputs"].as_array()?;
226            let cv_json = inputs[0].as_array()?;
227            let bits = inputs[1].as_u64()? as u32;
228            let expected_iscc = tc["outputs"]["iscc"].as_str()?;
229
230            let cv: Vec<i32> = cv_json
231                .iter()
232                .map(|v| v.as_i64().map(|n| n as i32))
233                .collect::<Option<Vec<i32>>>()?;
234
235            match gen_audio_code_v0(&cv, bits) {
236                Ok(result) if result.iscc == expected_iscc => Some(true),
237                Ok(result) => {
238                    eprintln!(
239                        "FAILED: {func_name}.{tc_name} — expected {expected_iscc}, got {}",
240                        result.iscc
241                    );
242                    Some(false)
243                }
244                Err(e) => {
245                    eprintln!("FAILED: {func_name}.{tc_name} — error: {e}");
246                    Some(false)
247                }
248            }
249        })();
250
251        match result {
252            Some(true) => {}
253            Some(false) => passed = false,
254            None => {
255                eprintln!("FAILED: {func_name}.{tc_name} — could not parse test inputs");
256                passed = false;
257            }
258        }
259    }
260    passed
261}
262
263/// Run conformance tests for `gen_video_code_v0`.
264fn run_video_tests(data: &serde_json::Value) -> bool {
265    let mut passed = true;
266    let section = &data["gen_video_code_v0"];
267    let cases = match section.as_object() {
268        Some(c) => c,
269        None => {
270            eprintln!("FAILED: gen_video_code_v0 section missing from conformance data");
271            return false;
272        }
273    };
274
275    for (tc_name, tc) in cases {
276        let func_name = "gen_video_code_v0";
277        let result = (|| {
278            let inputs = tc["inputs"].as_array()?;
279            let frames_json = inputs[0].as_array()?;
280            let bits = inputs[1].as_u64()? as u32;
281            let expected_iscc = tc["outputs"]["iscc"].as_str()?;
282
283            let frame_sigs: Option<Vec<Vec<i32>>> = frames_json
284                .iter()
285                .map(|frame| {
286                    frame
287                        .as_array()?
288                        .iter()
289                        .map(|v| v.as_i64().map(|n| n as i32))
290                        .collect::<Option<Vec<i32>>>()
291                })
292                .collect();
293            let frame_sigs = frame_sigs?;
294
295            match gen_video_code_v0(&frame_sigs, bits) {
296                Ok(result) if result.iscc == expected_iscc => Some(true),
297                Ok(result) => {
298                    eprintln!(
299                        "FAILED: {func_name}.{tc_name} — expected {expected_iscc}, got {}",
300                        result.iscc
301                    );
302                    Some(false)
303                }
304                Err(e) => {
305                    eprintln!("FAILED: {func_name}.{tc_name} — error: {e}");
306                    Some(false)
307                }
308            }
309        })();
310
311        match result {
312            Some(true) => {}
313            Some(false) => passed = false,
314            None => {
315                eprintln!("FAILED: {func_name}.{tc_name} — could not parse test inputs");
316                passed = false;
317            }
318        }
319    }
320    passed
321}
322
323/// Run conformance tests for `gen_mixed_code_v0`.
324fn run_mixed_tests(data: &serde_json::Value) -> bool {
325    let mut passed = true;
326    let section = &data["gen_mixed_code_v0"];
327    let cases = match section.as_object() {
328        Some(c) => c,
329        None => {
330            eprintln!("FAILED: gen_mixed_code_v0 section missing from conformance data");
331            return false;
332        }
333    };
334
335    for (tc_name, tc) in cases {
336        let func_name = "gen_mixed_code_v0";
337        let result = (|| {
338            let inputs = tc["inputs"].as_array()?;
339            let codes_json = inputs[0].as_array()?;
340            let bits = inputs[1].as_u64()? as u32;
341            let expected_iscc = tc["outputs"]["iscc"].as_str()?;
342
343            let codes_owned: Vec<String> = codes_json
344                .iter()
345                .filter_map(|v| v.as_str().map(String::from))
346                .collect();
347            let codes: Vec<&str> = codes_owned.iter().map(|s| s.as_str()).collect();
348
349            match gen_mixed_code_v0(&codes, bits) {
350                Ok(result) if result.iscc == expected_iscc => Some(true),
351                Ok(result) => {
352                    eprintln!(
353                        "FAILED: {func_name}.{tc_name} — expected {expected_iscc}, got {}",
354                        result.iscc
355                    );
356                    Some(false)
357                }
358                Err(e) => {
359                    eprintln!("FAILED: {func_name}.{tc_name} — error: {e}");
360                    Some(false)
361                }
362            }
363        })();
364
365        match result {
366            Some(true) => {}
367            Some(false) => passed = false,
368            None => {
369                eprintln!("FAILED: {func_name}.{tc_name} — could not parse test inputs");
370                passed = false;
371            }
372        }
373    }
374    passed
375}
376
377/// Decode a `"stream:<hex>"` value into bytes.
378fn decode_stream(s: &str) -> Option<Vec<u8>> {
379    let hex_data = s.strip_prefix("stream:")?;
380    hex::decode(hex_data).ok()
381}
382
383/// Run conformance tests for `gen_data_code_v0`.
384fn run_data_tests(data: &serde_json::Value) -> bool {
385    let mut passed = true;
386    let section = &data["gen_data_code_v0"];
387    let cases = match section.as_object() {
388        Some(c) => c,
389        None => {
390            eprintln!("FAILED: gen_data_code_v0 section missing from conformance data");
391            return false;
392        }
393    };
394
395    for (tc_name, tc) in cases {
396        let func_name = "gen_data_code_v0";
397        let result = (|| {
398            let inputs = tc["inputs"].as_array()?;
399            let stream_str = inputs[0].as_str()?;
400            let bits = inputs[1].as_u64()? as u32;
401            let expected_iscc = tc["outputs"]["iscc"].as_str()?;
402            let input_bytes = decode_stream(stream_str)?;
403
404            match gen_data_code_v0(&input_bytes, bits) {
405                Ok(result) if result.iscc == expected_iscc => Some(true),
406                Ok(result) => {
407                    eprintln!(
408                        "FAILED: {func_name}.{tc_name} — expected {expected_iscc}, got {}",
409                        result.iscc
410                    );
411                    Some(false)
412                }
413                Err(e) => {
414                    eprintln!("FAILED: {func_name}.{tc_name} — error: {e}");
415                    Some(false)
416                }
417            }
418        })();
419
420        match result {
421            Some(true) => {}
422            Some(false) => passed = false,
423            None => {
424                eprintln!("FAILED: {func_name}.{tc_name} — could not parse test inputs");
425                passed = false;
426            }
427        }
428    }
429    passed
430}
431
432/// Run conformance tests for `gen_instance_code_v0`.
433fn run_instance_tests(data: &serde_json::Value) -> bool {
434    let mut passed = true;
435    let section = &data["gen_instance_code_v0"];
436    let cases = match section.as_object() {
437        Some(c) => c,
438        None => {
439            eprintln!("FAILED: gen_instance_code_v0 section missing from conformance data");
440            return false;
441        }
442    };
443
444    for (tc_name, tc) in cases {
445        let func_name = "gen_instance_code_v0";
446        let result = (|| {
447            let inputs = tc["inputs"].as_array()?;
448            let stream_str = inputs[0].as_str()?;
449            let bits = inputs[1].as_u64()? as u32;
450            let expected_iscc = tc["outputs"]["iscc"].as_str()?;
451            let input_bytes = decode_stream(stream_str)?;
452
453            match gen_instance_code_v0(&input_bytes, bits) {
454                Ok(result) if result.iscc == expected_iscc => Some(true),
455                Ok(result) => {
456                    eprintln!(
457                        "FAILED: {func_name}.{tc_name} — expected {expected_iscc}, got {}",
458                        result.iscc
459                    );
460                    Some(false)
461                }
462                Err(e) => {
463                    eprintln!("FAILED: {func_name}.{tc_name} — error: {e}");
464                    Some(false)
465                }
466            }
467        })();
468
469        match result {
470            Some(true) => {}
471            Some(false) => passed = false,
472            None => {
473                eprintln!("FAILED: {func_name}.{tc_name} — could not parse test inputs");
474                passed = false;
475            }
476        }
477    }
478    passed
479}
480
481/// Run conformance tests for `gen_iscc_code_v0`.
482fn run_iscc_tests(data: &serde_json::Value) -> bool {
483    let mut passed = true;
484    let section = &data["gen_iscc_code_v0"];
485    let cases = match section.as_object() {
486        Some(c) => c,
487        None => {
488            eprintln!("FAILED: gen_iscc_code_v0 section missing from conformance data");
489            return false;
490        }
491    };
492
493    for (tc_name, tc) in cases {
494        let func_name = "gen_iscc_code_v0";
495        let result = (|| {
496            let inputs = tc["inputs"].as_array()?;
497            let codes_json = inputs[0].as_array()?;
498            let expected_iscc = tc["outputs"]["iscc"].as_str()?;
499
500            let codes_owned: Vec<String> = codes_json
501                .iter()
502                .filter_map(|v| v.as_str().map(String::from))
503                .collect();
504            let codes: Vec<&str> = codes_owned.iter().map(|s| s.as_str()).collect();
505
506            // Conformance vectors use default (non-wide) mode
507            match gen_iscc_code_v0(&codes, false) {
508                Ok(result) if result.iscc == expected_iscc => Some(true),
509                Ok(result) => {
510                    eprintln!(
511                        "FAILED: {func_name}.{tc_name} — expected {expected_iscc}, got {}",
512                        result.iscc
513                    );
514                    Some(false)
515                }
516                Err(e) => {
517                    eprintln!("FAILED: {func_name}.{tc_name} — error: {e}");
518                    Some(false)
519                }
520            }
521        })();
522
523        match result {
524            Some(true) => {}
525            Some(false) => passed = false,
526            None => {
527                eprintln!("FAILED: {func_name}.{tc_name} — could not parse test inputs");
528                passed = false;
529            }
530        }
531    }
532    passed
533}
534
535#[cfg(test)]
536mod tests {
537    use super::*;
538
539    #[test]
540    fn test_conformance_selftest_passes() {
541        assert!(conformance_selftest());
542    }
543}