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