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