wlan_fullmac_mlme/
mlme_main_loop.rs

1// Copyright 2024 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::convert::{fullmac_to_mlme, mlme_to_fullmac};
6use crate::device::DeviceOps;
7use crate::wlan_fullmac_impl_ifc_request_handler::serve_wlan_fullmac_impl_ifc_request_handler;
8use crate::{DriverState, FullmacDriverEvent, FullmacDriverEventSink};
9use anyhow::{Context, bail};
10use futures::channel::{mpsc, oneshot};
11use futures::{Future, StreamExt, select};
12use log::{error, info};
13use std::pin::Pin;
14use {
15    fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_fullmac as fidl_fullmac,
16    fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_mlme as fidl_mlme,
17    fuchsia_async as fasync,
18};
19
20/// Creates a future that implements the MLME main loop.
21///
22/// The MLME main loop is responsible for:
23/// - Converting and sending MLME requests from |mlme_request_stream| to the vendor driver via
24///   |device|.
25/// - Receiving requests from the vendor driver through |fullmac_ifc_request_stream|, and
26///   pushing them onto |driver_event_stream|.
27pub(crate) fn create_mlme_main_loop<D: DeviceOps>(
28    device: D,
29    mlme_request_stream: wlan_sme::MlmeStream,
30    mlme_event_sink: wlan_sme::MlmeEventSink,
31    driver_event_stream: mpsc::UnboundedReceiver<FullmacDriverEvent>,
32    driver_event_sink: FullmacDriverEventSink,
33    fullmac_ifc_request_stream: fidl_fullmac::WlanFullmacImplIfcRequestStream,
34) -> Pin<Box<impl Future<Output = anyhow::Result<()>>>> {
35    let main_loop = MlmeMainLoop {
36        device,
37        mlme_request_stream,
38        mlme_event_sink,
39        driver_event_stream,
40        is_bss_protected: false,
41        device_link_state: fidl_mlme::ControlledPortState::Closed,
42    };
43
44    Box::pin(main_loop.serve(fullmac_ifc_request_stream, driver_event_sink))
45}
46
47struct MlmeMainLoop<D: DeviceOps> {
48    device: D,
49    mlme_request_stream: wlan_sme::MlmeStream,
50    mlme_event_sink: wlan_sme::MlmeEventSink,
51    driver_event_stream: mpsc::UnboundedReceiver<FullmacDriverEvent>,
52    is_bss_protected: bool,
53    device_link_state: fidl_mlme::ControlledPortState,
54}
55
56impl<D: DeviceOps> MlmeMainLoop<D> {
57    /// Serves the MLME main loop.
58    ///
59    /// This:
60    /// - Spawns the background task that implements the WlanFullmacImplIfc server.
61    /// - Handles SME -> Vendor Driver requests by servicing |self.mlme_request_stream|.
62    /// - Handles Vendor Driver -> SME requests by servicing |self.driver_event_stream|.
63    ///
64    /// |self.driver_event_stream| is populated by the WlanFullmacImplIfc server task, except for
65    /// the `Stop` event which is sent by `FullmacMlmeHandle::stop`.
66    ///
67    /// Returns success if it receives the `Stop` event on |self.driver_event_stream|, and returns
68    /// an error in all other cases.
69    async fn serve(
70        mut self,
71        fullmac_ifc_request_stream: fidl_fullmac::WlanFullmacImplIfcRequestStream,
72        driver_event_sink: FullmacDriverEventSink,
73    ) -> anyhow::Result<()> {
74        let mac_role = self
75            .device
76            .query_device_info()?
77            .role
78            .context("Vendor driver query response missing MAC role")?;
79
80        // The WlanFullmacImplIfc server is a background task so that a blocking call into the
81        // vendor driver does not prevent MLME from handling incoming WlanFullmacImplIfc requests.
82        let (ifc_server_stop_sender, mut ifc_server_stop_receiver) = oneshot::channel::<()>();
83        let _fullmac_ifc_server_task = fasync::Task::spawn(async move {
84            serve_wlan_fullmac_impl_ifc_request_handler(
85                fullmac_ifc_request_stream,
86                driver_event_sink,
87            )
88            .await;
89            info!("WlanFullmacImplIfc server stopped");
90
91            // This signals that the fullmac_ifc_server_task exited before the main loop. It's fine
92            // if the main loop exits and drops the receiver before this task exits, so we ignore
93            // the result of this send.
94            let _ = ifc_server_stop_sender.send(());
95        });
96
97        loop {
98            select! {
99                mlme_request = self.mlme_request_stream.next() => match mlme_request {
100                    Some(req) => {
101                        if let Err(e) = self.handle_mlme_request(req) {
102                            error!("Failed to handle MLME req: {}", e);
103                        }
104                    },
105                    None => bail!("MLME request stream terminated unexpectedly."),
106                },
107                driver_event = self.driver_event_stream.next() => match driver_event {
108                    Some(event) => {
109                        match self.handle_driver_event(event, &mac_role) {
110                            Ok(DriverState::Running) => {},
111                            Ok(DriverState::Stopping) => return Ok(()),
112                            Err(e) => error!("Failed to handle driver event: {}", e),
113                        }
114                    },
115                    None => bail!("Driver event stream terminated unexpectedly."),
116                },
117                ifc_stop = ifc_server_stop_receiver => match ifc_stop {
118                    Ok(()) => bail!("WlanFullmacImplIfc request stream terminated unexpectedly."),
119                    Err(e) => bail!("WlanFullmacImplIfc server task dropped stop sender unexpectedly. {}", e),
120                },
121            }
122        }
123    }
124
125    fn handle_mlme_request(&mut self, req: wlan_sme::MlmeRequest) -> anyhow::Result<()> {
126        use wlan_sme::MlmeRequest::*;
127        match req {
128            Scan(req) => self.handle_mlme_scan_request(req)?,
129            Connect(req) => {
130                self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
131                self.is_bss_protected = !req.security_ie.is_empty();
132                self.device.connect(mlme_to_fullmac::convert_connect_request(req))?;
133            }
134            Reconnect(req) => {
135                self.device.reconnect(mlme_to_fullmac::convert_reconnect_request(req))?;
136            }
137            Roam(req) => {
138                self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
139                self.device.roam(mlme_to_fullmac::convert_roam_request(req))?;
140            }
141            AuthResponse(resp) => {
142                self.device.auth_resp(mlme_to_fullmac::convert_authenticate_response(resp))?;
143            }
144            Deauthenticate(req) => {
145                self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
146                self.device.deauth(mlme_to_fullmac::convert_deauthenticate_request(req))?;
147            }
148            AssocResponse(resp) => {
149                self.device.assoc_resp(mlme_to_fullmac::convert_associate_response(resp))?;
150            }
151            Disassociate(req) => {
152                self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
153                self.device.disassoc(mlme_to_fullmac::convert_disassociate_request(req))?;
154            }
155            Start(req) => {
156                self.device.start_bss(mlme_to_fullmac::convert_start_bss_request(req)?)?
157            }
158            Stop(req) => self.device.stop_bss(mlme_to_fullmac::convert_stop_bss_request(req)?)?,
159            SetKeys(req) => self.handle_mlme_set_keys_request(req)?,
160            Eapol(req) => self.device.eapol_tx(mlme_to_fullmac::convert_eapol_request(req))?,
161            SetCtrlPort(req) => self.set_link_state(req.state)?,
162            QueryDeviceInfo(responder) => {
163                let device_info =
164                    fullmac_to_mlme::convert_device_info(self.device.query_device_info()?)?;
165                responder.respond(device_info);
166            }
167            QueryMacSublayerSupport(_) => info!("QueryMacSublayerSupport is unsupported"),
168            QuerySecuritySupport(responder) => {
169                responder.respond(self.device.query_security_support()?)
170            }
171            QuerySpectrumManagementSupport(responder) => {
172                responder.respond(self.device.query_spectrum_management_support()?)
173            }
174            QueryTelemetrySupport(responder) => {
175                responder.respond(self.device.query_telemetry_support()?)
176            }
177            GetIfaceStats(responder) => responder.respond(self.device.get_iface_stats()?),
178            GetIfaceHistogramStats(responder) => {
179                responder.respond(self.device.get_iface_histogram_stats()?)
180            }
181            GetSignalReport(responder) => responder.respond(self.device.get_signal_report()?),
182            GetMinstrelStats(_, _) => info!("GetMinstrelStats is unsupported"),
183            ListMinstrelPeers(_) => info!("ListMinstrelPeers is unsupported"),
184            SaeHandshakeResp(resp) => self
185                .device
186                .sae_handshake_resp(mlme_to_fullmac::convert_sae_handshake_response(resp))?,
187            SaeFrameTx(frame) => {
188                self.device.sae_frame_tx(mlme_to_fullmac::convert_sae_frame(frame))?
189            }
190            WmmStatusReq => self.device.wmm_status_req()?,
191            FinalizeAssociation(..) => info!("FinalizeAssociation is unsupported"),
192            SetMacAddress(mac_addr, responder) => {
193                responder.respond(self.device.set_mac_address(
194                    fidl_fullmac::WlanFullmacImplSetMacAddressRequest { mac_addr },
195                )?)
196            }
197        };
198        Ok(())
199    }
200
201    fn handle_mlme_scan_request(&self, req: fidl_mlme::ScanRequest) -> anyhow::Result<()> {
202        if req.channel_list.is_empty() {
203            let end = fidl_mlme::ScanEnd {
204                txn_id: req.txn_id,
205                code: fidl_mlme::ScanResultCode::InvalidArgs,
206            };
207            self.mlme_event_sink.send(fidl_mlme::MlmeEvent::OnScanEnd { end });
208        } else {
209            self.device.start_scan(mlme_to_fullmac::convert_scan_request(req)?)?;
210        }
211        Ok(())
212    }
213
214    fn handle_mlme_set_keys_request(&self, req: fidl_mlme::SetKeysRequest) -> anyhow::Result<()> {
215        let fullmac_req = mlme_to_fullmac::convert_set_keys_request(&req)?;
216        let fullmac_resp = self.device.set_keys(fullmac_req)?;
217        let mlme_resp = fullmac_to_mlme::convert_set_keys_resp(fullmac_resp, &req)?;
218        self.mlme_event_sink.send(fidl_mlme::MlmeEvent::SetKeysConf { conf: mlme_resp });
219        Ok(())
220    }
221
222    fn handle_driver_event(
223        &mut self,
224        event: FullmacDriverEvent,
225        mac_role: &fidl_common::WlanMacRole,
226    ) -> anyhow::Result<DriverState> {
227        match event {
228            FullmacDriverEvent::Stop => return Ok(DriverState::Stopping),
229            FullmacDriverEvent::OnScanResult { result } => {
230                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::OnScanResult { result });
231            }
232            FullmacDriverEvent::OnScanEnd { end } => {
233                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::OnScanEnd { end });
234            }
235            FullmacDriverEvent::ConnectConf { resp } => {
236                // IEEE Std 802.11-2016, 9.4.2.57
237                // If BSS is protected, we do not open the controlled port yet until
238                // RSN association is established and the keys are installed, at which
239                // point we would receive a request to open the controlled port from SME.
240                if !self.is_bss_protected && resp.result_code == fidl_ieee80211::StatusCode::Success
241                {
242                    self.set_link_state(fidl_mlme::ControlledPortState::Open)?;
243                }
244                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::ConnectConf { resp });
245            }
246            FullmacDriverEvent::RoamConf { conf } => {
247                if *mac_role == fidl_common::WlanMacRole::Client {
248                    // Like for connect, SME will open the controlled port for a protected BSS.
249                    if !self.is_bss_protected
250                        && conf.status_code == fidl_ieee80211::StatusCode::Success
251                    {
252                        self.set_link_state(fidl_mlme::ControlledPortState::Open)?;
253                    }
254                }
255                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::RoamConf { conf });
256            }
257            FullmacDriverEvent::RoamStartInd { ind } => {
258                if *mac_role == fidl_common::WlanMacRole::Client {
259                    self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
260                }
261                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::RoamStartInd { ind });
262            }
263            FullmacDriverEvent::RoamResultInd { ind } => {
264                if *mac_role == fidl_common::WlanMacRole::Client {
265                    // Like for connect, SME will open the controlled port for a protected BSS.
266                    if !self.is_bss_protected
267                        && ind.status_code == fidl_ieee80211::StatusCode::Success
268                    {
269                        self.set_link_state(fidl_mlme::ControlledPortState::Open)?;
270                    }
271                }
272                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::RoamResultInd { ind });
273            }
274            FullmacDriverEvent::AuthInd { ind } => {
275                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::AuthenticateInd { ind });
276            }
277            FullmacDriverEvent::DeauthConf { resp } => {
278                if *mac_role == fidl_common::WlanMacRole::Client {
279                    self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
280                }
281                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::DeauthenticateConf { resp });
282            }
283            FullmacDriverEvent::DeauthInd { ind } => {
284                if *mac_role == fidl_common::WlanMacRole::Client {
285                    self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
286                }
287                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::DeauthenticateInd { ind });
288            }
289            FullmacDriverEvent::AssocInd { ind } => {
290                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::AssociateInd { ind });
291            }
292            FullmacDriverEvent::DisassocConf { resp } => {
293                if *mac_role == fidl_common::WlanMacRole::Client {
294                    self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
295                }
296                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::DisassociateConf { resp });
297            }
298            FullmacDriverEvent::DisassocInd { ind } => {
299                if *mac_role == fidl_common::WlanMacRole::Client {
300                    self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
301                }
302                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::DisassociateInd { ind });
303            }
304            FullmacDriverEvent::StartConf { resp } => {
305                if resp.result_code == fidl_mlme::StartResultCode::Success {
306                    self.set_link_state(fidl_mlme::ControlledPortState::Open)?;
307                }
308                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::StartConf { resp });
309            }
310            FullmacDriverEvent::StopConf { resp } => {
311                if resp.result_code == fidl_mlme::StopResultCode::Success {
312                    self.set_link_state(fidl_mlme::ControlledPortState::Closed)?;
313                }
314                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::StopConf { resp });
315            }
316            FullmacDriverEvent::EapolConf { resp } => {
317                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::EapolConf { resp });
318            }
319            FullmacDriverEvent::OnChannelSwitch { resp } => {
320                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::OnChannelSwitched { info: resp });
321            }
322            FullmacDriverEvent::SignalReport { ind } => {
323                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::SignalReport { ind });
324            }
325            FullmacDriverEvent::EapolInd { ind } => {
326                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::EapolInd { ind });
327            }
328            FullmacDriverEvent::OnPmkAvailable { info } => {
329                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::OnPmkAvailable { info });
330            }
331            FullmacDriverEvent::SaeHandshakeInd { ind } => {
332                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::OnSaeHandshakeInd { ind });
333            }
334            FullmacDriverEvent::SaeFrameRx { frame } => {
335                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::OnSaeFrameRx { frame });
336            }
337            FullmacDriverEvent::OnWmmStatusResp { status, resp } => {
338                self.mlme_event_sink.send(fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp });
339            }
340        }
341        Ok(DriverState::Running)
342    }
343
344    fn set_link_state(
345        &mut self,
346        new_link_state: fidl_mlme::ControlledPortState,
347    ) -> anyhow::Result<()> {
348        // TODO(https://fxbug.dev/42128153): Let SME handle these changes.
349        if new_link_state == self.device_link_state {
350            return Ok(());
351        }
352
353        let req = fidl_fullmac::WlanFullmacImplOnLinkStateChangedRequest {
354            online: Some(new_link_state == fidl_mlme::ControlledPortState::Open),
355            ..Default::default()
356        };
357
358        self.device.on_link_state_changed(req)?;
359        self.device_link_state = new_link_state;
360        Ok(())
361    }
362}
363
364#[cfg(test)]
365mod handle_mlme_request_tests {
366    use super::*;
367    use crate::device::test_utils::{DriverCall, FakeFullmacDevice, FakeFullmacDeviceMocks};
368    use assert_matches::assert_matches;
369    use fuchsia_sync::Mutex;
370    use std::sync::Arc;
371    use test_case::test_case;
372    use wlan_common::sink::UnboundedSink;
373    use {fidl_fuchsia_wlan_fullmac as fidl_fullmac, fidl_fuchsia_wlan_stats as fidl_stats};
374
375    #[test]
376    fn test_scan_request() {
377        let mut h = TestHelper::set_up();
378        let fidl_req = wlan_sme::MlmeRequest::Scan(fidl_mlme::ScanRequest {
379            txn_id: 1,
380            scan_type: fidl_mlme::ScanTypes::Passive,
381            channel_list: vec![2],
382            ssid_list: vec![vec![3u8; 4]],
383            probe_delay: 5,
384            min_channel_time: 6,
385            max_channel_time: 7,
386        });
387
388        h.mlme.handle_mlme_request(fidl_req).unwrap();
389
390        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::StartScan { req })) => req);
391        assert_eq!(driver_req.txn_id, Some(1));
392        assert_eq!(driver_req.scan_type, Some(fidl_fullmac::WlanScanType::Passive));
393        assert_eq!(driver_req.channels, Some(vec![2]));
394        assert_eq!(driver_req.min_channel_time, Some(6));
395        assert_eq!(driver_req.max_channel_time, Some(7));
396        assert_eq!(driver_req.ssids, Some(vec![vec![3u8; 4]]));
397    }
398
399    #[test]
400    fn test_scan_request_empty_ssid_list() {
401        let mut h = TestHelper::set_up();
402        let fidl_req = wlan_sme::MlmeRequest::Scan(fidl_mlme::ScanRequest {
403            txn_id: 1,
404            scan_type: fidl_mlme::ScanTypes::Active,
405            channel_list: vec![2],
406            ssid_list: vec![],
407            probe_delay: 5,
408            min_channel_time: 6,
409            max_channel_time: 7,
410        });
411
412        h.mlme.handle_mlme_request(fidl_req).unwrap();
413
414        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::StartScan { req })) => req);
415        assert_eq!(driver_req.scan_type, Some(fidl_fullmac::WlanScanType::Active));
416        assert!(driver_req.ssids.as_ref().unwrap().is_empty());
417    }
418
419    #[test]
420    fn test_scan_request_empty_channel_list() {
421        let mut h = TestHelper::set_up();
422        let fidl_req = wlan_sme::MlmeRequest::Scan(fidl_mlme::ScanRequest {
423            txn_id: 1,
424            scan_type: fidl_mlme::ScanTypes::Passive,
425            channel_list: vec![],
426            ssid_list: vec![vec![3u8; 4]],
427            probe_delay: 5,
428            min_channel_time: 6,
429            max_channel_time: 7,
430        });
431
432        h.mlme.handle_mlme_request(fidl_req).unwrap();
433
434        assert_matches!(h.driver_calls.try_next(), Err(_));
435        let scan_end = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(fidl_mlme::MlmeEvent::OnScanEnd { end })) => end);
436        assert_eq!(
437            scan_end,
438            fidl_mlme::ScanEnd { txn_id: 1, code: fidl_mlme::ScanResultCode::InvalidArgs }
439        );
440    }
441
442    #[test]
443    fn test_connect_request() {
444        let mut h = TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
445        let fidl_req = wlan_sme::MlmeRequest::Connect(fidl_mlme::ConnectRequest {
446            selected_bss: fidl_common::BssDescription {
447                bssid: [100u8; 6],
448                bss_type: fidl_common::BssType::Infrastructure,
449                beacon_period: 101,
450                capability_info: 102,
451                ies: vec![103u8, 104, 105],
452                channel: fidl_ieee80211::WlanChannel {
453                    primary: 106,
454                    cbw: fidl_ieee80211::ChannelBandwidth::Cbw40,
455                    secondary80: 0,
456                },
457                rssi_dbm: 107,
458                snr_db: 108,
459            },
460            connect_failure_timeout: 1u32,
461            auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
462            sae_password: vec![2u8, 3, 4],
463            wep_key: Some(Box::new(fidl_mlme::SetKeyDescriptor {
464                key: vec![5u8, 6],
465                key_id: 7,
466                key_type: fidl_mlme::KeyType::Group,
467                address: [8u8; 6],
468                rsc: 9,
469                cipher_suite_oui: [10u8; 3],
470                cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(
471                    11,
472                ),
473            })),
474            security_ie: vec![12u8, 13],
475        });
476
477        h.mlme.handle_mlme_request(fidl_req).unwrap();
478
479        assert!(h.mlme.is_bss_protected);
480
481        assert_matches!(
482            h.driver_calls.try_next(),
483            Ok(Some(DriverCall::OnLinkStateChanged { req })) => {
484              assert_eq!(req.online, Some(false));
485          }
486        );
487        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::ConnectReq { req })) => {
488            let selected_bss = req.selected_bss.clone().unwrap();
489            assert_eq!(selected_bss.bssid, [100u8; 6]);
490            assert_eq!(selected_bss.bss_type, fidl_common::BssType::Infrastructure);
491            assert_eq!(selected_bss.beacon_period, 101);
492            assert_eq!(selected_bss.capability_info, 102);
493            assert_eq!(selected_bss.ies, vec![103u8, 104, 105]);
494            assert_eq!(
495                selected_bss.channel,
496                fidl_ieee80211::WlanChannel {
497                    primary: 106,
498                    cbw: fidl_ieee80211::ChannelBandwidth::Cbw40,
499                    secondary80: 0,
500                }
501            );
502            assert_eq!(selected_bss.rssi_dbm, 107);
503            assert_eq!(selected_bss.snr_db, 108);
504
505            assert_eq!(req.connect_failure_timeout, Some(1u32));
506            assert_eq!(req.auth_type, Some(fidl_fullmac::WlanAuthType::OpenSystem));
507            assert_eq!(req.sae_password, Some(vec![2u8, 3, 4]));
508
509            let wep_key_desc = req.wep_key_desc.clone().unwrap();
510            assert_eq!(wep_key_desc.key, Some(vec![5u8, 6]));
511            assert_eq!(wep_key_desc.key_id, Some(7));
512            assert_eq!(wep_key_desc.key_type, Some(fidl_ieee80211::KeyType::Group));
513            assert_eq!(wep_key_desc.peer_addr, Some([8u8; 6]));
514            assert_eq!(wep_key_desc.rsc, Some(9));
515            assert_eq!(wep_key_desc.cipher_oui, Some([10u8; 3]));
516            assert_eq!(wep_key_desc.cipher_type, fidl_ieee80211::CipherSuiteType::from_primitive(11));
517
518            assert_eq!(req.security_ie, Some(vec![12u8, 13]));
519        });
520    }
521
522    #[test]
523    fn test_reconnect_request() {
524        let mut h = TestHelper::set_up();
525        let fidl_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
526            peer_sta_address: [1u8; 6],
527        });
528
529        h.mlme.handle_mlme_request(fidl_req).unwrap();
530
531        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::ReconnectReq { req })) => req);
532        assert_eq!(
533            driver_req,
534            fidl_fullmac::WlanFullmacImplReconnectRequest {
535                peer_sta_address: Some([1u8; 6]),
536                ..Default::default()
537            }
538        );
539    }
540
541    #[test]
542    fn test_authenticate_response() {
543        let mut h = TestHelper::set_up();
544        let fidl_req = wlan_sme::MlmeRequest::AuthResponse(fidl_mlme::AuthenticateResponse {
545            peer_sta_address: [1u8; 6],
546            result_code: fidl_mlme::AuthenticateResultCode::Success,
547        });
548
549        h.mlme.handle_mlme_request(fidl_req).unwrap();
550
551        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::AuthResp { resp })) => resp);
552        assert_eq!(
553            driver_req,
554            fidl_fullmac::WlanFullmacImplAuthRespRequest {
555                peer_sta_address: Some([1u8; 6]),
556                result_code: Some(fidl_fullmac::WlanAuthResult::Success),
557                ..Default::default()
558            }
559        );
560    }
561
562    #[test]
563    fn test_deauthenticate_request() {
564        let mut h = TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
565        let fidl_req = wlan_sme::MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
566            peer_sta_address: [1u8; 6],
567            reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
568        });
569
570        h.mlme.handle_mlme_request(fidl_req).unwrap();
571
572        assert_matches!(
573            h.driver_calls.try_next(),
574            Ok(Some(DriverCall::OnLinkStateChanged { req })) => {
575              assert_eq!(req.online, Some(false));
576          }
577        );
578        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::DeauthReq { req })) => req);
579        assert_eq!(driver_req.peer_sta_address, Some([1u8; 6]));
580        assert_eq!(driver_req.reason_code, Some(fidl_ieee80211::ReasonCode::LeavingNetworkDeauth));
581    }
582
583    #[test]
584    fn test_associate_response() {
585        let mut h = TestHelper::set_up();
586        let fidl_req = wlan_sme::MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
587            peer_sta_address: [1u8; 6],
588            result_code: fidl_mlme::AssociateResultCode::Success,
589            association_id: 2,
590            capability_info: 3,
591            rates: vec![4, 5],
592        });
593
594        h.mlme.handle_mlme_request(fidl_req).unwrap();
595
596        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::AssocResp { resp })) => resp);
597        assert_eq!(driver_req.peer_sta_address, Some([1u8; 6]));
598        assert_eq!(driver_req.result_code, Some(fidl_fullmac::WlanAssocResult::Success));
599        assert_eq!(driver_req.association_id, Some(2));
600    }
601
602    #[test]
603    fn test_disassociate_request() {
604        let mut h = TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
605        let fidl_req = wlan_sme::MlmeRequest::Disassociate(fidl_mlme::DisassociateRequest {
606            peer_sta_address: [1u8; 6],
607            reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc,
608        });
609
610        h.mlme.handle_mlme_request(fidl_req).unwrap();
611
612        assert_matches!(
613            h.driver_calls.try_next(),
614            Ok(Some(DriverCall::OnLinkStateChanged { req })) => {
615              assert_eq!(req.online, Some(false));
616          }
617        );
618
619        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::Disassoc{ req })) => req);
620        assert_eq!(driver_req.peer_sta_address, Some([1u8; 6]));
621        assert_eq!(
622            driver_req.reason_code,
623            Some(fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc)
624        );
625    }
626
627    #[test]
628    fn test_start_request() {
629        let mut h = TestHelper::set_up();
630        const SSID_LEN: usize = 2;
631        const RSNE_LEN: usize = 15;
632        let fidl_req = wlan_sme::MlmeRequest::Start(fidl_mlme::StartRequest {
633            ssid: vec![1u8; SSID_LEN],
634            bss_type: fidl_common::BssType::Infrastructure,
635            beacon_period: 3,
636            dtim_period: 4,
637            channel: 5,
638            capability_info: 6,
639            rates: vec![7, 8, 9],
640            country: fidl_mlme::Country { alpha2: [10, 11], suffix: 12 },
641            mesh_id: vec![13],
642            rsne: Some(vec![14; RSNE_LEN]),
643            phy: fidl_common::WlanPhyType::Vht,
644            channel_bandwidth: fidl_ieee80211::ChannelBandwidth::Cbw80,
645        });
646
647        h.mlme.handle_mlme_request(fidl_req).unwrap();
648
649        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::StartBss { req })) => req);
650
651        assert_eq!(driver_req.ssid, Some(vec![1u8; SSID_LEN]));
652        assert_eq!(driver_req.bss_type, Some(fidl_common::BssType::Infrastructure));
653        assert_eq!(driver_req.beacon_period, Some(3));
654        assert_eq!(driver_req.dtim_period, Some(4));
655        assert_eq!(driver_req.channel, Some(5));
656        assert_ne!(driver_req.rsne, Some(vec![14 as u8, RSNE_LEN as u8]));
657        assert_eq!(driver_req.vendor_ie, Some(vec![]));
658    }
659
660    #[test]
661    fn test_stop_request() {
662        let mut h = TestHelper::set_up();
663        const SSID_LEN: usize = 2;
664        let fidl_req =
665            wlan_sme::MlmeRequest::Stop(fidl_mlme::StopRequest { ssid: vec![1u8; SSID_LEN] });
666
667        h.mlme.handle_mlme_request(fidl_req).unwrap();
668
669        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::StopBss { req })) => req);
670        assert_eq!(driver_req.ssid, Some(vec![1u8; SSID_LEN]));
671    }
672
673    #[test]
674    fn test_set_keys_request() {
675        let mut h = TestHelper::set_up();
676        let fidl_req = wlan_sme::MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest {
677            keylist: vec![fidl_mlme::SetKeyDescriptor {
678                key: vec![5u8, 6],
679                key_id: 7,
680                key_type: fidl_mlme::KeyType::Group,
681                address: [8u8; 6],
682                rsc: 9,
683                cipher_suite_oui: [10u8; 3],
684                cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(
685                    11,
686                ),
687            }],
688        });
689
690        h.mlme.handle_mlme_request(fidl_req).unwrap();
691
692        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::SetKeys { req })) => req);
693        assert_eq!(driver_req.key_descriptors.as_ref().unwrap().len(), 1 as usize);
694        let key_descriptors = driver_req.key_descriptors.as_ref().unwrap();
695        assert_eq!(key_descriptors[0].key_id, Some(7));
696        assert_eq!(key_descriptors[0].key_type, Some(fidl_ieee80211::KeyType::Group));
697        assert_eq!(key_descriptors[0].peer_addr, Some([8u8; 6]));
698        assert_eq!(key_descriptors[0].rsc, Some(9));
699        assert_eq!(key_descriptors[0].cipher_oui, Some([10u8; 3]));
700        assert_eq!(
701            key_descriptors[0].cipher_type,
702            Some(fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(11))
703        );
704
705        let conf = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(fidl_mlme::MlmeEvent::SetKeysConf { conf })) => conf);
706        assert_eq!(
707            conf,
708            fidl_mlme::SetKeysConfirm {
709                results: vec![fidl_mlme::SetKeyResult { key_id: 7, status: 0 }]
710            }
711        );
712    }
713
714    #[test]
715    fn test_set_keys_request_partial_failure() {
716        let mut h = TestHelper::set_up();
717        const NUM_KEYS: usize = 3;
718        h.fake_device.lock().set_keys_resp_mock =
719            Some(fidl_fullmac::WlanFullmacSetKeysResp { statuslist: [0i32, 1, 0].to_vec() });
720        let mut keylist = vec![];
721        let key = fidl_mlme::SetKeyDescriptor {
722            key: vec![5u8, 6],
723            key_id: 7,
724            key_type: fidl_mlme::KeyType::Group,
725            address: [8u8; 6],
726            rsc: 9,
727            cipher_suite_oui: [10u8; 3],
728            cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(11),
729        };
730        for i in 0..NUM_KEYS {
731            keylist.push(fidl_mlme::SetKeyDescriptor { key_id: i as u16, ..key.clone() });
732        }
733        let fidl_req = wlan_sme::MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest { keylist });
734
735        h.mlme.handle_mlme_request(fidl_req).unwrap();
736
737        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::SetKeys { req })) => req);
738        assert_eq!(driver_req.key_descriptors.as_ref().unwrap().len(), NUM_KEYS as usize);
739        let key_descriptors = driver_req.key_descriptors.unwrap();
740        for i in 0..NUM_KEYS {
741            assert_eq!(key_descriptors[i].key_id, Some(i as u16));
742        }
743
744        let conf = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(fidl_mlme::MlmeEvent::SetKeysConf { conf })) => conf);
745        assert_eq!(
746            conf,
747            fidl_mlme::SetKeysConfirm {
748                results: vec![
749                    fidl_mlme::SetKeyResult { key_id: 0, status: 0 },
750                    fidl_mlme::SetKeyResult { key_id: 1, status: 1 },
751                    fidl_mlme::SetKeyResult { key_id: 2, status: 0 },
752                ]
753            }
754        );
755    }
756
757    #[test]
758    fn test_set_keys_request_too_many_keys() {
759        let mut h = TestHelper::set_up();
760        let key = fidl_mlme::SetKeyDescriptor {
761            key: vec![5u8, 6],
762            key_id: 7,
763            key_type: fidl_mlme::KeyType::Group,
764            address: [8u8; 6],
765            rsc: 9,
766            cipher_suite_oui: [10u8; 3],
767            cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(11),
768        };
769        let fidl_req = wlan_sme::MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest {
770            keylist: vec![key.clone(); 5],
771        });
772
773        assert!(h.mlme.handle_mlme_request(fidl_req).is_err());
774
775        // No SetKeys and SetKeysResp
776        assert_matches!(h.driver_calls.try_next(), Err(_));
777        assert_matches!(h.mlme_event_receiver.try_next(), Err(_));
778    }
779
780    #[test]
781    fn test_set_keys_request_when_resp_has_different_num_keys() {
782        let mut h = TestHelper::set_up();
783        h.fake_device.lock().set_keys_resp_mock =
784            Some(fidl_fullmac::WlanFullmacSetKeysResp { statuslist: [0i32; 2].to_vec() });
785        let fidl_req = wlan_sme::MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest {
786            keylist: vec![fidl_mlme::SetKeyDescriptor {
787                key: vec![5u8, 6],
788                key_id: 7,
789                key_type: fidl_mlme::KeyType::Group,
790                address: [8u8; 6],
791                rsc: 9,
792                cipher_suite_oui: [10u8; 3],
793                cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(
794                    11,
795                ),
796            }],
797        });
798
799        // An error is expected when converting the response
800        assert!(h.mlme.handle_mlme_request(fidl_req).is_err());
801
802        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::SetKeys { .. })));
803        // No SetKeysConf MLME event because the SetKeysResp from driver has different number of
804        // keys.
805        assert_matches!(h.mlme_event_receiver.try_next(), Err(_));
806    }
807
808    #[test]
809    fn test_eapol_request() {
810        let mut h = TestHelper::set_up();
811        let fidl_req = wlan_sme::MlmeRequest::Eapol(fidl_mlme::EapolRequest {
812            src_addr: [1u8; 6],
813            dst_addr: [2u8; 6],
814            data: vec![3u8; 4],
815        });
816
817        h.mlme.handle_mlme_request(fidl_req).unwrap();
818
819        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::EapolTx { req })) => req);
820        assert_eq!(driver_req.src_addr, Some([1u8; 6]));
821        assert_eq!(driver_req.dst_addr, Some([2u8; 6]));
822        assert_eq!(driver_req.data, Some(vec![3u8; 4]));
823    }
824
825    #[test_case(fidl_mlme::ControlledPortState::Open, true; "online")]
826    #[test_case(fidl_mlme::ControlledPortState::Closed, false; "offline")]
827    #[fuchsia::test(add_test_attr = false)]
828    fn test_set_ctrl_port(
829        controlled_port_state: fidl_mlme::ControlledPortState,
830        expected_link_state: bool,
831    ) {
832        let mut h = match controlled_port_state {
833            fidl_mlme::ControlledPortState::Open => TestHelper::set_up(),
834            fidl_mlme::ControlledPortState::Closed => {
835                TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open)
836            }
837        };
838        let fidl_req = wlan_sme::MlmeRequest::SetCtrlPort(fidl_mlme::SetControlledPortRequest {
839            peer_sta_address: [1u8; 6],
840            state: controlled_port_state,
841        });
842
843        h.mlme.handle_mlme_request(fidl_req).unwrap();
844
845        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::OnLinkStateChanged { req })) => {
846            assert_eq!(req.online, Some(expected_link_state));
847        });
848    }
849
850    #[test]
851    fn test_query_telemetry_support() {
852        let mut h = TestHelper::set_up();
853        let mocked_support = Ok(fidl_stats::TelemetrySupport {
854            inspect_counter_configs: Some(vec![fidl_stats::InspectCounterConfig {
855                counter_id: Some(1),
856                counter_name: Some("foo_counter".to_string()),
857                ..Default::default()
858            }]),
859            ..Default::default()
860        });
861        h.fake_device.lock().query_telemetry_support_mock.replace(mocked_support.clone());
862        let (support_responder, mut support_receiver) = wlan_sme::responder::Responder::new();
863        let fidl_req = wlan_sme::MlmeRequest::QueryTelemetrySupport(support_responder);
864
865        h.mlme.handle_mlme_request(fidl_req).unwrap();
866
867        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::QueryTelemetrySupport)));
868        let support = assert_matches!(support_receiver.try_recv(), Ok(Some(support)) => support);
869        assert_eq!(support, mocked_support);
870    }
871
872    #[test]
873    fn test_get_iface_stats() {
874        let mut h = TestHelper::set_up();
875        let mocked_stats = fidl_stats::IfaceStats {
876            connection_stats: Some(fidl_stats::ConnectionStats {
877                connection_id: Some(1),
878                rx_unicast_drop: Some(11),
879                rx_unicast_total: Some(22),
880                rx_multicast: Some(33),
881                tx_total: Some(44),
882                tx_drop: Some(55),
883                ..Default::default()
884            }),
885            ..Default::default()
886        };
887        h.fake_device
888            .lock()
889            .get_iface_stats_mock
890            .replace(fidl_mlme::GetIfaceStatsResponse::Stats(mocked_stats));
891        let (stats_responder, mut stats_receiver) = wlan_sme::responder::Responder::new();
892        let fidl_req = wlan_sme::MlmeRequest::GetIfaceStats(stats_responder);
893
894        h.mlme.handle_mlme_request(fidl_req).unwrap();
895
896        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::GetIfaceStats)));
897        let stats = assert_matches!(stats_receiver.try_recv(), Ok(Some(stats)) => stats);
898        let stats = assert_matches!(stats, fidl_mlme::GetIfaceStatsResponse::Stats(stats) => stats);
899        assert_eq!(
900            stats,
901            fidl_stats::IfaceStats {
902                connection_stats: Some(fidl_stats::ConnectionStats {
903                    connection_id: Some(1),
904                    rx_unicast_drop: Some(11),
905                    rx_unicast_total: Some(22),
906                    rx_multicast: Some(33),
907                    tx_total: Some(44),
908                    tx_drop: Some(55),
909                    ..Default::default()
910                }),
911                ..Default::default()
912            }
913        );
914    }
915
916    #[test]
917    fn test_get_iface_histogram_stats() {
918        let mut h = TestHelper::set_up();
919
920        let mocked_stats = fidl_stats::IfaceHistogramStats {
921            noise_floor_histograms: Some(vec![fidl_stats::NoiseFloorHistogram {
922                hist_scope: fidl_stats::HistScope::Station,
923                antenna_id: None,
924                noise_floor_samples: vec![fidl_stats::HistBucket {
925                    bucket_index: 2,
926                    num_samples: 3,
927                }],
928                invalid_samples: 4,
929            }]),
930            rssi_histograms: Some(vec![fidl_stats::RssiHistogram {
931                hist_scope: fidl_stats::HistScope::PerAntenna,
932                antenna_id: Some(Box::new(fidl_stats::AntennaId {
933                    freq: fidl_stats::AntennaFreq::Antenna5G,
934                    index: 5,
935                })),
936                rssi_samples: vec![fidl_stats::HistBucket { bucket_index: 6, num_samples: 7 }],
937                invalid_samples: 8,
938            }]),
939            rx_rate_index_histograms: Some(vec![fidl_stats::RxRateIndexHistogram {
940                hist_scope: fidl_stats::HistScope::Station,
941                antenna_id: None,
942                rx_rate_index_samples: vec![fidl_stats::HistBucket {
943                    bucket_index: 10,
944                    num_samples: 11,
945                }],
946                invalid_samples: 12,
947            }]),
948            snr_histograms: Some(vec![fidl_stats::SnrHistogram {
949                hist_scope: fidl_stats::HistScope::PerAntenna,
950                antenna_id: Some(Box::new(fidl_stats::AntennaId {
951                    freq: fidl_stats::AntennaFreq::Antenna2G,
952                    index: 13,
953                })),
954                snr_samples: vec![fidl_stats::HistBucket { bucket_index: 14, num_samples: 15 }],
955                invalid_samples: 16,
956            }]),
957            ..Default::default()
958        };
959
960        h.fake_device
961            .lock()
962            .get_iface_histogram_stats_mock
963            .replace(fidl_mlme::GetIfaceHistogramStatsResponse::Stats(mocked_stats.clone()));
964        let (stats_responder, mut stats_receiver) = wlan_sme::responder::Responder::new();
965        let fidl_req = wlan_sme::MlmeRequest::GetIfaceHistogramStats(stats_responder);
966
967        h.mlme.handle_mlme_request(fidl_req).unwrap();
968
969        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::GetIfaceHistogramStats)));
970        let stats = assert_matches!(stats_receiver.try_recv(), Ok(Some(stats)) => stats);
971        let stats = assert_matches!(stats, fidl_mlme::GetIfaceHistogramStatsResponse::Stats(stats) => stats);
972        assert_eq!(stats, mocked_stats);
973    }
974
975    #[test]
976    fn test_sae_handshake_resp() {
977        let mut h = TestHelper::set_up();
978        let fidl_req = wlan_sme::MlmeRequest::SaeHandshakeResp(fidl_mlme::SaeHandshakeResponse {
979            peer_sta_address: [1u8; 6],
980            status_code: fidl_ieee80211::StatusCode::AntiCloggingTokenRequired,
981        });
982
983        h.mlme.handle_mlme_request(fidl_req).unwrap();
984
985        let driver_req = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::SaeHandshakeResp { resp })) => resp);
986        assert_eq!(driver_req.peer_sta_address.unwrap(), [1u8; 6]);
987        assert_eq!(
988            driver_req.status_code.unwrap(),
989            fidl_ieee80211::StatusCode::AntiCloggingTokenRequired
990        );
991    }
992
993    #[test]
994    fn test_convert_sae_frame() {
995        let mut h = TestHelper::set_up();
996        let fidl_req = wlan_sme::MlmeRequest::SaeFrameTx(fidl_mlme::SaeFrame {
997            peer_sta_address: [1u8; 6],
998            status_code: fidl_ieee80211::StatusCode::Success,
999            seq_num: 2,
1000            sae_fields: vec![3u8; 4],
1001        });
1002
1003        h.mlme.handle_mlme_request(fidl_req).unwrap();
1004
1005        let driver_frame = assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::SaeFrameTx { frame })) => frame);
1006        assert_eq!(driver_frame.peer_sta_address.unwrap(), [1u8; 6]);
1007        assert_eq!(driver_frame.status_code.unwrap(), fidl_ieee80211::StatusCode::Success);
1008        assert_eq!(driver_frame.seq_num.unwrap(), 2);
1009        assert_eq!(driver_frame.sae_fields.unwrap(), vec![3u8; 4]);
1010    }
1011
1012    #[test]
1013    fn test_wmm_status_req() {
1014        let mut h = TestHelper::set_up();
1015        let fidl_req = wlan_sme::MlmeRequest::WmmStatusReq;
1016
1017        h.mlme.handle_mlme_request(fidl_req).unwrap();
1018
1019        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::WmmStatusReq)));
1020    }
1021
1022    #[test]
1023    fn test_set_mac_address_req() {
1024        let mut h = TestHelper::set_up();
1025        let (responder, mut receiver) = wlan_sme::responder::Responder::new();
1026        let mac_addr = [1u8; 6];
1027        let fidl_req = wlan_sme::MlmeRequest::SetMacAddress(mac_addr, responder);
1028
1029        h.mlme.handle_mlme_request(fidl_req).unwrap();
1030        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::SetMacAddress { req })) => {
1031            assert_eq!(req.mac_addr, mac_addr);
1032        });
1033        assert_matches!(receiver.try_recv(), Ok(Some(Ok(()))));
1034    }
1035
1036    pub struct TestHelper {
1037        fake_device: Arc<Mutex<FakeFullmacDeviceMocks>>,
1038        mlme: MlmeMainLoop<FakeFullmacDevice>,
1039        mlme_event_receiver: mpsc::UnboundedReceiver<fidl_mlme::MlmeEvent>,
1040        driver_calls: mpsc::UnboundedReceiver<DriverCall>,
1041        _mlme_request_sender: mpsc::UnboundedSender<wlan_sme::MlmeRequest>,
1042        _driver_event_sender: mpsc::UnboundedSender<FullmacDriverEvent>,
1043    }
1044
1045    impl TestHelper {
1046        pub fn set_up_with_link_state(device_link_state: fidl_mlme::ControlledPortState) -> Self {
1047            let (fake_device, driver_calls) = FakeFullmacDevice::new();
1048            let (mlme_request_sender, mlme_request_stream) = mpsc::unbounded();
1049            let (mlme_event_sender, mlme_event_receiver) = mpsc::unbounded();
1050            let mlme_event_sink = UnboundedSink::new(mlme_event_sender);
1051
1052            let (driver_event_sender, driver_event_stream) = mpsc::unbounded();
1053            let mocks = fake_device.mocks.clone();
1054
1055            let mlme = MlmeMainLoop {
1056                device: fake_device,
1057                mlme_request_stream,
1058                mlme_event_sink,
1059                driver_event_stream,
1060                is_bss_protected: false,
1061                device_link_state,
1062            };
1063            Self {
1064                fake_device: mocks,
1065                mlme,
1066                mlme_event_receiver,
1067                driver_calls,
1068                _mlme_request_sender: mlme_request_sender,
1069                _driver_event_sender: driver_event_sender,
1070            }
1071        }
1072
1073        // By default, link state starts off closed
1074        pub fn set_up() -> Self {
1075            Self::set_up_with_link_state(fidl_mlme::ControlledPortState::Closed)
1076        }
1077    }
1078}
1079#[cfg(test)]
1080mod handle_driver_event_tests {
1081    use super::*;
1082    use crate::device::test_utils::{DriverCall, FakeFullmacDevice, FakeFullmacDeviceMocks};
1083    use assert_matches::assert_matches;
1084    use fuchsia_sync::Mutex;
1085    use futures::Future;
1086    use futures::channel::mpsc;
1087    use futures::task::Poll;
1088    use std::pin::Pin;
1089    use std::sync::Arc;
1090    use test_case::test_case;
1091    use wlan_common::fake_fidl_bss_description;
1092    use wlan_common::sink::UnboundedSink;
1093    use {
1094        fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_internal as fidl_internal,
1095        fidl_fuchsia_wlan_mlme as fidl_mlme, fuchsia_async as fasync,
1096    };
1097
1098    fn create_bss_descriptions() -> fidl_common::BssDescription {
1099        fidl_common::BssDescription {
1100            bssid: [9u8; 6],
1101            bss_type: fidl_common::BssType::Infrastructure,
1102            beacon_period: 1,
1103            capability_info: 2,
1104            ies: vec![3, 4, 5],
1105            channel: fidl_ieee80211::WlanChannel {
1106                primary: 6,
1107                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
1108                secondary80: 0,
1109            },
1110            rssi_dbm: 7,
1111            snr_db: 8,
1112        }
1113    }
1114
1115    fn create_connect_request(security_ie: Vec<u8>) -> wlan_sme::MlmeRequest {
1116        wlan_sme::MlmeRequest::Connect(fidl_mlme::ConnectRequest {
1117            selected_bss: fake_fidl_bss_description!(Wpa2),
1118            connect_failure_timeout: 1u32,
1119            auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
1120            sae_password: vec![2u8, 3, 4],
1121            wep_key: None,
1122            security_ie,
1123        })
1124    }
1125
1126    #[test]
1127    fn test_on_scan_result() {
1128        let (mut h, mut test_fut) = TestHelper::set_up();
1129        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1130
1131        let bss = create_bss_descriptions();
1132        let scan_result = fidl_fullmac::WlanFullmacImplIfcOnScanResultRequest {
1133            txn_id: Some(42u64),
1134            timestamp_nanos: Some(1337i64),
1135            bss: Some(bss.clone()),
1136            ..Default::default()
1137        };
1138        assert_matches!(
1139            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.on_scan_result(&scan_result)),
1140            Poll::Ready(Ok(()))
1141        );
1142        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1143
1144        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1145        let result =
1146            assert_matches!(event, fidl_mlme::MlmeEvent::OnScanResult { result } => result);
1147        assert_eq!(result, fidl_mlme::ScanResult { txn_id: 42u64, timestamp_nanos: 1337i64, bss });
1148    }
1149
1150    #[test]
1151    fn test_on_scan_end() {
1152        let (mut h, mut test_fut) = TestHelper::set_up();
1153        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1154
1155        let scan_end = fidl_fullmac::WlanFullmacImplIfcOnScanEndRequest {
1156            txn_id: Some(42u64),
1157            code: Some(fidl_fullmac::WlanScanResult::Success),
1158            ..Default::default()
1159        };
1160        assert_matches!(
1161            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.on_scan_end(&scan_end)),
1162            Poll::Ready(Ok(()))
1163        );
1164        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1165
1166        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1167        let end = assert_matches!(event, fidl_mlme::MlmeEvent::OnScanEnd { end } => end);
1168        assert_eq!(
1169            end,
1170            fidl_mlme::ScanEnd { txn_id: 42u64, code: fidl_mlme::ScanResultCode::Success }
1171        );
1172    }
1173
1174    #[test]
1175    fn test_connect_conf() {
1176        let (mut h, mut test_fut) = TestHelper::set_up();
1177        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1178
1179        let connect_conf = fidl_fullmac::WlanFullmacImplIfcConnectConfRequest {
1180            peer_sta_address: Some([1u8; 6]),
1181            result_code: Some(fidl_ieee80211::StatusCode::Success),
1182            association_id: Some(2),
1183            association_ies: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]),
1184            ..Default::default()
1185        };
1186        assert_matches!(
1187            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.connect_conf(&connect_conf)),
1188            Poll::Ready(Ok(()))
1189        );
1190        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1191
1192        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1193        let conf = assert_matches!(event, fidl_mlme::MlmeEvent::ConnectConf { resp } => resp);
1194        assert_eq!(
1195            conf,
1196            fidl_mlme::ConnectConfirm {
1197                peer_sta_address: [1u8; 6],
1198                result_code: fidl_ieee80211::StatusCode::Success,
1199                association_id: 2,
1200                association_ies: vec![1, 2, 3, 4, 5, 6, 7, 8, 9],
1201            }
1202        );
1203    }
1204
1205    #[test_case(true, fidl_ieee80211::StatusCode::Success, false; "secure connect with success status is not online yet")]
1206    #[test_case(false, fidl_ieee80211::StatusCode::Success, true; "insecure connect with success status is online right away")]
1207    #[test_case(true, fidl_ieee80211::StatusCode::RefusedReasonUnspecified, false; "secure connect with failed status is not online")]
1208    #[test_case(false, fidl_ieee80211::StatusCode::RefusedReasonUnspecified, false; "insecure connect with failed status in not online")]
1209    #[fuchsia::test(add_test_attr = false)]
1210    fn test_connect_req_connect_conf_link_state(
1211        secure_connect: bool,
1212        connect_result_code: fidl_ieee80211::StatusCode,
1213        expected_online: bool,
1214    ) {
1215        let (mut h, mut test_fut) =
1216            TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
1217        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1218
1219        let connect_req =
1220            create_connect_request(if secure_connect { vec![7u8, 8] } else { vec![] });
1221        h.mlme_request_sender
1222            .unbounded_send(connect_req)
1223            .expect("sending ConnectReq should succeed");
1224        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1225
1226        let connect_conf = fidl_fullmac::WlanFullmacImplIfcConnectConfRequest {
1227            peer_sta_address: Some([1u8; 6]),
1228            result_code: Some(connect_result_code),
1229            association_id: Some(2),
1230            association_ies: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]),
1231            ..Default::default()
1232        };
1233
1234        assert_matches!(
1235            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.connect_conf(&connect_conf)),
1236            Poll::Ready(Ok(()))
1237        );
1238        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1239
1240        assert_matches!(
1241            h.driver_calls.try_next(),
1242            Ok(Some(DriverCall::OnLinkStateChanged { req })) => {
1243              assert_eq!(req.online, Some(false));
1244          }
1245        );
1246        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::ConnectReq { .. })));
1247        if expected_online {
1248            assert_matches!(
1249                h.driver_calls.try_next(),
1250                Ok(Some(DriverCall::OnLinkStateChanged { req })) => {
1251                  assert_eq!(req.online, Some(true));
1252              }
1253            );
1254        } else {
1255            assert_matches!(h.driver_calls.try_next(), Err(_));
1256        }
1257    }
1258
1259    #[test]
1260    fn test_auth_ind() {
1261        let (mut h, mut test_fut) = TestHelper::set_up();
1262        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1263
1264        let auth_ind = fidl_fullmac::WlanFullmacImplIfcAuthIndRequest {
1265            peer_sta_address: Some([1u8; 6]),
1266            auth_type: Some(fidl_fullmac::WlanAuthType::OpenSystem),
1267            ..Default::default()
1268        };
1269        assert_matches!(
1270            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.auth_ind(&auth_ind)),
1271            Poll::Ready(Ok(()))
1272        );
1273        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1274
1275        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1276        let ind = assert_matches!(event, fidl_mlme::MlmeEvent::AuthenticateInd { ind } => ind);
1277        assert_eq!(
1278            ind,
1279            fidl_mlme::AuthenticateIndication {
1280                peer_sta_address: [1u8; 6],
1281                auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
1282            }
1283        );
1284    }
1285
1286    #[test_case(fidl_common::WlanMacRole::Client; "client")]
1287    #[test_case(fidl_common::WlanMacRole::Ap; "ap")]
1288    #[fuchsia::test(add_test_attr = false)]
1289    fn test_deauth_conf(mac_role: fidl_common::WlanMacRole) {
1290        let (mut h, mut test_fut) =
1291            TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
1292        h.fake_device.lock().query_device_info_mock.as_mut().unwrap().role = Some(mac_role);
1293        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1294
1295        let deauth_conf = fidl_fullmac::WlanFullmacImplIfcDeauthConfRequest {
1296            peer_sta_address: Some([1u8; 6]),
1297            ..Default::default()
1298        };
1299        assert_matches!(
1300            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.deauth_conf(&deauth_conf)),
1301            Poll::Ready(Ok(()))
1302        );
1303        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1304
1305        if mac_role == fidl_common::WlanMacRole::Client {
1306            assert_matches!(
1307                h.driver_calls.try_next(),
1308                Ok(Some(DriverCall::OnLinkStateChanged { req })) => {
1309                  assert_eq!(req.online, Some(false));
1310              }
1311            );
1312        } else {
1313            assert_matches!(h.driver_calls.try_next(), Err(_));
1314        }
1315
1316        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1317        let conf =
1318            assert_matches!(event, fidl_mlme::MlmeEvent::DeauthenticateConf { resp } => resp);
1319        assert_eq!(conf, fidl_mlme::DeauthenticateConfirm { peer_sta_address: [1u8; 6] });
1320    }
1321
1322    #[test_case(fidl_common::WlanMacRole::Client; "client")]
1323    #[test_case(fidl_common::WlanMacRole::Ap; "ap")]
1324    #[fuchsia::test(add_test_attr = false)]
1325    fn test_deauth_ind(mac_role: fidl_common::WlanMacRole) {
1326        let (mut h, mut test_fut) =
1327            TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
1328        h.fake_device.lock().query_device_info_mock.as_mut().unwrap().role = Some(mac_role);
1329        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1330
1331        let deauth_ind = fidl_fullmac::WlanFullmacImplIfcDeauthIndRequest {
1332            peer_sta_address: Some([1u8; 6]),
1333            reason_code: Some(fidl_ieee80211::ReasonCode::LeavingNetworkDeauth),
1334            locally_initiated: Some(true),
1335            ..Default::default()
1336        };
1337        assert_matches!(
1338            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.deauth_ind(&deauth_ind)),
1339            Poll::Ready(Ok(()))
1340        );
1341        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1342
1343        if mac_role == fidl_common::WlanMacRole::Client {
1344            assert_matches!(
1345                h.driver_calls.try_next(),
1346                Ok(Some(DriverCall::OnLinkStateChanged { req })) =>{
1347                  assert_eq!(req.online, Some(false));
1348              }
1349            );
1350        } else {
1351            assert_matches!(h.driver_calls.try_next(), Err(_));
1352        }
1353
1354        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1355        let ind = assert_matches!(event, fidl_mlme::MlmeEvent::DeauthenticateInd { ind } => ind);
1356        assert_eq!(
1357            ind,
1358            fidl_mlme::DeauthenticateIndication {
1359                peer_sta_address: [1u8; 6],
1360                reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
1361                locally_initiated: true,
1362            }
1363        );
1364    }
1365
1366    #[test]
1367    fn test_assoc_ind() {
1368        let (mut h, mut test_fut) = TestHelper::set_up();
1369        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1370
1371        let assoc_ind = fidl_fullmac::WlanFullmacImplIfcAssocIndRequest {
1372            peer_sta_address: Some([1u8; 6]),
1373            listen_interval: Some(2),
1374            ssid: vec![3u8; 4].into(),
1375            rsne: Some(vec![5u8; 6]),
1376            vendor_ie: Some(vec![7u8; 8]),
1377            ..Default::default()
1378        };
1379        assert_matches!(
1380            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.assoc_ind(&assoc_ind)),
1381            Poll::Ready(Ok(()))
1382        );
1383        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1384
1385        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1386        let ind = assert_matches!(event, fidl_mlme::MlmeEvent::AssociateInd { ind } => ind);
1387        assert_eq!(
1388            ind,
1389            fidl_mlme::AssociateIndication {
1390                peer_sta_address: [1u8; 6],
1391                capability_info: 0,
1392                listen_interval: 2,
1393                ssid: Some(vec![3u8; 4]),
1394                rates: vec![],
1395                rsne: Some(vec![5u8; 6]),
1396            }
1397        );
1398    }
1399
1400    #[test_case(fidl_common::WlanMacRole::Client; "client")]
1401    #[test_case(fidl_common::WlanMacRole::Ap; "ap")]
1402    #[fuchsia::test(add_test_attr = false)]
1403    fn test_disassoc_conf(mac_role: fidl_common::WlanMacRole) {
1404        let (mut h, mut test_fut) =
1405            TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
1406        h.fake_device.lock().query_device_info_mock.as_mut().unwrap().role = Some(mac_role);
1407        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1408
1409        let disassoc_conf = fidl_fullmac::WlanFullmacImplIfcDisassocConfRequest {
1410            status: Some(1),
1411            ..Default::default()
1412        };
1413        assert_matches!(
1414            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.disassoc_conf(&disassoc_conf)),
1415            Poll::Ready(Ok(()))
1416        );
1417        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1418
1419        if mac_role == fidl_common::WlanMacRole::Client {
1420            assert_matches!(
1421                h.driver_calls.try_next(),
1422                Ok(Some(DriverCall::OnLinkStateChanged { req })) =>{
1423                  assert_eq!(req.online, Some(false));
1424              }
1425            );
1426        } else {
1427            assert_matches!(h.driver_calls.try_next(), Err(_));
1428        }
1429
1430        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1431        let conf = assert_matches!(event, fidl_mlme::MlmeEvent::DisassociateConf { resp } => resp);
1432        assert_eq!(conf, fidl_mlme::DisassociateConfirm { status: 1 });
1433    }
1434
1435    #[test_case(fidl_common::WlanMacRole::Client; "client")]
1436    #[test_case(fidl_common::WlanMacRole::Ap; "ap")]
1437    #[fuchsia::test(add_test_attr = false)]
1438    fn test_disassoc_ind(mac_role: fidl_common::WlanMacRole) {
1439        let (mut h, mut test_fut) =
1440            TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
1441        h.fake_device.lock().query_device_info_mock.as_mut().unwrap().role = Some(mac_role);
1442        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1443
1444        let disassoc_ind = fidl_fullmac::WlanFullmacImplIfcDisassocIndRequest {
1445            peer_sta_address: Some([1u8; 6]),
1446            reason_code: Some(fidl_ieee80211::ReasonCode::LeavingNetworkDeauth),
1447            locally_initiated: Some(true),
1448            ..Default::default()
1449        };
1450        assert_matches!(
1451            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.disassoc_ind(&disassoc_ind)),
1452            Poll::Ready(Ok(()))
1453        );
1454        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1455
1456        if mac_role == fidl_common::WlanMacRole::Client {
1457            assert_matches!(
1458                h.driver_calls.try_next(),
1459                Ok(Some(DriverCall::OnLinkStateChanged { req })) =>{
1460                  assert_eq!(req.online, Some(false));
1461              }
1462            );
1463        } else {
1464            assert_matches!(h.driver_calls.try_next(), Err(_));
1465        }
1466
1467        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1468        let ind = assert_matches!(event, fidl_mlme::MlmeEvent::DisassociateInd { ind } => ind);
1469        assert_eq!(
1470            ind,
1471            fidl_mlme::DisassociateIndication {
1472                peer_sta_address: [1u8; 6],
1473                reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
1474                locally_initiated: true,
1475            }
1476        );
1477    }
1478
1479    #[test_case(fidl_fullmac::StartResult::Success, true, fidl_mlme::StartResultCode::Success; "success start result")]
1480    #[test_case(fidl_fullmac::StartResult::BssAlreadyStartedOrJoined, false, fidl_mlme::StartResultCode::BssAlreadyStartedOrJoined; "other start result")]
1481    #[fuchsia::test(add_test_attr = false)]
1482    fn test_start_conf(
1483        start_result: fidl_fullmac::StartResult,
1484        expected_link_state_changed: bool,
1485        expected_fidl_result_code: fidl_mlme::StartResultCode,
1486    ) {
1487        let (mut h, mut test_fut) = TestHelper::set_up();
1488        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1489
1490        let start_conf = fidl_fullmac::WlanFullmacImplIfcStartConfRequest {
1491            result_code: Some(start_result),
1492            ..Default::default()
1493        };
1494        assert_matches!(
1495            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.start_conf(&start_conf)),
1496            Poll::Ready(Ok(()))
1497        );
1498        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1499
1500        if expected_link_state_changed {
1501            assert_matches!(
1502                h.driver_calls.try_next(),
1503                Ok(Some(DriverCall::OnLinkStateChanged { req }))=>{
1504                  assert_eq!(req.online, Some(true));
1505              }
1506            );
1507        } else {
1508            assert_matches!(h.driver_calls.try_next(), Err(_));
1509        }
1510
1511        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1512        let conf = assert_matches!(event, fidl_mlme::MlmeEvent::StartConf { resp } => resp);
1513        assert_eq!(conf, fidl_mlme::StartConfirm { result_code: expected_fidl_result_code });
1514    }
1515
1516    #[test]
1517    fn test_stop_conf() {
1518        let (mut h, mut test_fut) = TestHelper::set_up();
1519        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1520
1521        let stop_conf = fidl_fullmac::WlanFullmacImplIfcStopConfRequest {
1522            result_code: Some(fidl_fullmac::StopResult::Success),
1523            ..Default::default()
1524        };
1525        assert_matches!(
1526            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.stop_conf(&stop_conf)),
1527            Poll::Ready(Ok(()))
1528        );
1529        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1530
1531        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1532        let conf = assert_matches!(event, fidl_mlme::MlmeEvent::StopConf { resp } => resp);
1533        assert_eq!(
1534            conf,
1535            fidl_mlme::StopConfirm { result_code: fidl_mlme::StopResultCode::Success }
1536        );
1537    }
1538
1539    #[test]
1540    fn test_eapol_conf() {
1541        let (mut h, mut test_fut) = TestHelper::set_up();
1542        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1543
1544        let eapol_conf = fidl_fullmac::WlanFullmacImplIfcEapolConfRequest {
1545            result_code: Some(fidl_fullmac::EapolTxResult::Success),
1546            dst_addr: Some([1u8; 6]),
1547            ..Default::default()
1548        };
1549        assert_matches!(
1550            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.eapol_conf(&eapol_conf)),
1551            Poll::Ready(Ok(()))
1552        );
1553        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1554
1555        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1556        let conf = assert_matches!(event, fidl_mlme::MlmeEvent::EapolConf { resp } => resp);
1557        assert_eq!(
1558            conf,
1559            fidl_mlme::EapolConfirm {
1560                result_code: fidl_mlme::EapolResultCode::Success,
1561                dst_addr: [1u8; 6],
1562            }
1563        );
1564    }
1565
1566    #[test]
1567    fn test_on_channel_switch() {
1568        let (mut h, mut test_fut) = TestHelper::set_up();
1569        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1570
1571        let channel_switch_info = fidl_fullmac::WlanFullmacChannelSwitchInfo { new_channel: 9 };
1572        assert_matches!(
1573            h.exec.run_until_stalled(
1574                &mut h.fullmac_ifc_proxy.on_channel_switch(&channel_switch_info)
1575            ),
1576            Poll::Ready(Ok(()))
1577        );
1578        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1579
1580        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1581        let info = assert_matches!(event, fidl_mlme::MlmeEvent::OnChannelSwitched { info } => info);
1582        assert_eq!(info, fidl_internal::ChannelSwitchInfo { new_channel: 9 });
1583    }
1584
1585    #[test]
1586    fn test_roam_start_ind() {
1587        let (mut h, mut test_fut) =
1588            TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
1589        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1590
1591        let selected_bss = create_bss_descriptions();
1592        let roam_start_ind = fidl_fullmac::WlanFullmacImplIfcRoamStartIndRequest {
1593            selected_bssid: Some(selected_bss.bssid.clone()),
1594            selected_bss: Some(selected_bss.clone()),
1595            original_association_maintained: Some(false),
1596            ..Default::default()
1597        };
1598        assert_matches!(
1599            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.roam_start_ind(&roam_start_ind)),
1600            Poll::Ready(Ok(()))
1601        );
1602        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1603
1604        // Receipt of a roam start causes MLME to close the controlled port.
1605        assert_matches!(
1606            h.driver_calls.try_next(),
1607            Ok(Some(DriverCall::OnLinkStateChanged { req }))=>{
1608              assert_eq!(req.online, Some(false));
1609          }
1610        );
1611
1612        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1613        let ind = assert_matches!(event, fidl_mlme::MlmeEvent::RoamStartInd { ind } => ind);
1614
1615        // SME is notified of the roam start.
1616        assert_eq!(
1617            ind,
1618            fidl_mlme::RoamStartIndication {
1619                selected_bssid: selected_bss.bssid,
1620                selected_bss,
1621                original_association_maintained: false,
1622            }
1623        );
1624    }
1625
1626    #[test]
1627    fn test_roam_req() {
1628        let (mut h, mut test_fut) =
1629            TestHelper::set_up_with_link_state(fidl_mlme::ControlledPortState::Open);
1630
1631        let selected_bss = create_bss_descriptions();
1632        let roam_req = wlan_sme::MlmeRequest::Roam(fidl_mlme::RoamRequest {
1633            selected_bss: selected_bss.clone(),
1634        });
1635
1636        h.mlme_request_sender.unbounded_send(roam_req).expect("sending RoamReq should succeed");
1637        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1638
1639        // Receipt of a roam request causes MLME to close the controlled port.
1640        assert_matches!(
1641            h.driver_calls.try_next(),
1642            Ok(Some(DriverCall::OnLinkStateChanged { req })) => {
1643                assert_eq!(req.online, Some(false));
1644            }
1645        );
1646        assert_matches!(h.driver_calls.try_next(), Ok(Some(DriverCall::RoamReq { req })) => {
1647            assert_eq!(selected_bss, req.selected_bss.clone().unwrap());
1648        });
1649    }
1650
1651    #[test]
1652    fn test_roam_result_ind() {
1653        let (mut h, mut test_fut) = TestHelper::set_up();
1654        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1655
1656        let selected_bss = create_bss_descriptions();
1657        let original_association_maintained = false;
1658        let target_bss_authenticated = true;
1659        let association_id = 42;
1660        let association_ies = Vec::new();
1661
1662        let roam_result_ind = fidl_fullmac::WlanFullmacImplIfcRoamResultIndRequest {
1663            selected_bssid: Some(selected_bss.bssid.clone()),
1664            status_code: Some(fidl_ieee80211::StatusCode::Success),
1665            original_association_maintained: Some(original_association_maintained),
1666            target_bss_authenticated: Some(target_bss_authenticated),
1667            association_id: Some(association_id),
1668            association_ies: Some(association_ies.clone()),
1669            ..Default::default()
1670        };
1671
1672        assert_matches!(
1673            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.roam_result_ind(&roam_result_ind)),
1674            Poll::Ready(Ok(()))
1675        );
1676        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1677
1678        // Receipt of a roam result success causes MLME to open the controlled port on an open network.
1679        assert_matches!(
1680            h.driver_calls.try_next(),
1681            Ok(Some(DriverCall::OnLinkStateChanged { req }))=>{
1682              assert_eq!(req.online, Some(true));
1683          }
1684        );
1685
1686        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1687        let ind = assert_matches!(event, fidl_mlme::MlmeEvent::RoamResultInd { ind } => ind);
1688
1689        // SME is notified of the roam result.
1690        assert_eq!(
1691            ind,
1692            fidl_mlme::RoamResultIndication {
1693                selected_bssid: selected_bss.bssid,
1694                status_code: fidl_ieee80211::StatusCode::Success,
1695                original_association_maintained,
1696                target_bss_authenticated,
1697                association_id,
1698                association_ies,
1699            }
1700        );
1701    }
1702
1703    #[test]
1704    fn test_roam_conf() {
1705        let (mut h, mut test_fut) = TestHelper::set_up();
1706        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1707
1708        let selected_bss = create_bss_descriptions();
1709        let original_association_maintained = false;
1710        let target_bss_authenticated = true;
1711        let association_id = 42;
1712        let association_ies = Vec::new();
1713
1714        let roam_conf = fidl_fullmac::WlanFullmacImplIfcRoamConfRequest {
1715            selected_bssid: Some(selected_bss.bssid.clone()),
1716            status_code: Some(fidl_ieee80211::StatusCode::Success),
1717            original_association_maintained: Some(original_association_maintained),
1718            target_bss_authenticated: Some(target_bss_authenticated),
1719            association_id: Some(association_id),
1720            association_ies: Some(association_ies.clone()),
1721            ..Default::default()
1722        };
1723
1724        assert_matches!(
1725            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.roam_conf(&roam_conf)),
1726            Poll::Ready(Ok(()))
1727        );
1728        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1729
1730        // Receipt of a roam result success causes MLME to open the controlled port on an open network.
1731        assert_matches!(
1732            h.driver_calls.try_next(),
1733            Ok(Some(DriverCall::OnLinkStateChanged { req })) => {
1734                assert_eq!(req.online, Some(true));
1735            }
1736        );
1737
1738        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1739        let conf = assert_matches!(event, fidl_mlme::MlmeEvent::RoamConf { conf } => conf);
1740
1741        // SME is notified of the roam result.
1742        assert_eq!(
1743            conf,
1744            fidl_mlme::RoamConfirm {
1745                selected_bssid: selected_bss.bssid,
1746                status_code: fidl_ieee80211::StatusCode::Success,
1747                original_association_maintained,
1748                target_bss_authenticated,
1749                association_id,
1750                association_ies,
1751            }
1752        );
1753    }
1754
1755    #[test]
1756    fn test_signal_report() {
1757        let (mut h, mut test_fut) = TestHelper::set_up();
1758        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1759
1760        let signal_report_ind =
1761            fidl_fullmac::WlanFullmacSignalReportIndication { rssi_dbm: 1, snr_db: 2 };
1762        assert_matches!(
1763            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.signal_report(&signal_report_ind)),
1764            Poll::Ready(Ok(()))
1765        );
1766        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1767
1768        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1769        let ind = assert_matches!(event, fidl_mlme::MlmeEvent::SignalReport { ind } => ind);
1770        assert_eq!(ind, fidl_internal::SignalReportIndication { rssi_dbm: 1, snr_db: 2 });
1771    }
1772
1773    #[test]
1774    fn test_eapol_ind() {
1775        let (mut h, mut test_fut) = TestHelper::set_up();
1776        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1777
1778        let eapol_ind = fidl_fullmac::WlanFullmacImplIfcEapolIndRequest {
1779            src_addr: Some([1u8; 6]),
1780            dst_addr: Some([2u8; 6]),
1781            data: Some(vec![3u8; 4]),
1782            ..Default::default()
1783        };
1784        assert_matches!(
1785            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.eapol_ind(&eapol_ind)),
1786            Poll::Ready(Ok(()))
1787        );
1788        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1789
1790        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1791        let ind = assert_matches!(event, fidl_mlme::MlmeEvent::EapolInd { ind } => ind);
1792        assert_eq!(
1793            ind,
1794            fidl_mlme::EapolIndication {
1795                src_addr: [1u8; 6],
1796                dst_addr: [2u8; 6],
1797                data: vec![3u8; 4],
1798            }
1799        );
1800    }
1801
1802    #[test]
1803    fn test_on_pmk_available() {
1804        let (mut h, mut test_fut) = TestHelper::set_up();
1805        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1806
1807        let pmk_info = fidl_fullmac::WlanFullmacImplIfcOnPmkAvailableRequest {
1808            pmk: Some(vec![1u8; 2]),
1809            pmkid: Some(vec![3u8; 4]),
1810            ..Default::default()
1811        };
1812        assert_matches!(
1813            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.on_pmk_available(&pmk_info)),
1814            Poll::Ready(Ok(()))
1815        );
1816        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1817
1818        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1819        let info = assert_matches!(event, fidl_mlme::MlmeEvent::OnPmkAvailable { info } => info);
1820        assert_eq!(info, fidl_mlme::PmkInfo { pmk: vec![1u8; 2], pmkid: vec![3u8; 4] });
1821    }
1822
1823    #[test]
1824    fn test_sae_handshake_ind() {
1825        let (mut h, mut test_fut) = TestHelper::set_up();
1826        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1827
1828        let sae_handshake_ind = fidl_fullmac::WlanFullmacImplIfcSaeHandshakeIndRequest {
1829            peer_sta_address: Some([1u8; 6]),
1830            ..Default::default()
1831        };
1832        assert_matches!(
1833            h.exec
1834                .run_until_stalled(&mut h.fullmac_ifc_proxy.sae_handshake_ind(&sae_handshake_ind)),
1835            Poll::Ready(Ok(()))
1836        );
1837        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1838
1839        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1840        let ind = assert_matches!(event, fidl_mlme::MlmeEvent::OnSaeHandshakeInd { ind } => ind);
1841        assert_eq!(ind, fidl_mlme::SaeHandshakeIndication { peer_sta_address: [1u8; 6] });
1842    }
1843
1844    #[test]
1845    fn test_sae_frame_rx() {
1846        let (mut h, mut test_fut) = TestHelper::set_up();
1847        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1848
1849        let sae_frame = fidl_fullmac::SaeFrame {
1850            peer_sta_address: Some([1u8; 6]),
1851            status_code: Some(fidl_ieee80211::StatusCode::Success),
1852            seq_num: Some(2),
1853            sae_fields: Some(vec![3u8; 4]),
1854            ..Default::default()
1855        };
1856        assert_matches!(
1857            h.exec.run_until_stalled(&mut h.fullmac_ifc_proxy.sae_frame_rx(&sae_frame)),
1858            Poll::Ready(Ok(()))
1859        );
1860        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1861
1862        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1863        let frame = assert_matches!(event, fidl_mlme::MlmeEvent::OnSaeFrameRx { frame } => frame);
1864        assert_eq!(
1865            frame,
1866            fidl_mlme::SaeFrame {
1867                peer_sta_address: [1u8; 6],
1868                status_code: fidl_ieee80211::StatusCode::Success,
1869                seq_num: 2,
1870                sae_fields: vec![3u8; 4],
1871            }
1872        );
1873    }
1874
1875    #[test]
1876    fn test_on_wmm_status_resp() {
1877        let (mut h, mut test_fut) = TestHelper::set_up();
1878        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1879
1880        let status = zx::sys::ZX_OK;
1881        let wmm_params = fidl_common::WlanWmmParameters {
1882            apsd: true,
1883            ac_be_params: fidl_common::WlanWmmAccessCategoryParameters {
1884                ecw_min: 1,
1885                ecw_max: 2,
1886                aifsn: 3,
1887                txop_limit: 4,
1888                acm: true,
1889            },
1890            ac_bk_params: fidl_common::WlanWmmAccessCategoryParameters {
1891                ecw_min: 5,
1892                ecw_max: 6,
1893                aifsn: 7,
1894                txop_limit: 8,
1895                acm: false,
1896            },
1897            ac_vi_params: fidl_common::WlanWmmAccessCategoryParameters {
1898                ecw_min: 9,
1899                ecw_max: 10,
1900                aifsn: 11,
1901                txop_limit: 12,
1902                acm: true,
1903            },
1904            ac_vo_params: fidl_common::WlanWmmAccessCategoryParameters {
1905                ecw_min: 13,
1906                ecw_max: 14,
1907                aifsn: 15,
1908                txop_limit: 16,
1909                acm: false,
1910            },
1911        };
1912        assert_matches!(
1913            h.exec.run_until_stalled(
1914                &mut h.fullmac_ifc_proxy.on_wmm_status_resp(status, &wmm_params)
1915            ),
1916            Poll::Ready(Ok(()))
1917        );
1918        assert_matches!(h.exec.run_until_stalled(&mut test_fut), Poll::Pending);
1919
1920        let event = assert_matches!(h.mlme_event_receiver.try_next(), Ok(Some(ev)) => ev);
1921        let (status, resp) = assert_matches!(event, fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp } => (status, resp));
1922        assert_eq!(status, zx::sys::ZX_OK);
1923        assert_eq!(
1924            resp,
1925            fidl_internal::WmmStatusResponse {
1926                apsd: true,
1927                ac_be_params: fidl_internal::WmmAcParams {
1928                    ecw_min: 1,
1929                    ecw_max: 2,
1930                    aifsn: 3,
1931                    txop_limit: 4,
1932                    acm: true,
1933                },
1934                ac_bk_params: fidl_internal::WmmAcParams {
1935                    ecw_min: 5,
1936                    ecw_max: 6,
1937                    aifsn: 7,
1938                    txop_limit: 8,
1939                    acm: false,
1940                },
1941                ac_vi_params: fidl_internal::WmmAcParams {
1942                    ecw_min: 9,
1943                    ecw_max: 10,
1944                    aifsn: 11,
1945                    txop_limit: 12,
1946                    acm: true,
1947                },
1948                ac_vo_params: fidl_internal::WmmAcParams {
1949                    ecw_min: 13,
1950                    ecw_max: 14,
1951                    aifsn: 15,
1952                    txop_limit: 16,
1953                    acm: false,
1954                },
1955            }
1956        );
1957    }
1958
1959    struct TestHelper {
1960        fake_device: Arc<Mutex<FakeFullmacDeviceMocks>>,
1961        mlme_request_sender: mpsc::UnboundedSender<wlan_sme::MlmeRequest>,
1962        fullmac_ifc_proxy: fidl_fullmac::WlanFullmacImplIfcProxy,
1963        mlme_event_receiver: mpsc::UnboundedReceiver<fidl_mlme::MlmeEvent>,
1964        driver_calls: mpsc::UnboundedReceiver<DriverCall>,
1965        exec: fasync::TestExecutor,
1966    }
1967
1968    impl TestHelper {
1969        // By default, MLME is set up closed
1970        pub fn set_up() -> (Self, Pin<Box<impl Future<Output = Result<(), anyhow::Error>>>>) {
1971            Self::set_up_with_link_state(fidl_mlme::ControlledPortState::Closed)
1972        }
1973
1974        /// For tests that need to observe all OnLinkStateChanged calls, they can choose what link
1975        /// state the device starts with to ensure that none get filtered out by MLME.
1976        pub fn set_up_with_link_state(
1977            device_link_state: fidl_mlme::ControlledPortState,
1978        ) -> (Self, Pin<Box<impl Future<Output = Result<(), anyhow::Error>>>>) {
1979            let exec = fasync::TestExecutor::new();
1980
1981            let (fake_device, driver_call_receiver) = FakeFullmacDevice::new();
1982            let (mlme_request_sender, mlme_request_stream) = mpsc::unbounded();
1983            let (mlme_event_sender, mlme_event_receiver) = mpsc::unbounded();
1984            let mlme_event_sink = UnboundedSink::new(mlme_event_sender);
1985
1986            let (driver_event_sender, driver_event_stream) = mpsc::unbounded();
1987            let driver_event_sink = FullmacDriverEventSink(UnboundedSink::new(driver_event_sender));
1988
1989            let (fullmac_ifc_proxy, fullmac_ifc_request_stream) =
1990                fidl::endpoints::create_proxy_and_stream::<fidl_fullmac::WlanFullmacImplIfcMarker>(
1991                );
1992
1993            let mocks = fake_device.mocks.clone();
1994            let main_loop = MlmeMainLoop {
1995                device: fake_device,
1996                mlme_request_stream,
1997                mlme_event_sink,
1998                driver_event_stream,
1999                is_bss_protected: false,
2000                device_link_state,
2001            };
2002
2003            let test_fut = Box::pin(main_loop.serve(fullmac_ifc_request_stream, driver_event_sink));
2004
2005            let test_helper = TestHelper {
2006                fake_device: mocks,
2007                mlme_request_sender,
2008                fullmac_ifc_proxy,
2009                mlme_event_receiver,
2010                driver_calls: driver_call_receiver,
2011                exec,
2012            };
2013            (test_helper, test_fut)
2014        }
2015    }
2016}