#[macro_use]
mod support;
use std::fs;
use crafter::prelude::*;
#[test]
fn dot15d4_hex_fixture_decodes_short_data_frame() {
let frame = decode_hex_fixture("mac-data-short", fixture_str!("dot15d4/mac-data-short.hex"));
let packet = Packet::decode_from_link(LinkType::Ieee802154, &frame)
.expect("decode bare 802.15.4 short-addressing frame");
let mac = packet
.layer::<Dot15d4>()
.expect("Dot15d4 MAC layer present");
let fields = mac.inspection_fields();
assert_eq!(field_value(&fields, "frame_type"), Some("Data"));
assert_eq!(field_value(&fields, "seq"), Some("42"));
assert_eq!(field_value(&fields, "dest_addr"), Some("0x0000"));
assert_eq!(field_value(&fields, "src_addr"), Some("0xABCD"));
assert_eq!(field_value(&fields, "pan_id_compression"), Some("true"));
assert!(
packet.layer::<ZigbeeNwk>().is_none(),
"short MAC fixture should carry no Zigbee NWK layer"
);
}
#[test]
fn dot15d4_hex_fixture_decodes_extended_data_frame() {
let frame = decode_hex_fixture(
"mac-data-extended",
fixture_str!("dot15d4/mac-data-extended.hex"),
);
let packet = Packet::decode_from_link(LinkType::Ieee802154, &frame)
.expect("decode bare 802.15.4 extended-addressing frame");
let mac = packet
.layer::<Dot15d4>()
.expect("Dot15d4 MAC layer present");
let fields = mac.inspection_fields();
assert_eq!(field_value(&fields, "frame_type"), Some("Data"));
assert_eq!(field_value(&fields, "seq"), Some("17"));
assert_eq!(
field_value(&fields, "dest_addr"),
Some("0x0011223344556677")
);
assert_eq!(field_value(&fields, "src_addr"), Some("0x8899AABBCCDDEEFF"));
assert_eq!(field_value(&fields, "pan_id_compression"), Some("true"));
}
#[test]
fn dot15d4_hex_fixture_decodes_full_zigbee_nwk_aps_stack() {
let frame = decode_hex_fixture("zigbee-nwk-aps", fixture_str!("dot15d4/zigbee-nwk-aps.hex"));
let packet = Packet::decode_from_link(LinkType::Ieee802154, &frame)
.expect("decode bare 802.15.4 MAC + Zigbee NWK + APS frame");
let mac = packet
.layer::<Dot15d4>()
.expect("Dot15d4 MAC layer present");
let mac_fields = mac.inspection_fields();
assert_eq!(field_value(&mac_fields, "frame_type"), Some("Data"));
assert_eq!(field_value(&mac_fields, "seq"), Some("7"));
assert_eq!(field_value(&mac_fields, "dest_addr"), Some("0x0000"));
assert_eq!(field_value(&mac_fields, "src_addr"), Some("0xABCD"));
let nwk = packet
.layer::<ZigbeeNwk>()
.expect("ZigbeeNwk layer present");
let nwk_fields = nwk.inspection_fields();
assert_eq!(field_value(&nwk_fields, "dest"), Some("0x0000"));
assert_eq!(field_value(&nwk_fields, "src"), Some("0xabcd"));
assert_eq!(field_value(&nwk_fields, "radius"), Some("30"));
assert_eq!(field_value(&nwk_fields, "seq"), Some("66"));
let aps = packet
.layer::<ZigbeeAps>()
.expect("ZigbeeAps layer present");
let aps_fields = aps.inspection_fields();
assert_eq!(field_value(&aps_fields, "cluster"), Some("0x0006"));
assert_eq!(field_value(&aps_fields, "profile"), Some("0x0104"));
assert_eq!(field_value(&aps_fields, "dest_endpoint"), Some("1"));
assert_eq!(field_value(&aps_fields, "src_endpoint"), Some("1"));
assert_eq!(field_value(&aps_fields, "counter"), Some("9"));
}
#[test]
fn dot15d4_summary_golden_snapshots_match_hex_fixtures() {
assert_dot15d4_summary_fixture(
"mac-data-short",
fixture_str!("dot15d4/mac-data-short.hex"),
"summaries/dot15d4-mac-data-short.summary.txt",
);
assert_dot15d4_summary_fixture(
"mac-data-extended",
fixture_str!("dot15d4/mac-data-extended.hex"),
"summaries/dot15d4-mac-data-extended.summary.txt",
);
assert_dot15d4_summary_fixture(
"zigbee-nwk-aps",
fixture_str!("dot15d4/zigbee-nwk-aps.hex"),
"summaries/dot15d4-zigbee-nwk-aps.summary.txt",
);
}
fn assert_dot15d4_summary_fixture(label: &str, hex: &str, summary_path: &str) {
let frame = decode_hex_fixture(label, hex);
let packet = Packet::decode_from_link(LinkType::Ieee802154, &frame)
.unwrap_or_else(|err| panic!("{label} fixture should decode: {err}"));
let expected = read_summary_fixture(summary_path);
assert_eq!(
expected.trim_end(),
packet.summary().trim_end(),
"{label} summary did not match {summary_path}"
);
}
fn read_summary_fixture(path: &str) -> String {
fs::read_to_string(support::fixture_path(path))
.unwrap_or_else(|err| panic!("summary fixture {path} should be readable: {err}"))
}
fn field_value<'a>(fields: &'a [(&'static str, String)], name: &str) -> Option<&'a str> {
fields
.iter()
.find(|(field, _)| *field == name)
.map(|(_, value)| value.as_str())
}
fn decode_hex_fixture(label: &str, text: &str) -> Vec<u8> {
let mut compact = String::new();
for line in text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
for ch in line.chars().filter(|ch| !ch.is_whitespace()) {
assert!(
ch.is_ascii_hexdigit(),
"hex fixture {label} contains non-hex character {ch:?}"
);
compact.push(ch);
}
}
assert!(
compact.len() % 2 == 0,
"hex fixture {label} has an odd hex length"
);
compact
.as_bytes()
.chunks(2)
.map(|chunk| {
let byte = std::str::from_utf8(chunk)
.unwrap_or_else(|_| panic!("hex fixture {label} contains non-UTF8 hex"));
u8::from_str_radix(byte, 16)
.unwrap_or_else(|_| panic!("hex fixture {label} has invalid hex byte {byte}"))
})
.collect()
}