Skip to main content

wlancfg_lib/client/scan/
fidl_conversion.rs

1// Copyright 2022 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::types;
6use anyhow::{Error, format_err};
7use fidl::prelude::*;
8use fidl_fuchsia_wlan_policy as fidl_policy;
9use fidl_fuchsia_wlan_sme as fidl_sme;
10use futures::stream::TryStreamExt;
11use ieee80211::MacAddrBytes;
12use log::{debug, info};
13use measure_tape_for_scan_result::Measurable as _;
14
15// TODO(https://fxbug.dev/42160765): Remove this.
16// Size of FIDL message header and FIDL error-wrapped vector header
17const FIDL_HEADER_AND_ERR_WRAPPED_VEC_HEADER_SIZE: usize = 56;
18
19/// Convert the protection type we receive from the SME in scan results to the Policy layer
20/// security type. This function should only be used when converting to results for the public
21/// FIDL API, and not for internal use within Policy, where we should prefer the detailed SME
22/// security types.
23fn fidl_security_from_sme_protection(
24    protection: fidl_sme::Protection,
25    wpa3_supported: bool,
26) -> Option<fidl_policy::SecurityType> {
27    use fidl_policy::SecurityType;
28    use fidl_sme::Protection::*;
29    match protection {
30        Wpa3Enterprise | Wpa3Personal | Wpa2Wpa3Personal => {
31            Some(if wpa3_supported { SecurityType::Wpa3 } else { SecurityType::Wpa2 })
32        }
33        Wpa2Enterprise
34        | Wpa2Personal
35        | Wpa1Wpa2Personal
36        | Wpa2PersonalTkipOnly
37        | Wpa1Wpa2PersonalTkipOnly => Some(SecurityType::Wpa2),
38        Wpa1 => Some(SecurityType::Wpa),
39        Wep => Some(SecurityType::Wep),
40        // TODO(https://fxbug.dev/462514157): Map Owe and OpenOweTransition to correct security types
41        Owe => Some(SecurityType::None),
42        OpenOweTransition => Some(SecurityType::None),
43        Open => Some(SecurityType::None),
44        Unknown => None,
45    }
46}
47
48#[allow(clippy::ptr_arg, reason = "mass allow for https://fxbug.dev/381896734")]
49pub fn scan_result_to_policy_scan_result(
50    internal_results: &Vec<types::ScanResult>,
51) -> Vec<fidl_policy::ScanResult> {
52    let scan_results: Vec<fidl_policy::ScanResult> = internal_results
53        .iter()
54        .filter_map(|internal| {
55            // Determine wpa3 support from the scan result entries.
56            let wpa3_supported = internal.entries.iter().any(|bss| {
57                if let Ok(compatible) = &bss.compatibility {
58                    compatible
59                        .mutual_security_protocols()
60                        .contains(&wlan_common::security::SecurityDescriptor::WPA3_PERSONAL)
61                } else {
62                    false
63                }
64            });
65
66            if let Some(security) =
67                fidl_security_from_sme_protection(internal.security_type_detailed, wpa3_supported)
68            {
69                Some(fidl_policy::ScanResult {
70                    id: Some(fidl_policy::NetworkIdentifier {
71                        ssid: internal.ssid.to_vec(),
72                        type_: security,
73                    }),
74                    entries: Some(
75                        internal
76                            .entries
77                            .iter()
78                            .map(|input| {
79                                // Get the frequency. On error, default to Some(0) rather than None
80                                // to protect against consumer code that expects this field to
81                                // always be set.
82                                let frequency = input.channel.get_center_freq().unwrap_or(0);
83                                fidl_policy::Bss {
84                                    bssid: Some(input.bssid.to_array()),
85                                    rssi: Some(input.signal.rssi_dbm),
86                                    frequency: Some(frequency.into()), // u16.into() -> u32
87                                    timestamp_nanos: Some(input.timestamp.into_nanos()),
88                                    ..Default::default()
89                                }
90                            })
91                            .collect(),
92                    ),
93                    compatibility: Some(internal.compatibility),
94                    ..Default::default()
95                })
96            } else {
97                debug!(
98                    "Unknown security type present in scan results ({} BSSs)",
99                    internal.entries.len()
100                );
101                None
102            }
103        })
104        .collect();
105
106    scan_results
107}
108
109/// Send batches of results to the output iterator when getNext() is called on it.
110/// Send empty batch and close the channel when no results are remaining.
111pub async fn send_scan_results_over_fidl(
112    output_iterator: fidl::endpoints::ServerEnd<fidl_policy::ScanResultIteratorMarker>,
113    mut scan_results: &[fidl_policy::ScanResult],
114) -> Result<(), Error> {
115    // Wait to get a request for a chunk of scan results
116    let (mut stream, ctrl) = output_iterator.into_stream_and_control_handle();
117    let mut sent_some_results = false;
118
119    // Verify consumer is expecting results before each batch
120    loop {
121        if let Some(fidl_policy::ScanResultIteratorRequest::GetNext { responder }) =
122            stream.try_next().await?
123        {
124            let mut bytes_used = FIDL_HEADER_AND_ERR_WRAPPED_VEC_HEADER_SIZE;
125            let mut result_count = 0;
126            for result in scan_results {
127                bytes_used += result.measure().num_bytes;
128                if bytes_used > zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize {
129                    if result_count == 0 {
130                        return Err(format_err!("Single scan result too large to send via FIDL"));
131                    }
132                    // This result will not fit. Send batch and continue.
133                    break;
134                }
135                result_count += 1;
136            }
137            // It's ok to slice this by index, since we've just calculated the `result_count` above
138            // by iterating through elements of scan_results[]
139            #[expect(clippy::indexing_slicing)]
140            responder.send(Ok(&scan_results[..result_count]))?;
141            // It's ok to slice this by index, since we've just calculated the `result_count` above
142            // by iterating through elements of scan_results[]
143            #[expect(clippy::indexing_slicing)]
144            let remaining_results = &scan_results[result_count..];
145            scan_results = remaining_results;
146            sent_some_results = true;
147
148            // Guarantees empty batch is sent before channel is closed.
149            if result_count == 0 {
150                ctrl.shutdown();
151                return Ok(());
152            }
153        } else {
154            // This will happen if the iterator request stream was closed and we expected to send
155            // another response.
156            if sent_some_results {
157                // Some consumers may not care about all scan results, e.g. if they find the
158                // particular network they were looking for. This is not an error.
159                debug!("Scan result consumer closed channel before consuming all scan results");
160                return Ok(());
161            }
162            return Err(format_err!("Peer closed channel before receiving any scan results"));
163        }
164    }
165}
166
167/// On the next request for results, send an error to the output iterator and
168/// shut it down.
169pub async fn send_scan_error_over_fidl(
170    output_iterator: fidl::endpoints::ServerEnd<fidl_policy::ScanResultIteratorMarker>,
171    error_code: types::ScanError,
172) -> Result<(), fidl::Error> {
173    // Wait to get a request for a chunk of scan results
174    let (mut stream, ctrl) = output_iterator.into_stream_and_control_handle();
175    if let Some(req) = stream.try_next().await? {
176        let fidl_policy::ScanResultIteratorRequest::GetNext { responder } = req;
177        responder.send(Err(error_code))?;
178        ctrl.shutdown();
179    } else {
180        // This will happen if the iterator request stream was closed and we expected to send
181        // another response.
182        info!("Peer closed channel for getting scan results unexpectedly");
183    }
184    Ok(())
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use assert_matches::assert_matches;
191    use fidl_fuchsia_wlan_ieee80211::WlanBand::TwoGhz;
192    use fuchsia_async as fasync;
193    use futures::task::Poll;
194    use std::pin::pin;
195    use wlan_common::random_fidl_bss_description;
196    use wlan_common::scan::{Compatible, Incompatible};
197    use wlan_common::security::SecurityDescriptor;
198
199    fn generate_test_fidl_data() -> Vec<fidl_policy::ScanResult> {
200        const CENTER_FREQ_CHAN_1: u32 = 2412;
201        const CENTER_FREQ_CHAN_8: u32 = 2447;
202        const CENTER_FREQ_CHAN_11: u32 = 2462;
203        vec![
204            fidl_policy::ScanResult {
205                id: Some(fidl_policy::NetworkIdentifier {
206                    ssid: types::Ssid::try_from("duplicated ssid").unwrap().into(),
207                    type_: fidl_policy::SecurityType::Wpa3,
208                }),
209                entries: Some(vec![
210                    fidl_policy::Bss {
211                        bssid: Some([0, 0, 0, 0, 0, 0]),
212                        rssi: Some(0),
213                        frequency: Some(CENTER_FREQ_CHAN_1),
214                        timestamp_nanos: Some(zx::MonotonicInstant::get().into_nanos()),
215                        ..Default::default()
216                    },
217                    fidl_policy::Bss {
218                        bssid: Some([7, 8, 9, 10, 11, 12]),
219                        rssi: Some(13),
220                        frequency: Some(CENTER_FREQ_CHAN_11),
221                        timestamp_nanos: Some(zx::MonotonicInstant::get().into_nanos()),
222                        ..Default::default()
223                    },
224                ]),
225                compatibility: Some(fidl_policy::Compatibility::Supported),
226                ..Default::default()
227            },
228            fidl_policy::ScanResult {
229                id: Some(fidl_policy::NetworkIdentifier {
230                    ssid: types::Ssid::try_from("unique ssid").unwrap().into(),
231                    type_: fidl_policy::SecurityType::Wpa2,
232                }),
233                entries: Some(vec![fidl_policy::Bss {
234                    bssid: Some([1, 2, 3, 4, 5, 6]),
235                    rssi: Some(7),
236                    frequency: Some(CENTER_FREQ_CHAN_8),
237                    timestamp_nanos: Some(zx::MonotonicInstant::get().into_nanos()),
238                    ..Default::default()
239                }]),
240                compatibility: Some(fidl_policy::Compatibility::Supported),
241                ..Default::default()
242            },
243        ]
244    }
245
246    /// Generate a vector of FIDL scan results, each sized based on the input
247    /// vector parameter. Size, in bytes, must be greater than the baseline scan
248    /// result's size, measure below, and divisible into octets (by 8).
249    fn create_fidl_scan_results_from_size(
250        result_sizes: Vec<usize>,
251    ) -> Vec<fidl_policy::ScanResult> {
252        // Create a baseline result
253        let minimal_scan_result = fidl_policy::ScanResult {
254            id: Some(fidl_policy::NetworkIdentifier {
255                ssid: types::Ssid::empty().into(),
256                type_: fidl_policy::SecurityType::None,
257            }),
258            entries: Some(vec![]),
259            ..Default::default()
260        };
261        let minimal_result_size: usize = minimal_scan_result.measure().num_bytes;
262
263        // Create result with single entry
264        let mut scan_result_with_one_bss = minimal_scan_result.clone();
265        scan_result_with_one_bss.entries = Some(vec![fidl_policy::Bss::default()]);
266
267        // Size of each additional BSS entry to FIDL ScanResult
268        let empty_bss_entry_size: usize =
269            scan_result_with_one_bss.measure().num_bytes - minimal_result_size;
270
271        // Validate size is possible
272        if result_sizes.iter().any(|size| size < &minimal_result_size || !size.is_multiple_of(8)) {
273            panic!(
274                "Invalid size. Requested size must be larger than {minimal_result_size} minimum bytes and divisible into octets (by 8)"
275            );
276        }
277
278        let mut fidl_scan_results = vec![];
279        for size in result_sizes {
280            let mut scan_result = minimal_scan_result.clone();
281
282            let num_bss_for_ap = (size - minimal_result_size) / empty_bss_entry_size;
283            // Every 8 characters for SSID adds 8 bytes (1 octet).
284            let ssid_length =
285                (size - minimal_result_size) - (num_bss_for_ap * empty_bss_entry_size);
286
287            scan_result.id = Some(fidl_policy::NetworkIdentifier {
288                ssid: (0..ssid_length).map(|_| rand::random::<u8>()).collect(),
289                type_: fidl_policy::SecurityType::None,
290            });
291            scan_result.entries = Some(vec![fidl_policy::Bss::default(); num_bss_for_ap]);
292
293            // Validate result measures to expected size.
294            assert_eq!(scan_result.measure().num_bytes, size);
295
296            fidl_scan_results.push(scan_result);
297        }
298        fidl_scan_results
299    }
300
301    #[fuchsia::test]
302    fn scan_result_generate_from_size() {
303        let scan_results = create_fidl_scan_results_from_size(vec![112; 4]);
304        assert_eq!(scan_results.len(), 4);
305        assert!(scan_results.iter().all(|scan_result| scan_result.measure().num_bytes == 112));
306    }
307
308    #[fuchsia::test]
309    fn sme_protection_converts_to_policy_security() {
310        use super::fidl_policy::SecurityType;
311        use super::fidl_sme::Protection;
312        let wpa3_supported = true;
313        let wpa3_not_supported = false;
314        let test_pairs = vec![
315            // Below are pairs when WPA3 is supported.
316            (Protection::Wpa3Enterprise, wpa3_supported, Some(SecurityType::Wpa3)),
317            (Protection::Wpa3Personal, wpa3_supported, Some(SecurityType::Wpa3)),
318            (Protection::Wpa2Wpa3Personal, wpa3_supported, Some(SecurityType::Wpa3)),
319            (Protection::Wpa2Enterprise, wpa3_supported, Some(SecurityType::Wpa2)),
320            (Protection::Wpa2Personal, wpa3_supported, Some(SecurityType::Wpa2)),
321            (Protection::Wpa1Wpa2Personal, wpa3_supported, Some(SecurityType::Wpa2)),
322            (Protection::Wpa2PersonalTkipOnly, wpa3_supported, Some(SecurityType::Wpa2)),
323            (Protection::Wpa1Wpa2PersonalTkipOnly, wpa3_supported, Some(SecurityType::Wpa2)),
324            (Protection::Wpa1, wpa3_supported, Some(SecurityType::Wpa)),
325            (Protection::Wep, wpa3_supported, Some(SecurityType::Wep)),
326            // TODO(https://fxbug.dev/462514157): Map Owe and OpenOweTransition to correct security
327            // types.
328            (Protection::Owe, wpa3_supported, Some(SecurityType::None)),
329            (Protection::OpenOweTransition, wpa3_supported, Some(SecurityType::None)),
330            (Protection::Open, wpa3_supported, Some(SecurityType::None)),
331            (Protection::Unknown, wpa3_supported, None),
332            // Below are pairs when WPA3 is not supported.
333            (Protection::Wpa3Enterprise, wpa3_not_supported, Some(SecurityType::Wpa2)),
334            (Protection::Wpa3Personal, wpa3_not_supported, Some(SecurityType::Wpa2)),
335            (Protection::Wpa2Wpa3Personal, wpa3_not_supported, Some(SecurityType::Wpa2)),
336            (Protection::Wpa2Enterprise, wpa3_not_supported, Some(SecurityType::Wpa2)),
337            (Protection::Wpa2Personal, wpa3_not_supported, Some(SecurityType::Wpa2)),
338            (Protection::Wpa1Wpa2Personal, wpa3_not_supported, Some(SecurityType::Wpa2)),
339            (Protection::Wpa2PersonalTkipOnly, wpa3_not_supported, Some(SecurityType::Wpa2)),
340            (Protection::Wpa1Wpa2PersonalTkipOnly, wpa3_not_supported, Some(SecurityType::Wpa2)),
341            (Protection::Wpa1, wpa3_not_supported, Some(SecurityType::Wpa)),
342            (Protection::Wep, wpa3_not_supported, Some(SecurityType::Wep)),
343            // TODO(https://fxbug.dev/462514157): Map Owe and OpenOweTransition to correct security
344            // types.
345            (Protection::Owe, wpa3_not_supported, Some(SecurityType::None)),
346            (Protection::OpenOweTransition, wpa3_not_supported, Some(SecurityType::None)),
347            (Protection::Open, wpa3_not_supported, Some(SecurityType::None)),
348            (Protection::Unknown, wpa3_not_supported, None),
349        ];
350        for (input, wpa3_capable, output) in test_pairs {
351            assert_eq!(fidl_security_from_sme_protection(input, wpa3_capable), output);
352        }
353    }
354
355    #[fuchsia::test]
356    fn scan_results_converted_correctly() {
357        let fidl_aps = generate_test_fidl_data();
358        let internal_aps = vec![
359            types::ScanResult {
360                ssid: types::Ssid::try_from("duplicated ssid").unwrap(),
361                security_type_detailed: types::SecurityTypeDetailed::Wpa3Personal,
362                entries: vec![
363                    types::Bss {
364                        bssid: types::Bssid::from([0, 0, 0, 0, 0, 0]),
365                        signal: types::Signal { rssi_dbm: 0, snr_db: 1 },
366                        timestamp: zx::MonotonicInstant::from_nanos(
367                            fidl_aps[0].entries.as_ref().unwrap()[0].timestamp_nanos.unwrap(),
368                        ),
369                        channel: types::WlanChan::new(1, types::Cbw::Cbw20, TwoGhz),
370                        observation: types::ScanObservation::Passive,
371                        compatibility: Compatible::expect_ok([SecurityDescriptor::WPA3_PERSONAL]),
372                        bss_description: random_fidl_bss_description!(
373                            Wpa3,
374                            bssid: [0, 0, 0, 0, 0, 0],
375                            ssid: types::Ssid::try_from("duplicated ssid").unwrap(),
376                            rssi_dbm: 0,
377                            snr_db: 1,
378                            channel: types::WlanChan::new(1, types::Cbw::Cbw20, TwoGhz),
379                        )
380                        .into(),
381                    },
382                    types::Bss {
383                        bssid: types::Bssid::from([7, 8, 9, 10, 11, 12]),
384                        signal: types::Signal { rssi_dbm: 13, snr_db: 3 },
385                        timestamp: zx::MonotonicInstant::from_nanos(
386                            fidl_aps[0].entries.as_ref().unwrap()[1].timestamp_nanos.unwrap(),
387                        ),
388                        channel: types::WlanChan::new(11, types::Cbw::Cbw20, TwoGhz),
389                        observation: types::ScanObservation::Passive,
390                        compatibility: Incompatible::unknown(),
391                        bss_description: random_fidl_bss_description!(
392                            Wpa3,
393                            bssid: [7, 8, 9, 10, 11, 12],
394                            ssid: types::Ssid::try_from("duplicated ssid").unwrap(),
395                            rssi_dbm: 13,
396                            snr_db: 3,
397                            channel: types::WlanChan::new(11, types::Cbw::Cbw20, TwoGhz),
398                        )
399                        .into(),
400                    },
401                ],
402                compatibility: types::Compatibility::Supported,
403            },
404            types::ScanResult {
405                ssid: types::Ssid::try_from("unique ssid").unwrap(),
406                security_type_detailed: types::SecurityTypeDetailed::Wpa2Personal,
407                entries: vec![types::Bss {
408                    bssid: types::Bssid::from([1, 2, 3, 4, 5, 6]),
409                    signal: types::Signal { rssi_dbm: 7, snr_db: 2 },
410                    timestamp: zx::MonotonicInstant::from_nanos(
411                        fidl_aps[1].entries.as_ref().unwrap()[0].timestamp_nanos.unwrap(),
412                    ),
413                    channel: types::WlanChan::new(8, types::Cbw::Cbw20, TwoGhz),
414                    observation: types::ScanObservation::Passive,
415                    compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
416                    bss_description: random_fidl_bss_description!(
417                        Wpa2,
418                        bssid: [1, 2, 3, 4, 5, 6],
419                        ssid: types::Ssid::try_from("unique ssid").unwrap(),
420                        rssi_dbm: 7,
421                        snr_db: 2,
422                        channel: types::WlanChan::new(8, types::Cbw::Cbw20, TwoGhz),
423                    )
424                    .into(),
425                }],
426                compatibility: types::Compatibility::Supported,
427            },
428        ];
429        assert_eq!(fidl_aps, scan_result_to_policy_scan_result(&internal_aps));
430    }
431
432    #[fuchsia::test]
433    fn scan_results_converted_correctly_wpa3_unsupported() {
434        let mut fidl_aps = generate_test_fidl_data();
435        // Since WPA3 is not supported by the client in this test, the Wpa3Personal network
436        // should downgrade its reported security type to Wpa2.
437        fidl_aps[0].id.as_mut().unwrap().type_ = fidl_policy::SecurityType::Wpa2;
438
439        let internal_aps = vec![
440            types::ScanResult {
441                ssid: types::Ssid::try_from("duplicated ssid").unwrap(),
442                security_type_detailed: types::SecurityTypeDetailed::Wpa3Personal,
443                entries: vec![
444                    types::Bss {
445                        bssid: types::Bssid::from([0, 0, 0, 0, 0, 0]),
446                        signal: types::Signal { rssi_dbm: 0, snr_db: 1 },
447                        timestamp: zx::MonotonicInstant::from_nanos(
448                            fidl_aps[0].entries.as_ref().unwrap()[0].timestamp_nanos.unwrap(),
449                        ),
450                        channel: types::WlanChan::new(1, types::Cbw::Cbw20, TwoGhz),
451                        observation: types::ScanObservation::Passive,
452                        // Ensure WPA3 is omitted to simulate lack of WPA3 support
453                        compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
454                        bss_description: random_fidl_bss_description!(
455                            Wpa3,
456                            bssid: [0, 0, 0, 0, 0, 0],
457                            ssid: types::Ssid::try_from("duplicated ssid").unwrap(),
458                            rssi_dbm: 0,
459                            snr_db: 1,
460                            channel: types::WlanChan::new(1, types::Cbw::Cbw20, TwoGhz),
461                        )
462                        .into(),
463                    },
464                    types::Bss {
465                        bssid: types::Bssid::from([7, 8, 9, 10, 11, 12]),
466                        signal: types::Signal { rssi_dbm: 13, snr_db: 3 },
467                        timestamp: zx::MonotonicInstant::from_nanos(
468                            fidl_aps[0].entries.as_ref().unwrap()[1].timestamp_nanos.unwrap(),
469                        ),
470                        channel: types::WlanChan::new(11, types::Cbw::Cbw20, TwoGhz),
471                        observation: types::ScanObservation::Passive,
472                        compatibility: Incompatible::unknown(),
473                        bss_description: random_fidl_bss_description!(
474                            Wpa3,
475                            bssid: [7, 8, 9, 10, 11, 12],
476                            ssid: types::Ssid::try_from("duplicated ssid").unwrap(),
477                            rssi_dbm: 13,
478                            snr_db: 3,
479                            channel: types::WlanChan::new(11, types::Cbw::Cbw20, TwoGhz),
480                        )
481                        .into(),
482                    },
483                ],
484                compatibility: types::Compatibility::Supported,
485            },
486            types::ScanResult {
487                ssid: types::Ssid::try_from("unique ssid").unwrap(),
488                security_type_detailed: types::SecurityTypeDetailed::Wpa2Personal,
489                entries: vec![types::Bss {
490                    bssid: types::Bssid::from([1, 2, 3, 4, 5, 6]),
491                    signal: types::Signal { rssi_dbm: 7, snr_db: 2 },
492                    timestamp: zx::MonotonicInstant::from_nanos(
493                        fidl_aps[1].entries.as_ref().unwrap()[0].timestamp_nanos.unwrap(),
494                    ),
495                    channel: types::WlanChan::new(8, types::Cbw::Cbw20, TwoGhz),
496                    observation: types::ScanObservation::Passive,
497                    compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
498                    bss_description: random_fidl_bss_description!(
499                        Wpa2,
500                        bssid: [1, 2, 3, 4, 5, 6],
501                        ssid: types::Ssid::try_from("unique ssid").unwrap(),
502                        rssi_dbm: 7,
503                        snr_db: 2,
504                        channel: types::WlanChan::new(8, types::Cbw::Cbw20, TwoGhz),
505                    )
506                    .into(),
507                }],
508                compatibility: types::Compatibility::Supported,
509            },
510        ];
511        assert_eq!(fidl_aps, scan_result_to_policy_scan_result(&internal_aps));
512    }
513
514    // TODO(https://fxbug.dev/42131757): Separate test case for "empty final vector not consumed" vs "partial ap list"
515    // consumed.
516    #[fuchsia::test]
517    fn partial_scan_result_consumption_has_no_error() {
518        let mut exec = fasync::TestExecutor::new();
519        let scan_results = generate_test_fidl_data();
520
521        // Create an iterator and send scan results
522        let (iter, iter_server) = fidl::endpoints::create_proxy();
523        let send_fut = send_scan_results_over_fidl(iter_server, &scan_results);
524        let mut send_fut = pin!(send_fut);
525
526        // Request a chunk of scan results.
527        let mut output_iter_fut = iter.get_next();
528
529        // Send first chunk of scan results
530        assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
531
532        // Make sure the first chunk of results were delivered
533        assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
534            let results = result.expect("Failed to get next scan results").unwrap();
535            assert_eq!(results, scan_results);
536        });
537
538        // Close the channel without getting remaining results
539        // Note: as of the writing of this test, the "remaining results" are just the final message
540        // with an empty vector of networks that signify the end of results. That final empty vector
541        // is still considered part of the results, so this test successfully exercises the
542        // "partial results read" path.
543        drop(output_iter_fut);
544        drop(iter);
545
546        // This should not result in error, since some results were consumed
547        assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Ready(Ok(())));
548    }
549
550    #[fuchsia::test]
551    fn no_scan_result_consumption_has_error() {
552        let mut exec = fasync::TestExecutor::new();
553        let scan_results = generate_test_fidl_data();
554
555        // Create an iterator and send scan results
556        let (iter, iter_server) = fidl::endpoints::create_proxy();
557        let send_fut = send_scan_results_over_fidl(iter_server, &scan_results);
558        let mut send_fut = pin!(send_fut);
559
560        // Close the channel without getting results
561        drop(iter);
562
563        // This should result in error, since no results were consumed
564        assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Ready(Err(_)));
565    }
566
567    #[fuchsia::test]
568    fn scan_result_sends_max_message_size() {
569        let mut exec = fasync::TestExecutor::new();
570        let (iter, iter_server) = fidl::endpoints::create_proxy();
571
572        // Create a single scan result at the max allowed size to send in single
573        // FIDL message.
574        let fidl_scan_results = create_fidl_scan_results_from_size(vec![
575            zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize
576                - FIDL_HEADER_AND_ERR_WRAPPED_VEC_HEADER_SIZE,
577        ]);
578
579        let send_fut = send_scan_results_over_fidl(iter_server, &fidl_scan_results);
580        let mut send_fut = pin!(send_fut);
581
582        let mut output_iter_fut = iter.get_next();
583
584        assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
585
586        assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
587            let results = result.expect("Failed to get next scan results").unwrap();
588            assert_eq!(results, fidl_scan_results);
589        })
590    }
591
592    #[fuchsia::test]
593    fn scan_result_exceeding_max_size_throws_error() {
594        let mut exec = fasync::TestExecutor::new();
595        let (iter, iter_server) = fidl::endpoints::create_proxy();
596
597        // Create a single scan result exceeding the  max allowed size to send in single
598        // FIDL message.
599        let fidl_scan_results = create_fidl_scan_results_from_size(vec![
600            (zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize
601                - FIDL_HEADER_AND_ERR_WRAPPED_VEC_HEADER_SIZE)
602                + 8,
603        ]);
604
605        let send_fut = send_scan_results_over_fidl(iter_server, &fidl_scan_results);
606        let mut send_fut = pin!(send_fut);
607
608        let mut output_iter_fut = iter.get_next();
609
610        assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Ready(Err(_)));
611
612        assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(Err(_)));
613    }
614
615    #[fuchsia::test]
616    fn scan_result_sends_single_batch() {
617        let mut exec = fasync::TestExecutor::new();
618        let (iter, iter_server) = fidl::endpoints::create_proxy();
619
620        // Create a set of scan results that does not exceed the the max message
621        // size, so it should be sent in a single batch.
622        let fidl_scan_results =
623            create_fidl_scan_results_from_size(vec![
624                zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize / 4;
625                3
626            ]);
627
628        let send_fut = send_scan_results_over_fidl(iter_server, &fidl_scan_results);
629        let mut send_fut = pin!(send_fut);
630
631        let mut output_iter_fut = iter.get_next();
632
633        assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
634
635        assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
636            let results = result.expect("Failed to get next scan results").unwrap();
637            assert_eq!(results, fidl_scan_results);
638        });
639    }
640
641    #[fuchsia::test]
642    fn scan_result_sends_multiple_batches() {
643        let mut exec = fasync::TestExecutor::new();
644        let (iter, iter_server) = fidl::endpoints::create_proxy();
645
646        // Create a set of scan results that exceed the max FIDL message size, so
647        // they should be split into batches.
648        let fidl_scan_results =
649            create_fidl_scan_results_from_size(vec![
650                zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize / 8;
651                8
652            ]);
653
654        let send_fut = send_scan_results_over_fidl(iter_server, &fidl_scan_results);
655        let mut send_fut = pin!(send_fut);
656
657        let mut output_iter_fut = iter.get_next();
658
659        assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
660
661        let mut aggregate_results = vec![];
662        assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
663            let results = result.expect("Failed to get next scan results").unwrap();
664            assert_eq!(results.len(), 7);
665            aggregate_results.extend(results);
666        });
667
668        let mut output_iter_fut = iter.get_next();
669        assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
670        assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
671            let results = result.expect("Failed to get next scan results").unwrap();
672            assert_eq!(results.len(), 1);
673            aggregate_results.extend(results);
674        });
675        assert_eq!(aggregate_results, fidl_scan_results);
676    }
677}