use std::io::Write;
use crate::cbor::{CborError, Cid};
pub struct Encoder<W: Write> {
writer: W,
}
impl<W: Write> Encoder<W> {
pub fn new(writer: W) -> Self {
Encoder { writer }
}
pub fn into_inner(self) -> W {
self.writer
}
#[inline]
pub fn encode_u64(&mut self, v: u64) -> Result<(), CborError> {
self.write_type_value(0, v)
}
#[inline]
pub fn encode_i64(&mut self, v: i64) -> Result<(), CborError> {
if v >= 0 {
self.write_type_value(0, v as u64)
} else {
self.write_type_value(1, (-1 - v) as u64)
}
}
#[inline]
pub fn encode_bool(&mut self, v: bool) -> Result<(), CborError> {
self.writer.write_all(&[if v { 0xf5 } else { 0xf4 }])?;
Ok(())
}
#[inline]
pub fn encode_null(&mut self) -> Result<(), CborError> {
self.writer.write_all(&[0xf6])?;
Ok(())
}
pub fn encode_f64(&mut self, v: f64) -> Result<(), CborError> {
if v.is_nan() || v.is_infinite() {
return Err(CborError::InvalidCbor(
"NaN and Infinity not allowed in DRISL".into(),
));
}
let be = v.to_bits().to_be_bytes();
self.writer
.write_all(&[0xfb, be[0], be[1], be[2], be[3], be[4], be[5], be[6], be[7]])?;
Ok(())
}
#[inline]
pub fn encode_text(&mut self, v: &str) -> Result<(), CborError> {
self.write_type_value(3, v.len() as u64)?;
self.writer.write_all(v.as_bytes())?;
Ok(())
}
#[inline]
pub fn encode_bytes(&mut self, v: &[u8]) -> Result<(), CborError> {
self.write_type_value(2, v.len() as u64)?;
self.writer.write_all(v)?;
Ok(())
}
pub fn encode_array_header(&mut self, len: u64) -> Result<(), CborError> {
self.write_type_value(4, len)
}
pub fn encode_map_header(&mut self, len: u64) -> Result<(), CborError> {
self.write_type_value(5, len)
}
#[inline]
pub fn encode_cid(&mut self, cid: &Cid) -> Result<(), CborError> {
let cid_bytes = cid.to_bytes();
let mut buf = [0u8; 41];
buf[0] = 0xd8; buf[1] = 0x2a; buf[2] = 0x58; buf[3] = 0x25; buf[4] = 0x00; buf[5..].copy_from_slice(&cid_bytes);
self.writer.write_all(&buf)?;
Ok(())
}
#[inline(always)]
fn write_type_value(&mut self, major: u8, value: u64) -> Result<(), CborError> {
let major_bits = major << 5;
if value < 24 {
self.writer.write_all(&[major_bits | value as u8])?;
} else if value <= u8::MAX as u64 {
self.writer.write_all(&[major_bits | 24, value as u8])?;
} else if value <= u16::MAX as u64 {
let be = (value as u16).to_be_bytes();
self.writer.write_all(&[major_bits | 25, be[0], be[1]])?;
} else if value <= u32::MAX as u64 {
let be = (value as u32).to_be_bytes();
self.writer
.write_all(&[major_bits | 26, be[0], be[1], be[2], be[3]])?;
} else {
let be = value.to_be_bytes();
self.writer.write_all(&[
major_bits | 27,
be[0],
be[1],
be[2],
be[3],
be[4],
be[5],
be[6],
be[7],
])?;
}
Ok(())
}
}
pub fn encode_text_map<W: Write, F>(
enc: &mut Encoder<W>,
keys: &[&str],
mut encode_value: F,
) -> Result<(), CborError>
where
F: FnMut(&mut Encoder<W>, &str) -> Result<(), CborError>,
{
let mut sorted: Vec<&str> = keys.to_vec();
sorted.sort_by(|a, b| cbor_key_cmp(a, b));
enc.encode_map_header(sorted.len() as u64)?;
for key in sorted {
enc.encode_text(key)?;
encode_value(enc, key)?;
}
Ok(())
}
#[inline]
pub fn cbor_key_cmp(a: &str, b: &str) -> std::cmp::Ordering {
let a_len = a.len();
let b_len = b.len();
if a_len < 24 && b_len < 24 {
return a_len
.cmp(&b_len)
.then_with(|| a.as_bytes().cmp(b.as_bytes()));
}
let a_encoded_len = cbor_header_len(a_len as u64) + a_len;
let b_encoded_len = cbor_header_len(b_len as u64) + b_len;
a_encoded_len
.cmp(&b_encoded_len)
.then_with(|| a.as_bytes().cmp(b.as_bytes()))
}
#[inline]
fn cbor_header_len(value: u64) -> usize {
if value < 24 {
1
} else if value <= u8::MAX as u64 {
2
} else if value <= u16::MAX as u64 {
3
} else if value <= u32::MAX as u64 {
5
} else {
9
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unreachable
)]
mod tests {
use super::*;
use crate::cbor::Codec;
fn encode_to_bytes<F>(f: F) -> Vec<u8>
where
F: FnOnce(&mut Encoder<&mut Vec<u8>>) -> Result<(), CborError>,
{
let mut buf = Vec::new();
let mut enc = Encoder::new(&mut buf);
f(&mut enc).unwrap();
buf
}
#[test]
fn encode_small_positive_int() {
assert_eq!(encode_to_bytes(|e| e.encode_u64(0)), [0x00]);
assert_eq!(encode_to_bytes(|e| e.encode_u64(1)), [0x01]);
assert_eq!(encode_to_bytes(|e| e.encode_u64(23)), [0x17]);
}
#[test]
fn encode_one_byte_int() {
assert_eq!(encode_to_bytes(|e| e.encode_u64(24)), [0x18, 0x18]);
assert_eq!(encode_to_bytes(|e| e.encode_u64(255)), [0x18, 0xff]);
}
#[test]
fn encode_two_byte_int() {
assert_eq!(encode_to_bytes(|e| e.encode_u64(256)), [0x19, 0x01, 0x00]);
assert_eq!(encode_to_bytes(|e| e.encode_u64(65535)), [0x19, 0xff, 0xff]);
}
#[test]
fn encode_four_byte_int() {
assert_eq!(
encode_to_bytes(|e| e.encode_u64(65536)),
[0x1a, 0x00, 0x01, 0x00, 0x00]
);
}
#[test]
fn encode_eight_byte_int() {
assert_eq!(
encode_to_bytes(|e| e.encode_u64(u32::MAX as u64 + 1)),
[0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]
);
}
#[test]
fn encode_negative_int() {
assert_eq!(encode_to_bytes(|e| e.encode_i64(-1)), [0x20]);
assert_eq!(encode_to_bytes(|e| e.encode_i64(-24)), [0x37]);
assert_eq!(encode_to_bytes(|e| e.encode_i64(-25)), [0x38, 0x18]);
}
#[test]
fn encode_text() {
let buf = encode_to_bytes(|e| e.encode_text("hello"));
assert_eq!(buf[0], 0x65); assert_eq!(&buf[1..], b"hello");
}
#[test]
fn encode_bytes() {
let buf = encode_to_bytes(|e| e.encode_bytes(&[0xDE, 0xAD]));
assert_eq!(buf[0], 0x42); assert_eq!(&buf[1..], &[0xDE, 0xAD]);
}
#[test]
fn encode_bool_and_null() {
assert_eq!(encode_to_bytes(|e| e.encode_bool(true)), [0xf5]);
assert_eq!(encode_to_bytes(|e| e.encode_bool(false)), [0xf4]);
assert_eq!(encode_to_bytes(|e| e.encode_null()), [0xf6]);
}
#[test]
fn encode_float_always_64bit() {
let buf = encode_to_bytes(|e| e.encode_f64(0.0));
assert_eq!(buf.len(), 9);
assert_eq!(buf[0], 0xfb);
}
#[test]
fn encode_float_rejects_nan() {
let mut buf = Vec::new();
let mut enc = Encoder::new(&mut buf);
assert!(enc.encode_f64(f64::NAN).is_err());
}
#[test]
fn encode_float_rejects_infinity() {
let mut buf = Vec::new();
let mut enc = Encoder::new(&mut buf);
assert!(enc.encode_f64(f64::INFINITY).is_err());
assert!(enc.encode_f64(f64::NEG_INFINITY).is_err());
}
#[test]
fn encode_float_allows_neg_zero() {
let buf = encode_to_bytes(|e| e.encode_f64(-0.0));
assert_eq!(buf.len(), 9);
}
#[test]
fn encode_cid_tag42() {
let cid = Cid::compute(Codec::Drisl, b"test");
let buf = encode_to_bytes(|e| e.encode_cid(&cid));
assert_eq!(buf[0], 0xd8); assert_eq!(buf[1], 0x2a); }
#[test]
fn cbor_key_sort_order() {
use std::cmp::Ordering;
assert_eq!(cbor_key_cmp("a", "b"), Ordering::Less);
assert_eq!(cbor_key_cmp("b", "aa"), Ordering::Less);
assert_eq!(cbor_key_cmp("a", "aa"), Ordering::Less);
}
#[test]
fn encode_array() {
let mut buf = Vec::new();
{
let mut enc = Encoder::new(&mut buf);
enc.encode_array_header(3).unwrap();
enc.encode_u64(1).unwrap();
enc.encode_u64(2).unwrap();
enc.encode_u64(3).unwrap();
}
assert_eq!(buf[0], 0x83); assert_eq!(&buf[1..], [0x01, 0x02, 0x03]);
}
#[test]
fn encode_map_manual() {
let mut buf = Vec::new();
{
let mut enc = Encoder::new(&mut buf);
enc.encode_map_header(2).unwrap();
enc.encode_text("a").unwrap();
enc.encode_u64(1).unwrap();
enc.encode_text("b").unwrap();
enc.encode_u64(2).unwrap();
}
assert_eq!(buf[0], 0xa2); }
#[test]
fn encode_text_map_sorts_keys() {
let mut buf = Vec::new();
{
let mut enc = Encoder::new(&mut buf);
encode_text_map(&mut enc, &["b", "a"], |enc, key| match key {
"a" => enc.encode_u64(1),
"b" => enc.encode_u64(2),
_ => unreachable!(),
})
.unwrap();
}
assert_eq!(buf, [0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x02]);
}
#[test]
fn encode_text_map_shorter_keys_first() {
let mut buf = Vec::new();
{
let mut enc = Encoder::new(&mut buf);
encode_text_map(&mut enc, &["bb", "a"], |enc, key| match key {
"a" => enc.encode_u64(1),
"bb" => enc.encode_u64(2),
_ => unreachable!(),
})
.unwrap();
}
assert_eq!(buf, [0xa2, 0x61, 0x61, 0x01, 0x62, 0x62, 0x62, 0x02]);
}
#[test]
fn encode_empty_text() {
let buf = encode_to_bytes(|e| e.encode_text(""));
assert_eq!(buf, [0x60]); }
#[test]
fn encode_empty_bytes() {
let buf = encode_to_bytes(|e| e.encode_bytes(&[]));
assert_eq!(buf, [0x40]); }
#[test]
fn encode_empty_array() {
let buf = encode_to_bytes(|e| e.encode_array_header(0));
assert_eq!(buf, [0x80]); }
#[test]
fn encode_empty_map() {
let buf = encode_to_bytes(|e| e.encode_map_header(0));
assert_eq!(buf, [0xa0]); }
#[test]
fn into_inner_returns_writer() {
let buf = Vec::new();
let enc = Encoder::new(buf);
let recovered = enc.into_inner();
assert!(recovered.is_empty());
}
}