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