esp_radio/wifi/
mod.rs

1//! Wi-Fi
2
3#![deny(missing_docs)]
4
5pub mod event;
6mod internal;
7pub(crate) mod os_adapter;
8pub(crate) mod state;
9use alloc::{collections::vec_deque::VecDeque, string::String};
10use core::{
11    fmt::Debug,
12    marker::PhantomData,
13    mem::MaybeUninit,
14    ptr::addr_of,
15    task::Poll,
16    time::Duration,
17};
18
19use enumset::{EnumSet, EnumSetType};
20use esp_config::esp_config_int;
21use esp_hal::{asynch::AtomicWaker, system::Cpu};
22use esp_sync::NonReentrantMutex;
23#[cfg(all(any(feature = "sniffer", feature = "esp-now"), feature = "unstable"))]
24use esp_wifi_sys::include::wifi_pkt_rx_ctrl_t;
25#[cfg(feature = "wifi-eap")]
26use esp_wifi_sys::include::{
27    esp_eap_client_clear_ca_cert,
28    esp_eap_client_clear_certificate_and_key,
29    esp_eap_client_clear_identity,
30    esp_eap_client_clear_new_password,
31    esp_eap_client_clear_password,
32    esp_eap_client_clear_username,
33    esp_eap_client_set_ca_cert,
34    esp_eap_client_set_certificate_and_key,
35    esp_eap_client_set_disable_time_check,
36    esp_eap_client_set_fast_params,
37    esp_eap_client_set_identity,
38    esp_eap_client_set_new_password,
39    esp_eap_client_set_pac_file,
40    esp_eap_client_set_password,
41    esp_eap_client_set_ttls_phase2_method,
42    esp_eap_client_set_username,
43    esp_eap_fast_config,
44    esp_wifi_sta_enterprise_enable,
45};
46#[cfg(all(feature = "sniffer", feature = "unstable"))]
47#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
48use esp_wifi_sys::include::{
49    esp_wifi_80211_tx,
50    esp_wifi_set_promiscuous,
51    esp_wifi_set_promiscuous_rx_cb,
52    wifi_promiscuous_pkt_t,
53    wifi_promiscuous_pkt_type_t,
54};
55use esp_wifi_sys::{
56    c_types::c_uint,
57    include::{
58        WIFI_INIT_CONFIG_MAGIC,
59        WIFI_PROTOCOL_11AX,
60        WIFI_PROTOCOL_11B,
61        WIFI_PROTOCOL_11G,
62        WIFI_PROTOCOL_11N,
63        WIFI_PROTOCOL_LR,
64        esp_wifi_connect_internal,
65        esp_wifi_disconnect_internal,
66        wifi_init_config_t,
67        wifi_scan_channel_bitmap_t,
68    },
69};
70use num_derive::FromPrimitive;
71#[doc(hidden)]
72pub(crate) use os_adapter::*;
73use portable_atomic::{AtomicUsize, Ordering};
74use procmacros::BuilderLite;
75#[cfg(feature = "serde")]
76use serde::{Deserialize, Serialize};
77#[cfg(all(feature = "smoltcp", feature = "unstable"))]
78#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
79use smoltcp::phy::{Device, DeviceCapabilities, RxToken, TxToken};
80pub use state::*;
81
82use crate::{
83    Controller,
84    common_adapter::*,
85    esp_wifi_result,
86    hal::ram,
87    wifi::private::PacketBuffer,
88};
89
90const MTU: usize = esp_config_int!(usize, "ESP_RADIO_CONFIG_WIFI_MTU");
91
92#[cfg(all(feature = "csi", esp32c6))]
93use crate::binary::include::wifi_csi_acquire_config_t;
94#[cfg(feature = "csi")]
95#[instability::unstable]
96pub use crate::binary::include::wifi_csi_info_t;
97#[cfg(feature = "csi")]
98#[instability::unstable]
99use crate::binary::include::{
100    esp_wifi_set_csi,
101    esp_wifi_set_csi_config,
102    esp_wifi_set_csi_rx_cb,
103    wifi_csi_config_t,
104};
105use crate::binary::{
106    c_types,
107    include::{
108        self,
109        __BindgenBitfieldUnit,
110        esp_err_t,
111        esp_interface_t_ESP_IF_WIFI_AP,
112        esp_interface_t_ESP_IF_WIFI_STA,
113        esp_supplicant_deinit,
114        esp_supplicant_init,
115        esp_wifi_deinit_internal,
116        esp_wifi_get_mode,
117        esp_wifi_init_internal,
118        esp_wifi_internal_free_rx_buffer,
119        esp_wifi_internal_reg_rxcb,
120        esp_wifi_internal_tx,
121        esp_wifi_scan_start,
122        esp_wifi_set_config,
123        esp_wifi_set_country,
124        esp_wifi_set_mode,
125        esp_wifi_set_protocol,
126        esp_wifi_set_tx_done_cb,
127        esp_wifi_sta_get_rssi,
128        esp_wifi_start,
129        esp_wifi_stop,
130        g_wifi_default_wpa_crypto_funcs,
131        wifi_active_scan_time_t,
132        wifi_ap_config_t,
133        wifi_auth_mode_t,
134        wifi_cipher_type_t_WIFI_CIPHER_TYPE_CCMP,
135        wifi_config_t,
136        wifi_country_policy_t_WIFI_COUNTRY_POLICY_MANUAL,
137        wifi_country_t,
138        wifi_interface_t,
139        wifi_interface_t_WIFI_IF_AP,
140        wifi_interface_t_WIFI_IF_STA,
141        wifi_mode_t,
142        wifi_mode_t_WIFI_MODE_AP,
143        wifi_mode_t_WIFI_MODE_APSTA,
144        wifi_mode_t_WIFI_MODE_NULL,
145        wifi_mode_t_WIFI_MODE_STA,
146        wifi_pmf_config_t,
147        wifi_scan_config_t,
148        wifi_scan_threshold_t,
149        wifi_scan_time_t,
150        wifi_scan_type_t_WIFI_SCAN_TYPE_ACTIVE,
151        wifi_scan_type_t_WIFI_SCAN_TYPE_PASSIVE,
152        wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
153        wifi_sta_config_t,
154    },
155};
156
157/// Supported Wi-Fi authentication methods.
158#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd)]
159#[cfg_attr(feature = "defmt", derive(defmt::Format))]
160#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
161#[non_exhaustive]
162pub enum AuthMethod {
163    /// No authentication (open network).
164    None,
165
166    /// Wired Equivalent Privacy (WEP) authentication.
167    Wep,
168
169    /// Wi-Fi Protected Access (WPA) authentication.
170    Wpa,
171
172    /// Wi-Fi Protected Access 2 (WPA2) Personal authentication (default).
173    #[default]
174    Wpa2Personal,
175
176    /// WPA/WPA2 Personal authentication (supports both).
177    WpaWpa2Personal,
178
179    /// WPA2 Enterprise authentication.
180    Wpa2Enterprise,
181
182    /// WPA3 Personal authentication.
183    Wpa3Personal,
184
185    /// WPA2/WPA3 Personal authentication (supports both).
186    Wpa2Wpa3Personal,
187
188    /// WLAN Authentication and Privacy Infrastructure (WAPI).
189    WapiPersonal,
190}
191
192/// Supported Wi-Fi protocols.
193#[derive(Debug, Default, PartialOrd, EnumSetType)]
194#[cfg_attr(feature = "defmt", derive(defmt::Format))]
195#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
196#[non_exhaustive]
197pub enum Protocol {
198    /// 802.11b protocol.
199    P802D11B,
200
201    /// 802.11b/g protocol.
202    P802D11BG,
203
204    /// 802.11b/g/n protocol (default).
205    #[default]
206    P802D11BGN,
207
208    /// 802.11b/g/n long-range (LR) protocol.
209    P802D11BGNLR,
210
211    /// 802.11 long-range (LR) protocol.
212    P802D11LR,
213
214    /// 802.11b/g/n/ax protocol.
215    P802D11BGNAX,
216}
217
218impl Protocol {
219    fn to_mask(self) -> u32 {
220        match self {
221            Protocol::P802D11B => WIFI_PROTOCOL_11B,
222            Protocol::P802D11BG => WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G,
223            Protocol::P802D11BGN => WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N,
224            Protocol::P802D11BGNLR => {
225                WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR
226            }
227            Protocol::P802D11LR => WIFI_PROTOCOL_LR,
228            Protocol::P802D11BGNAX => {
229                WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_11AX
230            }
231        }
232    }
233}
234
235/// Secondary Wi-Fi channels.
236#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd)]
237#[cfg_attr(feature = "defmt", derive(defmt::Format))]
238#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
239pub enum SecondaryChannel {
240    // TODO: Need to extend that for 5GHz
241    /// No secondary channel (default).
242    #[default]
243    None,
244
245    /// Secondary channel is above the primary channel.
246    Above,
247
248    /// Secondary channel is below the primary channel.
249    Below,
250}
251
252/// Access point country information.
253#[derive(Clone, Debug, Default, PartialEq, Eq)]
254#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
255pub struct Country([u8; 2]);
256
257impl Country {
258    fn try_from_c(info: &wifi_country_t) -> Option<Self> {
259        // Find the null terminator or end of array
260        let cc_len = info
261            .cc
262            .iter()
263            .position(|&b| b == 0)
264            .unwrap_or(info.cc.len());
265
266        if cc_len < 2 {
267            return None;
268        }
269
270        // Validate that we have at least 2 valid ASCII characters
271        let cc_slice = &info.cc[..cc_len.min(2)];
272        if cc_slice.iter().all(|&b| b.is_ascii_uppercase()) {
273            Some(Self([cc_slice[0], cc_slice[1]]))
274        } else {
275            None
276        }
277    }
278
279    /// Returns the country code as a string slice.
280    pub fn country_code(&self) -> &str {
281        unsafe {
282            // SAFETY: we have verified in the constructor that the bytes are upper-case ASCII.
283            core::str::from_utf8_unchecked(&self.0)
284        }
285    }
286}
287
288#[cfg(feature = "defmt")]
289impl defmt::Format for Country {
290    fn format(&self, fmt: defmt::Formatter<'_>) {
291        self.country_code().format(fmt)
292    }
293}
294
295/// Information about a detected Wi-Fi access point.
296#[derive(Clone, Debug, Default, Eq, PartialEq)]
297#[cfg_attr(feature = "defmt", derive(defmt::Format))]
298#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
299#[non_exhaustive]
300pub struct AccessPointInfo {
301    /// The SSID of the access point.
302    // TODO: we can use the `alloc` feature once we have `defmt` 1.0.2
303    #[cfg_attr(feature = "defmt", defmt(Debug2Format))]
304    pub ssid: String,
305
306    /// The BSSID (MAC address) of the access point.
307    pub bssid: [u8; 6],
308
309    /// The channel the access point is operating on.
310    pub channel: u8,
311
312    /// The secondary channel configuration of the access point.
313    pub secondary_channel: SecondaryChannel,
314
315    /// The signal strength of the access point (RSSI).
316    pub signal_strength: i8,
317
318    /// The authentication method used by the access point.
319    pub auth_method: Option<AuthMethod>,
320
321    /// The country information of the access point (if available from beacon frames).
322    pub country: Option<Country>,
323}
324
325/// Configuration for a Wi-Fi access point.
326#[derive(BuilderLite, Clone, Eq, PartialEq)]
327#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
328pub struct AccessPointConfig {
329    /// The SSID of the access point.
330    #[builder_lite(reference)]
331    ssid: String,
332
333    /// Whether the SSID is hidden or visible.
334    ssid_hidden: bool,
335
336    /// The channel the access point will operate on.
337    channel: u8,
338
339    /// The secondary channel configuration.
340    secondary_channel: Option<u8>,
341
342    /// The set of protocols supported by the access point.
343    protocols: EnumSet<Protocol>,
344
345    /// The authentication method to be used by the access point.
346    auth_method: AuthMethod,
347
348    /// The password for securing the access point (if applicable).
349    #[builder_lite(reference)]
350    password: String,
351
352    /// The maximum number of connections allowed on the access point.
353    max_connections: u16,
354    /// Dtim period of the access point (Range: 1 ~ 10).
355    dtim_period: u8,
356
357    /// Time to force deauth the STA if the SoftAP doesn't receive any data.
358    #[builder_lite(unstable)]
359    beacon_timeout: u16,
360}
361
362impl AccessPointConfig {
363    fn validate(&self) -> Result<(), WifiError> {
364        if self.ssid.len() > 32 {
365            return Err(WifiError::InvalidArguments);
366        }
367
368        if self.password.len() > 64 {
369            return Err(WifiError::InvalidArguments);
370        }
371
372        if !(1..=10).contains(&self.dtim_period) {
373            return Err(WifiError::InvalidArguments);
374        }
375
376        Ok(())
377    }
378}
379
380impl Default for AccessPointConfig {
381    fn default() -> Self {
382        Self {
383            ssid: String::from("iot-device"),
384            ssid_hidden: false,
385            channel: 1,
386            secondary_channel: None,
387            protocols: (Protocol::P802D11B | Protocol::P802D11BG | Protocol::P802D11BGN),
388            auth_method: AuthMethod::None,
389            password: String::new(),
390            max_connections: 255,
391            dtim_period: 2,
392            beacon_timeout: 300,
393        }
394    }
395}
396
397impl core::fmt::Debug for AccessPointConfig {
398    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
399        f.debug_struct("AccessPointConfig")
400            .field("ssid", &self.ssid)
401            .field("ssid_hidden", &self.ssid_hidden)
402            .field("channel", &self.channel)
403            .field("secondary_channel", &self.secondary_channel)
404            .field("protocols", &self.protocols)
405            .field("auth_method", &self.auth_method)
406            .field("password", &"**REDACTED**")
407            .field("max_connections", &self.max_connections)
408            .field("dtim_period", &self.dtim_period)
409            .field("beacon_timeout", &self.beacon_timeout)
410            .finish()
411    }
412}
413
414#[cfg(feature = "defmt")]
415impl defmt::Format for AccessPointConfig {
416    fn format(&self, fmt: defmt::Formatter<'_>) {
417        defmt::write!(
418            fmt,
419            "AccessPointConfiguration {{\
420            ssid: {}, \
421            ssid_hidden: {}, \
422            channel: {}, \
423            secondary_channel: {}, \
424            protocols: {}, \
425            auth_method: {}, \
426            password: **REDACTED**, \
427            max_connections: {}, \
428            dtim_period: {}, \
429            beacon_timeout: {} \
430            }}",
431            self.ssid.as_str(),
432            self.ssid_hidden,
433            self.channel,
434            self.secondary_channel,
435            self.protocols,
436            self.auth_method,
437            self.max_connections,
438            self.dtim_period,
439            self.beacon_timeout
440        );
441    }
442}
443
444/// Wi-Fi scan method.
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
446#[cfg_attr(feature = "defmt", derive(defmt::Format))]
447#[repr(u8)]
448#[instability::unstable]
449pub enum ScanMethod {
450    /// Fast scan.
451    Fast,
452
453    /// Scan all channels.
454    AllChannels,
455}
456
457/// Client configuration for a Wi-Fi connection.
458#[derive(BuilderLite, Clone, Eq, PartialEq)]
459#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
460pub struct ClientConfig {
461    /// The SSID of the Wi-Fi network.
462    #[builder_lite(reference)]
463    ssid: String,
464
465    /// The BSSID (MAC address) of the client.
466    bssid: Option<[u8; 6]>,
467
468    /// The authentication method for the Wi-Fi connection.
469    auth_method: AuthMethod,
470
471    /// The password for the Wi-Fi connection.
472    #[builder_lite(reference)]
473    password: String,
474
475    /// The Wi-Fi channel to connect to.
476    channel: Option<u8>,
477
478    /// The set of protocols supported by the access point.
479    protocols: EnumSet<Protocol>,
480
481    /// Interval for station to listen to beacon from AP.
482    ///
483    /// The unit of listen interval is one beacon interval.
484    /// For example, if beacon interval is 100 ms and listen interval is 3,
485    /// the interval for station to listen to beacon is 300 ms
486    #[builder_lite(unstable)]
487    listen_interval: u16,
488
489    /// Time to disconnect from AP if no data is received.
490    ///
491    /// Must be between 6 and 31.
492    #[builder_lite(unstable)]
493    beacon_timeout: u16,
494
495    /// Number of connection retries station will do before moving to next AP.
496    ///
497    /// `scan_method` should be set as [`ScanMethod::AllChannels`] to use this config.
498    ///
499    /// Note: Enabling this may cause connection time to increase in case the best AP
500    /// doesn't behave properly.
501    #[builder_lite(unstable)]
502    failure_retry_cnt: u8,
503
504    /// Scan method.
505    #[builder_lite(unstable)]
506    scan_method: ScanMethod,
507}
508
509impl ClientConfig {
510    fn validate(&self) -> Result<(), WifiError> {
511        if self.ssid.len() > 32 {
512            return Err(WifiError::InvalidArguments);
513        }
514
515        if self.password.len() > 64 {
516            return Err(WifiError::InvalidArguments);
517        }
518
519        if !(6..=31).contains(&self.beacon_timeout) {
520            return Err(WifiError::InvalidArguments);
521        }
522
523        Ok(())
524    }
525}
526
527impl Default for ClientConfig {
528    fn default() -> Self {
529        ClientConfig {
530            ssid: String::new(),
531            bssid: None,
532            auth_method: AuthMethod::Wpa2Personal,
533            password: String::new(),
534            channel: None,
535            protocols: (Protocol::P802D11B | Protocol::P802D11BG | Protocol::P802D11BGN),
536            listen_interval: 3,
537            beacon_timeout: 6,
538            failure_retry_cnt: 1,
539            scan_method: ScanMethod::Fast,
540        }
541    }
542}
543
544impl core::fmt::Debug for ClientConfig {
545    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
546        f.debug_struct("ClientConfig")
547            .field("ssid", &self.ssid)
548            .field("bssid", &self.bssid)
549            .field("auth_method", &self.auth_method)
550            .field("password", &"**REDACTED**")
551            .field("channel", &self.channel)
552            .field("protocols", &self.protocols)
553            .field("listen_interval", &self.listen_interval)
554            .field("beacon_timeout", &self.beacon_timeout)
555            .field("failure_retry_cnt", &self.failure_retry_cnt)
556            .field("scan_method", &self.scan_method)
557            .finish()
558    }
559}
560
561#[cfg(feature = "defmt")]
562impl defmt::Format for ClientConfig {
563    fn format(&self, fmt: defmt::Formatter<'_>) {
564        defmt::write!(
565            fmt,
566            "ClientConfiguration {{\
567            ssid: {}, \
568            bssid: {:?}, \
569            auth_method: {:?}, \
570            password: **REDACTED**, \
571            channel: {:?}, \
572            protocols: {}, \
573            listen_interval: {}, \
574            beacon_timeout: {}, \
575            failure_retry_cnt: {}, \
576            scan_method: {} \
577            }}",
578            self.ssid.as_str(),
579            self.bssid,
580            self.auth_method,
581            self.channel,
582            self.protocols,
583            self.listen_interval,
584            self.beacon_timeout,
585            self.failure_retry_cnt,
586            self.scan_method
587        )
588    }
589}
590
591/// Configuration for EAP-FAST authentication protocol.
592#[derive(Clone, Debug, PartialEq, Eq)]
593#[cfg_attr(feature = "defmt", derive(defmt::Format))]
594#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
595#[cfg(feature = "wifi-eap")]
596#[instability::unstable]
597pub struct EapFastConfig {
598    /// Specifies the provisioning mode for EAP-FAST.
599    pub fast_provisioning: u8,
600    /// The maximum length of the PAC (Protected Access Credentials) list.
601    pub fast_max_pac_list_len: u8,
602    /// Indicates whether the PAC file is in binary format.
603    pub fast_pac_format_binary: bool,
604}
605
606/// Phase 2 authentication methods
607#[derive(Debug, Clone, PartialEq, Eq)]
608#[cfg_attr(feature = "defmt", derive(defmt::Format))]
609#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
610#[cfg(feature = "wifi-eap")]
611#[instability::unstable]
612pub enum TtlsPhase2Method {
613    /// EAP (Extensible Authentication Protocol).
614    Eap,
615
616    /// MSCHAPv2 (Microsoft Challenge Handshake Authentication Protocol 2).
617    Mschapv2,
618
619    /// MSCHAP (Microsoft Challenge Handshake Authentication Protocol).
620    Mschap,
621
622    /// PAP (Password Authentication Protocol).
623    Pap,
624
625    /// CHAP (Challenge Handshake Authentication Protocol).
626    Chap,
627}
628
629#[cfg(feature = "wifi-eap")]
630impl TtlsPhase2Method {
631    /// Maps the phase 2 method to a raw `u32` representation.
632    fn to_raw(&self) -> u32 {
633        match self {
634            TtlsPhase2Method::Eap => {
635                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_EAP
636            }
637            TtlsPhase2Method::Mschapv2 => {
638                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_MSCHAPV2
639            }
640            TtlsPhase2Method::Mschap => {
641                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_MSCHAP
642            }
643            TtlsPhase2Method::Pap => {
644                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_PAP
645            }
646            TtlsPhase2Method::Chap => {
647                esp_wifi_sys::include::esp_eap_ttls_phase2_types_ESP_EAP_TTLS_PHASE2_CHAP
648            }
649        }
650    }
651}
652
653#[cfg(feature = "wifi-eap")]
654type CertificateAndKey = (&'static [u8], &'static [u8], Option<&'static [u8]>);
655
656/// Configuration for an EAP (Extensible Authentication Protocol) client.
657#[derive(BuilderLite, Clone, PartialEq, Eq)]
658#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
659#[cfg(feature = "wifi-eap")]
660#[instability::unstable]
661pub struct EapClientConfig {
662    /// The SSID of the network the client is connecting to.
663    #[builder_lite(reference)]
664    ssid: String,
665
666    /// The BSSID (MAC Address) of the specific access point.
667    bssid: Option<[u8; 6]>,
668
669    /// The authentication method used for EAP.
670    auth_method: AuthMethod,
671
672    /// The identity used during authentication.
673    #[builder_lite(reference)]
674    identity: Option<String>,
675
676    /// The username used for inner authentication.
677    /// Some EAP methods require a username for authentication.
678    #[builder_lite(reference)]
679    username: Option<String>,
680
681    /// The password used for inner authentication.
682    #[builder_lite(reference)]
683    password: Option<String>,
684
685    /// A new password to be set during the authentication process.
686    /// Some methods support password changes during authentication.
687    #[builder_lite(reference)]
688    new_password: Option<String>,
689
690    /// Configuration for EAP-FAST.
691    #[builder_lite(reference)]
692    eap_fast_config: Option<EapFastConfig>,
693
694    /// A PAC (Protected Access Credential) file for EAP-FAST.
695    pac_file: Option<&'static [u8]>,
696
697    /// A boolean flag indicating whether time checking is enforced during
698    /// authentication.
699    time_check: bool,
700
701    /// A CA (Certificate Authority) certificate for validating the
702    /// authentication server's certificate.
703    ca_cert: Option<&'static [u8]>,
704
705    /// A tuple containing the client's certificate, private key, and an
706    /// intermediate certificate.
707    certificate_and_key: Option<CertificateAndKey>,
708    /// The Phase 2 authentication method used for EAP-TTLS.
709    #[builder_lite(reference)]
710    ttls_phase2_method: Option<TtlsPhase2Method>,
711
712    /// The specific Wi-Fi channel to use for the connection.
713    channel: Option<u8>,
714
715    /// The set of protocols supported by the access point.
716    protocols: EnumSet<Protocol>,
717
718    /// Interval for station to listen to beacon from AP.
719    ///
720    /// The unit of listen interval is one beacon interval.
721    /// For example, if beacon interval is 100 ms and listen interval is 3,
722    /// the interval for station to listen to beacon is 300 ms
723    #[builder_lite(unstable)]
724    listen_interval: u16,
725
726    /// Time to disconnect from AP if no data is received.
727    ///
728    /// Must be between 6 and 31.
729    #[builder_lite(unstable)]
730    beacon_timeout: u16,
731
732    /// Number of connection retries station will do before moving to next AP.
733    ///
734    /// `scan_method` should be set as [`ScanMethod::AllChannels`] to use this config.
735    ///
736    /// Note: Enabling this may cause connection time to increase in case the best AP
737    /// doesn't behave properly.
738    #[builder_lite(unstable)]
739    failure_retry_cnt: u8,
740
741    /// Scan method.
742    #[builder_lite(unstable)]
743    scan_method: ScanMethod,
744}
745
746#[cfg(feature = "wifi-eap")]
747impl EapClientConfig {
748    fn validate(&self) -> Result<(), WifiError> {
749        if self.ssid.len() > 32 {
750            return Err(WifiError::InvalidArguments);
751        }
752
753        if self.identity.as_ref().unwrap_or(&String::new()).len() > 128 {
754            return Err(WifiError::InvalidArguments);
755        }
756
757        if self.username.as_ref().unwrap_or(&String::new()).len() > 128 {
758            return Err(WifiError::InvalidArguments);
759        }
760
761        if self.password.as_ref().unwrap_or(&String::new()).len() > 64 {
762            return Err(WifiError::InvalidArguments);
763        }
764
765        if self.new_password.as_ref().unwrap_or(&String::new()).len() > 64 {
766            return Err(WifiError::InvalidArguments);
767        }
768
769        if !(6..=31).contains(&self.beacon_timeout) {
770            return Err(WifiError::InvalidArguments);
771        }
772
773        Ok(())
774    }
775}
776
777#[cfg(feature = "wifi-eap")]
778impl Debug for EapClientConfig {
779    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
780        f.debug_struct("EapClientConfig")
781            .field("ssid", &self.ssid)
782            .field("bssid", &self.bssid)
783            .field("auth_method", &self.auth_method)
784            .field("channel", &self.channel)
785            .field("identity", &self.identity)
786            .field("username", &self.username)
787            .field("password", &"**REDACTED**")
788            .field("new_password", &"**REDACTED**")
789            .field("eap_fast_config", &self.eap_fast_config)
790            .field("time_check", &self.time_check)
791            .field("pac_file set", &self.pac_file.is_some())
792            .field("ca_cert set", &self.ca_cert.is_some())
793            .field("certificate_and_key set", &"**REDACTED**")
794            .field("ttls_phase2_method", &self.ttls_phase2_method)
795            .field("protocols", &self.protocols)
796            .field("listen_interval", &self.listen_interval)
797            .field("beacon_timeout", &self.beacon_timeout)
798            .field("failure_retry_cnt", &self.failure_retry_cnt)
799            .field("scan_method", &self.scan_method)
800            .finish()
801    }
802}
803
804#[cfg(feature = "defmt")]
805#[cfg(feature = "wifi-eap")]
806impl defmt::Format for EapClientConfig {
807    fn format(&self, fmt: defmt::Formatter<'_>) {
808        defmt::write!(
809            fmt,
810            "EapClientConfiguration {{\
811            ssid: {}, \
812            bssid: {:?}, \
813            auth_method: {:?}, \
814            channel: {:?}, \
815            identity: {:?}, \
816            username: {:?}, \
817            password: **REDACTED**, \
818            new_password: **REDACTED**, \
819            eap_fast_config: {:?}, \
820            time_check: {}, \
821            pac_file: {}, \
822            ca_cert: {}, \
823            certificate_and_key: **REDACTED**, \
824            ttls_phase2_method: {:?}, \
825            protocols: {}, \
826            listen_interval: {}, \
827            beacon_timeout: {}, \
828            failure_retry_cnt: {}, \
829            scan_method: {},
830            }}",
831            self.ssid.as_str(),
832            self.bssid,
833            self.auth_method,
834            self.channel,
835            &self.identity.as_ref().map_or("", |v| v.as_str()),
836            &self.username.as_ref().map_or("", |v| v.as_str()),
837            self.eap_fast_config,
838            self.time_check,
839            self.pac_file,
840            self.ca_cert,
841            self.ttls_phase2_method,
842            self.protocols,
843            self.listen_interval,
844            self.beacon_timeout,
845            self.failure_retry_cnt,
846            self.scan_method
847        )
848    }
849}
850
851#[cfg(feature = "wifi-eap")]
852impl Default for EapClientConfig {
853    fn default() -> Self {
854        EapClientConfig {
855            ssid: String::new(),
856            bssid: None,
857            auth_method: AuthMethod::Wpa2Enterprise,
858            identity: None,
859            username: None,
860            password: None,
861            channel: None,
862            eap_fast_config: None,
863            time_check: false,
864            new_password: None,
865            pac_file: None,
866            ca_cert: None,
867            certificate_and_key: None,
868            ttls_phase2_method: None,
869            protocols: (Protocol::P802D11B | Protocol::P802D11BG | Protocol::P802D11BGN),
870            listen_interval: 3,
871            beacon_timeout: 6,
872            failure_retry_cnt: 1,
873            scan_method: ScanMethod::Fast,
874        }
875    }
876}
877
878/// Introduces Wi-Fi configuration options.
879#[derive(EnumSetType, Debug, PartialOrd)]
880#[cfg_attr(feature = "defmt", derive(defmt::Format))]
881#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
882#[non_exhaustive]
883pub enum Capability {
884    /// The device operates as a client, connecting to an existing network.
885    Client,
886
887    /// The device operates as an access point, allowing other devices to
888    /// connect to it.
889    AccessPoint,
890
891    /// The device can operate in both client and access point modes
892    /// simultaneously.
893    ApSta,
894}
895
896/// Configuration of Wi-Fi operation mode.
897#[allow(clippy::large_enum_variant)]
898#[derive(Clone, Debug, PartialEq, Eq, Default)]
899#[cfg_attr(feature = "defmt", derive(defmt::Format))]
900#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
901#[non_exhaustive]
902pub enum ModeConfig {
903    /// No configuration (default).
904    #[default]
905    None,
906
907    /// Client-only configuration.
908    Client(ClientConfig),
909
910    /// Access point-only configuration.
911    AccessPoint(AccessPointConfig),
912
913    /// Simultaneous client and access point configuration.
914    ApSta(ClientConfig, AccessPointConfig),
915
916    /// EAP client configuration for enterprise Wi-Fi.
917    #[cfg(feature = "wifi-eap")]
918    #[cfg_attr(feature = "serde", serde(skip))]
919    EapClient(EapClientConfig),
920}
921
922impl ModeConfig {
923    fn validate(&self) -> Result<(), WifiError> {
924        match self {
925            ModeConfig::None => Ok(()),
926            ModeConfig::Client(client_configuration) => client_configuration.validate(),
927            ModeConfig::AccessPoint(access_point_configuration) => {
928                access_point_configuration.validate()
929            }
930            ModeConfig::ApSta(client_configuration, access_point_configuration) => {
931                client_configuration.validate()?;
932                access_point_configuration.validate()
933            }
934            #[cfg(feature = "wifi-eap")]
935            ModeConfig::EapClient(eap_client_configuration) => eap_client_configuration.validate(),
936        }
937    }
938}
939
940trait AuthMethodExt {
941    fn to_raw(&self) -> wifi_auth_mode_t;
942    fn from_raw(raw: wifi_auth_mode_t) -> Self;
943}
944
945impl AuthMethodExt for AuthMethod {
946    fn to_raw(&self) -> wifi_auth_mode_t {
947        match self {
948            AuthMethod::None => include::wifi_auth_mode_t_WIFI_AUTH_OPEN,
949            AuthMethod::Wep => include::wifi_auth_mode_t_WIFI_AUTH_WEP,
950            AuthMethod::Wpa => include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK,
951            AuthMethod::Wpa2Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK,
952            AuthMethod::WpaWpa2Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK,
953            AuthMethod::Wpa2Enterprise => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE,
954            AuthMethod::Wpa3Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK,
955            AuthMethod::Wpa2Wpa3Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK,
956            AuthMethod::WapiPersonal => include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK,
957        }
958    }
959
960    fn from_raw(raw: wifi_auth_mode_t) -> Self {
961        match raw {
962            include::wifi_auth_mode_t_WIFI_AUTH_OPEN => AuthMethod::None,
963            include::wifi_auth_mode_t_WIFI_AUTH_WEP => AuthMethod::Wep,
964            include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK => AuthMethod::Wpa,
965            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK => AuthMethod::Wpa2Personal,
966            include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK => AuthMethod::WpaWpa2Personal,
967            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE => AuthMethod::Wpa2Enterprise,
968            include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK => AuthMethod::Wpa3Personal,
969            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK => AuthMethod::Wpa2Wpa3Personal,
970            include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK => AuthMethod::WapiPersonal,
971            _ => unreachable!(),
972        }
973    }
974}
975
976/// Wi-Fi Mode (Sta and/or Ap)
977#[derive(Debug, Clone, Copy, PartialEq)]
978#[cfg_attr(feature = "defmt", derive(defmt::Format))]
979#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
980#[non_exhaustive]
981pub enum WifiMode {
982    /// Station mode.
983    Sta,
984    /// Access Point mode.
985    Ap,
986    /// Both Station and Access Point modes.
987    ApSta,
988}
989
990impl WifiMode {
991    pub(crate) fn current() -> Result<Self, WifiError> {
992        let mut mode = wifi_mode_t_WIFI_MODE_NULL;
993        esp_wifi_result!(unsafe { esp_wifi_get_mode(&mut mode) })?;
994
995        Self::try_from(mode)
996    }
997
998    /// Returns true if this mode works as a client
999    pub fn is_sta(&self) -> bool {
1000        match self {
1001            Self::Sta | Self::ApSta => true,
1002            Self::Ap => false,
1003        }
1004    }
1005
1006    /// Returns true if this mode works as an access point
1007    pub fn is_ap(&self) -> bool {
1008        match self {
1009            Self::Sta => false,
1010            Self::Ap | Self::ApSta => true,
1011        }
1012    }
1013}
1014
1015impl TryFrom<&ModeConfig> for WifiMode {
1016    type Error = WifiError;
1017
1018    /// Based on the current `ModeConfig`, derives a `WifiMode` based on it.
1019    fn try_from(config: &ModeConfig) -> Result<Self, Self::Error> {
1020        let mode = match config {
1021            ModeConfig::None => return Err(WifiError::UnknownWifiMode),
1022            ModeConfig::AccessPoint(_) => Self::Ap,
1023            ModeConfig::Client(_) => Self::Sta,
1024            ModeConfig::ApSta(_, _) => Self::ApSta,
1025            #[cfg(feature = "wifi-eap")]
1026            ModeConfig::EapClient(_) => Self::Sta,
1027        };
1028
1029        Ok(mode)
1030    }
1031}
1032
1033#[doc(hidden)]
1034impl TryFrom<wifi_mode_t> for WifiMode {
1035    type Error = WifiError;
1036
1037    /// Converts a `wifi_mode_t` C-type into a `WifiMode`.
1038    fn try_from(value: wifi_mode_t) -> Result<Self, Self::Error> {
1039        #[allow(non_upper_case_globals)]
1040        match value {
1041            include::wifi_mode_t_WIFI_MODE_STA => Ok(Self::Sta),
1042            include::wifi_mode_t_WIFI_MODE_AP => Ok(Self::Ap),
1043            include::wifi_mode_t_WIFI_MODE_APSTA => Ok(Self::ApSta),
1044            _ => Err(WifiError::UnknownWifiMode),
1045        }
1046    }
1047}
1048
1049#[doc(hidden)]
1050impl From<WifiMode> for wifi_mode_t {
1051    fn from(val: WifiMode) -> Self {
1052        #[allow(non_upper_case_globals)]
1053        match val {
1054            WifiMode::Sta => wifi_mode_t_WIFI_MODE_STA,
1055            WifiMode::Ap => wifi_mode_t_WIFI_MODE_AP,
1056            WifiMode::ApSta => wifi_mode_t_WIFI_MODE_APSTA,
1057        }
1058    }
1059}
1060
1061#[cfg(feature = "csi")]
1062pub(crate) trait CsiCallback: FnMut(crate::binary::include::wifi_csi_info_t) {}
1063
1064#[cfg(feature = "csi")]
1065impl<T> CsiCallback for T where T: FnMut(crate::binary::include::wifi_csi_info_t) {}
1066
1067#[cfg(feature = "csi")]
1068unsafe extern "C" fn csi_rx_cb<C: CsiCallback>(
1069    ctx: *mut crate::wifi::c_types::c_void,
1070    data: *mut crate::binary::include::wifi_csi_info_t,
1071) {
1072    unsafe {
1073        let csi_callback = &mut *(ctx as *mut C);
1074        csi_callback(*data);
1075    }
1076}
1077
1078/// Channel state information (CSI) configuration.
1079#[derive(Clone, PartialEq, Eq)]
1080#[cfg(all(not(esp32c6), feature = "csi"))]
1081pub struct CsiConfig {
1082    /// Enable to receive legacy long training field(lltf) data.
1083    pub lltf_en: bool,
1084    /// Enable to receive HT long training field(htltf) data.
1085    pub htltf_en: bool,
1086    /// Enable to receive space time block code HT long training
1087    /// field(stbc-htltf2) data.
1088    pub stbc_htltf2_en: bool,
1089    /// Enable to generate htlft data by averaging lltf and ht_ltf data when
1090    /// receiving HT packet. Otherwise, use ht_ltf data directly.
1091    pub ltf_merge_en: bool,
1092    /// Enable to turn on channel filter to smooth adjacent sub-carrier. Disable
1093    /// it to keep independence of adjacent sub-carrier.
1094    pub channel_filter_en: bool,
1095    /// Manually scale the CSI data by left shifting or automatically scale the
1096    /// CSI data. If set true, please set the shift bits. false: automatically.
1097    /// true: manually.
1098    pub manu_scale: bool,
1099    /// Manually left shift bits of the scale of the CSI data. The range of the
1100    /// left shift bits is 0~15.
1101    pub shift: u8,
1102    /// Enable to dump 802.11 ACK frame.
1103    pub dump_ack_en: bool,
1104}
1105
1106/// Channel state information (CSI) configuration.
1107#[derive(Clone, PartialEq, Eq)]
1108#[cfg(all(esp32c6, feature = "csi"))]
1109pub struct CsiConfig {
1110    /// Enable to acquire CSI.
1111    pub enable: u32,
1112    /// Enable to acquire L-LTF when receiving a 11g PPDU.
1113    pub acquire_csi_legacy: u32,
1114    /// Enable to acquire HT-LTF when receiving an HT20 PPDU.
1115    pub acquire_csi_ht20: u32,
1116    /// Enable to acquire HT-LTF when receiving an HT40 PPDU.
1117    pub acquire_csi_ht40: u32,
1118    /// Enable to acquire HE-LTF when receiving an HE20 SU PPDU.
1119    pub acquire_csi_su: u32,
1120    /// Enable to acquire HE-LTF when receiving an HE20 MU PPDU.
1121    pub acquire_csi_mu: u32,
1122    /// Enable to acquire HE-LTF when receiving an HE20 DCM applied PPDU.
1123    pub acquire_csi_dcm: u32,
1124    /// Enable to acquire HE-LTF when receiving an HE20 Beamformed applied PPDU.
1125    pub acquire_csi_beamformed: u32,
1126    /// When receiving an STBC applied HE PPDU, 0- acquire the complete
1127    /// HE-LTF1,  1- acquire the complete HE-LTF2, 2- sample evenly among the
1128    /// HE-LTF1 and HE-LTF2.
1129    pub acquire_csi_he_stbc: u32,
1130    /// Vvalue 0-3.
1131    pub val_scale_cfg: u32,
1132    /// Enable to dump 802.11 ACK frame, default disabled.
1133    pub dump_ack_en: u32,
1134    /// Reserved.
1135    pub reserved: u32,
1136}
1137
1138#[cfg(feature = "csi")]
1139impl Default for CsiConfig {
1140    #[cfg(not(esp32c6))]
1141    fn default() -> Self {
1142        Self {
1143            lltf_en: true,
1144            htltf_en: true,
1145            stbc_htltf2_en: true,
1146            ltf_merge_en: true,
1147            channel_filter_en: true,
1148            manu_scale: false,
1149            shift: 0,
1150            dump_ack_en: false,
1151        }
1152    }
1153
1154    #[cfg(esp32c6)]
1155    fn default() -> Self {
1156        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi_he_types.h#L67-L82
1157        Self {
1158            enable: 1,
1159            acquire_csi_legacy: 1,
1160            acquire_csi_ht20: 1,
1161            acquire_csi_ht40: 1,
1162            acquire_csi_su: 1,
1163            acquire_csi_mu: 1,
1164            acquire_csi_dcm: 1,
1165            acquire_csi_beamformed: 1,
1166            acquire_csi_he_stbc: 2,
1167            val_scale_cfg: 2,
1168            dump_ack_en: 1,
1169            reserved: 19,
1170        }
1171    }
1172}
1173
1174#[doc(hidden)]
1175#[cfg(feature = "csi")]
1176impl From<CsiConfig> for wifi_csi_config_t {
1177    fn from(config: CsiConfig) -> Self {
1178        #[cfg(not(esp32c6))]
1179        {
1180            wifi_csi_config_t {
1181                lltf_en: config.lltf_en,
1182                htltf_en: config.htltf_en,
1183                stbc_htltf2_en: config.stbc_htltf2_en,
1184                ltf_merge_en: config.ltf_merge_en,
1185                channel_filter_en: config.channel_filter_en,
1186                manu_scale: config.manu_scale,
1187                shift: config.shift,
1188                dump_ack_en: config.dump_ack_en,
1189            }
1190        }
1191        #[cfg(esp32c6)]
1192        {
1193            wifi_csi_acquire_config_t {
1194                _bitfield_align_1: [0; 0],
1195                _bitfield_1: wifi_csi_acquire_config_t::new_bitfield_1(
1196                    config.enable,
1197                    config.acquire_csi_legacy,
1198                    config.acquire_csi_ht20,
1199                    config.acquire_csi_ht40,
1200                    config.acquire_csi_su,
1201                    config.acquire_csi_mu,
1202                    config.acquire_csi_dcm,
1203                    config.acquire_csi_beamformed,
1204                    config.acquire_csi_he_stbc,
1205                    config.val_scale_cfg,
1206                    config.dump_ack_en,
1207                    config.reserved,
1208                ),
1209            }
1210        }
1211    }
1212}
1213
1214#[cfg(feature = "csi")]
1215impl CsiConfig {
1216    /// Set CSI data configuration
1217    pub(crate) fn apply_config(&self) -> Result<(), WifiError> {
1218        let conf: wifi_csi_config_t = self.clone().into();
1219
1220        unsafe {
1221            esp_wifi_result!(esp_wifi_set_csi_config(&conf))?;
1222        }
1223        Ok(())
1224    }
1225
1226    /// Register the RX callback function of CSI data. Each time a CSI data is
1227    /// received, the callback function will be called.
1228    pub(crate) fn set_receive_cb<C: CsiCallback>(&mut self, cb: C) -> Result<(), WifiError> {
1229        let cb = alloc::boxed::Box::new(cb);
1230        let cb_ptr = alloc::boxed::Box::into_raw(cb) as *mut crate::wifi::c_types::c_void;
1231
1232        unsafe {
1233            esp_wifi_result!(esp_wifi_set_csi_rx_cb(Some(csi_rx_cb::<C>), cb_ptr))?;
1234        }
1235        Ok(())
1236    }
1237
1238    /// Enable or disable CSI
1239    pub(crate) fn set_csi(&self, enable: bool) -> Result<(), WifiError> {
1240        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi.h#L1241
1241        unsafe {
1242            esp_wifi_result!(esp_wifi_set_csi(enable))?;
1243        }
1244        Ok(())
1245    }
1246}
1247
1248static RX_QUEUE_SIZE: AtomicUsize = AtomicUsize::new(0);
1249static TX_QUEUE_SIZE: AtomicUsize = AtomicUsize::new(0);
1250
1251pub(crate) static DATA_QUEUE_RX_AP: NonReentrantMutex<VecDeque<PacketBuffer>> =
1252    NonReentrantMutex::new(VecDeque::new());
1253
1254pub(crate) static DATA_QUEUE_RX_STA: NonReentrantMutex<VecDeque<PacketBuffer>> =
1255    NonReentrantMutex::new(VecDeque::new());
1256
1257/// Common errors.
1258#[derive(Debug, Clone, Copy)]
1259#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1260#[non_exhaustive]
1261pub enum WifiError {
1262    /// Wi-Fi module is not initialized or not initialized for `Wi-Fi`
1263    /// operations.
1264    NotInitialized,
1265
1266    /// Internal Wi-Fi error.
1267    InternalError(InternalWifiError),
1268
1269    /// The device disconnected from the network or failed to connect to it.
1270    Disconnected,
1271
1272    /// Unknown Wi-Fi mode (not Sta/Ap/ApSta).
1273    UnknownWifiMode,
1274
1275    /// Unsupported operation or mode.
1276    Unsupported,
1277
1278    /// Passed arguments are invalid.
1279    InvalidArguments,
1280}
1281
1282/// Events generated by the Wi-Fi driver.
1283impl core::fmt::Display for WifiError {
1284    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1285        match self {
1286            WifiError::NotInitialized => write!(f, "Wi-Fi not initialized."),
1287            WifiError::InternalError(err) => write!(f, "Internal Wi-Fi error: {err:?}."),
1288            WifiError::Disconnected => write!(f, "Wi-Fi disconnected."),
1289            WifiError::UnknownWifiMode => write!(f, "Unknown Wi-Fi mode."),
1290            WifiError::Unsupported => write!(f, "Unsupported operation or mode."),
1291            WifiError::InvalidArguments => write!(f, "Invalid arguments."),
1292        }
1293    }
1294}
1295
1296impl core::error::Error for WifiError {}
1297
1298/// Events generated by the Wi-Fi driver.
1299#[derive(Debug, FromPrimitive, EnumSetType)]
1300#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1301#[non_exhaustive]
1302#[repr(i32)]
1303pub enum WifiEvent {
1304    /// Wi-Fi is ready for operation.
1305    WifiReady = 0,
1306    /// Scan operation has completed.
1307    ScanDone,
1308    /// Station mode started.
1309    StaStart,
1310    /// Station mode stopped.
1311    StaStop,
1312    /// Station connected to a network.
1313    StaConnected,
1314    /// Station disconnected from a network.
1315    StaDisconnected,
1316    /// Station authentication mode changed.
1317    StaAuthmodeChange,
1318
1319    /// Station WPS succeeds in enrollee mode.
1320    StaWpsErSuccess,
1321    /// Station WPS fails in enrollee mode.
1322    StaWpsErFailed,
1323    /// Station WPS timeout in enrollee mode.
1324    StaWpsErTimeout,
1325    /// Station WPS pin code in enrollee mode.
1326    StaWpsErPin,
1327    /// Station WPS overlap in enrollee mode.
1328    StaWpsErPbcOverlap,
1329
1330    /// Soft-AP start.
1331    ApStart,
1332    /// Soft-AP stop.
1333    ApStop,
1334    /// A station connected to Soft-AP.
1335    ApStaConnected,
1336    /// A station disconnected from Soft-AP.
1337    ApStaDisconnected,
1338    /// Received probe request packet in Soft-AP interface.
1339    ApProbeReqReceived,
1340
1341    /// Received report of FTM procedure.
1342    FtmReport,
1343
1344    /// AP's RSSI crossed configured threshold.
1345    StaBssRssiLow,
1346    /// Status indication of Action Tx operation.
1347    ActionTxStatus,
1348    /// Remain-on-Channel operation complete.
1349    RocDone,
1350
1351    /// Station beacon timeout.
1352    StaBeaconTimeout,
1353
1354    /// Connectionless module wake interval has started.
1355    ConnectionlessModuleWakeIntervalStart,
1356
1357    /// Soft-AP WPS succeeded in registrar mode.
1358    ApWpsRgSuccess,
1359    /// Soft-AP WPS failed in registrar mode.
1360    ApWpsRgFailed,
1361    /// Soft-AP WPS timed out in registrar mode.
1362    ApWpsRgTimeout,
1363    /// Soft-AP WPS pin code in registrar mode.
1364    ApWpsRgPin,
1365    /// Soft-AP WPS overlap in registrar mode.
1366    ApWpsRgPbcOverlap,
1367
1368    /// iTWT setup.
1369    ItwtSetup,
1370    /// iTWT teardown.
1371    ItwtTeardown,
1372    /// iTWT probe.
1373    ItwtProbe,
1374    /// iTWT suspended.
1375    ItwtSuspend,
1376    /// TWT wakeup event.
1377    TwtWakeup,
1378    /// bTWT setup.
1379    BtwtSetup,
1380    /// bTWT teardown.
1381    BtwtTeardown,
1382
1383    /// NAN (Neighbor Awareness Networking) discovery has started.
1384    NanStarted,
1385    /// NAN discovery has stopped.
1386    NanStopped,
1387    /// NAN service discovery match found.
1388    NanSvcMatch,
1389    /// Replied to a NAN peer with service discovery match.
1390    NanReplied,
1391    /// Received a follow-up message in NAN.
1392    NanReceive,
1393    /// Received NDP (Neighbor Discovery Protocol) request from a NAN peer.
1394    NdpIndication,
1395    /// NDP confirm indication.
1396    NdpConfirm,
1397    /// NAN datapath terminated indication.
1398    NdpTerminated,
1399    /// Wi-Fi home channel change, doesn't occur when scanning.
1400    HomeChannelChange,
1401
1402    /// Received Neighbor Report response.
1403    StaNeighborRep,
1404}
1405
1406/// Error originating from the underlying drivers
1407#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)]
1408#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1409#[non_exhaustive]
1410#[repr(i32)]
1411pub enum InternalWifiError {
1412    /// Out of memory
1413    NoMem            = 0x101,
1414
1415    /// Invalid argument
1416    InvalidArg       = 0x102,
1417
1418    /// Wi-Fi driver was not installed by [esp_wifi_init](crate::binary::include::esp_wifi_init)
1419    NotInit          = 0x3001,
1420
1421    /// Wi-Fi driver was not started by [esp_wifi_start]
1422    NotStarted       = 0x3002,
1423
1424    /// Wi-Fi driver was not stopped by [esp_wifi_stop]
1425    NotStopped       = 0x3003,
1426
1427    /// Wi-Fi interface error
1428    Interface        = 0x3004,
1429
1430    /// Wi-Fi mode error
1431    Mode             = 0x3005,
1432
1433    /// Wi-Fi internal state error
1434    State            = 0x3006,
1435
1436    /// Wi-Fi internal control block of station or soft-AP error
1437    Conn             = 0x3007,
1438
1439    /// Wi-Fi internal NVS module error
1440    Nvs              = 0x3008,
1441
1442    /// MAC address is invalid
1443    InvalidMac       = 0x3009,
1444
1445    /// SSID is invalid
1446    InvalidSsid      = 0x300A,
1447
1448    /// Password is invalid
1449    InvalidPassword  = 0x300B,
1450
1451    /// Timeout error
1452    Timeout          = 0x300C,
1453
1454    /// Wi-Fi is in sleep state (RF closed) and wakeup failed
1455    WakeFail         = 0x300D,
1456
1457    /// The caller would block
1458    WouldBlock       = 0x300E,
1459
1460    /// Station still in disconnect status
1461    NotConnected     = 0x300F,
1462
1463    /// Failed to post the event to Wi-Fi task
1464    PostFail         = 0x3012,
1465
1466    /// Invalid Wi-Fi state when init/deinit is called
1467    InvalidInitState = 0x3013,
1468
1469    /// Returned when Wi-Fi is stopping
1470    StopState        = 0x3014,
1471
1472    /// The Wi-Fi connection is not associated
1473    NotAssociated    = 0x3015,
1474
1475    /// The Wi-Fi TX is disallowed
1476    TxDisallowed     = 0x3016,
1477}
1478
1479/// Get the AP MAC address of the device.
1480pub fn ap_mac() -> [u8; 6] {
1481    let mut mac = [0u8; 6];
1482    unsafe {
1483        read_mac(mac.as_mut_ptr(), 1);
1484    }
1485    mac
1486}
1487
1488/// Get the STA MAC address of the device.
1489pub fn sta_mac() -> [u8; 6] {
1490    let mut mac = [0u8; 6];
1491    unsafe {
1492        read_mac(mac.as_mut_ptr(), 0);
1493    }
1494    mac
1495}
1496
1497#[cfg(esp32)]
1498fn set_mac_time_update_cb(wifi: crate::hal::peripherals::WIFI<'_>) {
1499    use esp_phy::MacTimeExt;
1500    use esp_wifi_sys::include::esp_wifi_internal_update_mac_time;
1501    unsafe {
1502        wifi.set_mac_time_update_cb(|duration| {
1503            esp_wifi_internal_update_mac_time(duration.as_micros() as u32);
1504        });
1505    }
1506}
1507
1508pub(crate) fn wifi_init(_wifi: crate::hal::peripherals::WIFI<'_>) -> Result<(), WifiError> {
1509    #[cfg(esp32)]
1510    set_mac_time_update_cb(_wifi);
1511    unsafe {
1512        #[cfg(coex)]
1513        esp_wifi_result!(coex_init())?;
1514
1515        esp_wifi_result!(esp_wifi_init_internal(addr_of!(internal::G_CONFIG)))?;
1516        esp_wifi_result!(esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_NULL))?;
1517
1518        esp_wifi_result!(esp_supplicant_init())?;
1519
1520        esp_wifi_result!(esp_wifi_set_tx_done_cb(Some(esp_wifi_tx_done_cb)))?;
1521
1522        esp_wifi_result!(esp_wifi_internal_reg_rxcb(
1523            esp_interface_t_ESP_IF_WIFI_STA,
1524            Some(recv_cb_sta)
1525        ))?;
1526
1527        // until we support APSTA we just register the same callback for AP and STA
1528        esp_wifi_result!(esp_wifi_internal_reg_rxcb(
1529            esp_interface_t_ESP_IF_WIFI_AP,
1530            Some(recv_cb_ap)
1531        ))?;
1532
1533        Ok(())
1534    }
1535}
1536
1537#[cfg(coex)]
1538pub(crate) fn coex_initialize() -> i32 {
1539    debug!("call coex-initialize");
1540    unsafe {
1541        let res = crate::binary::include::esp_coex_adapter_register(
1542            core::ptr::addr_of_mut!(internal::G_COEX_ADAPTER_FUNCS).cast(),
1543        );
1544        if res != 0 {
1545            error!("Error: esp_coex_adapter_register {}", res);
1546            return res;
1547        }
1548        let res = crate::binary::include::coex_pre_init();
1549        if res != 0 {
1550            error!("Error: coex_pre_init {}", res);
1551            return res;
1552        }
1553        0
1554    }
1555}
1556
1557pub(crate) unsafe extern "C" fn coex_init() -> i32 {
1558    #[cfg(coex)]
1559    {
1560        debug!("coex-init");
1561        #[allow(clippy::needless_return)]
1562        return unsafe { crate::binary::include::coex_init() };
1563    }
1564
1565    #[cfg(not(coex))]
1566    0
1567}
1568
1569fn wifi_deinit() -> Result<(), crate::InitializationError> {
1570    esp_wifi_result!(unsafe { esp_wifi_stop() })?;
1571    esp_wifi_result!(unsafe { esp_wifi_deinit_internal() })?;
1572    esp_wifi_result!(unsafe { esp_supplicant_deinit() })?;
1573    Ok(())
1574}
1575
1576unsafe extern "C" fn recv_cb_sta(
1577    buffer: *mut c_types::c_void,
1578    len: u16,
1579    eb: *mut c_types::c_void,
1580) -> esp_err_t {
1581    let packet = PacketBuffer { buffer, len, eb };
1582    // We must handle the result outside of the lock because
1583    // PacketBuffer::drop must not be called in a critical section.
1584    // Dropping an PacketBuffer will call `esp_wifi_internal_free_rx_buffer`
1585    // which will try to lock an internal mutex. If the mutex is already taken,
1586    // the function will try to trigger a context switch, which will fail if we
1587    // are in an interrupt-free context.
1588    match DATA_QUEUE_RX_STA.with(|queue| {
1589        if queue.len() < RX_QUEUE_SIZE.load(Ordering::Relaxed) {
1590            queue.push_back(packet);
1591            Ok(())
1592        } else {
1593            Err(packet)
1594        }
1595    }) {
1596        Ok(()) => {
1597            embassy::STA_RECEIVE_WAKER.wake();
1598            include::ESP_OK as esp_err_t
1599        }
1600        _ => {
1601            debug!("RX QUEUE FULL");
1602            include::ESP_ERR_NO_MEM as esp_err_t
1603        }
1604    }
1605}
1606
1607unsafe extern "C" fn recv_cb_ap(
1608    buffer: *mut c_types::c_void,
1609    len: u16,
1610    eb: *mut c_types::c_void,
1611) -> esp_err_t {
1612    let packet = PacketBuffer { buffer, len, eb };
1613    // We must handle the result outside of the critical section because
1614    // PacketBuffer::drop must not be called in a critical section.
1615    // Dropping an PacketBuffer will call `esp_wifi_internal_free_rx_buffer`
1616    // which will try to lock an internal mutex. If the mutex is already taken,
1617    // the function will try to trigger a context switch, which will fail if we
1618    // are in an interrupt-free context.
1619    match DATA_QUEUE_RX_AP.with(|queue| {
1620        if queue.len() < RX_QUEUE_SIZE.load(Ordering::Relaxed) {
1621            queue.push_back(packet);
1622            Ok(())
1623        } else {
1624            Err(packet)
1625        }
1626    }) {
1627        Ok(()) => {
1628            embassy::AP_RECEIVE_WAKER.wake();
1629            include::ESP_OK as esp_err_t
1630        }
1631        _ => {
1632            debug!("RX QUEUE FULL");
1633            include::ESP_ERR_NO_MEM as esp_err_t
1634        }
1635    }
1636}
1637
1638pub(crate) static WIFI_TX_INFLIGHT: AtomicUsize = AtomicUsize::new(0);
1639
1640fn decrement_inflight_counter() {
1641    unwrap!(
1642        WIFI_TX_INFLIGHT.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
1643            Some(x.saturating_sub(1))
1644        })
1645    );
1646}
1647
1648#[ram]
1649unsafe extern "C" fn esp_wifi_tx_done_cb(
1650    _ifidx: u8,
1651    _data: *mut u8,
1652    _data_len: *mut u16,
1653    _tx_status: bool,
1654) {
1655    trace!("esp_wifi_tx_done_cb");
1656
1657    decrement_inflight_counter();
1658
1659    embassy::TRANSMIT_WAKER.wake();
1660}
1661
1662/// Configuration for active or passive scan.
1663///
1664/// # Comparison of active and passive scan
1665///
1666/// |                                      | **Active** | **Passive** |
1667/// |--------------------------------------|------------|-------------|
1668/// | **Power consumption**                |    High    |     Low     |
1669/// | **Time required (typical behavior)** |     Low    |     High    |
1670#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1671#[non_exhaustive]
1672pub enum ScanTypeConfig {
1673    /// Active scan with min and max scan time per channel. This is the default
1674    /// and recommended if you are unsure.
1675    ///
1676    /// # Procedure
1677    /// 1. Send probe request on each channel.
1678    /// 2. Wait for probe response. Wait at least `min` time, but if no response is received, wait
1679    ///    up to `max` time.
1680    /// 3. Switch channel.
1681    /// 4. Repeat from 1.
1682    Active {
1683        /// Minimum scan time per channel. Defaults to 10ms.
1684        min: Duration,
1685        /// Maximum scan time per channel. Defaults to 20ms.
1686        max: Duration,
1687    },
1688    /// Passive scan
1689    ///
1690    /// # Procedure
1691    /// 1. Wait for beacon for given duration.
1692    /// 2. Switch channel.
1693    /// 3. Repeat from 1.
1694    ///
1695    /// # Note
1696    /// It is recommended to avoid duration longer thean 1500ms, as it may cause
1697    /// a station to disconnect from the AP.
1698    Passive(Duration),
1699}
1700
1701impl Default for ScanTypeConfig {
1702    fn default() -> Self {
1703        Self::Active {
1704            min: Duration::from_millis(10),
1705            max: Duration::from_millis(20),
1706        }
1707    }
1708}
1709
1710impl ScanTypeConfig {
1711    fn validate(&self) {
1712        if matches!(self, Self::Passive(dur) if *dur > Duration::from_millis(1500)) {
1713            warn!(
1714                "Passive scan duration longer than 1500ms may cause a station to disconnect from the AP"
1715            );
1716        }
1717    }
1718}
1719
1720/// Scan configuration
1721#[derive(Clone, Copy, Default, PartialEq, Eq, BuilderLite)]
1722pub struct ScanConfig<'a> {
1723    /// SSID to filter for.
1724    /// If [`None`] is passed, all SSIDs will be returned.
1725    /// If [`Some`] is passed, only the APs matching the given SSID will be
1726    /// returned.
1727    ssid: Option<&'a str>,
1728    /// BSSID to filter for.
1729    /// If [`None`] is passed, all BSSIDs will be returned.
1730    /// If [`Some`] is passed, only the APs matching the given BSSID will be
1731    /// returned.
1732    bssid: Option<[u8; 6]>,
1733    /// Channel to filter for.
1734    /// If [`None`] is passed, all channels will be returned.
1735    /// If [`Some`] is passed, only the APs on the given channel will be
1736    /// returned.
1737    channel: Option<u8>,
1738    /// Whether to show hidden networks.
1739    show_hidden: bool,
1740    /// Scan type, active or passive.
1741    scan_type: ScanTypeConfig,
1742    /// The maximum number of networks to return when scanning.
1743    /// If [`None`] is passed, all networks will be returned.
1744    /// If [`Some`] is passed, the specified number of networks will be returned.
1745    max: Option<usize>,
1746}
1747
1748pub(crate) fn wifi_start_scan(
1749    block: bool,
1750    ScanConfig {
1751        ssid,
1752        mut bssid,
1753        channel,
1754        show_hidden,
1755        scan_type,
1756        ..
1757    }: ScanConfig<'_>,
1758) -> i32 {
1759    scan_type.validate();
1760    let (scan_time, scan_type) = match scan_type {
1761        ScanTypeConfig::Active { min, max } => (
1762            wifi_scan_time_t {
1763                active: wifi_active_scan_time_t {
1764                    min: min.as_millis() as u32,
1765                    max: max.as_millis() as u32,
1766                },
1767                passive: 0,
1768            },
1769            wifi_scan_type_t_WIFI_SCAN_TYPE_ACTIVE,
1770        ),
1771        ScanTypeConfig::Passive(dur) => (
1772            wifi_scan_time_t {
1773                active: wifi_active_scan_time_t { min: 0, max: 0 },
1774                passive: dur.as_millis() as u32,
1775            },
1776            wifi_scan_type_t_WIFI_SCAN_TYPE_PASSIVE,
1777        ),
1778    };
1779
1780    let mut ssid_buf = ssid.map(|m| {
1781        let mut buf = alloc::vec::Vec::from_iter(m.bytes());
1782        buf.push(b'\0');
1783        buf
1784    });
1785
1786    let ssid = ssid_buf
1787        .as_mut()
1788        .map(|e| e.as_mut_ptr())
1789        .unwrap_or_else(core::ptr::null_mut);
1790    let bssid = bssid
1791        .as_mut()
1792        .map(|e| e.as_mut_ptr())
1793        .unwrap_or_else(core::ptr::null_mut);
1794
1795    let scan_config = wifi_scan_config_t {
1796        ssid,
1797        bssid,
1798        channel: channel.unwrap_or(0),
1799        show_hidden,
1800        scan_type,
1801        scan_time,
1802        home_chan_dwell_time: 0,
1803        channel_bitmap: wifi_scan_channel_bitmap_t {
1804            ghz_2_channels: 0,
1805            ghz_5_channels: 0,
1806        },
1807        coex_background_scan: false,
1808    };
1809
1810    unsafe { esp_wifi_scan_start(&scan_config, block) }
1811}
1812
1813mod private {
1814    use super::*;
1815
1816    #[derive(Debug)]
1817    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
1818    /// Take care not to drop this while in a critical section.
1819    ///
1820    /// Dropping an PacketBuffer will call
1821    /// `esp_wifi_internal_free_rx_buffer` which will try to lock an
1822    /// internal mutex. If the mutex is already taken, the function will try
1823    /// to trigger a context switch, which will fail if we are in a critical
1824    /// section.
1825    pub struct PacketBuffer {
1826        pub(crate) buffer: *mut c_types::c_void,
1827        pub(crate) len: u16,
1828        pub(crate) eb: *mut c_types::c_void,
1829    }
1830
1831    unsafe impl Send for PacketBuffer {}
1832
1833    impl Drop for PacketBuffer {
1834        fn drop(&mut self) {
1835            trace!("Dropping PacketBuffer, freeing memory");
1836            unsafe { esp_wifi_internal_free_rx_buffer(self.eb) };
1837        }
1838    }
1839
1840    impl PacketBuffer {
1841        pub fn as_slice_mut(&mut self) -> &mut [u8] {
1842            unsafe { core::slice::from_raw_parts_mut(self.buffer as *mut u8, self.len as usize) }
1843        }
1844    }
1845}
1846
1847/// Wi-Fi device operational modes.
1848#[derive(Debug, Clone, Copy)]
1849enum WifiDeviceMode {
1850    /// Station mode.
1851    Sta,
1852    /// Access Point mode.
1853    Ap,
1854}
1855
1856impl WifiDeviceMode {
1857    fn mac_address(&self) -> [u8; 6] {
1858        match self {
1859            WifiDeviceMode::Sta => sta_mac(),
1860            WifiDeviceMode::Ap => ap_mac(),
1861        }
1862    }
1863
1864    fn data_queue_rx(&self) -> &'static NonReentrantMutex<VecDeque<PacketBuffer>> {
1865        match self {
1866            WifiDeviceMode::Sta => &DATA_QUEUE_RX_STA,
1867            WifiDeviceMode::Ap => &DATA_QUEUE_RX_AP,
1868        }
1869    }
1870
1871    fn can_send(&self) -> bool {
1872        WIFI_TX_INFLIGHT.load(Ordering::SeqCst) < TX_QUEUE_SIZE.load(Ordering::Relaxed)
1873    }
1874
1875    fn increase_in_flight_counter(&self) {
1876        WIFI_TX_INFLIGHT.fetch_add(1, Ordering::SeqCst);
1877    }
1878
1879    fn tx_token(&self) -> Option<WifiTxToken> {
1880        if !self.can_send() {
1881            // TODO: perhaps we can use a counting semaphore with a short blocking timeout
1882            crate::preempt::yield_task();
1883        }
1884
1885        if self.can_send() {
1886            Some(WifiTxToken { mode: *self })
1887        } else {
1888            None
1889        }
1890    }
1891
1892    fn rx_token(&self) -> Option<(WifiRxToken, WifiTxToken)> {
1893        let is_empty = self.data_queue_rx().with(|q| q.is_empty());
1894        if is_empty || !self.can_send() {
1895            // TODO: use an OS queue with a short timeout
1896            crate::preempt::yield_task();
1897        }
1898
1899        let is_empty = is_empty && self.data_queue_rx().with(|q| q.is_empty());
1900
1901        if !is_empty {
1902            self.tx_token().map(|tx| (WifiRxToken { mode: *self }, tx))
1903        } else {
1904            None
1905        }
1906    }
1907
1908    fn interface(&self) -> wifi_interface_t {
1909        match self {
1910            WifiDeviceMode::Sta => wifi_interface_t_WIFI_IF_STA,
1911            WifiDeviceMode::Ap => wifi_interface_t_WIFI_IF_AP,
1912        }
1913    }
1914
1915    fn register_transmit_waker(&self, cx: &mut core::task::Context<'_>) {
1916        embassy::TRANSMIT_WAKER.register(cx.waker())
1917    }
1918
1919    fn register_receive_waker(&self, cx: &mut core::task::Context<'_>) {
1920        match self {
1921            WifiDeviceMode::Sta => embassy::STA_RECEIVE_WAKER.register(cx.waker()),
1922            WifiDeviceMode::Ap => embassy::AP_RECEIVE_WAKER.register(cx.waker()),
1923        }
1924    }
1925
1926    fn register_link_state_waker(&self, cx: &mut core::task::Context<'_>) {
1927        match self {
1928            WifiDeviceMode::Sta => embassy::STA_LINK_STATE_WAKER.register(cx.waker()),
1929            WifiDeviceMode::Ap => embassy::AP_LINK_STATE_WAKER.register(cx.waker()),
1930        }
1931    }
1932
1933    fn link_state(&self) -> embassy_net_driver::LinkState {
1934        match self {
1935            WifiDeviceMode::Sta => {
1936                if matches!(sta_state(), WifiStaState::Connected) {
1937                    embassy_net_driver::LinkState::Up
1938                } else {
1939                    embassy_net_driver::LinkState::Down
1940                }
1941            }
1942            WifiDeviceMode::Ap => {
1943                if matches!(ap_state(), WifiApState::Started) {
1944                    embassy_net_driver::LinkState::Up
1945                } else {
1946                    embassy_net_driver::LinkState::Down
1947                }
1948            }
1949        }
1950    }
1951}
1952
1953/// A wifi device implementing smoltcp's Device trait.
1954pub struct WifiDevice<'d> {
1955    _phantom: PhantomData<&'d ()>,
1956    mode: WifiDeviceMode,
1957}
1958
1959impl WifiDevice<'_> {
1960    /// Retrieves the MAC address of the Wi-Fi device.
1961    pub fn mac_address(&self) -> [u8; 6] {
1962        self.mode.mac_address()
1963    }
1964
1965    /// Receives data from the Wi-Fi device (only when `smoltcp` feature is
1966    /// disabled).
1967    #[cfg(not(feature = "smoltcp"))]
1968    pub fn receive(&mut self) -> Option<(WifiRxToken, WifiTxToken)> {
1969        self.mode.rx_token()
1970    }
1971
1972    /// Transmits data through the Wi-Fi device (only when `smoltcp` feature is
1973    /// disabled).
1974    #[cfg(not(feature = "smoltcp"))]
1975    pub fn transmit(&mut self) -> Option<WifiTxToken> {
1976        self.mode.tx_token()
1977    }
1978}
1979
1980fn convert_ap_info(record: &include::wifi_ap_record_t) -> AccessPointInfo {
1981    let str_len = record
1982        .ssid
1983        .iter()
1984        .position(|&c| c == 0)
1985        .unwrap_or(record.ssid.len());
1986    let ssid_ref = unsafe { core::str::from_utf8_unchecked(&record.ssid[..str_len]) };
1987
1988    let mut ssid = String::new();
1989    ssid.push_str(ssid_ref);
1990
1991    AccessPointInfo {
1992        ssid,
1993        bssid: record.bssid,
1994        channel: record.primary,
1995        secondary_channel: match record.second {
1996            include::wifi_second_chan_t_WIFI_SECOND_CHAN_NONE => SecondaryChannel::None,
1997            include::wifi_second_chan_t_WIFI_SECOND_CHAN_ABOVE => SecondaryChannel::Above,
1998            include::wifi_second_chan_t_WIFI_SECOND_CHAN_BELOW => SecondaryChannel::Below,
1999            _ => panic!(),
2000        },
2001        signal_strength: record.rssi,
2002        auth_method: Some(AuthMethod::from_raw(record.authmode)),
2003        country: Country::try_from_c(&record.country),
2004    }
2005}
2006
2007/// The radio metadata header of the received packet, which is the common header
2008/// at the beginning of all RX callback buffers in promiscuous mode.
2009#[cfg(not(esp32c6))]
2010#[derive(Debug, Clone, Copy)]
2011#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2012#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
2013pub struct RxControlInfo {
2014    /// Received Signal Strength Indicator (RSSI) of the packet, in dBm.
2015    pub rssi: i32,
2016    /// PHY rate encoding of the packet. Only valid for non-HT (802.11b/g)
2017    /// packets.
2018    pub rate: u32,
2019    /// Protocol of the received packet: 0 for non-HT (11bg), 1 for HT (11n), 3
2020    /// for VHT (11ac).
2021    pub sig_mode: u32,
2022    /// Modulation and Coding Scheme (MCS). Indicates modulation for HT (11n)
2023    /// packets.
2024    pub mcs: u32,
2025    /// Channel bandwidth of the packet: 0 for 20MHz, 1 for 40MHz.
2026    pub cwb: u32,
2027    /// Channel estimate smoothing: 1 recommends smoothing; 0 recommends
2028    /// per-carrier-independent estimate.
2029    pub smoothing: u32,
2030    /// Sounding indicator: 0 for sounding PPDU (used for channel estimation); 1
2031    /// for non-sounding PPDU.
2032    pub not_sounding: u32,
2033    /// Aggregation status: 0 for MPDU packet, 1 for AMPDU packet.
2034    pub aggregation: u32,
2035    /// Space-Time Block Coding (STBC) status: 0 for non-STBC packet, 1 for STBC
2036    /// packet.
2037    pub stbc: u32,
2038    /// Forward Error Correction (FEC) status: indicates if LDPC coding is used
2039    /// for 11n packets.
2040    pub fec_coding: u32,
2041    /// Short Guard Interval (SGI): 0 for long guard interval, 1 for short guard
2042    /// interval.
2043    pub sgi: u32,
2044    /// Number of subframes aggregated in an AMPDU packet.
2045    pub ampdu_cnt: u32,
2046    /// Primary channel on which the packet is received.
2047    pub channel: u32,
2048    /// Secondary channel on which the packet is received: 0 for none, 1 for
2049    /// above, 2 for below.
2050    pub secondary_channel: u32,
2051    /// Timestamp of when the packet is received, in microseconds. Precise only
2052    /// if modem sleep or light sleep is not enabled.
2053    pub timestamp: u32,
2054    /// Noise floor of the Radio Frequency module, in dBm.
2055    pub noise_floor: i32,
2056    /// Antenna number from which the packet is received: 0 for antenna 0, 1 for
2057    /// antenna 1.
2058    pub ant: u32,
2059    /// Length of the packet including the Frame Check Sequence (FCS).
2060    pub sig_len: u32,
2061    /// State of the packet: 0 for no error, other values indicate error codes.
2062    pub rx_state: u32,
2063}
2064
2065/// The radio metadata header of the received packet, which is the common header
2066/// at the beginning of all RX callback buffers in promiscuous mode.
2067#[cfg(esp32c6)]
2068#[derive(Debug, Clone, Copy)]
2069#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2070#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
2071pub struct RxControlInfo {
2072    /// Received Signal Strength Indicator (RSSI) of the packet, in dBm.
2073    pub rssi: i32,
2074    /// PHY rate encoding of the packet. Only valid for non-HT (802.11b/g)
2075    /// packets.
2076    pub rate: u32,
2077    /// Length of the received packet including the Frame Check Sequence (FCS).
2078    pub sig_len: u32,
2079    /// Reception state of the packet: 0 for no error, others indicate error
2080    /// codes.
2081    pub rx_state: u32,
2082    /// Length of the dump buffer.
2083    pub dump_len: u32,
2084    /// Length of HE-SIG-B field (802.11ax).
2085    pub he_sigb_len: u32,
2086    /// Indicates if this is a single MPDU.
2087    pub cur_single_mpdu: u32,
2088    /// Current baseband format.
2089    pub cur_bb_format: u32,
2090    /// Channel estimation validity.
2091    pub rx_channel_estimate_info_vld: u32,
2092    /// Length of the channel estimation.
2093    pub rx_channel_estimate_len: u32,
2094    /// Timing information in seconds.
2095    pub second: u32,
2096    /// Primary channel on which the packet is received.
2097    pub channel: u32,
2098    /// Noise floor of the Radio Frequency module, in dBm.
2099    pub noise_floor: i32,
2100    /// Indicates if this is a group-addressed frame.
2101    pub is_group: u32,
2102    /// End state of the packet reception.
2103    pub rxend_state: u32,
2104    /// Indicate whether the reception frame is from interface 3.
2105    pub rxmatch3: u32,
2106    /// Indicate whether the reception frame is from interface 2.
2107    pub rxmatch2: u32,
2108    /// Indicate whether the reception frame is from interface 1.
2109    pub rxmatch1: u32,
2110    /// Indicate whether the reception frame is from interface 0.
2111    pub rxmatch0: u32,
2112}
2113
2114#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
2115impl RxControlInfo {
2116    /// Create an instance from a raw pointer to [wifi_pkt_rx_ctrl_t].
2117    ///
2118    /// # Safety
2119    /// When calling this, you must ensure, that `rx_cntl` points to a valid
2120    /// instance of [wifi_pkt_rx_ctrl_t].
2121    pub unsafe fn from_raw(rx_cntl: *const wifi_pkt_rx_ctrl_t) -> Self {
2122        #[cfg(not(esp32c6))]
2123        let rx_control_info = unsafe {
2124            RxControlInfo {
2125                rssi: (*rx_cntl).rssi(),
2126                rate: (*rx_cntl).rate(),
2127                sig_mode: (*rx_cntl).sig_mode(),
2128                mcs: (*rx_cntl).mcs(),
2129                cwb: (*rx_cntl).cwb(),
2130                smoothing: (*rx_cntl).smoothing(),
2131                not_sounding: (*rx_cntl).not_sounding(),
2132                aggregation: (*rx_cntl).aggregation(),
2133                stbc: (*rx_cntl).stbc(),
2134                fec_coding: (*rx_cntl).fec_coding(),
2135                sgi: (*rx_cntl).sgi(),
2136                ampdu_cnt: (*rx_cntl).ampdu_cnt(),
2137                channel: (*rx_cntl).channel(),
2138                secondary_channel: (*rx_cntl).secondary_channel(),
2139                timestamp: (*rx_cntl).timestamp(),
2140                noise_floor: (*rx_cntl).noise_floor(),
2141                ant: (*rx_cntl).ant(),
2142                sig_len: (*rx_cntl).sig_len(),
2143                rx_state: (*rx_cntl).rx_state(),
2144            }
2145        };
2146        #[cfg(esp32c6)]
2147        let rx_control_info = unsafe {
2148            RxControlInfo {
2149                rssi: (*rx_cntl).rssi(),
2150                rate: (*rx_cntl).rate(),
2151                sig_len: (*rx_cntl).sig_len(),
2152                rx_state: (*rx_cntl).rx_state(),
2153                dump_len: (*rx_cntl).dump_len(),
2154                he_sigb_len: (*rx_cntl).he_sigb_len(),
2155                cur_single_mpdu: (*rx_cntl).cur_single_mpdu(),
2156                cur_bb_format: (*rx_cntl).cur_bb_format(),
2157                rx_channel_estimate_info_vld: (*rx_cntl).rx_channel_estimate_info_vld(),
2158                rx_channel_estimate_len: (*rx_cntl).rx_channel_estimate_len(),
2159                second: (*rx_cntl).second(),
2160                channel: (*rx_cntl).channel(),
2161                noise_floor: (*rx_cntl).noise_floor(),
2162                is_group: (*rx_cntl).is_group(),
2163                rxend_state: (*rx_cntl).rxend_state(),
2164                rxmatch3: (*rx_cntl).rxmatch3(),
2165                rxmatch2: (*rx_cntl).rxmatch2(),
2166                rxmatch1: (*rx_cntl).rxmatch1(),
2167                rxmatch0: (*rx_cntl).rxmatch0(),
2168            }
2169        };
2170        rx_control_info
2171    }
2172}
2173/// Represents a Wi-Fi packet in promiscuous mode.
2174#[cfg(all(feature = "sniffer", feature = "unstable"))]
2175#[instability::unstable]
2176pub struct PromiscuousPkt<'a> {
2177    /// Control information related to packet reception.
2178    pub rx_cntl: RxControlInfo,
2179    /// Frame type of the received packet.
2180    pub frame_type: wifi_promiscuous_pkt_type_t,
2181    /// Length of the received packet.
2182    pub len: usize,
2183    /// Data contained in the received packet.
2184    pub data: &'a [u8],
2185}
2186#[cfg(all(feature = "sniffer", feature = "unstable"))]
2187#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
2188impl PromiscuousPkt<'_> {
2189    /// # Safety
2190    /// When calling this, you have to ensure, that `buf` points to a valid
2191    /// [wifi_promiscuous_pkt_t].
2192    pub(crate) unsafe fn from_raw(
2193        buf: *const wifi_promiscuous_pkt_t,
2194        frame_type: wifi_promiscuous_pkt_type_t,
2195    ) -> Self {
2196        let rx_cntl = unsafe { RxControlInfo::from_raw(&(*buf).rx_ctrl) };
2197        let len = rx_cntl.sig_len as usize;
2198        PromiscuousPkt {
2199            rx_cntl,
2200            frame_type,
2201            len,
2202            data: unsafe {
2203                core::slice::from_raw_parts(
2204                    (buf as *const u8).add(core::mem::size_of::<wifi_pkt_rx_ctrl_t>()),
2205                    len,
2206                )
2207            },
2208        }
2209    }
2210}
2211
2212#[cfg(all(feature = "sniffer", feature = "unstable"))]
2213static SNIFFER_CB: NonReentrantMutex<Option<fn(PromiscuousPkt<'_>)>> = NonReentrantMutex::new(None);
2214
2215#[cfg(all(feature = "sniffer", feature = "unstable"))]
2216unsafe extern "C" fn promiscuous_rx_cb(buf: *mut core::ffi::c_void, frame_type: u32) {
2217    unsafe {
2218        if let Some(sniffer_callback) = SNIFFER_CB.with(|callback| *callback) {
2219            let promiscuous_pkt = PromiscuousPkt::from_raw(buf as *const _, frame_type);
2220            sniffer_callback(promiscuous_pkt);
2221        }
2222    }
2223}
2224
2225#[cfg(all(feature = "sniffer", feature = "unstable"))]
2226#[instability::unstable]
2227/// A Wi-Fi sniffer.
2228#[non_exhaustive]
2229pub struct Sniffer<'d> {
2230    _phantom: PhantomData<&'d ()>,
2231}
2232
2233#[cfg(all(feature = "sniffer", feature = "unstable"))]
2234impl Sniffer<'_> {
2235    pub(crate) fn new() -> Self {
2236        // This shouldn't fail, since the way this is created, means that wifi will
2237        // always be initialized.
2238        unwrap!(esp_wifi_result!(unsafe {
2239            esp_wifi_set_promiscuous_rx_cb(Some(promiscuous_rx_cb))
2240        }));
2241        Self {
2242            _phantom: PhantomData,
2243        }
2244    }
2245    /// Set promiscuous mode enabled or disabled.
2246    #[instability::unstable]
2247    pub fn set_promiscuous_mode(&self, enabled: bool) -> Result<(), WifiError> {
2248        esp_wifi_result!(unsafe { esp_wifi_set_promiscuous(enabled) })?;
2249        Ok(())
2250    }
2251    /// Transmit a raw frame.
2252    #[instability::unstable]
2253    pub fn send_raw_frame(
2254        &mut self,
2255        use_sta_interface: bool,
2256        buffer: &[u8],
2257        use_internal_seq_num: bool,
2258    ) -> Result<(), WifiError> {
2259        esp_wifi_result!(unsafe {
2260            esp_wifi_80211_tx(
2261                if use_sta_interface {
2262                    wifi_interface_t_WIFI_IF_STA
2263                } else {
2264                    wifi_interface_t_WIFI_IF_AP
2265                } as wifi_interface_t,
2266                buffer.as_ptr() as *const _,
2267                buffer.len() as i32,
2268                use_internal_seq_num,
2269            )
2270        })
2271    }
2272    /// Set the callback for receiving a packet.
2273    #[instability::unstable]
2274    pub fn set_receive_cb(&mut self, cb: fn(PromiscuousPkt<'_>)) {
2275        SNIFFER_CB.with(|callback| *callback = Some(cb));
2276    }
2277}
2278
2279// see https://docs.rs/smoltcp/0.7.1/smoltcp/phy/index.html
2280#[cfg(all(feature = "smoltcp", feature = "unstable"))]
2281#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
2282impl Device for WifiDevice<'_> {
2283    type RxToken<'a>
2284        = WifiRxToken
2285    where
2286        Self: 'a;
2287    type TxToken<'a>
2288        = WifiTxToken
2289    where
2290        Self: 'a;
2291
2292    fn receive(
2293        &mut self,
2294        _instant: smoltcp::time::Instant,
2295    ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
2296        self.mode.rx_token()
2297    }
2298
2299    fn transmit(&mut self, _instant: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
2300        self.mode.tx_token()
2301    }
2302
2303    fn capabilities(&self) -> smoltcp::phy::DeviceCapabilities {
2304        let mut caps = DeviceCapabilities::default();
2305        caps.max_transmission_unit = MTU;
2306        caps.max_burst_size = if esp_config_int!(usize, "ESP_RADIO_CONFIG_WIFI_MAX_BURST_SIZE") == 0
2307        {
2308            None
2309        } else {
2310            Some(esp_config_int!(
2311                usize,
2312                "ESP_RADIO_CONFIG_WIFI_MAX_BURST_SIZE"
2313            ))
2314        };
2315        caps
2316    }
2317}
2318
2319#[doc(hidden)]
2320#[derive(Debug)]
2321pub struct WifiRxToken {
2322    mode: WifiDeviceMode,
2323}
2324
2325impl WifiRxToken {
2326    /// Consumes the RX token and applies the callback function to the received
2327    /// data buffer.
2328    pub fn consume_token<R, F>(self, f: F) -> R
2329    where
2330        F: FnOnce(&mut [u8]) -> R,
2331    {
2332        let mut data = self.mode.data_queue_rx().with(|queue| {
2333            unwrap!(
2334                queue.pop_front(),
2335                "unreachable: transmit()/receive() ensures there is a packet to process"
2336            )
2337        });
2338
2339        // We handle the received data outside of the lock because
2340        // PacketBuffer::drop must not be called in a critical section.
2341        // Dropping an PacketBuffer will call `esp_wifi_internal_free_rx_buffer`
2342        // which will try to lock an internal mutex. If the mutex is already
2343        // taken, the function will try to trigger a context switch, which will
2344        // fail if we are in an interrupt-free context.
2345        let buffer = data.as_slice_mut();
2346        dump_packet_info(buffer);
2347
2348        f(buffer)
2349    }
2350}
2351
2352#[cfg(all(feature = "smoltcp", feature = "unstable"))]
2353#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
2354impl RxToken for WifiRxToken {
2355    fn consume<R, F>(self, f: F) -> R
2356    where
2357        F: FnOnce(&[u8]) -> R,
2358    {
2359        self.consume_token(|t| f(t))
2360    }
2361}
2362
2363#[doc(hidden)]
2364#[derive(Debug)]
2365pub struct WifiTxToken {
2366    mode: WifiDeviceMode,
2367}
2368
2369impl WifiTxToken {
2370    /// Consumes the TX token and applies the callback function to the received
2371    /// data buffer.
2372    pub fn consume_token<R, F>(self, len: usize, f: F) -> R
2373    where
2374        F: FnOnce(&mut [u8]) -> R,
2375    {
2376        self.mode.increase_in_flight_counter();
2377
2378        // (safety): creation of multiple Wi-Fi devices with the same mode is impossible
2379        // in safe Rust, therefore only smoltcp _or_ embassy-net can be used at
2380        // one time
2381        static mut BUFFER: [u8; MTU] = [0u8; MTU];
2382
2383        let buffer = unsafe { &mut BUFFER[..len] };
2384
2385        let res = f(buffer);
2386
2387        esp_wifi_send_data(self.mode.interface(), buffer);
2388
2389        res
2390    }
2391}
2392
2393#[cfg(all(feature = "smoltcp", feature = "unstable"))]
2394#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
2395impl TxToken for WifiTxToken {
2396    fn consume<R, F>(self, len: usize, f: F) -> R
2397    where
2398        F: FnOnce(&mut [u8]) -> R,
2399    {
2400        self.consume_token(len, f)
2401    }
2402}
2403
2404// FIXME data here has to be &mut because of `esp_wifi_internal_tx` signature,
2405// requiring a *mut ptr to the buffer Casting const to mut is instant UB, even
2406// though in reality `esp_wifi_internal_tx` copies the buffer into its own
2407// memory and does not modify
2408pub(crate) fn esp_wifi_send_data(interface: wifi_interface_t, data: &mut [u8]) {
2409    trace!("sending... {} bytes", data.len());
2410    dump_packet_info(data);
2411
2412    let len = data.len() as u16;
2413    let ptr = data.as_mut_ptr().cast();
2414
2415    let res = unsafe { esp_wifi_internal_tx(interface, ptr, len) };
2416
2417    if res != 0 {
2418        warn!("esp_wifi_internal_tx {}", res);
2419        decrement_inflight_counter();
2420    } else {
2421        trace!("esp_wifi_internal_tx ok");
2422    }
2423}
2424
2425fn dump_packet_info(_buffer: &mut [u8]) {
2426    #[cfg(dump_packets)]
2427    {
2428        info!("@WIFIFRAME {:?}", _buffer);
2429    }
2430}
2431
2432#[doc(hidden)]
2433#[macro_export]
2434macro_rules! esp_wifi_result {
2435    ($value:expr) => {{
2436        use num_traits::FromPrimitive;
2437        let result = $value;
2438        if result != esp_wifi_sys::include::ESP_OK as i32 {
2439            warn!("{} returned an error: {}", stringify!($value), result);
2440            Err(WifiError::InternalError(unwrap!(FromPrimitive::from_i32(
2441                result
2442            ))))
2443        } else {
2444            Ok::<(), WifiError>(())
2445        }
2446    }};
2447}
2448
2449pub(crate) mod embassy {
2450    use embassy_net_driver::{Capabilities, Driver, HardwareAddress, RxToken, TxToken};
2451    use esp_hal::asynch::AtomicWaker;
2452
2453    use super::*;
2454
2455    // We can get away with a single tx waker because the transmit queue is shared
2456    // between interfaces.
2457    pub(crate) static TRANSMIT_WAKER: AtomicWaker = AtomicWaker::new();
2458
2459    pub(crate) static AP_RECEIVE_WAKER: AtomicWaker = AtomicWaker::new();
2460    pub(crate) static AP_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new();
2461
2462    pub(crate) static STA_RECEIVE_WAKER: AtomicWaker = AtomicWaker::new();
2463    pub(crate) static STA_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new();
2464
2465    impl RxToken for WifiRxToken {
2466        fn consume<R, F>(self, f: F) -> R
2467        where
2468            F: FnOnce(&mut [u8]) -> R,
2469        {
2470            self.consume_token(f)
2471        }
2472    }
2473
2474    impl TxToken for WifiTxToken {
2475        fn consume<R, F>(self, len: usize, f: F) -> R
2476        where
2477            F: FnOnce(&mut [u8]) -> R,
2478        {
2479            self.consume_token(len, f)
2480        }
2481    }
2482
2483    impl Driver for WifiDevice<'_> {
2484        type RxToken<'a>
2485            = WifiRxToken
2486        where
2487            Self: 'a;
2488        type TxToken<'a>
2489            = WifiTxToken
2490        where
2491            Self: 'a;
2492
2493        fn receive(
2494            &mut self,
2495            cx: &mut core::task::Context<'_>,
2496        ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
2497            self.mode.register_receive_waker(cx);
2498            self.mode.register_transmit_waker(cx);
2499            self.mode.rx_token()
2500        }
2501
2502        fn transmit(&mut self, cx: &mut core::task::Context<'_>) -> Option<Self::TxToken<'_>> {
2503            self.mode.register_transmit_waker(cx);
2504            self.mode.tx_token()
2505        }
2506
2507        fn link_state(
2508            &mut self,
2509            cx: &mut core::task::Context<'_>,
2510        ) -> embassy_net_driver::LinkState {
2511            self.mode.register_link_state_waker(cx);
2512            self.mode.link_state()
2513        }
2514
2515        fn capabilities(&self) -> Capabilities {
2516            let mut caps = Capabilities::default();
2517            caps.max_transmission_unit = MTU;
2518            caps.max_burst_size =
2519                if esp_config_int!(usize, "ESP_RADIO_CONFIG_WIFI_MAX_BURST_SIZE") == 0 {
2520                    None
2521                } else {
2522                    Some(esp_config_int!(
2523                        usize,
2524                        "ESP_RADIO_CONFIG_WIFI_MAX_BURST_SIZE"
2525                    ))
2526                };
2527            caps
2528        }
2529
2530        fn hardware_address(&self) -> HardwareAddress {
2531            HardwareAddress::Ethernet(self.mac_address())
2532        }
2533    }
2534}
2535
2536/// Power saving mode settings for the modem.
2537#[non_exhaustive]
2538#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
2539#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2540pub enum PowerSaveMode {
2541    /// No power saving.
2542    #[default]
2543    None,
2544    /// Minimum power save mode. In this mode, station wakes up to receive beacon every DTIM
2545    /// period.
2546    Minimum,
2547    /// Maximum power save mode. In this mode, interval to receive beacons is determined by the
2548    /// `listen_interval` config option.
2549    Maximum,
2550}
2551
2552pub(crate) fn apply_power_saving(ps: PowerSaveMode) -> Result<(), WifiError> {
2553    esp_wifi_result!(unsafe {
2554        esp_wifi_sys::include::esp_wifi_set_ps(match ps {
2555            PowerSaveMode::None => esp_wifi_sys::include::wifi_ps_type_t_WIFI_PS_NONE,
2556            PowerSaveMode::Minimum => esp_wifi_sys::include::wifi_ps_type_t_WIFI_PS_MIN_MODEM,
2557            PowerSaveMode::Maximum => esp_wifi_sys::include::wifi_ps_type_t_WIFI_PS_MAX_MODEM,
2558        })
2559    })?;
2560    Ok(())
2561}
2562
2563struct FreeApListOnDrop;
2564impl FreeApListOnDrop {
2565    pub fn defuse(self) {
2566        core::mem::forget(self);
2567    }
2568}
2569
2570impl Drop for FreeApListOnDrop {
2571    fn drop(&mut self) {
2572        unsafe {
2573            include::esp_wifi_clear_ap_list();
2574        }
2575    }
2576}
2577
2578/// Represents the Wi-Fi controller and its associated interfaces.
2579#[non_exhaustive]
2580pub struct Interfaces<'d> {
2581    /// Station mode Wi-Fi device.
2582    pub sta: WifiDevice<'d>,
2583    /// Access Point mode Wi-Fi device.
2584    pub ap: WifiDevice<'d>,
2585    /// ESP-NOW interface.
2586    #[cfg(all(feature = "esp-now", feature = "unstable"))]
2587    #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
2588    pub esp_now: crate::esp_now::EspNow<'d>,
2589    /// Wi-Fi sniffer interface.
2590    #[cfg(all(feature = "sniffer", feature = "unstable"))]
2591    #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
2592    pub sniffer: Sniffer<'d>,
2593}
2594
2595/// Wi-Fi operating class.
2596///
2597/// Refer to Annex E of IEEE Std 802.11-2020.
2598#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
2599#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2600#[instability::unstable]
2601pub enum OperatingClass {
2602    /// The regulations under which the station/AP is operating encompass all environments for the
2603    /// current frequency band in the country.
2604    AllEnvironments,
2605
2606    /// The regulations under which the station/AP is operating are for an outdoor environment only.
2607    Outdoors,
2608
2609    /// The regulations under which the station/AP is operating are for an indoor environment only.
2610    Indoors,
2611
2612    /// The station/AP is operating under a noncountry entity. The first two octets of the
2613    /// noncountry entity is two ASCII ‘XX’ characters.
2614    NonCountryEntity,
2615
2616    /// Binary representation of the Operating Class table number currently in use. Refer to Annex E
2617    /// of IEEE Std 802.11-2020.
2618    Repr(u8),
2619}
2620
2621impl Default for OperatingClass {
2622    fn default() -> Self {
2623        OperatingClass::Repr(0) // TODO: is this valid?
2624    }
2625}
2626
2627impl OperatingClass {
2628    fn into_code(self) -> u8 {
2629        match self {
2630            OperatingClass::AllEnvironments => b' ',
2631            OperatingClass::Outdoors => b'O',
2632            OperatingClass::Indoors => b'I',
2633            OperatingClass::NonCountryEntity => b'X',
2634            OperatingClass::Repr(code) => code,
2635        }
2636    }
2637}
2638
2639/// Country information.
2640///
2641/// Defaults to China (CN) with Operating Class "0".
2642///
2643/// To create a [`CountryInfo`] instance, use the `from` method first, then set additional
2644/// properties using the builder methods.
2645///
2646/// ## Example
2647///
2648/// ```rust,no_run
2649/// use esp_radio::wifi::{CountryInfo, OperatingClass};
2650///
2651/// let country_info = CountryInfo::from(*b"CN").operating_class(OperatingClass::Indoors);
2652/// ```
2653///
2654/// For more information, see the [Wi-Fi Country Code in the ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-country-code).
2655#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BuilderLite)]
2656#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2657pub struct CountryInfo {
2658    /// Country code.
2659    #[builder_lite(skip)]
2660    country: [u8; 2],
2661
2662    /// Operating class.
2663    #[builder_lite(unstable)]
2664    operating_class: OperatingClass,
2665}
2666
2667impl From<[u8; 2]> for CountryInfo {
2668    fn from(country: [u8; 2]) -> Self {
2669        Self {
2670            country,
2671            operating_class: OperatingClass::default(),
2672        }
2673    }
2674}
2675
2676impl CountryInfo {
2677    fn into_blob(self) -> wifi_country_t {
2678        wifi_country_t {
2679            cc: [
2680                self.country[0],
2681                self.country[1],
2682                self.operating_class.into_code(),
2683            ],
2684            // TODO: these may be valid defaults, but they should be configurable.
2685            schan: 1,
2686            nchan: 13,
2687            max_tx_power: 20,
2688            policy: wifi_country_policy_t_WIFI_COUNTRY_POLICY_MANUAL,
2689        }
2690    }
2691}
2692
2693/// Wi-Fi configuration.
2694#[derive(Clone, Copy, BuilderLite, Debug)]
2695#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2696pub struct Config {
2697    /// Power save mode.
2698    power_save_mode: PowerSaveMode,
2699
2700    /// Country code.
2701    #[builder_lite(into)]
2702    #[builder_lite(unstable)]
2703    country_code: CountryInfo,
2704
2705    /// Size of the RX queue in frames.
2706    #[builder_lite(unstable)]
2707    rx_queue_size: usize,
2708    /// Size of the TX queue in frames.
2709    #[builder_lite(unstable)]
2710    tx_queue_size: usize,
2711
2712    /// Max number of WiFi static RX buffers.
2713    ///
2714    /// Each buffer takes approximately 1.6KB of RAM. The static rx buffers are allocated when
2715    /// esp_wifi_init is called, they are not freed until esp_wifi_deinit is called.
2716    ///
2717    /// WiFi hardware use these buffers to receive all 802.11 frames. A higher number may allow
2718    /// higher throughput but increases memory use. If [`Self::ampdu_rx_enable`] is enabled,
2719    /// this value is recommended to set equal or bigger than [`Self::rx_ba_win`] in order to
2720    /// achieve better throughput and compatibility with both stations and APs.
2721    #[builder_lite(unstable)]
2722    static_rx_buf_num: u8,
2723
2724    /// Max number of WiFi dynamic RX buffers
2725    ///
2726    /// Set the number of WiFi dynamic RX buffers, 0 means unlimited RX buffers will be allocated
2727    /// (provided sufficient free RAM). The size of each dynamic RX buffer depends on the size of
2728    /// the received data frame.
2729    ///
2730    /// For each received data frame, the WiFi driver makes a copy to an RX buffer and then
2731    /// delivers it to the high layer TCP/IP stack. The dynamic RX buffer is freed after the
2732    /// higher layer has successfully received the data frame.
2733    ///
2734    /// For some applications, WiFi data frames may be received faster than the application can
2735    /// process them. In these cases we may run out of memory if RX buffer number is unlimited
2736    /// (0).
2737    ///
2738    /// If a dynamic RX buffer limit is set, it should be at least the number of
2739    /// static RX buffers.
2740    #[builder_lite(unstable)]
2741    dynamic_rx_buf_num: u16,
2742
2743    /// Set the number of WiFi static TX buffers.
2744    ///
2745    /// Each buffer takes approximately 1.6KB of RAM.
2746    /// The static RX buffers are allocated when esp_wifi_init() is called, they are not released
2747    /// until esp_wifi_deinit() is called.
2748    ///
2749    /// For each transmitted data frame from the higher layer TCP/IP stack, the WiFi driver makes a
2750    /// copy of it in a TX buffer.
2751    ///
2752    /// For some applications especially UDP applications, the upper layer can deliver frames
2753    /// faster than WiFi layer can transmit. In these cases, we may run out of TX buffers.
2754    #[builder_lite(unstable)]
2755    static_tx_buf_num: u8,
2756
2757    /// Set the number of WiFi dynamic TX buffers.
2758    ///
2759    /// The size of each dynamic TX buffer is not fixed,
2760    /// it depends on the size of each transmitted data frame.
2761    ///
2762    /// For each transmitted frame from the higher layer TCP/IP stack, the WiFi driver makes a copy
2763    /// of it in a TX buffer.
2764    ///
2765    /// For some applications, especially UDP applications, the upper layer can deliver frames
2766    /// faster than WiFi layer can transmit. In these cases, we may run out of TX buffers.
2767    #[builder_lite(unstable)]
2768    dynamic_tx_buf_num: u16,
2769
2770    /// Select this option to enable AMPDU RX feature.
2771    #[builder_lite(unstable)]
2772    ampdu_rx_enable: bool,
2773
2774    /// Select this option to enable AMPDU TX feature.
2775    #[builder_lite(unstable)]
2776    ampdu_tx_enable: bool,
2777
2778    /// Select this option to enable AMSDU TX feature.
2779    #[builder_lite(unstable)]
2780    amsdu_tx_enable: bool,
2781
2782    /// Set the size of WiFi Block Ack RX window.
2783    ///
2784    /// Generally a bigger value means higher throughput and better compatibility but more memory.
2785    /// Most of time we should NOT change the default value unless special reason, e.g. test
2786    /// the maximum UDP RX throughput with iperf etc. For iperf test in shieldbox, the
2787    /// recommended value is 9~12.
2788    ///
2789    /// If PSRAM is used and WiFi memory is preferred to allocate in PSRAM first, the default and
2790    /// minimum value should be 16 to achieve better throughput and compatibility with both
2791    /// stations and APs.
2792    #[builder_lite(unstable)]
2793    rx_ba_win: u8,
2794}
2795
2796impl Default for Config {
2797    fn default() -> Self {
2798        Self {
2799            power_save_mode: PowerSaveMode::default(),
2800            country_code: CountryInfo::from(*b"CN"),
2801
2802            rx_queue_size: 5,
2803            tx_queue_size: 3,
2804
2805            static_rx_buf_num: 10,
2806            dynamic_rx_buf_num: 32,
2807
2808            static_tx_buf_num: 0,
2809            dynamic_tx_buf_num: 32,
2810
2811            ampdu_rx_enable: true,
2812            ampdu_tx_enable: true,
2813            amsdu_tx_enable: false,
2814
2815            rx_ba_win: 6,
2816        }
2817    }
2818}
2819
2820impl Config {
2821    fn validate(&self) {
2822        if self.rx_ba_win as u16 >= self.dynamic_rx_buf_num {
2823            warn!("RX BA window size should be less than the number of dynamic RX buffers.");
2824        }
2825        if self.rx_ba_win as u16 >= 2 * (self.static_rx_buf_num as u16) {
2826            warn!("RX BA window size should be less than twice the number of static RX buffers.");
2827        }
2828    }
2829}
2830
2831/// Create a Wi-Fi controller and it's associated interfaces.
2832///
2833/// Dropping the controller will deinitialize / stop Wi-Fi.
2834///
2835/// Make sure to **not** call this function while interrupts are disabled, or IEEE 802.15.4 is
2836/// currently in use.
2837pub fn new<'d>(
2838    _inited: &'d Controller<'d>,
2839    device: crate::hal::peripherals::WIFI<'d>,
2840    config: Config,
2841) -> Result<(WifiController<'d>, Interfaces<'d>), WifiError> {
2842    if crate::is_interrupts_disabled() {
2843        return Err(WifiError::Unsupported);
2844    }
2845
2846    config.validate();
2847
2848    unsafe {
2849        internal::G_CONFIG = wifi_init_config_t {
2850            osi_funcs: (&raw const internal::__ESP_RADIO_G_WIFI_OSI_FUNCS).cast_mut(),
2851
2852            wpa_crypto_funcs: g_wifi_default_wpa_crypto_funcs,
2853            static_rx_buf_num: config.static_rx_buf_num as _,
2854            dynamic_rx_buf_num: config.dynamic_rx_buf_num as _,
2855            tx_buf_type: esp_wifi_sys::include::CONFIG_ESP_WIFI_TX_BUFFER_TYPE as i32,
2856            static_tx_buf_num: config.static_tx_buf_num as _,
2857            dynamic_tx_buf_num: config.dynamic_tx_buf_num as _,
2858            rx_mgmt_buf_type: esp_wifi_sys::include::CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF as i32,
2859            rx_mgmt_buf_num: esp_wifi_sys::include::CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF as i32,
2860            cache_tx_buf_num: esp_wifi_sys::include::WIFI_CACHE_TX_BUFFER_NUM as i32,
2861            csi_enable: cfg!(feature = "csi") as i32,
2862            ampdu_rx_enable: config.ampdu_rx_enable as _,
2863            ampdu_tx_enable: config.ampdu_tx_enable as _,
2864            amsdu_tx_enable: config.amsdu_tx_enable as _,
2865            nvs_enable: 0,
2866            nano_enable: 0,
2867            rx_ba_win: config.rx_ba_win as _,
2868            wifi_task_core_id: Cpu::current() as _,
2869            beacon_max_len: esp_wifi_sys::include::WIFI_SOFTAP_BEACON_MAX_LEN as i32,
2870            mgmt_sbuf_num: esp_wifi_sys::include::WIFI_MGMT_SBUF_NUM as i32,
2871            feature_caps: internal::__ESP_RADIO_G_WIFI_FEATURE_CAPS,
2872            sta_disconnected_pm: false,
2873            espnow_max_encrypt_num: esp_wifi_sys::include::CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM
2874                as i32,
2875
2876            tx_hetb_queue_num: 3,
2877            dump_hesigb_enable: false,
2878
2879            magic: WIFI_INIT_CONFIG_MAGIC as i32,
2880        };
2881
2882        RX_QUEUE_SIZE.store(config.rx_queue_size, Ordering::Relaxed);
2883        TX_QUEUE_SIZE.store(config.tx_queue_size, Ordering::Relaxed);
2884    };
2885
2886    crate::wifi::wifi_init(device)?;
2887
2888    unsafe {
2889        let country = config.country_code.into_blob();
2890        esp_wifi_result!(esp_wifi_set_country(&country))?;
2891    }
2892
2893    // At some point the "High-speed ADC" entropy source became available.
2894    unsafe { esp_hal::rng::TrngSource::increase_entropy_source_counter() };
2895
2896    // Only create WifiController after we've enabled TRNG - otherwise returning an error from this
2897    // function will cause panic because WifiController::drop tries to disable the TRNG.
2898    let mut controller = WifiController {
2899        _phantom: Default::default(),
2900        beacon_timeout: 6,
2901        ap_beacon_timeout: 100,
2902    };
2903
2904    controller.set_power_saving(config.power_save_mode)?;
2905
2906    Ok((
2907        controller,
2908        Interfaces {
2909            sta: WifiDevice {
2910                _phantom: Default::default(),
2911                mode: WifiDeviceMode::Sta,
2912            },
2913            ap: WifiDevice {
2914                _phantom: Default::default(),
2915                mode: WifiDeviceMode::Ap,
2916            },
2917            #[cfg(all(feature = "esp-now", feature = "unstable"))]
2918            esp_now: crate::esp_now::EspNow::new_internal(),
2919            #[cfg(all(feature = "sniffer", feature = "unstable"))]
2920            sniffer: Sniffer::new(),
2921        },
2922    ))
2923}
2924
2925/// Wi-Fi controller.
2926#[non_exhaustive]
2927pub struct WifiController<'d> {
2928    _phantom: PhantomData<&'d ()>,
2929    // Things we have to remember due to how esp-wifi works:
2930    beacon_timeout: u16,
2931    ap_beacon_timeout: u16,
2932}
2933
2934impl Drop for WifiController<'_> {
2935    fn drop(&mut self) {
2936        if let Err(e) = crate::wifi::wifi_deinit() {
2937            warn!("Failed to cleanly deinit wifi: {:?}", e);
2938        }
2939
2940        esp_hal::rng::TrngSource::decrease_entropy_source_counter(unsafe {
2941            esp_hal::Internal::conjure()
2942        });
2943    }
2944}
2945
2946impl WifiController<'_> {
2947    /// Set CSI configuration and register the receiving callback.
2948    #[cfg(feature = "csi")]
2949    #[instability::unstable]
2950    pub fn set_csi(
2951        &mut self,
2952        mut csi: CsiConfig,
2953        cb: impl FnMut(crate::wifi::wifi_csi_info_t) + Send,
2954    ) -> Result<(), WifiError> {
2955        csi.apply_config()?;
2956        csi.set_receive_cb(cb)?;
2957        csi.set_csi(true)?;
2958
2959        Ok(())
2960    }
2961
2962    /// Set the Wi-Fi protocol.
2963    ///
2964    /// This will set the wifi protocol to the desired protocol, the default for
2965    /// this is: `WIFI_PROTOCOL_11B|WIFI_PROTOCOL_11G|WIFI_PROTOCOL_11N`
2966    ///
2967    /// # Arguments:
2968    ///
2969    /// * `protocols` - The desired protocols
2970    ///
2971    /// # Example:
2972    ///
2973    /// ```
2974    /// wifi_controller.set_protocol(Protocol::P802D11BGNLR.into());
2975    /// ```
2976    /// # Note
2977    /// Calling this function before `set_config` will return an error.
2978    pub fn set_protocol(&mut self, protocols: EnumSet<Protocol>) -> Result<(), WifiError> {
2979        let protocol = protocols
2980            .into_iter()
2981            .map(|v| match v {
2982                Protocol::P802D11B => WIFI_PROTOCOL_11B,
2983                Protocol::P802D11BG => WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G,
2984                Protocol::P802D11BGN => WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N,
2985                Protocol::P802D11BGNLR => {
2986                    WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR
2987                }
2988                Protocol::P802D11LR => WIFI_PROTOCOL_LR,
2989                Protocol::P802D11BGNAX => {
2990                    WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_11AX
2991                }
2992            })
2993            .fold(0, |combined, protocol| combined | protocol) as u8;
2994
2995        let mode = self.mode()?;
2996        if mode.is_sta() {
2997            esp_wifi_result!(unsafe {
2998                esp_wifi_set_protocol(wifi_interface_t_WIFI_IF_STA, protocol)
2999            })?;
3000        }
3001        if mode.is_ap() {
3002            esp_wifi_result!(unsafe {
3003                esp_wifi_set_protocol(wifi_interface_t_WIFI_IF_AP, protocol)
3004            })?;
3005        }
3006
3007        Ok(())
3008    }
3009
3010    fn apply_protocols(
3011        iface: wifi_interface_t,
3012        protocols: &EnumSet<Protocol>,
3013    ) -> Result<(), WifiError> {
3014        let mask = protocols.iter().fold(0, |acc, p| acc | p.to_mask());
3015        debug!("Setting protocols with mask {:b}", mask);
3016        esp_wifi_result!(unsafe { esp_wifi_set_protocol(iface, mask as u8) })
3017    }
3018
3019    /// Configures modem power saving.
3020    pub fn set_power_saving(&mut self, ps: PowerSaveMode) -> Result<(), WifiError> {
3021        apply_power_saving(ps)
3022    }
3023
3024    /// A blocking wifi network scan with caller-provided scanning options.
3025    pub fn scan_with_config(
3026        &mut self,
3027        config: ScanConfig<'_>,
3028    ) -> Result<alloc::vec::Vec<AccessPointInfo>, WifiError> {
3029        esp_wifi_result!(crate::wifi::wifi_start_scan(true, config))?;
3030        self.scan_results(config.max.unwrap_or(usize::MAX))
3031    }
3032
3033    fn scan_results(&mut self, max: usize) -> Result<alloc::vec::Vec<AccessPointInfo>, WifiError> {
3034        let mut scanned = alloc::vec::Vec::<AccessPointInfo>::new();
3035        let mut bss_total: u16 = max as u16;
3036
3037        // Prevents memory leak on error
3038        let guard = FreeApListOnDrop;
3039
3040        unsafe { esp_wifi_result!(include::esp_wifi_scan_get_ap_num(&mut bss_total))? };
3041
3042        guard.defuse();
3043
3044        let mut record: MaybeUninit<include::wifi_ap_record_t> = MaybeUninit::uninit();
3045        for _ in 0..usize::min(bss_total as usize, max) {
3046            let record = unsafe { MaybeUninit::assume_init_mut(&mut record) };
3047            unsafe { esp_wifi_result!(include::esp_wifi_scan_get_ap_record(record))? };
3048            let ap_info = convert_ap_info(record);
3049            scanned.push(ap_info);
3050        }
3051
3052        unsafe { esp_wifi_result!(include::esp_wifi_clear_ap_list())? };
3053
3054        Ok(scanned)
3055    }
3056
3057    /// Starts the Wi-Fi controller.
3058    ///
3059    /// This method is not blocking. To check if the controller has started, use the
3060    /// [`Self::is_started`] method.
3061    pub fn start(&mut self) -> Result<(), WifiError> {
3062        unsafe {
3063            esp_wifi_result!(esp_wifi_start())?;
3064
3065            let mode = WifiMode::current()?;
3066
3067            // This is not an if-else because in AP-STA mode, both are true
3068            if mode.is_ap() {
3069                esp_wifi_result!(include::esp_wifi_set_inactive_time(
3070                    wifi_interface_t_WIFI_IF_AP,
3071                    self.ap_beacon_timeout
3072                ))?;
3073            }
3074            if mode.is_sta() {
3075                esp_wifi_result!(include::esp_wifi_set_inactive_time(
3076                    wifi_interface_t_WIFI_IF_STA,
3077                    self.beacon_timeout
3078                ))?;
3079            }
3080        }
3081
3082        Ok(())
3083    }
3084
3085    /// Stops the Wi-Fi controller.
3086    ///
3087    /// This method is not blocking. Use the [`Self::is_started`] method to see if the controller is
3088    /// still running.
3089    pub fn stop(&mut self) -> Result<(), WifiError> {
3090        self.stop_impl()
3091    }
3092
3093    /// Connect Wi-Fi station to the AP.
3094    ///
3095    /// This method is not blocking. Use the [`Self::is_connected`] method to see if the station is
3096    /// connected.
3097    ///
3098    /// - If station is connected, call [`Self::disconnect`] to disconnect.
3099    /// - Calling [`Self::scan_with_config`] or [`Self::scan_with_config_async`] will not be
3100    ///   effective until connection between device and the AP is established.
3101    /// - If device is scanning and connecting at the same time, it will abort scanning and return a
3102    ///   warning message and error.
3103    pub fn connect(&mut self) -> Result<(), WifiError> {
3104        self.connect_impl()
3105    }
3106
3107    /// Disconnect Wi-Fi station from the AP.
3108    ///
3109    /// This method is not blocking. Use the [`Self::is_connected`] method to see if the station is
3110    /// still connected.
3111    pub fn disconnect(&mut self) -> Result<(), WifiError> {
3112        self.disconnect_impl()
3113    }
3114
3115    /// Get the RSSI information of AP to which the device is associated with.
3116    /// The value is obtained from the last beacon.
3117    ///
3118    /// <div class="warning">
3119    ///
3120    /// - Use this API only in STA or AP-STA mode.
3121    /// - This API should be called after the station has connected to an access point.
3122    /// </div>
3123    ///
3124    /// # Errors
3125    /// This function returns [`WifiError::Unsupported`] if the STA side isn't
3126    /// running. For example, when configured for AP only.
3127    pub fn rssi(&self) -> Result<i32, WifiError> {
3128        if self.mode()?.is_sta() {
3129            let mut rssi: i32 = 0;
3130            // Will return ESP_FAIL -1 if called in AP mode.
3131            esp_wifi_result!(unsafe { esp_wifi_sta_get_rssi(&mut rssi) })?;
3132            Ok(rssi)
3133        } else {
3134            Err(WifiError::Unsupported)
3135        }
3136    }
3137
3138    /// Get the supported capabilities of the controller.
3139    pub fn capabilities(&self) -> Result<EnumSet<crate::wifi::Capability>, WifiError> {
3140        let caps =
3141            enumset::enum_set! { Capability::Client | Capability::AccessPoint | Capability::ApSta };
3142
3143        Ok(caps)
3144    }
3145
3146    /// Set the configuration.
3147    ///
3148    /// This will set the mode accordingly.
3149    /// You need to use [`Self::connect`] for connecting to an AP.
3150    ///
3151    /// Passing [`ModeConfig::None`] will disable both, AP and STA mode.
3152    ///
3153    /// If you don't intend to use Wi-Fi anymore at all consider tearing down
3154    /// Wi-Fi completely.
3155    pub fn set_config(&mut self, conf: &ModeConfig) -> Result<(), WifiError> {
3156        conf.validate()?;
3157
3158        let mode = match conf {
3159            ModeConfig::None => wifi_mode_t_WIFI_MODE_NULL,
3160            ModeConfig::Client(_) => wifi_mode_t_WIFI_MODE_STA,
3161            ModeConfig::AccessPoint(_) => wifi_mode_t_WIFI_MODE_AP,
3162            ModeConfig::ApSta(_, _) => wifi_mode_t_WIFI_MODE_APSTA,
3163            #[cfg(feature = "wifi-eap")]
3164            ModeConfig::EapClient(_) => wifi_mode_t_WIFI_MODE_STA,
3165        };
3166
3167        esp_wifi_result!(unsafe { esp_wifi_set_mode(mode) })?;
3168
3169        match conf {
3170            ModeConfig::None => Ok(()),
3171            ModeConfig::Client(config) => {
3172                self.apply_sta_config(config)?;
3173                Self::apply_protocols(wifi_interface_t_WIFI_IF_STA, &config.protocols)
3174            }
3175            ModeConfig::AccessPoint(config) => {
3176                self.apply_ap_config(config)?;
3177                Self::apply_protocols(wifi_interface_t_WIFI_IF_AP, &config.protocols)
3178            }
3179            ModeConfig::ApSta(sta_config, ap_config) => {
3180                self.apply_ap_config(ap_config)?;
3181                Self::apply_protocols(wifi_interface_t_WIFI_IF_AP, &ap_config.protocols)?;
3182                self.apply_sta_config(sta_config)?;
3183                Self::apply_protocols(wifi_interface_t_WIFI_IF_STA, &sta_config.protocols)
3184            }
3185            #[cfg(feature = "wifi-eap")]
3186            ModeConfig::EapClient(config) => {
3187                self.apply_sta_eap_config(config)?;
3188                Self::apply_protocols(wifi_interface_t_WIFI_IF_STA, &config.protocols)
3189            }
3190        }
3191        .inspect_err(|_| {
3192            // we/the driver might have applied a partial configuration
3193            // so we better disable AP/STA just in case the caller ignores the error we
3194            // return here - they will run into futher errors this way
3195            unsafe { esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_NULL) };
3196        })?;
3197
3198        Ok(())
3199    }
3200
3201    /// Set the Wi-Fi mode.
3202    ///
3203    /// This will override the mode inferred by [`Self::set_config`].
3204    pub fn set_mode(&mut self, mode: WifiMode) -> Result<(), WifiError> {
3205        esp_wifi_result!(unsafe { esp_wifi_set_mode(mode.into()) })?;
3206        Ok(())
3207    }
3208
3209    fn stop_impl(&mut self) -> Result<(), WifiError> {
3210        esp_wifi_result!(unsafe { esp_wifi_stop() })
3211    }
3212
3213    fn connect_impl(&mut self) -> Result<(), WifiError> {
3214        // TODO: implement ROAMING
3215        esp_wifi_result!(unsafe { esp_wifi_connect_internal() })
3216    }
3217
3218    fn disconnect_impl(&mut self) -> Result<(), WifiError> {
3219        // TODO: implement ROAMING
3220        esp_wifi_result!(unsafe { esp_wifi_disconnect_internal() })
3221    }
3222
3223    /// Checks if the Wi-Fi controller has started. Returns true if STA and/or AP are started.
3224    ///
3225    /// This function should be called after the [`Self::start`] method to verify if the
3226    /// Wi-Fi controller has started successfully.
3227    pub fn is_started(&self) -> Result<bool, WifiError> {
3228        if matches!(
3229            crate::wifi::sta_state(),
3230            WifiStaState::Started | WifiStaState::Connected | WifiStaState::Disconnected
3231        ) {
3232            return Ok(true);
3233        }
3234        if matches!(crate::wifi::ap_state(), WifiApState::Started) {
3235            return Ok(true);
3236        }
3237        Ok(false)
3238    }
3239
3240    /// Checks if the Wi-Fi controller is connected to an AP.
3241    ///
3242    /// This function should be called after the [`Self::connect`] method to verify if
3243    /// the connection was successful.
3244    pub fn is_connected(&self) -> Result<bool, WifiError> {
3245        match crate::wifi::sta_state() {
3246            crate::wifi::WifiStaState::Connected => Ok(true),
3247            crate::wifi::WifiStaState::Disconnected => Err(WifiError::Disconnected),
3248            // FIXME: Should any other enum value trigger an error instead of returning false?
3249            _ => Ok(false),
3250        }
3251    }
3252
3253    fn mode(&self) -> Result<WifiMode, WifiError> {
3254        WifiMode::current()
3255    }
3256
3257    /// An async Wi-Fi network scan with caller-provided scanning options.
3258    pub async fn scan_with_config_async(
3259        &mut self,
3260        config: ScanConfig<'_>,
3261    ) -> Result<alloc::vec::Vec<AccessPointInfo>, WifiError> {
3262        Self::clear_events(WifiEvent::ScanDone);
3263        esp_wifi_result!(wifi_start_scan(false, config))?;
3264
3265        // Prevents memory leak if `scan_n`'s future is dropped.
3266        let guard = FreeApListOnDrop;
3267        WifiEventFuture::new(WifiEvent::ScanDone).await;
3268
3269        guard.defuse();
3270
3271        let result = self.scan_results(config.max.unwrap_or(usize::MAX))?;
3272
3273        Ok(result)
3274    }
3275
3276    /// Async version of [`Self::start`].
3277    ///
3278    /// This function will wait for the Wi-Fi controller to start before returning.
3279    pub async fn start_async(&mut self) -> Result<(), WifiError> {
3280        let mut events = enumset::enum_set! {};
3281
3282        let mode = self.mode()?;
3283        if mode.is_ap() {
3284            events |= WifiEvent::ApStart;
3285        }
3286        if mode.is_sta() {
3287            events |= WifiEvent::StaStart;
3288        }
3289
3290        Self::clear_events(events);
3291
3292        self.start()?;
3293
3294        self.wait_for_all_events(events, false).await;
3295
3296        Ok(())
3297    }
3298
3299    /// Async version of [`Self::stop`].
3300    ///
3301    /// This function will wait for the Wi-Fi controller to stop before returning.
3302    pub async fn stop_async(&mut self) -> Result<(), WifiError> {
3303        let mut events = enumset::enum_set! {};
3304
3305        let mode = self.mode()?;
3306        if mode.is_ap() {
3307            events |= WifiEvent::ApStop;
3308        }
3309        if mode.is_sta() {
3310            events |= WifiEvent::StaStop;
3311        }
3312
3313        Self::clear_events(events);
3314
3315        self.stop_impl()?;
3316
3317        self.wait_for_all_events(events, false).await;
3318
3319        reset_ap_state();
3320        reset_sta_state();
3321
3322        Ok(())
3323    }
3324
3325    /// Async version of [`Self::connect`].
3326    ///
3327    /// This function will wait for the connection to be established before returning.
3328    pub async fn connect_async(&mut self) -> Result<(), WifiError> {
3329        Self::clear_events(WifiEvent::StaConnected | WifiEvent::StaDisconnected);
3330
3331        let err = self.connect_impl().err();
3332
3333        if MultiWifiEventFuture::new(WifiEvent::StaConnected | WifiEvent::StaDisconnected)
3334            .await
3335            .contains(WifiEvent::StaDisconnected)
3336        {
3337            Err(err.unwrap_or(WifiError::Disconnected))
3338        } else {
3339            Ok(())
3340        }
3341    }
3342
3343    /// Async version of [`Self::disconnect`].
3344    ///
3345    /// This function will wait for the connection to be closed before returning.
3346    pub async fn disconnect_async(&mut self) -> Result<(), WifiError> {
3347        // If not connected, this will do nothing.
3348        // It will also wait forever for a `StaDisconnected` event that will never come.
3349        // Return early instead of hanging.
3350        if !matches!(self.is_connected(), Ok(true)) {
3351            return Ok(());
3352        }
3353
3354        Self::clear_events(WifiEvent::StaDisconnected);
3355        self.disconnect_impl()?;
3356        WifiEventFuture::new(WifiEvent::StaDisconnected).await;
3357
3358        Ok(())
3359    }
3360
3361    fn clear_events(events: impl Into<EnumSet<WifiEvent>>) {
3362        WIFI_EVENTS.with(|evts| evts.remove_all(events.into()));
3363    }
3364
3365    /// Wait for one [`WifiEvent`].
3366    pub async fn wait_for_event(&mut self, event: WifiEvent) {
3367        Self::clear_events(event);
3368        WifiEventFuture::new(event).await
3369    }
3370
3371    /// Wait for one of multiple [`WifiEvent`]s. Returns the events that
3372    /// occurred while waiting.
3373    pub async fn wait_for_events(
3374        &mut self,
3375        events: EnumSet<WifiEvent>,
3376        clear_pending: bool,
3377    ) -> EnumSet<WifiEvent> {
3378        if clear_pending {
3379            Self::clear_events(events);
3380        }
3381        MultiWifiEventFuture::new(events).await
3382    }
3383
3384    /// Wait for multiple [`WifiEvent`]s.
3385    pub async fn wait_for_all_events(
3386        &mut self,
3387        mut events: EnumSet<WifiEvent>,
3388        clear_pending: bool,
3389    ) {
3390        if clear_pending {
3391            Self::clear_events(events);
3392        }
3393
3394        while !events.is_empty() {
3395            let fired = MultiWifiEventFuture::new(events).await;
3396            events -= fired;
3397        }
3398    }
3399
3400    fn apply_ap_config(&mut self, config: &AccessPointConfig) -> Result<(), WifiError> {
3401        self.ap_beacon_timeout = config.beacon_timeout;
3402
3403        let mut cfg = wifi_config_t {
3404            ap: wifi_ap_config_t {
3405                ssid: [0; 32],
3406                password: [0; 64],
3407                ssid_len: 0,
3408                channel: config.channel,
3409                authmode: config.auth_method.to_raw(),
3410                ssid_hidden: if config.ssid_hidden { 1 } else { 0 },
3411                max_connection: config.max_connections as u8,
3412                beacon_interval: 100,
3413                pairwise_cipher: wifi_cipher_type_t_WIFI_CIPHER_TYPE_CCMP,
3414                ftm_responder: false,
3415                pmf_cfg: wifi_pmf_config_t {
3416                    capable: true,
3417                    required: false,
3418                },
3419                sae_pwe_h2e: 0,
3420                csa_count: 3,
3421                dtim_period: config.dtim_period,
3422                transition_disable: 0,
3423                sae_ext: 0,
3424                bss_max_idle_cfg: include::wifi_bss_max_idle_config_t {
3425                    period: 0,
3426                    protected_keep_alive: false,
3427                },
3428                gtk_rekey_interval: 0,
3429            },
3430        };
3431
3432        if config.auth_method == AuthMethod::None && !config.password.is_empty() {
3433            return Err(WifiError::InternalError(InternalWifiError::InvalidArg));
3434        }
3435
3436        unsafe {
3437            cfg.ap.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
3438            cfg.ap.ssid_len = config.ssid.len() as u8;
3439            cfg.ap.password[0..(config.password.len())].copy_from_slice(config.password.as_bytes());
3440
3441            esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_AP, &mut cfg))
3442        }
3443    }
3444
3445    fn apply_sta_config(&mut self, config: &ClientConfig) -> Result<(), WifiError> {
3446        self.beacon_timeout = config.beacon_timeout;
3447
3448        let mut cfg = wifi_config_t {
3449            sta: wifi_sta_config_t {
3450                ssid: [0; 32],
3451                password: [0; 64],
3452                scan_method: config.scan_method as c_uint,
3453                bssid_set: config.bssid.is_some(),
3454                bssid: config.bssid.unwrap_or_default(),
3455                channel: config.channel.unwrap_or(0),
3456                listen_interval: config.listen_interval,
3457                sort_method: wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
3458                threshold: wifi_scan_threshold_t {
3459                    rssi: -99,
3460                    authmode: config.auth_method.to_raw(),
3461                    rssi_5g_adjustment: 0,
3462                },
3463                pmf_cfg: wifi_pmf_config_t {
3464                    capable: true,
3465                    required: false,
3466                },
3467                sae_pwe_h2e: 3,
3468                _bitfield_align_1: [0; 0],
3469                _bitfield_1: __BindgenBitfieldUnit::new([0; 4]),
3470                failure_retry_cnt: config.failure_retry_cnt,
3471                _bitfield_align_2: [0; 0],
3472                _bitfield_2: __BindgenBitfieldUnit::new([0; 4]),
3473                sae_pk_mode: 0, // ??
3474                sae_h2e_identifier: [0; 32],
3475            },
3476        };
3477
3478        if config.auth_method == AuthMethod::None && !config.password.is_empty() {
3479            return Err(WifiError::InternalError(InternalWifiError::InvalidArg));
3480        }
3481
3482        unsafe {
3483            cfg.sta.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
3484            cfg.sta.password[0..(config.password.len())]
3485                .copy_from_slice(config.password.as_bytes());
3486
3487            esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_STA, &mut cfg))
3488        }
3489    }
3490
3491    #[cfg(feature = "wifi-eap")]
3492    fn apply_sta_eap_config(&mut self, config: &EapClientConfig) -> Result<(), WifiError> {
3493        self.beacon_timeout = config.beacon_timeout;
3494
3495        let mut cfg = wifi_config_t {
3496            sta: wifi_sta_config_t {
3497                ssid: [0; 32],
3498                password: [0; 64],
3499                scan_method: config.scan_method as c_uint,
3500                bssid_set: config.bssid.is_some(),
3501                bssid: config.bssid.unwrap_or_default(),
3502                channel: config.channel.unwrap_or(0),
3503                listen_interval: config.listen_interval,
3504                sort_method: wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
3505                threshold: wifi_scan_threshold_t {
3506                    rssi: -99,
3507                    authmode: config.auth_method.to_raw(),
3508                    rssi_5g_adjustment: 0,
3509                },
3510                pmf_cfg: wifi_pmf_config_t {
3511                    capable: true,
3512                    required: false,
3513                },
3514                sae_pwe_h2e: 3,
3515                _bitfield_align_1: [0; 0],
3516                _bitfield_1: __BindgenBitfieldUnit::new([0; 4]),
3517                failure_retry_cnt: config.failure_retry_cnt,
3518                _bitfield_align_2: [0; 0],
3519                _bitfield_2: __BindgenBitfieldUnit::new([0; 4]),
3520                sae_pk_mode: 0, // ??
3521                sae_h2e_identifier: [0; 32],
3522            },
3523        };
3524
3525        unsafe {
3526            cfg.sta.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
3527            esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_STA, &mut cfg))?;
3528
3529            if let Some(identity) = &config.identity {
3530                esp_wifi_result!(esp_eap_client_set_identity(
3531                    identity.as_str().as_ptr(),
3532                    identity.len() as i32
3533                ))?;
3534            } else {
3535                esp_eap_client_clear_identity();
3536            }
3537
3538            if let Some(username) = &config.username {
3539                esp_wifi_result!(esp_eap_client_set_username(
3540                    username.as_str().as_ptr(),
3541                    username.len() as i32
3542                ))?;
3543            } else {
3544                esp_eap_client_clear_username();
3545            }
3546
3547            if let Some(password) = &config.password {
3548                esp_wifi_result!(esp_eap_client_set_password(
3549                    password.as_str().as_ptr(),
3550                    password.len() as i32
3551                ))?;
3552            } else {
3553                esp_eap_client_clear_password();
3554            }
3555
3556            if let Some(new_password) = &config.new_password {
3557                esp_wifi_result!(esp_eap_client_set_new_password(
3558                    new_password.as_str().as_ptr(),
3559                    new_password.len() as i32
3560                ))?;
3561            } else {
3562                esp_eap_client_clear_new_password();
3563            }
3564
3565            if let Some(pac_file) = &config.pac_file {
3566                esp_wifi_result!(esp_eap_client_set_pac_file(
3567                    pac_file.as_ptr(),
3568                    pac_file.len() as i32
3569                ))?;
3570            }
3571
3572            if let Some(phase2_method) = &config.ttls_phase2_method {
3573                esp_wifi_result!(esp_eap_client_set_ttls_phase2_method(
3574                    phase2_method.to_raw()
3575                ))?;
3576            }
3577
3578            if let Some(ca_cert) = config.ca_cert {
3579                esp_wifi_result!(esp_eap_client_set_ca_cert(
3580                    ca_cert.as_ptr(),
3581                    ca_cert.len() as i32
3582                ))?;
3583            } else {
3584                esp_eap_client_clear_ca_cert();
3585            }
3586
3587            if let Some((cert, key, password)) = config.certificate_and_key {
3588                let (pwd, pwd_len) = if let Some(pwd) = password {
3589                    (pwd.as_ptr(), pwd.len() as i32)
3590                } else {
3591                    (core::ptr::null(), 0)
3592                };
3593
3594                esp_wifi_result!(esp_eap_client_set_certificate_and_key(
3595                    cert.as_ptr(),
3596                    cert.len() as i32,
3597                    key.as_ptr(),
3598                    key.len() as i32,
3599                    pwd,
3600                    pwd_len,
3601                ))?;
3602            } else {
3603                esp_eap_client_clear_certificate_and_key();
3604            }
3605
3606            if let Some(cfg) = &config.eap_fast_config {
3607                let params = esp_eap_fast_config {
3608                    fast_provisioning: cfg.fast_provisioning as i32,
3609                    fast_max_pac_list_len: cfg.fast_max_pac_list_len as i32,
3610                    fast_pac_format_binary: cfg.fast_pac_format_binary,
3611                };
3612                esp_wifi_result!(esp_eap_client_set_fast_params(params))?;
3613            }
3614
3615            esp_wifi_result!(esp_eap_client_set_disable_time_check(!&config.time_check))?;
3616
3617            // esp_eap_client_set_suiteb_192bit_certification unsupported because we build
3618            // without MBEDTLS
3619
3620            // esp_eap_client_use_default_cert_bundle unsupported because we build without
3621            // MBEDTLS
3622
3623            esp_wifi_result!(esp_wifi_sta_enterprise_enable())?;
3624
3625            Ok(())
3626        }
3627    }
3628}
3629
3630impl WifiEvent {
3631    pub(crate) fn waker(&self) -> &'static AtomicWaker {
3632        // for now use only one waker for all events
3633        // if that ever becomes a problem we might want to pick some events to use their
3634        // own
3635        static WAKER: AtomicWaker = AtomicWaker::new();
3636        &WAKER
3637    }
3638}
3639
3640#[must_use = "futures do nothing unless you `.await` or poll them"]
3641pub(crate) struct WifiEventFuture {
3642    event: WifiEvent,
3643}
3644
3645impl WifiEventFuture {
3646    /// Creates a new `Future` for the specified Wi-Fi event.
3647    pub fn new(event: WifiEvent) -> Self {
3648        Self { event }
3649    }
3650}
3651
3652impl core::future::Future for WifiEventFuture {
3653    type Output = ();
3654
3655    fn poll(
3656        self: core::pin::Pin<&mut Self>,
3657        cx: &mut core::task::Context<'_>,
3658    ) -> Poll<Self::Output> {
3659        self.event.waker().register(cx.waker());
3660        if WIFI_EVENTS.with(|events| events.remove(self.event)) {
3661            Poll::Ready(())
3662        } else {
3663            Poll::Pending
3664        }
3665    }
3666}
3667
3668#[must_use = "futures do nothing unless you `.await` or poll them"]
3669pub(crate) struct MultiWifiEventFuture {
3670    event: EnumSet<WifiEvent>,
3671}
3672
3673impl MultiWifiEventFuture {
3674    /// Creates a new `Future` for the specified set of Wi-Fi events.
3675    pub fn new(event: EnumSet<WifiEvent>) -> Self {
3676        Self { event }
3677    }
3678}
3679
3680impl core::future::Future for MultiWifiEventFuture {
3681    type Output = EnumSet<WifiEvent>;
3682
3683    fn poll(
3684        self: core::pin::Pin<&mut Self>,
3685        cx: &mut core::task::Context<'_>,
3686    ) -> Poll<Self::Output> {
3687        let output = WIFI_EVENTS.with(|events| {
3688            let active = events.intersection(self.event);
3689            events.remove_all(active);
3690            active
3691        });
3692        if output.is_empty() {
3693            for event in self.event.iter() {
3694                event.waker().register(cx.waker());
3695            }
3696
3697            Poll::Pending
3698        } else {
3699            Poll::Ready(output)
3700        }
3701    }
3702}