Skip to main content

dump_canonical/
dump_canonical.rs

1//! Canonical per-file payload dump for cross-language verification.
2//! Usage: cargo run -q --example dump_canonical -- <file.brz|.brdb> [--mode=hashes]
3//! Prints JSON: { "<path>": { "blake3": "<hex>", "len": <decompressed len> } }
4//! sorted by path. Hashes are over DECOMPRESSED payloads, so the output is
5//! independent of container framing and zstd implementation.
6use std::collections::BTreeMap;
7use std::path::PathBuf;
8
9use brdb::fs::BrFs;
10use brdb::{Brdb, BrFsReader, Brz, IntoReader};
11
12fn collect(fs: &BrFs, prefix: &str, out: &mut Vec<(String, Option<i64>)>) {
13    match fs {
14        BrFs::Root(children) => {
15            for (name, child) in children {
16                collect(child, name, out);
17            }
18        }
19        BrFs::Folder(_, children) => {
20            for (name, child) in children {
21                let path = format!("{prefix}/{name}");
22                collect(child, &path, out);
23            }
24        }
25        BrFs::File(file) => {
26            out.push((prefix.to_string(), file.content_id));
27        }
28    }
29}
30
31fn dump(reader: &impl BrFsReader) -> Result<(), Box<dyn std::error::Error>> {
32    let mut files = Vec::new();
33    collect(&reader.get_fs()?, "", &mut files);
34    files.sort_by(|a, b| a.0.cmp(&b.0));
35
36    let mut out = BTreeMap::new();
37    for (path, content_id) in files {
38        let content = match content_id {
39            Some(id) => reader.find_blob(id)?.read()?,
40            None => Vec::new(),
41        };
42        out.insert(
43            path,
44            serde_json::json!({
45                "blake3": blake3::hash(&content).to_hex().to_string(),
46                "len": content.len(),
47            }),
48        );
49    }
50    println!("{}", serde_json::to_string_pretty(&out)?);
51    Ok(())
52}
53
54fn main() -> Result<(), Box<dyn std::error::Error>> {
55    let mut args = std::env::args().skip(1);
56    let p = PathBuf::from(args.next().expect("usage: dump_canonical <file> [--mode=hashes]"));
57    if let Some(mode) = args.next() {
58        if mode != "--mode=hashes" {
59            // --mode=canonical is deliberately not implemented: the hashes gate
60            // (byte-identical payloads) plus the brs-js read-side tests subsume it.
61            return Err(format!("unsupported mode {mode}").into());
62        }
63    }
64    if p.extension().and_then(|e| e.to_str()) == Some("brdb") {
65        dump(&*Brdb::open(&p)?.into_reader())
66    } else {
67        dump(&*Brz::open(&p)?.into_reader())
68    }
69}