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