1use crate::client::types as client_types;
6use arbitrary::Arbitrary;
7#[cfg(test)]
8use fidl_fuchsia_wlan_internal as fidl_internal;
9use fidl_fuchsia_wlan_policy as fidl_policy;
10use fuchsia_async as fasync;
11use log::info;
12use std::cmp::Reverse;
13use std::collections::{HashMap, HashSet};
14use std::fmt::{self, Debug};
15pub use wlan_common::historical_list::HistoricalList;
16use wlan_common::historical_list::Timestamped;
17use wlan_common::security::wep::WepKey;
18use wlan_common::security::wpa::WpaDescriptor;
19use wlan_common::security::wpa::credential::{Passphrase, Psk};
20use wlan_common::security::{SecurityAuthenticator, SecurityDescriptor};
21
22pub const NUM_CONNECTION_RESULTS_PER_BSS: usize = 10;
25pub const MAX_PAST_CONNECTIONS: usize = 120;
26const WEP_40_ASCII_LEN: usize = 5;
28const WEP_40_HEX_LEN: usize = 10;
29const WEP_104_ASCII_LEN: usize = 13;
30const WEP_104_HEX_LEN: usize = 26;
31const WPA_MIN_PASSWORD_LEN: usize = 8;
32const WPA_MAX_PASSWORD_LEN: usize = 63;
33pub const WPA_PSK_BYTE_LEN: usize = 32;
34pub const PROB_HIDDEN_IF_SEEN_PASSIVE: f32 = 0.05;
36pub const PROB_HIDDEN_IF_CONNECT_PASSIVE: f32 = 0.0;
38pub const PROB_HIDDEN_IF_CONNECT_ACTIVE: f32 = 0.95;
40pub const PROB_HIDDEN_DEFAULT: f32 = 0.9;
43pub const PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE: f32 = 0.25;
45pub const PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE: f32 = 0.14;
48pub const HIDDEN_PROBABILITY_HIGH: f32 =
53 PROB_HIDDEN_DEFAULT - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
54pub const PROB_IS_HIDDEN: f32 = PROB_HIDDEN_IF_CONNECT_ACTIVE;
58pub const NUM_SCANS_TO_DECIDE_LIKELY_SINGLE_BSS: usize = 4;
59
60pub type SaveError = fidl_policy::NetworkConfigChangeError;
61
62#[derive(Clone, Debug, PartialEq)]
64struct HiddenProbabilityStats {
65 pub connected_active: bool,
66}
67
68impl HiddenProbabilityStats {
69 fn new() -> Self {
70 HiddenProbabilityStats { connected_active: false }
71 }
72}
73
74#[derive(Clone, Debug, PartialEq)]
77pub struct PerformanceStats {
78 pub connect_failures: HashMap<client_types::Bssid, ConnectFailureList>,
79 pub past_connections: HashMap<client_types::Bssid, PastConnectionList>,
80}
81
82impl Default for PerformanceStats {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88impl PerformanceStats {
89 pub fn new() -> Self {
90 Self { connect_failures: HashMap::new(), past_connections: HashMap::new() }
91 }
92
93 pub fn get_recent_connection_failures(
94 &self,
95 earliest_time: fasync::MonotonicInstant,
96 ) -> Vec<ConnectFailure> {
97 get_recent_for_bssid_map(&self.connect_failures, earliest_time)
98 }
99
100 pub fn get_recent_connections(
101 &self,
102 earliest_time: fasync::MonotonicInstant,
103 ) -> Vec<PastConnectionData> {
104 get_recent_for_bssid_map(&self.past_connections, earliest_time)
105 }
106}
107
108#[derive(Clone, Debug, PartialEq)]
111struct ScanStats {
112 pub have_seen_multi_bss: bool,
113 pub num_scans: usize,
115}
116
117impl ScanStats {
118 pub fn new() -> Self {
119 Self { have_seen_multi_bss: false, num_scans: 0 }
120 }
121}
122
123#[derive(Clone, Copy, Debug, PartialEq)]
124pub enum FailureReason {
125 CredentialRejected,
127 GeneralFailure,
129}
130
131#[derive(Clone, Copy, Debug, PartialEq)]
132pub struct ConnectFailure {
133 pub time: fasync::MonotonicInstant,
135 pub reason: FailureReason,
137 pub bssid: client_types::Bssid,
139}
140
141impl Timestamped for ConnectFailure {
142 fn time(&self) -> fasync::MonotonicInstant {
143 self.time
144 }
145}
146
147#[derive(Clone, Copy, Debug, PartialEq)]
149pub struct PastConnectionData {
150 pub bssid: client_types::Bssid,
151 pub disconnect_time: fasync::MonotonicInstant,
153 pub connection_uptime: zx::MonotonicDuration,
155 pub disconnect_reason: client_types::DisconnectReason,
157 pub signal_at_disconnect: client_types::Signal,
159 pub average_tx_rate: u32,
161}
162
163impl PastConnectionData {
164 pub fn new(
165 bssid: client_types::Bssid,
166 disconnect_time: fasync::MonotonicInstant,
167 connection_uptime: zx::MonotonicDuration,
168 disconnect_reason: client_types::DisconnectReason,
169 signal_at_disconnect: client_types::Signal,
170 average_tx_rate: u32,
171 ) -> Self {
172 Self {
173 bssid,
174 disconnect_time,
175 connection_uptime,
176 disconnect_reason,
177 signal_at_disconnect,
178 average_tx_rate,
179 }
180 }
181}
182
183impl Timestamped for PastConnectionData {
184 fn time(&self) -> fasync::MonotonicInstant {
185 self.disconnect_time
186 }
187}
188
189pub type ConnectFailureList = HistoricalList<ConnectFailure, NUM_CONNECTION_RESULTS_PER_BSS>;
190pub type PastConnectionList = HistoricalList<PastConnectionData, MAX_PAST_CONNECTIONS>;
191pub fn new_past_connection_list() -> PastConnectionList {
192 PastConnectionList::new()
193}
194fn get_recent_for_bssid_map<T, const N: usize>(
197 map: &HashMap<client_types::Bssid, HistoricalList<T, N>>,
198 earliest_time: fasync::MonotonicInstant,
199) -> Vec<T>
200where
201 T: Timestamped + Clone,
202{
203 let mut recents: Vec<T> =
204 map.values().flat_map(|list| list.get_recent(earliest_time)).collect();
205 recents.sort_by_key(|a| a.time());
206 recents
207}
208
209#[derive(Clone, Copy)]
211pub enum HiddenProbEvent {
212 SeenPassive,
214 ConnectPassive,
216 ConnectActive,
218 NotSeenActive,
220}
221
222#[derive(Clone, Debug, PartialEq)]
224pub struct NetworkConfig {
225 pub ssid: client_types::Ssid,
227 pub security_type: SecurityType,
228 pub credential: Credential,
230 pub has_ever_connected: bool,
232 pub hidden_probability: f32,
236 hidden_probability_stats: HiddenProbabilityStats,
238 pub perf_stats: PerformanceStats,
240 scan_stats: ScanStats,
243}
244
245impl NetworkConfig {
246 pub fn new(
249 id: NetworkIdentifier,
250 credential: Credential,
251 has_ever_connected: bool,
252 hidden_probability: Option<f32>,
253 ) -> Result<Self, NetworkConfigError> {
254 check_config_errors(&id.ssid, &id.security_type, &credential)?;
255
256 Ok(Self {
257 ssid: id.ssid,
258 security_type: id.security_type,
259 credential,
260 has_ever_connected,
261 hidden_probability: hidden_probability.unwrap_or(PROB_HIDDEN_DEFAULT).clamp(0.0, 1.0),
262 hidden_probability_stats: HiddenProbabilityStats::new(),
263 perf_stats: PerformanceStats::new(),
264 scan_stats: ScanStats::new(),
265 })
266 }
267
268 pub fn update_hidden_prob(&mut self, event: HiddenProbEvent) {
273 match event {
274 HiddenProbEvent::ConnectPassive => {
275 self.hidden_probability = PROB_HIDDEN_IF_CONNECT_PASSIVE;
276 }
277 HiddenProbEvent::SeenPassive => {
278 if self.hidden_probability > PROB_HIDDEN_IF_SEEN_PASSIVE {
281 self.hidden_probability = PROB_HIDDEN_IF_SEEN_PASSIVE;
282 }
283 }
284 HiddenProbEvent::ConnectActive => {
285 self.hidden_probability_stats.connected_active = true;
286 self.hidden_probability = PROB_HIDDEN_IF_CONNECT_ACTIVE;
287 }
288 HiddenProbEvent::NotSeenActive => {
289 if self.hidden_probability_stats.connected_active {
292 return;
293 }
294 if self.hidden_probability <= PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE {
296 return;
297 }
298 let new_prob = self.hidden_probability - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
301 self.hidden_probability = new_prob.max(PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
302 }
303 }
304 }
305
306 pub fn is_hidden(&self) -> bool {
307 self.hidden_probability >= PROB_IS_HIDDEN
308 }
309
310 #[allow(clippy::assign_op_pattern, reason = "mass allow for https://fxbug.dev/381896734")]
311 pub fn update_seen_multiple_bss(&mut self, multi_bss: bool) {
312 self.scan_stats.have_seen_multi_bss = self.scan_stats.have_seen_multi_bss || multi_bss;
313 self.scan_stats.num_scans = self.scan_stats.num_scans + 1;
314 }
315
316 #[allow(clippy::needless_return, reason = "mass allow for https://fxbug.dev/381896734")]
317 pub fn is_likely_single_bss(&self) -> bool {
320 return !self.scan_stats.have_seen_multi_bss
321 && self.scan_stats.num_scans > NUM_SCANS_TO_DECIDE_LIKELY_SINGLE_BSS;
322 }
323
324 pub fn get_recent_connection_failures(
327 &self,
328 earliest_time: fasync::MonotonicInstant,
329 ) -> Vec<ConnectFailure> {
330 self.perf_stats.get_recent_connection_failures(earliest_time)
331 }
332
333 pub fn get_recent_connections(
336 &self,
337 earliest_time: fasync::MonotonicInstant,
338 ) -> Vec<PastConnectionData> {
339 self.perf_stats.get_recent_connections(earliest_time)
340 }
341}
342
343impl From<&NetworkConfig> for fidl_policy::NetworkConfig {
344 fn from(network_config: &NetworkConfig) -> Self {
345 let network_id = fidl_policy::NetworkIdentifier {
346 ssid: network_config.ssid.to_vec(),
347 type_: network_config.security_type.into(),
348 };
349 let credential = network_config.credential.clone().into();
350 fidl_policy::NetworkConfig {
351 id: Some(network_id),
352 credential: Some(credential),
353 ..Default::default()
354 }
355 }
356}
357
358#[derive(Arbitrary)] #[derive(Clone, Debug, PartialEq)]
361pub enum Credential {
362 None,
363 Password(Vec<u8>),
364 Psk(Vec<u8>),
365}
366
367impl Credential {
368 pub fn from_bytes(bytes: impl AsRef<[u8]> + Into<Vec<u8>>) -> Self {
377 match bytes.as_ref().len() {
378 0 => Credential::None,
379 _ => Credential::Password(bytes.into()),
380 }
381 }
382
383 pub fn into_bytes(self) -> Vec<u8> {
386 match self {
387 Credential::Password(pwd) => pwd,
388 Credential::Psk(psk) => psk,
389 Credential::None => vec![],
390 }
391 }
392
393 pub fn derived_security_type(&self) -> SecurityType {
396 match self {
397 Credential::None => SecurityType::None,
398 _ => SecurityType::Wpa2,
399 }
400 }
401
402 pub fn type_str(&self) -> &str {
403 match self {
404 Credential::None => "None",
405 Credential::Password(_) => "Password",
406 Credential::Psk(_) => "PSK",
407 }
408 }
409}
410
411impl TryFrom<fidl_policy::Credential> for Credential {
412 type Error = NetworkConfigError;
413 fn try_from(credential: fidl_policy::Credential) -> Result<Self, Self::Error> {
415 match credential {
416 fidl_policy::Credential::None(fidl_policy::Empty {}) => Ok(Self::None),
417 fidl_policy::Credential::Password(pwd) => Ok(Self::Password(pwd)),
418 fidl_policy::Credential::Psk(psk) => Ok(Self::Psk(psk)),
419 _ => Err(NetworkConfigError::CredentialTypeInvalid),
420 }
421 }
422}
423
424impl From<Credential> for fidl_policy::Credential {
425 fn from(credential: Credential) -> Self {
426 match credential {
427 Credential::Password(pwd) => fidl_policy::Credential::Password(pwd),
428 Credential::Psk(psk) => fidl_policy::Credential::Psk(psk),
429 Credential::None => fidl_policy::Credential::None(fidl_policy::Empty),
430 }
431 }
432}
433
434#[cfg(test)]
440impl PartialEq<Option<fidl_internal::Credentials>> for Credential {
441 fn eq(&self, credentials: &Option<fidl_internal::Credentials>) -> bool {
442 use fidl_internal::{Credentials, WepCredentials, WpaCredentials};
443
444 match credentials {
445 None => matches!(self, Credential::None),
446 Some(Credentials::Wep(WepCredentials { key })) => {
447 if let Credential::Password(unparsed) = self {
448 WepKey::parse(unparsed).is_ok_and(|parsed| &Vec::from(parsed) == key)
452 } else {
453 false
454 }
455 }
456 Some(Credentials::Wpa(credentials)) => match credentials {
457 WpaCredentials::Passphrase(passphrase) => {
458 if let Credential::Password(unparsed) = self {
459 unparsed == &passphrase.clone()
460 } else {
461 false
462 }
463 }
464 WpaCredentials::Psk(psk) => {
465 if let Credential::Psk(unparsed) = self {
466 unparsed == &Vec::from(*psk)
467 } else {
468 false
469 }
470 }
471 _ => panic!("unrecognized FIDL variant"),
472 },
473 Some(_) => panic!("unrecognized FIDL variant"),
474 }
475 }
476}
477
478#[cfg(test)]
480impl PartialEq<Option<Box<fidl_internal::Credentials>>> for Credential {
481 fn eq(&self, credentials: &Option<Box<fidl_internal::Credentials>>) -> bool {
482 self.eq(&credentials.as_ref().map(|credentials| *credentials.clone()))
483 }
484}
485
486#[derive(Arbitrary)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
488pub enum SecurityType {
489 None,
490 Wep,
491 Wpa,
492 Wpa2,
493 Wpa3,
494}
495
496impl From<SecurityDescriptor> for SecurityType {
497 fn from(descriptor: SecurityDescriptor) -> Self {
498 match descriptor {
499 SecurityDescriptor::Open => SecurityType::None,
500 SecurityDescriptor::Owe => SecurityType::None,
502 SecurityDescriptor::Wep => SecurityType::Wep,
503 SecurityDescriptor::Wpa(wpa) => match wpa {
504 WpaDescriptor::Wpa1 { .. } => SecurityType::Wpa,
505 WpaDescriptor::Wpa2 { .. } => SecurityType::Wpa2,
506 WpaDescriptor::Wpa3 { .. } => SecurityType::Wpa3,
507 },
508 }
509 }
510}
511
512impl From<fidl_policy::SecurityType> for SecurityType {
513 fn from(security: fidl_policy::SecurityType) -> Self {
514 match security {
515 fidl_policy::SecurityType::None => SecurityType::None,
516 fidl_policy::SecurityType::Wep => SecurityType::Wep,
517 fidl_policy::SecurityType::Wpa => SecurityType::Wpa,
518 fidl_policy::SecurityType::Wpa2 => SecurityType::Wpa2,
519 fidl_policy::SecurityType::Wpa3 => SecurityType::Wpa3,
520 }
521 }
522}
523
524impl From<SecurityType> for fidl_policy::SecurityType {
525 fn from(security_type: SecurityType) -> Self {
526 match security_type {
527 SecurityType::None => fidl_policy::SecurityType::None,
528 SecurityType::Wep => fidl_policy::SecurityType::Wep,
529 SecurityType::Wpa => fidl_policy::SecurityType::Wpa,
530 SecurityType::Wpa2 => fidl_policy::SecurityType::Wpa2,
531 SecurityType::Wpa3 => fidl_policy::SecurityType::Wpa3,
532 }
533 }
534}
535
536impl SecurityType {
537 pub fn list_variants() -> Vec<Self> {
539 vec![
540 SecurityType::None,
541 SecurityType::Wep,
542 SecurityType::Wpa,
543 SecurityType::Wpa2,
544 SecurityType::Wpa3,
545 ]
546 }
547
548 pub fn is_compatible_with_scanned_type(
551 &self,
552 scanned_type: &client_types::SecurityTypeDetailed,
553 ) -> bool {
554 match self {
555 SecurityType::None => {
556 scanned_type == &client_types::SecurityTypeDetailed::Open
558 }
559 SecurityType::Wep => scanned_type == &client_types::SecurityTypeDetailed::Wep,
560 SecurityType::Wpa => {
561 scanned_type == &client_types::SecurityTypeDetailed::Wpa1
562 || scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2Personal
563 || scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2PersonalTkipOnly
564 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2Personal
565 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2PersonalTkipOnly
566 }
567 SecurityType::Wpa2 => {
568 scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2Personal
569 || scanned_type == &client_types::SecurityTypeDetailed::Wpa1Wpa2PersonalTkipOnly
570 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2Personal
571 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2PersonalTkipOnly
572 || scanned_type == &client_types::SecurityTypeDetailed::Wpa2Wpa3Personal
573 || scanned_type == &client_types::SecurityTypeDetailed::Wpa3Personal
574 }
575 SecurityType::Wpa3 => {
576 scanned_type == &client_types::SecurityTypeDetailed::Wpa2Wpa3Personal
577 || scanned_type == &client_types::SecurityTypeDetailed::Wpa3Personal
578 }
579 }
580 }
581}
582
583#[derive(Arbitrary)]
586#[derive(Clone, Eq, Hash, PartialEq)]
589#[cfg_attr(test, derive(Debug))]
590pub struct NetworkIdentifier {
591 pub ssid: client_types::Ssid,
592 pub security_type: SecurityType,
593}
594
595impl fmt::Display for NetworkIdentifier {
596 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597 write!(f, "NetworkIdentifier: {}, {:?}", self.ssid, self.security_type)
598 }
599}
600
601impl NetworkIdentifier {
602 pub fn new(ssid: client_types::Ssid, security_type: SecurityType) -> Self {
603 NetworkIdentifier { ssid, security_type }
604 }
605
606 #[cfg(test)]
607 pub fn try_from(ssid: &str, security_type: SecurityType) -> Result<Self, anyhow::Error> {
608 Ok(NetworkIdentifier { ssid: client_types::Ssid::try_from(ssid)?, security_type })
609 }
610}
611
612impl From<fidl_policy::NetworkIdentifier> for NetworkIdentifier {
613 fn from(id: fidl_policy::NetworkIdentifier) -> Self {
614 Self::new(client_types::Ssid::from_bytes_unchecked(id.ssid), id.type_.into())
615 }
616}
617
618impl From<NetworkIdentifier> for fidl_policy::NetworkIdentifier {
619 fn from(id: NetworkIdentifier) -> Self {
620 fidl_policy::NetworkIdentifier { ssid: id.ssid.into(), type_: id.security_type.into() }
621 }
622}
623
624impl From<NetworkConfig> for fidl_policy::NetworkConfig {
625 fn from(config: NetworkConfig) -> Self {
626 let network_id = NetworkIdentifier::new(config.ssid, config.security_type);
627 fidl_policy::NetworkConfig {
628 id: Some(fidl_policy::NetworkIdentifier::from(network_id)),
629 credential: Some(fidl_policy::Credential::from(config.credential)),
630 ..Default::default()
631 }
632 }
633}
634
635fn check_config_errors(
639 ssid: &client_types::Ssid,
640 security_type: &SecurityType,
641 credential: &Credential,
642) -> Result<(), NetworkConfigError> {
643 if ssid.is_empty() {
645 return Err(NetworkConfigError::SsidEmpty);
646 }
647 match security_type {
650 SecurityType::None => {
651 if let Credential::Psk(_) | Credential::Password(_) = credential {
652 return Err(NetworkConfigError::OpenNetworkPassword);
653 }
654 }
655 SecurityType::Wep => match credential {
658 Credential::Password(password) => match password.len() {
659 WEP_40_ASCII_LEN | WEP_104_ASCII_LEN => {}
661 WEP_40_HEX_LEN | WEP_104_HEX_LEN => {}
663 _ => {
664 return Err(NetworkConfigError::PasswordLen);
665 }
666 },
667 _ => {
668 return Err(NetworkConfigError::MissingPasswordPsk);
669 }
670 },
671 SecurityType::Wpa | SecurityType::Wpa2 | SecurityType::Wpa3 => match credential {
672 Credential::Password(pwd) => {
673 if pwd.len() < WPA_MIN_PASSWORD_LEN || pwd.len() > WPA_MAX_PASSWORD_LEN {
674 return Err(NetworkConfigError::PasswordLen);
675 }
676 }
677 Credential::Psk(psk) => {
678 if security_type == &SecurityType::Wpa3 {
679 return Err(NetworkConfigError::Wpa3Psk);
680 }
681 if psk.len() != WPA_PSK_BYTE_LEN {
682 return Err(NetworkConfigError::PskLen);
683 }
684 }
685 _ => {
686 return Err(NetworkConfigError::MissingPasswordPsk);
687 }
688 },
689 }
690 Ok(())
691}
692
693#[derive(Hash, PartialEq, Eq)]
696pub enum NetworkConfigError {
697 OpenNetworkPassword,
698 Wpa3Psk,
699 PasswordLen,
700 PskLen,
701 SsidEmpty,
702 MissingPasswordPsk,
703 ConfigMissingId,
704 ConfigMissingCredential,
705 CredentialTypeInvalid,
706 FileWriteError,
707 LegacyWriteError,
708 MaxSavedNetworksReached,
709}
710
711impl Debug for NetworkConfigError {
712 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
713 match self {
714 NetworkConfigError::OpenNetworkPassword => {
715 write!(f, "can't have an open network with a password or PSK")
716 }
717 NetworkConfigError::Wpa3Psk => {
718 write!(f, "can't use a PSK to connect to a WPA3 network")
719 }
720 NetworkConfigError::PasswordLen => write!(f, "invalid password length"),
721 NetworkConfigError::PskLen => write!(f, "invalid PSK length"),
722 NetworkConfigError::SsidEmpty => {
723 write!(f, "SSID must have a non-zero length")
724 }
725 NetworkConfigError::MissingPasswordPsk => {
726 write!(f, "no password or PSK provided but required by security type")
727 }
728 NetworkConfigError::ConfigMissingId => {
729 write!(f, "cannot create network config, network id is None")
730 }
731 NetworkConfigError::ConfigMissingCredential => {
732 write!(f, "cannot create network config, no credential is given")
733 }
734 NetworkConfigError::CredentialTypeInvalid => {
735 write!(f, "cannot convert fidl Credential, unknown variant")
736 }
737 NetworkConfigError::FileWriteError => {
738 write!(f, "error writing network config to file")
739 }
740 NetworkConfigError::LegacyWriteError => {
741 write!(f, "error writing network config to legacy storage")
742 }
743 NetworkConfigError::MaxSavedNetworksReached => {
744 write!(f, "maximum number of saved networks reached")
745 }
746 }
747 }
748}
749
750impl From<NetworkConfigError> for fidl_policy::NetworkConfigChangeError {
751 fn from(err: NetworkConfigError) -> Self {
752 match err {
753 NetworkConfigError::OpenNetworkPassword
754 | NetworkConfigError::MissingPasswordPsk
755 | NetworkConfigError::Wpa3Psk => {
756 fidl_policy::NetworkConfigChangeError::InvalidSecurityCredentialError
757 }
758 NetworkConfigError::PasswordLen | NetworkConfigError::PskLen => {
759 fidl_policy::NetworkConfigChangeError::CredentialLenError
760 }
761 NetworkConfigError::SsidEmpty => fidl_policy::NetworkConfigChangeError::SsidEmptyError,
762 NetworkConfigError::ConfigMissingId | NetworkConfigError::ConfigMissingCredential => {
763 fidl_policy::NetworkConfigChangeError::NetworkConfigMissingFieldError
764 }
765 NetworkConfigError::CredentialTypeInvalid => {
766 fidl_policy::NetworkConfigChangeError::UnsupportedCredentialError
767 }
768 NetworkConfigError::FileWriteError | NetworkConfigError::LegacyWriteError => {
769 fidl_policy::NetworkConfigChangeError::NetworkConfigWriteError
770 }
771 NetworkConfigError::MaxSavedNetworksReached => {
772 info!("Converting NetworkConfigError::MaxSavedNetworksReached to GeneralError");
775 fidl_policy::NetworkConfigChangeError::GeneralError
776 }
777 }
778 }
779}
780
781fn bind_credential_to_protocol(
790 protocol: SecurityDescriptor,
791 credential: &Credential,
792) -> Option<SecurityAuthenticator> {
793 match protocol {
794 SecurityDescriptor::Open => match credential {
795 Credential::None => protocol.bind(None).ok(),
796 _ => None,
797 },
798 SecurityDescriptor::Owe => match credential {
799 Credential::None => protocol.bind(None).ok(),
800 _ => None,
801 },
802 SecurityDescriptor::Wep => match credential {
803 Credential::Password(key) => {
804 WepKey::parse(key).ok().and_then(|key| protocol.bind(Some(key.into())).ok())
805 }
806 _ => None,
807 },
808 SecurityDescriptor::Wpa(wpa) => match wpa {
809 WpaDescriptor::Wpa1 { .. } | WpaDescriptor::Wpa2 { .. } => match credential {
810 Credential::Password(passphrase) => Passphrase::try_from(passphrase.as_slice())
811 .ok()
812 .and_then(|passphrase| protocol.bind(Some(passphrase.into())).ok()),
813 Credential::Psk(psk) => {
814 Psk::parse(psk).ok().and_then(|psk| protocol.bind(Some(psk.into())).ok())
815 }
816 _ => None,
817 },
818 WpaDescriptor::Wpa3 { .. } => match credential {
819 Credential::Password(passphrase) => Passphrase::try_from(passphrase.as_slice())
820 .ok()
821 .and_then(|passphrase| protocol.bind(Some(passphrase.into())).ok()),
822 _ => None,
823 },
824 },
825 }
826}
827
828pub fn select_authentication_method(
837 mutual_security_protocols: HashSet<SecurityDescriptor>,
838 credential: &Credential,
839) -> Option<SecurityAuthenticator> {
840 let mut protocols: Vec<_> = mutual_security_protocols.into_iter().collect();
841 protocols.sort_by_key(|protocol| {
842 Reverse(match protocol {
843 SecurityDescriptor::Open => 0,
844 SecurityDescriptor::Owe => 3,
845 SecurityDescriptor::Wep => 1,
846 SecurityDescriptor::Wpa(wpa) => match wpa {
847 WpaDescriptor::Wpa1 { .. } => 2,
848 WpaDescriptor::Wpa2 { .. } => 4,
849 WpaDescriptor::Wpa3 { .. } => 5,
850 },
851 })
852 });
853 protocols
854 .into_iter()
855 .flat_map(|protocol| bind_credential_to_protocol(protocol, credential))
856 .next()
857}
858
859#[cfg(test)]
860mod tests {
861 use super::*;
862 use crate::util::testing::{generate_string, random_connection_data};
863 use assert_matches::assert_matches;
864 use std::collections::VecDeque;
865 use test_case::test_case;
866 use wlan_common::security::wep::WepAuthenticator;
867 use wlan_common::security::wpa::{
868 Authentication, Wpa1Credentials, Wpa2PersonalCredentials, Wpa3PersonalCredentials,
869 WpaAuthenticator,
870 };
871
872 #[fuchsia::test]
873 fn new_network_config_none_credential() {
874 let credential = Credential::None;
875 let network_config = NetworkConfig::new(
876 NetworkIdentifier::try_from("foo", SecurityType::None).unwrap(),
877 credential.clone(),
878 false,
879 None,
880 )
881 .expect("Error creating network config for foo");
882
883 assert_eq!(
884 network_config,
885 NetworkConfig {
886 ssid: client_types::Ssid::try_from("foo").unwrap(),
887 security_type: SecurityType::None,
888 credential,
889 has_ever_connected: false,
890 hidden_probability: PROB_HIDDEN_DEFAULT,
891 hidden_probability_stats: HiddenProbabilityStats::new(),
892 perf_stats: PerformanceStats::new(),
893 scan_stats: ScanStats::new(),
894 }
895 );
896 }
897
898 #[fuchsia::test]
899 fn new_network_config_with_hidden_prob_some() {
900 let credential = Credential::None;
901 let network_config = NetworkConfig::new(
902 NetworkIdentifier::try_from("foo", SecurityType::None).unwrap(),
903 credential.clone(),
904 false,
905 Some(0.05),
906 )
907 .expect("Error creating network config for foo");
908
909 assert_eq!(network_config.hidden_probability, 0.05);
910 }
911
912 #[fuchsia::test]
913 fn new_network_config_password_credential() {
914 let credential = Credential::Password(b"foo-password".to_vec());
915
916 let network_config = NetworkConfig::new(
917 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
918 credential.clone(),
919 false,
920 None,
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 assert!(network_config.perf_stats.connect_failures.is_empty());
938 }
939
940 #[fuchsia::test]
941 fn new_network_config_psk_credential() {
942 let credential = Credential::Psk([1; WPA_PSK_BYTE_LEN].to_vec());
943
944 let network_config = NetworkConfig::new(
945 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
946 credential.clone(),
947 false,
948 None,
949 )
950 .expect("Error creating network config for foo");
951
952 assert_eq!(
953 network_config,
954 NetworkConfig {
955 ssid: client_types::Ssid::try_from("foo").unwrap(),
956 security_type: SecurityType::Wpa2,
957 credential,
958 has_ever_connected: false,
959 hidden_probability: PROB_HIDDEN_DEFAULT,
960 hidden_probability_stats: HiddenProbabilityStats::new(),
961 perf_stats: PerformanceStats::new(),
962 scan_stats: ScanStats::new(),
963 }
964 );
965 }
966
967 #[fuchsia::test]
968 fn new_network_config_invalid_password() {
969 let credential = Credential::Password([1; 64].to_vec());
970
971 let config_result = NetworkConfig::new(
972 NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap(),
973 credential,
974 false,
975 None,
976 );
977
978 assert_matches!(config_result, Err(NetworkConfigError::PasswordLen));
979 }
980
981 #[fuchsia::test]
982 fn new_network_config_invalid_psk() {
983 let credential = Credential::Psk(b"bar".to_vec());
984
985 let config_result = NetworkConfig::new(
986 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
987 credential,
988 false,
989 None,
990 );
991
992 assert_matches!(config_result, Err(NetworkConfigError::PskLen));
993 }
994
995 #[fuchsia::test]
996 fn check_config_errors_invalid_wep_password() {
997 let password = Credential::Password(b"1234567".to_vec());
999 assert_matches!(
1000 check_config_errors(
1001 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1002 &SecurityType::Wep,
1003 &password
1004 ),
1005 Err(NetworkConfigError::PasswordLen)
1006 );
1007 }
1008
1009 #[fuchsia::test]
1010 fn check_config_errors_invalid_wpa_password() {
1011 let short_password = Credential::Password(b"1234567".to_vec());
1013 assert_matches!(
1014 check_config_errors(
1015 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1016 &SecurityType::Wpa2,
1017 &short_password
1018 ),
1019 Err(NetworkConfigError::PasswordLen)
1020 );
1021
1022 let long_password = Credential::Password([5, 65].to_vec());
1024 assert_matches!(
1025 check_config_errors(
1026 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1027 &SecurityType::Wpa2,
1028 &long_password
1029 ),
1030 Err(NetworkConfigError::PasswordLen)
1031 );
1032 }
1033
1034 #[fuchsia::test]
1035 fn check_config_errors_invalid_wep_credential_variant() {
1036 let psk = Credential::Psk(b"12345".to_vec());
1038 assert_matches!(
1039 check_config_errors(
1040 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1041 &SecurityType::Wep,
1042 &psk
1043 ),
1044 Err(NetworkConfigError::MissingPasswordPsk)
1045 );
1046 }
1047
1048 #[fuchsia::test]
1049 fn check_config_errors_invalid_wpa_psk() {
1050 let short_psk = Credential::Psk([6; WPA_PSK_BYTE_LEN - 1].to_vec());
1052
1053 assert_matches!(
1054 check_config_errors(
1055 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1056 &SecurityType::Wpa2,
1057 &short_psk
1058 ),
1059 Err(NetworkConfigError::PskLen)
1060 );
1061
1062 let long_psk = Credential::Psk([7; WPA_PSK_BYTE_LEN + 1].to_vec());
1063 assert_matches!(
1064 check_config_errors(
1065 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1066 &SecurityType::Wpa2,
1067 &long_psk
1068 ),
1069 Err(NetworkConfigError::PskLen)
1070 );
1071 }
1072
1073 #[fuchsia::test]
1074 fn check_config_errors_invalid_security_credential() {
1075 let password = Credential::Password(b"password".to_vec());
1077 assert_matches!(
1078 check_config_errors(
1079 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1080 &SecurityType::None,
1081 &password
1082 ),
1083 Err(NetworkConfigError::OpenNetworkPassword)
1084 );
1085
1086 let psk = Credential::Psk([1; WPA_PSK_BYTE_LEN].to_vec());
1087 assert_matches!(
1088 check_config_errors(
1089 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1090 &SecurityType::None,
1091 &psk
1092 ),
1093 Err(NetworkConfigError::OpenNetworkPassword)
1094 );
1095 let password = Credential::None;
1097 assert_matches!(
1098 check_config_errors(
1099 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1100 &SecurityType::Wpa,
1101 &password
1102 ),
1103 Err(NetworkConfigError::MissingPasswordPsk)
1104 );
1105
1106 assert_matches!(
1107 check_config_errors(
1108 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1109 &SecurityType::Wpa2,
1110 &password
1111 ),
1112 Err(NetworkConfigError::MissingPasswordPsk)
1113 );
1114
1115 assert_matches!(
1116 check_config_errors(
1117 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1118 &SecurityType::Wpa3,
1119 &password
1120 ),
1121 Err(NetworkConfigError::MissingPasswordPsk)
1122 );
1123
1124 assert_matches!(
1125 check_config_errors(
1126 &client_types::Ssid::try_from("valid_ssid").unwrap(),
1127 &SecurityType::Wpa3,
1128 &psk
1129 ),
1130 Err(NetworkConfigError::Wpa3Psk)
1131 );
1132 }
1133
1134 #[fuchsia::test]
1135 fn check_config_errors_ssid_empty() {
1136 assert_matches!(
1137 check_config_errors(
1138 &client_types::Ssid::empty(),
1139 &SecurityType::None,
1140 &Credential::None
1141 ),
1142 Err(NetworkConfigError::SsidEmpty)
1143 );
1144 }
1145
1146 #[fuchsia::test]
1147 async fn test_connect_failures_by_bssid_add_and_get() {
1148 let mut perf_stats = PerformanceStats::new();
1149 let curr_time = fasync::MonotonicInstant::now();
1150
1151 let bssid_1 = client_types::Bssid::from([1; 6]);
1153 let failure_1_bssid_1 = ConnectFailure {
1154 time: curr_time - zx::MonotonicDuration::from_seconds(10),
1155 bssid: bssid_1,
1156 reason: FailureReason::GeneralFailure,
1157 };
1158 perf_stats.connect_failures.entry(bssid_1).or_default().add(failure_1_bssid_1);
1159
1160 let failure_2_bssid_1 = ConnectFailure {
1161 time: curr_time - zx::MonotonicDuration::from_seconds(5),
1162 bssid: bssid_1,
1163 reason: FailureReason::CredentialRejected,
1164 };
1165 perf_stats.connect_failures.entry(bssid_1).or_default().add(failure_2_bssid_1);
1166
1167 assert_eq!(
1169 perf_stats.get_recent_connection_failures(
1170 curr_time - zx::MonotonicDuration::from_seconds(10)
1171 ),
1172 vec![failure_1_bssid_1, failure_2_bssid_1]
1173 );
1174
1175 let bssid_2 = client_types::Bssid::from([2; 6]);
1177 let failure_1_bssid_2 = ConnectFailure {
1178 time: curr_time - zx::MonotonicDuration::from_seconds(3),
1179 bssid: bssid_2,
1180 reason: FailureReason::GeneralFailure,
1181 };
1182 perf_stats.connect_failures.entry(bssid_2).or_default().add(failure_1_bssid_2);
1183
1184 assert_eq!(
1186 perf_stats.get_recent_connection_failures(
1187 curr_time - zx::MonotonicDuration::from_seconds(10)
1188 ),
1189 vec![failure_1_bssid_1, failure_2_bssid_1, failure_1_bssid_2]
1190 );
1191
1192 assert_eq!(
1194 perf_stats
1195 .get_recent_connection_failures(curr_time - zx::MonotonicDuration::from_seconds(9)),
1196 vec![failure_2_bssid_1, failure_1_bssid_2]
1197 );
1198
1199 assert_eq!(perf_stats.get_recent_connection_failures(curr_time), vec![]);
1201
1202 assert_eq!(
1204 perf_stats.connect_failures.get(&bssid_1).cloned().unwrap_or_default(),
1205 HistoricalList(VecDeque::from_iter([failure_1_bssid_1, failure_2_bssid_1]))
1206 );
1207
1208 assert_eq!(
1209 perf_stats.connect_failures.get(&bssid_2).cloned().unwrap_or_default(),
1210 HistoricalList(VecDeque::from_iter([failure_1_bssid_2]))
1211 );
1212 }
1213
1214 #[fuchsia::test]
1215 async fn failure_list_add_and_get() {
1216 let mut connect_failures = ConnectFailureList::new();
1217
1218 let curr_time = fasync::MonotonicInstant::now();
1220 assert!(connect_failures.get_recent(curr_time).is_empty());
1221 let bssid = client_types::Bssid::from([1; 6]);
1222 let failure =
1223 ConnectFailure { time: curr_time, bssid, reason: FailureReason::GeneralFailure };
1224 connect_failures.add(failure);
1225
1226 let result_list = connect_failures.get_recent(curr_time);
1227 assert_eq!(1, result_list.len());
1228 assert_eq!(FailureReason::GeneralFailure, result_list[0].reason);
1229 assert_eq!(bssid, result_list[0].bssid);
1230 let later_time = fasync::MonotonicInstant::now();
1232 assert!(connect_failures.get_recent(later_time).is_empty());
1233 }
1234
1235 #[fuchsia::test]
1236 async fn test_failure_list_add_when_full() {
1237 let mut connect_failures = ConnectFailureList::new();
1238 let curr_time = fasync::MonotonicInstant::now();
1239
1240 for i in 0..NUM_CONNECTION_RESULTS_PER_BSS + 1 {
1242 connect_failures.add(ConnectFailure {
1243 time: curr_time + zx::MonotonicDuration::from_seconds(i as i64),
1244 reason: FailureReason::GeneralFailure,
1245 bssid: client_types::Bssid::from([1; 6]),
1246 })
1247 }
1248
1249 for (i, e) in connect_failures.0.iter().enumerate() {
1251 assert_eq!(e.time, curr_time + zx::MonotonicDuration::from_seconds(i as i64 + 1));
1252 }
1253 }
1254
1255 #[fuchsia::test]
1256 async fn test_past_connections_by_bssid_add_and_get() {
1257 let mut perf_stats = PerformanceStats::new();
1258 let curr_time = fasync::MonotonicInstant::now();
1259
1260 let mut data_1_bssid_1 = random_connection_data();
1262 let bssid_1 = data_1_bssid_1.bssid;
1263 data_1_bssid_1.disconnect_time = curr_time - zx::MonotonicDuration::from_seconds(10);
1264
1265 perf_stats.past_connections.entry(bssid_1).or_default().add(data_1_bssid_1);
1266
1267 let mut data_2_bssid_1 = random_connection_data();
1268 data_2_bssid_1.bssid = bssid_1;
1269 data_2_bssid_1.disconnect_time = curr_time - zx::MonotonicDuration::from_seconds(5);
1270 perf_stats.past_connections.entry(bssid_1).or_default().add(data_2_bssid_1);
1271
1272 assert_eq!(
1274 perf_stats.get_recent_connections(curr_time - zx::MonotonicDuration::from_seconds(10)),
1275 vec![data_1_bssid_1, data_2_bssid_1]
1276 );
1277
1278 let mut data_1_bssid_2 = random_connection_data();
1280 let bssid_2 = data_1_bssid_2.bssid;
1281 data_1_bssid_2.disconnect_time = curr_time - zx::MonotonicDuration::from_seconds(3);
1282 perf_stats.past_connections.entry(bssid_2).or_default().add(data_1_bssid_2);
1283
1284 assert_eq!(
1286 perf_stats.get_recent_connections(curr_time - zx::MonotonicDuration::from_seconds(10)),
1287 vec![data_1_bssid_1, data_2_bssid_1, data_1_bssid_2]
1288 );
1289
1290 assert_eq!(
1292 perf_stats.get_recent_connections(curr_time - zx::MonotonicDuration::from_seconds(9)),
1293 vec![data_2_bssid_1, data_1_bssid_2]
1294 );
1295
1296 assert_eq!(perf_stats.get_recent_connections(curr_time), vec![]);
1298
1299 assert_eq!(
1301 perf_stats.past_connections.get(&bssid_1).cloned().unwrap_or_default(),
1302 PastConnectionList { 0: VecDeque::from_iter([data_1_bssid_1, data_2_bssid_1]) }
1303 );
1304
1305 assert_eq!(
1306 perf_stats.past_connections.get(&bssid_2).cloned().unwrap_or_default(),
1307 PastConnectionList { 0: VecDeque::from_iter([data_1_bssid_2]) }
1308 );
1309 }
1310
1311 #[fuchsia::test]
1312 async fn test_network_config_get_recent_methods() {
1313 let mut network_config = NetworkConfig::new(
1314 NetworkIdentifier::try_from("test_ssid", SecurityType::None).unwrap(),
1315 Credential::None,
1316 false,
1317 None,
1318 )
1319 .expect("Failed to create network config");
1320
1321 let curr_time = fasync::MonotonicInstant::now();
1322 let bssid = client_types::Bssid::from([1; 6]);
1323
1324 let failure =
1325 ConnectFailure { time: curr_time, bssid, reason: FailureReason::GeneralFailure };
1326 network_config.perf_stats.connect_failures.entry(bssid).or_default().add(failure);
1327
1328 let mut data = random_connection_data();
1329 data.bssid = bssid;
1330 data.disconnect_time = curr_time;
1331 network_config.perf_stats.past_connections.entry(bssid).or_default().add(data);
1332
1333 assert_eq!(network_config.get_recent_connection_failures(curr_time), vec![failure]);
1334 assert_eq!(network_config.get_recent_connections(curr_time), vec![data]);
1335 }
1336
1337 #[fuchsia::test]
1338 async fn test_past_connections_list_add_when_full() {
1339 let mut past_connections_list = new_past_connection_list();
1340 let curr_time = fasync::MonotonicInstant::now();
1341
1342 for i in 0..past_connections_list.0.capacity() + 1 {
1344 let mut data = random_connection_data();
1345 data.bssid = client_types::Bssid::from([1; 6]);
1346 data.disconnect_time = curr_time + zx::MonotonicDuration::from_seconds(i as i64);
1347 past_connections_list.add(data);
1348 }
1349
1350 for (i, e) in past_connections_list.0.iter().enumerate() {
1352 assert_eq!(
1353 e.disconnect_time,
1354 curr_time + zx::MonotonicDuration::from_seconds(i as i64 + 1)
1355 );
1356 }
1357 }
1358
1359 #[fuchsia::test]
1360 async fn test_past_connections_list_add_and_get() {
1361 let mut past_connections_list = new_past_connection_list();
1362 let curr_time = fasync::MonotonicInstant::now();
1363 assert!(past_connections_list.get_recent(curr_time).is_empty());
1364
1365 let mut past_connection_data = random_connection_data();
1366 past_connection_data.disconnect_time = curr_time;
1367 past_connections_list.add(past_connection_data);
1369
1370 assert_eq!(past_connections_list.get_recent(curr_time).len(), 1);
1372 assert_matches!(past_connections_list.get_recent(curr_time).as_slice(), [data] => {
1373 assert_eq!(data, &past_connection_data.clone());
1374 });
1375 let earlier_time = curr_time - zx::MonotonicDuration::from_seconds(1);
1376 assert_matches!(past_connections_list.get_recent(earlier_time).as_slice(), [data] => {
1377 assert_eq!(data, &data.clone());
1378 });
1379 let later_time = curr_time + zx::MonotonicDuration::from_seconds(1);
1382 assert!(past_connections_list.get_recent(later_time).is_empty());
1383 }
1384
1385 #[fuchsia::test]
1386 fn test_credential_from_bytes() {
1387 assert_eq!(Credential::from_bytes(vec![1]), Credential::Password(vec![1]));
1388 assert_eq!(Credential::from_bytes(vec![2; 63]), Credential::Password(vec![2; 63]));
1389 assert_eq!(
1391 Credential::from_bytes(vec![2; WPA_PSK_BYTE_LEN]),
1392 Credential::Password(vec![2; WPA_PSK_BYTE_LEN])
1393 );
1394 assert_eq!(Credential::from_bytes(vec![]), Credential::None);
1395 }
1396
1397 #[fuchsia::test]
1398 fn test_derived_security_type_from_credential() {
1399 let password = Credential::Password(b"password".to_vec());
1400 let psk = Credential::Psk(b"psk-type".to_vec());
1401 let none = Credential::None;
1402
1403 assert_eq!(SecurityType::Wpa2, password.derived_security_type());
1404 assert_eq!(SecurityType::Wpa2, psk.derived_security_type());
1405 assert_eq!(SecurityType::None, none.derived_security_type());
1406 }
1407
1408 #[fuchsia::test]
1409 fn test_hidden_prob_calculation() {
1410 let mut network_config = NetworkConfig::new(
1411 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1412 Credential::None,
1413 false,
1414 None,
1415 )
1416 .expect("Failed to create network config");
1417 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_DEFAULT);
1418
1419 network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
1420 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1421
1422 network_config.update_hidden_prob(HiddenProbEvent::ConnectPassive);
1423 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1424
1425 network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
1428 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1429 }
1430
1431 #[fuchsia::test]
1432 fn test_hidden_prob_calc_active_connect() {
1433 let mut network_config = NetworkConfig::new(
1434 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1435 Credential::None,
1436 false,
1437 None,
1438 )
1439 .expect("Failed to create network config");
1440
1441 network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1442 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1443
1444 network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
1447 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1448
1449 network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1452 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1453 }
1454
1455 #[fuchsia::test]
1456 fn test_hidden_prob_calc_not_seen_in_active_scan_lowers_prob() {
1457 let mut network_config = NetworkConfig::new(
1460 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1461 Credential::None,
1462 false,
1463 None,
1464 )
1465 .expect("Failed to create network config");
1466
1467 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1468 let expected_prob = PROB_HIDDEN_DEFAULT - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
1469 assert_eq!(network_config.hidden_probability, expected_prob);
1470
1471 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1473 let expected_prob = expected_prob - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
1474 assert_eq!(network_config.hidden_probability, expected_prob);
1475 }
1476
1477 #[fuchsia::test]
1478 fn test_hidden_prob_calc_not_seen_in_active_scan_does_not_lower_past_threshold() {
1479 let mut network_config = NetworkConfig::new(
1480 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1481 Credential::None,
1482 false,
1483 None,
1484 )
1485 .expect("Failed to create network config");
1486
1487 network_config.hidden_probability = PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE + 0.01;
1490 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1491 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
1492
1493 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1495 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
1496 }
1497
1498 #[fuchsia::test]
1499 fn test_hidden_prob_calc_not_seen_in_active_scan_does_not_change_if_lower_than_threshold() {
1500 let mut network_config = NetworkConfig::new(
1501 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1502 Credential::None,
1503 false,
1504 None,
1505 )
1506 .expect("Failed to create network config");
1507
1508 let prob_before_update = PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE - 0.1;
1512 network_config.hidden_probability = prob_before_update;
1513 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1514 assert_eq!(network_config.hidden_probability, prob_before_update);
1515 }
1516
1517 #[fuchsia::test]
1518 fn test_hidden_prob_calc_not_seen_active_after_active_connect() {
1519 let mut network_config = NetworkConfig::new(
1522 NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
1523 Credential::None,
1524 false,
1525 None,
1526 )
1527 .expect("Failed to create network config");
1528
1529 network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1530 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1531
1532 network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
1535 assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1536 }
1537
1538 #[fuchsia::test]
1539 fn test_is_hidden_implementation() {
1540 let mut config = NetworkConfig::new(
1541 NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
1542 policy_wpa_password(),
1543 false,
1544 None,
1545 )
1546 .expect("Error creating network config for foo");
1547 config.update_hidden_prob(HiddenProbEvent::ConnectActive);
1548 assert!(config.is_hidden());
1549 }
1550
1551 fn policy_wep_key() -> Credential {
1552 Credential::Password("abcdef0000".as_bytes().to_vec())
1553 }
1554
1555 fn common_wep_key() -> WepKey {
1556 WepKey::parse("abcdef0000").unwrap()
1557 }
1558
1559 fn policy_wpa_password() -> Credential {
1560 Credential::Password("password".as_bytes().to_vec())
1561 }
1562
1563 fn common_wpa_password() -> Passphrase {
1564 Passphrase::try_from("password").unwrap()
1565 }
1566
1567 fn policy_wpa_psk() -> Credential {
1568 Credential::Psk(vec![0u8; WPA_PSK_BYTE_LEN])
1569 }
1570
1571 fn common_wpa_psk() -> Psk {
1572 Psk::from([0u8; WPA_PSK_BYTE_LEN])
1573 }
1574
1575 #[test_case(
1577 [SecurityDescriptor::OPEN],
1578 Credential::None
1579 =>
1580 Some(SecurityAuthenticator::Open)
1581 )]
1582 #[test_case(
1583 [SecurityDescriptor::OWE],
1584 Credential::None
1585 =>
1586 Some(SecurityAuthenticator::Owe)
1587 )]
1588 #[test_case(
1589 [SecurityDescriptor::WEP],
1590 policy_wep_key()
1591 =>
1592 Some(SecurityAuthenticator::Wep(WepAuthenticator {
1593 key: common_wep_key(),
1594 }))
1595 )]
1596 #[test_case(
1597 [SecurityDescriptor::WPA1],
1598 policy_wpa_password()
1599 =>
1600 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa1 {
1601 credentials: Wpa1Credentials::Passphrase(common_wpa_password()),
1602 }))
1603 )]
1604 #[test_case(
1605 [SecurityDescriptor::OPEN, SecurityDescriptor::OWE],
1606 Credential::None
1607 =>
1608 Some(SecurityAuthenticator::Owe)
1609 )]
1610 #[test_case(
1611 [SecurityDescriptor::WPA1, SecurityDescriptor::WPA2_PERSONAL],
1612 policy_wpa_psk()
1613 =>
1614 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa2 {
1615 cipher: None,
1616 authentication: Authentication::Personal(
1617 Wpa2PersonalCredentials::Psk(common_wpa_psk())
1618 ),
1619 }))
1620 )]
1621 #[test_case(
1622 [SecurityDescriptor::WPA2_PERSONAL, SecurityDescriptor::WPA3_PERSONAL],
1623 policy_wpa_password()
1624 =>
1625 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa3 {
1626 cipher: None,
1627 authentication: Authentication::Personal(
1628 Wpa3PersonalCredentials::Passphrase(common_wpa_password())
1629 ),
1630 }))
1631 )]
1632 #[test_case(
1633 [SecurityDescriptor::WPA2_PERSONAL],
1634 policy_wpa_password()
1635 =>
1636 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa2 {
1637 cipher: None,
1638 authentication: Authentication::Personal(
1639 Wpa2PersonalCredentials::Passphrase(common_wpa_password())
1640 ),
1641 }))
1642 )]
1643 #[test_case(
1644 [SecurityDescriptor::WPA2_PERSONAL, SecurityDescriptor::WPA3_PERSONAL],
1645 policy_wpa_psk()
1646 =>
1647 Some(SecurityAuthenticator::Wpa(WpaAuthenticator::Wpa2 {
1648 cipher: None,
1649 authentication: Authentication::Personal(
1650 Wpa2PersonalCredentials::Psk(common_wpa_psk())
1651 ),
1652 }))
1653 )]
1654 #[test_case(
1656 [SecurityDescriptor::WPA3_PERSONAL],
1657 policy_wpa_psk()
1658 =>
1659 None
1660 )]
1661 #[fuchsia::test(add_test_attr = false)]
1662 fn select_authentication_method_matrix(
1663 mutual_security_protocols: impl IntoIterator<Item = SecurityDescriptor>,
1664 credential: Credential,
1665 ) -> Option<SecurityAuthenticator> {
1666 super::select_authentication_method(
1667 mutual_security_protocols.into_iter().collect(),
1668 &credential,
1669 )
1670 }
1671
1672 #[test_case(SecurityType::None)]
1673 #[test_case(SecurityType::Wep)]
1674 #[test_case(SecurityType::Wpa)]
1675 #[test_case(SecurityType::Wpa2)]
1676 #[test_case(SecurityType::Wpa3)]
1677 fn test_security_type_list_includes_type(security: SecurityType) {
1678 let types = SecurityType::list_variants();
1679 assert!(types.contains(&security));
1680 }
1681
1682 #[fuchsia::test]
1684 fn test_security_type_list_completeness() {
1685 let security = SecurityType::Wpa;
1687 match security {
1690 SecurityType::None => {}
1691 SecurityType::Wep => {}
1692 SecurityType::Wpa => {}
1693 SecurityType::Wpa2 => {}
1694 SecurityType::Wpa3 => {}
1695 }
1696 }
1697
1698 #[fuchsia::test]
1699 fn test_is_likely_single_bss() {
1700 let ssid = generate_string();
1701 let mut network_config = NetworkConfig::new(
1702 NetworkIdentifier::try_from(&ssid, SecurityType::None).unwrap(),
1703 Credential::None,
1704 false,
1705 None,
1706 )
1707 .expect("Failed to create network config");
1708
1709 for _ in 0..5 {
1711 network_config.update_seen_multiple_bss(false);
1712 }
1713
1714 assert!(network_config.is_likely_single_bss());
1716 }
1717
1718 #[fuchsia::test]
1719 fn test_is_not_single_bss() {
1720 let ssid = generate_string();
1721 let mut network_config = NetworkConfig::new(
1722 NetworkIdentifier::try_from(&ssid, SecurityType::None).unwrap(),
1723 Credential::None,
1724 false,
1725 None,
1726 )
1727 .expect("Failed to create network config");
1728
1729 for _ in 0..5 {
1731 network_config.update_seen_multiple_bss(true);
1732 }
1733 network_config.update_seen_multiple_bss(false);
1734
1735 assert!(!network_config.is_likely_single_bss());
1737 }
1738
1739 #[fuchsia::test]
1740 fn test_cannot_yet_determine_single_bss() {
1741 let ssid = generate_string();
1742 let mut network_config = NetworkConfig::new(
1743 NetworkIdentifier::try_from(&ssid, SecurityType::None).unwrap(),
1744 Credential::None,
1745 false,
1746 None,
1747 )
1748 .expect("Failed to create network config");
1749
1750 network_config.update_seen_multiple_bss(false);
1752 network_config.update_seen_multiple_bss(false);
1753
1754 assert!(!network_config.is_likely_single_bss());
1756 }
1757}