Skip to main content

codec_cbor/
decode_deterministic_cbor.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use crate::{
6    deterministic::{try_string_copy, try_vec_with_capacity},
7    DeterministicCborError, DeterministicCborInteger, DeterministicCborMapEntry,
8    DeterministicCborMapKey, DeterministicCborValue,
9    MAX_DETERMINISTIC_CBOR_AGGREGATE_BYTE_STRING_BYTES,
10    MAX_DETERMINISTIC_CBOR_AGGREGATE_TEXT_BYTES, MAX_DETERMINISTIC_CBOR_CONTAINER_ENTRIES,
11    MAX_DETERMINISTIC_CBOR_INPUT_LEN, MAX_DETERMINISTIC_CBOR_NESTING_DEPTH,
12    MAX_DETERMINISTIC_CBOR_NODES,
13};
14use std::cmp::Ordering;
15use std::str;
16
17const MT_UINT: u8 = 0;
18const MT_NEGINT: u8 = 1;
19const MT_BYTES: u8 = 2;
20const MT_STRING: u8 = 3;
21const MT_ARRAY: u8 = 4;
22const MT_MAP: u8 = 5;
23const MT_TAG: u8 = 6;
24const MT_SIMPLE: u8 = 7;
25const SIMPLE_FALSE: u64 = 20;
26const SIMPLE_TRUE: u64 = 21;
27const SIMPLE_NULL: u64 = 22;
28const MIN_ELEMENT_ENCODED_LEN: usize = 1;
29
30/// Decode deterministic generic-CBOR bytes into the supported semantic value.
31///
32/// The decoder is strict: non-minimal integer/length encodings, indefinite
33/// lengths, unsupported major types, duplicate keys, out-of-order keys, invalid
34/// UTF-8, trailing bytes, and limit violations all fail closed.
35pub fn decode_deterministic_cbor(
36    bytes: &[u8],
37) -> Result<DeterministicCborValue, DeterministicCborError> {
38    if bytes.len() > MAX_DETERMINISTIC_CBOR_INPUT_LEN {
39        return Err(DeterministicCborError::InputTooLarge);
40    }
41    let mut limits = DecodeLimits::default();
42    let (value, offset) = decode_value(bytes, 0, 0, &mut limits)?;
43    if offset != bytes.len() {
44        return Err(DeterministicCborError::TrailingBytes);
45    }
46    Ok(value)
47}
48
49#[derive(Default)]
50struct DecodeLimits {
51    nodes: usize,
52    aggregate_text_bytes: usize,
53    aggregate_byte_string_bytes: usize,
54}
55
56impl DecodeLimits {
57    fn add_node(&mut self) -> Result<(), DeterministicCborError> {
58        self.nodes = checked_add(self.nodes, 1)?;
59        if self.nodes > MAX_DETERMINISTIC_CBOR_NODES {
60            return Err(DeterministicCborError::NodeLimitExceeded);
61        }
62        Ok(())
63    }
64
65    fn add_text_bytes(&mut self, len: usize) -> Result<(), DeterministicCborError> {
66        self.aggregate_text_bytes = checked_add(self.aggregate_text_bytes, len)?;
67        if self.aggregate_text_bytes > MAX_DETERMINISTIC_CBOR_AGGREGATE_TEXT_BYTES {
68            return Err(DeterministicCborError::AggregateTextBytesExceeded);
69        }
70        Ok(())
71    }
72
73    fn add_byte_string_bytes(&mut self, len: usize) -> Result<(), DeterministicCborError> {
74        self.aggregate_byte_string_bytes = checked_add(self.aggregate_byte_string_bytes, len)?;
75        if self.aggregate_byte_string_bytes > MAX_DETERMINISTIC_CBOR_AGGREGATE_BYTE_STRING_BYTES {
76            return Err(DeterministicCborError::AggregateByteStringBytesExceeded);
77        }
78        Ok(())
79    }
80}
81
82fn decode_value(
83    bytes: &[u8],
84    offset: usize,
85    depth: usize,
86    limits: &mut DecodeLimits,
87) -> Result<(DeterministicCborValue, usize), DeterministicCborError> {
88    limits.add_node()?;
89    let (major, argument, mut offset) = read_head(bytes, offset)?;
90
91    match major {
92        MT_UINT => Ok((
93            DeterministicCborValue::Integer(DeterministicCborInteger::unsigned(argument)),
94            offset,
95        )),
96        MT_NEGINT => Ok((
97            DeterministicCborValue::Integer(DeterministicCborInteger::negative(negative_value(
98                argument,
99            )?)?),
100            offset,
101        )),
102        MT_BYTES => {
103            let (value, next) = extract_bytes(bytes, offset, argument, limits)?;
104            Ok((DeterministicCborValue::Bytes(value), next))
105        }
106        MT_STRING => {
107            let (value, next) = extract_string(bytes, offset, argument, limits)?;
108            Ok((DeterministicCborValue::Text(value), next))
109        }
110        MT_ARRAY => {
111            let child_depth = descend(depth)?;
112            let item_count = container_count(argument)?;
113            bounded_capacity(item_count, bytes.len(), offset, MIN_ELEMENT_ENCODED_LEN)?;
114            // The declared count has already been bounded both semantically
115            // and against the remaining input. Exact allocation prevents Vec
116            // growth from abandoning earlier identity-bearing owners in
117            // allocator blocks that the final owner cannot wipe.
118            let mut values = try_vec_with_capacity(item_count)?;
119            for _ in 0..item_count {
120                let (value, next) = decode_value(bytes, offset, child_depth, limits)?;
121                values.push(value);
122                offset = next;
123            }
124            Ok((DeterministicCborValue::Array(values), offset))
125        }
126        MT_MAP => {
127            let child_depth = descend(depth)?;
128            let entry_count = container_count(argument)?;
129            let min_entry_len = checked_mul(MIN_ELEMENT_ENCODED_LEN, 2)?;
130            bounded_capacity(entry_count, bytes.len(), offset, min_entry_len)?;
131            let mut entries = try_vec_with_capacity(entry_count)?;
132            let mut previous_key_range: Option<(usize, usize)> = None;
133
134            for _ in 0..entry_count {
135                let key_start = offset;
136                let (key, key_end) = decode_key(bytes, offset, limits)?;
137                if let Some((previous_start, previous_end)) = previous_key_range {
138                    let previous_key = checked_slice(bytes, previous_start, previous_end)?;
139                    let current_key = checked_slice(bytes, key_start, key_end)?;
140                    match compare_encoded_keys(previous_key, current_key) {
141                        Ordering::Less => {}
142                        Ordering::Equal => return Err(DeterministicCborError::DuplicateMapKey),
143                        Ordering::Greater => {
144                            return Err(DeterministicCborError::MapKeysOutOfOrder);
145                        }
146                    }
147                }
148                previous_key_range = Some((key_start, key_end));
149                offset = key_end;
150
151                let (value, next) = decode_value(bytes, offset, child_depth, limits)?;
152                entries.push(DeterministicCborMapEntry::new(key, value));
153                offset = next;
154            }
155            Ok((DeterministicCborValue::Map(entries), offset))
156        }
157        MT_SIMPLE => match argument {
158            SIMPLE_FALSE => Ok((DeterministicCborValue::Bool(false), offset)),
159            SIMPLE_TRUE => Ok((DeterministicCborValue::Bool(true), offset)),
160            SIMPLE_NULL => Ok((DeterministicCborValue::Null, offset)),
161            _ => Err(DeterministicCborError::UnsupportedSimpleValue),
162        },
163        _ => Err(DeterministicCborError::UnsupportedMajorType),
164    }
165}
166
167fn decode_key(
168    bytes: &[u8],
169    offset: usize,
170    limits: &mut DecodeLimits,
171) -> Result<(DeterministicCborMapKey, usize), DeterministicCborError> {
172    limits.add_node()?;
173    let (major, argument, offset) = read_head(bytes, offset)?;
174    match major {
175        MT_UINT => Ok((
176            DeterministicCborMapKey::Integer(DeterministicCborInteger::unsigned(argument)),
177            offset,
178        )),
179        MT_NEGINT => Ok((
180            DeterministicCborMapKey::Integer(DeterministicCborInteger::negative(negative_value(
181                argument,
182            )?)?),
183            offset,
184        )),
185        MT_STRING => {
186            let (value, next) = extract_string(bytes, offset, argument, limits)?;
187            Ok((DeterministicCborMapKey::text(value), next))
188        }
189        _ => Err(DeterministicCborError::UnsupportedMapKeyType),
190    }
191}
192
193fn read_head(bytes: &[u8], offset: usize) -> Result<(u8, u64, usize), DeterministicCborError> {
194    if offset >= bytes.len() {
195        return Err(DeterministicCborError::UnexpectedEnd);
196    }
197    let first = *bytes
198        .get(offset)
199        .ok_or(DeterministicCborError::UnexpectedEnd)?;
200    let next_offset = checked_add(offset, 1)?;
201    let major = first >> 5;
202    let additional = first & 0x1f;
203    // Tags and floating-point/simple extensions are outside this closed
204    // profile. Reject them from the initial byte alone so float payload bits
205    // are never misinterpreted as an integer argument and misclassified as a
206    // canonical-integer failure.
207    if major == MT_TAG {
208        return Err(DeterministicCborError::UnsupportedMajorType);
209    }
210    if major == MT_SIMPLE && additional >= 24 {
211        return match additional {
212            24..=27 => Err(DeterministicCborError::UnsupportedSimpleValue),
213            _ => Err(DeterministicCborError::UnsupportedAdditionalInfo),
214        };
215    }
216    let (argument, after_argument) = read_argument(bytes, next_offset, additional)?;
217    Ok((major, argument, after_argument))
218}
219
220fn read_argument(
221    bytes: &[u8],
222    offset: usize,
223    additional: u8,
224) -> Result<(u64, usize), DeterministicCborError> {
225    match additional {
226        value @ 0..=23 => Ok((u64::from(value), offset)),
227        24 => {
228            let end = checked_end(offset, 1)?;
229            let value = u64::from(
230                *bytes
231                    .get(offset)
232                    .ok_or(DeterministicCborError::TruncatedArgument)?,
233            );
234            if value < 24 {
235                return Err(DeterministicCborError::NonCanonicalInteger);
236            }
237            Ok((value, end))
238        }
239        25 => {
240            let end = checked_end(offset, 2)?;
241            let encoded = checked_argument_slice(bytes, offset, end)?;
242            let value = u16::from_be_bytes(
243                <[u8; 2]>::try_from(encoded)
244                    .map_err(|_| DeterministicCborError::TruncatedArgument)?,
245            );
246            if value < 0x100 {
247                return Err(DeterministicCborError::NonCanonicalInteger);
248            }
249            Ok((u64::from(value), end))
250        }
251        26 => {
252            let end = checked_end(offset, 4)?;
253            let encoded = checked_argument_slice(bytes, offset, end)?;
254            let value = u32::from_be_bytes(
255                <[u8; 4]>::try_from(encoded)
256                    .map_err(|_| DeterministicCborError::TruncatedArgument)?,
257            );
258            if value < 0x1_0000 {
259                return Err(DeterministicCborError::NonCanonicalInteger);
260            }
261            Ok((u64::from(value), end))
262        }
263        27 => {
264            let end = checked_end(offset, 8)?;
265            let encoded = checked_argument_slice(bytes, offset, end)?;
266            let value = u64::from_be_bytes(
267                <[u8; 8]>::try_from(encoded)
268                    .map_err(|_| DeterministicCborError::TruncatedArgument)?,
269            );
270            if value < 0x1_0000_0000 {
271                return Err(DeterministicCborError::NonCanonicalInteger);
272            }
273            Ok((value, end))
274        }
275        _ => Err(DeterministicCborError::UnsupportedAdditionalInfo),
276    }
277}
278
279fn extract_bytes(
280    bytes: &[u8],
281    offset: usize,
282    len: u64,
283    limits: &mut DecodeLimits,
284) -> Result<(Vec<u8>, usize), DeterministicCborError> {
285    let len = usize::try_from(len).map_err(|_| DeterministicCborError::LengthTooLarge)?;
286    limits.add_byte_string_bytes(len)?;
287    let end = checked_end(offset, len)?;
288    let encoded =
289        checked_slice(bytes, offset, end).map_err(|_| DeterministicCborError::TruncatedBytes)?;
290    let mut value = try_vec_with_capacity(len)?;
291    value.extend_from_slice(encoded);
292    Ok((value, end))
293}
294
295fn extract_string(
296    bytes: &[u8],
297    offset: usize,
298    len: u64,
299    limits: &mut DecodeLimits,
300) -> Result<(String, usize), DeterministicCborError> {
301    let len = usize::try_from(len).map_err(|_| DeterministicCborError::LengthTooLarge)?;
302    limits.add_text_bytes(len)?;
303    let end = checked_end(offset, len)?;
304    let encoded =
305        checked_slice(bytes, offset, end).map_err(|_| DeterministicCborError::TruncatedBytes)?;
306    let text =
307        try_string_copy(str::from_utf8(encoded).map_err(|_| DeterministicCborError::InvalidUtf8)?)?;
308    Ok((text, end))
309}
310
311fn negative_value(argument: u64) -> Result<i64, DeterministicCborError> {
312    let value = (-1_i128)
313        .checked_sub(i128::from(argument))
314        .ok_or(DeterministicCborError::NegativeIntegerOutOfRange)?;
315    i64::try_from(value).map_err(|_| DeterministicCborError::NegativeIntegerOutOfRange)
316}
317
318fn container_count(argument: u64) -> Result<usize, DeterministicCborError> {
319    let count = usize::try_from(argument).map_err(|_| DeterministicCborError::LengthTooLarge)?;
320    if count > MAX_DETERMINISTIC_CBOR_CONTAINER_ENTRIES {
321        return Err(DeterministicCborError::ContainerEntriesExceeded);
322    }
323    Ok(count)
324}
325
326fn bounded_capacity(
327    count: usize,
328    total_len: usize,
329    offset: usize,
330    min_element_len: usize,
331) -> Result<(), DeterministicCborError> {
332    let remaining = checked_sub(total_len, offset)?;
333    let max_possible = remaining / min_element_len.max(1);
334    if count > max_possible {
335        return Err(DeterministicCborError::ContainerLengthExceedsInput);
336    }
337    Ok(())
338}
339
340fn compare_encoded_keys(left: &[u8], right: &[u8]) -> Ordering {
341    match left.len().cmp(&right.len()) {
342        Ordering::Equal => left.cmp(right),
343        ordering => ordering,
344    }
345}
346
347fn descend(depth: usize) -> Result<usize, DeterministicCborError> {
348    let next = checked_add(depth, 1)?;
349    if next > MAX_DETERMINISTIC_CBOR_NESTING_DEPTH {
350        return Err(DeterministicCborError::DepthExceeded);
351    }
352    Ok(next)
353}
354
355fn checked_end(offset: usize, len: usize) -> Result<usize, DeterministicCborError> {
356    checked_add(offset, len)
357}
358
359fn checked_argument_slice(
360    bytes: &[u8],
361    start: usize,
362    end: usize,
363) -> Result<&[u8], DeterministicCborError> {
364    checked_slice(bytes, start, end).map_err(|_| DeterministicCborError::TruncatedArgument)
365}
366
367fn checked_slice(bytes: &[u8], start: usize, end: usize) -> Result<&[u8], DeterministicCborError> {
368    bytes
369        .get(start..end)
370        .ok_or(DeterministicCborError::OffsetOverflow)
371}
372
373fn checked_add(left: usize, right: usize) -> Result<usize, DeterministicCborError> {
374    left.checked_add(right)
375        .ok_or(DeterministicCborError::OffsetOverflow)
376}
377
378fn checked_sub(left: usize, right: usize) -> Result<usize, DeterministicCborError> {
379    left.checked_sub(right)
380        .ok_or(DeterministicCborError::OffsetOverflow)
381}
382
383fn checked_mul(left: usize, right: usize) -> Result<usize, DeterministicCborError> {
384    left.checked_mul(right)
385        .ok_or(DeterministicCborError::OffsetOverflow)
386}