blob: 2d2a44ec004256e5667dd5e7d21a70b762d88954 [file]
// 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.
mod bound;
mod channel_switch;
mod convert_beacon;
mod lost_bss;
mod scanner;
mod state;
mod station;
use bound::BoundClient;
use station::{Client, ParsedConnectRequest};
#[cfg(test)]
mod test_utils;
use crate::ddk_converter;
use crate::device::{self, DeviceOps};
use crate::error::Error;
use channel_switch::ChannelState;
use fidl_fuchsia_wlan_common as fidl_common;
use fidl_fuchsia_wlan_driver as fidl_driver_common;
use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
use fidl_fuchsia_wlan_minstrel as fidl_minstrel;
use fidl_fuchsia_wlan_mlme as fidl_mlme;
use fidl_fuchsia_wlan_softmac as fidl_softmac;
use fidl_fuchsia_wlan_stats as fidl_stats;
use fuchsia_trace as trace;
use ieee80211::{Bssid, MacAddr, MacAddrBytes};
use log::{error, warn};
use scanner::Scanner;
use wlan_common::bss::BssDescription;
use wlan_common::capabilities::{ClientCapabilities, derive_join_capabilities};
use wlan_common::channel::Channel;
use wlan_common::ie::{self, Id};
use wlan_common::mac::{self, CapabilityInfo};
use wlan_common::sequence::SequenceManager;
use wlan_common::timer::Timer;
use wlan_trace as wtrace;
use zerocopy::SplitByteSlice;
pub use scanner::ScanError;
#[derive(Debug, Clone, PartialEq)]
pub enum TimedEvent {
/// Connecting to AP timed out.
Connecting,
/// Timeout for reassociating after a disassociation.
Reassociating,
/// Association status update includes checking for auto deauthentication due to beacon loss
/// and report signal strength
AssociationStatusCheck,
/// The delay for a scheduled channel switch has elapsed.
ChannelSwitch,
}
#[cfg(test)]
impl TimedEvent {
fn class(&self) -> TimedEventClass {
match self {
Self::Connecting => TimedEventClass::Connecting,
Self::Reassociating => TimedEventClass::Reassociating,
Self::AssociationStatusCheck => TimedEventClass::AssociationStatusCheck,
Self::ChannelSwitch => TimedEventClass::ChannelSwitch,
}
}
}
#[cfg(test)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum TimedEventClass {
Connecting,
Reassociating,
AssociationStatusCheck,
ChannelSwitch,
}
/// ClientConfig affects time duration used for different timeouts.
/// Originally added to more easily control behavior in tests.
#[repr(C)]
#[derive(Debug, Clone, Default)]
pub struct ClientConfig {
pub ensure_on_channel_time: zx::sys::zx_duration_t,
}
pub struct Context<D> {
_config: ClientConfig,
device: D,
timer: Timer<TimedEvent>,
seq_mgr: SequenceManager,
}
pub struct ClientMlme<D> {
sta: Option<Client>,
ctx: Context<D>,
scanner: Scanner,
channel_state: ChannelState,
}
impl<D: DeviceOps> crate::MlmeImpl for ClientMlme<D> {
type Config = ClientConfig;
type Device = D;
type TimerEvent = TimedEvent;
async fn new(
config: Self::Config,
mut device: Self::Device,
timer: Timer<TimedEvent>,
) -> Result<Self, anyhow::Error> {
let iface_mac = device::try_query_iface_mac(&mut device).await?;
Ok(Self {
sta: None,
ctx: Context { _config: config, device, timer, seq_mgr: SequenceManager::new() },
scanner: Scanner::new(iface_mac.into()),
channel_state: Default::default(),
})
}
async fn handle_mlme_request(
&mut self,
req: wlan_sme::MlmeRequest,
) -> Result<(), anyhow::Error> {
match req {
wlan_sme::MlmeRequest::Scan(req) => {
self.on_sme_scan(req).await;
Ok(())
}
wlan_sme::MlmeRequest::Connect(req) => {
self.on_sme_connect(req).await?;
Ok(())
}
wlan_sme::MlmeRequest::GetIfaceStats(responder) => {
self.on_sme_get_iface_stats(responder)?;
Ok(())
}
wlan_sme::MlmeRequest::GetIfaceHistogramStats(responder) => {
self.on_sme_get_iface_histogram_stats(responder)?;
Ok(())
}
wlan_sme::MlmeRequest::QueryDeviceInfo(responder) => {
self.on_sme_query_device_info(responder).await?;
Ok(())
}
wlan_sme::MlmeRequest::QueryMacSublayerSupport(responder) => {
self.on_sme_query_mac_sublayer_support(responder).await?;
Ok(())
}
wlan_sme::MlmeRequest::QuerySecuritySupport(responder) => {
self.on_sme_query_security_support(responder).await?;
Ok(())
}
wlan_sme::MlmeRequest::QuerySpectrumManagementSupport(responder) => {
self.on_sme_query_spectrum_management_support(responder).await?;
Ok(())
}
wlan_sme::MlmeRequest::ListMinstrelPeers(responder) => {
self.on_sme_list_minstrel_peers(responder)?;
Ok(())
}
wlan_sme::MlmeRequest::GetMinstrelStats(req, responder) => {
self.on_sme_get_minstrel_stats(responder, &req.peer_addr.into())?;
Ok(())
}
wlan_sme::MlmeRequest::GetSignalReport(responder) if self.sta.is_none() => {
responder.respond(Ok(fidl_stats::SignalReport::default()));
Ok(())
}
req if self.sta.is_some() => {
let sta = self.sta.as_mut().unwrap();
sta.bind(&mut self.ctx, &mut self.scanner, &mut self.channel_state)
.handle_mlme_request(req)
.await;
Ok(())
}
unhandled_request => {
if let wlan_sme::MlmeRequest::Reconnect(req) = &unhandled_request {
self.ctx.device.send_mlme_event(fidl_mlme::MlmeEvent::ConnectConf {
resp: fidl_mlme::ConnectConfirm {
peer_sta_address: req.peer_sta_address,
result_code: fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
association_id: 0,
association_ies: vec![],
},
})?;
}
Err(Error::Status(
format!(
"Failed to handle {} MLME request: request is unhandled in the current state. \
Connection context exists: {}, Main channel: {:?}, Scanning: {}.",
unhandled_request.name(),
self.sta.is_some(),
self.channel_state.get_main_channel(),
self.scanner.is_scanning(),
),
zx::Status::BAD_STATE,
).into())
}
}
}
async fn handle_mac_frame_rx(
&mut self,
bytes: &[u8],
rx_info: fidl_softmac::WlanRxInfo,
async_id: trace::Id,
) {
wtrace::duration!("ClientMlme::handle_mac_frame_rx");
// TODO(https://fxbug.dev/42120906): Send the entire frame to scanner.
if let Some(mgmt_frame) = mac::MgmtFrame::parse(bytes, false) {
let bssid = Bssid::from(mgmt_frame.mgmt_hdr.addr3);
match mgmt_frame.try_into_mgmt_body().1 {
Some(mac::MgmtBody::Beacon { bcn_hdr, elements }) => {
wtrace::duration!("MgmtBody::Beacon");
self.scanner.bind(&mut self.ctx).handle_ap_advertisement(
bssid,
bcn_hdr.beacon_interval,
bcn_hdr.capabilities,
elements,
rx_info.clone(),
);
}
Some(mac::MgmtBody::ProbeResp { probe_resp_hdr, elements }) => {
wtrace::duration!("MgmtBody::ProbeResp");
self.scanner.bind(&mut self.ctx).handle_ap_advertisement(
bssid,
probe_resp_hdr.beacon_interval,
probe_resp_hdr.capabilities,
elements,
rx_info.clone(),
)
}
_ => (),
}
}
if let Some(sta) = self.sta.as_mut() {
// Only pass the frame to a BoundClient under the following conditions:
// - ChannelState currently has a main channel.
// - ClientMlme received the frame on the main channel.
match self.channel_state.get_main_channel() {
Some(main_channel) if main_channel.primary == rx_info.channel.primary => {
sta.bind(&mut self.ctx, &mut self.scanner, &mut self.channel_state)
.handle_mac_frame_rx(bytes, rx_info, async_id)
.await;
}
Some(_) => {
wtrace::async_end_wlansoftmac_rx(async_id, "off main channel");
}
// TODO(https://fxbug.dev/42075118): This is only reachable because the Client state machine
// returns to the Joined state and clears the main channel upon deauthentication.
None => {
error!(
"Received MAC frame on channel {:?} while main channel is not set.",
rx_info.channel
);
wtrace::async_end_wlansoftmac_rx(async_id, "main channel not set");
}
}
} else {
wtrace::async_end_wlansoftmac_rx(async_id, "no bound client");
}
}
fn handle_eth_frame_tx(
&mut self,
bytes: &[u8],
async_id: trace::Id,
) -> Result<(), anyhow::Error> {
wtrace::duration!("ClientMlme::handle_eth_frame_tx");
match self.sta.as_mut() {
None => Err(Error::Status(
"Ethernet frame dropped (Client does not exist).".to_string(),
zx::Status::BAD_STATE,
)
.into()),
Some(sta) => sta
.bind(&mut self.ctx, &mut self.scanner, &mut self.channel_state)
.handle_eth_frame_tx(bytes, async_id)
.map_err(From::from),
}
}
async fn handle_scan_complete(&mut self, status: zx::Status, scan_id: u64) {
self.scanner.bind(&mut self.ctx).handle_scan_complete(status, scan_id).await;
}
async fn handle_timeout(&mut self, event: TimedEvent) {
if let Some(sta) = self.sta.as_mut() {
let mut bound = sta.bind(&mut self.ctx, &mut self.scanner, &mut self.channel_state);
bound.sta.state =
Some(bound.sta.state.take().unwrap().on_timed_event(&mut bound, event).await);
}
}
}
impl<D> ClientMlme<D> {
pub fn seq_mgr(&mut self) -> &mut SequenceManager {
&mut self.ctx.seq_mgr
}
fn on_sme_get_iface_stats(
&self,
responder: wlan_sme::responder::Responder<fidl_mlme::GetIfaceStatsResponse>,
) -> Result<(), Error> {
// TODO(https://fxbug.dev/42119762): Implement stats
let resp = fidl_mlme::GetIfaceStatsResponse::ErrorStatus(zx::sys::ZX_ERR_NOT_SUPPORTED);
responder.respond(resp);
Ok(())
}
fn on_sme_get_iface_histogram_stats(
&self,
responder: wlan_sme::responder::Responder<fidl_mlme::GetIfaceHistogramStatsResponse>,
) -> Result<(), Error> {
// TODO(https://fxbug.dev/42119762): Implement stats
let resp =
fidl_mlme::GetIfaceHistogramStatsResponse::ErrorStatus(zx::sys::ZX_ERR_NOT_SUPPORTED);
responder.respond(resp);
Ok(())
}
fn on_sme_list_minstrel_peers(
&self,
responder: wlan_sme::responder::Responder<fidl_mlme::MinstrelListResponse>,
) -> Result<(), Error> {
// TODO(https://fxbug.dev/42159791): Implement once Minstrel is in Rust.
error!("ListMinstrelPeers is not supported.");
let peers = fidl_minstrel::Peers { addrs: vec![] };
let resp = fidl_mlme::MinstrelListResponse { peers };
responder.respond(resp);
Ok(())
}
fn on_sme_get_minstrel_stats(
&self,
responder: wlan_sme::responder::Responder<fidl_mlme::MinstrelStatsResponse>,
_addr: &MacAddr,
) -> Result<(), Error> {
// TODO(https://fxbug.dev/42159791): Implement once Minstrel is in Rust.
error!("GetMinstrelStats is not supported.");
let resp = fidl_mlme::MinstrelStatsResponse { peer: None };
responder.respond(resp);
Ok(())
}
}
impl<D: DeviceOps> ClientMlme<D> {
pub async fn set_main_channel(
&mut self,
channel: fidl_ieee80211::WlanChannel,
) -> Result<(), zx::Status> {
self.channel_state.bind(&mut self.ctx, &mut self.scanner).set_main_channel(channel).await
}
async fn on_sme_scan(&mut self, req: fidl_mlme::ScanRequest) {
let txn_id = req.txn_id;
let _ = self.scanner.bind(&mut self.ctx).on_sme_scan(req).await.map_err(|e| {
error!("Scan failed in MLME: {:?}", e);
let code = match e {
Error::ScanError(scan_error) => scan_error.into(),
_ => fidl_mlme::ScanResultCode::InternalError,
};
self.ctx
.device
.send_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
end: fidl_mlme::ScanEnd { txn_id, code },
})
.unwrap_or_else(|e| error!("error sending MLME ScanEnd: {}", e));
});
}
async fn on_sme_connect(&mut self, req: fidl_mlme::ConnectRequest) -> Result<(), Error> {
// Cancel any ongoing scan so that it doesn't conflict with the connect request
// TODO(b/254290448): Use enable/disable scanning for better guarantees.
if let Err(e) = self.scanner.bind(&mut self.ctx).cancel_ongoing_scan().await {
warn!("Failed to cancel ongoing scan before connect: {}.", e);
}
let bssid = req.selected_bss.bssid;
let result = match req.selected_bss.try_into() {
Ok(bss) => {
let req = ParsedConnectRequest {
selected_bss: bss,
connect_failure_timeout: req.connect_failure_timeout,
auth_type: req.auth_type,
security_ie: req.security_ie,
};
self.join_device(&req.selected_bss).await.map(|cap| (req, cap))
}
Err(e) => Err(Error::Status(
format!("Error parsing BssDescription: {:?}", e),
zx::Status::IO_INVALID,
)),
};
match result {
Ok((req, client_capabilities)) => {
self.sta.replace(Client::new(
req,
device::try_query_iface_mac(&mut self.ctx.device).await?,
client_capabilities,
));
if let Some(sta) = &mut self.sta {
sta.bind(&mut self.ctx, &mut self.scanner, &mut self.channel_state)
.start_connecting()
.await;
}
Ok(())
}
Err(e) => {
error!("Error setting up device for join: {}", e);
// TODO(https://fxbug.dev/42120718): Only one failure code defined in IEEE 802.11-2016 6.3.4.3
// Can we do better?
self.ctx.device.send_mlme_event(fidl_mlme::MlmeEvent::ConnectConf {
resp: fidl_mlme::ConnectConfirm {
peer_sta_address: bssid,
result_code: fidl_ieee80211::StatusCode::JoinFailure,
association_id: 0,
association_ies: vec![],
},
})?;
Err(e)
}
}
}
async fn join_device(&mut self, bss: &BssDescription) -> Result<ClientCapabilities, Error> {
let info = ddk_converter::mlme_device_info_from_softmac(
device::try_query(&mut self.ctx.device).await?,
)?;
let join_caps = derive_join_capabilities(Channel::from(bss.channel), bss.rates(), &info)
.map_err(|e| {
Error::Status(
format!("Failed to derive join capabilities: {:?}", e),
zx::Status::NOT_SUPPORTED,
)
})?;
self.set_main_channel(bss.channel.into())
.await
.map_err(|status| Error::Status(format!("Error setting device channel"), status))?;
let join_bss_request = fidl_driver_common::JoinBssRequest {
bssid: Some(bss.bssid.to_array()),
bss_type: Some(fidl_ieee80211::BssType::Infrastructure),
remote: Some(true),
beacon_period: Some(bss.beacon_period),
..Default::default()
};
// Configure driver to pass frames from this BSS to MLME. Otherwise they will be dropped.
self.ctx
.device
.join_bss(&join_bss_request)
.await
.map(|()| join_caps)
.map_err(|status| Error::Status(format!("Error setting BSS in driver"), status))
}
async fn on_sme_query_device_info(
&mut self,
responder: wlan_sme::responder::Responder<fidl_mlme::DeviceInfo>,
) -> Result<(), Error> {
let info = ddk_converter::mlme_device_info_from_softmac(
device::try_query(&mut self.ctx.device).await?,
)?;
responder.respond(info);
Ok(())
}
async fn on_sme_query_mac_sublayer_support(
&mut self,
responder: wlan_sme::responder::Responder<fidl_common::MacSublayerSupport>,
) -> Result<(), Error> {
let support = device::try_query_mac_sublayer_support(&mut self.ctx.device).await?;
responder.respond(support);
Ok(())
}
async fn on_sme_query_security_support(
&mut self,
responder: wlan_sme::responder::Responder<fidl_common::SecuritySupport>,
) -> Result<(), Error> {
let support = device::try_query_security_support(&mut self.ctx.device).await?;
responder.respond(support);
Ok(())
}
async fn on_sme_query_spectrum_management_support(
&mut self,
responder: wlan_sme::responder::Responder<fidl_common::SpectrumManagementSupport>,
) -> Result<(), Error> {
let support = device::try_query_spectrum_management_support(&mut self.ctx.device).await?;
responder.respond(support);
Ok(())
}
}
pub struct ParsedAssociateResp {
pub association_id: u16,
pub capabilities: CapabilityInfo,
pub rates: Vec<ie::SupportedRate>,
pub ht_cap: Option<ie::HtCapabilities>,
pub vht_cap: Option<ie::VhtCapabilities>,
}
impl ParsedAssociateResp {
pub fn parse<B: SplitByteSlice>(assoc_resp_frame: &mac::AssocRespFrame<B>) -> Self {
let mut parsed = ParsedAssociateResp {
association_id: assoc_resp_frame.assoc_resp_hdr.aid,
capabilities: assoc_resp_frame.assoc_resp_hdr.capabilities,
rates: vec![],
ht_cap: None,
vht_cap: None,
};
for (id, body) in assoc_resp_frame.ies() {
match id {
Id::SUPPORTED_RATES => match ie::parse_supported_rates(body) {
Err(e) => warn!("invalid Supported Rates: {}", e),
Ok(supported_rates) => {
// safe to unwrap because supported rate is 1-byte long thus always aligned
parsed.rates.extend(supported_rates.iter());
}
},
Id::EXTENDED_SUPPORTED_RATES => match ie::parse_extended_supported_rates(body) {
Err(e) => warn!("invalid Extended Supported Rates: {}", e),
Ok(supported_rates) => {
// safe to unwrap because supported rate is 1-byte long thus always aligned
parsed.rates.extend(supported_rates.iter());
}
},
Id::HT_CAPABILITIES => match ie::parse_ht_capabilities(body) {
Err(e) => warn!("invalid HT Capabilities: {}", e),
Ok(ht_cap) => {
parsed.ht_cap = Some(*ht_cap);
}
},
Id::VHT_CAPABILITIES => match ie::parse_vht_capabilities(body) {
Err(e) => warn!("invalid VHT Capabilities: {}", e),
Ok(vht_cap) => {
parsed.vht_cap = Some(*vht_cap);
}
},
// TODO(https://fxbug.dev/42120297): parse vendor ID and include WMM param if exists
_ => {}
}
}
parsed
}
}
#[cfg(test)]
mod tests {
use super::state::DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT;
use super::*;
use crate::MlmeImpl;
use crate::client::test_utils::*;
use crate::device::{FakeDevice, LinkStatus, test_utils};
use crate::test_utils::MockWlanRxInfo;
use assert_matches::assert_matches;
use fidl_fuchsia_wlan_common as fidl_common;
use fidl_fuchsia_wlan_internal as fidl_internal;
use fidl_fuchsia_wlan_mlme as fidl_mlme;
use ieee80211::Ssid;
use wlan_common::channel::Cbw;
use wlan_common::fake_fidl_bss_description;
use wlan_sme::responder::Responder;
#[fuchsia::test(allow_stalls = false)]
async fn spawns_new_sta_on_connect_request_from_sme() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
assert!(me.get_bound_client().is_none(), "MLME should not contain client, yet");
me.on_sme_connect(fidl_mlme::ConnectRequest {
selected_bss: fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap()),
connect_failure_timeout: 100,
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
sae_password: vec![],
wep_key: None,
security_ie: vec![],
owe_public_key: None,
})
.await
.expect("valid ConnectRequest should be handled successfully");
me.get_bound_client().expect("client sta should have been created by now.");
}
#[fuchsia::test(allow_stalls = false)]
async fn fails_to_connect_if_channel_unknown() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
assert!(me.get_bound_client().is_none(), "MLME should not contain client, yet");
let mut req = fidl_mlme::ConnectRequest {
selected_bss: fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap()),
connect_failure_timeout: 100,
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
sae_password: vec![],
wep_key: None,
security_ie: vec![],
owe_public_key: None,
};
req.selected_bss.channel.cbw = fidl_fuchsia_wlan_ieee80211::ChannelBandwidth::unknown();
me.on_sme_connect(req)
.await
.expect_err("ConnectRequest with unknown channel should be rejected");
assert!(me.get_bound_client().is_none());
}
/// Consumes `TimedEvent` values from the `timer::EventStream` held by `mock_objects` and
/// handles each `TimedEvent` value with `mlme`. This function makes the following assertions:
///
/// - The `timer::EventStream` held by `mock_objects` starts with one `StatusCheckTimeout`
/// pending.
/// - For the `beacon_count` specified, `mlme` will consume the current `StatusCheckTimeout`
/// and schedule the next.
/// - `mlme` produces a `fidl_mlme::SignalReportIndication` for each StatusCheckTimeout
/// consumed.
async fn handle_association_status_checks_and_signal_reports(
mock_objects: &mut MockObjects,
mlme: &mut ClientMlme<FakeDevice>,
beacon_count: u32,
) {
for _ in 0..beacon_count / super::state::ASSOCIATION_STATUS_TIMEOUT_BEACON_COUNT {
let (_, timed_event, _) = mock_objects
.time_stream
.try_next()
.unwrap()
.expect("Should have scheduled a timed event");
mlme.handle_timeout(timed_event.event).await;
assert_eq!(mock_objects.fake_device_state.lock().wlan_queue.len(), 0);
mock_objects
.fake_device_state
.lock()
.next_mlme_msg::<fidl_internal::SignalReportIndication>()
.expect("error reading SignalReport.indication");
}
}
#[fuchsia::test(allow_stalls = false)]
async fn test_auto_deauth_uninterrupted_interval() {
let mut mock_objects = MockObjects::new().await;
let mut mlme = mock_objects.make_mlme().await;
mlme.make_client_station();
let mut client = mlme.get_bound_client().expect("client should be present");
client.move_to_associated_state();
// Verify timer is scheduled and move the time to immediately before auto deauth is triggered.
handle_association_status_checks_and_signal_reports(
&mut mock_objects,
&mut mlme,
DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT,
)
.await;
// One more timeout to trigger the auto deauth
let (_, timed_event, _) = mock_objects
.time_stream
.try_next()
.unwrap()
.expect("Should have scheduled a timed event");
// Verify that triggering event at deadline causes deauth
mlme.handle_timeout(timed_event.event).await;
mock_objects
.fake_device_state
.lock()
.next_mlme_msg::<fidl_internal::SignalReportIndication>()
.expect("error reading SignalReport.indication");
assert_eq!(mock_objects.fake_device_state.lock().wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&mock_objects.fake_device_state.lock().wlan_queue[0].0[..], &[
// Mgmt header:
0b1100_00_00, 0b00000000, // FC
0, 0, // Duration
6, 6, 6, 6, 6, 6, // addr1
7, 7, 7, 7, 7, 7, // addr2
6, 6, 6, 6, 6, 6, // addr3
0x10, 0, // Sequence Control
3, 0, // reason code
][..]);
let deauth_ind = mock_objects
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
.expect("error reading DEAUTHENTICATE.indication");
assert_eq!(
deauth_ind,
fidl_mlme::DeauthenticateIndication {
peer_sta_address: BSSID.to_array(),
reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
locally_initiated: true,
}
);
}
#[fuchsia::test(allow_stalls = false)]
async fn test_auto_deauth_received_beacon() {
let mut mock_objects = MockObjects::new().await;
let mut mlme = mock_objects.make_mlme().await;
mlme.make_client_station();
let mut client = mlme.get_bound_client().expect("client should be present");
client.move_to_associated_state();
// Move the countdown to just about to cause auto deauth.
handle_association_status_checks_and_signal_reports(
&mut mock_objects,
&mut mlme,
DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT,
)
.await;
// Receive beacon midway, so lost bss countdown is reset.
// If this beacon is not received, the next timeout will trigger auto deauth.
mlme.handle_mac_frame_rx(
BEACON_FRAME,
fidl_softmac::WlanRxInfo {
rx_flags: fidl_softmac::WlanRxInfoFlags::empty(),
valid_fields: fidl_softmac::WlanRxInfoValid::empty(),
phy: fidl_ieee80211::WlanPhyType::Dsss,
data_rate: 0,
channel: mlme.channel_state.get_main_channel().unwrap(),
mcs: 0,
rssi_dbm: 0,
snr_dbh: 0,
},
0.into(),
)
.await;
// Verify auto deauth is not triggered for the entire duration.
handle_association_status_checks_and_signal_reports(
&mut mock_objects,
&mut mlme,
DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT,
)
.await;
// Verify more timer is scheduled
let (_, timed_event2, _) = mock_objects
.time_stream
.try_next()
.unwrap()
.expect("Should have scheduled a timed event");
// Verify that triggering event at new deadline causes deauth
mlme.handle_timeout(timed_event2.event).await;
mock_objects
.fake_device_state
.lock()
.next_mlme_msg::<fidl_internal::SignalReportIndication>()
.expect("error reading SignalReport.indication");
assert_eq!(mock_objects.fake_device_state.lock().wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&mock_objects.fake_device_state.lock().wlan_queue[0].0[..], &[
// Mgmt header:
0b1100_00_00, 0b00000000, // FC
0, 0, // Duration
6, 6, 6, 6, 6, 6, // addr1
7, 7, 7, 7, 7, 7, // addr2
6, 6, 6, 6, 6, 6, // addr3
0x10, 0, // Sequence Control
3, 0, // reason code
][..]);
let deauth_ind = mock_objects
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
.expect("error reading DEAUTHENTICATE.indication");
assert_eq!(
deauth_ind,
fidl_mlme::DeauthenticateIndication {
peer_sta_address: BSSID.to_array(),
reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
locally_initiated: true,
}
);
}
#[fuchsia::test(allow_stalls = false)]
async fn client_send_scan_end_on_mlme_scan_busy() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
me.make_client_station();
// Issue a second scan before the first finishes
me.on_sme_scan(scan_req()).await;
me.on_sme_scan(fidl_mlme::ScanRequest { txn_id: 1338, ..scan_req() }).await;
let scan_end = m
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::ScanEnd>()
.expect("error reading MLME ScanEnd");
assert_eq!(
scan_end,
fidl_mlme::ScanEnd { txn_id: 1338, code: fidl_mlme::ScanResultCode::NotSupported }
);
}
#[fuchsia::test(allow_stalls = false)]
async fn client_send_scan_end_on_scan_busy() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
me.make_client_station();
// Issue a second scan before the first finishes
me.on_sme_scan(scan_req()).await;
me.on_sme_scan(fidl_mlme::ScanRequest { txn_id: 1338, ..scan_req() }).await;
let scan_end = m
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::ScanEnd>()
.expect("error reading MLME ScanEnd");
assert_eq!(
scan_end,
fidl_mlme::ScanEnd { txn_id: 1338, code: fidl_mlme::ScanResultCode::NotSupported }
);
}
#[fuchsia::test(allow_stalls = false)]
async fn client_send_scan_end_on_mlme_scan_invalid_args() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
me.make_client_station();
me.on_sme_scan(fidl_mlme::ScanRequest {
txn_id: 1337,
scan_type: fidl_mlme::ScanTypes::Passive,
channel_list: vec![], // empty channel list
ssid_list: vec![Ssid::try_from("ssid").unwrap().into()],
probe_delay: 0,
min_channel_time: 100,
max_channel_time: 300,
})
.await;
let scan_end = m
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::ScanEnd>()
.expect("error reading MLME ScanEnd");
assert_eq!(
scan_end,
fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::InvalidArgs }
);
}
#[fuchsia::test(allow_stalls = false)]
async fn client_send_scan_end_on_scan_invalid_args() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
me.make_client_station();
me.on_sme_scan(fidl_mlme::ScanRequest {
txn_id: 1337,
scan_type: fidl_mlme::ScanTypes::Passive,
channel_list: vec![6],
ssid_list: vec![Ssid::try_from("ssid").unwrap().into()],
probe_delay: 0,
min_channel_time: 300, // min > max
max_channel_time: 100,
})
.await;
let scan_end = m
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::ScanEnd>()
.expect("error reading MLME ScanEnd");
assert_eq!(
scan_end,
fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::InvalidArgs }
);
}
#[fuchsia::test(allow_stalls = false)]
async fn client_send_scan_end_on_passive_scan_fails() {
let mut m = MockObjects::new().await;
m.fake_device_state.lock().config.start_passive_scan_fails = true;
let mut me = m.make_mlme().await;
me.make_client_station();
me.on_sme_scan(scan_req()).await;
let scan_end = m
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::ScanEnd>()
.expect("error reading MLME ScanEnd");
assert_eq!(
scan_end,
fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::NotSupported }
);
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_respond_to_query_device_info() {
let mut mock_objects = MockObjects::new().await;
let mut mlme = mock_objects.make_mlme().await;
let (responder, receiver) = Responder::new();
mlme.handle_mlme_request(wlan_sme::MlmeRequest::QueryDeviceInfo(responder))
.await
.expect("Failed to send MlmeRequest::Connect");
assert_eq!(
receiver.await.unwrap(),
fidl_mlme::DeviceInfo {
sta_addr: IFACE_MAC.to_array(),
factory_addr: IFACE_MAC.to_array(),
role: fidl_common::WlanMacRole::Client,
bands: test_utils::fake_mlme_band_caps(),
softmac_hardware_capability: 0,
qos_capable: false,
}
);
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_respond_to_query_mac_sublayer_support() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let (responder, receiver) = Responder::new();
me.handle_mlme_request(wlan_sme::MlmeRequest::QueryMacSublayerSupport(responder))
.await
.expect("Failed to send MlmeRequest::Connect");
let resp = receiver.await.unwrap();
assert_eq!(resp.rate_selection_offload.unwrap().supported, Some(false));
assert_eq!(
resp.data_plane.unwrap().data_plane_type,
Some(fidl_common::DataPlaneType::EthernetDevice)
);
assert_eq!(resp.device.as_ref().unwrap().is_synthetic, Some(true));
assert_eq!(
resp.device.as_ref().unwrap().mac_implementation_type,
Some(fidl_common::MacImplementationType::Softmac)
);
assert_eq!(resp.device.unwrap().tx_status_report_supported, Some(true));
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_respond_to_query_security_support() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let (responder, receiver) = Responder::new();
assert_matches!(
me.handle_mlme_request(wlan_sme::MlmeRequest::QuerySecuritySupport(responder)).await,
Ok(())
);
let resp = receiver.await.unwrap();
assert_eq!(resp.mfp.unwrap().supported, Some(false));
assert_eq!(resp.sae.as_ref().unwrap().driver_handler_supported, Some(false));
assert_eq!(resp.sae.unwrap().sme_handler_supported, Some(false));
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_respond_to_query_spectrum_management_support() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let (responder, receiver) = Responder::new();
me.handle_mlme_request(wlan_sme::MlmeRequest::QuerySpectrumManagementSupport(responder))
.await
.expect("Failed to send MlmeRequest::QuerySpectrumManagementSupport");
assert_eq!(receiver.await.unwrap().dfs.unwrap().supported, Some(true));
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_connect_unprotected_happy_path() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let channel = Channel::new(6, Cbw::Cbw40);
let connect_req = fidl_mlme::ConnectRequest {
selected_bss: fake_fidl_bss_description!(Open,
ssid: Ssid::try_from("ssid").unwrap().into(),
bssid: BSSID.to_array(),
channel: channel.clone(),
),
connect_failure_timeout: 100,
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
sae_password: vec![],
wep_key: None,
security_ie: vec![],
owe_public_key: None,
};
me.handle_mlme_request(wlan_sme::MlmeRequest::Connect(connect_req))
.await
.expect("Failed to send MlmeRequest::Connect");
// Verify an event was queued up in the timer.
assert_matches!(drain_timeouts(&mut m.time_stream).get(&TimedEventClass::Connecting), Some(ids) => {
assert_eq!(ids.len(), 1);
});
// Verify authentication frame was sent to AP.
assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
let (frame, _txflags) = m.fake_device_state.lock().wlan_queue.remove(0);
#[rustfmt::skip]
let expected = vec![
// Mgmt Header:
0b1011_00_00, 0b00000000, // Frame Control
0, 0, // Duration
6, 6, 6, 6, 6, 6, // Addr1
7, 7, 7, 7, 7, 7, // Addr2
6, 6, 6, 6, 6, 6, // Addr3
0x10, 0, // Sequence Control
// Auth Header:
0, 0, // Algorithm Number (Open)
1, 0, // Txn Sequence Number
0, 0, // Status Code
];
assert_eq!(&frame[..], &expected[..]);
// Mock auth frame response from the AP
#[rustfmt::skip]
let auth_resp_success = vec![
// Mgmt Header:
0b1011_00_00, 0b00000000, // Frame Control
0, 0, // Duration
7, 7, 7, 7, 7, 7, // Addr1
7, 7, 7, 7, 7, 7, // Addr2
6, 6, 6, 6, 6, 6, // Addr3
0x10, 0, // Sequence Control
// Auth Header:
0, 0, // Algorithm Number (Open)
2, 0, // Txn Sequence Number
0, 0, // Status Code
];
me.handle_mac_frame_rx(
&auth_resp_success[..],
MockWlanRxInfo::with_channel(channel.into()).into(),
0.into(),
)
.await;
// Verify association request frame was went to AP
assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
let (frame, _txflags) = m.fake_device_state.lock().wlan_queue.remove(0);
#[rustfmt::skip]
let expected = vec![
// Mgmt header:
0, 0, // FC
0, 0, // Duration
6, 6, 6, 6, 6, 6, // addr1
7, 7, 7, 7, 7, 7, // addr2
6, 6, 6, 6, 6, 6, // addr3
0x20, 0, // Sequence Control
// Association Request header:
0x01, 0x00, // capability info
0, 0, // listen interval
// IEs
0, 4, // SSID id and length
0x73, 0x73, 0x69, 0x64, // SSID
1, 8, // supp rates id and length
2, 4, 11, 22, 12, 18, 24, 36, // supp rates
50, 4, // ext supp rates and length
48, 72, 96, 108, // ext supp rates
45, 26, // HT Cap id and length
0x63, 0, 0x17, 0xff, 0, 0, 0, // HT Cap \
0, 0, 0, 0, 0, 0, 0, 0, 1, // HT Cap \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // HT Cap
];
assert_eq!(&frame[..], &expected[..]);
// Mock assoc resp frame from the AP
#[rustfmt::skip]
let assoc_resp_success = vec![
// Mgmt Header:
0b0001_00_00, 0b00000000, // Frame Control
0, 0, // Duration
7, 7, 7, 7, 7, 7, // Addr1 == IFACE_MAC
7, 7, 7, 7, 7, 7, // Addr2
6, 6, 6, 6, 6, 6, // Addr3
0x20, 0, // Sequence Control
// Assoc Resp Header:
0, 0, // Capabilities
0, 0, // Status Code
42, 0, // AID
// IEs
// Basic Rates
0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
// HT Capabilities
0x2d, 0x1a, 0xef, 0x09, // HT capabilities info
0x17, // A-MPDU parameters
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// VHT Capabilities
0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, // VHT capabilities info
0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, // VHT supported MCS set
];
me.handle_mac_frame_rx(
&assoc_resp_success[..],
MockWlanRxInfo::with_channel(channel.into()).into(),
0.into(),
)
.await;
// Verify a successful connect conf is sent
let msg = m
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::ConnectConfirm>()
.expect("expect ConnectConf");
assert_eq!(
msg,
fidl_mlme::ConnectConfirm {
peer_sta_address: BSSID.to_array(),
result_code: fidl_ieee80211::StatusCode::Success,
association_id: 42,
association_ies: vec![
// IEs
// Basic Rates
0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
// HT Capabilities
0x2d, 0x1a, 0xef, 0x09, // HT capabilities info
0x17, // A-MPDU parameters
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, // VHT Capabilities
0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, // VHT capabilities info
0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, // VHT supported MCS set
],
}
);
// Verify eth link is up
assert_eq!(m.fake_device_state.lock().link_status, LinkStatus::UP);
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_connect_protected_happy_path() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let channel = Channel::new(6, Cbw::Cbw40);
let connect_req = fidl_mlme::ConnectRequest {
selected_bss: fake_fidl_bss_description!(Wpa2,
ssid: Ssid::try_from("ssid").unwrap().into(),
bssid: BSSID.to_array(),
channel: channel.clone(),
),
connect_failure_timeout: 100,
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
sae_password: vec![],
wep_key: None,
security_ie: vec![
48, 18, // RSNE header
1, 0, // Version
0x00, 0x0F, 0xAC, 4, // Group Cipher: CCMP-128
1, 0, 0x00, 0x0F, 0xAC, 4, // 1 Pairwise Cipher: CCMP-128
1, 0, 0x00, 0x0F, 0xAC, 2, // 1 AKM: PSK
],
owe_public_key: None,
};
me.handle_mlme_request(wlan_sme::MlmeRequest::Connect(connect_req))
.await
.expect("Failed to send MlmeRequest::Connect");
// Verify an event was queued up in the timer.
assert_matches!(drain_timeouts(&mut m.time_stream).get(&TimedEventClass::Connecting), Some(ids) => {
assert_eq!(ids.len(), 1);
});
// Verify authentication frame was sent to AP.
assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
let (frame, _txflags) = m.fake_device_state.lock().wlan_queue.remove(0);
#[rustfmt::skip]
let expected = vec![
// Mgmt Header:
0b1011_00_00, 0b00000000, // Frame Control
0, 0, // Duration
6, 6, 6, 6, 6, 6, // Addr1
7, 7, 7, 7, 7, 7, // Addr2
6, 6, 6, 6, 6, 6, // Addr3
0x10, 0, // Sequence Control
// Auth Header:
0, 0, // Algorithm Number (Open)
1, 0, // Txn Sequence Number
0, 0, // Status Code
];
assert_eq!(&frame[..], &expected[..]);
// Mock auth frame response from the AP
#[rustfmt::skip]
let auth_resp_success = vec![
// Mgmt Header:
0b1011_00_00, 0b00000000, // Frame Control
0, 0, // Duration
7, 7, 7, 7, 7, 7, // Addr1
7, 7, 7, 7, 7, 7, // Addr2
6, 6, 6, 6, 6, 6, // Addr3
0x10, 0, // Sequence Control
// Auth Header:
0, 0, // Algorithm Number (Open)
2, 0, // Txn Sequence Number
0, 0, // Status Code
];
me.handle_mac_frame_rx(
&auth_resp_success[..],
MockWlanRxInfo::with_channel(channel.into()).into(),
0.into(),
)
.await;
// Verify association request frame was went to AP
assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
let (frame, _txflags) = m.fake_device_state.lock().wlan_queue.remove(0);
#[rustfmt::skip]
let expected = vec![
// Mgmt header:
0, 0, // FC
0, 0, // Duration
6, 6, 6, 6, 6, 6, // addr1
7, 7, 7, 7, 7, 7, // addr2
6, 6, 6, 6, 6, 6, // addr3
0x20, 0, // Sequence Control
// Association Request header:
0x01, 0x00, // capability info
0, 0, // listen interval
// IEs
0, 4, // SSID id and length
0x73, 0x73, 0x69, 0x64, // SSID
1, 8, // supp rates id and length
2, 4, 11, 22, 12, 18, 24, 36, // supp rates
50, 4, // ext supp rates and length
48, 72, 96, 108, // ext supp rates
48, 18, // RSNE id and length
1, 0, // RSN \
0x00, 0x0F, 0xAC, 4, // RSN \
1, 0, 0x00, 0x0F, 0xAC, 4, // RSN \
1, 0, 0x00, 0x0F, 0xAC, 2, // RSN
45, 26, // HT Cap id and length
0x63, 0, 0x17, 0xff, 0, 0, 0, // HT Cap \
0, 0, 0, 0, 0, 0, 0, 0, 1, // HT Cap \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // HT Cap
];
assert_eq!(&frame[..], &expected[..]);
// Mock assoc resp frame from the AP
#[rustfmt::skip]
let assoc_resp_success = vec![
// Mgmt Header:
0b0001_00_00, 0b00000000, // Frame Control
0, 0, // Duration
7, 7, 7, 7, 7, 7, // Addr1 == IFACE_MAC
7, 7, 7, 7, 7, 7, // Addr2
6, 6, 6, 6, 6, 6, // Addr3
0x20, 0, // Sequence Control
// Assoc Resp Header:
0, 0, // Capabilities
0, 0, // Status Code
42, 0, // AID
// IEs
// Basic Rates
0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
// RSN
0x30, 18, 1, 0, // RSN header and version
0x00, 0x0F, 0xAC, 4, // Group Cipher: CCMP-128
1, 0, 0x00, 0x0F, 0xAC, 4, // 1 Pairwise Cipher: CCMP-128
1, 0, 0x00, 0x0F, 0xAC, 2, // 1 AKM: PSK
// HT Capabilities
0x2d, 0x1a, 0xef, 0x09, // HT capabilities info
0x17, // A-MPDU parameters
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Other HT Cap fields
// VHT Capabilities
0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, // VHT capabilities info
0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, // VHT supported MCS set
];
me.handle_mac_frame_rx(
&assoc_resp_success[..],
MockWlanRxInfo::with_channel(channel.into()).into(),
0.into(),
)
.await;
// Verify a successful connect conf is sent
let msg = m
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::ConnectConfirm>()
.expect("expect ConnectConf");
assert_eq!(
msg,
fidl_mlme::ConnectConfirm {
peer_sta_address: BSSID.to_array(),
result_code: fidl_ieee80211::StatusCode::Success,
association_id: 42,
association_ies: vec![
// IEs
// Basic Rates
0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, // RSN
0x30, 18, 1, 0, // RSN header and version
0x00, 0x0F, 0xAC, 4, // Group Cipher: CCMP-128
1, 0, 0x00, 0x0F, 0xAC, 4, // 1 Pairwise Cipher: CCMP-128
1, 0, 0x00, 0x0F, 0xAC, 2, // 1 AKM: PSK
// HT Capabilities
0x2d, 0x1a, 0xef, 0x09, // HT capabilities info
0x17, // A-MPDU parameters
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, // Other HT Cap fields
// VHT Capabilities
0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, // VHT capabilities info
0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, // VHT supported MCS set
],
}
);
// Verify that link is still down
assert_eq!(m.fake_device_state.lock().link_status, LinkStatus::DOWN);
// Send a request to open controlled port
me.handle_mlme_request(wlan_sme::MlmeRequest::SetCtrlPort(
fidl_mlme::SetControlledPortRequest {
peer_sta_address: BSSID.to_array(),
state: fidl_mlme::ControlledPortState::Open,
},
))
.await
.expect("expect sending msg to succeed");
// Verify that link is now up
assert_eq!(m.fake_device_state.lock().link_status, LinkStatus::UP);
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_connect_vht() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let channel = Channel::new(36, Cbw::Cbw40);
let connect_req = fidl_mlme::ConnectRequest {
selected_bss: fake_fidl_bss_description!(Open,
ssid: Ssid::try_from("ssid").unwrap().into(),
bssid: BSSID.to_array(),
channel: channel.clone(),
),
connect_failure_timeout: 100,
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
sae_password: vec![],
wep_key: None,
security_ie: vec![],
owe_public_key: None,
};
me.handle_mlme_request(wlan_sme::MlmeRequest::Connect(connect_req))
.await
.expect("Failed to send MlmeRequest::Connect.");
// Verify an event was queued up in the timer.
assert_matches!(drain_timeouts(&mut m.time_stream).get(&TimedEventClass::Connecting), Some(ids) => {
assert_eq!(ids.len(), 1);
});
// Auth frame
assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
let (_frame, _txflags) = m.fake_device_state.lock().wlan_queue.remove(0);
// Mock auth frame response from the AP
#[rustfmt::skip]
let auth_resp_success = vec![
// Mgmt Header:
0b1011_00_00, 0b00000000, // Frame Control
0, 0, // Duration
7, 7, 7, 7, 7, 7, // Addr1
7, 7, 7, 7, 7, 7, // Addr2
6, 6, 6, 6, 6, 6, // Addr3
0x10, 0, // Sequence Control
// Auth Header:
0, 0, // Algorithm Number (Open)
2, 0, // Txn Sequence Number
0, 0, // Status Code
];
me.handle_mac_frame_rx(
&auth_resp_success[..],
MockWlanRxInfo::with_channel(channel.into()).into(),
0.into(),
)
.await;
// Verify association request frame was went to AP
assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
let (frame, _txflags) = m.fake_device_state.lock().wlan_queue.remove(0);
#[rustfmt::skip]
let expected = vec![
// Mgmt header:
0, 0, // FC
0, 0, // Duration
6, 6, 6, 6, 6, 6, // addr1
7, 7, 7, 7, 7, 7, // addr2
6, 6, 6, 6, 6, 6, // addr3
0x20, 0, // Sequence Control
// Association Request header:
0x01, 0x00, // capability info
0, 0, // listen interval
// IEs
0, 4, // SSID id and length
0x73, 0x73, 0x69, 0x64, // SSID
1, 6, // supp rates id and length
2, 4, 11, 22, 48, 96, // supp rates
45, 26, // HT Cap id and length
0x63, 0, 0x17, 0xff, 0, 0, 0, // HT Cap \
0, 0, 0, 0, 0, 0, 0, 0, 1, // HT Cap \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // HT Cap
191, 12, // VHT Cap id and length
50, 80, 128, 15, 254, 255, 0, 0, 254, 255, 0, 0, // VHT Cap
];
assert_eq!(&frame[..], &expected[..]);
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_connect_timeout() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let connect_req = fidl_mlme::ConnectRequest {
selected_bss: fake_fidl_bss_description!(Open, bssid: BSSID.to_array()),
connect_failure_timeout: 100,
auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
sae_password: vec![],
wep_key: None,
security_ie: vec![],
owe_public_key: None,
};
me.handle_mlme_request(wlan_sme::MlmeRequest::Connect(connect_req))
.await
.expect("Failed to send MlmeRequest::Connect.");
// Verify an event was queued up in the timer.
let (event, _id) = assert_matches!(drain_timeouts(&mut m.time_stream).get(&TimedEventClass::Connecting), Some(events) => {
assert_eq!(events.len(), 1);
events[0].clone()
});
// Quick check that a frame was sent (this is authentication frame).
assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
let (_frame, _txflags) = m.fake_device_state.lock().wlan_queue.remove(0);
// Send connect timeout
me.handle_timeout(event).await;
// Verify a connect confirm message was sent
let msg = m
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::ConnectConfirm>()
.expect("expect msg");
assert_eq!(
msg,
fidl_mlme::ConnectConfirm {
peer_sta_address: BSSID.to_array(),
result_code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
association_id: 0,
association_ies: vec![],
},
);
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_reconnect_no_sta() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let reconnect_req = fidl_mlme::ReconnectRequest { peer_sta_address: [1, 2, 3, 4, 5, 6] };
let result = me.handle_mlme_request(wlan_sme::MlmeRequest::Reconnect(reconnect_req)).await;
let err = result.unwrap_err();
let mlme_err = err.downcast_ref::<Error>().expect("expected Mlme Error");
assert_matches!(mlme_err, Error::Status(_, zx::Status::BAD_STATE));
// Verify a connect confirm message was sent
let msg = m
.fake_device_state
.lock()
.next_mlme_msg::<fidl_mlme::ConnectConfirm>()
.expect("expect msg");
assert_eq!(
msg,
fidl_mlme::ConnectConfirm {
peer_sta_address: [1, 2, 3, 4, 5, 6],
result_code: fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
association_id: 0,
association_ies: vec![],
},
);
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_respond_to_get_iface_stats_with_error_status() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let (responder, receiver) = Responder::new();
me.handle_mlme_request(wlan_sme::MlmeRequest::GetIfaceStats(responder))
.await
.expect("Failed to send MlmeRequest::GetIfaceStats.");
assert_eq!(
receiver.await,
Ok(fidl_mlme::GetIfaceStatsResponse::ErrorStatus(zx::sys::ZX_ERR_NOT_SUPPORTED))
);
}
#[fuchsia::test(allow_stalls = false)]
async fn mlme_respond_to_get_iface_histogram_stats_with_error_status() {
let mut m = MockObjects::new().await;
let mut me = m.make_mlme().await;
let (responder, receiver) = Responder::new();
me.handle_mlme_request(wlan_sme::MlmeRequest::GetIfaceHistogramStats(responder))
.await
.expect("Failed to send MlmeRequest::GetIfaceHistogramStats");
assert_eq!(
receiver.await,
Ok(fidl_mlme::GetIfaceHistogramStatsResponse::ErrorStatus(
zx::sys::ZX_ERR_NOT_SUPPORTED
))
);
}
#[test]
fn drop_mgmt_frame_wrong_bssid() {
let frame = [
// Mgmt header 1101 for action frame
0b11010000, 0b00000000, // frame control
0, 0, // duration
7, 7, 7, 7, 7, 7, // addr1
6, 6, 6, 6, 6, 6, // addr2
0, 0, 0, 0, 0, 0, // addr3 (bssid should have been [6; 6])
0x10, 0, // sequence control
];
let frame = mac::MacFrame::parse(&frame[..], false).unwrap();
assert_eq!(false, make_client_station().should_handle_frame(&frame));
}
#[test]
fn drop_mgmt_frame_wrong_dst_addr() {
let frame = [
// Mgmt header 1101 for action frame
0b11010000, 0b00000000, // frame control
0, 0, // duration
0, 0, 0, 0, 0, 0, // addr1 (dst_addr should have been [7; 6])
6, 6, 6, 6, 6, 6, // addr2
6, 6, 6, 6, 6, 6, // addr3
0x10, 0, // sequence control
];
let frame = mac::MacFrame::parse(&frame[..], false).unwrap();
assert_eq!(false, make_client_station().should_handle_frame(&frame));
}
#[test]
fn mgmt_frame_ok_broadcast() {
let frame = [
// Mgmt header 1101 for action frame
0b11010000, 0b00000000, // frame control
0, 0, // duration
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // addr1 (dst_addr is broadcast)
6, 6, 6, 6, 6, 6, // addr2
6, 6, 6, 6, 6, 6, // addr3
0x10, 0, // sequence control
];
let frame = mac::MacFrame::parse(&frame[..], false).unwrap();
assert_eq!(true, make_client_station().should_handle_frame(&frame));
}
#[test]
fn mgmt_frame_ok_client_addr() {
let frame = [
// Mgmt header 1101 for action frame
0b11010000, 0b00000000, // frame control
0, 0, // duration
7, 7, 7, 7, 7, 7, // addr1 (dst_addr should have been [7; 6])
6, 6, 6, 6, 6, 6, // addr2
6, 6, 6, 6, 6, 6, // addr3
0x10, 0, // sequence control
];
let frame = mac::MacFrame::parse(&frame[..], false).unwrap();
assert_eq!(true, make_client_station().should_handle_frame(&frame));
}
#[test]
fn drop_data_frame_wrong_bssid() {
let frame = [
// Data header 0100
0b01001000,
0b00000010, // frame control. right 2 bits of octet 2: from_ds(1), to_ds(0)
0, 0, // duration
7, 7, 7, 7, 7, 7, // addr1 (dst_addr)
0, 0, 0, 0, 0, 0, // addr2 (bssid should have been [6; 6])
6, 6, 6, 6, 6, 6, // addr3
0x10, 0, // sequence control
];
let frame = mac::MacFrame::parse(&frame[..], false).unwrap();
assert_eq!(false, make_client_station().should_handle_frame(&frame));
}
#[test]
fn drop_data_frame_wrong_dst_addr() {
let frame = [
// Data header 0100
0b01001000,
0b00000010, // frame control. right 2 bits of octet 2: from_ds(1), to_ds(0)
0, 0, // duration
0, 0, 0, 0, 0, 0, // addr1 (dst_addr should have been [7; 6])
6, 6, 6, 6, 6, 6, // addr2 (bssid)
6, 6, 6, 6, 6, 6, // addr3
0x10, 0, // sequence control
];
let frame = mac::MacFrame::parse(&frame[..], false).unwrap();
assert_eq!(false, make_client_station().should_handle_frame(&frame));
}
#[test]
fn data_frame_ok_broadcast() {
let frame = [
// Data header 0100
0b01001000,
0b00000010, // frame control. right 2 bits of octet 2: from_ds(1), to_ds(0)
0, 0, // duration
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // addr1 (dst_addr is broadcast)
6, 6, 6, 6, 6, 6, // addr2 (bssid)
6, 6, 6, 6, 6, 6, // addr3
0x10, 0, // sequence control
];
let frame = mac::MacFrame::parse(&frame[..], false).unwrap();
assert_eq!(true, make_client_station().should_handle_frame(&frame));
}
#[test]
fn data_frame_ok_client_addr() {
let frame = [
// Data header 0100
0b01001000,
0b00000010, // frame control. right 2 bits of octet 2: from_ds(1), to_ds(0)
0, 0, // duration
7, 7, 7, 7, 7, 7, // addr1 (dst_addr)
6, 6, 6, 6, 6, 6, // addr2 (bssid)
6, 6, 6, 6, 6, 6, // addr3
0x10, 0, // sequence control
];
let frame = mac::MacFrame::parse(&frame[..], false).unwrap();
assert_eq!(true, make_client_station().should_handle_frame(&frame));
}
}