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