| // Copyright 2023 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| use super::packets::ieee80211::MacAddress; |
| use super::packets::mac80211_hwsim::{HwsimCmd, HwsimMsg, HwsimMsgHdr, NlMsgHdr}; |
| use crate::wifi::frame::Frame; |
| use crate::wifi::hwsim_attr_set::HwsimAttrSet; |
| use anyhow::{anyhow, Context}; |
| use bytes::Bytes; |
| use log::{debug, info, warn}; |
| use pdl_runtime::Packet; |
| use std::collections::{HashMap, HashSet}; |
| use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; |
| use std::sync::{Arc, RwLock}; |
| const NLMSG_MIN_TYPE: u16 = 0x10; |
| // Default values for mac80211_hwsim. |
| const RX_RATE: u32 = 0; |
| const SIGNAL: u32 = 4294967246; // -50 |
| |
| #[allow(dead_code)] |
| #[derive(Debug)] |
| pub enum HwsimCmdEnum { |
| Unspec, |
| Register, |
| Frame(Box<Frame>), |
| TxInfoFrame, |
| NewRadio, |
| DelRadio, |
| GetRadio, |
| AddMacAddr, |
| DelMacAddr, |
| } |
| |
| #[derive(Clone)] |
| struct Station { |
| client_id: u32, |
| // Ieee80211 source address |
| addr: MacAddress, |
| // Hwsim virtual address from HWSIM_ATTR_ADDR_TRANSMITTER |
| // Used to create the HwsimMsg to stations. |
| hwsim_addr: MacAddress, |
| } |
| |
| impl Station { |
| fn new(client_id: u32, addr: MacAddress, hwsim_addr: MacAddress) -> Self { |
| Self { client_id, addr, hwsim_addr } |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct Client { |
| pub enabled: Arc<AtomicBool>, |
| pub tx_count: Arc<AtomicU32>, |
| pub rx_count: Arc<AtomicU32>, |
| } |
| |
| impl Client { |
| fn new() -> Self { |
| Self { |
| enabled: Arc::new(AtomicBool::new(true)), |
| tx_count: Arc::new(AtomicU32::new(0)), |
| rx_count: Arc::new(AtomicU32::new(0)), |
| } |
| } |
| } |
| |
| pub struct Medium { |
| callback: HwsimCmdCallback, |
| // Ieee80211 source address |
| stations: RwLock<HashMap<MacAddress, Arc<Station>>>, |
| clients: RwLock<HashMap<u32, Client>>, |
| // BSSID. MAC address of the access point in WiFi Service. |
| hostapd_bssid: MacAddress, |
| // Simulate the re-transmission of frames sent to hostapd |
| ap_simulation: bool, |
| } |
| |
| type HwsimCmdCallback = fn(u32, &Bytes); |
| impl Medium { |
| pub fn new(callback: HwsimCmdCallback) -> Medium { |
| // Defined in external/qemu/android-qemu2-glue/emulation/WifiService.cpp |
| // TODO: Use hostapd_bssid to initialize hostapd. |
| let bssid_bytes: [u8; 6] = [0x00, 0x13, 0x10, 0x85, 0xfe, 0x01]; |
| Self { |
| callback, |
| stations: RwLock::new(HashMap::new()), |
| clients: RwLock::new(HashMap::new()), |
| hostapd_bssid: MacAddress::from(&bssid_bytes), |
| ap_simulation: true, |
| } |
| } |
| |
| pub fn add(&self, client_id: u32) { |
| let _ = self.clients.write().unwrap().entry(client_id).or_insert_with(|| { |
| info!("Insert client {}", client_id); |
| Client::new() |
| }); |
| } |
| |
| pub fn remove(&self, client_id: u32) { |
| self.stations.write().unwrap().retain(|_, s| s.client_id != client_id); |
| self.clients.write().unwrap().remove(&client_id); |
| } |
| |
| pub fn reset(&self, client_id: u32) { |
| if let Some(client) = self.clients.read().unwrap().get(&client_id) { |
| client.enabled.store(true, Ordering::Relaxed); |
| client.tx_count.store(0, Ordering::Relaxed); |
| client.rx_count.store(0, Ordering::Relaxed); |
| } |
| } |
| |
| pub fn get(&self, client_id: u32) -> Option<Client> { |
| self.clients.read().unwrap().get(&client_id).map(|c| c.to_owned()) |
| } |
| |
| fn contains_client(&self, client_id: u32) -> bool { |
| self.clients.read().unwrap().contains_key(&client_id) |
| } |
| |
| fn stations(&self) -> impl Iterator<Item = Arc<Station>> { |
| self.stations.read().unwrap().clone().into_values() |
| } |
| |
| fn contains_station(&self, addr: &MacAddress) -> bool { |
| self.stations.read().unwrap().contains_key(addr) |
| } |
| |
| fn get_station(&self, addr: &MacAddress) -> anyhow::Result<Arc<Station>> { |
| self.stations.read().unwrap().get(addr).context("get station").cloned() |
| } |
| |
| /// Process commands from the kernel's mac80211_hwsim subsystem. |
| /// |
| /// This is the processing that will be implemented: |
| /// |
| /// * The source MacAddress in 802.11 frames is re-mapped to a globally |
| /// unique MacAddress because resumed Emulator AVDs appear with the |
| /// same address. |
| /// |
| /// * 802.11 frames sent between stations |
| /// |
| /// * 802.11 multicast frames are re-broadcast to connected stations. |
| /// |
| pub fn process(&self, client_id: u32, packet: &Bytes) -> bool { |
| self.process_internal(client_id, packet).unwrap_or_else(move |e| { |
| // TODO: add this error to the netsim_session_stats |
| warn!("error processing wifi {e}"); |
| false |
| }) |
| } |
| |
| fn process_internal(&self, client_id: u32, packet: &Bytes) -> anyhow::Result<bool> { |
| let hwsim_msg = HwsimMsg::decode_full(packet)?; |
| |
| // The virtio handler only accepts HWSIM_CMD_FRAME, HWSIM_CMD_TX_INFO_FRAME and HWSIM_CMD_REPORT_PMSR |
| // in https://source.corp.google.com/h/kernel/pub/scm/linux/kernel/git/torvalds/linux/+/master:drivers/net/wireless/virtual/mac80211_hwsim.c |
| match hwsim_msg.hwsim_hdr.hwsim_cmd { |
| HwsimCmd::Frame => { |
| let frame = Frame::parse(&hwsim_msg)?; |
| // Incoming frame must contain transmitter, flag, cookie, and tx_info fields. |
| if frame.transmitter.is_none() |
| || frame.flags.is_none() |
| || frame.cookie.is_none() |
| || frame.tx_info.is_none() |
| { |
| return Err(anyhow!("Missing Hwsim attributes for incoming packet")); |
| } |
| // Use as receiver for outgoing HwsimMsg. |
| let hwsim_addr = frame.transmitter.context("transmitter")?; |
| let flags = frame.flags.context("flags")?; |
| let cookie = frame.cookie.context("cookie")?; |
| debug!( |
| "Frame chip {}, transmitter {}, flags {}, cookie {}, ieee80211 {}", |
| client_id, hwsim_addr, flags, cookie, frame.ieee80211 |
| ); |
| let src_addr = frame.ieee80211.get_source(); |
| // Creates Stations on the fly when there is no config file. |
| // WiFi Direct will use a randomized mac address for probing |
| // new networks. This block associates the new mac with the station. |
| let source = self |
| .stations |
| .write() |
| .unwrap() |
| .entry(src_addr) |
| .or_insert_with(|| { |
| info!( |
| "Insert station with client id {}, hwsimaddr: {}, \ |
| Ieee80211 addr: {}", |
| client_id, hwsim_addr, src_addr |
| ); |
| Arc::new(Station::new(client_id, src_addr, hwsim_addr)) |
| }) |
| .clone(); |
| if !self.contains_client(client_id) { |
| warn!("Client {} is missing", client_id); |
| self.add(client_id); |
| } |
| self.queue_frame(frame, &source) |
| } |
| _ => { |
| info!("Another command found {:?}", hwsim_msg); |
| Ok(false) |
| } |
| } |
| } |
| |
| /// Handle Wi-Fi MwsimMsg from libslirp and hostapd. |
| /// Send it to clients. |
| pub fn process_response(&self, packet: &Bytes) { |
| if let Err(e) = self.send_response(packet) { |
| warn!("{}", e); |
| } |
| } |
| |
| /// Determine the client id based on Ieee80211 destination and send to client. |
| fn send_response(&self, packet: &Bytes) -> anyhow::Result<()> { |
| // When Wi-Fi P2P is disabled, send all packets from WifiService to all clients. |
| if crate::config::get_disable_wifi_p2p() { |
| for client_id in self.clients.read().unwrap().keys() { |
| (self.callback)(*client_id, packet); |
| } |
| return Ok(()); |
| } |
| let hwsim_msg = HwsimMsg::decode_full(packet)?; |
| let hwsim_cmd = hwsim_msg.hwsim_hdr.hwsim_cmd; |
| match hwsim_cmd { |
| HwsimCmd::Frame => self.send_frame_response(packet, &hwsim_msg)?, |
| // TODO: Handle sending TxInfo frame for WifiService so we don't have to |
| // send duplicate HwsimMsg for all clients with the same Hwsim addr. |
| HwsimCmd::TxInfoFrame => self.send_tx_info_response(packet, &hwsim_msg)?, |
| _ => return Err(anyhow!("Invalid HwsimMsg cmd={:?}", hwsim_cmd)), |
| }; |
| Ok(()) |
| } |
| |
| fn send_frame_response(&self, packet: &Bytes, hwsim_msg: &HwsimMsg) -> anyhow::Result<()> { |
| let frame = Frame::parse(hwsim_msg)?; |
| let dest_addr = frame.ieee80211.get_destination(); |
| if let Ok(destination) = self.get_station(&dest_addr) { |
| self.send_from_ds_frame(packet, &frame, &destination)?; |
| } else if dest_addr.is_multicast() { |
| for destination in self.stations() { |
| self.send_from_ds_frame(packet, &frame, &destination)?; |
| } |
| } else { |
| warn!("Send frame response to unknown destination: {}", dest_addr); |
| } |
| Ok(()) |
| } |
| |
| /// Send frame from DS to STA. |
| fn send_from_ds_frame( |
| &self, |
| packet: &Bytes, |
| frame: &Frame, |
| destination: &Station, |
| ) -> anyhow::Result<()> { |
| if frame.attrs.receiver.context("receiver")? == destination.hwsim_addr { |
| (self.callback)(destination.client_id, packet); |
| } else { |
| // Broadcast: replace HwsimMsg destination but keep other attributes |
| let hwsim_msg = self |
| .create_hwsim_msg(frame, &destination.hwsim_addr) |
| .context("Create HwsimMsg from WifiService")?; |
| (self.callback)(destination.client_id, &hwsim_msg.encode_to_vec()?.into()); |
| } |
| self.incr_rx(destination.client_id)?; |
| Ok(()) |
| } |
| |
| fn send_tx_info_response(&self, packet: &Bytes, hwsim_msg: &HwsimMsg) -> anyhow::Result<()> { |
| let attrs = HwsimAttrSet::parse(&hwsim_msg.attributes).context("HwsimAttrSet")?; |
| let hwsim_addr = attrs.transmitter.context("missing transmitter")?; |
| let client_ids = self |
| .stations() |
| .filter(|v| v.hwsim_addr == hwsim_addr) |
| .map(|v| v.client_id) |
| .collect::<HashSet<_>>(); |
| if client_ids.len() > 1 { |
| warn!("multiple clients found for TxInfo frame"); |
| } |
| for client_id in client_ids { |
| if self.enabled(client_id)? { |
| (self.callback)(client_id, packet); |
| } |
| } |
| Ok(()) |
| } |
| |
| pub fn set_enabled(&self, client_id: u32, enabled: bool) { |
| if let Some(client) = self.clients.read().unwrap().get(&client_id) { |
| client.enabled.store(enabled, Ordering::Relaxed); |
| } |
| } |
| |
| fn enabled(&self, client_id: u32) -> anyhow::Result<bool> { |
| Ok(self |
| .clients |
| .read() |
| .unwrap() |
| .get(&client_id) |
| .context(format!("client {client_id} is missing"))? |
| .enabled |
| .load(Ordering::Relaxed)) |
| } |
| |
| /// Create tx info frame to station to ack HwsimMsg. |
| fn send_tx_info_frame(&self, frame: &Frame) -> anyhow::Result<()> { |
| let client_id = self.get_station(&frame.ieee80211.get_source())?.client_id; |
| let hwsim_msg_tx_info = build_tx_info(&frame.hwsim_msg).unwrap().encode_to_vec()?; |
| (self.callback)(client_id, &hwsim_msg_tx_info.into()); |
| Ok(()) |
| } |
| |
| fn incr_tx(&self, client_id: u32) -> anyhow::Result<()> { |
| self.clients |
| .read() |
| .unwrap() |
| .get(&client_id) |
| .context("incr_tx")? |
| .tx_count |
| .fetch_add(1, Ordering::Relaxed); |
| Ok(()) |
| } |
| |
| fn incr_rx(&self, client_id: u32) -> anyhow::Result<()> { |
| self.clients |
| .read() |
| .unwrap() |
| .get(&client_id) |
| .context("incr_rx")? |
| .rx_count |
| .fetch_add(1, Ordering::Relaxed); |
| Ok(()) |
| } |
| |
| // Send an 802.11 frame from a station to a station after wrapping in HwsimMsg. |
| // The HwsimMsg is processed by mac80211_hwsim framework in the guest OS. |
| // |
| // Simulates transmission through hostapd. |
| fn send_from_sta_frame( |
| &self, |
| frame: &Frame, |
| source: &Station, |
| destination: &Station, |
| ) -> anyhow::Result<()> { |
| if self.enabled(source.client_id)? && self.enabled(destination.client_id)? { |
| if let Some(packet) = self.create_hwsim_msg(frame, &destination.hwsim_addr) { |
| self.incr_tx(source.client_id)?; |
| self.incr_rx(destination.client_id)?; |
| (self.callback)(destination.client_id, &packet.encode_to_vec()?.into()); |
| log_hwsim_msg(frame, source.client_id, destination.client_id); |
| } |
| } |
| Ok(()) |
| } |
| |
| // Broadcast an 802.11 frame to all stations. |
| /// TODO: Compare with the implementations in mac80211_hwsim.c and wmediumd.c. |
| fn broadcast_from_sta_frame(&self, frame: &Frame, source: &Station) -> anyhow::Result<()> { |
| for destination in self.stations() { |
| if source.addr != destination.addr { |
| self.send_from_sta_frame(frame, source, &destination)?; |
| } |
| } |
| Ok(()) |
| } |
| |
| fn queue_frame(&self, frame: Frame, source: &Station) -> anyhow::Result<bool> { |
| let dest_addr = frame.ieee80211.get_destination(); |
| if self.contains_station(&dest_addr) { |
| debug!("Frame deliver from {} to {}", source.addr, dest_addr); |
| self.send_tx_info_frame(&frame)?; |
| let destination = self.get_station(&dest_addr)?; |
| self.send_from_sta_frame(&frame, source, &destination)?; |
| Ok(true) |
| } else if dest_addr.is_multicast() { |
| debug!("Frame multicast {}", frame.ieee80211); |
| self.send_tx_info_frame(&frame)?; |
| self.broadcast_from_sta_frame(&frame, source)?; |
| // Forward multicast packets to WifiService: |
| // 1. Stations send probe Request management frame scan network actively, |
| // so hostapd will send Probe Response for AndroidWiFi. |
| // 2. DNS packets. |
| // TODO: Only pass necessary packets to hostapd and libslirp. (e.g.: Don't forward mDNS packet.) |
| self.incr_tx(source.client_id)?; |
| Ok(false) |
| } else { |
| // pass to libslirp |
| self.incr_tx(source.client_id)?; |
| Ok(false) |
| } |
| } |
| |
| // Simulate transmission through hostapd by rewriting frames with 802.11 ToDS |
| // and hostapd_bssid to frames with FromDS set. |
| fn create_hwsim_attr( |
| &self, |
| frame: &Frame, |
| dest_hwsim_addr: &MacAddress, |
| ) -> anyhow::Result<Vec<u8>> { |
| let attrs = &frame.attrs; |
| let frame = match self.ap_simulation |
| && frame.ieee80211.is_to_ap() |
| && frame.ieee80211.get_bssid() == Some(self.hostapd_bssid) |
| { |
| true => frame.ieee80211.into_from_ap()?.encode_to_vec()?, |
| false => attrs.frame.clone().unwrap(), |
| }; |
| |
| let mut builder = HwsimAttrSet::builder(); |
| |
| // Attributes required by mac80211_hwsim. |
| builder.receiver(&dest_hwsim_addr.to_vec()); |
| builder.frame(&frame); |
| // Incoming HwsimMsg don't have rx_rate and signal. |
| builder.rx_rate(attrs.rx_rate_idx.unwrap_or(RX_RATE)); |
| builder.signal(attrs.signal.unwrap_or(SIGNAL)); |
| |
| attrs.flags.map(|v| builder.flags(v)); |
| attrs.freq.map(|v| builder.freq(v)); |
| attrs.tx_info.as_ref().map(|v| builder.tx_info(v)); |
| attrs.tx_info_flags.as_ref().map(|v| builder.tx_info_flags(v)); |
| |
| Ok(builder.build()?.attributes) |
| } |
| |
| // Simulates transmission through hostapd. |
| fn create_hwsim_msg(&self, frame: &Frame, dest_hwsim_addr: &MacAddress) -> Option<HwsimMsg> { |
| let hwsim_msg = &frame.hwsim_msg; |
| assert_eq!(hwsim_msg.hwsim_hdr.hwsim_cmd, HwsimCmd::Frame); |
| let attributes_result = self.create_hwsim_attr(frame, dest_hwsim_addr); |
| let attributes = match attributes_result { |
| Ok(attributes) => attributes, |
| Err(e) => { |
| warn!("Failed to create from_ap attributes. E: {}", e); |
| return None; |
| } |
| }; |
| |
| let nlmsg_len = hwsim_msg.nl_hdr.nlmsg_len + attributes.len() as u32 |
| - hwsim_msg.attributes.len() as u32; |
| let new_hwsim_msg = HwsimMsg { |
| nl_hdr: NlMsgHdr { |
| nlmsg_len, |
| nlmsg_type: NLMSG_MIN_TYPE, |
| nlmsg_flags: hwsim_msg.nl_hdr.nlmsg_flags, |
| nlmsg_seq: 0, |
| nlmsg_pid: 0, |
| }, |
| hwsim_hdr: hwsim_msg.hwsim_hdr.clone(), |
| attributes, |
| }; |
| Some(new_hwsim_msg) |
| } |
| } |
| |
| fn log_hwsim_msg(frame: &Frame, client_id: u32, dest_client_id: u32) { |
| debug!( |
| "Sent hwsim_msg from client {} to {}. flags {:?}, ieee80211 {}", |
| client_id, dest_client_id, frame.flags, frame.ieee80211, |
| ); |
| } |
| |
| /// Build TxInfoFrame HwsimMsg from CmdFrame HwsimMsg. |
| /// |
| /// Reference to ackLocalFrame() in external/qemu/android-qemu2-glue/emulation/VirtioWifiForwarder.cpp |
| fn build_tx_info(hwsim_msg: &HwsimMsg) -> anyhow::Result<HwsimMsg> { |
| let attrs = HwsimAttrSet::parse(&hwsim_msg.attributes).context("HwsimAttrSet").unwrap(); |
| |
| let hwsim_hdr = &hwsim_msg.hwsim_hdr; |
| let nl_hdr = &hwsim_msg.nl_hdr; |
| let mut new_attr_builder = HwsimAttrSet::builder(); |
| const HWSIM_TX_STAT_ACK: u32 = 1 << 2; |
| |
| new_attr_builder |
| .transmitter(&attrs.transmitter.context("transmitter")?.into()) |
| .flags(attrs.flags.context("flags")? | HWSIM_TX_STAT_ACK) |
| .cookie(attrs.cookie.context("cookie")?) |
| .signal(attrs.signal.unwrap_or(SIGNAL)) |
| .tx_info(attrs.tx_info.context("tx_info")?.as_slice()); |
| |
| let new_attr = new_attr_builder.build().unwrap(); |
| let nlmsg_len = |
| nl_hdr.nlmsg_len + new_attr.attributes.len() as u32 - attrs.attributes.len() as u32; |
| let new_hwsim_msg = HwsimMsg { |
| attributes: new_attr.attributes, |
| hwsim_hdr: HwsimMsgHdr { |
| hwsim_cmd: HwsimCmd::TxInfoFrame, |
| hwsim_version: 0, |
| reserved: hwsim_hdr.reserved, |
| }, |
| nl_hdr: NlMsgHdr { |
| nlmsg_len, |
| nlmsg_type: NLMSG_MIN_TYPE, |
| nlmsg_flags: nl_hdr.nlmsg_flags, |
| nlmsg_seq: 0, |
| nlmsg_pid: 0, |
| }, |
| }; |
| Ok(new_hwsim_msg) |
| } |
| |
| // It's used by radiotap.rs for packet capture. |
| pub fn parse_hwsim_cmd(packet: &[u8]) -> anyhow::Result<HwsimCmdEnum> { |
| let hwsim_msg = HwsimMsg::decode_full(packet)?; |
| match hwsim_msg.hwsim_hdr.hwsim_cmd { |
| HwsimCmd::Frame => { |
| let frame = Frame::parse(&hwsim_msg)?; |
| Ok(HwsimCmdEnum::Frame(Box::new(frame))) |
| } |
| HwsimCmd::TxInfoFrame => Ok(HwsimCmdEnum::TxInfoFrame), |
| _ => Err(anyhow!("Unknown HwsimMsg cmd={:?}", hwsim_msg.hwsim_hdr.hwsim_cmd)), |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::wifi::packets::ieee80211::parse_mac_address; |
| #[test] |
| fn test_remove() { |
| let hostapd_bssid: MacAddress = parse_mac_address("00:13:10:85:fe:01").unwrap(); |
| |
| let test_client_id: u32 = 1234; |
| let other_client_id: u32 = 5678; |
| let addr: MacAddress = parse_mac_address("00:0b:85:71:20:00").unwrap(); |
| let other_addr: MacAddress = parse_mac_address("00:0b:85:71:20:01").unwrap(); |
| let hwsim_addr: MacAddress = parse_mac_address("00:0b:85:71:20:ce").unwrap(); |
| let other_hwsim_addr: MacAddress = parse_mac_address("00:0b:85:71:20:cf").unwrap(); |
| |
| // Create a test Medium object |
| let callback: HwsimCmdCallback = |_, _| {}; |
| let medium = Medium { |
| callback, |
| stations: RwLock::new(HashMap::from([ |
| (addr, Arc::new(Station { client_id: test_client_id, addr, hwsim_addr })), |
| ( |
| other_addr, |
| Arc::new(Station { |
| client_id: other_client_id, |
| addr: other_addr, |
| hwsim_addr: other_hwsim_addr, |
| }), |
| ), |
| ])), |
| clients: RwLock::new(HashMap::from([ |
| (test_client_id, Client::new()), |
| (other_client_id, Client::new()), |
| ])), |
| hostapd_bssid, |
| ap_simulation: true, |
| }; |
| |
| medium.remove(test_client_id); |
| |
| assert!(!medium.contains_station(&addr)); |
| assert!(medium.contains_station(&other_addr)); |
| assert!(!medium.contains_client(test_client_id)); |
| assert!(medium.contains_client(other_client_id)); |
| } |
| |
| #[test] |
| fn test_netlink_attr() { |
| let packet: Vec<u8> = include!("test_packets/hwsim_cmd_frame.csv"); |
| assert!(parse_hwsim_cmd(&packet).is_ok()); |
| |
| let tx_info_packet: Vec<u8> = include!("test_packets/hwsim_cmd_tx_info.csv"); |
| assert!(parse_hwsim_cmd(&tx_info_packet).is_ok()); |
| } |
| |
| #[test] |
| fn test_netlink_attr_response_packet() { |
| // Response packet may not contain transmitter, flags, tx_info, or cookie fields. |
| let response_packet: Vec<u8> = |
| include!("test_packets/hwsim_cmd_frame_response_no_transmitter_flags_tx_info.csv"); |
| assert!(parse_hwsim_cmd(&response_packet).is_ok()); |
| |
| let response_packet2: Vec<u8> = |
| include!("test_packets/hwsim_cmd_frame_response_no_cookie.csv"); |
| assert!(parse_hwsim_cmd(&response_packet2).is_ok()); |
| } |
| |
| #[test] |
| fn test_is_mdns_packet() { |
| let packet: Vec<u8> = include!("test_packets/hwsim_cmd_frame_mdns.csv"); |
| let hwsim_msg = HwsimMsg::decode_full(&packet).unwrap(); |
| let mdns_frame = Frame::parse(&hwsim_msg).unwrap(); |
| assert!(!mdns_frame.ieee80211.get_source().is_multicast()); |
| assert!(mdns_frame.ieee80211.get_destination().is_multicast()); |
| } |
| |
| #[test] |
| fn test_build_tx_info_reconstruct() { |
| let packet: Vec<u8> = include!("test_packets/hwsim_cmd_tx_info.csv"); |
| let hwsim_msg = HwsimMsg::decode_full(&packet).unwrap(); |
| assert_eq!(hwsim_msg.hwsim_hdr().hwsim_cmd, HwsimCmd::TxInfoFrame); |
| |
| let new_hwsim_msg = build_tx_info(&hwsim_msg).unwrap(); |
| assert_eq!(hwsim_msg, new_hwsim_msg); |
| } |
| |
| #[test] |
| fn test_build_tx_info() { |
| let packet: Vec<u8> = include!("test_packets/hwsim_cmd_frame.csv"); |
| let hwsim_msg = HwsimMsg::decode_full(&packet).unwrap(); |
| let hwsim_msg_tx_info = build_tx_info(&hwsim_msg).unwrap(); |
| assert_eq!(hwsim_msg_tx_info.hwsim_hdr().hwsim_cmd, HwsimCmd::TxInfoFrame); |
| } |
| |
| fn build_tx_info_and_compare(frame_bytes: &Bytes, tx_info_expected_bytes: &Bytes) { |
| let frame = HwsimMsg::decode_full(frame_bytes).unwrap(); |
| let tx_info = build_tx_info(&frame).unwrap(); |
| |
| let tx_info_expected = HwsimMsg::decode_full(tx_info_expected_bytes).unwrap(); |
| |
| assert_eq!(tx_info.hwsim_hdr(), tx_info_expected.hwsim_hdr()); |
| assert_eq!(tx_info.nl_hdr(), tx_info_expected.nl_hdr()); |
| |
| let attrs = HwsimAttrSet::parse(tx_info.attributes()).context("HwsimAttrSet").unwrap(); |
| let attrs_expected = |
| HwsimAttrSet::parse(tx_info_expected.attributes()).context("HwsimAttrSet").unwrap(); |
| |
| // NOTE: TX info is different and the counts are all zeros in the TX info packet generated by WifiService. |
| // TODO: Confirm if the behavior is intended in WifiService. |
| assert_eq!(attrs.transmitter, attrs_expected.transmitter); |
| assert_eq!(attrs.flags, attrs_expected.flags); |
| assert_eq!(attrs.cookie, attrs_expected.cookie); |
| assert_eq!(attrs.signal, attrs_expected.signal); |
| } |
| |
| #[test] |
| fn test_build_tx_info_and_compare() { |
| let frame_bytes = Bytes::from(include!("test_packets/hwsim_cmd_frame_request.csv")); |
| let tx_info_expected_bytes = |
| Bytes::from(include!("test_packets/hwsim_cmd_tx_info_response.csv")); |
| build_tx_info_and_compare(&frame_bytes, &tx_info_expected_bytes); |
| } |
| |
| #[test] |
| fn test_build_tx_info_and_compare_mdns() { |
| let frame_bytes = Bytes::from(include!("test_packets/hwsim_cmd_frame_request_mdns.csv")); |
| let tx_info_expected_bytes = |
| Bytes::from(include!("test_packets/hwsim_cmd_tx_info_response_mdns.csv")); |
| build_tx_info_and_compare(&frame_bytes, &tx_info_expected_bytes); |
| } |
| } |