#![cfg(feature = "flatbuffers")]
use ridl_rt::flatbuffers::{
field, follow, read_bool, read_f64, read_i32, read_i64, read_u16, root, string, vector,
Builder, Field, Pos, TableField, Vector,
};
use ridl_rt::payload::{EncodeError, Malformed};
const SIZE: usize = 16;
const ALIGN: usize = 4;
const SLOTS: u16 = 3;
const X: u16 = 4;
const Y: u16 = 8;
const LABEL: u16 = 12;
fn encode_point<'o>(out: &'o mut [u8], x: i32, y: i32, label: Option<&str>) -> &'o [u8] {
let mut b = Builder::new(out);
let text = label.map(|l| b.push_string(l).expect("the buffer fits the string"));
let mut fields = [
TableField {
slot: 0,
offset: X,
value: Field::I32(x),
},
TableField {
slot: 1,
offset: Y,
value: Field::I32(y),
},
TableField {
slot: 2,
offset: LABEL,
value: Field::U32(0),
},
];
let count = match text {
Some(t) => {
fields[2].value = Field::Offset(t);
3
}
None => 2,
};
let table = b
.push_table(SIZE, ALIGN, SLOTS, &fields[..count])
.expect("the buffer fits the table");
b.finish(table, ALIGN).expect("the buffer fits the root")
}
#[rustfmt::skip]
const POINT_7_M2_HI: [u8; 40] = [
0x10, 0x00, 0x00, 0x00,
0x00, 0x00,
0x0a, 0x00,
0x10, 0x00,
0x04, 0x00,
0x08, 0x00,
0x0c, 0x00,
0x0a, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00,
0xfe, 0xff, 0xff, 0xff,
0x04, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00,
0x68, 0x69,
0x00,
0x00,
];
#[test]
fn a_table_encodes_to_the_bytes_the_format_defines() {
let mut out = [0u8; 64];
let bytes = encode_point(&mut out, 7, -2, Some("hi"));
assert_eq!(bytes, &POINT_7_M2_HI[..]);
}
#[test]
fn the_finished_buffer_ends_at_the_end_of_the_slice() {
let mut out = [0u8; 64];
let len = {
let bytes = encode_point(&mut out, 7, -2, Some("hi"));
assert_eq!(bytes.len(), 40);
bytes.len()
};
assert_eq!(&out[out.len() - len..], &POINT_7_M2_HI[..]);
assert!(out[..out.len() - len].iter().all(|&b| b == 0));
}
#[test]
fn a_buffer_reads_back_through_the_walk() {
let mut out = [0u8; 64];
let bytes = encode_point(&mut out, 7, -2, Some("hi"));
let table = root(bytes).expect("the root resolves");
assert_eq!(table, 16);
let x = field(bytes, table, 0, 4)
.expect("the slot is readable")
.expect("x is present");
assert_eq!(read_i32(bytes, x).expect("x reads"), 7);
let y = field(bytes, table, 1, 4)
.expect("the slot is readable")
.expect("y is present");
assert_eq!(read_i32(bytes, y).expect("y reads"), -2);
let label = field(bytes, table, 2, 4)
.expect("the slot is readable")
.expect("label is present");
assert_eq!(string(bytes, label).expect("the label reads"), "hi");
}
#[test]
fn an_omitted_field_is_absent_rather_than_an_error() {
let mut out = [0u8; 64];
let bytes = encode_point(&mut out, 1, 2, None);
let table = root(bytes).expect("the root resolves");
assert!(field(bytes, table, 0, 4)
.expect("the slot is readable")
.is_some());
assert_eq!(
field(bytes, table, 2, 4).expect("the slot is readable"),
None
);
}
#[test]
fn a_slot_past_the_vtable_is_absent() {
let mut out = [0u8; 64];
let bytes = encode_point(&mut out, 1, 2, Some("hi"));
let table = root(bytes).expect("the root resolves");
assert_eq!(
field(bytes, table, 9, 4).expect("the slot is readable"),
None
);
}
#[test]
fn every_read_is_bounded_by_the_buffer() {
let mut out = [0u8; 64];
let len = encode_point(&mut out, 7, -2, Some("hi")).len();
let full = &out[out.len() - len..];
let table = root(full).expect("the root resolves");
for cut in 1..len {
let short = &full[..cut];
let _ = root(short);
let _ = field(short, table.min(cut), 0, 4);
let _ = read_i32(short, cut.saturating_sub(2));
let _ = string(short, 28.min(cut));
let _ = vector(short, 28.min(cut), 4);
}
}
#[test]
fn a_read_outside_the_buffer_is_out_of_bounds() {
let buf = [0u8; 4];
assert_eq!(read_i32(&buf, 1), Err(Malformed::OutOfBounds));
assert_eq!(read_u16(&buf, 3), Err(Malformed::OutOfBounds));
assert_eq!(read_f64(&buf, 0), Err(Malformed::OutOfBounds));
assert_eq!(follow(&buf, 2), Err(Malformed::OutOfBounds));
}
#[test]
fn an_offset_off_the_end_is_out_of_bounds() {
let mut buf = [0u8; 8];
buf[..4].copy_from_slice(&8u32.to_le_bytes());
assert_eq!(follow(&buf, 0), Err(Malformed::OutOfBounds));
buf[..4].copy_from_slice(&7u32.to_le_bytes());
assert_eq!(follow(&buf, 0), Ok(7));
}
#[test]
fn a_string_that_is_not_utf8_is_rejected() {
let mut out = [0u8; 64];
let len = encode_point(&mut out, 1, 2, Some("hi")).len();
let start = out.len() - len;
out[start + 36] = 0xff;
let bytes = &out[start..];
let table = root(bytes).expect("the root resolves");
let label = field(bytes, table, 2, 4)
.expect("the slot is readable")
.expect("label is present");
assert_eq!(string(bytes, label), Err(Malformed::Utf8));
}
#[test]
fn a_buffer_reads_the_same_at_any_alignment() {
let mut out = [0u8; 64];
let len = encode_point(&mut out, 7, -2, Some("hi")).len();
let aligned = out[out.len() - len..].to_vec();
for shift in 0..4 {
let mut moved = vec![0u8; shift];
moved.extend_from_slice(&aligned);
let bytes = &moved[shift..];
let table = root(bytes).expect("the root resolves");
let x = field(bytes, table, 0, 4)
.expect("readable")
.expect("present");
assert_eq!(read_i32(bytes, x).expect("x reads"), 7);
}
}
#[test]
fn a_vector_of_scalars_round_trips() {
let mut out = [0u8; 64];
let elements: [u8; 12] = {
let mut e = [0u8; 12];
for (i, v) in [10i32, -20, 30].iter().enumerate() {
e[i * 4..i * 4 + 4].copy_from_slice(&v.to_le_bytes());
}
e
};
let bytes = {
let mut b = Builder::new(&mut out);
let v = b
.push_vector(&elements, 4)
.expect("the buffer fits the vector");
let table = b
.push_table(
8,
ALIGN,
1,
&[TableField {
slot: 0,
offset: 4,
value: Field::Offset(v),
}],
)
.expect("the buffer fits the table");
b.finish(table, ALIGN).expect("the buffer fits the root")
};
let table = root(bytes).expect("the root resolves");
let at = field(bytes, table, 0, 4)
.expect("readable")
.expect("present");
let v: Vector = vector(bytes, at, 4).expect("the vector resolves");
assert_eq!(v.len, 3);
let read: Vec<i32> = (0..v.len)
.map(|i| read_i32(bytes, v.element(i, 4)).expect("the element reads"))
.collect();
assert_eq!(read, vec![10, -20, 30]);
}
#[test]
fn a_vector_longer_than_the_buffer_is_out_of_bounds() {
let mut buf = [0u8; 16];
buf[..4].copy_from_slice(&4u32.to_le_bytes()); buf[4..8].copy_from_slice(&1000u32.to_le_bytes()); assert_eq!(vector(&buf, 0, 4), Err(Malformed::OutOfBounds));
}
#[test]
fn an_output_buffer_that_is_too_small_reports_what_it_needed() {
let mut out = [0u8; 8];
let mut b = Builder::new(&mut out);
let text = b.push_string("hi").expect("a 7-byte string fits 8 bytes");
let err = b
.push_table(
SIZE,
ALIGN,
SLOTS,
&[TableField {
slot: 2,
offset: LABEL,
value: Field::Offset(text),
}],
)
.expect_err("a 16-byte table does not fit the remaining 0 bytes");
assert_eq!(
err,
EncodeError::Capacity {
needed: 24,
available: 8
}
);
}
#[test]
fn encoding_is_deterministic_over_a_dirty_buffer() {
let mut clean = [0u8; 64];
let first = encode_point(&mut clean, 7, -2, Some("hi")).to_vec();
let mut dirty = [0xabu8; 64];
let second = encode_point(&mut dirty, 7, -2, Some("hi")).to_vec();
assert_eq!(first, second);
}
#[test]
fn a_child_is_written_before_the_parent_that_names_it() {
let mut out = [0u8; 64];
let mut b = Builder::new(&mut out);
let child: Pos = b.push_string("hi").expect("the string fits");
let parent = b
.push_table(
8,
ALIGN,
1,
&[TableField {
slot: 0,
offset: 4,
value: Field::Offset(child),
}],
)
.expect("the table fits");
assert!(child < parent);
}
#[test]
fn a_boolean_reads_as_zero_or_not() {
let mut out = [0u8; 32];
let mut b = Builder::new(&mut out);
let table = b
.push_table(
8,
ALIGN,
2,
&[
TableField {
slot: 0,
offset: 4,
value: Field::Bool(true),
},
TableField {
slot: 1,
offset: 5,
value: Field::Bool(false),
},
],
)
.expect("the table fits");
let bytes = b.finish(table, ALIGN).expect("the root fits");
let t = root(bytes).expect("the root resolves");
let yes = field(bytes, t, 0, 1).expect("readable").expect("present");
let no = field(bytes, t, 1, 1).expect("readable").expect("present");
assert!(read_bool(bytes, yes).expect("reads"));
assert!(!read_bool(bytes, no).expect("reads"));
}
#[test]
fn a_vector_of_eight_byte_elements_aligns_its_elements() {
let mut out = [0u8; 96];
let elements: [u8; 16] = {
let mut e = [0u8; 16];
e[..8].copy_from_slice(&1i64.to_le_bytes());
e[8..].copy_from_slice(&(-2i64).to_le_bytes());
e
};
let bytes = {
let mut b = Builder::new(&mut out);
let v = b
.push_vector(&elements, 8)
.expect("the buffer fits the vector");
let table = b
.push_table(
8,
ALIGN,
1,
&[TableField {
slot: 0,
offset: 4,
value: Field::Offset(v),
}],
)
.expect("the buffer fits the table");
b.finish(table, 8).expect("the buffer fits the root")
};
let table = root(bytes).expect("the root resolves");
let at = field(bytes, table, 0, 4)
.expect("readable")
.expect("present");
let v = vector(bytes, at, 8).expect("the vector resolves");
assert_eq!(v.len, 2);
assert_eq!(v.first % 8, 0, "the elements sit on an eight-byte boundary");
assert_eq!(bytes.len() % 8, 0, "and the buffer itself is eight-aligned");
assert_eq!(read_i64(bytes, v.element(0, 8)).expect("reads"), 1);
assert_eq!(read_i64(bytes, v.element(1, 8)).expect("reads"), -2);
}
#[test]
fn a_string_without_its_terminator_is_rejected() {
let mut out = [0u8; 64];
let len = encode_point(&mut out, 1, 2, Some("hi")).len();
let start = out.len() - len;
out[start + 38] = b'!'; let bytes = &out[start..];
let table = root(bytes).expect("the root resolves");
let label = field(bytes, table, 2, 4)
.expect("readable")
.expect("present");
assert_eq!(string(bytes, label), Err(Malformed::OutOfBounds));
}
#[test]
fn a_field_that_straddles_the_end_of_its_table_is_rejected() {
let mut out = [0u8; 64];
let len = encode_point(&mut out, 7, -2, Some("hi")).len();
let start = out.len() - len;
out[start + 10..start + 12].copy_from_slice(&14u16.to_le_bytes());
let bytes = &out[start..];
let table = root(bytes).expect("the root resolves");
assert_eq!(field(bytes, table, 0, 4), Err(Malformed::OutOfBounds));
}