blob: dab706e7f3c803a06bdd456e81f6db1d6a97d0f4 [file] [log] [blame]
// Copyright 2021 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.
#![cfg(test)]
use {
crate::{
client::{bss_selection::SignalData, types as client_types},
config_management::{
Credential, NetworkConfig, NetworkConfigError, NetworkIdentifier, PastConnectionData,
PastConnectionList, SavedNetworksManagerApi, ScanResultType,
},
},
async_trait::async_trait,
fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_sme as fidl_sme,
fuchsia_async as fasync, fuchsia_zircon as zx,
futures::{channel::mpsc, lock::Mutex},
log::info,
rand::Rng,
std::{collections::HashMap, convert::TryInto, sync::Arc},
wlan_common::hasher::WlanHasher,
};
pub struct FakeSavedNetworksManager {
saved_networks: Mutex<HashMap<NetworkIdentifier, Vec<NetworkConfig>>>,
connections_recorded: Mutex<Vec<ConnectionRecord>>,
connect_results_recorded: Mutex<Vec<ConnectResultRecord>>,
lookup_compatible_response: Mutex<LookupCompatibleResponse>,
pub fail_all_stores: bool,
pub active_scan_result_recorded: Arc<Mutex<bool>>,
pub passive_scan_result_recorded: Arc<Mutex<bool>>,
pub past_connections_response: PastConnectionList,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConnectionRecord {
pub id: NetworkIdentifier,
pub credential: Credential,
pub data: PastConnectionData,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConnectResultRecord {
pub id: NetworkIdentifier,
pub credential: Credential,
pub bssid: client_types::Bssid,
pub connect_result: fidl_sme::ConnectResult,
pub discovered_in_scan: Option<fidl_common::ScanType>,
}
/// Use a struct so that the option can be updated from None to Some to allow the response to be
/// set after FakeSavedNetworksManager is created. Use an optional response value rather than
/// defaulting to an empty vector so that if the response is not set, lookup_compatible will panic
/// for easier debugging.
struct LookupCompatibleResponse {
inner: Option<Vec<NetworkConfig>>,
}
impl LookupCompatibleResponse {
fn new() -> Self {
LookupCompatibleResponse { inner: None }
}
}
impl FakeSavedNetworksManager {
pub fn new() -> Self {
Self {
saved_networks: Mutex::new(HashMap::new()),
connections_recorded: Mutex::new(vec![]),
connect_results_recorded: Mutex::new(vec![]),
fail_all_stores: false,
lookup_compatible_response: Mutex::new(LookupCompatibleResponse::new()),
active_scan_result_recorded: Arc::new(Mutex::new(false)),
passive_scan_result_recorded: Arc::new(Mutex::new(false)),
past_connections_response: PastConnectionList::new(),
}
}
/// Create FakeSavedNetworksManager, saving network configs with the specified
/// network identifiers and credentials at init.
pub fn new_with_saved_networks(network_configs: Vec<(NetworkIdentifier, Credential)>) -> Self {
let saved_networks = network_configs
.into_iter()
.filter_map(|(id, cred)| {
NetworkConfig::new(id.clone(), cred, false).ok().map(|config| (id, vec![config]))
})
.collect::<HashMap<NetworkIdentifier, Vec<NetworkConfig>>>();
Self {
saved_networks: Mutex::new(saved_networks),
connections_recorded: Mutex::new(vec![]),
connect_results_recorded: Mutex::new(vec![]),
fail_all_stores: false,
lookup_compatible_response: Mutex::new(LookupCompatibleResponse::new()),
active_scan_result_recorded: Arc::new(Mutex::new(false)),
passive_scan_result_recorded: Arc::new(Mutex::new(false)),
past_connections_response: PastConnectionList::new(),
}
}
/// Returns the past connections as they were recorded, rather than how they would have been
/// stored.
pub fn get_recorded_past_connections(&self) -> Vec<ConnectionRecord> {
self.connections_recorded
.try_lock()
.expect("expect locking self.connections_recorded to succeed")
.clone()
}
pub fn get_recorded_connect_reslts(&self) -> Vec<ConnectResultRecord> {
self.connect_results_recorded
.try_lock()
.expect("expect locking self.connect_results_recorded to succeed")
.clone()
}
/// Manually change the hidden network probabiltiy of a saved network.
pub async fn update_hidden_prob(&self, id: NetworkIdentifier, hidden_prob: f32) {
let mut saved_networks = self.saved_networks.lock().await;
let networks = match saved_networks.get_mut(&id) {
Some(networks) => networks,
None => {
info!("Failed to find network to update");
return;
}
};
for network in networks.iter_mut() {
network.hidden_probability = hidden_prob;
}
}
pub fn set_lookup_compatible_response(&self, response: Vec<NetworkConfig>) {
self.lookup_compatible_response.try_lock().expect("failed to get lock").inner =
Some(response);
}
}
#[async_trait]
impl SavedNetworksManagerApi for FakeSavedNetworksManager {
async fn remove(
&self,
network_id: NetworkIdentifier,
credential: Credential,
) -> Result<bool, NetworkConfigError> {
let mut saved_networks = self.saved_networks.lock().await;
if let Some(network_configs) = saved_networks.get_mut(&network_id) {
let original_len = network_configs.len();
network_configs.retain(|cfg| cfg.credential != credential);
if original_len != network_configs.len() {
return Ok(true);
}
}
Ok(false)
}
async fn known_network_count(&self) -> usize {
unimplemented!()
}
async fn lookup(&self, id: &NetworkIdentifier) -> Vec<NetworkConfig> {
self.saved_networks.lock().await.get(id).cloned().unwrap_or_default()
}
async fn lookup_compatible(
&self,
_ssid: &client_types::Ssid,
_scan_security: client_types::SecurityTypeDetailed,
) -> Vec<NetworkConfig> {
self.lookup_compatible_response
.lock()
.await
.inner
.clone()
.expect("FakeSavedNetworksManager lookup_compatible response is not set")
}
/// Note that the configs-per-NetworkIdentifier limit is set to 1 in
/// this mock struct. If a NetworkIdentifier is already stored, writing
/// a config to it will evict the previously store one.
async fn store(
&self,
network_id: NetworkIdentifier,
credential: Credential,
) -> Result<Option<NetworkConfig>, NetworkConfigError> {
if self.fail_all_stores {
return Err(NetworkConfigError::StashWriteError);
}
let config = NetworkConfig::new(network_id.clone(), credential, false)?;
return Ok(self
.saved_networks
.lock()
.await
.insert(network_id, vec![config])
.map(|mut v| v.pop())
.flatten());
}
async fn record_connect_result(
&self,
id: NetworkIdentifier,
credential: &Credential,
bssid: client_types::Bssid,
connect_result: fidl_sme::ConnectResult,
discovered_in_scan: Option<fidl_common::ScanType>,
) {
self.connect_results_recorded.try_lock().expect("failed to record connect result").push(
ConnectResultRecord {
id: id.clone(),
credential: credential.clone(),
bssid,
connect_result,
discovered_in_scan,
},
);
}
async fn record_disconnect(
&self,
id: &NetworkIdentifier,
credential: &Credential,
data: PastConnectionData,
) {
let mut connections_recorded = self.connections_recorded.lock().await;
connections_recorded.push(ConnectionRecord {
id: id.clone(),
credential: credential.clone(),
data,
});
}
async fn record_periodic_metrics(&self) {}
async fn record_scan_result(
&self,
scan_type: ScanResultType,
_results: Vec<client_types::NetworkIdentifierDetailed>,
) {
match scan_type {
ScanResultType::Undirected => {
let mut v = self.passive_scan_result_recorded.lock().await;
*v = true;
}
ScanResultType::Directed(_) => {
let mut v = self.active_scan_result_recorded.lock().await;
*v = true
}
}
}
async fn get_networks(&self) -> Vec<NetworkConfig> {
self.saved_networks
.lock()
.await
.values()
.into_iter()
.map(|cfgs| cfgs.clone())
.flatten()
.collect()
}
async fn get_past_connections(
&self,
_id: &NetworkIdentifier,
_credential: &Credential,
_bssid: &client_types::Bssid,
) -> PastConnectionList {
self.past_connections_response.clone()
}
}
pub fn create_wlan_hasher() -> WlanHasher {
WlanHasher::new(rand::thread_rng().gen::<u64>().to_le_bytes())
}
pub fn create_inspect_persistence_channel() -> (mpsc::Sender<String>, mpsc::Receiver<String>) {
const DEFAULT_BUFFER_SIZE: usize = 100; // arbitrary value
mpsc::channel(DEFAULT_BUFFER_SIZE)
}
/// Create past connection data with all random values. Tests can set the values they care about.
pub fn random_connection_data() -> PastConnectionData {
let mut rng = rand::thread_rng();
let connect_time = fasync::Time::from_nanos(rng.gen::<u16>().into());
let time_to_connect = zx::Duration::from_seconds(rng.gen_range::<i64, _>(5..10).into());
let uptime = zx::Duration::from_seconds(rng.gen_range::<i64, _>(5..1000).into());
let disconnect_time = connect_time + time_to_connect + uptime;
PastConnectionData::new(
client_types::Bssid(
(0..6).map(|_| rng.gen::<u8>()).collect::<Vec<u8>>().try_into().unwrap(),
),
connect_time,
time_to_connect,
disconnect_time,
uptime,
client_types::DisconnectReason::DisconnectDetectedFromSme,
SignalData::new(rng.gen_range(-90..-20), rng.gen_range(10..50), 10),
rng.gen::<u8>().into(),
)
}