Skip to main content

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