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