blob: e9bd298950b583a1cee72ef58d31b5b88cc7be90 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
fidl_fuchsia_wlan_policy as fidl_policy, fuchsia_zircon as zx,
serde::{Deserialize, Serialize},
std::{
collections::VecDeque,
convert::TryFrom,
fmt::{self, Debug},
},
};
/// The maximum number of denied connection reasons we will store for one network at a time.
/// For now this number is chosen arbitrarily.
const NUM_DENY_REASONS: usize = 10;
/// constants for the constraints on valid credential values
const WEP_40_ASCII_LEN: usize = 5;
const WEP_40_HEX_LEN: usize = 10;
const WEP_104_ASCII_LEN: usize = 13;
const WEP_104_HEX_LEN: usize = 26;
const WPA_MIN_PASSWORD_LEN: usize = 8;
const WPA_MAX_PASSWORD_LEN: usize = 63;
/// The PSK provided must be the bytes form of the 64 hexadecimal character hash
pub const WPA_PSK_LEN: usize = 32;
/// constraint on valid SSID legnth
const MAX_SSID_LEN: usize = 32;
/// If we have seen a network in a passive scan, we will rarely actively scan for it.
pub const PROB_HIDDEN_IF_SEEN_PASSIVE: f32 = 0.05;
/// If we have connected to a network from a passive scan, we will never scan for it.
pub const PROB_HIDDEN_IF_CONNECT_PASSIVE: f32 = 0.0;
/// If we connected to a network after we had to scan actively to find it, it is likely hidden.
pub const PROB_HIDDEN_IF_CONNECT_ACTIVE: f32 = 0.95;
/// Default probability that we will actively scan for the network if we haven't seen it in any
/// passive scan.
pub const PROB_HIDDEN_DEFAULT: f32 = 0.9;
/// The lowest we will set the probability for actively scanning for a network.
pub const PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE: f32 = 0.25;
/// How much we will lower the probability of scanning for an active network if we don't see the
/// network in an active scan.
pub const PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE: f32 = 0.14;
pub type SaveError = fidl_policy::NetworkConfigChangeError;
/// In-memory history of things that we need to know to calculated hidden network probability.
#[derive(Clone, Debug, PartialEq)]
struct HiddenProbabilityStats {
pub connected_active: bool,
}
impl HiddenProbabilityStats {
fn new() -> Self {
HiddenProbabilityStats { connected_active: false }
}
}
/// History of connects, disconnects, and connection strength to estimate whether we can establish
/// and maintain connection with a network and if it is weakening. Used in choosing best network.
#[derive(Clone, Debug, PartialEq)]
pub struct PerformanceStats {
/// List of recent connection failures, used to determine whether we should try connecting
/// to a network again. Capacity of list is at least NUM_DENY_REASONS.
pub failure_list: ConnectFailureList,
}
impl PerformanceStats {
pub fn new() -> Self {
Self { failure_list: ConnectFailureList::new() }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FailureReason {
// Failed to join because the authenticator did not accept the credentials provided.
CredentialRejected,
// Failed to join for other reason, mapped from SME ConnectResultCode::Failed
GeneralFailure,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ConnectFailure {
// TODO(fxbug.dev/53858) Add BSSID of the AP we failed to connect to
/// For determining whether this connection failure is still relevant
pub time: zx::Time,
/// The reason that connection failed
pub reason: FailureReason,
}
/// Ring buffer that holds network denials. It starts empty and replaces oldest when full.
#[derive(Clone, Debug, PartialEq)]
pub struct ConnectFailureList(VecDeque<ConnectFailure>);
impl ConnectFailureList {
/// The max stored number of deny reasons is at least NUM_DENY_REASONS, decided by VecDeque
pub fn new() -> Self {
Self(VecDeque::with_capacity(NUM_DENY_REASONS))
}
/// This function will be used in future work when Network Denial reasons are recorded.
/// Record network denial information in the network config, dropping the oldest information
/// if the list of denial reasons is already full before adding.
pub fn add(&mut self, reason: FailureReason) {
if self.0.len() == self.0.capacity() {
self.0.pop_front();
}
self.0.push_back(ConnectFailure { time: zx::Time::get_monotonic(), reason });
}
/// This function will be used when Network Denial reasons are used to select a network.
/// Returns a list of the denials that happened at or after the given monotonic time.
pub fn get_recent(&self, earliest_time: zx::Time) -> Vec<ConnectFailure> {
self.0.iter().skip_while(|denial| denial.time < earliest_time).cloned().collect()
}
}
/// Used to allow hidden probability calculations to make use of what happened most recently
#[derive(Clone, Copy)]
pub enum HiddenProbEvent {
/// We just saw the network in a passive scan
SeenPassive,
/// We just connected to the network using passive scan results
ConnectPassive,
/// We just connected to the network after needing an active scan to see it.
ConnectActive,
/// We just actively scanned for the network and did not see it.
NotSeenActive,
}
/// Saved data for networks, to remember how to connect to a network and determine if we should.
#[derive(Clone, Debug, PartialEq)]
pub struct NetworkConfig {
/// (persist) SSID and security type to identify a network.
pub ssid: Vec<u8>,
pub security_type: SecurityType,
/// (persist) Credential to connect to a protected network or None if the network is open.
pub credential: Credential,
/// (persist) Remember whether our network indentifier and credential work.
pub has_ever_connected: bool,
/// How confident we are that this network is hidden, between 0 and 1. We will use
/// this number to probabilistically perform an active scan for the network. This is persisted
/// to maintain consistent behavior between reboots. 0 means not hidden.
pub hidden_probability: f32,
/// Data that we use to calculate hidden_probability.
hidden_probability_stats: HiddenProbabilityStats,
/// Used to estimate quality to determine whether we want to choose this network.
pub perf_stats: PerformanceStats,
}
impl NetworkConfig {
/// A new network config is created by loading from persistent storage on boot or when a new
/// network is saved.
pub fn new(
id: NetworkIdentifier,
credential: Credential,
has_ever_connected: bool,
) -> Result<Self, NetworkConfigError> {
check_config_errors(&id.ssid, &id.security_type, &credential)?;
Ok(Self {
ssid: id.ssid,
security_type: id.security_type,
credential,
has_ever_connected,
hidden_probability: PROB_HIDDEN_DEFAULT,
hidden_probability_stats: HiddenProbabilityStats::new(),
perf_stats: PerformanceStats::new(),
})
}
// Update the network config's probability that we will actively scan for the network.
// If a network has been both seen in a passive scan and connected to after an active scan,
// we will determine probability based on what happened most recently.
// TODO(63306) Add metric to see if we see conflicting passive/active events.
pub fn update_hidden_prob(&mut self, event: HiddenProbEvent) {
match event {
HiddenProbEvent::ConnectPassive => {
self.hidden_probability = PROB_HIDDEN_IF_CONNECT_PASSIVE;
}
HiddenProbEvent::SeenPassive => {
// If the probability hidden is lower from connecting to the network after a
// passive scan, don't change.
if self.hidden_probability > PROB_HIDDEN_IF_SEEN_PASSIVE {
self.hidden_probability = PROB_HIDDEN_IF_SEEN_PASSIVE;
}
}
HiddenProbEvent::ConnectActive => {
self.hidden_probability_stats.connected_active = true;
self.hidden_probability = PROB_HIDDEN_IF_CONNECT_ACTIVE;
}
HiddenProbEvent::NotSeenActive => {
// If we have previously required an active scan to connect this network, we are
// confident that it is hidden and don't care about this event.
if self.hidden_probability_stats.connected_active {
return;
}
// The probability will not be changed if already lower than the threshold.
if self.hidden_probability <= PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE {
return;
}
// If we failed to find the network in an active scan, lower the probability but
// not below a certain threshold.
let new_prob = self.hidden_probability - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
self.hidden_probability = new_prob.max(PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
}
}
}
}
impl From<&NetworkConfig> for fidl_policy::NetworkConfig {
fn from(network_config: &NetworkConfig) -> Self {
let network_id = fidl_policy::NetworkIdentifier {
ssid: network_config.ssid.clone(),
type_: network_config.security_type.clone().into(),
};
let credential = network_config.credential.clone().into();
fidl_policy::NetworkConfig {
id: Some(network_id),
credential: Some(credential),
..fidl_policy::NetworkConfig::EMPTY
}
}
}
/// The credential of a network connection. It mirrors the fidl_fuchsia_wlan_policy Credential
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Credential {
None,
Password(Vec<u8>),
Psk(Vec<u8>),
}
impl Credential {
/// Returns:
/// - an Open-Credential instance iff `bytes` is empty,
/// - a Password-Credential in all other cases.
/// This function does not support reading PSK from bytes because the PSK byte length overlaps
/// with a valid password length. This function should only be used to load legacy data, where
/// PSK was not supported.
/// Note: This function is of temporary nature to support legacy code.
pub fn from_bytes(bytes: impl AsRef<[u8]> + Into<Vec<u8>>) -> Self {
match bytes.as_ref().len() {
0 => Credential::None,
_ => Credential::Password(bytes.into()),
}
}
/// Transform credential into the bytes that represent the credential, dropping the information
/// of the type. This is used to support the legacy storage method.
pub fn into_bytes(self) -> Vec<u8> {
match self {
Credential::Password(pwd) => pwd,
Credential::Psk(psk) => psk,
Credential::None => vec![],
}
}
/// Choose a security type that fits the credential while we don't actually know the security type
/// of the saved networks. This should only be used if we don't have a specified security type.
pub fn derived_security_type(&self) -> SecurityType {
match self {
Credential::None => SecurityType::None,
_ => SecurityType::Wpa2,
}
}
}
impl TryFrom<fidl_policy::Credential> for Credential {
type Error = NetworkConfigError;
/// Create a Credential from a fidl Crednetial value.
fn try_from(credential: fidl_policy::Credential) -> Result<Self, Self::Error> {
match credential {
fidl_policy::Credential::None(fidl_policy::Empty {}) => Ok(Self::None),
fidl_policy::Credential::Password(pwd) => Ok(Self::Password(pwd)),
fidl_policy::Credential::Psk(psk) => Ok(Self::Psk(psk)),
_ => Err(NetworkConfigError::CredentialTypeInvalid),
}
}
}
impl From<Credential> for fidl_policy::Credential {
fn from(credential: Credential) -> Self {
match credential {
Credential::Password(pwd) => fidl_policy::Credential::Password(pwd),
Credential::Psk(psk) => fidl_policy::Credential::Psk(psk),
Credential::None => fidl_policy::Credential::None(fidl_policy::Empty),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum SecurityType {
None,
Wep,
Wpa,
Wpa2,
Wpa3,
}
impl From<fidl_policy::SecurityType> for SecurityType {
fn from(security: fidl_policy::SecurityType) -> Self {
match security {
fidl_policy::SecurityType::None => SecurityType::None,
fidl_policy::SecurityType::Wep => SecurityType::Wep,
fidl_policy::SecurityType::Wpa => SecurityType::Wpa,
fidl_policy::SecurityType::Wpa2 => SecurityType::Wpa2,
fidl_policy::SecurityType::Wpa3 => SecurityType::Wpa3,
}
}
}
impl From<SecurityType> for fidl_policy::SecurityType {
fn from(security_type: SecurityType) -> Self {
match security_type {
SecurityType::None => fidl_policy::SecurityType::None,
SecurityType::Wep => fidl_policy::SecurityType::Wep,
SecurityType::Wpa => fidl_policy::SecurityType::Wpa,
SecurityType::Wpa2 => fidl_policy::SecurityType::Wpa2,
SecurityType::Wpa3 => fidl_policy::SecurityType::Wpa3,
}
}
}
/// The network identifier is the SSID and security policy of the network, and it is used to
/// distinguish networks. It mirrors the NetworkIdentifier in fidl_fuchsia_wlan_policy.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct NetworkIdentifier {
pub ssid: Vec<u8>,
pub security_type: SecurityType,
}
impl NetworkIdentifier {
pub fn new(ssid: impl Into<Vec<u8>>, security_type: SecurityType) -> Self {
NetworkIdentifier { ssid: ssid.into(), security_type }
}
}
impl From<fidl_policy::NetworkIdentifier> for NetworkIdentifier {
fn from(id: fidl_policy::NetworkIdentifier) -> Self {
Self::new(id.ssid, id.type_.into())
}
}
impl From<NetworkIdentifier> for fidl_policy::NetworkIdentifier {
fn from(id: NetworkIdentifier) -> Self {
fidl_policy::NetworkIdentifier { ssid: id.ssid, type_: id.security_type.into() }
}
}
impl From<NetworkConfig> for fidl_policy::NetworkConfig {
fn from(config: NetworkConfig) -> Self {
let network_id = NetworkIdentifier::new(config.ssid, config.security_type);
fidl_policy::NetworkConfig {
id: Some(fidl_policy::NetworkIdentifier::from(network_id)),
credential: Some(fidl_policy::Credential::from(config.credential)),
..fidl_policy::NetworkConfig::EMPTY
}
}
}
/// Returns an error if the input network values are not valid or none if the values are valid.
/// For example it is an error if the network is Open (no password) but a password is supplied.
/// TODO(nmccracken) - Specific errors need to be added to the API and returned here
fn check_config_errors(
ssid: impl AsRef<[u8]>,
security_type: &SecurityType,
credential: &Credential,
) -> Result<(), NetworkConfigError> {
// Verify SSID has at most 32 characters
if ssid.as_ref().len() > MAX_SSID_LEN {
return Err(NetworkConfigError::SsidLen);
}
// Verify that credentials match the security type. This code only inspects the lengths of
// passphrases and PSKs; the underlying data is considered opaque here.
match security_type {
SecurityType::None => {
if let Credential::Psk(_) | Credential::Password(_) = credential {
return Err(NetworkConfigError::OpenNetworkPassword);
}
}
// Note that some vendors allow WEP passphrase and PSK lengths that are not described by
// IEEE 802.11. These lengths are unsupported. See also the `wep_deprecated` crate.
SecurityType::Wep => match credential {
Credential::Password(ref password) => match password.len() {
// ASCII encoding.
WEP_40_ASCII_LEN | WEP_104_ASCII_LEN => {}
// Hexadecimal encoding.
WEP_40_HEX_LEN | WEP_104_HEX_LEN => {}
_ => {
return Err(NetworkConfigError::PasswordLen);
}
},
_ => {
return Err(NetworkConfigError::MissingPasswordPsk);
}
},
SecurityType::Wpa | SecurityType::Wpa2 | SecurityType::Wpa3 => match credential {
Credential::Password(ref pwd) => {
if pwd.len() < WPA_MIN_PASSWORD_LEN || pwd.len() > WPA_MAX_PASSWORD_LEN {
return Err(NetworkConfigError::PasswordLen);
}
}
Credential::Psk(ref psk) => {
if psk.len() != WPA_PSK_LEN {
return Err(NetworkConfigError::PskLen);
}
}
_ => {
return Err(NetworkConfigError::MissingPasswordPsk);
}
},
}
Ok(())
}
/// Error codes representing problems in trying to save a network config, such as errors saving
/// or removing a network config, or for invalid values when trying to create a network config.
pub enum NetworkConfigError {
OpenNetworkPassword,
PasswordLen,
PskLen,
SsidLen,
MissingPasswordPsk,
ConfigMissingId,
ConfigMissingCredential,
CredentialTypeInvalid,
StashWriteError,
LegacyWriteError,
}
impl Debug for NetworkConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
NetworkConfigError::OpenNetworkPassword => {
write!(f, "can't have an open network with a password or PSK")
}
NetworkConfigError::PasswordLen => write!(f, "invalid password length"),
NetworkConfigError::PskLen => write!(f, "invalid PSK length"),
NetworkConfigError::SsidLen => {
write!(f, "SSID has max allowed length of {}", MAX_SSID_LEN)
}
NetworkConfigError::MissingPasswordPsk => {
write!(f, "no password or PSK provided but required by security type")
}
NetworkConfigError::ConfigMissingId => {
write!(f, "cannot create network config, network id is None")
}
NetworkConfigError::ConfigMissingCredential => {
write!(f, "cannot create network config, no credential is given")
}
NetworkConfigError::CredentialTypeInvalid => {
write!(f, "cannot convert fidl Credential, unknown variant")
}
NetworkConfigError::StashWriteError => {
write!(f, "error writing network config to stash")
}
NetworkConfigError::LegacyWriteError => {
write!(f, "error writing network config to legacy storage")
}
}
}
}
impl From<NetworkConfigError> for fidl_policy::NetworkConfigChangeError {
fn from(_err: NetworkConfigError) -> Self {
fidl_policy::NetworkConfigChangeError::GeneralError
}
}
#[cfg(test)]
mod tests {
use {super::*, wlan_common::assert_variant};
#[test]
fn new_network_config_none_credential() {
let credential = Credential::None;
let network_config = NetworkConfig::new(
NetworkIdentifier::new("foo", SecurityType::None),
credential.clone(),
false,
)
.expect("Error creating network config for foo");
assert_eq!(
network_config,
NetworkConfig {
ssid: b"foo".to_vec(),
security_type: SecurityType::None,
credential: credential,
has_ever_connected: false,
hidden_probability: PROB_HIDDEN_DEFAULT,
hidden_probability_stats: HiddenProbabilityStats::new(),
perf_stats: PerformanceStats::new()
}
);
}
#[test]
fn new_network_config_password_credential() {
let credential = Credential::Password(b"foo-password".to_vec());
let network_config = NetworkConfig::new(
NetworkIdentifier::new("foo", SecurityType::Wpa2),
credential.clone(),
false,
)
.expect("Error creating network config for foo");
assert_eq!(
network_config,
NetworkConfig {
ssid: b"foo".to_vec(),
security_type: SecurityType::Wpa2,
credential: credential,
has_ever_connected: false,
hidden_probability: PROB_HIDDEN_DEFAULT,
hidden_probability_stats: HiddenProbabilityStats::new(),
perf_stats: PerformanceStats::new()
}
);
assert!(network_config.perf_stats.failure_list.0.is_empty());
}
#[test]
fn new_network_config_psk_credential() {
let credential = Credential::Psk([1; WPA_PSK_LEN].to_vec());
let network_config = NetworkConfig::new(
NetworkIdentifier::new("foo", SecurityType::Wpa2),
credential.clone(),
false,
)
.expect("Error creating network config for foo");
assert_eq!(
network_config,
NetworkConfig {
ssid: b"foo".to_vec(),
security_type: SecurityType::Wpa2,
credential: credential,
has_ever_connected: false,
hidden_probability: PROB_HIDDEN_DEFAULT,
hidden_probability_stats: HiddenProbabilityStats::new(),
perf_stats: PerformanceStats::new()
}
);
}
#[test]
fn new_network_config_invalid_ssid() {
let credential = Credential::None;
let config_result = NetworkConfig::new(
NetworkIdentifier::new([1; 33].to_vec(), SecurityType::Wpa2),
credential,
false,
);
assert_variant!(config_result, Err(NetworkConfigError::SsidLen));
}
#[test]
fn new_network_config_invalid_password() {
let credential = Credential::Password([1; 64].to_vec());
let config_result =
NetworkConfig::new(NetworkIdentifier::new("foo", SecurityType::Wpa), credential, false);
assert_variant!(config_result, Err(NetworkConfigError::PasswordLen));
}
#[test]
fn new_network_config_invalid_psk() {
let credential = Credential::Psk(b"bar".to_vec());
let config_result = NetworkConfig::new(
NetworkIdentifier::new("foo", SecurityType::Wpa2),
credential,
false,
);
assert_variant!(config_result, Err(NetworkConfigError::PskLen));
}
#[test]
fn check_config_errors_invalid_wep_password() {
// Unsupported length (7).
let password = Credential::Password(b"1234567".to_vec());
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::Wep, &password),
Err(NetworkConfigError::PasswordLen)
);
}
#[test]
fn check_config_errors_invalid_wpa_password() {
// password too short
let short_password = Credential::Password(b"1234567".to_vec());
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::Wpa2, &short_password),
Err(NetworkConfigError::PasswordLen)
);
// password too long
let long_password = Credential::Password([5, 65].to_vec());
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::Wpa2, &long_password),
Err(NetworkConfigError::PasswordLen)
);
}
#[test]
fn check_config_errors_invalid_wep_credential_variant() {
// Unsupported variant (`Psk`).
let psk = Credential::Psk(b"12345".to_vec());
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::Wep, &psk),
Err(NetworkConfigError::MissingPasswordPsk)
);
}
#[test]
fn check_config_errors_invalid_wpa_psk() {
// PSK length not 32 characters
let short_psk = Credential::Psk([6; WPA_PSK_LEN - 1].to_vec());
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::Wpa2, &short_psk),
Err(NetworkConfigError::PskLen)
);
let long_psk = Credential::Psk([7; WPA_PSK_LEN + 1].to_vec());
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::Wpa2, &long_psk),
Err(NetworkConfigError::PskLen)
);
}
#[test]
fn check_config_errors_invalid_security_credential() {
// Use a password with open network.
let password = Credential::Password(b"password".to_vec());
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::None, &password),
Err(NetworkConfigError::OpenNetworkPassword)
);
let psk = Credential::Psk([1; WPA_PSK_LEN].to_vec());
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::None, &psk),
Err(NetworkConfigError::OpenNetworkPassword)
);
// Use no password with a protected network.
let password = Credential::None;
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::Wpa, &password),
Err(NetworkConfigError::MissingPasswordPsk)
);
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::Wpa2, &password),
Err(NetworkConfigError::MissingPasswordPsk)
);
assert_variant!(
check_config_errors(&b"valid_ssid".to_vec(), &SecurityType::Wpa3, &password),
Err(NetworkConfigError::MissingPasswordPsk)
);
}
#[test]
fn check_config_errors_invalid_ssid() {
// The longest valid SSID length is 32, so 33 characters is too long.
let long_ssid = [6; 33].to_vec();
assert_variant!(
check_config_errors(&long_ssid, &SecurityType::None, &Credential::None),
Err(NetworkConfigError::SsidLen)
);
}
#[test]
fn failure_list_add_and_get() {
let mut failure_list = ConnectFailureList::new();
// Get time before adding so we can get back everything we added.
let curr_time = zx::Time::get_monotonic();
assert!(failure_list.get_recent(curr_time).is_empty());
failure_list.add(FailureReason::GeneralFailure);
let result_list = failure_list.get_recent(curr_time);
assert_eq!(1, result_list.len());
assert_eq!(FailureReason::GeneralFailure, result_list[0].reason);
// Should not get any results if we request denials older than the specified time.
let later_time = zx::Time::get_monotonic();
assert!(failure_list.get_recent(later_time).is_empty());
}
#[test]
fn failure_list_add_when_full() {
let mut failure_list = ConnectFailureList::new();
let curr_time = zx::Time::get_monotonic();
assert!(failure_list.get_recent(curr_time).is_empty());
let failure_list_capacity = failure_list.0.capacity();
assert!(failure_list_capacity >= NUM_DENY_REASONS);
for _i in 0..failure_list_capacity + 2 {
failure_list.add(FailureReason::GeneralFailure);
}
// Since we do not know time of each entry in the list, check the other values and length
assert_eq!(failure_list_capacity, failure_list.get_recent(curr_time).len());
for denial in failure_list.get_recent(curr_time) {
assert_eq!(FailureReason::GeneralFailure, denial.reason);
}
}
#[test]
fn get_part_of_failure_list() {
let mut failure_list = ConnectFailureList::new();
// curr_time is before or at the part of the list we want and after the one we don't want
let curr_time = zx::Time::get_monotonic();
// Inject a failure into the list that is older than the specified time.
let old_time = curr_time - zx::Duration::from_seconds(1);
failure_list
.0
.push_back(ConnectFailure { reason: FailureReason::GeneralFailure, time: old_time });
// Choose half capacity to get so that we know the previous one is still in list
let half_capacity = failure_list.0.capacity() / 2;
for _ in 0..half_capacity {
failure_list.add(FailureReason::GeneralFailure);
}
// Since we do not know time of each entry in the list, check the other values and length
assert_eq!(half_capacity, failure_list.get_recent(curr_time).len());
for denial in failure_list.get_recent(curr_time) {
assert_eq!(FailureReason::GeneralFailure, denial.reason);
}
// Add one more and check list again
failure_list.add(FailureReason::GeneralFailure);
assert_eq!(half_capacity + 1, failure_list.get_recent(curr_time).len());
for denial in failure_list.get_recent(curr_time) {
assert_eq!(FailureReason::GeneralFailure, denial.reason);
}
}
#[test]
fn test_credential_from_bytes() {
assert_eq!(Credential::from_bytes(vec![1]), Credential::Password(vec![1]));
assert_eq!(Credential::from_bytes(vec![2; 63]), Credential::Password(vec![2; 63]));
// credential from bytes should only be used to load legacy data, so PSK won't be supported
assert_eq!(
Credential::from_bytes(vec![2; WPA_PSK_LEN]),
Credential::Password(vec![2; WPA_PSK_LEN])
);
assert_eq!(Credential::from_bytes(vec![]), Credential::None);
}
#[test]
fn test_derived_security_type_from_credential() {
let password = Credential::Password(b"password".to_vec());
let psk = Credential::Psk(b"psk-type".to_vec());
let none = Credential::None;
assert_eq!(SecurityType::Wpa2, password.derived_security_type());
assert_eq!(SecurityType::Wpa2, psk.derived_security_type());
assert_eq!(SecurityType::None, none.derived_security_type());
}
#[test]
fn test_hidden_prob_calculation() {
let mut network_config = NetworkConfig::new(
NetworkIdentifier::new(b"some_ssid".to_vec(), SecurityType::None),
Credential::None,
false,
)
.expect("Failed to create network config");
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_DEFAULT);
network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
network_config.update_hidden_prob(HiddenProbEvent::ConnectPassive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
// Hidden probability shouldn't go back up after seeing a network in a passive
// scan again after connecting with a passive scan
network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
}
#[test]
fn test_hidden_prob_calc_active_connect() {
let mut network_config = NetworkConfig::new(
NetworkIdentifier::new(b"some_ssid".to_vec(), SecurityType::None),
Credential::None,
false,
)
.expect("Failed to create network config");
network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
// If we see a network in a passive scan after connecting from an active scan,
// we won't care that we previously needed an active scan.
network_config.update_hidden_prob(HiddenProbEvent::SeenPassive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
// If we require an active scan to connect to a network, raise probability as if the
// network has become hidden.
network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
}
#[test]
fn test_hidden_prob_calc_not_seen_in_active_scan_lowers_prob() {
// Test that updating hidden probability after not seeing the network in a directed active
// scan lowers the hidden probability
let mut network_config = NetworkConfig::new(
NetworkIdentifier::new(b"some_ssid".to_vec(), SecurityType::None),
Credential::None,
false,
)
.expect("Failed to create network config");
network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
let expected_prob = PROB_HIDDEN_DEFAULT - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
assert_eq!(network_config.hidden_probability, expected_prob);
// If we update hidden probability again, the probability should lower again.
network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
let expected_prob = expected_prob - PROB_HIDDEN_INCREMENT_NOT_SEEN_ACTIVE;
assert_eq!(network_config.hidden_probability, expected_prob);
}
#[test]
fn test_hidden_prob_calc_not_seen_in_active_scan_does_not_lower_past_threshold() {
let mut network_config = NetworkConfig::new(
NetworkIdentifier::new(b"some_ssid".to_vec(), SecurityType::None),
Credential::None,
false,
)
.expect("Failed to create network config");
// If hidden probability is slightly above the minimum from not seing the network in an
// active scan, it should not be lowered past the minimum.
network_config.hidden_probability = PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE + 0.01;
network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
// If hidden probability is at the minimum, it should not be lowered.
network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE);
}
#[test]
fn test_hidden_prob_calc_not_seen_in_active_scan_does_not_change_if_lower_than_threshold() {
let mut network_config = NetworkConfig::new(
NetworkIdentifier::new(b"some_ssid".to_vec(), SecurityType::None),
Credential::None,
false,
)
.expect("Failed to create network config");
// If the hidden probability is lower than the minimum of not seeing the network in an,
// active scan, which could happen after seeing it in a passive scan, the hidden
// probability will not lower from this event.
let prob_before_update = PROB_HIDDEN_MIN_FROM_NOT_SEEN_ACTIVE - 0.1;
network_config.hidden_probability = prob_before_update;
network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
assert_eq!(network_config.hidden_probability, prob_before_update);
}
#[test]
fn test_hidden_prob_calc_not_seen_active_after_active_connect() {
// Test the specific case where we fail to see the network in an active scan after we
// previously connected to the network after an active scan was required.
let mut network_config = NetworkConfig::new(
NetworkIdentifier::new(b"some_ssid".to_vec(), SecurityType::None),
Credential::None,
false,
)
.expect("Failed to create network config");
network_config.update_hidden_prob(HiddenProbEvent::ConnectActive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
// If we update the probability after a not-seen-in-active-scan, the probability should
// still reflect that we think the network is hidden after the connect.
network_config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
assert_eq!(network_config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
}
}