blob: abdf8df0d24d117716c35242ca80130949102e3a [file] [log] [blame]
// Copyright 2018 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::{
capabilities::{ClientCapabilities, StaCapabilities},
client::{bss::BssInfo, rsn::Supplicant},
test_utils::*,
Ssid,
},
fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_mlme as fidl_mlme,
futures::channel::mpsc,
std::{
convert::TryInto,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
},
wlan_common::{
assert_variant,
bss::Protection,
channel,
ie::{rsn::rsne::Rsne, write_wpa1_ie, *},
mac,
},
wlan_rsn::{auth, format_rsn_err, rsna::UpdateSink, Error},
zerocopy::AsBytes,
};
fn fake_bss_description(ssid: Ssid, rsne: Option<Vec<u8>>) -> fidl_mlme::BssDescription {
fidl_mlme::BssDescription {
bssid: [7, 1, 2, 77, 53, 8],
ssid,
bss_type: fidl_mlme::BssTypes::Infrastructure,
beacon_period: 100,
dtim_period: 100,
timestamp: 0,
local_time: 0,
cap: mac::CapabilityInfo(0).with_privacy(rsne.is_some()).0,
rates: vec![0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c],
country: None,
rsne,
vendor_ies: None,
rcpi_dbmh: 0,
rsni_dbh: 0,
ht_cap: Some(Box::new(fidl_mlme::HtCapabilities { bytes: fake_ht_cap_bytes() })),
ht_op: Some(Box::new(fidl_mlme::HtOperation { bytes: fake_ht_op_bytes() })),
vht_cap: Some(Box::new(fidl_mlme::VhtCapabilities { bytes: fake_vht_cap_bytes() })),
vht_op: Some(Box::new(fidl_mlme::VhtOperation { bytes: fake_vht_op_bytes() })),
chan: fidl_common::WlanChan { primary: 1, secondary80: 0, cbw: fidl_common::Cbw::Cbw40 },
rssi_dbm: 0,
snr_db: 0,
}
}
pub fn fake_bss_with_bssid(ssid: Ssid, bssid: [u8; 6]) -> fidl_mlme::BssDescription {
fidl_mlme::BssDescription { bssid, ..fake_unprotected_bss_description(ssid) }
}
pub fn fake_bss_with_rates(ssid: Ssid, rates: Vec<u8>) -> fidl_mlme::BssDescription {
fidl_mlme::BssDescription { rates, ..fake_unprotected_bss_description(ssid) }
}
pub fn fake_bss_with_vendor_ies(ssid: Ssid, vendor_ies: Vec<u8>) -> fidl_mlme::BssDescription {
fidl_mlme::BssDescription {
vendor_ies: Some(vendor_ies),
..fake_unprotected_bss_description(ssid)
}
}
pub fn fake_unprotected_bss_description(ssid: Ssid) -> fidl_mlme::BssDescription {
fake_bss_description(ssid, None)
}
pub fn fake_wep_bss_description(ssid: Ssid) -> fidl_mlme::BssDescription {
let mut bss = fake_bss_description(ssid, None);
bss.cap = mac::CapabilityInfo(bss.cap).with_privacy(true).0;
bss
}
pub fn fake_wpa1_bss_description(ssid: Ssid) -> fidl_mlme::BssDescription {
let mut bss = fake_bss_description(ssid, None);
bss.cap = mac::CapabilityInfo(bss.cap).with_privacy(true).0;
let mut vendor_ies = vec![];
write_wpa1_ie(&mut vendor_ies, &make_wpa1_ie()).expect("failed to create wpa1 bss description");
bss.vendor_ies = Some(vendor_ies);
bss
}
pub fn fake_protected_bss_description(ssid: Ssid) -> fidl_mlme::BssDescription {
let a_rsne = Rsne::wpa2_psk_ccmp_rsne();
fake_bss_description(ssid, Some(a_rsne.into_bytes()))
}
pub fn fake_wpa3_mixed_bss_description(ssid: Ssid) -> fidl_mlme::BssDescription {
let a_rsne = Rsne::wpa3_mixed_psk_ccmp_rsne();
fake_bss_description(ssid, Some(a_rsne.into_bytes()))
}
pub fn fake_wpa3_bss_description(ssid: Ssid) -> fidl_mlme::BssDescription {
let a_rsne = Rsne::wpa3_ccmp_rsne();
fake_bss_description(ssid, Some(a_rsne.into_bytes()))
}
pub fn fake_vht_bss_description() -> fidl_mlme::BssDescription {
let bss = fake_bss_description(vec![], None);
fidl_mlme::BssDescription {
chan: fake_chan(36),
ht_cap: Some(Box::new(fidl_mlme::HtCapabilities {
bytes: fake_ht_capabilities().as_bytes().try_into().unwrap(),
})),
ht_op: Some(Box::new(fidl_mlme::HtOperation {
bytes: fake_ht_operation().as_bytes().try_into().unwrap(),
})),
vht_cap: Some(Box::new(fidl_mlme::VhtCapabilities {
bytes: fake_vht_capabilities().as_bytes().try_into().unwrap(),
})),
vht_op: Some(Box::new(fidl_mlme::VhtOperation {
bytes: fake_vht_operation().as_bytes().try_into().unwrap(),
})),
..bss
}
}
pub fn fake_bss_info() -> BssInfo {
BssInfo {
bssid: [55, 11, 22, 3, 9, 70],
ssid: b"foo".to_vec(),
rx_dbm: 0,
snr_db: 0,
channel: channel::Channel { primary: 1, cbw: channel::Cbw::Cbw20 },
protection: Protection::Wpa2Personal,
compatible: true,
ht_cap: Some(fidl_mlme::HtCapabilities {
bytes: fake_ht_capabilities().as_bytes().try_into().unwrap(),
}),
vht_cap: Some(fidl_mlme::VhtCapabilities {
bytes: fake_vht_capabilities().as_bytes().try_into().unwrap(),
}),
probe_resp_wsc: None,
wmm_param: None,
}
}
pub fn fake_chan(primary: u8) -> fidl_common::WlanChan {
fidl_common::WlanChan { primary, cbw: fidl_common::Cbw::Cbw20, secondary80: 0 }
}
pub fn fake_scan_request() -> fidl_mlme::ScanRequest {
fidl_mlme::ScanRequest {
txn_id: 1,
bss_type: fidl_mlme::BssTypes::Infrastructure,
bssid: [8, 2, 6, 2, 1, 11],
ssid: vec![],
scan_type: fidl_mlme::ScanTypes::Active,
probe_delay: 5,
channel_list: Some(vec![11]),
min_channel_time: 50,
max_channel_time: 50,
ssid_list: None,
}
}
pub fn fake_wmm_param() -> fidl_mlme::WmmParameter {
#[rustfmt::skip]
let wmm_param = fidl_mlme::WmmParameter {
bytes: [
0x80, // Qos Info - U-ASPD enabled
0x00, // reserved
0x03, 0xa4, 0x00, 0x00, // Best effort AC params
0x27, 0xa4, 0x00, 0x00, // Background AC params
0x42, 0x43, 0x5e, 0x00, // Video AC params
0x62, 0x32, 0x2f, 0x00, // Voice AC params
]
};
wmm_param
}
pub fn create_join_conf(result_code: fidl_mlme::JoinResultCodes) -> fidl_mlme::MlmeEvent {
fidl_mlme::MlmeEvent::JoinConf { resp: fidl_mlme::JoinConfirm { result_code } }
}
pub fn create_auth_conf(
bssid: [u8; 6],
result_code: fidl_mlme::AuthenticateResultCodes,
) -> fidl_mlme::MlmeEvent {
fidl_mlme::MlmeEvent::AuthenticateConf {
resp: fidl_mlme::AuthenticateConfirm {
peer_sta_address: bssid,
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
result_code,
},
}
}
pub fn fake_negotiated_channel_and_capabilities() -> (channel::Channel, ClientCapabilities) {
// Based on fake_bss_description, device_info and create_assoc_conf
let mut ht_cap = fake_ht_capabilities();
// Fuchsia does not support tx_stbc yet.
ht_cap.ht_cap_info = ht_cap.ht_cap_info.with_tx_stbc(false);
(
channel::Channel { primary: 1, cbw: channel::Cbw::Cbw40 },
ClientCapabilities(StaCapabilities {
cap_info: crate::test_utils::fake_capability_info(),
rates: [0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c]
.iter()
.cloned()
.map(SupportedRate)
.collect(),
ht_cap: Some(ht_cap),
vht_cap: Some(fake_vht_capabilities()),
}),
)
}
pub fn create_assoc_conf(result_code: fidl_mlme::AssociateResultCodes) -> fidl_mlme::MlmeEvent {
fidl_mlme::MlmeEvent::AssociateConf {
resp: fidl_mlme::AssociateConfirm {
result_code,
association_id: 55,
cap_info: crate::test_utils::fake_capability_info().raw(),
rates: vec![0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c],
// TODO(fxbug.dev/43938): mock with fake WMM param
wmm_param: None,
ht_cap: Some(Box::new(fidl_mlme::HtCapabilities { bytes: fake_ht_cap_bytes() })),
vht_cap: Some(Box::new(fidl_mlme::VhtCapabilities { bytes: fake_vht_cap_bytes() })),
},
}
}
pub fn expect_stream_empty<T>(stream: &mut mpsc::UnboundedReceiver<T>, error_msg: &str) {
assert_variant!(
stream.try_next(),
Ok(None) | Err(..),
format!("error, receiver not empty: {}", error_msg)
);
}
fn mock_supplicant(auth_method: auth::MethodName) -> (MockSupplicant, MockSupplicantController) {
let started = Arc::new(AtomicBool::new(false));
let start_failure = Arc::new(Mutex::new(None));
let sink = Arc::new(Mutex::new(Ok(UpdateSink::default())));
let on_eapol_frame_cb = Arc::new(Mutex::new(None));
let supplicant = MockSupplicant {
started: started.clone(),
start_failure: start_failure.clone(),
on_eapol_frame: sink.clone(),
on_eapol_frame_cb: on_eapol_frame_cb.clone(),
auth_method,
};
let mock = MockSupplicantController {
started,
start_failure,
mock_on_eapol_frame: sink,
on_eapol_frame_cb,
};
(supplicant, mock)
}
pub fn mock_psk_supplicant() -> (MockSupplicant, MockSupplicantController) {
mock_supplicant(auth::MethodName::Psk)
}
pub fn mock_sae_supplicant() -> (MockSupplicant, MockSupplicantController) {
mock_supplicant(auth::MethodName::Sae)
}
type Cb = dyn Fn() + Send + 'static;
pub struct MockSupplicant {
started: Arc<AtomicBool>,
start_failure: Arc<Mutex<Option<anyhow::Error>>>,
on_eapol_frame: Arc<Mutex<Result<UpdateSink, anyhow::Error>>>,
on_eapol_frame_cb: Arc<Mutex<Option<Box<Cb>>>>,
auth_method: auth::MethodName,
}
impl Supplicant for MockSupplicant {
fn start(&mut self) -> Result<(), Error> {
match &*self.start_failure.lock().unwrap() {
Some(error) => return Err(format_rsn_err!("{:?}", error)),
None => {
self.started.store(true, Ordering::SeqCst);
Ok(())
}
}
}
fn reset(&mut self) {
let _ = self.on_eapol_frame.lock().unwrap().as_mut().map(|updates| updates.clear());
}
fn on_eapol_frame(
&mut self,
update_sink: &mut UpdateSink,
_frame: eapol::Frame<&[u8]>,
) -> Result<(), Error> {
if let Some(cb) = self.on_eapol_frame_cb.lock().unwrap().as_mut() {
cb();
}
self.on_eapol_frame
.lock()
.unwrap()
.as_mut()
.map(|updates| {
update_sink.extend(updates.drain(..));
})
.map_err(|e| format_rsn_err!("{:?}", e))
}
fn on_sae_handshake_ind(&mut self, _update_sink: &mut UpdateSink) -> Result<(), anyhow::Error> {
unimplemented!()
}
fn on_sae_frame_rx(
&mut self,
_update_sink: &mut UpdateSink,
_frame: fidl_mlme::SaeFrame,
) -> Result<(), anyhow::Error> {
unimplemented!()
}
fn on_sae_timeout(
&mut self,
_update_sink: &mut UpdateSink,
_event_id: u64,
) -> Result<(), anyhow::Error> {
unimplemented!()
}
fn get_auth_cfg(&self) -> &auth::Config {
unimplemented!()
}
fn get_auth_method(&self) -> auth::MethodName {
self.auth_method
}
}
impl std::fmt::Debug for MockSupplicant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MockSupplicant cannot be formatted")
}
}
pub struct MockSupplicantController {
started: Arc<AtomicBool>,
start_failure: Arc<Mutex<Option<anyhow::Error>>>,
mock_on_eapol_frame: Arc<Mutex<Result<UpdateSink, anyhow::Error>>>,
on_eapol_frame_cb: Arc<Mutex<Option<Box<Cb>>>>,
}
impl MockSupplicantController {
pub fn set_start_failure(&self, error: anyhow::Error) {
self.start_failure.lock().unwrap().replace(error);
}
pub fn is_supplicant_started(&self) -> bool {
self.started.load(Ordering::SeqCst)
}
pub fn set_on_eapol_frame_results(&self, updates: UpdateSink) {
*self.mock_on_eapol_frame.lock().unwrap() = Ok(updates);
}
pub fn set_on_eapol_frame_callback<F>(&self, cb: F)
where
F: Fn() + Send + 'static,
{
*self.on_eapol_frame_cb.lock().unwrap() = Some(Box::new(cb));
}
pub fn set_on_eapol_frame_failure(&self, error: anyhow::Error) {
*self.mock_on_eapol_frame.lock().unwrap() = Err(error);
}
}