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