esp_radio/wifi/
mod.rs

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