#[derive(Debug, Default)]
pub struct CanonicalWriter {
buf: Vec<u8>,
}
impl CanonicalWriter {
#[must_use]
pub fn new() -> Self {
Self { buf: Vec::new() }
}
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self {
buf: Vec::with_capacity(cap),
}
}
pub fn u8(&mut self, v: u8) -> &mut Self {
self.buf.push(v);
self
}
pub fn u16(&mut self, v: u16) -> &mut Self {
self.buf.extend_from_slice(&v.to_le_bytes());
self
}
pub fn u32(&mut self, v: u32) -> &mut Self {
self.buf.extend_from_slice(&v.to_le_bytes());
self
}
pub fn u64(&mut self, v: u64) -> &mut Self {
self.buf.extend_from_slice(&v.to_le_bytes());
self
}
pub fn i64(&mut self, v: i64) -> &mut Self {
self.buf.extend_from_slice(&v.to_le_bytes());
self
}
pub fn u128(&mut self, v: u128) -> &mut Self {
self.buf.extend_from_slice(&v.to_le_bytes());
self
}
pub fn bool(&mut self, v: bool) -> &mut Self {
self.buf.push(u8::from(v));
self
}
pub fn bytes(&mut self, v: &[u8]) -> &mut Self {
let len = u32::try_from(v.len()).unwrap_or(u32::MAX);
self.u32(len);
self.buf.extend_from_slice(v);
self
}
pub fn str(&mut self, v: &str) -> &mut Self {
self.bytes(v.as_bytes())
}
pub fn fixed(&mut self, v: &[u8]) -> &mut Self {
self.buf.extend_from_slice(v);
self
}
pub fn option<T: ?Sized>(&mut self, v: Option<&T>, f: impl FnOnce(&mut Self, &T)) -> &mut Self {
match v {
None => {
self.u8(0);
}
Some(inner) => {
self.u8(1);
f(self, inner);
}
}
self
}
pub fn seq<T>(
&mut self,
items: impl ExactSizeIterator<Item = T>,
mut f: impl FnMut(&mut Self, T),
) -> &mut Self {
let len = u32::try_from(items.len()).unwrap_or(u32::MAX);
self.u32(len);
for item in items {
f(self, item);
}
self
}
#[must_use]
pub fn finish(self) -> Vec<u8> {
self.buf
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.buf
}
}
pub trait Canonical {
fn encode(&self, w: &mut CanonicalWriter);
#[must_use]
fn to_canonical_bytes(&self) -> Vec<u8> {
let mut w = CanonicalWriter::new();
self.encode(&mut w);
w.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn integers_are_little_endian_fixed_width() {
let mut w = CanonicalWriter::new();
w.u32(1);
assert_eq!(w.finish(), vec![1, 0, 0, 0]);
}
#[test]
fn length_prefix_disambiguates_concatenation() {
let mut a = CanonicalWriter::new();
a.str("ab").str("c");
let mut b = CanonicalWriter::new();
b.str("a").str("bc");
assert_ne!(a.finish(), b.finish());
}
#[test]
fn none_and_empty_are_distinct() {
let mut none = CanonicalWriter::new();
none.option(Option::<&str>::None, |w, v: &str| {
w.str(v);
});
let mut empty = CanonicalWriter::new();
empty.option(Some(""), |w, v: &str| {
w.str(v);
});
assert_ne!(none.finish(), empty.finish());
}
#[test]
fn sequence_counts_are_written() {
let mut w = CanonicalWriter::new();
w.seq([1u8, 2, 3].into_iter(), |w, v| {
w.u8(v);
});
assert_eq!(w.finish(), vec![3, 0, 0, 0, 1, 2, 3]);
}
#[test]
fn empty_sequence_encodes_as_zero_count() {
let mut w = CanonicalWriter::new();
w.seq(core::iter::empty::<u8>(), |w, v| {
w.u8(v);
});
assert_eq!(w.finish(), vec![0, 0, 0, 0]);
}
#[test]
fn encoding_is_deterministic() {
let build = || {
let mut w = CanonicalWriter::new();
w.u64(42)
.str("hello")
.bool(true)
.option(Some(&7u8), |w, v| {
w.u8(*v);
});
w.finish()
};
assert_eq!(build(), build());
}
}