1use 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 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 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 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 #[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 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 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 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 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 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 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 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 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 fn random_scan_result(rng: &mut ThreadRng) -> wlan_common::scan::ScanResult {
610 use wlan_common::security::SecurityDescriptor;
611
612 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}