use ciborium::value::Value as CborValue;
use libyay::Value;
use num_bigint::BigInt;
use num_traits::ToPrimitive;
use std::collections::HashMap;
use std::fmt::Write as FmtWrite;
pub fn decode(input: &[u8]) -> Result<Value, String> {
let cbor_value: CborValue =
ciborium::de::from_reader(input).map_err(|e| format!("CBOR decode error: {}", e))?;
cbor_to_value(&cbor_value)
}
fn cbor_to_value(cbor: &CborValue) -> Result<Value, String> {
match cbor {
CborValue::Null => Ok(Value::Null),
CborValue::Bool(b) => Ok(Value::Bool(*b)),
CborValue::Integer(i) => {
let n: i128 = (*i).into();
Ok(Value::Integer(BigInt::from(n)))
}
CborValue::Float(f) => Ok(Value::Float(*f)),
CborValue::Text(s) => Ok(Value::String(s.clone())),
CborValue::Bytes(b) => Ok(Value::Bytes(b.clone())),
CborValue::Array(arr) => {
let items: Result<Vec<Value>, String> = arr.iter().map(cbor_to_value).collect();
Ok(Value::Array(items?))
}
CborValue::Map(pairs) => {
let mut obj = HashMap::new();
for (k, v) in pairs {
let key = match k {
CborValue::Text(s) => s.clone(),
_ => return Err(format!("CBOR map key must be a text string, got: {:?}", k)),
};
obj.insert(key, cbor_to_value(v)?);
}
Ok(Value::Object(obj))
}
CborValue::Tag(tag, _) => Err(format!(
"CBOR tagged value (tag {}) has no YAY equivalent",
tag
)),
_ => Err(format!("CBOR value {:?} has no YAY equivalent", cbor)),
}
}
pub fn encode(value: &Value) -> Result<Vec<u8>, String> {
let mut buf = Vec::new();
write_value(&mut buf, value)?;
Ok(buf)
}
fn write_value(buf: &mut Vec<u8>, value: &Value) -> Result<(), String> {
match value {
Value::Null => {
buf.push(0xf6);
Ok(())
}
Value::Bool(b) => {
buf.push(if *b { 0xf5 } else { 0xf4 });
Ok(())
}
Value::Integer(n) => write_integer(buf, n),
Value::Float(f) => {
buf.push(0xfb);
buf.extend_from_slice(&f.to_be_bytes());
Ok(())
}
Value::String(s) => {
let bytes = s.as_bytes();
write_type_and_length(buf, 3, bytes.len() as u64); buf.extend_from_slice(bytes);
Ok(())
}
Value::Bytes(b) => {
write_type_and_length(buf, 2, b.len() as u64); buf.extend_from_slice(b);
Ok(())
}
Value::Array(arr) => {
write_type_and_length(buf, 4, arr.len() as u64); for item in arr {
write_value(buf, item)?;
}
Ok(())
}
Value::Object(obj) => {
let mut keys: Vec<&String> = obj.keys().collect();
keys.sort();
write_type_and_length(buf, 5, keys.len() as u64); for k in keys {
let key_bytes = k.as_bytes();
write_type_and_length(buf, 3, key_bytes.len() as u64);
buf.extend_from_slice(key_bytes);
write_value(buf, &obj[k])?;
}
Ok(())
}
}
}
fn write_type_and_length(buf: &mut Vec<u8>, major: u8, val: u64) {
let high = major << 5;
match val {
0..=23 => {
buf.push(high | val as u8);
}
24..=0xff => {
buf.push(high | 24);
buf.push(val as u8);
}
0x100..=0xffff => {
buf.push(high | 25);
buf.extend_from_slice(&(val as u16).to_be_bytes());
}
0x10000..=0xffff_ffff => {
buf.push(high | 26);
buf.extend_from_slice(&(val as u32).to_be_bytes());
}
_ => {
buf.push(high | 27);
buf.extend_from_slice(&val.to_be_bytes());
}
}
}
fn write_integer(buf: &mut Vec<u8>, n: &BigInt) -> Result<(), String> {
if n.sign() == num_bigint::Sign::Minus {
let abs_minus_1 = (-n) - BigInt::from(1);
let val = abs_minus_1.to_u64().ok_or_else(|| {
format!(
"integer {} exceeds CBOR's native integer range (-2^64 to 2^64-1)",
n
)
})?;
write_type_and_length(buf, 1, val);
} else {
let val = n.to_u64().ok_or_else(|| {
format!(
"integer {} exceeds CBOR's native integer range (-2^64 to 2^64-1)",
n
)
})?;
write_type_and_length(buf, 0, val);
}
Ok(())
}
pub fn diagnostic(input: &[u8]) -> Result<String, String> {
let cbor_value: CborValue =
ciborium::de::from_reader(input).map_err(|e| format!("CBOR decode error: {}", e))?;
let mut out = String::new();
diag_value(&mut out, &cbor_value, 0);
out.push('\n');
Ok(out)
}
fn diag_value(out: &mut String, val: &CborValue, indent: usize) {
match val {
CborValue::Null => out.push_str("null"),
CborValue::Bool(true) => out.push_str("true"),
CborValue::Bool(false) => out.push_str("false"),
CborValue::Integer(i) => {
let n: i128 = (*i).into();
write!(out, "{}", n).unwrap();
}
CborValue::Float(f) => {
diag_float(out, *f);
}
CborValue::Text(s) => {
diag_text(out, s);
}
CborValue::Bytes(b) => {
out.push_str("h'");
for byte in b {
write!(out, "{:02x}", byte).unwrap();
}
out.push('\'');
}
CborValue::Array(arr) => {
diag_array(out, arr, indent);
}
CborValue::Map(pairs) => {
diag_map(out, pairs, indent);
}
CborValue::Tag(tag, inner) => {
write!(out, "{}(", tag).unwrap();
diag_value(out, inner, indent);
out.push(')');
}
_ => {
write!(out, "<?unknown {:?}>", val).unwrap();
}
}
}
fn diag_float(out: &mut String, f: f64) {
if f.is_nan() {
out.push_str("NaN");
} else if f.is_infinite() {
if f.is_sign_positive() {
out.push_str("Infinity");
} else {
out.push_str("-Infinity");
}
} else if f == 0.0 && f.is_sign_negative() {
out.push_str("-0.0");
} else if f.fract() == 0.0 && f.abs() < 1e18 {
write!(out, "{:.1}", f).unwrap();
} else {
let s = format!("{}", f);
out.push_str(&s);
if !s.contains('.') && !s.contains('e') && !s.contains('E') {
out.push_str(".0");
}
}
}
fn diag_text(out: &mut String, s: &str) {
out.push('"');
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c.is_control() => {
write!(out, "\\u{:04x}", c as u32).unwrap();
}
c => out.push(c),
}
}
out.push('"');
}
fn diag_array(out: &mut String, arr: &[CborValue], indent: usize) {
if arr.is_empty() {
out.push_str("[]");
return;
}
if arr.len() <= 5 && arr.iter().all(is_simple_value) {
out.push('[');
for (i, item) in arr.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
diag_value(out, item, indent);
}
out.push(']');
} else {
out.push_str("[\n");
let child_indent = indent + 2;
for (i, item) in arr.iter().enumerate() {
for _ in 0..child_indent {
out.push(' ');
}
diag_value(out, item, child_indent);
if i < arr.len() - 1 {
out.push(',');
}
out.push('\n');
}
for _ in 0..indent {
out.push(' ');
}
out.push(']');
}
}
fn diag_map(out: &mut String, pairs: &[(CborValue, CborValue)], indent: usize) {
if pairs.is_empty() {
out.push_str("{}");
return;
}
out.push_str("{\n");
let child_indent = indent + 2;
for (i, (k, v)) in pairs.iter().enumerate() {
for _ in 0..child_indent {
out.push(' ');
}
diag_value(out, k, child_indent);
out.push_str(": ");
diag_value(out, v, child_indent);
if i < pairs.len() - 1 {
out.push(',');
}
out.push('\n');
}
for _ in 0..indent {
out.push(' ');
}
out.push('}');
}
fn is_simple_value(val: &CborValue) -> bool {
matches!(
val,
CborValue::Null
| CborValue::Bool(_)
| CborValue::Integer(_)
| CborValue::Float(_)
| CborValue::Text(_)
| CborValue::Bytes(_)
)
}