use crate::error::PipelineError;
use crate::json::{
self, be_bytes_to_decimal, decimal_to_be_bytes, decrement, float_val, increment, node_to_val,
Val,
};
const MAX_DEPTH: usize = 512;
fn err(msg: impl Into<String>) -> PipelineError {
PipelineError::Other(msg.into())
}
pub fn import(input: &[u8]) -> Result<String, PipelineError> {
let mut d = D { b: input, i: 0 };
let root = d.item(0)?;
if d.i != input.len() {
return Err(err("trailing bytes after the CBOR item"));
}
let Val::Obj(members) = root else {
return Err(err("root must be a CBOR map"));
};
json::import_val(&members, false)
}
struct D<'a> {
b: &'a [u8],
i: usize,
}
impl D<'_> {
fn u8(&mut self) -> Result<u8, PipelineError> {
let v = *self
.b
.get(self.i)
.ok_or_else(|| err("truncated CBOR input"))?;
self.i += 1;
Ok(v)
}
fn take(&mut self, n: u64) -> Result<&[u8], PipelineError> {
let n = usize::try_from(n).map_err(|_| err("CBOR length overflows"))?;
let end = self
.i
.checked_add(n)
.filter(|e| *e <= self.b.len())
.ok_or_else(|| err("truncated CBOR input"))?;
let out = &self.b[self.i..end];
self.i = end;
Ok(out)
}
fn arg(&mut self, ai: u8) -> Result<u64, PipelineError> {
Ok(match ai {
0..=23 => ai as u64,
24 => self.u8()? as u64,
25 => u16::from_be_bytes(self.take(2)?.try_into().expect("2 bytes")) as u64,
26 => u32::from_be_bytes(self.take(4)?.try_into().expect("4 bytes")) as u64,
27 => u64::from_be_bytes(self.take(8)?.try_into().expect("8 bytes")),
_ => return Err(err("reserved additional information in CBOR head")),
})
}
fn len(&mut self, ai: u8) -> Result<Option<u64>, PipelineError> {
if ai == 31 {
Ok(None)
} else {
self.arg(ai).map(Some)
}
}
fn eat_break(&mut self) -> Result<bool, PipelineError> {
if self.b.get(self.i) == Some(&0xff) {
self.i += 1;
return Ok(true);
}
if self.i >= self.b.len() {
return Err(err("truncated CBOR input"));
}
Ok(false)
}
fn string_body(&mut self, major: u8, ai: u8) -> Result<Vec<u8>, PipelineError> {
match self.len(ai)? {
Some(n) => Ok(self.take(n)?.to_vec()),
None => {
let mut out = Vec::new();
loop {
let ib = self.u8()?;
if ib == 0xff {
return Ok(out);
}
if ib >> 5 != major {
return Err(err("indefinite-length string with mixed chunk types"));
}
let n = self
.len(ib & 0x1f)?
.ok_or_else(|| err("nested indefinite-length string chunk"))?;
out.extend_from_slice(self.take(n)?);
}
}
}
}
fn bignum_bytes(&mut self) -> Result<Vec<u8>, PipelineError> {
let ib = self.u8()?;
if ib >> 5 != 2 {
return Err(err("bignum tag content must be a byte string"));
}
self.string_body(2, ib & 0x1f)
}
fn map_pair(
&mut self,
depth: usize,
members: &mut Vec<(String, Val)>,
seen: &mut std::collections::BTreeSet<String>,
) -> Result<(), PipelineError> {
let (typed, key) = match self.item(depth + 1)? {
Val::Str(s) => (format!("s:{s}"), s),
Val::Num(n) => (format!("n:{n}"), n),
Val::Bool(b) => (format!("b:{b}"), b.to_string()),
_ => return Err(err("unsupported CBOR map key type")),
};
if !seen.insert(typed) {
return Err(err(format!("duplicate map key: {key}")));
}
if !seen.insert(format!("c:{key}")) {
return Err(err(format!(
"CBOR keys collide after text coercion (kaiv object keys are text): {key}"
)));
}
let v = self.item(depth + 1)?;
members.push((key, v));
Ok(())
}
fn item(&mut self, depth: usize) -> Result<Val, PipelineError> {
if depth > MAX_DEPTH {
return Err(err("CBOR nesting too deep"));
}
let ib = self.u8()?;
let (major, ai) = (ib >> 5, ib & 0x1f);
Ok(match major {
0 => Val::Num(self.arg(ai)?.to_string()),
1 => Val::Num((-1i128 - self.arg(ai)? as i128).to_string()),
2 => Val::Typed {
lib: "std/enc".to_string(),
name: "bin".to_string(),
text: json::b64url_encode(&self.string_body(2, ai)?),
},
3 => Val::Str(
String::from_utf8(self.string_body(3, ai)?)
.map_err(|_| err("CBOR text string is not UTF-8"))?,
),
4 => {
let mut items = Vec::new();
match self.len(ai)? {
Some(n) => {
for _ in 0..n {
items.push(self.item(depth + 1)?);
}
}
None => {
while !self.eat_break()? {
items.push(self.item(depth + 1)?);
}
}
}
Val::Arr(items)
}
5 => {
let mut members = Vec::new();
let mut seen = std::collections::BTreeSet::new();
match self.len(ai)? {
Some(n) => {
for _ in 0..n {
self.map_pair(depth, &mut members, &mut seen)?;
}
}
None => {
while !self.eat_break()? {
self.map_pair(depth, &mut members, &mut seen)?;
}
}
}
Val::Obj(members)
}
6 => match self.arg(ai)? {
0 => match self.item(depth + 1)? {
Val::Str(s) => Val::Typed {
lib: "std/time".to_string(),
name: "datetime".to_string(),
text: s,
},
_ => return Err(err("tag 0 content must be a text string")),
},
2 => Val::Num(be_bytes_to_decimal(&self.bignum_bytes()?)),
3 => {
let mut bytes = self.bignum_bytes()?;
increment(&mut bytes);
Val::Num(format!("-{}", be_bytes_to_decimal(&bytes)))
}
_ => self.item(depth + 1)?,
},
7 => match ai {
20 => Val::Bool(false),
21 => Val::Bool(true),
22 | 23 => Val::Null,
25 => float_val(f16_to_f64(u16::from_be_bytes(
self.take(2)?.try_into().expect("2 bytes"),
))),
26 => {
float_val(f32::from_be_bytes(self.take(4)?.try_into().expect("4 bytes")) as f64)
}
27 => float_val(f64::from_be_bytes(
self.take(8)?.try_into().expect("8 bytes"),
)),
31 => return Err(err("unexpected break code")),
_ => {
if ai == 24 {
self.u8()?;
}
return Err(err("unsupported CBOR simple value"));
}
},
_ => unreachable!("major is 3 bits"),
})
}
}
fn f16_to_f64(h: u16) -> f64 {
let sign = if h & 0x8000 != 0 { -1f64 } else { 1f64 };
let exp = ((h >> 10) & 0x1f) as i32;
let man = (h & 0x3ff) as f64;
match exp {
0 => sign * man * (-24f64).exp2(),
0x1f => {
if man == 0.0 {
sign * f64::INFINITY
} else {
f64::NAN
}
}
_ => sign * (1.0 + man / 1024.0) * f64::from(exp - 15).exp2(),
}
}
pub fn export(canonical: &str) -> Result<Vec<u8>, PipelineError> {
let root = node_to_val(&json::tree(canonical)?)?;
let mut out = Vec::new();
emit(&root, &mut out, 0)?;
Ok(out)
}
fn head(major: u8, v: u64, out: &mut Vec<u8>) {
let m = major << 5;
if v < 24 {
out.push(m | v as u8);
} else if v <= 0xff {
out.push(m | 24);
out.push(v as u8);
} else if v <= 0xffff {
out.push(m | 25);
out.extend((v as u16).to_be_bytes());
} else if v <= 0xffff_ffff {
out.push(m | 26);
out.extend((v as u32).to_be_bytes());
} else {
out.push(m | 27);
out.extend(v.to_be_bytes());
}
}
fn emit(v: &Val, out: &mut Vec<u8>, depth: usize) -> Result<(), PipelineError> {
if depth > MAX_DEPTH {
return Err(err("nesting too deep for CBOR export"));
}
match v {
Val::Null => out.push(0xf6),
Val::Bool(false) => out.push(0xf4),
Val::Bool(true) => out.push(0xf5),
Val::Num(raw) => emit_num(raw, out)?,
Val::Str(s) => {
head(3, s.len() as u64, out);
out.extend_from_slice(s.as_bytes());
}
Val::Typed { lib, name, text } if lib == "std/enc" && name == "bin" => {
let b = json::b64url_decode(text).ok_or_else(|| err("invalid base64url payload"))?;
head(2, b.len() as u64, out);
out.extend_from_slice(&b);
}
Val::Typed { lib, name, text } if lib == "std/time" && name == "datetime" => {
out.push(0xc0);
head(3, text.len() as u64, out);
out.extend_from_slice(text.as_bytes());
}
Val::Typed { lib, text, .. } if lib == "std/num" => {
out.push(0xf9);
out.extend(match text.as_str() {
"inf" => [0x7c, 0x00],
"-inf" => [0xfc, 0x00],
_ => [0x7e, 0x00],
});
}
Val::Typed { text, .. } => {
head(3, text.len() as u64, out);
out.extend_from_slice(text.as_bytes());
}
Val::Arr(items) => {
head(4, items.len() as u64, out);
for item in items {
emit(item, out, depth + 1)?;
}
}
Val::Obj(members) => {
head(5, members.len() as u64, out);
for (k, mv) in members {
head(3, k.len() as u64, out);
out.extend_from_slice(k.as_bytes());
emit(mv, out, depth + 1)?;
}
}
}
Ok(())
}
fn emit_num(raw: &str, out: &mut Vec<u8>) -> Result<(), PipelineError> {
let neg = raw.starts_with('-');
let digits = raw.strip_prefix('-').unwrap_or(raw);
let int_shaped = !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit());
if !int_shaped {
let f: f64 = raw.parse().map_err(|_| err(format!("bad number: {raw}")))?;
emit_float(f, out);
return Ok(());
}
if let Ok(m) = digits.parse::<u128>() {
if !neg && m <= u64::MAX as u128 {
head(0, m as u64, out);
return Ok(());
}
if neg && m == 0 {
head(0, 0, out);
return Ok(());
}
if neg && m <= u64::MAX as u128 + 1 {
head(1, (m - 1) as u64, out);
return Ok(());
}
}
let mut bytes = decimal_to_be_bytes(digits);
if neg {
out.push(0xc3);
decrement(&mut bytes);
if bytes.first() == Some(&0) {
bytes.remove(0);
}
} else {
out.push(0xc2);
}
head(2, bytes.len() as u64, out);
out.extend_from_slice(&bytes);
Ok(())
}
fn emit_float(f: f64, out: &mut Vec<u8>) {
if f.is_nan() {
out.extend([0xf9, 0x7e, 0x00]);
} else if let Some(h) = f64_to_f16(f) {
out.push(0xf9);
out.extend(h.to_be_bytes());
} else {
let s = f as f32;
if f64::from(s) == f {
out.push(0xfa);
out.extend(s.to_bits().to_be_bytes());
} else {
out.push(0xfb);
out.extend(f.to_bits().to_be_bytes());
}
}
}
fn f64_to_f16(f: f64) -> Option<u16> {
let s = f as f32;
if f64::from(s) != f {
return None;
}
let bits = s.to_bits();
let sign = ((bits >> 16) & 0x8000) as u16;
let e = ((bits >> 23) & 0xff) as i32;
let man = bits & 0x007f_ffff;
if e == 0xff {
return (man == 0).then_some(sign | 0x7c00); }
if man == 0 && e == 0 {
return Some(sign); }
if e == 0 {
return None; }
let exp = e - 127;
if (-14..=15).contains(&exp) {
(man & 0x1fff == 0).then(|| sign | (((exp + 15) as u16) << 10) | (man >> 13) as u16)
} else if (-24..=-15).contains(&exp) {
let full = 0x0080_0000u32 | man;
let shift = 23 - (exp + 24) as u32;
(full & ((1u32 << shift) - 1) == 0).then(|| sign | (full >> shift) as u16)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cross_type_key_coercion_reported_accurately() {
let bytes = [0xa2, 0x01, 0x61, 0x78, 0x61, 0x31, 0x61, 0x79];
let e = format!("{}", import(&bytes).unwrap_err());
assert!(e.contains("collide"), "{e}");
}
#[test]
fn genuine_duplicate_key_still_errors() {
let bytes = [0xa2, 0x01, 0x61, 0x78, 0x01, 0x61, 0x79];
let e = format!("{}", import(&bytes).unwrap_err());
assert!(e.contains("duplicate map key"), "{e}");
}
fn roundtrip(src: &[u8]) -> Vec<u8> {
let authored = import(src).unwrap();
let raiv = crate::compile(authored.as_bytes()).unwrap();
let daiv = crate::denorm::denormalize(&raiv).unwrap();
export(&daiv).unwrap()
}
fn doc(key: &str, item: &[u8]) -> Vec<u8> {
let mut out = vec![0xa1, 0x60 | key.len() as u8];
out.extend_from_slice(key.as_bytes());
out.extend_from_slice(item);
out
}
#[test]
fn import_typing_and_natives() {
let mut src = vec![0xa5];
for (k, v) in [
("host", &b"\x65web01"[..]),
("port", b"\x19\x1f\x90"),
("ratio", b"\xf9\x3e\x00"),
("on", b"\xf5"),
("note", b"\xf6"),
] {
src.push(0x60 | k.len() as u8);
src.extend_from_slice(k.as_bytes());
src.extend_from_slice(v);
}
let out = import(&src).unwrap();
assert!(out.contains("host=web01\n"));
assert!(out.contains("!int\nport=8080\n"));
assert!(out.contains("!float\nratio=1.5\n"));
assert!(out.contains("!bool\non=true\n"));
assert!(out.contains("!null\nnote=\n"));
assert_eq!(roundtrip(&src), src);
}
#[test]
fn byte_strings_are_std_enc_bin() {
let src = doc("blob", &[0x43, 0x00, 0xff, 0x10]);
let out = import(&src).unwrap();
assert!(out.contains(".!types std/enc\n"));
let b64 = json::b64url_encode(&[0x00, 0xff, 0x10]);
assert!(out.contains(&format!("&bin\nblob={b64}\n")));
assert_eq!(roundtrip(&src), src);
}
#[test]
fn tag0_datetimes_are_std_time() {
let mut item = vec![0xc0, 0x74];
item.extend_from_slice(b"2026-07-03T21:00:00Z");
let src = doc("when", &item);
let out = import(&src).unwrap();
assert!(out.contains(".!types std/time\n"));
assert!(out.contains("&datetime\nwhen=2026-07-03T21:00:00Z\n"));
assert_eq!(roundtrip(&src), src);
}
#[test]
fn integers_exact_at_any_width() {
let mut src = vec![0xa4];
for (k, v) in [
(
"max",
&[0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff][..],
),
(
"min",
&[0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
),
("big", &[0xc2, 0x49, 1, 0, 0, 0, 0, 0, 0, 0, 0]),
("neg", &[0xc3, 0x49, 1, 0, 0, 0, 0, 0, 0, 0, 1]),
] {
src.push(0x60 | k.len() as u8);
src.extend_from_slice(k.as_bytes());
src.extend_from_slice(v);
}
let out = import(&src).unwrap();
assert!(out.contains("!int\nmax=18446744073709551615\n"));
assert!(out.contains("!int\nmin=-18446744073709551616\n"));
assert!(out.contains("!int\nbig=18446744073709551616\n"));
assert!(out.contains("!int\nneg=-18446744073709551618\n"));
assert_eq!(roundtrip(&src), src);
}
#[test]
fn nonfinite_floats_are_std_num() {
let mut src = vec![0xa3];
for (k, v) in [
("a", [0xf9, 0x7c, 0x00]),
("b", [0xf9, 0xfc, 0x00]),
("c", [0xf9, 0x7e, 0x00]),
] {
src.push(0x60 | k.len() as u8);
src.extend_from_slice(k.as_bytes());
src.extend_from_slice(&v);
}
let out = import(&src).unwrap();
assert!(out.contains(".!types std/num\n"));
assert!(out.contains("&inf\na=inf\n"));
assert!(out.contains("&inf\nb=-inf\n"));
assert!(out.contains("&nan\nc=nan\n"));
assert_eq!(roundtrip(&src), src);
}
#[test]
fn floats_reencode_shortest() {
let src = doc("x", &{
let mut v = vec![0xfb];
v.extend(0.1f64.to_bits().to_be_bytes());
v
});
assert_eq!(roundtrip(&src), src);
let src = doc("x", &[0xfa, 0x4b, 0x80, 0x00, 0x00]);
assert_eq!(roundtrip(&src), src);
let src = doc("x", &[0xfb, 0x3f, 0xf8, 0, 0, 0, 0, 0, 0]); assert_eq!(roundtrip(&src), doc("x", &[0xf9, 0x3e, 0x00]));
}
#[test]
fn indefinite_lengths_normalize() {
let src: Vec<u8> = vec![
0xbf, 0x61, b's', 0x7f, 0x61, b'a', 0x61, b'b', 0xff, 0x61, b'b', 0x5f, 0x41, 0x01, 0x42, 0x02, 0x03, 0xff, 0x61, b'l', 0x9f, 0x01, 0x02, 0xff, 0xff,
];
let out = import(&src).unwrap();
assert!(out.contains("s=ab\n"));
assert!(out.contains(&format!("&bin\nb={}\n", json::b64url_encode(&[1, 2, 3]))));
assert!(out.contains("!int\n/@l;=1;2\n"));
let definite: Vec<u8> = vec![
0xa3, 0x61, b's', 0x62, b'a', b'b', 0x61, b'b', 0x43, 0x01, 0x02, 0x03, 0x61, b'l',
0x82, 0x01, 0x02,
];
assert_eq!(roundtrip(&src), definite);
}
#[test]
fn scalar_keys_stringify() {
let out = import(&[0xa1, 0x01, 0x02]).unwrap();
assert!(out.contains("!int\n\"1\"=2\n"));
assert_eq!(roundtrip(&[0xa1, 0x01, 0x02]), vec![0xa1, 0x61, b'1', 0x02]);
}
#[test]
fn undefined_and_unknown_tags_degrade() {
let out = import(&doc("u", &[0xf7])).unwrap();
assert!(out.contains("!null\nu=\n"));
let out = import(&doc("u", &[0xd8, 0x20, 0x61, b'x'])).unwrap();
assert!(out.contains("u=x\n"));
}
#[test]
fn semantic_roundtrip() {
let mut src = vec![0xa4];
for (k, v) in [
("name", &b"\x63eu1"[..]),
("tags", b"\x82\x61a\x61b"),
("limits", b"\xa1\x63rps\x19\x01\xf4"),
("servers", b"\x82\xa1\x64host\x61a\xa1\x64host\x61b"),
] {
src.push(0x60 | k.len() as u8);
src.extend_from_slice(k.as_bytes());
src.extend_from_slice(v);
}
assert_eq!(roundtrip(&src), src);
}
#[test]
fn cross_format() {
let authored = crate::json::import(br#"{"a":[1,2],"b":"x"}"#).unwrap();
let raiv = crate::compile(authored.as_bytes()).unwrap();
let daiv = crate::denorm::denormalize(&raiv).unwrap();
assert_eq!(
export(&daiv).unwrap(),
vec![0xa2, 0x61, b'a', 0x82, 0x01, 0x02, 0x61, b'b', 0x61, b'x']
);
let src = doc("blob", &[0x42, 0x01, 0xff]);
let authored = import(&src).unwrap();
let raiv = crate::compile(authored.as_bytes()).unwrap();
let daiv = crate::denorm::denormalize(&raiv).unwrap();
let json = crate::json::export(&daiv).unwrap();
assert_eq!(json.trim_end(), r#"{"blob":"Af8"}"#);
}
#[test]
fn rejects_malformed_input() {
assert!(import(&[0x01]).is_err()); assert!(import(&[0xa0, 0x00]).is_err()); assert!(import(&[0xa1, 0x61, b'a']).is_err()); assert!(import(&[0xff]).is_err()); assert!(import(&doc("k", &[0x1c])).is_err()); assert!(import(&doc("k", &[0xf8, 0xff])).is_err()); assert!(import(&doc("k", &[0xf8])).is_err()); assert!(import(&[0xa2, 0x61, b'a', 0x01, 0x61, b'a', 0x02]).is_err());
assert!(import(&doc("k", &[0x61, 0xff])).is_err());
assert!(import(&[0xa1, 0x80, 0x01]).is_err());
}
}