wlan_mlme/ap/
context.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::ap::TimedEvent;
6use crate::device::DeviceOps;
7use crate::disconnect::LocallyInitiated;
8use crate::error::Error;
9use anyhow::format_err;
10use fdf::ArenaStaticBox;
11use ieee80211::{Bssid, MacAddr, MacAddrBytes, Ssid};
12use wlan_common::ie::rsn::rsne;
13use wlan_common::ie::{self};
14use wlan_common::mac::{self, Aid, AuthAlgorithmNumber, StatusCode};
15use wlan_common::sequence::SequenceManager;
16use wlan_common::timer::{EventHandle, Timer};
17use wlan_common::{TimeUnit, data_writer, mgmt_writer, wmm};
18use wlan_frame_writer::{write_frame, write_frame_with_fixed_slice};
19use zerocopy::byteorder::big_endian::U16 as BigEndianU16;
20use {fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_mlme as fidl_mlme};
21
22/// BeaconParams contains parameters that may be used to offload beaconing to the hardware.
23pub struct BeaconOffloadParams {
24    /// Offset from the start of the input buffer to the TIM element.
25    pub tim_ele_offset: usize,
26}
27
28pub struct Context<D> {
29    pub device: D,
30    pub timer: Timer<TimedEvent>,
31    pub seq_mgr: SequenceManager,
32    pub bssid: Bssid,
33}
34
35impl<D> Context<D> {
36    pub fn new(device: D, timer: Timer<TimedEvent>, bssid: Bssid) -> Self {
37        Self { device, timer, seq_mgr: SequenceManager::new(), bssid }
38    }
39
40    pub fn schedule_after(
41        &mut self,
42        duration: zx::MonotonicDuration,
43        event: TimedEvent,
44    ) -> EventHandle {
45        self.timer.schedule_after(duration, event)
46    }
47
48    /// Sends a WLAN authentication frame (IEEE Std 802.11-2016, 9.3.3.12) to the PHY.
49    pub fn make_auth_frame(
50        &mut self,
51        addr: MacAddr,
52        auth_alg_num: AuthAlgorithmNumber,
53        auth_txn_seq_num: u16,
54        status_code: StatusCode,
55    ) -> Result<ArenaStaticBox<[u8]>, Error> {
56        Ok(write_frame!({
57            headers: {
58                mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
59                    mac::FrameControl(0)
60                        .with_frame_type(mac::FrameType::MGMT)
61                        .with_mgmt_subtype(mac::MgmtSubtype::AUTH),
62                    addr,
63                    self.bssid,
64                    mac::SequenceControl(0).with_seq_num(self.seq_mgr.next_sns1(&addr) as u16)
65                ),
66                mac::AuthHdr: &mac::AuthHdr { auth_alg_num, auth_txn_seq_num, status_code },
67            }
68        })?)
69    }
70
71    /// Sends a WLAN association response frame (IEEE Std 802.11-2016, 9.3.3.7) to the PHY.
72    pub fn make_assoc_resp_frame(
73        &mut self,
74        addr: MacAddr,
75        capabilities: mac::CapabilityInfo,
76        aid: Aid,
77        rates: &[u8],
78        max_idle_period: Option<u16>,
79    ) -> Result<ArenaStaticBox<[u8]>, Error> {
80        Ok(write_frame!({
81            headers: {
82                mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
83                    mac::FrameControl(0)
84                        .with_frame_type(mac::FrameType::MGMT)
85                        .with_mgmt_subtype(mac::MgmtSubtype::ASSOC_RESP),
86                    addr,
87                    self.bssid,
88                    mac::SequenceControl(0).with_seq_num(self.seq_mgr.next_sns1(&addr) as u16)
89                ),
90                mac::AssocRespHdr: &mac::AssocRespHdr {
91                    capabilities,
92                    status_code: fidl_ieee80211::StatusCode::Success.into(),
93                    aid
94                },
95            },
96            // Order of association response frame body IEs is according to IEEE Std 802.11-2016,
97            // Table 9-30, numbered below.
98            ies: {
99                // 4: Supported Rates and BSS Membership Selectors
100                supported_rates: &rates,
101                // 5: Extended Supported Rates and BSS Membership Selectors
102                extended_supported_rates: {/* continue rates */},
103                // 19: BSS Max Idle Period
104                bss_max_idle_period?: if let Some(max_idle_period) = max_idle_period {
105                    ie::BssMaxIdlePeriod {
106                        max_idle_period,
107                        idle_options: ie::IdleOptions(0)
108                            // TODO(https://fxbug.dev/42113580): Support configuring this.
109                            .with_protected_keep_alive_required(false),
110                    }
111                },
112            }
113        })?)
114    }
115
116    /// Sends a WLAN association response frame (IEEE Std 802.11-2016, 9.3.3.7) to the PHY, but only
117    /// with the status code.
118    pub fn make_assoc_resp_frame_error(
119        &mut self,
120        addr: MacAddr,
121        capabilities: mac::CapabilityInfo,
122        status_code: StatusCode,
123    ) -> Result<ArenaStaticBox<[u8]>, Error> {
124        Ok(write_frame!({
125            headers: {
126                mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
127                    mac::FrameControl(0)
128                        .with_frame_type(mac::FrameType::MGMT)
129                        .with_mgmt_subtype(mac::MgmtSubtype::ASSOC_RESP),
130                    addr,
131                    self.bssid,
132                    mac::SequenceControl(0).with_seq_num(self.seq_mgr.next_sns1(&addr) as u16)
133                ),
134                mac::AssocRespHdr: &mac::AssocRespHdr {
135                    capabilities,
136                    status_code,
137                    aid: 0,
138                },
139            },
140        })?)
141    }
142
143    /// Sends a WLAN deauthentication frame (IEEE Std 802.11-2016, 9.3.3.1) to the PHY.
144    pub fn make_deauth_frame(
145        &mut self,
146        addr: MacAddr,
147        reason_code: mac::ReasonCode,
148    ) -> Result<ArenaStaticBox<[u8]>, Error> {
149        Ok(write_frame!({
150            headers: {
151                mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
152                    mac::FrameControl(0)
153                        .with_frame_type(mac::FrameType::MGMT)
154                        .with_mgmt_subtype(mac::MgmtSubtype::DEAUTH),
155                    addr,
156                    self.bssid,
157                    mac::SequenceControl(0).with_seq_num(self.seq_mgr.next_sns1(&addr) as u16)
158                ),
159                mac::DeauthHdr: &mac::DeauthHdr { reason_code },
160            },
161        })?)
162    }
163
164    /// Sends a WLAN disassociation frame (IEEE Std 802.11-2016, 9.3.3.5) to the PHY.
165    pub fn make_disassoc_frame(
166        &mut self,
167        addr: MacAddr,
168        reason_code: mac::ReasonCode,
169    ) -> Result<ArenaStaticBox<[u8]>, Error> {
170        Ok(write_frame!({
171            headers: {
172                mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
173                    mac::FrameControl(0)
174                        .with_frame_type(mac::FrameType::MGMT)
175                        .with_mgmt_subtype(mac::MgmtSubtype::DISASSOC),
176                    addr,
177                    self.bssid,
178                    mac::SequenceControl(0).with_seq_num(self.seq_mgr.next_sns1(&addr) as u16)
179                ),
180                mac::DisassocHdr: &mac::DisassocHdr { reason_code },
181            },
182        })?)
183    }
184
185    /// Sends a WLAN probe response frame (IEEE Std 802.11-2016, 9.3.3.11) to the PHY.
186    // TODO(https://fxbug.dev/42118243): Use this for devices that don't support probe request offload.
187    pub fn make_probe_resp_frame(
188        &mut self,
189        addr: MacAddr,
190        beacon_interval: TimeUnit,
191        capabilities: mac::CapabilityInfo,
192        ssid: &Ssid,
193        rates: &[u8],
194        channel: u8,
195        rsne: &[u8],
196    ) -> Result<ArenaStaticBox<[u8]>, Error> {
197        let rsne = (!rsne.is_empty())
198            .then(|| match rsne::from_bytes(rsne) {
199                Ok((_, x)) => Ok(x),
200                Err(e) => Err(format_err!("error parsing rsne {:?} : {:?}", rsne, e)),
201            })
202            .transpose()?;
203        Ok(write_frame!({
204            headers: {
205                mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
206                    mac::FrameControl(0)
207                        .with_frame_type(mac::FrameType::MGMT)
208                        .with_mgmt_subtype(mac::MgmtSubtype::PROBE_RESP),
209                    addr,
210                    self.bssid,
211                    mac::SequenceControl(0).with_seq_num(self.seq_mgr.next_sns1(&addr) as u16)
212                ),
213                mac::ProbeRespHdr: &mac::ProbeRespHdr::new(beacon_interval, capabilities),
214            },
215            // Order of beacon frame body IEs is according to IEEE Std 802.11-2016, Table 9-27,
216            // numbered below.
217            ies: {
218                // 4. Service Set Identifier (SSID)
219                ssid: ssid,
220                // 5. Supported Rates and BSS Membership Selectors
221                supported_rates: rates,
222                // 6. DSSS Parameter Set
223                dsss_param_set: &ie::DsssParamSet { current_channel: channel },
224                // 16. Extended Supported Rates and BSS Membership Selectors
225                extended_supported_rates: {/* continue rates */},
226                // 17. RSN
227                rsne?: rsne,
228
229            }
230        })?)
231    }
232
233    pub fn make_beacon_frame(
234        &self,
235        beacon_interval: TimeUnit,
236        capabilities: mac::CapabilityInfo,
237        ssid: &Ssid,
238        rates: &[u8],
239        channel: u8,
240        tim_header: ie::TimHeader,
241        tim_bitmap: &[u8],
242        rsne: &[u8],
243    ) -> Result<(ArenaStaticBox<[u8]>, BeaconOffloadParams), Error> {
244        let mut tim_ele_offset = 0;
245        let rsne = (!rsne.is_empty())
246            .then(|| match rsne::from_bytes(rsne) {
247                Ok((_, x)) => Ok(x),
248                Err(e) => Err(format_err!("error parsing rsne {:?} : {:?}", rsne, e)),
249            })
250            .transpose()?;
251        let buffer = write_frame!({
252            headers: {
253                mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
254                    mac::FrameControl(0)
255                        .with_frame_type(mac::FrameType::MGMT)
256                        .with_mgmt_subtype(mac::MgmtSubtype::BEACON),
257                    ieee80211::BROADCAST_ADDR,
258                    self.bssid,
259                    // The sequence control is 0 because the firmware will set it.
260                    mac::SequenceControl(0)
261                ),
262                mac::BeaconHdr: &mac::BeaconHdr::new(beacon_interval, capabilities),
263            },
264            // Order of beacon frame body IEs is according to IEEE Std 802.11-2016, Table 9-27,
265            // numbered below.
266            ies: {
267                // 4. Service Set Identifier (SSID)
268                ssid: ssid,
269                // 5. Supported Rates and BSS Membership Selectors
270                supported_rates: rates,
271                // 6. DSSS Parameter Set
272                dsss_param_set: &ie::DsssParamSet { current_channel: channel },
273                // 9. Traffic indication map (TIM)
274                // Write a placeholder TIM element, which the firmware will fill in.
275                // We only support hardware with hardware offload beaconing for now (e.g. ath10k, before it was removed).
276                tim_ele_offset @ tim: ie::TimView {
277                    header: tim_header,
278                    bitmap: tim_bitmap,
279                },
280                // 17. Extended Supported Rates and BSS Membership Selectors
281                extended_supported_rates: {/* continue rates */},
282                // 18. RSN
283                rsne?: rsne,
284
285            }
286        })?;
287        Ok((buffer, BeaconOffloadParams { tim_ele_offset }))
288    }
289    /// Sends a WLAN data frame (IEEE Std 802.11-2016, 9.3.2) to the PHY.
290    pub fn make_data_frame(
291        &mut self,
292        dst: MacAddr,
293        src: MacAddr,
294        protected: bool,
295        qos_ctrl: bool,
296        ether_type: u16,
297        payload: &[u8],
298    ) -> Result<ArenaStaticBox<[u8]>, Error> {
299        let qos_ctrl = if qos_ctrl {
300            Some(
301                wmm::derive_tid(ether_type, payload)
302                    .map_or(mac::QosControl(0), |tid| mac::QosControl(0).with_tid(tid as u16)),
303            )
304        } else {
305            None
306        };
307
308        Ok(write_frame!({
309            headers: {
310                mac::FixedDataHdrFields: &mac::FixedDataHdrFields {
311                    frame_ctrl: mac::FrameControl(0)
312                        .with_frame_type(mac::FrameType::DATA)
313                        .with_data_subtype(mac::DataSubtype(0).with_qos(qos_ctrl.is_some()))
314                        .with_protected(protected)
315                        .with_from_ds(true),
316                    duration: 0,
317                    addr1: dst,
318                    addr2: self.bssid.into(),
319                    addr3: src,
320                    seq_ctrl:  mac::SequenceControl(0).with_seq_num(
321                        match qos_ctrl.as_ref() {
322                            None => self.seq_mgr.next_sns1(&dst),
323                            Some(qos_ctrl) => self.seq_mgr.next_sns2(&dst, qos_ctrl.tid()),
324                        } as u16
325                    ),
326                },
327                mac::QosControl?: qos_ctrl,
328                mac::LlcHdr: &data_writer::make_snap_llc_hdr(ether_type),
329            },
330            payload: payload,
331        })?)
332    }
333
334    /// Sends an EAPoL data frame (IEEE Std 802.1X, 11.3) to the PHY.
335    pub fn make_eapol_frame(
336        &mut self,
337        dst_addr: MacAddr,
338        src_addr: MacAddr,
339        is_protected: bool,
340        eapol_frame: &[u8],
341    ) -> Result<ArenaStaticBox<[u8]>, Error> {
342        self.make_data_frame(
343            dst_addr,
344            src_addr,
345            is_protected,
346            false, // TODO(https://fxbug.dev/42113580): Support QoS.
347            mac::ETHER_TYPE_EAPOL,
348            eapol_frame,
349        )
350    }
351}
352
353impl<D: DeviceOps> Context<D> {
354    // MLME sender functions.
355
356    /// Sends MLME-START.confirm (IEEE Std 802.11-2016, 6.3.11.3) to the SME.
357    pub fn send_mlme_start_conf(
358        &mut self,
359        result_code: fidl_mlme::StartResultCode,
360    ) -> Result<(), Error> {
361        self.device
362            .send_mlme_event(fidl_mlme::MlmeEvent::StartConf {
363                resp: fidl_mlme::StartConfirm { result_code },
364            })
365            .map_err(|e| e.into())
366    }
367
368    /// Sends MLME-STOP.confirm to the SME.
369    pub fn send_mlme_stop_conf(
370        &mut self,
371        result_code: fidl_mlme::StopResultCode,
372    ) -> Result<(), Error> {
373        self.device
374            .send_mlme_event(fidl_mlme::MlmeEvent::StopConf {
375                resp: fidl_mlme::StopConfirm { result_code },
376            })
377            .map_err(|e| e.into())
378    }
379
380    /// Sends EAPOL.conf (fuchsia.wlan.mlme.EapolConfirm) to the SME.
381    pub fn send_mlme_eapol_conf(
382        &mut self,
383        result_code: fidl_mlme::EapolResultCode,
384        dst_addr: MacAddr,
385    ) -> Result<(), Error> {
386        self.device
387            .send_mlme_event(fidl_mlme::MlmeEvent::EapolConf {
388                resp: fidl_mlme::EapolConfirm { result_code, dst_addr: dst_addr.to_array() },
389            })
390            .map_err(|e| e.into())
391    }
392
393    /// Sends MLME-AUTHENTICATE.indication (IEEE Std 802.11-2016, 6.3.5.4) to the SME.
394    pub fn send_mlme_auth_ind(
395        &mut self,
396        peer_sta_address: MacAddr,
397        auth_type: fidl_mlme::AuthenticationTypes,
398    ) -> Result<(), Error> {
399        self.device
400            .send_mlme_event(fidl_mlme::MlmeEvent::AuthenticateInd {
401                ind: fidl_mlme::AuthenticateIndication {
402                    peer_sta_address: peer_sta_address.to_array(),
403                    auth_type,
404                },
405            })
406            .map_err(|e| e.into())
407    }
408
409    /// Sends MLME-DEAUTHENTICATE.indication (IEEE Std 802.11-2016, 6.3.6.4) to the SME.
410    pub fn send_mlme_deauth_ind(
411        &mut self,
412        peer_sta_address: MacAddr,
413        reason_code: fidl_ieee80211::ReasonCode,
414        locally_initiated: LocallyInitiated,
415    ) -> Result<(), Error> {
416        self.device
417            .send_mlme_event(fidl_mlme::MlmeEvent::DeauthenticateInd {
418                ind: fidl_mlme::DeauthenticateIndication {
419                    peer_sta_address: peer_sta_address.to_array(),
420                    reason_code,
421                    locally_initiated: locally_initiated.0,
422                },
423            })
424            .map_err(|e| e.into())
425    }
426
427    /// Sends MLME-ASSOCIATE.indication (IEEE Std 802.11-2016, 6.3.7.4) to the SME.
428    pub fn send_mlme_assoc_ind(
429        &mut self,
430        peer_sta_address: MacAddr,
431        listen_interval: u16,
432        ssid: Option<Ssid>,
433        capabilities: mac::CapabilityInfo,
434        rates: Vec<ie::SupportedRate>,
435        rsne: Option<Vec<u8>>,
436    ) -> Result<(), Error> {
437        self.device
438            .send_mlme_event(fidl_mlme::MlmeEvent::AssociateInd {
439                ind: fidl_mlme::AssociateIndication {
440                    peer_sta_address: peer_sta_address.to_array(),
441                    listen_interval,
442                    ssid: ssid.map(|s| s.into()),
443                    capability_info: capabilities.raw(),
444                    rates: rates.iter().map(|r| r.0).collect(),
445                    rsne,
446                    // TODO(https://fxbug.dev/42113580): Send everything else (e.g. HT capabilities).
447                },
448            })
449            .map_err(|e| e.into())
450    }
451
452    /// Sends MLME-DISASSOCIATE.indication (IEEE Std 802.11-2016, 6.3.9.3) to the SME.
453    pub fn send_mlme_disassoc_ind(
454        &mut self,
455        peer_sta_address: MacAddr,
456        reason_code: fidl_ieee80211::ReasonCode,
457        locally_initiated: LocallyInitiated,
458    ) -> Result<(), Error> {
459        self.device
460            .send_mlme_event(fidl_mlme::MlmeEvent::DisassociateInd {
461                ind: fidl_mlme::DisassociateIndication {
462                    peer_sta_address: peer_sta_address.to_array(),
463                    reason_code,
464                    locally_initiated: locally_initiated.0,
465                },
466            })
467            .map_err(|e| e.into())
468    }
469
470    /// Sends EAPOL.indication (fuchsia.wlan.mlme.EapolIndication) to the SME.
471    pub fn send_mlme_eapol_ind(
472        &mut self,
473        dst_addr: MacAddr,
474        src_addr: MacAddr,
475        data: &[u8],
476    ) -> Result<(), Error> {
477        self.device
478            .send_mlme_event(fidl_mlme::MlmeEvent::EapolInd {
479                ind: fidl_mlme::EapolIndication {
480                    dst_addr: dst_addr.to_array(),
481                    src_addr: src_addr.to_array(),
482                    data: data.to_vec(),
483                },
484            })
485            .map_err(|e| e.into())
486    }
487
488    // Netstack delivery functions.
489
490    /// Delivers the Ethernet II frame to the netstack.
491    pub fn deliver_eth_frame(
492        &mut self,
493        dst_addr: MacAddr,
494        src_addr: MacAddr,
495        protocol_id: u16,
496        body: &[u8],
497    ) -> Result<(), Error> {
498        let mut packet = [0u8; mac::MAX_ETH_FRAME_LEN];
499        let (frame_start, frame_end) = write_frame_with_fixed_slice!(&mut packet[..], {
500            headers: {
501                mac::EthernetIIHdr: &mac::EthernetIIHdr {
502                    da: dst_addr,
503                    sa: src_addr,
504                    ether_type: BigEndianU16::new(protocol_id),
505                },
506            },
507            payload: body,
508        })?;
509        self.device
510            .deliver_eth_frame(&packet[frame_start..frame_end])
511            .map_err(|s| Error::Status(format!("could not deliver Ethernet II frame"), s))
512    }
513}
514
515#[cfg(test)]
516mod test {
517    use super::*;
518    use crate::ap::ClientEvent;
519    use crate::device::FakeDevice;
520    use assert_matches::assert_matches;
521    use std::sync::LazyLock;
522    use wlan_common::timer::{self, create_timer};
523
524    static CLIENT_ADDR: LazyLock<MacAddr> = LazyLock::new(|| [1u8; 6].into());
525    static BSSID: LazyLock<Bssid> = LazyLock::new(|| [2u8; 6].into());
526    static CLIENT_ADDR2: LazyLock<MacAddr> = LazyLock::new(|| [3u8; 6].into());
527
528    fn make_context(
529        fake_device: FakeDevice,
530    ) -> (Context<FakeDevice>, timer::EventStream<TimedEvent>) {
531        let (timer, time_stream) = create_timer();
532        (Context::new(fake_device, timer, *BSSID), time_stream)
533    }
534
535    #[fuchsia::test(allow_stalls = false)]
536    async fn send_mlme_auth_ind() {
537        let (fake_device, fake_device_state) = FakeDevice::new().await;
538        let (mut ctx, _) = make_context(fake_device);
539        ctx.send_mlme_auth_ind(*CLIENT_ADDR, fidl_mlme::AuthenticationTypes::OpenSystem)
540            .expect("expected OK");
541        let msg = fake_device_state
542            .lock()
543            .next_mlme_msg::<fidl_mlme::AuthenticateIndication>()
544            .expect("expected MLME message");
545        assert_eq!(
546            msg,
547            fidl_mlme::AuthenticateIndication {
548                peer_sta_address: CLIENT_ADDR.to_array(),
549                auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
550            },
551        );
552    }
553
554    #[fuchsia::test(allow_stalls = false)]
555    async fn send_mlme_deauth_ind() {
556        let (fake_device, fake_device_state) = FakeDevice::new().await;
557        let (mut ctx, _) = make_context(fake_device);
558        ctx.send_mlme_deauth_ind(
559            *CLIENT_ADDR,
560            fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
561            LocallyInitiated(true),
562        )
563        .expect("expected OK");
564        let msg = fake_device_state
565            .lock()
566            .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
567            .expect("expected MLME message");
568        assert_eq!(
569            msg,
570            fidl_mlme::DeauthenticateIndication {
571                peer_sta_address: CLIENT_ADDR.to_array(),
572                reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
573                locally_initiated: true,
574            },
575        );
576    }
577
578    #[fuchsia::test(allow_stalls = false)]
579    async fn send_mlme_assoc_ind() {
580        let (fake_device, fake_device_state) = FakeDevice::new().await;
581        let (mut ctx, _) = make_context(fake_device);
582        ctx.send_mlme_assoc_ind(
583            *CLIENT_ADDR,
584            1,
585            Some(Ssid::try_from("coolnet").unwrap()),
586            mac::CapabilityInfo(0),
587            vec![ie::SupportedRate(1), ie::SupportedRate(2), ie::SupportedRate(3)],
588            None,
589        )
590        .expect("expected OK");
591        let msg = fake_device_state
592            .lock()
593            .next_mlme_msg::<fidl_mlme::AssociateIndication>()
594            .expect("expected MLME message");
595        assert_eq!(
596            msg,
597            fidl_mlme::AssociateIndication {
598                peer_sta_address: CLIENT_ADDR.to_array(),
599                listen_interval: 1,
600                ssid: Some(Ssid::try_from("coolnet").unwrap().into()),
601                capability_info: mac::CapabilityInfo(0).raw(),
602                rates: vec![1, 2, 3],
603                rsne: None,
604            },
605        );
606    }
607
608    #[fuchsia::test(allow_stalls = false)]
609    async fn send_mlme_disassoc_ind() {
610        let (fake_device, fake_device_state) = FakeDevice::new().await;
611        let (mut ctx, _) = make_context(fake_device);
612        ctx.send_mlme_disassoc_ind(
613            *CLIENT_ADDR,
614            fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc,
615            LocallyInitiated(true),
616        )
617        .expect("expected OK");
618        let msg = fake_device_state
619            .lock()
620            .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
621            .expect("expected MLME message");
622        assert_eq!(
623            msg,
624            fidl_mlme::DisassociateIndication {
625                peer_sta_address: CLIENT_ADDR.to_array(),
626                reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc,
627                locally_initiated: true,
628            },
629        );
630    }
631
632    #[fuchsia::test(allow_stalls = false)]
633    async fn send_mlme_eapol_ind() {
634        let (fake_device, fake_device_state) = FakeDevice::new().await;
635        let (mut ctx, _) = make_context(fake_device);
636        ctx.send_mlme_eapol_ind(*CLIENT_ADDR2, *CLIENT_ADDR, &[1, 2, 3, 4, 5][..])
637            .expect("expected OK");
638        let msg = fake_device_state
639            .lock()
640            .next_mlme_msg::<fidl_mlme::EapolIndication>()
641            .expect("expected MLME message");
642        assert_eq!(
643            msg,
644            fidl_mlme::EapolIndication {
645                dst_addr: CLIENT_ADDR2.to_array(),
646                src_addr: CLIENT_ADDR.to_array(),
647                data: vec![1, 2, 3, 4, 5],
648            },
649        );
650    }
651
652    #[fuchsia::test(allow_stalls = false)]
653    async fn schedule_after() {
654        let (fake_device, _) = FakeDevice::new().await;
655        let (mut ctx, mut time_stream) = make_context(fake_device);
656        let event_handle = ctx.schedule_after(
657            zx::MonotonicDuration::from_seconds(5),
658            TimedEvent::ClientEvent(MacAddr::from([1; 6]), ClientEvent::BssIdleTimeout),
659        );
660        let (_, timed_event, _) =
661            time_stream.try_next().unwrap().expect("Should have scheduled an event");
662        assert_eq!(timed_event.id, event_handle.id());
663
664        assert_matches!(
665            timed_event.event,
666            TimedEvent::ClientEvent(mac_addr, ClientEvent::BssIdleTimeout) => {
667                assert_eq!(MacAddr::from([1; 6]), mac_addr);
668            }
669        );
670        assert!(time_stream.try_next().is_err());
671    }
672
673    #[fuchsia::test(allow_stalls = false)]
674    async fn make_auth_frame() {
675        let (fake_device, _) = FakeDevice::new().await;
676        let (mut ctx, _) = make_context(fake_device);
677        let buffer = ctx
678            .make_auth_frame(
679                *CLIENT_ADDR,
680                AuthAlgorithmNumber::FAST_BSS_TRANSITION,
681                3,
682                fidl_ieee80211::StatusCode::TransactionSequenceError.into(),
683            )
684            .expect("error making auth frame");
685        assert_eq!(
686            &buffer[..],
687            &[
688                // Mgmt header
689                0b10110000, 0, // Frame Control
690                0, 0, // Duration
691                1, 1, 1, 1, 1, 1, // addr1
692                2, 2, 2, 2, 2, 2, // addr2
693                2, 2, 2, 2, 2, 2, // addr3
694                0x10, 0, // Sequence Control
695                // Auth header:
696                2, 0, // auth algorithm
697                3, 0, // auth txn seq num
698                14, 0, // Status code
699            ][..]
700        );
701    }
702
703    #[fuchsia::test(allow_stalls = false)]
704    async fn make_assoc_resp_frame() {
705        let (fake_device, _) = FakeDevice::new().await;
706        let (mut ctx, _) = make_context(fake_device);
707        let buffer = ctx
708            .make_assoc_resp_frame(
709                *CLIENT_ADDR,
710                mac::CapabilityInfo(0),
711                1,
712                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
713                Some(99),
714            )
715            .expect("error making assoc resp frame");
716        assert_eq!(
717            &buffer[..],
718            &[
719                // Mgmt header
720                0b00010000, 0, // Frame Control
721                0, 0, // Duration
722                1, 1, 1, 1, 1, 1, // addr1
723                2, 2, 2, 2, 2, 2, // addr2
724                2, 2, 2, 2, 2, 2, // addr3
725                0x10, 0, // Sequence Control
726                // Association response header:
727                0, 0, // Capabilities
728                0, 0, // status code
729                1, 0, // AID
730                // IEs
731                1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Rates
732                50, 2, 9, 10, // Extended rates
733                90, 3, 99, 0, 0, // BSS max idle period
734            ][..]
735        );
736    }
737
738    #[fuchsia::test(allow_stalls = false)]
739    async fn make_assoc_resp_frame_error() {
740        let (fake_device, _) = FakeDevice::new().await;
741        let (mut ctx, _) = make_context(fake_device);
742        let buffer = ctx
743            .make_assoc_resp_frame_error(
744                *CLIENT_ADDR,
745                mac::CapabilityInfo(0),
746                fidl_ieee80211::StatusCode::RejectedEmergencyServicesNotSupported.into(),
747            )
748            .expect("error making assoc resp frame error");
749        assert_eq!(
750            &buffer[..],
751            &[
752                // Mgmt header
753                0b00010000, 0, // Frame Control
754                0, 0, // Duration
755                1, 1, 1, 1, 1, 1, // addr1
756                2, 2, 2, 2, 2, 2, // addr2
757                2, 2, 2, 2, 2, 2, // addr3
758                0x10, 0, // Sequence Control
759                // Association response header:
760                0, 0, // Capabilities
761                94, 0, // status code
762                0, 0, // AID
763            ][..]
764        );
765    }
766
767    #[fuchsia::test(allow_stalls = false)]
768    async fn make_assoc_resp_frame_no_bss_max_idle_period() {
769        let (fake_device, _) = FakeDevice::new().await;
770        let (mut ctx, _) = make_context(fake_device);
771        let buffer = ctx
772            .make_assoc_resp_frame(
773                *CLIENT_ADDR,
774                mac::CapabilityInfo(0),
775                1,
776                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
777                None,
778            )
779            .expect("error making assoc resp frame");
780        assert_eq!(
781            &buffer[..],
782            &[
783                // Mgmt header
784                0b00010000, 0, // Frame Control
785                0, 0, // Duration
786                1, 1, 1, 1, 1, 1, // addr1
787                2, 2, 2, 2, 2, 2, // addr2
788                2, 2, 2, 2, 2, 2, // addr3
789                0x10, 0, // Sequence Control
790                // Association response header:
791                0, 0, // Capabilities
792                0, 0, // status code
793                1, 0, // AID
794                // IEs
795                1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Rates
796                50, 2, 9, 10, // Extended rates
797            ][..]
798        );
799    }
800
801    #[fuchsia::test(allow_stalls = false)]
802    async fn make_disassoc_frame() {
803        let (fake_device, _) = FakeDevice::new().await;
804        let (mut ctx, _) = make_context(fake_device);
805        let buffer = ctx
806            .make_disassoc_frame(
807                *CLIENT_ADDR,
808                fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc.into(),
809            )
810            .expect("error making disassoc frame");
811        assert_eq!(
812            &buffer[..],
813            &[
814                // Mgmt header
815                0b10100000, 0, // Frame Control
816                0, 0, // Duration
817                1, 1, 1, 1, 1, 1, // addr1
818                2, 2, 2, 2, 2, 2, // addr2
819                2, 2, 2, 2, 2, 2, // addr3
820                0x10, 0, // Sequence Control
821                // Disassoc header:
822                8, 0, // reason code
823            ][..]
824        );
825    }
826
827    #[fuchsia::test(allow_stalls = false)]
828    async fn make_probe_resp_frame() {
829        let (fake_device, _) = FakeDevice::new().await;
830        let (mut ctx, _) = make_context(fake_device);
831        let buffer = ctx
832            .make_probe_resp_frame(
833                *CLIENT_ADDR,
834                TimeUnit(10),
835                mac::CapabilityInfo(33),
836                &Ssid::try_from([1, 2, 3, 4, 5]).unwrap(),
837                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
838                2,
839                &[48, 2, 77, 88][..],
840            )
841            .expect("error making probe resp frame");
842        assert_eq!(
843            &buffer[..],
844            &[
845                // Mgmt header
846                0b01010000, 0, // Frame Control
847                0, 0, // Duration
848                1, 1, 1, 1, 1, 1, // addr1
849                2, 2, 2, 2, 2, 2, // addr2
850                2, 2, 2, 2, 2, 2, // addr3
851                0x10, 0, // Sequence Control
852                // Beacon header:
853                0, 0, 0, 0, 0, 0, 0, 0, // Timestamp zero since TSF Timer not implemented
854                10, 0, // Beacon interval
855                33, 0, // Capabilities
856                // IEs:
857                0, 5, 1, 2, 3, 4, 5, // SSID
858                1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Supported rates
859                3, 1, 2, // DSSS parameter set
860                50, 2, 9, 10, // Extended rates
861                48, 2, 77, 88, // RSNE
862            ][..]
863        );
864    }
865
866    #[fuchsia::test(allow_stalls = false)]
867    async fn make_beacon_frame() {
868        let (fake_device, _) = FakeDevice::new().await;
869        let (ctx, _) = make_context(fake_device);
870
871        let (buffer, params) = ctx
872            .make_beacon_frame(
873                TimeUnit(10),
874                mac::CapabilityInfo(33),
875                &Ssid::try_from([1, 2, 3, 4, 5]).unwrap(),
876                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
877                2,
878                ie::TimHeader { dtim_count: 1, dtim_period: 2, bmp_ctrl: ie::BitmapControl(0) },
879                &[1, 2, 3][..],
880                &[48, 2, 77, 88][..],
881            )
882            .expect("error making probe resp frame");
883        assert_eq!(
884            &buffer[..],
885            &[
886                // Mgmt header
887                0b10000000, 0, // Frame Control
888                0, 0, // Duration
889                255, 255, 255, 255, 255, 255, // addr1
890                2, 2, 2, 2, 2, 2, // addr2
891                2, 2, 2, 2, 2, 2, // addr3
892                0, 0, // Sequence Control
893                // Beacon header:
894                0, 0, 0, 0, 0, 0, 0, 0, // Timestamp zero since TSF Timer not implemented
895                10, 0, // Beacon interval
896                33, 0, // Capabilities
897                // IEs:
898                0, 5, 1, 2, 3, 4, 5, // SSID
899                1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Supported rates
900                3, 1, 2, // DSSS parameter set
901                5, 6, 1, 2, 0, 1, 2, 3, // TIM
902                50, 2, 9, 10, // Extended rates
903                48, 2, 77, 88, // RSNE
904            ][..]
905        );
906        assert_eq!(params.tim_ele_offset, 56);
907    }
908
909    #[fuchsia::test(allow_stalls = false)]
910    async fn make_data_frame() {
911        let (fake_device, _) = FakeDevice::new().await;
912        let (mut ctx, _) = make_context(fake_device);
913        let buffer = ctx
914            .make_data_frame(*CLIENT_ADDR2, *CLIENT_ADDR, false, false, 0x1234, &[1, 2, 3, 4, 5])
915            .expect("error making data frame");
916        assert_eq!(
917            &buffer[..],
918            &[
919                // Mgmt header
920                0b00001000, 0b00000010, // Frame Control
921                0, 0, // Duration
922                3, 3, 3, 3, 3, 3, // addr1
923                2, 2, 2, 2, 2, 2, // addr2
924                1, 1, 1, 1, 1, 1, // addr3
925                0x10, 0, // Sequence Control
926                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
927                0, 0, 0, // OUI
928                0x12, 0x34, // Protocol ID
929                // Data
930                1, 2, 3, 4, 5,
931            ][..]
932        );
933    }
934
935    #[fuchsia::test(allow_stalls = false)]
936    async fn make_data_frame_ipv4_qos() {
937        let (fake_device, _) = FakeDevice::new().await;
938        let (mut ctx, _) = make_context(fake_device);
939        let buffer = ctx
940            .make_data_frame(
941                *CLIENT_ADDR2,
942                *CLIENT_ADDR,
943                false,
944                true,
945                0x0800, // IPv4
946                // Not valid IPv4 payload (too short).
947                // However, we only care that it includes the DS field.
948                &[1, 0xB0, 3, 4, 5], // DSCP = 0b010110 (i.e. AF23)
949            )
950            .expect("error making data frame");
951        assert_eq!(
952            &buffer[..],
953            &[
954                // Mgmt header
955                0b10001000, 0b00000010, // Frame Control
956                0, 0, // Duration
957                3, 3, 3, 3, 3, 3, // addr1
958                2, 2, 2, 2, 2, 2, // addr2
959                1, 1, 1, 1, 1, 1, // addr3
960                0x10, 0, // Sequence Control
961                0x06, 0, // QoS Control - TID = 6
962                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
963                0, 0, 0, // OUI
964                0x08, 0x00, // Protocol ID
965                // Payload
966                1, 0xB0, 3, 4, 5,
967            ][..]
968        );
969    }
970
971    #[fuchsia::test(allow_stalls = false)]
972    async fn make_data_frame_ipv6_qos() {
973        let (fake_device, _) = FakeDevice::new().await;
974        let (mut ctx, _) = make_context(fake_device);
975        let buffer = ctx
976            .make_data_frame(
977                *CLIENT_ADDR2,
978                *CLIENT_ADDR,
979                false,
980                true,
981                0x86DD, // IPv6
982                // Not valid IPv6 payload (too short).
983                // However, we only care that it includes the DS field.
984                &[0b0101, 0b10000000, 3, 4, 5], // DSCP = 0b101100 (i.e. VOICE-ADMIT)
985            )
986            .expect("error making data frame");
987        assert_eq!(
988            &buffer[..],
989            &[
990                // Mgmt header
991                0b10001000, 0b00000010, // Frame Control
992                0, 0, // Duration
993                3, 3, 3, 3, 3, 3, // addr1
994                2, 2, 2, 2, 2, 2, // addr2
995                1, 1, 1, 1, 1, 1, // addr3
996                0x10, 0, // Sequence Control
997                0x03, 0, // QoS Control - TID = 3
998                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
999                0, 0, 0, // OUI
1000                0x86, 0xDD, // Protocol ID
1001                // Payload
1002                0b0101, 0b10000000, 3, 4, 5,
1003            ][..]
1004        );
1005    }
1006
1007    #[fuchsia::test(allow_stalls = false)]
1008    async fn make_eapol_frame() {
1009        let (fake_device, _) = FakeDevice::new().await;
1010        let (mut ctx, _) = make_context(fake_device);
1011        let buffer = ctx
1012            .make_eapol_frame(*CLIENT_ADDR2, *CLIENT_ADDR, false, &[1, 2, 3, 4, 5])
1013            .expect("error making eapol frame");
1014        assert_eq!(
1015            &buffer[..],
1016            &[
1017                // Mgmt header
1018                0b00001000, 0b00000010, // Frame Control
1019                0, 0, // Duration
1020                3, 3, 3, 3, 3, 3, // addr1
1021                2, 2, 2, 2, 2, 2, // addr2
1022                1, 1, 1, 1, 1, 1, // addr3
1023                0x10, 0, // Sequence Control
1024                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
1025                0, 0, 0, // OUI
1026                0x88, 0x8E, // EAPOL protocol ID
1027                // Data
1028                1, 2, 3, 4, 5,
1029            ][..]
1030        );
1031    }
1032
1033    #[fuchsia::test(allow_stalls = false)]
1034    async fn deliver_eth_frame() {
1035        let (fake_device, fake_device_state) = FakeDevice::new().await;
1036        let (mut ctx, _) = make_context(fake_device);
1037        ctx.deliver_eth_frame(*CLIENT_ADDR2, *CLIENT_ADDR, 0x1234, &[1, 2, 3, 4, 5][..])
1038            .expect("expected OK");
1039        assert_eq!(fake_device_state.lock().eth_queue.len(), 1);
1040        #[rustfmt::skip]
1041        assert_eq!(&fake_device_state.lock().eth_queue[0][..], &[
1042            3, 3, 3, 3, 3, 3,  // dest
1043            1, 1, 1, 1, 1, 1,  // src
1044            0x12, 0x34,        // ether_type
1045            // Data
1046            1, 2, 3, 4, 5,
1047        ][..]);
1048    }
1049}