1use crate::bss::BssDescription;
6use crate::mac::MacRole;
7use crate::security::SecurityDescriptor;
8use anyhow::format_err;
9use fidl_fuchsia_wlan_sme as fidl_sme;
10use std::borrow::Cow;
11use std::collections::{HashMap, HashSet};
12use std::error;
13use std::fmt::{self, Display, Formatter};
14
15#[cfg(target_os = "fuchsia")]
16use anyhow::Context as _;
17
18pub type Compatibility = Result<Compatible, Incompatible>;
27
28pub trait CompatibilityExt: Sized {
29 fn try_from_fidl(
30 compatibility: fidl_sme::Compatibility,
31 ) -> Result<Self, fidl_sme::Compatibility>;
32
33 fn into_fidl(self) -> fidl_sme::Compatibility;
34}
35
36impl CompatibilityExt for Compatibility {
37 fn try_from_fidl(
38 compatibility: fidl_sme::Compatibility,
39 ) -> Result<Self, fidl_sme::Compatibility> {
40 match compatibility {
41 fidl_sme::Compatibility::Compatible(compatible) => Compatible::try_from(compatible)
42 .map(Ok)
43 .map_err(fidl_sme::Compatibility::Compatible),
44 fidl_sme::Compatibility::Incompatible(incompatible) => {
45 Incompatible::try_from(incompatible)
46 .map(Err)
47 .map_err(fidl_sme::Compatibility::Incompatible)
48 }
49 }
50 }
51
52 fn into_fidl(self) -> fidl_sme::Compatibility {
53 match self {
54 Ok(compatible) => fidl_sme::Compatibility::Compatible(compatible.into()),
55 Err(incompatible) => fidl_sme::Compatibility::Incompatible(incompatible.into()),
56 }
57 }
58}
59
60#[derive(Debug, Clone, PartialEq)]
65pub struct Compatible {
66 mutual_security_protocols: HashSet<SecurityDescriptor>,
67}
68
69impl Compatible {
70 pub fn try_from_features(
79 mutual_security_protocols: impl IntoIterator<Item = SecurityDescriptor>,
80 ) -> Option<Self> {
81 let mutual_security_protocols: HashSet<_> = mutual_security_protocols.into_iter().collect();
82 if mutual_security_protocols.is_empty() {
83 None
84 } else {
85 Some(Compatible { mutual_security_protocols })
86 }
87 }
88
89 pub fn expect_ok(
102 mutual_security_protocols: impl IntoIterator<Item = SecurityDescriptor>,
103 ) -> Compatibility {
104 match Compatible::try_from_features(mutual_security_protocols) {
105 Some(compatibility) => Ok(compatibility),
106 None => panic!("mutual modes of operation are absent and imply incompatiblity"),
107 }
108 }
109
110 pub fn mutual_security_protocols(&self) -> &HashSet<SecurityDescriptor> {
116 &self.mutual_security_protocols
117 }
118}
119
120impl From<Compatible> for fidl_sme::Compatible {
121 fn from(compatibility: Compatible) -> Self {
122 let Compatible { mutual_security_protocols } = compatibility;
123 fidl_sme::Compatible {
124 mutual_security_protocols: mutual_security_protocols
125 .into_iter()
126 .map(From::from)
127 .collect(),
128 }
129 }
130}
131
132impl From<Compatible> for HashSet<SecurityDescriptor> {
133 fn from(compatibility: Compatible) -> Self {
134 compatibility.mutual_security_protocols
135 }
136}
137
138impl TryFrom<fidl_sme::Compatible> for Compatible {
139 type Error = fidl_sme::Compatible;
140
141 fn try_from(compatibility: fidl_sme::Compatible) -> Result<Self, Self::Error> {
142 let fidl_sme::Compatible { mutual_security_protocols } = compatibility;
143 match Compatible::try_from_features(
144 mutual_security_protocols.iter().cloned().map(From::from),
145 ) {
146 Some(compatible) => Ok(compatible),
147 None => Err(fidl_sme::Compatible { mutual_security_protocols }),
148 }
149 }
150}
151
152#[derive(Debug, Clone, PartialEq)]
159pub struct Incompatible {
160 description: Cow<'static, str>,
161 disjoint_security_protocols: Option<HashMap<SecurityDescriptor, MacRole>>,
162}
163
164impl Incompatible {
165 pub fn from_description(description: impl Into<Cow<'static, str>>) -> Self {
167 Incompatible { description: description.into(), disjoint_security_protocols: None }
168 }
169
170 pub fn try_from_features(
180 description: impl Into<Cow<'static, str>>,
181 disjoint_security_protocols: Option<
182 impl IntoIterator<Item = (SecurityDescriptor, MacRole)>,
183 >,
184 ) -> Option<Self> {
185 disjoint_security_protocols
186 .map(|disjoint_security_protocols| {
187 let mut unique_security_protocols = HashMap::new();
188 for (descriptor, role) in disjoint_security_protocols {
189 if let Some(previous) = unique_security_protocols.insert(descriptor, role) {
190 if role != previous {
191 return Err(role);
192 }
193 }
194 }
195 Ok(unique_security_protocols)
196 })
197 .transpose()
198 .ok()
199 .map(move |disjoint_security_protocols| Incompatible {
200 description: description.into(),
201 disjoint_security_protocols,
202 })
203 }
204
205 pub const fn unknown() -> Compatibility {
207 Err(Incompatible {
208 description: Cow::Borrowed("unknown"),
209 disjoint_security_protocols: None,
210 })
211 }
212
213 pub fn expect_err(
226 description: impl Into<Cow<'static, str>>,
227 disjoint_security_protocols: Option<
228 impl IntoIterator<Item = (SecurityDescriptor, MacRole)>,
229 >,
230 ) -> Compatibility {
231 match Incompatible::try_from_features(description, disjoint_security_protocols) {
232 Some(incompatible) => Err(incompatible),
233 None => panic!("disjoint modes of operation intersect and imply compatiblity"),
234 }
235 }
236
237 pub fn disjoint_security_protocols(&self) -> Option<&HashMap<SecurityDescriptor, MacRole>> {
247 self.disjoint_security_protocols.as_ref()
248 }
249}
250
251impl Display for Incompatible {
252 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
253 write!(formatter, "incompatibility detected")?;
254 if !self.description.is_empty() {
255 write!(formatter, ": {}", self.description)?;
256 }
257 if let Some(disjoint_security_protocols) = self.disjoint_security_protocols() {
258 let (client_security_protocols, bss_security_protocols) = disjoint_security_protocols
259 .iter()
260 .partition::<Vec<_>, _>(|(_, role)| matches!(role, MacRole::Client));
261 write!(
262 formatter,
263 ": supported BSS vs. client security protocols: {:?} vs. {:?}",
264 bss_security_protocols, client_security_protocols,
265 )?;
266 }
267 write!(formatter, ".")
268 }
269}
270
271impl error::Error for Incompatible {}
272
273impl From<Incompatible> for fidl_sme::Incompatible {
274 fn from(incompatible: Incompatible) -> Self {
275 let Incompatible { description, disjoint_security_protocols } = incompatible;
276 fidl_sme::Incompatible {
277 description: description.into(),
278 disjoint_security_protocols: disjoint_security_protocols.map(
279 |disjoint_security_protocols| {
280 disjoint_security_protocols
281 .into_iter()
282 .map(|(security_protocol, role)| fidl_sme::DisjointSecurityProtocol {
283 protocol: security_protocol.into(),
284 role: role.into(),
285 })
286 .collect()
287 },
288 ),
289 }
290 }
291}
292
293impl TryFrom<fidl_sme::Incompatible> for Incompatible {
294 type Error = fidl_sme::Incompatible;
295
296 fn try_from(incompatible: fidl_sme::Incompatible) -> Result<Self, Self::Error> {
297 let fidl_sme::Incompatible { description, disjoint_security_protocols } = incompatible;
298 match disjoint_security_protocols
299 .as_ref()
300 .map(|disjoint_security_protocols| {
301 disjoint_security_protocols
302 .iter()
303 .copied()
304 .map(|fidl_sme::DisjointSecurityProtocol { protocol, role }| {
305 MacRole::try_from(role).map(|role| (protocol.into(), role))
306 })
307 .collect::<Result<Vec<_>, _>>()
308 })
309 .transpose()
310 {
311 Ok(converted_security_protocols) => {
312 Incompatible::try_from_features(description.clone(), converted_security_protocols)
313 .ok_or(fidl_sme::Incompatible { description, disjoint_security_protocols })
314 }
315 Err(_) => Err(fidl_sme::Incompatible { description, disjoint_security_protocols }),
316 }
317 }
318}
319
320#[derive(Debug, Clone, PartialEq)]
321pub struct ScanResult {
322 pub compatibility: Compatibility,
323 #[cfg(target_os = "fuchsia")]
326 pub timestamp: zx::MonotonicInstant,
327 pub bss_description: BssDescription,
328}
329
330impl ScanResult {
331 pub fn is_compatible(&self) -> bool {
332 self.compatibility.is_ok()
333 }
334}
335
336impl From<ScanResult> for fidl_sme::ScanResult {
337 fn from(scan_result: ScanResult) -> fidl_sme::ScanResult {
338 let ScanResult {
339 compatibility,
340 #[cfg(target_os = "fuchsia")]
341 timestamp,
342 bss_description,
343 } = scan_result;
344 fidl_sme::ScanResult {
345 compatibility: compatibility.into_fidl(),
346 #[cfg(target_os = "fuchsia")]
347 timestamp_nanos: timestamp.into_nanos(),
348 #[cfg(not(target_os = "fuchsia"))]
349 timestamp_nanos: 0,
350 bss_description: bss_description.into(),
351 }
352 }
353}
354
355impl TryFrom<fidl_sme::ScanResult> for ScanResult {
356 type Error = anyhow::Error;
357
358 fn try_from(scan_result: fidl_sme::ScanResult) -> Result<ScanResult, Self::Error> {
359 let fidl_sme::ScanResult { compatibility, timestamp_nanos, bss_description } = scan_result;
360 Ok(ScanResult {
361 compatibility: Compatibility::try_from_fidl(compatibility)
362 .map_err(|_| format_err!("failed to convert FIDL `Compatibility`"))?,
363 #[cfg(target_os = "fuchsia")]
364 timestamp: zx::MonotonicInstant::from_nanos(timestamp_nanos),
365 bss_description: bss_description.try_into()?,
366 })
367 }
368}
369
370#[cfg(target_os = "fuchsia")]
372pub fn write_vmo(results: Vec<fidl_sme::ScanResult>) -> Result<fidl::Vmo, anyhow::Error> {
373 let bytes =
374 fidl::persist(&fidl_sme::ScanResultVector { results }).context("encoding scan results")?;
375 let vmo = fidl::Vmo::create(bytes.len() as u64).context("creating VMO for scan results")?;
376 vmo.write(&bytes, 0).context("writing scan results to VMO")?;
377 Ok(vmo)
378}
379
380#[cfg(target_os = "fuchsia")]
382pub fn read_vmo(vmo: fidl::Vmo) -> Result<Vec<fidl_sme::ScanResult>, anyhow::Error> {
383 let size = vmo.get_content_size().context("getting VMO content size")?;
384 let bytes = vmo.read_to_vec(0, size).context("reading VMO of scan results")?;
385 let scan_result_vector =
386 fidl::unpersist::<fidl_sme::ScanResultVector>(&bytes).context("decoding scan results")?;
387 Ok(scan_result_vector.results)
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn compatible_from_only_empty_is_none() {
396 assert!(Compatible::try_from_features([]).is_none());
397 }
398
399 #[test]
400 fn compatible_from_mutual_security_protocols_is_some() {
401 assert!(Compatible::try_from_features([
402 SecurityDescriptor::WPA2_PERSONAL,
403 SecurityDescriptor::WPA3_PERSONAL,
404 ])
405 .is_some());
406 }
407
408 #[test]
409 fn incompatible_from_only_none_is_some() {
410 assert!(Incompatible::try_from_features(
411 "dunno",
412 None::<[(SecurityDescriptor, MacRole); 0]>
413 )
414 .is_some());
415 }
416
417 #[test]
418 fn incompatible_from_only_some_empty_is_some() {
419 assert!(Incompatible::try_from_features("dunno", Some([])).is_some());
420 }
421
422 #[test]
423 fn incompatible_from_disjoint_security_protocols_is_some() {
424 assert!(Incompatible::try_from_features(
425 "dunno",
426 Some([
427 (SecurityDescriptor::WPA2_PERSONAL, MacRole::Client),
428 (SecurityDescriptor::WPA3_PERSONAL, MacRole::Ap),
429 ])
430 )
431 .is_some());
432 }
433
434 #[test]
435 fn incompatible_from_mutual_security_protocols_is_none() {
436 assert!(Incompatible::try_from_features(
437 "dunno",
438 Some([
439 (SecurityDescriptor::WPA3_PERSONAL, MacRole::Client),
440 (SecurityDescriptor::WPA3_PERSONAL, MacRole::Ap),
441 ])
442 )
443 .is_none());
444 }
445
446 #[test]
447 fn fidl_from_compatible_eq_expected() {
448 let security_protocol = SecurityDescriptor::OPEN;
449 let fidl =
450 fidl_sme::Compatible::from(Compatible::try_from_features([security_protocol]).unwrap());
451 assert_eq!(
452 fidl,
453 fidl_sme::Compatible { mutual_security_protocols: vec![security_protocol.into()] },
454 );
455 }
456
457 #[test]
458 fn compatible_try_from_fidl_eq_ok() {
459 let security_protocol = SecurityDescriptor::OPEN;
460 let compatible = Compatible::try_from(fidl_sme::Compatible {
461 mutual_security_protocols: vec![security_protocol.into()],
462 });
463 assert_eq!(compatible, Ok(Compatible::try_from_features([security_protocol]).unwrap()));
464 }
465
466 #[test]
467 fn compatible_try_from_fidl_eq_err() {
468 let fidl = fidl_sme::Compatible { mutual_security_protocols: vec![] };
469 let compatible = Compatible::try_from(fidl.clone());
470 assert_eq!(compatible, Err(fidl));
471 }
472
473 #[test]
474 fn fidl_from_incompatible_eq_expected() {
475 let fidl = fidl_sme::Incompatible::from(
476 Incompatible::try_from_features(
477 "dunno",
478 Some([(SecurityDescriptor::WPA3_PERSONAL, MacRole::Ap)]),
483 )
484 .unwrap(),
485 );
486 assert_eq!(
487 fidl,
488 fidl_sme::Incompatible {
489 description: String::from("dunno"),
490 disjoint_security_protocols: Some(vec![fidl_sme::DisjointSecurityProtocol {
491 protocol: SecurityDescriptor::WPA3_PERSONAL.into(),
492 role: MacRole::Ap.into(),
493 },]),
494 },
495 );
496 }
497
498 #[test]
499 fn incompatible_try_from_fidl_eq_expected() {
500 let incompatible = Incompatible::try_from(fidl_sme::Incompatible {
501 description: String::from("dunno"),
502 disjoint_security_protocols: Some(vec![
503 fidl_sme::DisjointSecurityProtocol {
504 protocol: SecurityDescriptor::WPA2_PERSONAL.into(),
505 role: MacRole::Client.into(),
506 },
507 fidl_sme::DisjointSecurityProtocol {
508 protocol: SecurityDescriptor::WPA3_PERSONAL.into(),
509 role: MacRole::Ap.into(),
510 },
511 ]),
512 });
513 assert_eq!(
514 incompatible,
515 Ok(Incompatible::try_from_features(
516 "dunno",
517 Some([
518 (SecurityDescriptor::WPA2_PERSONAL, MacRole::Client),
519 (SecurityDescriptor::WPA3_PERSONAL, MacRole::Ap),
520 ])
521 )
522 .unwrap()),
523 );
524 }
525
526 #[test]
527 fn fidl_from_compatibility_eq_expected() {
528 let security_protocol = SecurityDescriptor::OPEN;
529 let fidl = Compatible::expect_ok([security_protocol]).into_fidl();
530 assert_eq!(
531 fidl,
532 fidl_sme::Compatibility::Compatible(fidl_sme::Compatible {
533 mutual_security_protocols: vec![security_protocol.into()],
534 }),
535 );
536 }
537
538 #[test]
539 fn compatibility_try_from_fidl_eq_ok() {
540 let security_protocol = SecurityDescriptor::OPEN;
541 let compatibility = Compatibility::try_from_fidl(fidl_sme::Compatibility::Compatible(
542 fidl_sme::Compatible { mutual_security_protocols: vec![security_protocol.into()] },
543 ));
544 assert_eq!(compatibility, Ok(Compatible::expect_ok([security_protocol])));
545 }
546
547 #[test]
548 fn compatibility_try_from_fidl_eq_err() {
549 let fidl = fidl_sme::Compatibility::Compatible(fidl_sme::Compatible {
550 mutual_security_protocols: vec![],
551 });
552 let compatibility = Compatibility::try_from_fidl(fidl.clone());
553 assert_eq!(compatibility, Err(fidl));
554 }
555}