Skip to main content

aws_smithy_cbor/
encode.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime};
7
8/// Macro for delegating method calls to the encoder.
9///
10/// This macro generates wrapper methods for calling specific encoder methods on the encoder
11/// and returning a mutable reference to self for method chaining.
12///
13/// # Example
14///
15/// ```ignore
16/// delegate_method! {
17///     /// Wrapper method for encoding method `encode_str` on the encoder.
18///     encode_str_wrapper => encode_str(data: &str);
19///     /// Wrapper method for encoding method `encode_int` on the encoder.
20///     encode_int_wrapper => encode_int(value: i32);
21/// }
22/// ```
23macro_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
39/// We always write to a `Vec<u8>`, which is infallible in `minicbor`.
40/// <https://docs.rs/minicbor/latest/minicbor/encode/write/trait.Write.html#impl-Write-for-Vec%3Cu8%3E>
41const 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        /// Used when it's not cheap to calculate the size, i.e. when the struct has one or more
52        /// `Option`al members.
53        begin_map => begin_map();
54        /// Begins an indefinite-length array.
55        begin_array => begin_array();
56        /// Writes a boolean value.
57        boolean => bool(x: bool);
58        /// Writes a byte value.
59        byte => i8(x: i8);
60        /// Writes a short value.
61        short => i16(x: i16);
62        /// Writes an integer value.
63        integer => i32(x: i32);
64        /// Writes an long value.
65        long => i64(x: i64);
66        /// Writes an float value.
67        float => f32(x: f32);
68        /// Writes an double value.
69        double => f64(x: f64);
70        /// Writes a null tag.
71        null => null();
72        /// Writes an end tag.
73        end => end();
74    }
75
76    /// Maximum size of a CBOR type+length header: 1 byte major type + up to 8 bytes for the length.
77    const MAX_HEADER_LEN: usize = 9;
78
79    /// Writes a CBOR type+length header directly to the writer.
80    ///
81    /// Encodes the "additional information" field per RFC 8949 §3:
82    /// - 0..=23: length is stored directly in the low 5 bits of the initial byte.
83    /// - 24: one-byte uint follows (value 24..=0xff).
84    /// - 25: two-byte big-endian uint follows (value 0x100..=0xffff).
85    /// - 26: four-byte big-endian uint follows (value 0x1_0000..=0xffff_ffff).
86    /// - 27: eight-byte big-endian uint follows (larger values).
87    #[inline]
88    fn write_type_len(writer: &mut Vec<u8>, major: u8, len: usize) {
89        let mut buf = [0u8; Self::MAX_HEADER_LEN];
90        let n = match len {
91            0..=23 => {
92                buf[0] = major | len as u8;
93                1
94            }
95            24..=0xff => {
96                buf[0] = major | 24;
97                buf[1] = len as u8;
98                2
99            }
100            0x100..=0xffff => {
101                buf[0] = major | 25;
102                buf[1..3].copy_from_slice(&(len as u16).to_be_bytes());
103                3
104            }
105            0x1_0000..=0xffff_ffff => {
106                buf[0] = major | 26;
107                buf[1..5].copy_from_slice(&(len as u32).to_be_bytes());
108                5
109            }
110            _ => {
111                buf[0] = major | 27;
112                buf[1..9].copy_from_slice(&(len as u64).to_be_bytes());
113                9
114            }
115        };
116        writer.extend_from_slice(&buf[..n]);
117    }
118
119    /// Writes a definite length string. Collapses header+data into a single reserve+write.
120    pub fn str(&mut self, x: &str) -> &mut Self {
121        let writer = self.encoder.writer_mut();
122        let len = x.len();
123        writer.reserve(Self::MAX_HEADER_LEN + len);
124        Self::write_type_len(writer, 0x60, len);
125        writer.extend_from_slice(x.as_bytes());
126        self
127    }
128
129    /// Writes a `BigInteger` using preferred serialization per RFC 8949 §3.4.3.
130    ///
131    /// Values that fit in a CBOR major type 0 or 1 integer are encoded directly
132    /// (preferred serialization). Larger values use tag 2 (unsigned bignum) or
133    /// tag 3 (negative bignum). For tag 3, the byte string encodes `n` where
134    /// the value is `-1 - n`.
135    pub fn big_integer(&mut self, value: &BigInteger) -> &mut Self {
136        let value = value
137            .as_ref()
138            .parse()
139            .expect("BigInteger contains invalid value");
140        self.bigint(&value)
141    }
142
143    /// Writes a `BigDecimal` as a decimal fraction per RFC 8949 §3.4.4.
144    ///
145    /// Decimal fractions use tag 4 followed by a two-element array containing
146    /// the base-10 exponent and mantissa. The mantissa uses the same preferred
147    /// integer or bignum representation as [`Self::big_integer`].
148    pub fn big_decimal(&mut self, value: &BigDecimal) -> &mut Self {
149        let value: bigdecimal::BigDecimal = value
150            .as_ref()
151            .parse()
152            .expect("BigDecimal contains invalid value");
153        let (mantissa, scale) = value.into_bigint_and_scale();
154        let exponent = scale
155            .checked_neg()
156            .expect("BigDecimal exponent is outside the supported range");
157
158        self.encoder
159            .tag(minicbor::data::Tag::new(4))
160            .expect(INFALLIBLE_WRITE);
161        self.array(2);
162        self.encoder.i64(exponent).expect(INFALLIBLE_WRITE);
163        self.bigint(&mantissa)
164    }
165
166    fn bigint(&mut self, value: &num_bigint::BigInt) -> &mut Self {
167        use num_bigint::{BigInt, Sign};
168
169        let (sign, magnitude) = value.to_bytes_be();
170
171        match sign {
172            Sign::Plus | Sign::NoSign => {
173                // Try preferred serialization as major type 0.
174                if magnitude.len() <= 8 {
175                    let mut buf = [0u8; 8];
176                    buf[8 - magnitude.len()..].copy_from_slice(&magnitude);
177                    let val = u64::from_be_bytes(buf);
178                    self.encoder.u64(val).expect(INFALLIBLE_WRITE);
179                } else {
180                    self.encoder
181                        .tag(minicbor::data::Tag::new(2))
182                        .expect(INFALLIBLE_WRITE);
183                    // Preferred serialization: strip leading zeroes.
184                    let stripped = strip_leading_zeroes(&magnitude);
185                    self.encoder.bytes(stripped).expect(INFALLIBLE_WRITE);
186                }
187            }
188            Sign::Minus => {
189                // Tag 3 value = -1 - n, so n = -1 - value = |value| - 1.
190                let one = BigInt::from(1u8);
191                let n = (-value) - one;
192                let (_, n_bytes) = n.to_bytes_be();
193
194                // Try preferred serialization as major type 1.
195                if n_bytes.len() <= 8 {
196                    let mut buf = [0u8; 8];
197                    buf[8 - n_bytes.len()..].copy_from_slice(&n_bytes);
198                    let val = u64::from_be_bytes(buf);
199                    // Use i128 to represent -1 - val without overflow, then
200                    // convert to minicbor::data::Int which covers the full
201                    // CBOR major type 1 range.
202                    let neg = -1i128 - (val as i128);
203                    let int_val = minicbor::data::Int::try_from(neg)
204                        .expect("value fits in CBOR integer range");
205                    self.encoder.int(int_val).expect(INFALLIBLE_WRITE);
206                } else {
207                    self.encoder
208                        .tag(minicbor::data::Tag::new(3))
209                        .expect(INFALLIBLE_WRITE);
210                    let stripped = strip_leading_zeroes(&n_bytes);
211                    self.encoder.bytes(stripped).expect(INFALLIBLE_WRITE);
212                }
213            }
214        }
215        self
216    }
217
218    /// Writes a blob from a byte slice. Collapses header+data into a single reserve+write.
219    ///
220    /// Mirrors [`Self::str`]'s slice-input style. Prefer this over [`Self::blob`]
221    /// when the caller holds a byte slice rather than an owned [`Blob`] — for
222    /// example a schema-serde `ShapeSerializer::write_blob` implementation, which
223    /// receives an owned `Blob` and passes `value.as_ref()` because it has no
224    /// reason to consume it.
225    pub fn blob_bytes(&mut self, data: &[u8]) -> &mut Self {
226        let writer = self.encoder.writer_mut();
227        let len = data.len();
228        writer.reserve(Self::MAX_HEADER_LEN + len);
229        Self::write_type_len(writer, 0x40, len);
230        writer.extend_from_slice(data);
231        self
232    }
233
234    /// Writes a blob. Collapses header+data into a single reserve+write.
235    pub fn blob(&mut self, x: &Blob) -> &mut Self {
236        self.blob_bytes(x.as_ref())
237    }
238
239    /// Writes a fixed length array of given length.
240    pub fn array(&mut self, len: usize) -> &mut Self {
241        Self::write_type_len(self.encoder.writer_mut(), 0x80, len);
242        self
243    }
244
245    /// Writes a fixed length map of given length.
246    /// Used when we know the size in advance, i.e.:
247    /// - when a struct has all non-`Option`al members.
248    /// - when serializing `union` shapes (they can only have one member set).
249    /// - when serializing a `map` shape.
250    pub fn map(&mut self, len: usize) -> &mut Self {
251        Self::write_type_len(self.encoder.writer_mut(), 0xa0, len);
252        self
253    }
254
255    pub fn timestamp(&mut self, x: &DateTime) -> &mut Self {
256        self.encoder
257            .tag(minicbor::data::Tag::from(
258                minicbor::data::IanaTag::Timestamp,
259            ))
260            .expect(INFALLIBLE_WRITE);
261        self.encoder.f64(x.as_secs_f64()).expect(INFALLIBLE_WRITE);
262        self
263    }
264
265    pub fn into_writer(self) -> Vec<u8> {
266        self.encoder.into_writer()
267    }
268}
269
270/// Strips leading zero bytes from a big-endian byte slice.
271fn strip_leading_zeroes(bytes: &[u8]) -> &[u8] {
272    let start = bytes.iter().position(|&b| b != 0).unwrap_or(bytes.len());
273    &bytes[start..]
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use aws_smithy_types::Blob;
280
281    /// Verify our `str()` produces byte-identical output to minicbor's.
282    #[test]
283    fn str_matches_minicbor() {
284        let cases = [
285            "",                        // len 0
286            "a",                       // len 1 (in 0..=23 range)
287            "hello world!! test str",  // len 22 (still 0..=23)
288            "this is exactly 24 char", // len 24 (0x18, first 1-byte length)
289            &"x".repeat(0xff),         // len 255 (max 1-byte length)
290            &"y".repeat(0x100),        // len 256 (first 2-byte length)
291            &"z".repeat(0x1_0000),     // len 65536 (first 4-byte length)
292        ];
293        for input in &cases {
294            let mut ours = Encoder::new(Vec::new());
295            ours.str(input);
296
297            let mut theirs = minicbor::Encoder::new(Vec::new());
298            theirs.str(input).unwrap();
299
300            assert_eq!(
301                ours.into_writer(),
302                theirs.into_writer(),
303                "str mismatch for input len={}",
304                input.len()
305            );
306        }
307    }
308
309    /// Verify our `blob()` produces byte-identical output to minicbor's.
310    #[test]
311    fn blob_matches_minicbor() {
312        let cases: Vec<Vec<u8>> = vec![
313            vec![],               // empty
314            vec![0x42],           // 1 byte
315            vec![0xAB; 23],       // max inline length
316            vec![0xCD; 24],       // first 1-byte length
317            vec![0xEF; 0xff],     // max 1-byte length
318            vec![0x01; 0x100],    // first 2-byte length
319            vec![0x02; 0x1_0000], // first 4-byte length
320        ];
321        for input in &cases {
322            let mut ours = Encoder::new(Vec::new());
323            ours.blob(&Blob::new(input.clone()));
324
325            let mut theirs = minicbor::Encoder::new(Vec::new());
326            theirs.bytes(input).unwrap();
327
328            assert_eq!(
329                ours.into_writer(),
330                theirs.into_writer(),
331                "blob mismatch for input len={}",
332                input.len()
333            );
334        }
335    }
336
337    /// Verify chained `str()` calls don't corrupt encoder state for subsequent writes.
338    #[test]
339    fn str_chained_matches_minicbor() {
340        let mut ours = Encoder::new(Vec::new());
341        ours.str("key1").str("value1").str("key2").str("value2");
342
343        let mut theirs = minicbor::Encoder::new(Vec::new());
344        theirs
345            .str("key1")
346            .unwrap()
347            .str("value1")
348            .unwrap()
349            .str("key2")
350            .unwrap()
351            .str("value2")
352            .unwrap();
353
354        assert_eq!(ours.into_writer(), theirs.into_writer());
355    }
356
357    /// Verify `str()` works correctly inside a map structure (the real-world hot path).
358    #[test]
359    fn str_inside_map_matches_minicbor() {
360        let mut ours = Encoder::new(Vec::new());
361        ours.begin_map().str("TableName").str("my-table").end();
362
363        let mut theirs = minicbor::Encoder::new(Vec::new());
364        theirs
365            .begin_map()
366            .unwrap()
367            .str("TableName")
368            .unwrap()
369            .str("my-table")
370            .unwrap()
371            .end()
372            .unwrap();
373
374        assert_eq!(ours.into_writer(), theirs.into_writer());
375    }
376
377    /// Verify `str()` handles multi-byte UTF-8 correctly (CBOR text strings must be valid UTF-8).
378    #[test]
379    fn str_utf8_matches_minicbor() {
380        let cases = [
381            "café",          // 2-byte UTF-8
382            "日本語",        // 3-byte UTF-8
383            "🦀🔥",          // 4-byte UTF-8 (emoji)
384            "mixed: aé日🦀", // all byte widths
385        ];
386        for input in &cases {
387            let mut ours = Encoder::new(Vec::new());
388            ours.str(input);
389
390            let mut theirs = minicbor::Encoder::new(Vec::new());
391            theirs.str(input).unwrap();
392
393            assert_eq!(
394                ours.into_writer(),
395                theirs.into_writer(),
396                "str UTF-8 mismatch for {:?}",
397                input
398            );
399        }
400    }
401
402    #[test]
403    fn preferred_serialization_small_positive() {
404        // Small values use major type 0 directly, not tag 2.
405        let mut encoder = Encoder::new(Vec::new());
406        encoder.big_integer(&"0".parse().unwrap());
407        assert_eq!(encoder.into_writer(), vec![0x00]); // major type 0, value 0
408
409        let mut encoder = Encoder::new(Vec::new());
410        encoder.big_integer(&"23".parse().unwrap());
411        assert_eq!(encoder.into_writer(), vec![0x17]); // major type 0, value 23
412
413        let mut encoder = Encoder::new(Vec::new());
414        encoder.big_integer(&"256".parse().unwrap());
415        // major type 0, additional info 25 (2-byte), 0x0100
416        assert_eq!(encoder.into_writer(), vec![0x19, 0x01, 0x00]);
417    }
418
419    #[test]
420    fn preferred_serialization_small_negative() {
421        // Small negatives use major type 1 directly, not tag 3.
422        // Major type 1 value = -1 - argument.
423        let mut encoder = Encoder::new(Vec::new());
424        encoder.big_integer(&"-1".parse().unwrap());
425        assert_eq!(encoder.into_writer(), vec![0x20]); // -1 = -1-0, argument 0
426
427        let mut encoder = Encoder::new(Vec::new());
428        encoder.big_integer(&"-42".parse().unwrap());
429        // -42 = -1-41, argument 41 = 0x29 (major type 1, additional info 24, value 41)
430        assert_eq!(encoder.into_writer(), vec![0x38, 0x29]);
431    }
432
433    #[test]
434    fn preferred_serialization_u64_max() {
435        // u64::MAX = 18446744073709551615 fits in major type 0.
436        let mut encoder = Encoder::new(Vec::new());
437        encoder.big_integer(&"18446744073709551615".parse().unwrap());
438        let bytes = encoder.into_writer();
439        assert_eq!(bytes[0], 0x1b); // major type 0, 8-byte argument
440        assert_eq!(
441            &bytes[1..],
442            &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
443        );
444    }
445
446    #[test]
447    fn tag2_for_values_exceeding_u64() {
448        // 2^64 = 18446744073709551616 requires tag 2.
449        // RFC 8949 Appendix A: 0xc249010000000000000000
450        let mut encoder = Encoder::new(Vec::new());
451        encoder.big_integer(&"18446744073709551616".parse().unwrap());
452        let bytes = encoder.into_writer();
453        assert_eq!(
454            bytes,
455            vec![0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
456        );
457    }
458
459    #[test]
460    fn tag3_negative_bignum_rfc8949_example() {
461        // RFC 8949 Appendix A: -18446744073709551617 = 0xc349010000000000000000
462        // value = -1 - n, n = 18446744073709551616 = 2^64
463        let mut encoder = Encoder::new(Vec::new());
464        encoder.big_integer(&"-18446744073709551617".parse().unwrap());
465        let bytes = encoder.into_writer();
466        assert_eq!(
467            bytes,
468            vec![0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
469        );
470    }
471
472    #[test]
473    fn negative_at_major_type_1_boundary() {
474        // -18446744073709551616 = -1 - 18446744073709551615 = -1 - u64::MAX
475        // This fits in major type 1 with 8-byte argument = u64::MAX.
476        let mut encoder = Encoder::new(Vec::new());
477        encoder.big_integer(&"-18446744073709551616".parse().unwrap());
478        let bytes = encoder.into_writer();
479        assert_eq!(bytes[0], 0x3b); // major type 1, 8-byte argument
480        assert_eq!(
481            &bytes[1..],
482            &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
483        );
484    }
485
486    #[test]
487    fn tag2_strips_leading_zeroes() {
488        // A large number whose big-endian representation has no leading zeroes.
489        let mut encoder = Encoder::new(Vec::new());
490        let large = "123456789012345678901234567890";
491        encoder.big_integer(&large.parse().unwrap());
492        let bytes = encoder.into_writer();
493        assert_eq!(bytes[0], 0xc2); // tag 2
494                                    // Verify the byte string has no leading zero bytes.
495                                    // bytes[1] is the CBOR byte string length header.
496        let payload_start = if bytes[1] < 0x58 { 2 } else { 3 };
497        assert_ne!(bytes[payload_start], 0x00);
498    }
499
500    #[test]
501    fn decimal_fraction_rfc8949_example() {
502        // RFC 8949 §3.4.4: 273.15 = tag 4([-2, 27315]).
503        let mut encoder = Encoder::new(Vec::new());
504        encoder.big_decimal(&"273.15".parse().unwrap());
505        assert_eq!(
506            encoder.into_writer(),
507            vec![0xc4, 0x82, 0x21, 0x19, 0x6a, 0xb3]
508        );
509    }
510
511    #[test]
512    fn decimal_fraction_scientific_notation() {
513        let mut encoder = Encoder::new(Vec::new());
514        encoder.big_decimal(&"1.23e10".parse().unwrap());
515        // 1.23e10 = 123 * 10^8.
516        assert_eq!(encoder.into_writer(), vec![0xc4, 0x82, 0x08, 0x18, 0x7b]);
517    }
518
519    #[test]
520    fn decimal_fraction_preserves_scale() {
521        let mut encoder = Encoder::new(Vec::new());
522        encoder.big_decimal(&"1.2300".parse().unwrap());
523        // 1.2300 = 12300 * 10^-4.
524        assert_eq!(
525            encoder.into_writer(),
526            vec![0xc4, 0x82, 0x23, 0x19, 0x30, 0x0c]
527        );
528    }
529
530    #[test]
531    fn decimal_fraction_uses_bignum_mantissa() {
532        let mut encoder = Encoder::new(Vec::new());
533        encoder.big_decimal(&"18446744073709551616".parse().unwrap());
534        assert_eq!(
535            encoder.into_writer(),
536            vec![
537                0xc4, 0x82, 0x00, 0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
538            ]
539        );
540    }
541
542    #[test]
543    fn valid_big_decimal_can_be_encoded() {
544        for value in [
545            "0",
546            "+5",
547            "-0.0",
548            ".5",
549            "-.5",
550            "5.",
551            "1.5E+3",
552            "00123",
553            "00.1",
554            "0.123456789012345678901234567890",
555            "1.234e500",
556            "1e9223372036854775807",
557            "1e-9223372036854775807",
558        ] {
559            let mut encoder = Encoder::new(Vec::new());
560            encoder.big_decimal(&value.parse().unwrap());
561        }
562    }
563}