use std::io::{Read, Write};
use nextjson::formats::{Cbor, Format};
use nextjson::Value;
use crate::{Config, Error, Result};
#[cfg(feature = "fingerprint")]
use crate::{
frame::{encode_header, validate_header, HEADER_LEN},
schema::{config_fingerprint, hash_bytes, hash_u64, Fingerprint},
};
#[cfg(feature = "fingerprint")]
const FRAME_MAGIC: [u8; 4] = *b"RBCF";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CborConfig {
base: Config,
deterministic: bool,
}
impl CborConfig {
pub(crate) const fn new(base: Config) -> Self {
Self {
base,
deterministic: false,
}
}
pub const fn with_deterministic_encoding(mut self) -> Self {
self.deterministic = true;
self
}
pub const fn with_preserved_map_order(mut self) -> Self {
self.deterministic = false;
self
}
pub const fn is_deterministic(self) -> bool {
self.deterministic
}
pub const fn base_config(self) -> Config {
self.base
}
#[cfg(feature = "fingerprint")]
pub fn fingerprint<T: Fingerprint + ?Sized>(self) -> u64 {
let hash = config_fingerprint(T::TYPE_FINGERPRINT, self.base);
let hash = hash_bytes(hash, b"format:rfc8949-cbor");
hash_u64(hash, self.deterministic as u64)
}
#[cfg(feature = "fingerprint")]
pub const fn with_fingerprint(self) -> FingerprintedCborConfig {
FingerprintedCborConfig { config: self }
}
#[cfg(feature = "compression")]
pub const fn with_zstd_compression(self, level: i32) -> crate::CompressedConfig {
crate::CompressedConfig::cbor(self, level)
}
#[cfg(feature = "encryption")]
pub fn with_encryption(self, key: crate::EncryptionKey) -> crate::EncryptedConfig {
crate::EncryptedConfig::cbor(self, key)
}
pub fn serialize<T: nextjson::NsonSerialize + ?Sized>(self, value: &T) -> Result<Vec<u8>> {
let mut output = Vec::new();
self.serialize_into(&mut output, value)?;
Ok(output)
}
pub fn serialize_into<W: Write, T: nextjson::NsonSerialize + ?Sized>(
self,
mut writer: W,
value: &T,
) -> Result<()> {
let bytes = self.encode_value(value)?;
writer.write_all(&bytes)?;
Ok(())
}
fn encode_value<T: nextjson::NsonSerialize + ?Sized>(self, value: &T) -> Result<Vec<u8>> {
let bytes = if self.deterministic {
let value = nextjson::to_value(value).map_err(cbor_error)?;
let value = canonicalize(value)?;
Cbor.encode(&value).map_err(cbor_error)?
} else {
Cbor.encode(value).map_err(cbor_error)?
};
self.enforce_byte_limit(bytes.len())?;
Ok(bytes)
}
fn enforce_byte_limit(self, length: usize) -> Result<()> {
if let Some(limit) = self.base.limit {
if length as u64 > limit {
return Err(Error::SizeLimit { limit });
}
}
Ok(())
}
pub fn serialized_size<T: nextjson::NsonSerialize + ?Sized>(self, value: &T) -> Result<u64> {
let bytes = self.encode_value(value)?;
Ok(bytes.len() as u64)
}
pub fn deserialize<T: for<'de> nextjson::NsonDeserialize<'de>>(
self,
input: &[u8],
) -> Result<T> {
self.enforce_byte_limit(input.len())?;
let json = nextjson::cross_format::cbor_to_json(input).map_err(cbor_error)?;
let value: nextjson::Value = nextjson::from_slice(&json).map_err(cbor_error)?;
self.enforce_collection_limits(&value)?;
nextjson::from_value(value).map_err(cbor_error)
}
fn enforce_collection_limits(self, value: &nextjson::Value) -> Result<()> {
match value {
nextjson::Value::Array(items) => {
self.enforce_collection_limit(items.len())?;
for item in items {
self.enforce_collection_limits(item)?;
}
}
nextjson::Value::Object(map) => {
self.enforce_collection_limit(map.len())?;
for (_, item) in map.iter() {
self.enforce_collection_limits(item)?;
}
}
_ => {}
}
Ok(())
}
fn enforce_collection_limit(self, length: usize) -> Result<()> {
if let Some(limit) = self.base.collection_limit {
if length as u64 > limit {
return Err(Error::CollectionLimit { limit });
}
}
Ok(())
}
pub fn deserialize_from<R: Read, T: for<'de> nextjson::NsonDeserialize<'de>>(
self,
mut reader: R,
) -> Result<T> {
let max = self.base.limit.unwrap_or(u64::MAX);
let read_cap = max.saturating_add(1);
let mut bytes = Vec::new();
reader.by_ref().take(read_cap).read_to_end(&mut bytes)?;
if bytes.len() as u64 > max {
return Err(Error::SizeLimit { limit: max });
}
self.deserialize(&bytes)
}
}
#[cfg(feature = "fingerprint")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FingerprintedCborConfig {
config: CborConfig,
}
#[cfg(feature = "fingerprint")]
impl FingerprintedCborConfig {
pub const fn payload_config(self) -> CborConfig {
self.config
}
pub fn serialize<T: nextjson::NsonSerialize + Fingerprint + ?Sized>(
self,
value: &T,
) -> Result<Vec<u8>> {
let mut output = Vec::new();
self.serialize_into(&mut output, value)?;
Ok(output)
}
pub fn serialize_into<W: Write, T: nextjson::NsonSerialize + Fingerprint + ?Sized>(
self,
mut writer: W,
value: &T,
) -> Result<()> {
writer.write_all(&encode_header(FRAME_MAGIC, self.config.fingerprint::<T>()))?;
self.config.serialize_into(writer, value)
}
pub fn deserialize<T: for<'de> nextjson::NsonDeserialize<'de> + Fingerprint>(
self,
input: &[u8],
) -> Result<T> {
let payload = validate_header(FRAME_MAGIC, input, self.config.fingerprint::<T>())?;
self.config.deserialize(payload)
}
pub fn deserialize_from<R: Read, T: for<'de> nextjson::NsonDeserialize<'de> + Fingerprint>(
self,
mut reader: R,
) -> Result<T> {
let mut header = [0; HEADER_LEN];
reader.read_exact(&mut header)?;
validate_header(FRAME_MAGIC, &header, self.config.fingerprint::<T>())?;
self.config.deserialize_from(reader)
}
pub fn serialized_size<T: nextjson::NsonSerialize + Fingerprint + ?Sized>(
self,
value: &T,
) -> Result<u64> {
self.config
.serialized_size(value)?
.checked_add(HEADER_LEN as u64)
.ok_or(Error::SizeLimit { limit: u64::MAX })
}
}
fn canonicalize(value: Value) -> Result<Value> {
match value {
Value::Array(items) => items
.into_iter()
.map(canonicalize)
.collect::<Result<Vec<_>>>()
.map(Value::Array),
Value::Object(map) => {
let mut entries: Vec<(String, Value)> = map
.into_iter()
.map(|(key, value)| Ok((key, canonicalize(value)?)))
.collect::<Result<_>>()?;
entries.sort_by(|left, right| canonical_cmp(left.0.as_bytes(), right.0.as_bytes()));
if entries.windows(2).any(|pair| pair[0].0 == pair[1].0) {
return Err(Error::Cbor(
"deterministic maps cannot contain duplicate canonical keys".into(),
));
}
let mut out = nextjson::Map::new();
for (key, value) in entries {
out.insert(key, value);
}
Ok(Value::Object(out))
}
scalar => Ok(scalar),
}
}
fn canonical_cmp(left: &[u8], right: &[u8]) -> std::cmp::Ordering {
left.len().cmp(&right.len()).then_with(|| left.cmp(right))
}
fn cbor_error(error: nextjson::Error) -> Error {
Error::Cbor(error.to_string())
}