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