Skip to main content

esp_radio/wifi/
mod.rs

1//! # Wi-Fi (Station, Access Point and Station/AP-coexistence)
2//!
3//! ## Introduction
4//!
5//! The Wi-Fi module provides support for configuring and monitoring the Wi-Fi networking
6//! functionality. This includes configuration for:
7#![doc = concat!("- Station mode (aka STA mode or Wi-Fi client mode). ", chip_pretty!(), " connects to an access point.")]
8#![doc = concat!("- AP mode (aka Soft-AP mode or Access Point mode). Stations connect to the ", chip_pretty!(),".")]
9#![doc = concat!("- Station/AP-coexistence mode (", chip_pretty!(), " is concurrently an access point and a station connected to another access point).")]
10//! - Various security modes for the above (WPA, WPA2, ... Please note that WPA3 is currently not
11//!   supported)
12//! - Scanning for access points (active & passive scanning).
13//! - Promiscuous mode for monitoring of IEEE802.11 Wi-Fi packets.
14//!
15//! ## Expected heap memory usage
16//!
17//! These are numbers measured via `esp-alloc`'s "internal-heap-stats" feature.
18//!
19//! You can easily reproduce these measurements with your own application by checking the
20//! `max_usage`.
21//!
22//! Please note that for these measurements the default [ControllerConfig] values are used.
23//! Changing these (especially queue sizes) will change the results.
24//! Also the amount of used memory varies between different targets.
25//!
26//! * Station: 47 - 57k
27//! * Open Access Point: 53 - 63k
28//!
29//! ## Wi-Fi performance considerations
30//!
31//! The default configuration is quite conservative to reduce power and memory consumption.
32//!
33//! There are a number of settings which influence the general performance (at the cost of memory
34//! usage).
35//!
36//! Optimal settings are chip and applications specific. You can get inspiration from the [ESP-IDF examples](https://github.com/espressif/esp-idf/tree/release/v5.3/examples/wifi/iperf)
37//!
38//! Please note that the configuration keys are usually named slightly different and not all
39//! configuration keys apply.
40//!
41//! ## Troubleshooting
42//!
43//! ### Connection failures on boards with a weak antenna or in a poor RF environment
44//!
45//! The default maximum TX power is 20 (5dBm) on the 0.25dBm scale. The optimal value is
46//! board-dependent: if connections are unreliable, try adjusting it via
47//! `WifiController::set_max_tx_power` (requires the `unstable` feature) using a value in the
48//! range [8, 84]. Note that values above roughly 65 (~16dBm) have been reported to cause
49//! authentication failures on some hardware, so setting it to the maximum is not always better.
50
51use alloc::{borrow::ToOwned, collections::vec_deque::VecDeque, str, vec::Vec};
52use core::{
53    fmt::{Debug, Write},
54    marker::PhantomData,
55    mem::MaybeUninit,
56    ptr::addr_of,
57};
58
59use docsplay::Display;
60use embassy_sync::{blocking_mutex::raw::NoopRawMutex, waitqueue::GenericAtomicWaker};
61use enumset::{EnumSet, EnumSetType};
62use esp_config::esp_config_int;
63use esp_hal::system::Cpu;
64#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
65use esp_hal::time::{Duration, Instant};
66use esp_sync::NonReentrantMutex;
67use event::EVENT_CHANNEL;
68use portable_atomic::{AtomicU8, AtomicUsize, Ordering};
69use procmacros::BuilderLite;
70
71pub(crate) use self::os_adapter::*;
72#[cfg(all(feature = "sniffer", feature = "unstable"))]
73#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
74use self::sniffer::Sniffer;
75#[cfg(feature = "wifi-eap")]
76use self::sta::eap::EapStationConfig;
77use self::{
78    ap::{AccessPointConfig, AccessPointInfo, convert_ap_info},
79    private::PacketBuffer,
80    scan::{FreeApListOnDrop, ScanConfig, ScanResults, ScanTypeConfig},
81    sta::StationConfig,
82    state::*,
83};
84use crate::{
85    RadioRefGuard,
86    asynch::AtomicWaker,
87    hal::ram,
88    refcount::Refcount,
89    sys::{
90        c_types,
91        include::{self, *},
92    },
93    wifi::event::{EventInfo, WifiEvent},
94};
95pub mod ap;
96
97unstable_module!(
98    #[cfg(feature = "csi")]
99    #[cfg_attr(docsrs, doc(cfg(feature = "csi")))]
100    pub mod csi;
101    pub mod event;
102    #[cfg(feature = "sniffer")]
103    #[cfg_attr(docsrs, doc(cfg(feature = "sniffer")))]
104    pub mod sniffer;
105);
106
107pub mod scan;
108pub mod sta;
109
110pub(crate) mod os_adapter;
111pub(crate) mod state;
112
113#[cfg(not(esp32))]
114mod ftm_calibration;
115mod internal;
116
117const MTU: usize = esp_config_int!(usize, "ESP_RADIO_CONFIG_WIFI_MTU");
118
119// The total hardware encryption key slots available that are shared between
120// ESP-NOW encrypted peers and the AP connections.
121// See https://github.com/espressif/esp-idf/blob/master/components/esp_wifi/Kconfig#L589
122#[cfg(esp32c2)]
123const TOTAL_HW_ENCRYPT_KEYS: u8 = 4;
124#[cfg(not(esp32c2))]
125const TOTAL_HW_ENCRYPT_KEYS: u8 = 17;
126
127/// The link state of a network device.
128#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Hash)]
129#[cfg_attr(feature = "defmt", derive(defmt::Format))]
130enum LinkState {
131    /// The link is down.
132    #[default]
133    Down,
134    /// The link is up.
135    Up,
136}
137
138/// Supported Wi-Fi authentication methods.
139#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Hash)]
140#[cfg_attr(feature = "defmt", derive(defmt::Format))]
141#[non_exhaustive]
142pub enum AuthenticationMethod {
143    /// No authentication (open network).
144    None,
145
146    /// Wired Equivalent Privacy (WEP) authentication.
147    Wep,
148
149    /// Wi-Fi Protected Access (WPA) authentication.
150    Wpa,
151
152    /// Wi-Fi Protected Access 2 (WPA2) Personal authentication (default).
153    #[default]
154    Wpa2Personal,
155
156    /// WPA/WPA2 Personal authentication (supports both).
157    WpaWpa2Personal,
158
159    /// WPA2 Enterprise authentication.
160    Wpa2Enterprise,
161
162    /// WPA3 Personal authentication.
163    Wpa3Personal,
164
165    /// WPA2/WPA3 Personal authentication (supports both).
166    Wpa2Wpa3Personal,
167
168    /// WLAN Authentication and Privacy Infrastructure (WAPI).
169    WapiPersonal,
170
171    /// Opportunistic Wireless Encryption (OWE)
172    Owe,
173
174    /// WPA3 Enterprise Suite B 192-bit Encryption
175    Wpa3EntSuiteB192Bit,
176
177    /// This authentication mode will yield same result as [AuthenticationMethod::Wpa3Personal] and
178    /// is not recommended to be used. It will be deprecated in future, please use
179    /// [AuthenticationMethod::Wpa3Personal] instead.
180    Wpa3ExtPsk,
181
182    /// This authentication mode will yield same result as [AuthenticationMethod::Wpa3Personal] and
183    /// is not recommended to be used. It will be deprecated in future, please use
184    /// [AuthenticationMethod::Wpa3Personal] instead.
185    Wpa3ExtPskMixed,
186
187    /// Wi-Fi DPP / Wi-Fi Easy Connect
188    Dpp,
189
190    /// WPA3-Enterprise Only Mode
191    Wpa3Enterprise,
192
193    /// WPA3-Enterprise Transition Mode
194    Wpa2Wpa3Enterprise,
195
196    /// WPA-Enterprise security
197    WpaEnterprise,
198}
199
200/// Supported Wi-Fi protocols for each band.
201#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, BuilderLite)]
202#[cfg_attr(feature = "defmt", derive(defmt::Format))]
203#[non_exhaustive]
204pub struct Protocols {
205    /// Protocol for 2.4 GHz band.
206    _2_4: EnumSet<Protocol>,
207    /// Protocol for 5 GHz band.
208    #[cfg(wifi_has_5g)]
209    _5: EnumSet<Protocol>,
210}
211
212impl Default for Protocols {
213    fn default() -> Self {
214        Self {
215            _2_4: Protocol::B | Protocol::G | Protocol::N,
216            #[cfg(wifi_has_5g)]
217            _5: Protocol::AC | Protocol::A | Protocol::AX,
218        }
219    }
220}
221
222impl Protocols {
223    fn to_raw(self) -> wifi_protocols_t {
224        wifi_protocols_t {
225            ghz_2g: to_mask(self._2_4),
226            #[cfg(wifi_has_5g)]
227            ghz_5g: to_mask(self._5),
228            #[cfg(not(wifi_has_5g))]
229            ghz_5g: 0,
230        }
231    }
232}
233
234#[cfg_attr(docsrs, procmacros::doc_replace(
235    "hint_5g" => {
236        cfg(wifi_has_5g) => "The default protocol is AC/A/AX for band mode 5G.",
237        _ => ""
238    },
239))]
240/// Supported Wi-Fi protocols.
241///
242/// The default protocol is B/G/N for band mode 2.4G.
243/// # {hint_5g}
244#[derive(Debug, PartialOrd, Hash, EnumSetType)]
245#[cfg_attr(feature = "defmt", derive(defmt::Format))]
246#[non_exhaustive]
247pub enum Protocol {
248    /// 802.11b protocol
249    B,
250
251    /// 802.11g protocol
252    G,
253
254    /// 802.11n protocol
255    N,
256
257    /// Low Rate protocol
258    LR,
259
260    /// 802.11a protocol
261    A,
262
263    /// 802.11ac protocol
264    AC,
265
266    /// 802.11ax protocol
267    AX,
268}
269
270impl Protocol {
271    fn to_mask(self) -> u16 {
272        let mask = match self {
273            Protocol::B => WIFI_PROTOCOL_11B,
274            Protocol::G => WIFI_PROTOCOL_11G,
275            Protocol::N => WIFI_PROTOCOL_11N,
276            Protocol::LR => WIFI_PROTOCOL_LR,
277            Protocol::A => WIFI_PROTOCOL_11A,
278            Protocol::AC => WIFI_PROTOCOL_11AC,
279            Protocol::AX => WIFI_PROTOCOL_11AX,
280        };
281        mask as _
282    }
283}
284
285fn to_mask(protocols: EnumSet<Protocol>) -> u16 {
286    protocols.iter().fold(0, |acc, p| acc | p.to_mask())
287}
288
289/// Secondary Wi-Fi channels.
290#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Hash)]
291#[cfg_attr(feature = "defmt", derive(defmt::Format))]
292pub enum SecondaryChannel {
293    /// No secondary channel (default).
294    #[default]
295    None,
296
297    /// Secondary channel is above the primary channel.
298    Above,
299
300    /// Secondary channel is below the primary channel.
301    Below,
302}
303
304impl SecondaryChannel {
305    fn from_raw(raw: u32) -> Self {
306        match raw {
307            0 => SecondaryChannel::None,
308            1 => SecondaryChannel::Above,
309            2 => SecondaryChannel::Below,
310            _ => panic!("Invalid secondary channel value: {}", raw),
311        }
312    }
313
314    #[cfg(any(feature = "sniffer", feature = "esp-now"))]
315    fn from_raw_or_default(raw: u32) -> Self {
316        match raw {
317            0 => SecondaryChannel::None,
318            1 => SecondaryChannel::Above,
319            2 => SecondaryChannel::Below,
320            _ => SecondaryChannel::None,
321        }
322    }
323}
324
325#[cfg_attr(docsrs, procmacros::doc_replace(
326    "default_band_mode" => {
327        cfg(wifi_has_5g) => "BandMode::Auto",
328        _ => "BandMode::_2_4G"
329    },
330))]
331/// Wi-Fi band mode.
332///
333/// The default is [`__default_band_mode__`].
334#[allow(clippy::large_enum_variant)]
335#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
336#[cfg_attr(feature = "defmt", derive(defmt::Format))]
337#[non_exhaustive]
338pub enum BandMode {
339    /// Wi-Fi band mode is 2.4 GHz only.
340    #[cfg_attr(not(wifi_has_5g), default)]
341    _2_4G,
342    /// Wi-Fi band mode is 5 GHz only.
343    #[cfg(wifi_has_5g)]
344    _5G,
345    /// Wi-Fi band mode is 2.4 GHz + 5 GHz.
346    #[cfg_attr(wifi_has_5g, default)]
347    #[cfg(wifi_has_5g)]
348    Auto,
349}
350
351impl BandMode {
352    fn to_raw(&self) -> u32 {
353        match self {
354            BandMode::_2_4G => wifi_band_mode_t_WIFI_BAND_MODE_2G_ONLY,
355            #[cfg(wifi_has_5g)]
356            BandMode::_5G => wifi_band_mode_t_WIFI_BAND_MODE_5G_ONLY,
357            #[cfg(wifi_has_5g)]
358            BandMode::Auto => wifi_band_mode_t_WIFI_BAND_MODE_AUTO,
359        }
360    }
361}
362
363/// Configuration of Wi-Fi operation mode.
364#[allow(clippy::large_enum_variant)]
365#[derive(Clone, Debug, PartialEq, Eq, Hash)]
366#[cfg_attr(feature = "defmt", derive(defmt::Format))]
367#[non_exhaustive]
368pub enum Config {
369    /// Station configuration.
370    Station(StationConfig),
371
372    /// Access point configuration.
373    AccessPoint(AccessPointConfig),
374
375    /// Simultaneous station and access point configuration.
376    AccessPointStation(StationConfig, AccessPointConfig),
377
378    /// EAP station configuration for enterprise Wi-Fi.
379    #[cfg(feature = "wifi-eap")]
380    EapStation(EapStationConfig),
381}
382
383impl Config {
384    fn validate(&self) -> Result<(), WifiError> {
385        match self {
386            Config::Station(station_configuration) => station_configuration.validate(),
387            Config::AccessPoint(access_point_configuration) => {
388                access_point_configuration.validate()
389            }
390            Config::AccessPointStation(station_configuration, access_point_configuration) => {
391                station_configuration.validate()?;
392                access_point_configuration.validate()
393            }
394            #[cfg(feature = "wifi-eap")]
395            Config::EapStation(eap_station_configuration) => eap_station_configuration.validate(),
396        }
397    }
398}
399
400impl AuthenticationMethod {
401    fn to_raw(self) -> wifi_auth_mode_t {
402        match self {
403            AuthenticationMethod::None => include::wifi_auth_mode_t_WIFI_AUTH_OPEN,
404            AuthenticationMethod::Wep => include::wifi_auth_mode_t_WIFI_AUTH_WEP,
405            AuthenticationMethod::Wpa => include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK,
406            AuthenticationMethod::Wpa2Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK,
407            AuthenticationMethod::WpaWpa2Personal => {
408                include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK
409            }
410            AuthenticationMethod::Wpa2Enterprise => {
411                include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE
412            }
413            AuthenticationMethod::Wpa3Personal => include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK,
414            AuthenticationMethod::Wpa2Wpa3Personal => {
415                include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK
416            }
417            AuthenticationMethod::WapiPersonal => include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK,
418            AuthenticationMethod::Owe => include::wifi_auth_mode_t_WIFI_AUTH_OWE,
419            AuthenticationMethod::Wpa3EntSuiteB192Bit => {
420                include::wifi_auth_mode_t_WIFI_AUTH_WPA3_ENT_192
421            }
422            // Deprecated Ext-PSK variants have no dedicated IDF auth mode.
423            AuthenticationMethod::Wpa3ExtPsk | AuthenticationMethod::Wpa3ExtPskMixed => {
424                include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK
425            }
426            AuthenticationMethod::Dpp => include::wifi_auth_mode_t_WIFI_AUTH_DPP,
427            AuthenticationMethod::Wpa3Enterprise => {
428                include::wifi_auth_mode_t_WIFI_AUTH_WPA3_ENTERPRISE
429            }
430            AuthenticationMethod::Wpa2Wpa3Enterprise => {
431                include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_ENTERPRISE
432            }
433            AuthenticationMethod::WpaEnterprise => {
434                include::wifi_auth_mode_t_WIFI_AUTH_WPA_ENTERPRISE
435            }
436        }
437    }
438
439    fn from_raw(raw: wifi_auth_mode_t) -> Self {
440        match raw {
441            include::wifi_auth_mode_t_WIFI_AUTH_OPEN => AuthenticationMethod::None,
442            include::wifi_auth_mode_t_WIFI_AUTH_WEP => AuthenticationMethod::Wep,
443            include::wifi_auth_mode_t_WIFI_AUTH_WPA_PSK => AuthenticationMethod::Wpa,
444            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_PSK => AuthenticationMethod::Wpa2Personal,
445            include::wifi_auth_mode_t_WIFI_AUTH_WPA_WPA2_PSK => {
446                AuthenticationMethod::WpaWpa2Personal
447            }
448            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_ENTERPRISE => {
449                AuthenticationMethod::Wpa2Enterprise
450            }
451            include::wifi_auth_mode_t_WIFI_AUTH_WPA3_PSK => AuthenticationMethod::Wpa3Personal,
452            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_PSK => {
453                AuthenticationMethod::Wpa2Wpa3Personal
454            }
455            include::wifi_auth_mode_t_WIFI_AUTH_WAPI_PSK => AuthenticationMethod::WapiPersonal,
456            include::wifi_auth_mode_t_WIFI_AUTH_OWE => AuthenticationMethod::Owe,
457            include::wifi_auth_mode_t_WIFI_AUTH_WPA3_ENT_192 => {
458                AuthenticationMethod::Wpa3EntSuiteB192Bit
459            }
460            // Unused IDF auth-mode slots; same as the Ext-PSK write path.
461            include::wifi_auth_mode_t_WIFI_AUTH_DUMMY_1
462            | include::wifi_auth_mode_t_WIFI_AUTH_DUMMY_2 => AuthenticationMethod::Wpa3Personal,
463            include::wifi_auth_mode_t_WIFI_AUTH_DPP => AuthenticationMethod::Dpp,
464            include::wifi_auth_mode_t_WIFI_AUTH_WPA3_ENTERPRISE => {
465                AuthenticationMethod::Wpa3Enterprise
466            }
467            include::wifi_auth_mode_t_WIFI_AUTH_WPA2_WPA3_ENTERPRISE => {
468                AuthenticationMethod::Wpa2Wpa3Enterprise
469            }
470            include::wifi_auth_mode_t_WIFI_AUTH_WPA_ENTERPRISE => {
471                AuthenticationMethod::WpaEnterprise
472            }
473            // we const-assert we know all the auth-methods the wifi driver knows and it shouldn't
474            // return anything else.
475            //
476            // In fact from observation the drivers will return
477            // `wifi_auth_mode_t_WIFI_AUTH_OPEN` if the method is unsupported (e.g. any WPA3 in our
478            // case, since the supplicant isn't compiled to support it)
479            _ => AuthenticationMethod::None,
480        }
481    }
482}
483
484/// Wi-Fi Mode (Station and/or AccessPoint).
485#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
486#[cfg_attr(feature = "defmt", derive(defmt::Format))]
487#[non_exhaustive]
488enum WifiMode {
489    /// Station mode.
490    Station,
491    /// Access Point mode.
492    AccessPoint,
493    /// Both Access Point and Station modes.
494    AccessPointStation,
495}
496
497impl WifiMode {
498    pub(crate) fn current() -> Result<Self, WifiError> {
499        let mut mode = wifi_mode_t_WIFI_MODE_NULL;
500        esp_wifi_result!(unsafe { esp_wifi_get_mode(&mut mode) })?;
501
502        Ok(Self::from_raw(mode))
503    }
504
505    /// Returns true if this mode works as a station.
506    fn is_station(&self) -> bool {
507        match self {
508            Self::Station | Self::AccessPointStation => true,
509            Self::AccessPoint => false,
510        }
511    }
512
513    /// Returns true if this mode works as an access point.
514    fn is_access_point(&self) -> bool {
515        match self {
516            Self::Station => false,
517            Self::AccessPoint | Self::AccessPointStation => true,
518        }
519    }
520
521    /// Creates a `WifiMode` from a raw `wifi_mode_t` value.
522    fn from_raw(value: wifi_mode_t) -> Self {
523        #[allow(non_upper_case_globals)]
524        match value {
525            include::wifi_mode_t_WIFI_MODE_STA => Self::Station,
526            include::wifi_mode_t_WIFI_MODE_AP => Self::AccessPoint,
527            include::wifi_mode_t_WIFI_MODE_APSTA => Self::AccessPointStation,
528            _ => panic!("Invalid wifi mode value: {}", value),
529        }
530    }
531}
532
533impl From<&Config> for WifiMode {
534    fn from(config: &Config) -> Self {
535        match config {
536            Config::AccessPoint(_) => Self::AccessPoint,
537            Config::Station(_) => Self::Station,
538            Config::AccessPointStation(_, _) => Self::AccessPointStation,
539            #[cfg(feature = "wifi-eap")]
540            Config::EapStation(_) => Self::Station,
541        }
542    }
543}
544
545/// Reason for disconnection.
546#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
547#[cfg_attr(feature = "defmt", derive(defmt::Format))]
548#[non_exhaustive]
549pub enum DisconnectReason {
550    /// Unspecified reason
551    Unspecified,
552    /// Authentication expired
553    AuthenticationExpired,
554    /// Deauthentication due to leaving
555    AuthenticationLeave,
556    /// Disassociated due to inactivity
557    DisassociatedDueToInactivity,
558    /// Too many associated stations
559    AssociationTooMany,
560    /// Class 2 frame received from non authenticated station
561    Class2FrameFromNonAuthenticatedStation,
562    /// Class 3 frame received from non associated station
563    Class3FrameFromNonAssociatedStation,
564    /// Disassociated due to leaving
565    AssociationLeave,
566    /// Association but not authenticated
567    AssociationNotAuthenticated,
568    /// Disassociated due to poor power capability
569    DisassociatedPowerCapabilityBad,
570    /// Disassociated due to unsupported channel
571    DisassociatedUnsupportedChannel,
572    /// Disassociated due to BSS transition
573    BssTransitionDisassociated,
574    /// Invalid Information Element (IE)
575    IeInvalid,
576    /// MIC failure
577    MicFailure,
578    /// 4-way handshake timeout
579    FourWayHandshakeTimeout,
580    /// Group key update timeout
581    GroupKeyUpdateTimeout,
582    /// IE differs in 4-way handshake
583    IeIn4wayDiffers,
584    /// Invalid group cipher
585    GroupCipherInvalid,
586    /// Invalid pairwise cipher
587    PairwiseCipherInvalid,
588    /// Invalid AKMP
589    AkmpInvalid,
590    /// Unsupported RSN IE version
591    UnsupportedRsnIeVersion,
592    /// Invalid RSN IE capabilities
593    InvalidRsnIeCapabilities,
594    /// 802.1X authentication failed
595    _802_1xAuthenticationFailed,
596    /// Cipher suite rejected
597    CipherSuiteRejected,
598    /// TDLS peer unreachable
599    TdlsPeerUnreachable,
600    /// TDLS unspecified
601    TdlsUnspecified,
602    /// SSP requested disassociation
603    SspRequestedDisassociation,
604    /// No SSP roaming agreement
605    NoSspRoamingAgreement,
606    /// Bad cipher or AKM
607    BadCipherOrAkm,
608    /// Not authorized in this location
609    NotAuthorizedThisLocation,
610    /// Service change precludes TS
611    ServiceChangePercludesTs,
612    /// Unspecified QoS reason
613    UnspecifiedQos,
614    /// Not enough bandwidth
615    NotEnoughBandwidth,
616    /// Missing ACKs
617    MissingAcks,
618    /// Exceeded TXOP
619    ExceededTxOp,
620    /// Station leaving
621    StationLeaving,
622    /// End of Block Ack (BA)
623    EndBlockAck,
624    /// Unknown Block Ack (BA)
625    UnknownBlockAck,
626    /// Timeout
627    Timeout,
628    /// Peer initiated disassociation
629    PeerInitiated,
630    /// Access point initiated disassociation
631    AccessPointInitiatedDisassociation,
632    /// Invalid FT action frame count
633    InvalidFtActionFrameCount,
634    /// Invalid PMKID
635    InvalidPmkid,
636    /// Invalid MDE
637    InvalidMde,
638    /// Invalid FTE
639    InvalidFte,
640    /// Transmission link establishment failed
641    TransmissionLinkEstablishmentFailed,
642    /// Alternative channel occupied
643    AlterativeChannelOccupied,
644    /// Beacon timeout
645    BeaconTimeout,
646    /// No access point found
647    NoAccessPointFound,
648    /// Authentication failed
649    AuthenticationFailed,
650    /// Association failed
651    AssociationFailed,
652    /// Handshake timeout
653    HandshakeTimeout,
654    /// Connection failed
655    ConnectionFailed,
656    /// AP TSF reset
657    AccessPointTsfReset,
658    /// Roaming
659    Roaming,
660    /// Association comeback time too long
661    AssociationComebackTimeTooLong,
662    /// SA query timeout
663    SaQueryTimeout,
664    /// No AP found with compatible security
665    NoAccessPointFoundWithCompatibleSecurity,
666    /// No AP found in auth mode threshold
667    NoAccessPointFoundInAuthmodeThreshold,
668    /// No AP found in RSSI threshold
669    NoAccessPointFoundInRssiThreshold,
670}
671
672impl DisconnectReason {
673    fn from_raw(id: u16) -> Self {
674        match id {
675            1 => Self::Unspecified,
676            2 => Self::AuthenticationExpired,
677            3 => Self::AuthenticationLeave,
678            4 => Self::DisassociatedDueToInactivity,
679            5 => Self::AssociationTooMany,
680            6 => Self::Class2FrameFromNonAuthenticatedStation,
681            7 => Self::Class3FrameFromNonAssociatedStation,
682            8 => Self::AssociationLeave,
683            9 => Self::AssociationNotAuthenticated,
684            10 => Self::DisassociatedPowerCapabilityBad,
685            11 => Self::DisassociatedUnsupportedChannel,
686            12 => Self::BssTransitionDisassociated,
687            13 => Self::IeInvalid,
688            14 => Self::MicFailure,
689            15 => Self::FourWayHandshakeTimeout,
690            16 => Self::GroupKeyUpdateTimeout,
691            17 => Self::IeIn4wayDiffers,
692            18 => Self::GroupCipherInvalid,
693            19 => Self::PairwiseCipherInvalid,
694            20 => Self::AkmpInvalid,
695            21 => Self::UnsupportedRsnIeVersion,
696            22 => Self::InvalidRsnIeCapabilities,
697            23 => Self::_802_1xAuthenticationFailed,
698            24 => Self::CipherSuiteRejected,
699            25 => Self::TdlsPeerUnreachable,
700            26 => Self::TdlsUnspecified,
701            27 => Self::SspRequestedDisassociation,
702            28 => Self::NoSspRoamingAgreement,
703            29 => Self::BadCipherOrAkm,
704            30 => Self::NotAuthorizedThisLocation,
705            31 => Self::ServiceChangePercludesTs,
706            32 => Self::UnspecifiedQos,
707            33 => Self::NotEnoughBandwidth,
708            34 => Self::MissingAcks,
709            35 => Self::ExceededTxOp,
710            36 => Self::StationLeaving,
711            37 => Self::EndBlockAck,
712            38 => Self::UnknownBlockAck,
713            39 => Self::Timeout,
714            46 => Self::PeerInitiated,
715            47 => Self::AccessPointInitiatedDisassociation,
716            48 => Self::InvalidFtActionFrameCount,
717            49 => Self::InvalidPmkid,
718            50 => Self::InvalidMde,
719            51 => Self::InvalidFte,
720            67 => Self::TransmissionLinkEstablishmentFailed,
721            68 => Self::AlterativeChannelOccupied,
722            200 => Self::BeaconTimeout,
723            201 => Self::NoAccessPointFound,
724            202 => Self::AuthenticationFailed,
725            203 => Self::AssociationFailed,
726            204 => Self::HandshakeTimeout,
727            205 => Self::ConnectionFailed,
728            206 => Self::AccessPointTsfReset,
729            207 => Self::Roaming,
730            208 => Self::AssociationComebackTimeTooLong,
731            209 => Self::SaQueryTimeout,
732            210 => Self::NoAccessPointFoundWithCompatibleSecurity,
733            211 => Self::NoAccessPointFoundInAuthmodeThreshold,
734            212 => Self::NoAccessPointFoundInRssiThreshold,
735            _ => Self::Unspecified,
736        }
737    }
738}
739
740/// A Wi-Fi SSID.
741///
742/// Can be up to 32 bytes long (i.e. not characters). Longer SSIDs are rejected.
743#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
744pub struct Ssid {
745    ssid: [u8; 32],
746    len: u8,
747}
748
749impl Ssid {
750    pub(crate) fn new(ssid: &str) -> Result<Self, WifiError> {
751        let mut ssid_bytes = [0u8; 32];
752        let bytes = ssid.as_bytes();
753
754        if bytes.len() > 32 {
755            warn!("SSID is longer than 32 bytes");
756            return Err(WifiError::InvalidSsid);
757        }
758
759        let len = bytes.len();
760        ssid_bytes[..len].copy_from_slice(bytes);
761
762        Self::from_raw(&ssid_bytes, len as u8)
763    }
764
765    pub(crate) fn from_raw(ssid: &[u8], len: u8) -> Result<Self, WifiError> {
766        let len = len as usize;
767        if len > 32 {
768            warn!("SSID is longer than 32 bytes");
769            return Err(WifiError::InvalidSsid);
770        }
771        if ssid.len() < len {
772            warn!("SSID buffer shorter than reported length");
773            return Err(WifiError::InvalidSsid);
774        }
775
776        let mut ssid_bytes = [0u8; 32];
777        ssid_bytes[..len].copy_from_slice(&ssid[..len]);
778
779        Ok(Self {
780            ssid: ssid_bytes,
781            len: len as u8,
782        })
783    }
784
785    pub(crate) fn as_bytes(&self) -> &[u8] {
786        &self.ssid[..self.len as usize]
787    }
788
789    /// The length (in bytes) of the SSID.
790    pub fn len(&self) -> usize {
791        self.len as usize
792    }
793
794    /// Returns true if the SSID is empty.
795    pub fn is_empty(&self) -> bool {
796        self.len == 0
797    }
798
799    /// The SSID as a string slice.
800    pub fn as_str(&self) -> &str {
801        let part = &self.ssid[..self.len as usize];
802        match str::from_utf8(part) {
803            Ok(s) => s,
804            Err(e) => {
805                let (valid, _) = part.split_at(e.valid_up_to());
806                unsafe { str::from_utf8_unchecked(valid) }
807            }
808        }
809    }
810}
811
812impl Debug for Ssid {
813    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
814        f.write_char('"')?;
815        f.write_str(self.as_str())?;
816        f.write_char('"')
817    }
818}
819
820#[cfg(feature = "defmt")]
821impl defmt::Format for Ssid {
822    fn format(&self, fmt: defmt::Formatter<'_>) {
823        defmt::write!(fmt, "{}", self.as_str())
824    }
825}
826
827impl TryFrom<alloc::string::String> for Ssid {
828    type Error = WifiError;
829
830    fn try_from(ssid: alloc::string::String) -> Result<Self, Self::Error> {
831        Self::new(&ssid)
832    }
833}
834
835impl TryFrom<&str> for Ssid {
836    type Error = WifiError;
837
838    fn try_from(ssid: &str) -> Result<Self, Self::Error> {
839        Self::new(ssid)
840    }
841}
842
843impl TryFrom<&[u8]> for Ssid {
844    type Error = WifiError;
845
846    fn try_from(ssid: &[u8]) -> Result<Self, Self::Error> {
847        if ssid.len() > 32 {
848            warn!("SSID is longer than 32 bytes");
849            return Err(WifiError::InvalidSsid);
850        }
851
852        Self::from_raw(ssid, ssid.len() as u8)
853    }
854}
855
856/// Authentication Configuration for a Wi-Fi network.
857#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
858#[cfg_attr(feature = "defmt", derive(defmt::Format))]
859#[non_exhaustive]
860pub enum AuthenticationMethodConfig {
861    /// Open authentication.
862    Open,
863
864    /// Wired Equivalent Privacy (WEP) authentication and password.
865    Wep(Password),
866
867    /// Wi-Fi Protected Access (WPA) authentication and password.
868    Wpa(Password),
869
870    /// Wi-Fi Protected Access 2 (WPA2) Personal authentication and password.
871    Wpa2Personal(Password),
872
873    /// WPA/WPA2 Personal authentication and password (supports both).
874    WpaWpa2Personal(Password),
875}
876
877impl AuthenticationMethodConfig {
878    fn auth_method(&self) -> AuthenticationMethod {
879        match self {
880            AuthenticationMethodConfig::Open => AuthenticationMethod::None,
881            AuthenticationMethodConfig::Wep(_) => AuthenticationMethod::Wep,
882            AuthenticationMethodConfig::Wpa(_) => AuthenticationMethod::Wpa,
883            AuthenticationMethodConfig::Wpa2Personal(_) => AuthenticationMethod::Wpa2Personal,
884            AuthenticationMethodConfig::WpaWpa2Personal(_) => AuthenticationMethod::WpaWpa2Personal,
885        }
886    }
887
888    fn password(&self) -> Option<&[u8]> {
889        match self {
890            AuthenticationMethodConfig::Open => None,
891            AuthenticationMethodConfig::Wep(password)
892            | AuthenticationMethodConfig::Wpa(password)
893            | AuthenticationMethodConfig::Wpa2Personal(password)
894            | AuthenticationMethodConfig::WpaWpa2Personal(password) => Some(password.as_bytes()),
895        }
896    }
897}
898
899/// A password.
900///
901/// Can be up to 64 bytes long (i.e. not characters). Longer passwords are rejected.
902///
903/// Only the maximum length is checked here - further constraints depend on the
904/// authentication method (e.g. WPA requires at least 8 bytes, WEP requires
905/// exactly 5 or 13 bytes) and are rejected by the driver when applying the
906/// configuration.
907#[derive(Clone, Copy, PartialEq, Eq, Hash)]
908pub struct Password {
909    password: [u8; 64],
910    len: u8,
911}
912
913impl Password {
914    pub(crate) fn new(password: &str) -> Result<Self, WifiError> {
915        Self::from_raw(password.as_bytes())
916    }
917
918    pub(crate) fn from_raw(password: &[u8]) -> Result<Self, WifiError> {
919        if password.len() > 64 {
920            warn!("Password is longer than 64 bytes");
921            return Err(WifiError::InvalidPassword);
922        }
923
924        let mut pwd_bytes = [0u8; 64];
925        let len = password.len();
926        pwd_bytes[..len].copy_from_slice(password);
927
928        Ok(Self {
929            password: pwd_bytes,
930            len: len as u8,
931        })
932    }
933
934    pub(crate) fn as_bytes(&self) -> &[u8] {
935        &self.password[..self.len as usize]
936    }
937
938    /// The length (in bytes) of the password.
939    pub fn len(&self) -> usize {
940        self.len as usize
941    }
942
943    /// Returns true if the password is empty.
944    pub fn is_empty(&self) -> bool {
945        self.len == 0
946    }
947}
948
949impl Default for Password {
950    fn default() -> Self {
951        Self {
952            password: [0u8; 64],
953            len: 0,
954        }
955    }
956}
957
958impl Debug for Password {
959    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
960        f.write_str("**REDACTED**")
961    }
962}
963
964#[cfg(feature = "defmt")]
965impl defmt::Format for Password {
966    fn format(&self, fmt: defmt::Formatter<'_>) {
967        defmt::write!(fmt, "**REDACTED**")
968    }
969}
970
971impl TryFrom<alloc::string::String> for Password {
972    type Error = WifiError;
973
974    fn try_from(password: alloc::string::String) -> Result<Self, Self::Error> {
975        Self::new(&password)
976    }
977}
978
979impl TryFrom<&str> for Password {
980    type Error = WifiError;
981
982    fn try_from(password: &str) -> Result<Self, Self::Error> {
983        Self::new(password)
984    }
985}
986
987impl TryFrom<&[u8]> for Password {
988    type Error = WifiError;
989
990    fn try_from(password: &[u8]) -> Result<Self, Self::Error> {
991        Self::from_raw(password)
992    }
993}
994
995static TX_QUEUE_SIZE: AtomicUsize = AtomicUsize::new(0);
996
997/// A receive packet queue.
998///
999/// This struct is to encapsulate the queue AND the waker, so waking the waker
1000/// upon receiving a packet does not require another critical section.
1001///
1002/// The struct also uses VecDeque's capacity to avoid storing a copy of the maximum queue length.
1003struct PacketQueue {
1004    queue: VecDeque<PacketBuffer>,
1005
1006    // NoopRawMutex is safe here because we only access the waker in the queue's critical section.
1007    waker: GenericAtomicWaker<NoopRawMutex>,
1008}
1009
1010impl PacketQueue {
1011    const fn new() -> Self {
1012        Self {
1013            queue: VecDeque::new(),
1014            waker: GenericAtomicWaker::new(NoopRawMutex::new()),
1015        }
1016    }
1017
1018    fn change_capacity(&mut self, new_capacity: usize) -> Result<(), WifiError> {
1019        // If we've allocated more memory already than configured, use that instead of shrinking the
1020        // queue. We do not trim the queue if it's over capacity.
1021        let new_capacity = new_capacity.max(self.queue.capacity());
1022        let additional = new_capacity.saturating_sub(self.queue.capacity());
1023        self.queue
1024            .try_reserve_exact(additional)
1025            .map_err(|_| WifiError::OutOfMemory)
1026    }
1027
1028    fn push_back(&mut self, packet: PacketBuffer) -> Result<(), PacketBuffer> {
1029        if self.len() >= self.queue.capacity() {
1030            return Err(packet);
1031        }
1032
1033        self.queue.push_back(packet);
1034        self.waker.wake();
1035
1036        Ok(())
1037    }
1038
1039    fn pop_front(&mut self) -> Option<PacketBuffer> {
1040        self.queue.pop_front()
1041    }
1042
1043    fn len(&self) -> usize {
1044        self.queue.len()
1045    }
1046
1047    fn is_empty(&self) -> bool {
1048        self.queue.is_empty()
1049    }
1050
1051    fn register_waker(&mut self, waker: &core::task::Waker) {
1052        self.waker.register(waker);
1053    }
1054}
1055
1056static DATA_QUEUE_RX_AP: NonReentrantMutex<PacketQueue> =
1057    NonReentrantMutex::new(PacketQueue::new());
1058
1059static DATA_QUEUE_RX_STA: NonReentrantMutex<PacketQueue> =
1060    NonReentrantMutex::new(PacketQueue::new());
1061
1062/// Common errors.
1063#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, Hash)]
1064#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1065#[non_exhaustive]
1066pub enum WifiError {
1067    /// Unsupported operation or mode.
1068    Unsupported,
1069
1070    /// Passed arguments are invalid.
1071    InvalidArguments,
1072
1073    /// An unspecified error occurred in the radio driver.
1074    Other,
1075
1076    /// Out of memory.
1077    OutOfMemory,
1078
1079    /// SSID is invalid.
1080    InvalidSsid,
1081
1082    /// Password is invalid.
1083    InvalidPassword,
1084
1085    /// Station still in disconnect status.
1086    NotConnected,
1087}
1088
1089impl WifiError {
1090    fn from_error_code(code: i32) -> Self {
1091        use crate::sys::include::*;
1092
1093        // `ESP_FAIL` is a generic, unspecified failure. Map it to the opaque
1094        // `Other` variant.
1095        if code == ESP_FAIL {
1096            return WifiError::Other;
1097        }
1098
1099        match code as u32 {
1100            // Meaningful, public mappings. These are ordinary outcomes, so they
1101            // are returned without any logging.
1102            ESP_ERR_NO_MEM => WifiError::OutOfMemory,
1103            ESP_ERR_INVALID_ARG => WifiError::InvalidArguments,
1104            ESP_ERR_WIFI_SSID => WifiError::InvalidSsid,
1105            ESP_ERR_WIFI_PASSWORD => WifiError::InvalidPassword,
1106            ESP_ERR_WIFI_NOT_CONNECT => WifiError::NotConnected,
1107
1108            // Known driver state-machine and timeout codes. These occur in
1109            // perfectly normal operation.
1110            ESP_ERR_WIFI_NOT_INIT
1111            | ESP_ERR_WIFI_NOT_STARTED
1112            | ESP_ERR_WIFI_STATE
1113            | ESP_ERR_WIFI_CONN
1114            | ESP_ERR_WIFI_STOP_STATE
1115            | ESP_ERR_WIFI_TIMEOUT => WifiError::Other,
1116
1117            // Any code we don't recognise: warn so it can be reported, then fall
1118            // back to the opaque variant. We must never panic here - an
1119            // unexpected code from the driver should not bring down the firmware.
1120            _ => {
1121                warn!(
1122                    "Unmapped Wi-Fi error code: {}. Please open an issue at <https://github.com/esp-rs/esp-hal/issues>.",
1123                    code
1124                );
1125                WifiError::Other
1126            }
1127        }
1128    }
1129}
1130
1131impl core::error::Error for WifiError {}
1132
1133/// Errors that can occur during a Wi-Fi connection.
1134#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, Hash)]
1135#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1136#[non_exhaustive]
1137pub enum ConnectionError {
1138    /// The connection failed.
1139    Failed(sta::DisconnectedInfo),
1140
1141    /// A Wi-Fi error occurred.
1142    WifiError(WifiError),
1143}
1144
1145impl From<WifiError> for ConnectionError {
1146    fn from(error: WifiError) -> Self {
1147        ConnectionError::WifiError(error)
1148    }
1149}
1150
1151impl core::error::Error for ConnectionError {}
1152
1153#[cfg(esp32)]
1154fn set_mac_time_update_cb(_wifi: crate::hal::peripherals::WIFI<'_>) {
1155    use crate::sys::include::esp_wifi_internal_update_mac_time;
1156    unsafe {
1157        esp_phy::set_mac_time_update_cb(|duration| {
1158            esp_wifi_internal_update_mac_time(duration.as_micros() as u32);
1159        });
1160    }
1161}
1162
1163pub(crate) fn wifi_init(_wifi: crate::hal::peripherals::WIFI<'_>) -> Result<(), WifiError> {
1164    #[cfg(esp32)]
1165    set_mac_time_update_cb(_wifi);
1166    unsafe {
1167        #[cfg(feature = "coex")]
1168        esp_wifi_result!(coex_init())?;
1169
1170        esp_wifi_result!(esp_wifi_init_internal(addr_of!(internal::G_CONFIG)))?;
1171        esp_wifi_result!(esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_NULL))?;
1172
1173        esp_wifi_result!(esp_supplicant_init())?;
1174
1175        esp_wifi_result!(esp_wifi_set_tx_done_cb(Some(esp_wifi_tx_done_cb)))?;
1176
1177        esp_wifi_result!(esp_wifi_internal_reg_rxcb(
1178            wifi_interface_t_WIFI_IF_STA,
1179            Some(recv_cb_sta)
1180        ))?;
1181
1182        // until we support APSTA we just register the same callback for AP and station
1183        esp_wifi_result!(esp_wifi_internal_reg_rxcb(
1184            wifi_interface_t_WIFI_IF_AP,
1185            Some(recv_cb_ap)
1186        ))?;
1187
1188        Ok(())
1189    }
1190}
1191
1192#[cfg(feature = "coex")]
1193pub(crate) fn coex_initialize() -> i32 {
1194    debug!("call coex-initialize");
1195    unsafe {
1196        let res = crate::sys::include::esp_coex_adapter_register(
1197            core::ptr::addr_of_mut!(internal::G_COEX_ADAPTER_FUNCS).cast(),
1198        );
1199        if res != 0 {
1200            error!("Error: esp_coex_adapter_register {}", res);
1201            return res;
1202        }
1203        let res = crate::sys::include::coex_pre_init();
1204        if res != 0 {
1205            error!("Error: coex_pre_init {}", res);
1206            return res;
1207        }
1208        0
1209    }
1210}
1211
1212pub(crate) unsafe extern "C" fn coex_init() -> i32 {
1213    debug!("coex-init");
1214
1215    cfg_select! {
1216        feature = "coex" => unsafe { crate::sys::include::coex_init() },
1217        _ => 0,
1218    }
1219}
1220
1221fn wifi_deinit() -> Result<(), WifiError> {
1222    esp_wifi_result!(unsafe { esp_wifi_stop() })?;
1223
1224    // Drain RX queues before deinit so that any stale PacketBuffers are freed
1225    // while the driver is still alive. Without this, an Interface that outlives
1226    // the controller could hold PacketBuffers with dangling `eb` pointers.
1227    //
1228    // PacketBuffer::drop must run outside the queue's critical section because it
1229    // calls `esp_wifi_internal_free_rx_buffer`, which takes an internal mutex.
1230    while let Some(packet) = DATA_QUEUE_RX_STA.with(|q| q.pop_front()) {
1231        drop(packet);
1232    }
1233    while let Some(packet) = DATA_QUEUE_RX_AP.with(|q| q.pop_front()) {
1234        drop(packet);
1235    }
1236
1237    esp_wifi_result!(unsafe { esp_wifi_deinit_internal() })?;
1238    esp_wifi_result!(unsafe { esp_supplicant_deinit() })?;
1239    Ok(())
1240}
1241
1242unsafe extern "C" fn recv_cb_sta(
1243    buffer: *mut c_types::c_void,
1244    len: u16,
1245    eb: *mut c_types::c_void,
1246) -> esp_err_t {
1247    let packet = PacketBuffer { buffer, len, eb };
1248    // We must handle the result outside of the lock because
1249    // PacketBuffer::drop must not be called in a critical section.
1250    // Dropping an PacketBuffer will call `esp_wifi_internal_free_rx_buffer`
1251    // which will try to lock an internal mutex. If the mutex is already taken,
1252    // the function will try to trigger a context switch, which will fail if we
1253    // are in an interrupt-free context.
1254    match DATA_QUEUE_RX_STA.with(|queue| queue.push_back(packet)) {
1255        Ok(()) => include::ESP_OK as esp_err_t,
1256        _ => {
1257            debug!("RX QUEUE FULL");
1258            include::ESP_ERR_NO_MEM as esp_err_t
1259        }
1260    }
1261}
1262
1263unsafe extern "C" fn recv_cb_ap(
1264    buffer: *mut c_types::c_void,
1265    len: u16,
1266    eb: *mut c_types::c_void,
1267) -> esp_err_t {
1268    let packet = PacketBuffer { buffer, len, eb };
1269    // We must handle the result outside of the critical section because
1270    // PacketBuffer::drop must not be called in a critical section.
1271    // Dropping an PacketBuffer will call `esp_wifi_internal_free_rx_buffer`
1272    // which will try to lock an internal mutex. If the mutex is already taken,
1273    // the function will try to trigger a context switch, which will fail if we
1274    // are in an interrupt-free context.
1275    match DATA_QUEUE_RX_AP.with(|queue| queue.push_back(packet)) {
1276        Ok(()) => include::ESP_OK as esp_err_t,
1277        _ => {
1278            debug!("RX QUEUE FULL");
1279            include::ESP_ERR_NO_MEM as esp_err_t
1280        }
1281    }
1282}
1283
1284pub(crate) static WIFI_TX_INFLIGHT: AtomicUsize = AtomicUsize::new(0);
1285
1286fn decrement_inflight_counter() {
1287    unwrap!(
1288        WIFI_TX_INFLIGHT.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
1289            Some(x.saturating_sub(1))
1290        })
1291    );
1292}
1293
1294#[ram]
1295unsafe extern "C" fn esp_wifi_tx_done_cb(
1296    _ifidx: u8,
1297    _data: *mut u8,
1298    _data_len: *mut u16,
1299    _tx_status: bool,
1300) {
1301    trace!("esp_wifi_tx_done_cb");
1302
1303    decrement_inflight_counter();
1304
1305    TRANSMIT_WAKER.wake();
1306}
1307
1308pub(crate) fn wifi_start_scan(
1309    block: bool,
1310    ScanConfig {
1311        ssid,
1312        mut bssid,
1313        channel,
1314        show_hidden,
1315        scan_type,
1316        ..
1317    }: ScanConfig,
1318) -> i32 {
1319    scan_type.validate();
1320    let (scan_time, scan_type) = match scan_type {
1321        ScanTypeConfig::Active { min, max } => (
1322            wifi_scan_time_t {
1323                active: wifi_active_scan_time_t {
1324                    min: min.as_millis() as u32,
1325                    max: max.as_millis() as u32,
1326                },
1327                passive: 0,
1328            },
1329            wifi_scan_type_t_WIFI_SCAN_TYPE_ACTIVE,
1330        ),
1331        ScanTypeConfig::Passive(dur) => (
1332            wifi_scan_time_t {
1333                active: wifi_active_scan_time_t { min: 0, max: 0 },
1334                passive: dur.as_millis() as u32,
1335            },
1336            wifi_scan_type_t_WIFI_SCAN_TYPE_PASSIVE,
1337        ),
1338    };
1339
1340    let mut ssid_buf = ssid.map(|m| {
1341        let mut buf = Vec::from_iter(m.as_bytes().to_owned());
1342        buf.push(b'\0');
1343        buf
1344    });
1345
1346    let ssid = ssid_buf
1347        .as_mut()
1348        .map(|e| e.as_mut_ptr())
1349        .unwrap_or_else(core::ptr::null_mut);
1350    let bssid = bssid
1351        .as_mut()
1352        .map(|e| e.as_mut_ptr())
1353        .unwrap_or_else(core::ptr::null_mut);
1354
1355    let scan_config = wifi_scan_config_t {
1356        ssid,
1357        bssid,
1358        channel: channel.unwrap_or(0),
1359        show_hidden,
1360        scan_type,
1361        scan_time,
1362        home_chan_dwell_time: 0,
1363        channel_bitmap: wifi_scan_channel_bitmap_t {
1364            ghz_2_channels: 0,
1365            ghz_5_channels: 0,
1366        },
1367        coex_background_scan: false,
1368    };
1369
1370    unsafe { esp_wifi_scan_start(&scan_config, block) }
1371}
1372
1373mod private {
1374    use super::*;
1375
1376    /// Take care not to drop this while in a critical section.
1377    ///
1378    /// Dropping an PacketBuffer will call
1379    /// `esp_wifi_internal_free_rx_buffer` which will try to lock an
1380    /// internal mutex. If the mutex is already taken, the function will try
1381    /// to trigger a context switch, which will fail if we are in a critical
1382    /// section.
1383    #[derive(Debug)]
1384    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
1385    pub struct PacketBuffer {
1386        pub(crate) buffer: *mut c_types::c_void,
1387        pub(crate) len: u16,
1388        pub(crate) eb: *mut c_types::c_void,
1389    }
1390
1391    unsafe impl Send for PacketBuffer {}
1392
1393    impl Drop for PacketBuffer {
1394        fn drop(&mut self) {
1395            trace!("Dropping PacketBuffer, freeing memory");
1396            unsafe { esp_wifi_internal_free_rx_buffer(self.eb) };
1397        }
1398    }
1399
1400    impl PacketBuffer {
1401        pub fn as_slice_mut(&mut self) -> &mut [u8] {
1402            unsafe { core::slice::from_raw_parts_mut(self.buffer as *mut u8, self.len as usize) }
1403        }
1404    }
1405}
1406
1407/// Wi-Fi interface mode.
1408#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
1409#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1410enum InterfaceType {
1411    /// Station mode.
1412    Station,
1413    /// Access Point mode.
1414    AccessPoint,
1415}
1416
1417impl InterfaceType {
1418    fn mac_address(&self) -> [u8; 6] {
1419        use esp_hal::efuse::InterfaceMacAddress;
1420        let mac = match self {
1421            InterfaceType::Station => {
1422                esp_hal::efuse::interface_mac_address(InterfaceMacAddress::Station)
1423            }
1424            InterfaceType::AccessPoint => {
1425                esp_hal::efuse::interface_mac_address(InterfaceMacAddress::AccessPoint)
1426            }
1427        };
1428
1429        let mut out = [0u8; 6];
1430        out.copy_from_slice(mac.as_bytes());
1431        out
1432    }
1433
1434    fn data_queue_rx(&self) -> &'static NonReentrantMutex<PacketQueue> {
1435        match self {
1436            InterfaceType::Station => &DATA_QUEUE_RX_STA,
1437            InterfaceType::AccessPoint => &DATA_QUEUE_RX_AP,
1438        }
1439    }
1440
1441    fn can_send(&self) -> bool {
1442        WIFI_TX_INFLIGHT.load(Ordering::SeqCst) < TX_QUEUE_SIZE.load(Ordering::Relaxed)
1443    }
1444
1445    fn increase_in_flight_counter(&self) {
1446        WIFI_TX_INFLIGHT.fetch_add(1, Ordering::SeqCst);
1447    }
1448
1449    fn tx_token(&self) -> Option<WifiTxToken> {
1450        if !self.can_send() {
1451            // TODO: perhaps we can use a counting semaphore with a short blocking timeout
1452            crate::preempt::yield_task();
1453        }
1454
1455        if self.can_send() {
1456            // even checking for !Uninitialized would be enough to not crash
1457            if self.link_state() == LinkState::Up {
1458                return Some(WifiTxToken { mode: *self });
1459            }
1460        }
1461
1462        None
1463    }
1464
1465    fn rx_token(&self) -> Option<(WifiRxToken, WifiTxToken)> {
1466        let is_empty = self.data_queue_rx().with(|q| q.is_empty());
1467        if is_empty || !self.can_send() {
1468            // TODO: use an OS queue with a short timeout
1469            crate::preempt::yield_task();
1470        }
1471
1472        let is_empty = is_empty && self.data_queue_rx().with(|q| q.is_empty());
1473
1474        if !is_empty {
1475            self.tx_token().map(|tx| (WifiRxToken { mode: *self }, tx))
1476        } else {
1477            None
1478        }
1479    }
1480
1481    fn interface(&self) -> wifi_interface_t {
1482        match self {
1483            InterfaceType::Station => wifi_interface_t_WIFI_IF_STA,
1484            InterfaceType::AccessPoint => wifi_interface_t_WIFI_IF_AP,
1485        }
1486    }
1487
1488    fn register_transmit_waker(&self, waker: &core::task::Waker) {
1489        TRANSMIT_WAKER.register(waker)
1490    }
1491
1492    fn register_receive_waker(&self, waker: &core::task::Waker) {
1493        self.data_queue_rx().with(|q| q.register_waker(waker));
1494    }
1495
1496    fn register_link_state_waker(&self, waker: &core::task::Waker) {
1497        match self {
1498            InterfaceType::Station => STA_LINK_STATE_WAKER.register(waker),
1499            InterfaceType::AccessPoint => AP_LINK_STATE_WAKER.register(waker),
1500        }
1501    }
1502
1503    fn link_state(&self) -> LinkState {
1504        let is_up = match self {
1505            InterfaceType::Station => {
1506                matches!(station_state(), WifiStationState::Connected)
1507            }
1508            InterfaceType::AccessPoint => {
1509                matches!(access_point_state(), WifiAccessPointState::Started)
1510            }
1511        };
1512
1513        if is_up {
1514            LinkState::Up
1515        } else {
1516            LinkState::Down
1517        }
1518    }
1519}
1520
1521static SINGLETONS: AtomicU8 = AtomicU8::new(0);
1522
1523const STA_BIT: u8 = 1 << 0;
1524const AP_BIT: u8 = 1 << 1;
1525#[cfg(feature = "sniffer")]
1526pub(super) const SNIFFER_BIT: u8 = 1 << 2;
1527
1528pub(super) fn try_acquire(bit: u8) -> bool {
1529    SINGLETONS.fetch_or(bit, Ordering::AcqRel) & bit == 0
1530}
1531
1532pub(super) fn release(bit: u8) {
1533    SINGLETONS.fetch_and(!bit, Ordering::Release);
1534}
1535
1536/// Wi-Fi interface.
1537///
1538/// This implements the `embassy-net-driver` trait for up to three latest versions of that crate,
1539/// and the `xarxa-driver` trait.
1540/// While those crates aren't stable we make an exception here from the [API Guidelines](https://rust-lang.github.io/api-guidelines/necessities.html#c-stable)
1541/// in exposing unstable dependencies.
1542///
1543/// Each interface mode (station, access point) is a singleton — only one
1544/// instance of each can exist at a time. Create interfaces via
1545/// [`Interface::station()`] or [`Interface::access_point()`] before or after
1546/// calling [`WifiController::new()`]. Dropping the interface releases the singleton so it can
1547/// be created again.
1548#[derive(Debug, PartialEq, Eq, Hash)]
1549#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1550pub struct Interface {
1551    mode: InterfaceType,
1552}
1553
1554impl Interface {
1555    /// Creates the station-mode interface.
1556    ///
1557    /// # Panics
1558    ///
1559    /// Panics if a station interface already exists. Use [`try_station()`](Self::try_station)
1560    /// for a non-panicking alternative.
1561    pub fn station() -> Self {
1562        Self::try_station().expect("station interface already taken")
1563    }
1564
1565    /// Tries to create the station-mode interface.
1566    ///
1567    /// Returns `None` if a station interface already exists.
1568    pub fn try_station() -> Option<Self> {
1569        if try_acquire(STA_BIT) {
1570            Some(Self {
1571                mode: InterfaceType::Station,
1572            })
1573        } else {
1574            None
1575        }
1576    }
1577
1578    /// Creates the access-point-mode interface.
1579    ///
1580    /// # Panics
1581    ///
1582    /// Panics if an access-point interface already exists.
1583    /// Use [`try_access_point()`](Self::try_access_point) for a non-panicking alternative.
1584    pub fn access_point() -> Self {
1585        Self::try_access_point().expect("access point interface already taken")
1586    }
1587
1588    /// Tries to create the access-point-mode interface.
1589    ///
1590    /// Returns `None` if an access-point interface already exists.
1591    pub fn try_access_point() -> Option<Self> {
1592        if try_acquire(AP_BIT) {
1593            Some(Self {
1594                mode: InterfaceType::AccessPoint,
1595            })
1596        } else {
1597            None
1598        }
1599    }
1600
1601    #[procmacros::doc_replace]
1602    /// Retrieves the MAC address of the Wi-Fi device.
1603    ///
1604    /// ## Example
1605    ///
1606    /// ```rust,no_run
1607    /// # {before_snippet}
1608    /// let _controller = esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
1609    ///
1610    /// let station = esp_radio::wifi::Interface::station();
1611    /// let mac = station.mac_address();
1612    ///
1613    /// println!("Station MAC: {:02x?}", mac);
1614    /// # {after_snippet}
1615    /// ```
1616    pub fn mac_address(&self) -> [u8; 6] {
1617        self.mode.mac_address()
1618    }
1619
1620    #[doc(hidden)]
1621    /// Receives data from the Wi-Fi device.
1622    pub fn receive(&mut self) -> Option<(WifiRxToken, WifiTxToken)> {
1623        self.mode.rx_token()
1624    }
1625
1626    #[doc(hidden)]
1627    /// Transmits data through the Wi-Fi device.
1628    pub fn transmit(&mut self) -> Option<WifiTxToken> {
1629        self.mode.tx_token()
1630    }
1631}
1632
1633impl Drop for Interface {
1634    fn drop(&mut self) {
1635        let bit = match self.mode {
1636            InterfaceType::Station => STA_BIT,
1637            InterfaceType::AccessPoint => AP_BIT,
1638        };
1639        release(bit);
1640    }
1641}
1642
1643/// Supported Wi-Fi protocols for each band.
1644#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, BuilderLite)]
1645#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1646#[non_exhaustive]
1647pub struct Bandwidths {
1648    /// Bandwidth for 2.4 GHz band.
1649    _2_4: Bandwidth,
1650    /// Bandwidth for 5 GHz band.
1651    #[cfg(wifi_has_5g)]
1652    _5: Bandwidth,
1653}
1654
1655impl Bandwidths {
1656    fn to_raw(self) -> wifi_bandwidths_t {
1657        wifi_bandwidths_t {
1658            ghz_2g: self._2_4.to_raw(),
1659            #[cfg(wifi_has_5g)]
1660            ghz_5g: self._5.to_raw(),
1661            #[cfg(not(wifi_has_5g))]
1662            ghz_5g: 0,
1663        }
1664    }
1665}
1666
1667/// Wi-Fi bandwidth options.
1668#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
1669#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1670#[allow(
1671    clippy::enum_variant_names,
1672    reason = "MHz suffix indicates physical unit."
1673)]
1674#[non_exhaustive]
1675pub enum Bandwidth {
1676    /// 20 MHz bandwidth.
1677    _20MHz,
1678    /// 40 MHz bandwidth.
1679    _40MHz,
1680    /// 80 MHz bandwidth.
1681    _80MHz,
1682    /// 160 MHz bandwidth.
1683    _160MHz,
1684    /// 80+80 MHz bandwidth.
1685    _80_80MHz,
1686}
1687
1688impl Bandwidth {
1689    fn to_raw(self) -> wifi_bandwidth_t {
1690        match self {
1691            Bandwidth::_20MHz => wifi_bandwidth_t_WIFI_BW20,
1692            Bandwidth::_40MHz => wifi_bandwidth_t_WIFI_BW40,
1693            Bandwidth::_80MHz => wifi_bandwidth_t_WIFI_BW80,
1694            Bandwidth::_160MHz => wifi_bandwidth_t_WIFI_BW160,
1695            Bandwidth::_80_80MHz => wifi_bandwidth_t_WIFI_BW80_BW80,
1696        }
1697    }
1698
1699    fn from_raw(raw: wifi_bandwidth_t) -> Self {
1700        match raw {
1701            raw if raw == wifi_bandwidth_t_WIFI_BW20 => Bandwidth::_20MHz,
1702            raw if raw == wifi_bandwidth_t_WIFI_BW40 => Bandwidth::_40MHz,
1703            raw if raw == wifi_bandwidth_t_WIFI_BW80 => Bandwidth::_80MHz,
1704            raw if raw == wifi_bandwidth_t_WIFI_BW160 => Bandwidth::_160MHz,
1705            raw if raw == wifi_bandwidth_t_WIFI_BW80_BW80 => Bandwidth::_80_80MHz,
1706            _ => Bandwidth::_20MHz,
1707        }
1708    }
1709}
1710
1711/// The radio metadata header of the received packet, which is the common header
1712/// at the beginning of all RX callback buffers in promiscuous mode.
1713#[cfg(wifi_mac_version = "1")]
1714#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
1715#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1716#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
1717#[instability::unstable]
1718pub struct RxControlInfo {
1719    /// Received Signal Strength Indicator (RSSI) of the packet, in dBm.
1720    pub rssi: i32,
1721    /// PHY rate encoding of the packet. Only valid for non-HT (802.11b/g)
1722    /// packets.
1723    pub rate: u32,
1724    /// Protocol of the received packet: 0 for non-HT (11bg), 1 for HT (11n), 3
1725    /// for VHT (11ac).
1726    pub sig_mode: u32,
1727    /// Modulation and Coding Scheme (MCS). Indicates modulation for HT (11n)
1728    /// packets.
1729    pub mcs: u32,
1730    /// Channel bandwidth of the packet: 0 for 20MHz, 1 for 40MHz.
1731    pub cwb: u32,
1732    /// Channel estimate smoothing: 1 recommends smoothing; 0 recommends
1733    /// per-carrier-independent estimate.
1734    pub smoothing: u32,
1735    /// Sounding indicator: 0 for sounding PPDU (used for channel estimation); 1
1736    /// for non-sounding PPDU.
1737    pub not_sounding: u32,
1738    /// Aggregation status: 0 for MPDU packet, 1 for AMPDU packet.
1739    pub aggregation: u32,
1740    /// Space-Time Block Coding (STBC) status: 0 for non-STBC packet, 1 for STBC
1741    /// packet.
1742    pub stbc: u32,
1743    /// Forward Error Correction (FEC) status: indicates if LDPC coding is used
1744    /// for 11n packets.
1745    pub fec_coding: u32,
1746    /// Short Guard Interval (SGI): 0 for long guard interval, 1 for short guard
1747    /// interval.
1748    pub sgi: u32,
1749    /// Number of subframes aggregated in an AMPDU packet.
1750    pub ampdu_cnt: u32,
1751    /// Primary channel on which the packet is received.
1752    pub channel: u32,
1753    /// Secondary channel on which the packet is received.
1754    pub secondary_channel: SecondaryChannel,
1755    /// Timestamp of when the packet is received, in microseconds. Precise only
1756    /// if modem sleep or light sleep is not enabled.
1757    pub timestamp: Instant,
1758    /// Noise floor of the Radio Frequency module, in dBm.
1759    pub noise_floor: i32,
1760    /// Antenna number from which the packet is received: 0 for antenna 0, 1 for
1761    /// antenna 1.
1762    pub ant: u32,
1763    /// Length of the packet including the Frame Check Sequence (FCS).
1764    pub sig_len: u32,
1765    /// State of the packet: 0 for no error, other values indicate error codes.
1766    pub rx_state: u32,
1767}
1768
1769/// The radio metadata header of the received packet, which is the common header
1770/// at the beginning of all RX callback buffers in promiscuous mode.
1771#[cfg(wifi_mac_version = "2")]
1772#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
1773#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1774#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
1775#[instability::unstable]
1776pub struct RxControlInfo {
1777    /// Received Signal Strength Indicator (RSSI) of the packet, in dBm.
1778    pub rssi: i32,
1779    /// PHY rate encoding of the packet. Only valid for non-HT (802.11b/g)
1780    /// packets.
1781    pub rate: u32,
1782    /// Length of the received packet including the Frame Check Sequence (FCS).
1783    pub sig_len: u32,
1784    /// Reception state of the packet: 0 for no error, others indicate error
1785    /// codes.
1786    pub rx_state: u32,
1787    /// Length of the dump buffer.
1788    pub dump_len: u32,
1789    /// Length of HE-SIG-B field (802.11ax).
1790    pub he_sigb_len: u32,
1791    /// Indicates if this is a single MPDU.
1792    pub cur_single_mpdu: u32,
1793    /// Current baseband format.
1794    pub cur_bb_format: u32,
1795    /// Channel estimation validity.
1796    pub rx_channel_estimate_info_vld: u32,
1797    /// Length of the channel estimation.
1798    pub rx_channel_estimate_len: u32,
1799    /// The secondary channel if in HT40. Otherwise invalid.
1800    pub secondary_channel: SecondaryChannel,
1801    /// Primary channel on which the packet is received.
1802    pub channel: u32,
1803    /// Noise floor of the Radio Frequency module, in dBm.
1804    pub noise_floor: i32,
1805    /// Indicates if this is a group-addressed frame.
1806    pub is_group: u32,
1807    /// End state of the packet reception.
1808    pub rxend_state: u32,
1809    /// Indicate whether the reception frame is from interface 3.
1810    pub rxmatch3: u32,
1811    /// Indicate whether the reception frame is from interface 2.
1812    pub rxmatch2: u32,
1813    /// Indicate whether the reception frame is from interface 1.
1814    pub rxmatch1: u32,
1815    /// Indicate whether the reception frame is from interface 0.
1816    pub rxmatch0: u32,
1817    /// The local time when this packet is received. It is precise only if modem sleep or light
1818    /// sleep is not enabled. unit: microsecond.
1819    pub timestamp: Instant,
1820}
1821
1822/// The radio metadata header of the received packet, which is the common header
1823/// at the beginning of all RX callback buffers in promiscuous mode.
1824#[cfg(wifi_mac_version = "3")]
1825#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
1826#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1827#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
1828#[instability::unstable]
1829pub struct RxControlInfo {
1830    /// Received Signal Strength Indicator (RSSI) of the packet, in dBm.
1831    pub rssi: i32,
1832    /// PHY rate encoding of the packet. Only valid for non-HT (802.11b/g)
1833    /// packets.
1834    pub rate: u32,
1835    /// Length of the received packet including the Frame Check Sequence (FCS).
1836    pub sig_len: u32,
1837    /// Reception state of the packet: 0 for no error, others indicate error
1838    /// codes.
1839    pub rx_state: u32,
1840    /// Length of the dump buffer.
1841    pub dump_len: u32,
1842    /// Length of HE-SIG-B field (802.11ax).
1843    pub he_sigb_len: u32,
1844    /// Current baseband format.
1845    pub cur_bb_format: u32,
1846    /// Channel estimation validity.
1847    pub rx_channel_estimate_info_vld: u32,
1848    /// Length of the channel estimation.
1849    pub rx_channel_estimate_len: u32,
1850    /// The secondary channel if in HT40. Otherwise invalid.
1851    pub secondary_channel: SecondaryChannel,
1852    /// Primary channel on which the packet is received.
1853    pub channel: u32,
1854    /// Noise floor of the Radio Frequency module, in dBm.
1855    pub noise_floor: i32,
1856    /// Indicates if this is a group-addressed frame.
1857    pub is_group: u32,
1858    /// End state of the packet reception.
1859    pub rxend_state: u32,
1860    /// Indicate whether the reception frame is from interface 3.
1861    pub rxmatch3: u32,
1862    /// Indicate whether the reception frame is from interface 2.
1863    pub rxmatch2: u32,
1864    /// Indicate whether the reception frame is from interface 1.
1865    pub rxmatch1: u32,
1866    /// Indicate whether the reception frame is from interface 0.
1867    pub rxmatch0: u32,
1868    /// The local time when this packet is received. It is precise only if modem sleep or light
1869    /// sleep is not enabled. unit: microsecond.
1870    pub timestamp: Instant,
1871}
1872
1873#[cfg(all(any(feature = "esp-now", feature = "sniffer"), feature = "unstable"))]
1874impl RxControlInfo {
1875    // Signed bitfields are broken in rust-bindgen, see
1876    // https://github.com/esp-rs/esp-wifi-sys/issues/482.
1877    // Casting through u8 makes the intended 8-bit truncation explicit before sign extension.
1878    const fn sign_extend_i8_bitfield(value: i32) -> i32 {
1879        (value as u8 as i8) as i32
1880    }
1881
1882    /// Create an instance from a raw pointer to [wifi_pkt_rx_ctrl_t].
1883    ///
1884    /// # Safety
1885    /// When calling this, you must ensure, that `rx_cntl` points to a valid
1886    /// instance of [wifi_pkt_rx_ctrl_t].
1887    pub(super) unsafe fn from_raw(rx_cntl: *const wifi_pkt_rx_ctrl_t) -> Self {
1888        #[cfg(wifi_mac_version = "1")]
1889        let rx_control_info = unsafe {
1890            RxControlInfo {
1891                rssi: Self::sign_extend_i8_bitfield((*rx_cntl).rssi()),
1892                rate: (*rx_cntl).rate(),
1893                sig_mode: (*rx_cntl).sig_mode(),
1894                mcs: (*rx_cntl).mcs(),
1895                cwb: (*rx_cntl).cwb(),
1896                smoothing: (*rx_cntl).smoothing(),
1897                not_sounding: (*rx_cntl).not_sounding(),
1898                aggregation: (*rx_cntl).aggregation(),
1899                stbc: (*rx_cntl).stbc(),
1900                fec_coding: (*rx_cntl).fec_coding(),
1901                sgi: (*rx_cntl).sgi(),
1902                ampdu_cnt: (*rx_cntl).ampdu_cnt(),
1903                channel: (*rx_cntl).channel(),
1904                secondary_channel: SecondaryChannel::from_raw_or_default(
1905                    (*rx_cntl).secondary_channel(),
1906                ),
1907                timestamp: Instant::EPOCH + Duration::from_micros((*rx_cntl).timestamp() as u64),
1908                noise_floor: Self::sign_extend_i8_bitfield((*rx_cntl).noise_floor()),
1909                ant: (*rx_cntl).ant(),
1910                sig_len: (*rx_cntl).sig_len(),
1911                rx_state: (*rx_cntl).rx_state(),
1912            }
1913        };
1914        #[cfg(wifi_mac_version = "2")]
1915        let rx_control_info = unsafe {
1916            RxControlInfo {
1917                rssi: Self::sign_extend_i8_bitfield((*rx_cntl).rssi()),
1918                rate: (*rx_cntl).rate(),
1919                sig_len: (*rx_cntl).sig_len(),
1920                rx_state: (*rx_cntl).rx_state(),
1921                dump_len: (*rx_cntl).dump_len(),
1922                he_sigb_len: (*rx_cntl).he_sigb_len(),
1923                cur_single_mpdu: (*rx_cntl).cur_single_mpdu(),
1924                cur_bb_format: (*rx_cntl).cur_bb_format(),
1925                rx_channel_estimate_info_vld: (*rx_cntl).rx_channel_estimate_info_vld(),
1926                rx_channel_estimate_len: (*rx_cntl).rx_channel_estimate_len(),
1927                secondary_channel: SecondaryChannel::from_raw_or_default((*rx_cntl).second()),
1928                channel: (*rx_cntl).channel(),
1929                noise_floor: Self::sign_extend_i8_bitfield((*rx_cntl).noise_floor()),
1930                is_group: (*rx_cntl).is_group(),
1931                rxend_state: (*rx_cntl).rxend_state(),
1932                rxmatch3: (*rx_cntl).rxmatch3(),
1933                rxmatch2: (*rx_cntl).rxmatch2(),
1934                rxmatch1: (*rx_cntl).rxmatch1(),
1935                rxmatch0: (*rx_cntl).rxmatch0(),
1936                timestamp: Instant::EPOCH + Duration::from_micros((*rx_cntl).timestamp() as u64),
1937            }
1938        };
1939        #[cfg(wifi_mac_version = "3")]
1940        let rx_control_info = unsafe {
1941            RxControlInfo {
1942                rssi: Self::sign_extend_i8_bitfield((*rx_cntl).rssi()),
1943                rate: (*rx_cntl).rate(),
1944                sig_len: (*rx_cntl).sig_len(),
1945                rx_state: (*rx_cntl).rx_state(),
1946                dump_len: (*rx_cntl).dump_len(),
1947                he_sigb_len: (*rx_cntl).sigb_len(),
1948                cur_bb_format: (*rx_cntl).cur_bb_format(),
1949                rx_channel_estimate_info_vld: (*rx_cntl).rx_channel_estimate_info_vld(),
1950                rx_channel_estimate_len: (*rx_cntl).rx_channel_estimate_len(),
1951                secondary_channel: SecondaryChannel::from_raw_or_default((*rx_cntl).second()),
1952                channel: (*rx_cntl).channel(),
1953                noise_floor: Self::sign_extend_i8_bitfield((*rx_cntl).noise_floor()),
1954                is_group: (*rx_cntl).is_group(),
1955                rxend_state: (*rx_cntl).rxend_state(),
1956                rxmatch3: (*rx_cntl).rxmatch3(),
1957                rxmatch2: (*rx_cntl).rxmatch2(),
1958                rxmatch1: (*rx_cntl).rxmatch1(),
1959                rxmatch0: (*rx_cntl).rxmatch0(),
1960                timestamp: Instant::EPOCH + Duration::from_micros((*rx_cntl).timestamp() as u64),
1961            }
1962        };
1963        rx_control_info
1964    }
1965}
1966
1967#[doc(hidden)]
1968/// This token is deliberately hidden to avoid polluting the crate namespace with these typically
1969/// advanced usage types. We can't make them private, as they're used in various built-in network
1970/// stack impls. Once we are ready to stabilize these, we can remove the doc hidden cfg.
1971pub struct WifiRxToken {
1972    mode: InterfaceType,
1973}
1974
1975impl WifiRxToken {
1976    /// Consumes the RX token and applies the callback function to the received
1977    /// data buffer.
1978    pub fn consume_token<R, F>(self, f: F) -> R
1979    where
1980        F: FnOnce(&mut [u8]) -> R,
1981    {
1982        let mut data = self.mode.data_queue_rx().with(|queue| {
1983            unwrap!(
1984                queue.pop_front(),
1985                "unreachable: transmit()/receive() ensures there is a packet to process"
1986            )
1987        });
1988
1989        // We handle the received data outside of the lock because
1990        // PacketBuffer::drop must not be called in a critical section.
1991        // Dropping an PacketBuffer will call `esp_wifi_internal_free_rx_buffer`
1992        // which will try to lock an internal mutex. If the mutex is already
1993        // taken, the function will try to trigger a context switch, which will
1994        // fail if we are in an interrupt-free context.
1995        let buffer = data.as_slice_mut();
1996        dump_packet_info(buffer);
1997
1998        f(buffer)
1999    }
2000}
2001
2002#[doc(hidden)]
2003/// This token is deliberately hidden to avoid polluting the crate namespace with these typically
2004/// advanced usage types. We can't make them private, as they're used in various built-in network
2005/// stack impls. Once we are ready to stabilize these, we can remove the doc hidden cfg.
2006pub struct WifiTxToken {
2007    mode: InterfaceType,
2008}
2009
2010impl WifiTxToken {
2011    /// Consumes the TX token and applies the callback function to the received
2012    /// data buffer.
2013    pub fn consume_token<R, F>(self, len: usize, f: F) -> R
2014    where
2015        F: FnOnce(&mut [u8]) -> R,
2016    {
2017        self.mode.increase_in_flight_counter();
2018
2019        let mut buffer: [u8; MTU] = [0u8; MTU];
2020        let buffer = &mut buffer[..len];
2021
2022        let res = f(buffer);
2023
2024        esp_wifi_send_data(self.mode.interface(), buffer);
2025
2026        res
2027    }
2028}
2029
2030// FIXME data here has to be &mut because of `esp_wifi_internal_tx` signature,
2031// requiring a *mut ptr to the buffer Casting const to mut is instant UB, even
2032// though in reality `esp_wifi_internal_tx` copies the buffer into its own
2033// memory and does not modify
2034pub(crate) fn esp_wifi_send_data(interface: wifi_interface_t, data: &mut [u8]) {
2035    // `esp_wifi_internal_tx` will crash if wifi is uninitialized or de-inited
2036
2037    state::locked(|| {
2038        // even checking for !Uninitialized would be enough to not crash
2039        if (interface == wifi_interface_t_WIFI_IF_STA
2040            && !matches!(station_state(), WifiStationState::Connected))
2041            || (interface == wifi_interface_t_WIFI_IF_AP
2042                && !matches!(access_point_state(), WifiAccessPointState::Started))
2043        {
2044            return;
2045        }
2046
2047        trace!("sending... {} bytes", data.len());
2048        dump_packet_info(data);
2049
2050        let len = data.len() as u16;
2051        let ptr = data.as_mut_ptr().cast();
2052
2053        let res = unsafe { esp_wifi_internal_tx(interface, ptr, len) };
2054
2055        if res != include::ESP_OK as i32 {
2056            warn!("esp_wifi_internal_tx returned error: {}", res);
2057            decrement_inflight_counter();
2058        }
2059    })
2060}
2061
2062fn dump_packet_info(_buffer: &mut [u8]) {
2063    #[cfg(dump_packets)]
2064    {
2065        info!("@WIFIFRAME {:?}", _buffer);
2066    }
2067}
2068
2069macro_rules! esp_wifi_result {
2070    ($value:expr) => {{
2071        let result = $value;
2072        if result != $crate::sys::include::ESP_OK as i32 {
2073            // `from_error_code` decides how to classify and (only for truly
2074            // unmapped codes) log the result, so we don't warn here.
2075            Err(WifiError::from_error_code(result))
2076        } else {
2077            Ok::<(), WifiError>(())
2078        }
2079    }};
2080}
2081pub(crate) use esp_wifi_result;
2082
2083// We can get away with a single tx waker because the transmit queue is shared
2084// between interfaces.
2085static TRANSMIT_WAKER: AtomicWaker = AtomicWaker::new();
2086
2087static AP_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new();
2088static STA_LINK_STATE_WAKER: AtomicWaker = AtomicWaker::new();
2089
2090// we implement up to three latest versions of the embassy-net-driver
2091// (but 0.1 clashes with embassy-time-driver)
2092pub(crate) mod embassy_02 {
2093    use embassy_net_driver_02::{
2094        Capabilities,
2095        Driver,
2096        HardwareAddress,
2097        LinkState,
2098        RxToken,
2099        TxToken,
2100    };
2101
2102    use super::*;
2103
2104    impl RxToken for WifiRxToken {
2105        fn consume<R, F>(self, f: F) -> R
2106        where
2107            F: FnOnce(&mut [u8]) -> R,
2108        {
2109            self.consume_token(f)
2110        }
2111    }
2112
2113    impl TxToken for WifiTxToken {
2114        fn consume<R, F>(self, len: usize, f: F) -> R
2115        where
2116            F: FnOnce(&mut [u8]) -> R,
2117        {
2118            self.consume_token(len, f)
2119        }
2120    }
2121
2122    impl Driver for Interface {
2123        type RxToken<'a>
2124            = WifiRxToken
2125        where
2126            Self: 'a;
2127        type TxToken<'a>
2128            = WifiTxToken
2129        where
2130            Self: 'a;
2131
2132        fn receive(
2133            &mut self,
2134            cx: &mut core::task::Context<'_>,
2135        ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
2136            self.mode.register_receive_waker(cx.waker());
2137            self.mode.register_transmit_waker(cx.waker());
2138            self.mode.rx_token()
2139        }
2140
2141        fn transmit(&mut self, cx: &mut core::task::Context<'_>) -> Option<Self::TxToken<'_>> {
2142            self.mode.register_transmit_waker(cx.waker());
2143            self.mode.tx_token()
2144        }
2145
2146        fn link_state(&mut self, cx: &mut core::task::Context<'_>) -> LinkState {
2147            self.mode.register_link_state_waker(cx.waker());
2148            match self.mode.link_state() {
2149                super::LinkState::Down => LinkState::Down,
2150                super::LinkState::Up => LinkState::Up,
2151            }
2152        }
2153
2154        fn capabilities(&self) -> Capabilities {
2155            let mut caps = Capabilities::default();
2156            caps.max_transmission_unit = MTU;
2157            caps.max_burst_size =
2158                if esp_config_int!(usize, "ESP_RADIO_CONFIG_WIFI_MAX_BURST_SIZE") == 0 {
2159                    None
2160                } else {
2161                    Some(esp_config_int!(
2162                        usize,
2163                        "ESP_RADIO_CONFIG_WIFI_MAX_BURST_SIZE"
2164                    ))
2165                };
2166            caps
2167        }
2168
2169        fn hardware_address(&self) -> HardwareAddress {
2170            HardwareAddress::Ethernet(self.mac_address())
2171        }
2172    }
2173}
2174
2175pub(crate) mod xarxa {
2176    use xarxa_driver::{
2177        Capabilities,
2178        Driver,
2179        HardwareAddress,
2180        LinkState,
2181        NotSupported,
2182        PacketBuf,
2183        config::PACKET_BUF_SIZE,
2184    };
2185
2186    use super::*;
2187
2188    /// The largest frame this driver can pass on to the stack.
2189    ///
2190    /// The packet pool has a fixed buffer size, so a larger configured MTU cannot be used.
2191    const XARXA_MTU: usize = if MTU < PACKET_BUF_SIZE {
2192        MTU
2193    } else {
2194        PACKET_BUF_SIZE
2195    };
2196
2197    impl Driver for Interface {
2198        fn capabilities(&self) -> Capabilities {
2199            let mut caps = Capabilities::default();
2200            caps.max_transmission_unit = XARXA_MTU;
2201            caps
2202        }
2203
2204        fn hardware_address(&self) -> HardwareAddress {
2205            HardwareAddress::Ethernet(self.mac_address())
2206        }
2207
2208        fn link_state(&mut self) -> LinkState {
2209            match self.mode.link_state() {
2210                super::LinkState::Down => LinkState::Down,
2211                super::LinkState::Up => LinkState::Up,
2212            }
2213        }
2214
2215        fn register_waker(&mut self, waker: &core::task::Waker) -> Result<(), NotSupported> {
2216            // The driver has one waker per event, the stack has one waker for all of them.
2217            self.mode.register_receive_waker(waker);
2218            self.mode.register_transmit_waker(waker);
2219            self.mode.register_link_state_waker(waker);
2220            Ok(())
2221        }
2222
2223        fn receive(&mut self) -> Option<PacketBuf> {
2224            // We handle the received data outside of the lock because PacketBuffer::drop must
2225            // not be called in a critical section. Dropping a PacketBuffer calls
2226            // `esp_wifi_internal_free_rx_buffer` which will try to lock an internal mutex. If
2227            // the mutex is already taken, the function will try to trigger a context switch,
2228            // which will fail if we are in an interrupt-free context.
2229            let mut packet = self.mode.data_queue_rx().with(|queue| queue.pop_front())?;
2230
2231            let data = packet.as_slice_mut();
2232            dump_packet_info(data);
2233
2234            if data.len() > PACKET_BUF_SIZE {
2235                warn!(
2236                    "Dropping a {} byte frame, the packet buffer holds {} bytes",
2237                    data.len(),
2238                    PACKET_BUF_SIZE
2239                );
2240                return None;
2241            }
2242
2243            // The pool is shared with the rest of the stack and can be empty. Dropping the
2244            // packet is the only option, the hardware does not hold it for us.
2245            let mut buffer = PacketBuf::try_new()?;
2246            buffer.set_len(data.len());
2247            buffer.copy_from_slice(data);
2248
2249            Some(buffer)
2250        }
2251
2252        fn can_transmit(&mut self) -> bool {
2253            self.mode.can_send() && self.mode.link_state() == super::LinkState::Up
2254        }
2255
2256        fn transmit(&mut self, mut buffer: PacketBuf) -> Result<(), PacketBuf> {
2257            if !self.can_transmit() {
2258                return Err(buffer);
2259            }
2260
2261            self.mode.increase_in_flight_counter();
2262            esp_wifi_send_data(self.mode.interface(), &mut buffer);
2263
2264            Ok(())
2265        }
2266    }
2267}
2268
2269/// Power saving mode settings for the modem.
2270#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Hash)]
2271#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2272#[instability::unstable]
2273#[non_exhaustive]
2274pub enum PowerSaveMode {
2275    /// No power saving.
2276    #[default]
2277    None,
2278    /// Minimum power save mode. In this mode, station wakes up to receive beacon every DTIM
2279    /// period.
2280    Minimum,
2281    /// Maximum power save mode. In this mode, interval to receive beacons is determined by the
2282    /// `listen_interval` config option.
2283    Maximum,
2284}
2285
2286pub(crate) fn apply_power_saving(ps: PowerSaveMode) -> Result<(), WifiError> {
2287    esp_wifi_result!(unsafe {
2288        crate::sys::include::esp_wifi_set_ps(match ps {
2289            PowerSaveMode::None => crate::sys::include::wifi_ps_type_t_WIFI_PS_NONE,
2290            PowerSaveMode::Minimum => crate::sys::include::wifi_ps_type_t_WIFI_PS_MIN_MODEM,
2291            PowerSaveMode::Maximum => crate::sys::include::wifi_ps_type_t_WIFI_PS_MAX_MODEM,
2292        })
2293    })?;
2294    Ok(())
2295}
2296
2297/// Wi-Fi operating class.
2298///
2299/// Refer to Annex E of IEEE Std 802.11-2020.
2300#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
2301#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2302#[instability::unstable]
2303pub enum OperatingClass {
2304    /// The regulations under which the Station/Access Point is operating encompass all environments
2305    /// for the current frequency band in the country.
2306    AllEnvironments,
2307
2308    /// The regulations under which the Station/Access Point is operating are for an outdoor
2309    /// environment only.
2310    Outdoors,
2311
2312    /// The regulations under which the Station/Access Point is operating are for an indoor
2313    /// environment only.
2314    Indoors,
2315
2316    /// The Station/Access Point is operating under a noncountry entity. The first two octets of the
2317    /// noncountry entity is two ASCII ‘XX’ characters.
2318    NonCountryEntity,
2319
2320    /// Binary representation of the Operating Class table number currently in use. Refer to Annex E
2321    /// of IEEE Std 802.11-2020.
2322    Repr(u8),
2323}
2324
2325impl Default for OperatingClass {
2326    fn default() -> Self {
2327        OperatingClass::Repr(0) // TODO: is this valid?
2328    }
2329}
2330
2331impl OperatingClass {
2332    fn into_code(self) -> u8 {
2333        match self {
2334            OperatingClass::AllEnvironments => b' ',
2335            OperatingClass::Outdoors => b'O',
2336            OperatingClass::Indoors => b'I',
2337            OperatingClass::NonCountryEntity => b'X',
2338            OperatingClass::Repr(code) => code,
2339        }
2340    }
2341
2342    fn from_code(code: u8) -> Option<Self> {
2343        match code {
2344            b' ' => Some(OperatingClass::AllEnvironments),
2345            b'O' => Some(OperatingClass::Outdoors),
2346            b'I' => Some(OperatingClass::Indoors),
2347            b'X' => Some(OperatingClass::NonCountryEntity),
2348            code => Some(OperatingClass::Repr(code)),
2349        }
2350    }
2351}
2352
2353#[procmacros::doc_replace]
2354/// Country information.
2355///
2356/// Defaults to China (CN) with Operating Class "0".
2357///
2358/// To create a [`CountryInfo`] instance, use the `from` method first, then set additional
2359/// properties using the builder methods.
2360///
2361/// ## Example
2362///
2363/// ```rust,no_run
2364/// # {before_snippet}
2365/// use esp_radio::wifi::{CountryInfo, OperatingClass};
2366///
2367/// let country_info = CountryInfo::from(*b"CN").with_operating_class(OperatingClass::Indoors);
2368/// # {after_snippet}
2369/// ```
2370///
2371/// For more information, see the [Wi-Fi Country Code in the ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-country-code).
2372#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BuilderLite)]
2373#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2374#[instability::unstable]
2375pub struct CountryInfo {
2376    /// Country code.
2377    #[builder_lite(skip)]
2378    country: [u8; 2],
2379
2380    /// Operating class.
2381    #[builder_lite(unstable)]
2382    operating_class: OperatingClass,
2383}
2384
2385impl From<[u8; 2]> for CountryInfo {
2386    fn from(country: [u8; 2]) -> Self {
2387        Self {
2388            country,
2389            operating_class: OperatingClass::default(),
2390        }
2391    }
2392}
2393
2394impl CountryInfo {
2395    fn into_blob(self) -> wifi_country_t {
2396        wifi_country_t {
2397            cc: [
2398                self.country[0],
2399                self.country[1],
2400                self.operating_class.into_code(),
2401            ],
2402            // TODO: these may be valid defaults, but they should be configurable.
2403            schan: 1,
2404            nchan: 13,
2405            // This field is output-only: esp_wifi_set_country ignores it. The actual TX power
2406            // is controlled exclusively via esp_wifi_set_max_tx_power after WiFi start.
2407            // See: https://github.com/espressif/esp-idf/blob/20f5e18/components/esp_wifi/include/esp_wifi_types.h#L46
2408            max_tx_power: 0,
2409            policy: wifi_country_policy_t_WIFI_COUNTRY_POLICY_MANUAL,
2410
2411            #[cfg(wifi_has_5g)]
2412            wifi_5g_channel_mask: 0,
2413        }
2414    }
2415
2416    #[cfg_attr(not(feature = "unstable"), expect(dead_code))]
2417    fn try_from_c(info: &wifi_country_t) -> Option<Self> {
2418        let cc = &info.cc;
2419        let operating_class = OperatingClass::from_code(cc[2])?;
2420
2421        Some(Self {
2422            country: [cc[0], cc[1]],
2423            operating_class,
2424        })
2425    }
2426}
2427
2428/// Wi-Fi configuration.
2429#[derive(Clone, BuilderLite, Debug, Hash, PartialEq, Eq)]
2430#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2431#[non_exhaustive]
2432pub struct ControllerConfig {
2433    /// Country info.
2434    #[builder_lite(into)]
2435    #[builder_lite(unstable)]
2436    country_info: CountryInfo,
2437    /// Size of the RX queue in frames.
2438    #[builder_lite(unstable)]
2439    rx_queue_size: usize,
2440    /// Size of the TX queue in frames.
2441    #[builder_lite(unstable)]
2442    tx_queue_size: usize,
2443
2444    /// Max number of Wi-Fi static RX buffers.
2445    ///
2446    /// Each buffer takes approximately 1.6KB of RAM. The static rx buffers are allocated when
2447    /// esp_wifi_init is called, they are not freed until esp_wifi_deinit is called.
2448    ///
2449    /// Wi-Fi hardware use these buffers to receive all 802.11 frames. A higher number may allow
2450    /// higher throughput but increases memory use. If [`Self::ampdu_rx_enable`] is enabled,
2451    /// this value is recommended to set equal or bigger than [`Self::rx_ba_win`] in order to
2452    /// achieve better throughput and compatibility with both stations and APs.
2453    #[builder_lite(unstable)]
2454    static_rx_buf_num: u8,
2455
2456    /// Max number of Wi-Fi dynamic RX buffers
2457    ///
2458    /// Set the number of Wi-Fi dynamic RX buffers, 0 means unlimited RX buffers will be allocated
2459    /// (provided sufficient free RAM). The size of each dynamic RX buffer depends on the size of
2460    /// the received data frame.
2461    ///
2462    /// For each received data frame, the Wi-Fi driver makes a copy to an RX buffer and then
2463    /// delivers it to the high layer TCP/IP stack. The dynamic RX buffer is freed after the
2464    /// higher layer has successfully received the data frame.
2465    ///
2466    /// For some applications, Wi-Fi data frames may be received faster than the application can
2467    /// process them. In these cases we may run out of memory if RX buffer number is unlimited
2468    /// (0).
2469    ///
2470    /// If a dynamic RX buffer limit is set, it should be at least the number of
2471    /// static RX buffers.
2472    #[builder_lite(unstable)]
2473    dynamic_rx_buf_num: u16,
2474
2475    /// Set the number of Wi-Fi static TX buffers.
2476    ///
2477    /// Each buffer takes approximately 1.6KB of RAM.
2478    /// The static RX buffers are allocated when esp_wifi_init() is called, they are not released
2479    /// until esp_wifi_deinit() is called.
2480    ///
2481    /// For each transmitted data frame from the higher layer TCP/IP stack, the Wi-Fi driver makes
2482    /// a copy of it in a TX buffer.
2483    ///
2484    /// For some applications especially UDP applications, the upper layer can deliver frames
2485    /// faster than Wi-Fi layer can transmit. In these cases, we may run out of TX buffers.
2486    #[builder_lite(unstable)]
2487    static_tx_buf_num: u8,
2488
2489    /// Set the number of Wi-Fi dynamic TX buffers.
2490    ///
2491    /// The size of each dynamic TX buffer is not fixed,
2492    /// it depends on the size of each transmitted data frame.
2493    ///
2494    /// For each transmitted frame from the higher layer TCP/IP stack, the Wi-Fi driver makes a
2495    /// copy of it in a TX buffer.
2496    ///
2497    /// For some applications, especially UDP applications, the upper layer can deliver frames
2498    /// faster than Wi-Fi layer can transmit. In these cases, we may run out of TX buffers.
2499    #[builder_lite(unstable)]
2500    dynamic_tx_buf_num: u16,
2501
2502    /// Select this option to enable AMPDU RX feature.
2503    #[builder_lite(unstable)]
2504    ampdu_rx_enable: bool,
2505
2506    /// Select this option to enable AMPDU TX feature.
2507    #[builder_lite(unstable)]
2508    ampdu_tx_enable: bool,
2509
2510    /// Select this option to enable AMSDU TX feature.
2511    #[builder_lite(unstable)]
2512    amsdu_tx_enable: bool,
2513
2514    /// Set the size of Wi-Fi Block Ack RX window.
2515    ///
2516    /// Generally a bigger value means higher throughput and better compatibility but more memory.
2517    /// Most of time we should NOT change the default value unless special reason, e.g. test
2518    /// the maximum UDP RX throughput with iperf etc. For iperf test in shieldbox, the
2519    /// recommended value is 9~12.
2520    ///
2521    /// If PSRAM is used and Wi-Fi memory is preferred to allocate in PSRAM first, the default and
2522    /// minimum value should be 16 to achieve better throughput and compatibility with both
2523    /// stations and APs.
2524    #[builder_lite(unstable)]
2525    rx_ba_win: u8,
2526
2527    /// Enable WiFi Power Management for station at disconnected status.
2528    #[builder_lite(unstable)]
2529    sta_disconnected_pm: bool,
2530
2531    /// Maximum encrypt number of peers supported by ESP-NOW.
2532    ///
2533    /// There are a fixed number of hardware encryption keys, and they are shared between ESP-NOW
2534    /// and SoftAP. SoftAP will use however many are left over by ESP-NOW (unless configured to use
2535    /// fewer).
2536    #[builder_lite(unstable)]
2537    espnow_max_encrypt_num: u8,
2538
2539    /// Initial Wi-Fi configuration.
2540    #[builder_lite(reference)]
2541    initial_config: Config,
2542}
2543
2544impl Default for ControllerConfig {
2545    fn default() -> Self {
2546        Self {
2547            rx_queue_size: 5,
2548            tx_queue_size: 3,
2549
2550            static_rx_buf_num: 10,
2551            dynamic_rx_buf_num: 32,
2552
2553            static_tx_buf_num: 0,
2554            dynamic_tx_buf_num: 32,
2555
2556            ampdu_rx_enable: true,
2557            ampdu_tx_enable: true,
2558            amsdu_tx_enable: false,
2559
2560            rx_ba_win: 6,
2561
2562            sta_disconnected_pm: crate::sys::include::CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE
2563                == 1,
2564
2565            espnow_max_encrypt_num: crate::sys::include::CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM
2566                as _,
2567
2568            country_info: CountryInfo::from(*b"CN"),
2569
2570            initial_config: Config::Station(StationConfig::default()),
2571        }
2572    }
2573}
2574
2575impl ControllerConfig {
2576    fn validate(&self) {
2577        if self.rx_ba_win as u16 >= self.dynamic_rx_buf_num {
2578            warn!("RX BA window size should be less than the number of dynamic RX buffers.");
2579        }
2580        if self.rx_ba_win as u16 >= 2 * (self.static_rx_buf_num as u16) {
2581            warn!("RX BA window size should be less than twice the number of static RX buffers.");
2582        }
2583        if self.espnow_max_encrypt_num > TOTAL_HW_ENCRYPT_KEYS {
2584            warn!("ESP-NOW max encrypted peers should be at most the number of hardware keys.");
2585        }
2586    }
2587}
2588
2589static WIFI_REFCOUNT: Refcount = Refcount::new();
2590
2591/// Keeps Wi-Fi initialized until the last guard is dropped.
2592#[derive(Debug)]
2593#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2594pub(crate) struct WifiRefGuard {
2595    _radio_guard: RadioRefGuard,
2596}
2597
2598impl Clone for WifiRefGuard {
2599    fn clone(&self) -> Self {
2600        let _radio_guard = RadioRefGuard::new();
2601        WIFI_REFCOUNT.increment(|| {});
2602        Self { _radio_guard }
2603    }
2604}
2605
2606impl Drop for WifiRefGuard {
2607    fn drop(&mut self) {
2608        WIFI_REFCOUNT.decrement(|| {
2609            state::locked(|| {
2610                set_access_point_state(WifiAccessPointState::Uninitialized);
2611                set_station_state(WifiStationState::Uninitialized);
2612
2613                if let Err(e) = crate::wifi::wifi_deinit() {
2614                    warn!("Failed to cleanly deinit wifi: {:?}", e);
2615                }
2616
2617                #[cfg(rng_trng_supported)]
2618                esp_hal::if_unstable_hal! {
2619                    esp_hal::rng::TrngSource::decrease_entropy_source_counter(unsafe {
2620                        esp_hal::Internal::conjure()
2621                    });
2622                }
2623            })
2624        });
2625    }
2626}
2627
2628/// Wi-Fi controller.
2629///
2630/// When the controller is dropped, the Wi-Fi driver is deinitialized and Wi-Fi
2631/// is stopped unless an ESP-NOW or sniffer instance created from it is still
2632/// alive. In that case, Wi-Fi keeps running until that instance is dropped too.
2633#[derive(Debug)]
2634#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2635pub struct WifiController<'d> {
2636    _guard: WifiRefGuard,
2637    _phantom: PhantomData<&'d ()>,
2638}
2639
2640impl<'d> WifiController<'d> {
2641    #[procmacros::doc_replace(
2642        "esp_now" => {
2643            cfg(all(feature = "esp-now", feature = "unstable")) => "An ESP-NOW instance is available through the controller's [`WifiController::esp_now()`] method.",
2644            _ => "",
2645        },
2646        "sniffer" => {
2647            cfg(all(feature = "sniffer", feature = "unstable")) => "A sniffer instance is available through the controller's [`WifiController::sniffer()`] method.",
2648            _ => "",
2649        },
2650    )]
2651    /// Create a Wi-Fi controller. The default initial configuration is
2652    /// [`Config::Station`]`(`[`StationConfig::default()`]`)`.
2653    ///
2654    /// Dropping the controller deinitializes Wi-Fi unless an ESP-NOW or sniffer
2655    /// instance created from it is still alive.
2656    ///
2657    /// Create [`Interface`]s separately via [`Interface::station()`] /
2658    /// [`Interface::access_point()`].
2659    /// # {esp_now}
2660    /// # {sniffer}
2661    ///
2662    /// Make sure to **not** call this function while interrupts are disabled, or IEEE 802.15.4 is
2663    /// currently in use.
2664    ///
2665    /// ## Example
2666    ///
2667    /// ```rust,no_run
2668    /// # {before_snippet}
2669    /// let controller = esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
2670    /// let sta = esp_radio::wifi::Interface::station();
2671    /// # {after_snippet}
2672    /// ```
2673    pub fn new(
2674        device: crate::hal::peripherals::WIFI<'d>,
2675        config: ControllerConfig,
2676    ) -> Result<Self, WifiError> {
2677        config.validate();
2678
2679        event::enable_wifi_events(
2680            WifiEvent::StationStart
2681                | WifiEvent::StationStop
2682                | WifiEvent::StationConnected
2683                | WifiEvent::StationDisconnected
2684                | WifiEvent::AccessPointStart
2685                | WifiEvent::AccessPointStop
2686                | WifiEvent::AccessPointStationConnected
2687                | WifiEvent::AccessPointStationDisconnected
2688                | WifiEvent::ScanDone,
2689        );
2690
2691        let radio_guard = RadioRefGuard::new();
2692
2693        let first = WIFI_REFCOUNT.try_increment(|| -> Result<(), WifiError> {
2694            unsafe {
2695                internal::G_CONFIG = wifi_init_config_t {
2696                    osi_funcs: (&raw const internal::__ESP_RADIO_G_WIFI_OSI_FUNCS).cast_mut(),
2697
2698                    wpa_crypto_funcs: g_wifi_default_wpa_crypto_funcs,
2699                    static_rx_buf_num: config.static_rx_buf_num as _,
2700                    dynamic_rx_buf_num: config.dynamic_rx_buf_num as _,
2701                    tx_buf_type: crate::sys::include::CONFIG_ESP_WIFI_TX_BUFFER_TYPE as i32,
2702                    static_tx_buf_num: config.static_tx_buf_num as _,
2703                    dynamic_tx_buf_num: config.dynamic_tx_buf_num as _,
2704                    rx_mgmt_buf_type: crate::sys::include::CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF
2705                        as i32,
2706                    rx_mgmt_buf_num: crate::sys::include::CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF
2707                        as i32,
2708                    cache_tx_buf_num: crate::sys::include::WIFI_CACHE_TX_BUFFER_NUM as i32,
2709                    csi_enable: cfg!(feature = "csi") as i32,
2710                    ampdu_rx_enable: config.ampdu_rx_enable as _,
2711                    ampdu_tx_enable: config.ampdu_tx_enable as _,
2712                    amsdu_tx_enable: config.amsdu_tx_enable as _,
2713                    nvs_enable: 0,
2714                    nano_enable: 0,
2715                    rx_ba_win: config.rx_ba_win as _,
2716                    wifi_task_core_id: Cpu::current() as _,
2717                    beacon_max_len: crate::sys::include::WIFI_SOFTAP_BEACON_MAX_LEN as i32,
2718                    mgmt_sbuf_num: crate::sys::include::WIFI_MGMT_SBUF_NUM as i32,
2719                    feature_caps: internal::__ESP_RADIO_G_WIFI_FEATURE_CAPS,
2720                    sta_disconnected_pm: config.sta_disconnected_pm as _,
2721                    espnow_max_encrypt_num: config.espnow_max_encrypt_num as _,
2722
2723                    tx_hetb_queue_num: 3,
2724                    dump_hesigb_enable: false,
2725
2726                    // WIFI_INIT_CONFIG_DEFAULT: both are off unless the matching sdkconfig is set.
2727                    privacy_enhancements: false,
2728                    rmac_auto_reset_int: 0,
2729
2730                    magic: WIFI_INIT_CONFIG_MAGIC as i32,
2731                };
2732            }
2733
2734            DATA_QUEUE_RX_AP.with(|queue| queue.change_capacity(config.rx_queue_size))?;
2735            DATA_QUEUE_RX_STA.with(|queue| queue.change_capacity(config.rx_queue_size))?;
2736
2737            TX_QUEUE_SIZE.store(config.tx_queue_size, Ordering::Relaxed);
2738
2739            crate::wifi::wifi_init(device)?;
2740
2741            #[cfg(rng_trng_supported)]
2742            esp_hal::if_unstable_hal! {
2743                unsafe {
2744                    esp_hal::rng::TrngSource::increase_entropy_source_counter()
2745                };
2746            }
2747
2748            Ok(())
2749        })?;
2750
2751        if !first {
2752            warn!(
2753                "Wi-Fi is already initialized; init-only ControllerConfig settings were ignored: \
2754                 static_rx_buf_num, dynamic_rx_buf_num, static_tx_buf_num, dynamic_tx_buf_num, \
2755                 rx_queue_size, tx_queue_size, rx_ba_win, espnow_max_encrypt_num, \
2756                 ampdu_rx_enable, ampdu_tx_enable, amsdu_tx_enable"
2757            );
2758        }
2759
2760        let mut controller = WifiController {
2761            _guard: WifiRefGuard {
2762                _radio_guard: radio_guard,
2763            },
2764            _phantom: Default::default(),
2765        };
2766
2767        controller.set_country_info(&config.country_info)?;
2768        // Set a sane default power saving mode. The blob default is not the best for bandwidth.
2769        controller.set_power_saving(PowerSaveMode::default())?;
2770
2771        controller.set_config(&config.initial_config)?;
2772
2773        // Set a default TX power
2774        esp_wifi_result!(unsafe { esp_wifi_set_max_tx_power(20) })?;
2775
2776        Ok(controller)
2777    }
2778}
2779
2780impl WifiController<'_> {
2781    /// Returns an ESP-NOW instance independent of this controller.
2782    ///
2783    /// Wi-Fi stays initialized, configured, and running until the instance is
2784    /// dropped.
2785    ///
2786    /// # Panics
2787    ///
2788    /// Panics if an ESP-NOW instance already exists.
2789    #[cfg(feature = "esp-now")]
2790    #[instability::unstable]
2791    pub fn esp_now(&self) -> crate::esp_now::EspNow {
2792        crate::esp_now::EspNow::new_internal(self._guard.clone())
2793    }
2794
2795    /// Returns a sniffer instance independent of this controller.
2796    ///
2797    /// Wi-Fi stays initialized, configured, and running until the instance is
2798    /// dropped.
2799    ///
2800    /// # Panics
2801    ///
2802    /// Panics if a sniffer instance already exists.
2803    #[cfg(feature = "sniffer")]
2804    #[instability::unstable]
2805    pub fn sniffer(&self) -> Sniffer {
2806        Sniffer::new(self._guard.clone())
2807    }
2808
2809    /// Set CSI configuration and register the receiving callback.
2810    #[cfg(feature = "csi")]
2811    #[instability::unstable]
2812    pub fn set_csi(
2813        &mut self,
2814        mut csi: csi::CsiConfig,
2815        cb: impl FnMut(crate::wifi::csi::WifiCsiInfo<'_>) + Send,
2816    ) -> Result<(), WifiError> {
2817        csi.apply_config()?;
2818        csi.set_receive_cb(cb)?;
2819        csi.set_csi(true)?;
2820
2821        Ok(())
2822    }
2823
2824    #[procmacros::doc_replace]
2825    /// Set the Wi-Fi protocol.
2826    ///
2827    /// This will set the desired protocols.
2828    ///
2829    /// # Arguments:
2830    ///
2831    /// * `protocols` - The desired protocols
2832    ///
2833    /// # Example:
2834    ///
2835    /// ```rust,no_run
2836    /// # {before_snippet}
2837    /// # use esp_radio::wifi::{ap::AccessPointConfig, Config, ControllerConfig};
2838    /// use esp_radio::wifi::Protocols;
2839    ///
2840    /// let controller_config = ControllerConfig::default().with_initial_config(Config::AccessPoint(
2841    ///     AccessPointConfig::default().with_ssid("esp-radio".try_into()?),
2842    /// ));
2843    /// let mut wifi_controller =
2844    ///     esp_radio::wifi::WifiController::new(peripherals.WIFI, controller_config)?;
2845    ///
2846    /// wifi_controller.set_protocols(Protocols::default());
2847    /// # {after_snippet}
2848    /// ```
2849    ///
2850    /// # Note
2851    ///
2852    /// Calling this function before `set_config` will return an error.
2853    #[instability::unstable]
2854    pub fn set_protocols(&mut self, protocols: Protocols) -> Result<(), WifiError> {
2855        let mode = self.mode()?;
2856        if mode.is_station() {
2857            esp_wifi_result!(unsafe {
2858                esp_wifi_set_protocols(wifi_interface_t_WIFI_IF_STA, &mut protocols.to_raw())
2859            })?;
2860        }
2861        if mode.is_access_point() {
2862            esp_wifi_result!(unsafe {
2863                esp_wifi_set_protocols(wifi_interface_t_WIFI_IF_AP, &mut protocols.to_raw())
2864            })?;
2865        }
2866
2867        Ok(())
2868    }
2869
2870    fn apply_protocols(iface: wifi_interface_t, protocols: &Protocols) -> Result<(), WifiError> {
2871        esp_wifi_result!(unsafe { esp_wifi_set_protocols(iface, &mut protocols.to_raw()) })?;
2872        Ok(())
2873    }
2874
2875    #[procmacros::doc_replace]
2876    /// Configures modem power saving.
2877    ///
2878    /// ## Example
2879    ///
2880    /// ```rust,no_run
2881    /// # {before_snippet}
2882    /// # use esp_radio::wifi::PowerSaveMode;
2883    /// let mut controller =
2884    ///     esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
2885    /// controller.set_power_saving(PowerSaveMode::Maximum)?;
2886    /// # {after_snippet}
2887    /// ```
2888    #[instability::unstable]
2889    pub fn set_power_saving(&mut self, ps: PowerSaveMode) -> Result<(), WifiError> {
2890        apply_power_saving(ps)
2891    }
2892
2893    fn set_country_info(&mut self, country: &CountryInfo) -> Result<(), WifiError> {
2894        unsafe {
2895            let country = country.into_blob();
2896            esp_wifi_result!(esp_wifi_set_country(&country))?;
2897        }
2898        Ok(())
2899    }
2900
2901    #[procmacros::doc_replace]
2902    /// Get the RSSI information of access point to which the device is associated with.
2903    /// The value is obtained from the last beacon.
2904    ///
2905    /// <div class="warning">
2906    ///
2907    /// - Use this API only in Station or AccessPoint-Station mode.
2908    /// - This API should be called after the station has connected to an access point.
2909    /// </div>
2910    ///
2911    /// ## Example
2912    ///
2913    /// ```rust,no_run
2914    /// # {before_snippet}
2915    /// # let controller = esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
2916    /// // Assume the station has already been started and connected
2917    /// match controller.rssi() {
2918    ///     Ok(rssi) => {
2919    ///         println!("RSSI: {} dBm", rssi);
2920    ///     }
2921    ///     Err(e) => {
2922    ///         println!("Failed to get RSSI: {e:?}");
2923    ///     }
2924    /// }
2925    /// # {after_snippet}
2926    /// ```
2927    ///
2928    /// # Errors
2929    /// This function returns [`WifiError::Unsupported`] if the Station side isn't
2930    /// running. For example, when configured for access point only.
2931    pub fn rssi(&self) -> Result<i32, WifiError> {
2932        if self.mode()?.is_station() {
2933            let mut rssi: i32 = 0;
2934            // Will return ESP_FAIL -1 if called in access point mode.
2935            esp_wifi_result!(unsafe { esp_wifi_sta_get_rssi(&mut rssi) })?;
2936            Ok(rssi)
2937        } else {
2938            Err(WifiError::Unsupported)
2939        }
2940    }
2941
2942    #[procmacros::doc_replace]
2943    /// Get the Access Point information of access point to which the device is associated with.
2944    /// The value is obtained from the last beacon.
2945    ///
2946    /// <div class="warning">
2947    ///
2948    /// - Use this API only in Station or AccessPoint-Station mode.
2949    /// - This API should be called after the station has connected to an access point.
2950    /// </div>
2951    ///
2952    /// ## Example
2953    ///
2954    /// ```rust,no_run
2955    /// # {before_snippet}
2956    /// # let controller = esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
2957    /// // Assume the station has already been started and connected
2958    /// match controller.ap_info() {
2959    ///     Ok(info) => {
2960    ///         println!("BSSID: {}", info.bssid);
2961    ///     }
2962    ///     Err(e) => {
2963    ///         println!("Failed to get AP info: {e:?}");
2964    ///     }
2965    /// }
2966    /// # {after_snippet}
2967    /// ```
2968    ///
2969    /// # Errors
2970    /// This function returns [`WifiError::Unsupported`] if the Station side isn't
2971    /// running. For example, when configured for access point only.
2972    pub fn ap_info(&self) -> Result<AccessPointInfo, WifiError> {
2973        if self.mode()?.is_station() {
2974            let mut record: MaybeUninit<include::wifi_ap_record_t> = MaybeUninit::uninit();
2975            esp_wifi_result!(unsafe { esp_wifi_sta_get_ap_info(record.as_mut_ptr()) })?;
2976
2977            let record = unsafe { MaybeUninit::assume_init(record) };
2978            let ap_info = convert_ap_info(&record);
2979            Ok(ap_info)
2980        } else {
2981            Err(WifiError::Unsupported)
2982        }
2983    }
2984
2985    #[procmacros::doc_replace]
2986    /// Set the configuration and (re)start the controller as needed.
2987    ///
2988    /// This will set the mode accordingly.
2989    /// You need to use [`Self::connect_async`] for connecting to an access point.
2990    ///
2991    /// If you don't intend to use Wi-Fi anymore at all consider tearing down
2992    /// Wi-Fi completely.
2993    ///
2994    /// ## Errors
2995    ///
2996    /// If this function returns an error, the Wi-Fi mode is reset to `NULL` and
2997    /// the controller is stopped.
2998    ///
2999    /// ## Example
3000    ///
3001    /// ```rust,no_run
3002    /// # {before_snippet}
3003    /// # use esp_radio::wifi::{AuthenticationMethodConfig, Config, sta::StationConfig};
3004    /// # let mut controller =
3005    /// #    esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
3006    /// let station_config = Config::Station(
3007    ///     StationConfig::default()
3008    ///         .with_ssid("SSID".try_into()?)
3009    ///         .with_authentication(AuthenticationMethodConfig::Wpa2Personal(
3010    ///             "PASSWORD".try_into()?,
3011    ///         )),
3012    /// );
3013    ///
3014    /// controller.set_config(&station_config)?;
3015    /// # {after_snippet}
3016    pub fn set_config(&mut self, conf: &Config) -> Result<(), WifiError> {
3017        // We/the driver might have applied a partial configuration so we better disable
3018        // AccessPoint/Station just in case the caller ignores the error we return here -
3019        // they will run into further errors this way.
3020        struct ResetModeOnDrop;
3021        impl ResetModeOnDrop {
3022            /// Prevent resetting the Wi-Fi mode when the guard is dropped.
3023            fn defuse(self) {
3024                core::mem::forget(self);
3025            }
3026        }
3027        impl Drop for ResetModeOnDrop {
3028            fn drop(&mut self) {
3029                unsafe { esp_wifi_set_mode(wifi_mode_t_WIFI_MODE_NULL) };
3030                unwrap!(WifiController::stop_impl());
3031            }
3032        }
3033
3034        let reset_mode_on_error = ResetModeOnDrop;
3035
3036        conf.validate()?;
3037
3038        let mut previous_mode = 0u32;
3039        esp_wifi_result!(unsafe { esp_wifi_get_mode(&mut previous_mode) })?;
3040
3041        let mode = match conf {
3042            Config::Station(_) => wifi_mode_t_WIFI_MODE_STA,
3043            Config::AccessPoint(_) => wifi_mode_t_WIFI_MODE_AP,
3044            Config::AccessPointStation(_, _) => wifi_mode_t_WIFI_MODE_APSTA,
3045            #[cfg(feature = "wifi-eap")]
3046            Config::EapStation(_) => wifi_mode_t_WIFI_MODE_STA,
3047        };
3048
3049        if previous_mode != mode {
3050            Self::stop_impl()?;
3051        }
3052
3053        esp_wifi_result!(unsafe { esp_wifi_set_mode(mode) })?;
3054
3055        match conf {
3056            Config::Station(config) => {
3057                self.apply_sta_config(config)?;
3058                Self::apply_protocols(wifi_interface_t_WIFI_IF_STA, &config.protocols)?;
3059            }
3060            Config::AccessPoint(config) => {
3061                self.apply_ap_config(config)?;
3062                Self::apply_protocols(wifi_interface_t_WIFI_IF_AP, &config.protocols)?;
3063            }
3064            Config::AccessPointStation(sta_config, ap_config) => {
3065                self.apply_ap_config(ap_config)?;
3066                Self::apply_protocols(wifi_interface_t_WIFI_IF_AP, &ap_config.protocols)?;
3067                self.apply_sta_config(sta_config)?;
3068                Self::apply_protocols(wifi_interface_t_WIFI_IF_STA, &sta_config.protocols)?;
3069            }
3070            #[cfg(feature = "wifi-eap")]
3071            Config::EapStation(config) => {
3072                self.apply_sta_eap_config(config)?;
3073                Self::apply_protocols(wifi_interface_t_WIFI_IF_STA, &config.protocols)?;
3074            }
3075        }
3076
3077        if previous_mode != mode {
3078            set_access_point_state(WifiAccessPointState::Starting);
3079            set_station_state(WifiStationState::Starting);
3080
3081            // `esp_wifi_start` is actually not async - i.e. we get the even before it returns
3082            esp_wifi_result!(unsafe { esp_wifi_start() })?;
3083        }
3084
3085        reset_mode_on_error.defuse();
3086
3087        Ok(())
3088    }
3089
3090    /// Set Wi-Fi band mode.
3091    ///
3092    /// When the Wi-Fi band mode is set to [`BandMode::_2_4G`], it operates exclusively on the
3093    /// 2.4GHz channels.
3094    #[cfg_attr(
3095        wifi_has_5g,
3096        doc = r"
3097When the WiFi band mode is set to [`BandMode::_5G`], it operates exclusively on the 5GHz channels.
3098
3099When the WiFi band mode is set to [`BandMode::Auto`], it can operate on both the 2.4GHz and
31005GHz channels.
3101
3102When a WiFi band mode change triggers a band change, if no channel is set for the current
3103band, a default channel will be assigned: channel 1 for 2.4G band and channel 36 for 5G
3104band.
3105"
3106    )]
3107    /// The controller needs to be configured and started before setting the band mode.
3108    #[instability::unstable]
3109    pub fn set_band_mode(&mut self, band_mode: BandMode) -> Result<(), WifiError> {
3110        // Wi-Fi needs to be started in order to set the band mode
3111        esp_wifi_result!(unsafe { esp_wifi_set_band_mode(band_mode.to_raw()) })
3112    }
3113
3114    /// Sets the Wi-Fi channel bandwidth for the currently active interface(s).
3115    ///
3116    /// If the device is operating in station mode, the bandwidth is applied to the
3117    /// Station interface. If operating in access point mode, it is applied to the Access Point
3118    /// interface. In Station+Access Point mode, the bandwidth is set for both interfaces.
3119    #[instability::unstable]
3120    pub fn set_bandwidths(&mut self, bandwidths: Bandwidths) -> Result<(), WifiError> {
3121        let mode = self.mode()?;
3122        if mode.is_station() {
3123            esp_wifi_result!(unsafe {
3124                esp_wifi_set_bandwidths(wifi_interface_t_WIFI_IF_STA, &mut bandwidths.to_raw())
3125            })?;
3126        }
3127        if mode.is_access_point() {
3128            esp_wifi_result!(unsafe {
3129                esp_wifi_set_bandwidths(wifi_interface_t_WIFI_IF_AP, &mut bandwidths.to_raw())
3130            })?;
3131        }
3132
3133        Ok(())
3134    }
3135
3136    /// Returns the Wi-Fi channel bandwidth of the active interface.
3137    ///
3138    /// If the device is operating in station mode, the bandwidth of the Station
3139    /// interface is returned. If operating in access point mode, the bandwidth
3140    /// of the Access Point interface is returned. In Station+Access Point mode, the bandwidth of
3141    /// the Access Point interface is returned.
3142    #[instability::unstable]
3143    pub fn bandwidths(&self) -> Result<Bandwidths, WifiError> {
3144        let mut bw = wifi_bandwidths_t {
3145            ghz_2g: 0,
3146            ghz_5g: 0,
3147        };
3148
3149        let mode = self.mode()?;
3150        if mode.is_station() {
3151            esp_wifi_result!(unsafe {
3152                esp_wifi_get_bandwidths(wifi_interface_t_WIFI_IF_STA, &mut bw)
3153            })?;
3154        }
3155        if mode.is_access_point() {
3156            esp_wifi_result!(unsafe {
3157                esp_wifi_get_bandwidths(wifi_interface_t_WIFI_IF_AP, &mut bw)
3158            })?;
3159        }
3160
3161        Ok(Bandwidths {
3162            _2_4: Bandwidth::from_raw(bw.ghz_2g),
3163            #[cfg(wifi_has_5g)]
3164            _5: Bandwidth::from_raw(bw.ghz_5g),
3165        })
3166    }
3167
3168    /// Returns the current Wi-Fi channel configuration.
3169    #[instability::unstable]
3170    pub fn channel(&self) -> Result<(u8, SecondaryChannel), WifiError> {
3171        let mut primary = 0;
3172        let mut secondary = 0;
3173
3174        esp_wifi_result!(unsafe { esp_wifi_get_channel(&mut primary, &mut secondary) })?;
3175
3176        Ok((primary, SecondaryChannel::from_raw(secondary)))
3177    }
3178
3179    /// Sets the primary and secondary Wi-Fi channel.
3180    #[cfg_attr(
3181        wifi_has_5g,
3182        doc = r"
3183
3184When operating in 5 GHz band, the second channel is automatically determined by the primary
3185channel according to the 802.11 standard. Any manually configured second channel will be
3186ignored."
3187    )]
3188    #[instability::unstable]
3189    pub fn set_channel(
3190        &mut self,
3191        primary: u8,
3192        secondary: SecondaryChannel,
3193    ) -> Result<(), WifiError> {
3194        esp_wifi_result!(unsafe { esp_wifi_set_channel(primary, secondary as u32) })?;
3195
3196        Ok(())
3197    }
3198
3199    /// Set maximum transmitting power after WiFi start.
3200    ///
3201    /// Power unit is 0.25dBm, range is [8, 84] corresponding to 2dBm - 20dBm. The default is
3202    /// 20 (5dBm). Values above roughly 65 (~16dBm) have been reported to cause authentication
3203    /// failures on some hardware. See the
3204    /// [module-level troubleshooting section](self#troubleshooting) for details.
3205    #[instability::unstable]
3206    pub fn set_max_tx_power(&mut self, power: i8) -> Result<(), WifiError> {
3207        esp_wifi_result!(unsafe { esp_wifi_set_max_tx_power(power) })
3208    }
3209
3210    fn stop_impl() -> Result<(), WifiError> {
3211        set_access_point_state(WifiAccessPointState::Stopping);
3212        set_station_state(WifiStationState::Stopping);
3213
3214        esp_wifi_result!(unsafe { esp_wifi_stop() })
3215    }
3216
3217    fn connect_impl(&mut self) -> Result<(), WifiError> {
3218        set_station_state(WifiStationState::Connecting);
3219
3220        // TODO: implement ROAMING
3221        esp_wifi_result!(unsafe { esp_wifi_connect_internal() })
3222    }
3223
3224    fn disconnect_impl(&mut self) -> Result<(), WifiError> {
3225        set_station_state(WifiStationState::Disconnecting);
3226
3227        // TODO: implement ROAMING
3228        esp_wifi_result!(unsafe { esp_wifi_disconnect_internal() })
3229    }
3230
3231    #[procmacros::doc_replace]
3232    /// Checks if the Wi-Fi controller is currently connected to an access point.
3233    /// ## Example
3234    ///
3235    /// ```rust,no_run
3236    /// # {before_snippet}
3237    /// # use esp_radio::wifi::WifiError;
3238    /// # let controller = esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
3239    /// if controller.is_connected() {
3240    ///     println!("Station is connected");
3241    /// } else {
3242    ///     println!("Station is not connected yet");
3243    /// }
3244    /// # {after_snippet}
3245    /// ```
3246    #[instability::unstable]
3247    pub fn is_connected(&self) -> bool {
3248        matches!(
3249            crate::wifi::station_state(),
3250            crate::wifi::WifiStationState::Connected
3251        )
3252    }
3253
3254    fn mode(&self) -> Result<WifiMode, WifiError> {
3255        WifiMode::current()
3256    }
3257
3258    #[procmacros::doc_replace]
3259    /// An async Wi-Fi network scan with caller-provided scanning options.
3260    ///
3261    /// Scanning is not supported in AcessPoint-only mode.
3262    ///
3263    /// ## Example
3264    ///
3265    /// ```rust,no_run
3266    /// # {before_snippet}
3267    /// # use esp_radio::wifi::{WifiController, scan::ScanConfig};
3268    /// # let mut controller = esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
3269    /// // Create a scan configuration (e.g., scan up to 10 APs)
3270    /// let scan_config = ScanConfig::default().with_max(10);
3271    /// let result = controller.scan_async(&scan_config).await.unwrap();
3272    /// for ap in result {
3273    ///     println!("{:?}", ap);
3274    /// }
3275    /// # {after_snippet}
3276    /// ```
3277    pub async fn scan_async(
3278        &mut self,
3279        config: &ScanConfig,
3280    ) -> Result<Vec<AccessPointInfo>, WifiError> {
3281        let mut subscriber = EVENT_CHANNEL
3282            .subscriber()
3283            .expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
3284
3285        esp_wifi_result!(wifi_start_scan(false, *config))?;
3286
3287        // Prevents memory leak if `scan_async`'s future is dropped.
3288        let guard = FreeApListOnDrop;
3289
3290        loop {
3291            let event = subscriber.next_message_pure().await;
3292            if let EventInfo::ScanDone {
3293                status: _status,
3294                number: _number,
3295                scan_id: _scan_id,
3296            } = event
3297            {
3298                break;
3299            }
3300        }
3301
3302        guard.defuse();
3303
3304        let limit = config.max.unwrap_or(usize::MAX);
3305        Ok(ScanResults::new(self)?.take(limit).collect::<Vec<_>>())
3306    }
3307
3308    #[procmacros::doc_replace]
3309    /// Connect Wi-Fi station to the AP.
3310    ///
3311    /// Use [Self::disconnect_async] to disconnect.
3312    ///
3313    /// Calling [Self::scan_async] will not be effective until
3314    /// connection between device and the AP is established.
3315    ///
3316    /// If device is scanning and connecting at the same time, it will abort scanning and return a
3317    /// warning message and error.
3318    ///
3319    /// ## Example
3320    ///
3321    /// ```rust,no_run
3322    /// # {before_snippet}
3323    /// # use esp_radio::wifi::{Config, sta::StationConfig};
3324    ///
3325    /// # let mut controller =
3326    /// #   esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
3327    ///
3328    /// match controller.connect_async().await {
3329    ///     Ok(_) => {
3330    ///         println!("Wifi connected!");
3331    ///     }
3332    ///     Err(e) => {
3333    ///         println!("Failed to connect to wifi: {e:?}");
3334    ///     }
3335    /// }
3336    /// # {after_snippet}
3337    pub async fn connect_async(&mut self) -> Result<sta::ConnectedInfo, ConnectionError> {
3338        let mut subscriber = EVENT_CHANNEL
3339            .subscriber()
3340            .expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
3341
3342        self.connect_impl()?;
3343
3344        let result = loop {
3345            let event = subscriber.next_message().await;
3346            if let embassy_sync::pubsub::WaitResult::Message(event) = event {
3347                match event {
3348                    EventInfo::StationConnected { .. } => {
3349                        break event;
3350                    }
3351                    EventInfo::StationDisconnected { .. } => {
3352                        break event;
3353                    }
3354                    _ => (),
3355                }
3356            }
3357        };
3358
3359        match result {
3360            event::EventInfo::StationConnected {
3361                ssid,
3362                bssid,
3363                channel,
3364                authmode,
3365                aid,
3366            } => Ok(sta::ConnectedInfo {
3367                ssid,
3368                bssid,
3369                channel,
3370                authmode: AuthenticationMethod::from_raw(authmode),
3371                aid,
3372            }),
3373            event::EventInfo::StationDisconnected {
3374                ssid,
3375                bssid,
3376                reason,
3377                rssi,
3378            } => Err(ConnectionError::Failed(sta::DisconnectedInfo {
3379                ssid,
3380                bssid,
3381                reason: DisconnectReason::from_raw(reason),
3382                rssi,
3383            })),
3384            _ => unreachable!(),
3385        }
3386    }
3387
3388    #[procmacros::doc_replace]
3389    /// Disconnect Wi-Fi station from the AP.
3390    ///
3391    /// If a connection attempt is currently in progress, it is aborted.
3392    ///
3393    /// This function will wait for the connection to be closed before returning.
3394    ///
3395    /// ## Example
3396    ///
3397    /// ```rust,no_run
3398    /// # {before_snippet}
3399    /// # use esp_radio::wifi::{Config, sta::StationConfig};
3400    ///
3401    /// # let mut controller =
3402    /// #    esp_radio::wifi::WifiController::new(peripherals.WIFI, Default::default())?;
3403    /// match controller.disconnect_async().await {
3404    ///     Ok(info) => {
3405    ///         println!("Station disconnected successfully. {info:?}");
3406    ///     }
3407    ///     Err(e) => {
3408    ///         println!("Failed to disconnect: {e:?}");
3409    ///     }
3410    /// }
3411    /// # {after_snippet}
3412    pub async fn disconnect_async(&mut self) -> Result<sta::DisconnectedInfo, WifiError> {
3413        // If neither connected nor connecting it would wait forever for a `StationDisconnected`
3414        // event that will never happen. Return early instead of hanging. Disconnecting while
3415        // `Connecting` is allowed: `esp_wifi_disconnect` cancels an in-progress connection
3416        // attempt and posts a `StationDisconnected` event.
3417        if !matches!(
3418            station_state(),
3419            WifiStationState::Connected | WifiStationState::Connecting
3420        ) {
3421            return Err(WifiError::NotConnected);
3422        }
3423
3424        let mut subscriber = EVENT_CHANNEL
3425            .subscriber()
3426            .expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
3427
3428        self.disconnect_impl()?;
3429
3430        loop {
3431            let event = subscriber.next_message_pure().await;
3432
3433            if let event::EventInfo::StationDisconnected {
3434                ssid,
3435                bssid,
3436                reason,
3437                rssi,
3438            } = event
3439            {
3440                break Ok(sta::DisconnectedInfo {
3441                    ssid,
3442                    bssid,
3443                    reason: DisconnectReason::from_raw(reason),
3444                    rssi,
3445                });
3446            }
3447        }
3448    }
3449
3450    /// Wait until the station gets disconnected from the AP.
3451    pub async fn wait_for_disconnect_async(&self) -> Result<sta::DisconnectedInfo, WifiError> {
3452        // If not connected it would wait forever for a `StationDisconnected` event that will never
3453        // happen. Return early instead of hanging.
3454        if !self.is_connected() {
3455            return Err(WifiError::NotConnected);
3456        }
3457
3458        let mut subscriber = EVENT_CHANNEL
3459            .subscriber()
3460            .expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
3461
3462        loop {
3463            let event = subscriber.next_message_pure().await;
3464
3465            if let event::EventInfo::StationDisconnected {
3466                ssid,
3467                bssid,
3468                reason,
3469                rssi,
3470            } = event
3471            {
3472                break Ok(sta::DisconnectedInfo {
3473                    ssid,
3474                    bssid,
3475                    reason: DisconnectReason::from_raw(reason),
3476                    rssi,
3477                });
3478            }
3479        }
3480    }
3481
3482    /// Wait for connected / disconnected events.
3483    pub async fn wait_for_access_point_connected_event_async(
3484        &self,
3485    ) -> Result<ap::EventInfo, WifiError> {
3486        let mut subscriber = EVENT_CHANNEL
3487            .subscriber()
3488            .expect("Unable to subscribe to events - consider increasing the internal event channel subscriber count");
3489
3490        loop {
3491            let event = subscriber.next_message_pure().await;
3492
3493            match event {
3494                event::EventInfo::AccessPointStationConnected {
3495                    mac,
3496                    aid,
3497                    is_mesh_child,
3498                } => {
3499                    break Ok(ap::EventInfo::Connected(ap::ConnectedInfo {
3500                        mac,
3501                        aid,
3502                        is_mesh_child,
3503                    }));
3504                }
3505                event::EventInfo::AccessPointStationDisconnected {
3506                    mac,
3507                    aid,
3508                    is_mesh_child,
3509                    reason,
3510                } => {
3511                    break Ok(ap::EventInfo::Disconnected(ap::DisconnectedInfo {
3512                        mac,
3513                        aid: aid as u16,
3514                        is_mesh_child,
3515                        reason: DisconnectReason::from_raw(reason),
3516                    }));
3517                }
3518                _ => (),
3519            }
3520        }
3521    }
3522
3523    /// Subscribe to events.
3524    ///
3525    /// # Errors
3526    /// This returns [WifiError::Other] if no more subscriptions are available.
3527    /// Consider increasing the internal event channel subscriber count in this case.
3528    #[instability::unstable]
3529    pub fn subscribe<'a>(&'a self) -> Result<event::EventSubscriber<'a>, WifiError> {
3530        if let Ok(subscriber) = EVENT_CHANNEL.subscriber() {
3531            return Ok(event::EventSubscriber::new(subscriber));
3532        }
3533
3534        Err(WifiError::Other)
3535    }
3536
3537    fn apply_ap_config(&mut self, config: &AccessPointConfig) -> Result<(), WifiError> {
3538        config.validate()?;
3539
3540        // The upper limit of connections available for the AP. The user set limit in
3541        // apply_ap_config is clipped to this value
3542        let ap_max_connections = TOTAL_HW_ENCRYPT_KEYS
3543            .saturating_sub(unsafe { internal::G_CONFIG.espnow_max_encrypt_num } as _);
3544
3545        let mut cfg = wifi_config_t {
3546            ap: wifi_ap_config_t {
3547                ssid: [0; 32],
3548                password: [0; 64],
3549                ssid_len: 0,
3550                channel: config.channel,
3551                authmode: config.authentication.auth_method().to_raw(),
3552                ssid_hidden: if config.ssid_hidden { 1 } else { 0 },
3553                // Clip max_connection in the same way as done internally in esp_wifi_set_config.
3554                // Doing this here so that we can do easy comparisons below
3555                max_connection: (config.max_connections as u8).min(ap_max_connections),
3556                beacon_interval: 100,
3557                pairwise_cipher: wifi_cipher_type_t_WIFI_CIPHER_TYPE_CCMP,
3558                ftm_responder: false,
3559                pmf_cfg: wifi_pmf_config_t {
3560                    capable: true,
3561                    required: false,
3562                },
3563                sae_pwe_h2e: 0,
3564                csa_count: 3,
3565                dtim_period: config.dtim_period,
3566                _bitfield_align_1: [0; 0],
3567                // transition_disable, sae_ext, wpa3_compatible_mode, reserved.
3568                // wpa3_compatible_mode is opt-in: enabling it overrides AP authmode.
3569                _bitfield_1: wifi_ap_config_t::new_bitfield_1(0, 0, 0, 0),
3570                bss_max_idle_cfg: include::wifi_bss_max_idle_config_t {
3571                    period: 0,
3572                    protected_keep_alive: false,
3573                },
3574                gtk_rekey_interval: 0,
3575            },
3576        };
3577
3578        unsafe {
3579            cfg.ap.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
3580            cfg.ap.ssid_len = config.ssid.len() as u8;
3581            if let Some(password) = config.authentication.password() {
3582                cfg.ap.password[0..(password.len())].copy_from_slice(password);
3583            }
3584
3585            // Compare the new ap config with the current. Only update if something is changing.
3586            // This avoids unnecessary connection issues.
3587            let mut current: wifi_config_t = core::mem::zeroed();
3588            if esp_wifi_get_config(wifi_interface_t_WIFI_IF_AP, &mut current)
3589                == include::ESP_OK as i32
3590                && current.ap == cfg.ap
3591            {
3592                return Ok(());
3593            }
3594
3595            esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_AP, &mut cfg))
3596        }
3597    }
3598
3599    fn apply_sta_config(&mut self, config: &StationConfig) -> Result<(), WifiError> {
3600        config.validate()?;
3601
3602        let mut cfg = wifi_config_t {
3603            sta: wifi_sta_config_t {
3604                ssid: [0; 32],
3605                password: [0; 64],
3606                scan_method: config.scan_method as c_types::c_uint,
3607                bssid_set: config.bssid.is_some(),
3608                bssid: config.bssid.unwrap_or_default(),
3609                channel: config.channel.unwrap_or(0),
3610                listen_interval: config.listen_interval,
3611                sort_method: wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
3612                threshold: wifi_scan_threshold_t {
3613                    rssi: -99,
3614                    authmode: config.authentication.auth_method().to_raw(),
3615                    rssi_5g_adjustment: 0,
3616                },
3617                pmf_cfg: wifi_pmf_config_t {
3618                    capable: true,
3619                    required: false,
3620                },
3621                sae_pwe_h2e: 3,
3622                _bitfield_align_1: [0; 0],
3623                _bitfield_1: __BindgenBitfieldUnit::new([0; 4]),
3624                failure_retry_cnt: config.failure_retry_cnt,
3625                _bitfield_align_2: [0; 0],
3626                _bitfield_2: __BindgenBitfieldUnit::new([0; 4]),
3627                sae_pk_mode: 0, // ??
3628                sae_h2e_identifier: [0; 32],
3629            },
3630        };
3631
3632        unsafe {
3633            cfg.sta.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
3634            if let Some(password) = config.authentication.password() {
3635                cfg.sta.password[0..(password.len())].copy_from_slice(password);
3636            }
3637
3638            // Compare the new sta config with the current. Only update if something is changing.
3639            // This avoids unnecessary connection issues.
3640            let mut current: wifi_config_t = core::mem::zeroed();
3641            if esp_wifi_get_config(wifi_interface_t_WIFI_IF_STA, &mut current)
3642                == include::ESP_OK as i32
3643                && current.sta == cfg.sta
3644            {
3645                return Ok(());
3646            }
3647
3648            esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_STA, &mut cfg))
3649        }
3650    }
3651
3652    #[cfg(feature = "wifi-eap")]
3653    fn apply_sta_eap_config(&mut self, config: &EapStationConfig) -> Result<(), WifiError> {
3654        let mut cfg = wifi_config_t {
3655            sta: wifi_sta_config_t {
3656                ssid: [0; 32],
3657                password: [0; 64],
3658                scan_method: config.scan_method as c_types::c_uint,
3659                bssid_set: config.bssid.is_some(),
3660                bssid: config.bssid.unwrap_or_default(),
3661                channel: config.channel.unwrap_or(0),
3662                listen_interval: config.listen_interval,
3663                sort_method: wifi_sort_method_t_WIFI_CONNECT_AP_BY_SIGNAL,
3664                threshold: wifi_scan_threshold_t {
3665                    rssi: -99,
3666                    authmode: config.auth_method.to_raw(),
3667                    rssi_5g_adjustment: 0,
3668                },
3669                pmf_cfg: wifi_pmf_config_t {
3670                    capable: true,
3671                    required: false,
3672                },
3673                sae_pwe_h2e: 3,
3674                _bitfield_align_1: [0; 0],
3675                _bitfield_1: __BindgenBitfieldUnit::new([0; 4]),
3676                failure_retry_cnt: config.failure_retry_cnt,
3677                _bitfield_align_2: [0; 0],
3678                _bitfield_2: __BindgenBitfieldUnit::new([0; 4]),
3679                sae_pk_mode: 0, // ??
3680                sae_h2e_identifier: [0; 32],
3681            },
3682        };
3683
3684        unsafe {
3685            cfg.sta.ssid[0..(config.ssid.len())].copy_from_slice(config.ssid.as_bytes());
3686            esp_wifi_result!(esp_wifi_set_config(wifi_interface_t_WIFI_IF_STA, &mut cfg))?;
3687
3688            if let Some(identity) = &config.identity {
3689                esp_wifi_result!(esp_eap_client_set_identity(
3690                    identity.as_str().as_ptr(),
3691                    identity.len() as i32
3692                ))?;
3693            } else {
3694                esp_eap_client_clear_identity();
3695            }
3696
3697            if let Some(username) = &config.username {
3698                esp_wifi_result!(esp_eap_client_set_username(
3699                    username.as_str().as_ptr(),
3700                    username.len() as i32
3701                ))?;
3702            } else {
3703                esp_eap_client_clear_username();
3704            }
3705
3706            if let Some(password) = &config.password {
3707                esp_wifi_result!(esp_eap_client_set_password(
3708                    password.as_str().as_ptr(),
3709                    password.len() as i32
3710                ))?;
3711            } else {
3712                esp_eap_client_clear_password();
3713            }
3714
3715            if let Some(new_password) = &config.new_password {
3716                esp_wifi_result!(esp_eap_client_set_new_password(
3717                    new_password.as_str().as_ptr(),
3718                    new_password.len() as i32
3719                ))?;
3720            } else {
3721                esp_eap_client_clear_new_password();
3722            }
3723
3724            if let Some(pac_file) = &config.pac_file {
3725                esp_wifi_result!(esp_eap_client_set_pac_file(
3726                    pac_file.as_ptr(),
3727                    pac_file.len() as i32
3728                ))?;
3729            }
3730
3731            if let Some(phase2_method) = &config.ttls_phase2_method {
3732                esp_wifi_result!(esp_eap_client_set_ttls_phase2_method(
3733                    phase2_method.to_raw()
3734                ))?;
3735            }
3736
3737            if let Some(ca_cert) = config.ca_cert {
3738                esp_wifi_result!(esp_eap_client_set_ca_cert(
3739                    ca_cert.as_ptr(),
3740                    ca_cert.len() as i32
3741                ))?;
3742            } else {
3743                esp_eap_client_clear_ca_cert();
3744            }
3745
3746            if let Some((cert, key, password)) = config.certificate_and_key {
3747                let (pwd, pwd_len) = if let Some(pwd) = password {
3748                    (pwd.as_ptr(), pwd.len() as i32)
3749                } else {
3750                    (core::ptr::null(), 0)
3751                };
3752
3753                esp_wifi_result!(esp_eap_client_set_certificate_and_key(
3754                    cert.as_ptr(),
3755                    cert.len() as i32,
3756                    key.as_ptr(),
3757                    key.len() as i32,
3758                    pwd,
3759                    pwd_len,
3760                ))?;
3761            } else {
3762                esp_eap_client_clear_certificate_and_key();
3763            }
3764
3765            if let Some(cfg) = &config.eap_fast_config {
3766                let params = esp_eap_fast_config {
3767                    fast_provisioning: cfg.fast_provisioning as i32,
3768                    fast_max_pac_list_len: cfg.fast_max_pac_list_len as i32,
3769                    fast_pac_format_binary: cfg.fast_pac_format_binary,
3770                };
3771                esp_wifi_result!(esp_eap_client_set_fast_params(params))?;
3772            }
3773
3774            esp_wifi_result!(esp_eap_client_set_disable_time_check(!&config.time_check))?;
3775
3776            // esp_eap_client_set_suiteb_192bit_certification unsupported because we build
3777            // without MBEDTLS
3778
3779            // esp_eap_client_use_default_cert_bundle unsupported because we build without
3780            // MBEDTLS
3781
3782            esp_wifi_result!(esp_wifi_sta_enterprise_enable())?;
3783
3784            Ok(())
3785        }
3786    }
3787}