Skip to main content

wlan_service_util/
client.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 anyhow::{Context as _, Error, format_err};
6use fidl::endpoints;
7use fidl_fuchsia_wlan_common as fidl_common;
8use fidl_fuchsia_wlan_common::WlanMacRole;
9use fidl_fuchsia_wlan_device_service::DeviceMonitorProxy;
10use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
11use fidl_fuchsia_wlan_internal as fidl_internal;
12use fidl_fuchsia_wlan_sme as fidl_sme;
13use futures::stream::TryStreamExt;
14use ieee80211::Ssid;
15use wlan_common::bss::{BssDescription, Protection};
16use wlan_common::security::SecurityError;
17use wlan_common::security::wep::WepKey;
18use wlan_common::security::wpa::credential::{Passphrase, Psk};
19
20type WlanService = DeviceMonitorProxy;
21
22/// Context for negotiating an `Authentication` (security protocol and credentials).
23///
24/// This ephemeral type joins a BSS description with credential data to negotiate an
25/// `Authentication`. See the `TryFrom` implementation below.
26#[derive(Clone, Debug)]
27struct SecurityContext {
28    pub bss: BssDescription,
29    pub unparsed_credential_bytes: Vec<u8>,
30}
31
32/// Negotiates an `Authentication` from security information (given credentials and a BSS
33/// description). The security protocol is based on the protection information described by the BSS
34/// description. This is used to parse and validate the given credentials.
35///
36/// This is necessary, because the service utilities communicate directly with SME, which requires
37/// more detailed information than the Policy layer.
38impl TryFrom<SecurityContext> for fidl_internal::Authentication {
39    type Error = SecurityError;
40
41    fn try_from(context: SecurityContext) -> Result<Self, SecurityError> {
42        let SecurityContext { bss, unparsed_credential_bytes } = context;
43        fn parse_wpa_credentials(
44            unparsed_credential_bytes: Vec<u8>,
45        ) -> Result<fidl_internal::Credentials, SecurityError> {
46            // Interpret credentials as a PSK first. This format is more specific.
47            if let Ok(psk) = Psk::try_from(unparsed_credential_bytes.as_slice()) {
48                Ok(fidl_internal::Credentials::Wpa(fidl_internal::WpaCredentials::Psk(psk.into())))
49            } else {
50                let passphrase = Passphrase::try_from(unparsed_credential_bytes)?;
51                Ok(fidl_internal::Credentials::Wpa(fidl_internal::WpaCredentials::Passphrase(
52                    passphrase.into(),
53                )))
54            }
55        }
56
57        match bss.protection() {
58            // Unsupported.
59            // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
60            Protection::Unknown | Protection::Wpa2Enterprise | Protection::Wpa3Enterprise => {
61                Err(SecurityError::Unsupported)
62            }
63            Protection::Open | Protection::Owe | Protection::OpenOweTransition
64                if unparsed_credential_bytes.len() > 0 =>
65            {
66                Err(SecurityError::Incompatible)
67            }
68            Protection::Open | Protection::OpenOweTransition => Ok(fidl_internal::Authentication {
69                protocol: fidl_internal::Protocol::Open,
70                credentials: None,
71            }),
72            Protection::Owe => Ok(fidl_internal::Authentication {
73                protocol: fidl_internal::Protocol::Owe,
74                credentials: None,
75            }),
76            Protection::Wep => WepKey::parse(unparsed_credential_bytes.as_slice())
77                .map(|key| fidl_internal::Authentication {
78                    protocol: fidl_internal::Protocol::Wep,
79                    credentials: Some(Box::new(fidl_internal::Credentials::Wep(
80                        fidl_internal::WepCredentials { key: key.into() },
81                    ))),
82                })
83                .map_err(From::from),
84            Protection::Wpa1 => {
85                parse_wpa_credentials(unparsed_credential_bytes).map(|credentials| {
86                    fidl_internal::Authentication {
87                        protocol: fidl_internal::Protocol::Wpa1,
88                        credentials: Some(Box::new(credentials)),
89                    }
90                })
91            }
92            Protection::Wpa1Wpa2PersonalTkipOnly
93            | Protection::Wpa1Wpa2Personal
94            | Protection::Wpa2PersonalTkipOnly
95            | Protection::Wpa2Personal => {
96                parse_wpa_credentials(unparsed_credential_bytes).map(|credentials| {
97                    fidl_internal::Authentication {
98                        protocol: fidl_internal::Protocol::Wpa2Personal,
99                        credentials: Some(Box::new(credentials)),
100                    }
101                })
102            }
103            Protection::Wpa2Wpa3Personal => {
104                // Use WPA2 for transitional networks when a PSK is supplied.
105                if let Ok(psk) = Psk::try_from(unparsed_credential_bytes.as_slice()) {
106                    Ok(fidl_internal::Authentication {
107                        protocol: fidl_internal::Protocol::Wpa2Personal,
108                        credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
109                            fidl_internal::WpaCredentials::Psk(psk.into()),
110                        ))),
111                    })
112                } else {
113                    Passphrase::try_from(unparsed_credential_bytes)
114                        .map(|passphrase| fidl_internal::Authentication {
115                            protocol: fidl_internal::Protocol::Wpa3Personal,
116                            credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
117                                fidl_internal::WpaCredentials::Passphrase(passphrase.into()),
118                            ))),
119                        })
120                        .map_err(From::from)
121                }
122            }
123            Protection::Wpa3Personal => Passphrase::try_from(unparsed_credential_bytes)
124                .map(|passphrase| fidl_internal::Authentication {
125                    protocol: fidl_internal::Protocol::Wpa3Personal,
126                    credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
127                        fidl_internal::WpaCredentials::Passphrase(passphrase.into()),
128                    ))),
129                })
130                .map_err(From::from),
131        }
132    }
133}
134
135pub async fn get_sme_proxy(
136    wlan_svc: &WlanService,
137    iface_id: u16,
138) -> Result<fidl_sme::ClientSmeProxy, Error> {
139    let (sme_proxy, sme_remote) = endpoints::create_proxy();
140    let result = wlan_svc
141        .get_client_sme(iface_id, sme_remote)
142        .await
143        .context("error sending GetClientSme request")?;
144    match result {
145        Ok(()) => Ok(sme_proxy),
146        Err(e) => Err(format_err!(
147            "Failed to get client sme proxy for interface id {} with error {}",
148            iface_id,
149            e
150        )),
151    }
152}
153
154pub async fn get_first_sme(wlan_svc: &WlanService) -> Result<fidl_sme::ClientSmeProxy, Error> {
155    let iface_id = super::get_first_iface(wlan_svc, WlanMacRole::Client)
156        .await
157        .context("failed to get iface")?;
158    get_sme_proxy(&wlan_svc, iface_id).await
159}
160
161pub async fn connect(
162    iface_sme_proxy: &fidl_sme::ClientSmeProxy,
163    target_ssid: Ssid,
164    target_pwd: Vec<u8>,
165    target_bss_desc: fidl_ieee80211::BssDescription,
166) -> Result<bool, Error> {
167    let (connection_proxy, connection_remote) = endpoints::create_proxy();
168
169    // Create a `ConnectRequest` using the given network information.
170    // Negotiate a security protocol based on the given credential and BSS metadata. Note that the
171    // resulting `Authentication` may be invalid.
172    let authentication = fidl_internal::Authentication::try_from(SecurityContext {
173        bss: BssDescription::try_from(target_bss_desc.clone())?,
174        unparsed_credential_bytes: target_pwd,
175    })?;
176    let req = fidl_sme::ConnectRequest {
177        ssid: target_ssid.clone().into(),
178        bss_description: target_bss_desc,
179        authentication,
180        deprecated_scan_type: fidl_common::ScanType::Passive,
181        multiple_bss_candidates: false, // only used for metrics, select an arbitrary value
182    };
183
184    let _result = iface_sme_proxy.connect(&req, Some(connection_remote))?;
185
186    let connection_result_code = handle_connect_transaction(connection_proxy).await?;
187
188    if !matches!(connection_result_code, fidl_ieee80211::StatusCode::Success) {
189        log::error!("Failed to connect to network: {:?}", connection_result_code);
190        return Ok(false);
191    }
192
193    let client_status_response =
194        iface_sme_proxy.status().await.context("failed to check status from sme_proxy")?;
195    Ok(match client_status_response {
196        fidl_sme::ClientStatusResponse::Connected(serving_ap_info) => {
197            if serving_ap_info.ssid != target_ssid {
198                log::error!(
199                    "Connected to wrong network: {:?}. Expected: {:?}.",
200                    serving_ap_info.ssid.as_slice(),
201                    target_ssid
202                );
203                false
204            } else {
205                true
206            }
207        }
208        fidl_sme::ClientStatusResponse::Connecting(_)
209        | fidl_sme::ClientStatusResponse::Roaming(_)
210        | fidl_sme::ClientStatusResponse::Idle(_) => {
211            log::error!(
212                "Unexpected status {:?} after {:?}",
213                client_status_response,
214                connection_result_code
215            );
216            false
217        }
218    })
219}
220
221async fn handle_connect_transaction(
222    connect_transaction: fidl_sme::ConnectTransactionProxy,
223) -> Result<fidl_ieee80211::StatusCode, Error> {
224    let mut event_stream = connect_transaction.take_event_stream();
225    let mut result_code = fidl_ieee80211::StatusCode::RefusedReasonUnspecified;
226
227    if let Some(evt) = event_stream
228        .try_next()
229        .await
230        .context("failed to receive connect result before the channel was closed")?
231    {
232        match evt {
233            fidl_sme::ConnectTransactionEvent::OnConnectResult { result } => {
234                result_code = result.code;
235            }
236            other => {
237                return Err(format_err!(
238                    "Expected ConnectTransactionEvent::OnConnectResult event, got {:?}",
239                    other
240                ));
241            }
242        }
243    }
244
245    Ok(result_code)
246}
247
248pub async fn disconnect(iface_sme_proxy: &fidl_sme::ClientSmeProxy) -> Result<(), Error> {
249    iface_sme_proxy
250        .disconnect(fidl_sme::UserDisconnectReason::WlanServiceUtilTesting)
251        .await
252        .context("failed to trigger disconnect")?;
253
254    // check the status and ensure we are not connected to or connecting to anything
255    let client_status_response =
256        iface_sme_proxy.status().await.context("failed to check status from sme_proxy")?;
257    match client_status_response {
258        fidl_sme::ClientStatusResponse::Connected(_)
259        | fidl_sme::ClientStatusResponse::Connecting(_)
260        | fidl_sme::ClientStatusResponse::Roaming(_) => {
261            Err(format_err!("Disconnect confirmation failed: {:?}", client_status_response))
262        }
263        fidl_sme::ClientStatusResponse::Idle(_) => Ok(()),
264    }
265}
266
267pub async fn disconnect_all(wlan_svc: &WlanService) -> Result<(), Error> {
268    let wlan_iface_ids =
269        super::get_iface_list(wlan_svc).await.context("Connect: failed to get wlan iface list")?;
270
271    let mut error_msg = format!("");
272    for iface_id in wlan_iface_ids {
273        let result = wlan_svc.query_iface(iface_id).await.context("querying iface info")?;
274
275        match result {
276            Ok(query_info) => {
277                if query_info.role == WlanMacRole::Client {
278                    let sme_proxy = get_sme_proxy(&wlan_svc, iface_id)
279                        .await
280                        .context("Disconnect all: failed to get iface sme proxy")?;
281                    if let Err(e) = disconnect(&sme_proxy).await {
282                        error_msg =
283                            format!("{}Error disconnecting iface {}: {}\n", error_msg, iface_id, e);
284                        log::error!("disconnect_all: disconnect err on iface {}: {}", iface_id, e);
285                    }
286                }
287            }
288            Err(zx::sys::ZX_ERR_NOT_FOUND) => {
289                error_msg = format!("{}no query response on iface {}\n", error_msg, iface_id);
290                log::error!("disconnect_all: iface query empty on iface {}", iface_id);
291            }
292            Err(e) => {
293                error_msg = format!("{}failed querying iface {}: {}\n", error_msg, iface_id, e);
294                log::error!("disconnect_all: query err on iface {}: {}", iface_id, e);
295            }
296        }
297    }
298    if error_msg.is_empty() { Ok(()) } else { Err(format_err!("{}", error_msg)) }
299}
300
301pub async fn passive_scan(
302    iface_sme_proxy: &fidl_sme::ClientSmeProxy,
303) -> Result<Vec<fidl_sme::ScanResult>, Error> {
304    let vmo = iface_sme_proxy
305        .scan(&fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![] }))
306        .await
307        .context("error sending scan request")?
308        .map_err(|scan_error_code| format_err!("Scan error: {:?}", scan_error_code))?;
309    wlan_common::scan::read_vmo(vmo)
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315    use crate::*;
316    use assert_matches::assert_matches;
317    use fidl::endpoints::RequestStream;
318
319    use fidl_fuchsia_wlan_device_service::{
320        self as wlan_service, DeviceMonitorMarker, DeviceMonitorProxy, DeviceMonitorRequest,
321        DeviceMonitorRequestStream,
322    };
323    use fidl_fuchsia_wlan_internal as fidl_internal;
324    use fidl_fuchsia_wlan_sme::{
325        ClientSmeMarker, ClientSmeRequest, ClientSmeRequestStream, Protection,
326    };
327    use fuchsia_async::TestExecutor;
328    use futures::stream::{StreamExt, StreamFuture};
329    use futures::task::Poll;
330    use ieee80211::Ssid;
331    use rand::Rng as _;
332    use std::convert::{TryFrom, TryInto};
333    use std::pin::pin;
334    use wlan_common::channel::{Cbw, Channel};
335    use wlan_common::fake_fidl_bss_description;
336    use wlan_common::scan::write_vmo;
337
338    fn generate_random_wpa2_bss_description() -> fidl_ieee80211::BssDescription {
339        let mut rng = rand::rng();
340        fidl_ieee80211::BssDescription {
341            bssid: (0..6).map(|_| rng.random::<u8>()).collect::<Vec<u8>>().try_into().unwrap(),
342            beacon_period: rng.random::<u16>(),
343            rssi_dbm: rng.random::<i8>(),
344            channel: fidl_ieee80211::ChannelNumber {
345                number: rng.random::<u8>(),
346                band: if rng.random() {
347                    fidl_ieee80211::WlanBand::TwoGhz
348                } else {
349                    fidl_ieee80211::WlanBand::FiveGhz
350                },
351            },
352            snr_db: rng.random::<i8>(),
353            ..fake_fidl_bss_description!(Wpa2)
354        }
355    }
356
357    fn extract_sme_server_from_get_client_sme_req_and_respond(
358        exec: &mut TestExecutor,
359        req_stream: &mut DeviceMonitorRequestStream,
360        result: Result<(), zx::Status>,
361    ) -> fidl_sme::ClientSmeRequestStream {
362        let req = exec.run_until_stalled(&mut req_stream.next());
363
364        let (responder, fake_sme_server) = assert_matches!(
365            req,
366            Poll::Ready(Some(Ok(DeviceMonitorRequest::GetClientSme { iface_id: _, sme_server, responder })))
367            => (responder, sme_server)
368        );
369
370        // now send the response back
371        responder
372            .send(result.map_err(|e| e.into_raw()))
373            .expect("fake sme proxy response: send failed");
374
375        // and return the stream
376        // let sme_stream = fake_sme_server.into_stream();
377        // sme_stream
378        fake_sme_server.into_stream()
379    }
380
381    fn respond_to_get_client_sme_request(
382        exec: &mut TestExecutor,
383        req_stream: &mut DeviceMonitorRequestStream,
384        result: Result<(), zx::Status>,
385    ) {
386        let req = exec.run_until_stalled(&mut req_stream.next());
387
388        let responder = assert_matches!(
389            req,
390            Poll::Ready(Some(Ok(DeviceMonitorRequest::GetClientSme { responder, .. }))) => responder
391        );
392
393        // now send the response back
394        responder
395            .send(result.map_err(|e| e.into_raw()))
396            .expect("fake sme proxy response: send failed")
397    }
398
399    fn respond_to_client_sme_disconnect_request(
400        exec: &mut TestExecutor,
401        req_stream: &mut ClientSmeRequestStream,
402    ) {
403        let req = exec.run_until_stalled(&mut req_stream.next());
404        let responder = assert_matches!(
405            req,
406            Poll::Ready(Some(Ok(ClientSmeRequest::Disconnect { responder, .. }))) => responder
407        );
408
409        // now send the response back
410        responder.send().expect("fake disconnect response: send failed")
411    }
412
413    fn respond_to_client_sme_status_request(
414        exec: &mut TestExecutor,
415        req_stream: &mut ClientSmeRequestStream,
416        status: &StatusResponse,
417    ) {
418        let req = exec.run_until_stalled(&mut req_stream.next());
419        let responder = assert_matches!(
420            req,
421            Poll::Ready(Some(Ok(ClientSmeRequest::Status { responder }))) => responder
422        );
423
424        // Send appropriate status response
425        match status {
426            StatusResponse::Idle => {
427                let response = fidl_sme::ClientStatusResponse::Idle(fidl_sme::Empty {});
428                responder.send(&response).expect("Failed to send StatusResponse.");
429            }
430            StatusResponse::Connected => {
431                let serving_ap_info =
432                    create_serving_ap_info_using_ssid(Ssid::try_from([1, 2, 3, 4]).unwrap());
433                let response = fidl_sme::ClientStatusResponse::Connected(serving_ap_info);
434                responder.send(&response).expect("Failed to send StatusResponse.");
435            }
436            StatusResponse::Connecting => {
437                let response = fidl_sme::ClientStatusResponse::Connecting(vec![1, 2, 3, 4]);
438                responder.send(&response).expect("Failed to send StatusResponse.");
439            }
440        }
441    }
442
443    fn test_get_first_sme(iface_list: &[WlanMacRole]) -> Result<(), Error> {
444        let (mut exec, proxy, mut req_stream) =
445            crate::tests::setup_fake_service::<DeviceMonitorMarker>();
446        let fut = get_first_sme(&proxy);
447        let mut fut = pin!(fut);
448
449        let ifaces = (0..iface_list.len() as u16).collect();
450
451        assert!(exec.run_until_stalled(&mut fut).is_pending());
452        crate::tests::respond_to_query_iface_list_request(&mut exec, &mut req_stream, ifaces);
453
454        for mac_role in iface_list {
455            // iface query response
456            assert!(exec.run_until_stalled(&mut fut).is_pending());
457
458            crate::tests::respond_to_query_iface_request(
459                &mut exec,
460                &mut req_stream,
461                *mac_role,
462                Some([1, 2, 3, 4, 5, 6]),
463            );
464
465            if *mac_role == WlanMacRole::Client {
466                // client sme proxy
467                assert!(exec.run_until_stalled(&mut fut).is_pending());
468                respond_to_get_client_sme_request(&mut exec, &mut req_stream, Ok(()));
469                break;
470            }
471        }
472
473        let _proxy = exec.run_singlethreaded(&mut fut)?;
474        Ok(())
475    }
476
477    fn test_disconnect_all(iface_list: &[(WlanMacRole, StatusResponse)]) -> Result<(), Error> {
478        let (mut exec, proxy, mut req_stream) =
479            crate::tests::setup_fake_service::<DeviceMonitorMarker>();
480        let fut = disconnect_all(&proxy);
481        let mut fut = pin!(fut);
482
483        let ifaces = (0..iface_list.len() as u16).collect();
484
485        assert!(exec.run_until_stalled(&mut fut).is_pending());
486        crate::tests::respond_to_query_iface_list_request(&mut exec, &mut req_stream, ifaces);
487
488        for (mac_role, status) in iface_list {
489            // iface query response
490            assert!(exec.run_until_stalled(&mut fut).is_pending());
491            crate::tests::respond_to_query_iface_request(
492                &mut exec,
493                &mut req_stream,
494                *mac_role,
495                Some([1, 2, 3, 4, 5, 6]),
496            );
497
498            if *mac_role == WlanMacRole::Client {
499                // Get the Client SME server (to send the responses for the following 2 SME requests)
500                assert!(exec.run_until_stalled(&mut fut).is_pending());
501                let mut fake_sme_server_stream =
502                    extract_sme_server_from_get_client_sme_req_and_respond(
503                        &mut exec,
504                        &mut req_stream,
505                        Ok(()),
506                    );
507
508                // Disconnect
509                assert!(exec.run_until_stalled(&mut fut).is_pending());
510                respond_to_client_sme_disconnect_request(&mut exec, &mut fake_sme_server_stream);
511
512                assert!(exec.run_until_stalled(&mut fut).is_pending());
513
514                // Send appropriate status response
515                respond_to_client_sme_status_request(
516                    &mut exec,
517                    &mut fake_sme_server_stream,
518                    status,
519                );
520            }
521        }
522        exec.run_singlethreaded(&mut fut)
523    }
524
525    // iface list contains an AP and a client. Test should pass
526    #[test]
527    fn check_get_client_sme_success() {
528        let iface_list: Vec<WlanMacRole> = vec![WlanMacRole::Ap, WlanMacRole::Client];
529        test_get_first_sme(&iface_list).expect("expect success but failed");
530    }
531
532    // iface list is empty. Test should fail
533    #[test]
534    fn check_get_client_sme_no_devices() {
535        let iface_list: Vec<WlanMacRole> = Vec::new();
536        test_get_first_sme(&iface_list).expect_err("expect fail but succeeded");
537    }
538
539    // iface list does not contain a client. Test should fail
540    #[test]
541    fn check_get_client_sme_no_clients() {
542        let iface_list: Vec<WlanMacRole> = vec![WlanMacRole::Ap, WlanMacRole::Ap];
543        test_get_first_sme(&iface_list).expect_err("expect fail but succeeded");
544    }
545
546    // test disconnect_all with a Client and an AP. Test should pass
547    // as AP IF will be ignored and Client IF delete should succeed.
548    #[test]
549    fn check_disconnect_all_client_and_ap_success() {
550        let iface_list: Vec<(WlanMacRole, StatusResponse)> = vec![
551            (WlanMacRole::Ap, StatusResponse::Idle),
552            (WlanMacRole::Client, StatusResponse::Idle),
553        ];
554        test_disconnect_all(&iface_list).expect("Expect success but failed")
555    }
556
557    // test disconnect_all with 2 Clients. Test should pass as both the
558    // IFs are clients and both deletes should succeed.
559    #[test]
560    fn check_disconnect_all_all_clients_success() {
561        let iface_list: Vec<(WlanMacRole, StatusResponse)> = vec![
562            (WlanMacRole::Client, StatusResponse::Idle),
563            (WlanMacRole::Client, StatusResponse::Idle),
564        ];
565        test_disconnect_all(&iface_list).expect("Expect success but failed");
566    }
567
568    // test disconnect_all with 2 Clients, one disconnect failure
569    #[test]
570    fn check_disconnect_all_all_clients_fail() {
571        let iface_list: Vec<(WlanMacRole, StatusResponse)> = vec![
572            (WlanMacRole::Ap, StatusResponse::Connected),
573            (WlanMacRole::Client, StatusResponse::Connected),
574        ];
575        test_disconnect_all(&iface_list).expect_err("Expect fail but succeeded");
576    }
577
578    // test disconnect_all with no Clients
579    #[test]
580    fn check_disconnect_all_no_clients_success() {
581        let iface_list: Vec<(WlanMacRole, StatusResponse)> =
582            vec![(WlanMacRole::Ap, StatusResponse::Idle), (WlanMacRole::Ap, StatusResponse::Idle)];
583        test_disconnect_all(&iface_list).expect("Expect success but failed");
584    }
585
586    #[test]
587    fn list_ifaces_returns_iface_id_vector() {
588        let mut exec = TestExecutor::new();
589        let (wlan_monitor, server) = create_wlan_monitor_util();
590        let mut next_device_monitor_req = server.into_future();
591
592        let ifaces: Vec<u16> = vec![0, 1, 35, 36];
593
594        let fut = get_iface_list(&wlan_monitor);
595        let mut fut = pin!(fut);
596        assert!(exec.run_until_stalled(&mut fut).is_pending());
597
598        send_iface_list_response(&mut exec, &mut next_device_monitor_req, ifaces.clone());
599
600        let complete = exec.run_until_stalled(&mut fut);
601
602        let list_response = match complete {
603            Poll::Ready(result) => result,
604            _ => panic!("Expected an iface list response"),
605        };
606
607        let response = match list_response {
608            Ok(response) => response,
609            Err(_) => panic!("Expected a valid list response"),
610        };
611
612        // now verify the response
613        assert_eq!(response, ifaces)
614    }
615
616    #[test]
617    fn list_ifaces_properly_handles_zero_ifaces() {
618        let mut exec = TestExecutor::new();
619        let (wlan_monitor, server) = create_wlan_monitor_util();
620        let mut next_device_monitor_req = server.into_future();
621
622        // create the data to use in the response
623        let iface_id_list: Vec<u16> = vec![];
624        let iface_list_vec = vec![];
625
626        let fut = get_iface_list(&wlan_monitor);
627        let mut fut = pin!(fut);
628        assert!(exec.run_until_stalled(&mut fut).is_pending());
629
630        send_iface_list_response(&mut exec, &mut next_device_monitor_req, iface_list_vec);
631
632        let complete = exec.run_until_stalled(&mut fut);
633
634        let list_response = match complete {
635            Poll::Ready(result) => result,
636            _ => panic!("Expected an iface list response"),
637        };
638
639        let response = match list_response {
640            Ok(response) => response,
641            Err(_) => panic!("Expected a valid list response"),
642        };
643
644        // now verify the response
645        assert_eq!(response, iface_id_list)
646    }
647
648    #[test]
649    fn list_phys_returns_iface_id_vector() {
650        let mut exec = TestExecutor::new();
651        let (monitor_service, server) = create_wlan_monitor_util();
652        let mut next_device_service_req = server.into_future();
653
654        // create the data to use in the response
655        let phy_id_list: Vec<u16> = vec![0, 1, 35, 36];
656        let mut phy_list_vec = vec![];
657        for id in &phy_id_list {
658            phy_list_vec.push(*id);
659        }
660
661        let fut = get_phy_list(&monitor_service);
662        let mut fut = pin!(fut);
663        assert!(exec.run_until_stalled(&mut fut).is_pending());
664
665        send_phy_list_response(&mut exec, &mut next_device_service_req, phy_list_vec);
666
667        let complete = exec.run_until_stalled(&mut fut);
668
669        let list_response = match complete {
670            Poll::Ready(result) => result,
671            _ => panic!("Expected an phy list response"),
672        };
673
674        let response = match list_response {
675            Ok(response) => response,
676            Err(_) => panic!("Expected a valid list response"),
677        };
678
679        // now verify the response
680        assert_eq!(response, phy_id_list)
681    }
682
683    #[test]
684    fn list_phys_properly_handles_zero_phys() {
685        let mut exec = TestExecutor::new();
686        let (monitor_service, server) = create_wlan_monitor_util();
687        let mut next_device_service_req = server.into_future();
688
689        // create the data to use in the response
690        let phy_id_list: Vec<u16> = vec![];
691        let phy_list_vec = vec![];
692
693        let fut = get_phy_list(&monitor_service);
694        let mut fut = pin!(fut);
695        assert!(exec.run_until_stalled(&mut fut).is_pending());
696
697        send_phy_list_response(&mut exec, &mut next_device_service_req, phy_list_vec);
698
699        let complete = exec.run_until_stalled(&mut fut);
700
701        let list_response = match complete {
702            Poll::Ready(result) => result,
703            _ => panic!("Expected an phy list response"),
704        };
705
706        let response = match list_response {
707            Ok(response) => response,
708            Err(_) => panic!("Expected a valid list response"),
709        };
710
711        // now verify the response
712        assert_eq!(response, phy_id_list)
713    }
714
715    fn poll_device_monitor_req(
716        exec: &mut TestExecutor,
717        next_device_monitor_req: &mut StreamFuture<DeviceMonitorRequestStream>,
718    ) -> Poll<DeviceMonitorRequest> {
719        exec.run_until_stalled(next_device_monitor_req).map(|(req, stream)| {
720            *next_device_monitor_req = stream.into_future();
721            req.expect("did not expect the DeviceMonitorRequestStream to end")
722                .expect("error polling device service request stream")
723        })
724    }
725
726    fn send_iface_list_response(
727        exec: &mut TestExecutor,
728        server: &mut StreamFuture<wlan_service::DeviceMonitorRequestStream>,
729        ifaces: Vec<u16>,
730    ) {
731        let responder = match poll_device_monitor_req(exec, server) {
732            Poll::Ready(DeviceMonitorRequest::ListIfaces { responder }) => responder,
733            Poll::Pending => panic!("expected a request to be available"),
734            _ => panic!("expected a ListIfaces request"),
735        };
736
737        // now send the response back
738        let _result = responder.send(&ifaces[..]);
739    }
740
741    fn send_phy_list_response(
742        exec: &mut TestExecutor,
743        server: &mut StreamFuture<wlan_service::DeviceMonitorRequestStream>,
744        phy_list_vec: Vec<u16>,
745    ) {
746        let responder = match poll_device_monitor_req(exec, server) {
747            Poll::Ready(DeviceMonitorRequest::ListPhys { responder }) => responder,
748            Poll::Pending => panic!("expected a request to be available"),
749            _ => panic!("expected a ListPhys request"),
750        };
751
752        // now send the response back
753        let _result = responder.send(&phy_list_vec);
754    }
755
756    #[test]
757    fn get_client_sme_valid_iface() {
758        let mut exec = TestExecutor::new();
759        let (wlan_monitor, server) = create_wlan_monitor_util();
760        let mut next_device_monitor_req = server.into_future();
761
762        let fut = get_sme_proxy(&wlan_monitor, 1);
763        let mut fut = pin!(fut);
764        assert!(exec.run_until_stalled(&mut fut).is_pending());
765
766        // pass in that we expect this to succeed
767        send_sme_proxy_response(&mut exec, &mut next_device_monitor_req, Ok(()));
768
769        let () = match exec.run_until_stalled(&mut fut) {
770            Poll::Ready(Ok(_)) => (),
771            _ => panic!("Expected a status response"),
772        };
773    }
774
775    fn send_sme_proxy_response(
776        exec: &mut TestExecutor,
777        server: &mut StreamFuture<wlan_service::DeviceMonitorRequestStream>,
778        result: Result<(), zx::Status>,
779    ) {
780        let responder = match poll_device_monitor_req(exec, server) {
781            Poll::Ready(DeviceMonitorRequest::GetClientSme { responder, .. }) => responder,
782            Poll::Pending => panic!("expected a request to be available"),
783            _ => panic!("expected a GetClientSme request"),
784        };
785
786        // now send the response back
787        let _result = responder.send(result.map_err(|e| e.into_raw()));
788    }
789
790    #[test]
791    fn get_client_sme_invalid_iface() {
792        let mut exec = TestExecutor::new();
793        let (wlan_monitor, server) = create_wlan_monitor_util();
794        let mut next_device_monitor_req = server.into_future();
795
796        let fut = get_sme_proxy(&wlan_monitor, 1);
797        let mut fut = pin!(fut);
798        assert!(exec.run_until_stalled(&mut fut).is_pending());
799
800        // pass in that we expect this to fail with zx::Status::NOT_FOUND
801        send_sme_proxy_response(
802            &mut exec,
803            &mut next_device_monitor_req,
804            Err(zx::Status::NOT_FOUND),
805        );
806
807        let complete = exec.run_until_stalled(&mut fut);
808
809        match complete {
810            Poll::Ready(Err(_)) => (),
811            _ => panic!("Expected a status response"),
812        };
813    }
814
815    #[test]
816    fn connect_success_returns_true() {
817        let connect_result =
818            test_wpa2_connect("TestAp", "password", "TestAp", fidl_ieee80211::StatusCode::Success);
819        assert!(connect_result);
820    }
821
822    #[test]
823    fn connect_failed_returns_false() {
824        let connect_result = test_wpa2_connect(
825            "TestAp",
826            "password",
827            "",
828            fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
829        );
830        assert!(!connect_result);
831    }
832
833    #[test]
834    fn connect_different_ssid_returns_false() {
835        let connect_result = test_wpa2_connect(
836            "TestAp1",
837            "password",
838            "TestAp2",
839            fidl_ieee80211::StatusCode::Success,
840        );
841        assert!(!connect_result);
842    }
843
844    fn test_wpa2_connect(
845        target_ssid: &str,
846        password: &str,
847        connected_to_ssid: &str,
848        result_code: fidl_ieee80211::StatusCode,
849    ) -> bool {
850        let target_ssid = Ssid::try_from(target_ssid).unwrap();
851        let connected_to_ssid = Ssid::try_from(connected_to_ssid).unwrap();
852
853        let mut exec = TestExecutor::new();
854        let (client_sme, server) = create_client_sme_proxy();
855        let mut next_client_sme_req = server.into_future();
856
857        let target_password = password.as_bytes();
858        let target_bss_desc = generate_random_wpa2_bss_description();
859
860        let fut = connect(
861            &client_sme,
862            target_ssid.clone(),
863            target_password.to_vec(),
864            target_bss_desc.clone(),
865        );
866        let mut fut = pin!(fut);
867        assert!(exec.run_until_stalled(&mut fut).is_pending());
868
869        // have the request, need to send a response
870        send_connect_request_response(
871            &mut exec,
872            &mut next_client_sme_req,
873            &target_ssid,
874            fidl_internal::Authentication::try_from(SecurityContext {
875                bss: BssDescription::try_from(target_bss_desc.clone()).unwrap(),
876                unparsed_credential_bytes: target_password.to_vec(),
877            })
878            .unwrap(),
879            result_code,
880        );
881
882        // if connection is successful, status is requested to extract ssid
883        if result_code == fidl_ieee80211::StatusCode::Success {
884            assert!(exec.run_until_stalled(&mut fut).is_pending());
885            send_status_response(
886                &mut exec,
887                &mut next_client_sme_req,
888                Some(connected_to_ssid),
889                None,
890            );
891        }
892
893        let complete = exec.run_until_stalled(&mut fut);
894
895        let connection_result = match complete {
896            Poll::Ready(result) => result,
897            _ => panic!("Expected a connect response"),
898        };
899
900        let returned_bool = match connection_result {
901            Ok(response) => response,
902            _ => panic!("Expected a valid connection result"),
903        };
904
905        returned_bool
906    }
907
908    #[test]
909    fn connect_properly_passes_network_info_with_password() {
910        let mut exec = TestExecutor::new();
911        let (client_sme, server) = create_client_sme_proxy();
912        let mut next_client_sme_req = server.into_future();
913
914        let target_ssid = Ssid::try_from("TestAp").unwrap();
915        let target_password = "password".as_bytes();
916        let target_bss_desc = generate_random_wpa2_bss_description();
917
918        let fut = connect(
919            &client_sme,
920            target_ssid.clone(),
921            target_password.to_vec(),
922            target_bss_desc.clone(),
923        );
924        let mut fut = pin!(fut);
925        assert!(exec.run_until_stalled(&mut fut).is_pending());
926
927        // verify the connect request info
928        verify_connect_request_info(
929            &mut exec,
930            &mut next_client_sme_req,
931            &target_ssid,
932            fidl_internal::Authentication::try_from(SecurityContext {
933                bss: BssDescription::try_from(target_bss_desc.clone()).unwrap(),
934                unparsed_credential_bytes: target_password.to_vec(),
935            })
936            .unwrap(),
937            target_bss_desc,
938        );
939    }
940
941    #[test]
942    fn connect_properly_passes_network_info_open() {
943        let mut exec = TestExecutor::new();
944        let (client_sme, server) = create_client_sme_proxy();
945        let mut next_client_sme_req = server.into_future();
946
947        let target_ssid = Ssid::try_from("TestAp").unwrap();
948        let target_password = "".as_bytes();
949        let target_bss_desc = fake_fidl_bss_description!(Open);
950
951        let fut = connect(
952            &client_sme,
953            target_ssid.clone(),
954            target_password.to_vec(),
955            target_bss_desc.clone(),
956        );
957        let mut fut = pin!(fut);
958        assert!(exec.run_until_stalled(&mut fut).is_pending());
959
960        // verify the connect request info
961        verify_connect_request_info(
962            &mut exec,
963            &mut next_client_sme_req,
964            &target_ssid,
965            fidl_internal::Authentication::try_from(SecurityContext {
966                bss: BssDescription::try_from(target_bss_desc.clone()).unwrap(),
967                unparsed_credential_bytes: vec![],
968            })
969            .unwrap(),
970            target_bss_desc,
971        );
972    }
973
974    fn verify_connect_request_info(
975        exec: &mut TestExecutor,
976        server: &mut StreamFuture<ClientSmeRequestStream>,
977        expected_ssid: &Ssid,
978        expected_authentication: fidl_internal::Authentication,
979        expected_bss_desc: fidl_ieee80211::BssDescription,
980    ) {
981        match poll_client_sme_request(exec, server) {
982            Poll::Ready(ClientSmeRequest::Connect { req, .. }) => {
983                assert_eq!(expected_ssid, &req.ssid);
984                assert_eq!(req.authentication, expected_authentication);
985                assert_eq!(req.bss_description, expected_bss_desc);
986            }
987            _ => panic!("expected a Connect request"),
988        }
989    }
990
991    fn send_connect_request_response(
992        exec: &mut TestExecutor,
993        server: &mut StreamFuture<ClientSmeRequestStream>,
994        expected_ssid: &Ssid,
995        expected_authentication: fidl_internal::Authentication,
996        connect_result: fidl_ieee80211::StatusCode,
997    ) {
998        let responder = match poll_client_sme_request(exec, server) {
999            Poll::Ready(ClientSmeRequest::Connect { req, txn, .. }) => {
1000                assert_eq!(expected_ssid, &req.ssid[..]);
1001                assert_eq!(req.authentication, expected_authentication);
1002                txn.expect("expected a Connect transaction channel")
1003            }
1004            Poll::Pending => panic!("expected a request to be available"),
1005            _ => panic!("expected a Connect request"),
1006        };
1007        let connect_transaction = responder.into_stream().control_handle();
1008        connect_transaction
1009            .send_on_connect_result(&fidl_sme::ConnectResult {
1010                code: connect_result,
1011                is_credential_rejected: false,
1012                is_reconnect: false,
1013            })
1014            .expect("failed to send OnConnectResult to ConnectTransaction");
1015    }
1016
1017    fn poll_client_sme_request(
1018        exec: &mut TestExecutor,
1019        next_client_sme_req: &mut StreamFuture<ClientSmeRequestStream>,
1020    ) -> Poll<ClientSmeRequest> {
1021        exec.run_until_stalled(next_client_sme_req).map(|(req, stream)| {
1022            *next_client_sme_req = stream.into_future();
1023            req.expect("did not expect the ClientSmeRequestStream to end")
1024                .expect("error polling client sme request stream")
1025        })
1026    }
1027
1028    fn create_client_sme_proxy() -> (fidl_sme::ClientSmeProxy, ClientSmeRequestStream) {
1029        let (proxy, server) = endpoints::create_proxy::<ClientSmeMarker>();
1030        let server = server.into_stream();
1031        (proxy, server)
1032    }
1033
1034    fn create_wlan_monitor_util() -> (DeviceMonitorProxy, DeviceMonitorRequestStream) {
1035        let (proxy, server) = endpoints::create_proxy::<DeviceMonitorMarker>();
1036        let server = server.into_stream();
1037        (proxy, server)
1038    }
1039
1040    enum StatusResponse {
1041        Idle,
1042        Connected,
1043        Connecting,
1044    }
1045
1046    #[test]
1047    fn disconnect_with_empty_status_response() {
1048        if let Poll::Ready(result) = test_disconnect(StatusResponse::Idle) {
1049            return assert!(result.is_ok());
1050        }
1051        panic!("disconnect did not return a Poll::Ready")
1052    }
1053
1054    #[test]
1055    fn disconnect_fail_because_connected() {
1056        if let Poll::Ready(result) = test_disconnect(StatusResponse::Connected) {
1057            return assert!(result.is_err());
1058        }
1059        panic!("disconnect did not return a Poll::Ready")
1060    }
1061
1062    #[test]
1063    fn disconnect_fail_because_connecting() {
1064        if let Poll::Ready(result) = test_disconnect(StatusResponse::Connecting) {
1065            return assert!(result.is_err());
1066        }
1067        panic!("disconnect did not return a Poll::Ready")
1068    }
1069
1070    fn test_disconnect(status: StatusResponse) -> Poll<Result<(), Error>> {
1071        let mut exec = TestExecutor::new();
1072        let (client_sme, server) = create_client_sme_proxy();
1073        let mut client_sme_req = server.into_future();
1074
1075        let fut = disconnect(&client_sme);
1076        let mut fut = pin!(fut);
1077        assert!(exec.run_until_stalled(&mut fut).is_pending());
1078
1079        send_disconnect_request_response(&mut exec, &mut client_sme_req);
1080
1081        assert!(exec.run_until_stalled(&mut fut).is_pending());
1082
1083        match status {
1084            StatusResponse::Idle => {
1085                send_status_response(&mut exec, &mut client_sme_req, None, None)
1086            }
1087            StatusResponse::Connected => send_status_response(
1088                &mut exec,
1089                &mut client_sme_req,
1090                Some(Ssid::try_from([1, 2, 3, 4]).unwrap()),
1091                None,
1092            ),
1093            StatusResponse::Connecting => send_status_response(
1094                &mut exec,
1095                &mut client_sme_req,
1096                None,
1097                Some(Ssid::try_from([1, 2, 3, 4]).unwrap()),
1098            ),
1099        }
1100
1101        exec.run_until_stalled(&mut fut)
1102    }
1103
1104    fn send_disconnect_request_response(
1105        exec: &mut TestExecutor,
1106        server: &mut StreamFuture<ClientSmeRequestStream>,
1107    ) {
1108        let rsp = match poll_client_sme_request(exec, server) {
1109            Poll::Ready(ClientSmeRequest::Disconnect { responder, .. }) => responder,
1110            Poll::Pending => panic!("Expected a DisconnectRequest"),
1111            _ => panic!("Expected a DisconnectRequest"),
1112        };
1113        rsp.send().expect("Failed to send DisconnectResponse.");
1114    }
1115
1116    fn create_serving_ap_info_using_ssid(ssid: Ssid) -> fidl_sme::ServingApInfo {
1117        fidl_sme::ServingApInfo {
1118            bssid: [0, 1, 2, 3, 4, 5],
1119            ssid: ssid.into(),
1120            rssi_dbm: -30,
1121            snr_db: 10,
1122            primary: fidl_ieee80211::ChannelNumber {
1123                number: 1,
1124                band: fidl_ieee80211::WlanBand::TwoGhz,
1125            },
1126            protection: Protection::Wpa2Personal,
1127            bandwidth: fidl_ieee80211::ChannelBandwidth::Cbw20,
1128            vht_secondary_80_channel: fidl_ieee80211::ChannelNumber {
1129                number: 0,
1130                band: fidl_ieee80211::WlanBand::TwoGhz,
1131            },
1132        }
1133    }
1134
1135    fn send_status_response(
1136        exec: &mut TestExecutor,
1137        server: &mut StreamFuture<ClientSmeRequestStream>,
1138        connected_to_ssid: Option<Ssid>,
1139        connecting_to_ssid: Option<Ssid>,
1140    ) {
1141        let rsp = match poll_client_sme_request(exec, server) {
1142            Poll::Ready(ClientSmeRequest::Status { responder }) => responder,
1143            Poll::Pending => panic!("Expected a StatusRequest"),
1144            _ => panic!("Expected a StatusRequest"),
1145        };
1146
1147        let response = match (connected_to_ssid, connecting_to_ssid) {
1148            (Some(_), Some(_)) => panic!("SME cannot simultaneously be Connected and Connecting."),
1149            (Some(ssid), None) => {
1150                let serving_ap_info = create_serving_ap_info_using_ssid(ssid);
1151                fidl_sme::ClientStatusResponse::Connected(serving_ap_info)
1152            }
1153            (None, Some(ssid)) => fidl_sme::ClientStatusResponse::Connecting(ssid.to_vec()),
1154            (None, None) => fidl_sme::ClientStatusResponse::Idle(fidl_sme::Empty {}),
1155        };
1156
1157        rsp.send(&response).expect("Failed to send StatusResponse.");
1158    }
1159
1160    #[test]
1161    fn scan_success_returns_empty_results() {
1162        assert_eq!(test_scan(&[]), &[]);
1163    }
1164
1165    #[test]
1166    fn scan_success_returns_results() {
1167        let scan_results = &[
1168            create_scan_result(
1169                [0, 1, 2, 3, 4, 5],
1170                Ssid::try_from("foo").unwrap(),
1171                -30,
1172                20,
1173                Channel::new(1, Cbw::Cbw20, fidl_ieee80211::WlanBand::TwoGhz),
1174                Protection::Wpa2Personal,
1175                Some(fidl_sme::Compatibility::Compatible(fidl_sme::Compatible {
1176                    mutual_security_protocols: vec![fidl_internal::Protocol::Wpa2Personal],
1177                })),
1178            ),
1179            create_scan_result(
1180                [1, 2, 3, 4, 5, 6],
1181                Ssid::try_from("hello").unwrap(),
1182                -60,
1183                10,
1184                Channel::new(2, Cbw::Cbw20, fidl_ieee80211::WlanBand::TwoGhz),
1185                Protection::Wpa2Personal,
1186                None,
1187            ),
1188        ];
1189
1190        assert_eq!(test_scan(scan_results), scan_results);
1191    }
1192
1193    #[test]
1194    fn scan_error_correctly_handled() {
1195        // need to expect an error
1196        assert!(test_scan_error().is_err())
1197    }
1198
1199    fn test_scan(scan_results: &[fidl_sme::ScanResult]) -> Vec<fidl_sme::ScanResult> {
1200        let mut exec = TestExecutor::new();
1201        let (client_sme, server) = create_client_sme_proxy();
1202        let mut client_sme_req = server.into_future();
1203
1204        let fut = passive_scan(&client_sme);
1205        let mut fut = pin!(fut);
1206        assert!(exec.run_until_stalled(&mut fut).is_pending());
1207
1208        send_scan_result_response(&mut exec, &mut client_sme_req, scan_results);
1209
1210        let complete = exec.run_until_stalled(&mut fut);
1211        let request_result = match complete {
1212            Poll::Ready(result) => result,
1213            _ => panic!("Expected a scan request result"),
1214        };
1215        let returned_scan_results = request_result.expect("failed to get scan results");
1216
1217        returned_scan_results
1218    }
1219
1220    fn send_scan_result_response(
1221        exec: &mut TestExecutor,
1222        server: &mut StreamFuture<fidl_sme::ClientSmeRequestStream>,
1223        scan_results: &[fidl_sme::ScanResult],
1224    ) {
1225        match poll_client_sme_request(exec, server) {
1226            Poll::Ready(fidl_sme::ClientSmeRequest::Scan { responder, .. }) => {
1227                let vmo = write_vmo(scan_results.to_vec()).expect("failed to write VMO");
1228                responder.send(Ok(vmo)).expect("failed to send scan results")
1229            }
1230            Poll::Pending => panic!("expected a request to be available"),
1231            _ => panic!("expected a scan request"),
1232        }
1233    }
1234
1235    fn test_scan_error() -> Result<(), Error> {
1236        let mut exec = TestExecutor::new();
1237        let (client_sme, server) = create_client_sme_proxy();
1238        let mut client_sme_req = server.into_future();
1239
1240        let fut = passive_scan(&client_sme);
1241        let mut fut = pin!(fut);
1242        assert!(exec.run_until_stalled(&mut fut).is_pending());
1243
1244        send_scan_error_response(&mut exec, &mut client_sme_req);
1245        let _ = exec.run_until_stalled(&mut fut)?;
1246        Ok(())
1247    }
1248
1249    fn send_scan_error_response(
1250        exec: &mut TestExecutor,
1251        server: &mut StreamFuture<fidl_sme::ClientSmeRequestStream>,
1252    ) {
1253        match poll_client_sme_request(exec, server) {
1254            Poll::Ready(fidl_sme::ClientSmeRequest::Scan { responder, .. }) => responder
1255                .send(Err(fidl_sme::ScanErrorCode::InternalError))
1256                .expect("failed to send ScanError"),
1257            Poll::Pending => panic!("expected a request to be available"),
1258            _ => panic!("expected a scan request"),
1259        };
1260    }
1261
1262    fn create_scan_result(
1263        bssid: [u8; 6],
1264        ssid: Ssid,
1265        rssi_dbm: i8,
1266        snr_db: i8,
1267        channel: Channel,
1268        protection: Protection,
1269        compatibility: Option<fidl_sme::Compatibility>,
1270    ) -> fidl_sme::ScanResult {
1271        fidl_sme::ScanResult {
1272            compatibility: compatibility.unwrap_or(fidl_sme::Compatibility::Compatible(
1273                fidl_sme::Compatible { mutual_security_protocols: vec![] },
1274            )),
1275            timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
1276            bss_description: fake_fidl_bss_description!(
1277                protection => protection,
1278                bssid: bssid,
1279                ssid: ssid,
1280                rssi_dbm: rssi_dbm,
1281                snr_db: snr_db,
1282                channel: channel,
1283            ),
1284        }
1285    }
1286
1287    fn send_destroy_iface_response(
1288        exec: &mut TestExecutor,
1289        server: &mut StreamFuture<wlan_service::DeviceMonitorRequestStream>,
1290        status: zx::Status,
1291    ) {
1292        let responder = match poll_device_monitor_req(exec, server) {
1293            Poll::Ready(DeviceMonitorRequest::DestroyIface { responder, .. }) => responder,
1294            Poll::Pending => panic!("expected a request to be available"),
1295            _ => panic!("expected a destroy iface request"),
1296        };
1297
1298        // now send the response back
1299        let _result = responder.send(status.into_raw());
1300    }
1301
1302    #[test]
1303    fn test_destroy_single_iface_ok() {
1304        let mut exec = TestExecutor::new();
1305        let (monitor_service, server) = create_wlan_monitor_util();
1306        let mut next_device_service_req = server.into_future();
1307
1308        let fut = destroy_iface(&monitor_service, 0);
1309        let mut fut = pin!(fut);
1310        assert!(exec.run_until_stalled(&mut fut).is_pending());
1311
1312        send_destroy_iface_response(&mut exec, &mut next_device_service_req, zx::Status::OK);
1313
1314        match exec.run_until_stalled(&mut fut) {
1315            Poll::Ready(Ok(_)) => (),
1316            _ => panic!("Expected a status response"),
1317        };
1318    }
1319}