use core::marker::PhantomData;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(not(feature = "alloc"))]
use crate::limits::DEFAULT_MAX_DEPTH;
use crate::profile::{
check_encoded_key_order, validate_bignum_bytes, validate_f64_bits, MAX_SAFE_INTEGER,
};
use crate::utf8;
use crate::{CborError, DecodeLimits, ErrorCode};
pub trait DecodeError: Sized {
fn new(code: ErrorCode, offset: usize) -> Self;
}
impl DecodeError for CborError {
#[inline]
fn new(code: ErrorCode, offset: usize) -> Self {
Self::new(code, offset)
}
}
pub struct Cursor<'a, E: DecodeError> {
data: &'a [u8],
pos: usize,
_marker: PhantomData<E>,
}
impl<'a, E: DecodeError> Cursor<'a, E> {
#[inline]
pub const fn with_pos(data: &'a [u8], pos: usize) -> Self {
Self {
data,
pos,
_marker: PhantomData,
}
}
#[inline]
pub const fn position(&self) -> usize {
self.pos
}
#[inline]
pub const fn data(&self) -> &'a [u8] {
self.data
}
#[inline]
pub fn read_u8(&mut self) -> Result<u8, E> {
read_u8_at(self.data, &mut self.pos)
}
#[inline]
pub fn read_exact(&mut self, n: usize) -> Result<&'a [u8], E> {
read_exact_at(self.data, &mut self.pos, n)
}
#[inline]
pub fn read_be_u64(&mut self) -> Result<u64, E> {
let s = self.read_exact(8)?;
Ok(u64::from_be_bytes([
s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7],
]))
}
}
#[inline]
fn read_u8_at<E: DecodeError>(data: &[u8], pos: &mut usize) -> Result<u8, E> {
let off = *pos;
let b = *data
.get(*pos)
.ok_or_else(|| E::new(ErrorCode::UnexpectedEof, off))?;
*pos += 1;
Ok(b)
}
#[inline]
fn read_exact_at<'a, E: DecodeError>(
data: &'a [u8],
pos: &mut usize,
n: usize,
) -> Result<&'a [u8], E> {
let off = *pos;
let end = pos
.checked_add(n)
.ok_or_else(|| E::new(ErrorCode::LengthOverflow, off))?;
if end > data.len() {
return Err(E::new(ErrorCode::UnexpectedEof, off));
}
let s = &data[*pos..end];
*pos = end;
Ok(s)
}
pub fn read_u8(data: &[u8], pos: &mut usize) -> Result<u8, CborError> {
read_u8_at::<CborError>(data, pos)
}
pub fn read_exact<'a>(data: &'a [u8], pos: &mut usize, n: usize) -> Result<&'a [u8], CborError> {
read_exact_at::<CborError>(data, pos, n)
}
#[inline]
pub fn read_uint_arg_at<const CHECKED: bool, E: DecodeError>(
data: &[u8],
pos: &mut usize,
ai: u8,
off: usize,
) -> Result<u64, E> {
match ai {
0..=23 => Ok(u64::from(ai)),
24 => {
let v = u64::from(read_u8_at::<E>(data, pos)?);
if CHECKED && v < 24 {
return Err(E::new(ErrorCode::NonCanonicalEncoding, off));
}
Ok(v)
}
25 => {
let v = u64::from({
let s = read_exact_at::<E>(data, pos, 2)?;
u16::from_be_bytes([s[0], s[1]])
});
if CHECKED && u8::try_from(v).is_ok() {
return Err(E::new(ErrorCode::NonCanonicalEncoding, off));
}
Ok(v)
}
26 => {
let v = u64::from({
let s = read_exact_at::<E>(data, pos, 4)?;
u32::from_be_bytes([s[0], s[1], s[2], s[3]])
});
if CHECKED && u16::try_from(v).is_ok() {
return Err(E::new(ErrorCode::NonCanonicalEncoding, off));
}
Ok(v)
}
27 => {
let v = {
let s = read_exact_at::<E>(data, pos, 8)?;
u64::from_be_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]])
};
if CHECKED && u32::try_from(v).is_ok() {
return Err(E::new(ErrorCode::NonCanonicalEncoding, off));
}
Ok(v)
}
_ => Err(E::new(ErrorCode::ReservedAdditionalInfo, off)),
}
}
#[inline]
pub fn read_uint_arg<const CHECKED: bool, E: DecodeError>(
cursor: &mut Cursor<'_, E>,
ai: u8,
off: usize,
) -> Result<u64, E> {
read_uint_arg_at::<CHECKED, E>(cursor.data, &mut cursor.pos, ai, off)
}
#[inline]
pub fn read_len_at<const CHECKED: bool, E: DecodeError>(
data: &[u8],
pos: &mut usize,
ai: u8,
off: usize,
) -> Result<usize, E> {
if ai == 31 {
return Err(E::new(ErrorCode::IndefiniteLengthForbidden, off));
}
let len = read_uint_arg_at::<CHECKED, E>(data, pos, ai, off)?;
usize::try_from(len).map_err(|_| E::new(ErrorCode::LengthOverflow, off))
}
#[inline]
pub fn read_len<const CHECKED: bool, E: DecodeError>(
cursor: &mut Cursor<'_, E>,
ai: u8,
off: usize,
) -> Result<usize, E> {
read_len_at::<CHECKED, E>(cursor.data, &mut cursor.pos, ai, off)
}
pub fn read_uint_trusted(
data: &[u8],
pos: &mut usize,
ai: u8,
off: usize,
) -> Result<u64, CborError> {
read_uint_arg_at::<false, CborError>(data, pos, ai, off)
}
pub fn read_len_trusted(
data: &[u8],
pos: &mut usize,
ai: u8,
off: usize,
) -> Result<usize, CborError> {
read_len_at::<false, CborError>(data, pos, ai, off)
}
#[inline]
pub fn parse_text_from_header<'a, const CHECKED: bool, E: DecodeError>(
cursor: &mut Cursor<'a, E>,
limits: Option<&DecodeLimits>,
off: usize,
ai: u8,
) -> Result<&'a str, E> {
let len = read_len::<CHECKED, E>(cursor, ai, off)?;
if let Some(limits) = limits {
if len > limits.max_text_len {
return Err(E::new(ErrorCode::TextLenLimitExceeded, off));
}
}
let bytes = cursor.read_exact(len)?;
let s = if CHECKED {
utf8::validate(bytes).map_err(|()| E::new(ErrorCode::Utf8Invalid, off))?
} else {
utf8::trusted(bytes).map_err(|()| E::new(ErrorCode::Utf8Invalid, off))?
};
Ok(s)
}
#[inline]
pub fn parse_bignum<'a, const CHECKED: bool, E: DecodeError>(
cursor: &mut Cursor<'a, E>,
limits: Option<&DecodeLimits>,
off: usize,
ai: u8,
) -> Result<(bool, &'a [u8]), E> {
let tag = read_uint_arg::<CHECKED, E>(cursor, ai, off)?;
let negative = match tag {
2 => false,
3 => true,
_ => return Err(E::new(ErrorCode::ForbiddenOrMalformedTag, off)),
};
let m_off = cursor.position();
let first = cursor.read_u8()?;
let m_major = first >> 5;
let m_ai = first & 0x1f;
if m_major != 2 {
return Err(E::new(ErrorCode::ForbiddenOrMalformedTag, m_off));
}
let m_len = read_len::<CHECKED, E>(cursor, m_ai, m_off)?;
if let Some(limits) = limits {
if m_len > limits.max_bytes_len {
return Err(E::new(ErrorCode::BytesLenLimitExceeded, m_off));
}
}
let mag = cursor.read_exact(m_len)?;
if CHECKED {
validate_bignum_bytes(negative, mag).map_err(|code| E::new(code, m_off))?;
}
Ok((negative, mag))
}
#[inline]
pub fn check_map_key_order<E: DecodeError>(
data: &[u8],
prev_key_range: &mut Option<(usize, usize)>,
key_start: usize,
key_end: usize,
) -> Result<(), E> {
if let Some((ps, pe)) = *prev_key_range {
let prev = &data[ps..pe];
let curr = &data[key_start..key_end];
if let Err(code) = check_encoded_key_order(prev, curr) {
return Err(E::new(code, key_start));
}
}
*prev_key_range = Some((key_start, key_end));
Ok(())
}
#[derive(Clone, Copy)]
enum Frame {
Array {
remaining: usize,
},
Map {
remaining_pairs: usize,
expecting_key: bool,
prev_key_range: Option<(usize, usize)>,
},
}
impl Frame {
#[inline]
const fn is_done(&self) -> bool {
match self {
Self::Array { remaining } => *remaining == 0,
Self::Map {
remaining_pairs,
expecting_key,
..
} => *remaining_pairs == 0 && *expecting_key,
}
}
}
trait StackOps {
fn is_empty(&self) -> bool;
fn push<E: DecodeError>(&mut self, frame: Frame, off: usize) -> Result<(), E>;
fn pop(&mut self) -> Option<Frame>;
fn peek(&self) -> Option<&Frame>;
fn peek_mut(&mut self) -> Option<&mut Frame>;
}
#[cfg(feature = "alloc")]
const INLINE_FRAMES: usize = 32;
#[cfg(feature = "alloc")]
struct FrameStack {
inline: [Frame; INLINE_FRAMES],
len: usize,
heap: Option<Vec<Frame>>,
}
#[cfg(feature = "alloc")]
impl FrameStack {
const fn new() -> Self {
Self {
inline: [Frame::Array { remaining: 0 }; INLINE_FRAMES],
len: 0,
heap: None,
}
}
#[inline]
fn clear(&mut self) {
self.len = 0;
if let Some(items) = &mut self.heap {
items.clear();
}
}
#[inline]
fn is_empty(&self) -> bool {
self.heap.as_ref().map_or(self.len == 0, Vec::is_empty)
}
#[inline]
fn push<E: DecodeError>(&mut self, frame: Frame, off: usize) -> Result<(), E> {
match &mut self.heap {
Some(items) => {
try_reserve_vec::<Frame, E>(items, 1, off)?;
items.push(frame);
}
None => {
if self.len < INLINE_FRAMES {
self.inline[self.len] = frame;
self.len += 1;
} else {
let mut items = Vec::new();
try_reserve_vec::<Frame, E>(&mut items, self.len + 1, off)?;
items.extend_from_slice(&self.inline[..self.len]);
items.push(frame);
self.heap = Some(items);
}
}
}
Ok(())
}
#[inline]
fn pop(&mut self) -> Option<Frame> {
match &mut self.heap {
Some(items) => items.pop(),
None => {
if self.len == 0 {
None
} else {
self.len -= 1;
Some(self.inline[self.len])
}
}
}
}
#[inline]
#[allow(clippy::option_if_let_else)]
fn peek(&self) -> Option<&Frame> {
match &self.heap {
Some(items) => items.last(),
None => {
if self.len == 0 {
None
} else {
Some(&self.inline[self.len - 1])
}
}
}
}
#[inline]
fn peek_mut(&mut self) -> Option<&mut Frame> {
match &mut self.heap {
Some(items) => items.last_mut(),
None => {
if self.len == 0 {
None
} else {
Some(&mut self.inline[self.len - 1])
}
}
}
}
}
#[cfg(feature = "alloc")]
impl StackOps for FrameStack {
#[inline]
fn is_empty(&self) -> bool {
Self::is_empty(self)
}
#[inline]
fn push<E: DecodeError>(&mut self, frame: Frame, off: usize) -> Result<(), E> {
Self::push(self, frame, off)
}
#[inline]
fn pop(&mut self) -> Option<Frame> {
Self::pop(self)
}
#[inline]
fn peek(&self) -> Option<&Frame> {
Self::peek(self)
}
#[inline]
fn peek_mut(&mut self) -> Option<&mut Frame> {
Self::peek_mut(self)
}
}
#[cfg(feature = "alloc")]
#[inline]
fn try_reserve_vec<T, E: DecodeError>(
v: &mut Vec<T>,
additional: usize,
offset: usize,
) -> Result<(), E> {
let needed = v
.len()
.checked_add(additional)
.ok_or_else(|| E::new(ErrorCode::LengthOverflow, offset))?;
if needed <= v.capacity() {
return Ok(());
}
let elem_size = core::mem::size_of::<T>();
if elem_size != 0 {
let max = (isize::MAX as usize) / elem_size;
if needed > max {
return Err(E::new(ErrorCode::LengthOverflow, offset));
}
}
v.try_reserve(additional)
.map_err(|_| E::new(ErrorCode::AllocationFailed, offset))
}
#[cfg(not(feature = "alloc"))]
struct FrameStack<const N: usize> {
inline: [Option<Frame>; N],
len: usize,
}
#[cfg(not(feature = "alloc"))]
impl<const N: usize> FrameStack<N> {
const fn new() -> Self {
Self {
inline: [None; N],
len: 0,
}
}
#[inline]
fn clear(&mut self) {
self.len = 0;
}
#[inline]
fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
fn push<E: DecodeError>(&mut self, frame: Frame, off: usize) -> Result<(), E> {
if self.len < N {
self.inline[self.len] = Some(frame);
self.len += 1;
Ok(())
} else {
Err(E::new(ErrorCode::DepthLimitExceeded, off))
}
}
#[inline]
fn pop(&mut self) -> Option<Frame> {
if self.len == 0 {
return None;
}
self.len -= 1;
self.inline[self.len].take()
}
#[inline]
fn peek(&self) -> Option<&Frame> {
if self.len == 0 {
return None;
}
self.inline[self.len - 1].as_ref()
}
#[inline]
fn peek_mut(&mut self) -> Option<&mut Frame> {
if self.len == 0 {
return None;
}
self.inline[self.len - 1].as_mut()
}
}
#[cfg(not(feature = "alloc"))]
impl<const N: usize> StackOps for FrameStack<N> {
#[inline]
fn is_empty(&self) -> bool {
Self::is_empty(self)
}
#[inline]
fn push<E: DecodeError>(&mut self, frame: Frame, off: usize) -> Result<(), E> {
Self::push(self, frame, off)
}
#[inline]
fn pop(&mut self) -> Option<Frame> {
Self::pop(self)
}
#[inline]
fn peek(&self) -> Option<&Frame> {
Self::peek(self)
}
#[inline]
fn peek_mut(&mut self) -> Option<&mut Frame> {
Self::peek_mut(self)
}
}
#[cfg(feature = "alloc")]
pub struct SkipScratch {
stack: FrameStack,
}
#[cfg(not(feature = "alloc"))]
pub struct SkipScratch {
stack: FrameStack<INLINE_STACK>,
}
impl SkipScratch {
#[cfg(feature = "alloc")]
pub(crate) const fn new() -> Self {
Self {
stack: FrameStack::new(),
}
}
#[cfg(not(feature = "alloc"))]
pub(crate) const fn new() -> Self {
Self {
stack: FrameStack::<INLINE_STACK>::new(),
}
}
}
#[cfg(not(feature = "alloc"))]
const INLINE_STACK: usize = DEFAULT_MAX_DEPTH + 2;
#[inline]
fn bump_items<E: DecodeError>(
limits: Option<&DecodeLimits>,
items_seen: &mut usize,
add: usize,
off: usize,
) -> Result<(), E> {
let Some(limits) = limits else {
return Ok(());
};
*items_seen = items_seen
.checked_add(add)
.ok_or_else(|| E::new(ErrorCode::LengthOverflow, off))?;
if *items_seen > limits.max_total_items {
return Err(E::new(ErrorCode::TotalItemsLimitExceeded, off));
}
Ok(())
}
#[inline]
fn ensure_depth<E: DecodeError>(
limits: Option<&DecodeLimits>,
next_depth: usize,
off: usize,
) -> Result<(), E> {
let Some(limits) = limits else {
return Ok(());
};
if next_depth > limits.max_depth {
return Err(E::new(ErrorCode::DepthLimitExceeded, off));
}
Ok(())
}
#[inline]
fn consume_value<E: DecodeError>(frame: &mut Frame, off: usize) -> Result<(), E> {
match frame {
Frame::Array { remaining } => {
*remaining = remaining
.checked_sub(1)
.ok_or_else(|| E::new(ErrorCode::MalformedCanonical, off))?;
}
Frame::Map {
remaining_pairs,
expecting_key,
..
} => {
if *expecting_key {
return Err(E::new(ErrorCode::MalformedCanonical, off));
}
*remaining_pairs = remaining_pairs
.checked_sub(1)
.ok_or_else(|| E::new(ErrorCode::MalformedCanonical, off))?;
*expecting_key = true;
}
}
Ok(())
}
#[allow(clippy::too_many_lines)]
#[inline]
fn skip_primitive<const CHECKED: bool, E: DecodeError>(
cursor: &mut Cursor<'_, E>,
limits: Option<&DecodeLimits>,
items_seen: &mut usize,
next_depth: usize,
off: usize,
major: u8,
ai: u8,
) -> Result<Option<Frame>, E> {
match major {
0 => {
let v = read_uint_arg::<CHECKED, E>(cursor, ai, off)?;
if CHECKED && v > MAX_SAFE_INTEGER {
return Err(E::new(ErrorCode::IntegerOutsideSafeRange, off));
}
Ok(None)
}
1 => {
let n = read_uint_arg::<CHECKED, E>(cursor, ai, off)?;
if CHECKED && n >= MAX_SAFE_INTEGER {
return Err(E::new(ErrorCode::IntegerOutsideSafeRange, off));
}
Ok(None)
}
2 => {
let len = read_len::<CHECKED, E>(cursor, ai, off)?;
if let Some(limits) = limits {
if len > limits.max_bytes_len {
return Err(E::new(ErrorCode::BytesLenLimitExceeded, off));
}
}
let _ = cursor.read_exact(len)?;
Ok(None)
}
3 => {
let len = read_len::<CHECKED, E>(cursor, ai, off)?;
if let Some(limits) = limits {
if len > limits.max_text_len {
return Err(E::new(ErrorCode::TextLenLimitExceeded, off));
}
}
let bytes = cursor.read_exact(len)?;
if CHECKED {
utf8::validate(bytes).map_err(|()| E::new(ErrorCode::Utf8Invalid, off))?;
}
Ok(None)
}
4 => {
let len = read_len::<CHECKED, E>(cursor, ai, off)?;
if let Some(limits) = limits {
if len > limits.max_array_len {
return Err(E::new(ErrorCode::ArrayLenLimitExceeded, off));
}
}
bump_items::<E>(limits, items_seen, len, off)?;
ensure_depth::<E>(limits, next_depth, off)?;
if len == 0 {
Ok(None)
} else {
Ok(Some(Frame::Array { remaining: len }))
}
}
5 => {
let len = read_len::<CHECKED, E>(cursor, ai, off)?;
if let Some(limits) = limits {
if len > limits.max_map_len {
return Err(E::new(ErrorCode::MapLenLimitExceeded, off));
}
}
let items = len
.checked_mul(2)
.ok_or_else(|| E::new(ErrorCode::LengthOverflow, off))?;
bump_items::<E>(limits, items_seen, items, off)?;
ensure_depth::<E>(limits, next_depth, off)?;
if len == 0 {
Ok(None)
} else {
Ok(Some(Frame::Map {
remaining_pairs: len,
expecting_key: true,
prev_key_range: None,
}))
}
}
6 => {
let _ = parse_bignum::<CHECKED, E>(cursor, limits, off, ai)?;
Ok(None)
}
7 => {
match ai {
20..=22 => {}
27 => {
let bits = cursor.read_be_u64()?;
if CHECKED {
validate_f64_bits(bits).map_err(|code| E::new(code, off))?;
}
}
24 => {
let simple = cursor.read_u8()?;
if simple < 24 {
return Err(E::new(ErrorCode::NonCanonicalEncoding, off));
}
return Err(E::new(ErrorCode::UnsupportedSimpleValue, off));
}
28..=30 => return Err(E::new(ErrorCode::ReservedAdditionalInfo, off)),
_ => return Err(E::new(ErrorCode::UnsupportedSimpleValue, off)),
}
Ok(None)
}
_ => Err(E::new(ErrorCode::MalformedCanonical, off)),
}
}
#[allow(clippy::too_many_lines)]
fn skip_one_value_inner<const CHECKED: bool, E: DecodeError, S: StackOps>(
cursor: &mut Cursor<'_, E>,
limits: Option<&DecodeLimits>,
items_seen: &mut usize,
base_depth: usize,
stack: &mut S,
) -> Result<(), E> {
let mut local_depth: usize = 0;
let mut started = false;
loop {
while let Some(frame) = stack.peek() {
if !frame.is_done() {
break;
}
let _ = stack
.pop()
.ok_or_else(|| E::new(ErrorCode::MalformedCanonical, cursor.position()))?;
local_depth = local_depth.saturating_sub(1);
if stack.is_empty() && started {
return Ok(());
}
}
if let Some(Frame::Map {
expecting_key: true,
..
}) = stack.peek()
{
let frame = stack
.peek_mut()
.ok_or_else(|| E::new(ErrorCode::MalformedCanonical, cursor.position()))?;
let Frame::Map {
expecting_key,
prev_key_range,
..
} = frame
else {
return Err(E::new(ErrorCode::MalformedCanonical, cursor.position()));
};
let key_start = cursor.position();
let ib = cursor.read_u8()?;
let major = ib >> 5;
let ai = ib & 0x1f;
if major != 3 {
return Err(E::new(ErrorCode::MapKeyMustBeText, key_start));
}
if CHECKED {
let _ = parse_text_from_header::<CHECKED, E>(cursor, limits, key_start, ai)?;
} else {
let len = read_len::<CHECKED, E>(cursor, ai, key_start)?;
if let Some(limits) = limits {
if len > limits.max_text_len {
return Err(E::new(ErrorCode::TextLenLimitExceeded, key_start));
}
}
let _ = cursor.read_exact(len)?;
}
let key_end = cursor.position();
if CHECKED {
check_map_key_order::<E>(cursor.data(), prev_key_range, key_start, key_end)?;
}
*expecting_key = false;
continue;
}
let off = cursor.position();
let ib = cursor.read_u8()?;
let major = ib >> 5;
let ai = ib & 0x1f;
let next_depth = base_depth + local_depth + 1;
let new_frame =
skip_primitive::<CHECKED, E>(cursor, limits, items_seen, next_depth, off, major, ai)?;
started = true;
if let Some(frame) = stack.peek_mut() {
consume_value::<E>(frame, off)?;
} else if new_frame.is_none() {
return Ok(());
}
if let Some(frame) = new_frame {
stack.push::<E>(frame, off)?;
local_depth = local_depth.saturating_add(1);
}
}
}
#[allow(clippy::too_many_lines)]
pub fn skip_one_value<const CHECKED: bool, E: DecodeError>(
cursor: &mut Cursor<'_, E>,
limits: Option<&DecodeLimits>,
items_seen: &mut usize,
base_depth: usize,
) -> Result<(), E> {
#[cfg(feature = "alloc")]
let mut stack = FrameStack::new();
#[cfg(not(feature = "alloc"))]
let mut stack = FrameStack::<INLINE_STACK>::new();
skip_one_value_inner::<CHECKED, E, _>(cursor, limits, items_seen, base_depth, &mut stack)
}
pub fn skip_one_value_with_scratch<const CHECKED: bool, E: DecodeError>(
cursor: &mut Cursor<'_, E>,
limits: Option<&DecodeLimits>,
items_seen: &mut usize,
base_depth: usize,
scratch: &mut SkipScratch,
) -> Result<(), E> {
scratch.stack.clear();
skip_one_value_inner::<CHECKED, E, _>(
cursor,
limits,
items_seen,
base_depth,
&mut scratch.stack,
)
}