wlan_hw_sim/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::event::action::{self, AuthenticationControl, AuthenticationTap};
6use crate::event::{Handler, branch};
7use fidl::endpoints::{create_endpoints, create_proxy};
8use fidl_fuchsia_wlan_common::WlanMacRole;
9use fidl_fuchsia_wlan_tap::{WlanRxInfo, WlantapPhyConfig, WlantapPhyProxy};
10use fuchsia_component::client::connect_to_protocol_at;
11use ieee80211::{Bssid, MacAddr, Ssid};
12use std::future::Future;
13use std::pin::pin;
14use std::sync::LazyLock;
15use wlan_common::bss::Protection;
16use wlan_common::channel::{Cbw, Channel};
17use wlan_common::ie::rsn::cipher::{CIPHER_CCMP_128, CIPHER_TKIP, Cipher};
18use wlan_common::ie::rsn::rsne;
19use wlan_common::ie::rsn::suite_filter::DEFAULT_GROUP_MGMT_CIPHER;
20use wlan_common::ie::wpa;
21use wlan_common::{TimeUnit, data_writer, mac, mgmt_writer};
22use wlan_frame_writer::write_frame_to_vec;
23use wlan_rsn::rsna::UpdateSink;
24use {
25    fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
26    fidl_fuchsia_wlan_mlme as fidl_mlme, fidl_fuchsia_wlan_policy as fidl_policy,
27    fidl_fuchsia_wlan_softmac as fidl_wlan_softmac,
28};
29
30pub mod event;
31pub mod netdevice_helper;
32pub mod test_utils;
33
34pub use wlancfg_helper::*;
35
36mod config;
37mod wlancfg_helper;
38
39pub const PSK_STR_LEN: usize = 64;
40
41pub static CLIENT_MAC_ADDR: LazyLock<MacAddr> =
42    LazyLock::new(|| [0x67, 0x62, 0x6f, 0x6e, 0x69, 0x6b].into());
43pub static AP_MAC_ADDR: LazyLock<Bssid> =
44    LazyLock::new(|| [0x70, 0xf1, 0x1c, 0x05, 0x2d, 0x7f].into());
45pub static AP_SSID: LazyLock<Ssid> = LazyLock::new(|| Ssid::try_from("ap_ssid").unwrap());
46pub static ETH_DST_MAC: LazyLock<MacAddr> =
47    LazyLock::new(|| [0x65, 0x74, 0x68, 0x64, 0x73, 0x74].into());
48
49pub const WLANCFG_DEFAULT_AP_CHANNEL: Channel = Channel { primary: 11, cbw: Cbw::Cbw20 };
50
51// TODO(https://fxbug.dev/42060050): This sleep was introduced to preserve the old timing behavior
52// of scanning when hw-sim depending on the SoftMAC driver iterating through all of the
53// channels.
54pub static ARTIFICIAL_SCAN_SLEEP: LazyLock<zx::MonotonicDuration> =
55    LazyLock::new(|| zx::MonotonicDuration::from_seconds(2));
56
57// Once a client interface is available for scanning, it takes up to around 30s for a scan
58// to complete (see https://fxbug.dev/42061276). Allow at least double that amount of time to reduce
59// flakiness and longer than the timeout WLAN policy should have.
60pub static SCAN_RESPONSE_TEST_TIMEOUT: LazyLock<zx::MonotonicDuration> =
61    LazyLock::new(|| zx::MonotonicDuration::from_seconds(70));
62
63/// A client supplicant.
64///
65/// Provides the client and security components necessary to attempt a connection via Policy.
66pub struct Supplicant<'a> {
67    pub controller: &'a fidl_policy::ClientControllerProxy,
68    pub state_update_stream: &'a mut fidl_policy::ClientStateUpdatesRequestStream,
69    pub security_type: fidl_policy::SecurityType,
70    pub password: Option<&'a str>,
71}
72
73impl<'a> Supplicant<'a> {
74    /// Clones the supplicant through reborrowing of mutable references.
75    ///
76    /// # Examples
77    ///
78    /// This function can be used for templating.
79    ///
80    /// ```rust,ignore
81    /// let mut supplicant = Supplicant { /* ... */ }; // Template.
82    /// // ...
83    /// // Connect via the template supplicant but with a particular password.
84    /// let _ = connect(Supplicant { password: "********", ..supplicant.reborrow() });
85    /// ```
86    pub fn reborrow(&mut self) -> Supplicant<'_> {
87        Supplicant {
88            controller: self.controller,
89            state_update_stream: &mut *self.state_update_stream,
90            security_type: self.security_type,
91            password: self.password,
92        }
93    }
94}
95
96pub fn default_wlantap_config_client() -> WlantapPhyConfig {
97    wlantap_config_client(format!("wlantap-client"), *CLIENT_MAC_ADDR)
98}
99
100pub fn wlantap_config_client(name: String, mac_addr: MacAddr) -> WlantapPhyConfig {
101    config::create_wlantap_config(name, mac_addr, WlanMacRole::Client)
102}
103
104pub fn default_wlantap_config_ap() -> WlantapPhyConfig {
105    wlantap_config_ap(format!("wlantap-ap"), (*AP_MAC_ADDR).into())
106}
107
108pub fn wlantap_config_ap(name: String, mac_addr: MacAddr) -> WlantapPhyConfig {
109    config::create_wlantap_config(name, mac_addr, WlanMacRole::Ap)
110}
111
112pub fn rx_info_with_default_ap() -> WlanRxInfo {
113    rx_info_with_valid_rssi(&WLANCFG_DEFAULT_AP_CHANNEL, 0)
114}
115
116fn rx_info_with_valid_rssi(channel: &Channel, rssi_dbm: i8) -> WlanRxInfo {
117    WlanRxInfo {
118        rx_flags: 0,
119        valid_fields: if rssi_dbm == 0 {
120            0
121        } else {
122            fidl_wlan_softmac::WlanRxInfoValid::RSSI.bits()
123        },
124        phy: fidl_common::WlanPhyType::Dsss,
125        data_rate: 0,
126        channel: fidl_ieee80211::WlanChannel::from(channel),
127        mcs: 0,
128        rssi_dbm,
129        snr_dbh: 0,
130    }
131}
132
133pub fn send_sae_authentication_frame(
134    sae_frame: &fidl_mlme::SaeFrame,
135    channel: &Channel,
136    bssid: &Bssid,
137    proxy: &WlantapPhyProxy,
138) -> Result<(), anyhow::Error> {
139    let buffer = write_frame_to_vec!({
140        headers: {
141            mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
142                mac::FrameControl(0)
143                    .with_frame_type(mac::FrameType::MGMT)
144                    .with_mgmt_subtype(mac::MgmtSubtype::AUTH),
145                *CLIENT_MAC_ADDR,
146                bssid.clone().into(),
147                mac::SequenceControl(0).with_seq_num(123),
148            ),
149            mac::AuthHdr: &mac::AuthHdr {
150                auth_alg_num: mac::AuthAlgorithmNumber::SAE,
151                auth_txn_seq_num: sae_frame.seq_num,
152                status_code: sae_frame.status_code.into(),
153            },
154        },
155        body: &sae_frame.sae_fields[..],
156    })?;
157    proxy.rx(&buffer, &rx_info_with_valid_rssi(channel, 0))?;
158    Ok(())
159}
160
161pub fn send_open_authentication(
162    channel: &Channel,
163    bssid: &Bssid,
164    status_code: impl Into<mac::StatusCode>,
165    proxy: &WlantapPhyProxy,
166) -> Result<(), anyhow::Error> {
167    let buffer = write_frame_to_vec!({
168        headers: {
169            mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
170                mac::FrameControl(0)
171                    .with_frame_type(mac::FrameType::MGMT)
172                    .with_mgmt_subtype(mac::MgmtSubtype::AUTH),
173                *CLIENT_MAC_ADDR,
174                bssid.clone().into(),
175                mac::SequenceControl(0).with_seq_num(123),
176            ),
177            mac::AuthHdr: &mac::AuthHdr {
178                auth_alg_num: mac::AuthAlgorithmNumber::OPEN,
179                auth_txn_seq_num: 2,
180                status_code: status_code.into(),
181            },
182        },
183    })?;
184    proxy.rx(&buffer, &rx_info_with_valid_rssi(channel, 0))?;
185    Ok(())
186}
187
188pub fn send_association_response(
189    channel: &Channel,
190    bssid: &Bssid,
191    status_code: impl Into<mac::StatusCode>,
192    proxy: &WlantapPhyProxy,
193) -> Result<(), anyhow::Error> {
194    let buffer = write_frame_to_vec!({
195        headers: {
196            mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
197                mac::FrameControl(0)
198                    .with_frame_type(mac::FrameType::MGMT)
199                    .with_mgmt_subtype(mac::MgmtSubtype::ASSOC_RESP),
200                *CLIENT_MAC_ADDR,
201                bssid.clone().into(),
202                mac::SequenceControl(0).with_seq_num(123),
203            ),
204            mac::AssocRespHdr: &mac::AssocRespHdr {
205                capabilities: mac::CapabilityInfo(0).with_ess(true).with_short_preamble(true),
206                status_code: status_code.into(),
207                aid: 2, // does not matter
208            },
209        },
210        ies: {
211            // These rates will be captured in assoc_cfg to initialize Minstrel. 11b rates are
212            // ignored.
213            // tx_vec_idx:        _     _     _   129   130     _   131   132
214            supported_rates: &[0x82, 0x84, 0x8b, 0x0c, 0x12, 0x96, 0x18, 0x24],
215            // tx_vec_idx:              133 134 basic_135  136
216            extended_supported_rates:  &[48, 72, 128 + 96, 108],
217        },
218    })?;
219    proxy.rx(&buffer, &rx_info_with_valid_rssi(channel, 0))?;
220    Ok(())
221}
222
223pub fn send_disassociate(
224    channel: &Channel,
225    bssid: &Bssid,
226    reason_code: impl Into<mac::ReasonCode>,
227    proxy: &WlantapPhyProxy,
228) -> Result<(), anyhow::Error> {
229    let buffer = write_frame_to_vec!({
230        headers: {
231            mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
232                mac::FrameControl(0)
233                    .with_frame_type(mac::FrameType::MGMT)
234                    .with_mgmt_subtype(mac::MgmtSubtype::DISASSOC),
235                *CLIENT_MAC_ADDR,
236                bssid.clone().into(),
237                mac::SequenceControl(0).with_seq_num(123),
238            ),
239            mac::DisassocHdr: &mac::DisassocHdr {
240                reason_code: reason_code.into(),
241            },
242        },
243    })?;
244    proxy.rx(&buffer, &rx_info_with_valid_rssi(channel, 0))?;
245    Ok(())
246}
247
248pub fn password_or_psk_to_policy_credential<S: ToString>(
249    password_or_psk: Option<S>,
250) -> fidl_policy::Credential {
251    return match password_or_psk {
252        None => fidl_policy::Credential::None(fidl_policy::Empty),
253        Some(p) => {
254            let p = p.to_string().as_bytes().to_vec();
255            if p.len() == PSK_STR_LEN {
256                // The PSK is given in a 64 character hexadecimal string.
257                let psk = hex::decode(p).expect("Failed to decode psk");
258                fidl_policy::Credential::Psk(psk)
259            } else {
260                fidl_policy::Credential::Password(p)
261            }
262        }
263    };
264}
265
266pub fn create_authenticator(
267    bssid: &Bssid,
268    ssid: &Ssid,
269    password_or_psk: &str,
270    // The group key cipher
271    gtk_cipher: Cipher,
272    // The advertised protection in the IEs during the 4-way handshake
273    advertised_protection: Protection,
274    // The protection used for the actual handshake
275    supplicant_protection: Protection,
276) -> wlan_rsn::Authenticator {
277    let nonce_rdr =
278        wlan_rsn::nonce::NonceReader::new(&bssid.clone().into()).expect("creating nonce reader");
279    let gtk_provider = wlan_rsn::GtkProvider::new(gtk_cipher, 1, 0).expect("creating gtk provider");
280
281    let advertised_protection_info = match advertised_protection {
282        Protection::Wpa3Personal => wlan_rsn::ProtectionInfo::Rsne(rsne::Rsne::wpa3_rsne()),
283        Protection::Wpa2Wpa3Personal => {
284            wlan_rsn::ProtectionInfo::Rsne(rsne::Rsne::wpa2_wpa3_rsne())
285        }
286        Protection::Wpa2Personal | Protection::Wpa1Wpa2Personal => wlan_rsn::ProtectionInfo::Rsne(
287            rsne::Rsne::wpa2_rsne_with_caps(rsne::RsnCapabilities(0)),
288        ),
289        Protection::Wpa2PersonalTkipOnly | Protection::Wpa1Wpa2PersonalTkipOnly => {
290            panic!("need tkip support")
291        }
292        Protection::Wpa1 => {
293            wlan_rsn::ProtectionInfo::LegacyWpa(wpa::fake_wpa_ies::fake_deprecated_wpa1_vendor_ie())
294        }
295        _ => {
296            panic!("{} not implemented", advertised_protection)
297        }
298    };
299
300    match supplicant_protection {
301        Protection::Wpa1 | Protection::Wpa2Personal => {
302            let psk = match password_or_psk.len() {
303                PSK_STR_LEN => {
304                    // The PSK is given in a 64 character hexadecimal string.
305                    hex::decode(password_or_psk).expect("Failed to decode psk").into_boxed_slice()
306                }
307                _ => {
308                    wlan_rsn::psk::compute(password_or_psk.as_bytes(), ssid).expect("computing PSK")
309                }
310            };
311            let supplicant_protection_info = match supplicant_protection {
312                Protection::Wpa1 => wlan_rsn::ProtectionInfo::LegacyWpa(
313                    wpa::fake_wpa_ies::fake_deprecated_wpa1_vendor_ie(),
314                ),
315                Protection::Wpa2Personal => wlan_rsn::ProtectionInfo::Rsne(
316                    rsne::Rsne::wpa2_rsne_with_caps(rsne::RsnCapabilities(0)),
317                ),
318                _ => unreachable!("impossible combination in this nested match"),
319            };
320            wlan_rsn::Authenticator::new_wpa2psk_ccmp128(
321                nonce_rdr,
322                std::sync::Arc::new(fuchsia_sync::Mutex::new(gtk_provider)),
323                psk,
324                *CLIENT_MAC_ADDR,
325                supplicant_protection_info,
326                bssid.clone().into(),
327                advertised_protection_info,
328            )
329            .expect("creating authenticator")
330        }
331        Protection::Wpa3Personal => {
332            let igtk_provider = wlan_rsn::IgtkProvider::new(DEFAULT_GROUP_MGMT_CIPHER)
333                .expect("creating igtk provider");
334            let supplicant_protection_info =
335                wlan_rsn::ProtectionInfo::Rsne(rsne::Rsne::wpa3_rsne());
336            wlan_rsn::Authenticator::new_wpa3(
337                nonce_rdr,
338                std::sync::Arc::new(fuchsia_sync::Mutex::new(gtk_provider)),
339                std::sync::Arc::new(fuchsia_sync::Mutex::new(igtk_provider)),
340                ssid.clone(),
341                password_or_psk.as_bytes().to_vec(),
342                *CLIENT_MAC_ADDR,
343                supplicant_protection_info,
344                bssid.clone().into(),
345                advertised_protection_info,
346            )
347            .expect("creating authenticator")
348        }
349        _ => {
350            panic!("Cannot create an authenticator for {}", supplicant_protection)
351        }
352    }
353}
354
355pub enum ApAdvertisementMode {
356    Beacon,
357    ProbeResponse,
358}
359
360pub trait ApAdvertisement {
361    fn mode(&self) -> ApAdvertisementMode;
362    fn channel(&self) -> &Channel;
363    fn bssid(&self) -> &Bssid;
364    fn ssid(&self) -> &Ssid;
365    fn protection(&self) -> &Protection;
366    fn rssi_dbm(&self) -> i8;
367    fn wsc_ie(&self) -> Option<&Vec<u8>>;
368
369    fn beacon_interval(&self) -> TimeUnit {
370        TimeUnit::DEFAULT_BEACON_INTERVAL * 20u16
371    }
372
373    fn capabilities(&self) -> mac::CapabilityInfo {
374        mac::CapabilityInfo(0)
375            // IEEE Std 802.11-2016, 9.4.1.4: An AP sets the ESS subfield to 1 and the IBSS
376            // subfield to 0 within transmitted Beacon or Probe Response frames.
377            .with_ess(true)
378            .with_ibss(false)
379            // IEEE Std 802.11-2016, 9.4.1.4: An AP sets the Privacy subfield to 1 within
380            // transmitted Beacon, Probe Response, (Re)Association Response frames if data
381            // confidentiality is required for all Data frames exchanged within the BSS.
382            .with_privacy(*self.protection() != Protection::Open)
383    }
384
385    fn send(&self, phy: &WlantapPhyProxy) -> Result<(), anyhow::Error> {
386        let buffer = self.generate_frame()?;
387        phy.rx(&buffer, &rx_info_with_valid_rssi(&self.channel(), self.rssi_dbm()))?;
388        Ok(())
389    }
390
391    fn generate_frame(&self) -> Result<Vec<u8>, anyhow::Error> {
392        let mode = self.mode();
393        let protection = self.protection();
394        let beacon_header = match mode {
395            ApAdvertisementMode::Beacon => {
396                Some(mac::BeaconHdr::new(self.beacon_interval(), self.capabilities()))
397            }
398            _ => None,
399        };
400        let probe_response_header = match mode {
401            ApAdvertisementMode::ProbeResponse => {
402                Some(mac::ProbeRespHdr::new(self.beacon_interval(), self.capabilities()))
403            }
404            _ => None,
405        };
406
407        let buffer = write_frame_to_vec!({
408            headers: {
409                mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
410                    mac::FrameControl(0)
411                        .with_frame_type(mac::FrameType::MGMT)
412                        .with_mgmt_subtype(match mode {
413                            ApAdvertisementMode::Beacon => mac::MgmtSubtype::BEACON,
414                            ApAdvertisementMode::ProbeResponse{..} => mac::MgmtSubtype::PROBE_RESP
415                        }),
416                    match mode {
417                        ApAdvertisementMode::Beacon => ieee80211::BROADCAST_ADDR,
418                        ApAdvertisementMode::ProbeResponse{..} => *CLIENT_MAC_ADDR
419                    },
420                    *self.bssid(),
421                    mac::SequenceControl(0).with_seq_num(123),
422                ),
423                mac::BeaconHdr?: beacon_header,
424                mac::ProbeRespHdr?: probe_response_header,
425            },
426            ies: {
427                ssid: &self.ssid(),
428                supported_rates: &[0x82, 0x84, 0x8b, 0x0c, 0x12, 0x96, 0x18, 0x24, 0x30, 0x48, 0xe0, 0x6c],
429                extended_supported_rates: { /* continues from supported_rates */ },
430                dsss_param_set: &ie::DsssParamSet { current_channel: self.channel().primary },
431                rsne?: match protection {
432                    Protection::Unknown => panic!("Cannot send beacon with unknown protection"),
433                    Protection::Open | Protection::Wep | Protection::Wpa1 => None,
434                    Protection::Wpa1Wpa2Personal | Protection::Wpa2Personal =>
435                        Some(rsne::Rsne::wpa2_rsne_with_caps(rsne::RsnCapabilities(0))),
436                    Protection::Wpa2Wpa3Personal => Some(rsne::Rsne::wpa2_wpa3_rsne()),
437                    Protection::Wpa3Personal => Some(rsne::Rsne::wpa3_rsne()),
438                    _ => panic!("unsupported fake beacon: {:?}", protection),
439                },
440                wpa1?: match protection {
441                    Protection::Unknown => panic!("Cannot send beacon with unknown protection"),
442                    Protection::Open | Protection::Wep => None,
443                    Protection::Wpa1 | Protection::Wpa1Wpa2Personal => Some(wpa::fake_wpa_ies::fake_deprecated_wpa1_vendor_ie()),
444                    Protection::Wpa2Personal | Protection::Wpa2Wpa3Personal | Protection::Wpa3Personal => None,
445                    _ => panic!("unsupported fake beacon: {:?}", protection),
446                },
447                wsc?: self.wsc_ie()
448            },
449        })?;
450        Ok(buffer.into())
451    }
452}
453
454pub struct Beacon {
455    pub channel: Channel,
456    pub bssid: Bssid,
457    pub ssid: Ssid,
458    pub protection: Protection,
459    pub rssi_dbm: i8,
460}
461
462impl ApAdvertisement for Beacon {
463    fn mode(&self) -> ApAdvertisementMode {
464        ApAdvertisementMode::Beacon
465    }
466    fn channel(&self) -> &Channel {
467        &self.channel
468    }
469    fn bssid(&self) -> &Bssid {
470        &self.bssid
471    }
472    fn ssid(&self) -> &Ssid {
473        &self.ssid
474    }
475    fn protection(&self) -> &Protection {
476        &self.protection
477    }
478    fn rssi_dbm(&self) -> i8 {
479        self.rssi_dbm
480    }
481    fn wsc_ie(&self) -> Option<&Vec<u8>> {
482        None
483    }
484}
485
486pub struct ProbeResponse {
487    pub channel: Channel,
488    pub bssid: Bssid,
489    pub ssid: Ssid,
490    pub protection: Protection,
491    pub rssi_dbm: i8,
492    pub wsc_ie: Option<Vec<u8>>,
493}
494
495impl ApAdvertisement for ProbeResponse {
496    fn mode(&self) -> ApAdvertisementMode {
497        ApAdvertisementMode::ProbeResponse
498    }
499    fn channel(&self) -> &Channel {
500        &self.channel
501    }
502    fn bssid(&self) -> &Bssid {
503        &self.bssid
504    }
505    fn ssid(&self) -> &Ssid {
506        &self.ssid
507    }
508    fn protection(&self) -> &Protection {
509        &self.protection
510    }
511    fn rssi_dbm(&self) -> i8 {
512        self.rssi_dbm
513    }
514    fn wsc_ie(&self) -> Option<&Vec<u8>> {
515        self.wsc_ie.as_ref()
516    }
517}
518
519pub async fn save_network_and_wait_until_connected(
520    test_ns_prefix: &str,
521    ssid: &Ssid,
522    security_type: fidl_policy::SecurityType,
523    credential: fidl_policy::Credential,
524) -> (fidl_policy::ClientControllerProxy, fidl_policy::ClientStateUpdatesRequestStream) {
525    // Connect to the client policy service and get a client controller.
526    let (client_controller, mut client_state_update_stream) =
527        wlancfg_helper::init_client_controller(test_ns_prefix).await;
528
529    save_network(&client_controller, ssid, security_type, credential).await;
530
531    // Wait until the policy layer indicates that the client has successfully connected.
532    let id = fidl_policy::NetworkIdentifier { ssid: ssid.to_vec(), type_: security_type.clone() };
533    wait_until_client_state(&mut client_state_update_stream, |update| {
534        has_id_and_state(update, &id, fidl_policy::ConnectionState::Connected)
535    })
536    .await;
537
538    (client_controller, client_state_update_stream)
539}
540
541/// Runs a future until completion or timeout with a client event handler that attempts to connect
542/// to an AP with the given SSID, BSSID, and protection.
543pub async fn connect_or_timeout_with<F>(
544    helper: &mut test_utils::TestHelper,
545    timeout: zx::MonotonicDuration,
546    ssid: &Ssid,
547    bssid: &Bssid,
548    protection: &Protection,
549    authenticator: Option<wlan_rsn::Authenticator>,
550    future: F,
551) -> F::Output
552where
553    F: Future + Unpin,
554{
555    let phy = helper.proxy();
556    let channel = Channel::new(1, Cbw::Cbw20);
557    let beacons = [Beacon {
558        channel,
559        bssid: bssid.clone(),
560        ssid: ssid.clone(),
561        protection: protection.clone(),
562        rssi_dbm: -30,
563    }];
564    let mut control = authenticator
565        .map(|authenticator| AuthenticationControl { updates: UpdateSink::new(), authenticator });
566    let connect = if let Some(ref mut control) = control {
567        let tap = AuthenticationTap { control, handler: action::authenticate_with_control_state() };
568        event::boxed(action::connect_with_authentication_tap(
569            &phy, ssid, bssid, &channel, protection, tap,
570        ))
571    } else {
572        event::boxed(action::connect_with_open_authentication(
573            &phy, ssid, bssid, &channel, protection,
574        ))
575    };
576    helper
577        .run_until_complete_or_timeout(
578            timeout,
579            format!(
580                "connecting to {} ({:02X?}) with {:?} protection",
581                ssid.to_string_not_redactable(),
582                bssid,
583                protection,
584            ),
585            branch::or((
586                event::on_scan(action::send_advertisements_and_scan_completion(&phy, beacons)),
587                event::on_transmit(connect),
588            ))
589            .expect("failed to connect client"),
590            future,
591        )
592        .await
593}
594
595/// Waits for a timeout or Policy to establish a connection to an AP with the given SSID, BSSID,
596/// and protection.
597pub async fn connect_or_timeout(
598    helper: &mut test_utils::TestHelper,
599    timeout: zx::MonotonicDuration,
600    ssid: &Ssid,
601    bssid: &Bssid,
602    bss_protection: &Protection,
603    password_or_psk: Option<&str>,
604    security_type: fidl_policy::SecurityType,
605) {
606    let authenticator = match bss_protection {
607        Protection::Wpa3Personal | Protection::Wpa2Wpa3Personal => {
608            password_or_psk.map(|password_or_psk| {
609                create_authenticator(
610                    bssid,
611                    ssid,
612                    password_or_psk,
613                    CIPHER_CCMP_128,
614                    *bss_protection,
615                    Protection::Wpa3Personal,
616                )
617            })
618        }
619        Protection::Wpa2Personal | Protection::Wpa1Wpa2Personal => {
620            password_or_psk.map(|password_or_psk| {
621                create_authenticator(
622                    bssid,
623                    ssid,
624                    password_or_psk,
625                    CIPHER_CCMP_128,
626                    *bss_protection,
627                    Protection::Wpa2Personal,
628                )
629            })
630        }
631        Protection::Wpa2PersonalTkipOnly | Protection::Wpa1Wpa2PersonalTkipOnly => {
632            panic!("Hardware simulator does not support WPA2-TKIP.")
633        }
634        Protection::Wpa1 => password_or_psk.map(|password_or_psk| {
635            create_authenticator(
636                bssid,
637                ssid,
638                password_or_psk,
639                CIPHER_TKIP,
640                *bss_protection,
641                Protection::Wpa1,
642            )
643        }),
644        Protection::Open => None,
645        _ => {
646            panic!("Unsupported WLAN protection: {}", bss_protection)
647        }
648    };
649
650    let credential = password_or_psk_to_policy_credential(password_or_psk);
651    let test_ns_prefix = helper.test_ns_prefix().to_string();
652    let connect = pin!(save_network_and_wait_until_connected(
653        &test_ns_prefix,
654        ssid,
655        security_type,
656        credential
657    ));
658    connect_or_timeout_with(helper, timeout, ssid, bssid, bss_protection, authenticator, connect)
659        .await;
660}
661
662pub fn rx_wlan_data_frame(
663    channel: &Channel,
664    addr1: &MacAddr,
665    addr2: &MacAddr,
666    addr3: &MacAddr,
667    payload: &[u8],
668    ether_type: u16,
669    phy: &WlantapPhyProxy,
670) -> Result<(), anyhow::Error> {
671    let buffer = write_frame_to_vec!({
672        headers: {
673            mac::FixedDataHdrFields: &mac::FixedDataHdrFields {
674                frame_ctrl: mac::FrameControl(0)
675                    .with_frame_type(mac::FrameType::DATA)
676                    .with_data_subtype(mac::DataSubtype(0))
677                    .with_from_ds(true),
678                duration: 0,
679                addr1: *addr1,
680                addr2: *addr2,
681                addr3: *addr3,
682                seq_ctrl: mac::SequenceControl(0).with_seq_num(3),
683            },
684            mac::LlcHdr: &data_writer::make_snap_llc_hdr(ether_type),
685        },
686        payload: payload,
687    })?;
688
689    phy.rx(&buffer, &rx_info_with_valid_rssi(channel, 0))?;
690    Ok(())
691}
692
693pub async fn loop_until_iface_is_found(helper: &mut test_utils::TestHelper) {
694    // Connect to the client policy service and get a client controller.
695    let policy_provider =
696        connect_to_protocol_at::<fidl_policy::ClientProviderMarker>(helper.test_ns_prefix())
697            .expect("connecting to wlan policy");
698    let (client_controller, server_end) = create_proxy();
699    let (update_client_end, _update_server_end) = create_endpoints();
700    let () =
701        policy_provider.get_controller(server_end, update_client_end).expect("getting controller");
702
703    // Attempt to issue a scan command until the request succeeds.  Scanning will fail until a
704    // client interface is available.  A successful response to a scan request indicates that the
705    // client policy layer is ready to use.
706    // TODO(https://fxbug.dev/42135259): Figure out a new way to signal that the client policy layer is ready to go.
707    let mut retry = test_utils::RetryWithBackoff::infinite_with_max_interval(
708        zx::MonotonicDuration::from_seconds(10),
709    );
710    loop {
711        let (scan_proxy, server_end) = create_proxy();
712        client_controller.scan_for_networks(server_end).expect("requesting scan");
713
714        let fut = pin!(async move { scan_proxy.get_next().await.expect("getting scan results") });
715
716        let phy = helper.proxy();
717        match helper
718            .run_until_complete_or_timeout(
719                *SCAN_RESPONSE_TEST_TIMEOUT,
720                "receive a scan response",
721                event::on_scan(action::send_advertisements_and_scan_completion(
722                    &phy,
723                    [] as [Beacon; 0],
724                )),
725                fut,
726            )
727            .await
728        {
729            Err(_) => {
730                retry.sleep_unless_after_deadline().await.unwrap_or_else(|_| {
731                    panic!("Wlanstack did not recognize the interface in time")
732                });
733            }
734            Ok(_) => return,
735        }
736    }
737}