use aws_smithy_types::{Blob, DateTime};
macro_rules! delegate_method {
($($(#[$meta:meta])* $wrapper_name:ident => $encoder_name:ident($($param_name:ident : $param_type:ty),*);)+) => {
$(
pub fn $wrapper_name(&mut self, $($param_name: $param_type),*) -> &mut Self {
self.encoder.$encoder_name($($param_name)*).expect(INFALLIBLE_WRITE);
self
}
)+
};
}
#[derive(Debug, Clone)]
pub struct Encoder {
encoder: minicbor::Encoder<Vec<u8>>,
}
const INFALLIBLE_WRITE: &str = "write failed";
impl Encoder {
pub fn new(writer: Vec<u8>) -> Self {
Self {
encoder: minicbor::Encoder::new(writer),
}
}
delegate_method! {
begin_map => begin_map();
boolean => bool(x: bool);
byte => i8(x: i8);
short => i16(x: i16);
integer => i32(x: i32);
long => i64(x: i64);
float => f32(x: f32);
double => f64(x: f64);
null => null();
end => end();
}
const MAX_HEADER_LEN: usize = 9;
#[inline]
fn write_type_len(writer: &mut Vec<u8>, major: u8, len: usize) {
let mut buf = [0u8; Self::MAX_HEADER_LEN];
let n = match len {
0..=23 => {
buf[0] = major | len as u8;
1
}
24..=0xff => {
buf[0] = major | 24;
buf[1] = len as u8;
2
}
0x100..=0xffff => {
buf[0] = major | 25;
buf[1..3].copy_from_slice(&(len as u16).to_be_bytes());
3
}
0x1_0000..=0xffff_ffff => {
buf[0] = major | 26;
buf[1..5].copy_from_slice(&(len as u32).to_be_bytes());
5
}
_ => {
buf[0] = major | 27;
buf[1..9].copy_from_slice(&(len as u64).to_be_bytes());
9
}
};
writer.extend_from_slice(&buf[..n]);
}
pub fn str(&mut self, x: &str) -> &mut Self {
let writer = self.encoder.writer_mut();
let len = x.len();
writer.reserve(Self::MAX_HEADER_LEN + len);
Self::write_type_len(writer, 0x60, len);
writer.extend_from_slice(x.as_bytes());
self
}
pub fn blob(&mut self, x: &Blob) -> &mut Self {
let data = x.as_ref();
let writer = self.encoder.writer_mut();
let len = data.len();
writer.reserve(Self::MAX_HEADER_LEN + len);
Self::write_type_len(writer, 0x40, len);
writer.extend_from_slice(data);
self
}
pub fn array(&mut self, len: usize) -> &mut Self {
Self::write_type_len(self.encoder.writer_mut(), 0x80, len);
self
}
pub fn map(&mut self, len: usize) -> &mut Self {
Self::write_type_len(self.encoder.writer_mut(), 0xa0, len);
self
}
pub fn timestamp(&mut self, x: &DateTime) -> &mut Self {
self.encoder
.tag(minicbor::data::Tag::from(
minicbor::data::IanaTag::Timestamp,
))
.expect(INFALLIBLE_WRITE);
self.encoder.f64(x.as_secs_f64()).expect(INFALLIBLE_WRITE);
self
}
pub fn into_writer(self) -> Vec<u8> {
self.encoder.into_writer()
}
}
#[cfg(test)]
mod tests {
use super::Encoder;
use aws_smithy_types::Blob;
#[test]
fn str_matches_minicbor() {
let cases = [
"", "a", "hello world!! test str", "this is exactly 24 char", &"x".repeat(0xff), &"y".repeat(0x100), &"z".repeat(0x1_0000), ];
for input in &cases {
let mut ours = Encoder::new(Vec::new());
ours.str(input);
let mut theirs = minicbor::Encoder::new(Vec::new());
theirs.str(input).unwrap();
assert_eq!(
ours.into_writer(),
theirs.into_writer(),
"str mismatch for input len={}",
input.len()
);
}
}
#[test]
fn blob_matches_minicbor() {
let cases: Vec<Vec<u8>> = vec![
vec![], vec![0x42], vec![0xAB; 23], vec![0xCD; 24], vec![0xEF; 0xff], vec![0x01; 0x100], vec![0x02; 0x1_0000], ];
for input in &cases {
let mut ours = Encoder::new(Vec::new());
ours.blob(&Blob::new(input.clone()));
let mut theirs = minicbor::Encoder::new(Vec::new());
theirs.bytes(input).unwrap();
assert_eq!(
ours.into_writer(),
theirs.into_writer(),
"blob mismatch for input len={}",
input.len()
);
}
}
#[test]
fn str_chained_matches_minicbor() {
let mut ours = Encoder::new(Vec::new());
ours.str("key1").str("value1").str("key2").str("value2");
let mut theirs = minicbor::Encoder::new(Vec::new());
theirs
.str("key1")
.unwrap()
.str("value1")
.unwrap()
.str("key2")
.unwrap()
.str("value2")
.unwrap();
assert_eq!(ours.into_writer(), theirs.into_writer());
}
#[test]
fn str_inside_map_matches_minicbor() {
let mut ours = Encoder::new(Vec::new());
ours.begin_map().str("TableName").str("my-table").end();
let mut theirs = minicbor::Encoder::new(Vec::new());
theirs
.begin_map()
.unwrap()
.str("TableName")
.unwrap()
.str("my-table")
.unwrap()
.end()
.unwrap();
assert_eq!(ours.into_writer(), theirs.into_writer());
}
#[test]
fn str_utf8_matches_minicbor() {
let cases = [
"café", "日本語", "🦀🔥", "mixed: aé日🦀", ];
for input in &cases {
let mut ours = Encoder::new(Vec::new());
ours.str(input);
let mut theirs = minicbor::Encoder::new(Vec::new());
theirs.str(input).unwrap();
assert_eq!(
ours.into_writer(),
theirs.into_writer(),
"str UTF-8 mismatch for {:?}",
input
);
}
}
}