blob: e3b1d6283dbb5c94a6a3a37be5733e740bca6771 [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.
mod bss;
mod capabilities;
mod event;
pub mod info;
mod inspect;
mod protection;
mod rsn;
mod scan;
mod state;
mod wpa;
#[cfg(test)]
pub mod test_utils;
use {
self::{
event::Event,
info::InfoReporter,
protection::Protection,
rsn::{get_wpa2_rsna, get_wpa3_rsna},
scan::{DiscoveryScan, JoinScan, ScanScheduler},
state::{ClientState, ConnectCommand},
wpa::get_legacy_wpa_association,
},
crate::{
clone_utils::clone_bss_desc,
responder::Responder,
sink::{InfoSink, MlmeSink},
timer::{self, TimedEvent},
InfoStream, MlmeRequest, MlmeStream, Ssid,
},
fidl_fuchsia_wlan_common as fidl_common,
fidl_fuchsia_wlan_mlme::{
self as fidl_mlme, BssDescription, DeviceInfo, MlmeEvent, ScanRequest,
},
fidl_fuchsia_wlan_sme as fidl_sme,
fuchsia_inspect_contrib::{inspect_insert, inspect_log, log::InspectListClosure},
futures::channel::{mpsc, oneshot},
log::{error, info},
std::sync::Arc,
wep_deprecated,
wlan_common::{
self,
bss::{BssDescriptionExt, Protection as BssProtection},
format::MacFmt,
mac::MacAddr,
RadioConfig,
},
wlan_inspect::wrappers::InspectWlanChan,
wlan_rsn::auth,
};
pub use self::{
bss::{BssInfo, ClientConfig},
info::{InfoEvent, ScanResult},
};
// This is necessary to trick the private-in-public checker.
// A private module is not allowed to include private types in its interface,
// even though the module itself is private and will never be exported.
// As a workaround, we add another private module with public types.
mod internal {
use {
crate::{
client::{event::Event, info::InfoReporter, inspect, ConnectionAttemptId},
sink::MlmeSink,
timer::Timer,
},
fidl_fuchsia_wlan_mlme::DeviceInfo,
std::sync::Arc,
};
pub struct Context {
pub device_info: Arc<DeviceInfo>,
pub mlme_sink: MlmeSink,
pub(crate) timer: Timer<Event>,
pub att_id: ConnectionAttemptId,
pub(crate) inspect: Arc<inspect::SmeTree>,
pub(crate) info: InfoReporter,
pub(crate) is_softmac: bool,
}
}
use self::internal::*;
pub type TimeStream = timer::TimeStream<Event>;
pub struct ConnectConfig {
responder: Responder<ConnectResult>,
credential: fidl_sme::Credential,
radio_cfg: RadioConfig,
}
// An automatically increasing sequence number that uniquely identifies a logical
// connection attempt. For example, a new connection attempt can be triggered
// by a DisassociateInd message from the MLME.
pub type ConnectionAttemptId = u64;
pub type ScanTxnId = u64;
pub struct ClientSme {
cfg: ClientConfig,
state: Option<ClientState>,
scan_sched: ScanScheduler<Responder<BssDiscoveryResult>, ConnectConfig>,
context: Context,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ConnectResult {
Success,
Canceled,
Failed(ConnectFailure),
}
impl<T: Into<ConnectFailure>> From<T> for ConnectResult {
fn from(failure: T) -> Self {
ConnectResult::Failed(failure.into())
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ConnectFailure {
SelectNetworkFailure(SelectNetworkFailure),
ScanFailure(fidl_mlme::ScanResultCodes),
JoinFailure(fidl_mlme::JoinResultCodes),
AuthenticationFailure(fidl_mlme::AuthenticateResultCodes),
AssociationFailure(AssociationFailure),
EstablishRsnaFailure(EstablishRsnaFailure),
}
impl ConnectFailure {
pub fn is_timeout(&self) -> bool {
// Note: we don't return true for JoinFailureTimeout because it's the only join failure
// type, so in practice it's returned whether there's a timeout or not.
// For association, we don't have a failure type for timeout, so cannot deduce
// whether an association failure is due to timeout.
//
// TODO(fxbug.dev/29897): Change JOIN_FAILURE_TIMEOUT -> JOIN_FAILURE
match self {
ConnectFailure::AuthenticationFailure(failure) => match failure {
fidl_mlme::AuthenticateResultCodes::AuthFailureTimeout => true,
_ => false,
},
ConnectFailure::EstablishRsnaFailure(failure) => match failure {
EstablishRsnaFailure {
reason: EstablishRsnaFailureReason::KeyFrameExchangeTimeout,
..
}
| EstablishRsnaFailure {
reason: EstablishRsnaFailureReason::OverallTimeout, ..
} => true,
_ => false,
},
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CredentialErrorMessage(String);
impl From<CredentialErrorMessage> for SelectNetworkFailure {
fn from(msg: CredentialErrorMessage) -> Self {
SelectNetworkFailure::CredentialError(msg)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum SelectNetworkFailure {
NoScanResultWithSsid,
NoCompatibleNetwork,
CredentialError(CredentialErrorMessage),
}
impl From<SelectNetworkFailure> for ConnectFailure {
fn from(failure: SelectNetworkFailure) -> Self {
ConnectFailure::SelectNetworkFailure(failure)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct AssociationFailure {
pub bss_protection: BssProtection,
pub code: fidl_mlme::AssociateResultCodes,
}
impl From<AssociationFailure> for ConnectFailure {
fn from(failure: AssociationFailure) -> Self {
ConnectFailure::AssociationFailure(failure)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct EstablishRsnaFailure {
pub auth_method: Option<auth::MethodName>,
pub reason: EstablishRsnaFailureReason,
}
#[derive(Clone, Debug, PartialEq)]
pub enum EstablishRsnaFailureReason {
StartSupplicantFailed,
KeyFrameExchangeTimeout,
OverallTimeout,
InternalError,
}
impl From<EstablishRsnaFailure> for ConnectFailure {
fn from(failure: EstablishRsnaFailure) -> Self {
ConnectFailure::EstablishRsnaFailure(failure)
}
}
pub type BssDiscoveryResult = Result<Vec<BssInfo>, fidl_mlme::ScanResultCodes>;
#[derive(Clone, Debug, PartialEq)]
pub struct Status {
pub connected_to: Option<BssInfo>,
pub connecting_to: Option<Ssid>,
}
impl ClientSme {
pub fn new(
cfg: ClientConfig,
info: DeviceInfo,
iface_tree_holder: Arc<wlan_inspect::iface_mgr::IfaceTreeHolder>,
inspect_hasher: wlan_inspect::InspectHasher,
is_softmac: bool,
) -> (Self, MlmeStream, InfoStream, TimeStream) {
let device_info = Arc::new(info);
let (mlme_sink, mlme_stream) = mpsc::unbounded();
let (info_sink, info_stream) = mpsc::unbounded();
let (mut timer, time_stream) = timer::create_timer();
let inspect = Arc::new(inspect::SmeTree::new(&iface_tree_holder.node, inspect_hasher));
iface_tree_holder.add_iface_subtree(inspect.clone());
timer.schedule(event::InspectPulseCheck);
(
ClientSme {
cfg,
state: Some(ClientState::new(cfg)),
scan_sched: ScanScheduler::new(Arc::clone(&device_info)),
context: Context {
mlme_sink: MlmeSink::new(mlme_sink),
device_info,
timer,
att_id: 0,
inspect,
info: InfoReporter::new(InfoSink::new(info_sink)),
is_softmac,
},
},
mlme_stream,
info_stream,
time_stream,
)
}
pub fn on_connect_command(
&mut self,
req: fidl_sme::ConnectRequest,
) -> oneshot::Receiver<ConnectResult> {
let (responder, receiver) = Responder::new();
if req.ssid.len() > wlan_common::ie::SSID_MAX_LEN {
// TODO(fxbug.dev/42081): Use a more accurate error (InvalidSsidArg) for this error.
responder.respond(SelectNetworkFailure::NoScanResultWithSsid.into());
return receiver;
}
// Cancel any ongoing connect attempt
self.state = self.state.take().map(|state| state.cancel_ongoing_connect(&mut self.context));
let ssid = req.ssid;
// We want to default to Active scan so that for routers that support WSC, we can retrieve
// AP metadata from the probe response. However, for SoftMAC, we default to passive scan
// because we do not have a proper active scan implementation for DFS channels.
let scan_request = if self.context.is_softmac
&& req.deprecated_scan_type == fidl_common::ScanType::Passive
{
fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {})
} else {
fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
ssids: vec![],
channels: vec![],
})
};
info!("SME received a connect command. Initiating a join scan with targeted SSID");
let (canceled_token, req) = self.scan_sched.enqueue_scan_to_join(JoinScan {
ssid: ssid.clone(),
token: ConnectConfig {
responder,
credential: req.credential,
radio_cfg: RadioConfig::from_fidl(req.radio_cfg),
},
scan_request,
});
// If the new scan replaced an existing pending JoinScan, notify the existing transaction
if let Some(token) = canceled_token {
report_connect_finished(
Some(token.responder),
&mut self.context,
ConnectResult::Canceled,
);
}
self.context.info.report_connect_started(ssid);
self.send_scan_request(req);
receiver
}
pub fn on_disconnect_command(&mut self) {
self.state = self.state.take().map(|state| state.disconnect(&mut self.context));
self.context.inspect.update_pulse(self.status());
}
pub fn on_scan_command(
&mut self,
scan_request: fidl_sme::ScanRequest,
) -> oneshot::Receiver<BssDiscoveryResult> {
info!("SME received a scan command, initiating a discovery scan");
let (responder, receiver) = Responder::new();
let scan = DiscoveryScan::new(responder, scan_request);
let req = self.scan_sched.enqueue_scan_to_discover(scan);
self.send_scan_request(req);
receiver
}
pub fn status(&self) -> Status {
let status = self.state.as_ref().expect("expected state to be always present").status();
if status.connecting_to.is_some() {
status
} else {
// If the association machine is not connecting to a network, but the scanner
// has a queued 'JoinScan', include the SSID we are trying to connect to
Status {
connecting_to: self.scan_sched.get_join_scan().map(|s| s.ssid.clone()),
..status
}
}
}
fn send_scan_request(&mut self, req: Option<ScanRequest>) {
if let Some(req) = req {
let is_join_scan = self.scan_sched.is_scanning_to_join();
let is_connected = self.status().connected_to.is_some();
self.context.info.report_scan_started(req.clone(), is_join_scan, is_connected);
self.context.mlme_sink.send(MlmeRequest::Scan(req));
}
}
}
impl super::Station for ClientSme {
type Event = Event;
fn on_mlme_event(&mut self, event: MlmeEvent) {
match event {
MlmeEvent::OnScanResult { result } => {
self.scan_sched.on_mlme_scan_result(result);
}
MlmeEvent::OnScanEnd { end } => {
let txn_id = end.txn_id;
let (result, request) = self.scan_sched.on_mlme_scan_end(end);
// Finalize stats for previous scan first before sending scan request for the next
// one, which would also start stats collection for new scan scan.
self.context.info.report_scan_ended(txn_id, &result);
self.send_scan_request(request);
match result {
scan::ScanResult::None => (),
scan::ScanResult::JoinScanFinished { token, result: Ok(bss_list) } => {
let mut inspect_msg: Option<String> = None;
let best_bss = self.cfg.get_best_bss(&bss_list);
if let Some(ref best_bss) = best_bss {
self.context.info.report_candidate_network(clone_bss_desc(best_bss));
}
let best_bssid = best_bss.as_ref().map(|bss| bss.bssid.clone());
match best_bss {
// BSS found and compatible.
Some(best_bss) if self.cfg.is_bss_compatible(best_bss) => {
match get_protection(
&self.context.device_info,
&self.cfg,
&token.credential,
&best_bss,
) {
Ok(protection) => {
inspect_msg.replace("attempt to connect".to_string());
let cmd = ConnectCommand {
bss: Box::new(clone_bss_desc(&best_bss)),
responder: Some(token.responder),
protection,
radio_cfg: token.radio_cfg,
};
self.state = self
.state
.take()
.map(|state| state.connect(cmd, &mut self.context));
}
Err(credential_error_message) => {
inspect_msg.replace(format!(
"cannot join: {:?}",
credential_error_message
));
error!(
"cannot join '{}' ({}): {:?}",
String::from_utf8_lossy(&best_bss.ssid[..]),
best_bss.bssid.to_mac_str(),
credential_error_message,
);
report_connect_finished(
Some(token.responder),
&mut self.context,
SelectNetworkFailure::from(credential_error_message)
.into(),
);
}
}
}
// Incompatible network
Some(incompatible_bss) => {
inspect_msg.replace("incompatible BSS".to_string());
error!("incompatible BSS: {:?}", &incompatible_bss);
report_connect_finished(
Some(token.responder),
&mut self.context,
SelectNetworkFailure::NoCompatibleNetwork.into(),
);
}
// No matching BSS found
None => {
inspect_msg.replace("no matching BSS".to_string());
error!("no matching BSS found");
report_connect_finished(
Some(token.responder),
&mut self.context,
SelectNetworkFailure::NoScanResultWithSsid.into(),
);
}
};
inspect_log_join_scan(
&mut self.context,
&bss_list,
best_bssid,
inspect_msg,
);
}
scan::ScanResult::JoinScanFinished { token, result: Err(e) } => {
inspect_log!(
self.context.inspect.join_scan_events.lock(),
result: format!("scan failure: {:?}", e),
);
error!("cannot join network because scan failed: {:?}", e);
let result = ConnectFailure::ScanFailure(e).into();
report_connect_finished(Some(token.responder), &mut self.context, result);
}
scan::ScanResult::DiscoveryFinished { tokens, result } => {
let result = result.map(|bss_list| {
bss_list
.iter()
.map(|bss| self.cfg.convert_bss_description(&bss, None))
.collect()
});
for responder in tokens {
responder.respond(result.clone());
}
}
}
}
other => {
self.state =
self.state.take().map(|state| state.on_mlme_event(other, &mut self.context));
}
};
self.context.inspect.update_pulse(self.status());
}
fn on_timeout(&mut self, timed_event: TimedEvent<Event>) {
self.state = self.state.take().map(|state| match timed_event.event {
event @ Event::EstablishingRsnaTimeout(..)
| event @ Event::KeyFrameExchangeTimeout(..)
| event @ Event::ConnectionPing(..)
| event @ Event::SaeTimeout(..) => {
state.handle_timeout(timed_event.id, event, &mut self.context)
}
Event::InspectPulseCheck(..) => {
self.context.timer.schedule(event::InspectPulseCheck);
state
}
});
// Because `self.status()` relies on the value of `self.state` to be present, we cannot
// retrieve it and update pulse node inside the closure above.
self.context.inspect.update_pulse(self.status());
}
}
fn inspect_log_join_scan(
ctx: &mut Context,
bss_list: &[fidl_mlme::BssDescription],
candidate_bssid: Option<MacAddr>,
result_msg: Option<String>,
) {
let inspect_bss = InspectListClosure(&bss_list, |node_writer, key, bss| {
inspect_insert!(node_writer, var key: {
bssid: bss.bssid.to_mac_str(),
bssid_hash: ctx.inspect.hasher.hash_mac_addr(bss.bssid),
ssid: String::from_utf8_lossy(&bss.ssid[..]).as_ref(),
ssid_hash: ctx.inspect.hasher.hash(&bss.ssid[..]),
channel: InspectWlanChan(&bss.chan),
rcpi_dbm: bss.rcpi_dbmh / 2,
rsni_db: bss.rsni_dbh / 2,
rssi_dbm: bss.rssi_dbm,
});
});
let hasher = &ctx.inspect.hasher;
inspect_log!(ctx.inspect.join_scan_events.lock(), {
bss_list: inspect_bss,
candidate_bss: {
bssid?: candidate_bssid.as_ref().map(|bssid| bssid.to_mac_str()),
bssid_hash?: candidate_bssid.map(|bssid| hasher.hash_mac_addr(bssid)),
},
result?: result_msg,
});
}
fn report_connect_finished(
responder: Option<Responder<ConnectResult>>,
context: &mut Context,
result: ConnectResult,
) {
if let Some(responder) = responder {
responder.respond(result.clone());
context.info.report_connect_finished(result);
}
}
pub fn get_protection(
device_info: &DeviceInfo,
client_config: &ClientConfig,
credential: &fidl_sme::Credential,
bss: &BssDescription,
) -> Result<Protection, CredentialErrorMessage> {
match bss.get_protection() {
wlan_common::bss::Protection::Open => match credential {
fidl_sme::Credential::None(_) => Ok(Protection::Open),
_ => Err(CredentialErrorMessage("credentials provided for open network".to_string())),
},
wlan_common::bss::Protection::Wep => match credential {
fidl_sme::Credential::Password(pwd) => wep_deprecated::derive_key(&pwd[..])
.map(Protection::Wep)
.map_err(|e| CredentialErrorMessage(e.to_string())),
_ => {
Err(CredentialErrorMessage(format!("unsupported credential type {:?}", credential)))
}
},
wlan_common::bss::Protection::Wpa1 => {
get_legacy_wpa_association(device_info, credential, bss).map_err(|e| {
CredentialErrorMessage(format!("failed to get protection for legacy WPA: {}", e))
})
}
// If WPA3 is supported, we will only treat Wpa2/Wpa3 transition APs as WPA3.
wlan_common::bss::Protection::Wpa3Personal
| wlan_common::bss::Protection::Wpa2Wpa3Personal
if client_config.wpa3_supported =>
{
get_wpa3_rsna(device_info, credential, bss).map_err(|e| {
CredentialErrorMessage(format!("failed to get protection for WPA3: {}", e))
})
}
wlan_common::bss::Protection::Wpa1Wpa2Personal
| wlan_common::bss::Protection::Wpa2Personal
| wlan_common::bss::Protection::Wpa2Wpa3Personal => {
get_wpa2_rsna(device_info, credential, bss)
.map_err(|e| CredentialErrorMessage(e.to_string()))
}
wlan_common::bss::Protection::Unknown => {
Err(CredentialErrorMessage("unknown protection type for targeted SSID".to_string()))
}
other => Err(CredentialErrorMessage(format!(
"unsupported protection type for targeted SSID: {:?}",
other
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Config as SmeConfig;
use fidl_fuchsia_wlan_mlme as fidl_mlme;
use fuchsia_inspect as finspect;
use wlan_common::{assert_variant, fake_bss, ie::rsn::akm, RadioConfig};
use super::test_utils::{
create_assoc_conf, create_auth_conf, create_join_conf, expect_stream_empty,
};
use crate::test_utils;
use crate::Station;
const CLIENT_ADDR: [u8; 6] = [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67];
const DUMMY_HASH_KEY: [u8; 8] = [88, 77, 66, 55, 44, 33, 22, 11];
fn report_fake_scan_result(sme: &mut ClientSme, bss: fidl_mlme::BssDescription) {
sme.on_mlme_event(MlmeEvent::OnScanResult {
result: fidl_mlme::ScanResult { txn_id: 1, bss },
});
sme.on_mlme_event(MlmeEvent::OnScanEnd {
end: fidl_mlme::ScanEnd { txn_id: 1, code: fidl_mlme::ScanResultCodes::Success },
});
}
#[test]
fn test_get_protection() {
let dev_info = test_utils::fake_device_info(CLIENT_ADDR);
let client_config = Default::default();
// Open network without credentials:
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let bss = fake_bss!(Open);
let protection = get_protection(&dev_info, &client_config, &credential, &bss);
assert_variant!(protection, Ok(Protection::Open));
// Open network with credentials:
let credential = fidl_sme::Credential::Password(b"somepass".to_vec());
let bss = fake_bss!(Open);
get_protection(&dev_info, &client_config, &credential, &bss)
.expect_err("unprotected network cannot use password");
// RSN with user entered password:
let credential = fidl_sme::Credential::Password(b"somepass".to_vec());
let bss = fake_bss!(Wpa2);
let protection = get_protection(&dev_info, &client_config, &credential, &bss);
assert_variant!(protection, Ok(Protection::Rsna(_)));
// RSN with user entered PSK:
let credential = fidl_sme::Credential::Psk(vec![0xAC; 32]);
let bss = fake_bss!(Wpa2);
let protection = get_protection(&dev_info, &client_config, &credential, &bss);
assert_variant!(protection, Ok(Protection::Rsna(_)));
// RSN without credentials:
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let bss = fake_bss!(Wpa2);
get_protection(&dev_info, &client_config, &credential, &bss)
.expect_err("protected network requires password");
}
#[test]
fn test_get_protection_wep() {
let dev_info = test_utils::fake_device_info(CLIENT_ADDR);
let client_config = ClientConfig::from_config(SmeConfig::default().with_wep(), false);
// WEP-40 with credentials:
let credential = fidl_sme::Credential::Password(b"wep40".to_vec());
let bss = fake_bss!(Wep);
let protection = get_protection(&dev_info, &client_config, &credential, &bss);
assert_variant!(protection, Ok(Protection::Wep(_)));
// WEP-104 with credentials:
let credential = fidl_sme::Credential::Password(b"superinsecure".to_vec());
let bss = fake_bss!(Wep);
let protection = get_protection(&dev_info, &client_config, &credential, &bss);
assert_variant!(protection, Ok(Protection::Wep(_)));
// WEP without credentials:
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let bss = fake_bss!(Wep);
get_protection(&dev_info, &client_config, &credential, &bss)
.expect_err("WEP network not supported");
// WEP with invalid credentials:
let credential = fidl_sme::Credential::Password(b"wep".to_vec());
let bss = fake_bss!(Wep);
get_protection(&dev_info, &client_config, &credential, &bss)
.expect_err("expected error for invalid WEP credentials");
}
#[test]
fn test_get_protection_sae() {
let dev_info = test_utils::fake_device_info(CLIENT_ADDR);
let mut client_config = ClientConfig::from_config(SmeConfig::default(), true);
// WPA3, supported
let credential = fidl_sme::Credential::Password(b"somepass".to_vec());
let bss = fake_bss!(Wpa3);
let protection = get_protection(&dev_info, &client_config, &credential, &bss);
assert_variant!(protection, Ok(Protection::Rsna(rsna)) => {
assert_eq!(rsna.negotiated_protection.akm.suite_type, akm::SAE)
});
// WPA2/3, supported
let credential = fidl_sme::Credential::Password(b"somepass".to_vec());
let bss = fake_bss!(Wpa2Wpa3);
let protection = get_protection(&dev_info, &client_config, &credential, &bss);
assert_variant!(protection, Ok(Protection::Rsna(rsna)) => {
assert_eq!(rsna.negotiated_protection.akm.suite_type, akm::SAE)
});
client_config.wpa3_supported = false;
// WPA3, unsupported
let credential = fidl_sme::Credential::Password(b"somepass".to_vec());
let bss = fake_bss!(Wpa3);
let protection = get_protection(&dev_info, &client_config, &credential, &bss);
assert_variant!(protection, Err(_));
// WPA2/3, WPA3 unsupported, downgrade to WPA2
let credential = fidl_sme::Credential::Password(b"somepass".to_vec());
let bss = fake_bss!(Wpa2Wpa3);
let protection = get_protection(&dev_info, &client_config, &credential, &bss);
assert_variant!(protection, Ok(Protection::Rsna(rsna)) => {
assert_eq!(rsna.negotiated_protection.akm.suite_type, akm::PSK)
});
}
#[test]
fn status_connecting_to() {
let (mut sme, _mlme_stream, _info_stream, _time_stream) = create_sme();
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
// Issue a connect command and expect the status to change appropriately.
// We also check that the association machine state is still disconnected
// to make sure that the status comes from the scanner.
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
// Push a fake scan result into SME. We should still be connecting to "foo",
// but the status should now come from the state machine and not from the scanner.
report_fake_scan_result(&mut sme, fake_bss!(Open, ssid: b"foo".to_vec()));
assert_eq!(Some(b"foo".to_vec()), sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
// As soon as connect command is issued for "bar", the status changes immediately
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv2 = sme.on_connect_command(connect_req(b"bar".to_vec(), credential));
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"bar".to_vec()) },
sme.status()
);
}
#[test]
fn connecting_to_wep_network_supported() {
let inspector = finspect::Inspector::new();
let sme_root_node = inspector.root().create_child("sme");
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = ClientSme::new(
ClientConfig::from_config(SmeConfig::default().with_wep(), false),
test_utils::fake_device_info(CLIENT_ADDR),
Arc::new(wlan_inspect::iface_mgr::IfaceTreeHolder::new(sme_root_node)),
wlan_inspect::InspectHasher::new(DUMMY_HASH_KEY),
true, // is_softmac
);
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
// Issue a connect command and verify that connecting_to status is changed for upper
// layer (but not underlying state machine) and a scan request is sent to MLME.
let credential = fidl_sme::Credential::Password(b"wep40".to_vec());
let req = connect_req(b"foo".to_vec(), credential);
let _recv = sme.on_connect_command(req);
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Scan(..))));
// Simulate scan end and verify that underlying state machine's status is changed,
// and a join request is sent to MLME.
report_fake_scan_result(&mut sme, fake_bss!(Wep, ssid: b"foo".to_vec()));
assert_eq!(Some(b"foo".to_vec()), sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Join(..))));
}
#[test]
fn connecting_to_wep_network_unsupported() {
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = create_sme();
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
// Issue a connect command and verify that connecting_to status is changed for upper
// layer (but not underlying state machine) and a scan request is sent to MLME.
let credential = fidl_sme::Credential::Password(b"wep40".to_vec());
let req = connect_req(b"foo".to_vec(), credential);
let mut connect_fut = sme.on_connect_command(req);
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Scan(..))));
// Simulate scan end and verify that underlying state machine's status is not changed,
report_fake_scan_result(&mut sme, fake_bss!(Wep, ssid: b"foo".to_vec()));
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_connect_result_failed(&mut connect_fut);
}
#[test]
fn connecting_password_supplied_for_protected_network() {
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = create_sme();
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
// Issue a connect command and verify that connecting_to status is changed for upper
// layer (but not underlying state machine) and a scan request is sent to MLME.
let credential = fidl_sme::Credential::Password(b"somepass".to_vec());
let req = connect_req(b"foo".to_vec(), credential);
let _recv = sme.on_connect_command(req);
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Scan(..))));
// Simulate scan end and verify that underlying state machine's status is changed,
// and a join request is sent to MLME.
report_fake_scan_result(&mut sme, fake_bss!(Wpa2, ssid: b"foo".to_vec()));
assert_eq!(Some(b"foo".to_vec()), sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Join(..))));
}
#[test]
fn connecting_psk_supplied_for_protected_network() {
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = create_sme();
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
// Issue a connect command and verify that connecting_to status is changed for upper
// layer (but not underlying state machine) and a scan request is sent to MLME.
// IEEE Std 802.11-2016, J.4.2, Test case 1
// PSK for SSID "IEEE" and password "password".
#[rustfmt::skip]
let psk = vec![
0xf4, 0x2c, 0x6f, 0xc5, 0x2d, 0xf0, 0xeb, 0xef,
0x9e, 0xbb, 0x4b, 0x90, 0xb3, 0x8a, 0x5f, 0x90,
0x2e, 0x83, 0xfe, 0x1b, 0x13, 0x5a, 0x70, 0xe2,
0x3a, 0xed, 0x76, 0x2e, 0x97, 0x10, 0xa1, 0x2e,
];
let credential = fidl_sme::Credential::Psk(psk);
let req = connect_req(b"IEEE".to_vec(), credential);
let _recv = sme.on_connect_command(req);
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"IEEE".to_vec()) },
sme.status()
);
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Scan(..))));
// Simulate scan end and verify that underlying state machine's status is changed,
// and a join request is sent to MLME.
report_fake_scan_result(&mut sme, fake_bss!(Wpa2, ssid: b"IEEE".to_vec()));
assert_eq!(Some(b"IEEE".to_vec()), sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"IEEE".to_vec()) },
sme.status()
);
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Join(..))));
}
#[test]
fn connecting_password_supplied_for_unprotected_network() {
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = create_sme();
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
let credential = fidl_sme::Credential::Password(b"somepass".to_vec());
let req = connect_req(b"foo".to_vec(), credential);
let mut connect_fut = sme.on_connect_command(req);
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
// Push a fake scan result into SME. We should not attempt to connect
// because a password was supplied for unprotected network. So both the
// SME client and underlying state machine should report not connecting
// anymore.
report_fake_scan_result(&mut sme, fake_bss!(Open, ssid: b"foo".to_vec()));
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
// No join request should be sent to MLME
loop {
match mlme_stream.try_next() {
Ok(event) => match event {
Some(MlmeRequest::Join(..)) => panic!("unexpected join request to MLME"),
None => break,
_ => (),
},
Err(e) => {
assert_eq!(e.to_string(), "receiver channel is empty");
break;
}
}
}
// User should get a message that connection failed
assert_variant!(
connect_fut.try_recv(),
Ok(
Some(
ConnectResult::Failed(
ConnectFailure::SelectNetworkFailure(
SelectNetworkFailure::CredentialError(_),
),
),
),
)
);
}
#[test]
fn connecting_psk_supplied_for_unprotected_network() {
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = create_sme();
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
let credential = fidl_sme::Credential::Psk(b"somepass".to_vec());
let req = connect_req(b"foo".to_vec(), credential);
let mut connect_fut = sme.on_connect_command(req);
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
// Push a fake scan result into SME. We should not attempt to connect
// because a password was supplied for unprotected network. So both the
// SME client and underlying state machine should report not connecting
// anymore.
report_fake_scan_result(&mut sme, fake_bss!(Open, ssid: b"foo".to_vec()));
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
// No join request should be sent to MLME
loop {
match mlme_stream.try_next() {
Ok(event) => match event {
Some(MlmeRequest::Join(..)) => panic!("unexpected join request to MLME"),
None => break,
_ => (),
},
Err(e) => {
assert_eq!(e.to_string(), "receiver channel is empty");
break;
}
}
}
// User should get a message that connection failed
assert_variant!(
connect_fut.try_recv(),
Ok(
Some(
ConnectResult::Failed(
ConnectFailure::SelectNetworkFailure(
SelectNetworkFailure::CredentialError(_),
),
),
),
)
);
}
#[test]
fn connecting_no_password_supplied_for_protected_network() {
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = create_sme();
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let req = connect_req(b"foo".to_vec(), credential);
let mut connect_fut = sme.on_connect_command(req);
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(
Status { connected_to: None, connecting_to: Some(b"foo".to_vec()) },
sme.status()
);
// Push a fake scan result into SME. We should not attempt to connect
// because no password was supplied for a protected network.
report_fake_scan_result(&mut sme, fake_bss!(Wpa2, ssid: b"foo".to_vec()));
assert_eq!(None, sme.state.as_ref().unwrap().status().connecting_to);
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
// No join request should be sent to MLME
loop {
match mlme_stream.try_next() {
Ok(event) => match event {
Some(MlmeRequest::Join(..)) => panic!("unexpected join request sent to MLME"),
None => break,
_ => (),
},
Err(e) => {
assert_eq!(e.to_string(), "receiver channel is empty");
break;
}
}
}
// User should get a message that connection failed
assert_variant!(
connect_fut.try_recv(),
Ok(
Some(
ConnectResult::Failed(
ConnectFailure::SelectNetworkFailure(
SelectNetworkFailure::CredentialError(_),
),
),
),
)
);
}
#[test]
fn connecting_no_scan_result_with_ssid() {
let (mut sme, _mlme_stream, _info_stream, _time_stream) = create_sme();
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let mut connect_fut = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
let bss_desc = fake_bss!(Open, ssid: b"bar".to_vec());
report_fake_scan_result(&mut sme, bss_desc);
assert_variant!(connect_fut.try_recv(), Ok(Some(failure)) => {
assert_eq!(failure, SelectNetworkFailure::NoScanResultWithSsid.into());
});
}
#[test]
fn connecting_no_compatible_network_found() {
let (mut sme, _mlme_stream, _info_stream, _time_stream) = create_sme();
let credential = fidl_sme::Credential::Password(b"password".to_vec());
let mut connect_fut = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
let bss_desc = fidl_mlme::BssDescription {
cap: wlan_common::mac::CapabilityInfo(0).with_privacy(false).0,
..fake_bss!(Wpa2, ssid: b"foo".to_vec())
};
// Make our check flag this BSS as incompatible
report_fake_scan_result(&mut sme, bss_desc);
assert_variant!(connect_fut.try_recv(), Ok(Some(failure)) => {
assert_eq!(failure, SelectNetworkFailure::NoCompatibleNetwork.into());
});
}
#[test]
fn connection_rejected_ssid_too_long() {
let (mut sme, _mlme_stream, _info_stream, _time_stream) = create_sme();
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
// SSID is one byte too long
let mut connect_fut = sme.on_connect_command(connect_req([65; 33].to_vec(), credential));
assert_variant!(connect_fut.try_recv(), Ok(Some(failure)) => {
assert_eq!(failure, SelectNetworkFailure::NoScanResultWithSsid.into());
});
}
#[test]
fn new_connect_attempt_cancels_pending_connect() {
let (mut sme, _mlme_stream, _info_stream, _time_stream) = create_sme();
let req = connect_req(b"foo".to_vec(), fidl_sme::Credential::None(fidl_sme::Empty));
let mut connect_fut1 = sme.on_connect_command(req);
let req2 = connect_req(b"foo".to_vec(), fidl_sme::Credential::None(fidl_sme::Empty));
let mut connect_fut2 = sme.on_connect_command(req2);
// User should get a message that first connection attempt is canceled
assert_connect_result(&mut connect_fut1, ConnectResult::Canceled);
// Report scan result to transition second connection attempt past scan. This is to verify
// that connection attempt will be canceled even in the middle of joining the network
report_fake_scan_result(&mut sme, fake_bss!(Open, ssid: b"foo".to_vec()));
let req3 = connect_req(b"foo".to_vec(), fidl_sme::Credential::None(fidl_sme::Empty));
let mut _connect_fut3 = sme.on_connect_command(req3);
// Verify that second connection attempt is canceled as new connect request comes in
assert_connect_result(&mut connect_fut2, ConnectResult::Canceled);
}
#[test]
fn test_info_event_complete_connect() {
let (mut sme, _mlme_stream, mut info_stream, _time_stream) = create_sme();
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
let bss_desc = fake_bss!(Open, ssid: b"foo".to_vec());
let bssid = bss_desc.bssid;
report_fake_scan_result(&mut sme, bss_desc);
sme.on_mlme_event(create_join_conf(fidl_mlme::JoinResultCodes::Success));
sme.on_mlme_event(create_auth_conf(bssid, fidl_mlme::AuthenticateResultCodes::Success));
sme.on_mlme_event(create_assoc_conf(fidl_mlme::AssociateResultCodes::Success));
assert_variant!(info_stream.try_next(), Ok(Some(InfoEvent::ConnectionPing(..))));
assert_variant!(info_stream.try_next(), Ok(Some(InfoEvent::ConnectStats(stats))) => {
let scan_stats = stats.join_scan_stats().expect("expect join scan stats");
assert!(!scan_stats.scan_start_while_connected);
assert!(scan_stats.scan_time().into_nanos() > 0);
assert_eq!(scan_stats.result, ScanResult::Success);
assert_eq!(scan_stats.bss_count, 1);
assert!(stats.auth_time().is_some());
assert!(stats.assoc_time().is_some());
assert!(stats.rsna_time().is_none());
assert!(stats.connect_time().into_nanos() > 0);
assert_eq!(stats.result, ConnectResult::Success);
assert!(stats.candidate_network.is_some());
assert!(stats.previous_disconnect_info.is_none());
});
}
#[test]
fn test_info_event_failed_connect() {
let (mut sme, _mlme_stream, mut info_stream, _time_stream) = create_sme();
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
let bss_desc = fake_bss!(Open, ssid: b"foo".to_vec());
let bssid = bss_desc.bssid;
report_fake_scan_result(&mut sme, bss_desc);
sme.on_mlme_event(create_join_conf(fidl_mlme::JoinResultCodes::Success));
let auth_failure = fidl_mlme::AuthenticateResultCodes::Refused;
sme.on_mlme_event(create_auth_conf(bssid, auth_failure));
let result = ConnectResult::Failed(ConnectFailure::AuthenticationFailure(auth_failure));
assert_variant!(info_stream.try_next(), Ok(Some(InfoEvent::ConnectStats(stats))) => {
assert_eq!(stats.join_scan_stats().expect("no scan stats").result, ScanResult::Success);
assert!(stats.auth_time().is_some());
// no association time since requests already fails at auth step
assert!(stats.assoc_time().is_none());
assert!(stats.rsna_time().is_none());
assert!(stats.connect_time().into_nanos() > 0);
assert_eq!(stats.result, result);
assert!(stats.candidate_network.is_some());
});
expect_stream_empty(&mut info_stream, "unexpected event in info stream");
}
#[test]
fn test_info_event_connect_canceled_during_scan() {
let (mut sme, _mlme_stream, mut info_stream, _time_stream) = create_sme();
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
// Send another connect request, which should cancel first one
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
assert_variant!(info_stream.try_next(), Ok(Some(InfoEvent::ConnectStats(stats))) => {
assert!(stats.scan_start_stats.is_some());
// Connect attempt cancels before scan is finished. Since we send stats right away,
// end stats is blank and a complete scan stats cannot be constructed.
assert!(stats.scan_end_stats.is_none());
assert!(stats.join_scan_stats().is_none());
assert!(stats.connect_time().into_nanos() > 0);
assert_eq!(stats.result, ConnectResult::Canceled);
assert!(stats.candidate_network.is_none());
});
// Old scan finishes. However, no join scan stats is sent
report_fake_scan_result(&mut sme, fake_bss!(Open, ssid: b"foo".to_vec()));
assert_variant!(info_stream.try_next(), Err(_));
}
#[test]
fn test_info_event_connect_canceled_post_scan() {
let (mut sme, _mlme_stream, mut info_stream, _time_stream) = create_sme();
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
report_fake_scan_result(&mut sme, fake_bss!(Open, ssid: b"foo".to_vec()));
// Send another connect request, which should cancel first one
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
assert_variant!(info_stream.try_next(), Ok(Some(InfoEvent::ConnectStats(stats))) => {
assert_eq!(stats.join_scan_stats().expect("no scan stats").result, ScanResult::Success);
assert!(stats.connect_time().into_nanos() > 0);
assert_eq!(stats.result, ConnectResult::Canceled);
assert!(stats.candidate_network.is_some());
});
}
#[test]
fn test_info_event_candidate_network_multiple_bss() {
let (mut sme, _mlme_stream, mut info_stream, _time_stream) = create_sme();
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
let bss = fake_bss!(Open, ssid: b"foo".to_vec(), bssid: [1; 6]);
sme.on_mlme_event(MlmeEvent::OnScanResult {
result: fidl_mlme::ScanResult { txn_id: 1, bss },
});
let bss = fake_bss!(Open, ssid: b"foo".to_vec(), bssid: [3; 6]);
sme.on_mlme_event(MlmeEvent::OnScanResult {
result: fidl_mlme::ScanResult { txn_id: 1, bss },
});
// This scan result should not be counted since it's not the SSID we request
let bss = fake_bss!(Open, ssid: b"bar".to_vec());
sme.on_mlme_event(MlmeEvent::OnScanResult {
result: fidl_mlme::ScanResult { txn_id: 1, bss },
});
sme.on_mlme_event(MlmeEvent::OnScanEnd {
end: fidl_mlme::ScanEnd { txn_id: 1, code: fidl_mlme::ScanResultCodes::Success },
});
// Stop connecting attempt early since we just want to get ConnectStats
sme.on_mlme_event(create_join_conf(fidl_mlme::JoinResultCodes::JoinFailureTimeout));
assert_variant!(info_stream.try_next(), Ok(Some(InfoEvent::ConnectStats(stats))) => {
assert_eq!(stats.join_scan_stats().expect("no scan stats").bss_count, 2);
assert!(stats.candidate_network.is_some());
});
}
#[test]
fn test_info_event_dont_suppress_bss() {
let (mut sme, _mlme_strem, _info_stream, _time_stream) = create_sme();
let mut recv =
sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}));
let bss = fake_bss!(Open, ssid: b"foo".to_vec(), bssid: [3; 6]);
sme.on_mlme_event(MlmeEvent::OnScanResult {
result: fidl_mlme::ScanResult { txn_id: 1, bss },
});
let bss = fake_bss!(Open, ssid: b"foo".to_vec(), bssid: [4; 6]);
sme.on_mlme_event(MlmeEvent::OnScanResult {
result: fidl_mlme::ScanResult { txn_id: 1, bss },
});
sme.on_mlme_event(MlmeEvent::OnScanEnd {
end: fidl_mlme::ScanEnd { txn_id: 1, code: fidl_mlme::ScanResultCodes::Success },
});
// check that both BSS are received at the end of a scan
assert_variant!(recv.try_recv(), Ok(Some(Ok(bss_info))) => {
let mut reported_bss_ssid = bss_info.into_iter().map(|bss| (bss.ssid, bss.bssid)).collect::<Vec<_>>();
reported_bss_ssid.sort();
assert_eq!(reported_bss_ssid, vec![(b"foo".to_vec(), [3; 6]), (b"foo".to_vec(), [4; 6])]);
})
}
#[test]
fn test_info_event_discovery_scan() {
let (mut sme, _mlme_stream, mut info_stream, _time_stream) = create_sme();
let _recv =
sme.on_scan_command(fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
ssids: vec![],
channels: vec![],
}));
report_fake_scan_result(&mut sme, fake_bss!(Open, rates: vec![12]));
assert_variant!(info_stream.try_next(), Ok(Some(InfoEvent::DiscoveryScanStats(scan_stats))) => {
assert!(!scan_stats.scan_start_while_connected);
assert!(scan_stats.scan_time().into_nanos() > 0);
assert_eq!(scan_stats.scan_type, fidl_mlme::ScanTypes::Active);
assert_eq!(scan_stats.result, ScanResult::Success);
assert_eq!(scan_stats.bss_count, 1);
});
}
#[test]
fn test_on_connect_command_default_to_active_scan() {
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = create_sme();
sme.context.is_softmac = false;
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Scan(req))) => {
assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
});
}
#[test]
fn test_on_connect_command_softmac_adhere_to_scan_type_arg_passive() {
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = create_sme();
sme.context.is_softmac = true;
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
let _recv = sme.on_connect_command(connect_req(b"foo".to_vec(), credential));
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Scan(req))) => {
assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
});
}
#[test]
fn test_on_connect_command_softmac_adhere_to_scan_type_arg_active() {
let (mut sme, mut mlme_stream, _info_stream, _time_stream) = create_sme();
sme.context.is_softmac = true;
assert_eq!(Status { connected_to: None, connecting_to: None }, sme.status());
let mut req = connect_req(b"foo".to_vec(), fidl_sme::Credential::None(fidl_sme::Empty));
req.deprecated_scan_type = fidl_common::ScanType::Active;
let _recv = sme.on_connect_command(req);
assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Scan(req))) => {
assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
});
}
fn assert_connect_result(
connect_result_receiver: &mut oneshot::Receiver<ConnectResult>,
expected: ConnectResult,
) {
match connect_result_receiver.try_recv() {
Ok(Some(actual)) => assert_eq!(expected, actual),
other => panic!("expect {:?}, got {:?}", expected, other),
}
}
fn assert_connect_result_failed(connect_fut: &mut oneshot::Receiver<ConnectResult>) {
assert_variant!(connect_fut.try_recv(), Ok(Some(ConnectResult::Failed(..))));
}
fn connect_req(ssid: Ssid, credential: fidl_sme::Credential) -> fidl_sme::ConnectRequest {
fidl_sme::ConnectRequest {
ssid,
credential,
radio_cfg: RadioConfig::default().to_fidl(),
deprecated_scan_type: fidl_common::ScanType::Passive,
}
}
fn create_sme() -> (ClientSme, MlmeStream, InfoStream, TimeStream) {
let inspector = finspect::Inspector::new();
let sme_root_node = inspector.root().create_child("sme");
ClientSme::new(
ClientConfig::default(),
test_utils::fake_device_info(CLIENT_ADDR),
Arc::new(wlan_inspect::iface_mgr::IfaceTreeHolder::new(sme_root_node)),
wlan_inspect::InspectHasher::new(DUMMY_HASH_KEY),
true, // is_softmac
)
}
}