use bnb::{BitEnum, bin, bitfield, bitflags, u2, u3, u4, u5, u7};
use tracing::info;
#[bitfield(u8, bits = lsb)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Quality {
snr: u5, confidence: u3, }
#[derive(BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
#[bit_enum(u2)]
enum Priority {
Low,
Normal,
High,
Critical,
}
#[derive(BitEnum, Clone, Copy, Debug, PartialEq, Eq)]
#[bit_enum(u4)]
enum SensorKind {
Temperature,
Pressure,
Humidity,
Voltage,
#[catch_all]
Unknown(u4),
}
#[bitfield(u32, bits = msb)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Header {
version: u3, priority: Priority, kind: SensorKind, quality: Quality, channels: u7, seq: u8, }
#[bitflags(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Status {
calibrated: bool,
fault: bool,
low_battery: bool,
retransmit: bool,
}
#[bin(big, validate = frame_is_sound)]
#[derive(Debug, Clone, PartialEq, Eq)]
struct TelemetryFrame {
header: Header,
status: Status,
#[reserved]
rsv: u8, #[brw(count_prefix = u8)] samples: Vec<u16>,
#[bw(calc = self.crc())]
#[builder(default)]
crc: u16,
}
impl TelemetryFrame {
fn crc(&self) -> u16 {
let mut c: u16 = 0xFFFF ^ u16::from(self.status.bits());
for &s in &self.samples {
c = c.rotate_left(7) ^ s;
}
c
}
}
fn frame_is_sound(f: &TelemetryFrame) -> Result<(), String> {
if f.header.version() == u3::new(0) {
return Err("protocol version must be >= 1".into());
}
if f.samples.is_empty() {
return Err("a frame must carry at least one sample".into());
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.without_time()
.init();
let header = Header::new()
.with_version(u3::new(1))
.with_priority(Priority::High)
.with_kind(SensorKind::Pressure)
.with_quality(
Quality::new()
.with_snr(u5::new(27))
.with_confidence(u3::new(6)),
)
.with_channels(u7::new(4))
.with_seq(0x2A);
let frame = TelemetryFrame::builder()
.header(header)
.status(Status::CALIBRATED | Status::LOW_BATTERY)
.samples(vec![0x1001, 0x1002, 0x1003])
.build()?; info!(
?header,
"built header (3-level nesting; bitfield Debug decomposes it)"
);
info!(
is_canonical = frame.is_canonical(),
diff = ?frame.canonical_diff(), "freshly built frame",
);
assert!(!frame.is_canonical());
assert_eq!(frame.canonical_diff(), ["crc"]);
let verbatim = frame.to_bytes()?; let canonical = frame.to_canonical_bytes()?; info!(
verbatim_len = verbatim.len(),
canonical_crc = %format!("0x{:04x}", frame.crc()),
"to_bytes is verbatim (crc=0); to_canonical_bytes fills the crc in",
);
let decoded = TelemetryFrame::decode_exact(&canonical)?;
info!(
version = %decoded.header.version(),
priority = ?decoded.header.priority(),
kind = ?decoded.header.kind(),
snr = %decoded.header.quality().snr(),
samples = decoded.samples.len(),
is_canonical = decoded.is_canonical(),
"decoded the canonical frame",
);
assert!(decoded.is_canonical());
assert_eq!(decoded.samples, vec![0x1001, 0x1002, 0x1003]);
assert_eq!(decoded.to_bytes()?, canonical);
let mut tampered = decoded.clone();
tampered.rsv = 0xFF; assert_eq!(tampered.to_bytes()?[5], 0xFF); assert_eq!(tampered.to_canonical_bytes()?[5], 0x00); info!(diff = ?tampered.canonical_diff(), "rsv set to 0xFF → not canonical");
let missing = TelemetryFrame::builder()
.header(header)
.samples(vec![0x1])
.build(); info!(error = %missing.unwrap_err(), "builder rejects a missing required field");
let bad = TelemetryFrame::builder()
.header(Header::new().with_version(u3::new(0)))
.status(Status::empty())
.samples(vec![0x1])
.build();
assert!(bad.is_err());
info!(error = %bad.unwrap_err(), "validate rejects an unsound frame");
info!("all checks passed");
Ok(())
}