| // 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 channel_listener; |
| mod channel_scheduler; |
| mod channel_switch; |
| mod convert_beacon; |
| mod lost_bss; |
| mod scanner; |
| mod state; |
| mod stats; |
| #[cfg(test)] |
| mod test_utils; |
| |
| use { |
| crate::{ |
| akm_algorithm, |
| block_ack::BlockAckTx, |
| buffer::{BufferProvider, OutBuf}, |
| device::Device, |
| disconnect::LocallyInitiated, |
| error::Error, |
| logger, |
| }, |
| anyhow::{self, format_err}, |
| banjo_fuchsia_hardware_wlan_softmac as banjo_wlan_softmac, |
| banjo_fuchsia_wlan_common as banjo_common, banjo_fuchsia_wlan_internal as banjo_internal, |
| channel_listener::{ChannelListenerSource, ChannelListenerState}, |
| channel_scheduler::ChannelScheduler, |
| fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_minstrel as fidl_minstrel, |
| fidl_fuchsia_wlan_mlme as fidl_mlme, fuchsia_zircon as zx, |
| ieee80211::{Bssid, MacAddr, Ssid}, |
| log::{error, warn}, |
| scanner::Scanner, |
| state::States, |
| std::convert::TryInto, |
| wlan_common::{ |
| appendable::Appendable, |
| bss::BssDescription, |
| buffer_writer::BufferWriter, |
| capabilities::{derive_join_capabilities, ClientCapabilities}, |
| channel::Channel, |
| data_writer, |
| ie::{self, rsn::rsne, Id, Reader}, |
| mac::{self, Aid, CapabilityInfo, PowerState}, |
| mgmt_writer, |
| sequence::SequenceManager, |
| time::TimeUnit, |
| timer::{EventId, Timer}, |
| wmm, |
| }, |
| wlan_frame_writer::{write_frame, write_frame_with_dynamic_buf, write_frame_with_fixed_buf}, |
| zerocopy::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, Clone, PartialEq)] |
| pub enum TimedEvent { |
| ChannelScheduler, |
| ScannerProbeDelay(banjo_common::WlanChannel), |
| /// 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, |
| } |
| |
| #[cfg(test)] |
| impl TimedEvent { |
| fn class(&self) -> TimedEventClass { |
| match self { |
| Self::ChannelScheduler => TimedEventClass::ChannelScheduler, |
| Self::ScannerProbeDelay(_) => TimedEventClass::ScannerProbeDelay, |
| Self::Connecting => TimedEventClass::Connecting, |
| Self::Reassociating => TimedEventClass::Reassociating, |
| Self::AssociationStatusCheck => TimedEventClass::AssociationStatusCheck, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| #[derive(Debug, PartialEq, Eq, Hash)] |
| pub enum TimedEventClass { |
| ChannelScheduler, |
| ScannerProbeDelay, |
| Connecting, |
| Reassociating, |
| 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 crate::MlmeImpl for ClientMlme { |
| type Config = ClientConfig; |
| type TimerEvent = TimedEvent; |
| fn new( |
| config: ClientConfig, |
| device: Device, |
| buf_provider: BufferProvider, |
| timer: Timer<TimedEvent>, |
| ) -> Self { |
| Self::new(config, device, buf_provider, timer) |
| } |
| fn handle_mlme_message(&mut self, msg: fidl_mlme::MlmeRequest) -> Result<(), anyhow::Error> { |
| Self::handle_mlme_msg(self, msg).map_err(|e| e.into()) |
| } |
| fn handle_mac_frame_rx( |
| &mut self, |
| bytes: &[u8], |
| rx_info: banjo_fuchsia_hardware_wlan_softmac::WlanRxInfo, |
| ) { |
| Self::on_mac_frame_rx(self, bytes, rx_info) |
| } |
| fn handle_eth_frame_tx(&mut self, bytes: &[u8]) -> Result<(), anyhow::Error> { |
| Self::on_eth_frame_tx(self, bytes).map_err(|e| e.into()) |
| } |
| fn handle_scan_complete(&mut self, status: zx::Status, scan_id: u64) { |
| Self::handle_scan_complete(self, status, scan_id); |
| } |
| fn handle_timeout(&mut self, event_id: EventId, event: TimedEvent) { |
| Self::handle_timed_event(self, event_id, event) |
| } |
| fn access_device(&mut self) -> &mut Device { |
| &mut self.ctx.device |
| } |
| } |
| |
| impl ClientMlme { |
| pub fn new( |
| config: ClientConfig, |
| device: Device, |
| buf_provider: BufferProvider, |
| timer: Timer<TimedEvent>, |
| ) -> Self { |
| logger::init(); |
| |
| let iface_mac = device.wlan_softmac_info().sta_addr; |
| 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_common::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_rx(&mut self, frame: &[u8], rx_info: banjo_wlan_softmac::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.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.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) |
| } |
| } |
| |
| pub fn handle_mlme_msg(&mut self, msg: fidl_mlme::MlmeRequest) -> Result<(), Error> { |
| use fidl_mlme::MlmeRequest as MlmeMsg; |
| |
| match msg { |
| // Handle non station specific MLME messages first (Join, Scan, etc.) |
| MlmeMsg::StartScan { req, .. } => Ok(self.on_sme_scan(req)), |
| MlmeMsg::ConnectReq { req, .. } => self.on_sme_connect(req), |
| MlmeMsg::StatsQueryReq { .. } => self.on_sme_stats_query(), |
| MlmeMsg::GetIfaceCounterStats { responder } => { |
| self.on_sme_get_iface_counter_stats(responder) |
| } |
| MlmeMsg::GetIfaceHistogramStats { responder } => { |
| self.on_sme_get_iface_histogram_stats(responder) |
| } |
| MlmeMsg::QueryDeviceInfo { responder } => self.on_sme_query_device_info(responder), |
| MlmeMsg::QueryDiscoverySupport { responder } => { |
| self.on_sme_query_discovery_support(responder) |
| } |
| MlmeMsg::QueryMacSublayerSupport { responder } => { |
| self.on_sme_query_mac_sublayer_support(responder) |
| } |
| MlmeMsg::QuerySecuritySupport { responder } => { |
| self.on_sme_query_security_support(responder) |
| } |
| MlmeMsg::QuerySpectrumManagementSupport { responder } => { |
| self.on_sme_query_spectrum_management_support(responder) |
| } |
| MlmeMsg::ListMinstrelPeers { responder } => self.on_sme_list_minstrel_peers(responder), |
| MlmeMsg::GetMinstrelStats { responder, req } => { |
| self.on_sme_get_minstrel_stats(responder, &req.peer_addr) |
| } |
| other_message => match &mut self.sta { |
| None => { |
| if let MlmeMsg::ReconnectReq { req, .. } = other_message { |
| self.ctx.device.mlme_control_handle().send_connect_conf( |
| &mut 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!("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(); |
| let txn_id = req.txn_id; |
| let _ = self |
| .scanner |
| .bind(&mut self.ctx) |
| .on_sme_scan( |
| req, |
| |ctx, scanner| channel_state.bind(ctx, scanner, sta), |
| &mut self.chan_sched, |
| ) |
| .map_err(|e| { |
| error!("Scan failed in MLME: {:?}", e); |
| let code = match e { |
| Error::ScanError(scan_error) => scan_error.into(), |
| _ => fidl_mlme::ScanResultCode::InternalError, |
| }; |
| let _ = self |
| .ctx |
| .device |
| .mlme_control_handle() |
| .send_on_scan_end(&mut fidl_mlme::ScanEnd { txn_id, code }) |
| .map_err(|e| { |
| error!("error sending MLME ScanEnd: {}", e); |
| }); |
| }); |
| } |
| |
| pub fn handle_scan_complete(&mut self, status: zx::Status, scan_id: u64) { |
| self.scanner.bind(&mut self.ctx).handle_scan_complete(status, scan_id); |
| } |
| |
| 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 |
| let channel_state = &mut self.channel_state; |
| let sta = self.sta.as_mut(); |
| self.scanner.bind(&mut self.ctx).cancel_ongoing_scan( |
| |ctx, scanner| channel_state.bind(ctx, scanner, sta), |
| &mut self.chan_sched, |
| ); |
| |
| 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, |
| sae_password: req.sae_password, |
| wep_key: req.wep_key.map(|k| *k), |
| security_ie: req.security_ie, |
| }; |
| self.join_device(&req.selected_bss).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, |
| self.ctx.device.wlan_softmac_info().sta_addr, |
| client_capabilities, |
| )); |
| if let Some(sta) = &mut self.sta { |
| sta.bind( |
| &mut self.ctx, |
| &mut self.scanner, |
| &mut self.chan_sched, |
| &mut self.channel_state, |
| ) |
| .start_connecting(); |
| } |
| Ok(()) |
| } |
| Err(e) => { |
| error!("Error setting up device for join: {}", e); |
| // TODO(fxbug.dev/44317): Only one failure code defined in IEEE 802.11-2016 6.3.4.3 |
| // Can we do better? |
| self.ctx.device.mlme_control_handle().send_connect_conf( |
| &mut fidl_mlme::ConnectConfirm { |
| peer_sta_address: bssid, |
| result_code: fidl_ieee80211::StatusCode::JoinFailure, |
| association_id: 0, |
| association_ies: vec![], |
| }, |
| )?; |
| Err(e) |
| } |
| } |
| } |
| |
| fn join_device(&mut self, bss: &BssDescription) -> Result<ClientCapabilities, Error> { |
| let wlan_softmac_info = self.ctx.device.wlan_softmac_info(); |
| let join_caps = derive_join_capabilities( |
| Channel::from(bss.channel), |
| bss.rates(), |
| &crate::ddk_converter::device_info_from_wlan_softmac_info(wlan_softmac_info)?, |
| ) |
| .map_err(|e| { |
| Error::Status( |
| format!("Failed to derive join capabilities: {:?}", e), |
| zx::Status::NOT_SUPPORTED, |
| ) |
| })?; |
| |
| let channel = crate::ddk_converter::ddk_channel_from_fidl(bss.channel.into()); |
| self.set_main_channel(channel) |
| .map_err(|status| Error::Status(format!("Error setting device channel"), status))?; |
| |
| let bss_config = banjo_internal::BssConfig { |
| bssid: bss.bssid.0, |
| bss_type: banjo_internal::BssType::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(|()| join_caps) |
| .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.mlme_control_handle().send_stats_query_resp(&mut resp).map_err(|e| e.into()) |
| } |
| |
| fn on_sme_get_iface_counter_stats( |
| &self, |
| responder: fidl_mlme::MlmeGetIfaceCounterStatsResponder, |
| ) -> Result<(), Error> { |
| let mut resp = |
| fidl_mlme::GetIfaceCounterStatsResponse::ErrorStatus(zx::sys::ZX_ERR_NOT_SUPPORTED); |
| responder.send(&mut resp).map_err(|e| e.into()) |
| } |
| |
| fn on_sme_get_iface_histogram_stats( |
| &self, |
| responder: fidl_mlme::MlmeGetIfaceHistogramStatsResponder, |
| ) -> Result<(), Error> { |
| let mut resp = |
| fidl_mlme::GetIfaceHistogramStatsResponse::ErrorStatus(zx::sys::ZX_ERR_NOT_SUPPORTED); |
| responder.send(&mut resp).map_err(|e| e.into()) |
| } |
| |
| fn on_sme_query_device_info( |
| &self, |
| responder: fidl_mlme::MlmeQueryDeviceInfoResponder, |
| ) -> Result<(), Error> { |
| let wlan_softmac_info = self.ctx.device.wlan_softmac_info(); |
| let mut info = crate::ddk_converter::device_info_from_wlan_softmac_info(wlan_softmac_info)?; |
| responder.send(&mut info).map_err(|e| e.into()) |
| } |
| |
| fn on_sme_query_discovery_support( |
| &self, |
| responder: fidl_mlme::MlmeQueryDiscoverySupportResponder, |
| ) -> Result<(), Error> { |
| let ddk_support = self.ctx.device.discovery_support(); |
| let mut support = crate::ddk_converter::convert_ddk_discovery_support(ddk_support)?; |
| responder.send(&mut support).map_err(|e| e.into()) |
| } |
| |
| fn on_sme_query_mac_sublayer_support( |
| &self, |
| responder: fidl_mlme::MlmeQueryMacSublayerSupportResponder, |
| ) -> Result<(), Error> { |
| let ddk_support = self.ctx.device.mac_sublayer_support(); |
| let mut support = crate::ddk_converter::convert_ddk_mac_sublayer_support(ddk_support)?; |
| responder.send(&mut support).map_err(|e| e.into()) |
| } |
| |
| fn on_sme_query_security_support( |
| &self, |
| responder: fidl_mlme::MlmeQuerySecuritySupportResponder, |
| ) -> Result<(), Error> { |
| let ddk_support = self.ctx.device.security_support(); |
| let mut support = crate::ddk_converter::convert_ddk_security_support(ddk_support)?; |
| responder.send(&mut support).map_err(|e| e.into()) |
| } |
| |
| fn on_sme_query_spectrum_management_support( |
| &self, |
| responder: fidl_mlme::MlmeQuerySpectrumManagementSupportResponder, |
| ) -> Result<(), Error> { |
| let ddk_support = self.ctx.device.spectrum_management_support(); |
| let mut support = |
| crate::ddk_converter::convert_ddk_spectrum_management_support(ddk_support)?; |
| responder.send(&mut support).map_err(|e| e.into()) |
| } |
| |
| fn on_sme_list_minstrel_peers( |
| &self, |
| responder: fidl_mlme::MlmeListMinstrelPeersResponder, |
| ) -> Result<(), Error> { |
| // TODO(fxbug.dev/79543): Implement once Minstrel is in Rust. |
| error!("ListMinstrelPeers is not supported."); |
| let peers = fidl_minstrel::Peers { addrs: vec![] }; |
| let mut resp = fidl_mlme::MinstrelListResponse { peers }; |
| responder.send(&mut resp).map_err(|e| e.into()) |
| } |
| |
| fn on_sme_get_minstrel_stats( |
| &self, |
| responder: fidl_mlme::MlmeGetMinstrelStatsResponder, |
| _addr: &[u8; 6], |
| ) -> Result<(), Error> { |
| // TODO(fxbug.dev/79543): Implement once Minstrel is in Rust. |
| error!("GetMinstrelStats is not supported."); |
| let mut resp = fidl_mlme::MinstrelStatsResponse { peer: None }; |
| responder.send(&mut resp).map_err(|e| e.into()) |
| } |
| |
| pub fn on_eth_frame_tx<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_tx(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, event: TimedEvent) { |
| 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 connect_req: ParsedConnectRequest, |
| pub iface_mac: MacAddr, |
| pub client_capabilities: ClientCapabilities, |
| pub connect_timeout: Option<EventId>, |
| } |
| |
| impl Client { |
| pub fn new( |
| connect_req: ParsedConnectRequest, |
| iface_mac: MacAddr, |
| client_capabilities: ClientCapabilities, |
| ) -> Self { |
| Self { |
| state: Some(States::new_initial()), |
| connect_req, |
| iface_mac, |
| client_capabilities, |
| connect_timeout: None, |
| } |
| } |
| |
| pub fn ssid(&self) -> &Ssid { |
| &self.connect_req.selected_bss.ssid |
| } |
| |
| pub fn bssid(&self) -> Bssid { |
| self.connect_req.selected_bss.bssid |
| } |
| |
| pub fn beacon_period(&self) -> zx::Duration { |
| zx::Duration::from(TimeUnit(self.connect_req.selected_bss.beacon_period)) |
| } |
| |
| pub fn eapol_required(&self) -> bool { |
| self.connect_req.selected_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. |
| || self.connect_req.selected_bss.find_wpa_ie().is_some() |
| } |
| |
| 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, banjo_wlan_softmac::WlanTxInfoFlags(0)) |
| .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> { |
| 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, |
| status_code: fidl_ieee80211::StatusCode, |
| sae_fields: Vec<u8>, |
| ) { |
| self.forward_sae_frame_rx(seq_num, status_code, sae_fields) |
| } |
| |
| fn forward_sae_handshake_ind(&mut self) { |
| self.forward_sae_handshake_ind() |
| } |
| } |
| |
| 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, |
| fidl_ieee80211::StatusCode::Success.into(), |
| &[], |
| ) |
| } |
| |
| /// 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) -> Result<(), Error> { |
| let ssid = self.sta.ssid().clone(); |
| let cap = &self.sta.client_capabilities.0; |
| let capability_info = cap.capability_info.0; |
| let rates: Vec<u8> = cap.rates.iter().map(|r| r.rate()).collect(); |
| let ht_cap = cap.ht_cap; |
| let vht_cap = cap.vht_cap; |
| let security_ie = self.sta.connect_req.security_ie.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(capability_info), |
| listen_interval: 0, |
| }, |
| }, |
| ies: { |
| ssid: ssid, |
| supported_rates: rates, |
| extended_supported_rates: {/* continue rates */}, |
| rsne?: if !security_ie.is_empty() && security_ie[0] == ie::Id::RSNE.0 { |
| rsne::from_bytes(&security_ie[..]) |
| .map_err(|e| format_err!("error parsing rsne {:?} : {:?}", security_ie, e))? |
| .1 |
| }, |
| ht_cap?: ht_cap, |
| vht_cap?: 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, banjo_wlan_softmac::WlanTxInfoFlags(0)) |
| .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 |
| }; |
| |
| // IEEE Std 802.11-2016, Table 9-26 specifies address field contents and their relation |
| // to the addr fields. |
| // TODO(fxbug.dev/51295): Support A-MSDU address field contents. |
| |
| // We do not currently support RA other than the BSS. |
| // TODO(fxbug.dev/45833): Support to_ds = false and alternative RA for TDLS. |
| let to_ds = true; |
| let from_ds = src != self.sta.iface_mac; |
| // Detect when SA != TA, in which case we use addr4. |
| let addr1 = self.sta.bssid().0; |
| let addr2 = self.sta.iface_mac; |
| let addr3 = match (to_ds, from_ds) { |
| (false, false) => self.sta.bssid().0, |
| (false, true) => src, |
| (true, _) => dst, |
| }; |
| let addr4 = if from_ds && to_ds { Some(src) } 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(to_ds) |
| .with_from_ds(from_ds), |
| duration: 0, |
| addr1, |
| addr2, |
| addr3, |
| 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::Addr4?: addr4, |
| 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 => banjo_wlan_softmac::WlanTxInfoFlags::FAVOR_RELIABILITY, |
| _ => banjo_wlan_softmac::WlanTxInfoFlags(0), |
| }; |
| 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 |
| .mlme_control_handle() |
| .send_eapol_ind(&mut fidl_mlme::EapolIndication { |
| src_addr, |
| dst_addr, |
| data: eapol_frame.to_vec(), |
| }) |
| .map_err(|e| e.into()) |
| } |
| |
| /// 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::EapolResultCode::Success, |
| Err(e) => { |
| error!("error sending EAPoL frame: {}", e); |
| fidl_mlme::EapolResultCode::TransmissionFailure |
| } |
| }; |
| |
| // Report transmission result to SME. |
| let result = self |
| .ctx |
| .device |
| .mlme_control_handle() |
| .send_eapol_conf(&mut fidl_mlme::EapolConfirm { result_code, dst_addr: dst }); |
| 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: banjo_wlan_softmac::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_tx<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 |
| } |
| |
| pub fn start_connecting(&mut self) { |
| // Safe: |state| is never None and always replaced with Some(..). |
| let next_state = self.sta.state.take().unwrap().start_connecting(self); |
| self.sta.state.replace(next_state); |
| } |
| |
| pub fn handle_mlme_msg(&mut self, msg: fidl_mlme::MlmeRequest) { |
| // 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); |
| } |
| |
| fn send_connect_conf_failure(&mut self, result_code: fidl_ieee80211::StatusCode) { |
| self.sta.connect_timeout.take(); |
| let bssid = self.sta.connect_req.selected_bss.bssid.0; |
| self.send_connect_conf_failure_with_bssid(bssid, result_code); |
| } |
| |
| /// Send ConnectConf failure with BSSID specified. |
| /// The connect timeout is not cleared as this method may be called with a foreign BSSID. |
| fn send_connect_conf_failure_with_bssid( |
| &mut self, |
| bssid: [u8; 6], |
| result_code: fidl_ieee80211::StatusCode, |
| ) { |
| let mut connect_conf = fidl_mlme::ConnectConfirm { |
| peer_sta_address: bssid, |
| result_code, |
| association_id: 0, |
| association_ies: vec![], |
| }; |
| let result = self.ctx.device.mlme_control_handle().send_connect_conf(&mut connect_conf); |
| if let Err(e) = result { |
| error!("error sending MLME-CONNECT.confirm: {}", e); |
| } |
| } |
| |
| fn send_connect_conf_success(&mut self, association_id: u16, association_ies: &[u8]) { |
| self.sta.connect_timeout.take(); |
| let mut connect_conf = fidl_mlme::ConnectConfirm { |
| peer_sta_address: self.sta.connect_req.selected_bss.bssid.0, |
| result_code: fidl_ieee80211::StatusCode::Success, |
| association_id, |
| association_ies: association_ies.to_vec(), |
| }; |
| let result = self.ctx.device.mlme_control_handle().send_connect_conf(&mut connect_conf); |
| if let Err(e) = result { |
| error!("error sending MLME-CONNECT.confirm: {}", e); |
| } |
| } |
| |
| /// Sends an MLME-DEAUTHENTICATE.indication message to the joined BSS. |
| fn send_deauthenticate_ind( |
| &mut self, |
| reason_code: fidl_ieee80211::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.mlme_control_handle().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_ieee80211::ReasonCode, |
| locally_initiated: LocallyInitiated, |
| ) { |
| let result = self.ctx.device.mlme_control_handle().send_disassociate_ind( |
| &mut fidl_mlme::DisassociateIndication { |
| peer_sta_address: self.sta.bssid().0, |
| reason_code: reason_code, |
| 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, |
| status_code: fidl_ieee80211::StatusCode, |
| sae_fields: Vec<u8>, |
| ) { |
| let result = |
| self.ctx.device.mlme_control_handle().send_on_sae_frame_rx(&mut fidl_mlme::SaeFrame { |
| peer_sta_address: self.sta.bssid().0, |
| seq_num, |
| status_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.mlme_control_handle().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, banjo_wlan_softmac::WlanTxInfoFlags(0)) |
| } |
| |
| 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"), |
| } |
| } |
| } |
| |
| pub struct ParsedConnectRequest { |
| pub selected_bss: BssDescription, |
| pub connect_failure_timeout: u32, |
| pub auth_type: fidl_mlme::AuthenticationTypes, |
| pub sae_password: Vec<u8>, |
| pub wep_key: Option<fidl_mlme::SetKeyDescriptor>, |
| pub security_ie: Vec<u8>, |
| } |
| |
| 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 from<B: ByteSlice>(assoc_resp_hdr: &mac::AssocRespHdr, elements: B) -> Self { |
| let mut parsed_assoc_resp = ParsedAssociateResp { |
| association_id: assoc_resp_hdr.aid, |
| capabilities: assoc_resp_hdr.capabilities, |
| rates: vec![], |
| ht_cap: None, |
| vht_cap: None, |
| }; |
| |
| for (id, body) in Reader::new(elements) { |
| 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_assoc_resp.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_assoc_resp.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_assoc_resp.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_assoc_resp.vht_cap = Some(*vht_cap); |
| } |
| }, |
| // TODO(fxbug.dev/43938): parse vendor ID and include WMM param if exists |
| _ => {} |
| } |
| } |
| parsed_assoc_resp |
| } |
| } |
| |
| 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, test_utils::drain_timeouts}, |
| device::{FakeDevice, LinkStatus}, |
| test_utils::{fake_control_handle, MockWlanRxInfo}, |
| MlmeImpl, |
| }, |
| fidl::endpoints::create_proxy_and_stream, |
| fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_internal as fidl_internal, |
| fuchsia_async as fasync, |
| futures::{task::Poll, StreamExt}, |
| std::convert::TryFrom, |
| wlan_common::{ |
| assert_variant, |
| capabilities::StaCapabilities, |
| channel::Cbw, |
| fake_bss_description, fake_fidl_bss_description, ie, |
| stats::SignalStrengthAverage, |
| test_utils::{fake_capabilities::fake_client_capabilities, fake_frames::*}, |
| timer::{create_timer, TimeStream}, |
| }, |
| 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_common::WlanChannel = banjo_common::WlanChannel { |
| primary: 11, |
| cbw: banjo_common::ChannelBandwidth::CBW20, |
| 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, |
| timer: Option<Timer<super::TimedEvent>>, |
| time_stream: TimeStream<super::TimedEvent>, |
| } |
| |
| impl MockObjects { |
| fn new(executor: &fasync::TestExecutor) -> Self { |
| let (timer, time_stream) = create_timer(); |
| Self { fake_device: FakeDevice::new(executor), timer: Some(timer), time_stream } |
| } |
| |
| 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.timer.take().unwrap(), |
| ); |
| 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, |
| scan_type: fidl_mlme::ScanTypes::Passive, |
| channel_list: vec![SCAN_CHANNEL_PRIMARY], |
| ssid_list: vec![Ssid::try_from("ssid").unwrap().into()], |
| probe_delay: 0, |
| min_channel_time: 100, |
| max_channel_time: 300, |
| } |
| } |
| |
| fn make_client_station() -> Client { |
| let connect_req = ParsedConnectRequest { |
| selected_bss: fake_bss_description!(Open, bssid: BSSID.0), |
| connect_failure_timeout: 100, |
| auth_type: fidl_mlme::AuthenticationTypes::OpenSystem, |
| sae_password: vec![], |
| wep_key: None, |
| security_ie: vec![], |
| }; |
| Client::new(connect_req, IFACE_MAC, fake_client_capabilities()) |
| } |
| |
| fn make_client_station_protected() -> Client { |
| let connect_req = ParsedConnectRequest { |
| selected_bss: fake_bss_description!(Wpa2, bssid: BSSID.0), |
| connect_failure_timeout: 100, |
| auth_type: fidl_mlme::AuthenticationTypes::OpenSystem, |
| sae_password: vec![], |
| wep_key: None, |
| security_ie: RSNE.to_vec(), |
| }; |
| Client::new(connect_req, IFACE_MAC, fake_client_capabilities()) |
| } |
| |
| impl ClientMlme { |
| fn make_client_station(&mut self) { |
| self.sta.replace(make_client_station()); |
| } |
| |
| fn make_client_station_protected(&mut self) { |
| self.sta.replace(make_client_station_protected()); |
| } |
| |
| 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, |
| assoc_resp_ies: vec![], |
| 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); |
| } |
| |
| fn close_controlled_port(&mut self, exec: &fasync::TestExecutor) { |
| let (control_handle, _) = fake_control_handle(exec); |
| self.handle_mlme_msg(fidl_mlme::MlmeRequest::SetControlledPort { |
| req: fidl_mlme::SetControlledPortRequest { |
| peer_sta_address: BSSID.0, |
| state: fidl_mlme::ControlledPortState::Closed, |
| }, |
| control_handle, |
| }); |
| } |
| } |
| |
| #[test] |
| fn spawns_new_sta_on_connect_request_from_sme() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| 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![], |
| }) |
| .expect("valid ConnectRequest 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| 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!(Wpa2, 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![], |
| }) |
| .expect("valid ConnectRequest 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 wpa1_implies_sta_eapol_required() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| 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!(Wpa1, 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![], |
| }) |
| .expect("valid ConnectRequest 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| 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![], |
| }) |
| .expect("valid ConnectRequest 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_connect_req_cancels_ongoing_scan() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| assert!(me.get_bound_client().is_none(), "MLME should not contain client, yet"); |
| |
| me.on_sme_scan(scan_req()); |
| assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY); |
| |
| // There should be a scheduled event for channel scheduler |
| let (_, channel_event) = |
| m.time_stream.try_next().unwrap().expect("Should have scheduled a timed event"); |
| assert_variant!(channel_event.event, super::TimedEvent::ChannelScheduler); |
| |
| // Send a connect request |
| let bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap()); |
| me.on_sme_connect(fidl_mlme::ConnectRequest { |
| selected_bss: bss.clone(), |
| connect_failure_timeout: 100, |
| auth_type: fidl_mlme::AuthenticationTypes::OpenSystem, |
| sae_password: vec![], |
| wep_key: None, |
| security_ie: vec![], |
| }) |
| .expect("valid ConnectRequest should be handled successfully"); |
| |
| // Verify that scan is canceled |
| let msg = |
| m.fake_device.next_mlme_msg::<fidl_mlme::ScanEnd>().expect("error reading SCAN.end"); |
| assert_eq!(msg.txn_id, scan_req().txn_id); |
| assert_eq!(msg.code, fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware); |
| |
| // Verify that channel has switched to the BSS's |
| assert_eq!(me.ctx.device.channel().primary, bss.channel.primary); |
| |
| // Verify that time scheduler timeout is no-op |
| me.handle_timed_event(channel_event.id, channel_event.event); |
| assert_eq!(me.ctx.device.channel().primary, bss.channel.primary); |
| } |
| |
| #[test] |
| fn test_ensure_on_channel_followed_by_scheduled_scan() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 |
| let (_, timed_event) = |
| m.time_stream.try_next().unwrap().expect("Should have scheduled a timed event"); |
| me.handle_timed_event(timed_event.id, timed_event.event); |
| assert_eq!(me.ctx.device.channel().primary, SCAN_CHANNEL_PRIMARY); |
| } |
| |
| #[test] |
| fn test_active_scan_scheduling() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 |
| let (_, channel_event) = |
| m.time_stream.try_next().unwrap().expect("Should have scheduled a timed event"); |
| assert_variant!(channel_event.event, super::TimedEvent::ChannelScheduler); |
| let (_, probe_delay_event) = |
| m.time_stream.try_next().unwrap().expect("Should have scheduled a timed event"); |
| assert_variant!(probe_delay_event.event, super::TimedEvent::ScannerProbeDelay(_)); |
| |
| me.handle_timed_event(probe_delay_event.id, probe_delay_event.event); |
| |
| // 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, 8, // supp_rates id and length |
| 0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, // supp_rates |
| 50, 4, // extended supported rates id and length |
| 0x30, 0x48, 0x60, 0x6c // extended supported 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 |
| me.handle_timed_event(channel_event.id, channel_event.event); |
| 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::ScanResultCode::Success); |
| } |
| |
| #[test] |
| fn test_no_power_state_frame_when_client_is_not_connected() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 |
| let (_, channel_event) = |
| m.time_stream.try_next().unwrap().expect("Should have scheduled a timed event"); |
| me.handle_timed_event(channel_event.id, channel_event.event); |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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.time_stream.try_next().unwrap(); |
| |
| // 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 |
| let (_, channel_event) = |
| m.time_stream.try_next().unwrap().expect("Should have scheduled a timed event"); |
| me.handle_timed_event(channel_event.id, channel_event.event); |
| 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 (_, timed_event) = |
| m.time_stream.try_next().unwrap().expect("Should have scheduled a timed event"); |
| me.handle_timed_event(timed_event.id, timed_event.event); |
| assert_eq!(m.fake_device.wlan_queue.len(), 0); |
| m.fake_device |
| .next_mlme_msg::<fidl_internal::SignalReportIndication>() |
| .expect("error reading SignalReport.indication"); |
| } |
| } |
| |
| #[test] |
| fn test_auto_deauth_uninterrupted_interval() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 (_, timed_event) = |
| m.time_stream.try_next().unwrap().expect("Should have scheduled a timed event"); |
| |
| // Verify that triggering event at deadline causes deauth |
| me.handle_timed_event(timed_event.id, timed_event.event); |
| m.fake_device |
| .next_mlme_msg::<fidl_internal::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_ieee80211::ReasonCode::LeavingNetworkDeauth, |
| locally_initiated: true, |
| } |
| ); |
| } |
| |
| #[test] |
| fn test_auto_deauth_received_beacon() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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_rx( |
| BEACON_FRAME, |
| banjo_wlan_softmac::WlanRxInfo { |
| rx_flags: banjo_wlan_softmac::WlanRxInfoFlags(0), |
| valid_fields: 0, |
| phy: banjo_common::WlanPhyType::DSSS, |
| data_rate: 0, |
| channel: banjo_common::WlanChannel { |
| primary: 11, |
| cbw: banjo_common::ChannelBandwidth::CBW20, |
| secondary80: 0, |
| }, |
| mcs: 0, |
| rssi_dbm: 0, |
| snr_dbh: 0, |
| }, |
| ); |
| |
| // 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 (_, timed_event2) = |
| m.time_stream.try_next().unwrap().expect("Should have scheduled a timed event"); |
| |
| // Verify that triggering event at new deadline causes deauth |
| me.handle_timed_event(timed_event2.id, timed_event2.event); |
| m.fake_device |
| .next_mlme_msg::<fidl_internal::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_ieee80211::ReasonCode::LeavingNetworkDeauth, |
| locally_initiated: true, |
| } |
| ); |
| } |
| |
| #[test] |
| fn client_send_open_auth_frame() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| let connect_req = ParsedConnectRequest { |
| selected_bss: fake_bss_description!(Wpa2, |
| ssid: Ssid::try_from([11, 22, 33, 44]).unwrap(), |
| bssid: BSSID.0, |
| ), |
| connect_failure_timeout: 100, |
| auth_type: fidl_mlme::AuthenticationTypes::OpenSystem, |
| sae_password: vec![], |
| wep_key: None, |
| security_ie: RSNE.to_vec(), |
| }; |
| let client_capabilities = ClientCapabilities(StaCapabilities { |
| capability_info: CapabilityInfo(0x1234), |
| rates: vec![8u8, 7, 6, 5, 4, 3, 2, 1, 0].into_iter().map(ie::SupportedRate).collect(), |
| ht_cap: ie::parse_ht_capabilities(&(0..26).collect::<Vec<u8>>()[..]).map(|h| *h).ok(), |
| vht_cap: ie::parse_vht_capabilities(&(100..112).collect::<Vec<u8>>()[..]) |
| .map(|v| *v) |
| .ok(), |
| }); |
| me.sta.replace(Client::new(connect_req, IFACE_MAC, client_capabilities)); |
| let mut client = me.get_bound_client().expect("client should be present"); |
| client.send_assoc_req_frame().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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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(IFACE_MAC, [4; 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 |
| 7, 7, 7, 7, 7, 7, // addr2 |
| 4, 4, 4, 4, 4, 4, // 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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( |
| IFACE_MAC, |
| [4; 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 |
| 7, 7, 7, 7, 7, 7, // addr2 |
| 4, 4, 4, 4, 4, 4, // 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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( |
| IFACE_MAC, |
| [4; 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 |
| 7, 7, 7, 7, 7, 7, // addr2 |
| 4, 4, 4, 4, 4, 4, // 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_data_frame_from_ds() { |
| let payload = vec![5; 8]; |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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([3; 6], [4; 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, 0b000000_11, // FC (ToDS=1, FromDS=1) |
| 0, 0, // Duration |
| 6, 6, 6, 6, 6, 6, // addr1 |
| 7, 7, 7, 7, 7, 7, // addr2 = IFACE_MAC |
| 4, 4, 4, 4, 4, 4, // addr3 |
| 0x10, 0, // Sequence Control |
| 3, 3, 3, 3, 3, 3, // addr4 |
| // 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_deauthentication_notification() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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(fidl_ieee80211::ReasonCode::ApInitiated.into()) |
| .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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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[..], MockWlanRxInfo::default().into()); |
| |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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[..], MockWlanRxInfo::default().into()); |
| |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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[..], MockWlanRxInfo::default().into()); |
| |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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[..], MockWlanRxInfo::default().into()); |
| |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| me.make_client_station_protected(); |
| let mut client = me.get_bound_client().expect("client should be present"); |
| client.move_to_associated_state(); |
| client.close_controlled_port(&exec); |
| |
| client.on_mac_frame(&data_frame[..], MockWlanRxInfo::default().into()); |
| |
| // 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| me.make_client_station_protected(); |
| let mut client = me.get_bound_client().expect("client should be present"); |
| client.move_to_associated_state(); |
| client.close_controlled_port(&exec); |
| |
| client.on_mac_frame(&eapol_frame[..], MockWlanRxInfo::default().into()); |
| |
| // 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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[..], MockWlanRxInfo::default().into()); |
| |
| // 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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::EapolResultCode::Success, |
| dst_addr: BSSID.0, |
| } |
| ); |
| |
| // 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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::EapolResultCode::TransmissionFailure, |
| dst_addr: [2; 6], |
| } |
| ); |
| |
| // Verify EAPoL frame was not sent over the air. |
| assert!(m.fake_device.wlan_queue.is_empty()); |
| } |
| |
| #[test] |
| fn send_ps_poll_frame() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut mock = MockObjects::new(&exec); |
| 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 exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut mock = MockObjects::new(&exec); |
| 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_connect_conf() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| me.make_client_station(); |
| let mut client = me.get_bound_client().expect("client should be present"); |
| |
| client.send_connect_conf_success(42, &[0, 5, 3, 4, 5, 6, 7]); |
| let connect_conf = m |
| .fake_device |
| .next_mlme_msg::<fidl_mlme::ConnectConfirm>() |
| .expect("error reading Connect.confirm"); |
| assert_eq!( |
| connect_conf, |
| fidl_mlme::ConnectConfirm { |
| peer_sta_address: BSSID.0, |
| result_code: fidl_ieee80211::StatusCode::Success, |
| association_id: 42, |
| association_ies: vec![0, 5, 3, 4, 5, 6, 7], |
| } |
| ); |
| } |
| |
| #[test] |
| fn client_send_failed_connect_conf() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| me.make_client_station(); |
| let mut client = me.get_bound_client().expect("client should be present"); |
| client.send_connect_conf_failure(fidl_ieee80211::StatusCode::DeniedNoMoreStas); |
| let connect_conf = m |
| .fake_device |
| .next_mlme_msg::<fidl_mlme::ConnectConfirm>() |
| .expect("error reading Connect.confirm"); |
| assert_eq!( |
| connect_conf, |
| fidl_mlme::ConnectConfirm { |
| peer_sta_address: BSSID.0, |
| result_code: fidl_ieee80211::StatusCode::DeniedNoMoreStas, |
| association_id: 0, |
| association_ies: vec![], |
| } |
| ); |
| } |
| |
| #[test] |
| fn client_send_scan_end_on_mlme_scan_busy() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| me.make_client_station(); |
| |
| // Issue a second scan before the first finishes |
| me.on_sme_scan(scan_req()); |
| me.on_sme_scan(fidl_mlme::ScanRequest { txn_id: 1338, ..scan_req() }); |
| |
| let scan_end = m |
| .fake_device |
| .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 } |
| ); |
| } |
| |
| #[test] |
| fn client_send_scan_end_on_offload_scan_busy() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| |
| // Configure the fake device to offload scan |
| m.fake_device.discovery_support.scan_offload.supported = true; |
| let mut me = m.make_mlme(); |
| me.make_client_station(); |
| |
| // Issue a second scan before the first finishes |
| me.on_sme_scan(scan_req()); |
| me.on_sme_scan(fidl_mlme::ScanRequest { txn_id: 1338, ..scan_req() }); |
| |
| let scan_end = m |
| .fake_device |
| .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 } |
| ); |
| } |
| |
| #[test] |
| fn client_send_scan_end_on_mlme_scan_invalid_args() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| |
| 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, |
| }); |
| let scan_end = m |
| .fake_device |
| .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 } |
| ); |
| } |
| |
| #[test] |
| fn client_send_scan_end_on_offload_scan_invalid_args() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| |
| // Configure the fake device to offload scan |
| m.fake_device.discovery_support.scan_offload.supported = true; |
| let mut me = m.make_mlme(); |
| |
| 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, |
| }); |
| let scan_end = m |
| .fake_device |
| .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 } |
| ); |
| } |
| |
| #[test] |
| fn client_send_scan_end_on_offload_scan_fails() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| |
| // Configure the fake device to offload scan and fail on passive scans |
| m.fake_device.discovery_support.scan_offload.supported = true; |
| let device = m.fake_device.as_device_fail_start_passive_scan(); |
| let mut me = m.make_mlme_with_device(device); |
| |
| me.make_client_station(); |
| me.on_sme_scan(scan_req()); |
| let scan_end = m |
| .fake_device |
| .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 } |
| ); |
| } |
| |
| #[test] |
| fn mlme_respond_to_query_device_info() { |
| let mut exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| |
| let (mlme_proxy, mut mlme_req_stream) = create_proxy_and_stream::<fidl_mlme::MlmeMarker>() |
| .expect("failed to create Mlme proxy"); |
| let mut query_fut = mlme_proxy.query_device_info(); |
| assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Pending); |
| let mlme_req = assert_variant!(exec.run_until_stalled(&mut mlme_req_stream.next()), Poll::Ready(Some(Ok(req))) => match req { |
| fidl_mlme::MlmeRequest::QueryDeviceInfo { .. } => req, |
| other => panic!("unexpected MlmeRequest: {:?}", other), |
| }); |
| |
| assert_variant!(me.handle_mlme_msg(mlme_req), Ok(())); |
| let info = assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Ready(Ok(r)) => r); |
| let expected = crate::ddk_converter::device_info_from_wlan_softmac_info(m.fake_device.info) |
| .expect("Failed to convert DDK WlanSoftmacInfo"); |
| assert_eq!(info, expected); |
| } |
| |
| #[test] |
| fn mlme_respond_to_query_discovery_support() { |
| let mut exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| |
| let (mlme_proxy, mut mlme_req_stream) = create_proxy_and_stream::<fidl_mlme::MlmeMarker>() |
| .expect("failed to create Mlme proxy"); |
| let mut query_fut = mlme_proxy.query_discovery_support(); |
| assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Pending); |
| let mlme_req = assert_variant!(exec.run_until_stalled(&mut mlme_req_stream.next()), Poll::Ready(Some(Ok(req))) => match req { |
| fidl_mlme::MlmeRequest::QueryDiscoverySupport { .. } => req, |
| other => panic!("unexpected MlmeRequest: {:?}", other), |
| }); |
| |
| assert_variant!(me.handle_mlme_msg(mlme_req), Ok(())); |
| let resp = assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Ready(Ok(r)) => r); |
| assert_eq!(resp.scan_offload.supported, false); |
| assert_eq!(resp.probe_response_offload.supported, false); |
| } |
| |
| #[test] |
| fn mlme_respond_to_query_mac_sublayer_support() { |
| let mut exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| |
| let (mlme_proxy, mut mlme_req_stream) = create_proxy_and_stream::<fidl_mlme::MlmeMarker>() |
| .expect("failed to create Mlme proxy"); |
| let mut query_fut = mlme_proxy.query_mac_sublayer_support(); |
| assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Pending); |
| let mlme_req = assert_variant!(exec.run_until_stalled(&mut mlme_req_stream.next()), Poll::Ready(Some(Ok(req))) => match req { |
| fidl_mlme::MlmeRequest::QueryMacSublayerSupport { .. } => req, |
| other => panic!("unexpected MlmeRequest: {:?}", other), |
| }); |
| |
| assert_variant!(me.handle_mlme_msg(mlme_req), Ok(())); |
| let resp = assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Ready(Ok(r)) => r); |
| assert_eq!(resp.rate_selection_offload.supported, false); |
| assert_eq!(resp.data_plane.data_plane_type, fidl_common::DataPlaneType::EthernetDevice); |
| assert_eq!(resp.device.is_synthetic, true); |
| assert_eq!( |
| resp.device.mac_implementation_type, |
| fidl_common::MacImplementationType::Softmac |
| ); |
| assert_eq!(resp.device.tx_status_report_supported, true); |
| } |
| |
| #[test] |
| fn mlme_respond_to_query_security_support() { |
| let mut exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| |
| let (mlme_proxy, mut mlme_req_stream) = create_proxy_and_stream::<fidl_mlme::MlmeMarker>() |
| .expect("failed to create Mlme proxy"); |
| let mut query_fut = mlme_proxy.query_security_support(); |
| assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Pending); |
| let mlme_req = assert_variant!(exec.run_until_stalled(&mut mlme_req_stream.next()), Poll::Ready(Some(Ok(req))) => match req { |
| fidl_mlme::MlmeRequest::QuerySecuritySupport { .. } => req, |
| other => panic!("unexpected MlmeRequest: {:?}", other), |
| }); |
| |
| assert_variant!(me.handle_mlme_msg(mlme_req), Ok(())); |
| let resp = assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Ready(Ok(r)) => r); |
| assert_eq!(resp.mfp.supported, false); |
| assert_eq!(resp.sae.driver_handler_supported, false); |
| assert_eq!(resp.sae.sme_handler_supported, false); |
| } |
| |
| #[test] |
| fn mlme_respond_to_query_spectrum_management_support() { |
| let mut exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| |
| let (mlme_proxy, mut mlme_req_stream) = create_proxy_and_stream::<fidl_mlme::MlmeMarker>() |
| .expect("failed to create Mlme proxy"); |
| let mut query_fut = mlme_proxy.query_spectrum_management_support(); |
| assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Pending); |
| let mlme_req = assert_variant!(exec.run_until_stalled(&mut mlme_req_stream.next()), Poll::Ready(Some(Ok(req))) => match req { |
| fidl_mlme::MlmeRequest::QuerySpectrumManagementSupport { .. } => req, |
| other => panic!("unexpected MlmeRequest: {:?}", other), |
| }); |
| |
| assert_variant!(me.handle_mlme_msg(mlme_req), Ok(())); |
| let resp = assert_variant!(exec.run_until_stalled(&mut query_fut), Poll::Ready(Ok(r)) => r); |
| assert_eq!(resp.dfs.supported, true); |
| } |
| |
| #[test] |
| fn mlme_connect_unprotected_happy_path() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| let (control_handle, _) = fake_control_handle(&exec); |
| let connect_req = fidl_mlme::ConnectRequest { |
| selected_bss: fake_fidl_bss_description!(Open, |
| ssid: Ssid::try_from("ssid").unwrap().into(), |
| bssid: BSSID.0, |
| channel: Channel::new(6, Cbw::Cbw40), |
| ), |
| connect_failure_timeout: 100, |
| auth_type: fidl_mlme::AuthenticationTypes::OpenSystem, |
| sae_password: vec![], |
| wep_key: None, |
| security_ie: vec![], |
| }; |
| let result = me.handle_mlme_msg(fidl_mlme::MlmeRequest::ConnectReq { |
| req: connect_req, |
| control_handle, |
| }); |
| assert_variant!(result, Ok(())); |
| |
| // Verify an event was queued up in the timer. |
| assert_variant!(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.wlan_queue.len(), 1); |
| let (frame, _txflags) = m.fake_device.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.on_mac_frame_rx(&auth_resp_success[..], MockWlanRxInfo::default().into()); |
| |
| // Verify association request frame was went to AP |
| assert_eq!(m.fake_device.wlan_queue.len(), 1); |
| let (frame, _txflags) = m.fake_device.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.on_mac_frame_rx(&assoc_resp_success[..], MockWlanRxInfo::default().into()); |
| |
| // Verify a successful connect conf is sent |
| let msg = |
| m.fake_device.next_mlme_msg::<fidl_mlme::ConnectConfirm>().expect("expect ConnectConf"); |
| assert_eq!( |
| msg, |
| fidl_mlme::ConnectConfirm { |
| peer_sta_address: BSSID.0, |
| 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.link_status, LinkStatus::UP); |
| } |
| |
| #[test] |
| fn mlme_connect_protected_happy_path() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| let (control_handle, _) = fake_control_handle(&exec); |
| let connect_req = fidl_mlme::ConnectRequest { |
| selected_bss: fake_fidl_bss_description!(Wpa2, |
| ssid: Ssid::try_from("ssid").unwrap().into(), |
| bssid: BSSID.0, |
| channel: Channel::new(6, Cbw::Cbw40), |
| ), |
| 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 |
| ], |
| }; |
| let result = me.handle_mlme_msg(fidl_mlme::MlmeRequest::ConnectReq { |
| req: connect_req, |
| control_handle, |
| }); |
| assert_variant!(result, Ok(())); |
| |
| // Verify an event was queued up in the timer. |
| assert_variant!(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.wlan_queue.len(), 1); |
| let (frame, _txflags) = m.fake_device.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.on_mac_frame_rx(&auth_resp_success[..], MockWlanRxInfo::default().into()); |
| |
| // Verify association request frame was went to AP |
| assert_eq!(m.fake_device.wlan_queue.len(), 1); |
| let (frame, _txflags) = m.fake_device.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.on_mac_frame_rx(&assoc_resp_success[..], MockWlanRxInfo::default().into()); |
| |
| // Verify a successful connect conf is sent |
| let msg = |
| m.fake_device.next_mlme_msg::<fidl_mlme::ConnectConfirm>().expect("expect ConnectConf"); |
| assert_eq!( |
| msg, |
| fidl_mlme::ConnectConfirm { |
| peer_sta_address: BSSID.0, |
| 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.link_status, LinkStatus::DOWN); |
| |
| // Send a request to open controlled port |
| let (control_handle, _) = fake_control_handle(&exec); |
| me.handle_mlme_message(fidl_mlme::MlmeRequest::SetControlledPort { |
| req: fidl_mlme::SetControlledPortRequest { |
| peer_sta_address: BSSID.0, |
| state: fidl_mlme::ControlledPortState::Open, |
| }, |
| control_handle, |
| }) |
| .expect("expect sending msg to succeed"); |
| |
| // Verify that link is now up |
| assert_eq!(m.fake_device.link_status, LinkStatus::UP); |
| } |
| |
| #[test] |
| fn mlme_connect_vht() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| let (control_handle, _) = fake_control_handle(&exec); |
| let connect_req = fidl_mlme::ConnectRequest { |
| selected_bss: fake_fidl_bss_description!(Open, |
| ssid: Ssid::try_from("ssid").unwrap().into(), |
| bssid: BSSID.0, |
| channel: Channel::new(36, Cbw::Cbw40), |
| ), |
| connect_failure_timeout: 100, |
| auth_type: fidl_mlme::AuthenticationTypes::OpenSystem, |
| sae_password: vec![], |
| wep_key: None, |
| security_ie: vec![], |
| }; |
| let result = me.handle_mlme_msg(fidl_mlme::MlmeRequest::ConnectReq { |
| req: connect_req, |
| control_handle, |
| }); |
| assert_variant!(result, Ok(())); |
| |
| // Verify an event was queued up in the timer. |
| assert_variant!(drain_timeouts(&mut m.time_stream).get(&TimedEventClass::Connecting), Some(ids) => { |
| assert_eq!(ids.len(), 1); |
| }); |
| |
| // Auth frame |
| assert_eq!(m.fake_device.wlan_queue.len(), 1); |
| let (_frame, _txflags) = m.fake_device.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.on_mac_frame_rx(&auth_resp_success[..], MockWlanRxInfo::default().into()); |
| |
| // Verify association request frame was went to AP |
| assert_eq!(m.fake_device.wlan_queue.len(), 1); |
| let (frame, _txflags) = m.fake_device.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[..]); |
| } |
| |
| #[test] |
| fn mlme_connect_timeout() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| let (control_handle, _) = fake_control_handle(&exec); |
| let connect_req = fidl_mlme::ConnectRequest { |
| selected_bss: fake_fidl_bss_description!(Open, bssid: BSSID.0), |
| connect_failure_timeout: 100, |
| auth_type: fidl_mlme::AuthenticationTypes::OpenSystem, |
| sae_password: vec![], |
| wep_key: None, |
| security_ie: vec![], |
| }; |
| let result = me.handle_mlme_msg(fidl_mlme::MlmeRequest::ConnectReq { |
| req: connect_req, |
| control_handle, |
| }); |
| assert_variant!(result, Ok(())); |
| |
| // Verify an event was queued up in the timer. |
| let (event, id) = assert_variant!(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.wlan_queue.len(), 1); |
| let (_frame, _txflags) = m.fake_device.wlan_queue.remove(0); |
| |
| // Send connect timeout |
| me.handle_timed_event(id, event); |
| |
| // Verify a connect confirm message was sent |
| let msg = m.fake_device.next_mlme_msg::<fidl_mlme::ConnectConfirm>().expect("expect msg"); |
| assert_eq!( |
| msg, |
| fidl_mlme::ConnectConfirm { |
| peer_sta_address: BSSID.0, |
| result_code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout, |
| association_id: 0, |
| association_ies: vec![], |
| }, |
| ); |
| } |
| |
| #[test] |
| fn mlme_reconnect_no_sta() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| let (control_handle, _) = fake_control_handle(&exec); |
| |
| let reconnect_req = fidl_mlme::ReconnectRequest { peer_sta_address: [1, 2, 3, 4, 5, 6] }; |
| let result = me.handle_mlme_msg(fidl_mlme::MlmeRequest::ReconnectReq { |
| req: reconnect_req, |
| control_handle, |
| }); |
| assert_variant!(result, Err(Error::Status(_, zx::Status::BAD_STATE))); |
| |
| // Verify a connect confirm message was sent |
| let msg = m.fake_device.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![], |
| }, |
| ); |
| } |
| |
| #[test] |
| fn mlme_respond_to_stats_query_with_empty_response() { |
| let exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| let (control_handle, _) = fake_control_handle(&exec); |
| let stats_query_req = fidl_mlme::MlmeRequest::StatsQueryReq { control_handle }; |
| 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 mlme_respond_to_get_iface_counter_stats_with_error_status() { |
| let mut exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| |
| let (mlme_proxy, mut mlme_req_stream) = create_proxy_and_stream::<fidl_mlme::MlmeMarker>() |
| .expect("failed to create Mlme proxy"); |
| let mut stats_fut = mlme_proxy.get_iface_counter_stats(); |
| assert_variant!(exec.run_until_stalled(&mut stats_fut), Poll::Pending); |
| let mlme_req = assert_variant!(exec.run_until_stalled(&mut mlme_req_stream.next()), Poll::Ready(Some(Ok(req))) => match req { |
| fidl_mlme::MlmeRequest::GetIfaceCounterStats { .. } => req, |
| other => panic!("unexpected MlmeRequest: {:?}", other), |
| }); |
| |
| assert_variant!(me.handle_mlme_msg(mlme_req), Ok(())); |
| let resp = assert_variant!(exec.run_until_stalled(&mut stats_fut), Poll::Ready(Ok(r)) => r); |
| assert_eq!( |
| resp, |
| fidl_mlme::GetIfaceCounterStatsResponse::ErrorStatus(zx::sys::ZX_ERR_NOT_SUPPORTED) |
| ); |
| } |
| |
| #[test] |
| fn mlme_respond_to_get_iface_histogram_stats_with_error_status() { |
| let mut exec = fasync::TestExecutor::new().expect("failed to create an executor"); |
| let mut m = MockObjects::new(&exec); |
| let mut me = m.make_mlme(); |
| |
| let (mlme_proxy, mut mlme_req_stream) = create_proxy_and_stream::<fidl_mlme::MlmeMarker>() |
| .expect("failed to create Mlme proxy"); |
| let mut stats_fut = mlme_proxy.get_iface_histogram_stats(); |
| assert_variant!(exec.run_until_stalled(&mut stats_fut), Poll::Pending); |
| let mlme_req = assert_variant!(exec.run_until_stalled(&mut mlme_req_stream.next()), Poll::Ready(Some(Ok(req))) => match req { |
| fidl_mlme::MlmeRequest::GetIfaceHistogramStats { .. } => req, |
| other => panic!("unexpected MlmeRequest: {:?}", other), |
| }); |
| |
| assert_variant!(me.handle_mlme_msg(mlme_req), Ok(())); |
| let resp = assert_variant!(exec.run_until_stalled(&mut stats_fut), Poll::Ready(Ok(r)) => r); |
| assert_eq!( |
| resp, |
| fidl_mlme::GetIfaceHistogramStatsResponse::ErrorStatus(zx::sys::ZX_ERR_NOT_SUPPORTED) |
| ); |
| } |
| |
| #[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)); |
| } |
| } |