use std::collections::BTreeMap;
fn write_uint(out: &mut Vec<u8>, major: u8, value: u64) {
debug_assert!(major < 8, "CBOR major type fits in 3 bits");
let prefix = major << 5;
if value < 24 {
out.push(prefix | (value as u8));
} else if value < 0x100 {
out.push(prefix | 24);
out.push(value as u8);
} else if value < 0x1_0000 {
out.push(prefix | 25);
out.extend_from_slice(&(value as u16).to_be_bytes());
} else if value < 0x1_0000_0000 {
out.push(prefix | 26);
out.extend_from_slice(&(value as u32).to_be_bytes());
} else {
out.push(prefix | 27);
out.extend_from_slice(&value.to_be_bytes());
}
}
#[derive(Debug, Clone)]
pub enum Value {
UInt(u64),
Bytes(Vec<u8>),
Text(String),
Array(Vec<Value>),
Map(BTreeMap<Vec<u8>, Value>),
}
impl Value {
pub fn uint(v: u64) -> Self {
Self::UInt(v)
}
pub fn bytes(b: impl Into<Vec<u8>>) -> Self {
Self::Bytes(b.into())
}
pub fn text(s: impl Into<String>) -> Self {
Self::Text(s.into())
}
pub fn map() -> MapBuilder {
MapBuilder {
entries: BTreeMap::new(),
}
}
pub fn encode(&self) -> Vec<u8> {
let mut out = Vec::new();
self.encode_into(&mut out);
out
}
fn encode_into(&self, out: &mut Vec<u8>) {
match self {
Self::UInt(v) => write_uint(out, 0, *v),
Self::Bytes(b) => {
write_uint(out, 2, b.len() as u64);
out.extend_from_slice(b);
}
Self::Text(s) => {
write_uint(out, 3, s.len() as u64);
out.extend_from_slice(s.as_bytes());
}
Self::Array(items) => {
write_uint(out, 4, items.len() as u64);
for v in items {
v.encode_into(out);
}
}
Self::Map(m) => {
write_uint(out, 5, m.len() as u64);
for (k_bytes, v) in m {
out.extend_from_slice(k_bytes);
v.encode_into(out);
}
}
}
}
}
#[derive(Debug, Default)]
pub struct MapBuilder {
entries: BTreeMap<Vec<u8>, Value>,
}
impl MapBuilder {
pub fn entry(mut self, key: Value, value: Value) -> Self {
self.entries.insert(key.encode(), value);
self
}
pub fn text_entry(self, key: &str, value: Value) -> Self {
self.entry(Value::text(key), value)
}
pub fn build(self) -> Value {
Value::Map(self.entries)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn small_uint_uses_short_form() {
assert_eq!(Value::uint(5).encode(), vec![0x05]);
assert_eq!(Value::uint(23).encode(), vec![0x17]);
assert_eq!(Value::uint(24).encode(), vec![0x18, 0x18]);
assert_eq!(Value::uint(0xFF).encode(), vec![0x18, 0xFF]);
assert_eq!(Value::uint(0x100).encode(), vec![0x19, 0x01, 0x00]);
assert_eq!(
Value::uint(0x1_0000_0000).encode(),
vec![0x1B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
);
}
#[test]
fn bytes_carry_length_prefix() {
assert_eq!(
Value::bytes(vec![0xDE, 0xAD]).encode(),
vec![0x42, 0xDE, 0xAD]
);
}
#[test]
fn text_carries_length_prefix() {
assert_eq!(Value::text("hi").encode(), vec![0x62, b'h', b'i']);
}
#[test]
fn single_pack_bundle_id_fixture() {
let pack_hash = [0xAAu8; 32];
let size_bytes: u64 = 4096;
let encoded = Value::map()
.text_entry("kind", Value::text("single-pack"))
.text_entry("pack_hash", Value::bytes(pack_hash.to_vec()))
.text_entry("size_bytes", Value::uint(size_bytes))
.build()
.encode();
let expected = hex::decode(concat!(
"a3",
"646b696e64",
"6b73696e676c652d7061636b",
"697061636b5f68617368",
"5820",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"6a73697a655f6279746573",
"191000",
))
.unwrap();
assert_eq!(encoded, expected);
}
#[test]
fn map_keys_sort_canonically_regardless_of_insertion_order() {
let a = Value::map()
.text_entry("zzz", Value::uint(1))
.text_entry("aaa", Value::uint(2))
.build()
.encode();
let b = Value::map()
.text_entry("aaa", Value::uint(2))
.text_entry("zzz", Value::uint(1))
.build()
.encode();
assert_eq!(a, b);
}
}