#![cfg(feature = "whad")]
pub use crafter::{CrafterError, Result};
pub mod wire {
pub use crafter::wire::{Result, WireError};
}
pub(crate) const WHAD_TARGET_PROTOCOL_VERSION: u32 = 3;
pub(crate) mod proto {
#![allow(dead_code)]
#![allow(clippy::enum_variant_names)]
include!(concat!(env!("OUT_DIR"), "/whad_proto.rs"));
}
#[path = "../src/wire/backend/whad/framing.rs"]
mod framing;
#[path = "../src/wire/backend/whad/messages.rs"]
mod messages;
#[path = "../src/wire/backend/whad/dot15d4.rs"]
mod dot15d4;
use dot15d4::{
build_dot15d4_message, build_dot15d4_send, build_dot15d4_send_raw, build_dot15d4_sniff,
build_dot15d4_start, build_dot15d4_stop, parse_dot15d4_received, WhadDot15d4Rx,
};
use framing::{encode_message, FrameDecoder};
use messages::{build_send_raw_pdu, parse_received_pdu, WhadRxPdu};
use prost::Message as _;
const BLE_ADVERTISING_ACCESS_ADDRESS: u32 = 0x8E89_BED6;
const DOC_BLE_ADV_ADDRESS: [u8; 6] = [0x46, 0x53, 0x00, 0x5e, 0x00, 0x00];
const DOC_BLE_FLAGS_AD: [u8; 3] = [0x02, 0x01, 0x06];
#[test]
fn send_raw_pdu_frames_and_decodes_as_whad_message() {
let pdu = sample_adv_ind_pdu();
let message = build_send_raw_pdu(37, BLE_ADVERTISING_ACCESS_ADDRESS, &pdu);
let frame = encode_message(&message);
let mut decoder = FrameDecoder::default();
decoder.push(&frame);
let payload = decoder.next().expect("framed WHAD payload decodes");
assert_eq!(decoder.next(), None);
let decoded = proto::Message::decode(payload.as_slice()).expect("WHAD message decodes");
match decoded.msg {
Some(proto::message::Msg::Ble(ble)) => match ble.msg {
Some(proto::ble::message::Msg::SendRawPdu(command)) => {
assert_eq!(command.conn_handle, 37);
assert_eq!(command.access_address, BLE_ADVERTISING_ACCESS_ADDRESS);
assert_eq!(command.pdu, pdu);
assert_eq!(command.direction, proto::ble::BleDirection::Unknown as i32);
}
other => panic!("expected SendRawPdu, got {other:?}"),
},
other => panic!("expected BLE message, got {other:?}"),
}
}
#[test]
fn received_advertising_pdu_frames_in_chunks_and_parses() {
let message = proto::Message {
msg: Some(proto::message::Msg::Ble(proto::ble::Message {
msg: Some(proto::ble::message::Msg::AdvPdu(
proto::ble::AdvPduReceived {
adv_type: proto::ble::BleAdvType::AdvNonconnInd as i32,
rssi: -42,
bd_address: DOC_BLE_ADV_ADDRESS.to_vec(),
adv_data: DOC_BLE_FLAGS_AD.to_vec(),
addr_type: proto::ble::BleAddrType::Public as i32,
channel: 37,
phy: proto::ble::BlePhy::Le1m as i32,
},
)),
})),
};
let frame = encode_message(&message);
let mut decoder = FrameDecoder::default();
decoder.push(&frame[..5]);
assert_eq!(decoder.next(), None);
decoder.push(&frame[5..]);
let payload = decoder.next().expect("split WHAD frame decodes");
assert_eq!(decoder.next(), None);
let decoded = proto::Message::decode(payload.as_slice()).expect("WHAD message decodes");
let received = parse_received_pdu(&decoded).expect("received advertising PDU parses");
assert_eq!(
received,
WhadRxPdu {
channel: 37,
rssi: -42,
crc_valid: true,
access_address: BLE_ADVERTISING_ACCESS_ADDRESS,
pdu: expected_adv_nonconn_ind_pdu(),
}
);
}
fn sample_adv_ind_pdu() -> Vec<u8> {
let mut pdu = vec![
0x00,
(DOC_BLE_ADV_ADDRESS.len() + DOC_BLE_FLAGS_AD.len()) as u8,
];
pdu.extend_from_slice(&DOC_BLE_ADV_ADDRESS);
pdu.extend_from_slice(&DOC_BLE_FLAGS_AD);
pdu
}
fn expected_adv_nonconn_ind_pdu() -> Vec<u8> {
let mut pdu = vec![
0x02,
(DOC_BLE_ADV_ADDRESS.len() + DOC_BLE_FLAGS_AD.len()) as u8,
];
pdu.extend_from_slice(&DOC_BLE_ADV_ADDRESS);
pdu.extend_from_slice(&DOC_BLE_FLAGS_AD);
pdu
}
const DOC_DOT15D4_PDU: [u8; 9] = [0x41, 0x88, 0x02, 0xAB, 0xCD, 0xEF, 0x01, 0xDE, 0xAD];
fn dot15d4_framing_round_trip(message: &proto::Message) -> proto::Message {
let frame = encode_message(message);
let mut decoder = FrameDecoder::default();
decoder.push(&frame);
let payload = decoder.next().expect("framed WHAD dot15d4 payload decodes");
assert_eq!(decoder.next(), None);
proto::Message::decode(payload.as_slice()).expect("WHAD dot15d4 message decodes")
}
fn dot15d4_payload(message: proto::Message) -> proto::dot15d4::message::Msg {
match message.msg {
Some(proto::message::Msg::Dot15d4(dot15d4)) => {
dot15d4.msg.expect("dot15d4 message carries a payload")
}
other => panic!("expected top-level dot15d4 message, got {other:?}"),
}
}
#[test]
fn dot15d4_framing_command_messages_round_trip() {
let sniff = dot15d4_payload(dot15d4_framing_round_trip(&build_dot15d4_sniff(15)));
match sniff {
proto::dot15d4::message::Msg::Sniff(command) => assert_eq!(command.channel, 15),
other => panic!("expected dot15d4 sniff command, got {other:?}"),
}
let send_pdu = DOC_DOT15D4_PDU.to_vec();
let send = dot15d4_payload(dot15d4_framing_round_trip(&build_dot15d4_send(
20,
send_pdu.clone(),
)));
match send {
proto::dot15d4::message::Msg::Send(command) => {
assert_eq!(command.channel, 20);
assert_eq!(command.pdu, send_pdu);
}
other => panic!("expected dot15d4 send command, got {other:?}"),
}
let raw_pdu = DOC_DOT15D4_PDU.to_vec();
let send_raw = dot15d4_payload(dot15d4_framing_round_trip(&build_dot15d4_send_raw(
26,
raw_pdu.clone(),
0xBEEF,
)));
match send_raw {
proto::dot15d4::message::Msg::SendRaw(command) => {
assert_eq!(command.channel, 26);
assert_eq!(command.pdu, raw_pdu);
assert_eq!(command.fcs, 0xBEEF);
}
other => panic!("expected dot15d4 send-raw command, got {other:?}"),
}
let start = dot15d4_payload(dot15d4_framing_round_trip(&build_dot15d4_start()));
match start {
proto::dot15d4::message::Msg::Start(_) => {}
other => panic!("expected dot15d4 start command, got {other:?}"),
}
let stop = dot15d4_payload(dot15d4_framing_round_trip(&build_dot15d4_stop()));
match stop {
proto::dot15d4::message::Msg::Stop(_) => {}
other => panic!("expected dot15d4 stop command, got {other:?}"),
}
}
#[test]
fn dot15d4_framing_received_pdus_round_trip() {
let raw_pdu = DOC_DOT15D4_PDU.to_vec();
let raw_message = build_dot15d4_message(proto::dot15d4::message::Msg::RawPdu(
proto::dot15d4::RawPduReceived {
channel: 15,
rssi: Some(-57),
timestamp: Some(123),
fcs_validity: true,
pdu: raw_pdu.clone(),
fcs: 0xBEEF,
lqi: Some(200),
},
));
let decoded_raw = dot15d4_framing_round_trip(&raw_message);
let parsed_raw = parse_dot15d4_received(&decoded_raw).expect("framed raw PDU parses");
assert_eq!(
parsed_raw,
WhadDot15d4Rx {
channel: 15,
rssi: -57,
fcs_valid: true,
lqi: 200,
pdu: raw_pdu,
}
);
let pdu = DOC_DOT15D4_PDU.to_vec();
let pdu_message = build_dot15d4_message(proto::dot15d4::message::Msg::Pdu(
proto::dot15d4::PduReceived {
channel: 20,
rssi: Some(-70),
timestamp: Some(456),
fcs_validity: false,
pdu: pdu.clone(),
lqi: Some(120),
},
));
let decoded_pdu = dot15d4_framing_round_trip(&pdu_message);
let parsed_pdu = parse_dot15d4_received(&decoded_pdu).expect("framed PDU parses");
assert_eq!(
parsed_pdu,
WhadDot15d4Rx {
channel: 20,
rssi: -70,
fcs_valid: true,
lqi: 120,
pdu,
}
);
}
#[test]
fn dot15d4_framing_received_pdu_in_chunks_parses() {
let pdu = DOC_DOT15D4_PDU.to_vec();
let message = build_dot15d4_message(proto::dot15d4::message::Msg::RawPdu(
proto::dot15d4::RawPduReceived {
channel: 26,
rssi: Some(-42),
timestamp: Some(789),
fcs_validity: true,
pdu: pdu.clone(),
fcs: 0x1234,
lqi: Some(180),
},
));
let frame = encode_message(&message);
let mut decoder = FrameDecoder::default();
decoder.push(&frame[..5]);
assert_eq!(decoder.next(), None);
decoder.push(&frame[5..]);
let payload = decoder.next().expect("split WHAD dot15d4 frame decodes");
assert_eq!(decoder.next(), None);
let decoded = proto::Message::decode(payload.as_slice()).expect("WHAD dot15d4 message decodes");
let received = parse_dot15d4_received(&decoded).expect("received dot15d4 PDU parses");
assert_eq!(
received,
WhadDot15d4Rx {
channel: 26,
rssi: -42,
fcs_valid: true,
lqi: 180,
pdu,
}
);
}
#[test]
fn dot15d4_framing_concatenated_frames_decode_as_two() {
let first = build_dot15d4_sniff(11);
let second = build_dot15d4_send(26, DOC_DOT15D4_PDU.to_vec());
let mut bytes = encode_message(&first);
bytes.extend_from_slice(&encode_message(&second));
let mut decoder = FrameDecoder::default();
decoder.push(&bytes);
let first_payload = decoder
.next()
.expect("first framed dot15d4 message decodes");
let second_payload = decoder
.next()
.expect("second framed dot15d4 message decodes");
assert_eq!(decoder.next(), None);
let first_decoded =
proto::Message::decode(first_payload.as_slice()).expect("first dot15d4 message decodes");
match dot15d4_payload(first_decoded) {
proto::dot15d4::message::Msg::Sniff(command) => assert_eq!(command.channel, 11),
other => panic!("expected dot15d4 sniff command, got {other:?}"),
}
let second_decoded =
proto::Message::decode(second_payload.as_slice()).expect("second dot15d4 message decodes");
match dot15d4_payload(second_decoded) {
proto::dot15d4::message::Msg::Send(command) => {
assert_eq!(command.channel, 26);
assert_eq!(command.pdu, DOC_DOT15D4_PDU.to_vec());
}
other => panic!("expected dot15d4 send command, got {other:?}"),
}
}