aws_smithy_cbor/
encode.rs1use aws_smithy_types::{Blob, DateTime};
7
8macro_rules! delegate_method {
24 ($($(#[$meta:meta])* $wrapper_name:ident => $encoder_name:ident($($param_name:ident : $param_type:ty),*);)+) => {
25 $(
26 pub fn $wrapper_name(&mut self, $($param_name: $param_type),*) -> &mut Self {
27 self.encoder.$encoder_name($($param_name)*).expect(INFALLIBLE_WRITE);
28 self
29 }
30 )+
31 };
32}
33
34#[derive(Debug, Clone)]
35pub struct Encoder {
36 encoder: minicbor::Encoder<Vec<u8>>,
37}
38
39const INFALLIBLE_WRITE: &str = "write failed";
42
43impl Encoder {
44 pub fn new(writer: Vec<u8>) -> Self {
45 Self {
46 encoder: minicbor::Encoder::new(writer),
47 }
48 }
49
50 delegate_method! {
51 begin_map => begin_map();
54 boolean => bool(x: bool);
56 byte => i8(x: i8);
58 short => i16(x: i16);
60 integer => i32(x: i32);
62 long => i64(x: i64);
64 float => f32(x: f32);
66 double => f64(x: f64);
68 null => null();
70 end => end();
72 }
73
74 const MAX_HEADER_LEN: usize = 9;
76
77 #[inline]
86 fn write_type_len(writer: &mut Vec<u8>, major: u8, len: usize) {
87 let mut buf = [0u8; Self::MAX_HEADER_LEN];
88 let n = match len {
89 0..=23 => {
90 buf[0] = major | len as u8;
91 1
92 }
93 24..=0xff => {
94 buf[0] = major | 24;
95 buf[1] = len as u8;
96 2
97 }
98 0x100..=0xffff => {
99 buf[0] = major | 25;
100 buf[1..3].copy_from_slice(&(len as u16).to_be_bytes());
101 3
102 }
103 0x1_0000..=0xffff_ffff => {
104 buf[0] = major | 26;
105 buf[1..5].copy_from_slice(&(len as u32).to_be_bytes());
106 5
107 }
108 _ => {
109 buf[0] = major | 27;
110 buf[1..9].copy_from_slice(&(len as u64).to_be_bytes());
111 9
112 }
113 };
114 writer.extend_from_slice(&buf[..n]);
115 }
116
117 pub fn str(&mut self, x: &str) -> &mut Self {
119 let writer = self.encoder.writer_mut();
120 let len = x.len();
121 writer.reserve(Self::MAX_HEADER_LEN + len);
122 Self::write_type_len(writer, 0x60, len);
123 writer.extend_from_slice(x.as_bytes());
124 self
125 }
126
127 pub fn blob(&mut self, x: &Blob) -> &mut Self {
129 let data = x.as_ref();
130 let writer = self.encoder.writer_mut();
131 let len = data.len();
132 writer.reserve(Self::MAX_HEADER_LEN + len);
133 Self::write_type_len(writer, 0x40, len);
134 writer.extend_from_slice(data);
135 self
136 }
137
138 pub fn array(&mut self, len: usize) -> &mut Self {
140 Self::write_type_len(self.encoder.writer_mut(), 0x80, len);
141 self
142 }
143
144 pub fn map(&mut self, len: usize) -> &mut Self {
150 Self::write_type_len(self.encoder.writer_mut(), 0xa0, len);
151 self
152 }
153
154 pub fn timestamp(&mut self, x: &DateTime) -> &mut Self {
155 self.encoder
156 .tag(minicbor::data::Tag::from(
157 minicbor::data::IanaTag::Timestamp,
158 ))
159 .expect(INFALLIBLE_WRITE);
160 self.encoder.f64(x.as_secs_f64()).expect(INFALLIBLE_WRITE);
161 self
162 }
163
164 pub fn into_writer(self) -> Vec<u8> {
165 self.encoder.into_writer()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::Encoder;
172 use aws_smithy_types::Blob;
173
174 #[test]
176 fn str_matches_minicbor() {
177 let cases = [
178 "", "a", "hello world!! test str", "this is exactly 24 char", &"x".repeat(0xff), &"y".repeat(0x100), &"z".repeat(0x1_0000), ];
186 for input in &cases {
187 let mut ours = Encoder::new(Vec::new());
188 ours.str(input);
189
190 let mut theirs = minicbor::Encoder::new(Vec::new());
191 theirs.str(input).unwrap();
192
193 assert_eq!(
194 ours.into_writer(),
195 theirs.into_writer(),
196 "str mismatch for input len={}",
197 input.len()
198 );
199 }
200 }
201
202 #[test]
204 fn blob_matches_minicbor() {
205 let cases: Vec<Vec<u8>> = vec![
206 vec![], vec![0x42], vec![0xAB; 23], vec![0xCD; 24], vec![0xEF; 0xff], vec![0x01; 0x100], vec![0x02; 0x1_0000], ];
214 for input in &cases {
215 let mut ours = Encoder::new(Vec::new());
216 ours.blob(&Blob::new(input.clone()));
217
218 let mut theirs = minicbor::Encoder::new(Vec::new());
219 theirs.bytes(input).unwrap();
220
221 assert_eq!(
222 ours.into_writer(),
223 theirs.into_writer(),
224 "blob mismatch for input len={}",
225 input.len()
226 );
227 }
228 }
229
230 #[test]
232 fn str_chained_matches_minicbor() {
233 let mut ours = Encoder::new(Vec::new());
234 ours.str("key1").str("value1").str("key2").str("value2");
235
236 let mut theirs = minicbor::Encoder::new(Vec::new());
237 theirs
238 .str("key1")
239 .unwrap()
240 .str("value1")
241 .unwrap()
242 .str("key2")
243 .unwrap()
244 .str("value2")
245 .unwrap();
246
247 assert_eq!(ours.into_writer(), theirs.into_writer());
248 }
249
250 #[test]
252 fn str_inside_map_matches_minicbor() {
253 let mut ours = Encoder::new(Vec::new());
254 ours.begin_map().str("TableName").str("my-table").end();
255
256 let mut theirs = minicbor::Encoder::new(Vec::new());
257 theirs
258 .begin_map()
259 .unwrap()
260 .str("TableName")
261 .unwrap()
262 .str("my-table")
263 .unwrap()
264 .end()
265 .unwrap();
266
267 assert_eq!(ours.into_writer(), theirs.into_writer());
268 }
269
270 #[test]
272 fn str_utf8_matches_minicbor() {
273 let cases = [
274 "café", "日本語", "🦀🔥", "mixed: aé日🦀", ];
279 for input in &cases {
280 let mut ours = Encoder::new(Vec::new());
281 ours.str(input);
282
283 let mut theirs = minicbor::Encoder::new(Vec::new());
284 theirs.str(input).unwrap();
285
286 assert_eq!(
287 ours.into_writer(),
288 theirs.into_writer(),
289 "str UTF-8 mismatch for {:?}",
290 input
291 );
292 }
293 }
294}