Skip to main content

vest_lib/cbor/
mod.rs

1//! Concise Binary Object Representation (CBOR) formats.
2//!
3//! This module implements the basic generic data model from RFC 8949.
4//! It is allocation-gated because recursive generic values necessarily contain
5//! `Box`es and `Vec`s.
6//! Definite byte and text strings borrow directly from the input;
7//! allocation is needed only for fragmented strings and recursive items.
8//!
9//! [`CborFmt<false>`] accepts the well-formed representation variants described
10//! by RFC 8949. [`CborFmt<true>`] additionally requires preferred integer,
11//! length, and tag arguments and rejects indefinite-length items (RFC 8949
12//! section 4.2.1).
13//!
14//! ## Limitations
15//!
16//! - Map-key ordering is not yet enforced in [`CborFmt<true>`].
17//! - Floating-point widths are retained in [`CborFloat`], so shortest-width
18//! floating-point normalization is likewise not yet imposed.
19//!
20//! See the [CBOR guide](https://secure-foundations.github.io/vest/guide/cbor.html)
21//! for the runtime workflow, ownership model, and deterministic-profile scope.
22mod chunk;
23pub mod format;
24mod head;
25mod value;
26
27pub use format::CborFmt;
28pub use head::{
29    BreakFmt, CborHead, CborHeadFmt, CborHeadValue, CborInitial, CborInitialFmt, MajorType, BREAK,
30};
31pub use value::{CborArray, CborBytes, CborFloat, CborMap, CborText, CborValue, CborValueSpec};
32
33/// Default maximum nesting depth for generic CBOR values.
34pub const MAX_RECURSION_DEPTH: usize = 30;
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use crate::core::exec::{ByteLen, Parser, Prepare, SerializerExt};
40    use alloc::borrow::ToOwned;
41
42    const GENERAL: bool = false;
43    const DETERMINISTIC: bool = true;
44
45    #[test]
46    fn rfc8949_major_types_conformance() {
47        let format = CborFmt::<GENERAL>;
48
49        // Major 0: Unsigned integers
50        assert_eq!(format.parse(&&[0x00][..]), Ok((1, CborValue::Integer(0))));
51        assert_eq!(format.parse(&&[0x17][..]), Ok((1, CborValue::Integer(23))));
52        assert_eq!(format.parse(&&[0x18, 0x18][..]), Ok((2, CborValue::Integer(24))));
53        assert_eq!(format.parse(&&[0x19, 0x01, 0x00][..]), Ok((3, CborValue::Integer(256))));
54        assert_eq!(
55            format.parse(&&[0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff][..]),
56            Ok((9, CborValue::Integer(u64::MAX as i128)))
57        );
58
59        // Major 1: Negative integers (-1 - n)
60        assert_eq!(format.parse(&&[0x20][..]), Ok((1, CborValue::Integer(-1))));
61        assert_eq!(format.parse(&&[0x37][..]), Ok((1, CborValue::Integer(-24))));
62        assert_eq!(format.parse(&&[0x38, 0x18][..]), Ok((2, CborValue::Integer(-25))));
63        assert_eq!(format.parse(&&[0x39, 0x03, 0xe7][..]), Ok((3, CborValue::Integer(-1000))));
64        assert_eq!(
65            format.parse(&&[0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff][..]),
66            Ok((9, CborValue::Integer(-1i128 - u64::MAX as i128)))
67        );
68
69        // Major 2: Byte strings (definite, zero-copy borrow)
70        let raw_bytes = [0x44, 0x01, 0x02, 0x03, 0x04];
71        let (consumed, parsed) = format.parse(&&raw_bytes[..]).unwrap();
72        assert_eq!(consumed, 5);
73        match parsed {
74            CborValue::Bytes(CborBytes::Definite(slice)) => {
75                assert_eq!(slice, &[1, 2, 3, 4]);
76                assert_eq!(slice.as_ptr(), raw_bytes[1..].as_ptr());
77            }
78            _ => panic!("expected definite byte string"),
79        }
80
81        // Major 3: Text strings (definite, zero-copy UTF-8 borrow)
82        let raw_text = [0x63, 0xe6, 0xb0, 0xb4]; // UTF-8 for '水'
83        let (consumed, parsed) = format.parse(&&raw_text[..]).unwrap();
84        assert_eq!(consumed, 4);
85        match parsed {
86            CborValue::Text(CborText::Definite(s)) => {
87                assert_eq!(s, "水");
88                assert_eq!(s.as_ptr(), raw_text[1..].as_ptr());
89            }
90            _ => panic!("expected definite text string"),
91        }
92
93        // Major 4: Arrays
94        assert_eq!(format.parse(&&[0x80][..]), Ok((1, CborValue::Array(vec![]))));
95        assert_eq!(
96            format.parse(&&[0x82, 0x01, 0x02][..]),
97            Ok((3, CborValue::Array(vec![CborValue::Integer(1), CborValue::Integer(2)])))
98        );
99
100        // Major 5: Maps
101        assert_eq!(format.parse(&&[0xa0][..]), Ok((1, CborValue::Map(vec![]))));
102        assert_eq!(
103            format.parse(&&[0xa1, 0x61, b'a', 0x01][..]),
104            Ok((
105                4,
106                CborValue::Map(vec![(
107                    CborValue::Text(CborText::Definite("a")),
108                    CborValue::Integer(1)
109                )])
110            ))
111        );
112
113        // Major 6: Tags
114        assert_eq!(
115            format.parse(&&[0xc0, 0x60][..]),
116            Ok((2, CborValue::Tag(0, alloc::boxed::Box::new(CborValue::Text(CborText::Definite(""))))))
117        );
118
119        // Major 7: Simple values and Floats
120        assert_eq!(format.parse(&&[0xf4][..]), Ok((1, CborValue::Bool(false))));
121        assert_eq!(format.parse(&&[0xf5][..]), Ok((1, CborValue::Bool(true))));
122        assert_eq!(format.parse(&&[0xf6][..]), Ok((1, CborValue::Null)));
123        assert_eq!(format.parse(&&[0xf7][..]), Ok((1, CborValue::Undefined)));
124        assert_eq!(format.parse(&&[0xf0][..]), Ok((1, CborValue::Simple(16))));
125        assert_eq!(format.parse(&&[0xf8, 0x20][..]), Ok((2, CborValue::Simple(32))));
126        assert_eq!(format.parse(&&[0xf9, 0x3c, 0x00][..]), Ok((3, CborValue::Float(CborFloat::F16(0x3c00)))));
127        assert_eq!(format.parse(&&[0xfa, 0x47, 0xc3, 0x50, 0x00][..]), Ok((5, CborValue::Float(CborFloat::F32(0x47c35000)))));
128        assert_eq!(format.parse(&&[0xfb, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00][..]), Ok((9, CborValue::Float(CborFloat::F64(0x3ff8000000000000)))));
129    }
130
131    #[test]
132    fn indefinite_streaming_and_chunk_security() {
133        let format = CborFmt::<GENERAL>;
134
135        // Valid indefinite byte string and text string (flattening)
136        let byte_chunks = [0x5f, 0x42, 0x01, 0x02, 0x41, 0x03, 0xff];
137        assert_eq!(
138            format.parse(&&byte_chunks[..]),
139            Ok((byte_chunks.len(), CborValue::Bytes(CborBytes::Indefinite(vec![1, 2, 3]))))
140        );
141
142        let text_chunks = [0x7f, 0x62, b'h', b'i', 0x61, b'!', 0xff];
143        assert_eq!(
144            format.parse(&&text_chunks[..]),
145            Ok((text_chunks.len(), CborValue::Text(CborText::Indefinite("hi!".to_owned()))))
146        );
147
148        // Valid indefinite array and map
149        let array = [0x9f, 0x01, 0x02, 0xff];
150        assert_eq!(
151            format.parse(&&array[..]),
152            Ok((array.len(), CborValue::Array(vec![CborValue::Integer(1), CborValue::Integer(2)])))
153        );
154
155        let map = [0xbf, 0x61, b'k', 0x01, 0xff];
156        assert_eq!(
157            format.parse(&&map[..]),
158            Ok((
159                map.len(),
160                CborValue::Map(vec![(
161                    CborValue::Text(CborText::Definite("k")),
162                    CborValue::Integer(1)
163                )])
164            ))
165        );
166
167        // RFC 8949 §3.2.3 Chunk Security Violations (Must be rejected)
168        // 1. Nested indefinite string chunks are forbidden
169        assert!(format.parse(&&[0x5f, 0x5f, 0x41, 0x01, 0xff, 0xff][..]).is_err());
170
171        // 2. Major type mismatch inside indefinite chunk
172        assert!(format.parse(&&[0x5f, 0x61, b'x', 0xff][..]).is_err());
173        assert!(format.parse(&&[0x7f, 0x41, 0x01, 0xff][..]).is_err());
174        assert!(format.parse(&&[0x5f, 0x01, 0xff][..]).is_err());
175
176        // 3. UTF-8 code point split across chunks (each text chunk must be valid UTF-8)
177        let split_utf8 = [0x7f, 0x61, 0xc2, 0x61, 0xa2, 0xff];
178        assert!(format.parse(&&split_utf8[..]).is_err());
179
180        // 4. Odd number of items in indefinite map (break cannot substitute map value)
181        assert!(format.parse(&&[0xbf, 0x01, 0xff][..]).is_err());
182
183        // 5. Standalone or misplaced break byte
184        assert!(format.parse(&&[0xff][..]).is_err());
185        assert!(format.parse(&&[0x81, 0xff][..]).is_err());
186    }
187
188    #[test]
189    fn deterministic_dcbor_vs_general_cbor() {
190        let det = CborFmt::<DETERMINISTIC>;
191        let gen = CborFmt::<GENERAL>;
192
193        // Non-minimal unsigned integers (e.g. 23 encoded in 2 bytes)
194        let non_minimal_uint = [0x18, 0x17];
195        assert!(det.parse(&&non_minimal_uint[..]).is_err());
196        assert_eq!(gen.parse(&&non_minimal_uint[..]), Ok((2, CborValue::Integer(23))));
197
198        // Non-minimal negative integers (e.g. -24 encoded in 2 bytes)
199        let non_minimal_neg = [0x38, 0x17];
200        assert!(det.parse(&&non_minimal_neg[..]).is_err());
201        assert_eq!(gen.parse(&&non_minimal_neg[..]), Ok((2, CborValue::Integer(-24))));
202
203        // Non-minimal length header
204        let non_minimal_bstr_len = [0x58, 0x01, 0xaa];
205        assert!(det.parse(&&non_minimal_bstr_len[..]).is_err());
206        assert!(gen.parse(&&non_minimal_bstr_len[..]).is_ok());
207
208        // Non-minimal tag header
209        let non_minimal_tag = [0xd8, 0x01, 0x00];
210        assert!(det.parse(&&non_minimal_tag[..]).is_err());
211        assert!(gen.parse(&&non_minimal_tag[..]).is_ok());
212
213        // Indefinite-length framing is strictly rejected in deterministic mode
214        assert!(det.parse(&&[0x5f, 0x41, 0x01, 0xff][..]).is_err());
215        assert!(det.parse(&&[0x7f, 0x61, b'a', 0xff][..]).is_err());
216        assert!(det.parse(&&[0x9f, 0x01, 0xff][..]).is_err());
217        assert!(det.parse(&&[0xbf, 0x01, 0x02, 0xff][..]).is_err());
218    }
219
220    #[test]
221    fn truncation_and_malformed_input_robustness() {
222        let format = CborFmt::<GENERAL>;
223
224        // Truncated header arguments
225        assert!(format.parse(&&[0x18][..]).is_err());
226        assert!(format.parse(&&[0x19, 0x01][..]).is_err());
227        assert!(format.parse(&&[0x1a, 0x00, 0x01][..]).is_err());
228        assert!(format.parse(&&[0x1b, 0x00, 0x00, 0x00, 0x01][..]).is_err());
229
230        // Truncated string payloads
231        assert!(format.parse(&&[0x45, 0x01, 0x02, 0x03][..]).is_err()); // claims 5 bytes, provides 3
232        assert!(format.parse(&&[0x65, b'a', b'b'][..]).is_err()); // claims 5 bytes, provides 2
233
234        // Truncated containers
235        assert!(format.parse(&&[0x83, 0x01, 0x02][..]).is_err()); // claims 3 items, provides 2
236        assert!(format.parse(&&[0xa1, 0x01][..]).is_err()); // key without value
237
238        // Unclosed indefinite containers
239        assert!(format.parse(&&[0x9f, 0x01, 0x02][..]).is_err());
240        assert!(format.parse(&&[0x5f, 0x41, 0x01][..]).is_err());
241
242        // Reserved additional information values (28..30)
243        assert!(format.parse(&&[0x1c][..]).is_err());
244        assert!(format.parse(&&[0x1d][..]).is_err());
245        assert!(format.parse(&&[0x1e][..]).is_err());
246    }
247
248    #[test]
249    fn recursion_depth_limit_defense() {
250        // 4-level nested array: [[[[1]]]] -> [0x81, 0x81, 0x81, 0x81, 0x01]
251        let deeply_nested = [0x81, 0x81, 0x81, 0x81, 0x01];
252
253        // Allowed when nesting budget is sufficient
254        let format_deep = CborFmt::<GENERAL, 6>;
255        assert!(format_deep.parse(&&deeply_nested[..]).is_ok());
256
257        // Rejected when depth limit is exceeded (prevents stack overflow)
258        let format_shallow = CborFmt::<GENERAL, 3>;
259        assert!(format_shallow.parse(&&deeply_nested[..]).is_err());
260    }
261
262    #[test]
263    fn serialization_and_roundtrip_invariants() {
264        let format = CborFmt::<DETERMINISTIC, 8>;
265
266        let value = CborValue::Map(vec![
267            (
268                CborValue::Integer(1),
269                CborValue::Array(vec![CborValue::Bool(true), CborValue::Null]),
270            ),
271            (
272                CborValue::Text(CborText::Definite("tag")),
273                CborValue::Tag(42, alloc::boxed::Box::new(CborValue::Integer(100))),
274            ),
275        ]);
276
277        // Pre-serialization size bound agreement
278        let len = format.prepare(&value).unwrap();
279        assert_eq!(format.length(&value), len);
280
281        // In-place serialization
282        let mut buffer = vec![0u8; len];
283        format.serialize(&value, buffer.as_mut_slice());
284
285        // Round-trip invertibility: parse(serialize(v)) == v
286        assert_eq!(format.parse(&&buffer[..]), Ok((len, value)));
287
288        // Serialization normalizes indefinite-length strings to definite encoding
289        let gen_format = CborFmt::<GENERAL, 8>;
290        let indefinite_bytes = CborValue::Bytes(CborBytes::Indefinite(vec![1, 2, 3]));
291        let len = gen_format.prepare(&indefinite_bytes).unwrap();
292        let mut buffer = vec![0u8; len];
293        gen_format.serialize(&indefinite_bytes, buffer.as_mut_slice());
294        assert_eq!(buffer, [0x43, 0x01, 0x02, 0x03]);
295    }
296}