blob: bb87f062f78a3b314855f63d4b5d6b64773a4ad7 [file]
// 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 std::fmt;
use super::packets::ieee80211::MacAddress;
use super::packets::mac80211_hwsim::{self, HwsimAttr, HwsimAttrChild::*, TxRate, TxRateFlag};
use super::packets::netlink::NlAttrHdr;
use anyhow::anyhow;
use pdl_runtime::Packet;
use std::option::Option;
/// Parse or Build the Hwsim attributes into a set.
///
/// Hwsim attributes are used to exchange data between kernel's
/// mac80211_hwsim subsystem and a user space process and include:
///
/// HWSIM_ATTR_ADDR_TRANSMITTER,
/// HWSIM_ATTR_ADDR_RECEIVER,
/// HWSIM_ATTR_FRAME,
/// HWSIM_ATTR_FLAGS,
/// HWSIM_ATTR_RX_RATE,
/// HWSIM_ATTR_SIGNAL,
/// HWSIM_ATTR_COOKIE,
/// HWSIM_ATTR_FREQ (optional)
/// HWSIM_ATTR_TX_INFO (new use)
/// HWSIM_ATTR_TX_INFO_FLAGS (new use)
/// Aligns a length to the specified alignment boundary (`NLA_ALIGNTO`).
///
/// # Arguments
///
/// * `array_length`: The length in bytes to be aligned.
///
/// # Returns
///
/// * The aligned length, which is a multiple of `NLA_ALIGNTO`.
///
fn nla_align(array_length: usize) -> usize {
const NLA_ALIGNTO: usize = 4;
array_length.wrapping_add(NLA_ALIGNTO - 1) & !(NLA_ALIGNTO - 1)
}
#[derive(Default)]
pub struct HwsimAttrSetBuilder {
transmitter: Option<MacAddress>,
receiver: Option<MacAddress>,
frame: Option<Vec<u8>>,
flags: Option<u32>,
rx_rate_idx: Option<u32>,
signal: Option<u32>,
cookie: Option<u64>,
freq: Option<u32>,
tx_info: Option<Vec<TxRate>>,
tx_info_flags: Option<Vec<TxRateFlag>>,
attributes: Vec<u8>,
}
#[derive(Debug)]
pub struct HwsimAttrSet {
pub transmitter: Option<MacAddress>,
pub receiver: Option<MacAddress>,
pub frame: Option<Vec<u8>>,
pub flags: Option<u32>,
pub rx_rate_idx: Option<u32>,
pub signal: Option<u32>,
pub cookie: Option<u64>,
pub freq: Option<u32>,
pub tx_info: Option<Vec<TxRate>>,
pub tx_info_flags: Option<Vec<TxRateFlag>>,
pub attributes: Vec<u8>,
}
/// Builder pattern for each of the HWSIM_ATTR used in conjunction
/// with the HwsimAttr packet formats defined in `mac80211_hwsim.pdl`
///
/// Used during `parse` or to create new HwsimCmd packets containing
/// an attributes vector.
///
/// The attributes field will contain the raw bytes in NLA format
/// in the order of method calls.
impl HwsimAttrSetBuilder {
// Add packet to the attributes vec and pad for proper NLA
// alignment. This provides for to_bytes for a HwsimMsg for
// packets constructed by the Builder.
fn extend_attributes<P: Packet>(&mut self, packet: P) {
let mut vec: Vec<u8> = packet.encode_to_vec().unwrap();
let nla_padding = nla_align(vec.len()) - vec.len();
vec.extend(vec![0; nla_padding]);
self.attributes.extend(vec);
}
pub fn transmitter(&mut self, transmitter: &[u8; 6]) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrAddrTransmitterBuilder {
address: *transmitter,
nla_m: 0,
nla_o: 0,
}
.build(),
);
self.transmitter = Some(MacAddress::from(transmitter));
self
}
pub fn receiver(&mut self, receiver: &[u8; 6]) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrAddrReceiverBuilder { address: *receiver, nla_m: 0, nla_o: 0 }
.build(),
);
self.receiver = Some(MacAddress::from(receiver));
self
}
pub fn frame(&mut self, frame: &[u8]) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrFrameBuilder { data: (*frame).to_vec(), nla_m: 0, nla_o: 0 }
.build(),
);
self.frame = Some(frame.to_vec());
self
}
pub fn flags(&mut self, flags: u32) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrFlagsBuilder { flags, nla_m: 0, nla_o: 0 }.build(),
);
self.flags = Some(flags);
self
}
pub fn rx_rate(&mut self, rx_rate_idx: u32) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrRxRateBuilder { rx_rate_idx, nla_m: 0, nla_o: 0 }.build(),
);
self.rx_rate_idx = Some(rx_rate_idx);
self
}
pub fn signal(&mut self, signal: u32) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrSignalBuilder { signal, nla_m: 0, nla_o: 0 }.build(),
);
self.signal = Some(signal);
self
}
pub fn cookie(&mut self, cookie: u64) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrCookieBuilder { cookie, nla_m: 0, nla_o: 0 }.build(),
);
self.cookie = Some(cookie);
self
}
pub fn freq(&mut self, freq: u32) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrFreqBuilder { freq, nla_m: 0, nla_o: 0 }.build(),
);
self.freq = Some(freq);
self
}
pub fn tx_info(&mut self, tx_info: &[TxRate]) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrTxInfoBuilder {
tx_rates: (*tx_info).to_vec(),
nla_m: 0,
nla_o: 0,
}
.build(),
);
self.tx_info = Some(tx_info.to_vec());
self
}
pub fn tx_info_flags(&mut self, tx_rate_flags: &[TxRateFlag]) -> &mut Self {
self.extend_attributes(
mac80211_hwsim::HwsimAttrTxInfoFlagsBuilder {
tx_rate_flags: (*tx_rate_flags).to_vec(),
nla_m: 0,
nla_o: 0,
}
.build(),
);
self.tx_info_flags = Some(tx_rate_flags.to_vec());
self
}
pub fn build(self) -> anyhow::Result<HwsimAttrSet> {
Ok(HwsimAttrSet {
transmitter: self.transmitter,
receiver: self.receiver,
cookie: self.cookie,
flags: self.flags,
rx_rate_idx: self.rx_rate_idx,
signal: self.signal,
frame: self.frame,
freq: self.freq,
tx_info: self.tx_info,
tx_info_flags: self.tx_info_flags,
attributes: self.attributes,
})
}
}
impl fmt::Display for HwsimAttrSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{ ")?;
self.transmitter.map(|v| write!(f, "transmitter: {}, ", v));
self.receiver.map(|v| write!(f, "receiver: {}, ", v));
self.cookie.map(|v| write!(f, "cookie: {}, ", v));
self.flags.map(|v| write!(f, "flags: {}, ", v));
self.rx_rate_idx.map(|v| write!(f, "rx_rate_idx: {}, ", v));
self.signal.map(|v| write!(f, "signal: {}, ", v));
self.frame.as_ref().map(|v| write!(f, "frame: {:?}, ", &v));
self.freq.map(|v| write!(f, "freq: {}, ", v));
self.tx_info.as_ref().map(|v| write!(f, "tx_info: {:?}, ", &v));
self.tx_info_flags.as_ref().map(|v| write!(f, "tx_info_flags: {:?}, ", &v));
write!(f, "}}")?;
Ok(())
}
}
impl HwsimAttrSet {
/// Creates a new `HwsimAttrSetBuilder` with default settings, ready for configuring attributes.
///
/// # Returns
///
/// * A new `HwsimAttrSetBuilder` instance, initialized with default values.
///
/// # Examples
///
/// ```rust
/// let mut builder = HwsimAttrSetBuilder::builder();
/// builder.signal(42).cookie(32); // Example attribute configuration
/// let attr_set = builder.build();
/// ```
pub fn builder() -> HwsimAttrSetBuilder {
HwsimAttrSetBuilder::default()
}
/// Parse and validates the attributes from a HwsimMsg command.
pub fn parse(attributes: &[u8]) -> anyhow::Result<HwsimAttrSet> {
Self::parse_with_frame_transmitter(attributes, Option::None, Option::None)
}
/// Parse and validates the attributes from a HwsimMsg command.
/// Update frame and transmitter if provided.
pub fn parse_with_frame_transmitter(
attributes: &[u8],
frame: Option<&[u8]>,
transmitter: Option<&[u8; 6]>,
) -> anyhow::Result<HwsimAttrSet> {
let mut index: usize = 0;
let mut builder = HwsimAttrSet::builder();
while index < attributes.len() {
// Parse a generic netlink attribute to get the size
let nla_hdr = NlAttrHdr::parse(&attributes[index..index + 4]).unwrap();
let nla_len = nla_hdr.nla_len as usize;
// Now parse a single attribute at a time from the
// attributes to allow padding per attribute.
let hwsim_attr = HwsimAttr::parse(&attributes[index..index + nla_len])?;
match hwsim_attr.specialize() {
HwsimAttrAddrTransmitter(child) => {
builder.transmitter(transmitter.unwrap_or(child.get_address()))
}
HwsimAttrAddrReceiver(child) => builder.receiver(child.get_address()),
HwsimAttrFrame(child) => builder.frame(frame.unwrap_or(child.get_data())),
HwsimAttrFlags(child) => builder.flags(child.get_flags()),
HwsimAttrRxRate(child) => builder.rx_rate(child.get_rx_rate_idx()),
HwsimAttrSignal(child) => builder.signal(child.get_signal()),
HwsimAttrCookie(child) => builder.cookie(child.get_cookie()),
HwsimAttrFreq(child) => builder.freq(child.get_freq()),
HwsimAttrTxInfo(child) => builder.tx_info(child.get_tx_rates()),
HwsimAttrTxInfoFlags(child) => builder.tx_info_flags(child.get_tx_rate_flags()),
_ => {
return Err(anyhow!(
"Invalid attribute message: {:?}",
hwsim_attr.get_nla_type() as u32
))
}
};
// Manually step through the attribute bytes aligning as
// we go because netlink aligns each attribute which isn't
// a feature of PDL parser.
index += nla_align(nla_len);
}
builder.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wifi::packets::ieee80211::parse_mac_address;
use crate::wifi::packets::mac80211_hwsim::{HwsimCmd, HwsimMsg};
use anyhow::Context;
use anyhow::Error;
// Validate `HwsimAttrSet` attribute parsing from byte vector.
#[test]
fn test_attr_set_parse() {
let packet: Vec<u8> = include!("test_packets/hwsim_cmd_frame.csv");
let hwsim_msg = HwsimMsg::parse(&packet).unwrap();
assert_eq!(hwsim_msg.get_hwsim_hdr().hwsim_cmd, HwsimCmd::Frame);
let attrs = HwsimAttrSet::parse(hwsim_msg.get_attributes()).unwrap();
// Validate each attribute parsed
assert_eq!(attrs.transmitter, MacAddress::try_from(11670786u64).ok());
assert!(attrs.receiver.is_none());
assert!(attrs.frame.is_some());
assert_eq!(attrs.flags, Some(2));
assert!(attrs.rx_rate_idx.is_none());
assert!(attrs.signal.is_none());
assert_eq!(attrs.cookie, Some(201));
assert_eq!(attrs.freq, Some(2422));
assert!(attrs.tx_info.is_some());
}
// Validate the contents of the `attributes` bytes constructed by
// the Builder by matching with the bytes containing the input
// attributes. Confirms attribute order, packet format and
// padding.
#[test]
fn test_attr_set_attributes() {
let packet: Vec<u8> = include!("test_packets/hwsim_cmd_frame.csv");
let hwsim_msg = HwsimMsg::parse(&packet).unwrap();
assert_eq!(hwsim_msg.get_hwsim_hdr().hwsim_cmd, HwsimCmd::Frame);
let attrs = HwsimAttrSet::parse(hwsim_msg.get_attributes()).unwrap();
assert_eq!(&attrs.attributes, hwsim_msg.get_attributes());
}
/// Validate changing frame and transmitter during the parse.
/// 1. Check if reinserting the same values results in identical bytes.
/// 2. Insert modified values, parse to bytes, and parse back again to check
/// if the round trip values are identical.
#[test]
fn test_attr_set_parse_with_frame_transmitter() -> Result<(), Error> {
let packet: Vec<u8> = include!("test_packets/hwsim_cmd_frame.csv");
let hwsim_msg = HwsimMsg::parse(&packet)?;
assert_eq!(hwsim_msg.get_hwsim_hdr().hwsim_cmd, HwsimCmd::Frame);
let attrs = HwsimAttrSet::parse(hwsim_msg.get_attributes())?;
let transmitter: [u8; 6] = attrs.transmitter.context("transmitter")?.into();
let mod_attrs = HwsimAttrSet::parse_with_frame_transmitter(
hwsim_msg.get_attributes(),
attrs.frame.as_deref(),
Some(&transmitter),
)?;
assert_eq!(attrs.attributes, mod_attrs.attributes);
// Change frame and transmitter.
let mod_frame = Some(vec![0, 1, 2, 3]);
let mod_transmitter: Option<[u8; 6]> =
Some(parse_mac_address("00:0b:85:71:20:ce").context("transmitter")?.into());
let mod_attrs = HwsimAttrSet::parse_with_frame_transmitter(
&attrs.attributes,
mod_frame.as_deref(),
mod_transmitter.as_ref(),
)?;
let parsed_attrs = HwsimAttrSet::parse(&mod_attrs.attributes)?;
assert_eq!(parsed_attrs.transmitter, mod_transmitter.map(|t| MacAddress::from(&t)));
assert_eq!(parsed_attrs.frame, mod_frame);
Ok(())
}
#[test]
fn test_hwsim_attr_set_display() {
let packet: Vec<u8> = include!("test_packets/hwsim_cmd_frame.csv");
let hwsim_msg = HwsimMsg::parse(&packet).unwrap();
let attrs = HwsimAttrSet::parse(hwsim_msg.get_attributes()).unwrap();
let fmt_attrs = format!("{}", attrs);
assert!(fmt_attrs.contains("transmitter: 02:15:b2:00:00:00"));
assert!(fmt_attrs.contains("cookie: 201"));
}
}