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::Owe => SecurityType::None,
507 SecurityDescriptor::Wep => SecurityType::Wep,
508 SecurityDescriptor::Wpa(wpa) => match wpa {
509 WpaDescriptor::Wpa1 { .. } => SecurityType::Wpa,
510 WpaDescriptor::Wpa2 { .. } => SecurityType::Wpa2,
511 WpaDescriptor::Wpa3 { .. } => SecurityType::Wpa3,
512 },
513 }
514 }
515}
516
517impl From<fidl_policy::SecurityType> for SecurityType {
518 fn from(security: fidl_policy::SecurityType) -> Self {
519 match security {
520 fidl_policy::SecurityType::None => SecurityType::None,
521 fidl_policy::SecurityType::Wep => SecurityType::Wep,
522 fidl_policy::SecurityType::Wpa => SecurityType::Wpa,
523 fidl_policy::SecurityType::Wpa2 => SecurityType::Wpa2,
524 fidl_policy::SecurityType::Wpa3 => SecurityType::Wpa3,
525 }
526 }
527}
528
529impl From<SecurityType> for fidl_policy::SecurityType {
530 fn from(security_type: SecurityType) -> Self {
531 match security_type {
532 SecurityType::None => fidl_policy::SecurityType::None,
533 SecurityType::Wep => fidl_policy::SecurityType::Wep,
534 SecurityType::Wpa => fidl_policy::SecurityType::Wpa,
535 SecurityType::Wpa2 => fidl_policy::SecurityType::Wpa2,
536 SecurityType::Wpa3 => fidl_policy::SecurityType::Wpa3,
537 }
538 }
539}
540
541impl SecurityType {
542 pub fn list_variants() -> Vec<Self> {
544 vec![
545 SecurityType::None,
546 SecurityType::Wep,
547 SecurityType::Wpa,
548 SecurityType::Wpa2,
549 SecurityType::Wpa3,
550 ]
551 }
552
553 pub fn is_compatible_with_scanned_type(
556 &self,
557 scanned_type: &client_types::SecurityTypeDetailed,
558 ) -> bool {
559 match self {
560 SecurityType::None => {
561 scanned_type == &client_types::SecurityTypeDetailed::Open
563 }
564 SecurityType::Wep => scanned_type == &client_types::SecurityTypeDetailed::Wep,
565 SecurityType::Wpa => {
566 scanned_type == &client_types::SecurityTypeDetailed::Wpa1
567 || scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2Personal
568 || scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2PersonalTkipOnly
569 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2Personal
570 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2PersonalTkipOnly
571 }
572 SecurityType::Wpa2 => {
573 scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2Personal
574 || scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2PersonalTkipOnly
575 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2Personal
576 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2PersonalTkipOnly
577 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2Wpa3Personal
578 || scanned_type == &client_types::SecurityTypeDetailed::Wpa3Personal
579 }
580 SecurityType::Wpa3 => {
581 scanned_type == &client_types::SecurityTypeDetailed::Wpa2Wpa3Personal
582 || scanned_type == &client_types::SecurityTypeDetailed::Wpa3Personal
583 }
584 }
585 }
586}
587
588#[derive(Arbitrary)]
591#[derive(Clone, Eq, Hash, PartialEq)]
594#[cfg_attr(test, derive(Debug))]
595pub struct NetworkIdentifier {
596 pub ssid: client_types::Ssid,
597 pub security_type: SecurityType,
598}
599
600impl NetworkIdentifier {
601 pub fn new(ssid: client_types::Ssid, security_type: SecurityType) -> Self {
602 NetworkIdentifier { ssid, security_type }
603 }
604
605 #[cfg(test)]
606 pub fn try_from(ssid: &str, security_type: SecurityType) -> Result<Self, anyhow::Error> {
607 Ok(NetworkIdentifier { ssid: client_types::Ssid::try_from(ssid)?, security_type })
608 }
609}
610
611impl From<fidl_policy::NetworkIdentifier> for NetworkIdentifier {
612 fn from(id: fidl_policy::NetworkIdentifier) -> Self {
613 Self::new(client_types::Ssid::from_bytes_unchecked(id.ssid), id.type_.into())
614 }
615}
616
617impl From<NetworkIdentifier> for fidl_policy::NetworkIdentifier {
618 fn from(id: NetworkIdentifier) -> Self {
619 fidl_policy::NetworkIdentifier { ssid: id.ssid.into(), type_: id.security_type.into() }
620 }
621}
622
623impl From<NetworkConfig> for fidl_policy::NetworkConfig {
624 fn from(config: NetworkConfig) -> Self {
625 let network_id = NetworkIdentifier::new(config.ssid, config.security_type);
626 fidl_policy::NetworkConfig {
627 id: Some(fidl_policy::NetworkIdentifier::from(network_id)),
628 credential: Some(fidl_policy::Credential::from(config.credential)),
629 ..Default::default()
630 }
631 }
632}
633
634fn check_config_errors(
638 ssid: &client_types::Ssid,
639 security_type: &SecurityType,
640 credential: &Credential,
641) -> Result<(), NetworkConfigError> {
642 if ssid.is_empty() {
644 return Err(NetworkConfigError::SsidEmpty);
645 }
646 match security_type {
649 SecurityType::None => {
650 if let Credential::Psk(_) | Credential::Password(_) = credential {
651 return Err(NetworkConfigError::OpenNetworkPassword);
652 }
653 }
654 SecurityType::Wep => match credential {
657 Credential::Password(password) => match password.len() {
658 WEP_40_ASCII_LEN | WEP_104_ASCII_LEN => {}
660 WEP_40_HEX_LEN | WEP_104_HEX_LEN => {}
662 _ => {
663 return Err(NetworkConfigError::PasswordLen);
664 }
665 },
666 _ => {
667 return Err(NetworkConfigError::MissingPasswordPsk);
668 }
669 },
670 SecurityType::Wpa | SecurityType::Wpa2 | SecurityType::Wpa3 => match credential {
671 Credential::Password(pwd) => {
672 if pwd.len() < WPA_MIN_PASSWORD_LEN || pwd.len() > WPA_MAX_PASSWORD_LEN {
673 return Err(NetworkConfigError::PasswordLen);
674 }
675 }
676 Credential::Psk(psk) => {
677 if security_type == &SecurityType::Wpa3 {
678 return Err(NetworkConfigError::Wpa3Psk);
679 }
680 if psk.len() != WPA_PSK_BYTE_LEN {
681 return Err(NetworkConfigError::PskLen);
682 }
683 }
684 _ => {
685 return Err(NetworkConfigError::MissingPasswordPsk);
686 }
687 },
688 }
689 Ok(())
690}
691
692#[derive(Hash, PartialEq, Eq)]
695pub enum NetworkConfigError {
696 OpenNetworkPassword,
697 Wpa3Psk,
698 PasswordLen,
699 PskLen,
700 SsidEmpty,
701 MissingPasswordPsk,
702 ConfigMissingId,
703 ConfigMissingCredential,
704 CredentialTypeInvalid,
705 FileWriteError,
706 LegacyWriteError,
707}
708
709impl Debug for NetworkConfigError {
710 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
711 match self {
712 NetworkConfigError::OpenNetworkPassword => {
713 write!(f, "can't have an open network with a password or PSK")
714 }
715 NetworkConfigError::Wpa3Psk => {
716 write!(f, "can't use a PSK to connect to a WPA3 network")
717 }
718 NetworkConfigError::PasswordLen => write!(f, "invalid password length"),
719 NetworkConfigError::PskLen => write!(f, "invalid PSK length"),
720 NetworkConfigError::SsidEmpty => {
721 write!(f, "SSID must have a non-zero length")
722 }
723 NetworkConfigError::MissingPasswordPsk => {
724 write!(f, "no password or PSK provided but required by security type")
725 }
726 NetworkConfigError::ConfigMissingId => {
727 write!(f, "cannot create network config, network id is None")
728 }
729 NetworkConfigError::ConfigMissingCredential => {
730 write!(f, "cannot create network config, no credential is given")
731 }
732 NetworkConfigError::CredentialTypeInvalid => {
733 write!(f, "cannot convert fidl Credential, unknown variant")
734 }
735 NetworkConfigError::FileWriteError => {
736 write!(f, "error writing network config to file")
737 }
738 NetworkConfigError::LegacyWriteError => {
739 write!(f, "error writing network config to legacy storage")
740 }
741 }
742 }
743}
744
745impl From<NetworkConfigError> for fidl_policy::NetworkConfigChangeError {
746 fn from(err: NetworkConfigError) -> Self {
747 match err {
748 NetworkConfigError::OpenNetworkPassword
749 | NetworkConfigError::MissingPasswordPsk
750 | NetworkConfigError::Wpa3Psk => {
751 fidl_policy::NetworkConfigChangeError::InvalidSecurityCredentialError
752 }
753 NetworkConfigError::PasswordLen | NetworkConfigError::PskLen => {
754 fidl_policy::NetworkConfigChangeError::CredentialLenError
755 }
756 NetworkConfigError::SsidEmpty => fidl_policy::NetworkConfigChangeError::SsidEmptyError,
757 NetworkConfigError::ConfigMissingId | NetworkConfigError::ConfigMissingCredential => {
758 fidl_policy::NetworkConfigChangeError::NetworkConfigMissingFieldError
759 }
760 NetworkConfigError::CredentialTypeInvalid => {
761 fidl_policy::NetworkConfigChangeError::UnsupportedCredentialError
762 }
763 NetworkConfigError::FileWriteError | NetworkConfigError::LegacyWriteError => {
764 fidl_policy::NetworkConfigChangeError::NetworkConfigWriteError
765 }
766 }
767 }
768}
769
770fn bind_credential_to_protocol(
779 protocol: SecurityDescriptor,
780 credential: &Credential,
781) -> Option<SecurityAuthenticator> {
782 match protocol {
783 SecurityDescriptor::Open => match credential {
784 Credential::None => protocol.bind(None).ok(),
785 _ => None,
786 },
787 SecurityDescriptor::Owe => match credential {
788 Credential::None => protocol.bind(None).ok(),
789 _ => None,
790 },
791 SecurityDescriptor::Wep => match credential {
792 Credential::Password(key) => {
793 WepKey::parse(key).ok().and_then(|key| protocol.bind(Some(key.into())).ok())
794 }
795 _ => None,
796 },
797 SecurityDescriptor::Wpa(wpa) => match wpa {
798 WpaDescriptor::Wpa1 { .. } | WpaDescriptor::Wpa2 { .. } => match credential {
799 Credential::Password(passphrase) => Passphrase::try_from(passphrase.as_slice())
800 .ok()
801 .and_then(|passphrase| protocol.bind(Some(passphrase.into())).ok()),
802 Credential::Psk(psk) => {
803 Psk::parse(psk).ok().and_then(|psk| protocol.bind(Some(psk.into())).ok())
804 }
805 _ => None,
806 },
807 WpaDescriptor::Wpa3 { .. } => match credential {
808 Credential::Password(passphrase) => Passphrase::try_from(passphrase.as_slice())
809 .ok()
810 .and_then(|passphrase| protocol.bind(Some(passphrase.into())).ok()),
811 _ => None,
812 },
813 },
814 }
815}
816
817pub fn select_authentication_method(
826 mutual_security_protocols: HashSet<SecurityDescriptor>,
827 credential: &Credential,
828) -> Option<SecurityAuthenticator> {
829 let mut protocols: Vec<_> = mutual_security_protocols.into_iter().collect();
830 protocols.sort_by_key(|protocol| {
831 Reverse(match protocol {
832 SecurityDescriptor::Open => 0,
833 SecurityDescriptor::Owe => 3,
834 SecurityDescriptor::Wep => 1,
835 SecurityDescriptor::Wpa(wpa) => match wpa {
836 WpaDescriptor::Wpa1 { .. } => 2,
837 WpaDescriptor::Wpa2 { .. } => 4,
838 WpaDescriptor::Wpa3 { .. } => 5,
839 },
840 })
841 });
842 protocols
843 .into_iter()
844 .flat_map(|protocol| bind_credential_to_protocol(protocol, credential))
845 .next()
846}
847
848#[cfg(test)]
849mod tests {
850 use super::*;
851 use crate::util::testing::{generate_string, random_connection_data};
852 use assert_matches::assert_matches;
853 use std::collections::VecDeque;
854 use test_case::test_case;
855 use wlan_common::security::wep::WepAuthenticator;
856 use wlan_common::security::wpa::{
857 Authentication, Wpa1Credentials, Wpa2PersonalCredentials, Wpa3PersonalCredentials,
858 WpaAuthenticator,
859 };
860
861 #[fuchsia::test]
862 fn new_network_config_none_credential() {
863 let credential = Credential::None;
864 let network_config = NetworkConfig::new(
865 NetworkIdentifier::try_from("foo", SecurityType::None).unwrap(),
866 credential.clone(),
867 false,
868 )
869 .expect("Error creating network config for foo");
870
871 assert_eq!(
872 network_config,
873 NetworkConfig {
874 ssid: client_types::Ssid::try_from("foo").unwrap(),
875 security_type: SecurityType::None,
876 credential,
877 has_ever_connected: false,
878 hidden_probability: PROB_HIDDEN_DEFAULT,
879 hidden_probability_stats: HiddenProbabilityStats::new(),
880 perf_stats: PerformanceStats::new(),
881 scan_stats: ScanStats::new(),
882 }
883 );
884 }
885
886 #[fuchsia::test]
887 fn new_network_config_password_credential() {
888 let credential = Credential::Password(b"foo-password".to_vec());
889
890 let network_config = NetworkConfig::new(
891 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
892 credential.clone(),
893 false,
894 )
895 .expect("Error creating network config for foo");
896
897 assert_eq!(
898 network_config,
899 NetworkConfig {
900 ssid: client_types::Ssid::try_from("foo").unwrap(),
901 security_type: SecurityType::Wpa2,
902 credential,
903 has_ever_connected: false,
904 hidden_probability: PROB_HIDDEN_DEFAULT,
905 hidden_probability_stats: HiddenProbabilityStats::new(),
906 perf_stats: PerformanceStats::new(),
907 scan_stats: ScanStats::new(),
908 }
909 );
910 assert!(network_config.perf_stats.connect_failures.0.is_empty());
911 }
912
913 #[fuchsia::test]
914 fn new_network_config_psk_credential() {
915 let credential = Credential::Psk([1; WPA_PSK_BYTE_LEN].to_vec());
916
917 let network_config = NetworkConfig::new(
918 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
919 credential.clone(),
920 false,
921 )
922 .expect("Error creating network config for foo");
923
924 assert_eq!(
925 network_config,
926 NetworkConfig {
927 ssid: client_types::Ssid::try_from("foo").unwrap(),
928 security_type: SecurityType::Wpa2,
929 credential,
930 has_ever_connected: false,
931 hidden_probability: PROB_HIDDEN_DEFAULT,
932 hidden_probability_stats: HiddenProbabilityStats::new(),
933 perf_stats: PerformanceStats::new(),
934 scan_stats: ScanStats::new(),
935 }
936 );
937 }
938
939 #[fuchsia::test]
940 fn new_network_config_invalid_password() {
941 let credential = Credential::Password([1; 64].to_vec());
942
943 let config_result = NetworkConfig::new(
944 NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap(),
945 credential,
946 false,
947 );
948
949 assert_matches!(config_result, Err(NetworkConfigError::PasswordLen));
950 }
951
952 #[fuchsia::test]
953 fn new_network_config_invalid_psk() {
954 let credential = Credential::Psk(b"bar".to_vec());
955
956 let config_result = NetworkConfig::new(
957 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
958 credential,
959 false,
960 );
961
962 assert_matches!(config_result, Err(NetworkConfigError::PskLen));
963 }
964
965 #[fuchsia::test]
966 fn check_config_errors_invalid_wep_password() {
967 let password = Credential::Password(b"1234567".to_vec());
969 assert_matches!(
970 check_config_errors(
971 &client_types::Ssid::try_from("valid_ssid").unwrap(),
972 &SecurityType::Wep,
973 &password
974 ),
975 Err(NetworkConfigError::PasswordLen)
976 );
977 }
978
979 #[fuchsia::test]
980 fn check_config_errors_invalid_wpa_password() {
981 let short_password = Credential::Password(b"1234567".to_vec());
983 assert_matches!(
984 check_config_errors(
985 &client_types::Ssid::try_from("valid_ssid").unwrap(),
986 &SecurityType::Wpa2,
987 &short_password
988 ),
989 Err(NetworkConfigError::PasswordLen)
990 );
991
992 let long_password = Credential::Password([5, 65].to_vec());
994 assert_matches!(
995 check_config_errors(
996 &client_types::Ssid::try_from("valid_ssid").unwrap(),
997 &SecurityType::Wpa2,
998 &long_password
999 ),
1000 Err(NetworkConfigError::PasswordLen)
1001 );
1002 }
1003
1004 #[fuchsia::test]
1005 fn check_config_errors_invalid_wep_credential_variant() {
1006 let psk = Credential::Psk(b"12345".to_vec());
1008 assert_matches!(
1009 check_config_errors(
1010 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1011 &SecurityType::Wep,
1012 &psk
1013 ),
1014 Err(NetworkConfigError::MissingPasswordPsk)
1015 );
1016 }
1017
1018 #[fuchsia::test]
1019 fn check_config_errors_invalid_wpa_psk() {
1020 let short_psk = Credential::Psk([6; WPA_PSK_BYTE_LEN - 1].to_vec());
1022
1023 assert_matches!(
1024 check_config_errors(
1025 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1026 &SecurityType::Wpa2,
1027 &short_psk
1028 ),
1029 Err(NetworkConfigError::PskLen)
1030 );
1031
1032 let long_psk = Credential::Psk([7; WPA_PSK_BYTE_LEN + 1].to_vec());
1033 assert_matches!(
1034 check_config_errors(
1035 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1036 &SecurityType::Wpa2,
1037 &long_psk
1038 ),
1039 Err(NetworkConfigError::PskLen)
1040 );
1041 }
1042
1043 #[fuchsia::test]
1044 fn check_config_errors_invalid_security_credential() {
1045 let password = Credential::Password(b"password".to_vec());
1047 assert_matches!(
1048 check_config_errors(
1049 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1050 &SecurityType::None,
1051 &password
1052 ),
1053 Err(NetworkConfigError::OpenNetworkPassword)
1054 );
1055
1056 let psk = Credential::Psk([1; WPA_PSK_BYTE_LEN].to_vec());
1057 assert_matches!(
1058 check_config_errors(
1059 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1060 &SecurityType::None,
1061 &psk
1062 ),
1063 Err(NetworkConfigError::OpenNetworkPassword)
1064 );
1065 let password = Credential::None;
1067 assert_matches!(
1068 check_config_errors(
1069 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1070 &SecurityType::Wpa,
1071 &password
1072 ),
1073 Err(NetworkConfigError::MissingPasswordPsk)
1074 );
1075
1076 assert_matches!(
1077 check_config_errors(
1078 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1079 &SecurityType::Wpa2,
1080 &password
1081 ),
1082 Err(NetworkConfigError::MissingPasswordPsk)
1083 );
1084
1085 assert_matches!(
1086 check_config_errors(
1087 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1088 &SecurityType::Wpa3,
1089 &password
1090 ),
1091 Err(NetworkConfigError::MissingPasswordPsk)
1092 );
1093
1094 assert_matches!(
1095 check_config_errors(
1096 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1097 &SecurityType::Wpa3,
1098 &psk
1099 ),
1100 Err(NetworkConfigError::Wpa3Psk)
1101 );
1102 }
1103
1104 #[fuchsia::test]
1105 fn check_config_errors_ssid_empty() {
1106 assert_matches!(
1107 check_config_errors(
1108 &client_types::Ssid::empty(),
1109 &SecurityType::None,
1110 &Credential::None
1111 ),
1112 Err(NetworkConfigError::SsidEmpty)
1113 );
1114 }
1115
1116 #[fasync::run_singlethreaded(test)]
1117 async fn test_connect_failures_by_bssid_add_and_get() {
1118 let mut connect_failures = HistoricalListsByBssid::new();
1119 let curr_time = fasync::MonotonicInstant::now();
1120
1121 let bssid_1 = client_types::Bssid::from([1; 6]);
1123 let failure_1_bssid_1 = ConnectFailure {
1124 time: curr_time - zx::MonotonicDuration::from_seconds(10),
1125 bssid: bssid_1,
1126 reason: FailureReason::GeneralFailure,
1127 };
1128 connect_failures.add(bssid_1, failure_1_bssid_1);
1129
1130 let failure_2_bssid_1 = ConnectFailure {
1131 time: curr_time - zx::MonotonicDuration::from_seconds(5),
1132 bssid: bssid_1,
1133 reason: FailureReason::CredentialRejected,
1134 };
1135 connect_failures.add(bssid_1, failure_2_bssid_1);
1136
1137 assert_eq!(
1139 connect_failures
1140 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(10)),
1141 vec![failure_1_bssid_1, failure_2_bssid_1]
1142 );
1143
1144 let bssid_2 = client_types::Bssid::from([2; 6]);
1146 let failure_1_bssid_2 = ConnectFailure {
1147 time: curr_time - zx::MonotonicDuration::from_seconds(3),
1148 bssid: bssid_2,
1149 reason: FailureReason::GeneralFailure,
1150 };
1151 connect_failures.add(bssid_2, failure_1_bssid_2);
1152
1153 assert_eq!(
1155 connect_failures
1156 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(10)),
1157 vec![failure_1_bssid_1, failure_2_bssid_1, failure_1_bssid_2]
1158 );
1159
1160 assert_eq!(
1162 connect_failures
1163 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(9)),
1164 vec![failure_2_bssid_1, failure_1_bssid_2]
1165 );
1166
1167 assert_eq!(connect_failures.get_recent_for_network(curr_time), vec![]);
1169
1170 assert_eq!(
1172 connect_failures.get_list_for_bss(&bssid_1),
1173 HistoricalList(VecDeque::from_iter([failure_1_bssid_1, failure_2_bssid_1]))
1174 );
1175
1176 assert_eq!(
1177 connect_failures.get_list_for_bss(&bssid_2),
1178 HistoricalList(VecDeque::from_iter([failure_1_bssid_2]))
1179 );
1180 }
1181
1182 #[fasync::run_singlethreaded(test)]
1183 async fn failure_list_add_and_get() {
1184 let mut connect_failures = HistoricalList::new(NUM_CONNECTION_RESULTS_PER_BSS);
1185
1186 let curr_time = fasync::MonotonicInstant::now();
1188 assert!(connect_failures.get_recent(curr_time).is_empty());
1189 let bssid = client_types::Bssid::from([1; 6]);
1190 let failure =
1191 ConnectFailure { time: curr_time, bssid, reason: FailureReason::GeneralFailure };
1192 connect_failures.add(failure);
1193
1194 let result_list = connect_failures.get_recent(curr_time);
1195 assert_eq!(1, result_list.len());
1196 assert_eq!(FailureReason::GeneralFailure, result_list[0].reason);
1197 assert_eq!(bssid, result_list[0].bssid);
1198 let later_time = fasync::MonotonicInstant::now();
1200 assert!(connect_failures.get_recent(later_time).is_empty());
1201 }
1202
1203 #[fasync::run_singlethreaded(test)]
1204 async fn test_failure_list_add_when_full() {
1205 let mut connect_failures = HistoricalList::new(NUM_CONNECTION_RESULTS_PER_BSS);
1206 let curr_time = fasync::MonotonicInstant::now();
1207
1208 for i in 0..connect_failures.0.capacity() + 1 {
1210 connect_failures.add(ConnectFailure {
1211 time: curr_time + zx::MonotonicDuration::from_seconds(i as i64),
1212 reason: FailureReason::GeneralFailure,
1213 bssid: client_types::Bssid::from([1; 6]),
1214 })
1215 }
1216
1217 for (i, e) in connect_failures.0.iter().enumerate() {
1219 assert_eq!(e.time, curr_time + zx::MonotonicDuration::from_seconds(i as i64 + 1));
1220 }
1221 }
1222
1223 #[fasync::run_singlethreaded(test)]
1224 async fn test_past_connections_by_bssid_add_and_get() {
1225 let mut past_connections_list = HistoricalListsByBssid::new();
1226 let curr_time = fasync::MonotonicInstant::now();
1227
1228 let mut data_1_bssid_1 = random_connection_data();
1230 let bssid_1 = data_1_bssid_1.bssid;
1231 data_1_bssid_1.disconnect_time = curr_time - zx::MonotonicDuration::from_seconds(10);
1232
1233 past_connections_list.add(bssid_1, data_1_bssid_1);
1234
1235 let mut data_2_bssid_1 = random_connection_data();
1236 data_2_bssid_1.bssid = bssid_1;
1237 data_2_bssid_1.disconnect_time = curr_time - zx::MonotonicDuration::from_seconds(5);
1238 past_connections_list.add(bssid_1, data_2_bssid_1);
1239
1240 assert_eq!(
1242 past_connections_list
1243 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(10)),
1244 vec![data_1_bssid_1, data_2_bssid_1]
1245 );
1246
1247 let mut data_1_bssid_2 = random_connection_data();
1249 let bssid_2 = data_1_bssid_2.bssid;
1250 data_1_bssid_2.disconnect_time = curr_time - zx::MonotonicDuration::from_seconds(3);
1251 past_connections_list.add(bssid_2, data_1_bssid_2);
1252
1253 assert_eq!(
1255 past_connections_list
1256 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(10)),
1257 vec![data_1_bssid_1, data_2_bssid_1, data_1_bssid_2]
1258 );
1259
1260 assert_eq!(
1262 past_connections_list
1263 .get_recent_for_network(curr_time - zx::MonotonicDuration::from_seconds(9)),
1264 vec![data_2_bssid_1, data_1_bssid_2]
1265 );
1266
1267 assert_eq!(past_connections_list.get_recent_for_network(curr_time), vec![]);
1269
1270 assert_eq!(
1272 past_connections_list.get_list_for_bss(&bssid_1),
1273 PastConnectionList { 0: VecDeque::from_iter([data_1_bssid_1, data_2_bssid_1]) }
1274 );
1275
1276 assert_eq!(
1277 past_connections_list.get_list_for_bss(&bssid_2),
1278 PastConnectionList { 0: VecDeque::from_iter([data_1_bssid_2]) }
1279 );
1280 }
1281
1282 #[fasync::run_singlethreaded(test)]
1283 async fn test_past_connections_list_add_when_full() {
1284 let mut past_connections_list = PastConnectionList::default();
1285 let curr_time = fasync::MonotonicInstant::now();
1286
1287 for i in 0..past_connections_list.0.capacity() + 1 {
1289 let mut data = random_connection_data();
1290 data.bssid = client_types::Bssid::from([1; 6]);
1291 data.disconnect_time = curr_time + zx::MonotonicDuration::from_seconds(i as i64);
1292 past_connections_list.add(data);
1293 }
1294
1295 for (i, e) in past_connections_list.0.iter().enumerate() {
1297 assert_eq!(
1298 e.disconnect_time,
1299 curr_time + zx::MonotonicDuration::from_seconds(i as i64 + 1)
1300 );
1301 }
1302 }
1303
1304 #[fasync::run_singlethreaded(test)]
1305 async fn test_past_connections_list_add_and_get() {
1306 let mut past_connections_list = PastConnectionList::default();
1307 let curr_time = fasync::MonotonicInstant::now();
1308 assert!(past_connections_list.get_recent(curr_time).is_empty());
1309
1310 let mut past_connection_data = random_connection_data();
1311 past_connection_data.disconnect_time = curr_time;
1312 past_connections_list.add(past_connection_data);
1314
1315 assert_eq!(past_connections_list.get_recent(curr_time).len(), 1);
1317 assert_matches!(past_connections_list.get_recent(curr_time).as_slice(), [data] => {
1318 assert_eq!(data, &past_connection_data.clone());
1319 });
1320 let earlier_time = curr_time - zx::MonotonicDuration::from_seconds(1);
1321 assert_matches!(past_connections_list.get_recent(earlier_time).as_slice(), [data] => {
1322 assert_eq!(data, &data.clone());
1323 });
1324 let later_time = curr_time + zx::MonotonicDuration::from_seconds(1);
1327 assert!(past_connections_list.get_recent(later_time).is_empty());
1328 }
1329
1330 #[fuchsia::test]
1331 fn test_credential_from_bytes() {
1332 assert_eq!(Credential::from_bytes(vec![1]), Credential::Password(vec![1]));
1333 assert_eq!(Credential::from_bytes(vec![2; 63]), Credential::Password(vec![2; 63]));
1334 assert_eq!(
1336 Credential::from_bytes(vec![2; WPA_PSK_BYTE_LEN]),
1337 Credential::Password(vec![2; WPA_PSK_BYTE_LEN])
1338 );
1339 assert_eq!(Credential::from_bytes(vec![]), Credential::None);
1340 }
1341
1342 #[fuchsia::test]
1343 fn test_derived_security_type_from_credential() {
1344 let password = Credential::Password(b"password".to_vec());
1345 let psk = Credential::Psk(b"psk-type".to_vec());
1346 let none = Credential::None;
1347
1348 assert_eq!(SecurityType::Wpa2, password.derived_security_type());
1349 assert_eq!(SecurityType::Wpa2, psk.derived_security_type());
1350 assert_eq!(SecurityType::None, none.derived_security_type());
1351 }
1352
1353 #[fuchsia::test]
1354 fn test_hidden_prob_calculation() {
1355 let mut network_config = NetworkConfig::new(
1356 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1357 Credential::None,
1358 false,
1359 )
1360 .expect("Failed to create network config");
1361 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_DEFAULT);
1362
1363 network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
1364 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1365
1366 network_config.update_hidden_prob(HiddenProbEvent::ConnectPassive);
1367 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1368
1369 network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
1372 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1373 }
1374
1375 #[fuchsia::test]
1376 fn test_hidden_prob_calc_active_connect() {
1377 let mut network_config = NetworkConfig::new(
1378 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1379 Credential::None,
1380 false,
1381 )
1382 .expect("Failed to create network config");
1383
1384 network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1385 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1386
1387 network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
1390 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1391
1392 network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1395 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1396 }
1397
1398 #[fuchsia::test]
1399 fn test_hidden_prob_calc_not_seen_in_active_scan_lowers_prob() {
1400 let mut network_config = NetworkConfig::new(
1403 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1404 Credential::None,
1405 false,
1406 )
1407 .expect("Failed to create network config");
1408
1409 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1410 let expected_prob = PROB_HIDDEN_DEFAULT - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
1411 assert_eq!(network_config.hidden_probability, expected_prob);
1412
1413 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1415 let expected_prob = expected_prob - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
1416 assert_eq!(network_config.hidden_probability, expected_prob);
1417 }
1418
1419 #[fuchsia::test]
1420 fn test_hidden_prob_calc_not_seen_in_active_scan_does_not_lower_past_threshold() {
1421 let mut network_config = NetworkConfig::new(
1422 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1423 Credential::None,
1424 false,
1425 )
1426 .expect("Failed to create network config");
1427
1428 network_config.hidden_probability = PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE + 0.01;
1431 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1432 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
1433
1434 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1436 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
1437 }
1438
1439 #[fuchsia::test]
1440 fn test_hidden_prob_calc_not_seen_in_active_scan_does_not_change_if_lower_than_threshold() {
1441 let mut network_config = NetworkConfig::new(
1442 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1443 Credential::None,
1444 false,
1445 )
1446 .expect("Failed to create network config");
1447
1448 let prob_before_update = PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE - 0.1;
1452 network_config.hidden_probability = prob_before_update;
1453 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1454 assert_eq!(network_config.hidden_probability, prob_before_update);
1455 }
1456
1457 #[fuchsia::test]
1458 fn test_hidden_prob_calc_not_seen_active_after_active_connect() {
1459 let mut network_config = NetworkConfig::new(
1462 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1463 Credential::None,
1464 false,
1465 )
1466 .expect("Failed to create network config");
1467
1468 network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1469 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1470
1471 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1474 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1475 }
1476
1477 #[fuchsia::test]
1478 fn test_is_hidden_implementation() {
1479 let mut config = NetworkConfig::new(
1480 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
1481 policy_wpa_password(),
1482 false,
1483 )
1484 .expect("Error creating network config for foo");
1485 config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1486 assert!(config.is_hidden());
1487 }
1488
1489 fn policy_wep_key() -> Credential {
1490 Credential::Password("abcdef0000".as_bytes().to_vec())
1491 }
1492
1493 fn common_wep_key() -> WepKey {
1494 WepKey::parse("abcdef0000").unwrap()
1495 }
1496
1497 fn policy_wpa_password() -> Credential {
1498 Credential::Password("password".as_bytes().to_vec())
1499 }
1500
1501 fn common_wpa_password() -> Passphrase {
1502 Passphrase::try_from("password").unwrap()
1503 }
1504
1505 fn policy_wpa_psk() -> Credential {
1506 Credential::Psk(vec![0u8; WPA_PSK_BYTE_LEN])
1507 }
1508
1509 fn common_wpa_psk() -> Psk {
1510 Psk::from([0u8; WPA_PSK_BYTE_LEN])
1511 }
1512
1513 #[test_case(
1515 [SecurityDescriptor::OPEN],
1516 Credential::None
1517 =>
1518 Some(SecurityAuthenticator::Open)
1519 )]
1520 #[test_case(
1521 [SecurityDescriptor::OWE],
1522 Credential::None
1523 =>
1524 Some(SecurityAuthenticator::Owe)
1525 )]
1526 #[test_case(
1527 [SecurityDescriptor::WEP],
1528 policy_wep_key()
1529 =>
1530 Some(SecurityAuthenticator::Wep(WepAuthenticator {
1531 key: common_wep_key(),
1532 }))
1533 )]
1534 #[test_case(
1535 [SecurityDescriptor::WPA1],
1536 policy_wpa_password()
1537 =>
1538 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa1 {
1539 credentials: Wpa1Credentials::Passphrase(common_wpa_password()),
1540 }))
1541 )]
1542 #[test_case(
1543 [SecurityDescriptor::OPEN, SecurityDescriptor::OWE],
1544 Credential::None
1545 =>
1546 Some(SecurityAuthenticator::Owe)
1547 )]
1548 #[test_case(
1549 [SecurityDescriptor::WPA1, SecurityDescriptor::WPA2_PERSONAL],
1550 policy_wpa_psk()
1551 =>
1552 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa2 {
1553 cipher: None,
1554 authentication: Authentication::Personal(
1555 Wpa2PersonalCredentials::Psk(common_wpa_psk())
1556 ),
1557 }))
1558 )]
1559 #[test_case(
1560 [SecurityDescriptor::WPA2_PERSONAL, SecurityDescriptor::WPA3_PERSONAL],
1561 policy_wpa_password()
1562 =>
1563 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa3 {
1564 cipher: None,
1565 authentication: Authentication::Personal(
1566 Wpa3PersonalCredentials::Passphrase(common_wpa_password())
1567 ),
1568 }))
1569 )]
1570 #[test_case(
1571 [SecurityDescriptor::WPA2_PERSONAL],
1572 policy_wpa_password()
1573 =>
1574 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa2 {
1575 cipher: None,
1576 authentication: Authentication::Personal(
1577 Wpa2PersonalCredentials::Passphrase(common_wpa_password())
1578 ),
1579 }))
1580 )]
1581 #[test_case(
1582 [SecurityDescriptor::WPA2_PERSONAL, SecurityDescriptor::WPA3_PERSONAL],
1583 policy_wpa_psk()
1584 =>
1585 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa2 {
1586 cipher: None,
1587 authentication: Authentication::Personal(
1588 Wpa2PersonalCredentials::Psk(common_wpa_psk())
1589 ),
1590 }))
1591 )]
1592 #[test_case(
1594 [SecurityDescriptor::WPA3_PERSONAL],
1595 policy_wpa_psk()
1596 =>
1597 None
1598 )]
1599 #[fuchsia::test(add_test_attr = false)]
1600 fn select_authentication_method_matrix(
1601 mutual_security_protocols: impl IntoIterator<Item = SecurityDescriptor>,
1602 credential: Credential,
1603 ) -> Option<SecurityAuthenticator> {
1604 super::select_authentication_method(
1605 mutual_security_protocols.into_iter().collect(),
1606 &credential,
1607 )
1608 }
1609
1610 #[test_case(SecurityType::None)]
1611 #[test_case(SecurityType::Wep)]
1612 #[test_case(SecurityType::Wpa)]
1613 #[test_case(SecurityType::Wpa2)]
1614 #[test_case(SecurityType::Wpa3)]
1615 fn test_security_type_list_includes_type(security: SecurityType) {
1616 let types = SecurityType::list_variants();
1617 assert!(types.contains(&security));
1618 }
1619
1620 #[fuchsia::test]
1622 fn test_security_type_list_completeness() {
1623 let security = SecurityType::Wpa;
1625 match security {
1628 SecurityType::None => {}
1629 SecurityType::Wep => {}
1630 SecurityType::Wpa => {}
1631 SecurityType::Wpa2 => {}
1632 SecurityType::Wpa3 => {}
1633 }
1634 }
1635
1636 #[fuchsia::test]
1637 fn test_is_likely_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(false);
1649 }
1650
1651 assert!(network_config.is_likely_single_bss());
1653 }
1654
1655 #[fuchsia::test]
1656 fn test_is_not_single_bss() {
1657 let ssid = generate_string();
1658 let mut network_config = NetworkConfig::new(
1659 NetworkIdentifier::try_from(&ssid, SecurityType::None).unwrap(),
1660 Credential::None,
1661 false,
1662 )
1663 .expect("Failed to create network config");
1664
1665 for _ in 0..5 {
1667 network_config.update_seen_multiple_bss(true);
1668 }
1669 network_config.update_seen_multiple_bss(false);
1670
1671 assert!(!network_config.is_likely_single_bss());
1673 }
1674
1675 #[fuchsia::test]
1676 fn test_cannot_yet_determine_single_bss() {
1677 let ssid = generate_string();
1678 let mut network_config = NetworkConfig::new(
1679 NetworkIdentifier::try_from(&ssid, SecurityType::None).unwrap(),
1680 Credential::None,
1681 false,
1682 )
1683 .expect("Failed to create network config");
1684
1685 network_config.update_seen_multiple_bss(false);
1687 network_config.update_seen_multiple_bss(false);
1688
1689 assert!(!network_config.is_likely_single_bss());
1691 }
1692}