Skip to main content

aws_smithy_cbor/
decode.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use std::borrow::Cow;
7
8use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime};
9use minicbor::decode::Error;
10
11use crate::data::Type;
12
13/// Provides functions for decoding a CBOR object with a known schema.
14///
15/// Although CBOR is a self-describing format, this decoder is tailored for cases where the schema
16/// is known in advance. Therefore, the caller can determine which object key exists at the current
17/// position by calling `str` method, and call the relevant function based on the predetermined schema
18/// for that key. If an unexpected key is encountered, the caller can use the `skip` method to skip
19/// over the element.
20#[derive(Debug, Clone)]
21pub struct Decoder<'b> {
22    decoder: minicbor::Decoder<'b>,
23}
24
25/// When any of the decode methods are called they look for that particular data type at the current
26/// position. If the CBOR data tag does not match the type, a `DeserializeError` is returned.
27#[derive(Debug)]
28pub struct DeserializeError {
29    #[allow(dead_code)]
30    _inner: Error,
31}
32
33impl std::fmt::Display for DeserializeError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        self._inner.fmt(f)
36    }
37}
38
39impl std::error::Error for DeserializeError {}
40
41impl DeserializeError {
42    pub(crate) fn new(inner: Error) -> Self {
43        Self { _inner: inner }
44    }
45
46    /// More than one union variant was detected: `unexpected_type` was unexpected.
47    pub fn unexpected_union_variant(unexpected_type: Type, at: usize) -> Self {
48        Self {
49            _inner: Error::type_mismatch(unexpected_type.into_minicbor_type())
50                .with_message("encountered unexpected union variant; expected end of union")
51                .at(at),
52        }
53    }
54
55    /// Unknown union variant was detected. Servers reject unknown union varaints.
56    pub fn unknown_union_variant(variant_name: &str, at: usize) -> Self {
57        Self {
58            _inner: Error::message(format!("encountered unknown union variant {variant_name}"))
59                .at(at),
60        }
61    }
62
63    /// More than one union variant was detected, but we never even got to parse the first one.
64    /// We immediately raise this error when detecting a union serialized as a fixed-length CBOR
65    /// map whose length (specified upfront) is a value different than 1.
66    pub fn mixed_union_variants(at: usize) -> Self {
67        Self {
68            _inner: Error::message(
69                "encountered mixed variants in union; expected a single union variant to be set",
70            )
71            .at(at),
72        }
73    }
74
75    /// Expected end of stream but more data is available.
76    pub fn expected_end_of_stream(at: usize) -> Self {
77        Self {
78            _inner: Error::message("encountered additional data; expected end of stream").at(at),
79        }
80    }
81
82    /// Returns a custom error with an offset.
83    pub fn custom(message: impl Into<Cow<'static, str>>, at: usize) -> Self {
84        Self {
85            _inner: Error::message(message.into()).at(at),
86        }
87    }
88
89    /// An unexpected type was encountered.
90    // We handle this one when decoding sparse collections: we have to expect either a `null` or an
91    // item, so we try decoding both.
92    pub fn is_type_mismatch(&self) -> bool {
93        self._inner.is_type_mismatch()
94    }
95}
96
97/// Macro for delegating method calls to the decoder.
98///
99/// This macro generates wrapper methods for calling specific methods on the decoder and returning
100/// the result with error handling.
101///
102/// # Example
103///
104/// ```ignore
105/// delegate_method! {
106///     /// Wrapper method for encoding method `encode_str` on the decoder.
107///     encode_str_wrapper => encode_str(String);
108///     /// Wrapper method for encoding method `encode_int` on the decoder.
109///     encode_int_wrapper => encode_int(i32);
110/// }
111/// ```
112macro_rules! delegate_method {
113    ($($(#[$meta:meta])* $wrapper_name:ident => $encoder_name:ident($result_type:ty);)+) => {
114        $(
115            pub fn $wrapper_name(&mut self) -> Result<$result_type, DeserializeError> {
116                self.decoder.$encoder_name().map_err(DeserializeError::new)
117            }
118        )+
119    };
120}
121
122impl<'b> Decoder<'b> {
123    pub fn new(bytes: &'b [u8]) -> Self {
124        Self {
125            decoder: minicbor::Decoder::new(bytes),
126        }
127    }
128
129    pub fn datatype(&self) -> Result<Type, DeserializeError> {
130        self.decoder
131            .datatype()
132            .map(Type::new)
133            .map_err(DeserializeError::new)
134    }
135
136    delegate_method! {
137        /// Skips the current CBOR element.
138        skip => skip(());
139        /// Reads a boolean at the current position.
140        boolean => bool(bool);
141        /// Reads a byte at the current position.
142        byte => i8(i8);
143        /// Reads a short at the current position.
144        short => i16(i16);
145        /// Reads a integer at the current position.
146        integer => i32(i32);
147        /// Reads a long at the current position.
148        long => i64(i64);
149        /// Reads a float at the current position.
150        float => f32(f32);
151        /// Reads a double at the current position.
152        double => f64(f64);
153        /// Reads a null CBOR element at the current position.
154        null => null(());
155        /// Returns the number of elements in a definite list. For indefinite lists it returns a `None`.
156        list => array(Option<u64>);
157        /// Returns the number of elements in a definite map. For indefinite map it returns a `None`.
158        map => map(Option<u64>);
159    }
160
161    /// Returns the current position of the buffer, which will be decoded when any of the methods is called.
162    pub fn position(&self) -> usize {
163        self.decoder.position()
164    }
165
166    /// Set the current decode position.
167    pub fn set_position(&mut self, pos: usize) {
168        self.decoder.set_position(pos)
169    }
170
171    /// Returns a `Cow::Borrowed(&str)` if the element at the current position in the buffer is a definite
172    /// length string. Otherwise, it returns a `Cow::Owned(String)` if the element at the current position is an
173    /// indefinite-length string. An error is returned if the element is neither a definite length nor an
174    /// indefinite-length string.
175    pub fn str(&mut self) -> Result<Cow<'b, str>, DeserializeError> {
176        let bookmark = self.decoder.position();
177        match self.decoder.str() {
178            Ok(str_value) => Ok(Cow::Borrowed(str_value)),
179            Err(e) if e.is_type_mismatch() => {
180                // Move the position back to the start of the CBOR element and then try
181                // decoding it as an indefinite length string.
182                self.decoder.set_position(bookmark);
183                Ok(Cow::Owned(self.string()?))
184            }
185            Err(e) => Err(DeserializeError::new(e)),
186        }
187    }
188
189    /// Allocates and returns a `String` if the element at the current position in the buffer is either a
190    /// definite-length or an indefinite-length string. Otherwise, an error is returned if the element is not a string type.
191    pub fn string(&mut self) -> Result<String, DeserializeError> {
192        let mut iter = self.decoder.str_iter().map_err(DeserializeError::new)?;
193        let head = iter.next();
194
195        let decoded_string = match head {
196            None => String::new(),
197            Some(head) => {
198                let mut combined_chunks = String::from(head.map_err(DeserializeError::new)?);
199                for chunk in iter {
200                    combined_chunks.push_str(chunk.map_err(DeserializeError::new)?);
201                }
202                combined_chunks
203            }
204        };
205
206        Ok(decoded_string)
207    }
208
209    /// Returns a `blob` if the element at the current position in the buffer is a byte string. Otherwise,
210    /// a `DeserializeError` error is returned.
211    pub fn blob(&mut self) -> Result<Blob, DeserializeError> {
212        let iter = self.decoder.bytes_iter().map_err(DeserializeError::new)?;
213        let parts: Vec<&[u8]> = iter
214            .collect::<Result<_, _>>()
215            .map_err(DeserializeError::new)?;
216
217        Ok(if parts.len() == 1 {
218            Blob::new(parts[0]) // Directly convert &[u8] to Blob if there's only one part.
219        } else {
220            Blob::new(parts.concat()) // Concatenate all parts into a single Blob.
221        })
222    }
223
224    /// Returns a `DateTime` if the element at the current position in the buffer is a `timestamp`. Otherwise,
225    /// a `DeserializeError` error is returned.
226    pub fn timestamp(&mut self) -> Result<DateTime, DeserializeError> {
227        let tag = self.decoder.tag().map_err(DeserializeError::new)?;
228        let timestamp_tag = minicbor::data::Tag::from(minicbor::data::IanaTag::Timestamp);
229
230        if tag != timestamp_tag {
231            Err(DeserializeError::new(Error::message(
232                "expected timestamp tag",
233            )))
234        } else {
235            // RFC 8949 §3.4.2: tag 1 content MUST be int OR float.
236            // Values that are more granular than millisecond precision SHOULD be truncated to fit
237            // millisecond precision for epoch-seconds:
238            // https://smithy.io/2.0/spec/protocol-traits.html#timestamp-formats
239            //
240            // Without truncation, the `RpcV2CborDateTimeWithFractionalSeconds` protocol test would
241            // fail since the upstream test expect `123000000` in subsec but the decoded actual
242            // subsec would be `123000025`.
243            // https://github.com/smithy-lang/smithy/blob/6466fe77c65b8a17b219f0b0a60c767915205f95/smithy-protocol-tests/model/rpcv2Cbor/fractional-seconds.smithy#L17
244            let epoch_seconds = match self.decoder.datatype().map_err(DeserializeError::new)? {
245                minicbor::data::Type::F16
246                | minicbor::data::Type::F32
247                | minicbor::data::Type::F64 => self.decoder.f64().map_err(DeserializeError::new)?,
248                _ => self.decoder.i64().map_err(DeserializeError::new)? as f64,
249            };
250            let mut result = DateTime::from_secs_f64(epoch_seconds);
251            let subsec_nanos = result.subsec_nanos();
252            result.set_subsec_nanos((subsec_nanos / 1_000_000) * 1_000_000);
253            Ok(result)
254        }
255    }
256
257    /// Returns a `BigInteger` from either a CBOR tag 2/3 (bignum) or a plain integer.
258    ///
259    /// Per RFC 8949 §3.4.3, tag 2 encodes unsigned bignum `n` and tag 3 encodes
260    /// negative bignum `-1 - n`, where `n` is the unsigned integer from the byte
261    /// string in network byte order. Plain CBOR integers (major types 0 and 1)
262    /// are also accepted per preferred serialization rules.
263    pub fn big_integer(&mut self) -> Result<BigInteger, DeserializeError> {
264        self.bigint()?
265            .to_string()
266            .parse()
267            .map_err(|_| DeserializeError::new(Error::message("invalid bignum value")))
268    }
269
270    /// Returns a `BigDecimal` from a CBOR decimal fraction.
271    ///
272    /// Per RFC 8949 §3.4.4, a decimal fraction is tag 4 followed by a
273    /// two-element array containing a base-10 exponent and an integer or
274    /// bignum mantissa.
275    pub fn big_decimal(&mut self) -> Result<BigDecimal, DeserializeError> {
276        let tag_position = self.decoder.position();
277        let tag = self.decoder.tag().map_err(DeserializeError::new)?;
278        if tag.as_u64() != 4 {
279            return Err(DeserializeError::new(
280                Error::message("expected CBOR tag 4 (decimal fraction)").at(tag_position),
281            ));
282        }
283
284        let len = self.decoder.array().map_err(DeserializeError::new)?;
285        if let Some(len) = len {
286            if len != 2 {
287                return Err(DeserializeError::new(Error::message(
288                    "expected decimal fraction array with two elements",
289                )));
290            }
291        }
292
293        let exponent = self.decimal_exponent()?;
294        let mantissa = self.bigint()?;
295
296        if len.is_none() {
297            match self.decoder.datatype().map_err(DeserializeError::new)? {
298                minicbor::data::Type::Break => {
299                    self.decoder.skip().map_err(DeserializeError::new)?;
300                }
301                _ => {
302                    return Err(DeserializeError::new(Error::message(
303                        "expected decimal fraction array with two elements",
304                    )));
305                }
306            }
307        }
308
309        let scale = exponent.checked_neg().ok_or_else(|| {
310            DeserializeError::new(Error::message(
311                "big decimal fraction exponent is outside the supported range",
312            ))
313        })?;
314        let value = bigdecimal::BigDecimal::new(mantissa, scale);
315        value
316            .to_string()
317            .parse()
318            .map_err(|_| DeserializeError::new(Error::message("invalid decimal fraction value")))
319    }
320
321    fn decimal_exponent(&mut self) -> Result<i64, DeserializeError> {
322        let value = match self.decoder.datatype().map_err(DeserializeError::new)? {
323            minicbor::data::Type::U8
324            | minicbor::data::Type::U16
325            | minicbor::data::Type::U32
326            | minicbor::data::Type::U64 => {
327                let value = self.decoder.u64().map_err(DeserializeError::new)?;
328                i64::try_from(value).map_err(|_| {
329                    DeserializeError::new(Error::message(
330                        "big decimal fraction exponent is outside the supported range",
331                    ))
332                })?
333            }
334            minicbor::data::Type::I8
335            | minicbor::data::Type::I16
336            | minicbor::data::Type::I32
337            | minicbor::data::Type::I64 => self.decoder.i64().map_err(DeserializeError::new)?,
338            minicbor::data::Type::Int => {
339                let value: i128 = self.decoder.int().map_err(DeserializeError::new)?.into();
340                i64::try_from(value).map_err(|_| {
341                    DeserializeError::new(Error::message(
342                        "big decimal fraction exponent is outside the supported range",
343                    ))
344                })?
345            }
346            _ => {
347                return Err(DeserializeError::new(Error::message(
348                    "expected integer decimal fraction exponent",
349                )));
350            }
351        };
352        Ok(value)
353    }
354
355    fn bigint(&mut self) -> Result<num_bigint::BigInt, DeserializeError> {
356        use num_bigint::BigInt;
357
358        match self.decoder.datatype().map_err(DeserializeError::new)? {
359            minicbor::data::Type::Tag => {
360                let tag = self.decoder.tag().map_err(DeserializeError::new)?;
361                let bytes = self.decoder.bytes().map_err(DeserializeError::new)?;
362                let n = BigInt::from_bytes_be(num_bigint::Sign::Plus, bytes);
363
364                let value = match tag.as_u64() {
365                    2 => n,
366                    3 => -n - 1, // tag 3 value = -1 - n
367                    _ => {
368                        return Err(DeserializeError::new(Error::message(
369                            "expected CBOR tag 2 (positive bignum) or tag 3 (negative bignum)",
370                        )));
371                    }
372                };
373                Ok(value)
374            }
375            minicbor::data::Type::U8
376            | minicbor::data::Type::U16
377            | minicbor::data::Type::U32
378            | minicbor::data::Type::U64 => {
379                let value = self.decoder.u64().map_err(DeserializeError::new)?;
380                Ok(BigInt::from(value))
381            }
382            minicbor::data::Type::I8
383            | minicbor::data::Type::I16
384            | minicbor::data::Type::I32
385            | minicbor::data::Type::I64 => {
386                let value = self.decoder.i64().map_err(DeserializeError::new)?;
387                Ok(BigInt::from(value))
388            }
389            // Int covers CBOR major type 1 values that exceed i64 range
390            // (argument > i64::MAX, i.e. values from -2^64 to -(i64::MAX+2)).
391            minicbor::data::Type::Int => {
392                let int_val = self.decoder.int().map_err(DeserializeError::new)?;
393                let value: i128 = int_val.into();
394                Ok(BigInt::from(value))
395            }
396            _ => Err(DeserializeError::new(Error::message(
397                "expected CBOR integer or bignum tag",
398            ))),
399        }
400    }
401}
402
403#[allow(dead_code)] // to avoid `never constructed` warning
404#[derive(Debug)]
405pub struct ArrayIter<'a, 'b, T> {
406    inner: minicbor::decode::ArrayIter<'a, 'b, T>,
407}
408
409impl<'b, T: minicbor::Decode<'b, ()>> Iterator for ArrayIter<'_, 'b, T> {
410    type Item = Result<T, DeserializeError>;
411
412    fn next(&mut self) -> Option<Self::Item> {
413        self.inner
414            .next()
415            .map(|opt| opt.map_err(DeserializeError::new))
416    }
417}
418
419#[allow(dead_code)] // to avoid `never constructed` warning
420#[derive(Debug)]
421pub struct MapIter<'a, 'b, K, V> {
422    inner: minicbor::decode::MapIter<'a, 'b, K, V>,
423}
424
425impl<'b, K, V> Iterator for MapIter<'_, 'b, K, V>
426where
427    K: minicbor::Decode<'b, ()>,
428    V: minicbor::Decode<'b, ()>,
429{
430    type Item = Result<(K, V), DeserializeError>;
431
432    fn next(&mut self) -> Option<Self::Item> {
433        self.inner
434            .next()
435            .map(|opt| opt.map_err(DeserializeError::new))
436    }
437}
438
439pub fn set_optional<B, F>(builder: B, decoder: &mut Decoder, f: F) -> Result<B, DeserializeError>
440where
441    F: Fn(B, &mut Decoder) -> Result<B, DeserializeError>,
442{
443    match decoder.datatype()? {
444        crate::data::Type::Null => {
445            decoder.null()?;
446            Ok(builder)
447        }
448        _ => f(builder, decoder),
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use crate::Decoder;
455    use aws_smithy_types::date_time::Format;
456
457    #[test]
458    fn test_definite_str_is_cow_borrowed() {
459        // Definite length key `thisIsAKey`.
460        let definite_bytes = [
461            0x6a, 0x74, 0x68, 0x69, 0x73, 0x49, 0x73, 0x41, 0x4b, 0x65, 0x79,
462        ];
463        let mut decoder = Decoder::new(&definite_bytes);
464        let member = decoder.str().expect("could not decode str");
465        assert_eq!(member, "thisIsAKey");
466        assert!(matches!(member, std::borrow::Cow::Borrowed(_)));
467    }
468
469    #[test]
470    fn test_indefinite_str_is_cow_owned() {
471        // Indefinite length key `this`, `Is`, `A` and `Key`.
472        let indefinite_bytes = [
473            0x7f, 0x64, 0x74, 0x68, 0x69, 0x73, 0x62, 0x49, 0x73, 0x61, 0x41, 0x63, 0x4b, 0x65,
474            0x79, 0xff,
475        ];
476        let mut decoder = Decoder::new(&indefinite_bytes);
477        let member = decoder.str().expect("could not decode str");
478        assert_eq!(member, "thisIsAKey");
479        assert!(matches!(member, std::borrow::Cow::Owned(_)));
480    }
481
482    #[test]
483    fn test_empty_str_works() {
484        let bytes = [0x60];
485        let mut decoder = Decoder::new(&bytes);
486        let member = decoder.str().expect("could not decode empty str");
487        assert_eq!(member, "");
488    }
489
490    #[test]
491    fn test_empty_blob_works() {
492        let bytes = [0x40];
493        let mut decoder = Decoder::new(&bytes);
494        let member = decoder.blob().expect("could not decode an empty blob");
495        assert_eq!(member, aws_smithy_types::Blob::new([]));
496    }
497
498    #[test]
499    fn test_indefinite_length_blob() {
500        // Indefinite length blob containing bytes corresponding to `indefinite-byte, chunked, on each comma`.
501        // https://cbor.nemo157.com/#type=hex&value=bf69626c6f6256616c75655f50696e646566696e6974652d627974652c49206368756e6b65642c4e206f6e206561636820636f6d6d61ffff
502        let indefinite_bytes = [
503            0x5f, 0x50, 0x69, 0x6e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x2d, 0x62,
504            0x79, 0x74, 0x65, 0x2c, 0x49, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x65, 0x64, 0x2c,
505            0x4e, 0x20, 0x6f, 0x6e, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x63, 0x6f, 0x6d, 0x6d,
506            0x61, 0xff,
507        ];
508        let mut decoder = Decoder::new(&indefinite_bytes);
509        let member = decoder.blob().expect("could not decode blob");
510        assert_eq!(
511            member,
512            aws_smithy_types::Blob::new("indefinite-byte, chunked, on each comma".as_bytes())
513        );
514    }
515
516    #[test]
517    fn test_timestamp_should_be_truncated_to_fit_millisecond_precision() {
518        // Input bytes are derived from the `RpcV2CborDateTimeWithFractionalSeconds` protocol test,
519        // extracting portion representing a timestamp value.
520        let bytes = [
521            0xc1, 0xfb, 0x41, 0xcc, 0x37, 0xdb, 0x38, 0x0f, 0xbe, 0x77, 0xff,
522        ];
523        let mut decoder = Decoder::new(&bytes);
524        let timestamp = decoder.timestamp().expect("should decode timestamp");
525        assert_eq!(
526            timestamp,
527            aws_smithy_types::date_time::DateTime::from_str(
528                "2000-01-02T20:34:56.123Z",
529                Format::DateTime
530            )
531            .unwrap()
532        );
533    }
534
535    #[test]
536    fn big_integer_round_trip_positive() {
537        for value in ["0", "1", "23", "256", "65535", "18446744073709551615"] {
538            let mut encoder = crate::Encoder::new(Vec::new());
539            encoder.big_integer(&value.parse().unwrap());
540            let bytes = encoder.into_writer();
541            let mut decoder = Decoder::new(&bytes);
542            let result = decoder.big_integer().expect("should decode");
543            assert_eq!(result.as_ref(), value, "round-trip failed for {value}");
544        }
545    }
546
547    #[test]
548    fn big_integer_round_trip_negative() {
549        for value in ["-1", "-42", "-256", "-18446744073709551616"] {
550            let mut encoder = crate::Encoder::new(Vec::new());
551            encoder.big_integer(&value.parse().unwrap());
552            let bytes = encoder.into_writer();
553            let mut decoder = Decoder::new(&bytes);
554            let result = decoder.big_integer().expect("should decode");
555            assert_eq!(result.as_ref(), value, "round-trip failed for {value}");
556        }
557    }
558
559    #[test]
560    fn big_integer_round_trip_large() {
561        let large_pos = "123456789012345678901234567890";
562        let large_neg = "-123456789012345678901234567890";
563        for value in [large_pos, large_neg] {
564            let mut encoder = crate::Encoder::new(Vec::new());
565            encoder.big_integer(&value.parse().unwrap());
566            let bytes = encoder.into_writer();
567            let mut decoder = Decoder::new(&bytes);
568            let result = decoder.big_integer().expect("should decode");
569            assert_eq!(result.as_ref(), value, "round-trip failed for {value}");
570        }
571    }
572
573    #[test]
574    fn big_integer_rfc8949_appendix_a_positive() {
575        // RFC 8949 Appendix A: 18446744073709551616 (2^64) = 0xc249010000000000000000
576        let bytes = [
577            0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
578        ];
579        let mut decoder = Decoder::new(&bytes);
580        let result = decoder.big_integer().expect("should decode");
581        assert_eq!(result.as_ref(), "18446744073709551616");
582    }
583
584    #[test]
585    fn big_integer_rfc8949_appendix_a_negative() {
586        // RFC 8949 Appendix A: -18446744073709551617 = 0xc349010000000000000000
587        let bytes = [
588            0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
589        ];
590        let mut decoder = Decoder::new(&bytes);
591        let result = decoder.big_integer().expect("should decode");
592        assert_eq!(result.as_ref(), "-18446744073709551617");
593    }
594
595    #[test]
596    fn big_integer_from_plain_cbor_unsigned() {
597        let mut enc = minicbor::Encoder::new(Vec::new());
598        enc.u64(9999).unwrap();
599        let bytes = enc.into_writer();
600        let mut decoder = Decoder::new(&bytes);
601        let result = decoder.big_integer().expect("should decode plain integer");
602        assert_eq!(result.as_ref(), "9999");
603    }
604
605    #[test]
606    fn big_integer_from_plain_cbor_negative() {
607        let mut enc = minicbor::Encoder::new(Vec::new());
608        enc.i64(-500).unwrap();
609        let bytes = enc.into_writer();
610        let mut decoder = Decoder::new(&bytes);
611        let result = decoder
612            .big_integer()
613            .expect("should decode negative plain integer");
614        assert_eq!(result.as_ref(), "-500");
615    }
616
617    #[test]
618    fn big_integer_from_plain_cbor_positive_signed() {
619        // A positive value such as +123 is encoded as CBOR major type 0 (unsigned)
620        // per preferred serialization and must decode back to the same value.
621        let mut enc = minicbor::Encoder::new(Vec::new());
622        enc.i64(123).unwrap();
623        let bytes = enc.into_writer();
624        let mut decoder = Decoder::new(&bytes);
625        let result = decoder
626            .big_integer()
627            .expect("should decode positive plain integer");
628        assert_eq!(result.as_ref(), "123");
629    }
630
631    #[test]
632    fn big_integer_tag3_empty_byte_string() {
633        // Tag 3 with empty byte string = -1 - 0 = -1
634        let bytes = [0xc3, 0x40]; // tag 3, empty byte string
635        let mut decoder = Decoder::new(&bytes);
636        let result = decoder.big_integer().expect("should decode");
637        assert_eq!(result.as_ref(), "-1");
638    }
639
640    #[test]
641    fn big_integer_tag2_empty_byte_string() {
642        // Tag 2 with empty byte string = 0
643        let bytes = [0xc2, 0x40]; // tag 2, empty byte string
644        let mut decoder = Decoder::new(&bytes);
645        let result = decoder.big_integer().expect("should decode");
646        assert_eq!(result.as_ref(), "0");
647    }
648
649    #[test]
650    fn big_integer_rejects_invalid_tag() {
651        // Tag 4 (decimal fraction) should be rejected.
652        let bytes = [0xc4, 0x82, 0x21, 0x19, 0x6a, 0xb3];
653        let mut decoder = Decoder::new(&bytes);
654        assert!(decoder.big_integer().is_err());
655    }
656
657    #[test]
658    fn big_integer_decode_major_type_1_exceeding_i64() {
659        // CBOR major type 1 with argument u64::MAX (0x3b + 8 bytes of 0xff).
660        // Value = -1 - u64::MAX = -18446744073709551616.
661        // This exercises the minicbor Type::Int path in the decoder.
662        let bytes = [0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
663        let mut decoder = Decoder::new(&bytes);
664        let result = decoder
665            .big_integer()
666            .expect("should decode major type 1 > i64::MAX");
667        assert_eq!(result.as_ref(), "-18446744073709551616");
668    }
669
670    #[test]
671    fn big_decimal_rfc8949_example() {
672        // RFC 8949 §3.4.4: 273.15 = tag 4([-2, 27315]).
673        let bytes = [0xc4, 0x82, 0x21, 0x19, 0x6a, 0xb3];
674        let mut decoder = Decoder::new(&bytes);
675        let result = decoder.big_decimal().expect("should decode");
676        assert_eq!(result.as_ref(), "273.15");
677    }
678
679    #[test]
680    fn big_decimal_round_trip_preserves_numeric_value() {
681        for value in [
682            "0",
683            "0.0",
684            "-0.0",
685            "+5",
686            ".5",
687            "5.",
688            "1.2300",
689            "-273.15",
690            "1.23e10",
691            "1e2",
692            "1.5E+3",
693            "1E-10",
694            "0.123456789012345678901234567890",
695            "18446744073709551616",
696            "-18446744073709551617",
697        ] {
698            let mut encoder = crate::Encoder::new(Vec::new());
699            encoder.big_decimal(&value.parse().unwrap());
700            let bytes = encoder.into_writer();
701            let mut decoder = Decoder::new(&bytes);
702            let result = decoder.big_decimal().expect("should decode");
703
704            // CBOR decimal fractions preserve the numeric value, but not the
705            // original spelling. BigDecimal equality compares its inner string, so compare the parsed values instead.
706            // See https://github.com/smithy-lang/smithy-rs/issues/4863
707            let actual: bigdecimal::BigDecimal = result.as_ref().parse().unwrap();
708            let expected: bigdecimal::BigDecimal = value.parse().unwrap();
709            assert_eq!(
710                actual,
711                expected,
712                "round-trip failed for {value}, decoded as {}",
713                result.as_ref()
714            );
715        }
716    }
717
718    #[test]
719    fn big_decimal_accepts_indefinite_array() {
720        let bytes = [0xc4, 0x9f, 0x21, 0x19, 0x6a, 0xb3, 0xff];
721        let mut decoder = Decoder::new(&bytes);
722        let result = decoder.big_decimal().expect("should decode");
723        assert_eq!(result.as_ref(), "273.15");
724    }
725
726    #[test]
727    fn big_decimal_rejects_invalid_tag() {
728        let bytes = [0x00, 0xc5, 0x82, 0x00, 0x01];
729        let mut decoder = Decoder::new(&bytes);
730        decoder.set_position(1);
731        let error = decoder.big_decimal().expect_err("tag 5 must be rejected");
732        assert_eq!(
733            error.to_string(),
734            "decode error at position 1: expected CBOR tag 4 (decimal fraction)"
735        );
736    }
737
738    #[test]
739    fn big_decimal_rejects_invalid_array_length() {
740        for bytes in [
741            &[0xc4, 0x81, 0x00][..],
742            &[0xc4, 0x83, 0x00, 0x01, 0x02][..],
743            &[0xc4, 0x9f, 0x00, 0x01, 0x02, 0xff][..],
744        ] {
745            let mut decoder = Decoder::new(bytes);
746            assert!(decoder.big_decimal().is_err());
747        }
748    }
749
750    #[test]
751    fn big_decimal_rejects_non_integer_components() {
752        for bytes in [&[0xc4, 0x82, 0xf4, 0x01][..], &[0xc4, 0x82, 0x00, 0xf4][..]] {
753            let mut decoder = Decoder::new(bytes);
754            assert!(decoder.big_decimal().is_err());
755        }
756    }
757
758    #[test]
759    fn big_decimal_rejects_unsupported_exponents() {
760        for bytes in [
761            // Exponent u64::MAX does not fit in bigdecimal's i64 scale.
762            &[
763                0xc4, 0x82, 0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
764            ][..],
765            // Exponent i64::MIN cannot be negated into a bigdecimal scale.
766            &[
767                0xc4, 0x82, 0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
768            ][..],
769        ] {
770            let mut decoder = Decoder::new(bytes);
771            let error = decoder
772                .big_decimal()
773                .expect_err("unsupported exponent must be rejected");
774            assert_eq!(
775                error.to_string(),
776                "decode error: big decimal fraction exponent is outside the supported range"
777            );
778        }
779    }
780
781    #[test]
782    fn test_timestamp_integer_epoch_seconds() {
783        // RFC 8949 §3.4.2: tag 1 content MUST be int OR float.
784        // tag(1) + uint(1700000000) = 0xc1 0x1a 0x65 0x53 0xf1 0x00
785        let bytes = [0xc1u8, 0x1a, 0x65, 0x53, 0xf1, 0x00];
786        let mut decoder = Decoder::new(&bytes);
787        let timestamp = decoder
788            .timestamp()
789            .expect("should decode integer timestamp");
790        assert_eq!(timestamp, aws_smithy_types::DateTime::from_secs(1700000000));
791    }
792}