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, 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 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 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 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 #[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 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 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 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 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 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 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 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 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 fn random_scan_result(rng: &mut ThreadRng) -> wlan_common::scan::ScanResult {
581 use wlan_common::security::SecurityDescriptor;
582
583 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}