use crate::{
ALIGNMENT, BUFFER_SIZE_SIZE, TYPE_ARRAY, TYPE_BOOLEAN, TYPE_ENUMERATION, TYPE_F32, TYPE_F64,
TYPE_I16, TYPE_I32, TYPE_I64, TYPE_I8, TYPE_MAP, TYPE_OPTION, TYPE_STRING, TYPE_STRUCTURE,
TYPE_U16, TYPE_U32, TYPE_U64, TYPE_U8, TYPE_UNIT, TYPE_UUID, TYPE_VALUE,
};
pub const MAX_DEPTH: usize = 128;
pub fn well_formed(buffer: &[u8]) -> Result<(), String> {
let size = buffer
.get(..BUFFER_SIZE_SIZE)
.map(|prefix| u32::from_le_bytes(prefix.try_into().expect("4 bytes")) as usize)
.ok_or("it is shorter than its size prefix")?;
if !(BUFFER_SIZE_SIZE..=buffer.len()).contains(&size) {
return Err(format!(
"its size prefix counts {size} of its {} bytes",
buffer.len()
));
}
let mut walk = Walk {
buffer: &buffer[..size],
at: BUFFER_SIZE_SIZE,
};
walk.value(0)
}
struct Walk<'a> {
buffer: &'a [u8],
at: usize,
}
impl<'a> Walk<'a> {
fn take(&mut self, len: usize) -> Result<&'a [u8], String> {
let bytes = self
.at
.checked_add(len)
.and_then(|end| self.buffer.get(self.at..end))
.ok_or_else(|| format!("a value runs past the end, at byte {}", self.at))?;
self.at += len;
Ok(bytes)
}
fn skip(&mut self, len: usize) -> Result<(), String> {
self.take(len).map(drop)
}
fn u8(&mut self) -> Result<u8, String> {
Ok(self.take(1)?[0])
}
fn u32(&mut self) -> Result<usize, String> {
let bytes = self.take(4)?.try_into().expect("4 bytes");
Ok(u32::from_le_bytes(bytes) as usize)
}
fn align(&mut self) -> Result<(), String> {
self.skip(ALIGNMENT - self.at % ALIGNMENT)
}
fn string(&mut self) -> Result<(), String> {
let len = self.u32()?;
let at = self.at;
std::str::from_utf8(self.take(len)?)
.map(drop)
.map_err(|_| format!("a string is not UTF-8, at byte {at}"))
}
fn fields(&mut self, depth: usize) -> Result<(), String> {
for _ in 0..self.u32()? {
self.skip(16)?;
self.value(depth)?;
}
Ok(())
}
fn value(&mut self, depth: usize) -> Result<(), String> {
if depth == MAX_DEPTH {
return Err(format!("its values nest deeper than {MAX_DEPTH}"));
}
let depth = depth + 1;
let at = self.at;
match self.u8()? {
TYPE_UNIT => Ok(()),
TYPE_BOOLEAN | TYPE_U8 | TYPE_I8 => self.skip(1),
TYPE_U16 | TYPE_I16 => self.skip(2),
TYPE_U32 | TYPE_I32 | TYPE_F32 => self.skip(4),
TYPE_U64 | TYPE_I64 | TYPE_F64 => self.skip(8),
TYPE_STRING => self.string(),
TYPE_UUID => self.skip(16),
TYPE_STRUCTURE => {
self.skip(16)?;
self.fields(depth)
}
TYPE_ENUMERATION => {
self.skip(32)?;
self.value(depth)
}
TYPE_OPTION => match self.u8()? {
0 => Ok(()),
_ => self.value(depth),
},
TYPE_MAP => {
self.skip(16)?;
for _ in 0..self.u32()? {
self.string()?;
self.skip(16)?;
self.value(depth)?;
}
Ok(())
}
TYPE_ARRAY => self.array(depth),
kind => Err(format!("type {kind} at byte {at} is not a value's")),
}
}
fn array(&mut self, depth: usize) -> Result<(), String> {
let ty = self.u8()?;
let count = self.u32()?;
let primitive = match ty {
TYPE_BOOLEAN | TYPE_U8 | TYPE_I8 => Some(1),
TYPE_U16 | TYPE_I16 => Some(2),
TYPE_U32 | TYPE_I32 | TYPE_F32 => Some(4),
TYPE_U64 | TYPE_I64 | TYPE_F64 => Some(8),
_ => None,
};
if let Some(size) = primitive {
self.align()?;
let len = count
.checked_mul(size)
.ok_or("an array is longer than a buffer can be")?;
return self.skip(len);
}
match ty {
TYPE_STRING => (0..count).try_for_each(|_| self.string()),
TYPE_STRUCTURE => {
self.skip(16)?;
(0..count).try_for_each(|_| self.fields(depth))
}
TYPE_ENUMERATION => {
self.skip(16)?;
(0..count).try_for_each(|_| {
self.skip(16)?;
self.value(depth)
})
}
TYPE_VALUE => (0..count).try_for_each(|_| self.value(depth)),
ty => Err(format!("an array of type {ty} is not a value's")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::froto_value::{deserialize, serialize};
use crate::BufferWriter;
use arora_types::keyvalue::{KeyValue, KeyValueField};
use arora_types::value::{
Enumeration, EnumerationWithoutId, Structure, StructureField, StructureWithoutId, Value,
};
use uuid::Uuid;
fn id(n: u8) -> Uuid {
Uuid::from_bytes([n; 16])
}
fn values() -> Vec<Value> {
let field = |n, value| StructureField {
id: id(n),
value: Box::new(value),
};
let mut map = KeyValue::new_with_id(id(9));
map.set_field(KeyValueField::new_with_id_and_option(
"key",
id(10),
Some(Value::U16(7)),
));
vec![
Value::Unit,
Value::Boolean(true),
Value::I64(-3),
Value::F32(1.5),
Value::String("héllo".into()),
Value::Uuid(id(1)),
Value::Option(Some(Box::new(Value::U8(1)))),
Value::Option(None),
Value::KeyValue(map),
Value::Structure(Structure {
id: id(2),
fields: vec![
field(3, Value::U32(4)),
field(4, Value::ArrayU8(vec![1, 2, 3])),
field(5, Value::ArrayF64(vec![0.5, 2.0])),
field(6, Value::ArrayString(vec!["a".into(), "".into()])),
field(
7,
Value::Enumeration(Enumeration {
id: id(5),
variant_id: id(6),
value: Box::new(Value::I16(-1)),
}),
),
],
}),
Value::ArrayStructure {
id: id(7),
elements: vec![
StructureWithoutId {
fields: vec![field(8, Value::Boolean(false))],
},
StructureWithoutId { fields: vec![] },
],
},
Value::ArrayEnumeration {
id: id(8),
elements: vec![EnumerationWithoutId {
variant_id: id(9),
value: Box::new(Value::Unit),
}],
},
Value::ArrayValue(vec![Value::U64(1), Value::String("b".into())]),
Value::ArrayBoolean(vec![]),
]
}
#[test]
fn a_buffer_the_writer_wrote_is_well_formed_and_reads_back() {
for value in values() {
let buffer = serialize(&value);
assert_eq!(well_formed(&buffer), Ok(()), "{value:?}");
assert_eq!(deserialize(&buffer), value);
}
}
#[test]
fn a_truncated_buffer_is_refused() {
for value in values() {
let buffer = serialize(&value);
for len in 0..buffer.len() {
let mut truncated = buffer[..len].to_vec();
if len >= 4 {
truncated[..4].copy_from_slice(&(len as u32).to_le_bytes());
}
assert!(well_formed(&truncated).is_err(), "{value:?} cut to {len}");
}
}
}
#[test]
fn a_buffer_accepted_decodes_whatever_its_bytes() {
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let mut panicked = Vec::new();
for value in values() {
let buffer = serialize(&value).into_vec();
for at in 4..buffer.len() {
for byte in [0, 1, 3, 13, 15, 16, 19, 20, 0x7f, 0x80, 0xff] {
let mut changed = buffer.clone();
changed[at] = byte;
if well_formed(&changed).is_ok()
&& std::panic::catch_unwind(|| deserialize(&changed)).is_err()
{
panicked.push((value.clone(), at, byte));
}
}
}
}
std::panic::set_hook(hook);
assert!(panicked.is_empty(), "accepted, then panicked: {panicked:?}");
}
#[test]
fn a_size_prefix_not_counting_the_buffer_is_refused() {
let buffer = serialize(&Value::U32(1)).into_vec();
for size in [0u32, 3, buffer.len() as u32 + 1] {
let mut changed = buffer.clone();
changed[..4].copy_from_slice(&size.to_le_bytes());
assert!(well_formed(&changed).is_err(), "{size}");
}
assert!(well_formed(&[]).is_err());
assert!(well_formed(&[8, 0]).is_err());
}
#[test]
fn values_nested_past_the_limit_are_refused() {
let nested = |depth: usize| {
let mut value = Value::Unit;
for _ in 1..depth {
value = Value::Option(Some(Box::new(value)));
}
serialize(&value)
};
assert_eq!(well_formed(&nested(MAX_DEPTH)), Ok(()));
assert!(well_formed(&nested(MAX_DEPTH + 1))
.unwrap_err()
.contains("nest deeper"));
}
#[test]
fn an_error_buffer_or_a_string_not_utf_8_is_refused() {
let mut writer = BufferWriter::new();
writer.add_error("refused");
assert!(well_formed(&writer.finalize()).is_err());
let mut buffer = serialize(&Value::String("ab".into())).into_vec();
let last = buffer.len() - 1;
buffer[last] = 0xff;
assert!(well_formed(&buffer).unwrap_err().contains("not UTF-8"));
}
}