1use crate::client::types;
6use anyhow::{Error, format_err};
7use fidl::prelude::*;
8use futures::stream::TryStreamExt;
9use ieee80211::MacAddrBytes;
10use log::{debug, info};
11use measure_tape_for_scan_result::Measurable as _;
12use {fidl_fuchsia_wlan_policy as fidl_policy, fidl_fuchsia_wlan_sme as fidl_sme};
13
14const FIDL_HEADER_AND_ERR_WRAPPED_VEC_HEADER_SIZE: usize = 56;
17
18fn fidl_security_from_sme_protection(
23 protection: fidl_sme::Protection,
24 wpa3_supported: bool,
25) -> Option<fidl_policy::SecurityType> {
26 use fidl_policy::SecurityType;
27 use fidl_sme::Protection::*;
28 match protection {
29 Wpa3Enterprise | Wpa3Personal | Wpa2Wpa3Personal => {
30 Some(if wpa3_supported { SecurityType::Wpa3 } else { SecurityType::Wpa2 })
31 }
32 Wpa2Enterprise
33 | Wpa2Personal
34 | Wpa1Wpa2Personal
35 | Wpa2PersonalTkipOnly
36 | Wpa1Wpa2PersonalTkipOnly => Some(SecurityType::Wpa2),
37 Wpa1 => Some(SecurityType::Wpa),
38 Wep => Some(SecurityType::Wep),
39 Owe => Some(SecurityType::None),
41 OpenOweTransition => Some(SecurityType::None),
42 Open => Some(SecurityType::None),
43 Unknown => None,
44 }
45}
46
47#[allow(clippy::ptr_arg, reason = "mass allow for https://fxbug.dev/381896734")]
48pub fn scan_result_to_policy_scan_result(
49 internal_results: &Vec<types::ScanResult>,
50 wpa3_supported: bool,
51) -> Vec<fidl_policy::ScanResult> {
52 let scan_results: Vec<fidl_policy::ScanResult> = internal_results
53 .iter()
54 .filter_map(|internal| {
55 if let Some(security) =
56 fidl_security_from_sme_protection(internal.security_type_detailed, wpa3_supported)
57 {
58 Some(fidl_policy::ScanResult {
59 id: Some(fidl_policy::NetworkIdentifier {
60 ssid: internal.ssid.to_vec(),
61 type_: security,
62 }),
63 entries: Some(
64 internal
65 .entries
66 .iter()
67 .map(|input| {
68 let frequency = input.channel.get_center_freq().unwrap_or(0);
72 fidl_policy::Bss {
73 bssid: Some(input.bssid.to_array()),
74 rssi: Some(input.signal.rssi_dbm),
75 frequency: Some(frequency.into()), timestamp_nanos: Some(input.timestamp.into_nanos()),
77 ..Default::default()
78 }
79 })
80 .collect(),
81 ),
82 compatibility: Some(internal.compatibility),
83 ..Default::default()
84 })
85 } else {
86 debug!(
87 "Unknown security type present in scan results ({} BSSs)",
88 internal.entries.len()
89 );
90 None
91 }
92 })
93 .collect();
94
95 scan_results
96}
97
98pub async fn send_scan_results_over_fidl(
101 output_iterator: fidl::endpoints::ServerEnd<fidl_policy::ScanResultIteratorMarker>,
102 mut scan_results: &[fidl_policy::ScanResult],
103) -> Result<(), Error> {
104 let (mut stream, ctrl) = output_iterator.into_stream_and_control_handle();
106 let mut sent_some_results = false;
107
108 loop {
110 if let Some(fidl_policy::ScanResultIteratorRequest::GetNext { responder }) =
111 stream.try_next().await?
112 {
113 let mut bytes_used = FIDL_HEADER_AND_ERR_WRAPPED_VEC_HEADER_SIZE;
114 let mut result_count = 0;
115 for result in scan_results {
116 bytes_used += result.measure().num_bytes;
117 if bytes_used > zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize {
118 if result_count == 0 {
119 return Err(format_err!("Single scan result too large to send via FIDL"));
120 }
121 break;
123 }
124 result_count += 1;
125 }
126 #[expect(clippy::indexing_slicing)]
129 responder.send(Ok(&scan_results[..result_count]))?;
130 #[expect(clippy::indexing_slicing)]
133 let remaining_results = &scan_results[result_count..];
134 scan_results = remaining_results;
135 sent_some_results = true;
136
137 if result_count == 0 {
139 ctrl.shutdown();
140 return Ok(());
141 }
142 } else {
143 if sent_some_results {
146 debug!("Scan result consumer closed channel before consuming all scan results");
149 return Ok(());
150 }
151 return Err(format_err!("Peer closed channel before receiving any scan results"));
152 }
153 }
154}
155
156pub async fn send_scan_error_over_fidl(
159 output_iterator: fidl::endpoints::ServerEnd<fidl_policy::ScanResultIteratorMarker>,
160 error_code: types::ScanError,
161) -> Result<(), fidl::Error> {
162 let (mut stream, ctrl) = output_iterator.into_stream_and_control_handle();
164 if let Some(req) = stream.try_next().await? {
165 let fidl_policy::ScanResultIteratorRequest::GetNext { responder } = req;
166 responder.send(Err(error_code))?;
167 ctrl.shutdown();
168 } else {
169 info!("Peer closed channel for getting scan results unexpectedly");
172 }
173 Ok(())
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use assert_matches::assert_matches;
180 use fuchsia_async as fasync;
181 use futures::task::Poll;
182 use std::pin::pin;
183 use wlan_common::random_fidl_bss_description;
184 use wlan_common::scan::{Compatible, Incompatible};
185 use wlan_common::security::SecurityDescriptor;
186
187 fn generate_test_fidl_data() -> Vec<fidl_policy::ScanResult> {
188 const CENTER_FREQ_CHAN_1: u32 = 2412;
189 const CENTER_FREQ_CHAN_8: u32 = 2447;
190 const CENTER_FREQ_CHAN_11: u32 = 2462;
191 vec![
192 fidl_policy::ScanResult {
193 id: Some(fidl_policy::NetworkIdentifier {
194 ssid: types::Ssid::try_from("duplicated ssid").unwrap().into(),
195 type_: fidl_policy::SecurityType::Wpa2,
196 }),
197 entries: Some(vec![
198 fidl_policy::Bss {
199 bssid: Some([0, 0, 0, 0, 0, 0]),
200 rssi: Some(0),
201 frequency: Some(CENTER_FREQ_CHAN_1),
202 timestamp_nanos: Some(zx::MonotonicInstant::get().into_nanos()),
203 ..Default::default()
204 },
205 fidl_policy::Bss {
206 bssid: Some([7, 8, 9, 10, 11, 12]),
207 rssi: Some(13),
208 frequency: Some(CENTER_FREQ_CHAN_11),
209 timestamp_nanos: Some(zx::MonotonicInstant::get().into_nanos()),
210 ..Default::default()
211 },
212 ]),
213 compatibility: Some(fidl_policy::Compatibility::Supported),
214 ..Default::default()
215 },
216 fidl_policy::ScanResult {
217 id: Some(fidl_policy::NetworkIdentifier {
218 ssid: types::Ssid::try_from("unique ssid").unwrap().into(),
219 type_: fidl_policy::SecurityType::Wpa2,
220 }),
221 entries: Some(vec![fidl_policy::Bss {
222 bssid: Some([1, 2, 3, 4, 5, 6]),
223 rssi: Some(7),
224 frequency: Some(CENTER_FREQ_CHAN_8),
225 timestamp_nanos: Some(zx::MonotonicInstant::get().into_nanos()),
226 ..Default::default()
227 }]),
228 compatibility: Some(fidl_policy::Compatibility::Supported),
229 ..Default::default()
230 },
231 ]
232 }
233
234 fn create_fidl_scan_results_from_size(
238 result_sizes: Vec<usize>,
239 ) -> Vec<fidl_policy::ScanResult> {
240 let minimal_scan_result = fidl_policy::ScanResult {
242 id: Some(fidl_policy::NetworkIdentifier {
243 ssid: types::Ssid::empty().into(),
244 type_: fidl_policy::SecurityType::None,
245 }),
246 entries: Some(vec![]),
247 ..Default::default()
248 };
249 let minimal_result_size: usize = minimal_scan_result.measure().num_bytes;
250
251 let mut scan_result_with_one_bss = minimal_scan_result.clone();
253 scan_result_with_one_bss.entries = Some(vec![fidl_policy::Bss::default()]);
254
255 let empty_bss_entry_size: usize =
257 scan_result_with_one_bss.measure().num_bytes - minimal_result_size;
258
259 if result_sizes.iter().any(|size| size < &minimal_result_size || !size.is_multiple_of(8)) {
261 panic!(
262 "Invalid size. Requested size must be larger than {minimal_result_size} minimum bytes and divisible into octets (by 8)"
263 );
264 }
265
266 let mut fidl_scan_results = vec![];
267 for size in result_sizes {
268 let mut scan_result = minimal_scan_result.clone();
269
270 let num_bss_for_ap = (size - minimal_result_size) / empty_bss_entry_size;
271 let ssid_length =
273 (size - minimal_result_size) - (num_bss_for_ap * empty_bss_entry_size);
274
275 scan_result.id = Some(fidl_policy::NetworkIdentifier {
276 ssid: (0..ssid_length).map(|_| rand::random::<u8>()).collect(),
277 type_: fidl_policy::SecurityType::None,
278 });
279 scan_result.entries = Some(vec![fidl_policy::Bss::default(); num_bss_for_ap]);
280
281 assert_eq!(scan_result.measure().num_bytes, size);
283
284 fidl_scan_results.push(scan_result);
285 }
286 fidl_scan_results
287 }
288
289 #[fuchsia::test]
290 fn scan_result_generate_from_size() {
291 let scan_results = create_fidl_scan_results_from_size(vec![112; 4]);
292 assert_eq!(scan_results.len(), 4);
293 assert!(scan_results.iter().all(|scan_result| scan_result.measure().num_bytes == 112));
294 }
295
296 #[fuchsia::test]
297 fn sme_protection_converts_to_policy_security() {
298 use super::fidl_policy::SecurityType;
299 use super::fidl_sme::Protection;
300 let wpa3_supported = true;
301 let wpa3_not_supported = false;
302 let test_pairs = vec![
303 (Protection::Wpa3Enterprise, wpa3_supported, Some(SecurityType::Wpa3)),
305 (Protection::Wpa3Personal, wpa3_supported, Some(SecurityType::Wpa3)),
306 (Protection::Wpa2Wpa3Personal, wpa3_supported, Some(SecurityType::Wpa3)),
307 (Protection::Wpa2Enterprise, wpa3_supported, Some(SecurityType::Wpa2)),
308 (Protection::Wpa2Personal, wpa3_supported, Some(SecurityType::Wpa2)),
309 (Protection::Wpa1Wpa2Personal, wpa3_supported, Some(SecurityType::Wpa2)),
310 (Protection::Wpa2PersonalTkipOnly, wpa3_supported, Some(SecurityType::Wpa2)),
311 (Protection::Wpa1Wpa2PersonalTkipOnly, wpa3_supported, Some(SecurityType::Wpa2)),
312 (Protection::Wpa1, wpa3_supported, Some(SecurityType::Wpa)),
313 (Protection::Wep, wpa3_supported, Some(SecurityType::Wep)),
314 (Protection::Owe, wpa3_supported, Some(SecurityType::None)),
317 (Protection::OpenOweTransition, wpa3_supported, Some(SecurityType::None)),
318 (Protection::Open, wpa3_supported, Some(SecurityType::None)),
319 (Protection::Unknown, wpa3_supported, None),
320 (Protection::Wpa3Enterprise, wpa3_not_supported, Some(SecurityType::Wpa2)),
322 (Protection::Wpa3Personal, wpa3_not_supported, Some(SecurityType::Wpa2)),
323 (Protection::Wpa2Wpa3Personal, wpa3_not_supported, Some(SecurityType::Wpa2)),
324 (Protection::Wpa2Enterprise, wpa3_not_supported, Some(SecurityType::Wpa2)),
325 (Protection::Wpa2Personal, wpa3_not_supported, Some(SecurityType::Wpa2)),
326 (Protection::Wpa1Wpa2Personal, wpa3_not_supported, Some(SecurityType::Wpa2)),
327 (Protection::Wpa2PersonalTkipOnly, wpa3_not_supported, Some(SecurityType::Wpa2)),
328 (Protection::Wpa1Wpa2PersonalTkipOnly, wpa3_not_supported, Some(SecurityType::Wpa2)),
329 (Protection::Wpa1, wpa3_not_supported, Some(SecurityType::Wpa)),
330 (Protection::Wep, wpa3_not_supported, Some(SecurityType::Wep)),
331 (Protection::Owe, wpa3_not_supported, Some(SecurityType::None)),
334 (Protection::OpenOweTransition, wpa3_not_supported, Some(SecurityType::None)),
335 (Protection::Open, wpa3_not_supported, Some(SecurityType::None)),
336 (Protection::Unknown, wpa3_not_supported, None),
337 ];
338 for (input, wpa3_capable, output) in test_pairs {
339 assert_eq!(fidl_security_from_sme_protection(input, wpa3_capable), output);
340 }
341 }
342
343 #[fuchsia::test]
344 fn scan_results_converted_correctly() {
345 let fidl_aps = generate_test_fidl_data();
346 let internal_aps = vec![
347 types::ScanResult {
348 ssid: types::Ssid::try_from("duplicated ssid").unwrap(),
349 security_type_detailed: types::SecurityTypeDetailed::Wpa3Personal,
350 entries: vec![
351 types::Bss {
352 bssid: types::Bssid::from([0, 0, 0, 0, 0, 0]),
353 signal: types::Signal { rssi_dbm: 0, snr_db: 1 },
354 timestamp: zx::MonotonicInstant::from_nanos(
355 fidl_aps[0].entries.as_ref().unwrap()[0].timestamp_nanos.unwrap(),
356 ),
357 channel: types::WlanChan::new(1, types::Cbw::Cbw20),
358 observation: types::ScanObservation::Passive,
359 compatibility: Compatible::expect_ok([SecurityDescriptor::WPA3_PERSONAL]),
360 bss_description: random_fidl_bss_description!(
361 Wpa3,
362 bssid: [0, 0, 0, 0, 0, 0],
363 ssid: types::Ssid::try_from("duplicated ssid").unwrap(),
364 rssi_dbm: 0,
365 snr_db: 1,
366 channel: types::WlanChan::new(1, types::Cbw::Cbw20),
367 )
368 .into(),
369 },
370 types::Bss {
371 bssid: types::Bssid::from([7, 8, 9, 10, 11, 12]),
372 signal: types::Signal { rssi_dbm: 13, snr_db: 3 },
373 timestamp: zx::MonotonicInstant::from_nanos(
374 fidl_aps[0].entries.as_ref().unwrap()[1].timestamp_nanos.unwrap(),
375 ),
376 channel: types::WlanChan::new(11, types::Cbw::Cbw20),
377 observation: types::ScanObservation::Passive,
378 compatibility: Incompatible::unknown(),
379 bss_description: random_fidl_bss_description!(
380 Wpa3,
381 bssid: [7, 8, 9, 10, 11, 12],
382 ssid: types::Ssid::try_from("duplicated ssid").unwrap(),
383 rssi_dbm: 13,
384 snr_db: 3,
385 channel: types::WlanChan::new(11, types::Cbw::Cbw20),
386 )
387 .into(),
388 },
389 ],
390 compatibility: types::Compatibility::Supported,
391 },
392 types::ScanResult {
393 ssid: types::Ssid::try_from("unique ssid").unwrap(),
394 security_type_detailed: types::SecurityTypeDetailed::Wpa2Personal,
395 entries: vec![types::Bss {
396 bssid: types::Bssid::from([1, 2, 3, 4, 5, 6]),
397 signal: types::Signal { rssi_dbm: 7, snr_db: 2 },
398 timestamp: zx::MonotonicInstant::from_nanos(
399 fidl_aps[1].entries.as_ref().unwrap()[0].timestamp_nanos.unwrap(),
400 ),
401 channel: types::WlanChan::new(8, types::Cbw::Cbw20),
402 observation: types::ScanObservation::Passive,
403 compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
404 bss_description: random_fidl_bss_description!(
405 Wpa2,
406 bssid: [1, 2, 3, 4, 5, 6],
407 ssid: types::Ssid::try_from("unique ssid").unwrap(),
408 rssi_dbm: 7,
409 snr_db: 2,
410 channel: types::WlanChan::new(8, types::Cbw::Cbw20),
411 )
412 .into(),
413 }],
414 compatibility: types::Compatibility::Supported,
415 },
416 ];
417 assert_eq!(fidl_aps, scan_result_to_policy_scan_result(&internal_aps, false));
418 }
419
420 #[fuchsia::test]
423 fn partial_scan_result_consumption_has_no_error() {
424 let mut exec = fasync::TestExecutor::new();
425 let scan_results = generate_test_fidl_data();
426
427 let (iter, iter_server) = fidl::endpoints::create_proxy();
429 let send_fut = send_scan_results_over_fidl(iter_server, &scan_results);
430 let mut send_fut = pin!(send_fut);
431
432 let mut output_iter_fut = iter.get_next();
434
435 assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
437
438 assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
440 let results = result.expect("Failed to get next scan results").unwrap();
441 assert_eq!(results, scan_results);
442 });
443
444 drop(output_iter_fut);
450 drop(iter);
451
452 assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Ready(Ok(())));
454 }
455
456 #[fuchsia::test]
457 fn no_scan_result_consumption_has_error() {
458 let mut exec = fasync::TestExecutor::new();
459 let scan_results = generate_test_fidl_data();
460
461 let (iter, iter_server) = fidl::endpoints::create_proxy();
463 let send_fut = send_scan_results_over_fidl(iter_server, &scan_results);
464 let mut send_fut = pin!(send_fut);
465
466 drop(iter);
468
469 assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Ready(Err(_)));
471 }
472
473 #[fuchsia::test]
474 fn scan_result_sends_max_message_size() {
475 let mut exec = fasync::TestExecutor::new();
476 let (iter, iter_server) = fidl::endpoints::create_proxy();
477
478 let fidl_scan_results = create_fidl_scan_results_from_size(vec![
481 zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize
482 - FIDL_HEADER_AND_ERR_WRAPPED_VEC_HEADER_SIZE,
483 ]);
484
485 let send_fut = send_scan_results_over_fidl(iter_server, &fidl_scan_results);
486 let mut send_fut = pin!(send_fut);
487
488 let mut output_iter_fut = iter.get_next();
489
490 assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
491
492 assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
493 let results = result.expect("Failed to get next scan results").unwrap();
494 assert_eq!(results, fidl_scan_results);
495 })
496 }
497
498 #[fuchsia::test]
499 fn scan_result_exceeding_max_size_throws_error() {
500 let mut exec = fasync::TestExecutor::new();
501 let (iter, iter_server) = fidl::endpoints::create_proxy();
502
503 let fidl_scan_results = create_fidl_scan_results_from_size(vec![
506 (zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize
507 - FIDL_HEADER_AND_ERR_WRAPPED_VEC_HEADER_SIZE)
508 + 8,
509 ]);
510
511 let send_fut = send_scan_results_over_fidl(iter_server, &fidl_scan_results);
512 let mut send_fut = pin!(send_fut);
513
514 let mut output_iter_fut = iter.get_next();
515
516 assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Ready(Err(_)));
517
518 assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(Err(_)));
519 }
520
521 #[fuchsia::test]
522 fn scan_result_sends_single_batch() {
523 let mut exec = fasync::TestExecutor::new();
524 let (iter, iter_server) = fidl::endpoints::create_proxy();
525
526 let fidl_scan_results =
529 create_fidl_scan_results_from_size(vec![
530 zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize / 4;
531 3
532 ]);
533
534 let send_fut = send_scan_results_over_fidl(iter_server, &fidl_scan_results);
535 let mut send_fut = pin!(send_fut);
536
537 let mut output_iter_fut = iter.get_next();
538
539 assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
540
541 assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
542 let results = result.expect("Failed to get next scan results").unwrap();
543 assert_eq!(results, fidl_scan_results);
544 });
545 }
546
547 #[fuchsia::test]
548 fn scan_result_sends_multiple_batches() {
549 let mut exec = fasync::TestExecutor::new();
550 let (iter, iter_server) = fidl::endpoints::create_proxy();
551
552 let fidl_scan_results =
555 create_fidl_scan_results_from_size(vec![
556 zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize / 8;
557 8
558 ]);
559
560 let send_fut = send_scan_results_over_fidl(iter_server, &fidl_scan_results);
561 let mut send_fut = pin!(send_fut);
562
563 let mut output_iter_fut = iter.get_next();
564
565 assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
566
567 let mut aggregate_results = vec![];
568 assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
569 let results = result.expect("Failed to get next scan results").unwrap();
570 assert_eq!(results.len(), 7);
571 aggregate_results.extend(results);
572 });
573
574 let mut output_iter_fut = iter.get_next();
575 assert_matches!(exec.run_until_stalled(&mut send_fut), Poll::Pending);
576 assert_matches!(exec.run_until_stalled(&mut output_iter_fut), Poll::Ready(result) => {
577 let results = result.expect("Failed to get next scan results").unwrap();
578 assert_eq!(results.len(), 1);
579 aggregate_results.extend(results);
580 });
581 assert_eq!(aggregate_results, fidl_scan_results);
582 }
583}