#[derive(Clone)]
pub struct LayoutField {
pub path: &'static str,
pub ty: &'static str,
pub lo: u32,
pub hi: u32,
pub nested: Option<fn() -> &'static [LayoutField]>,
}
pub trait BodyLayout {
fn layout() -> &'static [LayoutField];
}
impl std::fmt::Debug for LayoutField {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.nested.is_some() {
write!(
f,
"{} bytes {:#04x}..{:#04x} ({})",
self.path,
self.lo / 8,
(self.hi + 1) / 8,
self.ty,
)
} else {
write!(
f,
"{} bits {}..={} ({})",
self.path, self.lo, self.hi, self.ty
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cbin::{self, Cbin, Header};
use nord_bits_derive::bitbody;
use std::io::Cursor;
#[bitbody(2)]
#[derive(Default)]
struct Inner {
#[bits(0..=0)]
pub flag: bool,
#[bits(4..=11)]
pub level: u8,
}
#[bitbody(6)]
struct Outer {
#[bits(0..=15)]
word: u16,
#[at(0x02..0x04)]
pub inner: Inner,
#[bits(40..=47)]
pub level: u8,
}
fn body() -> Outer {
let mut b = Outer::try_from([0xab, 0xcd, 0x0f, 0xf0, 0xff, 0x00]).unwrap();
b.word = 0x0102;
b.inner.level = 0x55;
b.level = 7;
b
}
#[test]
fn the_codec_is_the_declaration() {
let raw = <[u8; 6]>::from(&body());
assert_eq!(raw, [0x01, 0x02, 0x05, 0x50, 0xff, 0x07]);
let back = Outer::try_from(raw).unwrap();
assert_eq!(back.word, 0x0102);
assert_eq!(back.inner.level, 0x55);
assert_eq!(back.level, 7);
}
#[test]
fn a_bitbody_rides_the_container() {
for generation in [cbin::Generation::V1, cbin::Generation::V0] {
let mut header = Header::new("tstb", (2, 5), 7);
header.generation = generation;
let file = Cbin {
header,
body: body(),
};
let mut bytes = Cursor::new(Vec::new());
file.write_to(&mut bytes).unwrap();
let mut bytes = Cursor::new(bytes.into_inner());
let back: Cbin<Outer> = cbin::read(&mut bytes, "tstb").unwrap();
assert_eq!(back.header.slot(), (2, 5));
assert_eq!(<[u8; 6]>::from(&back.body), <[u8; 6]>::from(&body()));
}
}
#[test]
fn paths_recurse_through_nested_bodies() {
let b = body();
let paths: Vec<String> = b.fields().into_iter().map(|f| f.path).collect();
assert_eq!(paths, ["inner.flag", "inner.level", "level"]);
let mut b = body();
b.set_field("inner.level", "3").unwrap();
assert_eq!(b.inner.level, 3);
b.set_field("level", "9").unwrap();
assert_eq!(b.level, 9);
assert_eq!(b.inner.level, 3);
assert!(b.set_field("word", "1").is_err(), "private is not a path");
}
#[test]
fn the_layout_is_readable_as_data() {
let fields = Outer::layout();
let rendered: Vec<String> = fields.iter().map(|f| format!("{f:?}")).collect();
assert_eq!(
rendered,
[
"word bits 0..=15 (u16)",
"inner bytes 0x02..0x04 (Inner)",
"level bits 40..=47 (u8)",
],
);
let nested = fields[1].nested.expect("inner is nested");
let rendered: Vec<String> = nested().iter().map(|f| format!("{f:?}")).collect();
assert_eq!(
rendered,
["flag bits 0..=0 (bool)", "level bits 4..=11 (u8)"]
);
}
}