blob: 6513d834bd4f077ad6a8a994608f615fb9417b45 [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 {
crate::client::{bss_selection::SignalData, types as client_types},
arbitrary::Arbitrary,
fidl_fuchsia_wlan_common_security as fidl_security, fidl_fuchsia_wlan_policy as fidl_policy,
fuchsia_async as fasync, fuchsia_zircon as zx,
std::{
collections::{HashMap, VecDeque},
convert::TryFrom,
fmt::{self, Debug},
},
};
/// The max number of connection results we will store per BSS at a time. For now, this number is
/// chosen arbitartily.
const NUM_CONNECTION_RESULTS_PER_BSS: 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;
pub const WPA_PSK_BYTE_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 {
pub connect_failures: ConnectFailuresByBssid,
pub past_connections: PastConnectionsByBssid,
}
impl PerformanceStats {
pub fn new() -> Self {
Self {
connect_failures: ConnectFailuresByBssid::new(),
past_connections: PastConnectionsByBssid::new(),
}
}
}
/// Trait for time function, for use in HistoricalList get_recent functions and AddAndGetRecent
/// associated type
pub trait Time {
fn time(&self) -> fasync::Time;
}
/// Trait for use in HistoricalListsByBssid generic
pub trait AddAndGetRecent {
type Data;
fn add(&mut self, historical_data: Self::Data);
fn get_recent(&self, earliest_time: fasync::Time) -> Vec<Self::Data>;
}
/// Generic struct for list that stores historical data in a VecDeque, up to the some number of most
/// recent entries.
#[derive(Clone, Debug, PartialEq)]
pub struct HistoricalList<T: Time>(VecDeque<T>);
impl<T> HistoricalList<T>
where
T: Time + Clone,
{
pub fn new() -> Self {
Self(VecDeque::with_capacity(NUM_CONNECTION_RESULTS_PER_BSS))
}
}
impl<T> AddAndGetRecent for HistoricalList<T>
where
T: Time + Clone,
{
type Data = T;
/// Add a new entry, purging the oldest if at capacity
fn add(&mut self, historical_data: T) {
if self.0.len() == self.0.capacity() {
let _ = self.0.pop_front();
}
self.0.push_back(historical_data);
}
/// Retrieve list of entries with a time more recent than earliest_time, sorted from oldest to
/// newest. May be empty.
fn get_recent(&self, earliest_time: fasync::Time) -> Vec<T> {
let i = self.0.partition_point(|data| data.time() < earliest_time);
return self.0.iter().skip(i).cloned().collect();
}
}
impl<T> Default for HistoricalList<T>
where
T: Time + Clone,
{
fn default() -> Self {
Self::new()
}
}
/// Generic struct for map from BSSID to HistoricalList
#[derive(Clone, Debug, PartialEq)]
pub struct HistoricalListsByBssid<List>(HashMap<client_types::Bssid, List>);
impl<Data, List> HistoricalListsByBssid<List>
where
Data: Time + Clone,
List: AddAndGetRecent<Data = Data> + Default + Clone,
{
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn add(&mut self, bssid: client_types::Bssid, data: Data) {
self.0.entry(bssid).or_default().add(data);
}
/// Retrieve list of Data entries to any BSS with a time more recent than earliest_time, sorted
/// from oldest to newest. May be empty.
pub fn get_recent_for_network(&self, earliest_time: fasync::Time) -> Vec<Data> {
let mut recents: Vec<Data> = vec![];
for bssid in self.0.keys() {
recents.append(&mut self.get_list_for_bss(bssid).get_recent(earliest_time));
}
recents.sort_by(|a, b| a.time().cmp(&b.time()));
recents
}
/// Retrieve List for a particular BSS, in order to retrieve BSS specific Data entries.
pub fn get_list_for_bss(&self, bssid: &client_types::Bssid) -> List {
self.0.get(bssid).cloned().unwrap_or_default()
}
}
#[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 {
/// For determining whether this connection failure is still relevant
pub time: fasync::Time,
/// The reason that connection failed
pub reason: FailureReason,
/// The BSSID that we failed to connect to
pub bssid: client_types::Bssid,
}
impl Time for ConnectFailure {
fn time(&self) -> fasync::Time {
self.time
}
}
/// Data points related to historical connection
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PastConnectionData {
pub bssid: client_types::Bssid,
/// Time at which connect was first attempted
pub connection_attempt_time: fasync::Time,
/// Duration from connection attempt to success
pub time_to_connect: zx::Duration,
/// Time at which the connection was ended
pub disconnect_time: fasync::Time,
/// The time that the connection was up - from established to disconnected.
pub connection_uptime: zx::Duration,
/// Cause of disconnect or failure to connect
pub disconnect_reason: client_types::DisconnectReason,
/// Final signal strength measure before disconnect
pub signal_data_at_disconnect: SignalData,
/// Average phy rate over connection duration
pub average_tx_rate: u32,
}
impl PastConnectionData {
pub fn new(
bssid: client_types::Bssid,
connection_attempt_time: fasync::Time,
time_to_connect: zx::Duration,
disconnect_time: fasync::Time,
connection_uptime: zx::Duration,
disconnect_reason: client_types::DisconnectReason,
signal_data_at_disconnect: SignalData,
average_tx_rate: u32,
) -> Self {
Self {
bssid,
connection_attempt_time,
time_to_connect,
disconnect_time,
connection_uptime,
disconnect_reason,
signal_data_at_disconnect,
average_tx_rate,
}
}
}
impl Time for PastConnectionData {
fn time(&self) -> fasync::Time {
self.disconnect_time
}
}
/// Data structures for storing historical connection information for a BSS.
pub type ConnectFailureList = HistoricalList<ConnectFailure>;
pub type PastConnectionList = HistoricalList<PastConnectionData>;
/// Data structures storing historical connection information for whole networks (one or more BSSs)
pub type ConnectFailuresByBssid = HistoricalListsByBssid<ConnectFailureList>;
pub type PastConnectionsByBssid = HistoricalListsByBssid<PastConnectionList>;
/// 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: client_types::Ssid,
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.to_vec(),
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(Arbitrary)] // Derive Arbitrary for fuzzer
#[derive(Clone, Debug, PartialEq)]
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),
}
}
}
// TODO(fxbug.dev/102606): Remove this conversion. Once calls to `select_authentication_method` are
// removed from the state machine, there will instead be an
// `Authentication` (or `SecurityAuthenticator`) field in
// `ScannedCandidate` which can be more directly compared to SME
// `ConnectRequest`s in tests.
#[cfg(test)]
impl From<Option<fidl_security::Credentials>> for Credential {
fn from(credentials: Option<fidl_security::Credentials>) -> Self {
use fidl_security::{Credentials, WepCredentials, WpaCredentials};
match credentials {
None => Credential::None,
Some(Credentials::Wep(WepCredentials { key })) => Credential::Password(key.into()),
Some(Credentials::Wpa(credentials)) => match credentials {
WpaCredentials::Passphrase(passphrase) => Credential::Password(passphrase.into()),
WpaCredentials::Psk(psk) => Credential::Psk(psk.into()),
_ => panic!("unrecognized FIDL variant"),
},
Some(_) => panic!("unrecognized FIDL variant"),
}
}
}
// TODO(fxbug.dev/102606): Remove this conversion. See the similar conversion above.
#[cfg(test)]
impl From<Option<Box<fidl_security::Credentials>>> for Credential {
fn from(credentials: Option<Box<fidl_security::Credentials>>) -> Self {
credentials.map(|credentials| *credentials).into()
}
}
#[derive(Arbitrary)] // Derive Arbitrary for fuzzer
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
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(Arbitrary)] // Derive Arbitrary for fuzzer
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct NetworkIdentifier {
pub ssid: client_types::Ssid,
pub security_type: SecurityType,
}
impl NetworkIdentifier {
pub fn new(ssid: client_types::Ssid, security_type: SecurityType) -> Self {
NetworkIdentifier { ssid: ssid.into(), security_type }
}
#[cfg(test)]
pub fn try_from(ssid: &str, security_type: SecurityType) -> Result<Self, anyhow::Error> {
Ok(NetworkIdentifier { ssid: client_types::Ssid::try_from(ssid)?, security_type })
}
}
impl From<fidl_policy::NetworkIdentifier> for NetworkIdentifier {
fn from(id: fidl_policy::NetworkIdentifier) -> Self {
Self::new(client_types::Ssid::from_bytes_unchecked(id.ssid), id.type_.into())
}
}
impl From<NetworkIdentifier> for fidl_policy::NetworkIdentifier {
fn from(id: NetworkIdentifier) -> Self {
fidl_policy::NetworkIdentifier { ssid: id.ssid.into(), 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: &client_types::Ssid,
security_type: &SecurityType,
credential: &Credential,
) -> Result<(), NetworkConfigError> {
// Verify SSID has at least 1 byte.
if ssid.len() < 1 as usize {
return Err(NetworkConfigError::SsidEmpty);
}
// 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_BYTE_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,
SsidEmpty,
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::SsidEmpty => {
write!(f, "SSID must have a non-zero length")
}
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 {
match err {
NetworkConfigError::OpenNetworkPassword | NetworkConfigError::MissingPasswordPsk => {
fidl_policy::NetworkConfigChangeError::InvalidSecurityCredentialError
}
NetworkConfigError::PasswordLen | NetworkConfigError::PskLen => {
fidl_policy::NetworkConfigChangeError::CredentialLenError
}
NetworkConfigError::SsidEmpty => fidl_policy::NetworkConfigChangeError::SsidEmptyError,
NetworkConfigError::ConfigMissingId | NetworkConfigError::ConfigMissingCredential => {
fidl_policy::NetworkConfigChangeError::NetworkConfigMissingFieldError
}
NetworkConfigError::CredentialTypeInvalid => {
fidl_policy::NetworkConfigChangeError::UnsupportedCredentialError
}
NetworkConfigError::StashWriteError | NetworkConfigError::LegacyWriteError => {
fidl_policy::NetworkConfigChangeError::NetworkConfigWriteError
}
}
}
}
/// Creates an `Authentication` FIDL message based on the given criteria.
///
/// The criteria for a security protocol are typically derived from a target network. This
/// includes information regarding protocols and credentials, but also includes information
/// that is used to determine supported modes of operation. This is necessary, because while
/// any filtering may discard incompatible target networks, it does not provide the specific
/// supported modes of operation for compatible target networks. Transitional WPA2-WPA3
/// networks in particular require this information, as it is generally not known whether WPA2
/// or WPA3 features should be used, only that at least one set of features is compatible.
///
/// Returns `None` if no appropriate authentication method can be selected for the given criteria.
/// This includes information about hardware support, so this function may return `None` if the
/// only suitable protocol for a network is unsupported.
pub fn select_authentication_method(
security_type_detailed: client_types::SecurityTypeDetailed,
credential: Credential,
has_wpa3_support: bool,
) -> Option<fidl_security::Authentication> {
use {
client_types::SecurityTypeDetailed,
fidl_security::{Authentication, Credentials, Protocol, WepCredentials, WpaCredentials},
};
fn into_fidl_passphrase(passphrase: Vec<u8>) -> Option<Credentials> {
Some(Credentials::Wpa(WpaCredentials::Passphrase(passphrase)))
}
fn try_into_fidl_psk(psk: Vec<u8>) -> Option<Option<Credentials>> {
<[u8; WPA_PSK_BYTE_LEN]>::try_from(psk)
.ok()
.map(|psk| Some(Credentials::Wpa(WpaCredentials::Psk(psk))))
}
// TODO(fxbug.dev/98899): Hardware and driver features are not queried comprehensively for
// security protocol support. Once work on new interfaces for querying
// driver features tracked by fxbug.dev/88315 lands, use this
// information to refine the selection of a security protocol. With the
// exception of WPA3, support for security protocols is assumed.
// Select a security protocol based on the given network protection (the security protocols
// used by the AP), hardware support, and credential compatibility.
let (protocol, credentials) = match security_type_detailed {
SecurityTypeDetailed::Open => match credential {
Credential::None => Some((Protocol::Open, None)),
_ => None,
},
SecurityTypeDetailed::Wep => match credential {
Credential::Password(key) => {
Some((Protocol::Wep, Some(Credentials::Wep(WepCredentials { key }))))
}
_ => None,
},
SecurityTypeDetailed::Wpa1 => match credential {
Credential::Password(passphrase) => {
Some((Protocol::Wpa1, into_fidl_passphrase(passphrase)))
}
Credential::Psk(psk) => try_into_fidl_psk(psk).map(|psk| (Protocol::Wpa1, psk)),
_ => None,
},
SecurityTypeDetailed::Wpa1Wpa2PersonalTkipOnly
| SecurityTypeDetailed::Wpa1Wpa2Personal
| SecurityTypeDetailed::Wpa2PersonalTkipOnly
| SecurityTypeDetailed::Wpa2Personal => match credential {
Credential::Password(passphrase) => {
Some((Protocol::Wpa2Personal, into_fidl_passphrase(passphrase)))
}
Credential::Psk(psk) => try_into_fidl_psk(psk).map(|psk| (Protocol::Wpa2Personal, psk)),
_ => None,
},
// For WPA2-WPA3 transitional networks, consider both hardware support and
// authentication method support. If WPA3 is not compatible, use WPA2.
SecurityTypeDetailed::Wpa2Wpa3Personal => match credential {
Credential::Password(passphrase) => {
let passphrase = into_fidl_passphrase(passphrase);
let protocol =
if has_wpa3_support { Protocol::Wpa3Personal } else { Protocol::Wpa2Personal };
Some((protocol, passphrase))
}
Credential::Psk(psk) => try_into_fidl_psk(psk).map(|psk| (Protocol::Wpa2Personal, psk)),
_ => None,
},
// For WPA3 networks, consider hardware support.
SecurityTypeDetailed::Wpa3Personal => match credential {
Credential::Password(passphrase) => has_wpa3_support
.then_some((Protocol::Wpa3Personal, into_fidl_passphrase(passphrase))),
_ => None,
},
// TODO(fxbug.dev/92693): Implement conversions for WPA Enterprise.
SecurityTypeDetailed::Wpa2Enterprise
| SecurityTypeDetailed::Wpa3Enterprise
| SecurityTypeDetailed::Unknown => None,
}?;
Some(Authentication { protocol, credentials: credentials.map(Box::new) })
}
#[cfg(test)]
mod tests {
use {
super::*, crate::util::testing::random_connection_data, std::iter::FromIterator,
test_case::test_case, wlan_common::assert_variant,
};
#[fuchsia::test]
fn new_network_config_none_credential() {
let credential = Credential::None;
let network_config = NetworkConfig::new(
NetworkIdentifier::try_from("foo", SecurityType::None).unwrap(),
credential.clone(),
false,
)
.expect("Error creating network config for foo");
assert_eq!(
network_config,
NetworkConfig {
ssid: client_types::Ssid::try_from("foo").unwrap(),
security_type: SecurityType::None,
credential: credential,
has_ever_connected: false,
hidden_probability: PROB_HIDDEN_DEFAULT,
hidden_probability_stats: HiddenProbabilityStats::new(),
perf_stats: PerformanceStats::new()
}
);
}
#[fuchsia::test]
fn new_network_config_password_credential() {
let credential = Credential::Password(b"foo-password".to_vec());
let network_config = NetworkConfig::new(
NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
credential.clone(),
false,
)
.expect("Error creating network config for foo");
assert_eq!(
network_config,
NetworkConfig {
ssid: client_types::Ssid::try_from("foo").unwrap(),
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.connect_failures.0.is_empty());
}
#[fuchsia::test]
fn new_network_config_psk_credential() {
let credential = Credential::Psk([1; WPA_PSK_BYTE_LEN].to_vec());
let network_config = NetworkConfig::new(
NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
credential.clone(),
false,
)
.expect("Error creating network config for foo");
assert_eq!(
network_config,
NetworkConfig {
ssid: client_types::Ssid::try_from("foo").unwrap(),
security_type: SecurityType::Wpa2,
credential: credential,
has_ever_connected: false,
hidden_probability: PROB_HIDDEN_DEFAULT,
hidden_probability_stats: HiddenProbabilityStats::new(),
perf_stats: PerformanceStats::new()
}
);
}
#[fuchsia::test]
fn new_network_config_invalid_password() {
let credential = Credential::Password([1; 64].to_vec());
let config_result = NetworkConfig::new(
NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap(),
credential,
false,
);
assert_variant!(config_result, Err(NetworkConfigError::PasswordLen));
}
#[fuchsia::test]
fn new_network_config_invalid_psk() {
let credential = Credential::Psk(b"bar".to_vec());
let config_result = NetworkConfig::new(
NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap(),
credential,
false,
);
assert_variant!(config_result, Err(NetworkConfigError::PskLen));
}
#[fuchsia::test]
fn check_config_errors_invalid_wep_password() {
// Unsupported length (7).
let password = Credential::Password(b"1234567".to_vec());
assert_variant!(
check_config_errors(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::Wep,
&password
),
Err(NetworkConfigError::PasswordLen)
);
}
#[fuchsia::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(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::Wpa2,
&short_password
),
Err(NetworkConfigError::PasswordLen)
);
// password too long
let long_password = Credential::Password([5, 65].to_vec());
assert_variant!(
check_config_errors(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::Wpa2,
&long_password
),
Err(NetworkConfigError::PasswordLen)
);
}
#[fuchsia::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(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::Wep,
&psk
),
Err(NetworkConfigError::MissingPasswordPsk)
);
}
#[fuchsia::test]
fn check_config_errors_invalid_wpa_psk() {
// PSK length not 32 characters
let short_psk = Credential::Psk([6; WPA_PSK_BYTE_LEN - 1].to_vec());
assert_variant!(
check_config_errors(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::Wpa2,
&short_psk
),
Err(NetworkConfigError::PskLen)
);
let long_psk = Credential::Psk([7; WPA_PSK_BYTE_LEN + 1].to_vec());
assert_variant!(
check_config_errors(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::Wpa2,
&long_psk
),
Err(NetworkConfigError::PskLen)
);
}
#[fuchsia::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(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::None,
&password
),
Err(NetworkConfigError::OpenNetworkPassword)
);
let psk = Credential::Psk([1; WPA_PSK_BYTE_LEN].to_vec());
assert_variant!(
check_config_errors(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::None,
&psk
),
Err(NetworkConfigError::OpenNetworkPassword)
);
// Use no password with a protected network.
let password = Credential::None;
assert_variant!(
check_config_errors(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::Wpa,
&password
),
Err(NetworkConfigError::MissingPasswordPsk)
);
assert_variant!(
check_config_errors(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::Wpa2,
&password
),
Err(NetworkConfigError::MissingPasswordPsk)
);
assert_variant!(
check_config_errors(
&client_types::Ssid::try_from("valid_ssid").unwrap(),
&SecurityType::Wpa3,
&password
),
Err(NetworkConfigError::MissingPasswordPsk)
);
}
#[fuchsia::test]
fn check_config_errors_ssid_empty() {
assert_variant!(
check_config_errors(
&client_types::Ssid::empty(),
&SecurityType::None,
&Credential::None
),
Err(NetworkConfigError::SsidEmpty)
);
}
#[fasync::run_singlethreaded(test)]
async fn test_connect_failures_by_bssid_add_and_get() {
let mut connect_failures = ConnectFailuresByBssid::new();
let curr_time = fasync::Time::now();
// Add two failures for BSSID_1
let bssid_1 = client_types::Bssid([1; 6]);
let failure_1_bssid_1 = ConnectFailure {
time: curr_time - zx::Duration::from_seconds(10),
bssid: bssid_1.clone(),
reason: FailureReason::GeneralFailure,
};
connect_failures.add(bssid_1.clone(), failure_1_bssid_1.clone());
let failure_2_bssid_1 = ConnectFailure {
time: curr_time - zx::Duration::from_seconds(5),
bssid: bssid_1.clone(),
reason: FailureReason::CredentialRejected,
};
connect_failures.add(bssid_1.clone(), failure_2_bssid_1.clone());
// Verify get_recent_for_network(curr_time - 10) retrieves both entries
assert_eq!(
connect_failures.get_recent_for_network(curr_time - zx::Duration::from_seconds(10)),
vec![failure_1_bssid_1, failure_2_bssid_1]
);
// Add one failure for BSSID_2
let bssid_2 = client_types::Bssid([2; 6]);
let failure_1_bssid_2 = ConnectFailure {
time: curr_time - zx::Duration::from_seconds(3),
bssid: bssid_2.clone(),
reason: FailureReason::GeneralFailure,
};
connect_failures.add(bssid_2.clone(), failure_1_bssid_2.clone());
// Verify get_recent_for_network(curr_time - 10) includes entries from both BSSIDs
assert_eq!(
connect_failures.get_recent_for_network(curr_time - zx::Duration::from_seconds(10)),
vec![failure_1_bssid_1, failure_2_bssid_1, failure_1_bssid_2]
);
// Verify get_recent_for_network(curr_time - 9) excludes older entries
assert_eq!(
connect_failures.get_recent_for_network(curr_time - zx::Duration::from_seconds(9)),
vec![failure_2_bssid_1, failure_1_bssid_2]
);
// Verify get_recent_for_network(curr_time) is empty
assert_eq!(connect_failures.get_recent_for_network(curr_time), vec![]);
// Verify get_list_for_bss retrieves correct ConnectFailureLists
assert_eq!(
connect_failures.get_list_for_bss(&bssid_1),
ConnectFailureList { 0: VecDeque::from_iter([failure_1_bssid_1, failure_2_bssid_1]) }
);
assert_eq!(
connect_failures.get_list_for_bss(&bssid_2),
ConnectFailureList { 0: VecDeque::from_iter([failure_1_bssid_2]) }
);
}
#[fasync::run_singlethreaded(test)]
async fn failure_list_add_and_get() {
let mut connect_failures = ConnectFailureList::new();
// Get time before adding so we can get back everything we added.
let curr_time = fasync::Time::now();
assert!(connect_failures.get_recent(curr_time).is_empty());
let bssid = client_types::Bssid([1; 6]);
let failure =
ConnectFailure { time: curr_time, bssid, reason: FailureReason::GeneralFailure };
connect_failures.add(failure);
let result_list = connect_failures.get_recent(curr_time);
assert_eq!(1, result_list.len());
assert_eq!(FailureReason::GeneralFailure, result_list[0].reason);
assert_eq!(bssid, result_list[0].bssid);
// Should not get any results if we request denials older than the specified time.
let later_time = fasync::Time::now();
assert!(connect_failures.get_recent(later_time).is_empty());
}
#[fasync::run_singlethreaded(test)]
async fn test_failure_list_add_when_full() {
let mut connect_failures = ConnectFailureList::new();
let curr_time = fasync::Time::now();
// Add to list, exceeding the capacity by one entry
for i in 0..connect_failures.0.capacity() + 1 {
connect_failures.add(ConnectFailure {
time: curr_time + zx::Duration::from_seconds(i as i64),
reason: FailureReason::GeneralFailure,
bssid: client_types::Bssid([1; 6]),
})
}
// Validate entry with time = curr_time was evicted.
for (i, e) in connect_failures.0.iter().enumerate() {
assert_eq!(e.time, curr_time + zx::Duration::from_seconds(i as i64 + 1));
}
}
#[fasync::run_singlethreaded(test)]
async fn test_past_connections_by_bssid_add_and_get() {
let mut past_connections_list = PastConnectionsByBssid::new();
let curr_time = fasync::Time::now();
// Add two past_connections for BSSID_1
let mut data_1_bssid_1 = random_connection_data();
let bssid_1 = data_1_bssid_1.bssid;
data_1_bssid_1.disconnect_time = curr_time - zx::Duration::from_seconds(10);
past_connections_list.add(bssid_1.clone(), data_1_bssid_1.clone());
let mut data_2_bssid_1 = random_connection_data();
data_2_bssid_1.bssid = bssid_1;
data_2_bssid_1.disconnect_time = curr_time - zx::Duration::from_seconds(5);
past_connections_list.add(bssid_1.clone(), data_2_bssid_1.clone());
// Verify get_recent_for_network(curr_time - 10) retrieves both entries
assert_eq!(
past_connections_list
.get_recent_for_network(curr_time - zx::Duration::from_seconds(10)),
vec![data_1_bssid_1, data_2_bssid_1]
);
// Add one past_connection for BSSID_2
let mut data_1_bssid_2 = random_connection_data();
let bssid_2 = data_1_bssid_2.bssid;
data_1_bssid_2.disconnect_time = curr_time - zx::Duration::from_seconds(3);
past_connections_list.add(bssid_2.clone(), data_1_bssid_2.clone());
// Verify get_recent_for_network(curr_time - 10) includes entries from both BSSIDs
assert_eq!(
past_connections_list
.get_recent_for_network(curr_time - zx::Duration::from_seconds(10)),
vec![data_1_bssid_1, data_2_bssid_1, data_1_bssid_2]
);
// Verify get_recent_for_network(curr_time - 9) excludes older entries
assert_eq!(
past_connections_list.get_recent_for_network(curr_time - zx::Duration::from_seconds(9)),
vec![data_2_bssid_1, data_1_bssid_2]
);
// Verify get_recent_for_network(curr_time) is empty
assert_eq!(past_connections_list.get_recent_for_network(curr_time), vec![]);
// Verify get_list_for_bss retrieves correct PastConnectionLists
assert_eq!(
past_connections_list.get_list_for_bss(&bssid_1),
PastConnectionList { 0: VecDeque::from_iter([data_1_bssid_1, data_2_bssid_1]) }
);
assert_eq!(
past_connections_list.get_list_for_bss(&bssid_2),
PastConnectionList { 0: VecDeque::from_iter([data_1_bssid_2]) }
);
}
#[fasync::run_singlethreaded(test)]
async fn test_past_connections_list_add_when_full() {
let mut past_connections_list = PastConnectionList::new();
let curr_time = fasync::Time::now();
// Add to list, exceeding the capacity by one entry
for i in 0..past_connections_list.0.capacity() + 1 {
let mut data = random_connection_data();
data.bssid = client_types::Bssid([1; 6]);
data.disconnect_time = curr_time + zx::Duration::from_seconds(i as i64);
past_connections_list.add(data);
}
// Validate entry with time = curr_time was evicted.
for (i, e) in past_connections_list.0.iter().enumerate() {
assert_eq!(e.disconnect_time, curr_time + zx::Duration::from_seconds(i as i64 + 1));
}
}
#[fasync::run_singlethreaded(test)]
async fn test_past_connections_list_add_and_get() {
let mut past_connections_list = PastConnectionList::new();
let curr_time = fasync::Time::now();
assert!(past_connections_list.get_recent(curr_time).is_empty());
let mut past_connection_data = random_connection_data();
past_connection_data.disconnect_time = curr_time;
// Add a past connection
past_connections_list.add(past_connection_data.clone());
// We should get back the added data when specifying the same or an earlier time.
assert_eq!(past_connections_list.get_recent(curr_time).len(), 1);
assert_variant!(past_connections_list.get_recent(curr_time).as_slice(), [data] => {
assert_eq!(data, &past_connection_data.clone());
});
let earlier_time = curr_time - zx::Duration::from_seconds(1);
assert_variant!(past_connections_list.get_recent(earlier_time).as_slice(), [data] => {
assert_eq!(data, &data.clone());
});
// The results should be empty if the requested time is after the latest past connection's
// time.
let later_time = curr_time + zx::Duration::from_seconds(1);
assert!(past_connections_list.get_recent(later_time).is_empty());
}
#[fuchsia::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_BYTE_LEN]),
Credential::Password(vec![2; WPA_PSK_BYTE_LEN])
);
assert_eq!(Credential::from_bytes(vec![]), Credential::None);
}
#[fuchsia::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());
}
#[fuchsia::test]
fn test_hidden_prob_calculation() {
let mut network_config = NetworkConfig::new(
NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
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);
}
#[fuchsia::test]
fn test_hidden_prob_calc_active_connect() {
let mut network_config = NetworkConfig::new(
NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
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);
}
#[fuchsia::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::try_from("some_ssid", SecurityType::None).unwrap(),
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);
}
#[fuchsia::test]
fn test_hidden_prob_calc_not_seen_in_active_scan_does_not_lower_past_threshold() {
let mut network_config = NetworkConfig::new(
NetworkIdentifier::try_from("some_ssid", SecurityType::None).unwrap(),
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);
}
#[fuchsia::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::try_from("some_ssid", SecurityType::None).unwrap(),
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);
}
#[fuchsia::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::try_from("some_ssid", SecurityType::None).unwrap(),
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);
}
fn policy_wep_key() -> Credential {
Credential::Password("abcdef0000".as_bytes().to_vec())
}
fn fidl_wep_key() -> Box<fidl_security::Credentials> {
Box::new(fidl_security::Credentials::Wep(fidl_security::WepCredentials {
key: "abcdef0000".as_bytes().to_vec(),
}))
}
fn policy_wpa_password() -> Credential {
Credential::Password("password".as_bytes().to_vec())
}
fn fidl_wpa_password() -> Box<fidl_security::Credentials> {
Box::new(fidl_security::Credentials::Wpa(fidl_security::WpaCredentials::Passphrase(
"password".as_bytes().to_vec(),
)))
}
fn policy_wpa_psk() -> Credential {
Credential::Psk(vec![0u8; WPA_PSK_BYTE_LEN])
}
fn fidl_wpa_psk() -> Box<fidl_security::Credentials> {
Box::new(fidl_security::Credentials::Wpa(fidl_security::WpaCredentials::Psk(
[0u8; WPA_PSK_BYTE_LEN],
)))
}
// Expect successful mapping in the following cases.
#[test_case(
client_types::SecurityTypeDetailed::Open,
Credential::None,
false
=>
Some(fidl_security::Authentication {
protocol: fidl_security::Protocol::Open,
credentials: None,
})
)]
#[test_case(
client_types::SecurityTypeDetailed::Wep,
policy_wep_key(),
false
=>
Some(fidl_security::Authentication {
protocol: fidl_security::Protocol::Wep,
credentials: Some(fidl_wep_key()),
})
)]
#[test_case(
client_types::SecurityTypeDetailed::Wpa2Wpa3Personal,
policy_wpa_password(),
true
=>
Some(fidl_security::Authentication {
protocol: fidl_security::Protocol::Wpa3Personal,
credentials: Some(fidl_wpa_password()),
})
)]
#[test_case(
client_types::SecurityTypeDetailed::Wpa2Wpa3Personal,
policy_wpa_password(),
false
=>
Some(fidl_security::Authentication {
protocol: fidl_security::Protocol::Wpa2Personal,
credentials: Some(fidl_wpa_password()),
})
)]
#[test_case(
client_types::SecurityTypeDetailed::Wpa2Wpa3Personal,
policy_wpa_psk(),
true
=>
Some(fidl_security::Authentication {
protocol: fidl_security::Protocol::Wpa2Personal,
credentials: Some(fidl_wpa_psk()),
})
)]
// Expect failed mapping in the following cases.
#[test_case(
client_types::SecurityTypeDetailed::Wpa3Personal,
policy_wpa_password(),
false
=>
None
)]
#[test_case(
client_types::SecurityTypeDetailed::Wpa3Personal,
policy_wpa_psk(),
true
=>
None
)]
#[fuchsia::test(add_test_attr = false)]
fn select_authentication_method_matrix(
security_type_detailed: client_types::SecurityTypeDetailed,
credential: Credential,
has_wpa3_support: bool,
) -> Option<fidl_security::Authentication> {
super::select_authentication_method(security_type_detailed, credential, has_wpa3_support)
}
}