From 2dc421a589f885a71036f41a9dcc62d8e4ae4e46 Mon Sep 17 00:00:00 2001 From: umgefahren <55623006+umgefahren@users.noreply.github.com> Date: Wed, 13 Dec 2023 18:08:23 +0100 Subject: [PATCH] Extended the Scan API --- cyw43/Cargo.toml | 4 +- cyw43/src/control.rs | 92 ++++++++++++++++++++++++++++--- cyw43/src/events.rs | 4 +- cyw43/src/runner.rs | 2 +- cyw43/src/structs.rs | 128 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 205 insertions(+), 25 deletions(-) diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml index 293c0098..b839436d 100644 --- a/cyw43/Cargo.toml +++ b/cyw43/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [features] -defmt = ["dep:defmt"] +defmt = ["dep:defmt", "heapless/defmt-03"] log = ["dep:log"] # Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`. @@ -26,6 +26,8 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-rc.2" } num_enum = { version = "0.5.7", default-features = false } +heapless = "0.8.0" + [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/" diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs index 826edfe1..c236ec5a 100644 --- a/cyw43/src/control.rs +++ b/cyw43/src/control.rs @@ -1,5 +1,6 @@ use core::cmp::{max, min}; use core::iter::zip; +use core::time::Duration; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::{HardwareAddress, LinkState}; @@ -29,6 +30,45 @@ pub struct Control<'a> { ioctl_state: &'a IoctlState, } +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ScanType { + Active { + /// Period of time to wait on each channel when active scanning. + dwell_time: Option, + }, + Passive { + /// Period of time to wait on each channel when passive scanning. + dwell_time: Option, + }, +} + +#[derive(Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScanOptions { + pub ssid: Option>, + /// If set to `None`, all APs will be returned. If set to `Some`, only APs + /// with the specified BSSID will be returned. + pub bssid: Option<[u8; 6]>, + /// Number of probes to send on each channel. + pub nprobes: Option, + /// Time to spend waiting on the home channel. + pub home_time: Option, + pub scan_type: ScanType, +} + +impl Default for ScanOptions { + fn default() -> Self { + Self { + ssid: None, + bssid: None, + nprobes: None, + home_time: None, + scan_type: ScanType::Passive { dwell_time: None }, + } + } +} + impl<'a> Control<'a> { pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { Self { @@ -458,22 +498,56 @@ impl<'a> Control<'a> { /// # Note /// Device events are currently implemented using a bounded queue. /// To not miss any events, you should make sure to always await the stream. - pub async fn scan(&mut self) -> Scanner<'_> { + pub async fn scan(&mut self, scan_opts: ScanOptions) -> Scanner<'_> { + const SCANTYPE_ACTIVE: u8 = 0; const SCANTYPE_PASSIVE: u8 = 1; + let mut active_time = !0; + let mut passive_time = !0; + + let scan_type = match scan_opts.scan_type { + ScanType::Active { dwell_time: None } => SCANTYPE_ACTIVE, + ScanType::Active { + dwell_time: Some(dwell_time), + } => { + active_time = dwell_time.as_millis() as u32; + if active_time == !0 { + active_time = !0 - 1; + } + SCANTYPE_ACTIVE + } + ScanType::Passive { dwell_time: None } => SCANTYPE_PASSIVE, + ScanType::Passive { + dwell_time: Some(dwell_time), + } => { + passive_time = dwell_time.as_millis() as u32; + if passive_time == !0 { + passive_time = !0 - 1; + } + SCANTYPE_PASSIVE + } + }; + let scan_params = ScanParams { version: 1, action: 1, sync_id: 1, - ssid_len: 0, - ssid: [0; 32], - bssid: [0xff; 6], + ssid_len: scan_opts.ssid.as_ref().map(|e| e.as_bytes().len() as u32).unwrap_or(0), + ssid: scan_opts + .ssid + .map(|e| { + let mut ssid = [0; 32]; + ssid[..e.as_bytes().len()].copy_from_slice(e.as_bytes()); + ssid + }) + .unwrap_or([0; 32]), + bssid: scan_opts.bssid.unwrap_or([0xff; 6]), bss_type: 2, - scan_type: SCANTYPE_PASSIVE, - nprobes: !0, - active_time: !0, - passive_time: !0, - home_time: !0, + scan_type, + nprobes: scan_opts.nprobes.unwrap_or(!0).into(), + active_time, + passive_time, + home_time: scan_opts.home_time.map(|e| e.as_millis() as u32).unwrap_or(!0), channel_num: 0, channel_list: [0; 1], }; diff --git a/cyw43/src/events.rs b/cyw43/src/events.rs index a94c49a0..b2a76f21 100644 --- a/cyw43/src/events.rs +++ b/cyw43/src/events.rs @@ -311,13 +311,13 @@ pub struct Status { pub status: u32, } -#[derive(Clone, Copy)] +#[derive(Clone)] pub enum Payload { None, BssInfo(BssInfo), } -#[derive(Clone, Copy)] +#[derive(Clone)] pub struct Message { pub header: Status, diff --git a/cyw43/src/runner.rs b/cyw43/src/runner.rs index 83aee6b4..c06a7480 100644 --- a/cyw43/src/runner.rs +++ b/cyw43/src/runner.rs @@ -427,7 +427,7 @@ where let Some(bss_info) = BssInfo::parse(bss_info) else { return; }; - events::Payload::BssInfo(*bss_info) + events::Payload::BssInfo(bss_info) } Event::ESCAN_RESULT => events::Payload::None, _ => events::Payload::None, diff --git a/cyw43/src/structs.rs b/cyw43/src/structs.rs index 5ba633c7..81b1d077 100644 --- a/cyw43/src/structs.rs +++ b/cyw43/src/structs.rs @@ -457,7 +457,7 @@ impl ScanResults { let (scan_results, bssinfo) = packet.split_at_mut(ScanResults::SIZE); let scan_results = ScanResults::from_bytes_mut(scan_results.try_into().unwrap()); - if scan_results.bss_count > 0 && bssinfo.len() < BssInfo::SIZE { + if scan_results.bss_count > 0 && bssinfo.len() < BssInfoInternal::SIZE { warn!("Scan result, incomplete BssInfo"); return None; } @@ -466,10 +466,49 @@ impl ScanResults { } } -/// Wifi Scan Result #[derive(Clone, Copy)] -// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BssSecurity { + Open, + Wpa2, + Wpa, + WepPsk, + None, +} + +#[derive(Clone, Copy)] #[repr(C, packed(2))] +struct BssInfoInternal { + version: u32, + length: u32, + bssid: [u8; 6], + beacon_period: u16, + capability: u16, + ssid_len: u8, + ssid: [u8; 32], + rateset_count: u32, + rateset_rates: [u8; 16], + chanspec: u16, + atim_window: u16, + dtim_period: u8, + rssi: i16, + phy_noise: i8, + n_cap: u8, + nbss_cap: u32, + ctl_ch: u8, + reserved32: [u32; 1], + flags: u8, + reserved: [u8; 3], + basic_mcs: [u8; 16], + ie_offset: u16, + ie_length: u32, + snr: i16, +} + +impl_bytes!(BssInfoInternal); + +/// Wifi Scan Result +#[derive(Clone)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub struct BssInfo { pub version: u32, @@ -477,20 +516,85 @@ pub struct BssInfo { pub bssid: [u8; 6], pub beacon_period: u16, pub capability: u16, - pub ssid_len: u8, - pub ssid: [u8; 32], - // there will be more stuff here + pub ssid: heapless::String<32>, + pub rssi: i16, + pub security: BssSecurity, } -impl_bytes!(BssInfo); impl BssInfo { - pub fn parse(packet: &mut [u8]) -> Option<&mut Self> { - if packet.len() < BssInfo::SIZE { + pub fn parse(packet: &mut [u8]) -> Option { + if packet.len() < BssInfoInternal::SIZE { return None; } - Some(BssInfo::from_bytes_mut( - packet[..BssInfo::SIZE].as_mut().try_into().unwrap(), - )) + const DOT11_CAP_PRIVACY: u16 = 0x0010; + const DOT11_IE_ID_RSN: u8 = 48; + const DOT11_IE_ID_VENDOR_SPECIFIC: u8 = 221; + const WPA_OUI_TYPE1: &[u8] = b"\x00\x50\xF2\x01"; + + let bss_info = *BssInfoInternal::from_bytes_mut(packet[..BssInfoInternal::SIZE].as_mut().try_into().unwrap()); + let mut ie_ptr = bss_info.ie_offset as usize; + let ie_top = ie_ptr + bss_info.ie_length as usize; + let mut ie_rsn = None; + let mut ie_wpa = None; + while ie_ptr < ie_top { + let ie_type = packet[ie_ptr]; + let ie_len = packet[ie_ptr + 1] as usize; + if ie_ptr + 2 + ie_len <= ie_top { + match ie_type { + DOT11_IE_ID_RSN => { + ie_rsn = Some(ie_ptr); + } + DOT11_IE_ID_VENDOR_SPECIFIC => { + if &packet[ie_ptr + 2..ie_ptr + 6] == WPA_OUI_TYPE1 { + ie_wpa = Some(ie_ptr); + } + } + _ => {} + } + } + ie_ptr += 2 + ie_len; + } + + let mut security_field = 0; + if ie_rsn.is_some() { + security_field |= 4; + } + if ie_wpa.is_some() { + security_field |= 2; + } + if bss_info.capability & DOT11_CAP_PRIVACY > 0 { + security_field |= 1; + } + let security = match security_field { + 4 => BssSecurity::Wpa2, + 2 => BssSecurity::Wpa, + 1 => BssSecurity::WepPsk, + 0 => BssSecurity::Open, + _ => BssSecurity::None, + }; + let ssid = heapless::String::from_utf8( + heapless::Vec::from_slice(&bss_info.ssid[..bss_info.ssid_len as usize]).unwrap(), + ) + .ok()?; + let BssInfoInternal { + version, + length, + bssid, + beacon_period, + capability, + rssi, + .. + } = bss_info; + Some(Self { + version, + length, + bssid, + beacon_period, + capability, + ssid, + rssi, + security, + }) } }