esp_wifi/wifi/
mod.rs

1//! WiFi
2
3pub mod event;
4pub(crate) mod os_adapter;
5pub(crate) mod state;
6use alloc::collections::vec_deque::VecDeque;
7use core::{
8    fmt::Debug,
9    marker::PhantomData,
10    mem::{self, MaybeUninit},
11    ptr::addr_of,
12    task::Poll,
13    time::Duration,
14};
15
16use enumset::{EnumSet, EnumSetType};
17use esp_hal::{asynch::AtomicWaker, sync::Locked};
18use esp_wifi_sys::include::{
19    esp_eap_client_clear_ca_cert,
20    esp_eap_client_clear_certificate_and_key,
21    esp_eap_client_clear_identity,
22    esp_eap_client_clear_new_password,
23    esp_eap_client_clear_password,
24    esp_eap_client_clear_username,
25    esp_eap_client_set_ca_cert,
26    esp_eap_client_set_certificate_and_key,
27    esp_eap_client_set_disable_time_check,
28    esp_eap_client_set_fast_params,
29    esp_eap_client_set_identity,
30    esp_eap_client_set_new_password,
31    esp_eap_client_set_pac_file,
32    esp_eap_client_set_password,
33    esp_eap_client_set_ttls_phase2_method,
34    esp_eap_client_set_username,
35    esp_eap_fast_config,
36    esp_wifi_sta_enterprise_enable,
37    wifi_pkt_rx_ctrl_t,
38    wifi_scan_channel_bitmap_t,
39    WIFI_PROTOCOL_11AX,
40    WIFI_PROTOCOL_11B,
41    WIFI_PROTOCOL_11G,
42    WIFI_PROTOCOL_11N,
43    WIFI_PROTOCOL_LR,
44};
45#[cfg(feature = "sniffer")]
46use esp_wifi_sys::include::{
47    esp_wifi_80211_tx,
48    esp_wifi_set_promiscuous,
49    esp_wifi_set_promiscuous_rx_cb,
50    wifi_promiscuous_pkt_t,
51    wifi_promiscuous_pkt_type_t,
52};
53use num_derive::FromPrimitive;
54#[doc(hidden)]
55pub(crate) use os_adapter::*;
56#[cfg(feature = "sniffer")]
57use portable_atomic::AtomicBool;
58use portable_atomic::{AtomicUsize, Ordering};
59#[cfg(feature = "serde")]
60use serde::{Deserialize, Serialize};
61#[cfg(feature = "smoltcp")]
62use smoltcp::phy::{Device, DeviceCapabilities, RxToken, TxToken};
63pub use state::*;
64
65use crate::{
66    common_adapter::*,
67    config::PowerSaveMode,
68    esp_wifi_result,
69    hal::ram,
70    wifi::private::EspWifiPacketBuffer,
71    EspWifiController,
72};
73
74const MTU: usize = crate::CONFIG.mtu;
75
76#[cfg(coex)]
77use include::{coex_adapter_funcs_t, coex_pre_init, esp_coex_adapter_register};
78
79#[cfg(all(feature = "csi", esp32c6))]
80use crate::binary::include::wifi_csi_acquire_config_t;
81#[cfg(feature = "csi")]
82pub use crate::binary::include::wifi_csi_info_t;
83#[cfg(feature = "csi")]
84use crate::binary::include::{
85    esp_wifi_set_csi,
86    esp_wifi_set_csi_config,
87    esp_wifi_set_csi_rx_cb,
88    wifi_csi_config_t,
89};
90use crate::binary::{
91    c_types,
92    include::{
93        self,
94        __BindgenBitfieldUnit,
95        esp_err_t,
96        esp_interface_t_ESP_IF_WIFI_AP,
97        esp_interface_t_ESP_IF_WIFI_STA,
98        esp_supplicant_deinit,
99        esp_supplicant_init,
100        esp_wifi_connect,
101        esp_wifi_deinit_internal,
102        esp_wifi_disconnect,
103        esp_wifi_get_mode,
104        esp_wifi_init_internal,
105        esp_wifi_internal_free_rx_buffer,
106        esp_wifi_internal_reg_rxcb,
107        esp_wifi_internal_tx,
108        esp_wifi_scan_start,
109        esp_wifi_set_config,
110        esp_wifi_set_country,
111        esp_wifi_set_mode,
112        esp_wifi_set_protocol,
113        esp_wifi_set_tx_done_cb,
114        esp_wifi_start,
115        esp_wifi_stop,
116        g_wifi_default_wpa_crypto_funcs,
117        wifi_active_scan_time_t,
118        wifi_ap_config_t,
119        wifi_auth_mode_t,
120        wifi_cipher_type_t_WIFI_CIPHER_TYPE_CCMP,
121        wifi_config_t,
122        wifi_country_policy_t_WIFI_COUNTRY_POLICY_MANUAL,
123        wifi_country_t,
124        wifi_init_config_t,
125        wifi_interface_t,
126        wifi_interface_t_WIFI_IF_AP,
127        wifi_interface_t_WIFI_IF_STA,
128        wifi_mode_t,
129        wifi_mode_t_WIFI_MODE_AP,
130        wifi_mode_t_WIFI_MODE_APSTA,
131        wifi_mode_t_WIFI_MODE_NULL,
132        wifi_mode_t_WIFI_MODE_STA,
133        wifi_osi_funcs_t,
134        wifi_pmf_config_t,
135        wifi_scan_config_t,
136        wifi_scan_threshold_t,
137        wifi_scan_time_t,
138        wifi_scan_type_t_WIFI_SCAN_TYPE_ACTIVE,
139        wifi_scan_type_t_WIFI_SCAN_TYPE_PASSIVE,
140        wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
141        wifi_sta_config_t,
142        wpa_crypto_funcs_t,
143        ESP_WIFI_OS_ADAPTER_MAGIC,
144        ESP_WIFI_OS_ADAPTER_VERSION,
145        WIFI_INIT_CONFIG_MAGIC,
146    },
147};
148
149/// Supported Wi-Fi authentication methods.
150#[derive(EnumSetType, Debug, PartialOrd)]
151#[cfg_attr(feature = "defmt", derive(defmt::Format))]
152#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
153#[derive(Default)]
154#[allow(clippy::upper_case_acronyms)] // FIXME
155pub enum AuthMethod {
156    /// No authentication (open network).
157    None,
158
159    /// Wired Equivalent Privacy (WEP) authentication.
160    WEP,
161
162    /// Wi-Fi Protected Access (WPA) authentication.
163    WPA,
164
165    /// Wi-Fi Protected Access 2 (WPA2) Personal authentication (default).
166    #[default]
167    WPA2Personal,
168
169    /// WPA/WPA2 Personal authentication (supports both).
170    WPAWPA2Personal,
171
172    /// WPA2 Enterprise authentication.
173    WPA2Enterprise,
174
175    /// WPA3 Personal authentication.
176    WPA3Personal,
177
178    /// WPA2/WPA3 Personal authentication (supports both).
179    WPA2WPA3Personal,
180
181    /// WLAN Authentication and Privacy Infrastructure (WAPI).
182    WAPIPersonal,
183}
184
185/// Supported Wi-Fi protocols.
186#[derive(EnumSetType, Debug, PartialOrd)]
187#[cfg_attr(feature = "defmt", derive(defmt::Format))]
188#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
189#[derive(Default)]
190pub enum Protocol {
191    /// 802.11b protocol.
192    P802D11B,
193
194    /// 802.11b/g protocol.
195    P802D11BG,
196
197    /// 802.11b/g/n protocol (default).
198    #[default]
199    P802D11BGN,
200
201    /// 802.11b/g/n long-range (LR) protocol.
202    P802D11BGNLR,
203
204    /// 802.11 long-range (LR) protocol.
205    P802D11LR,
206
207    /// 802.11b/g/n/ax protocol.
208    P802D11BGNAX,
209}
210
211/// Secondary Wi-Fi channels.
212#[derive(EnumSetType, Debug, PartialOrd)]
213#[cfg_attr(feature = "defmt", derive(defmt::Format))]
214#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
215#[derive(Default)]
216pub enum SecondaryChannel {
217    // TODO: Need to extend that for 5GHz
218    /// No secondary channel (default).
219    #[default]
220    None,
221
222    /// Secondary channel is above the primary channel.
223    Above,
224
225    /// Secondary channel is below the primary channel.
226    Below,
227}
228
229/// Information about a detected Wi-Fi access point.
230#[derive(Clone, Debug, Default, PartialEq, Eq)]
231#[cfg_attr(feature = "defmt", derive(defmt::Format))]
232#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
233pub struct AccessPointInfo {
234    /// The SSID of the access point.
235    pub ssid: heapless::String<32>,
236
237    /// The BSSID (MAC address) of the access point.
238    pub bssid: [u8; 6],
239
240    /// The channel the access point is operating on.
241    pub channel: u8,
242
243    /// The secondary channel configuration of the access point.
244    pub secondary_channel: SecondaryChannel,
245
246    /// The signal strength of the access point (RSSI).
247    pub signal_strength: i8,
248
249    /// The set of protocols supported by the access point.
250    #[cfg_attr(feature = "defmt", defmt(Debug2Format))]
251    pub protocols: EnumSet<Protocol>,
252
253    /// The authentication method used by the access point.
254    pub auth_method: Option<AuthMethod>,
255}
256
257/// Configuration for a Wi-Fi access point.
258#[derive(Clone, Debug, PartialEq, Eq)]
259#[cfg_attr(feature = "defmt", derive(defmt::Format))]
260#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
261pub struct AccessPointConfiguration {
262    /// The SSID of the access point.
263    pub ssid: heapless::String<32>,
264
265    /// Whether the SSID is hidden or visible.
266    pub ssid_hidden: bool,
267
268    /// The channel the access point will operate on.
269    pub channel: u8,
270
271    /// The secondary channel configuration.
272    pub secondary_channel: Option<u8>,
273
274    /// The set of protocols supported by the access point.
275    #[cfg_attr(feature = "defmt", defmt(Debug2Format))]
276    pub protocols: EnumSet<Protocol>,
277
278    /// The authentication method to be used by the access point.
279    pub auth_method: AuthMethod,
280
281    /// The password for securing the access point (if applicable).
282    pub password: heapless::String<64>,
283
284    /// The maximum number of connections allowed on the access point.
285    pub max_connections: u16,
286}
287
288impl Default for AccessPointConfiguration {
289    fn default() -> Self {
290        Self {
291            ssid: unwrap!("iot-device".try_into()),
292            ssid_hidden: false,
293            channel: 1,
294            secondary_channel: None,
295            protocols: Protocol::P802D11B | Protocol::P802D11BG | Protocol::P802D11BGN,
296            auth_method: AuthMethod::None,
297            password: heapless::String::new(),
298            max_connections: 255,
299        }
300    }
301}
302
303/// Client configuration for a Wi-Fi connection.
304#[derive(Clone, PartialEq, Eq)]
305#[cfg_attr(feature = "defmt", derive(defmt::Format))]
306#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
307pub struct ClientConfiguration {
308    /// The SSID of the Wi-Fi network.
309    pub ssid: heapless::String<32>,
310
311    /// The BSSID (MAC address) of the client.
312    pub bssid: Option<[u8; 6]>,
313
314    // pub protocol: Protocol,
315    /// The authentication method for the Wi-Fi connection.
316    pub auth_method: AuthMethod,
317
318    /// The password for the Wi-Fi connection.
319    pub password: heapless::String<64>,
320
321    /// The Wi-Fi channel to connect to.
322    pub channel: Option<u8>,
323}
324
325impl Debug for ClientConfiguration {
326    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
327        f.debug_struct("ClientConfiguration")
328            .field("ssid", &self.ssid)
329            .field("bssid", &self.bssid)
330            .field("auth_method", &self.auth_method)
331            .field("channel", &self.channel)
332            .finish()
333    }
334}
335
336impl Default for ClientConfiguration {
337    fn default() -> Self {
338        ClientConfiguration {
339            ssid: heapless::String::new(),
340            bssid: None,
341            auth_method: Default::default(),
342            password: heapless::String::new(),
343            channel: None,
344        }
345    }
346}
347
348#[cfg(feature = "csi")]
349pub(crate) trait CsiCallback: FnMut(crate::binary::include::wifi_csi_info_t) {}
350
351#[cfg(feature = "csi")]
352impl<T> CsiCallback for T where T: FnMut(crate::binary::include::wifi_csi_info_t) {}
353
354#[cfg(feature = "csi")]
355unsafe extern "C" fn csi_rx_cb<C: CsiCallback>(
356    ctx: *mut crate::wifi::c_types::c_void,
357    data: *mut crate::binary::include::wifi_csi_info_t,
358) {
359    let csi_callback = unsafe { &mut *(ctx as *mut C) };
360    csi_callback(*data);
361}
362
363#[derive(Clone, PartialEq, Eq)]
364// https://github.com/esp-rs/esp-wifi-sys/blob/main/esp-wifi-sys/headers/local/esp_wifi_types_native.h#L94
365/// Channel state information(CSI) configuration
366#[cfg(all(not(esp32c6), feature = "csi"))]
367pub struct CsiConfig {
368    /// Enable to receive legacy long training field(lltf) data.
369    pub lltf_en: bool,
370    /// Enable to receive HT long training field(htltf) data.
371    pub htltf_en: bool,
372    /// Enable to receive space time block code HT long training
373    /// field(stbc-htltf2) data.
374    pub stbc_htltf2_en: bool,
375    /// Enable to generate htlft data by averaging lltf and ht_ltf data when
376    /// receiving HT packet. Otherwise, use ht_ltf data directly.
377    pub ltf_merge_en: bool,
378    /// Enable to turn on channel filter to smooth adjacent sub-carrier. Disable
379    /// it to keep independence of adjacent sub-carrier.
380    pub channel_filter_en: bool,
381    /// Manually scale the CSI data by left shifting or automatically scale the
382    /// CSI data. If set true, please set the shift bits. false: automatically.
383    /// true: manually.
384    pub manu_scale: bool,
385    /// Manually left shift bits of the scale of the CSI data. The range of the
386    /// left shift bits is 0~15.
387    pub shift: u8,
388    /// Enable to dump 802.11 ACK frame.
389    pub dump_ack_en: bool,
390}
391
392#[derive(Clone, PartialEq, Eq)]
393#[cfg(all(esp32c6, feature = "csi"))]
394// See https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/src/include/esp32c6.rs#L5702-L5705
395pub struct CsiConfig {
396    /// Enable to acquire CSI.
397    pub enable: u32,
398    /// Enable to acquire L-LTF when receiving a 11g PPDU.
399    pub acquire_csi_legacy: u32,
400    /// Enable to acquire HT-LTF when receiving an HT20 PPDU.
401    pub acquire_csi_ht20: u32,
402    /// Enable to acquire HT-LTF when receiving an HT40 PPDU.
403    pub acquire_csi_ht40: u32,
404    /// Enable to acquire HE-LTF when receiving an HE20 SU PPDU.
405    pub acquire_csi_su: u32,
406    /// Enable to acquire HE-LTF when receiving an HE20 MU PPDU.
407    pub acquire_csi_mu: u32,
408    /// Enable to acquire HE-LTF when receiving an HE20 DCM applied PPDU.
409    pub acquire_csi_dcm: u32,
410    /// Enable to acquire HE-LTF when receiving an HE20 Beamformed applied PPDU.
411    pub acquire_csi_beamformed: u32,
412    /// Wwhen receiving an STBC applied HE PPDU, 0- acquire the complete
413    /// HE-LTF1,  1- acquire the complete HE-LTF2, 2- sample evenly among the
414    /// HE-LTF1 and HE-LTF2.
415    pub acquire_csi_he_stbc: u32,
416    /// Vvalue 0-3.
417    pub val_scale_cfg: u32,
418    /// Enable to dump 802.11 ACK frame, default disabled.
419    pub dump_ack_en: u32,
420    /// Reserved.
421    pub reserved: u32,
422}
423
424#[cfg(feature = "csi")]
425impl Default for CsiConfig {
426    #[cfg(not(esp32c6))]
427    fn default() -> Self {
428        Self {
429            lltf_en: true,
430            htltf_en: true,
431            stbc_htltf2_en: true,
432            ltf_merge_en: true,
433            channel_filter_en: true,
434            manu_scale: false,
435            shift: 0,
436            dump_ack_en: false,
437        }
438    }
439
440    #[cfg(esp32c6)]
441    fn default() -> Self {
442        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi_he_types.h#L67-L82
443        Self {
444            enable: 1,
445            acquire_csi_legacy: 1,
446            acquire_csi_ht20: 1,
447            acquire_csi_ht40: 1,
448            acquire_csi_su: 1,
449            acquire_csi_mu: 1,
450            acquire_csi_dcm: 1,
451            acquire_csi_beamformed: 1,
452            acquire_csi_he_stbc: 2,
453            val_scale_cfg: 2,
454            dump_ack_en: 1,
455            reserved: 19,
456        }
457    }
458}
459
460#[cfg(feature = "csi")]
461impl From<CsiConfig> for wifi_csi_config_t {
462    fn from(config: CsiConfig) -> Self {
463        #[cfg(not(esp32c6))]
464        {
465            wifi_csi_config_t {
466                lltf_en: config.lltf_en,
467                htltf_en: config.htltf_en,
468                stbc_htltf2_en: config.stbc_htltf2_en,
469                ltf_merge_en: config.ltf_merge_en,
470                channel_filter_en: config.channel_filter_en,
471                manu_scale: config.manu_scale,
472                shift: config.shift,
473                dump_ack_en: config.dump_ack_en,
474            }
475        }
476        #[cfg(esp32c6)]
477        {
478            wifi_csi_acquire_config_t {
479                _bitfield_align_1: [0; 0],
480                _bitfield_1: wifi_csi_acquire_config_t::new_bitfield_1(
481                    config.enable,
482                    config.acquire_csi_legacy,
483                    config.acquire_csi_ht20,
484                    config.acquire_csi_ht40,
485                    config.acquire_csi_su,
486                    config.acquire_csi_mu,
487                    config.acquire_csi_dcm,
488                    config.acquire_csi_beamformed,
489                    config.acquire_csi_he_stbc,
490                    config.val_scale_cfg,
491                    config.dump_ack_en,
492                    config.reserved,
493                ),
494            }
495        }
496    }
497}
498
499#[cfg(feature = "csi")]
500impl CsiConfig {
501    /// Set CSI data configuration
502    pub(crate) fn apply_config(&self) -> Result<(), WifiError> {
503        let conf: wifi_csi_config_t = self.clone().into();
504
505        unsafe {
506            esp_wifi_result!(esp_wifi_set_csi_config(&conf))?;
507        }
508        Ok(())
509    }
510
511    /// Register the RX callback function of CSI data. Each time a CSI data is
512    /// received, the callback function will be called.
513    pub(crate) fn set_receive_cb<C: CsiCallback>(&mut self, cb: C) -> Result<(), WifiError> {
514        let cb = alloc::boxed::Box::new(cb);
515        let cb_ptr = alloc::boxed::Box::into_raw(cb) as *mut crate::wifi::c_types::c_void;
516
517        unsafe {
518            esp_wifi_result!(esp_wifi_set_csi_rx_cb(Some(csi_rx_cb::<C>), cb_ptr))?;
519        }
520        Ok(())
521    }
522
523    /// Enable or disable CSI
524    pub(crate) fn set_csi(&self, enable: bool) -> Result<(), WifiError> {
525        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi.h#L1241
526        unsafe {
527            esp_wifi_result!(esp_wifi_set_csi(enable))?;
528        }
529        Ok(())
530    }
531}
532
533/// Configuration for EAP-FAST authentication protocol.
534#[derive(Clone, Debug, PartialEq, Eq)]
535#[cfg_attr(feature = "defmt", derive(defmt::Format))]
536#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
537pub struct EapFastConfig {
538    /// Specifies the provisioning mode for EAP-FAST.
539    pub fast_provisioning: u8,
540    /// The maximum length of the PAC (Protected Access Credentials) list.
541    pub fast_max_pac_list_len: u8,
542    /// Indicates whether the PAC file is in binary format.
543    pub fast_pac_format_binary: bool,
544}
545
546/// Phase 2 authentication methods
547#[derive(Debug, Clone, PartialEq, Eq)]
548#[cfg_attr(feature = "defmt", derive(defmt::Format))]
549#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
550pub enum TtlsPhase2Method {
551    /// EAP (Extensible Authentication Protocol).
552    Eap,
553
554    /// MSCHAPv2 (Microsoft Challenge Handshake Authentication Protocol 2).
555    Mschapv2,
556
557    /// MSCHAP (Microsoft Challenge Handshake Authentication Protocol).
558    Mschap,
559
560    /// PAP (Password Authentication Protocol).
561    Pap,
562
563    /// CHAP (Challenge Handshake Authentication Protocol).
564    Chap,
565}
566
567impl TtlsPhase2Method {
568    /// Maps the phase 2 method to a raw `u32` representation.
569    fn to_raw(&self) -> u32 {
570        match self {
571            TtlsPhase2Method::Eap => {
572                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_EAP
573            }
574            TtlsPhase2Method::Mschapv2 => {
575                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_MSCHAPV2
576            }
577            TtlsPhase2Method::Mschap => {
578                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_MSCHAP
579            }
580            TtlsPhase2Method::Pap => {
581                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_PAP
582            }
583            TtlsPhase2Method::Chap => {
584                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_CHAP
585            }
586        }
587    }
588}
589
590/// Configuration for an EAP (Extensible Authentication Protocol) client.
591#[derive(Clone, PartialEq, Eq)]
592#[cfg_attr(feature = "defmt", derive(defmt::Format))]
593#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
594pub struct EapClientConfiguration {
595    /// The SSID of the network the client is connecting to.
596    pub ssid: heapless::String<32>,
597
598    /// The BSSID (MAC Address) of the specific access point.
599    pub bssid: Option<[u8; 6]>,
600
601    // pub protocol: Protocol,
602    /// The authentication method used for EAP.
603    pub auth_method: AuthMethod,
604
605    /// The identity used during authentication.
606    pub identity: Option<heapless::String<128>>,
607
608    /// The username used for inner authentication.
609    /// Some EAP methods require a username for authentication.
610    pub username: Option<heapless::String<128>>,
611
612    /// The password used for inner authentication.
613    pub password: Option<heapless::String<64>>,
614
615    /// A new password to be set during the authentication process.
616    /// Some methods support password changes during authentication.
617    pub new_password: Option<heapless::String<64>>,
618
619    /// Configuration for EAP-FAST.
620    pub eap_fast_config: Option<EapFastConfig>,
621
622    /// A PAC (Protected Access Credential) file for EAP-FAST.
623    pub pac_file: Option<&'static [u8]>,
624
625    /// A boolean flag indicating whether time checking is enforced during
626    /// authentication.
627    pub time_check: bool,
628
629    /// A CA (Certificate Authority) certificate for validating the
630    /// authentication server's certificate.
631    pub ca_cert: Option<&'static [u8]>,
632
633    /// A tuple containing the client's certificate, private key, and an
634    /// intermediate certificate.
635    #[allow(clippy::type_complexity)]
636    pub certificate_and_key: Option<(&'static [u8], &'static [u8], Option<&'static [u8]>)>,
637
638    /// The Phase 2 authentication method used for EAP-TTLS.
639    pub ttls_phase2_method: Option<TtlsPhase2Method>,
640
641    /// The specific Wi-Fi channel to use for the connection.
642    pub channel: Option<u8>,
643}
644
645impl Debug for EapClientConfiguration {
646    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
647        f.debug_struct("EapClientConfiguration")
648            .field("ssid", &self.ssid)
649            .field("bssid", &self.bssid)
650            .field("auth_method", &self.auth_method)
651            .field("channel", &self.channel)
652            .field("identity", &self.identity)
653            .field("username", &self.username)
654            .field("eap_fast_config", &self.eap_fast_config)
655            .field("time_check", &self.time_check)
656            .field("pac_file set", &self.pac_file.is_some())
657            .field("ca_cert set", &self.ca_cert.is_some())
658            .field(
659                "certificate_and_key set",
660                &self.certificate_and_key.is_some(),
661            )
662            .field("ttls_phase2_method", &self.ttls_phase2_method)
663            .finish()
664    }
665}
666
667impl Default for EapClientConfiguration {
668    fn default() -> Self {
669        EapClientConfiguration {
670            ssid: heapless::String::new(),
671            bssid: None,
672            auth_method: AuthMethod::WPA2Enterprise,
673            identity: None,
674            username: None,
675            password: None,
676            channel: None,
677            eap_fast_config: None,
678            time_check: false,
679            new_password: None,
680            pac_file: None,
681            ca_cert: None,
682            certificate_and_key: None,
683            ttls_phase2_method: None,
684        }
685    }
686}
687
688/// Introduces Wi-Fi configuration options.
689#[derive(EnumSetType, Debug, PartialOrd)]
690#[cfg_attr(feature = "defmt", derive(defmt::Format))]
691#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
692pub enum Capability {
693    /// The device operates as a client, connecting to an existing network.
694    Client,
695
696    /// The device operates as an access point, allowing other devices to
697    /// connect to it.
698    AccessPoint,
699
700    /// The device can operate in both client and access point modes
701    /// simultaneously.
702    Mixed,
703}
704
705/// Configuration of Wi-Fi operation mode.
706#[derive(Clone, Debug, PartialEq, Eq)]
707#[cfg_attr(feature = "defmt", derive(defmt::Format))]
708#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
709#[derive(Default)]
710#[allow(clippy::large_enum_variant)]
711pub enum Configuration {
712    /// No configuration (default).
713    #[default]
714    None,
715
716    /// Client-only configuration.
717    Client(ClientConfiguration),
718
719    /// Access point-only configuration.
720    AccessPoint(AccessPointConfiguration),
721
722    /// Simultaneous client and access point configuration.
723    Mixed(ClientConfiguration, AccessPointConfiguration),
724
725    /// EAP client configuration for enterprise Wi-Fi.
726    #[cfg_attr(feature = "serde", serde(skip))]
727    EapClient(EapClientConfiguration),
728}
729
730impl Configuration {
731    /// Returns a reference to the client configuration if available.
732    pub fn as_client_conf_ref(&self) -> Option<&ClientConfiguration> {
733        match self {
734            Self::Client(client_conf) | Self::Mixed(client_conf, _) => Some(client_conf),
735            _ => None,
736        }
737    }
738
739    /// Returns a reference to the access point configuration if available.
740    pub fn as_ap_conf_ref(&self) -> Option<&AccessPointConfiguration> {
741        match self {
742            Self::AccessPoint(ap_conf) | Self::Mixed(_, ap_conf) => Some(ap_conf),
743            _ => None,
744        }
745    }
746
747    /// Returns a mutable reference to the client configuration, creating it if
748    /// necessary.
749    pub fn as_client_conf_mut(&mut self) -> &mut ClientConfiguration {
750        match self {
751            Self::Client(client_conf) => client_conf,
752            Self::Mixed(_, _) => {
753                let prev = mem::replace(self, Self::None);
754                match prev {
755                    Self::Mixed(client_conf, _) => {
756                        *self = Self::Client(client_conf);
757                        self.as_client_conf_mut()
758                    }
759                    _ => unreachable!(),
760                }
761            }
762            _ => {
763                *self = Self::Client(Default::default());
764                self.as_client_conf_mut()
765            }
766        }
767    }
768
769    /// Returns a mutable reference to the access point configuration, creating
770    /// it if necessary.
771    pub fn as_ap_conf_mut(&mut self) -> &mut AccessPointConfiguration {
772        match self {
773            Self::AccessPoint(ap_conf) => ap_conf,
774            Self::Mixed(_, _) => {
775                let prev = mem::replace(self, Self::None);
776                match prev {
777                    Self::Mixed(_, ap_conf) => {
778                        *self = Self::AccessPoint(ap_conf);
779                        self.as_ap_conf_mut()
780                    }
781                    _ => unreachable!(),
782                }
783            }
784            _ => {
785                *self = Self::AccessPoint(Default::default());
786                self.as_ap_conf_mut()
787            }
788        }
789    }
790
791    /// Retrieves mutable references to both the `ClientConfiguration`
792    /// and `AccessPointConfiguration`.
793    pub fn as_mixed_conf_mut(
794        &mut self,
795    ) -> (&mut ClientConfiguration, &mut AccessPointConfiguration) {
796        match self {
797            Self::Mixed(client_conf, ref mut ap_conf) => (client_conf, ap_conf),
798            Self::AccessPoint(_) => {
799                let prev = mem::replace(self, Self::None);
800                match prev {
801                    Self::AccessPoint(ap_conf) => {
802                        *self = Self::Mixed(Default::default(), ap_conf);
803                        self.as_mixed_conf_mut()
804                    }
805                    _ => unreachable!(),
806                }
807            }
808            Self::Client(_) => {
809                let prev = mem::replace(self, Self::None);
810                match prev {
811                    Self::Client(client_conf) => {
812                        *self = Self::Mixed(client_conf, Default::default());
813                        self.as_mixed_conf_mut()
814                    }
815                    _ => unreachable!(),
816                }
817            }
818            _ => {
819                *self = Self::Mixed(Default::default(), Default::default());
820                self.as_mixed_conf_mut()
821            }
822        }
823    }
824}
825
826trait AuthMethodExt {
827    fn to_raw(&self) -> wifi_auth_mode_t;
828    fn from_raw(raw: wifi_auth_mode_t) -> Self;
829}
830
831impl AuthMethodExt for AuthMethod {
832    fn to_raw(&self) -> wifi_auth_mode_t {
833        match self {
834            AuthMethod::None => include::wifi_auth_mode_t_WIFI_AUTH_OPEN,
835            AuthMethod::WEP => include::wifi_auth_mode_t_WIFI_AUTH_WEP,
836            AuthMethod::WPA => include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK,
837            AuthMethod::WPA2Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK,
838            AuthMethod::WPAWPA2Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK,
839            AuthMethod::WPA2Enterprise => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE,
840            AuthMethod::WPA3Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK,
841            AuthMethod::WPA2WPA3Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK,
842            AuthMethod::WAPIPersonal => include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK,
843        }
844    }
845
846    fn from_raw(raw: wifi_auth_mode_t) -> Self {
847        match raw {
848            include::wifi_auth_mode_t_WIFI_AUTH_OPEN => AuthMethod::None,
849            include::wifi_auth_mode_t_WIFI_AUTH_WEP => AuthMethod::WEP,
850            include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK => AuthMethod::WPA,
851            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK => AuthMethod::WPA2Personal,
852            include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK => AuthMethod::WPAWPA2Personal,
853            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE => AuthMethod::WPA2Enterprise,
854            include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK => AuthMethod::WPA3Personal,
855            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK => AuthMethod::WPA2WPA3Personal,
856            include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK => AuthMethod::WAPIPersonal,
857            _ => unreachable!(),
858        }
859    }
860}
861
862/// Wifi Mode (Sta and/or Ap)
863#[derive(Debug, Clone, Copy, PartialEq)]
864#[cfg_attr(feature = "defmt", derive(defmt::Format))]
865#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
866pub enum WifiMode {
867    /// Station mode.
868    Sta,
869    /// Access Point mode.
870    Ap,
871    /// Both Station and Access Point modes.
872    ApSta,
873}
874
875impl WifiMode {
876    pub(crate) fn current() -> Result<Self, WifiError> {
877        let mut mode = wifi_mode_t_WIFI_MODE_NULL;
878        esp_wifi_result!(unsafe { esp_wifi_get_mode(&mut mode) })?;
879
880        Self::try_from(mode)
881    }
882
883    /// Returns true if this mode works as a client
884    pub fn is_sta(&self) -> bool {
885        match self {
886            Self::Sta | Self::ApSta => true,
887            Self::Ap => false,
888        }
889    }
890
891    /// Returns true if this mode works as an access point
892    pub fn is_ap(&self) -> bool {
893        match self {
894            Self::Sta => false,
895            Self::Ap | Self::ApSta => true,
896        }
897    }
898}
899
900impl TryFrom<&Configuration> for WifiMode {
901    type Error = WifiError;
902
903    /// Based on the current `Configuration`, derives a `WifiMode` based on it.
904    fn try_from(config: &Configuration) -> Result<Self, Self::Error> {
905        let mode = match config {
906            Configuration::None => return Err(WifiError::UnknownWifiMode),
907            Configuration::AccessPoint(_) => Self::Ap,
908            Configuration::Client(_) => Self::Sta,
909            Configuration::Mixed(_, _) => Self::ApSta,
910            Configuration::EapClient(_) => Self::Sta,
911        };
912
913        Ok(mode)
914    }
915}
916
917impl TryFrom<wifi_mode_t> for WifiMode {
918    type Error = WifiError;
919
920    /// Converts a `wifi_mode_t` C-type into a `WifiMode`.
921    fn try_from(value: wifi_mode_t) -> Result<Self, Self::Error> {
922        #[allow(non_upper_case_globals)]
923        match value {
924            include::wifi_mode_t_WIFI_MODE_STA => Ok(Self::Sta),
925            include::wifi_mode_t_WIFI_MODE_AP => Ok(Self::Ap),
926            include::wifi_mode_t_WIFI_MODE_APSTA => Ok(Self::ApSta),
927            _ => Err(WifiError::UnknownWifiMode),
928        }
929    }
930}
931
932impl From<WifiMode> for wifi_mode_t {
933    fn from(val: WifiMode) -> Self {
934        #[allow(non_upper_case_globals)]
935        match val {
936            WifiMode::Sta => wifi_mode_t_WIFI_MODE_STA,
937            WifiMode::Ap => wifi_mode_t_WIFI_MODE_AP,
938            WifiMode::ApSta => wifi_mode_t_WIFI_MODE_APSTA,
939        }
940    }
941}
942
943const RX_QUEUE_SIZE: usize = crate::CONFIG.rx_queue_size;
944const TX_QUEUE_SIZE: usize = crate::CONFIG.tx_queue_size;
945
946pub(crate) static DATA_QUEUE_RX_AP: Locked<VecDeque<EspWifiPacketBuffer>> =
947    Locked::new(VecDeque::new());
948
949pub(crate) static DATA_QUEUE_RX_STA: Locked<VecDeque<EspWifiPacketBuffer>> =
950    Locked::new(VecDeque::new());
951
952/// Common errors.
953#[derive(Debug, Clone, Copy)]
954#[cfg_attr(feature = "defmt", derive(defmt::Format))]
955pub enum WifiError {
956    /// Wi-Fi module is not initialized or not initialized for `Wi-Fi`
957    /// operations.
958    NotInitialized,
959
960    /// Internal Wi-Fi error.
961    InternalError(InternalWifiError),
962
963    /// The device disconnected from the network or failed to connect to it.
964    Disconnected,
965
966    /// Unknown Wi-Fi mode (not Sta/Ap/ApSta).
967    UnknownWifiMode,
968
969    /// Unsupported operation or mode.
970    Unsupported,
971}
972
973/// Events generated by the WiFi driver.
974#[repr(i32)]
975#[derive(Debug, FromPrimitive, EnumSetType)]
976#[cfg_attr(feature = "defmt", derive(defmt::Format))]
977pub enum WifiEvent {
978    /// Wi-Fi is ready for operation.
979    WifiReady = 0,
980    /// Scan operation has completed.
981    ScanDone,
982    /// Station mode started.
983    StaStart,
984    /// Station mode stopped.
985    StaStop,
986    /// Station connected to a network.
987    StaConnected,
988    /// Station disconnected from a network.
989    StaDisconnected,
990    /// Station authentication mode changed.
991    StaAuthmodeChange,
992
993    /// Station WPS succeeds in enrollee mode.
994    StaWpsErSuccess,
995    /// Station WPS fails in enrollee mode.
996    StaWpsErFailed,
997    /// Station WPS timeout in enrollee mode.
998    StaWpsErTimeout,
999    /// Station WPS pin code in enrollee mode.
1000    StaWpsErPin,
1001    /// Station WPS overlap in enrollee mode.
1002    StaWpsErPbcOverlap,
1003
1004    /// Soft-AP start.
1005    ApStart,
1006    /// Soft-AP stop.
1007    ApStop,
1008    /// A station connected to Soft-AP.
1009    ApStaconnected,
1010    /// A station disconnected from Soft-AP.
1011    ApStadisconnected,
1012    /// Received probe request packet in Soft-AP interface.
1013    ApProbereqrecved,
1014
1015    /// Received report of FTM procedure.
1016    FtmReport,
1017
1018    /// AP's RSSI crossed configured threshold.
1019    StaBssRssiLow,
1020    /// Status indication of Action Tx operation.
1021    ActionTxStatus,
1022    /// Remain-on-Channel operation complete.
1023    RocDone,
1024
1025    /// Station beacon timeout.
1026    StaBeaconTimeout,
1027
1028    /// Connectionless module wake interval has started.
1029    ConnectionlessModuleWakeIntervalStart,
1030
1031    /// Soft-AP WPS succeeded in registrar mode.
1032    ApWpsRgSuccess,
1033    /// Soft-AP WPS failed in registrar mode.
1034    ApWpsRgFailed,
1035    /// Soft-AP WPS timed out in registrar mode.
1036    ApWpsRgTimeout,
1037    /// Soft-AP WPS pin code in registrar mode.
1038    ApWpsRgPin,
1039    /// Soft-AP WPS overlap in registrar mode.
1040    ApWpsRgPbcOverlap,
1041
1042    /// iTWT setup.
1043    ItwtSetup,
1044    /// iTWT teardown.
1045    ItwtTeardown,
1046    /// iTWT probe.
1047    ItwtProbe,
1048    /// iTWT suspended.
1049    ItwtSuspend,
1050    /// TWT wakeup event.
1051    TwtWakeup,
1052    /// bTWT setup.
1053    BtwtSetup,
1054    /// bTWT teardown.
1055    BtwtTeardown,
1056
1057    /// NAN (Neighbor Awareness Networking) discovery has started.
1058    NanStarted,
1059    /// NAN discovery has stopped.
1060    NanStopped,
1061    /// NAN service discovery match found.
1062    NanSvcMatch,
1063    /// Replied to a NAN peer with service discovery match.
1064    NanReplied,
1065    /// Received a follow-up message in NAN.
1066    NanReceive,
1067    /// Received NDP (Neighbor Discovery Protocol) request from a NAN peer.
1068    NdpIndication,
1069    /// NDP confirm indication.
1070    NdpConfirm,
1071    /// NAN datapath terminated indication.
1072    NdpTerminated,
1073    /// Wi-Fi home channel change, doesn't occur when scanning.
1074    HomeChannelChange,
1075
1076    /// Received Neighbor Report response.
1077    StaNeighborRep,
1078}
1079
1080/// Error originating from the underlying drivers
1081#[repr(i32)]
1082#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)]
1083#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1084#[allow(clippy::enum_variant_names)] // FIXME remove prefix
1085pub enum InternalWifiError {
1086    /// Out of memory
1087    EspErrNoMem          = 0x101,
1088
1089    /// Invalid argument
1090    EspErrInvalidArg     = 0x102,
1091
1092    /// WiFi driver was not installed by esp_wifi_init
1093    EspErrWifiNotInit    = 0x3001,
1094
1095    /// WiFi driver was not started by esp_wifi_start
1096    EspErrWifiNotStarted = 0x3002,
1097
1098    /// WiFi driver was not stopped by esp_wifi_stop
1099    EspErrWifiNotStopped = 0x3003,
1100
1101    /// WiFi interface error
1102    EspErrWifiIf         = 0x3004,
1103
1104    /// WiFi mode error
1105    EspErrWifiMode       = 0x3005,
1106
1107    /// WiFi internal state error
1108    EspErrWifiState      = 0x3006,
1109
1110    /// WiFi internal control block of station or soft-AP error
1111    EspErrWifiConn       = 0x3007,
1112
1113    /// WiFi internal NVS module error
1114    EspErrWifiNvs        = 0x3008,
1115
1116    /// MAC address is invalid
1117    EspErrWifiMac        = 0x3009,
1118
1119    /// SSID is invalid
1120    EspErrWifiSsid       = 0x300A,
1121
1122    /// Password is invalid
1123    EspErrWifiPassword   = 0x300B,
1124
1125    /// Timeout error
1126    EspErrWifiTimeout    = 0x300C,
1127
1128    /// WiFi is in sleep state(RF closed) and wakeup fail
1129    EspErrWifiWakeFail   = 0x300D,
1130
1131    /// The caller would block
1132    EspErrWifiWouldBlock = 0x300E,
1133
1134    /// Station still in disconnect status
1135    EspErrWifiNotConnect = 0x300F,
1136
1137    /// Failed to post the event to WiFi task
1138    EspErrWifiPost       = 0x3012,
1139
1140    /// Invalid WiFi state when init/deinit is called
1141    EspErrWifiInitState  = 0x3013,
1142
1143    /// Returned when WiFi is stopping
1144    EspErrWifiStopState  = 0x3014,
1145
1146    /// The WiFi connection is not associated
1147    EspErrWifiNotAssoc   = 0x3015,
1148
1149    /// The WiFi TX is disallowed
1150    EspErrWifiTxDisallow = 0x3016,
1151}
1152
1153#[cfg(all(coex, any(esp32, esp32c2, esp32c3, esp32c6, esp32s3)))]
1154static mut G_COEX_ADAPTER_FUNCS: coex_adapter_funcs_t = coex_adapter_funcs_t {
1155    _version: include::COEX_ADAPTER_VERSION as i32,
1156    _task_yield_from_isr: Some(task_yield_from_isr),
1157    _semphr_create: Some(semphr_create),
1158    _semphr_delete: Some(semphr_delete),
1159    _semphr_take_from_isr: Some(semphr_take_from_isr_wrapper),
1160    _semphr_give_from_isr: Some(semphr_give_from_isr_wrapper),
1161    _semphr_take: Some(semphr_take),
1162    _semphr_give: Some(semphr_give),
1163    _is_in_isr: Some(is_in_isr_wrapper),
1164    _malloc_internal: Some(malloc),
1165    _free: Some(free),
1166    _esp_timer_get_time: Some(esp_timer_get_time),
1167    _env_is_chip: Some(env_is_chip),
1168    _magic: include::COEX_ADAPTER_MAGIC as i32,
1169    _timer_disarm: Some(ets_timer_disarm),
1170    _timer_done: Some(ets_timer_done),
1171    _timer_setfn: Some(ets_timer_setfn),
1172    _timer_arm_us: Some(ets_timer_arm_us),
1173
1174    #[cfg(esp32)]
1175    _spin_lock_create: Some(spin_lock_create),
1176    #[cfg(esp32)]
1177    _spin_lock_delete: Some(spin_lock_delete),
1178    #[cfg(esp32)]
1179    _int_disable: Some(wifi_int_disable),
1180    #[cfg(esp32)]
1181    _int_enable: Some(wifi_int_restore),
1182
1183    #[cfg(esp32c2)]
1184    _slowclk_cal_get: Some(slowclk_cal_get),
1185};
1186
1187#[cfg(coex)]
1188unsafe extern "C" fn semphr_take_from_isr_wrapper(
1189    semphr: *mut c_types::c_void,
1190    hptw: *mut c_types::c_void,
1191) -> i32 {
1192    crate::common_adapter::semphr_take_from_isr(semphr as *const (), hptw as *const ())
1193}
1194
1195#[cfg(coex)]
1196unsafe extern "C" fn semphr_give_from_isr_wrapper(
1197    semphr: *mut c_types::c_void,
1198    hptw: *mut c_types::c_void,
1199) -> i32 {
1200    crate::common_adapter::semphr_give_from_isr(semphr as *const (), hptw as *const ())
1201}
1202
1203#[cfg(coex)]
1204unsafe extern "C" fn is_in_isr_wrapper() -> i32 {
1205    // like original implementation
1206    0
1207}
1208
1209#[cfg(coex)]
1210pub(crate) fn coex_initialize() -> i32 {
1211    debug!("call coex-initialize");
1212    unsafe {
1213        let res = esp_coex_adapter_register(core::ptr::addr_of_mut!(G_COEX_ADAPTER_FUNCS).cast());
1214        if res != 0 {
1215            error!("Error: esp_coex_adapter_register {}", res);
1216            return res;
1217        }
1218        let res = coex_pre_init();
1219        if res != 0 {
1220            error!("Error: coex_pre_init {}", res);
1221            return res;
1222        }
1223        0
1224    }
1225}
1226
1227pub(crate) unsafe extern "C" fn coex_init() -> i32 {
1228    #[cfg(coex)]
1229    {
1230        debug!("coex-init");
1231        #[allow(clippy::needless_return)]
1232        return include::coex_init();
1233    }
1234
1235    #[cfg(not(coex))]
1236    0
1237}
1238
1239#[no_mangle]
1240static g_wifi_osi_funcs: wifi_osi_funcs_t = wifi_osi_funcs_t {
1241    _version: ESP_WIFI_OS_ADAPTER_VERSION as i32,
1242    _env_is_chip: Some(env_is_chip),
1243    _set_intr: Some(set_intr),
1244    _clear_intr: Some(clear_intr),
1245    _set_isr: Some(os_adapter_chip_specific::set_isr),
1246    _ints_on: Some(ints_on),
1247    _ints_off: Some(ints_off),
1248    _is_from_isr: Some(is_from_isr),
1249    _spin_lock_create: Some(spin_lock_create),
1250    _spin_lock_delete: Some(spin_lock_delete),
1251    _wifi_int_disable: Some(wifi_int_disable),
1252    _wifi_int_restore: Some(wifi_int_restore),
1253    _task_yield_from_isr: Some(task_yield_from_isr),
1254    _semphr_create: Some(semphr_create),
1255    _semphr_delete: Some(semphr_delete),
1256    _semphr_take: Some(semphr_take),
1257    _semphr_give: Some(semphr_give),
1258    _wifi_thread_semphr_get: Some(wifi_thread_semphr_get),
1259    _mutex_create: Some(mutex_create),
1260    _recursive_mutex_create: Some(recursive_mutex_create),
1261    _mutex_delete: Some(mutex_delete),
1262    _mutex_lock: Some(mutex_lock),
1263    _mutex_unlock: Some(mutex_unlock),
1264    _queue_create: Some(queue_create),
1265    _queue_delete: Some(queue_delete),
1266    _queue_send: Some(queue_send),
1267    _queue_send_from_isr: Some(queue_send_from_isr),
1268    _queue_send_to_back: Some(queue_send_to_back),
1269    _queue_send_to_front: Some(queue_send_to_front),
1270    _queue_recv: Some(queue_recv),
1271    _queue_msg_waiting: Some(queue_msg_waiting),
1272    _event_group_create: Some(event_group_create),
1273    _event_group_delete: Some(event_group_delete),
1274    _event_group_set_bits: Some(event_group_set_bits),
1275    _event_group_clear_bits: Some(event_group_clear_bits),
1276    _event_group_wait_bits: Some(event_group_wait_bits),
1277    _task_create_pinned_to_core: Some(task_create_pinned_to_core),
1278    _task_create: Some(task_create),
1279    _task_delete: Some(task_delete),
1280    _task_delay: Some(task_delay),
1281    _task_ms_to_tick: Some(task_ms_to_tick),
1282    _task_get_current_task: Some(task_get_current_task),
1283    _task_get_max_priority: Some(task_get_max_priority),
1284    _malloc: Some(malloc),
1285    _free: Some(free),
1286    _event_post: Some(event_post),
1287    _get_free_heap_size: Some(get_free_heap_size),
1288    _rand: Some(rand),
1289    _dport_access_stall_other_cpu_start_wrap: Some(dport_access_stall_other_cpu_start_wrap),
1290    _dport_access_stall_other_cpu_end_wrap: Some(dport_access_stall_other_cpu_end_wrap),
1291    _wifi_apb80m_request: Some(wifi_apb80m_request),
1292    _wifi_apb80m_release: Some(wifi_apb80m_release),
1293    _phy_disable: Some(phy_disable),
1294    _phy_enable: Some(phy_enable),
1295    _phy_update_country_info: Some(phy_update_country_info),
1296    _read_mac: Some(read_mac),
1297    _timer_arm: Some(ets_timer_arm),
1298    _timer_disarm: Some(ets_timer_disarm),
1299    _timer_done: Some(ets_timer_done),
1300    _timer_setfn: Some(ets_timer_setfn),
1301    _timer_arm_us: Some(ets_timer_arm_us),
1302    _wifi_reset_mac: Some(wifi_reset_mac),
1303    _wifi_clock_enable: Some(wifi_clock_enable),
1304    _wifi_clock_disable: Some(wifi_clock_disable),
1305    _wifi_rtc_enable_iso: Some(wifi_rtc_enable_iso),
1306    _wifi_rtc_disable_iso: Some(wifi_rtc_disable_iso),
1307    _esp_timer_get_time: Some(esp_timer_get_time),
1308    _nvs_set_i8: Some(nvs_set_i8),
1309    _nvs_get_i8: Some(nvs_get_i8),
1310    _nvs_set_u8: Some(nvs_set_u8),
1311    _nvs_get_u8: Some(nvs_get_u8),
1312    _nvs_set_u16: Some(nvs_set_u16),
1313    _nvs_get_u16: Some(nvs_get_u16),
1314    _nvs_open: Some(nvs_open),
1315    _nvs_close: Some(nvs_close),
1316    _nvs_commit: Some(nvs_commit),
1317    _nvs_set_blob: Some(nvs_set_blob),
1318    _nvs_get_blob: Some(nvs_get_blob),
1319    _nvs_erase_key: Some(nvs_erase_key),
1320    _get_random: Some(get_random),
1321    _get_time: Some(get_time),
1322    _random: Some(random),
1323    #[cfg(feature = "sys-logs")]
1324    _log_write: Some(log_write),
1325    #[cfg(not(feature = "sys-logs"))]
1326    _log_write: None,
1327    #[cfg(feature = "sys-logs")]
1328    _log_writev: Some(log_writev),
1329    #[cfg(not(feature = "sys-logs"))]
1330    _log_writev: None,
1331    _log_timestamp: Some(log_timestamp),
1332    _malloc_internal: Some(malloc_internal),
1333    _realloc_internal: Some(realloc_internal),
1334    _calloc_internal: Some(calloc_internal),
1335    _zalloc_internal: Some(zalloc_internal),
1336    _wifi_malloc: Some(wifi_malloc),
1337    _wifi_realloc: Some(wifi_realloc),
1338    _wifi_calloc: Some(wifi_calloc),
1339    _wifi_zalloc: Some(wifi_zalloc),
1340    _wifi_create_queue: Some(wifi_create_queue),
1341    _wifi_delete_queue: Some(wifi_delete_queue),
1342    _coex_init: Some(coex_init),
1343    _coex_deinit: Some(coex_deinit),
1344    _coex_enable: Some(coex_enable),
1345    _coex_disable: Some(coex_disable),
1346    _coex_status_get: Some(coex_status_get),
1347    _coex_condition_set: None,
1348    _coex_wifi_request: Some(coex_wifi_request),
1349    _coex_wifi_release: Some(coex_wifi_release),
1350    _coex_wifi_channel_set: Some(coex_wifi_channel_set),
1351    _coex_event_duration_get: Some(coex_event_duration_get),
1352    _coex_pti_get: Some(coex_pti_get),
1353    _coex_schm_status_bit_clear: Some(coex_schm_status_bit_clear),
1354    _coex_schm_status_bit_set: Some(coex_schm_status_bit_set),
1355    _coex_schm_interval_set: Some(coex_schm_interval_set),
1356    _coex_schm_interval_get: Some(coex_schm_interval_get),
1357    _coex_schm_curr_period_get: Some(coex_schm_curr_period_get),
1358    _coex_schm_curr_phase_get: Some(coex_schm_curr_phase_get),
1359    #[cfg(any(esp32c3, esp32c2, esp32c6, esp32h2, esp32s3, esp32s2))]
1360    _slowclk_cal_get: Some(slowclk_cal_get),
1361    #[cfg(any(esp32, esp32s2))]
1362    _phy_common_clock_disable: Some(os_adapter_chip_specific::phy_common_clock_disable),
1363    #[cfg(any(esp32, esp32s2))]
1364    _phy_common_clock_enable: Some(os_adapter_chip_specific::phy_common_clock_enable),
1365    _coex_register_start_cb: Some(coex_register_start_cb),
1366
1367    #[cfg(esp32c6)]
1368    _regdma_link_set_write_wait_content: Some(
1369        os_adapter_chip_specific::regdma_link_set_write_wait_content_dummy,
1370    ),
1371    #[cfg(esp32c6)]
1372    _sleep_retention_find_link_by_id: Some(
1373        os_adapter_chip_specific::sleep_retention_find_link_by_id_dummy,
1374    ),
1375    _coex_schm_process_restart: Some(coex_schm_process_restart_wrapper),
1376    _coex_schm_register_cb: Some(coex_schm_register_cb_wrapper),
1377
1378    _magic: ESP_WIFI_OS_ADAPTER_MAGIC as i32,
1379
1380    _coex_schm_flexible_period_set: Some(coex_schm_flexible_period_set),
1381    _coex_schm_flexible_period_get: Some(coex_schm_flexible_period_get),
1382};
1383
1384const WIFI_ENABLE_WPA3_SAE: u64 = 1 << 0;
1385const WIFI_ENABLE_ENTERPRISE: u64 = 1 << 7;
1386// const WIFI_FTM_INITIATOR: u64 = 1 << 2;
1387// const WIFI_FTM_RESPONDER: u64 = 1 << 3;
1388// const WIFI_ENABLE_GCMP: u64 = 1 << 4;
1389// const WIFI_ENABLE_GMAC: u64 = 1 << 5;
1390// const WIFI_ENABLE_11R: u64 = 1 << 6;
1391
1392const WIFI_FEATURE_CAPS: u64 = WIFI_ENABLE_WPA3_SAE | WIFI_ENABLE_ENTERPRISE;
1393
1394#[no_mangle]
1395static mut g_wifi_feature_caps: u64 = WIFI_FEATURE_CAPS;
1396
1397static mut G_CONFIG: wifi_init_config_t = wifi_init_config_t {
1398    osi_funcs: addr_of!(g_wifi_osi_funcs).cast_mut(),
1399
1400    // dummy for now - populated in init
1401    wpa_crypto_funcs: wpa_crypto_funcs_t {
1402        size: 0,
1403        version: 1,
1404        aes_wrap: None,
1405        aes_unwrap: None,
1406        hmac_sha256_vector: None,
1407        sha256_prf: None,
1408        hmac_md5: None,
1409        hamc_md5_vector: None,
1410        hmac_sha1: None,
1411        hmac_sha1_vector: None,
1412        sha1_prf: None,
1413        sha1_vector: None,
1414        pbkdf2_sha1: None,
1415        rc4_skip: None,
1416        md5_vector: None,
1417        aes_encrypt: None,
1418        aes_encrypt_init: None,
1419        aes_encrypt_deinit: None,
1420        aes_decrypt: None,
1421        aes_decrypt_init: None,
1422        aes_decrypt_deinit: None,
1423        aes_128_encrypt: None,
1424        aes_128_decrypt: None,
1425        omac1_aes_128: None,
1426        ccmp_decrypt: None,
1427        ccmp_encrypt: None,
1428        aes_gmac: None,
1429        sha256_vector: None,
1430        crc32: None,
1431    },
1432    static_rx_buf_num: crate::CONFIG.static_rx_buf_num as i32,
1433    dynamic_rx_buf_num: crate::CONFIG.dynamic_rx_buf_num as i32,
1434    tx_buf_type: esp_wifi_sys::include::CONFIG_ESP_WIFI_TX_BUFFER_TYPE as i32,
1435    static_tx_buf_num: crate::CONFIG.static_tx_buf_num as i32,
1436    dynamic_tx_buf_num: crate::CONFIG.dynamic_tx_buf_num as i32,
1437    rx_mgmt_buf_type: esp_wifi_sys::include::CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF as i32,
1438    rx_mgmt_buf_num: esp_wifi_sys::include::CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF as i32,
1439    cache_tx_buf_num: esp_wifi_sys::include::WIFI_CACHE_TX_BUFFER_NUM as i32,
1440    csi_enable: cfg!(feature = "csi") as i32,
1441    ampdu_rx_enable: crate::CONFIG.ampdu_rx_enable as i32,
1442    ampdu_tx_enable: crate::CONFIG.ampdu_tx_enable as i32,
1443    amsdu_tx_enable: crate::CONFIG.amsdu_tx_enable as i32,
1444    nvs_enable: 0,
1445    nano_enable: 0,
1446    rx_ba_win: crate::CONFIG.rx_ba_win as i32,
1447    wifi_task_core_id: 0,
1448    beacon_max_len: esp_wifi_sys::include::WIFI_SOFTAP_BEACON_MAX_LEN as i32,
1449    mgmt_sbuf_num: esp_wifi_sys::include::WIFI_MGMT_SBUF_NUM as i32,
1450    feature_caps: WIFI_FEATURE_CAPS,
1451    sta_disconnected_pm: false,
1452    espnow_max_encrypt_num: esp_wifi_sys::include::CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM as i32,
1453    magic: WIFI_INIT_CONFIG_MAGIC as i32,
1454
1455    tx_hetb_queue_num: 3,
1456    dump_hesigb_enable: false,
1457};
1458
1459/// Get the STA MAC address
1460pub fn sta_mac(mac: &mut [u8; 6]) {
1461    unsafe {
1462        read_mac(mac as *mut u8, 0);
1463    }
1464}
1465
1466/// Get the AP MAC address
1467pub fn ap_mac(mac: &mut [u8; 6]) {
1468    unsafe {
1469        read_mac(mac as *mut u8, 1);
1470    }
1471}
1472
1473pub(crate) fn wifi_init() -> Result<(), WifiError> {
1474    unsafe {
1475        G_CONFIG.wpa_crypto_funcs = g_wifi_default_wpa_crypto_funcs;
1476        G_CONFIG.feature_caps = g_wifi_feature_caps;
1477
1478        #[cfg(coex)]
1479        esp_wifi_result!(coex_init())?;
1480
1481        esp_wifi_result!(esp_wifi_init_internal(addr_of!(G_CONFIG)))?;
1482        esp_wifi_result!(esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_NULL))?;
1483
1484        esp_wifi_result!(esp_supplicant_init())?;
1485
1486        esp_wifi_result!(esp_wifi_set_tx_done_cb(Some(esp_wifi_tx_done_cb)))?;
1487
1488        esp_wifi_result!(esp_wifi_internal_reg_rxcb(
1489            esp_interface_t_ESP_IF_WIFI_STA,
1490            Some(recv_cb_sta)
1491        ))?;
1492
1493        // until we support APSTA we just register the same callback for AP and STA
1494        esp_wifi_result!(esp_wifi_internal_reg_rxcb(
1495            esp_interface_t_ESP_IF_WIFI_AP,
1496            Some(recv_cb_ap)
1497        ))?;
1498
1499        #[cfg(any(esp32, esp32s3))]
1500        {
1501            static mut NVS_STRUCT: [u32; 12] = [0; 12];
1502            chip_specific::g_misc_nvs = addr_of!(NVS_STRUCT) as u32;
1503        }
1504
1505        crate::flags::WIFI.fetch_add(1, Ordering::SeqCst);
1506
1507        Ok(())
1508    }
1509}
1510
1511pub(crate) fn wifi_deinit() -> Result<(), crate::InitializationError> {
1512    esp_wifi_result!(unsafe { esp_wifi_stop() })?;
1513    esp_wifi_result!(unsafe { esp_wifi_deinit_internal() })?;
1514    esp_wifi_result!(unsafe { esp_supplicant_deinit() })?;
1515    Ok(())
1516}
1517
1518unsafe extern "C" fn recv_cb_sta(
1519    buffer: *mut c_types::c_void,
1520    len: u16,
1521    eb: *mut c_types::c_void,
1522) -> esp_err_t {
1523    let packet = EspWifiPacketBuffer { buffer, len, eb };
1524    // We must handle the result outside of the lock because
1525    // EspWifiPacketBuffer::drop must not be called in a critical section.
1526    // Dropping an EspWifiPacketBuffer will call `esp_wifi_internal_free_rx_buffer`
1527    // which will try to lock an internal mutex. If the mutex is already taken,
1528    // the function will try to trigger a context switch, which will fail if we
1529    // are in an interrupt-free context.
1530    if let Ok(()) = DATA_QUEUE_RX_STA.with(|queue| {
1531        if queue.len() < RX_QUEUE_SIZE {
1532            queue.push_back(packet);
1533            Ok(())
1534        } else {
1535            Err(packet)
1536        }
1537    }) {
1538        embassy::STA_RECEIVE_WAKER.wake();
1539        include::ESP_OK as esp_err_t
1540    } else {
1541        debug!("RX QUEUE FULL");
1542        include::ESP_ERR_NO_MEM as esp_err_t
1543    }
1544}
1545
1546unsafe extern "C" fn recv_cb_ap(
1547    buffer: *mut c_types::c_void,
1548    len: u16,
1549    eb: *mut c_types::c_void,
1550) -> esp_err_t {
1551    let packet = EspWifiPacketBuffer { buffer, len, eb };
1552    // We must handle the result outside of the critical section because
1553    // EspWifiPacketBuffer::drop must not be called in a critical section.
1554    // Dropping an EspWifiPacketBuffer will call `esp_wifi_internal_free_rx_buffer`
1555    // which will try to lock an internal mutex. If the mutex is already taken,
1556    // the function will try to trigger a context switch, which will fail if we
1557    // are in an interrupt-free context.
1558    if let Ok(()) = DATA_QUEUE_RX_AP.with(|queue| {
1559        if queue.len() < RX_QUEUE_SIZE {
1560            queue.push_back(packet);
1561            Ok(())
1562        } else {
1563            Err(packet)
1564        }
1565    }) {
1566        embassy::AP_RECEIVE_WAKER.wake();
1567        include::ESP_OK as esp_err_t
1568    } else {
1569        debug!("RX QUEUE FULL");
1570        include::ESP_ERR_NO_MEM as esp_err_t
1571    }
1572}
1573
1574pub(crate) static WIFI_TX_INFLIGHT: AtomicUsize = AtomicUsize::new(0);
1575
1576fn decrement_inflight_counter() {
1577    unwrap!(
1578        WIFI_TX_INFLIGHT.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
1579            Some(x.saturating_sub(1))
1580        })
1581    );
1582}
1583
1584#[ram]
1585unsafe extern "C" fn esp_wifi_tx_done_cb(
1586    _ifidx: u8,
1587    _data: *mut u8,
1588    _data_len: *mut u16,
1589    _tx_status: bool,
1590) {
1591    trace!("esp_wifi_tx_done_cb");
1592
1593    decrement_inflight_counter();
1594
1595    embassy::TRANSMIT_WAKER.wake();
1596}
1597
1598pub(crate) fn wifi_start() -> Result<(), WifiError> {
1599    unsafe {
1600        esp_wifi_result!(esp_wifi_start())?;
1601
1602        let mode = WifiMode::current()?;
1603
1604        // This is not an if-else because in AP-STA mode, both are true
1605        if mode.is_ap() {
1606            esp_wifi_result!(include::esp_wifi_set_inactive_time(
1607                wifi_interface_t_WIFI_IF_AP,
1608                crate::CONFIG.ap_beacon_timeout
1609            ))?;
1610        }
1611        if mode.is_sta() {
1612            esp_wifi_result!(include::esp_wifi_set_inactive_time(
1613                wifi_interface_t_WIFI_IF_STA,
1614                crate::CONFIG.beacon_timeout
1615            ))?;
1616        };
1617
1618        let mut cntry_code = [0u8; 3];
1619        cntry_code[..crate::CONFIG.country_code.len()]
1620            .copy_from_slice(crate::CONFIG.country_code.as_bytes());
1621        cntry_code[2] = crate::CONFIG.country_code_operating_class;
1622
1623        #[allow(clippy::useless_transmute)]
1624        let country = wifi_country_t {
1625            // FIXME once we bumped the MSRV accordingly (see https://github.com/esp-rs/esp-hal/pull/3027#discussion_r1944718266)
1626            #[allow(clippy::useless_transmute)]
1627            cc: core::mem::transmute::<[u8; 3], [core::ffi::c_char; 3]>(cntry_code),
1628            schan: 1,
1629            nchan: 13,
1630            max_tx_power: 20,
1631            policy: wifi_country_policy_t_WIFI_COUNTRY_POLICY_MANUAL,
1632        };
1633        esp_wifi_result!(esp_wifi_set_country(&country))?;
1634    }
1635
1636    Ok(())
1637}
1638
1639/// Configuration for active or passive scan. For details see the [WIFI Alliance FAQ](https://www.wi-fi.org/knowledge-center/faq/what-are-passive-and-active-scanning).
1640///
1641/// # Comparison of active and passive scan
1642///
1643/// |                                      | **Active** | **Passive** |
1644/// |--------------------------------------|------------|-------------|
1645/// | **Power consumption**                |    High    |     Low     |
1646/// | **Time required (typical behavior)** |     Low    |     High    |
1647#[derive(Clone, Copy, PartialEq, Eq)]
1648pub enum ScanTypeConfig {
1649    /// Active scan with min and max scan time per channel. This is the default
1650    /// and recommended if you are unsure.
1651    ///
1652    /// # Procedure
1653    /// 1. Send probe request on each channel.
1654    /// 2. Wait for probe response. Wait at least `min` time, but if no response
1655    ///    is received, wait up to `max` time.
1656    /// 3. Switch channel.
1657    /// 4. Repeat from 1.
1658    Active {
1659        /// Minimum scan time per channel. Defaults to 10ms.
1660        min: Duration,
1661        /// Maximum scan time per channel. Defaults to 20ms.
1662        max: Duration,
1663    },
1664    /// Passive scan
1665    ///
1666    /// # Procedure
1667    /// 1. Wait for beacon for given duration.
1668    /// 2. Switch channel.
1669    /// 3. Repeat from 1.
1670    ///
1671    /// # Note
1672    /// It is recommended to avoid duration longer thean 1500ms, as it may cause
1673    /// a station to disconnect from the AP.
1674    Passive(Duration),
1675}
1676
1677impl Default for ScanTypeConfig {
1678    fn default() -> Self {
1679        Self::Active {
1680            min: Duration::from_millis(10),
1681            max: Duration::from_millis(20),
1682        }
1683    }
1684}
1685
1686impl ScanTypeConfig {
1687    fn validate(&self) {
1688        if matches!(self, Self::Passive(dur) if *dur > Duration::from_millis(1500)) {
1689            warn!("Passive scan duration longer than 1500ms may cause a station to disconnect from the AP");
1690        }
1691    }
1692}
1693
1694/// Scan configuration
1695#[derive(Clone, Copy, Default, PartialEq, Eq)]
1696pub struct ScanConfig<'a> {
1697    /// SSID to filter for.
1698    /// If [`None`] is passed, all SSIDs will be returned.
1699    /// If [`Some`] is passed, only the APs matching the given SSID will be
1700    /// returned.
1701    pub ssid: Option<&'a str>,
1702    /// BSSID to filter for.
1703    /// If [`None`] is passed, all BSSIDs will be returned.
1704    /// If [`Some`] is passed, only the APs matching the given BSSID will be
1705    /// returned.
1706    pub bssid: Option<[u8; 6]>,
1707    /// Channel to filter for.
1708    /// If [`None`] is passed, all channels will be returned.
1709    /// If [`Some`] is passed, only the APs on the given channel will be
1710    /// returned.
1711    pub channel: Option<u8>,
1712    /// Whether to show hidden networks.
1713    pub show_hidden: bool,
1714    /// Scan type, active or passive.
1715    pub scan_type: ScanTypeConfig,
1716}
1717
1718pub(crate) fn wifi_start_scan(
1719    block: bool,
1720    ScanConfig {
1721        ssid,
1722        mut bssid,
1723        channel,
1724        show_hidden,
1725        scan_type,
1726    }: ScanConfig<'_>,
1727) -> i32 {
1728    scan_type.validate();
1729    let (scan_time, scan_type) = match scan_type {
1730        ScanTypeConfig::Active { min, max } => (
1731            wifi_scan_time_t {
1732                active: wifi_active_scan_time_t {
1733                    min: min.as_millis() as u32,
1734                    max: max.as_millis() as u32,
1735                },
1736                passive: 0,
1737            },
1738            wifi_scan_type_t_WIFI_SCAN_TYPE_ACTIVE,
1739        ),
1740        ScanTypeConfig::Passive(dur) => (
1741            wifi_scan_time_t {
1742                active: wifi_active_scan_time_t { min: 0, max: 0 },
1743                passive: dur.as_millis() as u32,
1744            },
1745            wifi_scan_type_t_WIFI_SCAN_TYPE_PASSIVE,
1746        ),
1747    };
1748
1749    let mut ssid_buf = ssid.map(|m| {
1750        let mut buf = heapless::Vec::<u8, 33>::from_iter(m.bytes());
1751        unwrap!(buf.push(b'\0').ok());
1752        buf
1753    });
1754
1755    let ssid = ssid_buf
1756        .as_mut()
1757        .map(|e| e.as_mut_ptr())
1758        .unwrap_or_else(core::ptr::null_mut);
1759    let bssid = bssid
1760        .as_mut()
1761        .map(|e| e.as_mut_ptr())
1762        .unwrap_or_else(core::ptr::null_mut);
1763
1764    let scan_config = wifi_scan_config_t {
1765        ssid,
1766        bssid,
1767        channel: channel.unwrap_or(0),
1768        show_hidden,
1769        scan_type,
1770        scan_time,
1771        home_chan_dwell_time: 0,
1772        channel_bitmap: wifi_scan_channel_bitmap_t {
1773            ghz_2_channels: 0,
1774            ghz_5_channels: 0,
1775        },
1776    };
1777
1778    unsafe { esp_wifi_scan_start(&scan_config, block) }
1779}
1780
1781mod private {
1782    use super::*;
1783
1784    #[derive(Debug)]
1785    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
1786    /// Take care not to drop this while in a critical section.
1787    ///
1788    /// Dropping an EspWifiPacketBuffer will call
1789    /// `esp_wifi_internal_free_rx_buffer` which will try to lock an
1790    /// internal mutex. If the mutex is already taken, the function will try
1791    /// to trigger a context switch, which will fail if we are in a critical
1792    /// section.
1793    pub struct EspWifiPacketBuffer {
1794        pub(crate) buffer: *mut c_types::c_void,
1795        pub(crate) len: u16,
1796        pub(crate) eb: *mut c_types::c_void,
1797    }
1798
1799    unsafe impl Send for EspWifiPacketBuffer {}
1800
1801    impl Drop for EspWifiPacketBuffer {
1802        fn drop(&mut self) {
1803            trace!("Dropping EspWifiPacketBuffer, freeing memory");
1804            unsafe { esp_wifi_internal_free_rx_buffer(self.eb) };
1805        }
1806    }
1807
1808    impl EspWifiPacketBuffer {
1809        pub fn as_slice_mut(&mut self) -> &mut [u8] {
1810            unsafe { core::slice::from_raw_parts_mut(self.buffer as *mut u8, self.len as usize) }
1811        }
1812    }
1813}
1814
1815/// Provides methods for retrieving the Wi-Fi mode and MAC address.
1816#[derive(Debug, Clone, Copy)]
1817pub enum WifiDeviceMode {
1818    Sta,
1819    Ap,
1820}
1821
1822impl WifiDeviceMode {
1823    fn mac_address(&self) -> [u8; 6] {
1824        match self {
1825            WifiDeviceMode::Sta => {
1826                let mut mac = [0; 6];
1827                sta_mac(&mut mac);
1828                mac
1829            }
1830            WifiDeviceMode::Ap => {
1831                let mut mac = [0; 6];
1832                ap_mac(&mut mac);
1833                mac
1834            }
1835        }
1836    }
1837
1838    fn data_queue_rx(&self) -> &'static Locked<VecDeque<EspWifiPacketBuffer>> {
1839        match self {
1840            WifiDeviceMode::Sta => &DATA_QUEUE_RX_STA,
1841            WifiDeviceMode::Ap => &DATA_QUEUE_RX_AP,
1842        }
1843    }
1844
1845    fn can_send(&self) -> bool {
1846        WIFI_TX_INFLIGHT.load(Ordering::SeqCst) < TX_QUEUE_SIZE
1847    }
1848
1849    fn increase_in_flight_counter(&self) {
1850        WIFI_TX_INFLIGHT.fetch_add(1, Ordering::SeqCst);
1851    }
1852
1853    fn tx_token(&self) -> Option<WifiTxToken> {
1854        if !self.can_send() {
1855            crate::preempt::yield_task();
1856        }
1857
1858        if self.can_send() {
1859            Some(WifiTxToken { mode: *self })
1860        } else {
1861            None
1862        }
1863    }
1864
1865    fn rx_token(&self) -> Option<(WifiRxToken, WifiTxToken)> {
1866        let is_empty = self.data_queue_rx().with(|q| q.is_empty());
1867        if is_empty || !self.can_send() {
1868            crate::preempt::yield_task();
1869        }
1870
1871        let is_empty = is_empty && self.data_queue_rx().with(|q| q.is_empty());
1872
1873        if !is_empty {
1874            self.tx_token().map(|tx| (WifiRxToken { mode: *self }, tx))
1875        } else {
1876            None
1877        }
1878    }
1879
1880    fn interface(&self) -> wifi_interface_t {
1881        match self {
1882            WifiDeviceMode::Sta => wifi_interface_t_WIFI_IF_STA,
1883            WifiDeviceMode::Ap => wifi_interface_t_WIFI_IF_AP,
1884        }
1885    }
1886
1887    fn register_transmit_waker(&self, cx: &mut core::task::Context<'_>) {
1888        embassy::TRANSMIT_WAKER.register(cx.waker())
1889    }
1890
1891    fn register_receive_waker(&self, cx: &mut core::task::Context<'_>) {
1892        match self {
1893            WifiDeviceMode::Sta => embassy::STA_RECEIVE_WAKER.register(cx.waker()),
1894            WifiDeviceMode::Ap => embassy::AP_RECEIVE_WAKER.register(cx.waker()),
1895        }
1896    }
1897
1898    fn register_link_state_waker(&self, cx: &mut core::task::Context<'_>) {
1899        match self {
1900            WifiDeviceMode::Sta => embassy::STA_LINK_STATE_WAKER.register(cx.waker()),
1901            WifiDeviceMode::Ap => embassy::AP_LINK_STATE_WAKER.register(cx.waker()),
1902        }
1903    }
1904
1905    fn link_state(&self) -> embassy_net_driver::LinkState {
1906        match self {
1907            WifiDeviceMode::Sta => {
1908                if matches!(sta_state(), WifiState::StaConnected) {
1909                    embassy_net_driver::LinkState::Up
1910                } else {
1911                    embassy_net_driver::LinkState::Down
1912                }
1913            }
1914            WifiDeviceMode::Ap => {
1915                if matches!(ap_state(), WifiState::ApStarted) {
1916                    embassy_net_driver::LinkState::Up
1917                } else {
1918                    embassy_net_driver::LinkState::Down
1919                }
1920            }
1921        }
1922    }
1923}
1924
1925/// A wifi device implementing smoltcp's Device trait.
1926pub struct WifiDevice<'d> {
1927    _phantom: PhantomData<&'d ()>,
1928    mode: WifiDeviceMode,
1929}
1930
1931impl WifiDevice<'_> {
1932    /// Retrieves the MAC address of the Wi-Fi device.
1933    pub fn mac_address(&self) -> [u8; 6] {
1934        self.mode.mac_address()
1935    }
1936
1937    /// Receives data from the Wi-Fi device (only when `smoltcp` feature is
1938    /// disabled).
1939    #[cfg(not(feature = "smoltcp"))]
1940    pub fn receive(&mut self) -> Option<(WifiRxToken, WifiTxToken)> {
1941        self.mode.rx_token()
1942    }
1943
1944    /// Transmits data through the Wi-Fi device (only when `smoltcp` feature is
1945    /// disabled).
1946    #[cfg(not(feature = "smoltcp"))]
1947    pub fn transmit(&mut self) -> Option<WifiTxToken> {
1948        self.mode.tx_token()
1949    }
1950}
1951
1952fn convert_ap_info(record: &include::wifi_ap_record_t) -> AccessPointInfo {
1953    let str_len = record
1954        .ssid
1955        .iter()
1956        .position(|&c| c == 0)
1957        .unwrap_or(record.ssid.len());
1958    let ssid_ref = unsafe { core::str::from_utf8_unchecked(&record.ssid[..str_len]) };
1959
1960    let mut ssid = heapless::String::<32>::new();
1961    unwrap!(ssid.push_str(ssid_ref));
1962
1963    AccessPointInfo {
1964        ssid,
1965        bssid: record.bssid,
1966        channel: record.primary,
1967        secondary_channel: match record.second {
1968            include::wifi_second_chan_t_WIFI_SECOND_CHAN_NONE => SecondaryChannel::None,
1969            include::wifi_second_chan_t_WIFI_SECOND_CHAN_ABOVE => SecondaryChannel::Above,
1970            include::wifi_second_chan_t_WIFI_SECOND_CHAN_BELOW => SecondaryChannel::Below,
1971            _ => panic!(),
1972        },
1973        signal_strength: record.rssi,
1974        protocols: EnumSet::empty(), // TODO
1975        auth_method: Some(AuthMethod::from_raw(record.authmode)),
1976    }
1977}
1978
1979/// The radio metadata header of the received packet, which is the common header
1980/// at the beginning of all RX callback buffers in promiscuous mode.
1981#[cfg(not(any(esp32c6)))]
1982#[derive(Debug, Clone, Copy)]
1983#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1984pub struct RxControlInfo {
1985    /// Received Signal Strength Indicator (RSSI) of the packet, in dBm.
1986    pub rssi: i32,
1987    /// PHY rate encoding of the packet. Only valid for non-HT (802.11b/g)
1988    /// packets.
1989    pub rate: u32,
1990    /// Protocol of the received packet: 0 for non-HT (11bg), 1 for HT (11n), 3
1991    /// for VHT (11ac).
1992    pub sig_mode: u32,
1993    /// Modulation and Coding Scheme (MCS). Indicates modulation for HT (11n)
1994    /// packets.
1995    pub mcs: u32,
1996    /// Channel bandwidth of the packet: 0 for 20MHz, 1 for 40MHz.
1997    pub cwb: u32,
1998    /// Channel estimate smoothing: 1 recommends smoothing; 0 recommends
1999    /// per-carrier-independent estimate.
2000    pub smoothing: u32,
2001    /// Sounding indicator: 0 for sounding PPDU (used for channel estimation); 1
2002    /// for non-sounding PPDU.
2003    pub not_sounding: u32,
2004    /// Aggregation status: 0 for MPDU packet, 1 for AMPDU packet.
2005    pub aggregation: u32,
2006    /// Space-Time Block Coding (STBC) status: 0 for non-STBC packet, 1 for STBC
2007    /// packet.
2008    pub stbc: u32,
2009    /// Forward Error Correction (FEC) status: indicates if LDPC coding is used
2010    /// for 11n packets.
2011    pub fec_coding: u32,
2012    /// Short Guard Interval (SGI): 0 for long guard interval, 1 for short guard
2013    /// interval.
2014    pub sgi: u32,
2015    /// Number of subframes aggregated in an AMPDU packet.
2016    pub ampdu_cnt: u32,
2017    /// Primary channel on which the packet is received.
2018    pub channel: u32,
2019    /// Secondary channel on which the packet is received: 0 for none, 1 for
2020    /// above, 2 for below.
2021    pub secondary_channel: u32,
2022    /// Timestamp of when the packet is received, in microseconds. Precise only
2023    /// if modem sleep or light sleep is not enabled.
2024    pub timestamp: u32,
2025    /// Noise floor of the Radio Frequency module, in dBm.
2026    pub noise_floor: i32,
2027    /// Antenna number from which the packet is received: 0 for antenna 0, 1 for
2028    /// antenna 1.
2029    pub ant: u32,
2030    /// Length of the packet including the Frame Check Sequence (FCS).
2031    pub sig_len: u32,
2032    /// State of the packet: 0 for no error, other values indicate error codes.
2033    pub rx_state: u32,
2034}
2035
2036/// The radio metadata header of the received packet, which is the common header
2037/// at the beginning of all RX callback buffers in promiscuous mode.
2038#[cfg(esp32c6)]
2039#[derive(Debug, Clone, Copy)]
2040#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2041pub struct RxControlInfo {
2042    /// Received Signal Strength Indicator (RSSI) of the packet, in dBm.
2043    pub rssi: i32,
2044    /// PHY rate encoding of the packet. Only valid for non-HT (802.11b/g)
2045    /// packets.
2046    pub rate: u32,
2047    /// Length of the received packet including the Frame Check Sequence (FCS).
2048    pub sig_len: u32,
2049    /// Reception state of the packet: 0 for no error, others indicate error
2050    /// codes.
2051    pub rx_state: u32,
2052    /// Length of the dump buffer.
2053    pub dump_len: u32,
2054    /// Length of HE-SIG-B field (802.11ax).
2055    pub he_sigb_len: u32,
2056    /// Indicates if this is a single MPDU.
2057    pub cur_single_mpdu: u32,
2058    /// Current baseband format.
2059    pub cur_bb_format: u32,
2060    /// Channel estimation validity.
2061    pub rx_channel_estimate_info_vld: u32,
2062    /// Length of the channel estimation.
2063    pub rx_channel_estimate_len: u32,
2064    /// Timing information in seconds.
2065    pub second: u32,
2066    /// Primary channel on which the packet is received.
2067    pub channel: u32,
2068    /// Noise floor of the Radio Frequency module, in dBm.
2069    pub noise_floor: i32,
2070    /// Indicates if this is a group-addressed frame.
2071    pub is_group: u32,
2072    /// End state of the packet reception.
2073    pub rxend_state: u32,
2074    /// Indicate whether the reception frame is from interface 3.
2075    pub rxmatch3: u32,
2076    /// Indicate whether the reception frame is from interface 2.
2077    pub rxmatch2: u32,
2078    /// Indicate whether the reception frame is from interface 1.
2079    pub rxmatch1: u32,
2080    /// Indicate whether the reception frame is from interface 0.
2081    pub rxmatch0: u32,
2082}
2083impl RxControlInfo {
2084    /// Create an instance from a raw pointer to [wifi_pkt_rx_ctrl_t].
2085    ///
2086    /// # Safety
2087    /// When calling this, you must ensure, that `rx_cntl` points to a valid
2088    /// instance of [wifi_pkt_rx_ctrl_t].
2089    pub unsafe fn from_raw(rx_cntl: *const wifi_pkt_rx_ctrl_t) -> Self {
2090        #[cfg(not(esp32c6))]
2091        let rx_control_info = RxControlInfo {
2092            rssi: (*rx_cntl).rssi(),
2093            rate: (*rx_cntl).rate(),
2094            sig_mode: (*rx_cntl).sig_mode(),
2095            mcs: (*rx_cntl).mcs(),
2096            cwb: (*rx_cntl).cwb(),
2097            smoothing: (*rx_cntl).smoothing(),
2098            not_sounding: (*rx_cntl).not_sounding(),
2099            aggregation: (*rx_cntl).aggregation(),
2100            stbc: (*rx_cntl).stbc(),
2101            fec_coding: (*rx_cntl).fec_coding(),
2102            sgi: (*rx_cntl).sgi(),
2103            ampdu_cnt: (*rx_cntl).ampdu_cnt(),
2104            channel: (*rx_cntl).channel(),
2105            secondary_channel: (*rx_cntl).secondary_channel(),
2106            timestamp: (*rx_cntl).timestamp(),
2107            noise_floor: (*rx_cntl).noise_floor(),
2108            ant: (*rx_cntl).ant(),
2109            sig_len: (*rx_cntl).sig_len(),
2110            rx_state: (*rx_cntl).rx_state(),
2111        };
2112        #[cfg(esp32c6)]
2113        let rx_control_info = RxControlInfo {
2114            rssi: (*rx_cntl).rssi(),
2115            rate: (*rx_cntl).rate(),
2116            sig_len: (*rx_cntl).sig_len(),
2117            rx_state: (*rx_cntl).rx_state(),
2118            dump_len: (*rx_cntl).dump_len(),
2119            he_sigb_len: (*rx_cntl).he_sigb_len(),
2120            cur_single_mpdu: (*rx_cntl).cur_single_mpdu(),
2121            cur_bb_format: (*rx_cntl).cur_bb_format(),
2122            rx_channel_estimate_info_vld: (*rx_cntl).rx_channel_estimate_info_vld(),
2123            rx_channel_estimate_len: (*rx_cntl).rx_channel_estimate_len(),
2124            second: (*rx_cntl).second(),
2125            channel: (*rx_cntl).channel(),
2126            noise_floor: (*rx_cntl).noise_floor(),
2127            is_group: (*rx_cntl).is_group(),
2128            rxend_state: (*rx_cntl).rxend_state(),
2129            rxmatch3: (*rx_cntl).rxmatch3(),
2130            rxmatch2: (*rx_cntl).rxmatch2(),
2131            rxmatch1: (*rx_cntl).rxmatch1(),
2132            rxmatch0: (*rx_cntl).rxmatch0(),
2133        };
2134        rx_control_info
2135    }
2136}
2137/// Represents a Wi-Fi packet in promiscuous mode.
2138#[cfg(feature = "sniffer")]
2139pub struct PromiscuousPkt<'a> {
2140    /// Control information related to packet reception.
2141    pub rx_cntl: RxControlInfo,
2142    /// Frame type of the received packet.
2143    pub frame_type: wifi_promiscuous_pkt_type_t,
2144    /// Length of the received packet.
2145    pub len: usize,
2146    /// Data contained in the received packet.
2147    pub data: &'a [u8],
2148}
2149#[cfg(feature = "sniffer")]
2150impl PromiscuousPkt<'_> {
2151    /// # Safety
2152    /// When calling this, you have to ensure, that `buf` points to a valid
2153    /// [wifi_promiscuous_pkt_t].
2154    pub(crate) unsafe fn from_raw(
2155        buf: *const wifi_promiscuous_pkt_t,
2156        frame_type: wifi_promiscuous_pkt_type_t,
2157    ) -> Self {
2158        let rx_cntl = RxControlInfo::from_raw(&(*buf).rx_ctrl);
2159        let len = rx_cntl.sig_len as usize;
2160        PromiscuousPkt {
2161            rx_cntl,
2162            frame_type,
2163            len,
2164            data: core::slice::from_raw_parts(
2165                (buf as *const u8).add(core::mem::size_of::<wifi_pkt_rx_ctrl_t>()),
2166                len,
2167            ),
2168        }
2169    }
2170}
2171
2172#[cfg(feature = "sniffer")]
2173static SNIFFER_CB: Locked<Option<fn(PromiscuousPkt<'_>)>> = Locked::new(None);
2174
2175#[cfg(feature = "sniffer")]
2176unsafe extern "C" fn promiscuous_rx_cb(buf: *mut core::ffi::c_void, frame_type: u32) {
2177    if let Some(sniffer_callback) = SNIFFER_CB.with(|callback| *callback) {
2178        let promiscuous_pkt = PromiscuousPkt::from_raw(buf as *const _, frame_type);
2179        sniffer_callback(promiscuous_pkt);
2180    }
2181}
2182
2183#[cfg(feature = "sniffer")]
2184/// A wifi sniffer.
2185pub struct Sniffer {
2186    promiscuous_mode_enabled: AtomicBool,
2187}
2188#[cfg(feature = "sniffer")]
2189impl Sniffer {
2190    pub(crate) fn new() -> Self {
2191        // This shouldn't fail, since the way this is created, means that wifi will
2192        // always be initialized.
2193        unwrap!(esp_wifi_result!(unsafe {
2194            esp_wifi_set_promiscuous_rx_cb(Some(promiscuous_rx_cb))
2195        }));
2196        Self {
2197            promiscuous_mode_enabled: AtomicBool::new(false),
2198        }
2199    }
2200    /// Set promiscuous mode enabled or disabled.
2201    pub fn set_promiscuous_mode(&self, enabled: bool) -> Result<(), WifiError> {
2202        esp_wifi_result!(unsafe { esp_wifi_set_promiscuous(enabled) })?;
2203        self.promiscuous_mode_enabled
2204            .store(enabled, Ordering::Relaxed);
2205        Ok(())
2206    }
2207    /// Transmit a raw frame.
2208    pub fn send_raw_frame(
2209        &mut self,
2210        use_sta_interface: bool,
2211        buffer: &[u8],
2212        use_internal_seq_num: bool,
2213    ) -> Result<(), WifiError> {
2214        esp_wifi_result!(unsafe {
2215            esp_wifi_80211_tx(
2216                if use_sta_interface {
2217                    wifi_interface_t_WIFI_IF_STA
2218                } else {
2219                    wifi_interface_t_WIFI_IF_AP
2220                } as wifi_interface_t,
2221                buffer.as_ptr() as *const _,
2222                buffer.len() as i32,
2223                use_internal_seq_num,
2224            )
2225        })
2226    }
2227    /// Set the callback for receiving a packet.
2228    pub fn set_receive_cb(&mut self, cb: fn(PromiscuousPkt<'_>)) {
2229        SNIFFER_CB.with(|callback| *callback = Some(cb));
2230    }
2231}
2232
2233// see https://docs.rs/smoltcp/0.7.1/smoltcp/phy/index.html
2234#[cfg(feature = "smoltcp")]
2235impl Device for WifiDevice<'_> {
2236    type RxToken<'a>
2237        = WifiRxToken
2238    where
2239        Self: 'a;
2240    type TxToken<'a>
2241        = WifiTxToken
2242    where
2243        Self: 'a;
2244
2245    fn receive(
2246        &mut self,
2247        _instant: smoltcp::time::Instant,
2248    ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
2249        self.mode.rx_token()
2250    }
2251
2252    fn transmit(&mut self, _instant: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
2253        self.mode.tx_token()
2254    }
2255
2256    fn capabilities(&self) -> smoltcp::phy::DeviceCapabilities {
2257        let mut caps = DeviceCapabilities::default();
2258        caps.max_transmission_unit = MTU;
2259        caps.max_burst_size = if crate::CONFIG.max_burst_size == 0 {
2260            None
2261        } else {
2262            Some(crate::CONFIG.max_burst_size)
2263        };
2264        caps
2265    }
2266}
2267
2268#[doc(hidden)]
2269#[derive(Debug)]
2270pub struct WifiRxToken {
2271    mode: WifiDeviceMode,
2272}
2273
2274impl WifiRxToken {
2275    /// Consumes the RX token and applies the callback function to the received
2276    /// data buffer.
2277    pub fn consume_token<R, F>(self, f: F) -> R
2278    where
2279        F: FnOnce(&mut [u8]) -> R,
2280    {
2281        let mut data = self.mode.data_queue_rx().with(|queue| {
2282            unwrap!(
2283                queue.pop_front(),
2284                "unreachable: transmit()/receive() ensures there is a packet to process"
2285            )
2286        });
2287
2288        // We handle the received data outside of the lock because
2289        // EspWifiPacketBuffer::drop must not be called in a critical section.
2290        // Dropping an EspWifiPacketBuffer will call `esp_wifi_internal_free_rx_buffer`
2291        // which will try to lock an internal mutex. If the mutex is already
2292        // taken, the function will try to trigger a context switch, which will
2293        // fail if we are in an interrupt-free context.
2294        let buffer = data.as_slice_mut();
2295        dump_packet_info(buffer);
2296
2297        f(buffer)
2298    }
2299}
2300
2301#[cfg(feature = "smoltcp")]
2302impl RxToken for WifiRxToken {
2303    fn consume<R, F>(self, f: F) -> R
2304    where
2305        F: FnOnce(&[u8]) -> R,
2306    {
2307        self.consume_token(|t| f(t))
2308    }
2309}
2310
2311#[doc(hidden)]
2312#[derive(Debug)]
2313pub struct WifiTxToken {
2314    mode: WifiDeviceMode,
2315}
2316
2317impl WifiTxToken {
2318    /// Consumes the TX token and applies the callback function to the received
2319    /// data buffer.
2320    pub fn consume_token<R, F>(self, len: usize, f: F) -> R
2321    where
2322        F: FnOnce(&mut [u8]) -> R,
2323    {
2324        self.mode.increase_in_flight_counter();
2325
2326        // (safety): creation of multiple WiFi devices with the same mode is impossible
2327        // in safe Rust, therefore only smoltcp _or_ embassy-net can be used at
2328        // one time
2329        static mut BUFFER: [u8; MTU] = [0u8; MTU];
2330
2331        let buffer = unsafe { &mut BUFFER[..len] };
2332
2333        let res = f(buffer);
2334
2335        esp_wifi_send_data(self.mode.interface(), buffer);
2336
2337        res
2338    }
2339}
2340
2341#[cfg(feature = "smoltcp")]
2342impl TxToken for WifiTxToken {
2343    fn consume<R, F>(self, len: usize, f: F) -> R
2344    where
2345        F: FnOnce(&mut [u8]) -> R,
2346    {
2347        self.consume_token(len, f)
2348    }
2349}
2350
2351// FIXME data here has to be &mut because of `esp_wifi_internal_tx` signature,
2352// requiring a *mut ptr to the buffer Casting const to mut is instant UB, even
2353// though in reality `esp_wifi_internal_tx` copies the buffer into its own
2354// memory and does not modify
2355pub(crate) fn esp_wifi_send_data(interface: wifi_interface_t, data: &mut [u8]) {
2356    trace!("sending... {} bytes", data.len());
2357    dump_packet_info(data);
2358
2359    let len = data.len() as u16;
2360    let ptr = data.as_mut_ptr().cast();
2361
2362    let res = unsafe { esp_wifi_internal_tx(interface, ptr, len) };
2363
2364    if res != 0 {
2365        warn!("esp_wifi_internal_tx {}", res);
2366        decrement_inflight_counter();
2367    } else {
2368        trace!("esp_wifi_internal_tx ok");
2369    }
2370}
2371
2372fn apply_ap_config(config: &AccessPointConfiguration) -> Result<(), WifiError> {
2373    let mut cfg = wifi_config_t {
2374        ap: wifi_ap_config_t {
2375            ssid: [0; 32],
2376            password: [0; 64],
2377            ssid_len: 0,
2378            channel: config.channel,
2379            authmode: config.auth_method.to_raw(),
2380            ssid_hidden: if config.ssid_hidden { 1 } else { 0 },
2381            max_connection: config.max_connections as u8,
2382            beacon_interval: 100,
2383            pairwise_cipher: wifi_cipher_type_t_WIFI_CIPHER_TYPE_CCMP,
2384            ftm_responder: false,
2385            pmf_cfg: wifi_pmf_config_t {
2386                capable: true,
2387                required: false,
2388            },
2389            sae_pwe_h2e: 0,
2390            csa_count: 3,
2391            dtim_period: 2,
2392        },
2393    };
2394
2395    if config.auth_method == AuthMethod::None && !config.password.is_empty() {
2396        return Err(WifiError::InternalError(
2397            InternalWifiError::EspErrInvalidArg,
2398        ));
2399    }
2400
2401    unsafe {
2402        cfg.ap.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
2403        cfg.ap.ssid_len = config.ssid.len() as u8;
2404        cfg.ap.password[0..(config.password.len())].copy_from_slice(config.password.as_bytes());
2405
2406        esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_AP, &mut cfg))
2407    }
2408}
2409
2410fn apply_sta_config(config: &ClientConfiguration) -> Result<(), WifiError> {
2411    let mut cfg = wifi_config_t {
2412        sta: wifi_sta_config_t {
2413            ssid: [0; 32],
2414            password: [0; 64],
2415            scan_method: crate::CONFIG.scan_method,
2416            bssid_set: config.bssid.is_some(),
2417            bssid: config.bssid.unwrap_or_default(),
2418            channel: config.channel.unwrap_or(0),
2419            listen_interval: crate::CONFIG.listen_interval,
2420            sort_method: wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
2421            threshold: wifi_scan_threshold_t {
2422                rssi: -99,
2423                authmode: config.auth_method.to_raw(),
2424            },
2425            pmf_cfg: wifi_pmf_config_t {
2426                capable: true,
2427                required: false,
2428            },
2429            sae_pwe_h2e: 3,
2430            _bitfield_align_1: [0; 0],
2431            _bitfield_1: __BindgenBitfieldUnit::new([0; 4]),
2432            failure_retry_cnt: crate::CONFIG.failure_retry_cnt,
2433            _bitfield_align_2: [0; 0],
2434            _bitfield_2: __BindgenBitfieldUnit::new([0; 4]),
2435            sae_pk_mode: 0, // ??
2436            sae_h2e_identifier: [0; 32],
2437        },
2438    };
2439
2440    if config.auth_method == AuthMethod::None && !config.password.is_empty() {
2441        return Err(WifiError::InternalError(
2442            InternalWifiError::EspErrInvalidArg,
2443        ));
2444    }
2445
2446    unsafe {
2447        cfg.sta.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
2448        cfg.sta.password[0..(config.password.len())].copy_from_slice(config.password.as_bytes());
2449
2450        esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_STA, &mut cfg))
2451    }
2452}
2453
2454fn apply_sta_eap_config(config: &EapClientConfiguration) -> Result<(), WifiError> {
2455    let mut cfg = wifi_config_t {
2456        sta: wifi_sta_config_t {
2457            ssid: [0; 32],
2458            password: [0; 64],
2459            scan_method: crate::CONFIG.scan_method,
2460            bssid_set: config.bssid.is_some(),
2461            bssid: config.bssid.unwrap_or_default(),
2462            channel: config.channel.unwrap_or(0),
2463            listen_interval: crate::CONFIG.listen_interval,
2464            sort_method: wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
2465            threshold: wifi_scan_threshold_t {
2466                rssi: -99,
2467                authmode: config.auth_method.to_raw(),
2468            },
2469            pmf_cfg: wifi_pmf_config_t {
2470                capable: true,
2471                required: false,
2472            },
2473            sae_pwe_h2e: 3,
2474            _bitfield_align_1: [0; 0],
2475            _bitfield_1: __BindgenBitfieldUnit::new([0; 4]),
2476            failure_retry_cnt: crate::CONFIG.failure_retry_cnt,
2477            _bitfield_align_2: [0; 0],
2478            _bitfield_2: __BindgenBitfieldUnit::new([0; 4]),
2479            sae_pk_mode: 0, // ??
2480            sae_h2e_identifier: [0; 32],
2481        },
2482    };
2483
2484    unsafe {
2485        cfg.sta.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
2486        esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_STA, &mut cfg))?;
2487
2488        if let Some(identity) = &config.identity {
2489            esp_wifi_result!(esp_eap_client_set_identity(
2490                identity.as_str().as_ptr(),
2491                identity.len() as i32
2492            ))?;
2493        } else {
2494            esp_eap_client_clear_identity();
2495        }
2496
2497        if let Some(username) = &config.username {
2498            esp_wifi_result!(esp_eap_client_set_username(
2499                username.as_str().as_ptr(),
2500                username.len() as i32
2501            ))?;
2502        } else {
2503            esp_eap_client_clear_username();
2504        }
2505
2506        if let Some(password) = &config.password {
2507            esp_wifi_result!(esp_eap_client_set_password(
2508                password.as_str().as_ptr(),
2509                password.len() as i32
2510            ))?;
2511        } else {
2512            esp_eap_client_clear_password();
2513        }
2514
2515        if let Some(new_password) = &config.new_password {
2516            esp_wifi_result!(esp_eap_client_set_new_password(
2517                new_password.as_str().as_ptr(),
2518                new_password.len() as i32
2519            ))?;
2520        } else {
2521            esp_eap_client_clear_new_password();
2522        }
2523
2524        if let Some(pac_file) = &config.pac_file {
2525            esp_wifi_result!(esp_eap_client_set_pac_file(
2526                pac_file.as_ptr(),
2527                pac_file.len() as i32
2528            ))?;
2529        }
2530
2531        if let Some(phase2_method) = &config.ttls_phase2_method {
2532            esp_wifi_result!(esp_eap_client_set_ttls_phase2_method(
2533                phase2_method.to_raw()
2534            ))?;
2535        }
2536
2537        if let Some(ca_cert) = config.ca_cert {
2538            esp_wifi_result!(esp_eap_client_set_ca_cert(
2539                ca_cert.as_ptr(),
2540                ca_cert.len() as i32
2541            ))?;
2542        } else {
2543            esp_eap_client_clear_ca_cert();
2544        }
2545
2546        if let Some((cert, key, password)) = config.certificate_and_key {
2547            let (pwd, pwd_len) = if let Some(pwd) = password {
2548                (pwd.as_ptr(), pwd.len() as i32)
2549            } else {
2550                (core::ptr::null(), 0)
2551            };
2552
2553            esp_wifi_result!(esp_eap_client_set_certificate_and_key(
2554                cert.as_ptr(),
2555                cert.len() as i32,
2556                key.as_ptr(),
2557                key.len() as i32,
2558                pwd,
2559                pwd_len,
2560            ))?;
2561        } else {
2562            esp_eap_client_clear_certificate_and_key();
2563        }
2564
2565        if let Some(cfg) = &config.eap_fast_config {
2566            let params = esp_eap_fast_config {
2567                fast_provisioning: cfg.fast_provisioning as i32,
2568                fast_max_pac_list_len: cfg.fast_max_pac_list_len as i32,
2569                fast_pac_format_binary: cfg.fast_pac_format_binary,
2570            };
2571            esp_wifi_result!(esp_eap_client_set_fast_params(params))?;
2572        }
2573
2574        esp_wifi_result!(esp_eap_client_set_disable_time_check(!&config.time_check))?;
2575
2576        // esp_eap_client_set_suiteb_192bit_certification unsupported because we build
2577        // without MBEDTLS
2578
2579        // esp_eap_client_use_default_cert_bundle unsupported because we build without
2580        // MBEDTLS
2581
2582        esp_wifi_result!(esp_wifi_sta_enterprise_enable())?;
2583
2584        Ok(())
2585    }
2586}
2587
2588fn dump_packet_info(_buffer: &mut [u8]) {
2589    #[cfg(dump_packets)]
2590    {
2591        info!("@WIFIFRAME {:?}", _buffer);
2592    }
2593}
2594
2595#[doc(hidden)]
2596#[macro_export]
2597macro_rules! esp_wifi_result {
2598    ($value:expr) => {{
2599        use num_traits::FromPrimitive;
2600        let result = $value;
2601        if result != esp_wifi_sys::include::ESP_OK as i32 {
2602            warn!("{} returned an error: {}", stringify!($value), result);
2603            Err(WifiError::InternalError(unwrap!(FromPrimitive::from_i32(
2604                result
2605            ))))
2606        } else {
2607            Ok::<(), WifiError>(())
2608        }
2609    }};
2610}
2611
2612pub(crate) mod embassy {
2613    use embassy_net_driver::{Capabilities, Driver, HardwareAddress, RxToken, TxToken};
2614    use esp_hal::asynch::AtomicWaker;
2615
2616    use super::*;
2617
2618    // We can get away with a single tx waker because the transmit queue is shared
2619    // between interfaces.
2620    pub(crate) static TRANSMIT_WAKER: AtomicWaker = AtomicWaker::new();
2621
2622    pub(crate) static AP_RECEIVE_WAKER: AtomicWaker = AtomicWaker::new();
2623    pub(crate) static AP_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new();
2624
2625    pub(crate) static STA_RECEIVE_WAKER: AtomicWaker = AtomicWaker::new();
2626    pub(crate) static STA_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new();
2627
2628    impl RxToken for WifiRxToken {
2629        fn consume<R, F>(self, f: F) -> R
2630        where
2631            F: FnOnce(&mut [u8]) -> R,
2632        {
2633            self.consume_token(f)
2634        }
2635    }
2636
2637    impl TxToken for WifiTxToken {
2638        fn consume<R, F>(self, len: usize, f: F) -> R
2639        where
2640            F: FnOnce(&mut [u8]) -> R,
2641        {
2642            self.consume_token(len, f)
2643        }
2644    }
2645
2646    impl Driver for WifiDevice<'_> {
2647        type RxToken<'a>
2648            = WifiRxToken
2649        where
2650            Self: 'a;
2651        type TxToken<'a>
2652            = WifiTxToken
2653        where
2654            Self: 'a;
2655
2656        fn receive(
2657            &mut self,
2658            cx: &mut core::task::Context<'_>,
2659        ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
2660            self.mode.register_receive_waker(cx);
2661            self.mode.register_transmit_waker(cx);
2662            self.mode.rx_token()
2663        }
2664
2665        fn transmit(&mut self, cx: &mut core::task::Context<'_>) -> Option<Self::TxToken<'_>> {
2666            self.mode.register_transmit_waker(cx);
2667            self.mode.tx_token()
2668        }
2669
2670        fn link_state(
2671            &mut self,
2672            cx: &mut core::task::Context<'_>,
2673        ) -> embassy_net_driver::LinkState {
2674            self.mode.register_link_state_waker(cx);
2675            self.mode.link_state()
2676        }
2677
2678        fn capabilities(&self) -> Capabilities {
2679            let mut caps = Capabilities::default();
2680            caps.max_transmission_unit = MTU;
2681            caps.max_burst_size = if crate::CONFIG.max_burst_size == 0 {
2682                None
2683            } else {
2684                Some(crate::CONFIG.max_burst_size)
2685            };
2686            caps
2687        }
2688
2689        fn hardware_address(&self) -> HardwareAddress {
2690            HardwareAddress::Ethernet(self.mac_address())
2691        }
2692    }
2693}
2694
2695pub(crate) fn apply_power_saving(ps: PowerSaveMode) -> Result<(), WifiError> {
2696    esp_wifi_result!(unsafe { esp_wifi_sys::include::esp_wifi_set_ps(ps.into()) })?;
2697    Ok(())
2698}
2699
2700struct FreeApListOnDrop;
2701impl FreeApListOnDrop {
2702    pub fn defuse(self) {
2703        core::mem::forget(self);
2704    }
2705}
2706
2707impl Drop for FreeApListOnDrop {
2708    fn drop(&mut self) {
2709        unsafe {
2710            include::esp_wifi_clear_ap_list();
2711        }
2712    }
2713}
2714
2715#[non_exhaustive]
2716pub struct Interfaces<'d> {
2717    pub sta: WifiDevice<'d>,
2718    pub ap: WifiDevice<'d>,
2719}
2720
2721/// Create a WiFi controller and it's associated interfaces.
2722///
2723/// Dropping the controller will deinitialize / stop WiFi.
2724pub fn new<'d>(
2725    inited: &'d EspWifiController<'d>,
2726    _device: impl crate::hal::peripheral::Peripheral<P = crate::hal::peripherals::WIFI> + 'd,
2727) -> Result<(WifiController<'d>, Interfaces<'d>), WifiError> {
2728    if !inited.wifi() {
2729        crate::wifi::wifi_init()?;
2730    }
2731    Ok((
2732        WifiController {
2733            _phantom: Default::default(),
2734            #[cfg(feature = "sniffer")]
2735            sniffer_taken: AtomicBool::new(false),
2736        },
2737        Interfaces {
2738            sta: WifiDevice {
2739                _phantom: Default::default(),
2740                mode: WifiDeviceMode::Sta,
2741            },
2742            ap: WifiDevice {
2743                _phantom: Default::default(),
2744                mode: WifiDeviceMode::Ap,
2745            },
2746        },
2747    ))
2748}
2749
2750#[non_exhaustive]
2751pub struct WifiController<'d> {
2752    _phantom: PhantomData<&'d ()>,
2753    #[cfg(feature = "sniffer")]
2754    sniffer_taken: AtomicBool,
2755}
2756
2757impl Drop for WifiController<'_> {
2758    fn drop(&mut self) {
2759        if unwrap!(
2760            crate::flags::WIFI.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
2761                Some(x.saturating_sub(1))
2762            })
2763        ) == 0
2764        {
2765            if let Err(e) = crate::wifi::wifi_deinit() {
2766                warn!("Failed to cleanly deinit wifi: {:?}", e);
2767            }
2768        }
2769    }
2770}
2771
2772impl WifiController<'_> {
2773    /// Attempts to take the sniffer, returns `Some(Sniffer)` if successful,
2774    /// otherwise `None`.
2775    #[cfg(feature = "sniffer")]
2776    pub fn take_sniffer(&self) -> Option<Sniffer> {
2777        if !self.sniffer_taken.fetch_or(true, Ordering::Acquire) {
2778            Some(Sniffer::new())
2779        } else {
2780            None
2781        }
2782    }
2783
2784    /// Set CSI configuration and register the receiving callback.
2785    #[cfg(feature = "csi")]
2786    pub fn set_csi(
2787        &mut self,
2788        mut csi: CsiConfig,
2789        cb: impl FnMut(crate::wifi::wifi_csi_info_t) + Send,
2790    ) -> Result<(), WifiError> {
2791        csi.apply_config()?;
2792        csi.set_receive_cb(cb)?;
2793        csi.set_csi(true)?;
2794
2795        Ok(())
2796    }
2797
2798    /// Set the wifi protocol.
2799    ///
2800    /// This will set the wifi protocol to the desired protocol, the default for
2801    /// this is: `WIFI_PROTOCOL_11B|WIFI_PROTOCOL_11G|WIFI_PROTOCOL_11N`
2802    ///
2803    /// # Arguments:
2804    ///
2805    /// * `protocols` - The desired protocols
2806    ///
2807    /// # Example:
2808    ///
2809    /// ```
2810    /// wifi_controller.set_protocol(Protocol::P802D11BGNLR.into());
2811    /// ```
2812    pub fn set_protocol(&mut self, protocols: EnumSet<Protocol>) -> Result<(), WifiError> {
2813        let protocol = protocols
2814            .into_iter()
2815            .map(|v| match v {
2816                Protocol::P802D11B => WIFI_PROTOCOL_11B,
2817                Protocol::P802D11BG => WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G,
2818                Protocol::P802D11BGN => WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N,
2819                Protocol::P802D11BGNLR => {
2820                    WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR
2821                }
2822                Protocol::P802D11LR => WIFI_PROTOCOL_LR,
2823                Protocol::P802D11BGNAX => {
2824                    WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_11AX
2825                }
2826            })
2827            .fold(0, |combined, protocol| combined | protocol) as u8;
2828
2829        let mode = self.mode()?;
2830        if mode.is_sta() {
2831            esp_wifi_result!(unsafe {
2832                esp_wifi_set_protocol(wifi_interface_t_WIFI_IF_STA, protocol)
2833            })?;
2834        }
2835        if mode.is_ap() {
2836            esp_wifi_result!(unsafe {
2837                esp_wifi_set_protocol(wifi_interface_t_WIFI_IF_AP, protocol)
2838            })?;
2839        }
2840
2841        Ok(())
2842    }
2843
2844    /// Configures modem power saving
2845    pub fn set_power_saving(&mut self, ps: PowerSaveMode) -> Result<(), WifiError> {
2846        apply_power_saving(ps)
2847    }
2848
2849    /// A blocking wifi network scan with caller-provided scanning options.
2850    pub fn scan_with_config_sync<const N: usize>(
2851        &mut self,
2852        config: ScanConfig<'_>,
2853    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), WifiError> {
2854        esp_wifi_result!(crate::wifi::wifi_start_scan(true, config))?;
2855
2856        let count = self.scan_result_count()?;
2857        let result = self.scan_results()?;
2858
2859        Ok((result, count))
2860    }
2861
2862    fn scan_result_count(&mut self) -> Result<usize, WifiError> {
2863        let mut bss_total: u16 = 0;
2864
2865        // Prevents memory leak on error
2866        let guard = FreeApListOnDrop;
2867
2868        unsafe { esp_wifi_result!(include::esp_wifi_scan_get_ap_num(&mut bss_total))? };
2869
2870        guard.defuse();
2871
2872        Ok(bss_total as usize)
2873    }
2874
2875    fn scan_results<const N: usize>(
2876        &mut self,
2877    ) -> Result<heapless::Vec<AccessPointInfo, N>, WifiError> {
2878        let mut scanned = heapless::Vec::<AccessPointInfo, N>::new();
2879        let mut bss_total: u16 = N as u16;
2880
2881        let mut records: [MaybeUninit<include::wifi_ap_record_t>; N] = [MaybeUninit::uninit(); N];
2882
2883        // Prevents memory leak on error
2884        let guard = FreeApListOnDrop;
2885
2886        unsafe {
2887            esp_wifi_result!(include::esp_wifi_scan_get_ap_records(
2888                &mut bss_total,
2889                records[0].as_mut_ptr(),
2890            ))?
2891        };
2892
2893        guard.defuse();
2894
2895        for i in 0..bss_total {
2896            let record = unsafe { MaybeUninit::assume_init_ref(&records[i as usize]) };
2897            let ap_info = convert_ap_info(record);
2898
2899            scanned.push(ap_info).ok();
2900        }
2901
2902        Ok(scanned)
2903    }
2904
2905    /// A blocking wifi network scan with default scanning options.
2906    pub fn scan_n<const N: usize>(
2907        &mut self,
2908    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), WifiError> {
2909        self.scan_with_config_sync(Default::default())
2910    }
2911
2912    /// Starts the WiFi controller.
2913    pub fn start(&mut self) -> Result<(), WifiError> {
2914        crate::wifi::wifi_start()
2915    }
2916
2917    /// Stops the WiFi controller.
2918    pub fn stop(&mut self) -> Result<(), WifiError> {
2919        self.stop_impl()
2920    }
2921
2922    /// Connect WiFi station to the AP.
2923    ///
2924    /// - If station is connected , call [Self::disconnect] to disconnect.
2925    /// - Scanning will not be effective until connection between device and the
2926    ///   AP is established.
2927    /// - If device is scanning and connecting at the same time, it will abort
2928    ///   scanning and return a warning message and error
2929    pub fn connect(&mut self) -> Result<(), WifiError> {
2930        self.connect_impl()
2931    }
2932
2933    /// Disconnect WiFi station from the AP.
2934    pub fn disconnect(&mut self) -> Result<(), WifiError> {
2935        self.disconnect_impl()
2936    }
2937
2938    /// Get the supported capabilities of the controller.
2939    pub fn capabilities(&self) -> Result<EnumSet<crate::wifi::Capability>, WifiError> {
2940        let caps =
2941            enumset::enum_set! { Capability::Client | Capability::AccessPoint | Capability::Mixed };
2942        Ok(caps)
2943    }
2944
2945    /// Set the configuration.
2946    ///
2947    /// This will set the mode accordingly.
2948    /// You need to use Wifi::connect() for connecting to an AP.
2949    pub fn set_configuration(&mut self, conf: &Configuration) -> Result<(), WifiError> {
2950        let mode = match conf {
2951            Configuration::None => {
2952                return Err(WifiError::InternalError(
2953                    InternalWifiError::EspErrInvalidArg,
2954                ));
2955            }
2956            Configuration::Client(_) => wifi_mode_t_WIFI_MODE_STA,
2957            Configuration::AccessPoint(_) => wifi_mode_t_WIFI_MODE_AP,
2958            Configuration::Mixed(_, _) => wifi_mode_t_WIFI_MODE_APSTA,
2959            Configuration::EapClient(_) => wifi_mode_t_WIFI_MODE_STA,
2960        };
2961
2962        esp_wifi_result!(unsafe { esp_wifi_set_mode(mode) })?;
2963
2964        match conf {
2965            Configuration::None => {
2966                return Err(WifiError::InternalError(
2967                    InternalWifiError::EspErrInvalidArg,
2968                ));
2969            }
2970            Configuration::Client(config) => {
2971                apply_sta_config(config)?;
2972            }
2973            Configuration::AccessPoint(config) => {
2974                apply_ap_config(config)?;
2975            }
2976            Configuration::Mixed(sta_config, ap_config) => {
2977                apply_ap_config(ap_config)?;
2978                apply_sta_config(sta_config)?;
2979            }
2980            Configuration::EapClient(config) => {
2981                apply_sta_eap_config(config)?;
2982            }
2983        };
2984
2985        Ok(())
2986    }
2987
2988    /// Set the WiFi mode.
2989    ///
2990    /// This will override the mode inferred by [Self::set_configuration].
2991    pub fn set_mode(&mut self, mode: WifiMode) -> Result<(), WifiError> {
2992        esp_wifi_result!(unsafe { esp_wifi_set_mode(mode.into()) })?;
2993        Ok(())
2994    }
2995
2996    fn stop_impl(&mut self) -> Result<(), WifiError> {
2997        esp_wifi_result!(unsafe { esp_wifi_stop() })
2998    }
2999
3000    fn connect_impl(&mut self) -> Result<(), WifiError> {
3001        esp_wifi_result!(unsafe { esp_wifi_connect() })
3002    }
3003
3004    fn disconnect_impl(&mut self) -> Result<(), WifiError> {
3005        esp_wifi_result!(unsafe { esp_wifi_disconnect() })
3006    }
3007
3008    /// Checks if the WiFi controller has started.
3009    ///
3010    /// This function should be called after the `start` method to verify if the
3011    /// WiFi has started successfully.
3012    pub fn is_started(&self) -> Result<bool, WifiError> {
3013        if matches!(
3014            crate::wifi::sta_state(),
3015            WifiState::StaStarted | WifiState::StaConnected | WifiState::StaDisconnected
3016        ) {
3017            return Ok(true);
3018        }
3019        if matches!(crate::wifi::ap_state(), WifiState::ApStarted) {
3020            return Ok(true);
3021        }
3022        Ok(false)
3023    }
3024
3025    /// Checks if the WiFi controller is connected to an AP.
3026    ///
3027    /// This function should be called after the `connect` method to verify if
3028    /// the connection was successful.
3029    pub fn is_connected(&self) -> Result<bool, WifiError> {
3030        match crate::wifi::sta_state() {
3031            crate::wifi::WifiState::StaConnected => Ok(true),
3032            crate::wifi::WifiState::StaDisconnected => Err(WifiError::Disconnected),
3033            // FIXME: Should any other enum value trigger an error instead of returning false?
3034            _ => Ok(false),
3035        }
3036    }
3037
3038    fn mode(&self) -> Result<WifiMode, WifiError> {
3039        WifiMode::current()
3040    }
3041
3042    /// Async version of [`crate::wifi::WifiController`]'s `scan_n` method
3043    pub async fn scan_n_async<const N: usize>(
3044        &mut self,
3045    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), WifiError> {
3046        self.scan_with_config_async(Default::default()).await
3047    }
3048
3049    /// An async wifi network scan with caller-provided scanning options.
3050    pub async fn scan_with_config_async<const N: usize>(
3051        &mut self,
3052        config: ScanConfig<'_>,
3053    ) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), WifiError> {
3054        Self::clear_events(WifiEvent::ScanDone);
3055        esp_wifi_result!(wifi_start_scan(false, config))?;
3056
3057        // Prevents memory leak if `scan_n`'s future is dropped.
3058        let guard = FreeApListOnDrop;
3059        WifiEventFuture::new(WifiEvent::ScanDone).await;
3060
3061        guard.defuse();
3062
3063        let count = self.scan_result_count()?;
3064        let result = self.scan_results()?;
3065
3066        Ok((result, count))
3067    }
3068
3069    /// Async version of [`crate::wifi::WifiController`]'s `start` method
3070    pub async fn start_async(&mut self) -> Result<(), WifiError> {
3071        let mut events = enumset::enum_set! {};
3072
3073        let mode = self.mode()?;
3074        if mode.is_ap() {
3075            events |= WifiEvent::ApStart;
3076        }
3077        if mode.is_sta() {
3078            events |= WifiEvent::StaStart;
3079        }
3080
3081        Self::clear_events(events);
3082
3083        wifi_start()?;
3084
3085        self.wait_for_all_events(events, false).await;
3086
3087        Ok(())
3088    }
3089
3090    /// Async version of [`crate::wifi::WifiController`]'s `stop` method
3091    pub async fn stop_async(&mut self) -> Result<(), WifiError> {
3092        let mut events = enumset::enum_set! {};
3093
3094        let mode = self.mode()?;
3095        if mode.is_ap() {
3096            events |= WifiEvent::ApStop;
3097        }
3098        if mode.is_sta() {
3099            events |= WifiEvent::StaStop;
3100        }
3101
3102        Self::clear_events(events);
3103
3104        crate::wifi::WifiController::stop_impl(self)?;
3105
3106        self.wait_for_all_events(events, false).await;
3107
3108        reset_ap_state();
3109        reset_sta_state();
3110
3111        Ok(())
3112    }
3113
3114    /// Async version of [`crate::wifi::WifiController`]'s `connect` method
3115    pub async fn connect_async(&mut self) -> Result<(), WifiError> {
3116        Self::clear_events(WifiEvent::StaConnected | WifiEvent::StaDisconnected);
3117
3118        let err = crate::wifi::WifiController::connect_impl(self).err();
3119
3120        if MultiWifiEventFuture::new(WifiEvent::StaConnected | WifiEvent::StaDisconnected)
3121            .await
3122            .contains(WifiEvent::StaDisconnected)
3123        {
3124            Err(err.unwrap_or(WifiError::Disconnected))
3125        } else {
3126            Ok(())
3127        }
3128    }
3129
3130    /// Async version of [`crate::wifi::WifiController`]'s `Disconnect`
3131    /// method
3132    pub async fn disconnect_async(&mut self) -> Result<(), WifiError> {
3133        // If not connected, this will do nothing.
3134        // It will also wait forever for a `StaDisconnected` event that will never come.
3135        // Return early instead of hanging.
3136        if !matches!(self.is_connected(), Ok(true)) {
3137            return Ok(());
3138        }
3139
3140        Self::clear_events(WifiEvent::StaDisconnected);
3141        crate::wifi::WifiController::disconnect_impl(self)?;
3142        WifiEventFuture::new(WifiEvent::StaDisconnected).await;
3143
3144        Ok(())
3145    }
3146
3147    fn clear_events(events: impl Into<EnumSet<WifiEvent>>) {
3148        WIFI_EVENTS.with(|evts| evts.get_mut().remove_all(events.into()));
3149    }
3150
3151    /// Wait for one [`WifiEvent`].
3152    pub async fn wait_for_event(&mut self, event: WifiEvent) {
3153        Self::clear_events(event);
3154        WifiEventFuture::new(event).await
3155    }
3156
3157    /// Wait for one of multiple [`WifiEvent`]s. Returns the events that
3158    /// occurred while waiting.
3159    pub async fn wait_for_events(
3160        &mut self,
3161        events: EnumSet<WifiEvent>,
3162        clear_pending: bool,
3163    ) -> EnumSet<WifiEvent> {
3164        if clear_pending {
3165            Self::clear_events(events);
3166        }
3167        MultiWifiEventFuture::new(events).await
3168    }
3169
3170    /// Wait for multiple [`WifiEvent`]s.
3171    pub async fn wait_for_all_events(
3172        &mut self,
3173        mut events: EnumSet<WifiEvent>,
3174        clear_pending: bool,
3175    ) {
3176        if clear_pending {
3177            Self::clear_events(events);
3178        }
3179
3180        while !events.is_empty() {
3181            let fired = MultiWifiEventFuture::new(events).await;
3182            events -= fired;
3183        }
3184    }
3185}
3186
3187impl WifiEvent {
3188    pub(crate) fn waker(&self) -> &'static AtomicWaker {
3189        // for now use only one waker for all events
3190        // if that ever becomes a problem we might want to pick some events to use their
3191        // own
3192        static WAKER: AtomicWaker = AtomicWaker::new();
3193        &WAKER
3194    }
3195}
3196
3197#[must_use = "futures do nothing unless you `.await` or poll them"]
3198pub(crate) struct WifiEventFuture {
3199    event: WifiEvent,
3200}
3201
3202impl WifiEventFuture {
3203    /// Creates a new `Future` for the specified WiFi event.
3204    pub fn new(event: WifiEvent) -> Self {
3205        Self { event }
3206    }
3207}
3208
3209impl core::future::Future for WifiEventFuture {
3210    type Output = ();
3211
3212    fn poll(
3213        self: core::pin::Pin<&mut Self>,
3214        cx: &mut core::task::Context<'_>,
3215    ) -> Poll<Self::Output> {
3216        self.event.waker().register(cx.waker());
3217        if WIFI_EVENTS.with(|events| events.get_mut().remove(self.event)) {
3218            Poll::Ready(())
3219        } else {
3220            Poll::Pending
3221        }
3222    }
3223}
3224
3225#[must_use = "futures do nothing unless you `.await` or poll them"]
3226pub(crate) struct MultiWifiEventFuture {
3227    event: EnumSet<WifiEvent>,
3228}
3229
3230impl MultiWifiEventFuture {
3231    /// Creates a new `Future` for the specified set of WiFi events.
3232    pub fn new(event: EnumSet<WifiEvent>) -> Self {
3233        Self { event }
3234    }
3235}
3236
3237impl core::future::Future for MultiWifiEventFuture {
3238    type Output = EnumSet<WifiEvent>;
3239
3240    fn poll(
3241        self: core::pin::Pin<&mut Self>,
3242        cx: &mut core::task::Context<'_>,
3243    ) -> Poll<Self::Output> {
3244        let output = WIFI_EVENTS.with(|events| {
3245            let events = events.get_mut();
3246            let active = events.intersection(self.event);
3247            events.remove_all(active);
3248            active
3249        });
3250        if output.is_empty() {
3251            for event in self.event.iter() {
3252                event.waker().register(cx.waker());
3253            }
3254
3255            Poll::Pending
3256        } else {
3257            Poll::Ready(output)
3258        }
3259    }
3260}