use alloc::boxed::Box;
use alloc::vec::Vec;
use crate::encode;
use crate::profile::{
checked_text_len, cmp_text_keys_by_canonical_encoding, validate_bignum_bytes,
validate_f64_bits, validate_int_safe_i64, CANONICAL_NAN_BITS, NEGATIVE_ZERO_BITS,
};
use crate::{CborError, ErrorCode};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BigInt {
negative: bool,
magnitude: Vec<u8>,
}
impl BigInt {
pub fn new(negative: bool, magnitude: Vec<u8>) -> Result<Self, CborError> {
validate_bignum_bytes(negative, &magnitude).map_err(|code| CborError::new(code, 0))?;
Ok(Self {
negative,
magnitude,
})
}
#[inline]
#[must_use]
pub const fn is_negative(&self) -> bool {
self.negative
}
#[inline]
#[must_use]
pub fn magnitude(&self) -> &[u8] {
&self.magnitude
}
#[inline]
pub(crate) const fn new_unchecked(negative: bool, magnitude: Vec<u8>) -> Self {
Self {
negative,
magnitude,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct F64Bits(u64);
impl F64Bits {
pub fn new(bits: u64) -> Result<Self, CborError> {
validate_f64_bits(bits).map_err(|code| CborError::new(code, 0))?;
Ok(Self(bits))
}
pub fn try_from_f64(value: f64) -> Result<Self, CborError> {
let bits = value.to_bits();
if bits == NEGATIVE_ZERO_BITS {
return Err(CborError::new(ErrorCode::NegativeZeroForbidden, 0));
}
if value.is_nan() {
return Ok(Self(CANONICAL_NAN_BITS));
}
Ok(Self(bits))
}
#[inline]
#[must_use]
pub const fn bits(self) -> u64 {
self.0
}
#[inline]
pub(crate) const fn new_unchecked(bits: u64) -> Self {
Self(bits)
}
#[inline]
#[must_use]
pub fn to_f64(self) -> f64 {
f64::from_bits(self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CborMap {
entries: Box<[(Box<str>, CborValue)]>,
}
impl CborMap {
pub fn new(mut entries: Vec<(Box<str>, CborValue)>) -> Result<Self, CborError> {
for (k, _) in &entries {
checked_text_len(k.len()).map_err(|code| CborError::new(code, 0))?;
}
entries.sort_by(|(ka, _), (kb, _)| cmp_text_keys_by_canonical_encoding(ka, kb));
for w in entries.windows(2) {
if w[0].0 == w[1].0 {
return Err(CborError::new(ErrorCode::DuplicateMapKey, 0));
}
}
Ok(Self {
entries: entries.into_boxed_slice(),
})
}
#[inline]
pub(crate) fn from_sorted_entries(entries: Vec<(Box<str>, CborValue)>) -> Self {
Self {
entries: entries.into_boxed_slice(),
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&str, &CborValue)> {
self.entries.iter().map(|(k, v)| (k.as_ref(), v))
}
#[inline]
pub(crate) fn entries(&self) -> &[(Box<str>, CborValue)] {
&self.entries
}
#[must_use]
pub fn get(&self, key: &str) -> Option<&CborValue> {
let idx = self
.entries
.binary_search_by(|(k, _)| cmp_text_keys_by_canonical_encoding(k, key))
.ok()?;
Some(&self.entries[idx].1)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CborInteger(IntegerRepr);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IntegerRepr {
Safe(i64),
Big(BigInt),
}
impl CborInteger {
pub fn safe(value: i64) -> Result<Self, CborError> {
validate_int_safe_i64(value).map_err(|code| CborError::new(code, 0))?;
Ok(Self(IntegerRepr::Safe(value)))
}
pub fn big(negative: bool, magnitude: Vec<u8>) -> Result<Self, CborError> {
Ok(Self(IntegerRepr::Big(BigInt::new(negative, magnitude)?)))
}
#[inline]
#[must_use]
pub const fn from_bigint(big: BigInt) -> Self {
Self(IntegerRepr::Big(big))
}
#[inline]
#[must_use]
pub const fn is_safe(&self) -> bool {
matches!(self.0, IntegerRepr::Safe(_))
}
#[inline]
#[must_use]
pub const fn is_big(&self) -> bool {
matches!(self.0, IntegerRepr::Big(_))
}
#[inline]
#[must_use]
pub const fn as_i64(&self) -> Option<i64> {
match &self.0 {
IntegerRepr::Safe(v) => Some(*v),
IntegerRepr::Big(_) => None,
}
}
#[inline]
#[must_use]
pub const fn as_bigint(&self) -> Option<&BigInt> {
match &self.0 {
IntegerRepr::Big(b) => Some(b),
IntegerRepr::Safe(_) => None,
}
}
#[inline]
pub(crate) const fn new_safe_unchecked(value: i64) -> Self {
Self(IntegerRepr::Safe(value))
}
}
impl From<BigInt> for CborInteger {
fn from(value: BigInt) -> Self {
Self(IntegerRepr::Big(value))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CborValue(ValueRepr);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValueRepr {
Integer(CborInteger),
Bytes(Vec<u8>),
Text(Box<str>),
Array(Box<[CborValue]>),
Map(CborMap),
Bool(bool),
Null,
Float(F64Bits),
}
impl CborValue {
#[inline]
#[must_use]
pub const fn integer(value: CborInteger) -> Self {
Self(ValueRepr::Integer(value))
}
pub fn int(value: i64) -> Result<Self, CborError> {
Ok(Self(ValueRepr::Integer(CborInteger::safe(value)?)))
}
pub fn bigint(negative: bool, magnitude: Vec<u8>) -> Result<Self, CborError> {
Ok(Self(ValueRepr::Integer(CborInteger::big(
negative, magnitude,
)?)))
}
#[inline]
#[must_use]
pub const fn bytes(bytes: Vec<u8>) -> Self {
Self(ValueRepr::Bytes(bytes))
}
#[must_use]
pub fn text<S: Into<Box<str>>>(s: S) -> Self {
Self(ValueRepr::Text(s.into()))
}
#[inline]
#[must_use]
pub fn array(items: impl Into<Box<[Self]>>) -> Self {
Self(ValueRepr::Array(items.into()))
}
#[inline]
#[must_use]
pub const fn map(map: CborMap) -> Self {
Self(ValueRepr::Map(map))
}
#[inline]
#[must_use]
pub const fn bool(value: bool) -> Self {
Self(ValueRepr::Bool(value))
}
#[inline]
#[must_use]
pub const fn null() -> Self {
Self(ValueRepr::Null)
}
#[inline]
#[must_use]
pub const fn float(bits: F64Bits) -> Self {
Self(ValueRepr::Float(bits))
}
pub fn float64(value: f64) -> Result<Self, CborError> {
Ok(Self::float(F64Bits::try_from_f64(value)?))
}
#[inline]
#[must_use]
pub const fn as_integer(&self) -> Option<&CborInteger> {
match &self.0 {
ValueRepr::Integer(i) => Some(i),
_ => None,
}
}
#[inline]
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
self.as_integer().and_then(CborInteger::as_i64)
}
#[inline]
#[must_use]
pub fn as_bigint(&self) -> Option<&BigInt> {
self.as_integer().and_then(CborInteger::as_bigint)
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> Option<&[u8]> {
match &self.0 {
ValueRepr::Bytes(b) => Some(b),
_ => None,
}
}
#[inline]
#[must_use]
pub fn as_text(&self) -> Option<&str> {
match &self.0 {
ValueRepr::Text(s) => Some(s.as_ref()),
_ => None,
}
}
#[inline]
#[must_use]
pub fn as_array(&self) -> Option<&[Self]> {
match &self.0 {
ValueRepr::Array(items) => Some(items.as_ref()),
_ => None,
}
}
#[inline]
#[must_use]
pub const fn as_map(&self) -> Option<&CborMap> {
match &self.0 {
ValueRepr::Map(map) => Some(map),
_ => None,
}
}
#[inline]
#[must_use]
pub const fn as_bool(&self) -> Option<bool> {
match &self.0 {
ValueRepr::Bool(b) => Some(*b),
_ => None,
}
}
#[inline]
#[must_use]
pub const fn is_null(&self) -> bool {
matches!(self.0, ValueRepr::Null)
}
#[inline]
#[must_use]
pub const fn as_float(&self) -> Option<F64Bits> {
match &self.0 {
ValueRepr::Float(bits) => Some(*bits),
_ => None,
}
}
pub fn encode_canonical(&self) -> Result<Vec<u8>, CborError> {
encode::encode_to_vec(self)
}
#[cfg(feature = "sha2")]
#[cfg_attr(docsrs, doc(cfg(feature = "sha2")))]
pub fn sha256_canonical(&self) -> Result<[u8; 32], CborError> {
encode::encode_sha256(self)
}
#[inline]
pub(crate) const fn repr(&self) -> &ValueRepr {
&self.0
}
}
#[inline]
#[must_use]
pub fn cbor_equal(a: &CborValue, b: &CborValue) -> bool {
a == b
}