diff options
Diffstat (limited to 'cyw43/src/control.rs')
| -rw-r--r-- | cyw43/src/control.rs | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs new file mode 100644 index 000000000..6919d569e --- /dev/null +++ b/cyw43/src/control.rs | |||
| @@ -0,0 +1,457 @@ | |||
| 1 | use core::cmp::{max, min}; | ||
| 2 | |||
| 3 | use ch::driver::LinkState; | ||
| 4 | use embassy_net_driver_channel as ch; | ||
| 5 | use embassy_time::{Duration, Timer}; | ||
| 6 | |||
| 7 | pub use crate::bus::SpiBusCyw43; | ||
| 8 | use crate::consts::*; | ||
| 9 | use crate::events::{Event, EventSubscriber, Events}; | ||
| 10 | use crate::fmt::Bytes; | ||
| 11 | use crate::ioctl::{IoctlState, IoctlType}; | ||
| 12 | use crate::structs::*; | ||
| 13 | use crate::{countries, events, PowerManagementMode}; | ||
| 14 | |||
| 15 | #[derive(Debug)] | ||
| 16 | pub struct Error { | ||
| 17 | pub status: u32, | ||
| 18 | } | ||
| 19 | |||
| 20 | pub struct Control<'a> { | ||
| 21 | state_ch: ch::StateRunner<'a>, | ||
| 22 | events: &'a Events, | ||
| 23 | ioctl_state: &'a IoctlState, | ||
| 24 | } | ||
| 25 | |||
| 26 | impl<'a> Control<'a> { | ||
| 27 | pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { | ||
| 28 | Self { | ||
| 29 | state_ch, | ||
| 30 | events: event_sub, | ||
| 31 | ioctl_state, | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | pub async fn init(&mut self, clm: &[u8]) { | ||
| 36 | const CHUNK_SIZE: usize = 1024; | ||
| 37 | |||
| 38 | debug!("Downloading CLM..."); | ||
| 39 | |||
| 40 | let mut offs = 0; | ||
| 41 | for chunk in clm.chunks(CHUNK_SIZE) { | ||
| 42 | let mut flag = DOWNLOAD_FLAG_HANDLER_VER; | ||
| 43 | if offs == 0 { | ||
| 44 | flag |= DOWNLOAD_FLAG_BEGIN; | ||
| 45 | } | ||
| 46 | offs += chunk.len(); | ||
| 47 | if offs == clm.len() { | ||
| 48 | flag |= DOWNLOAD_FLAG_END; | ||
| 49 | } | ||
| 50 | |||
| 51 | let header = DownloadHeader { | ||
| 52 | flag, | ||
| 53 | dload_type: DOWNLOAD_TYPE_CLM, | ||
| 54 | len: chunk.len() as _, | ||
| 55 | crc: 0, | ||
| 56 | }; | ||
| 57 | let mut buf = [0; 8 + 12 + CHUNK_SIZE]; | ||
| 58 | buf[0..8].copy_from_slice(b"clmload\x00"); | ||
| 59 | buf[8..20].copy_from_slice(&header.to_bytes()); | ||
| 60 | buf[20..][..chunk.len()].copy_from_slice(&chunk); | ||
| 61 | self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) | ||
| 62 | .await; | ||
| 63 | } | ||
| 64 | |||
| 65 | // check clmload ok | ||
| 66 | assert_eq!(self.get_iovar_u32("clmload_status").await, 0); | ||
| 67 | |||
| 68 | debug!("Configuring misc stuff..."); | ||
| 69 | |||
| 70 | // Disable tx gloming which transfers multiple packets in one request. | ||
| 71 | // 'glom' is short for "conglomerate" which means "gather together into | ||
| 72 | // a compact mass". | ||
| 73 | self.set_iovar_u32("bus:txglom", 0).await; | ||
| 74 | self.set_iovar_u32("apsta", 1).await; | ||
| 75 | |||
| 76 | // read MAC addr. | ||
| 77 | let mut mac_addr = [0; 6]; | ||
| 78 | assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); | ||
| 79 | debug!("mac addr: {:02x}", Bytes(&mac_addr)); | ||
| 80 | |||
| 81 | let country = countries::WORLD_WIDE_XX; | ||
| 82 | let country_info = CountryInfo { | ||
| 83 | country_abbrev: [country.code[0], country.code[1], 0, 0], | ||
| 84 | country_code: [country.code[0], country.code[1], 0, 0], | ||
| 85 | rev: if country.rev == 0 { -1 } else { country.rev as _ }, | ||
| 86 | }; | ||
| 87 | self.set_iovar("country", &country_info.to_bytes()).await; | ||
| 88 | |||
| 89 | // set country takes some time, next ioctls fail if we don't wait. | ||
| 90 | Timer::after(Duration::from_millis(100)).await; | ||
| 91 | |||
| 92 | // Set antenna to chip antenna | ||
| 93 | self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; | ||
| 94 | |||
| 95 | self.set_iovar_u32("bus:txglom", 0).await; | ||
| 96 | Timer::after(Duration::from_millis(100)).await; | ||
| 97 | //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? | ||
| 98 | //Timer::after(Duration::from_millis(100)).await; | ||
| 99 | self.set_iovar_u32("ampdu_ba_wsize", 8).await; | ||
| 100 | Timer::after(Duration::from_millis(100)).await; | ||
| 101 | self.set_iovar_u32("ampdu_mpdu", 4).await; | ||
| 102 | Timer::after(Duration::from_millis(100)).await; | ||
| 103 | //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes | ||
| 104 | |||
| 105 | //Timer::after(Duration::from_millis(100)).await; | ||
| 106 | |||
| 107 | // evts | ||
| 108 | let mut evts = EventMask { | ||
| 109 | iface: 0, | ||
| 110 | events: [0xFF; 24], | ||
| 111 | }; | ||
| 112 | |||
| 113 | // Disable spammy uninteresting events. | ||
| 114 | evts.unset(Event::RADIO); | ||
| 115 | evts.unset(Event::IF); | ||
| 116 | evts.unset(Event::PROBREQ_MSG); | ||
| 117 | evts.unset(Event::PROBREQ_MSG_RX); | ||
| 118 | evts.unset(Event::PROBRESP_MSG); | ||
| 119 | evts.unset(Event::PROBRESP_MSG); | ||
| 120 | evts.unset(Event::ROAM); | ||
| 121 | |||
| 122 | self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; | ||
| 123 | |||
| 124 | Timer::after(Duration::from_millis(100)).await; | ||
| 125 | |||
| 126 | // set wifi up | ||
| 127 | self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; | ||
| 128 | |||
| 129 | Timer::after(Duration::from_millis(100)).await; | ||
| 130 | |||
| 131 | self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto | ||
| 132 | self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any | ||
| 133 | |||
| 134 | Timer::after(Duration::from_millis(100)).await; | ||
| 135 | |||
| 136 | self.state_ch.set_ethernet_address(mac_addr); | ||
| 137 | |||
| 138 | debug!("INIT DONE"); | ||
| 139 | } | ||
| 140 | |||
| 141 | pub async fn set_power_management(&mut self, mode: PowerManagementMode) { | ||
| 142 | // power save mode | ||
| 143 | let mode_num = mode.mode(); | ||
| 144 | if mode_num == 2 { | ||
| 145 | self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await; | ||
| 146 | self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await; | ||
| 147 | self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; | ||
| 148 | self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; | ||
| 149 | } | ||
| 150 | self.ioctl_set_u32(86, 0, mode_num).await; | ||
| 151 | } | ||
| 152 | |||
| 153 | pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { | ||
| 154 | self.set_iovar_u32("ampdu_ba_wsize", 8).await; | ||
| 155 | |||
| 156 | self.ioctl_set_u32(134, 0, 0).await; // wsec = open | ||
| 157 | self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; | ||
| 158 | self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 | ||
| 159 | self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) | ||
| 160 | |||
| 161 | let mut i = SsidInfo { | ||
| 162 | len: ssid.len() as _, | ||
| 163 | ssid: [0; 32], | ||
| 164 | }; | ||
| 165 | i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); | ||
| 166 | |||
| 167 | self.wait_for_join(i).await | ||
| 168 | } | ||
| 169 | |||
| 170 | pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> { | ||
| 171 | self.set_iovar_u32("ampdu_ba_wsize", 8).await; | ||
| 172 | |||
| 173 | self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 | ||
| 174 | self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; | ||
| 175 | self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; | ||
| 176 | self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; | ||
| 177 | |||
| 178 | Timer::after(Duration::from_millis(100)).await; | ||
| 179 | |||
| 180 | let mut pfi = PassphraseInfo { | ||
| 181 | len: passphrase.len() as _, | ||
| 182 | flags: 1, | ||
| 183 | passphrase: [0; 64], | ||
| 184 | }; | ||
| 185 | pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); | ||
| 186 | self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) | ||
| 187 | .await; // WLC_SET_WSEC_PMK | ||
| 188 | |||
| 189 | self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 | ||
| 190 | self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) | ||
| 191 | self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth | ||
| 192 | |||
| 193 | let mut i = SsidInfo { | ||
| 194 | len: ssid.len() as _, | ||
| 195 | ssid: [0; 32], | ||
| 196 | }; | ||
| 197 | i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); | ||
| 198 | |||
| 199 | self.wait_for_join(i).await | ||
| 200 | } | ||
| 201 | |||
| 202 | async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { | ||
| 203 | self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); | ||
| 204 | let mut subscriber = self.events.queue.subscriber().unwrap(); | ||
| 205 | // the actual join operation starts here | ||
| 206 | // we make sure to enable events before so we don't miss any | ||
| 207 | |||
| 208 | // set_ssid | ||
| 209 | self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) | ||
| 210 | .await; | ||
| 211 | |||
| 212 | // to complete the join, we wait for a SET_SSID event | ||
| 213 | // we also save the AUTH status for the user, it may be interesting | ||
| 214 | let mut auth_status = 0; | ||
| 215 | let status = loop { | ||
| 216 | let msg = subscriber.next_message_pure().await; | ||
| 217 | if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS { | ||
| 218 | auth_status = msg.header.status; | ||
| 219 | } else if msg.header.event_type == Event::SET_SSID { | ||
| 220 | // join operation ends with SET_SSID event | ||
| 221 | break msg.header.status; | ||
| 222 | } | ||
| 223 | }; | ||
| 224 | |||
| 225 | self.events.mask.disable_all(); | ||
| 226 | if status == EStatus::SUCCESS { | ||
| 227 | // successful join | ||
| 228 | self.state_ch.set_link_state(LinkState::Up); | ||
| 229 | debug!("JOINED"); | ||
| 230 | Ok(()) | ||
| 231 | } else { | ||
| 232 | warn!("JOIN failed with status={} auth={}", status, auth_status); | ||
| 233 | Err(Error { status }) | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { | ||
| 238 | assert!(gpio_n < 3); | ||
| 239 | self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 }) | ||
| 240 | .await | ||
| 241 | } | ||
| 242 | |||
| 243 | pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) { | ||
| 244 | self.start_ap(ssid, "", Security::OPEN, channel).await; | ||
| 245 | } | ||
| 246 | |||
| 247 | pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) { | ||
| 248 | self.start_ap(ssid, passphrase, Security::WPA2_AES_PSK, channel).await; | ||
| 249 | } | ||
| 250 | |||
| 251 | async fn start_ap(&mut self, ssid: &str, passphrase: &str, security: Security, channel: u8) { | ||
| 252 | if security != Security::OPEN | ||
| 253 | && (passphrase.as_bytes().len() < MIN_PSK_LEN || passphrase.as_bytes().len() > MAX_PSK_LEN) | ||
| 254 | { | ||
| 255 | panic!("Passphrase is too short or too long"); | ||
| 256 | } | ||
| 257 | |||
| 258 | // Temporarily set wifi down | ||
| 259 | self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; | ||
| 260 | |||
| 261 | // Turn off APSTA mode | ||
| 262 | self.set_iovar_u32("apsta", 0).await; | ||
| 263 | |||
| 264 | // Set wifi up again | ||
| 265 | self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; | ||
| 266 | |||
| 267 | // Turn on AP mode | ||
| 268 | self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; | ||
| 269 | |||
| 270 | // Set SSID | ||
| 271 | let mut i = SsidInfoWithIndex { | ||
| 272 | index: 0, | ||
| 273 | ssid_info: SsidInfo { | ||
| 274 | len: ssid.as_bytes().len() as _, | ||
| 275 | ssid: [0; 32], | ||
| 276 | }, | ||
| 277 | }; | ||
| 278 | i.ssid_info.ssid[..ssid.as_bytes().len()].copy_from_slice(ssid.as_bytes()); | ||
| 279 | self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; | ||
| 280 | |||
| 281 | // Set channel number | ||
| 282 | self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await; | ||
| 283 | |||
| 284 | // Set security | ||
| 285 | self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; | ||
| 286 | |||
| 287 | if security != Security::OPEN { | ||
| 288 | self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK | ||
| 289 | |||
| 290 | Timer::after(Duration::from_millis(100)).await; | ||
| 291 | |||
| 292 | // Set passphrase | ||
| 293 | let mut pfi = PassphraseInfo { | ||
| 294 | len: passphrase.as_bytes().len() as _, | ||
| 295 | flags: 1, // WSEC_PASSPHRASE | ||
| 296 | passphrase: [0; 64], | ||
| 297 | }; | ||
| 298 | pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); | ||
| 299 | self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) | ||
| 300 | .await; | ||
| 301 | } | ||
| 302 | |||
| 303 | // Change mutlicast rate from 1 Mbps to 11 Mbps | ||
| 304 | self.set_iovar_u32("2g_mrate", 11000000 / 500000).await; | ||
| 305 | |||
| 306 | // Start AP | ||
| 307 | self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP | ||
| 308 | } | ||
| 309 | |||
| 310 | async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { | ||
| 311 | let mut buf = [0; 8]; | ||
| 312 | buf[0..4].copy_from_slice(&val1.to_le_bytes()); | ||
| 313 | buf[4..8].copy_from_slice(&val2.to_le_bytes()); | ||
| 314 | self.set_iovar(name, &buf).await | ||
| 315 | } | ||
| 316 | |||
| 317 | async fn set_iovar_u32(&mut self, name: &str, val: u32) { | ||
| 318 | self.set_iovar(name, &val.to_le_bytes()).await | ||
| 319 | } | ||
| 320 | |||
| 321 | async fn get_iovar_u32(&mut self, name: &str) -> u32 { | ||
| 322 | let mut buf = [0; 4]; | ||
| 323 | let len = self.get_iovar(name, &mut buf).await; | ||
| 324 | assert_eq!(len, 4); | ||
| 325 | u32::from_le_bytes(buf) | ||
| 326 | } | ||
| 327 | |||
| 328 | async fn set_iovar(&mut self, name: &str, val: &[u8]) { | ||
| 329 | self.set_iovar_v::<64>(name, val).await | ||
| 330 | } | ||
| 331 | |||
| 332 | async fn set_iovar_v<const BUFSIZE: usize>(&mut self, name: &str, val: &[u8]) { | ||
| 333 | debug!("set {} = {:02x}", name, Bytes(val)); | ||
| 334 | |||
| 335 | let mut buf = [0; BUFSIZE]; | ||
| 336 | buf[..name.len()].copy_from_slice(name.as_bytes()); | ||
| 337 | buf[name.len()] = 0; | ||
| 338 | buf[name.len() + 1..][..val.len()].copy_from_slice(val); | ||
| 339 | |||
| 340 | let total_len = name.len() + 1 + val.len(); | ||
| 341 | self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) | ||
| 342 | .await; | ||
| 343 | } | ||
| 344 | |||
| 345 | // TODO this is not really working, it always returns all zeros. | ||
| 346 | async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { | ||
| 347 | debug!("get {}", name); | ||
| 348 | |||
| 349 | let mut buf = [0; 64]; | ||
| 350 | buf[..name.len()].copy_from_slice(name.as_bytes()); | ||
| 351 | buf[name.len()] = 0; | ||
| 352 | |||
| 353 | let total_len = max(name.len() + 1, res.len()); | ||
| 354 | let res_len = self | ||
| 355 | .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) | ||
| 356 | .await; | ||
| 357 | |||
| 358 | let out_len = min(res.len(), res_len); | ||
| 359 | res[..out_len].copy_from_slice(&buf[..out_len]); | ||
| 360 | out_len | ||
| 361 | } | ||
| 362 | |||
| 363 | async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { | ||
| 364 | let mut buf = val.to_le_bytes(); | ||
| 365 | self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; | ||
| 366 | } | ||
| 367 | |||
| 368 | async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { | ||
| 369 | struct CancelOnDrop<'a>(&'a IoctlState); | ||
| 370 | |||
| 371 | impl CancelOnDrop<'_> { | ||
| 372 | fn defuse(self) { | ||
| 373 | core::mem::forget(self); | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | impl Drop for CancelOnDrop<'_> { | ||
| 378 | fn drop(&mut self) { | ||
| 379 | self.0.cancel_ioctl(); | ||
| 380 | } | ||
| 381 | } | ||
| 382 | |||
| 383 | let ioctl = CancelOnDrop(self.ioctl_state); | ||
| 384 | |||
| 385 | ioctl.0.do_ioctl(kind, cmd, iface, buf).await; | ||
| 386 | let resp_len = ioctl.0.wait_complete().await; | ||
| 387 | |||
| 388 | ioctl.defuse(); | ||
| 389 | |||
| 390 | resp_len | ||
| 391 | } | ||
| 392 | |||
| 393 | /// Start a wifi scan | ||
| 394 | /// | ||
| 395 | /// Returns a `Stream` of networks found by the device | ||
| 396 | /// | ||
| 397 | /// # Note | ||
| 398 | /// Device events are currently implemented using a bounded queue. | ||
| 399 | /// To not miss any events, you should make sure to always await the stream. | ||
| 400 | pub async fn scan(&mut self) -> Scanner<'_> { | ||
| 401 | const SCANTYPE_PASSIVE: u8 = 1; | ||
| 402 | |||
| 403 | let scan_params = ScanParams { | ||
| 404 | version: 1, | ||
| 405 | action: 1, | ||
| 406 | sync_id: 1, | ||
| 407 | ssid_len: 0, | ||
| 408 | ssid: [0; 32], | ||
| 409 | bssid: [0xff; 6], | ||
| 410 | bss_type: 2, | ||
| 411 | scan_type: SCANTYPE_PASSIVE, | ||
| 412 | nprobes: !0, | ||
| 413 | active_time: !0, | ||
| 414 | passive_time: !0, | ||
| 415 | home_time: !0, | ||
| 416 | channel_num: 0, | ||
| 417 | channel_list: [0; 1], | ||
| 418 | }; | ||
| 419 | |||
| 420 | self.events.mask.enable(&[Event::ESCAN_RESULT]); | ||
| 421 | let subscriber = self.events.queue.subscriber().unwrap(); | ||
| 422 | self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await; | ||
| 423 | |||
| 424 | Scanner { | ||
| 425 | subscriber, | ||
| 426 | events: &self.events, | ||
| 427 | } | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | pub struct Scanner<'a> { | ||
| 432 | subscriber: EventSubscriber<'a>, | ||
| 433 | events: &'a Events, | ||
| 434 | } | ||
| 435 | |||
| 436 | impl Scanner<'_> { | ||
| 437 | /// wait for the next found network | ||
| 438 | pub async fn next(&mut self) -> Option<BssInfo> { | ||
| 439 | let event = self.subscriber.next_message_pure().await; | ||
| 440 | if event.header.status != EStatus::PARTIAL { | ||
| 441 | self.events.mask.disable_all(); | ||
| 442 | return None; | ||
| 443 | } | ||
| 444 | |||
| 445 | if let events::Payload::BssInfo(bss) = event.payload { | ||
| 446 | Some(bss) | ||
| 447 | } else { | ||
| 448 | None | ||
| 449 | } | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | impl Drop for Scanner<'_> { | ||
| 454 | fn drop(&mut self) { | ||
| 455 | self.events.mask.disable_all(); | ||
| 456 | } | ||
| 457 | } | ||
