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