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::big_endian::BigEndianU16;
13use wlan_common::ie::rsn::rsne;
14use wlan_common::ie::{self};
15use wlan_common::mac::{self, Aid, AuthAlgorithmNumber, StatusCode};
16use wlan_common::sequence::SequenceManager;
17use wlan_common::timer::{EventHandle, Timer};
18use wlan_common::{data_writer, mgmt_writer, wmm, TimeUnit};
19use wlan_frame_writer::{write_frame, write_frame_with_fixed_slice};
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::from_native(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 lazy_static::lazy_static;
521    use wlan_common::assert_variant;
522    use wlan_common::timer::{self, create_timer};
523
524    lazy_static! {
525        static ref CLIENT_ADDR: MacAddr = [1u8; 6].into();
526        static ref BSSID: Bssid = [2u8; 6].into();
527        static ref CLIENT_ADDR2: MacAddr = [3u8; 6].into();
528    }
529
530    fn make_context(
531        fake_device: FakeDevice,
532    ) -> (Context<FakeDevice>, timer::EventStream<TimedEvent>) {
533        let (timer, time_stream) = create_timer();
534        (Context::new(fake_device, timer, *BSSID), time_stream)
535    }
536
537    #[fuchsia::test(allow_stalls = false)]
538    async fn send_mlme_auth_ind() {
539        let (fake_device, fake_device_state) = FakeDevice::new().await;
540        let (mut ctx, _) = make_context(fake_device);
541        ctx.send_mlme_auth_ind(*CLIENT_ADDR, fidl_mlme::AuthenticationTypes::OpenSystem)
542            .expect("expected OK");
543        let msg = fake_device_state
544            .lock()
545            .next_mlme_msg::<fidl_mlme::AuthenticateIndication>()
546            .expect("expected MLME message");
547        assert_eq!(
548            msg,
549            fidl_mlme::AuthenticateIndication {
550                peer_sta_address: CLIENT_ADDR.to_array(),
551                auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
552            },
553        );
554    }
555
556    #[fuchsia::test(allow_stalls = false)]
557    async fn send_mlme_deauth_ind() {
558        let (fake_device, fake_device_state) = FakeDevice::new().await;
559        let (mut ctx, _) = make_context(fake_device);
560        ctx.send_mlme_deauth_ind(
561            *CLIENT_ADDR,
562            fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
563            LocallyInitiated(true),
564        )
565        .expect("expected OK");
566        let msg = fake_device_state
567            .lock()
568            .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
569            .expect("expected MLME message");
570        assert_eq!(
571            msg,
572            fidl_mlme::DeauthenticateIndication {
573                peer_sta_address: CLIENT_ADDR.to_array(),
574                reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
575                locally_initiated: true,
576            },
577        );
578    }
579
580    #[fuchsia::test(allow_stalls = false)]
581    async fn send_mlme_assoc_ind() {
582        let (fake_device, fake_device_state) = FakeDevice::new().await;
583        let (mut ctx, _) = make_context(fake_device);
584        ctx.send_mlme_assoc_ind(
585            *CLIENT_ADDR,
586            1,
587            Some(Ssid::try_from("coolnet").unwrap()),
588            mac::CapabilityInfo(0),
589            vec![ie::SupportedRate(1), ie::SupportedRate(2), ie::SupportedRate(3)],
590            None,
591        )
592        .expect("expected OK");
593        let msg = fake_device_state
594            .lock()
595            .next_mlme_msg::<fidl_mlme::AssociateIndication>()
596            .expect("expected MLME message");
597        assert_eq!(
598            msg,
599            fidl_mlme::AssociateIndication {
600                peer_sta_address: CLIENT_ADDR.to_array(),
601                listen_interval: 1,
602                ssid: Some(Ssid::try_from("coolnet").unwrap().into()),
603                capability_info: mac::CapabilityInfo(0).raw(),
604                rates: vec![1, 2, 3],
605                rsne: None,
606            },
607        );
608    }
609
610    #[fuchsia::test(allow_stalls = false)]
611    async fn send_mlme_disassoc_ind() {
612        let (fake_device, fake_device_state) = FakeDevice::new().await;
613        let (mut ctx, _) = make_context(fake_device);
614        ctx.send_mlme_disassoc_ind(
615            *CLIENT_ADDR,
616            fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc,
617            LocallyInitiated(true),
618        )
619        .expect("expected OK");
620        let msg = fake_device_state
621            .lock()
622            .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
623            .expect("expected MLME message");
624        assert_eq!(
625            msg,
626            fidl_mlme::DisassociateIndication {
627                peer_sta_address: CLIENT_ADDR.to_array(),
628                reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc,
629                locally_initiated: true,
630            },
631        );
632    }
633
634    #[fuchsia::test(allow_stalls = false)]
635    async fn send_mlme_eapol_ind() {
636        let (fake_device, fake_device_state) = FakeDevice::new().await;
637        let (mut ctx, _) = make_context(fake_device);
638        ctx.send_mlme_eapol_ind(*CLIENT_ADDR2, *CLIENT_ADDR, &[1, 2, 3, 4, 5][..])
639            .expect("expected OK");
640        let msg = fake_device_state
641            .lock()
642            .next_mlme_msg::<fidl_mlme::EapolIndication>()
643            .expect("expected MLME message");
644        assert_eq!(
645            msg,
646            fidl_mlme::EapolIndication {
647                dst_addr: CLIENT_ADDR2.to_array(),
648                src_addr: CLIENT_ADDR.to_array(),
649                data: vec![1, 2, 3, 4, 5],
650            },
651        );
652    }
653
654    #[fuchsia::test(allow_stalls = false)]
655    async fn schedule_after() {
656        let (fake_device, _) = FakeDevice::new().await;
657        let (mut ctx, mut time_stream) = make_context(fake_device);
658        let event_handle = ctx.schedule_after(
659            zx::MonotonicDuration::from_seconds(5),
660            TimedEvent::ClientEvent(MacAddr::from([1; 6]), ClientEvent::BssIdleTimeout),
661        );
662        let (_, timed_event, _) =
663            time_stream.try_next().unwrap().expect("Should have scheduled an event");
664        assert_eq!(timed_event.id, event_handle.id());
665
666        assert_variant!(
667            timed_event.event,
668            TimedEvent::ClientEvent(mac_addr, ClientEvent::BssIdleTimeout) => {
669                assert_eq!(MacAddr::from([1; 6]), mac_addr);
670            }
671        );
672        assert!(time_stream.try_next().is_err());
673    }
674
675    #[fuchsia::test(allow_stalls = false)]
676    async fn make_auth_frame() {
677        let (fake_device, _) = FakeDevice::new().await;
678        let (mut ctx, _) = make_context(fake_device);
679        let buffer = ctx
680            .make_auth_frame(
681                *CLIENT_ADDR,
682                AuthAlgorithmNumber::FAST_BSS_TRANSITION,
683                3,
684                fidl_ieee80211::StatusCode::TransactionSequenceError.into(),
685            )
686            .expect("error making auth frame");
687        assert_eq!(
688            &buffer[..],
689            &[
690                // Mgmt header
691                0b10110000, 0, // Frame Control
692                0, 0, // Duration
693                1, 1, 1, 1, 1, 1, // addr1
694                2, 2, 2, 2, 2, 2, // addr2
695                2, 2, 2, 2, 2, 2, // addr3
696                0x10, 0, // Sequence Control
697                // Auth header:
698                2, 0, // auth algorithm
699                3, 0, // auth txn seq num
700                14, 0, // Status code
701            ][..]
702        );
703    }
704
705    #[fuchsia::test(allow_stalls = false)]
706    async fn make_assoc_resp_frame() {
707        let (fake_device, _) = FakeDevice::new().await;
708        let (mut ctx, _) = make_context(fake_device);
709        let buffer = ctx
710            .make_assoc_resp_frame(
711                *CLIENT_ADDR,
712                mac::CapabilityInfo(0),
713                1,
714                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
715                Some(99),
716            )
717            .expect("error making assoc resp frame");
718        assert_eq!(
719            &buffer[..],
720            &[
721                // Mgmt header
722                0b00010000, 0, // Frame Control
723                0, 0, // Duration
724                1, 1, 1, 1, 1, 1, // addr1
725                2, 2, 2, 2, 2, 2, // addr2
726                2, 2, 2, 2, 2, 2, // addr3
727                0x10, 0, // Sequence Control
728                // Association response header:
729                0, 0, // Capabilities
730                0, 0, // status code
731                1, 0, // AID
732                // IEs
733                1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Rates
734                50, 2, 9, 10, // Extended rates
735                90, 3, 99, 0, 0, // BSS max idle period
736            ][..]
737        );
738    }
739
740    #[fuchsia::test(allow_stalls = false)]
741    async fn make_assoc_resp_frame_error() {
742        let (fake_device, _) = FakeDevice::new().await;
743        let (mut ctx, _) = make_context(fake_device);
744        let buffer = ctx
745            .make_assoc_resp_frame_error(
746                *CLIENT_ADDR,
747                mac::CapabilityInfo(0),
748                fidl_ieee80211::StatusCode::RejectedEmergencyServicesNotSupported.into(),
749            )
750            .expect("error making assoc resp frame error");
751        assert_eq!(
752            &buffer[..],
753            &[
754                // Mgmt header
755                0b00010000, 0, // Frame Control
756                0, 0, // Duration
757                1, 1, 1, 1, 1, 1, // addr1
758                2, 2, 2, 2, 2, 2, // addr2
759                2, 2, 2, 2, 2, 2, // addr3
760                0x10, 0, // Sequence Control
761                // Association response header:
762                0, 0, // Capabilities
763                94, 0, // status code
764                0, 0, // AID
765            ][..]
766        );
767    }
768
769    #[fuchsia::test(allow_stalls = false)]
770    async fn make_assoc_resp_frame_no_bss_max_idle_period() {
771        let (fake_device, _) = FakeDevice::new().await;
772        let (mut ctx, _) = make_context(fake_device);
773        let buffer = ctx
774            .make_assoc_resp_frame(
775                *CLIENT_ADDR,
776                mac::CapabilityInfo(0),
777                1,
778                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
779                None,
780            )
781            .expect("error making assoc resp frame");
782        assert_eq!(
783            &buffer[..],
784            &[
785                // Mgmt header
786                0b00010000, 0, // Frame Control
787                0, 0, // Duration
788                1, 1, 1, 1, 1, 1, // addr1
789                2, 2, 2, 2, 2, 2, // addr2
790                2, 2, 2, 2, 2, 2, // addr3
791                0x10, 0, // Sequence Control
792                // Association response header:
793                0, 0, // Capabilities
794                0, 0, // status code
795                1, 0, // AID
796                // IEs
797                1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Rates
798                50, 2, 9, 10, // Extended rates
799            ][..]
800        );
801    }
802
803    #[fuchsia::test(allow_stalls = false)]
804    async fn make_disassoc_frame() {
805        let (fake_device, _) = FakeDevice::new().await;
806        let (mut ctx, _) = make_context(fake_device);
807        let buffer = ctx
808            .make_disassoc_frame(
809                *CLIENT_ADDR,
810                fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc.into(),
811            )
812            .expect("error making disassoc frame");
813        assert_eq!(
814            &buffer[..],
815            &[
816                // Mgmt header
817                0b10100000, 0, // Frame Control
818                0, 0, // Duration
819                1, 1, 1, 1, 1, 1, // addr1
820                2, 2, 2, 2, 2, 2, // addr2
821                2, 2, 2, 2, 2, 2, // addr3
822                0x10, 0, // Sequence Control
823                // Disassoc header:
824                8, 0, // reason code
825            ][..]
826        );
827    }
828
829    #[fuchsia::test(allow_stalls = false)]
830    async fn make_probe_resp_frame() {
831        let (fake_device, _) = FakeDevice::new().await;
832        let (mut ctx, _) = make_context(fake_device);
833        let buffer = ctx
834            .make_probe_resp_frame(
835                *CLIENT_ADDR,
836                TimeUnit(10),
837                mac::CapabilityInfo(33),
838                &Ssid::try_from([1, 2, 3, 4, 5]).unwrap(),
839                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
840                2,
841                &[48, 2, 77, 88][..],
842            )
843            .expect("error making probe resp frame");
844        assert_eq!(
845            &buffer[..],
846            &[
847                // Mgmt header
848                0b01010000, 0, // Frame Control
849                0, 0, // Duration
850                1, 1, 1, 1, 1, 1, // addr1
851                2, 2, 2, 2, 2, 2, // addr2
852                2, 2, 2, 2, 2, 2, // addr3
853                0x10, 0, // Sequence Control
854                // Beacon header:
855                0, 0, 0, 0, 0, 0, 0, 0, // Timestamp zero since TSF Timer not implemented
856                10, 0, // Beacon interval
857                33, 0, // Capabilities
858                // IEs:
859                0, 5, 1, 2, 3, 4, 5, // SSID
860                1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Supported rates
861                3, 1, 2, // DSSS parameter set
862                50, 2, 9, 10, // Extended rates
863                48, 2, 77, 88, // RSNE
864            ][..]
865        );
866    }
867
868    #[fuchsia::test(allow_stalls = false)]
869    async fn make_beacon_frame() {
870        let (fake_device, _) = FakeDevice::new().await;
871        let (ctx, _) = make_context(fake_device);
872
873        let (buffer, params) = ctx
874            .make_beacon_frame(
875                TimeUnit(10),
876                mac::CapabilityInfo(33),
877                &Ssid::try_from([1, 2, 3, 4, 5]).unwrap(),
878                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
879                2,
880                ie::TimHeader { dtim_count: 1, dtim_period: 2, bmp_ctrl: ie::BitmapControl(0) },
881                &[1, 2, 3][..],
882                &[48, 2, 77, 88][..],
883            )
884            .expect("error making probe resp frame");
885        assert_eq!(
886            &buffer[..],
887            &[
888                // Mgmt header
889                0b10000000, 0, // Frame Control
890                0, 0, // Duration
891                255, 255, 255, 255, 255, 255, // addr1
892                2, 2, 2, 2, 2, 2, // addr2
893                2, 2, 2, 2, 2, 2, // addr3
894                0, 0, // Sequence Control
895                // Beacon header:
896                0, 0, 0, 0, 0, 0, 0, 0, // Timestamp zero since TSF Timer not implemented
897                10, 0, // Beacon interval
898                33, 0, // Capabilities
899                // IEs:
900                0, 5, 1, 2, 3, 4, 5, // SSID
901                1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Supported rates
902                3, 1, 2, // DSSS parameter set
903                5, 6, 1, 2, 0, 1, 2, 3, // TIM
904                50, 2, 9, 10, // Extended rates
905                48, 2, 77, 88, // RSNE
906            ][..]
907        );
908        assert_eq!(params.tim_ele_offset, 56);
909    }
910
911    #[fuchsia::test(allow_stalls = false)]
912    async fn make_data_frame() {
913        let (fake_device, _) = FakeDevice::new().await;
914        let (mut ctx, _) = make_context(fake_device);
915        let buffer = ctx
916            .make_data_frame(*CLIENT_ADDR2, *CLIENT_ADDR, false, false, 0x1234, &[1, 2, 3, 4, 5])
917            .expect("error making data frame");
918        assert_eq!(
919            &buffer[..],
920            &[
921                // Mgmt header
922                0b00001000, 0b00000010, // Frame Control
923                0, 0, // Duration
924                3, 3, 3, 3, 3, 3, // addr1
925                2, 2, 2, 2, 2, 2, // addr2
926                1, 1, 1, 1, 1, 1, // addr3
927                0x10, 0, // Sequence Control
928                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
929                0, 0, 0, // OUI
930                0x12, 0x34, // Protocol ID
931                // Data
932                1, 2, 3, 4, 5,
933            ][..]
934        );
935    }
936
937    #[fuchsia::test(allow_stalls = false)]
938    async fn make_data_frame_ipv4_qos() {
939        let (fake_device, _) = FakeDevice::new().await;
940        let (mut ctx, _) = make_context(fake_device);
941        let buffer = ctx
942            .make_data_frame(
943                *CLIENT_ADDR2,
944                *CLIENT_ADDR,
945                false,
946                true,
947                0x0800, // IPv4
948                // Not valid IPv4 payload (too short).
949                // However, we only care that it includes the DS field.
950                &[1, 0xB0, 3, 4, 5], // DSCP = 0b010110 (i.e. AF23)
951            )
952            .expect("error making data frame");
953        assert_eq!(
954            &buffer[..],
955            &[
956                // Mgmt header
957                0b10001000, 0b00000010, // Frame Control
958                0, 0, // Duration
959                3, 3, 3, 3, 3, 3, // addr1
960                2, 2, 2, 2, 2, 2, // addr2
961                1, 1, 1, 1, 1, 1, // addr3
962                0x10, 0, // Sequence Control
963                0x06, 0, // QoS Control - TID = 6
964                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
965                0, 0, 0, // OUI
966                0x08, 0x00, // Protocol ID
967                // Payload
968                1, 0xB0, 3, 4, 5,
969            ][..]
970        );
971    }
972
973    #[fuchsia::test(allow_stalls = false)]
974    async fn make_data_frame_ipv6_qos() {
975        let (fake_device, _) = FakeDevice::new().await;
976        let (mut ctx, _) = make_context(fake_device);
977        let buffer = ctx
978            .make_data_frame(
979                *CLIENT_ADDR2,
980                *CLIENT_ADDR,
981                false,
982                true,
983                0x86DD, // IPv6
984                // Not valid IPv6 payload (too short).
985                // However, we only care that it includes the DS field.
986                &[0b0101, 0b10000000, 3, 4, 5], // DSCP = 0b101100 (i.e. VOICE-ADMIT)
987            )
988            .expect("error making data frame");
989        assert_eq!(
990            &buffer[..],
991            &[
992                // Mgmt header
993                0b10001000, 0b00000010, // Frame Control
994                0, 0, // Duration
995                3, 3, 3, 3, 3, 3, // addr1
996                2, 2, 2, 2, 2, 2, // addr2
997                1, 1, 1, 1, 1, 1, // addr3
998                0x10, 0, // Sequence Control
999                0x03, 0, // QoS Control - TID = 3
1000                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
1001                0, 0, 0, // OUI
1002                0x86, 0xDD, // Protocol ID
1003                // Payload
1004                0b0101, 0b10000000, 3, 4, 5,
1005            ][..]
1006        );
1007    }
1008
1009    #[fuchsia::test(allow_stalls = false)]
1010    async fn make_eapol_frame() {
1011        let (fake_device, _) = FakeDevice::new().await;
1012        let (mut ctx, _) = make_context(fake_device);
1013        let buffer = ctx
1014            .make_eapol_frame(*CLIENT_ADDR2, *CLIENT_ADDR, false, &[1, 2, 3, 4, 5])
1015            .expect("error making eapol frame");
1016        assert_eq!(
1017            &buffer[..],
1018            &[
1019                // Mgmt header
1020                0b00001000, 0b00000010, // Frame Control
1021                0, 0, // Duration
1022                3, 3, 3, 3, 3, 3, // addr1
1023                2, 2, 2, 2, 2, 2, // addr2
1024                1, 1, 1, 1, 1, 1, // addr3
1025                0x10, 0, // Sequence Control
1026                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
1027                0, 0, 0, // OUI
1028                0x88, 0x8E, // EAPOL protocol ID
1029                // Data
1030                1, 2, 3, 4, 5,
1031            ][..]
1032        );
1033    }
1034
1035    #[fuchsia::test(allow_stalls = false)]
1036    async fn deliver_eth_frame() {
1037        let (fake_device, fake_device_state) = FakeDevice::new().await;
1038        let (mut ctx, _) = make_context(fake_device);
1039        ctx.deliver_eth_frame(*CLIENT_ADDR2, *CLIENT_ADDR, 0x1234, &[1, 2, 3, 4, 5][..])
1040            .expect("expected OK");
1041        assert_eq!(fake_device_state.lock().eth_queue.len(), 1);
1042        #[rustfmt::skip]
1043        assert_eq!(&fake_device_state.lock().eth_queue[0][..], &[
1044            3, 3, 3, 3, 3, 3,  // dest
1045            1, 1, 1, 1, 1, 1,  // src
1046            0x12, 0x34,        // ether_type
1047            // Data
1048            1, 2, 3, 4, 5,
1049        ][..]);
1050    }
1051}