use crate::{Dot11, Packet, Radiotap, DOT11_TAG_DS_PARAMETER_SET, DOT11_TAG_SSID};
use super::record::{PacketRecord, TransformTrace, WifiDecryptState, WifiMetadata};
use super::transform::{PacketTransform, TransformOutput};
use super::Result;
#[derive(Debug, Clone, Default)]
pub struct Dot11Metadata {
input_count: usize,
annotated_count: usize,
}
impl Dot11Metadata {
pub fn new() -> Self {
Self::default()
}
pub const fn input_count(&self) -> usize {
self.input_count
}
pub const fn annotated_count(&self) -> usize {
self.annotated_count
}
pub fn annotate(&mut self, record: PacketRecord) -> Result<TransformOutput> {
self.transform_to_output(record)
}
}
impl PacketTransform for Dot11Metadata {
fn name(&self) -> &'static str {
"dot11-metadata"
}
fn transform(
&mut self,
mut record: PacketRecord,
emit: &mut dyn FnMut(PacketRecord) -> Result<()>,
) -> Result<()> {
self.input_count += 1;
if let Some(wifi) = metadata_from_packet(record.packet(), record.metadata().wifi().cloned())
{
record = record.with_wifi_metadata(wifi);
record
.metadata_mut()
.push_transform_trace(TransformTrace::new(self.name()).with_note("annotated"));
self.annotated_count += 1;
}
emit(record)
}
}
fn metadata_from_packet(packet: &Packet, existing: Option<WifiMetadata>) -> Option<WifiMetadata> {
let mut wifi = existing.unwrap_or_default();
let mut found = false;
if let Some(radiotap) = packet.layer::<Radiotap>() {
if let Some(channel) = radiotap.channel_value() {
let frequency = channel.frequency();
wifi = wifi.with_frequency_mhz(u32::from(frequency));
if let Some(channel_number) = channel_number_from_frequency_mhz(frequency) {
wifi = wifi.with_channel(channel_number);
}
found = true;
}
if let Some(signal) = radiotap.antenna_signal_value() {
wifi = wifi.with_signal_dbm(i16::from(signal));
found = true;
}
}
if let Some(dot11) = packet.layer::<Dot11>() {
wifi = wifi.with_dot11_frame_type(dot11.frame_type());
found = true;
if let Some(subtype) = dot11.management_subtype() {
wifi = wifi.with_dot11_management_subtype(subtype);
}
if let Some(subtype) = dot11.control_subtype() {
wifi = wifi.with_dot11_control_subtype(subtype);
}
if let Some(subtype) = dot11.data_subtype() {
wifi = wifi.with_dot11_data_subtype(subtype);
}
if let Some(bssid) = dot11.bssid() {
wifi = wifi.with_bssid(bssid);
}
if let Some(transmitter) = dot11.transmitter() {
wifi = wifi.with_transmitter(transmitter);
}
if let Some(receiver) = dot11.receiver() {
wifi = wifi.with_receiver(receiver);
}
let protected = dot11.is_protected();
wifi = wifi
.with_protected(protected)
.with_decrypt_state(if protected {
WifiDecryptState::NotAttempted
} else {
WifiDecryptState::NotRequired
});
for tag in dot11.tagged_parameters() {
match tag.id() {
DOT11_TAG_SSID => {
wifi = wifi.with_ssid(tag.value().to_vec());
}
DOT11_TAG_DS_PARAMETER_SET => {
if let Some(channel) = tag.value().first().copied() {
wifi = wifi.with_channel(u16::from(channel));
}
}
_ => {}
}
}
}
found.then_some(wifi)
}
fn channel_number_from_frequency_mhz(frequency: u16) -> Option<u16> {
match frequency {
2412..=2472 if (frequency - 2407) % 5 == 0 => Some((frequency - 2407) / 5),
2484 => Some(14),
5005..=5895 if (frequency - 5000) % 5 == 0 => Some((frequency - 5000) / 5),
5955..=7115 if (frequency - 5950) % 5 == 0 => Some((frequency - 5950) / 5),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Dot11DataSubtype, Dot11FrameType, Dot11ManagementSubtype, LinkType, MacAddr, Raw};
fn mac(last: u8) -> MacAddr {
MacAddr::new([0x02, 0x00, 0x00, 0x00, 0x00, last])
}
#[test]
fn dot11_metadata_annotates_radiotap_beacon() {
let ap = mac(1);
let packet = Radiotap::new().channel((2412, 0x00a0)).antenna_signal(-42)
/ Dot11::beacon()
.addr1(MacAddr::BROADCAST)
.addr2(ap)
.addr3(ap)
.ssid(b"mesh-a")
.ds_parameter_set(6);
let mut transform = Dot11Metadata::new();
let output = transform
.annotate(PacketRecord::new(packet).with_link_type(LinkType::Radiotap))
.unwrap();
assert_eq!(transform.input_count(), 1);
assert_eq!(transform.annotated_count(), 1);
assert_eq!(output.len(), 1);
let record = &output.records()[0];
assert_eq!(record.metadata().link_type(), Some(LinkType::Radiotap));
assert_eq!(record.metadata().transforms()[0].name(), "dot11-metadata");
let wifi = record.metadata().wifi().unwrap();
assert_eq!(wifi.ssid(), Some(b"mesh-a".as_slice()));
assert_eq!(wifi.ssid_str(), Some("mesh-a"));
assert_eq!(wifi.bssid(), Some(ap));
assert_eq!(wifi.transmitter(), Some(ap));
assert_eq!(wifi.receiver(), Some(MacAddr::BROADCAST));
assert_eq!(wifi.channel(), Some(6));
assert_eq!(wifi.frequency_mhz(), Some(2412));
assert_eq!(wifi.signal_dbm(), Some(-42));
assert_eq!(wifi.dot11_frame_type(), Some(Dot11FrameType::Management));
assert_eq!(
wifi.dot11_management_subtype(),
Some(Dot11ManagementSubtype::Beacon)
);
assert_eq!(wifi.protected(), Some(false));
assert_eq!(wifi.decrypt_state(), Some(WifiDecryptState::NotRequired));
}
#[test]
fn dot11_metadata_marks_protected_data_without_decrypting() {
let ap = mac(2);
let station = mac(3);
let frame_control = Dot11::data().frame_control_value().with_protected(true);
let packet = Radiotap::new()
/ Dot11::data()
.frame_control(frame_control)
.addr1(ap)
.addr2(station)
.addr3(ap)
/ Raw::from([0xaa, 0xbb, 0xcc, 0xdd]);
let mut transform = Dot11Metadata::new();
let output = transform.annotate(PacketRecord::new(packet)).unwrap();
let wifi = output.records()[0].metadata().wifi().unwrap();
assert_eq!(wifi.dot11_frame_type(), Some(Dot11FrameType::Data));
assert_eq!(wifi.dot11_data_subtype(), Some(Dot11DataSubtype::Data));
assert_eq!(wifi.bssid(), Some(ap));
assert_eq!(wifi.transmitter(), Some(station));
assert_eq!(wifi.receiver(), Some(ap));
assert_eq!(wifi.protected(), Some(true));
assert_eq!(wifi.decrypt_state(), Some(WifiDecryptState::NotAttempted));
assert!(output.records()[0].packet().layer::<Raw>().is_some());
}
#[test]
fn dot11_metadata_passes_non_dot11_records_unchanged() {
let mut transform = Dot11Metadata::new();
let record = PacketRecord::new(Raw::from("opaque"));
let output = transform.annotate(record).unwrap();
assert_eq!(output.len(), 1);
assert_eq!(transform.input_count(), 1);
assert_eq!(transform.annotated_count(), 0);
assert!(output.records()[0].metadata().wifi().is_none());
assert!(output.records()[0].metadata().transforms().is_empty());
assert_eq!(output.records()[0].packet().summary(), "Raw(len=6)");
}
#[test]
fn dot11_metadata_derives_channel_from_radiotap_frequency() {
let packet = Radiotap::new().channel((2437, 0x00a0)) / Dot11::probe_request();
let mut transform = Dot11Metadata::new();
let output = transform.annotate(PacketRecord::new(packet)).unwrap();
let wifi = output.records()[0].metadata().wifi().unwrap();
assert_eq!(wifi.frequency_mhz(), Some(2437));
assert_eq!(wifi.channel(), Some(6));
assert_eq!(
wifi.dot11_management_subtype(),
Some(Dot11ManagementSubtype::ProbeRequest)
);
}
}