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