blob: 1decfe96ee43c0241b29f0b3a02bde35f824788e [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
mod channel_listener;
mod channel_scheduler;
mod convert_beacon;
mod lost_bss;
mod scanner;
mod state;
mod stats;
use {
crate::{
akm_algorithm,
block_ack::BlockAckTx,
buffer::{BufferProvider, OutBuf},
device::{Device, TxFlags},
disconnect::LocallyInitiated,
error::Error,
logger,
timer::*,
},
anyhow::{self, format_err},
banjo_ddk_protocol_wlan_info as banjo_wlan_info, banjo_ddk_protocol_wlan_mac as banjo_wlan_mac,
channel_listener::{ChannelListenerSource, ChannelListenerState},
channel_scheduler::ChannelScheduler,
fidl_fuchsia_wlan_mlme as fidl_mlme, fuchsia_zircon as zx,
log::{error, warn},
scanner::Scanner,
state::States,
static_assertions::assert_eq_size,
std::convert::TryInto,
wlan_common::{
appendable::Appendable,
bss::BssDescriptionExt,
buffer_writer::BufferWriter,
data_writer,
ie::{self, parse_ht_capabilities, parse_vht_capabilities, rsn::rsne, Id, Reader},
mac::{self, Aid, Bssid, MacAddr, PowerState},
mgmt_writer,
sequence::SequenceManager,
time::TimeUnit,
wmm,
},
wlan_frame_writer::{write_frame, write_frame_with_dynamic_buf, write_frame_with_fixed_buf},
zerocopy::{AsBytes, ByteSlice},
};
pub use scanner::ScanError;
/// Maximum size of EAPOL frames forwarded to SME.
/// TODO(fxbug.dev/34845): Evaluate whether EAPOL size restriction is needed.
const MAX_EAPOL_FRAME_LEN: usize = 255;
#[derive(Debug, PartialEq)]
pub enum TimedEvent {
/// Authentication timed out. AP did not complete authentication in time.
Authenticating,
/// Association timed out. AP did not complete association in time.
Associating,
ChannelScheduler,
ScannerProbeDelay(banjo_wlan_info::WlanChannel),
/// Association status update includes checking for auto deauthentication due to beacon loss
/// and report signal strength
AssociationStatusCheck,
}
/// ClientConfig affects time duration used for different timeouts.
/// Originally added to more easily control behavior in tests.
#[repr(C)]
#[derive(Debug, Clone)]
pub struct ClientConfig {
ensure_on_channel_time: zx::sys::zx_duration_t,
}
pub struct Context {
config: ClientConfig,
device: Device,
buf_provider: BufferProvider,
timer: Timer<TimedEvent>,
seq_mgr: SequenceManager,
}
pub struct ClientMlme {
sta: Option<Client>,
ctx: Context,
scanner: Scanner,
chan_sched: ChannelScheduler,
channel_state: ChannelListenerState,
}
impl ClientMlme {
pub fn new(
config: ClientConfig,
device: Device,
buf_provider: BufferProvider,
scheduler: Scheduler,
) -> Self {
// TODO(fxbug.dev/41417): Remove this once devmgr installs a Rust logger.
logger::install();
let iface_mac = device.wlan_info().ifc_info.mac_addr;
let timer = Timer::<TimedEvent>::new(scheduler);
Self {
sta: None,
ctx: Context { config, device, buf_provider, timer, seq_mgr: SequenceManager::new() },
scanner: Scanner::new(iface_mac),
chan_sched: ChannelScheduler::new(),
channel_state: Default::default(),
}
}
pub fn seq_mgr(&mut self) -> &mut SequenceManager {
&mut self.ctx.seq_mgr
}
pub fn set_main_channel(
&mut self,
channel: banjo_wlan_info::WlanChannel,
) -> Result<(), zx::Status> {
self.ctx.device.set_channel(channel)?;
self.channel_state.main_channel = Some(channel);
Ok(())
}
pub fn on_channel(&self) -> bool {
let channel = self.ctx.device.channel();
self.channel_state.main_channel.map(|c| c == channel).unwrap_or(false)
}
pub fn on_mac_frame(&mut self, frame: &[u8], rx_info: Option<banjo_wlan_mac::WlanRxInfo>) {
// TODO(fxbug.dev/44487): Send the entire frame to scanner.
match mac::MacFrame::parse(frame, false) {
Some(mac::MacFrame::Mgmt { mgmt_hdr, body, .. }) => {
let bssid = Bssid(mgmt_hdr.addr3);
let frame_ctrl = mgmt_hdr.frame_ctrl;
match mac::MgmtBody::parse(frame_ctrl.mgmt_subtype(), body) {
Some(mac::MgmtBody::Beacon { bcn_hdr, elements }) => {
self.scanner.bind(&mut self.ctx).handle_beacon_or_probe_response(
bssid,
bcn_hdr.timestamp,
bcn_hdr.beacon_interval,
bcn_hdr.capabilities,
elements,
rx_info,
);
}
Some(mac::MgmtBody::ProbeResp { probe_resp_hdr, elements }) => {
self.scanner.bind(&mut self.ctx).handle_beacon_or_probe_response(
bssid,
probe_resp_hdr.timestamp,
probe_resp_hdr.beacon_interval,
probe_resp_hdr.capabilities,
elements,
rx_info,
)
}
_ => (),
}
}
_ => (),
}
if let Some(sta) = self.sta.as_mut() {
sta.bind(
&mut self.ctx,
&mut self.scanner,
&mut self.chan_sched,
&mut self.channel_state,
)
.on_mac_frame(frame, rx_info)
}
}
#[allow(deprecated)] // Allow until main message loop is in Rust.
pub fn handle_mlme_msg(&mut self, msg: fidl_mlme::MlmeRequestMessage) -> Result<(), Error> {
use fidl_mlme::MlmeRequestMessage as MlmeMsg;
match msg {
// Handle non station specific MLME messages first (Join, Scan, etc.)
MlmeMsg::StartScan { req } => Ok(self.on_sme_scan(req)),
MlmeMsg::JoinReq { req } => self.on_sme_join(req),
MlmeMsg::StatsQueryReq {} => self.on_sme_stats_query(),
other_message => match &mut self.sta {
None => Err(Error::Status(format!("No client sta."), zx::Status::BAD_STATE)),
Some(sta) => Ok(sta
.bind(
&mut self.ctx,
&mut self.scanner,
&mut self.chan_sched,
&mut self.channel_state,
)
.handle_mlme_msg(other_message)),
},
}
}
fn on_sme_scan(&mut self, req: fidl_mlme::ScanRequest) {
let channel_state = &mut self.channel_state;
let sta = self.sta.as_mut();
// No need to handle result because scanner already send ScanEnd if it errors out
let _result = self.scanner.bind(&mut self.ctx).on_sme_scan(
req,
|ctx, scanner| channel_state.bind(ctx, scanner, sta),
&mut self.chan_sched,
);
}
pub fn handle_hw_scan_complete(&mut self, status: banjo_wlan_mac::WlanHwScan) {
self.scanner.bind(&mut self.ctx).handle_hw_scan_complete(status);
}
fn on_sme_join(&mut self, req: fidl_mlme::JoinRequest) -> Result<(), Error> {
let bss = req.selected_bss;
match self.join_device(&bss) {
Ok(()) => {
self.sta.replace(Client::new(
bss.ssid.clone(),
Bssid(bss.bssid),
self.ctx.device.wlan_info().ifc_info.mac_addr,
bss.beacon_period,
bss.rsne.is_some()
// TODO (fxb/61020): Add detection of WPA1 in softmac for testing
// purposes only. In particular, connect-to-wpa1-network relies
// on this half of the OR statement.
|| bss.find_wpa_ie().is_some(),
));
self.ctx.device.access_sme_sender(|sender| {
sender.send_join_conf(&mut fidl_mlme::JoinConfirm {
result_code: fidl_mlme::JoinResultCodes::Success,
})
})
}
Err(e) => {
error!("Error setting up device for join: {}", e);
self.ctx.device.access_sme_sender(|sender| {
sender.send_join_conf(&mut fidl_mlme::JoinConfirm {
// TODO(fxbug.dev/44317): Only one failure code defined in IEEE 802.11-2016 6.3.4.3
// Can we do better?
result_code: fidl_mlme::JoinResultCodes::JoinFailureTimeout,
})
})?;
Err(e)
}
}
}
fn join_device(&mut self, bss: &fidl_mlme::BssDescription) -> Result<(), Error> {
let channel = crate::ddk_converter::ddk_channel_from_fidl(bss.chan);
self.set_main_channel(channel)
.map_err(|status| Error::Status(format!("Error setting device channel"), status))?;
let bss_config = banjo_wlan_info::WlanBssConfig {
bssid: bss.bssid.clone(),
bss_type: banjo_wlan_info::WlanBssType::INFRASTRUCTURE,
remote: true,
};
// Configure driver to pass frames from this BSS to MLME. Otherwise they will be dropped.
self.ctx
.device
.configure_bss(bss_config)
.map_err(|status| Error::Status(format!("Error setting BSS in driver"), status))
}
fn on_sme_stats_query(&self) -> Result<(), Error> {
// TODO(fxbug.dev/43456): Implement stats
let mut resp = stats::empty_stats_query_response();
self.ctx.device.access_sme_sender(|sender| sender.send_stats_query_resp(&mut resp))
}
pub fn on_eth_frame<B: ByteSlice>(&mut self, bytes: B) -> Result<(), Error> {
match self.sta.as_mut() {
None => Err(Error::Status(
format!("Ethernet frame dropped (Client does not exist)."),
zx::Status::BAD_STATE,
)),
Some(sta) => sta
.bind(
&mut self.ctx,
&mut self.scanner,
&mut self.chan_sched,
&mut self.channel_state,
)
.on_eth_frame(bytes),
}
}
/// Called when a previously scheduled `TimedEvent` fired.
/// Return true if auto-deauth has triggered. Return false otherwise.
pub fn handle_timed_event(&mut self, event_id: EventId) {
let event = match self.ctx.timer.triggered(&event_id) {
Some(event) => event,
None => {
error!(
"event for given ID {:?} already consumed;\
this should NOT happen - ignoring event",
event_id
);
return;
}
};
match event {
TimedEvent::ChannelScheduler => {
let mut listener =
self.channel_state.bind(&mut self.ctx, &mut self.scanner, self.sta.as_mut());
// We are not scheduling new event, so it doesn't matter what source we bind here
let mut chan_sched =
self.chan_sched.bind(&mut listener, ChannelListenerSource::Others);
chan_sched.handle_timeout();
}
TimedEvent::ScannerProbeDelay(channel) => {
self.scanner.bind(&mut self.ctx).handle_probe_delay_timeout(channel);
}
other_event => {
if let Some(sta) = self.sta.as_mut() {
return sta
.bind(
&mut self.ctx,
&mut self.scanner,
&mut self.chan_sched,
&mut self.channel_state,
)
.handle_timed_event(other_event, event_id);
}
}
}
}
}
/// A STA running in Client mode.
/// The Client STA is in its early development process and does not yet manage its internal state
/// machine or track negotiated capabilities.
pub struct Client {
state: Option<States>,
pub ssid: Vec<u8>,
pub bssid: Bssid,
pub iface_mac: MacAddr,
beacon_period: u16,
pub eapol_required: bool,
}
impl Client {
pub fn new(
ssid: Vec<u8>,
bssid: Bssid,
iface_mac: MacAddr,
beacon_period: u16,
eapol_required: bool,
) -> Self {
Self {
state: Some(States::new_initial()),
ssid: ssid.to_vec(),
bssid,
iface_mac,
beacon_period,
eapol_required,
}
}
pub fn bind<'a>(
&'a mut self,
ctx: &'a mut Context,
scanner: &'a mut Scanner,
chan_sched: &'a mut ChannelScheduler,
channel_state: &'a mut ChannelListenerState,
) -> BoundClient<'a> {
BoundClient { sta: self, ctx, scanner, chan_sched, channel_state }
}
pub fn pre_switch_off_channel(&mut self, ctx: &mut Context) {
// Safe to unwrap() because state is never None.
let mut state = self.state.take().unwrap();
state.pre_switch_off_channel(self, ctx);
self.state.replace(state);
}
pub fn handle_back_on_channel(&mut self, ctx: &mut Context) {
// Safe to unwrap() because state is never None.
let mut state = self.state.take().unwrap();
state.handle_back_on_channel(self, ctx);
self.state.replace(state);
}
/// Sends a power management data frame to the associated AP indicating that the client has
/// entered the given power state. See `PowerState`.
///
/// # Errors
///
/// Returns an error if the data frame cannot be sent to the AP.
fn send_power_state_frame(
&mut self,
ctx: &mut Context,
state: PowerState,
) -> Result<(), Error> {
let (buf, bytes_written) = write_frame!(&mut ctx.buf_provider, {
headers: {
mac::FixedDataHdrFields: &mac::FixedDataHdrFields {
frame_ctrl: mac::FrameControl(0)
.with_frame_type(mac::FrameType::DATA)
.with_data_subtype(mac::DataSubtype(0).with_null(true))
.with_power_mgmt(state)
.with_to_ds(true),
duration: 0,
addr1: self.bssid.0,
addr2: self.iface_mac,
addr3: self.bssid.0,
seq_ctrl: mac::SequenceControl(0)
.with_seq_num(ctx.seq_mgr.next_sns1(&self.bssid.0) as u16)
},
},
})?;
let out_buf = OutBuf::from(buf, bytes_written);
ctx.device
.send_wlan_frame(out_buf, TxFlags::NONE)
.map_err(|error| Error::Status(format!("error sending power management frame"), error))
}
/// Only management and data frames should be processed. Furthermore, the source address should
/// be the BSSID the client associated to and the receiver address should either be non-unicast
/// or the client's MAC address.
fn should_handle_frame<B: ByteSlice>(&self, mac_frame: &mac::MacFrame<B>) -> bool {
// Technically, |transmitter_addr| and |receiver_addr| would be more accurate but using src
// src and dst to be consistent with |data_dst_addr()|.
let (src_addr, dst_addr) = match mac_frame {
mac::MacFrame::Mgmt { mgmt_hdr, .. } => (Some(mgmt_hdr.addr3), mgmt_hdr.addr1),
mac::MacFrame::Data { fixed_fields, .. } => {
(mac::data_bssid(&fixed_fields), mac::data_dst_addr(&fixed_fields))
}
// Control frames are not supported. Drop them.
_ => return false,
};
src_addr.map_or(false, |src_addr| src_addr == self.bssid.0)
&& (!is_unicast(dst_addr) || dst_addr == self.iface_mac)
}
}
/// A MAC address is a unicast address if the least significant bit of the first octet is 0.
/// See "individual/group bit" in
/// https://standards.ieee.org/content/dam/ieee-standards/standards/web/documents/tutorials/macgrp.pdf
fn is_unicast(addr: MacAddr) -> bool {
addr[0] & 1 == 0
}
pub struct BoundClient<'a> {
sta: &'a mut Client,
// TODO(fxbug.dev/44079): pull everything out of Context and plop them here.
ctx: &'a mut Context,
scanner: &'a mut Scanner,
chan_sched: &'a mut ChannelScheduler,
channel_state: &'a mut ChannelListenerState,
}
impl<'a> akm_algorithm::AkmAction for BoundClient<'a> {
type EventId = EventId;
fn send_auth_frame(
&mut self,
auth_type: mac::AuthAlgorithmNumber,
seq_num: u16,
result_code: mac::StatusCode,
auth_content: &[u8],
) -> Result<(), anyhow::Error> {
self.send_auth_frame(auth_type, seq_num, result_code, auth_content).map_err(|e| e.into())
}
fn forward_sme_sae_rx(
&mut self,
seq_num: u16,
result_code: fidl_mlme::AuthenticateResultCodes,
sae_fields: Vec<u8>,
) {
self.forward_sae_frame_rx(seq_num, result_code, sae_fields)
}
fn forward_sae_handshake_ind(&mut self) {
self.forward_sae_handshake_ind()
}
fn schedule_auth_timeout(&mut self, duration: TimeUnit) -> EventId {
let deadline = self.ctx.timer.now() + duration.into();
self.ctx.timer.schedule_event(deadline, TimedEvent::Authenticating)
}
fn cancel_auth_timeout(&mut self, id: EventId) {
self.ctx.timer.cancel_event(id)
}
}
impl<'a> BoundClient<'a> {
/// Delivers a single MSDU to the STA's underlying device. The MSDU is delivered as an
/// Ethernet II frame.
/// Returns Err(_) if writing or delivering the Ethernet II frame failed.
fn deliver_msdu<B: ByteSlice>(&mut self, msdu: mac::Msdu<B>) -> Result<(), Error> {
let mac::Msdu { dst_addr, src_addr, llc_frame } = msdu;
let (buf, bytes_written) = write_frame_with_fixed_buf!([0u8; mac::MAX_ETH_FRAME_LEN], {
headers: {
mac::EthernetIIHdr: &mac::EthernetIIHdr {
da: dst_addr,
sa: src_addr,
ether_type: llc_frame.hdr.protocol_id,
},
},
payload: &llc_frame.body,
})?;
let (written, _remaining) = buf.split_at(bytes_written);
self.ctx
.device
.deliver_eth_frame(written)
.map_err(|s| Error::Status(format!("could not deliver Ethernet II frame"), s))
}
pub fn send_auth_frame(
&mut self,
auth_type: mac::AuthAlgorithmNumber,
seq_num: u16,
result_code: mac::StatusCode,
auth_content: &[u8],
) -> Result<(), Error> {
let (buf, bytes_written) = write_frame!(&mut self.ctx.buf_provider, {
headers: {
mac::MgmtHdr: &mgmt_writer::mgmt_hdr_to_ap(
mac::FrameControl(0)
.with_frame_type(mac::FrameType::MGMT)
.with_mgmt_subtype(mac::MgmtSubtype::AUTH),
self.sta.bssid,
self.sta.iface_mac,
mac::SequenceControl(0)
.with_seq_num(self.ctx.seq_mgr.next_sns1(&self.sta.bssid.0) as u16)
),
mac::AuthHdr: &mac::AuthHdr {
auth_alg_num: auth_type,
auth_txn_seq_num: seq_num,
status_code: result_code,
},
},
body: auth_content,
})?;
let out_buf = OutBuf::from(buf, bytes_written);
self.send_mgmt_or_ctrl_frame(out_buf)
.map_err(|s| Error::Status(format!("error sending open auth frame"), s))
}
/// Sends an authentication frame using Open System authentication.
pub fn send_open_auth_frame(&mut self) -> Result<(), Error> {
self.send_auth_frame(mac::AuthAlgorithmNumber::OPEN, 1, mac::StatusCode::SUCCESS, &[])
}
/// Sends an association request frame based on device capability.
// TODO(fxbug.dev/39148): Use an IE set instead of individual IEs.
pub fn send_assoc_req_frame(
&mut self,
cap_info: u16,
rates: &[u8],
rsne: &[u8],
ht_cap: &[u8],
vht_cap: &[u8],
) -> Result<(), Error> {
let ssid = self.sta.ssid.clone();
let (buf, bytes_written) = write_frame!(&mut self.ctx.buf_provider, {
headers: {
mac::MgmtHdr: &mgmt_writer::mgmt_hdr_to_ap(
mac::FrameControl(0)
.with_frame_type(mac::FrameType::MGMT)
.with_mgmt_subtype(mac::MgmtSubtype::ASSOC_REQ),
self.sta.bssid,
self.sta.iface_mac,
mac::SequenceControl(0)
.with_seq_num(self.ctx.seq_mgr.next_sns1(&self.sta.bssid.0) as u16)
),
mac::AssocReqHdr: &mac::AssocReqHdr {
capabilities: mac::CapabilityInfo(cap_info),
listen_interval: 0,
},
},
ies: {
ssid: ssid,
supported_rates: rates,
extended_supported_rates: {/* continue rates */},
rsne?: if !rsne.is_empty() {
rsne::from_bytes(rsne)
.map_err(|e| format_err!("error parsing rsne {:?} : {:?}", rsne, e))?
.1
},
ht_cap?: if !ht_cap.is_empty() { *parse_ht_capabilities(ht_cap)? },
vht_cap?: if !vht_cap.is_empty() { *parse_vht_capabilities(vht_cap)? },
},
})?;
let out_buf = OutBuf::from(buf, bytes_written);
self.send_mgmt_or_ctrl_frame(out_buf)
.map_err(|s| Error::Status(format!("error sending assoc req frame"), s))
}
/// Sends a "keep alive" response to the BSS. A keep alive response is a NULL data frame sent as
/// a response to the AP transmitting NULL data frames to the client.
// Note: This function was introduced to meet C++ MLME feature parity. However, there needs to
// be some investigation, whether these "keep alive" frames are the right way of keeping a
// client associated to legacy APs.
fn send_keep_alive_resp_frame(&mut self) -> Result<(), Error> {
let (buf, bytes_written) = write_frame!(&mut self.ctx.buf_provider, {
headers: {
mac::FixedDataHdrFields: &data_writer::data_hdr_client_to_ap(
mac::FrameControl(0)
.with_frame_type(mac::FrameType::DATA)
.with_data_subtype(mac::DataSubtype(0).with_null(true)),
self.sta.bssid,
self.sta.iface_mac,
mac::SequenceControl(0)
.with_seq_num(self.ctx.seq_mgr.next_sns1(&self.sta.bssid.0) as u16)
),
},
})?;
let out_buf = OutBuf::from(buf, bytes_written);
self.ctx
.device
.send_wlan_frame(out_buf, TxFlags::NONE)
.map_err(|s| Error::Status(format!("error sending keep alive frame"), s))
}
pub fn send_deauth_frame(&mut self, reason_code: mac::ReasonCode) -> Result<(), Error> {
let (buf, bytes_written) = write_frame!(&mut self.ctx.buf_provider, {
headers: {
mac::MgmtHdr: &mgmt_writer::mgmt_hdr_to_ap(
mac::FrameControl(0)
.with_frame_type(mac::FrameType::MGMT)
.with_mgmt_subtype(mac::MgmtSubtype::DEAUTH),
self.sta.bssid,
self.sta.iface_mac,
mac::SequenceControl(0)
.with_seq_num(self.ctx.seq_mgr.next_sns1(&self.sta.bssid.0) as u16)
),
mac::DeauthHdr: &mac::DeauthHdr {
reason_code,
},
},
})?;
let out_buf = OutBuf::from(buf, bytes_written);
let result = self
.send_mgmt_or_ctrl_frame(out_buf)
.map_err(|s| Error::Status(format!("error sending deauthenticate frame"), s));
// Clear main_channel since there is no "main channel" after deauthenticating
self.channel_state.main_channel = None;
result
}
/// Sends the given payload as a data frame over the air.
pub fn send_data_frame(
&mut self,
src: MacAddr,
dst: MacAddr,
is_protected: bool,
qos_ctrl: bool,
ether_type: u16,
payload: &[u8],
) -> Result<(), Error> {
let qos_ctrl = if qos_ctrl {
Some(
wmm::derive_tid(ether_type, payload)
.map_or(mac::QosControl(0), |tid| mac::QosControl(0).with_tid(tid as u16)),
)
} else {
None
};
let (buf, bytes_written) = write_frame!(&mut self.ctx.buf_provider, {
headers: {
mac::FixedDataHdrFields: &mac::FixedDataHdrFields {
frame_ctrl: mac::FrameControl(0)
.with_frame_type(mac::FrameType::DATA)
.with_data_subtype(mac::DataSubtype(0).with_qos(qos_ctrl.is_some()))
.with_protected(is_protected)
.with_to_ds(true),
duration: 0,
addr1: self.sta.bssid.0,
addr2: src,
addr3: dst,
seq_ctrl: mac::SequenceControl(0).with_seq_num(
match qos_ctrl.as_ref() {
None => self.ctx.seq_mgr.next_sns1(&dst),
Some(qos_ctrl) => self.ctx.seq_mgr.next_sns2(&dst, qos_ctrl.tid()),
} as u16
)
},
mac::QosControl?: qos_ctrl,
mac::LlcHdr: &data_writer::make_snap_llc_hdr(ether_type),
},
payload: payload,
})?;
let out_buf = OutBuf::from(buf, bytes_written);
let tx_flags = match ether_type {
mac::ETHER_TYPE_EAPOL => TxFlags::FAVOR_RELIABILITY,
_ => TxFlags::NONE,
};
self.ctx
.device
.send_wlan_frame(out_buf, tx_flags)
.map_err(|s| Error::Status(format!("error sending data frame"), s))
}
/// Sends an MLME-EAPOL.indication to MLME's SME peer.
/// Note: MLME-EAPOL.indication is a custom Fuchsia primitive and not defined in IEEE 802.11.
fn send_eapol_indication(
&mut self,
src_addr: MacAddr,
dst_addr: MacAddr,
eapol_frame: &[u8],
) -> Result<(), Error> {
if eapol_frame.len() > MAX_EAPOL_FRAME_LEN {
return Err(Error::Internal(format_err!(
"EAPOL frame too large: {}",
eapol_frame.len()
)));
}
self.ctx.device.access_sme_sender(|sender| {
sender.send_eapol_ind(&mut fidl_mlme::EapolIndication {
src_addr,
dst_addr,
data: eapol_frame.to_vec(),
})
})
}
/// Sends an EAPoL frame over the air and reports transmission status to SME via an
/// MLME-EAPOL.confirm message.
pub fn send_eapol_frame(
&mut self,
src: MacAddr,
dst: MacAddr,
is_protected: bool,
eapol_frame: &[u8],
) {
// TODO(fxbug.dev/34910): EAPoL frames can be send in QoS data frames. However, Fuchsia's old C++
// MLME never sent EAPoL frames in QoS data frames. For feature parity do the same.
let result = self.send_data_frame(
src,
dst,
is_protected,
false, /* don't use QoS */
mac::ETHER_TYPE_EAPOL,
eapol_frame,
);
let result_code = match result {
Ok(()) => fidl_mlme::EapolResultCodes::Success,
Err(e) => {
error!("error sending EAPoL frame: {}", e);
fidl_mlme::EapolResultCodes::TransmissionFailure
}
};
// Report transmission result to SME.
let result = self.ctx.device.access_sme_sender(|sender| {
sender.send_eapol_conf(&mut fidl_mlme::EapolConfirm { result_code })
});
if let Err(e) = result {
error!("error sending MLME-EAPOL.confirm message: {}", e);
}
}
pub fn send_ps_poll_frame(&mut self, aid: Aid) -> Result<(), Error> {
const PS_POLL_ID_MASK: u16 = 0b11000000_00000000;
let (buf, bytes_written) = write_frame!(&mut self.ctx.buf_provider, {
headers: {
mac::FrameControl: &mac::FrameControl(0)
.with_frame_type(mac::FrameType::CTRL)
.with_ctrl_subtype(mac::CtrlSubtype::PS_POLL),
mac::PsPoll: &mac::PsPoll {
// IEEE 802.11-2016 9.3.1.5 states the ID in the PS-Poll frame is the
// association ID with the 2 MSBs set to 1.
masked_aid: aid | PS_POLL_ID_MASK,
bssid: self.sta.bssid,
ta: self.sta.iface_mac,
},
},
})?;
let out_buf = OutBuf::from(buf, bytes_written);
self.send_mgmt_or_ctrl_frame(out_buf)
.map_err(|s| Error::Status(format!("error sending PS-Poll frame"), s))
}
/// Called when a previously scheduled `TimedEvent` fired.
pub fn handle_timed_event(&mut self, event: TimedEvent, event_id: EventId) {
self.sta.state = Some(self.sta.state.take().unwrap().on_timed_event(self, event, event_id))
}
/// Called when an arbitrary frame was received over the air.
pub fn on_mac_frame<B: ByteSlice>(
&mut self,
bytes: B,
rx_info: Option<banjo_wlan_mac::WlanRxInfo>,
) {
// Safe: |state| is never None and always replaced with Some(..).
self.sta.state = Some(self.sta.state.take().unwrap().on_mac_frame(self, bytes, rx_info));
}
pub fn on_eth_frame<B: ByteSlice>(&mut self, frame: B) -> Result<(), Error> {
// Safe: |state| is never None and always replaced with Some(..).
let state = self.sta.state.take().unwrap();
let result = state.on_eth_frame(self, frame);
self.sta.state.replace(state);
result
}
#[allow(deprecated)] // Allow until main message loop is in Rust.
pub fn handle_mlme_msg(&mut self, msg: fidl_mlme::MlmeRequestMessage) {
// Safe: |state| is never None and always replaced with Some(..).
let next_state = self.sta.state.take().unwrap().handle_mlme_msg(self, msg);
self.sta.state.replace(next_state);
}
/// Sends an MLME-AUTHENTICATE.confirm message to the SME with authentication type
/// `Open System` as only open authentication is supported.
fn send_authenticate_conf(
&mut self,
auth_type: fidl_mlme::AuthenticationTypes,
result_code: fidl_mlme::AuthenticateResultCodes,
) {
let result = self.ctx.device.access_sme_sender(|sender| {
sender.send_authenticate_conf(&mut fidl_mlme::AuthenticateConfirm {
peer_sta_address: self.sta.bssid.0,
auth_type,
result_code,
})
});
if let Err(e) = result {
error!("error sending MLME-AUTHENTICATE.confirm: {}", e);
}
}
/// Sends an MLME-ASSOCIATE.confirm message to the SME.
fn send_associate_conf_failure(&mut self, result_code: fidl_mlme::AssociateResultCodes) {
// AID used for reporting failed associations to SME.
const FAILED_ASSOCIATION_AID: mac::Aid = 0;
let mut assoc_conf = fidl_mlme::AssociateConfirm {
association_id: FAILED_ASSOCIATION_AID,
cap_info: 0,
result_code,
rates: vec![],
wmm_param: None,
ht_cap: None,
vht_cap: None,
};
let result =
self.ctx.device.access_sme_sender(|sender| sender.send_associate_conf(&mut assoc_conf));
if let Err(e) = result {
error!("error sending MLME-AUTHENTICATE.confirm: {}", e);
}
}
fn send_associate_conf_success<B: ByteSlice>(
&mut self,
association_id: mac::Aid,
cap_info: mac::CapabilityInfo,
elements: B,
) {
type HtCapArray = [u8; fidl_mlme::HT_CAP_LEN as usize];
type VhtCapArray = [u8; fidl_mlme::VHT_CAP_LEN as usize];
let mut assoc_conf = fidl_mlme::AssociateConfirm {
association_id,
cap_info: cap_info.raw(),
result_code: fidl_mlme::AssociateResultCodes::Success,
rates: vec![],
wmm_param: None,
ht_cap: None,
vht_cap: None,
};
for (id, body) in Reader::new(elements) {
match id {
Id::SUPPORTED_RATES => {
// safe to unwrap because supported rate is 1-byte long thus always aligned
assoc_conf.rates.extend_from_slice(&body);
}
Id::EXT_SUPPORTED_RATES => {
// safe to unwrap because supported rate is 1-byte thus always aligned
assoc_conf.rates.extend_from_slice(&body);
}
Id::HT_CAPABILITIES => match ie::parse_ht_capabilities(body) {
Err(e) => error!("invalid HT Capabilities: {}", e),
Ok(ht_cap) => {
assert_eq_size!(ie::HtCapabilities, HtCapArray);
let bytes: HtCapArray = ht_cap.as_bytes().try_into().unwrap();
assoc_conf.ht_cap = Some(Box::new(fidl_mlme::HtCapabilities { bytes }))
}
},
Id::VHT_CAPABILITIES => match ie::parse_vht_capabilities(body) {
Err(e) => error!("invalid VHT Capabilities: {}", e),
Ok(vht_cap) => {
assert_eq_size!(ie::VhtCapabilities, VhtCapArray);
let bytes: VhtCapArray = vht_cap.as_bytes().try_into().unwrap();
assoc_conf.vht_cap = Some(Box::new(fidl_mlme::VhtCapabilities { bytes }))
}
},
// TODO(fxbug.dev/43938): parse vendor ID and include WMM param if exists
_ => {}
}
}
let result =
self.ctx.device.access_sme_sender(|sender| sender.send_associate_conf(&mut assoc_conf));
if let Err(e) = result {
error!("error sending MLME-AUTHENTICATE.confirm: {}", e);
}
}
/// Sends an MLME-DEAUTHENTICATE.indication message to the joined BSS.
fn send_deauthenticate_ind(
&mut self,
reason_code: fidl_mlme::ReasonCode,
locally_initiated: LocallyInitiated,
) {
// Clear main_channel since there is no "main channel" after deauthenticating
self.channel_state.main_channel = None;
let result = self.ctx.device.access_sme_sender(|sender| {
sender.send_deauthenticate_ind(&mut fidl_mlme::DeauthenticateIndication {
peer_sta_address: self.sta.bssid.0,
reason_code,
locally_initiated: locally_initiated.0,
})
});
if let Err(e) = result {
error!("error sending MLME-DEAUTHENTICATE.indication: {}", e);
}
}
/// Sends an MLME-DISASSOCIATE.indication message to the joined BSS.
fn send_disassoc_ind(
&mut self,
reason_code: fidl_mlme::ReasonCode,
locally_initiated: LocallyInitiated,
) {
let result = self.ctx.device.access_sme_sender(|sender| {
sender.send_disassociate_ind(&mut fidl_mlme::DisassociateIndication {
peer_sta_address: self.sta.bssid.0,
reason_code: reason_code.into_primitive(),
locally_initiated: locally_initiated.0,
})
});
if let Err(e) = result {
error!("error sending MLME-DEAUTHENTICATE.indication: {}", e);
}
}
/// Sends an sae frame rx message to the SME.
fn forward_sae_frame_rx(
&mut self,
seq_num: u16,
result_code: fidl_mlme::AuthenticateResultCodes,
sae_fields: Vec<u8>,
) {
let result = self.ctx.device.access_sme_sender(|sender| {
sender.send_on_sae_frame_rx(&mut fidl_mlme::SaeFrame {
peer_sta_address: self.sta.bssid.0,
seq_num,
result_code,
sae_fields,
})
});
if let Err(e) = result {
error!("error sending OnSaeFrameRx: {}", e);
}
}
fn forward_sae_handshake_ind(&mut self) {
let result = self.ctx.device.access_sme_sender(|sender| {
sender.send_on_sae_handshake_ind(&mut fidl_mlme::SaeHandshakeIndication {
peer_sta_address: self.sta.bssid.0,
})
});
if let Err(e) = result {
error!("error sending OnSaeHandshakeInd: {}", e);
}
}
fn is_on_channel(&self) -> bool {
let channel = self.ctx.device.channel();
self.channel_state.main_channel.map(|c| c == channel).unwrap_or(false)
}
fn send_mgmt_or_ctrl_frame(&mut self, out_buf: OutBuf) -> Result<(), zx::Status> {
self.ensure_on_channel();
self.ctx.device.send_wlan_frame(out_buf, TxFlags::NONE)
}
fn ensure_on_channel(&mut self) {
match self.channel_state.main_channel {
Some(main_channel) => {
let duration = zx::Duration::from_nanos(self.ctx.config.ensure_on_channel_time);
let mut listener = self.channel_state.bind(self.ctx, self.scanner, Some(self.sta));
self.chan_sched
.bind(&mut listener, ChannelListenerSource::Others)
.schedule_immediate(main_channel, duration);
}
None => warn!("main channel not set, cannot ensure on channel"),
}
}
}
impl<'a> BlockAckTx for BoundClient<'a> {
/// Sends a BlockAck frame to the associated AP.
///
/// BlockAck frames are described by 802.11-2016, section 9.6.5.2, 9.6.5.3, and 9.6.5.4.
fn send_block_ack_frame(&mut self, n: usize, body: &[u8]) -> Result<(), Error> {
let mut buffer = self.ctx.buf_provider.get_buffer(n)?;
let mut writer = BufferWriter::new(&mut buffer[..]);
write_block_ack_hdr(&mut writer, self.sta.bssid, self.sta.iface_mac, &mut self.ctx.seq_mgr)
.and_then(|_| writer.append_bytes(body).map_err(Into::into))?;
let n = writer.bytes_written();
let buffer = OutBuf::from(buffer, n);
self.send_mgmt_or_ctrl_frame(buffer)
.map_err(|status| Error::Status(format!("error sending BlockAck frame"), status))
}
}
/// Writes the header of the management frame for BlockAck frames to the given buffer.
///
/// The address may be that of the originator or recipient. The frame formats are described by IEEE
/// Std 802.11-2016, 9.6.5.
fn write_block_ack_hdr<B: Appendable>(
buffer: &mut B,
bssid: Bssid,
addr: MacAddr,
seq_mgr: &mut SequenceManager,
) -> Result<usize, Error> {
// The management header differs for APs and clients. The frame control and management header
// are constructed here, but AP and client STAs share the code that constructs the body. See
// the `block_ack` module.
write_frame_with_dynamic_buf!(
buffer,
{
headers: {
mac::MgmtHdr: &mgmt_writer::mgmt_hdr_to_ap(
mac::FrameControl(0)
.with_frame_type(mac::FrameType::MGMT)
.with_mgmt_subtype(mac::MgmtSubtype::ACTION),
bssid,
addr,
mac::SequenceControl(0)
.with_seq_num(seq_mgr.next_sns1(&bssid.0) as u16),
),
},
}
)
.map(|(_, n)| n)
}
#[cfg(test)]
mod tests {
use {
super::{state::DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT, *},
crate::{
block_ack::{self, BlockAckState, Closed, ADDBA_REQ_FRAME_LEN, ADDBA_RESP_FRAME_LEN},
buffer::FakeBufferProvider,
client::lost_bss::LostBssCounter,
device::FakeDevice,
},
fidl_fuchsia_wlan_common as fidl_common,
wlan_common::{
assert_variant,
ie::{
self, fake_wpa_ie, get_rsn_ie_bytes, get_vendor_ie_bytes_for_wpa_ie,
rsn::fake_wpa2_a_rsne,
},
stats::SignalStrengthAverage,
test_utils::fake_frames::*,
TimeUnit,
},
wlan_statemachine::*,
};
const BSSID: Bssid = Bssid([6u8; 6]);
const IFACE_MAC: MacAddr = [7u8; 6];
const RSNE: &[u8] = &[
0x30, 0x14, // ID and len
1, 0, // version
0x00, 0x0f, 0xac, 0x04, // group data cipher suite
0x01, 0x00, // pairwise cipher suite count
0x00, 0x0f, 0xac, 0x04, // pairwise cipher suite list
0x01, 0x00, // akm suite count
0x00, 0x0f, 0xac, 0x02, // akm suite list
0xa8, 0x04, // rsn capabilities
];
const MAIN_CHANNEL: banjo_wlan_info::WlanChannel = banjo_wlan_info::WlanChannel {
primary: 11,
cbw: banjo_wlan_info::WlanChannelBandwidth::_20,
secondary80: 0,
};
const SCAN_CHANNEL_PRIMARY: u8 = 6;
// Note: not necessarily valid beacon frame.
#[rustfmt::skip]
const BEACON_FRAME: &'static [u8] = &[
// Mgmt header
0b10000000, 0, // Frame Control
0, 0, // Duration
255, 255, 255, 255, 255, 255, // addr1
6, 6, 6, 6, 6, 6, // addr2
6, 6, 6, 6, 6, 6, // addr3
0, 0, // Sequence Control
// Beacon header:
0, 0, 0, 0, 0, 0, 0, 0, // Timestamp
10, 0, // Beacon interval
33, 0, // Capabilities
// IEs:
0, 4, 0x73, 0x73, 0x69, 0x64, // SSID - "ssid"
1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Supported rates
3, 1, 11, // DSSS parameter set - channel 11
5, 4, 0, 0, 0, 0, // TIM
];
struct MockObjects {
fake_device: FakeDevice,
fake_scheduler: FakeScheduler,
}
impl MockObjects {
fn new() -> Self {
Self { fake_device: FakeDevice::new(), fake_scheduler: FakeScheduler::new() }
}
fn make_mlme(&mut self) -> ClientMlme {
let device = self.fake_device.as_device();
self.make_mlme_with_device(device)
}
fn make_mlme_with_device(&mut self, device: Device) -> ClientMlme {
let config = ClientConfig { ensure_on_channel_time: 0 };
let mut mlme = ClientMlme::new(
config,
device,
FakeBufferProvider::new(),
self.fake_scheduler.as_scheduler(),
);
mlme.set_main_channel(MAIN_CHANNEL).expect("unable to set main channel");
mlme
}
}
fn scan_req() -> fidl_mlme::ScanRequest {
fidl_mlme::ScanRequest {
txn_id: 1337,
bss_type: fidl_mlme::BssTypes::Infrastructure,
bssid: BSSID.0,
ssid: b"ssid".to_vec(),
scan_type: fidl_mlme::ScanTypes::Passive,
probe_delay: 0,
channel_list: Some(vec![SCAN_CHANNEL_PRIMARY]),
min_channel_time: 100,
max_channel_time: 300,
ssid_list: None,
}
}
fn make_client_station() -> Client {
Client::new(vec![], BSSID, IFACE_MAC, TimeUnit::DEFAULT_BEACON_INTERVAL.0, false)
}
impl ClientMlme {
fn make_client_station(&mut self) {
self.sta.replace(make_client_station());
}
fn get_bound_client(&mut self) -> Option<BoundClient<'_>> {
match self.sta.as_mut() {
None => None,
Some(sta) => Some(sta.bind(
&mut self.ctx,
&mut self.scanner,
&mut self.chan_sched,
&mut self.channel_state,
)),
}
}
}
impl BoundClient<'_> {
fn move_to_associated_state(&mut self) {
use super::state::*;
let status_check_timeout =
schedule_association_status_timeout(self.sta.beacon_period, &mut self.ctx.timer);
let state =
States::from(wlan_statemachine::testing::new_state(Associated(Association {
aid: 42,
controlled_port_open: true,
ap_ht_op: None,
ap_vht_op: None,
qos: Qos::Disabled,
lost_bss_counter: LostBssCounter::start(
self.sta.beacon_period,
DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT,
),
status_check_timeout,
signal_strength_average: SignalStrengthAverage::new(),
block_ack_state: StateMachine::new(BlockAckState::from(State::new(Closed))),
})));
self.sta.state.replace(state);
}
#[allow(deprecated)] // MlmeRequestMessage is deprecated
fn close_controlled_port(&mut self) {
self.sta.eapol_required = true;
self.handle_mlme_msg(fidl_mlme::MlmeRequestMessage::SetControlledPort {
req: fidl_mlme::SetControlledPortRequest {
peer_sta_address: BSSID.0,
state: fidl_mlme::ControlledPortState::Closed,
},
});
}
}
#[test]
fn spawns_new_sta_on_join_request_from_sme() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
assert!(me.get_bound_client().is_none(), "MLME should not contain client, yet");
me.on_sme_join(fidl_mlme::JoinRequest {
selected_bss: wlan_common::test_utils::fake_stas::fake_bss_description(
vec![],
None,
None,
),
join_failure_timeout: 42,
nav_sync_delay: 42,
op_rates: vec![1, 2, 3],
phy: fidl_common::Phy::Erp,
cbw: fidl_common::Cbw::Cbw20,
})
.expect("valid JoinRequest should be handled successfully");
me.get_bound_client().expect("client sta should have been created by now.");
}
#[test]
fn rsn_ie_implies_sta_eapol_required() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
assert!(me.get_bound_client().is_none(), "MLME should not contain client, yet");
let rsne_bytes = get_rsn_ie_bytes(&fake_wpa2_a_rsne());
me.on_sme_join(fidl_mlme::JoinRequest {
selected_bss: wlan_common::test_utils::fake_stas::fake_bss_description(
vec![],
Some(rsne_bytes),
None,
),
join_failure_timeout: 42,
nav_sync_delay: 42,
op_rates: vec![1, 2, 3],
phy: fidl_common::Phy::Erp,
cbw: fidl_common::Cbw::Cbw20,
})
.expect("valid JoinRequest should be handled successfully");
let client = me.get_bound_client().expect("client sta should have been created by now.");
assert!(client.sta.eapol_required);
}
#[test]
fn wpa_ie_implies_sta_eapol_required() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
assert!(me.get_bound_client().is_none(), "MLME should not contain client, yet");
let vendor_ies_bytes = get_vendor_ie_bytes_for_wpa_ie(&fake_wpa_ie())
.expect("could not write WPA IE to Vendor IE buffer");
me.on_sme_join(fidl_mlme::JoinRequest {
selected_bss: wlan_common::test_utils::fake_stas::fake_bss_description(
vec![],
None,
Some(vendor_ies_bytes),
),
join_failure_timeout: 42,
nav_sync_delay: 42,
op_rates: vec![1, 2, 3],
phy: fidl_common::Phy::Erp,
cbw: fidl_common::Cbw::Cbw20,
})
.expect("valid JoinRequest should be handled successfully");
let client = me.get_bound_client().expect("client sta should have been created by now.");
assert!(client.sta.eapol_required);
}
#[test]
fn no_wpa_or_rsn_ie_implies_sta_eapol_not_required() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
assert!(me.get_bound_client().is_none(), "MLME should not contain client, yet");
me.on_sme_join(fidl_mlme::JoinRequest {
selected_bss: wlan_common::test_utils::fake_stas::fake_bss_description(
vec![],
None,
None,
),
join_failure_timeout: 42,
nav_sync_delay: 42,
op_rates: vec![1, 2, 3],
phy: fidl_common::Phy::Erp,
cbw: fidl_common::Cbw::Cbw20,
})
.expect("valid JoinRequest should be handled successfully");
let client = me.get_bound_client().expect("client sta should have been created by now.");
assert!(!client.sta.eapol_required);
}
#[test]
fn test_ensure_on_channel_followed_by_scheduled_scan() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.send_open_auth_frame().expect("error delivering WLAN frame");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
// Verify ensure_on_channel. That is, scheduling scan request would not cause channel to be
// switched right away, while frame is still being sent.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel(), MAIN_CHANNEL);
// Verify that triggering scheduled timeout by channel scheduler would switch channel
assert_eq!(m.fake_scheduler.deadlines.len(), 1);
me.handle_timed_event(m.fake_scheduler.next_id.into());
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
}
#[test]
fn test_active_scan_scheduling() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let scan_req = fidl_mlme::ScanRequest {
scan_type: fidl_mlme::ScanTypes::Active,
probe_delay: 5,
..scan_req()
};
let scan_txn_id = scan_req.txn_id;
me.on_sme_scan(scan_req);
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
// There should be two scheduled events, one by channel scheduler for scanned channel,
// another by scanner for delayed sending of probe request
assert_eq!(m.fake_scheduler.deadlines.len(), 2);
let (id, _deadline) = m.fake_scheduler.next_event().expect("expect scheduled event [1]");
me.handle_timed_event(id);
// Verify that probe delay is sent.
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Mgmt header:
0b0100_00_00, 0b00000000, // FC
0, 0, // Duration
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // addr1
7, 7, 7, 7, 7, 7, // addr2
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // addr3
0x10, 0, // Sequence Control
// IEs
0, 4, // SSID id and length
115, 115, 105, 100, // SSID
1, 6, // supp_rates id and length
12, 24, 48, 54, 96, 108, // supp_rates
][..]);
m.fake_device.wlan_queue.clear();
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
// Trigger timeout by channel scheduler, indicating end of scan request
let (id, _deadline) = m.fake_scheduler.next_event().expect("expect scheduled event [2]");
me.handle_timed_event(id);
assert_eq!(me.ctx.device.channel(), MAIN_CHANNEL);
let msg =
m.fake_device.next_mlme_msg::<fidl_mlme::ScanEnd>().expect("error reading SCAN.end");
assert_eq!(msg.txn_id, scan_txn_id);
assert_eq!(msg.code, fidl_mlme::ScanResultCodes::Success);
}
#[test]
fn test_no_power_state_frame_when_client_is_not_connected() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
// Verify no power state frame is sent
assert_eq!(m.fake_device.wlan_queue.len(), 0);
// There should be one scheduled event for end of channel period
assert_eq!(m.fake_scheduler.deadlines.len(), 1);
let (id, _deadline) = m.fake_scheduler.next_event().expect("expect scheduled event");
me.handle_timed_event(id);
assert_eq!(me.ctx.device.channel(), MAIN_CHANNEL);
// Verify no power state frame is sent
assert_eq!(m.fake_device.wlan_queue.len(), 0);
}
#[test]
fn test_send_power_state_frame_when_switching_channel_while_connected() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
// Pretend that client is associated by starting LostBssCounter
client.move_to_associated_state();
// clear the LostBssCounter timeout.
m.fake_scheduler.deadlines.clear();
// Send scan request to trigger channel switch
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
// Verify that power state frame is sent
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Data header:
0b0100_10_00, 0b00010001, // 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
][..]);
m.fake_device.wlan_queue.clear();
// There should be one scheduled event for end of channel period
assert_eq!(m.fake_scheduler.deadlines.len(), 1);
let (id, _deadline) = m.fake_scheduler.next_event().expect("expect scheduled event");
me.handle_timed_event(id);
assert_eq!(me.ctx.device.channel(), MAIN_CHANNEL);
// Verify that power state frame is sent
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Data header:
0b0100_10_00, 0b00000001, // 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
][..]);
}
// Auto-deauth is tied to singal report by AssociationStatusCheck timeout
fn advance_auto_deauth(m: &mut MockObjects, me: &mut ClientMlme, beacon_count: u32) {
for _ in 0..beacon_count / super::state::ASSOCIATION_STATUS_TIMEOUT_BEACON_COUNT {
let (id, deadline) = assert_variant!(m.fake_scheduler.next_event(), Some(ev) => ev);
m.fake_scheduler.set_time(deadline);
me.handle_timed_event(id);
assert_eq!(m.fake_device.wlan_queue.len(), 0);
m.fake_device
.next_mlme_msg::<fidl_mlme::SignalReportIndication>()
.expect("error reading SignalReport.indication");
}
}
#[test]
fn test_auto_deauth_uninterrupted_interval() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.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.
advance_auto_deauth(&mut m, &mut me, DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT);
// One more timeout to trigger the auto deauth
let (id, deadline) = assert_variant!(m.fake_scheduler.next_event(), Some(ev) => ev);
// Verify that triggering event at deadline causes deauth
m.fake_scheduler.set_time(deadline);
me.handle_timed_event(id);
m.fake_device
.next_mlme_msg::<fidl_mlme::SignalReportIndication>()
.expect("error reading SignalReport.indication");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.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 = m
.fake_device
.next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
.expect("error reading DEAUTHENTICATE.indication");
assert_eq!(
deauth_ind,
fidl_mlme::DeauthenticateIndication {
peer_sta_address: BSSID.0,
reason_code: fidl_mlme::ReasonCode::LeavingNetworkDeauth,
locally_initiated: true,
}
);
}
#[test]
fn test_auto_deauth_received_beacon() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.move_to_associated_state();
// Move the countdown to just about to cause auto deauth.
advance_auto_deauth(&mut m, &mut me, DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT);
// Receive beacon midway, so lost bss countdown is reset.
// If this beacon is not received, the next timeout will trigger auto deauth.
me.on_mac_frame(BEACON_FRAME, None);
// Verify auto deauth is not triggered for the entire duration.
advance_auto_deauth(&mut m, &mut me, DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT);
// Verify more timer is scheduled
let (id2, deadline2) = assert_variant!(m.fake_scheduler.next_event(), Some(ev) => ev);
// Verify that triggering event at new deadline causes deauth
m.fake_scheduler.set_time(deadline2);
me.handle_timed_event(id2);
m.fake_device
.next_mlme_msg::<fidl_mlme::SignalReportIndication>()
.expect("error reading SignalReport.indication");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.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 = m
.fake_device
.next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
.expect("error reading DEAUTHENTICATE.indication");
assert_eq!(
deauth_ind,
fidl_mlme::DeauthenticateIndication {
peer_sta_address: BSSID.0,
reason_code: fidl_mlme::ReasonCode::LeavingNetworkDeauth,
locally_initiated: true,
}
);
}
#[test]
fn client_send_open_auth_frame() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.send_open_auth_frame().expect("error delivering WLAN frame");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Mgmt header:
0b1011_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
// Auth header:
0, 0, // auth algorithm
1, 0, // auth txn seq num
0, 0, // status code
][..]);
// Verify ensure_on_channel. That is, scheduling scan request would not cause channel to be
// switched right away, while frame is still being sent.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel(), MAIN_CHANNEL);
}
#[test]
fn client_send_assoc_req_frame() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.sta.ssid = [11, 22, 33, 44].to_vec();
client
.send_assoc_req_frame(
0x1234, // capability info
&[8, 7, 6, 5, 4, 3, 2, 1, 0], // rates
RSNE, // RSNE (including ID and len)
&(0..26).collect::<Vec<u8>>()[..], // HT Capabilities
&(100..112).collect::<Vec<u8>>()[..], // VHT Capabilities
)
.expect("error delivering WLAN frame");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
assert_eq!(
&m.fake_device.wlan_queue[0].0[..],
&[
// 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
0x10, 0, // Sequence Control
// Association Request header:
0x34, 0x12, // capability info
0, 0, // listen interval
// IEs
0, 4, // SSID id and length
11, 22, 33, 44, // SSID
1, 8, // supp rates id and length
8, 7, 6, 5, 4, 3, 2, 1, // supp rates
50, 1, // ext supp rates and length
0, // ext supp rates
0x30, 0x14, // RSNE ID and len
1, 0, // RSNE version
0x00, 0x0f, 0xac, 0x04, // RSNE group data cipher suite
0x01, 0x00, // RSNE pairwise cipher suite count
0x00, 0x0f, 0xac, 0x04, // RSNE pairwise cipher suite list
0x01, 0x00, // RSNE akm suite count
0x00, 0x0f, 0xac, 0x02, // RSNE akm suite list
0xa8, 0x04, // RSNE rsn capabilities
45, 26, // HT Cap id and length
0, 1, 2, 3, 4, 5, 6, 7, // HT Cap \
8, 9, 10, 11, 12, 13, 14, 15, // HT Cap \
16, 17, 18, 19, 20, 21, 22, 23, // HT Cap \
24, 25, // HT Cap (26 bytes)
191, 12, // VHT Cap id and length
100, 101, 102, 103, 104, 105, 106, 107, // VHT Cap \
108, 109, 110, 111, // VHT Cap (12 bytes)
][..]
);
// Verify ensure_on_channel. That is, scheduling scan request would not cause channel to be
// switched right away, while frame is still being sent.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel(), MAIN_CHANNEL);
}
#[test]
fn client_send_keep_alive_resp_frame() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.send_keep_alive_resp_frame().expect("error delivering WLAN frame");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Data header:
0b0100_10_00, 0b0000000_1, // 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
][..]);
// Verify no ensure_on_channel. That is, scheduling scan request would cause channel to be
// switched right away.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
}
#[test]
fn client_send_data_frame() {
let payload = vec![5; 8];
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client
.send_data_frame([2; 6], [3; 6], false, false, 0x1234, &payload[..])
.expect("error delivering WLAN frame");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Data header:
0b0000_10_00, 0b0000000_1, // FC
0, 0, // Duration
6, 6, 6, 6, 6, 6, // addr1
2, 2, 2, 2, 2, 2, // addr2
3, 3, 3, 3, 3, 3, // addr3
0x10, 0, // Sequence Control
// LLC header:
0xAA, 0xAA, 0x03, // DSAP, SSAP, Control
0, 0, 0, // OUI
0x12, 0x34, // Protocol ID
// Payload
5, 5, 5, 5, 5, 5, 5, 5,
][..]);
// Verify no ensure_on_channel. That is, scheduling scan request would cause channel to be
// switched right away.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
}
#[test]
fn client_send_data_frame_ipv4_qos() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
let mut client = make_client_station();
client
.bind(&mut me.ctx, &mut me.scanner, &mut me.chan_sched, &mut me.channel_state)
.send_data_frame(
[2; 6],
[3; 6],
false,
true,
0x0800, // IPv4
&[1, 0xB0, 3, 4, 5], // DSCP = 0b101100 (i.e. VOICE-ADMIT)
)
.expect("error delivering WLAN frame");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Data header:
0b1000_10_00, 0b0000000_1, // FC
0, 0, // Duration
6, 6, 6, 6, 6, 6, // addr1
2, 2, 2, 2, 2, 2, // addr2
3, 3, 3, 3, 3, 3, // addr3
0x10, 0, // Sequence Control
0x06, 0, // QoS Control - TID = 6
// LLC header:
0xAA, 0xAA, 0x03, // DSAP, SSAP, Control
0, 0, 0, // OUI
0x08, 0x00, // Protocol ID
// Payload
1, 0xB0, 3, 4, 5,
][..]);
// Verify no ensure_on_channel. That is, scheduling scan request would cause channel to be
// switched right away.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
}
#[test]
fn client_send_data_frame_ipv6_qos() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
let mut client = make_client_station();
client
.bind(&mut me.ctx, &mut me.scanner, &mut me.chan_sched, &mut me.channel_state)
.send_data_frame(
[2; 6],
[3; 6],
false,
true,
0x86DD, // IPv6
&[0b0101, 0b10000000, 3, 4, 5], // DSCP = 0b010110 (i.e. AF23)
)
.expect("error delivering WLAN frame");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Data header:
0b1000_10_00, 0b0000000_1, // FC
0, 0, // Duration
6, 6, 6, 6, 6, 6, // addr1
2, 2, 2, 2, 2, 2, // addr2
3, 3, 3, 3, 3, 3, // addr3
0x10, 0, // Sequence Control
0x03, 0, // QoS Control - TID = 3
// LLC header:
0xAA, 0xAA, 0x03, // DSAP, SSAP, Control
0, 0, 0, // OUI
0x86, 0xDD, // Protocol ID
// Payload
0b0101, 0b10000000, 3, 4, 5,
][..]);
// Verify no ensure_on_channel. That is, scheduling scan request would cause channel to be
// switched right away.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
}
#[test]
fn client_send_deauthentication_notification() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client
.send_deauth_frame(mac::ReasonCode::AP_INITIATED)
.expect("error delivering WLAN frame");
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.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
47, 0, // reason code
][..]);
// Verify ensure_on_channel. That is, scheduling scan request would not cause channel to be
// switched right away, while frame is still being sent.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel(), MAIN_CHANNEL);
}
#[test]
fn respond_to_keep_alive_request() {
#[rustfmt::skip]
let data_frame = vec![
// Data header:
0b0100_10_00, 0b000000_1_0, // FC
0, 0, // Duration
7, 7, 7, 7, 7, 7, // addr1
6, 6, 6, 6, 6, 6, // addr2
42, 42, 42, 42, 42, 42, // addr3
0x10, 0, // Sequence Control
];
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.move_to_associated_state();
client.on_mac_frame(&data_frame[..], None);
assert_eq!(m.fake_device.wlan_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Data header:
0b0100_10_00, 0b0000000_1, // 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
][..]);
// Verify no ensure_on_channel. That is, scheduling scan request would cause channel to be
// switched right away.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
}
#[test]
fn data_frame_to_ethernet_single_llc() {
let mut data_frame = make_data_frame_single_llc(None, None);
data_frame[1] = 0b00000010; // from_ds = 1, to_ds = 0 when AP sends to client (us)
data_frame[4..10].copy_from_slice(&IFACE_MAC); // addr1 - receiver - client (us)
data_frame[10..16].copy_from_slice(&BSSID.0); // addr2 - bssid
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.move_to_associated_state();
client.on_mac_frame(&data_frame[..], None);
assert_eq!(m.fake_device.eth_queue.len(), 1);
#[rustfmt::skip]
assert_eq!(m.fake_device.eth_queue[0], [
7, 7, 7, 7, 7, 7, // dst_addr
5, 5, 5, 5, 5, 5, // src_addr
9, 10, // ether_type
11, 11, 11, // payload
]);
}
#[test]
fn data_frame_to_ethernet_amsdu() {
let mut data_frame = make_data_frame_amsdu();
data_frame[1] = 0b00000010; // from_ds = 1, to_ds = 0 when AP sends to client (us)
data_frame[4..10].copy_from_slice(&IFACE_MAC); // addr1 - receiver - client (us)
data_frame[10..16].copy_from_slice(&BSSID.0); // addr2 - bssid
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.move_to_associated_state();
client.on_mac_frame(&data_frame[..], None);
let queue = &m.fake_device.eth_queue;
assert_eq!(queue.len(), 2);
#[rustfmt::skip]
let mut expected_first_eth_frame = vec![
0x78, 0x8a, 0x20, 0x0d, 0x67, 0x03, // dst_addr
0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xab, // src_addr
0x08, 0x00, // ether_type
];
expected_first_eth_frame.extend_from_slice(MSDU_1_PAYLOAD);
assert_eq!(queue[0], &expected_first_eth_frame[..]);
#[rustfmt::skip]
let mut expected_second_eth_frame = vec![
0x78, 0x8a, 0x20, 0x0d, 0x67, 0x04, // dst_addr
0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xac, // src_addr
0x08, 0x01, // ether_type
];
expected_second_eth_frame.extend_from_slice(MSDU_2_PAYLOAD);
assert_eq!(queue[1], &expected_second_eth_frame[..]);
}
#[test]
fn data_frame_to_ethernet_amsdu_padding_too_short() {
let mut data_frame = make_data_frame_amsdu_padding_too_short();
data_frame[1] = 0b00000010; // from_ds = 1, to_ds = 0 when AP sends to client (us)
data_frame[4..10].copy_from_slice(&IFACE_MAC); // addr1 - receiver - client (us)
data_frame[10..16].copy_from_slice(&BSSID.0); // addr2 - bssid
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.move_to_associated_state();
client.on_mac_frame(&data_frame[..], None);
let queue = &m.fake_device.eth_queue;
assert_eq!(queue.len(), 1);
#[rustfmt::skip]
let mut expected_first_eth_frame = vec![
0x78, 0x8a, 0x20, 0x0d, 0x67, 0x03, // dst_addr
0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xab, // src_addr
0x08, 0x00, // ether_type
];
expected_first_eth_frame.extend_from_slice(MSDU_1_PAYLOAD);
assert_eq!(queue[0], &expected_first_eth_frame[..]);
}
#[test]
fn data_frame_controlled_port_closed() {
let mut data_frame = make_data_frame_single_llc(None, None);
data_frame[1] = 0b00000010; // from_ds = 1, to_ds = 0 when AP sends to client (us)
data_frame[4..10].copy_from_slice(&IFACE_MAC); // addr1 - receiver - client (us)
data_frame[10..16].copy_from_slice(&BSSID.0); // addr2 - bssid
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.move_to_associated_state();
client.close_controlled_port();
client.on_mac_frame(&data_frame[..], None);
// Verify frame was not sent to netstack.
assert_eq!(m.fake_device.eth_queue.len(), 0);
}
#[test]
fn eapol_frame_controlled_port_closed() {
let (src_addr, dst_addr, mut eapol_frame) = make_eapol_frame(IFACE_MAC);
eapol_frame[1] = 0b00000010; // from_ds = 1, to_ds = 0 when AP sends to client (us)
eapol_frame[4..10].copy_from_slice(&IFACE_MAC); // addr1 - receiver - client (us)
eapol_frame[10..16].copy_from_slice(&BSSID.0); // addr2 - bssid
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.move_to_associated_state();
client.close_controlled_port();
client.on_mac_frame(&eapol_frame[..], None);
// Verify EAPoL frame was not sent to netstack.
assert_eq!(m.fake_device.eth_queue.len(), 0);
// Verify EAPoL frame was sent to SME.
let eapol_ind = m
.fake_device
.next_mlme_msg::<fidl_mlme::EapolIndication>()
.expect("error reading EAPOL.indication");
assert_eq!(
eapol_ind,
fidl_mlme::EapolIndication { src_addr, dst_addr, data: EAPOL_PDU.to_vec() }
);
}
#[test]
fn eapol_frame_is_controlled_port_open() {
let (src_addr, dst_addr, mut eapol_frame) = make_eapol_frame(IFACE_MAC);
eapol_frame[1] = 0b00000010; // from_ds = 1, to_ds = 0 when AP sends to client (us)
eapol_frame[4..10].copy_from_slice(&IFACE_MAC); // addr1 - receiver - client (us)
eapol_frame[10..16].copy_from_slice(&BSSID.0); // addr2 - bssid
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.move_to_associated_state();
client.on_mac_frame(&eapol_frame[..], None);
// Verify EAPoL frame was not sent to netstack.
assert_eq!(m.fake_device.eth_queue.len(), 0);
// Verify EAPoL frame was sent to SME.
let eapol_ind = m
.fake_device
.next_mlme_msg::<fidl_mlme::EapolIndication>()
.expect("error reading EAPOL.indication");
assert_eq!(
eapol_ind,
fidl_mlme::EapolIndication { src_addr, dst_addr, data: EAPOL_PDU.to_vec() }
);
}
#[test]
fn send_eapol_ind_too_large() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client
.send_eapol_indication([1; 6], [2; 6], &[5; 256])
.expect_err("sending too large EAPOL frame should fail");
m.fake_device
.next_mlme_msg::<fidl_mlme::EapolIndication>()
.expect_err("expected empty channel");
}
#[test]
fn send_eapol_ind_success() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client
.send_eapol_indication([1; 6], [2; 6], &[5; 200])
.expect("expected EAPOL.indication to be sent");
let eapol_ind = m
.fake_device
.next_mlme_msg::<fidl_mlme::EapolIndication>()
.expect("error reading EAPOL.indication");
assert_eq!(
eapol_ind,
fidl_mlme::EapolIndication { src_addr: [1; 6], dst_addr: [2; 6], data: vec![5; 200] }
);
}
#[test]
fn send_eapol_frame_success() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.send_eapol_frame(IFACE_MAC, BSSID.0, false, &[5; 8]);
// Verify EAPOL.confirm message was sent to SME.
let eapol_confirm = m
.fake_device
.next_mlme_msg::<fidl_mlme::EapolConfirm>()
.expect("error reading EAPOL.confirm");
assert_eq!(
eapol_confirm,
fidl_mlme::EapolConfirm { result_code: fidl_mlme::EapolResultCodes::Success }
);
// Verify EAPoL frame was sent over the air.
#[rustfmt::skip]
assert_eq!(&m.fake_device.wlan_queue[0].0[..], &[
// Data header:
0b0000_10_00, 0b0000000_1, // 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
// LLC header:
0xaa, 0xaa, 0x03, // dsap ssap ctrl
0x00, 0x00, 0x00, // oui
0x88, 0x8E, // protocol id (EAPOL)
// EAPoL PDU:
5, 5, 5, 5, 5, 5, 5, 5,
][..]);
}
#[test]
fn send_eapol_frame_failure() {
let mut m = MockObjects::new();
let device = m.fake_device.as_device_fail_wlan_tx();
let mut me = m.make_mlme_with_device(device);
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.send_eapol_frame([1; 6], [2; 6], false, &[5; 200]);
// Verify EAPOL.confirm message was sent to SME.
let eapol_confirm = m
.fake_device
.next_mlme_msg::<fidl_mlme::EapolConfirm>()
.expect("error reading EAPOL.confirm");
assert_eq!(
eapol_confirm,
fidl_mlme::EapolConfirm {
result_code: fidl_mlme::EapolResultCodes::TransmissionFailure
}
);
// Verify EAPoL frame was not sent over the air.
assert!(m.fake_device.wlan_queue.is_empty());
}
#[test]
fn send_ps_poll_frame() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.send_ps_poll_frame(0xABCD).expect("failed sending PS POLL frame");
// Verify ensure_on_channel. That is, scheduling scan request would not cause channel to be
// switched right away, while frame is still being sent.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel(), MAIN_CHANNEL);
}
#[test]
fn send_power_state_doze_frame_success() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
let mut client = make_client_station();
client
.send_power_state_frame(&mut me.ctx, PowerState::DOZE)
.expect("failed sending doze frame");
client
.send_power_state_frame(&mut me.ctx, PowerState::AWAKE)
.expect("failed sending awake frame");
// Verify no ensure_on_channel. That is, scheduling scan request would cause channel to be
// switched right away.
me.on_sme_scan(scan_req());
assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY);
}
#[test]
fn send_addba_req_frame() {
let mut mock = MockObjects::new();
let mut mlme = mock.make_mlme();
mlme.make_client_station();
let mut client = mlme.get_bound_client().expect("client should be present");
let mut body = [0u8; 16];
let mut writer = BufferWriter::new(&mut body[..]);
block_ack::write_addba_req_body(&mut writer, 1)
.and_then(|_| client.send_block_ack_frame(ADDBA_REQ_FRAME_LEN, writer.into_written()))
.expect("failed sending addba frame");
assert_eq!(
&mock.fake_device.wlan_queue[0].0[..],
&[
// Mgmt header 1101 for action frame
0b11010000, 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
// Action frame header (Also part of ADDBA request frame)
0x03, // Action Category: block ack (0x03)
0x00, // block ack action: ADDBA request (0x00)
1, // block ack dialog token
0b00000011, 0b00010000, // block ack parameters (u16)
0, 0, // block ack timeout (u16) (0: disabled)
0b00010000, 0, // block ack starting sequence number: fragment 0, sequence 1
][..]
);
}
#[test]
fn send_addba_resp_frame() {
let mut mock = MockObjects::new();
let mut mlme = mock.make_mlme();
mlme.make_client_station();
let mut client = mlme.get_bound_client().expect("client should be present");
let mut body = [0u8; 16];
let mut writer = BufferWriter::new(&mut body[..]);
block_ack::write_addba_resp_body(&mut writer, 1)
.and_then(|_| client.send_block_ack_frame(ADDBA_RESP_FRAME_LEN, writer.into_written()))
.expect("failed sending addba frame");
assert_eq!(
&mock.fake_device.wlan_queue[0].0[..],
&[
// Mgmt header 1101 for action frame
0b11010000, 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
// Action frame header (Also part of ADDBA response frame)
0x03, // Action Category: block ack (0x03)
0x01, // block ack action: ADDBA response (0x01)
1, // block ack dialog token
0, 0, // status
0b00000011, 0b00010000, // block ack parameters (u16)
0, 0, // block ack timeout (u16) (0: disabled)
][..]
);
}
#[test]
fn client_send_successful_associate_conf() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
let mut ies = vec![];
let rates = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let rates_writer = ie::RatesWriter::try_new(&rates[..]).expect("Valid rates");
// It should work even if ext_supp_rates shows up before supp_rates
rates_writer.write_ext_supported_rates(&mut ies);
rates_writer.write_supported_rates(&mut ies);
ie::write_ht_capabilities(&mut ies, &ie::fake_ht_capabilities()).expect("Valid HT Cap");
ie::write_vht_capabilities(&mut ies, &ie::fake_vht_capabilities()).expect("Valid VHT Cap");
client.send_associate_conf_success(42, mac::CapabilityInfo(0x1234), &ies[..]);
let associate_conf = m
.fake_device
.next_mlme_msg::<fidl_mlme::AssociateConfirm>()
.expect("error reading Associate.confirm");
assert_eq!(
associate_conf,
fidl_mlme::AssociateConfirm {
association_id: 42,
cap_info: 0x1234,
result_code: fidl_mlme::AssociateResultCodes::Success,
rates: vec![9, 10, 1, 2, 3, 4, 5, 6, 7, 8],
wmm_param: None,
ht_cap: Some(Box::new(fidl_mlme::HtCapabilities {
bytes: ie::fake_ht_capabilities().as_bytes().try_into().unwrap()
})),
vht_cap: Some(Box::new(fidl_mlme::VhtCapabilities {
bytes: ie::fake_vht_capabilities().as_bytes().try_into().unwrap()
})),
}
);
}
#[test]
fn client_send_failed_associate_conf() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
me.make_client_station();
let mut client = me.get_bound_client().expect("client should be present");
client.send_associate_conf_failure(fidl_mlme::AssociateResultCodes::RefusedExternalReason);
let associate_conf = m
.fake_device
.next_mlme_msg::<fidl_mlme::AssociateConfirm>()
.expect("error reading Associate.confirm");
assert_eq!(
associate_conf,
fidl_mlme::AssociateConfirm {
association_id: 0,
cap_info: 0,
result_code: fidl_mlme::AssociateResultCodes::RefusedExternalReason,
rates: vec![],
wmm_param: None,
ht_cap: None,
vht_cap: None,
}
);
}
#[test]
#[allow(deprecated)] // Needed for raw MLME message until main loop lives in Rust
fn mlme_respond_to_stats_query_with_empty_response() {
let mut m = MockObjects::new();
let mut me = m.make_mlme();
let stats_query_req = fidl_mlme::MlmeRequestMessage::StatsQueryReq {};
let result = me.handle_mlme_msg(stats_query_req);
assert_variant!(result, Ok(()));
let stats_query_resp = m
.fake_device
.next_mlme_msg::<fidl_mlme::StatsQueryResponse>()
.expect("Should receive a stats query response");
assert_eq!(stats_query_resp, stats::empty_stats_query_response());
}
#[test]
fn unicast_addresses() {
assert!(is_unicast([0; 6]));
assert!(is_unicast([0xfe; 6]));
}
#[test]
fn non_unicast_addresses() {
assert!(!is_unicast([0xff; 6])); // broadcast
assert!(!is_unicast([0x33, 0x33, 0, 0, 0, 0])); // IPv6 multicast
assert!(!is_unicast([0x01, 0x00, 0x53, 0, 0, 0])); // IPv4 multicast
}
#[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));
}
}