use std::collections::BTreeMap;
mod decode;
mod encode;
mod value;
pub(crate) use decode::{decode, decode_prefix, DecodeError};
pub(crate) use encode::EncodeError;
pub(crate) use value::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Profile {
LegacyC2paRs0784Indefinite,
LegacyPipelineBDefinite,
CanonicalForHashedSubstructures,
}
impl Profile {
pub fn indefinite(self) -> bool {
matches!(self, Profile::LegacyC2paRs0784Indefinite)
}
pub fn sort_keys(self) -> bool {
matches!(self, Profile::CanonicalForHashedSubstructures)
}
}
pub fn encode(value: &Value, profile: Profile) -> Result<Vec<u8>, EncodeError> {
let mut out = Vec::new();
encode::encode_into(&mut out, value, profile)?;
Ok(out)
}
pub fn canonical_sha256(value: &Value) -> Result<[u8; 32], EncodeError> {
use sha2::{Digest, Sha256};
let bytes = encode(value, Profile::CanonicalForHashedSubstructures)?;
let mut h = Sha256::new();
h.update(&bytes);
Ok(h.finalize().into())
}
pub type Map = Vec<(Value, Value)>;
pub fn map_from_pairs(pairs: impl IntoIterator<Item = (String, Value)>) -> Value {
Value::Map(
pairs
.into_iter()
.map(|(k, v)| (Value::Text(k), v))
.collect(),
)
}
pub fn canonical_sort(map: &mut Map) {
let mut keyed: BTreeMap<Vec<u8>, (Value, Value)> = BTreeMap::new();
for (k, v) in map.drain(..) {
let enc = encode(&k, Profile::CanonicalForHashedSubstructures).unwrap_or_default();
let sort_key = {
let mut sk = Vec::with_capacity(enc.len() + 8);
sk.extend_from_slice(&(enc.len() as u64).to_be_bytes());
sk.extend_from_slice(&enc);
sk
};
keyed.insert(sort_key, (k, v));
}
map.extend(keyed.into_values());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn definite_vs_indefinite_map_header() {
let v = map_from_pairs([("a".into(), Value::Integer(1))]);
let def = encode(&v, Profile::LegacyPipelineBDefinite).unwrap();
let indef = encode(&v, Profile::LegacyC2paRs0784Indefinite).unwrap();
assert_eq!(def[0], 0xa1);
assert_eq!(indef[0], 0xbf);
assert_eq!(*indef.last().unwrap(), 0xff);
}
#[test]
fn canonical_sorts_keys() {
let v = Value::Map(vec![
(Value::Text("aa".into()), Value::Integer(1)),
(Value::Text("b".into()), Value::Integer(2)),
]);
let enc = encode(&v, Profile::CanonicalForHashedSubstructures).unwrap();
assert_eq!(enc[0], 0xa2);
assert_eq!(&enc[1..3], &[0x61, 0x62]);
}
#[test]
fn roundtrip_bytes_preserved() {
let v = Value::Map(vec![
(
Value::Text("sig".into()),
Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef]),
),
(Value::Text("n".into()), Value::Integer(-5)),
]);
for p in [
Profile::LegacyPipelineBDefinite,
Profile::CanonicalForHashedSubstructures,
] {
let enc = encode(&v, p).unwrap();
let dec = decode(&enc).unwrap();
if let Value::Map(m) = &dec {
let sig = m
.iter()
.find(|(k, _)| *k == Value::Text("sig".into()))
.unwrap();
assert_eq!(sig.1, Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef]));
} else {
panic!("expected map");
}
}
}
}
#[cfg(test)]
mod canonical_parity;
#[cfg(test)]
mod fuzz_robustness;