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 fidl::endpoints::RequestStream;
317    use fidl_fuchsia_wlan_common as fidl_common;
318    use fidl_fuchsia_wlan_device_service::{
319        self as wlan_service, DeviceMonitorMarker, DeviceMonitorProxy, DeviceMonitorRequest,
320        DeviceMonitorRequestStream,
321    };
322    use fidl_fuchsia_wlan_internal as fidl_internal;
323    use fidl_fuchsia_wlan_sme::{
324        ClientSmeMarker, ClientSmeRequest, ClientSmeRequestStream, Protection,
325    };
326    use fuchsia_async::TestExecutor;
327    use futures::stream::{StreamExt, StreamFuture};
328    use futures::task::Poll;
329    use ieee80211::Ssid;
330    use rand::Rng as _;
331    use std::convert::{TryFrom, TryInto};
332    use std::pin::pin;
333    use wlan_common::channel::{Cbw, Channel};
334    use wlan_common::scan::write_vmo;
335    use wlan_common::{assert_variant, fake_fidl_bss_description};
336
337    fn generate_random_wpa2_bss_description() -> fidl_fuchsia_wlan_internal::BssDescription {
338        let mut rng = rand::rng();
339        fidl_fuchsia_wlan_internal::BssDescription {
340            bssid: (0..6).map(|_| rng.r#gen::<u8>()).collect::<Vec<u8>>().try_into().unwrap(),
341            beacon_period: rng.r#gen::<u16>(),
342            rssi_dbm: rng.r#gen::<i8>(),
343            channel: fidl_ieee80211::WlanChannel {
344                primary: rng.r#gen::<u8>(),
345                cbw: match rng.r#gen_range(0..5) {
346                    0 => fidl_ieee80211::ChannelBandwidth::Cbw20,
347                    1 => fidl_ieee80211::ChannelBandwidth::Cbw40,
348                    2 => fidl_ieee80211::ChannelBandwidth::Cbw40Below,
349                    3 => fidl_ieee80211::ChannelBandwidth::Cbw80,
350                    4 => fidl_ieee80211::ChannelBandwidth::Cbw160,
351                    5 => fidl_ieee80211::ChannelBandwidth::Cbw80P80,
352                    _ => panic!(),
353                },
354                secondary80: rng.r#gen::<u8>(),
355            },
356            snr_db: rng.r#gen::<i8>(),
357            ..fake_fidl_bss_description!(Wpa2)
358        }
359    }
360
361    fn extract_sme_server_from_get_client_sme_req_and_respond(
362        exec: &mut TestExecutor,
363        req_stream: &mut DeviceMonitorRequestStream,
364        result: Result<(), zx::Status>,
365    ) -> fidl_sme::ClientSmeRequestStream {
366        let req = exec.run_until_stalled(&mut req_stream.next());
367
368        let (responder, fake_sme_server) = assert_variant !(
369            req,
370            Poll::Ready(Some(Ok(DeviceMonitorRequest::GetClientSme{ iface_id:_, sme_server, responder})))
371            => (responder, sme_server));
372
373        // now send the response back
374        responder
375            .send(result.map_err(|e| e.into_raw()))
376            .expect("fake sme proxy response: send failed");
377
378        // and return the stream
379        // let sme_stream = fake_sme_server.into_stream();
380        // sme_stream
381        fake_sme_server.into_stream()
382    }
383
384    fn respond_to_get_client_sme_request(
385        exec: &mut TestExecutor,
386        req_stream: &mut DeviceMonitorRequestStream,
387        result: Result<(), zx::Status>,
388    ) {
389        let req = exec.run_until_stalled(&mut req_stream.next());
390
391        let responder = assert_variant !(
392            req,
393            Poll::Ready(Some(Ok(DeviceMonitorRequest::GetClientSme{ responder, ..})))
394            => responder);
395
396        // now send the response back
397        responder
398            .send(result.map_err(|e| e.into_raw()))
399            .expect("fake sme proxy response: send failed")
400    }
401
402    fn respond_to_client_sme_disconnect_request(
403        exec: &mut TestExecutor,
404        req_stream: &mut ClientSmeRequestStream,
405    ) {
406        let req = exec.run_until_stalled(&mut req_stream.next());
407        let responder = assert_variant !(
408            req,
409            Poll::Ready(Some(Ok(ClientSmeRequest::Disconnect{ responder, .. })))
410            => responder);
411
412        // now send the response back
413        responder.send().expect("fake disconnect response: send failed")
414    }
415
416    fn respond_to_client_sme_status_request(
417        exec: &mut TestExecutor,
418        req_stream: &mut ClientSmeRequestStream,
419        status: &StatusResponse,
420    ) {
421        let req = exec.run_until_stalled(&mut req_stream.next());
422        let responder = assert_variant !(
423            req,
424            Poll::Ready(Some(Ok(ClientSmeRequest::Status{ responder})))
425            => responder);
426
427        // Send appropriate status response
428        match status {
429            StatusResponse::Idle => {
430                let response = fidl_sme::ClientStatusResponse::Idle(fidl_sme::Empty {});
431                responder.send(&response).expect("Failed to send StatusResponse.");
432            }
433            StatusResponse::Connected => {
434                let serving_ap_info =
435                    create_serving_ap_info_using_ssid(Ssid::try_from([1, 2, 3, 4]).unwrap());
436                let response = fidl_sme::ClientStatusResponse::Connected(serving_ap_info);
437                responder.send(&response).expect("Failed to send StatusResponse.");
438            }
439            StatusResponse::Connecting => {
440                let response = fidl_sme::ClientStatusResponse::Connecting(vec![1, 2, 3, 4]);
441                responder.send(&response).expect("Failed to send StatusResponse.");
442            }
443        }
444    }
445
446    fn test_get_first_sme(iface_list: &[WlanMacRole]) -> Result<(), Error> {
447        let (mut exec, proxy, mut req_stream) =
448            crate::tests::setup_fake_service::<DeviceMonitorMarker>();
449        let fut = get_first_sme(&proxy);
450        let mut fut = pin!(fut);
451
452        let ifaces = (0..iface_list.len() as u16).collect();
453
454        assert!(exec.run_until_stalled(&mut fut).is_pending());
455        crate::tests::respond_to_query_iface_list_request(&mut exec, &mut req_stream, ifaces);
456
457        for mac_role in iface_list {
458            // iface query response
459            assert!(exec.run_until_stalled(&mut fut).is_pending());
460
461            crate::tests::respond_to_query_iface_request(
462                &mut exec,
463                &mut req_stream,
464                *mac_role,
465                Some([1, 2, 3, 4, 5, 6]),
466            );
467
468            if *mac_role == WlanMacRole::Client {
469                // client sme proxy
470                assert!(exec.run_until_stalled(&mut fut).is_pending());
471                respond_to_get_client_sme_request(&mut exec, &mut req_stream, Ok(()));
472                break;
473            }
474        }
475
476        let _proxy = exec.run_singlethreaded(&mut fut)?;
477        Ok(())
478    }
479
480    fn test_disconnect_all(iface_list: &[(WlanMacRole, StatusResponse)]) -> Result<(), Error> {
481        let (mut exec, proxy, mut req_stream) =
482            crate::tests::setup_fake_service::<DeviceMonitorMarker>();
483        let fut = disconnect_all(&proxy);
484        let mut fut = pin!(fut);
485
486        let ifaces = (0..iface_list.len() as u16).collect();
487
488        assert!(exec.run_until_stalled(&mut fut).is_pending());
489        crate::tests::respond_to_query_iface_list_request(&mut exec, &mut req_stream, ifaces);
490
491        for (mac_role, status) in iface_list {
492            // iface query response
493            assert!(exec.run_until_stalled(&mut fut).is_pending());
494            crate::tests::respond_to_query_iface_request(
495                &mut exec,
496                &mut req_stream,
497                *mac_role,
498                Some([1, 2, 3, 4, 5, 6]),
499            );
500
501            if *mac_role == WlanMacRole::Client {
502                // Get the Client SME server (to send the responses for the following 2 SME requests)
503                assert!(exec.run_until_stalled(&mut fut).is_pending());
504                let mut fake_sme_server_stream =
505                    extract_sme_server_from_get_client_sme_req_and_respond(
506                        &mut exec,
507                        &mut req_stream,
508                        Ok(()),
509                    );
510
511                // Disconnect
512                assert!(exec.run_until_stalled(&mut fut).is_pending());
513                respond_to_client_sme_disconnect_request(&mut exec, &mut fake_sme_server_stream);
514
515                assert!(exec.run_until_stalled(&mut fut).is_pending());
516
517                // Send appropriate status response
518                respond_to_client_sme_status_request(
519                    &mut exec,
520                    &mut fake_sme_server_stream,
521                    status,
522                );
523            }
524        }
525        exec.run_singlethreaded(&mut fut)
526    }
527
528    // iface list contains an AP and a client. Test should pass
529    #[test]
530    fn check_get_client_sme_success() {
531        let iface_list: Vec<WlanMacRole> = vec![WlanMacRole::Ap, WlanMacRole::Client];
532        test_get_first_sme(&iface_list).expect("expect success but failed");
533    }
534
535    // iface list is empty. Test should fail
536    #[test]
537    fn check_get_client_sme_no_devices() {
538        let iface_list: Vec<WlanMacRole> = Vec::new();
539        test_get_first_sme(&iface_list).expect_err("expect fail but succeeded");
540    }
541
542    // iface list does not contain a client. Test should fail
543    #[test]
544    fn check_get_client_sme_no_clients() {
545        let iface_list: Vec<WlanMacRole> = vec![WlanMacRole::Ap, WlanMacRole::Ap];
546        test_get_first_sme(&iface_list).expect_err("expect fail but succeeded");
547    }
548
549    // test disconnect_all with a Client and an AP. Test should pass
550    // as AP IF will be ignored and Client IF delete should succeed.
551    #[test]
552    fn check_disconnect_all_client_and_ap_success() {
553        let iface_list: Vec<(WlanMacRole, StatusResponse)> = vec![
554            (WlanMacRole::Ap, StatusResponse::Idle),
555            (WlanMacRole::Client, StatusResponse::Idle),
556        ];
557        test_disconnect_all(&iface_list).expect("Expect success but failed")
558    }
559
560    // test disconnect_all with 2 Clients. Test should pass as both the
561    // IFs are clients and both deletes should succeed.
562    #[test]
563    fn check_disconnect_all_all_clients_success() {
564        let iface_list: Vec<(WlanMacRole, StatusResponse)> = vec![
565            (WlanMacRole::Client, StatusResponse::Idle),
566            (WlanMacRole::Client, StatusResponse::Idle),
567        ];
568        test_disconnect_all(&iface_list).expect("Expect success but failed");
569    }
570
571    // test disconnect_all with 2 Clients, one disconnect failure
572    #[test]
573    fn check_disconnect_all_all_clients_fail() {
574        let iface_list: Vec<(WlanMacRole, StatusResponse)> = vec![
575            (WlanMacRole::Ap, StatusResponse::Connected),
576            (WlanMacRole::Client, StatusResponse::Connected),
577        ];
578        test_disconnect_all(&iface_list).expect_err("Expect fail but succeeded");
579    }
580
581    // test disconnect_all with no Clients
582    #[test]
583    fn check_disconnect_all_no_clients_success() {
584        let iface_list: Vec<(WlanMacRole, StatusResponse)> =
585            vec![(WlanMacRole::Ap, StatusResponse::Idle), (WlanMacRole::Ap, StatusResponse::Idle)];
586        test_disconnect_all(&iface_list).expect("Expect success but failed");
587    }
588
589    #[test]
590    fn list_ifaces_returns_iface_id_vector() {
591        let mut exec = TestExecutor::new();
592        let (wlan_monitor, server) = create_wlan_monitor_util();
593        let mut next_device_monitor_req = server.into_future();
594
595        let ifaces: Vec<u16> = vec![0, 1, 35, 36];
596
597        let fut = get_iface_list(&wlan_monitor);
598        let mut fut = pin!(fut);
599        assert!(exec.run_until_stalled(&mut fut).is_pending());
600
601        send_iface_list_response(&mut exec, &mut next_device_monitor_req, ifaces.clone());
602
603        let complete = exec.run_until_stalled(&mut fut);
604
605        let list_response = match complete {
606            Poll::Ready(result) => result,
607            _ => panic!("Expected an iface list response"),
608        };
609
610        let response = match list_response {
611            Ok(response) => response,
612            Err(_) => panic!("Expected a valid list response"),
613        };
614
615        // now verify the response
616        assert_eq!(response, ifaces)
617    }
618
619    #[test]
620    fn list_ifaces_properly_handles_zero_ifaces() {
621        let mut exec = TestExecutor::new();
622        let (wlan_monitor, server) = create_wlan_monitor_util();
623        let mut next_device_monitor_req = server.into_future();
624
625        // create the data to use in the response
626        let iface_id_list: Vec<u16> = vec![];
627        let iface_list_vec = vec![];
628
629        let fut = get_iface_list(&wlan_monitor);
630        let mut fut = pin!(fut);
631        assert!(exec.run_until_stalled(&mut fut).is_pending());
632
633        send_iface_list_response(&mut exec, &mut next_device_monitor_req, iface_list_vec);
634
635        let complete = exec.run_until_stalled(&mut fut);
636
637        let list_response = match complete {
638            Poll::Ready(result) => result,
639            _ => panic!("Expected an iface list response"),
640        };
641
642        let response = match list_response {
643            Ok(response) => response,
644            Err(_) => panic!("Expected a valid list response"),
645        };
646
647        // now verify the response
648        assert_eq!(response, iface_id_list)
649    }
650
651    #[test]
652    fn list_phys_returns_iface_id_vector() {
653        let mut exec = TestExecutor::new();
654        let (monitor_service, server) = create_wlan_monitor_util();
655        let mut next_device_service_req = server.into_future();
656
657        // create the data to use in the response
658        let phy_id_list: Vec<u16> = vec![0, 1, 35, 36];
659        let mut phy_list_vec = vec![];
660        for id in &phy_id_list {
661            phy_list_vec.push(*id);
662        }
663
664        let fut = get_phy_list(&monitor_service);
665        let mut fut = pin!(fut);
666        assert!(exec.run_until_stalled(&mut fut).is_pending());
667
668        send_phy_list_response(&mut exec, &mut next_device_service_req, phy_list_vec);
669
670        let complete = exec.run_until_stalled(&mut fut);
671
672        let list_response = match complete {
673            Poll::Ready(result) => result,
674            _ => panic!("Expected an phy list response"),
675        };
676
677        let response = match list_response {
678            Ok(response) => response,
679            Err(_) => panic!("Expected a valid list response"),
680        };
681
682        // now verify the response
683        assert_eq!(response, phy_id_list)
684    }
685
686    #[test]
687    fn list_phys_properly_handles_zero_phys() {
688        let mut exec = TestExecutor::new();
689        let (monitor_service, server) = create_wlan_monitor_util();
690        let mut next_device_service_req = server.into_future();
691
692        // create the data to use in the response
693        let phy_id_list: Vec<u16> = vec![];
694        let phy_list_vec = vec![];
695
696        let fut = get_phy_list(&monitor_service);
697        let mut fut = pin!(fut);
698        assert!(exec.run_until_stalled(&mut fut).is_pending());
699
700        send_phy_list_response(&mut exec, &mut next_device_service_req, phy_list_vec);
701
702        let complete = exec.run_until_stalled(&mut fut);
703
704        let list_response = match complete {
705            Poll::Ready(result) => result,
706            _ => panic!("Expected an phy list response"),
707        };
708
709        let response = match list_response {
710            Ok(response) => response,
711            Err(_) => panic!("Expected a valid list response"),
712        };
713
714        // now verify the response
715        assert_eq!(response, phy_id_list)
716    }
717
718    fn poll_device_monitor_req(
719        exec: &mut TestExecutor,
720        next_device_monitor_req: &mut StreamFuture<DeviceMonitorRequestStream>,
721    ) -> Poll<DeviceMonitorRequest> {
722        exec.run_until_stalled(next_device_monitor_req).map(|(req, stream)| {
723            *next_device_monitor_req = stream.into_future();
724            req.expect("did not expect the DeviceMonitorRequestStream to end")
725                .expect("error polling device service request stream")
726        })
727    }
728
729    fn send_iface_list_response(
730        exec: &mut TestExecutor,
731        server: &mut StreamFuture<wlan_service::DeviceMonitorRequestStream>,
732        ifaces: Vec<u16>,
733    ) {
734        let responder = match poll_device_monitor_req(exec, server) {
735            Poll::Ready(DeviceMonitorRequest::ListIfaces { responder }) => responder,
736            Poll::Pending => panic!("expected a request to be available"),
737            _ => panic!("expected a ListIfaces request"),
738        };
739
740        // now send the response back
741        let _result = responder.send(&ifaces[..]);
742    }
743
744    fn send_phy_list_response(
745        exec: &mut TestExecutor,
746        server: &mut StreamFuture<wlan_service::DeviceMonitorRequestStream>,
747        phy_list_vec: Vec<u16>,
748    ) {
749        let responder = match poll_device_monitor_req(exec, server) {
750            Poll::Ready(DeviceMonitorRequest::ListPhys { responder }) => responder,
751            Poll::Pending => panic!("expected a request to be available"),
752            _ => panic!("expected a ListPhys request"),
753        };
754
755        // now send the response back
756        let _result = responder.send(&phy_list_vec);
757    }
758
759    #[test]
760    fn get_client_sme_valid_iface() {
761        let mut exec = TestExecutor::new();
762        let (wlan_monitor, server) = create_wlan_monitor_util();
763        let mut next_device_monitor_req = server.into_future();
764
765        let fut = get_sme_proxy(&wlan_monitor, 1);
766        let mut fut = pin!(fut);
767        assert!(exec.run_until_stalled(&mut fut).is_pending());
768
769        // pass in that we expect this to succeed
770        send_sme_proxy_response(&mut exec, &mut next_device_monitor_req, Ok(()));
771
772        let () = match exec.run_until_stalled(&mut fut) {
773            Poll::Ready(Ok(_)) => (),
774            _ => panic!("Expected a status response"),
775        };
776    }
777
778    fn send_sme_proxy_response(
779        exec: &mut TestExecutor,
780        server: &mut StreamFuture<wlan_service::DeviceMonitorRequestStream>,
781        result: Result<(), zx::Status>,
782    ) {
783        let responder = match poll_device_monitor_req(exec, server) {
784            Poll::Ready(DeviceMonitorRequest::GetClientSme { responder, .. }) => responder,
785            Poll::Pending => panic!("expected a request to be available"),
786            _ => panic!("expected a GetClientSme request"),
787        };
788
789        // now send the response back
790        let _result = responder.send(result.map_err(|e| e.into_raw()));
791    }
792
793    #[test]
794    fn get_client_sme_invalid_iface() {
795        let mut exec = TestExecutor::new();
796        let (wlan_monitor, server) = create_wlan_monitor_util();
797        let mut next_device_monitor_req = server.into_future();
798
799        let fut = get_sme_proxy(&wlan_monitor, 1);
800        let mut fut = pin!(fut);
801        assert!(exec.run_until_stalled(&mut fut).is_pending());
802
803        // pass in that we expect this to fail with zx::Status::NOT_FOUND
804        send_sme_proxy_response(
805            &mut exec,
806            &mut next_device_monitor_req,
807            Err(zx::Status::NOT_FOUND),
808        );
809
810        let complete = exec.run_until_stalled(&mut fut);
811
812        match complete {
813            Poll::Ready(Err(_)) => (),
814            _ => panic!("Expected a status response"),
815        };
816    }
817
818    #[test]
819    fn connect_success_returns_true() {
820        let connect_result =
821            test_wpa2_connect("TestAp", "password", "TestAp", fidl_ieee80211::StatusCode::Success);
822        assert!(connect_result);
823    }
824
825    #[test]
826    fn connect_failed_returns_false() {
827        let connect_result = test_wpa2_connect(
828            "TestAp",
829            "password",
830            "",
831            fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
832        );
833        assert!(!connect_result);
834    }
835
836    #[test]
837    fn connect_different_ssid_returns_false() {
838        let connect_result = test_wpa2_connect(
839            "TestAp1",
840            "password",
841            "TestAp2",
842            fidl_ieee80211::StatusCode::Success,
843        );
844        assert!(!connect_result);
845    }
846
847    fn test_wpa2_connect(
848        target_ssid: &str,
849        password: &str,
850        connected_to_ssid: &str,
851        result_code: fidl_ieee80211::StatusCode,
852    ) -> bool {
853        let target_ssid = Ssid::try_from(target_ssid).unwrap();
854        let connected_to_ssid = Ssid::try_from(connected_to_ssid).unwrap();
855
856        let mut exec = TestExecutor::new();
857        let (client_sme, server) = create_client_sme_proxy();
858        let mut next_client_sme_req = server.into_future();
859
860        let target_password = password.as_bytes();
861        let target_bss_desc = generate_random_wpa2_bss_description();
862
863        let fut = connect(
864            &client_sme,
865            target_ssid.clone(),
866            target_password.to_vec(),
867            target_bss_desc.clone(),
868        );
869        let mut fut = pin!(fut);
870        assert!(exec.run_until_stalled(&mut fut).is_pending());
871
872        // have the request, need to send a response
873        send_connect_request_response(
874            &mut exec,
875            &mut next_client_sme_req,
876            &target_ssid,
877            fidl_internal::Authentication::try_from(SecurityContext {
878                bss: BssDescription::try_from(target_bss_desc.clone()).unwrap(),
879                unparsed_credential_bytes: target_password.to_vec(),
880            })
881            .unwrap(),
882            result_code,
883        );
884
885        // if connection is successful, status is requested to extract ssid
886        if result_code == fidl_ieee80211::StatusCode::Success {
887            assert!(exec.run_until_stalled(&mut fut).is_pending());
888            send_status_response(
889                &mut exec,
890                &mut next_client_sme_req,
891                Some(connected_to_ssid),
892                None,
893            );
894        }
895
896        let complete = exec.run_until_stalled(&mut fut);
897
898        let connection_result = match complete {
899            Poll::Ready(result) => result,
900            _ => panic!("Expected a connect response"),
901        };
902
903        let returned_bool = match connection_result {
904            Ok(response) => response,
905            _ => panic!("Expected a valid connection result"),
906        };
907
908        returned_bool
909    }
910
911    #[test]
912    fn connect_properly_passes_network_info_with_password() {
913        let mut exec = TestExecutor::new();
914        let (client_sme, server) = create_client_sme_proxy();
915        let mut next_client_sme_req = server.into_future();
916
917        let target_ssid = Ssid::try_from("TestAp").unwrap();
918        let target_password = "password".as_bytes();
919        let target_bss_desc = generate_random_wpa2_bss_description();
920
921        let fut = connect(
922            &client_sme,
923            target_ssid.clone(),
924            target_password.to_vec(),
925            target_bss_desc.clone(),
926        );
927        let mut fut = pin!(fut);
928        assert!(exec.run_until_stalled(&mut fut).is_pending());
929
930        // verify the connect request info
931        verify_connect_request_info(
932            &mut exec,
933            &mut next_client_sme_req,
934            &target_ssid,
935            fidl_internal::Authentication::try_from(SecurityContext {
936                bss: BssDescription::try_from(target_bss_desc.clone()).unwrap(),
937                unparsed_credential_bytes: target_password.to_vec(),
938            })
939            .unwrap(),
940            target_bss_desc,
941        );
942    }
943
944    #[test]
945    fn connect_properly_passes_network_info_open() {
946        let mut exec = TestExecutor::new();
947        let (client_sme, server) = create_client_sme_proxy();
948        let mut next_client_sme_req = server.into_future();
949
950        let target_ssid = Ssid::try_from("TestAp").unwrap();
951        let target_password = "".as_bytes();
952        let target_bss_desc = fake_fidl_bss_description!(Open);
953
954        let fut = connect(
955            &client_sme,
956            target_ssid.clone(),
957            target_password.to_vec(),
958            target_bss_desc.clone(),
959        );
960        let mut fut = pin!(fut);
961        assert!(exec.run_until_stalled(&mut fut).is_pending());
962
963        // verify the connect request info
964        verify_connect_request_info(
965            &mut exec,
966            &mut next_client_sme_req,
967            &target_ssid,
968            fidl_internal::Authentication::try_from(SecurityContext {
969                bss: BssDescription::try_from(target_bss_desc.clone()).unwrap(),
970                unparsed_credential_bytes: vec![],
971            })
972            .unwrap(),
973            target_bss_desc,
974        );
975    }
976
977    fn verify_connect_request_info(
978        exec: &mut TestExecutor,
979        server: &mut StreamFuture<ClientSmeRequestStream>,
980        expected_ssid: &Ssid,
981        expected_authentication: fidl_internal::Authentication,
982        expected_bss_desc: fidl_ieee80211::BssDescription,
983    ) {
984        match poll_client_sme_request(exec, server) {
985            Poll::Ready(ClientSmeRequest::Connect { req, .. }) => {
986                assert_eq!(expected_ssid, &req.ssid);
987                assert_eq!(req.authentication, expected_authentication);
988                assert_eq!(req.bss_description, expected_bss_desc);
989            }
990            _ => panic!("expected a Connect request"),
991        }
992    }
993
994    fn send_connect_request_response(
995        exec: &mut TestExecutor,
996        server: &mut StreamFuture<ClientSmeRequestStream>,
997        expected_ssid: &Ssid,
998        expected_authentication: fidl_internal::Authentication,
999        connect_result: fidl_ieee80211::StatusCode,
1000    ) {
1001        let responder = match poll_client_sme_request(exec, server) {
1002            Poll::Ready(ClientSmeRequest::Connect { req, txn, .. }) => {
1003                assert_eq!(expected_ssid, &req.ssid[..]);
1004                assert_eq!(req.authentication, expected_authentication);
1005                txn.expect("expected a Connect transaction channel")
1006            }
1007            Poll::Pending => panic!("expected a request to be available"),
1008            _ => panic!("expected a Connect request"),
1009        };
1010        let connect_transaction = responder
1011            .into_stream()
1012            .expect("failed to create a connect transaction stream")
1013            .control_handle();
1014        connect_transaction
1015            .send_on_connect_result(&fidl_sme::ConnectResult {
1016                code: connect_result,
1017                is_credential_rejected: false,
1018                is_reconnect: false,
1019            })
1020            .expect("failed to send OnConnectResult to ConnectTransaction");
1021    }
1022
1023    fn poll_client_sme_request(
1024        exec: &mut TestExecutor,
1025        next_client_sme_req: &mut StreamFuture<ClientSmeRequestStream>,
1026    ) -> Poll<ClientSmeRequest> {
1027        exec.run_until_stalled(next_client_sme_req).map(|(req, stream)| {
1028            *next_client_sme_req = stream.into_future();
1029            req.expect("did not expect the ClientSmeRequestStream to end")
1030                .expect("error polling client sme request stream")
1031        })
1032    }
1033
1034    fn create_client_sme_proxy() -> (fidl_sme::ClientSmeProxy, ClientSmeRequestStream) {
1035        let (proxy, server) = endpoints::create_proxy::<ClientSmeMarker>();
1036        let server = server.into_stream();
1037        (proxy, server)
1038    }
1039
1040    fn create_wlan_monitor_util() -> (DeviceMonitorProxy, DeviceMonitorRequestStream) {
1041        let (proxy, server) = endpoints::create_proxy::<DeviceMonitorMarker>();
1042        let server = server.into_stream();
1043        (proxy, server)
1044    }
1045
1046    enum StatusResponse {
1047        Idle,
1048        Connected,
1049        Connecting,
1050    }
1051
1052    #[test]
1053    fn disconnect_with_empty_status_response() {
1054        if let Poll::Ready(result) = test_disconnect(StatusResponse::Idle) {
1055            return assert!(result.is_ok());
1056        }
1057        panic!("disconnect did not return a Poll::Ready")
1058    }
1059
1060    #[test]
1061    fn disconnect_fail_because_connected() {
1062        if let Poll::Ready(result) = test_disconnect(StatusResponse::Connected) {
1063            return assert!(result.is_err());
1064        }
1065        panic!("disconnect did not return a Poll::Ready")
1066    }
1067
1068    #[test]
1069    fn disconnect_fail_because_connecting() {
1070        if let Poll::Ready(result) = test_disconnect(StatusResponse::Connecting) {
1071            return assert!(result.is_err());
1072        }
1073        panic!("disconnect did not return a Poll::Ready")
1074    }
1075
1076    fn test_disconnect(status: StatusResponse) -> Poll<Result<(), Error>> {
1077        let mut exec = TestExecutor::new();
1078        let (client_sme, server) = create_client_sme_proxy();
1079        let mut client_sme_req = server.into_future();
1080
1081        let fut = disconnect(&client_sme);
1082        let mut fut = pin!(fut);
1083        assert!(exec.run_until_stalled(&mut fut).is_pending());
1084
1085        send_disconnect_request_response(&mut exec, &mut client_sme_req);
1086
1087        assert!(exec.run_until_stalled(&mut fut).is_pending());
1088
1089        match status {
1090            StatusResponse::Idle => {
1091                send_status_response(&mut exec, &mut client_sme_req, None, None)
1092            }
1093            StatusResponse::Connected => send_status_response(
1094                &mut exec,
1095                &mut client_sme_req,
1096                Some(Ssid::try_from([1, 2, 3, 4]).unwrap()),
1097                None,
1098            ),
1099            StatusResponse::Connecting => send_status_response(
1100                &mut exec,
1101                &mut client_sme_req,
1102                None,
1103                Some(Ssid::try_from([1, 2, 3, 4]).unwrap()),
1104            ),
1105        }
1106
1107        exec.run_until_stalled(&mut fut)
1108    }
1109
1110    fn send_disconnect_request_response(
1111        exec: &mut TestExecutor,
1112        server: &mut StreamFuture<ClientSmeRequestStream>,
1113    ) {
1114        let rsp = match poll_client_sme_request(exec, server) {
1115            Poll::Ready(ClientSmeRequest::Disconnect { responder, .. }) => responder,
1116            Poll::Pending => panic!("Expected a DisconnectRequest"),
1117            _ => panic!("Expected a DisconnectRequest"),
1118        };
1119        rsp.send().expect("Failed to send DisconnectResponse.");
1120    }
1121
1122    fn create_serving_ap_info_using_ssid(ssid: Ssid) -> fidl_sme::ServingApInfo {
1123        fidl_sme::ServingApInfo {
1124            bssid: [0, 1, 2, 3, 4, 5],
1125            ssid: ssid.into(),
1126            rssi_dbm: -30,
1127            snr_db: 10,
1128            channel: fidl_ieee80211::WlanChannel {
1129                primary: 1,
1130                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
1131                secondary80: 0,
1132            },
1133            protection: Protection::Wpa2Personal,
1134        }
1135    }
1136
1137    fn send_status_response(
1138        exec: &mut TestExecutor,
1139        server: &mut StreamFuture<ClientSmeRequestStream>,
1140        connected_to_ssid: Option<Ssid>,
1141        connecting_to_ssid: Option<Ssid>,
1142    ) {
1143        let rsp = match poll_client_sme_request(exec, server) {
1144            Poll::Ready(ClientSmeRequest::Status { responder }) => responder,
1145            Poll::Pending => panic!("Expected a StatusRequest"),
1146            _ => panic!("Expected a StatusRequest"),
1147        };
1148
1149        let response = match (connected_to_ssid, connecting_to_ssid) {
1150            (Some(_), Some(_)) => panic!("SME cannot simultaneously be Connected and Connecting."),
1151            (Some(ssid), None) => {
1152                let serving_ap_info = create_serving_ap_info_using_ssid(ssid);
1153                fidl_sme::ClientStatusResponse::Connected(serving_ap_info)
1154            }
1155            (None, Some(ssid)) => fidl_sme::ClientStatusResponse::Connecting(ssid.to_vec()),
1156            (None, None) => fidl_sme::ClientStatusResponse::Idle(fidl_sme::Empty {}),
1157        };
1158
1159        rsp.send(&response).expect("Failed to send StatusResponse.");
1160    }
1161
1162    #[test]
1163    fn scan_success_returns_empty_results() {
1164        assert_eq!(test_scan(&[]), &[]);
1165    }
1166
1167    #[test]
1168    fn scan_success_returns_results() {
1169        let scan_results = &[
1170            create_scan_result(
1171                [0, 1, 2, 3, 4, 5],
1172                Ssid::try_from("foo").unwrap(),
1173                -30,
1174                20,
1175                Channel::new(1, Cbw::Cbw20),
1176                Protection::Wpa2Personal,
1177                Some(fidl_sme::Compatibility {
1178                    mutual_security_protocols: vec![fidl_internal::Protocol::Wpa2Personal],
1179                }),
1180            ),
1181            create_scan_result(
1182                [1, 2, 3, 4, 5, 6],
1183                Ssid::try_from("hello").unwrap(),
1184                -60,
1185                10,
1186                Channel::new(2, Cbw::Cbw20),
1187                Protection::Wpa2Personal,
1188                None,
1189            ),
1190        ];
1191
1192        assert_eq!(test_scan(scan_results), scan_results);
1193    }
1194
1195    #[test]
1196    fn scan_error_correctly_handled() {
1197        // need to expect an error
1198        assert!(test_scan_error().is_err())
1199    }
1200
1201    fn test_scan(scan_results: &[fidl_sme::ScanResult]) -> Vec<fidl_sme::ScanResult> {
1202        let mut exec = TestExecutor::new();
1203        let (client_sme, server) = create_client_sme_proxy();
1204        let mut client_sme_req = server.into_future();
1205
1206        let fut = passive_scan(&client_sme);
1207        let mut fut = pin!(fut);
1208        assert!(exec.run_until_stalled(&mut fut).is_pending());
1209
1210        send_scan_result_response(&mut exec, &mut client_sme_req, scan_results);
1211
1212        let complete = exec.run_until_stalled(&mut fut);
1213        let request_result = match complete {
1214            Poll::Ready(result) => result,
1215            _ => panic!("Expected a scan request result"),
1216        };
1217        let returned_scan_results = request_result.expect("failed to get scan results");
1218
1219        returned_scan_results
1220    }
1221
1222    fn send_scan_result_response(
1223        exec: &mut TestExecutor,
1224        server: &mut StreamFuture<fidl_sme::ClientSmeRequestStream>,
1225        scan_results: &[fidl_sme::ScanResult],
1226    ) {
1227        match poll_client_sme_request(exec, server) {
1228            Poll::Ready(fidl_sme::ClientSmeRequest::Scan { responder, .. }) => {
1229                let vmo = write_vmo(scan_results.to_vec()).expect("failed to write VMO");
1230                responder.send(Ok(vmo)).expect("failed to send scan results")
1231            }
1232            Poll::Pending => panic!("expected a request to be available"),
1233            _ => panic!("expected a scan request"),
1234        }
1235    }
1236
1237    fn test_scan_error() -> Result<(), Error> {
1238        let mut exec = TestExecutor::new();
1239        let (client_sme, server) = create_client_sme_proxy();
1240        let mut client_sme_req = server.into_future();
1241
1242        let fut = passive_scan(&client_sme);
1243        let mut fut = pin!(fut);
1244        assert!(exec.run_until_stalled(&mut fut).is_pending());
1245
1246        send_scan_error_response(&mut exec, &mut client_sme_req);
1247        let _ = exec.run_until_stalled(&mut fut)?;
1248        Ok(())
1249    }
1250
1251    fn send_scan_error_response(
1252        exec: &mut TestExecutor,
1253        server: &mut StreamFuture<fidl_sme::ClientSmeRequestStream>,
1254    ) {
1255        match poll_client_sme_request(exec, server) {
1256            Poll::Ready(fidl_sme::ClientSmeRequest::Scan { responder, .. }) => responder
1257                .send(Err(fidl_sme::ScanErrorCode::InternalError))
1258                .expect("failed to send ScanError"),
1259            Poll::Pending => panic!("expected a request to be available"),
1260            _ => panic!("expected a scan request"),
1261        };
1262    }
1263
1264    fn create_scan_result(
1265        bssid: [u8; 6],
1266        ssid: Ssid,
1267        rssi_dbm: i8,
1268        snr_db: i8,
1269        channel: Channel,
1270        protection: Protection,
1271        compatibility: Option<fidl_sme::Compatibility>,
1272    ) -> fidl_sme::ScanResult {
1273        fidl_sme::ScanResult {
1274            compatibility: compatibility.map(Box::new),
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}