wlan_sme/serve/
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 crate::client::{
6    self as client_sme, ConnectResult, ConnectTransactionEvent, ConnectTransactionStream,
7    RoamResult,
8};
9use crate::{MlmeEventStream, MlmeSink, MlmeStream};
10use fidl::endpoints::{RequestStream, ServerEnd};
11use fidl_fuchsia_wlan_common::BssDescription as BssDescriptionFidl;
12use fidl_fuchsia_wlan_sme::{self as fidl_sme, ClientSmeRequest, TelemetryRequest};
13use fuchsia_sync::Mutex;
14use futures::channel::mpsc;
15use futures::prelude::*;
16use futures::select;
17use ieee80211::MacAddrBytes;
18use log::error;
19use std::pin::pin;
20use std::sync::Arc;
21use wlan_common::scan::write_vmo;
22use {
23    fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
24    fidl_fuchsia_wlan_mlme as fidl_mlme, fuchsia_inspect_auto_persist as auto_persist,
25};
26
27pub type Endpoint = ServerEnd<fidl_sme::ClientSmeMarker>;
28type Sme = client_sme::ClientSme;
29
30#[allow(clippy::too_many_arguments, reason = "mass allow for https://fxbug.dev/381896734")]
31pub fn serve(
32    cfg: crate::Config,
33    device_info: fidl_mlme::DeviceInfo,
34    security_support: fidl_common::SecuritySupport,
35    spectrum_management_support: fidl_common::SpectrumManagementSupport,
36    event_stream: MlmeEventStream,
37    new_fidl_clients: mpsc::UnboundedReceiver<Endpoint>,
38    new_telemetry_fidl_clients: mpsc::UnboundedReceiver<
39        fidl::endpoints::ServerEnd<fidl_sme::TelemetryMarker>,
40    >,
41    inspector: fuchsia_inspect::Inspector,
42    inspect_node: fuchsia_inspect::Node,
43    persistence_req_sender: auto_persist::PersistenceReqSender,
44) -> (MlmeSink, MlmeStream, impl Future<Output = Result<(), anyhow::Error>>) {
45    let wpa3_supported = security_support.mfp.supported
46        && (security_support.sae.driver_handler_supported
47            || security_support.sae.sme_handler_supported);
48    let cfg = client_sme::ClientConfig::from_config(cfg, wpa3_supported);
49    let (sme, mlme_sink, mlme_stream, time_stream) = Sme::new(
50        cfg,
51        device_info,
52        inspector,
53        inspect_node,
54        persistence_req_sender,
55        security_support,
56        spectrum_management_support,
57    );
58    let fut = async move {
59        let sme = Arc::new(Mutex::new(sme));
60        let mlme_sme = super::serve_mlme_sme(event_stream, Arc::clone(&sme), time_stream);
61        let sme_fidl = super::serve_fidl(&*sme, new_fidl_clients, handle_fidl_request);
62        let telemetry_fidl =
63            super::serve_fidl(&*sme, new_telemetry_fidl_clients, handle_telemetry_fidl_request);
64        let mlme_sme = pin!(mlme_sme);
65        let sme_fidl = pin!(sme_fidl);
66        select! {
67            mlme_sme = mlme_sme.fuse() => mlme_sme?,
68            sme_fidl = sme_fidl.fuse() => match sme_fidl? {},
69            telemetry_fidl = telemetry_fidl.fuse() => match telemetry_fidl? {},
70        }
71        Ok(())
72    };
73    (mlme_sink, mlme_stream, fut)
74}
75
76async fn handle_fidl_request(
77    sme: &Mutex<Sme>,
78    request: fidl_sme::ClientSmeRequest,
79) -> Result<(), fidl::Error> {
80    #[allow(clippy::unit_arg, reason = "mass allow for https://fxbug.dev/381896734")]
81    match request {
82        ClientSmeRequest::Scan { req, responder } => Ok(scan(sme, req, |result| match result {
83            Ok(scan_results) => responder.send(Ok(write_vmo(scan_results)?)).map_err(|e| e.into()),
84            Err(e) => responder.send(Err(e)).map_err(|e| e.into()),
85        })
86        .await
87        .unwrap_or_else(|e| error!("Error handling a scan transaction: {:?}", e))),
88        ClientSmeRequest::Connect { req, txn, .. } => Ok(connect(sme, txn, req)
89            .await
90            .unwrap_or_else(|e| error!("Error handling a connect transaction: {:?}", e))),
91        ClientSmeRequest::Roam { req, .. } => Ok(roam(sme, req)),
92        ClientSmeRequest::Disconnect { responder, reason } => {
93            disconnect(sme, reason, responder);
94            Ok(())
95        }
96        ClientSmeRequest::Status { responder } => responder.send(&status(sme)),
97        ClientSmeRequest::WmmStatus { responder } => wmm_status(sme, responder).await,
98        ClientSmeRequest::ScanForController { req, responder } => {
99            Ok(scan(sme, req, |result| match result {
100                Ok(results) => responder.send(Ok(&results[..])).map_err(|e| e.into()),
101                Err(e) => responder.send(Err(e)).map_err(|e| e.into()),
102            })
103            .await
104            .unwrap_or_else(|e| error!("Error handling a test scan transaction: {:?}", e)))
105        }
106        ClientSmeRequest::SetMacAddress { mac_addr, responder } => {
107            Ok(set_mac_address(sme, mac_addr, responder).await?)
108        }
109    }
110}
111
112async fn handle_telemetry_fidl_request(
113    sme: &Mutex<Sme>,
114    request: TelemetryRequest,
115) -> Result<(), fidl::Error> {
116    match request {
117        TelemetryRequest::QueryTelemetrySupport { responder, .. } => {
118            let support_fut = sme.lock().query_telemetry_support();
119            let support = support_fut
120                .await
121                .map_err(|_| zx::Status::CONNECTION_ABORTED.into_raw())
122                .and_then(|result| result);
123            responder.send(support.as_ref().map_err(|e| *e))
124        }
125        TelemetryRequest::GetIfaceStats { responder, .. } => {
126            let iface_stats_fut = sme.lock().iface_stats();
127            let iface_stats = iface_stats_fut
128                .await
129                .map_err(|_| zx::Status::CONNECTION_ABORTED.into_raw())
130                .and_then(|stats| match stats {
131                    fidl_mlme::GetIfaceStatsResponse::Stats(stats) => Ok(stats),
132                    fidl_mlme::GetIfaceStatsResponse::ErrorStatus(err) => Err(err),
133                });
134            responder.send(iface_stats.as_ref().map_err(|e| *e))
135        }
136        TelemetryRequest::GetHistogramStats { responder, .. } => {
137            let histogram_stats_fut = sme.lock().histogram_stats();
138            let histogram_stats = histogram_stats_fut
139                .await
140                .map_err(|_| zx::Status::CONNECTION_ABORTED.into_raw())
141                .and_then(|stats| match stats {
142                    fidl_mlme::GetIfaceHistogramStatsResponse::Stats(stats) => Ok(stats),
143                    fidl_mlme::GetIfaceHistogramStatsResponse::ErrorStatus(err) => Err(err),
144                });
145            responder.send(histogram_stats.as_ref().map_err(|e| *e))
146        }
147        TelemetryRequest::GetSignalReport { responder, .. } => {
148            let signal_report_fut = sme.lock().signal_report();
149            let signal_report = signal_report_fut
150                .await
151                .map_err(|_| zx::Status::CONNECTION_ABORTED.into_raw())
152                .and_then(|result| result);
153            responder.send(signal_report.as_ref().map_err(|e| *e))
154        }
155        TelemetryRequest::CloneInspectVmo { responder } => {
156            let inspect_vmo =
157                sme.lock().on_clone_inspect_vmo().ok_or_else(|| zx::Status::INTERNAL.into_raw());
158            responder.send(inspect_vmo)
159        }
160    }
161}
162
163async fn scan(
164    sme: &Mutex<Sme>,
165    request: fidl_sme::ScanRequest,
166    responder: impl FnOnce(
167        Result<Vec<fidl_sme::ScanResult>, fidl_sme::ScanErrorCode>,
168    ) -> Result<(), anyhow::Error>,
169) -> Result<(), anyhow::Error> {
170    let receiver = sme.lock().on_scan_command(request);
171    let receive_result = match receiver.await {
172        Ok(receive_result) => receive_result,
173        Err(e) => {
174            error!("Scan receiver error: {:?}", e);
175            responder(Err(fidl_sme::ScanErrorCode::InternalError))?;
176            return Ok(());
177        }
178    };
179
180    match receive_result {
181        Ok(scan_results) => {
182            let results = scan_results.into_iter().map(Into::into).collect::<Vec<_>>();
183            responder(Ok(results))
184        }
185        Err(mlme_scan_result_code) => {
186            let scan_error_code = match mlme_scan_result_code {
187                fidl_mlme::ScanResultCode::Success | fidl_mlme::ScanResultCode::InvalidArgs => {
188                    error!("Internal scan error: {:?}", mlme_scan_result_code);
189                    fidl_sme::ScanErrorCode::InternalError
190                }
191                fidl_mlme::ScanResultCode::NotSupported => fidl_sme::ScanErrorCode::NotSupported,
192                fidl_mlme::ScanResultCode::InternalError => {
193                    fidl_sme::ScanErrorCode::InternalMlmeError
194                }
195                fidl_mlme::ScanResultCode::ShouldWait => fidl_sme::ScanErrorCode::ShouldWait,
196                fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware => {
197                    fidl_sme::ScanErrorCode::CanceledByDriverOrFirmware
198                }
199            };
200            responder(Err(scan_error_code))
201        }
202    }?;
203    Ok(())
204}
205
206async fn connect(
207    sme: &Mutex<Sme>,
208    txn: Option<ServerEnd<fidl_sme::ConnectTransactionMarker>>,
209    req: fidl_sme::ConnectRequest,
210) -> Result<(), anyhow::Error> {
211    #[allow(clippy::manual_map, reason = "mass allow for https://fxbug.dev/381896734")]
212    let handle = match txn {
213        None => None,
214        Some(txn) => Some(txn.into_stream().control_handle()),
215    };
216    let connect_txn_stream = sme.lock().on_connect_command(req);
217    serve_connect_txn_stream(handle, connect_txn_stream).await?;
218    Ok(())
219}
220
221async fn serve_connect_txn_stream(
222    handle: Option<fidl_sme::ConnectTransactionControlHandle>,
223    mut connect_txn_stream: ConnectTransactionStream,
224) -> Result<(), anyhow::Error> {
225    if let Some(handle) = handle {
226        loop {
227            match connect_txn_stream.next().await {
228                Some(event) => match event {
229                    ConnectTransactionEvent::OnConnectResult { result, is_reconnect } => {
230                        let connect_result = convert_connect_result(&result, is_reconnect);
231                        handle.send_on_connect_result(&connect_result)
232                    }
233                    ConnectTransactionEvent::OnRoamResult { result } => {
234                        let roam_result = convert_roam_result(&result);
235                        handle.send_on_roam_result(&roam_result)
236                    }
237                    ConnectTransactionEvent::OnDisconnect { info } => {
238                        handle.send_on_disconnect(&info)
239                    }
240                    ConnectTransactionEvent::OnSignalReport { ind } => {
241                        handle.send_on_signal_report(&ind)
242                    }
243                    ConnectTransactionEvent::OnChannelSwitched { info } => {
244                        handle.send_on_channel_switched(&info)
245                    }
246                }?,
247                // SME has dropped the ConnectTransaction endpoint, likely due to a disconnect.
248                None => return Ok(()),
249            }
250        }
251    }
252    Ok(())
253}
254
255fn roam(sme: &Mutex<Sme>, req: fidl_sme::RoamRequest) {
256    sme.lock().on_roam_command(req);
257}
258
259fn disconnect(
260    sme: &Mutex<Sme>,
261    policy_disconnect_reason: fidl_sme::UserDisconnectReason,
262    responder: fidl_sme::ClientSmeDisconnectResponder,
263) {
264    sme.lock().on_disconnect_command(policy_disconnect_reason, responder);
265}
266
267fn status(sme: &Mutex<Sme>) -> fidl_sme::ClientStatusResponse {
268    sme.lock().status().into()
269}
270
271async fn wmm_status(
272    sme: &Mutex<Sme>,
273    responder: fidl_sme::ClientSmeWmmStatusResponder,
274) -> Result<(), fidl::Error> {
275    let receiver = sme.lock().wmm_status();
276    let wmm_status = match receiver.await {
277        Ok(result) => result,
278        Err(_) => Err(zx::sys::ZX_ERR_CANCELED),
279    };
280    responder.send(wmm_status.as_ref().map_err(|e| *e))
281}
282
283async fn set_mac_address(
284    sme: &Mutex<Sme>,
285    mac_addr: [u8; 6],
286    responder: fidl_sme::ClientSmeSetMacAddressResponder,
287) -> Result<(), fidl::Error> {
288    let receiver = sme.lock().set_mac_address(mac_addr);
289    let resp = match receiver.await {
290        Ok(result) => result,
291        Err(_) => Err(zx::sys::ZX_ERR_CANCELED),
292    };
293    responder.send(resp)
294}
295
296fn convert_connect_result(result: &ConnectResult, is_reconnect: bool) -> fidl_sme::ConnectResult {
297    let (code, is_credential_rejected) = match result {
298        ConnectResult::Success => (fidl_ieee80211::StatusCode::Success, false),
299        ConnectResult::Canceled => (fidl_ieee80211::StatusCode::Canceled, false),
300        ConnectResult::Failed(failure) => {
301            (failure.status_code(), failure.likely_due_to_credential_rejected())
302        }
303    };
304    fidl_sme::ConnectResult { code, is_credential_rejected, is_reconnect }
305}
306
307fn convert_roam_result(result: &RoamResult) -> fidl_sme::RoamResult {
308    match result {
309        RoamResult::Success(bss) => {
310            let bss_description = Some(Box::new(BssDescriptionFidl::from(*bss.clone())));
311            fidl_sme::RoamResult {
312                bssid: bss.bssid.to_array(),
313                status_code: fidl_ieee80211::StatusCode::Success,
314                // Must always be false on roam success.
315                original_association_maintained: false,
316                bss_description,
317                disconnect_info: None,
318                is_credential_rejected: false,
319            }
320        }
321        RoamResult::Failed(failure) => {
322            #[allow(clippy::manual_map, reason = "mass allow for https://fxbug.dev/381896734")]
323            fidl_sme::RoamResult {
324                bssid: failure.selected_bssid.to_array(),
325                status_code: failure.status_code,
326                // Current implementation assumes that all roam attempts incur disassociation from the
327                // original BSS. When this changes (e.g. due to Fast BSS Transition support), this
328                // hard-coded field should be set from the RoamResult enum.
329                original_association_maintained: false,
330                bss_description: match &failure.selected_bss {
331                    Some(bss) => Some(Box::new(bss.clone().into())),
332                    None => None,
333                },
334                disconnect_info: Some(Box::new(failure.disconnect_info)),
335                is_credential_rejected: failure.likely_due_to_credential_rejected(),
336            }
337        }
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344    use crate::client::{ConnectFailure, EstablishRsnaFailure, EstablishRsnaFailureReason};
345    use assert_matches::assert_matches;
346    use fidl::endpoints::create_proxy_and_stream;
347    use fidl_fuchsia_wlan_mlme::ScanResultCode;
348    use fidl_fuchsia_wlan_sme::{self as fidl_sme};
349    use futures::stream::StreamFuture;
350    use futures::task::Poll;
351    use rand::Rng;
352    use rand::prelude::ThreadRng;
353    use std::pin::pin;
354    use test_case::test_case;
355    use wlan_common::random_bss_description;
356    use wlan_common::scan::{self, Incompatible};
357    use wlan_rsn::auth;
358    use {fidl_fuchsia_wlan_internal as fidl_internal, fuchsia_async as fasync};
359
360    #[test]
361    fn test_convert_connect_result() {
362        assert_eq!(
363            convert_connect_result(&ConnectResult::Success, false),
364            fidl_sme::ConnectResult {
365                code: fidl_ieee80211::StatusCode::Success,
366                is_credential_rejected: false,
367                is_reconnect: false,
368            }
369        );
370        assert_eq!(
371            convert_connect_result(&ConnectResult::Canceled, true),
372            fidl_sme::ConnectResult {
373                code: fidl_ieee80211::StatusCode::Canceled,
374                is_credential_rejected: false,
375                is_reconnect: true,
376            }
377        );
378        let connect_result =
379            ConnectResult::Failed(ConnectFailure::ScanFailure(ScanResultCode::ShouldWait));
380        assert_eq!(
381            convert_connect_result(&connect_result, false),
382            fidl_sme::ConnectResult {
383                code: fidl_ieee80211::StatusCode::Canceled,
384                is_credential_rejected: false,
385                is_reconnect: false,
386            }
387        );
388
389        let connect_result =
390            ConnectResult::Failed(ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
391                auth_method: Some(auth::MethodName::Psk),
392                reason: EstablishRsnaFailureReason::InternalError,
393            }));
394        assert_eq!(
395            convert_connect_result(&connect_result, false),
396            fidl_sme::ConnectResult {
397                code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
398                is_credential_rejected: false,
399                is_reconnect: false,
400            }
401        );
402
403        let connect_result =
404            ConnectResult::Failed(ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
405                auth_method: Some(auth::MethodName::Psk),
406                reason: EstablishRsnaFailureReason::RsnaResponseTimeout(
407                    wlan_rsn::Error::LikelyWrongCredential,
408                ),
409            }));
410        assert_eq!(
411            convert_connect_result(&connect_result, false),
412            fidl_sme::ConnectResult {
413                code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
414                is_credential_rejected: true,
415                is_reconnect: false,
416            }
417        );
418
419        let connect_result =
420            ConnectResult::Failed(ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
421                auth_method: Some(auth::MethodName::Psk),
422                reason: EstablishRsnaFailureReason::RsnaCompletionTimeout(
423                    wlan_rsn::Error::LikelyWrongCredential,
424                ),
425            }));
426        assert_eq!(
427            convert_connect_result(&connect_result, false),
428            fidl_sme::ConnectResult {
429                code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
430                is_credential_rejected: true,
431                is_reconnect: false,
432            }
433        );
434
435        let connect_result =
436            ConnectResult::Failed(ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
437                auth_method: Some(auth::MethodName::Psk),
438                reason: EstablishRsnaFailureReason::RsnaCompletionTimeout(
439                    wlan_rsn::Error::MissingGtkProvider,
440                ),
441            }));
442        assert_eq!(
443            convert_connect_result(&connect_result, false),
444            fidl_sme::ConnectResult {
445                code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
446                is_credential_rejected: false,
447                is_reconnect: false,
448            }
449        );
450
451        let connect_result =
452            ConnectResult::Failed(ConnectFailure::ScanFailure(ScanResultCode::InternalError));
453        assert_eq!(
454            convert_connect_result(&connect_result, false),
455            fidl_sme::ConnectResult {
456                code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
457                is_credential_rejected: false,
458                is_reconnect: false,
459            }
460        );
461    }
462
463    // TODO(https://fxbug.dev/42164611): There is no test coverage for consistency between MLME scan results
464    // and SME scan results produced by wlanstack. In particular, the timestamp_nanos field
465    // of fidl_mlme::ScanResult is dropped in SME, and no tests reveal this problem.
466
467    #[test_case(1, true; "with 1 result")]
468    #[test_case(2, true; "with 2 results")]
469    #[test_case(30, true; "with 30 results")]
470    #[test_case(4000, true; "with 4000 results")]
471    #[test_case(50000, false; "with 50000 results")]
472    #[test_case(100000, false; "with 100000 results")]
473    fn scan_results_are_effectively_unbounded(number_of_scan_results: usize, randomize: bool) {
474        let mut exec = fasync::TestExecutor::new();
475        let (client_sme_proxy, mut client_sme_stream) =
476            create_proxy_and_stream::<fidl_sme::ClientSmeMarker>();
477
478        // Request scan
479        async fn request_and_collect_result(
480            client_sme_proxy: &fidl_sme::ClientSmeProxy,
481        ) -> fidl_sme::ClientSmeScanResult {
482            client_sme_proxy
483                .scan(&fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}))
484                .await
485                .expect("FIDL request failed")
486        }
487
488        let result_fut = request_and_collect_result(&client_sme_proxy);
489        let mut result_fut = pin!(result_fut);
490
491        assert_matches!(exec.run_until_stalled(&mut result_fut), Poll::Pending);
492
493        // Generate and send scan results
494        let mut rng = rand::rng();
495        let scan_result_list = if randomize {
496            (0..number_of_scan_results).map(|_| random_scan_result(&mut rng).into()).collect()
497        } else {
498            vec![random_scan_result(&mut rng).into(); number_of_scan_results]
499        };
500        assert_matches!(exec.run_until_stalled(&mut client_sme_stream.next()),
501                        Poll::Ready(Some(Ok(fidl_sme::ClientSmeRequest::Scan {
502                            req: _, responder,
503                        }))) => {
504                            let vmo = write_vmo(scan_result_list.clone()).expect("failed to write VMO");
505                            responder.send(Ok(vmo)).expect("failed to send scan results");
506                        }
507        );
508
509        // Verify scan results
510        assert_matches!(exec.run_until_stalled(&mut result_fut), Poll::Ready(Ok(vmo)) => {
511            assert_eq!(scan_result_list, scan::read_vmo(vmo).expect("failed to read VMO"));
512        })
513    }
514
515    #[test]
516    fn test_serve_connect_txn_stream() {
517        let mut exec = fasync::TestExecutor::new();
518
519        let (sme_proxy, sme_connect_txn_stream) = mpsc::unbounded();
520        let (fidl_client_proxy, fidl_connect_txn_stream) =
521            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
522        let fidl_client_fut = fidl_client_proxy.take_event_stream().into_future();
523        let mut fidl_client_fut = pin!(fidl_client_fut);
524        let fidl_connect_txn_handle = fidl_connect_txn_stream.control_handle();
525
526        let test_fut =
527            serve_connect_txn_stream(Some(fidl_connect_txn_handle), sme_connect_txn_stream);
528        let mut test_fut = pin!(test_fut);
529
530        // Test sending OnConnectResult
531        sme_proxy
532            .unbounded_send(ConnectTransactionEvent::OnConnectResult {
533                result: ConnectResult::Success,
534                is_reconnect: true,
535            })
536            .expect("expect sending ConnectTransactionEvent to succeed");
537        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Pending);
538        let event = assert_matches!(poll_stream_fut(&mut exec, &mut fidl_client_fut), Poll::Ready(Some(Ok(event))) => event);
539        assert_matches!(
540            event,
541            fidl_sme::ConnectTransactionEvent::OnConnectResult {
542                result: fidl_sme::ConnectResult {
543                    code: fidl_ieee80211::StatusCode::Success,
544                    is_credential_rejected: false,
545                    is_reconnect: true,
546                }
547            }
548        );
549
550        // Test sending OnDisconnect
551        let input_info = fidl_sme::DisconnectInfo {
552            is_sme_reconnecting: true,
553            disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
554                reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
555                mlme_event_name: fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
556            }),
557        };
558        sme_proxy
559            .unbounded_send(ConnectTransactionEvent::OnDisconnect { info: input_info })
560            .expect("expect sending ConnectTransactionEvent to succeed");
561        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Pending);
562        let event = assert_matches!(poll_stream_fut(&mut exec, &mut fidl_client_fut), Poll::Ready(Some(Ok(event))) => event);
563        assert_matches!(event, fidl_sme::ConnectTransactionEvent::OnDisconnect { info: output_info } => {
564            assert_eq!(input_info, output_info);
565        });
566
567        // Test sending OnSignalReport
568        let input_ind = fidl_internal::SignalReportIndication { rssi_dbm: -40, snr_db: 30 };
569        sme_proxy
570            .unbounded_send(ConnectTransactionEvent::OnSignalReport { ind: input_ind })
571            .expect("expect sending ConnectTransactionEvent to succeed");
572        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Pending);
573        let event = assert_matches!(poll_stream_fut(&mut exec, &mut fidl_client_fut), Poll::Ready(Some(Ok(event))) => event);
574        assert_matches!(event, fidl_sme::ConnectTransactionEvent::OnSignalReport { ind } => {
575            assert_eq!(input_ind, ind);
576        });
577
578        // Test sending OnChannelSwitched
579        let input_info = fidl_internal::ChannelSwitchInfo { new_channel: 8 };
580        sme_proxy
581            .unbounded_send(ConnectTransactionEvent::OnChannelSwitched { info: input_info })
582            .expect("expect sending ConnectTransactionEvent to succeed");
583        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Pending);
584        let event = assert_matches!(poll_stream_fut(&mut exec, &mut fidl_client_fut), Poll::Ready(Some(Ok(event))) => event);
585        assert_matches!(event, fidl_sme::ConnectTransactionEvent::OnChannelSwitched { info } => {
586            assert_eq!(input_info, info);
587        });
588
589        // When SME proxy is dropped, the fut should terminate
590        std::mem::drop(sme_proxy);
591        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Ready(Ok(())));
592    }
593
594    fn poll_stream_fut<S: Stream + std::marker::Unpin>(
595        exec: &mut fasync::TestExecutor,
596        stream_fut: &mut StreamFuture<S>,
597    ) -> Poll<Option<S::Item>> {
598        exec.run_until_stalled(stream_fut).map(|(item, stream)| {
599            *stream_fut = stream.into_future();
600            item
601        })
602    }
603
604    // Create roughly over 2k bytes ScanResult
605    fn random_scan_result(rng: &mut ThreadRng) -> wlan_common::scan::ScanResult {
606        use wlan_common::security::SecurityDescriptor;
607
608        // TODO(https://fxbug.dev/42164451): Merge this with a similar function in wlancfg.
609        wlan_common::scan::ScanResult {
610            compatibility: match rng.random_range(0..4) {
611                0 => wlan_common::scan::Compatible::expect_ok([SecurityDescriptor::OPEN]),
612                1 => wlan_common::scan::Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
613                2 => wlan_common::scan::Compatible::expect_ok([
614                    SecurityDescriptor::WPA2_PERSONAL,
615                    SecurityDescriptor::WPA3_PERSONAL,
616                ]),
617                _ => Incompatible::unknown(),
618            },
619            timestamp: zx::MonotonicInstant::from_nanos(rng.random()),
620            bss_description: random_bss_description!(),
621        }
622    }
623}