use serde_json::{Map, Number, Value};
use crate::Error;
pub(crate) fn parse(data: &[u8]) -> Result<Value, Error> {
let mut pos: usize = 0usize;
let value: Value = parse_value(data, &mut pos)?;
Ok(value)
}
fn read_byte(data: &[u8], pos: &mut usize) -> Result<u8, Error> {
if *pos >= data.len() {
return Err(Error::Format("unexpected end of UBJ data".into()));
}
let b: u8 = data[*pos];
*pos += 1;
Ok(b)
}
fn read_n<const N: usize>(data: &[u8], pos: &mut usize) -> Result<[u8; N], Error> {
if *pos + N > data.len() {
return Err(Error::Format("unexpected end of UBJ data".into()));
}
let arr: [u8; N] = data[*pos..*pos + N].try_into().unwrap();
*pos += N;
Ok(arr)
}
fn read_i8(data: &[u8], pos: &mut usize) -> Result<i8, Error> {
Ok(read_byte(data, pos)? as i8)
}
fn read_u8(data: &[u8], pos: &mut usize) -> Result<u8, Error> {
read_byte(data, pos)
}
fn read_i16(data: &[u8], pos: &mut usize) -> Result<i16, Error> {
Ok(i16::from_be_bytes(read_n::<2>(data, pos)?))
}
fn read_i32(data: &[u8], pos: &mut usize) -> Result<i32, Error> {
Ok(i32::from_be_bytes(read_n::<4>(data, pos)?))
}
fn read_i64(data: &[u8], pos: &mut usize) -> Result<i64, Error> {
Ok(i64::from_be_bytes(read_n::<8>(data, pos)?))
}
fn read_f32(data: &[u8], pos: &mut usize) -> Result<f32, Error> {
Ok(f32::from_be_bytes(read_n::<4>(data, pos)?))
}
fn read_f64(data: &[u8], pos: &mut usize) -> Result<f64, Error> {
Ok(f64::from_be_bytes(read_n::<8>(data, pos)?))
}
fn read_string(data: &[u8], pos: &mut usize) -> Result<String, Error> {
let len: usize = read_count(data, pos)?;
if *pos + len > data.len() {
return Err(Error::Format("UBJ string truncated".into()));
}
let s: String = std::str::from_utf8(&data[*pos..*pos + len])
.map_err(|_| Error::Format("UBJ string is not valid UTF-8".into()))?
.to_owned();
*pos += len;
Ok(s)
}
fn read_count(data: &[u8], pos: &mut usize) -> Result<usize, Error> {
let marker: u8 = read_byte(data, pos)?;
let n: i64 = match marker {
b'i' => read_i8(data, pos)? as i64,
b'U' => read_u8(data, pos)? as i64,
b'I' => read_i16(data, pos)? as i64,
b'l' => read_i32(data, pos)? as i64,
b'L' => read_i64(data, pos)?,
m => {
return Err(Error::Format(format!(
"expected integer type for count, got {m:#04x}"
)));
}
};
if n < 0 {
return Err(Error::Format(format!("negative UBJ count: {n}")));
}
Ok(n as usize)
}
fn int_value(n: i64) -> Value {
Value::Number(Number::from(n))
}
fn uint_value(n: u64) -> Value {
Value::Number(Number::from(n))
}
fn float_value(f: f64) -> Result<Value, Error> {
Number::from_f64(f)
.map(Value::Number)
.ok_or_else(|| Error::Format(format!("non-finite float {f} in UBJ")))
}
fn parse_value(data: &[u8], pos: &mut usize) -> Result<Value, Error> {
let marker: u8 = read_byte(data, pos)?;
match marker {
b'N' => parse_value(data, pos), b'Z' => Ok(Value::Null),
b'T' => Ok(Value::Bool(true)),
b'F' => Ok(Value::Bool(false)),
b'i' => Ok(int_value(read_i8(data, pos)? as i64)),
b'U' => Ok(uint_value(read_u8(data, pos)? as u64)),
b'I' => Ok(int_value(read_i16(data, pos)? as i64)),
b'l' => Ok(int_value(read_i32(data, pos)? as i64)),
b'L' => Ok(int_value(read_i64(data, pos)?)),
b'd' => float_value(read_f32(data, pos)? as f64),
b'D' => float_value(read_f64(data, pos)?),
b'C' => Ok(Value::String((read_byte(data, pos)? as char).to_string())),
b'S' => Ok(Value::String(read_string(data, pos)?)),
b'H' => Ok(Value::String(read_string(data, pos)?)), b'[' => parse_array(data, pos),
b'{' => parse_object(data, pos),
m => Err(Error::Format(format!("unknown UBJ type marker {m:#04x}"))),
}
}
fn read_typed(data: &[u8], pos: &mut usize, type_marker: u8) -> Result<Value, Error> {
match type_marker {
b'Z' => Ok(Value::Null),
b'T' => Ok(Value::Bool(true)),
b'F' => Ok(Value::Bool(false)),
b'i' => Ok(int_value(read_i8(data, pos)? as i64)),
b'U' => Ok(uint_value(read_u8(data, pos)? as u64)),
b'I' => Ok(int_value(read_i16(data, pos)? as i64)),
b'l' => Ok(int_value(read_i32(data, pos)? as i64)),
b'L' => Ok(int_value(read_i64(data, pos)?)),
b'd' => float_value(read_f32(data, pos)? as f64),
b'D' => float_value(read_f64(data, pos)?),
b'C' => Ok(Value::String((read_byte(data, pos)? as char).to_string())),
b'S' => Ok(Value::String(read_string(data, pos)?)),
m => Err(Error::Format(format!(
"unsupported typed container element: {m:#04x}"
))),
}
}
fn parse_array(data: &[u8], pos: &mut usize) -> Result<Value, Error> {
if *pos >= data.len() {
return Err(Error::Format("truncated UBJ array".into()));
}
if data[*pos] == b'$' {
*pos += 1;
let type_marker = read_byte(data, pos)?;
match read_byte(data, pos)? {
b'#' => {}
m => {
return Err(Error::Format(format!(
"expected '#' after '$' in array, got {m:#04x}"
)));
}
}
let count = read_count(data, pos)?;
let arr = (0..count)
.map(|_| read_typed(data, pos, type_marker))
.collect::<Result<Vec<_>, _>>()?;
return Ok(Value::Array(arr));
}
if data[*pos] == b'#' {
*pos += 1;
let count: usize = read_count(data, pos)?;
let arr: Vec<Value> = (0..count)
.map(|_| parse_value(data, pos))
.collect::<Result<Vec<_>, _>>()?;
return Ok(Value::Array(arr));
}
let mut arr: Vec<Value> = Vec::new();
loop {
if *pos >= data.len() {
return Err(Error::Format("unterminated UBJ array".into()));
}
if data[*pos] == b']' {
*pos += 1;
break;
}
arr.push(parse_value(data, pos)?);
}
Ok(Value::Array(arr))
}
fn parse_object(data: &[u8], pos: &mut usize) -> Result<Value, Error> {
if *pos >= data.len() {
return Err(Error::Format("truncated UBJ object".into()));
}
if data[*pos] == b'$' {
*pos += 1;
let type_marker: u8 = read_byte(data, pos)?;
match read_byte(data, pos)? {
b'#' => {}
m => {
return Err(Error::Format(format!(
"expected '#' after '$' in object, got {m:#04x}"
)));
}
}
let count: usize = read_count(data, pos)?;
let mut map: Map<String, Value> = Map::with_capacity(count);
for _ in 0..count {
let key = read_string(data, pos)?;
let value = read_typed(data, pos, type_marker)?;
map.insert(key, value);
}
return Ok(Value::Object(map));
}
if data[*pos] == b'#' {
*pos += 1;
let count: usize = read_count(data, pos)?;
let mut map: Map<String, Value> = Map::with_capacity(count);
for _ in 0..count {
let key = read_string(data, pos)?;
let value = parse_value(data, pos)?;
map.insert(key, value);
}
return Ok(Value::Object(map));
}
let mut map: Map<String, Value> = Map::new();
loop {
if *pos >= data.len() {
return Err(Error::Format("unterminated UBJ object".into()));
}
if data[*pos] == b'}' {
*pos += 1;
break;
}
let key = read_string(data, pos)?;
let value = parse_value(data, pos)?;
map.insert(key, value);
}
Ok(Value::Object(map))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_simple_object() {
let data: &[u8] = &[
b'{', b'i', 1, b'a', b'i', 42, b'i', 1, b'b', b'T', b'}',
];
let v: Value = parse(data).unwrap();
assert_eq!(v["a"], Value::Number(42.into()));
assert_eq!(v["b"], Value::Bool(true));
}
#[test]
fn parse_typed_int32_array() {
let mut data: Vec<u8> = vec![b'[', b'$', b'l', b'#', b'i', 3];
data.extend_from_slice(&1_i32.to_be_bytes());
data.extend_from_slice(&(-1_i32).to_be_bytes());
data.extend_from_slice(&2_i32.to_be_bytes());
let v = parse(&data).unwrap();
let arr = v.as_array().unwrap();
assert_eq!(arr[0], Value::Number(1.into()));
assert_eq!(arr[1], Value::Number((-1_i64).into()));
assert_eq!(arr[2], Value::Number(2.into()));
}
#[test]
fn parse_typed_f32_array() {
let mut data: Vec<u8> = vec![b'[', b'$', b'd', b'#', b'i', 2];
data.extend_from_slice(&0.5_f32.to_be_bytes());
data.extend_from_slice(&(-0.5_f32).to_be_bytes());
let v: Value = parse(&data).unwrap();
let arr: &Vec<Value> = v.as_array().unwrap();
let a: f32 = arr[0].as_f64().unwrap() as f32;
let b: f32 = arr[1].as_f64().unwrap() as f32;
approx::assert_abs_diff_eq!(a, 0.5_f32, epsilon = 1e-7);
approx::assert_abs_diff_eq!(b, -0.5_f32, epsilon = 1e-7);
}
#[test]
fn parse_nested_string_values() {
let data: &[u8] = &[
b'{', b'i', 4, b'n', b'a', b'm', b'e', b'S', b'i', 5, b'h', b'e', b'l', b'l', b'o',
b'}',
];
let v = parse(data).unwrap();
assert_eq!(v["name"], Value::String("hello".into()));
}
}