esp_wifi/wifi/
mod.rs

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