use std::alloc::{alloc_zeroed, dealloc, Layout}; use std::collections::HashMap; use std::ffi::c_void; use std::ptr; use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::Mutex; use super::net::{netif_carrier_off, netif_carrier_on, NetDevice}; #[derive(Clone, Default)] struct WirelessEventState { new_sta: Option<[u8; 6]>, mgmt_rx_freq: u32, mgmt_rx_signal: i32, mgmt_rx_len: usize, mgmt_rx_data: Vec, mgmt_tx_cookie: u64, mgmt_tx_len: usize, mgmt_tx_ack: bool, mgmt_tx_data: Vec, sched_scan_reqid: u64, roc_cookie: u64, roc_chan_freq: u16, roc_band: u32, roc_duration: u32, roc_active: bool, } #[repr(C)] pub struct WiphyBands { bands: [usize; 3], } unsafe impl Send for WiphyBands {} lazy_static::lazy_static! { static ref WIRELESS_EVENTS: Mutex> = Mutex::new(HashMap::new()); static ref BSS_REGISTRY: Mutex>> = Mutex::new(Vec::new()); static ref BSS_IES: Mutex>> = Mutex::new(HashMap::new()); static ref WIPY_BANDS_MAP: Mutex> = Mutex::new(HashMap::new()); } #[repr(C)] pub struct Wiphy { pub priv_data: *mut c_void, pub registered: AtomicI32, pub interface_modes: u32, pub max_scan_ssids: i32, pub max_scan_ie_len: i32, priv_alloc_size: usize, priv_alloc_align: usize, } #[repr(C)] pub struct WirelessDev { pub wiphy: *mut Wiphy, pub netdev: *mut c_void, pub iftype: u32, pub scan_in_flight: bool, pub scan_aborted: bool, pub connecting: bool, pub connected: bool, pub locally_generated: bool, pub last_status: u16, pub last_reason: u16, pub has_bssid: bool, pub last_bssid: [u8; 6], } #[repr(C)] pub struct Cfg80211ScanInfo { pub aborted: bool, } #[repr(C)] pub struct Cfg80211ScanRequest { pub wiphy: *mut Wiphy, pub wdev: *mut WirelessDev, pub n_ssids: u32, pub n_channels: u32, } #[repr(C)] pub struct Cfg80211Ssid { pub ssid: [u8; 32], pub ssid_len: u8, } #[repr(C)] pub struct KeyParams { pub key: *const u8, pub key_len: u8, pub cipher: u32, pub key_idx: u8, } #[repr(C)] pub struct Cfg80211ConnectParams { pub ssid: *const u8, pub ssid_len: usize, pub bssid: *const u8, pub ie: *const u8, pub ie_len: usize, pub key: KeyParams, } #[repr(C)] pub struct StationParameters { pub supported_rates: *const u8, pub supported_rates_len: usize, pub sta_flags_mask: u32, pub sta_flags_set: u32, } fn update_event_state(key: usize, update: F) where F: FnOnce(&mut WirelessEventState), { if let Ok(mut events) = WIRELESS_EVENTS.lock() { update(events.entry(key).or_default()); } } #[no_mangle] pub extern "C" fn wiphy_new_nm( _ops: *const c_void, sizeof_priv: usize, _requested_name: *const u8, ) -> *mut Wiphy { let mut wiphy = Box::new(Wiphy { priv_data: ptr::null_mut(), registered: AtomicI32::new(0), interface_modes: 0, max_scan_ssids: 4, max_scan_ie_len: 512, priv_alloc_size: 0, priv_alloc_align: 0, }); if sizeof_priv != 0 { let layout = match Layout::from_size_align(sizeof_priv, 16) { Ok(layout) => layout, Err(_) => return ptr::null_mut(), }; let ptr = unsafe { alloc_zeroed(layout) } as *mut c_void; if ptr.is_null() { return ptr::null_mut(); } wiphy.priv_data = ptr; wiphy.priv_alloc_size = sizeof_priv; wiphy.priv_alloc_align = 16; } Box::into_raw(wiphy) } #[no_mangle] pub extern "C" fn wiphy_free(wiphy: *mut Wiphy) { if wiphy.is_null() { return; } let wiphy_key = wiphy as usize; if let Ok(mut events) = WIRELESS_EVENTS.lock() { events.remove(&wiphy_key); } if let Ok(mut registry) = BSS_REGISTRY.lock() { if let Ok(mut ies_map) = BSS_IES.lock() { for entry in registry.iter() { if entry.wiphy == wiphy_key { let ptr = entry.as_ref() as *const Cfg80211Bss as usize; ies_map.remove(&ptr); } } } registry.retain(|e| e.wiphy != wiphy_key); } if let Ok(mut bands_map) = WIPY_BANDS_MAP.lock() { bands_map.remove(&wiphy_key); } unsafe { let wiphy_box = Box::from_raw(wiphy); if !wiphy_box.priv_data.is_null() { if let Ok(layout) = Layout::from_size_align( wiphy_box.priv_alloc_size.max(1), wiphy_box.priv_alloc_align.max(1), ) { dealloc(wiphy_box.priv_data.cast::(), layout); } } } } #[no_mangle] pub extern "C" fn wiphy_register(wiphy: *mut Wiphy) -> i32 { if wiphy.is_null() { return -22; } if unsafe { &*wiphy }.registered.load(Ordering::Acquire) != 0 { return -16; } unsafe { &*wiphy }.registered.store(1, Ordering::Release); 0 } #[no_mangle] pub extern "C" fn wiphy_unregister(wiphy: *mut Wiphy) { if wiphy.is_null() { return; } unsafe { &*wiphy }.registered.store(0, Ordering::Release); } #[no_mangle] pub extern "C" fn cfg80211_scan_done( request: *mut Cfg80211ScanRequest, info: *const Cfg80211ScanInfo, ) { if request.is_null() { return; } let wdev = unsafe { (*request).wdev }; if wdev.is_null() { return; } unsafe { (*wdev).scan_in_flight = false; (*wdev).scan_aborted = if info.is_null() { false } else { (*info).aborted }; } } fn netdev_to_wireless_dev(dev: *mut c_void) -> *mut WirelessDev { if dev.is_null() { return ptr::null_mut(); } let dev = dev.cast::(); unsafe { (*dev).ieee80211_ptr.cast::() } } fn copy_bssid(dst: &mut WirelessDev, bssid: *const u8) { if bssid.is_null() { dst.has_bssid = false; dst.last_bssid = [0; 6]; return; } unsafe { ptr::copy_nonoverlapping(bssid, dst.last_bssid.as_mut_ptr(), dst.last_bssid.len()); } dst.has_bssid = true; } #[no_mangle] pub extern "C" fn cfg80211_connect_result( dev: *mut c_void, bssid: *const u8, _req_ie: *const u8, _req_ie_len: usize, _resp_ie: *const u8, _resp_ie_len: usize, status: u16, _gfp: u32, ) { let wdev = netdev_to_wireless_dev(dev); if wdev.is_null() { return; } unsafe { let wdev_ref = &mut *wdev; wdev_ref.connecting = false; wdev_ref.connected = status == 0; wdev_ref.last_status = status; wdev_ref.locally_generated = false; copy_bssid(wdev_ref, bssid); } if status == 0 { netif_carrier_on(dev.cast::()); } else { netif_carrier_off(dev.cast::()); } } #[no_mangle] pub extern "C" fn cfg80211_disconnected( dev: *mut c_void, reason: u16, _ie: *const u8, _ie_len: usize, locally_generated: bool, _gfp: u32, ) { let wdev = netdev_to_wireless_dev(dev); if !wdev.is_null() { unsafe { let wdev_ref = &mut *wdev; wdev_ref.connecting = false; wdev_ref.connected = false; wdev_ref.last_reason = reason; wdev_ref.locally_generated = locally_generated; wdev_ref.has_bssid = false; wdev_ref.last_bssid = [0; 6]; } } netif_carrier_off(dev.cast::()); } #[no_mangle] pub extern "C" fn cfg80211_connect_bss( dev: *mut c_void, bssid: *const u8, req_ie: *const u8, req_ie_len: usize, resp_ie: *const u8, resp_ie_len: usize, status: u16, gfp: u32, ) { cfg80211_connect_result( dev, bssid, req_ie, req_ie_len, resp_ie, resp_ie_len, status, gfp, ) } #[no_mangle] pub extern "C" fn cfg80211_ready_on_channel( wdev: *mut WirelessDev, cookie: u64, chan: *mut c_void, _chan_type: u32, duration: u32, _gfp: u32, ) { if wdev.is_null() { return; } let key = wdev as usize; let (freq, band) = if chan.is_null() { (0u16, 0u32) } else { let ch = chan.cast::(); unsafe { ((*ch).center_freq, (*ch).band) } }; update_event_state(key, |state| { state.roc_cookie = cookie; state.roc_chan_freq = freq; state.roc_band = band; state.roc_duration = duration; state.roc_active = true; }); log::trace!( "cfg80211_ready_on_channel: wdev={:#x} cookie={} freq={} duration={}", key, cookie, freq, duration ); } #[repr(C)] pub struct Ieee80211Channel { pub band: u32, pub center_freq: u16, pub hw_value: u16, pub flags: u32, pub max_power: i8, pub max_reg_power: i8, pub max_antenna_gain: i8, pub beacon_found: bool, } pub const NL80211_BAND_2GHZ: u32 = 0; pub const NL80211_BAND_5GHZ: u32 = 1; pub const NL80211_BAND_6GHZ: u32 = 2; pub const IEEE80211_CHAN_DISABLED: u32 = 1 << 0; pub const IEEE80211_CHAN_NO_IR: u32 = 1 << 1; pub const IEEE80211_CHAN_RADAR: u32 = 1 << 2; pub const IEEE80211_CHAN_NO_HT40PLUS: u32 = 1 << 3; pub const IEEE80211_CHAN_NO_HT40MINUS: u32 = 1 << 4; pub const IEEE80211_CHAN_NO_OFDM: u32 = 1 << 5; pub const IEEE80211_CHAN_NO_80MHZ: u32 = 1 << 6; pub const IEEE80211_CHAN_NO_160MHZ: u32 = 1 << 7; #[repr(C)] pub struct Ieee80211Rate { pub flags: u32, pub bitrate: u16, pub hw_value: u16, pub hw_value_short: u16, } pub const IEEE80211_RATE_SHORT_PREAMBLE: u32 = 1 << 0; pub const IEEE80211_RATE_MANDATORY: u32 = 1 << 1; pub const IEEE80211_RATE_ERP_G: u32 = 1 << 2; #[repr(C)] pub struct Ieee80211SupportedBand { pub channels: *mut Ieee80211Channel, pub n_channels: usize, pub bitrates: *mut Ieee80211Rate, pub n_bitrates: usize, pub ht_cap: *mut c_void, pub vht_cap: *mut c_void, } #[no_mangle] pub extern "C" fn wiphy_bands_append( wiphy: *mut Wiphy, band_idx: u32, band: *mut Ieee80211SupportedBand, ) -> i32 { if wiphy.is_null() || band.is_null() { return -22; } if band_idx > NL80211_BAND_6GHZ { return -22; } let band_ref = unsafe { &*band }; if band_ref.n_channels == 0 || band_ref.channels.is_null() { return -22; } let key = wiphy as usize; if let Ok(mut map) = WIPY_BANDS_MAP.lock() { let entry = map .entry(key) .or_insert_with(|| WiphyBands { bands: [0; 3] }); entry.bands[band_idx as usize] = band as usize; } 0 } #[repr(C)] pub struct Cfg80211Bss { pub bssid: [u8; 6], pub channel: *mut Ieee80211Channel, pub signal: i16, pub capability: u16, pub beacon_interval: u16, pub ies: *const u8, pub ies_len: usize, wiphy: usize, } unsafe impl Send for Cfg80211Bss {} #[no_mangle] pub extern "C" fn cfg80211_inform_bss( wiphy: *mut Wiphy, wdev: *mut WirelessDev, _freq: u32, bssid: *const u8, _tsf: u64, capability: u16, beacon_interval: u16, ies: *const u8, ies_len: usize, signal: i32, _gfp: u32, ) -> *mut Cfg80211Bss { if wiphy.is_null() || wdev.is_null() || bssid.is_null() { return ptr::null_mut(); } let mut bssid_bytes = [0; 6]; unsafe { ptr::copy_nonoverlapping(bssid, bssid_bytes.as_mut_ptr(), bssid_bytes.len()); } let Ok(mut registry) = BSS_REGISTRY.lock() else { log::warn!("cfg80211_inform_bss: registry lock failed"); return ptr::null_mut(); }; let clamped_signal = signal.clamp(i16::MIN as i32, i16::MAX as i32) as i16; let ies_owned: Vec = if !ies.is_null() && ies_len > 0 { unsafe { std::slice::from_raw_parts(ies, ies_len) }.to_vec() } else { Vec::new() }; for entry in registry.iter_mut() { if entry.bssid == bssid_bytes && entry.wiphy == wiphy as usize { entry.signal = clamped_signal; entry.capability = capability; entry.beacon_interval = beacon_interval; let entry_ptr = entry.as_ref() as *const Cfg80211Bss as usize; if let Ok(mut ies_map) = BSS_IES.lock() { entry.ies_len = ies_owned.len(); if ies_owned.is_empty() { entry.ies = ptr::null(); ies_map.remove(&entry_ptr); } else { let stored = ies_map.entry(entry_ptr).or_default(); stored.clear(); stored.extend_from_slice(&ies_owned); entry.ies = stored.as_ptr(); } } return entry.as_ref() as *const Cfg80211Bss as *mut Cfg80211Bss; } } let bss = Box::new(Cfg80211Bss { bssid: bssid_bytes, channel: ptr::null_mut(), signal: clamped_signal, capability, beacon_interval, ies: ptr::null(), ies_len: 0, wiphy: wiphy as usize, }); registry.push(bss); let entry = match registry.last_mut() { Some(e) => e, None => return ptr::null_mut(), }; let entry_ptr = entry.as_ref() as *const Cfg80211Bss as usize; if let Ok(mut ies_map) = BSS_IES.lock() { if !ies_owned.is_empty() { ies_map.insert(entry_ptr, ies_owned); entry.ies = ies_map[&entry_ptr].as_ptr(); entry.ies_len = ies_map[&entry_ptr].len(); } } entry.as_ref() as *const Cfg80211Bss as *mut Cfg80211Bss } #[no_mangle] pub extern "C" fn cfg80211_put_bss(bss: *mut Cfg80211Bss) { if bss.is_null() { return; } let bss_addr = bss as usize; let Ok(mut registry) = BSS_REGISTRY.lock() else { log::warn!("cfg80211_put_bss: registry lock failed"); return; }; let before = registry.len(); registry.retain(|entry| entry.as_ref() as *const Cfg80211Bss as usize != bss_addr); let removed = before != registry.len(); if removed { if let Ok(mut ies_map) = BSS_IES.lock() { ies_map.remove(&bss_addr); } } log::trace!( "cfg80211_put_bss: released reference bss={:#x} (removed={})", bss_addr, removed ); } #[no_mangle] pub extern "C" fn cfg80211_get_bss( wiphy: *mut Wiphy, _band: u32, bssid: *const u8, ssid: *const u8, ssid_len: usize, _bss_type: u32, _privacy: u32, ) -> *mut Cfg80211Bss { if wiphy.is_null() { return ptr::null_mut(); } let Ok(registry) = BSS_REGISTRY.lock() else { return ptr::null_mut(); }; let want_bssid = if bssid.is_null() { None } else { let mut bytes = [0u8; 6]; unsafe { ptr::copy_nonoverlapping(bssid, bytes.as_mut_ptr(), 6) }; Some(bytes) }; let want_ssid = if ssid.is_null() || ssid_len == 0 { None } else { let slice = unsafe { std::slice::from_raw_parts(ssid, ssid_len) }; Some(slice.to_vec()) }; for entry in registry.iter() { if entry.wiphy != wiphy as usize { continue; } if let Some(ref wb) = want_bssid { if entry.bssid != *wb { continue; } } if let Some(ref ws) = want_ssid { if entry.ies.is_null() || entry.ies_len < ws.len() + 2 { continue; } unsafe { let ies_slice = std::slice::from_raw_parts(entry.ies, entry.ies_len); let mut offset = 0; let mut found = false; while offset + 2 <= ies_slice.len() { let tag_id = ies_slice[offset]; let tag_len = ies_slice[offset + 1] as usize; if offset + 2 + tag_len > ies_slice.len() { break; } if tag_id == 0 && tag_len == ws.len() { if &ies_slice[offset + 2..offset + 2 + tag_len] == ws.as_slice() { found = true; break; } } offset += 2 + tag_len; } if !found { continue; } } } return entry.as_ref() as *const Cfg80211Bss as *mut Cfg80211Bss; } ptr::null_mut() } #[no_mangle] pub extern "C" fn cfg80211_new_sta( dev: *mut c_void, mac_addr: *const u8, _params: *const StationParameters, _gfp: u32, ) { if dev.is_null() || mac_addr.is_null() { return; } let wdev = netdev_to_wireless_dev(dev); if wdev.is_null() || unsafe { (*wdev).wiphy }.is_null() { return; } let mut addr = [0u8; 6]; unsafe { ptr::copy_nonoverlapping(mac_addr, addr.as_mut_ptr(), addr.len()) }; update_event_state(unsafe { (*wdev).wiphy as usize }, |state| { state.new_sta = Some(addr) }); } #[no_mangle] pub extern "C" fn cfg80211_rx_mgmt( wdev: *mut WirelessDev, freq: u32, sig_dbm: i32, buf: *const u8, len: usize, _gfp: u32, ) { if wdev.is_null() || (buf.is_null() && len != 0) { return; } let frame_data = if !buf.is_null() && len > 0 { unsafe { std::slice::from_raw_parts(buf, len) }.to_vec() } else { Vec::new() }; update_event_state(wdev as usize, |state| { state.mgmt_rx_freq = freq; state.mgmt_rx_signal = sig_dbm; state.mgmt_rx_len = len; state.mgmt_rx_data = frame_data; }); log::debug!( "cfg80211_rx_mgmt: wdev={:#x} freq={} sig={} len={}", wdev as usize, freq, sig_dbm, len ); } #[no_mangle] pub extern "C" fn cfg80211_mgmt_tx_status( wdev: *mut WirelessDev, cookie: u64, buf: *const u8, len: usize, ack: bool, _gfp: u32, ) { if wdev.is_null() || (buf.is_null() && len != 0) { return; } let frame_data = if !buf.is_null() && len > 0 { unsafe { std::slice::from_raw_parts(buf, len) }.to_vec() } else { Vec::new() }; update_event_state(wdev as usize, |state| { state.mgmt_tx_cookie = cookie; state.mgmt_tx_len = len; state.mgmt_tx_ack = ack; state.mgmt_tx_data = frame_data; }); log::debug!( "cfg80211_mgmt_tx_status: wdev={:#x} cookie={} len={} ack={}", wdev as usize, cookie, len, ack ); } #[no_mangle] pub extern "C" fn cfg80211_sched_scan_results(wiphy: *mut Wiphy, reqid: u64) { if wiphy.is_null() { return; } update_event_state(wiphy as usize, |state| state.sched_scan_reqid = reqid); } #[no_mangle] pub extern "C" fn ieee80211_channel_to_frequency(chan: u32, band: u32) -> u32 { match band { NL80211_BAND_2GHZ => match chan { 14 => 2484, 1..=13 => 2407 + chan * 5, _ => 0, }, NL80211_BAND_5GHZ => 5000 + chan * 5, NL80211_BAND_6GHZ => { if chan == 2 { 5935 } else if chan >= 1 { 5950 + chan * 5 } else { 0 } } _ => 0, } } #[no_mangle] pub extern "C" fn ieee80211_frequency_to_channel(freq: u32) -> u32 { match freq { 2484 => 14, 2412..=2472 => (freq - 2407) / 5, 5000..=5895 => (freq - 5000) / 5, 5935 => 2, 5955..=7115 => (freq - 5950) / 5, _ => 0, } } #[cfg(test)] mod tests { use super::*; use crate::rust_impl::net::{alloc_netdev_mqs, free_netdev, netif_carrier_ok}; use std::ffi::CString; #[test] fn wiphy_registration_round_trip_works() { let wiphy = wiphy_new_nm(ptr::null(), 0, ptr::null()); assert!(!wiphy.is_null()); assert_eq!(wiphy_register(wiphy), 0); assert_eq!(wiphy_register(wiphy), -16); assert_eq!(unsafe { (*wiphy).registered.load(Ordering::Acquire) }, 1); wiphy_unregister(wiphy); assert_eq!(unsafe { (*wiphy).registered.load(Ordering::Acquire) }, 0); wiphy_free(wiphy); } #[test] fn scan_and_connect_lifecycle_updates_wireless_state() { let name = CString::new("wlan%d").expect("valid test CString"); let dev = alloc_netdev_mqs(0, name.as_ptr().cast::(), 0, None, 1, 1); assert!(!dev.is_null()); let wiphy = wiphy_new_nm(ptr::null(), 32, ptr::null()); assert!(!wiphy.is_null()); let mut wdev = WirelessDev { wiphy, netdev: dev.cast::(), iftype: 0, scan_in_flight: true, scan_aborted: false, connecting: true, connected: false, locally_generated: false, last_status: u16::MAX, last_reason: 0, has_bssid: false, last_bssid: [0; 6], }; unsafe { (*dev).ieee80211_ptr = (&mut wdev as *mut WirelessDev).cast::(); } let mut request = Cfg80211ScanRequest { wiphy, wdev: &mut wdev, n_ssids: 1, n_channels: 1, }; let info = Cfg80211ScanInfo { aborted: true }; cfg80211_scan_done(&mut request, &info); assert!(!wdev.scan_in_flight); assert!(wdev.scan_aborted); let bssid = [1, 2, 3, 4, 5, 6]; cfg80211_connect_result( dev.cast::(), bssid.as_ptr(), ptr::null(), 0, ptr::null(), 0, 0, 0, ); assert!(wdev.connected); assert_eq!(wdev.last_status, 0); assert!(wdev.has_bssid); assert_eq!(wdev.last_bssid, bssid); assert_eq!(netif_carrier_ok(dev), 1); cfg80211_disconnected(dev.cast::(), 7, ptr::null(), 0, true, 0); assert!(!wdev.connected); assert_eq!(wdev.last_reason, 7); assert!(wdev.locally_generated); assert_eq!(netif_carrier_ok(dev), 0); wiphy_free(wiphy); free_netdev(dev); } #[test] fn ieee80211_channel_creation_and_flags_work() { let channel = Ieee80211Channel { band: NL80211_BAND_5GHZ, center_freq: 5180, hw_value: 36, flags: IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_80MHZ, max_power: 20, max_reg_power: 23, max_antenna_gain: 6, beacon_found: true, }; assert_eq!(channel.band, NL80211_BAND_5GHZ); assert_eq!(channel.center_freq, 5180); assert_eq!(channel.hw_value, 36); assert_ne!(channel.flags & IEEE80211_CHAN_NO_IR, 0); assert_ne!(channel.flags & IEEE80211_CHAN_RADAR, 0); assert_ne!(channel.flags & IEEE80211_CHAN_NO_80MHZ, 0); assert_eq!(channel.flags & IEEE80211_CHAN_DISABLED, 0); assert!(channel.beacon_found); } #[test] fn cfg80211_events_and_channel_frequency_conversions_work() { let name = CString::new("wlan%d").expect("valid test CString"); let dev = alloc_netdev_mqs(0, name.as_ptr().cast::(), 0, None, 1, 1); assert!(!dev.is_null()); let wiphy = wiphy_new_nm(ptr::null(), 0, ptr::null()); assert!(!wiphy.is_null()); let mut wdev = WirelessDev { wiphy, netdev: dev.cast::(), iftype: 0, scan_in_flight: false, scan_aborted: false, connecting: false, connected: false, locally_generated: false, last_status: 0, last_reason: 0, has_bssid: false, last_bssid: [0; 6], }; unsafe { (*dev).ieee80211_ptr = (&mut wdev as *mut WirelessDev).cast::() }; let sta = [6u8, 5, 4, 3, 2, 1]; cfg80211_new_sta(dev.cast::(), sta.as_ptr(), ptr::null(), 0); cfg80211_rx_mgmt(&mut wdev, 2412, -42, sta.as_ptr(), sta.len(), 0); cfg80211_mgmt_tx_status(&mut wdev, 99, sta.as_ptr(), sta.len(), true, 0); cfg80211_sched_scan_results(wiphy, 1234); let events = WIRELESS_EVENTS.lock().expect("wireless events lock"); let wiphy_state = events.get(&(wiphy as usize)).expect("wiphy event state"); assert_eq!(wiphy_state.new_sta, Some(sta)); assert_eq!(wiphy_state.sched_scan_reqid, 1234); let wdev_state = events .get(&((&mut wdev as *mut WirelessDev) as usize)) .expect("wdev event state"); assert_eq!(wdev_state.mgmt_rx_freq, 2412); assert_eq!(wdev_state.mgmt_rx_signal, -42); assert_eq!(wdev_state.mgmt_rx_data, sta.to_vec()); assert_eq!(wdev_state.mgmt_tx_cookie, 99); assert!(wdev_state.mgmt_tx_ack); assert_eq!(wdev_state.mgmt_tx_data, sta.to_vec()); drop(events); assert_eq!(ieee80211_channel_to_frequency(1, NL80211_BAND_2GHZ), 2412); assert_eq!(ieee80211_channel_to_frequency(36, NL80211_BAND_5GHZ), 5180); assert_eq!(ieee80211_frequency_to_channel(2484), 14); assert_eq!(ieee80211_frequency_to_channel(5955), 1); wiphy_free(wiphy); free_netdev(dev); } #[test] fn test_cfg80211_put_bss_removes_from_registry() { let name = CString::new("wlan%d").expect("valid test CString"); let dev = alloc_netdev_mqs(0, name.as_ptr().cast::(), 0, None, 1, 1); assert!(!dev.is_null()); let wiphy = wiphy_new_nm(ptr::null(), 32, ptr::null()); assert!(!wiphy.is_null()); let mut wdev = WirelessDev { wiphy, netdev: dev.cast::(), iftype: 0, scan_in_flight: false, scan_aborted: false, connecting: false, connected: false, locally_generated: false, last_status: 0, last_reason: 0, has_bssid: false, last_bssid: [0; 6], }; unsafe { (*dev).ieee80211_ptr = (&mut wdev as *mut WirelessDev).cast::() }; let bssid: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; let ies = [1u8, 2, 3, 4]; let bss = cfg80211_inform_bss( wiphy, &mut wdev as *mut WirelessDev, 2412, bssid.as_ptr(), 0, 0x0431, 100, ies.as_ptr(), ies.len(), -50, 0, ); assert!(!bss.is_null()); let found = cfg80211_get_bss(wiphy, 0, bssid.as_ptr(), ptr::null(), 0, 0, 0); assert!( !found.is_null(), "BSS should be in registry after inform_bss" ); cfg80211_put_bss(bss); let after_put = cfg80211_get_bss(wiphy, 0, bssid.as_ptr(), ptr::null(), 0, 0, 0); assert!( after_put.is_null(), "BSS should be removed from registry after put_bss" ); wiphy_free(wiphy); free_netdev(dev); } }