mod macro_ {
use bnb::bitstream::ErrorKind;
use bnb::{bin, u4, u12};
#[bin]
#[derive(Debug, PartialEq)]
struct Msg {
tag: u4,
#[brw(count_prefix = u16)]
items: Vec<u8>,
}
#[bin]
#[derive(Debug, PartialEq)]
struct Manual {
tag: u4,
#[br(temp)]
#[bw(calc = u16::try_from(self.items.len()).expect("test lengths fit"))]
n: u16,
#[br(count = n)]
items: Vec<u8>,
}
#[test]
fn round_trips_and_prefix_is_not_stored() {
let m = Msg {
tag: u4::new(5),
items: vec![0xAA, 0xBB, 0xCC],
};
let bytes = m.to_bytes().unwrap();
let decoded = Msg::decode_exact(&bytes).unwrap();
assert_eq!(decoded, m);
let built = Msg::builder()
.tag(u4::new(5))
.items(vec![0xAA, 0xBB, 0xCC])
.build()
.unwrap();
assert_eq!(built, m);
}
#[test]
fn golden_bytes_and_byte_identity_with_the_manual_triad() {
let sugar = Msg {
tag: u4::new(0xF),
items: vec![0xAA, 0xBB],
};
let manual = Manual {
tag: u4::new(0xF),
items: vec![0xAA, 0xBB],
};
let sugar_bytes = sugar.to_bytes().unwrap();
assert_eq!(sugar_bytes, manual.to_bytes().unwrap());
assert_eq!(sugar_bytes, [0xF0, 0x00, 0x2A, 0xAB, 0xB0]);
}
#[test]
fn zero_count_reads_empty() {
let m = Msg {
tag: u4::new(1),
items: vec![],
};
let bytes = m.to_bytes().unwrap();
let decoded = Msg::decode_exact(&bytes).unwrap();
assert!(decoded.items.is_empty());
}
#[bin]
#[derive(Debug, PartialEq)]
struct Packed {
tag: u4,
#[brw(count_prefix = u12)]
items: Vec<u8>,
}
#[test]
fn uint_prefix_is_bit_native() {
let m = Packed {
tag: u4::new(0xA),
items: vec![0x11, 0x22],
};
let bytes = m.to_bytes().unwrap();
assert_eq!(bytes, [0xA0, 0x02, 0x11, 0x22]);
assert_eq!(Packed::decode_exact(&bytes).unwrap(), m);
}
#[test]
fn encode_overflow_is_checked_not_truncated() {
#[bin]
#[derive(Debug, PartialEq)]
struct Tiny {
tag: u4,
#[brw(count_prefix = u8)]
items: Vec<u8>,
}
let m = Tiny {
tag: u4::new(0),
items: vec![0u8; 300],
};
let err = m.to_bytes().unwrap_err();
assert!(
matches!(&err.kind, ErrorKind::Convert { message } if message.contains("300")),
"expected a Convert error naming the length, got {err:?}"
);
assert_eq!(err.field, Some("items"), "error names the user's field");
}
#[bin(little)]
#[derive(Debug, PartialEq)]
enum Field {
#[bin(magic = 0x01u8)]
Name {
#[brw(count_prefix = u8)]
text: Vec<u8>,
},
#[bin(magic = 0x02u8)]
Age { age: u8 },
}
#[test]
fn enum_variant_prefix_round_trips() {
let f = Field::Name {
text: b"hi".to_vec(),
};
let bytes = f.to_bytes().unwrap();
assert_eq!(bytes, [0x01, 0x02, b'h', b'i']);
assert_eq!(Field::decode_exact(&bytes).unwrap(), f);
}
#[bin]
#[derive(Debug, PartialEq, Clone)]
struct Record {
a: u8,
b: u8,
}
#[bin]
#[derive(Debug, PartialEq)]
struct Table {
tag: u4,
#[brw(count_prefix = u8)]
#[nested]
rows: Vec<Record>,
}
#[test]
fn nested_elements_round_trip() {
let t = Table {
tag: u4::new(2),
rows: vec![Record { a: 1, b: 2 }, Record { a: 3, b: 4 }],
};
let bytes = t.to_bytes().unwrap();
assert_eq!(Table::decode_exact(&bytes).unwrap(), t);
}
#[bin(read_only, ctx(columns: u8))]
#[derive(Debug, PartialEq)]
struct Row {
#[br(count = columns)]
values: Vec<u8>,
}
#[bin(read_only)]
#[derive(Debug, PartialEq)]
struct Grid {
columns: u8,
#[brw(count_prefix = u8)]
#[br(ctx { columns })]
#[nested]
rows: Vec<Row>,
}
#[test]
fn composes_with_ctx_forwarding() {
let wire = [0x02, 0x02, 0x0A, 0x0B, 0x0C, 0x0D];
let g = Grid::decode_exact(&wire).unwrap();
assert_eq!(g.columns, 2);
assert_eq!(g.rows.len(), 2);
assert_eq!(g.rows[0].values, vec![0x0A, 0x0B]);
assert_eq!(g.rows[1].values, vec![0x0C, 0x0D]);
}
#[bin(read_only)]
#[derive(Debug, PartialEq)]
struct RxOnly {
tag: u4,
#[brw(count_prefix = u8)]
items: Vec<u8>,
}
#[bin(write_only)]
#[derive(Debug)]
struct TxOnly {
tag: u4,
#[brw(count_prefix = u8)]
items: Vec<u8>,
}
#[test]
fn read_only_and_write_only_work() {
let tx = TxOnly {
tag: u4::new(3),
items: vec![0x42],
};
let bytes = tx.to_bytes().unwrap();
let rx = RxOnly::decode_exact(&bytes).unwrap();
assert_eq!(rx.items, vec![0x42]);
}
#[test]
fn forged_over_count_is_a_graceful_eof() {
let mut w = bnb::bitstream::BitWriter::new();
w.write(u4::new(0)).unwrap();
w.write(5u16).unwrap();
w.write(0xAAu8).unwrap();
w.write(0xBBu8).unwrap();
let wire = w.into_bytes();
let err = Msg::decode_exact(&wire).unwrap_err();
assert!(
matches!(err.kind, ErrorKind::UnexpectedEof { .. }),
"got {err:?}"
);
assert_eq!(err.field, Some("items"));
}
#[test]
fn forged_huge_count_does_not_preallocate() {
let mut w = bnb::bitstream::BitWriter::new();
w.write(u4::new(0)).unwrap();
w.write(u16::MAX).unwrap();
let wire = w.into_bytes();
let err = Msg::decode_exact(&wire).unwrap_err();
assert!(matches!(err.kind, ErrorKind::UnexpectedEof { .. }));
}
#[test]
fn forged_under_count_is_trailing_bytes() {
let mut w = bnb::bitstream::BitWriter::new();
w.write(u4::new(0)).unwrap();
w.write(1u16).unwrap();
w.write(0xAAu8).unwrap();
w.write(0xBBu8).unwrap();
let wire = w.into_bytes();
let err = Msg::decode_exact(&wire).unwrap_err();
assert!(
matches!(err.kind, ErrorKind::TrailingBytes { .. }),
"got {err:?}"
);
}
}