use ciborium::value::Value;
pub const SELF_DESCRIBE_TAG: u64 = 55799;
pub const MAGIC: &str = "GTS1";
pub const VERSION: u8 = 1;
pub fn encode(v: &Value) -> Vec<u8> {
let mut out = Vec::new();
ciborium::ser::into_writer(v, &mut out).expect("CBOR encoding to a Vec cannot fail");
out
}
pub fn deterministic(v: &Value) -> Value {
match v {
Value::Tag(tag, inner) => Value::Tag(*tag, Box::new(deterministic(inner))),
Value::Array(items) => Value::Array(items.iter().map(deterministic).collect()),
Value::Map(entries) => {
let mut keyed: Vec<(Vec<u8>, Value, Value)> = entries
.iter()
.map(|(k, val)| (encode(k), k.clone(), deterministic(val)))
.collect();
keyed.sort_by(|a, b| a.0.cmp(&b.0));
Value::Map(keyed.into_iter().map(|(_, k, val)| (k, val)).collect())
}
other => other.clone(),
}
}
pub fn canonical(v: &Value) -> Vec<u8> {
encode(&deterministic(v))
}
pub fn blake3_256(data: &[u8]) -> Vec<u8> {
blake3::hash(data).as_bytes().to_vec()
}
pub fn hex(data: &[u8]) -> String {
data.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn digest_str(data: &[u8]) -> String {
format!("blake3:{}", hex(&blake3_256(data)))
}
pub fn map_get<'a>(entries: &'a [(Value, Value)], key: &str) -> Option<&'a Value> {
entries
.iter()
.find(|(k, _)| matches!(k, Value::Text(t) if t == key))
.map(|(_, v)| v)
}
fn hash_excluding(entries: &[(Value, Value)], excluded: &[&str]) -> Vec<u8> {
let content: Vec<(Value, Value)> = entries
.iter()
.filter(|(k, _)| !matches!(k, Value::Text(t) if excluded.contains(&t.as_str())))
.cloned()
.collect();
blake3_256(&canonical(&Value::Map(content)))
}
pub fn content_id(frame: &[(Value, Value)]) -> Vec<u8> {
hash_excluding(frame, &["id", "sig"])
}
pub fn header_id(header: &[(Value, Value)]) -> Vec<u8> {
hash_excluding(header, &["id"])
}
struct Counting<'a> {
data: &'a [u8],
pos: usize,
}
impl std::io::Read for Counting<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = buf.len().min(self.data.len() - self.pos);
buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]);
self.pos += n;
Ok(n)
}
}
pub fn iter_items(data: &[u8]) -> (Vec<(usize, Value)>, Option<usize>) {
let mut reader = Counting { data, pos: 0 };
let mut out = Vec::new();
let mut torn = None;
loop {
let start = reader.pos;
if start == data.len() {
break;
}
match ciborium::de::from_reader::<Value, _>(&mut reader) {
Ok(item) => out.push((start, item)),
Err(_) => {
torn = Some(start);
break;
}
}
}
(out, torn)
}
pub fn unwrap_header(item: &Value) -> Result<&Vec<(Value, Value)>, String> {
let inner = match item {
Value::Tag(tag, inner) => {
if *tag != SELF_DESCRIBE_TAG {
return Err(format!("unexpected CBOR tag {tag} on the header item"));
}
inner.as_ref()
}
other => other,
};
match inner {
Value::Map(entries) => Ok(entries),
_ => Err("header item is not a CBOR map".to_string()),
}
}