Skip to main content

iscc_lib/
conformance.rs

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