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