use crate::c2pa_cbor::Value;
use serde_json::{Map, Value as Json};
pub fn cbor_to_json(value: &Value) -> Json {
match value {
Value::Integer(n) => {
if let Ok(i) = i64::try_from(*n) {
Json::from(i)
} else {
Json::String(n.to_string())
}
}
Value::Bytes(b) => Json::String(base64_encode(b)),
Value::Text(s) => Json::String(s.clone()),
Value::Array(items) => Json::Array(items.iter().map(cbor_to_json).collect()),
Value::Map(entries) => {
let mut map = Map::new();
for (k, v) in entries {
map.insert(key_to_string(k), cbor_to_json(v));
}
Json::Object(map)
}
Value::Tag(_, inner) => cbor_to_json(inner),
Value::Bool(b) => Json::Bool(*b),
Value::Null => Json::Null,
Value::Float(f) => serde_json::Number::from_f64(*f)
.map(Json::Number)
.unwrap_or(Json::Null),
}
}
fn key_to_string(key: &Value) -> String {
match key {
Value::Text(s) => s.clone(),
Value::Integer(n) => n.to_string(),
other => format!("{other:?}"),
}
}
fn base64_encode(input: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(input.len().div_ceil(3) * 4);
for chunk in input.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = *chunk.get(1).unwrap_or(&0) as u32;
let b2 = *chunk.get(2).unwrap_or(&0) as u32;
let n = (b0 << 16) | (b1 << 8) | b2;
out.push(ALPHABET[(n >> 18 & 0x3F) as usize] as char);
out.push(ALPHABET[(n >> 12 & 0x3F) as usize] as char);
if chunk.len() > 1 {
out.push(ALPHABET[(n >> 6 & 0x3F) as usize] as char);
} else {
out.push('=');
}
if chunk.len() > 2 {
out.push(ALPHABET[(n & 0x3F) as usize] as char);
} else {
out.push('=');
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base64_matches_known_vectors() {
assert_eq!(base64_encode(b""), "");
assert_eq!(base64_encode(b"f"), "Zg==");
assert_eq!(base64_encode(b"fo"), "Zm8=");
assert_eq!(base64_encode(b"foo"), "Zm9v");
assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
}
#[test]
fn bytes_become_base64_in_json() {
let v = Value::Map(vec![(
Value::Text("h".into()),
Value::Bytes(vec![0xDE, 0xAD]),
)]);
let json = cbor_to_json(&v);
assert_eq!(json["h"], Json::String("3q0=".to_string()));
}
}