From 93f3cce9e4313b47c1061451c582445663bae697 Mon Sep 17 00:00:00 2001 From: Jannik Beyerstedt Date: Mon, 11 May 2026 22:06:37 +0200 Subject: [PATCH] refactor: Move sniffer to own module --- .clippy.toml | 1 + src/main.rs | 107 +++++++++++++-------------------------------------- src/radio.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 81 deletions(-) create mode 100644 src/radio.rs diff --git a/.clippy.toml b/.clippy.toml index 76f6c1d..98e9866 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1 +1,2 @@ stack-size-threshold = 1024 +doc-valid-idents = ["WiFi", ".."] diff --git a/src/main.rs b/src/main.rs index 8c96a47..6c740ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,64 +12,14 @@ use embassy_time::{Duration, Timer}; use esp_backtrace as _; use esp_hal::clock::CpuClock; use esp_hal::timer::timg::TimerGroup; -use log::{debug, error, info, warn}; +use esp_radio::wifi; +use log::{debug, info, warn}; extern crate alloc; -const WIFI_CHANNEL: core::ffi::c_int = 180; +mod radio; -unsafe extern "C" { - /// Changes the 802.11 PHY channel - /// - /// C function signature: `void phy_change_channel(int,int,int,int)` - /// - /// Options: - /// - `primary`: Channel frequency in MHz - /// - `ignored1`: Ignored (set to 1) - /// - `ignored2`: Ignored (set to 0) - /// - `ht_mode`: Probably HT mode - pub unsafe fn phy_change_channel( - primary: core::ffi::c_int, - ignored1: core::ffi::c_int, - ignored2: core::ffi::c_int, - ht_mode: core::ffi::c_int, - ) -> core::ffi::c_int; - - /// Enables 802.11p mode on the ESP32-C5 - /// - /// C function signature: `void phy_11p_set(int,int)` - /// - /// Options: - /// - `enable`: Boolean option to enable 802.11p mode - /// - `zero`: Unknown, but need to be zero - pub unsafe fn phy_11p_set(enable: u8, zero: u8); -} - -#[repr(u8)] -#[derive(Debug)] -#[allow(dead_code)] -enum EspWifiPromiscuousPktType { - /// Management frame, indicates 'buf' argument is wifi_promiscuous_pkt_t - MGMT = 0, - /// Control frame, indicates 'buf' argument is wifi_promiscuous_pkt_t - CTRL = 1, - /// Data frame, indicates 'buf' argument is wifi_promiscuous_pkt_t - DATA = 2, - /// Other type, such as MIMO etc. 'buf' argument is wifi_promiscuous_pkt_t but the payload is zero length. - MISC = 3, -} - -impl EspWifiPromiscuousPktType { - #[allow(dead_code)] - pub fn as_repr(self) -> u8 { - self as u8 - } - - /// Converts to underlying type of `wifi_promiscuous_pkt_type_t` (used by `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`) - pub fn as_frame_type(self) -> core::ffi::c_uint { - (self as u8) as core::ffi::c_uint - } -} +const WIFI_CHANNEL: radio::Channel = 180; // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: @@ -115,34 +65,9 @@ async fn main(spawner: Spawner) -> ! { .expect("Failed to initialize Wi-Fi controller"); let mut sniffer = interfaces.sniffer; - sniffer - .set_promiscuous_mode(true) - .expect("Failed to enable promiscuous mode"); + radio::setup_wifi_sniffer(WIFI_CHANNEL, &mut sniffer, handle_frame) + .expect("Fatal error initializing 802.11p sniffer"); - sniffer.set_receive_cb(|packet| { - // Ignore frames with errors and non-data frames - if packet.rx_cntl.rx_state != 0 { - warn!("Received frame has RX error: {}", packet.rx_cntl.rx_state); - return; - } - if packet.frame_type != EspWifiPromiscuousPktType::DATA.as_frame_type() { - debug!("Received frame is not a DATA frame: {}", packet.frame_type); - return; - } - - // TODO: handle 802.11 frame - info!("Received frame with {} bytes", packet.len); - }); - - unsafe { - phy_11p_set(1, 0); - match phy_change_channel(5000 + (WIFI_CHANNEL * 5), 1, 0, 0) { - 0 => {} - ret => { - error!("ERROR: Failed to change channel: {ret}"); - } - } - } info!("WiFi promiscuous mode on channel {WIFI_CHANNEL} running"); // TODO: Spawn some tasks @@ -154,3 +79,23 @@ async fn main(spawner: Spawner) -> ! { Timer::after(Duration::from_secs(1)).await; } } + +#[allow( + clippy::needless_pass_by_value, + reason = "adhering to callback interface" +)] +fn handle_frame(frame: wifi::sniffer::PromiscuousPkt<'_>) { + // Ignore frames with errors and non-data frames + if frame.rx_cntl.rx_state != 0 { + warn!("Received frame has RX error: {}", frame.rx_cntl.rx_state); + return; + } + + if !radio::PromiscuousPktType::Data.matches(frame.frame_type) { + debug!("Received frame is not a DATA frame: {}", frame.frame_type); + return; + } + + // TODO: handle 802.11 frame + info!("Received frame with {} bytes", frame.len); +} diff --git a/src/radio.rs b/src/radio.rs new file mode 100644 index 0000000..83a5c27 --- /dev/null +++ b/src/radio.rs @@ -0,0 +1,98 @@ +//! IEEE 802.11p C-ITS Capture with ESP32-C5 + +// ----------------------------- +// missing interface definitions +// ----------------------------- + +use alloc::format; +use alloc::string::String; + +use esp_radio::wifi::sniffer; + +pub type Channel = core::ffi::c_int; + +unsafe extern "C" { + /// Changes the 802.11 PHY channel + /// + /// C function signature: `void phy_change_channel(int,int,int,int)` + /// + /// Options: + /// - `primary`: Channel frequency in MHz + /// - `ignored1`: Ignored (set to 1) + /// - `ignored2`: Ignored (set to 0) + /// - `ht_mode`: Probably HT mode + unsafe fn phy_change_channel( + primary: Channel, + ignored1: core::ffi::c_int, + ignored2: core::ffi::c_int, + ht_mode: core::ffi::c_int, + ) -> core::ffi::c_int; + + /// Enables 802.11p mode on the ESP32-C5 + /// + /// C function signature: `void phy_11p_set(int,int)` + /// + /// Options: + /// - `enable`: Boolean option to enable 802.11p mode + /// - `zero`: Unknown, but need to be zero + unsafe fn phy_11p_set(enable: u8, zero: u8); +} + +/// Enum values for `wifi_promiscuous_pkt_type_t` used in `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type` +/// +/// Original value is actually `core::ffi::c_uint`, but that can't be used in `#[repr()]` attributes, +/// so we're using `u8` as the smallest suitable type here that can be casted to a bigger type. +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +#[allow(dead_code)] +pub(crate) enum PromiscuousPktType { + /// Management frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t` + Mgmt = 0, + /// Control frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t` + Ctrl = 1, + /// Data frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t` + Data = 2, + /// Other type, such as MIMO etc. 'buf' argument is `wifi_promiscuous_pkt_t` but the payload is zero length. + Misc = 3, +} + +impl PromiscuousPktType { + #[allow(dead_code)] + pub fn as_repr(self) -> u8 { + self as u8 + } + + /// Converts to underlying type of `wifi_promiscuous_pkt_type_t` (used by `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`) + pub fn as_frame_type(self) -> core::ffi::c_uint { + core::ffi::c_uint::from(self.as_repr()) + } + + /// Determines if + pub fn matches(self, frame_type: core::ffi::c_uint) -> bool { + self.as_frame_type() == frame_type + } +} + +/// Configure WiFi Sniffer to a certain 802.11p channel +pub fn setup_wifi_sniffer( + channel: Channel, + sniffer: &mut sniffer::Sniffer, + callback: fn(sniffer::PromiscuousPkt<'_>), +) -> Result<(), String> { + sniffer + .set_promiscuous_mode(true) + .map_err(|err| format!("Failed to enable promiscuous mode: {err:?}"))?; + + sniffer.set_receive_cb(callback); + + // convert channel number to frequency in MHz + let primary = 5000 + (channel * 5); + + unsafe { + phy_11p_set(1, 0); + match phy_change_channel(primary, 1, 0, 0) { + 0 => Ok(()), + ret => Err(format!("Failed to change channel: {ret}")), + } + } +}