1use crate::client::types as client_types;
6use crate::util::historical_list::{HistoricalList, Timestamped};
7use arbitrary::Arbitrary;
8#[cfg(test)]
9use fidl_fuchsia_wlan_common_security as fidl_security;
10use std::cmp::Reverse;
11use std::collections::{HashMap, HashSet};
12use std::fmt::{self, Debug};
13use wlan_common::security::wep::WepKey;
14use wlan_common::security::wpa::WpaDescriptor;
15use wlan_common::security::wpa::credential::{Passphrase, Psk};
16use wlan_common::security::{SecurityAuthenticator, SecurityDescriptor};
17use {fidl_fuchsia_wlan_policy as fidl_policy, fuchsia_async as fasync};
18
19const NUM_CONNECTION_RESULTS_PER_BSS: usize = 10;
22const WEP_40_ASCII_LEN: usize = 5;
24const WEP_40_HEX_LEN: usize = 10;
25const WEP_104_ASCII_LEN: usize = 13;
26const WEP_104_HEX_LEN: usize = 26;
27const WPA_MIN_PASSWORD_LEN: usize = 8;
28const WPA_MAX_PASSWORD_LEN: usize = 63;
29pub const WPA_PSK_BYTE_LEN: usize = 32;
30pub const PROB_HIDDEN_IF_SEEN_PASSIVE: f32 = 0.05;
32pub const PROB_HIDDEN_IF_CONNECT_PASSIVE: f32 = 0.0;
34pub const PROB_HIDDEN_IF_CONNECT_ACTIVE: f32 = 0.95;
36pub const PROB_HIDDEN_DEFAULT: f32 = 0.9;
39pub const PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE: f32 = 0.25;
41pub const PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE: f32 = 0.14;
44pub const HIDDEN_PROBABILITY_HIGH: f32 =
49 PROB_HIDDEN_DEFAULT - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
50pub const PROB_IS_HIDDEN: f32 = PROB_HIDDEN_IF_CONNECT_ACTIVE;
54pub const NUM_SCANS_TO_DECIDE_LIKELY_SINGLE_BSS: usize = 4;
55
56pub type SaveError = fidl_policy::NetworkConfigChangeError;
57
58#[derive(Clone, Debug, PartialEq)]
60struct HiddenProbabilityStats {
61 pub connected_active: bool,
62}
63
64impl HiddenProbabilityStats {
65 fn new() -> Self {
66 HiddenProbabilityStats { connected_active: false }
67 }
68}
69
70#[derive(Clone, Debug, PartialEq)]
73pub struct PerformanceStats {
74 pub connect_failures: HistoricalListsByBssid<ConnectFailure>,
75 pub past_connections: HistoricalListsByBssid<PastConnectionData>,
76}
77
78impl Default for PerformanceStats {
79 fn default() -> Self {
80 Self::new()
81 }
82}
83
84impl PerformanceStats {
85 pub fn new() -> Self {
86 Self {
87 connect_failures: HistoricalListsByBssid::new(),
88 past_connections: HistoricalListsByBssid::new(),
89 }
90 }
91}
92
93#[derive(Clone, Debug, PartialEq)]
96struct ScanStats {
97 pub have_seen_multi_bss: bool,
98 pub num_scans: usize,
100}
101
102impl ScanStats {
103 pub fn new() -> Self {
104 Self { have_seen_multi_bss: false, num_scans: 0 }
105 }
106}
107
108#[derive(Clone, Copy, Debug, PartialEq)]
109pub enum FailureReason {
110 CredentialRejected,
112 GeneralFailure,
114}
115
116#[derive(Clone, Copy, Debug, PartialEq)]
117pub struct ConnectFailure {
118 pub time: fasync::MonotonicInstant,
120 pub reason: FailureReason,
122 pub bssid: client_types::Bssid,
124}
125
126impl Timestamped for ConnectFailure {
127 fn time(&self) -> fasync::MonotonicInstant {
128 self.time
129 }
130}
131
132#[derive(Clone, Copy, Debug, PartialEq)]
134pub struct PastConnectionData {
135 pub bssid: client_types::Bssid,
136 pub disconnect_time: fasync::MonotonicInstant,
138 pub connection_uptime: zx::MonotonicDuration,
140 pub disconnect_reason: client_types::DisconnectReason,
142 pub signal_at_disconnect: client_types::Signal,
144 pub average_tx_rate: u32,
146}
147
148impl PastConnectionData {
149 pub fn new(
150 bssid: client_types::Bssid,
151 disconnect_time: fasync::MonotonicInstant,
152 connection_uptime: zx::MonotonicDuration,
153 disconnect_reason: client_types::DisconnectReason,
154 signal_at_disconnect: client_types::Signal,
155 average_tx_rate: u32,
156 ) -> Self {
157 Self {
158 bssid,
159 disconnect_time,
160 connection_uptime,
161 disconnect_reason,
162 signal_at_disconnect,
163 average_tx_rate,
164 }
165 }
166}
167
168impl Timestamped for PastConnectionData {
169 fn time(&self) -> fasync::MonotonicInstant {
170 self.disconnect_time
171 }
172}
173
174pub type PastConnectionList = HistoricalList<PastConnectionData>;
176impl Default for PastConnectionList {
177 fn default() -> Self {
178 Self::new(NUM_CONNECTION_RESULTS_PER_BSS)
179 }
180}
181
182#[derive(Clone, Debug, PartialEq)]
184pub struct HistoricalListsByBssid<T: Timestamped>(HashMap<client_types::Bssid, HistoricalList<T>>);
185
186impl<T> Default for HistoricalListsByBssid<T>
187where
188 T: Timestamped + Clone,
189{
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195impl<T> HistoricalListsByBssid<T>
196where
197 T: Timestamped + Clone,
198{
199 pub fn new() -> Self {
200 Self(HashMap::new())
201 }
202
203 pub fn add(&mut self, bssid: client_types::Bssid, data: T) {
204 self.0
205 .entry(bssid)
206 .or_insert_with(|| HistoricalList::new(NUM_CONNECTION_RESULTS_PER_BSS))
207 .add(data);
208 }
209
210 pub fn get_recent_for_network(&self, earliest_time: fasync::MonotonicInstant) -> Vec<T> {
213 let mut recents: Vec<T> = vec![];
214 for bssid in self.0.keys() {
215 recents.append(&mut self.get_list_for_bss(bssid).get_recent(earliest_time));
216 }
217 recents.sort_by_key(|a| a.time());
218 recents
219 }
220
221 pub fn get_list_for_bss(&self, bssid: &client_types::Bssid) -> HistoricalList<T> {
223 self.0
224 .get(bssid)
225 .cloned()
226 .unwrap_or_else(|| HistoricalList::new(NUM_CONNECTION_RESULTS_PER_BSS))
227 }
228}
229
230#[derive(Clone, Copy)]
232pub enum HiddenProbEvent {
233 SeenPassive,
235 ConnectPassive,
237 ConnectActive,
239 NotSeenActive,
241}
242
243#[derive(Clone, Debug, PartialEq)]
245pub struct NetworkConfig {
246 pub ssid: client_types::Ssid,
248 pub security_type: SecurityType,
249 pub credential: Credential,
251 pub has_ever_connected: bool,
253 pub hidden_probability: f32,
257 hidden_probability_stats: HiddenProbabilityStats,
259 pub perf_stats: PerformanceStats,
261 scan_stats: ScanStats,
264}
265
266impl NetworkConfig {
267 pub fn new(
270 id: NetworkIdentifier,
271 credential: Credential,
272 has_ever_connected: bool,
273 ) -> Result<Self, NetworkConfigError> {
274 check_config_errors(&id.ssid, &id.security_type, &credential)?;
275
276 Ok(Self {
277 ssid: id.ssid,
278 security_type: id.security_type,
279 credential,
280 has_ever_connected,
281 hidden_probability: PROB_HIDDEN_DEFAULT,
282 hidden_probability_stats: HiddenProbabilityStats::new(),
283 perf_stats: PerformanceStats::new(),
284 scan_stats: ScanStats::new(),
285 })
286 }
287
288 pub fn update_hidden_prob(&mut self, event: HiddenProbEvent) {
293 match event {
294 HiddenProbEvent::ConnectPassive => {
295 self.hidden_probability = PROB_HIDDEN_IF_CONNECT_PASSIVE;
296 }
297 HiddenProbEvent::SeenPassive => {
298 if self.hidden_probability > PROB_HIDDEN_IF_SEEN_PASSIVE {
301 self.hidden_probability = PROB_HIDDEN_IF_SEEN_PASSIVE;
302 }
303 }
304 HiddenProbEvent::ConnectActive => {
305 self.hidden_probability_stats.connected_active = true;
306 self.hidden_probability = PROB_HIDDEN_IF_CONNECT_ACTIVE;
307 }
308 HiddenProbEvent::NotSeenActive => {
309 if self.hidden_probability_stats.connected_active {
312 return;
313 }
314 if self.hidden_probability <= PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE {
316 return;
317 }
318 let new_prob = self.hidden_probability - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
321 self.hidden_probability = new_prob.max(PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
322 }
323 }
324 }
325
326 pub fn is_hidden(&self) -> bool {
327 self.hidden_probability >= PROB_IS_HIDDEN
328 }
329
330 #[allow(clippy::assign_op_pattern, reason = "mass allow for https://fxbug.dev/381896734")]
331 pub fn update_seen_multiple_bss(&mut self, multi_bss: bool) {
332 self.scan_stats.have_seen_multi_bss = self.scan_stats.have_seen_multi_bss || multi_bss;
333 self.scan_stats.num_scans = self.scan_stats.num_scans + 1;
334 }
335
336 #[allow(clippy::needless_return, reason = "mass allow for https://fxbug.dev/381896734")]
337 pub fn is_likely_single_bss(&self) -> bool {
340 return !self.scan_stats.have_seen_multi_bss
341 && self.scan_stats.num_scans > NUM_SCANS_TO_DECIDE_LIKELY_SINGLE_BSS;
342 }
343}
344
345impl From<&NetworkConfig> for fidl_policy::NetworkConfig {
346 fn from(network_config: &NetworkConfig) -> Self {
347 let network_id = fidl_policy::NetworkIdentifier {
348 ssid: network_config.ssid.to_vec(),
349 type_: network_config.security_type.into(),
350 };
351 let credential = network_config.credential.clone().into();
352 fidl_policy::NetworkConfig {
353 id: Some(network_id),
354 credential: Some(credential),
355 ..Default::default()
356 }
357 }
358}
359
360#[derive(Arbitrary)] #[derive(Clone, Debug, PartialEq)]
363pub enum Credential {
364 None,
365 Password(Vec<u8>),
366 Psk(Vec<u8>),
367}
368
369impl Credential {
370 #[allow(clippy::doc_lazy_continuation, reason = "mass allow for https://fxbug.dev/381896734")]
374 #[allow(clippy::doc_lazy_continuation, reason = "mass allow for https://fxbug.dev/381896734")]
376 #[allow(clippy::doc_lazy_continuation, reason = "mass allow for https://fxbug.dev/381896734")]
378 #[allow(clippy::doc_lazy_continuation, reason = "mass allow for https://fxbug.dev/381896734")]
380 pub fn from_bytes(bytes: impl AsRef<[u8]> + Into<Vec<u8>>) -> Self {
382 match bytes.as_ref().len() {
383 0 => Credential::None,
384 _ => Credential::Password(bytes.into()),
385 }
386 }
387
388 pub fn into_bytes(self) -> Vec<u8> {
391 match self {
392 Credential::Password(pwd) => pwd,
393 Credential::Psk(psk) => psk,
394 Credential::None => vec![],
395 }
396 }
397
398 pub fn derived_security_type(&self) -> SecurityType {
401 match self {
402 Credential::None => SecurityType::None,
403 _ => SecurityType::Wpa2,
404 }
405 }
406
407 pub fn type_str(&self) -> &str {
408 match self {
409 Credential::None => "None",
410 Credential::Password(_) => "Password",
411 Credential::Psk(_) => "PSK",
412 }
413 }
414}
415
416impl TryFrom<fidl_policy::Credential> for Credential {
417 type Error = NetworkConfigError;
418 fn try_from(credential: fidl_policy::Credential) -> Result<Self, Self::Error> {
420 match credential {
421 fidl_policy::Credential::None(fidl_policy::Empty {}) => Ok(Self::None),
422 fidl_policy::Credential::Password(pwd) => Ok(Self::Password(pwd)),
423 fidl_policy::Credential::Psk(psk) => Ok(Self::Psk(psk)),
424 _ => Err(NetworkConfigError::CredentialTypeInvalid),
425 }
426 }
427}
428
429impl From<Credential> for fidl_policy::Credential {
430 fn from(credential: Credential) -> Self {
431 match credential {
432 Credential::Password(pwd) => fidl_policy::Credential::Password(pwd),
433 Credential::Psk(psk) => fidl_policy::Credential::Psk(psk),
434 Credential::None => fidl_policy::Credential::None(fidl_policy::Empty),
435 }
436 }
437}
438
439#[cfg(test)]
445impl PartialEq<Option<fidl_security::Credentials>> for Credential {
446 fn eq(&self, credentials: &Option<fidl_security::Credentials>) -> bool {
447 use fidl_security::{Credentials, WepCredentials, WpaCredentials};
448
449 match credentials {
450 None => matches!(self, Credential::None),
451 Some(Credentials::Wep(WepCredentials { key })) => {
452 if let Credential::Password(unparsed) = self {
453 WepKey::parse(unparsed).is_ok_and(|parsed| &Vec::from(parsed) == key)
457 } else {
458 false
459 }
460 }
461 Some(Credentials::Wpa(credentials)) => match credentials {
462 WpaCredentials::Passphrase(passphrase) => {
463 if let Credential::Password(unparsed) = self {
464 unparsed == &passphrase.clone()
465 } else {
466 false
467 }
468 }
469 WpaCredentials::Psk(psk) => {
470 if let Credential::Psk(unparsed) = self {
471 unparsed == &Vec::from(*psk)
472 } else {
473 false
474 }
475 }
476 _ => panic!("unrecognized FIDL variant"),
477 },
478 Some(_) => panic!("unrecognized FIDL variant"),
479 }
480 }
481}
482
483#[cfg(test)]
485impl PartialEq<Option<Box<fidl_security::Credentials>>> for Credential {
486 fn eq(&self, credentials: &Option<Box<fidl_security::Credentials>>) -> bool {
487 self.eq(&credentials.as_ref().map(|credentials| *credentials.clone()))
488 }
489}
490
491#[derive(Arbitrary)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
493pub enum SecurityType {
494 None,
495 Wep,
496 Wpa,
497 Wpa2,
498 Wpa3,
499}
500
501impl From<SecurityDescriptor> for SecurityType {
502 fn from(descriptor: SecurityDescriptor) -> Self {
503 match descriptor {
504 SecurityDescriptor::Open => SecurityType::None,
505 SecurityDescriptor::Wep => SecurityType::Wep,
506 SecurityDescriptor::Wpa(wpa) => match wpa {
507 WpaDescriptor::Wpa1 { .. } => SecurityType::Wpa,
508 WpaDescriptor::Wpa2 { .. } => SecurityType::Wpa2,
509 WpaDescriptor::Wpa3 { .. } => SecurityType::Wpa3,
510 },
511 }
512 }
513}
514
515impl From<fidl_policy::SecurityType> for SecurityType {
516 fn from(security: fidl_policy::SecurityType) -> Self {
517 match security {
518 fidl_policy::SecurityType::None => SecurityType::None,
519 fidl_policy::SecurityType::Wep => SecurityType::Wep,
520 fidl_policy::SecurityType::Wpa => SecurityType::Wpa,
521 fidl_policy::SecurityType::Wpa2 => SecurityType::Wpa2,
522 fidl_policy::SecurityType::Wpa3 => SecurityType::Wpa3,
523 }
524 }
525}
526
527impl From<SecurityType> for fidl_policy::SecurityType {
528 fn from(security_type: SecurityType) -> Self {
529 match security_type {
530 SecurityType::None => fidl_policy::SecurityType::None,
531 SecurityType::Wep => fidl_policy::SecurityType::Wep,
532 SecurityType::Wpa => fidl_policy::SecurityType::Wpa,
533 SecurityType::Wpa2 => fidl_policy::SecurityType::Wpa2,
534 SecurityType::Wpa3 => fidl_policy::SecurityType::Wpa3,
535 }
536 }
537}
538
539impl SecurityType {
540 pub fn list_variants() -> Vec<Self> {
542 vec![
543 SecurityType::None,
544 SecurityType::Wep,
545 SecurityType::Wpa,
546 SecurityType::Wpa2,
547 SecurityType::Wpa3,
548 ]
549 }
550
551 pub fn is_compatible_with_scanned_type(
554 &self,
555 scanned_type: &client_types::SecurityTypeDetailed,
556 ) -> bool {
557 match self {
558 SecurityType::None => {
559 scanned_type == &client_types::SecurityTypeDetailed::Open
561 }
562 SecurityType::Wep => scanned_type == &client_types::SecurityTypeDetailed::Wep,
563 SecurityType::Wpa => {
564 scanned_type == &client_types::SecurityTypeDetailed::Wpa1
565 || scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2Personal
566 || scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2PersonalTkipOnly
567 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2Personal
568 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2PersonalTkipOnly
569 }
570 SecurityType::Wpa2 => {
571 scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2Personal
572 || scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2PersonalTkipOnly
573 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2Personal
574 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2PersonalTkipOnly
575 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2Wpa3Personal
576 || scanned_type == &client_types::SecurityTypeDetailed::Wpa3Personal
577 }
578 SecurityType::Wpa3 => {
579 scanned_type == &client_types::SecurityTypeDetailed::Wpa2Wpa3Personal
580 || scanned_type == &client_types::SecurityTypeDetailed::Wpa3Personal
581 }
582 }
583 }
584}
585
586#[derive(Arbitrary)]
589#[derive(Clone, Eq, Hash, PartialEq)]
592#[cfg_attr(test, derive(Debug))]
593pub struct NetworkIdentifier {
594 pub ssid: client_types::Ssid,
595 pub security_type: SecurityType,
596}
597
598impl NetworkIdentifier {
599 pub fn new(ssid: client_types::Ssid, security_type: SecurityType) -> Self {
600 NetworkIdentifier { ssid, security_type }
601 }
602
603 #[cfg(test)]
604 pub fn try_from(ssid: &str, security_type: SecurityType) -> Result<Self, anyhow::Error> {
605 Ok(NetworkIdentifier { ssid: client_types::Ssid::try_from(ssid)?, security_type })
606 }
607}
608
609impl From<fidl_policy::NetworkIdentifier> for NetworkIdentifier {
610 fn from(id: fidl_policy::NetworkIdentifier) -> Self {
611 Self::new(client_types::Ssid::from_bytes_unchecked(id.ssid), id.type_.into())
612 }
613}
614
615impl From<NetworkIdentifier> for fidl_policy::NetworkIdentifier {
616 fn from(id: NetworkIdentifier) -> Self {
617 fidl_policy::NetworkIdentifier { ssid: id.ssid.into(), type_: id.security_type.into() }
618 }
619}
620
621impl From<NetworkConfig> for fidl_policy::NetworkConfig {
622 fn from(config: NetworkConfig) -> Self {
623 let network_id = NetworkIdentifier::new(config.ssid, config.security_type);
624 fidl_policy::NetworkConfig {
625 id: Some(fidl_policy::NetworkIdentifier::from(network_id)),
626 credential: Some(fidl_policy::Credential::from(config.credential)),
627 ..Default::default()
628 }
629 }
630}
631
632fn check_config_errors(
636 ssid: &client_types::Ssid,
637 security_type: &SecurityType,
638 credential: &Credential,
639) -> Result<(), NetworkConfigError> {
640 if ssid.is_empty() {
642 return Err(NetworkConfigError::SsidEmpty);
643 }
644 match security_type {
647 SecurityType::None => {
648 if let Credential::Psk(_) | Credential::Password(_) = credential {
649 return Err(NetworkConfigError::OpenNetworkPassword);
650 }
651 }
652 SecurityType::Wep => match credential {
655 Credential::Password(password) => match password.len() {
656 WEP_40_ASCII_LEN | WEP_104_ASCII_LEN => {}
658 WEP_40_HEX_LEN | WEP_104_HEX_LEN => {}
660 _ => {
661 return Err(NetworkConfigError::PasswordLen);
662 }
663 },
664 _ => {
665 return Err(NetworkConfigError::MissingPasswordPsk);
666 }
667 },
668 SecurityType::Wpa | SecurityType::Wpa2 | SecurityType::Wpa3 => match credential {
669 Credential::Password(pwd) => {
670 if pwd.len() < WPA_MIN_PASSWORD_LEN || pwd.len() > WPA_MAX_PASSWORD_LEN {
671 return Err(NetworkConfigError::PasswordLen);
672 }
673 }
674 Credential::Psk(psk) => {
675 if security_type == &SecurityType::Wpa3 {
676 return Err(NetworkConfigError::Wpa3Psk);
677 }
678 if psk.len() != WPA_PSK_BYTE_LEN {
679 return Err(NetworkConfigError::PskLen);
680 }
681 }
682 _ => {
683 return Err(NetworkConfigError::MissingPasswordPsk);
684 }
685 },
686 }
687 Ok(())
688}
689
690#[derive(Hash, PartialEq, Eq)]
693pub enum NetworkConfigError {
694 OpenNetworkPassword,
695 Wpa3Psk,
696 PasswordLen,
697 PskLen,
698 SsidEmpty,
699 MissingPasswordPsk,
700 ConfigMissingId,
701 ConfigMissingCredential,
702 CredentialTypeInvalid,
703 FileWriteError,
704 LegacyWriteError,
705}
706
707impl Debug for NetworkConfigError {
708 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
709 match self {
710 NetworkConfigError::OpenNetworkPassword => {
711 write!(f, "can't have an open network with a password or PSK")
712 }
713 NetworkConfigError::Wpa3Psk => {
714 write!(f, "can't use a PSK to connect to a WPA3 network")
715 }
716 NetworkConfigError::PasswordLen => write!(f, "invalid password length"),
717 NetworkConfigError::PskLen => write!(f, "invalid PSK length"),
718 NetworkConfigError::SsidEmpty => {
719 write!(f, "SSID must have a non-zero length")
720 }
721 NetworkConfigError::MissingPasswordPsk => {
722 write!(f, "no password or PSK provided but required by security type")
723 }
724 NetworkConfigError::ConfigMissingId => {
725 write!(f, "cannot create network config, network id is None")
726 }
727 NetworkConfigError::ConfigMissingCredential => {
728 write!(f, "cannot create network config, no credential is given")
729 }
730 NetworkConfigError::CredentialTypeInvalid => {
731 write!(f, "cannot convert fidl Credential, unknown variant")
732 }
733 NetworkConfigError::FileWriteError => {
734 write!(f, "error writing network config to file")
735 }
736 NetworkConfigError::LegacyWriteError => {
737 write!(f, "error writing network config to legacy storage")
738 }
739 }
740 }
741}
742
743impl From<NetworkConfigError> for fidl_policy::NetworkConfigChangeError {
744 fn from(err: NetworkConfigError) -> Self {
745 match err {
746 NetworkConfigError::OpenNetworkPassword
747 | NetworkConfigError::MissingPasswordPsk
748 | NetworkConfigError::Wpa3Psk => {
749 fidl_policy::NetworkConfigChangeError::InvalidSecurityCredentialError
750 }
751 NetworkConfigError::PasswordLen | NetworkConfigError::PskLen => {
752 fidl_policy::NetworkConfigChangeError::CredentialLenError
753 }
754 NetworkConfigError::SsidEmpty => fidl_policy::NetworkConfigChangeError::SsidEmptyError,
755 NetworkConfigError::ConfigMissingId | NetworkConfigError::ConfigMissingCredential => {
756 fidl_policy::NetworkConfigChangeError::NetworkConfigMissingFieldError
757 }
758 NetworkConfigError::CredentialTypeInvalid => {
759 fidl_policy::NetworkConfigChangeError::UnsupportedCredentialError
760 }
761 NetworkConfigError::FileWriteError | NetworkConfigError::LegacyWriteError => {
762 fidl_policy::NetworkConfigChangeError::NetworkConfigWriteError
763 }
764 }
765 }
766}
767
768fn bind_credential_to_protocol(
777 protocol: SecurityDescriptor,
778 credential: &Credential,
779) -> Option<SecurityAuthenticator> {
780 match protocol {
781 SecurityDescriptor::Open => match credential {
782 Credential::None => protocol.bind(None).ok(),
783 _ => None,
784 },
785 SecurityDescriptor::Wep => match credential {
786 Credential::Password(key) => {
787 WepKey::parse(key).ok().and_then(|key| protocol.bind(Some(key.into())).ok())
788 }
789 _ => None,
790 },
791 SecurityDescriptor::Wpa(wpa) => match wpa {
792 WpaDescriptor::Wpa1 { .. } | WpaDescriptor::Wpa2 { .. } => match credential {
793 Credential::Password(passphrase) => Passphrase::try_from(passphrase.as_slice())
794 .ok()
795 .and_then(|passphrase| protocol.bind(Some(passphrase.into())).ok()),
796 Credential::Psk(psk) => {
797 Psk::parse(psk).ok().and_then(|psk| protocol.bind(Some(psk.into())).ok())
798 }
799 _ => None,
800 },
801 WpaDescriptor::Wpa3 { .. } => match credential {
802 Credential::Password(passphrase) => Passphrase::try_from(passphrase.as_slice())
803 .ok()
804 .and_then(|passphrase| protocol.bind(Some(passphrase.into())).ok()),
805 _ => None,
806 },
807 },
808 }
809}
810
811pub fn select_authentication_method(
820 mutual_security_protocols: HashSet<SecurityDescriptor>,
821 credential: &Credential,
822) -> Option<SecurityAuthenticator> {
823 let mut protocols: Vec<_> = mutual_security_protocols.into_iter().collect();
824 protocols.sort_by_key(|protocol| {
825 Reverse(match protocol {
826 SecurityDescriptor::Open => 0,
827 SecurityDescriptor::Wep => 1,
828 SecurityDescriptor::Wpa(wpa) => match wpa {
829 WpaDescriptor::Wpa1 { .. } => 2,
830 WpaDescriptor::Wpa2 { .. } => 3,
831 WpaDescriptor::Wpa3 { .. } => 4,
832 },
833 })
834 });
835 protocols
836 .into_iter()
837 .flat_map(|protocol| bind_credential_to_protocol(protocol, credential))
838 .next()
839}
840
841#[cfg(test)]
842mod tests {
843 use super::*;
844 use crate::util::testing::{generate_string, random_connection_data};
845 use assert_matches::assert_matches;
846 use std::collections::VecDeque;
847 use test_case::test_case;
848 use wlan_common::security::wep::WepAuthenticator;
849 use wlan_common::security::wpa::{
850 Authentication, Wpa1Credentials, Wpa2PersonalCredentials, Wpa3PersonalCredentials,
851 WpaAuthenticator,
852 };
853
854 #[fuchsia::test]
855 fn new_network_config_none_credential() {
856 let credential = Credential::None;
857 let network_config = NetworkConfig::new(
858 NetworkIdentifier::try_from("foo", SecurityType::None).unwrap(),
859 credential.clone(),
860 false,
861 )
862 .expect("Error creating network config for foo");
863
864 assert_eq!(
865 network_config,
866 NetworkConfig {
867 ssid: client_types::Ssid::try_from("foo").unwrap(),
868 security_type: SecurityType::None,
869 credential,
870 has_ever_connected: false,
871 hidden_probability: PROB_HIDDEN_DEFAULT,
872 hidden_probability_stats: HiddenProbabilityStats::new(),
873 perf_stats: PerformanceStats::new(),
874 scan_stats: ScanStats::new(),
875 }
876 );
877 }
878
879 #[fuchsia::test]
880 fn new_network_config_password_credential() {
881 let credential = Credential::Password(b"foo-password".to_vec());
882
883 let network_config = NetworkConfig::new(
884 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
885 credential.clone(),
886 false,
887 )
888 .expect("Error creating network config for foo");
889
890 assert_eq!(
891 network_config,
892 NetworkConfig {
893 ssid: client_types::Ssid::try_from("foo").unwrap(),
894 security_type: SecurityType::Wpa2,
895 credential,
896 has_ever_connected: false,
897 hidden_probability: PROB_HIDDEN_DEFAULT,
898 hidden_probability_stats: HiddenProbabilityStats::new(),
899 perf_stats: PerformanceStats::new(),
900 scan_stats: ScanStats::new(),
901 }
902 );
903 assert!(network_config.perf_stats.connect_failures.0.is_empty());
904 }
905
906 #[fuchsia::test]
907 fn new_network_config_psk_credential() {
908 let credential = Credential::Psk([1; WPA_PSK_BYTE_LEN].to_vec());
909
910 let network_config = NetworkConfig::new(
911 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
912 credential.clone(),
913 false,
914 )
915 .expect("Error creating network config for foo");
916
917 assert_eq!(
918 network_config,
919 NetworkConfig {
920 ssid: client_types::Ssid::try_from("foo").unwrap(),
921 security_type: SecurityType::Wpa2,
922 credential,
923 has_ever_connected: false,
924 hidden_probability: PROB_HIDDEN_DEFAULT,
925 hidden_probability_stats: HiddenProbabilityStats::new(),
926 perf_stats: PerformanceStats::new(),
927 scan_stats: ScanStats::new(),
928 }
929 );
930 }
931
932 #[fuchsia::test]
933 fn new_network_config_invalid_password() {
934 let credential = Credential::Password([1; 64].to_vec());
935
936 let config_result = NetworkConfig::new(
937 NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap(),
938 credential,
939 false,
940 );
941
942 assert_matches!(config_result, Err(NetworkConfigError::PasswordLen));
943 }
944
945 #[fuchsia::test]
946 fn new_network_config_invalid_psk() {
947 let credential = Credential::Psk(b"bar".to_vec());
948
949 let config_result = NetworkConfig::new(
950 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
951 credential,
952 false,
953 );
954
955 assert_matches!(config_result, Err(NetworkConfigError::PskLen));
956 }
957
958 #[fuchsia::test]
959 fn check_config_errors_invalid_wep_password() {
960 let password = Credential::Password(b"1234567".to_vec());
962 assert_matches!(
963 check_config_errors(
964 &client_types::Ssid::try_from("valid_ssid").unwrap(),
965 &SecurityType::Wep,
966 &password
967 ),
968 Err(NetworkConfigError::PasswordLen)
969 );
970 }
971
972 #[fuchsia::test]
973 fn check_config_errors_invalid_wpa_password() {
974 let short_password = Credential::Password(b"1234567".to_vec());
976 assert_matches!(
977 check_config_errors(
978 &client_types::Ssid::try_from("valid_ssid").unwrap(),
979 &SecurityType::Wpa2,
980 &short_password
981 ),
982 Err(NetworkConfigError::PasswordLen)
983 );
984
985 let long_password = Credential::Password([5, 65].to_vec());
987 assert_matches!(
988 check_config_errors(
989 &client_types::Ssid::try_from("valid_ssid").unwrap(),
990 &SecurityType::Wpa2,
991 &long_password
992 ),
993 Err(NetworkConfigError::PasswordLen)
994 );
995 }
996
997 #[fuchsia::test]
998 fn check_config_errors_invalid_wep_credential_variant() {
999 let psk = Credential::Psk(b"12345".to_vec());
1001 assert_matches!(
1002 check_config_errors(
1003 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1004 &SecurityType::Wep,
1005 &psk
1006 ),
1007 Err(NetworkConfigError::MissingPasswordPsk)
1008 );
1009 }
1010
1011 #[fuchsia::test]
1012 fn check_config_errors_invalid_wpa_psk() {
1013 let short_psk = Credential::Psk([6; WPA_PSK_BYTE_LEN - 1].to_vec());
1015
1016 assert_matches!(
1017 check_config_errors(
1018 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1019 &SecurityType::Wpa2,
1020 &short_psk
1021 ),
1022 Err(NetworkConfigError::PskLen)
1023 );
1024
1025 let long_psk = Credential::Psk([7; WPA_PSK_BYTE_LEN + 1].to_vec());
1026 assert_matches!(
1027 check_config_errors(
1028 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1029 &SecurityType::Wpa2,
1030 &long_psk
1031 ),
1032 Err(NetworkConfigError::PskLen)
1033 );
1034 }
1035
1036 #[fuchsia::test]
1037 fn check_config_errors_invalid_security_credential() {
1038 let password = Credential::Password(b"password".to_vec());
1040 assert_matches!(
1041 check_config_errors(
1042 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1043 &SecurityType::None,
1044 &password
1045 ),
1046 Err(NetworkConfigError::OpenNetworkPassword)
1047 );
1048
1049 let psk = Credential::Psk([1; WPA_PSK_BYTE_LEN].to_vec());
1050 assert_matches!(
1051 check_config_errors(
1052 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1053 &SecurityType::None,
1054 &psk
1055 ),
1056 Err(NetworkConfigError::OpenNetworkPassword)
1057 );
1058 let password = Credential::None;
1060 assert_matches!(
1061 check_config_errors(
1062 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1063 &SecurityType::Wpa,
1064 &password
1065 ),
1066 Err(NetworkConfigError::MissingPasswordPsk)
1067 );
1068
1069 assert_matches!(
1070 check_config_errors(
1071 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1072 &SecurityType::Wpa2,
1073 &password
1074 ),
1075 Err(NetworkConfigError::MissingPasswordPsk)
1076 );
1077
1078 assert_matches!(
1079 check_config_errors(
1080 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1081 &SecurityType::Wpa3,
1082 &password
1083 ),
1084 Err(NetworkConfigError::MissingPasswordPsk)
1085 );
1086
1087 assert_matches!(
1088 check_config_errors(
1089 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1090 &SecurityType::Wpa3,
1091 &psk
1092 ),
1093 Err(NetworkConfigError::Wpa3Psk)
1094 );
1095 }
1096
1097 #[fuchsia::test]
1098 fn check_config_errors_ssid_empty() {
1099 assert_matches!(
1100 check_config_errors(
1101 &client_types::Ssid::empty(),
1102 &SecurityType::None,
1103 &Credential::None
1104 ),
1105 Err(NetworkConfigError::SsidEmpty)
1106 );
1107 }
1108
1109 #[fasync::run_singlethreaded(test)]
1110 async fn test_connect_failures_by_bssid_add_and_get() {
1111 let mut connect_failures = HistoricalListsByBssid::new();
1112 let curr_time = fasync::MonotonicInstant::now();
1113
1114 let bssid_1 = client_types::Bssid::from([1; 6]);
1116 let failure_1_bssid_1 = ConnectFailure {
1117 time: curr_time - zx::MonotonicDuration::from_seconds(10),
1118 bssid: bssid_1,
1119 reason: FailureReason::GeneralFailure,
1120 };
1121 connect_failures.add(bssid_1, failure_1_bssid_1);
1122
1123 let failure_2_bssid_1 = ConnectFailure {
1124 time: curr_time - zx::MonotonicDuration::from_seconds(5),
1125 bssid: bssid_1,
1126 reason: FailureReason::CredentialRejected,
1127 };
1128 connect_failures.add(bssid_1, failure_2_bssid_1);
1129
1130 assert_eq!(
1132 connect_failures
1133 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(10)),
1134 vec![failure_1_bssid_1, failure_2_bssid_1]
1135 );
1136
1137 let bssid_2 = client_types::Bssid::from([2; 6]);
1139 let failure_1_bssid_2 = ConnectFailure {
1140 time: curr_time - zx::MonotonicDuration::from_seconds(3),
1141 bssid: bssid_2,
1142 reason: FailureReason::GeneralFailure,
1143 };
1144 connect_failures.add(bssid_2, failure_1_bssid_2);
1145
1146 assert_eq!(
1148 connect_failures
1149 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(10)),
1150 vec![failure_1_bssid_1, failure_2_bssid_1, failure_1_bssid_2]
1151 );
1152
1153 assert_eq!(
1155 connect_failures
1156 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(9)),
1157 vec![failure_2_bssid_1, failure_1_bssid_2]
1158 );
1159
1160 assert_eq!(connect_failures.get_recent_for_network(curr_time), vec![]);
1162
1163 assert_eq!(
1165 connect_failures.get_list_for_bss(&bssid_1),
1166 HistoricalList(VecDeque::from_iter([failure_1_bssid_1, failure_2_bssid_1]))
1167 );
1168
1169 assert_eq!(
1170 connect_failures.get_list_for_bss(&bssid_2),
1171 HistoricalList(VecDeque::from_iter([failure_1_bssid_2]))
1172 );
1173 }
1174
1175 #[fasync::run_singlethreaded(test)]
1176 async fn failure_list_add_and_get() {
1177 let mut connect_failures = HistoricalList::new(NUM_CONNECTION_RESULTS_PER_BSS);
1178
1179 let curr_time = fasync::MonotonicInstant::now();
1181 assert!(connect_failures.get_recent(curr_time).is_empty());
1182 let bssid = client_types::Bssid::from([1; 6]);
1183 let failure =
1184 ConnectFailure { time: curr_time, bssid, reason: FailureReason::GeneralFailure };
1185 connect_failures.add(failure);
1186
1187 let result_list = connect_failures.get_recent(curr_time);
1188 assert_eq!(1, result_list.len());
1189 assert_eq!(FailureReason::GeneralFailure, result_list[0].reason);
1190 assert_eq!(bssid, result_list[0].bssid);
1191 let later_time = fasync::MonotonicInstant::now();
1193 assert!(connect_failures.get_recent(later_time).is_empty());
1194 }
1195
1196 #[fasync::run_singlethreaded(test)]
1197 async fn test_failure_list_add_when_full() {
1198 let mut connect_failures = HistoricalList::new(NUM_CONNECTION_RESULTS_PER_BSS);
1199 let curr_time = fasync::MonotonicInstant::now();
1200
1201 for i in 0..connect_failures.0.capacity() + 1 {
1203 connect_failures.add(ConnectFailure {
1204 time: curr_time + zx::MonotonicDuration::from_seconds(i as i64),
1205 reason: FailureReason::GeneralFailure,
1206 bssid: client_types::Bssid::from([1; 6]),
1207 })
1208 }
1209
1210 for (i, e) in connect_failures.0.iter().enumerate() {
1212 assert_eq!(e.time, curr_time + zx::MonotonicDuration::from_seconds(i as i64 + 1));
1213 }
1214 }
1215
1216 #[fasync::run_singlethreaded(test)]
1217 async fn test_past_connections_by_bssid_add_and_get() {
1218 let mut past_connections_list = HistoricalListsByBssid::new();
1219 let curr_time = fasync::MonotonicInstant::now();
1220
1221 let mut data_1_bssid_1 = random_connection_data();
1223 let bssid_1 = data_1_bssid_1.bssid;
1224 data_1_bssid_1.disconnect_time = curr_time - zx::MonotonicDuration::from_seconds(10);
1225
1226 past_connections_list.add(bssid_1, data_1_bssid_1);
1227
1228 let mut data_2_bssid_1 = random_connection_data();
1229 data_2_bssid_1.bssid = bssid_1;
1230 data_2_bssid_1.disconnect_time = curr_time - zx::MonotonicDuration::from_seconds(5);
1231 past_connections_list.add(bssid_1, data_2_bssid_1);
1232
1233 assert_eq!(
1235 past_connections_list
1236 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(10)),
1237 vec![data_1_bssid_1, data_2_bssid_1]
1238 );
1239
1240 let mut data_1_bssid_2 = random_connection_data();
1242 let bssid_2 = data_1_bssid_2.bssid;
1243 data_1_bssid_2.disconnect_time = curr_time - zx::MonotonicDuration::from_seconds(3);
1244 past_connections_list.add(bssid_2, data_1_bssid_2);
1245
1246 assert_eq!(
1248 past_connections_list
1249 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(10)),
1250 vec![data_1_bssid_1, data_2_bssid_1, data_1_bssid_2]
1251 );
1252
1253 assert_eq!(
1255 past_connections_list
1256 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(9)),
1257 vec![data_2_bssid_1, data_1_bssid_2]
1258 );
1259
1260 assert_eq!(past_connections_list.get_recent_for_network(curr_time), vec![]);
1262
1263 assert_eq!(
1265 past_connections_list.get_list_for_bss(&bssid_1),
1266 PastConnectionList { 0: VecDeque::from_iter([data_1_bssid_1, data_2_bssid_1]) }
1267 );
1268
1269 assert_eq!(
1270 past_connections_list.get_list_for_bss(&bssid_2),
1271 PastConnectionList { 0: VecDeque::from_iter([data_1_bssid_2]) }
1272 );
1273 }
1274
1275 #[fasync::run_singlethreaded(test)]
1276 async fn test_past_connections_list_add_when_full() {
1277 let mut past_connections_list = PastConnectionList::default();
1278 let curr_time = fasync::MonotonicInstant::now();
1279
1280 for i in 0..past_connections_list.0.capacity() + 1 {
1282 let mut data = random_connection_data();
1283 data.bssid = client_types::Bssid::from([1; 6]);
1284 data.disconnect_time = curr_time + zx::MonotonicDuration::from_seconds(i as i64);
1285 past_connections_list.add(data);
1286 }
1287
1288 for (i, e) in past_connections_list.0.iter().enumerate() {
1290 assert_eq!(
1291 e.disconnect_time,
1292 curr_time + zx::MonotonicDuration::from_seconds(i as i64 + 1)
1293 );
1294 }
1295 }
1296
1297 #[fasync::run_singlethreaded(test)]
1298 async fn test_past_connections_list_add_and_get() {
1299 let mut past_connections_list = PastConnectionList::default();
1300 let curr_time = fasync::MonotonicInstant::now();
1301 assert!(past_connections_list.get_recent(curr_time).is_empty());
1302
1303 let mut past_connection_data = random_connection_data();
1304 past_connection_data.disconnect_time = curr_time;
1305 past_connections_list.add(past_connection_data);
1307
1308 assert_eq!(past_connections_list.get_recent(curr_time).len(), 1);
1310 assert_matches!(past_connections_list.get_recent(curr_time).as_slice(), [data] => {
1311 assert_eq!(data, &past_connection_data.clone());
1312 });
1313 let earlier_time = curr_time - zx::MonotonicDuration::from_seconds(1);
1314 assert_matches!(past_connections_list.get_recent(earlier_time).as_slice(), [data] => {
1315 assert_eq!(data, &data.clone());
1316 });
1317 let later_time = curr_time + zx::MonotonicDuration::from_seconds(1);
1320 assert!(past_connections_list.get_recent(later_time).is_empty());
1321 }
1322
1323 #[fuchsia::test]
1324 fn test_credential_from_bytes() {
1325 assert_eq!(Credential::from_bytes(vec![1]), Credential::Password(vec![1]));
1326 assert_eq!(Credential::from_bytes(vec![2; 63]), Credential::Password(vec![2; 63]));
1327 assert_eq!(
1329 Credential::from_bytes(vec![2; WPA_PSK_BYTE_LEN]),
1330 Credential::Password(vec![2; WPA_PSK_BYTE_LEN])
1331 );
1332 assert_eq!(Credential::from_bytes(vec![]), Credential::None);
1333 }
1334
1335 #[fuchsia::test]
1336 fn test_derived_security_type_from_credential() {
1337 let password = Credential::Password(b"password".to_vec());
1338 let psk = Credential::Psk(b"psk-type".to_vec());
1339 let none = Credential::None;
1340
1341 assert_eq!(SecurityType::Wpa2, password.derived_security_type());
1342 assert_eq!(SecurityType::Wpa2, psk.derived_security_type());
1343 assert_eq!(SecurityType::None, none.derived_security_type());
1344 }
1345
1346 #[fuchsia::test]
1347 fn test_hidden_prob_calculation() {
1348 let mut network_config = NetworkConfig::new(
1349 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1350 Credential::None,
1351 false,
1352 )
1353 .expect("Failed to create network config");
1354 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_DEFAULT);
1355
1356 network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
1357 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1358
1359 network_config.update_hidden_prob(HiddenProbEvent::ConnectPassive);
1360 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1361
1362 network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
1365 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1366 }
1367
1368 #[fuchsia::test]
1369 fn test_hidden_prob_calc_active_connect() {
1370 let mut network_config = NetworkConfig::new(
1371 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1372 Credential::None,
1373 false,
1374 )
1375 .expect("Failed to create network config");
1376
1377 network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1378 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1379
1380 network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
1383 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1384
1385 network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1388 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1389 }
1390
1391 #[fuchsia::test]
1392 fn test_hidden_prob_calc_not_seen_in_active_scan_lowers_prob() {
1393 let mut network_config = NetworkConfig::new(
1396 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1397 Credential::None,
1398 false,
1399 )
1400 .expect("Failed to create network config");
1401
1402 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1403 let expected_prob = PROB_HIDDEN_DEFAULT - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
1404 assert_eq!(network_config.hidden_probability, expected_prob);
1405
1406 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1408 let expected_prob = expected_prob - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
1409 assert_eq!(network_config.hidden_probability, expected_prob);
1410 }
1411
1412 #[fuchsia::test]
1413 fn test_hidden_prob_calc_not_seen_in_active_scan_does_not_lower_past_threshold() {
1414 let mut network_config = NetworkConfig::new(
1415 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1416 Credential::None,
1417 false,
1418 )
1419 .expect("Failed to create network config");
1420
1421 network_config.hidden_probability = PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE + 0.01;
1424 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1425 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
1426
1427 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1429 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
1430 }
1431
1432 #[fuchsia::test]
1433 fn test_hidden_prob_calc_not_seen_in_active_scan_does_not_change_if_lower_than_threshold() {
1434 let mut network_config = NetworkConfig::new(
1435 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1436 Credential::None,
1437 false,
1438 )
1439 .expect("Failed to create network config");
1440
1441 let prob_before_update = PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE - 0.1;
1445 network_config.hidden_probability = prob_before_update;
1446 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1447 assert_eq!(network_config.hidden_probability, prob_before_update);
1448 }
1449
1450 #[fuchsia::test]
1451 fn test_hidden_prob_calc_not_seen_active_after_active_connect() {
1452 let mut network_config = NetworkConfig::new(
1455 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1456 Credential::None,
1457 false,
1458 )
1459 .expect("Failed to create network config");
1460
1461 network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1462 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1463
1464 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1467 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1468 }
1469
1470 #[fuchsia::test]
1471 fn test_is_hidden_implementation() {
1472 let mut config = NetworkConfig::new(
1473 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
1474 policy_wpa_password(),
1475 false,
1476 )
1477 .expect("Error creating network config for foo");
1478 config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1479 assert!(config.is_hidden());
1480 }
1481
1482 fn policy_wep_key() -> Credential {
1483 Credential::Password("abcdef0000".as_bytes().to_vec())
1484 }
1485
1486 fn common_wep_key() -> WepKey {
1487 WepKey::parse("abcdef0000").unwrap()
1488 }
1489
1490 fn policy_wpa_password() -> Credential {
1491 Credential::Password("password".as_bytes().to_vec())
1492 }
1493
1494 fn common_wpa_password() -> Passphrase {
1495 Passphrase::try_from("password").unwrap()
1496 }
1497
1498 fn policy_wpa_psk() -> Credential {
1499 Credential::Psk(vec![0u8; WPA_PSK_BYTE_LEN])
1500 }
1501
1502 fn common_wpa_psk() -> Psk {
1503 Psk::from([0u8; WPA_PSK_BYTE_LEN])
1504 }
1505
1506 #[test_case(
1508 [SecurityDescriptor::OPEN],
1509 Credential::None
1510 =>
1511 Some(SecurityAuthenticator::Open)
1512 )]
1513 #[test_case(
1514 [SecurityDescriptor::WEP],
1515 policy_wep_key()
1516 =>
1517 Some(SecurityAuthenticator::Wep(WepAuthenticator {
1518 key: common_wep_key(),
1519 }))
1520 )]
1521 #[test_case(
1522 [SecurityDescriptor::WPA1],
1523 policy_wpa_password()
1524 =>
1525 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa1 {
1526 credentials: Wpa1Credentials::Passphrase(common_wpa_password()),
1527 }))
1528 )]
1529 #[test_case(
1530 [SecurityDescriptor::WPA1, SecurityDescriptor::WPA2_PERSONAL],
1531 policy_wpa_psk()
1532 =>
1533 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa2 {
1534 cipher: None,
1535 authentication: Authentication::Personal(
1536 Wpa2PersonalCredentials::Psk(common_wpa_psk())
1537 ),
1538 }))
1539 )]
1540 #[test_case(
1541 [SecurityDescriptor::WPA2_PERSONAL, SecurityDescriptor::WPA3_PERSONAL],
1542 policy_wpa_password()
1543 =>
1544 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa3 {
1545 cipher: None,
1546 authentication: Authentication::Personal(
1547 Wpa3PersonalCredentials::Passphrase(common_wpa_password())
1548 ),
1549 }))
1550 )]
1551 #[test_case(
1552 [SecurityDescriptor::WPA2_PERSONAL],
1553 policy_wpa_password()
1554 =>
1555 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa2 {
1556 cipher: None,
1557 authentication: Authentication::Personal(
1558 Wpa2PersonalCredentials::Passphrase(common_wpa_password())
1559 ),
1560 }))
1561 )]
1562 #[test_case(
1563 [SecurityDescriptor::WPA2_PERSONAL, SecurityDescriptor::WPA3_PERSONAL],
1564 policy_wpa_psk()
1565 =>
1566 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa2 {
1567 cipher: None,
1568 authentication: Authentication::Personal(
1569 Wpa2PersonalCredentials::Psk(common_wpa_psk())
1570 ),
1571 }))
1572 )]
1573 #[test_case(
1575 [SecurityDescriptor::WPA3_PERSONAL],
1576 policy_wpa_psk()
1577 =>
1578 None
1579 )]
1580 #[fuchsia::test(add_test_attr = false)]
1581 fn select_authentication_method_matrix(
1582 mutual_security_protocols: impl IntoIterator<Item = SecurityDescriptor>,
1583 credential: Credential,
1584 ) -> Option<SecurityAuthenticator> {
1585 super::select_authentication_method(
1586 mutual_security_protocols.into_iter().collect(),
1587 &credential,
1588 )
1589 }
1590
1591 #[test_case(SecurityType::None)]
1592 #[test_case(SecurityType::Wep)]
1593 #[test_case(SecurityType::Wpa)]
1594 #[test_case(SecurityType::Wpa2)]
1595 #[test_case(SecurityType::Wpa3)]
1596 fn test_security_type_list_includes_type(security: SecurityType) {
1597 let types = SecurityType::list_variants();
1598 assert!(types.contains(&security));
1599 }
1600
1601 #[fuchsia::test]
1603 fn test_security_type_list_completeness() {
1604 let security = SecurityType::Wpa;
1606 match security {
1609 SecurityType::None => {}
1610 SecurityType::Wep => {}
1611 SecurityType::Wpa => {}
1612 SecurityType::Wpa2 => {}
1613 SecurityType::Wpa3 => {}
1614 }
1615 }
1616
1617 #[fuchsia::test]
1618 fn test_is_likely_single_bss() {
1619 let ssid = generate_string();
1620 let mut network_config = NetworkConfig::new(
1621 NetworkIdentifier::try_from(&ssid, SecurityType::None).unwrap(),
1622 Credential::None,
1623 false,
1624 )
1625 .expect("Failed to create network config");
1626
1627 for _ in 0..5 {
1629 network_config.update_seen_multiple_bss(false);
1630 }
1631
1632 assert!(network_config.is_likely_single_bss());
1634 }
1635
1636 #[fuchsia::test]
1637 fn test_is_not_single_bss() {
1638 let ssid = generate_string();
1639 let mut network_config = NetworkConfig::new(
1640 NetworkIdentifier::try_from(&ssid, SecurityType::None).unwrap(),
1641 Credential::None,
1642 false,
1643 )
1644 .expect("Failed to create network config");
1645
1646 for _ in 0..5 {
1648 network_config.update_seen_multiple_bss(true);
1649 }
1650 network_config.update_seen_multiple_bss(false);
1651
1652 assert!(!network_config.is_likely_single_bss());
1654 }
1655
1656 #[fuchsia::test]
1657 fn test_cannot_yet_determine_single_bss() {
1658 let ssid = generate_string();
1659 let mut network_config = NetworkConfig::new(
1660 NetworkIdentifier::try_from(&ssid, SecurityType::None).unwrap(),
1661 Credential::None,
1662 false,
1663 )
1664 .expect("Failed to create network config");
1665
1666 network_config.update_seen_multiple_bss(false);
1668 network_config.update_seen_multiple_bss(false);
1669
1670 assert!(!network_config.is_likely_single_bss());
1672 }
1673}