use alloc::format;
use alloc::string::ToString;
use alloc::vec::Vec;
use ciborium::value::Value;
use serde::{de::DeserializeOwned, Serialize};
use crate::error::EncodingError;
pub fn to_cbor_canonical<T: Serialize>(value: &T) -> Result<Vec<u8>, EncodingError> {
let mut intermediate = Vec::new();
ciborium::ser::into_writer(value, &mut intermediate)
.map_err(|e| EncodingError::CborSer(e.to_string()))?;
let parsed: Value = ciborium::de::from_reader(intermediate.as_slice())
.map_err(|e| EncodingError::CborDe(e.to_string()))?;
let canonical = canonicalise(parsed)?;
let mut out = Vec::new();
ciborium::ser::into_writer(&canonical, &mut out)
.map_err(|e| EncodingError::CborSer(e.to_string()))?;
Ok(out)
}
pub fn from_cbor<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, EncodingError> {
let parsed: Value = ciborium::de::from_reader(bytes)
.map_err(|e| EncodingError::CborDe(e.to_string()))?;
reject_floats(&parsed)?;
let mut buf = Vec::new();
ciborium::ser::into_writer(&parsed, &mut buf)
.map_err(|e| EncodingError::CborSer(e.to_string()))?;
ciborium::de::from_reader::<T, _>(buf.as_slice())
.map_err(|e| EncodingError::CborDe(e.to_string()))
}
pub fn to_json<T: Serialize>(value: &T) -> Result<Vec<u8>, EncodingError> {
serde_json::to_vec(value).map_err(EncodingError::JsonSer)
}
pub fn from_json<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, EncodingError> {
serde_json::from_slice(bytes).map_err(EncodingError::JsonSer)
}
fn canonicalise(value: Value) -> Result<Value, EncodingError> {
match value {
Value::Float(_) => Err(EncodingError::FloatForbidden),
Value::Array(items) => {
let mut out = Vec::with_capacity(items.len());
for item in items {
out.push(canonicalise(item)?);
}
Ok(Value::Array(out))
}
Value::Map(pairs) => {
let mut canonicalised = Vec::with_capacity(pairs.len());
for (k, v) in pairs {
let k = canonicalise(k)?;
let v = canonicalise(v)?;
let mut k_bytes = Vec::new();
ciborium::ser::into_writer(&k, &mut k_bytes)
.map_err(|e| EncodingError::CborSer(e.to_string()))?;
canonicalised.push((k_bytes, k, v));
}
canonicalised.sort_by(|a, b| match a.0.len().cmp(&b.0.len()) {
core::cmp::Ordering::Equal => a.0.cmp(&b.0),
other => other,
});
for w in canonicalised.windows(2) {
if w[0].0 == w[1].0 {
return Err(EncodingError::CborSer(format!(
"duplicate map key in canonical encoding: {} bytes",
w[0].0.len()
)));
}
}
Ok(Value::Map(
canonicalised.into_iter().map(|(_, k, v)| (k, v)).collect(),
))
}
Value::Tag(tag, inner) => Ok(Value::Tag(tag, alloc::boxed::Box::new(canonicalise(*inner)?))),
other => Ok(other),
}
}
fn reject_floats(value: &Value) -> Result<(), EncodingError> {
match value {
Value::Float(_) => Err(EncodingError::FloatForbidden),
Value::Array(items) => {
for i in items {
reject_floats(i)?;
}
Ok(())
}
Value::Map(pairs) => {
for (k, v) in pairs {
reject_floats(k)?;
reject_floats(v)?;
}
Ok(())
}
Value::Tag(_, inner) => reject_floats(inner),
_ => Ok(()),
}
}