Skip to main content

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        ClientSmeRequest::InstallApfPacketFilter { program, responder } => {
114            let receiver = sme.lock().install_apf_packet_filter(program);
115            let resp = match receiver.await {
116                Ok(result) => result,
117                Err(_) => Err(zx::sys::ZX_ERR_CANCELED),
118            };
119            responder.send(resp).unwrap_or_else(|e| error!("Error sending response: {:?}", e));
120            Ok(())
121        }
122        ClientSmeRequest::ReadApfPacketFilterData { responder } => {
123            let receiver = sme.lock().read_apf_packet_filter_data();
124            let resp = match receiver.await {
125                Ok(result) => result.map(|r| r.memory),
126                Err(_) => Err(zx::sys::ZX_ERR_CANCELED),
127            };
128            responder
129                .send(resp.as_ref().map(|m| &m[..]).map_err(|e| *e))
130                .unwrap_or_else(|e| error!("Error sending response: {:?}", e));
131            Ok(())
132        }
133        ClientSmeRequest::SetApfPacketFilterEnabled { enabled, responder } => {
134            let receiver = sme.lock().set_apf_packet_filter_enabled(enabled);
135            let resp = match receiver.await {
136                Ok(result) => result,
137                Err(_) => Err(zx::sys::ZX_ERR_CANCELED),
138            };
139            responder.send(resp).unwrap_or_else(|e| error!("Error sending response: {:?}", e));
140            Ok(())
141        }
142        ClientSmeRequest::GetApfPacketFilterEnabled { responder } => {
143            let receiver = sme.lock().get_apf_packet_filter_enabled();
144            let resp = match receiver.await {
145                Ok(result) => result.map(|r| r.enabled),
146                Err(_) => Err(zx::sys::ZX_ERR_CANCELED),
147            };
148            responder
149                .send(resp.as_ref().map(|e| *e).map_err(|e| *e))
150                .unwrap_or_else(|e| error!("Error sending response: {:?}", e));
151            Ok(())
152        }
153    }
154}
155
156async fn handle_telemetry_fidl_request(
157    sme: &Mutex<Sme>,
158    request: TelemetryRequest,
159) -> Result<(), fidl::Error> {
160    match request {
161        TelemetryRequest::QueryTelemetrySupport { responder, .. } => {
162            let support_fut = sme.lock().query_telemetry_support();
163            let support = support_fut
164                .await
165                .map_err(|_| zx::Status::CONNECTION_ABORTED.into_raw())
166                .and_then(|result| result);
167            responder.send(support.as_ref().map_err(|e| *e))
168        }
169        TelemetryRequest::GetIfaceStats { responder, .. } => {
170            let iface_stats_fut = sme.lock().iface_stats();
171            let iface_stats = iface_stats_fut
172                .await
173                .map_err(|_| zx::Status::CONNECTION_ABORTED.into_raw())
174                .and_then(|stats| match stats {
175                    fidl_mlme::GetIfaceStatsResponse::Stats(stats) => Ok(stats),
176                    fidl_mlme::GetIfaceStatsResponse::ErrorStatus(err) => Err(err),
177                });
178            responder.send(iface_stats.as_ref().map_err(|e| *e))
179        }
180        TelemetryRequest::GetHistogramStats { responder, .. } => {
181            let histogram_stats_fut = sme.lock().histogram_stats();
182            let histogram_stats = histogram_stats_fut
183                .await
184                .map_err(|_| zx::Status::CONNECTION_ABORTED.into_raw())
185                .and_then(|stats| match stats {
186                    fidl_mlme::GetIfaceHistogramStatsResponse::Stats(stats) => Ok(stats),
187                    fidl_mlme::GetIfaceHistogramStatsResponse::ErrorStatus(err) => Err(err),
188                });
189            responder.send(histogram_stats.as_ref().map_err(|e| *e))
190        }
191        TelemetryRequest::GetSignalReport { responder, .. } => {
192            let signal_report_fut = sme.lock().signal_report();
193            let signal_report = signal_report_fut
194                .await
195                .map_err(|_| zx::Status::CONNECTION_ABORTED.into_raw())
196                .and_then(|result| result);
197            responder.send(signal_report.as_ref().map_err(|e| *e))
198        }
199        TelemetryRequest::CloneInspectVmo { responder } => {
200            let inspect_vmo =
201                sme.lock().on_clone_inspect_vmo().ok_or_else(|| zx::Status::INTERNAL.into_raw());
202            responder.send(inspect_vmo)
203        }
204    }
205}
206
207async fn scan(
208    sme: &Mutex<Sme>,
209    request: fidl_sme::ScanRequest,
210    responder: impl FnOnce(
211        Result<Vec<fidl_sme::ScanResult>, fidl_sme::ScanErrorCode>,
212    ) -> Result<(), anyhow::Error>,
213) -> Result<(), anyhow::Error> {
214    let receiver = sme.lock().on_scan_command(request);
215    let receive_result = match receiver.await {
216        Ok(receive_result) => receive_result,
217        Err(e) => {
218            error!("Scan receiver error: {:?}", e);
219            responder(Err(fidl_sme::ScanErrorCode::InternalError))?;
220            return Ok(());
221        }
222    };
223
224    match receive_result {
225        Ok(scan_results) => {
226            let results = scan_results.into_iter().map(Into::into).collect::<Vec<_>>();
227            responder(Ok(results))
228        }
229        Err(mlme_scan_result_code) => {
230            let scan_error_code = match mlme_scan_result_code {
231                fidl_mlme::ScanResultCode::Success | fidl_mlme::ScanResultCode::InvalidArgs => {
232                    error!("Internal scan error: {:?}", mlme_scan_result_code);
233                    fidl_sme::ScanErrorCode::InternalError
234                }
235                fidl_mlme::ScanResultCode::NotSupported => fidl_sme::ScanErrorCode::NotSupported,
236                fidl_mlme::ScanResultCode::InternalError => {
237                    fidl_sme::ScanErrorCode::InternalMlmeError
238                }
239                fidl_mlme::ScanResultCode::ShouldWait => fidl_sme::ScanErrorCode::ShouldWait,
240                fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware => {
241                    fidl_sme::ScanErrorCode::CanceledByDriverOrFirmware
242                }
243            };
244            responder(Err(scan_error_code))
245        }
246    }?;
247    Ok(())
248}
249
250async fn connect(
251    sme: &Mutex<Sme>,
252    txn: Option<ServerEnd<fidl_sme::ConnectTransactionMarker>>,
253    req: fidl_sme::ConnectRequest,
254) -> Result<(), anyhow::Error> {
255    #[allow(clippy::manual_map, reason = "mass allow for https://fxbug.dev/381896734")]
256    let handle = match txn {
257        None => None,
258        Some(txn) => Some(txn.into_stream().control_handle()),
259    };
260    let connect_txn_stream = sme.lock().on_connect_command(req);
261    serve_connect_txn_stream(handle, connect_txn_stream).await?;
262    Ok(())
263}
264
265async fn serve_connect_txn_stream(
266    handle: Option<fidl_sme::ConnectTransactionControlHandle>,
267    mut connect_txn_stream: ConnectTransactionStream,
268) -> Result<(), anyhow::Error> {
269    if let Some(handle) = handle {
270        loop {
271            match connect_txn_stream.next().await {
272                Some(event) => match event {
273                    ConnectTransactionEvent::OnConnectResult { result, is_reconnect } => {
274                        let connect_result = convert_connect_result(&result, is_reconnect);
275                        handle.send_on_connect_result(&connect_result)
276                    }
277                    ConnectTransactionEvent::OnRoamResult { result } => {
278                        let roam_result = convert_roam_result(&result);
279                        handle.send_on_roam_result(&roam_result)
280                    }
281                    ConnectTransactionEvent::OnDisconnect { info } => {
282                        handle.send_on_disconnect(&info)
283                    }
284                    ConnectTransactionEvent::OnSignalReport { ind } => {
285                        handle.send_on_signal_report(&ind)
286                    }
287                    ConnectTransactionEvent::OnChannelSwitched { info } => {
288                        handle.send_on_channel_switched(&info)
289                    }
290                }?,
291                // SME has dropped the ConnectTransaction endpoint, likely due to a disconnect.
292                None => return Ok(()),
293            }
294        }
295    }
296    Ok(())
297}
298
299fn roam(sme: &Mutex<Sme>, req: fidl_sme::RoamRequest) {
300    sme.lock().on_roam_command(req);
301}
302
303fn disconnect(
304    sme: &Mutex<Sme>,
305    policy_disconnect_reason: fidl_sme::UserDisconnectReason,
306    responder: fidl_sme::ClientSmeDisconnectResponder,
307) {
308    sme.lock().on_disconnect_command(policy_disconnect_reason, responder);
309}
310
311fn status(sme: &Mutex<Sme>) -> fidl_sme::ClientStatusResponse {
312    sme.lock().status().into()
313}
314
315async fn wmm_status(
316    sme: &Mutex<Sme>,
317    responder: fidl_sme::ClientSmeWmmStatusResponder,
318) -> Result<(), fidl::Error> {
319    let receiver = sme.lock().wmm_status();
320    let wmm_status = match receiver.await {
321        Ok(result) => result,
322        Err(_) => Err(zx::sys::ZX_ERR_CANCELED),
323    };
324    responder.send(wmm_status.as_ref().map_err(|e| *e))
325}
326
327async fn set_mac_address(
328    sme: &Mutex<Sme>,
329    mac_addr: [u8; 6],
330    responder: fidl_sme::ClientSmeSetMacAddressResponder,
331) -> Result<(), fidl::Error> {
332    let receiver = sme.lock().set_mac_address(mac_addr);
333    let resp = match receiver.await {
334        Ok(result) => result,
335        Err(_) => Err(zx::sys::ZX_ERR_CANCELED),
336    };
337    responder.send(resp)
338}
339
340fn convert_connect_result(result: &ConnectResult, is_reconnect: bool) -> fidl_sme::ConnectResult {
341    let (code, is_credential_rejected) = match result {
342        ConnectResult::Success => (fidl_ieee80211::StatusCode::Success, false),
343        ConnectResult::Canceled => (fidl_ieee80211::StatusCode::Canceled, false),
344        ConnectResult::Failed(failure) => {
345            (failure.status_code(), failure.likely_due_to_credential_rejected())
346        }
347    };
348    fidl_sme::ConnectResult { code, is_credential_rejected, is_reconnect }
349}
350
351fn convert_roam_result(result: &RoamResult) -> fidl_sme::RoamResult {
352    match result {
353        RoamResult::Success(bss) => {
354            let bss_description = Some(Box::new(BssDescriptionFidl::from(*bss.clone())));
355            fidl_sme::RoamResult {
356                bssid: bss.bssid.to_array(),
357                status_code: fidl_ieee80211::StatusCode::Success,
358                // Must always be false on roam success.
359                original_association_maintained: false,
360                bss_description,
361                disconnect_info: None,
362                is_credential_rejected: false,
363            }
364        }
365        RoamResult::Failed(failure) => {
366            #[allow(clippy::manual_map, reason = "mass allow for https://fxbug.dev/381896734")]
367            fidl_sme::RoamResult {
368                bssid: failure.selected_bssid.to_array(),
369                status_code: failure.status_code,
370                // Current implementation assumes that all roam attempts incur disassociation from the
371                // original BSS. When this changes (e.g. due to Fast BSS Transition support), this
372                // hard-coded field should be set from the RoamResult enum.
373                original_association_maintained: false,
374                bss_description: match &failure.selected_bss {
375                    Some(bss) => Some(Box::new(bss.clone().into())),
376                    None => None,
377                },
378                disconnect_info: Some(Box::new(failure.disconnect_info)),
379                is_credential_rejected: failure.likely_due_to_credential_rejected(),
380            }
381        }
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use crate::client::{ConnectFailure, EstablishRsnaFailure, EstablishRsnaFailureReason};
389    use crate::test_utils;
390    use assert_matches::assert_matches;
391    use fidl::endpoints::create_proxy_and_stream;
392    use fidl_fuchsia_wlan_mlme::ScanResultCode;
393    use fidl_fuchsia_wlan_sme::{self as fidl_sme};
394    use futures::stream::StreamFuture;
395    use futures::task::Poll;
396    use rand::Rng;
397    use rand::prelude::ThreadRng;
398    use std::pin::pin;
399    use test_case::test_case;
400    use wlan_common::random_bss_description;
401    use wlan_common::scan::{self, Incompatible};
402    use wlan_rsn::auth;
403    use {fidl_fuchsia_wlan_internal as fidl_internal, fuchsia_async as fasync};
404
405    #[test]
406    fn test_convert_connect_result() {
407        assert_eq!(
408            convert_connect_result(&ConnectResult::Success, false),
409            fidl_sme::ConnectResult {
410                code: fidl_ieee80211::StatusCode::Success,
411                is_credential_rejected: false,
412                is_reconnect: false,
413            }
414        );
415        assert_eq!(
416            convert_connect_result(&ConnectResult::Canceled, true),
417            fidl_sme::ConnectResult {
418                code: fidl_ieee80211::StatusCode::Canceled,
419                is_credential_rejected: false,
420                is_reconnect: true,
421            }
422        );
423        let connect_result =
424            ConnectResult::Failed(ConnectFailure::ScanFailure(ScanResultCode::ShouldWait));
425        assert_eq!(
426            convert_connect_result(&connect_result, false),
427            fidl_sme::ConnectResult {
428                code: fidl_ieee80211::StatusCode::Canceled,
429                is_credential_rejected: false,
430                is_reconnect: false,
431            }
432        );
433
434        let connect_result =
435            ConnectResult::Failed(ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
436                auth_method: Some(auth::MethodName::Psk),
437                reason: EstablishRsnaFailureReason::InternalError,
438            }));
439        assert_eq!(
440            convert_connect_result(&connect_result, false),
441            fidl_sme::ConnectResult {
442                code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
443                is_credential_rejected: false,
444                is_reconnect: false,
445            }
446        );
447
448        let connect_result =
449            ConnectResult::Failed(ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
450                auth_method: Some(auth::MethodName::Psk),
451                reason: EstablishRsnaFailureReason::RsnaResponseTimeout(
452                    wlan_rsn::Error::LikelyWrongCredential,
453                ),
454            }));
455        assert_eq!(
456            convert_connect_result(&connect_result, false),
457            fidl_sme::ConnectResult {
458                code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
459                is_credential_rejected: true,
460                is_reconnect: false,
461            }
462        );
463
464        let connect_result =
465            ConnectResult::Failed(ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
466                auth_method: Some(auth::MethodName::Psk),
467                reason: EstablishRsnaFailureReason::RsnaCompletionTimeout(
468                    wlan_rsn::Error::LikelyWrongCredential,
469                ),
470            }));
471        assert_eq!(
472            convert_connect_result(&connect_result, false),
473            fidl_sme::ConnectResult {
474                code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
475                is_credential_rejected: true,
476                is_reconnect: false,
477            }
478        );
479
480        let connect_result =
481            ConnectResult::Failed(ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
482                auth_method: Some(auth::MethodName::Psk),
483                reason: EstablishRsnaFailureReason::RsnaCompletionTimeout(
484                    wlan_rsn::Error::MissingGtkProvider,
485                ),
486            }));
487        assert_eq!(
488            convert_connect_result(&connect_result, false),
489            fidl_sme::ConnectResult {
490                code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
491                is_credential_rejected: false,
492                is_reconnect: false,
493            }
494        );
495
496        let connect_result =
497            ConnectResult::Failed(ConnectFailure::ScanFailure(ScanResultCode::InternalError));
498        assert_eq!(
499            convert_connect_result(&connect_result, false),
500            fidl_sme::ConnectResult {
501                code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
502                is_credential_rejected: false,
503                is_reconnect: false,
504            }
505        );
506    }
507
508    // TODO(https://fxbug.dev/42164611): There is no test coverage for consistency between MLME scan results
509    // and SME scan results produced by wlanstack. In particular, the timestamp_nanos field
510    // of fidl_mlme::ScanResult is dropped in SME, and no tests reveal this problem.
511
512    #[test_case(1, true; "with 1 result")]
513    #[test_case(2, true; "with 2 results")]
514    #[test_case(30, true; "with 30 results")]
515    #[test_case(4000, true; "with 4000 results")]
516    #[test_case(50000, false; "with 50000 results")]
517    #[test_case(100000, false; "with 100000 results")]
518    fn scan_results_are_effectively_unbounded(number_of_scan_results: usize, randomize: bool) {
519        let mut exec = fasync::TestExecutor::new();
520        let (client_sme_proxy, mut client_sme_stream) =
521            create_proxy_and_stream::<fidl_sme::ClientSmeMarker>();
522
523        // Request scan
524        async fn request_and_collect_result(
525            client_sme_proxy: &fidl_sme::ClientSmeProxy,
526        ) -> fidl_sme::ClientSmeScanResult {
527            client_sme_proxy
528                .scan(&fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}))
529                .await
530                .expect("FIDL request failed")
531        }
532
533        let result_fut = request_and_collect_result(&client_sme_proxy);
534        let mut result_fut = pin!(result_fut);
535
536        assert_matches!(exec.run_until_stalled(&mut result_fut), Poll::Pending);
537
538        // Generate and send scan results
539        let mut rng = rand::rng();
540        let scan_result_list = if randomize {
541            (0..number_of_scan_results).map(|_| random_scan_result(&mut rng).into()).collect()
542        } else {
543            vec![random_scan_result(&mut rng).into(); number_of_scan_results]
544        };
545        assert_matches!(exec.run_until_stalled(&mut client_sme_stream.next()),
546                        Poll::Ready(Some(Ok(fidl_sme::ClientSmeRequest::Scan {
547                            req: _, responder,
548                        }))) => {
549                            let vmo = write_vmo(scan_result_list.clone()).expect("failed to write VMO");
550                            responder.send(Ok(vmo)).expect("failed to send scan results");
551                        }
552        );
553
554        // Verify scan results
555        assert_matches!(exec.run_until_stalled(&mut result_fut), Poll::Ready(Ok(vmo)) => {
556            assert_eq!(scan_result_list, scan::read_vmo(vmo).expect("failed to read VMO"));
557        })
558    }
559
560    #[test]
561    fn test_serve_connect_txn_stream() {
562        let mut exec = fasync::TestExecutor::new();
563
564        let (sme_proxy, sme_connect_txn_stream) = mpsc::unbounded();
565        let (fidl_client_proxy, fidl_connect_txn_stream) =
566            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
567        let fidl_client_fut = fidl_client_proxy.take_event_stream().into_future();
568        let mut fidl_client_fut = pin!(fidl_client_fut);
569        let fidl_connect_txn_handle = fidl_connect_txn_stream.control_handle();
570
571        let test_fut =
572            serve_connect_txn_stream(Some(fidl_connect_txn_handle), sme_connect_txn_stream);
573        let mut test_fut = pin!(test_fut);
574
575        // Test sending OnConnectResult
576        sme_proxy
577            .unbounded_send(ConnectTransactionEvent::OnConnectResult {
578                result: ConnectResult::Success,
579                is_reconnect: true,
580            })
581            .expect("expect sending ConnectTransactionEvent to succeed");
582        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Pending);
583        let event = assert_matches!(poll_stream_fut(&mut exec, &mut fidl_client_fut), Poll::Ready(Some(Ok(event))) => event);
584        assert_matches!(
585            event,
586            fidl_sme::ConnectTransactionEvent::OnConnectResult {
587                result: fidl_sme::ConnectResult {
588                    code: fidl_ieee80211::StatusCode::Success,
589                    is_credential_rejected: false,
590                    is_reconnect: true,
591                }
592            }
593        );
594
595        // Test sending OnDisconnect
596        let input_info = fidl_sme::DisconnectInfo {
597            is_sme_reconnecting: true,
598            disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
599                reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
600                mlme_event_name: fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
601            }),
602        };
603        sme_proxy
604            .unbounded_send(ConnectTransactionEvent::OnDisconnect { info: input_info })
605            .expect("expect sending ConnectTransactionEvent to succeed");
606        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Pending);
607        let event = assert_matches!(poll_stream_fut(&mut exec, &mut fidl_client_fut), Poll::Ready(Some(Ok(event))) => event);
608        assert_matches!(event, fidl_sme::ConnectTransactionEvent::OnDisconnect { info: output_info } => {
609            assert_eq!(input_info, output_info);
610        });
611
612        // Test sending OnSignalReport
613        let input_ind = fidl_internal::SignalReportIndication { rssi_dbm: -40, snr_db: 30 };
614        sme_proxy
615            .unbounded_send(ConnectTransactionEvent::OnSignalReport { ind: input_ind })
616            .expect("expect sending ConnectTransactionEvent to succeed");
617        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Pending);
618        let event = assert_matches!(poll_stream_fut(&mut exec, &mut fidl_client_fut), Poll::Ready(Some(Ok(event))) => event);
619        assert_matches!(event, fidl_sme::ConnectTransactionEvent::OnSignalReport { ind } => {
620            assert_eq!(input_ind, ind);
621        });
622
623        // Test sending OnChannelSwitched
624        let input_info = fidl_internal::ChannelSwitchInfo { new_channel: 8 };
625        sme_proxy
626            .unbounded_send(ConnectTransactionEvent::OnChannelSwitched { info: input_info })
627            .expect("expect sending ConnectTransactionEvent to succeed");
628        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Pending);
629        let event = assert_matches!(poll_stream_fut(&mut exec, &mut fidl_client_fut), Poll::Ready(Some(Ok(event))) => event);
630        assert_matches!(event, fidl_sme::ConnectTransactionEvent::OnChannelSwitched { info } => {
631            assert_eq!(input_info, info);
632        });
633
634        // When SME proxy is dropped, the fut should terminate
635        std::mem::drop(sme_proxy);
636        assert_matches!(exec.run_until_stalled(&mut test_fut), Poll::Ready(Ok(())));
637    }
638
639    fn poll_stream_fut<S: Stream + std::marker::Unpin>(
640        exec: &mut fasync::TestExecutor,
641        stream_fut: &mut StreamFuture<S>,
642    ) -> Poll<Option<S::Item>> {
643        exec.run_until_stalled(stream_fut).map(|(item, stream)| {
644            *stream_fut = stream.into_future();
645            item
646        })
647    }
648
649    // Create roughly over 2k bytes ScanResult
650    fn random_scan_result(rng: &mut ThreadRng) -> wlan_common::scan::ScanResult {
651        use wlan_common::security::SecurityDescriptor;
652
653        // TODO(https://fxbug.dev/42164451): Merge this with a similar function in wlancfg.
654        wlan_common::scan::ScanResult {
655            compatibility: match rng.random_range(0..4) {
656                0 => wlan_common::scan::Compatible::expect_ok([SecurityDescriptor::OPEN]),
657                1 => wlan_common::scan::Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
658                2 => wlan_common::scan::Compatible::expect_ok([
659                    SecurityDescriptor::WPA2_PERSONAL,
660                    SecurityDescriptor::WPA3_PERSONAL,
661                ]),
662                _ => Incompatible::unknown(),
663            },
664            timestamp: zx::MonotonicInstant::from_nanos(rng.random()),
665            bss_description: random_bss_description!(),
666        }
667    }
668
669    #[test]
670    fn test_handle_fidl_request_apf() {
671        let mut exec = fasync::TestExecutor::new();
672        let inspector = fuchsia_inspect::Inspector::default();
673        let (sme, _mlme_sink, mut mlme_stream, _time_stream) = client_sme::ClientSme::new(
674            client_sme::ClientConfig::default(),
675            test_utils::fake_device_info([0; 6].into()),
676            inspector.clone(),
677            inspector.root().create_child("sme"),
678            wlan_common::test_utils::fake_features::fake_security_support(),
679            wlan_common::test_utils::fake_features::fake_spectrum_management_support_empty(),
680        );
681        let sme = Mutex::new(sme);
682
683        // Test InstallApfPacketFilter
684        let (proxy, stream) = create_proxy_and_stream::<fidl_sme::ClientSmeMarker>();
685        let program = vec![1, 2, 3];
686        let mut install_fut = proxy.install_apf_packet_filter(&program);
687        let mut stream = pin!(stream);
688
689        // Run handle_fidl_request for the install request
690        assert_matches!(exec.run_until_stalled(&mut stream.next()), Poll::Ready(Some(Ok(req))) => {
691            let mut handle_fut = pin!(handle_fidl_request(&sme, req));
692            assert_matches!(exec.run_until_stalled(&mut handle_fut), Poll::Pending);
693
694            // Forward from SME to MLME
695            assert_matches!(exec.run_until_stalled(&mut mlme_stream.next()), Poll::Ready(Some(crate::MlmeRequest::InstallApfPacketFilter(req, responder))) => {
696                assert_eq!(req.program, program);
697                responder.respond(Ok(()));
698            });
699
700            assert_matches!(exec.run_until_stalled(&mut handle_fut), Poll::Ready(Ok(())));
701        });
702        assert_matches!(exec.run_until_stalled(&mut install_fut), Poll::Ready(Ok(Ok(()))));
703
704        // Test ReadApfPacketFilterData
705        let mut read_fut = proxy.read_apf_packet_filter_data();
706        assert_matches!(exec.run_until_stalled(&mut stream.next()), Poll::Ready(Some(Ok(req))) => {
707            let mut handle_fut = pin!(handle_fidl_request(&sme, req));
708            assert_matches!(exec.run_until_stalled(&mut handle_fut), Poll::Pending);
709
710            // Forward from SME to MLME
711            assert_matches!(exec.run_until_stalled(&mut mlme_stream.next()), Poll::Ready(Some(crate::MlmeRequest::ReadApfPacketFilterData(responder))) => {
712                responder.respond(Ok(fidl_mlme::MlmeReadApfPacketFilterDataResponse {
713                    memory: vec![4, 5, 6],
714                }));
715            });
716
717            assert_matches!(exec.run_until_stalled(&mut handle_fut), Poll::Ready(Ok(())));
718        });
719        assert_matches!(exec.run_until_stalled(&mut read_fut), Poll::Ready(Ok(Ok(data))) => {
720            assert_eq!(data, vec![4, 5, 6]);
721        });
722
723        // Test SetApfPacketFilterEnabled
724        let mut set_enabled_fut = proxy.set_apf_packet_filter_enabled(true);
725        assert_matches!(exec.run_until_stalled(&mut stream.next()), Poll::Ready(Some(Ok(req))) => {
726            let mut handle_fut = pin!(handle_fidl_request(&sme, req));
727            assert_matches!(exec.run_until_stalled(&mut handle_fut), Poll::Pending);
728
729            // Forward from SME to MLME
730            assert_matches!(exec.run_until_stalled(&mut mlme_stream.next()), Poll::Ready(Some(crate::MlmeRequest::SetApfPacketFilterEnabled(req, responder))) => {
731                assert!(req.enabled);
732                responder.respond(Ok(()));
733            });
734
735            assert_matches!(exec.run_until_stalled(&mut handle_fut), Poll::Ready(Ok(())));
736        });
737        assert_matches!(exec.run_until_stalled(&mut set_enabled_fut), Poll::Ready(Ok(Ok(()))));
738
739        // Test GetApfPacketFilterEnabled
740        let mut get_enabled_fut = proxy.get_apf_packet_filter_enabled();
741        assert_matches!(exec.run_until_stalled(&mut stream.next()), Poll::Ready(Some(Ok(req))) => {
742            let mut handle_fut = pin!(handle_fidl_request(&sme, req));
743            assert_matches!(exec.run_until_stalled(&mut handle_fut), Poll::Pending);
744
745            // Forward from SME to MLME
746            assert_matches!(exec.run_until_stalled(&mut mlme_stream.next()), Poll::Ready(Some(crate::MlmeRequest::GetApfPacketFilterEnabled(responder))) => {
747                responder.respond(Ok(fidl_mlme::MlmeGetApfPacketFilterEnabledResponse {
748                    enabled: true,
749                }));
750            });
751
752            assert_matches!(exec.run_until_stalled(&mut handle_fut), Poll::Ready(Ok(())));
753        });
754        assert_matches!(exec.run_until_stalled(&mut get_enabled_fut), Poll::Ready(Ok(Ok(true))));
755    }
756}