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