use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::cmp::Ordering;
use crate::encode;
use crate::float::{validate_f64_bits, CANONICAL_NAN_BITS, NEGATIVE_ZERO_BITS};
use crate::limits::{MAX_SAFE_INTEGER_I64, MIN_SAFE_INTEGER};
use crate::order::cmp_text_keys_by_canonical_encoding;
use crate::{CborError, CborErrorCode};
const MAX_SAFE_INTEGER_BE: [u8; 7] = [0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
#[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(CborError::encode)?;
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(CborError::encode)?;
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::encode(CborErrorCode::NegativeZeroForbidden));
}
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: Vec<(String, CborValue)>,
}
impl CborMap {
pub fn new(mut entries: Vec<(String, CborValue)>) -> Result<Self, CborError> {
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::encode(CborErrorCode::DuplicateMapKey));
}
}
Ok(Self { entries })
}
#[inline]
pub(crate) const fn from_sorted_entries(entries: Vec<(String, CborValue)>) -> Self {
Self { entries }
}
#[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_str(), v))
}
#[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 enum CborValue {
Int(i64),
Bignum(BigInt),
Bytes(Vec<u8>),
Text(String),
Array(Vec<Self>),
Map(CborMap),
Bool(bool),
Null,
Float(F64Bits),
}
impl CborValue {
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)
}
#[must_use]
pub fn text<S: AsRef<str>>(s: S) -> Self {
Self::Text(s.as_ref().to_string())
}
}
#[inline]
#[must_use]
pub fn cbor_equal(a: &CborValue, b: &CborValue) -> bool {
a == b
}
fn validate_bignum_bytes(negative: bool, magnitude: &[u8]) -> Result<(), CborErrorCode> {
if magnitude.is_empty() || magnitude[0] == 0 {
return Err(CborErrorCode::BignumNotCanonical);
}
let cmp = cmp_big_endian(magnitude, &MAX_SAFE_INTEGER_BE);
let outside = if negative {
cmp != Ordering::Less
} else {
cmp == Ordering::Greater
};
if !outside {
return Err(CborErrorCode::BignumMustBeOutsideSafeRange);
}
Ok(())
}
fn cmp_big_endian(a: &[u8], b: &[u8]) -> Ordering {
match a.len().cmp(&b.len()) {
Ordering::Equal => a.cmp(b),
other => other,
}
}
pub const fn validate_int_safe_i64(v: i64) -> Result<(), CborErrorCode> {
if v < MIN_SAFE_INTEGER || v > MAX_SAFE_INTEGER_I64 {
return Err(CborErrorCode::IntegerOutsideSafeRange);
}
Ok(())
}
pub fn validate_bignum(negative: bool, magnitude: &[u8]) -> Result<(), CborErrorCode> {
validate_bignum_bytes(negative, magnitude)
}
pub const fn validate_f64(bits: u64) -> Result<(), CborErrorCode> {
validate_f64_bits(bits)
}