blob: 4b2389404186188d615b6e5a08fd4ac1e4ac5d86 [file] [log] [blame]
// Copyright 2020 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.
use {
crate::frame_writer::BufferWrite,
quote::quote,
syn::{
parse::{Parse, ParseStream},
Error, Expr, ExprIf, Ident, Result, Token,
},
};
const IE_SSID: &str = "ssid";
const IE_HT_CAPABILITIES: &str = "ht_cap";
const IE_VHT_CAPABILITIES: &str = "vht_cap";
const IE_SUPPORTED_RATES: &str = "supported_rates";
const IE_SUPPORTED_EXTENDED_RATES: &str = "extended_supported_rates";
const IE_RSNE: &str = "rsne";
const IE_BSS_MAX_IDLE_PERIOD: &str = "bss_max_idle_period";
const IE_DSSS_PARAM_SET: &str = "dsss_param_set";
const IE_WPA1: &str = "wpa1";
/// Field carrying necessary meta information to generate relevant tokens.
#[derive(Hash, Eq, Ord, PartialEq, PartialOrd, Clone, Copy)]
pub enum Ie {
Ssid,
HtCaps,
VhtCaps,
Rates,
ExtendedRates {
// `true` if supported rates should be continued.
// `false` if the user specified a dedicated extended supported rates set.
continue_rates: bool,
},
Rsne,
BssMaxIdlePeriod,
DsssParamSet,
Wpa1,
}
pub struct IeDefinition {
pub name: Ident,
pub type_: Ie,
value: Expr,
optional: bool,
}
/// Declares a new local variable `$name` by evaluating the given expression `$expr`.
/// If-Expressions are treated specially:
/// * if the IE is `$optional` and an else-branch was provided the return type is expected to be
/// `Option<V>` with `V` representing the IE's value type.
/// * if no else-branch was provided the return type must *not* be an `Option<_>`.
/// Instead, if the if-expression's condition evaluates to `true` the then-branch is evaluated
/// and wrapped with `Some(_)`.
/// If the if-expression's condition evaluates to `false` None will be used instead and the
/// then-branch is not executed.
/// * if the IE is *not* `$optional` and an else-branch was specified the expression must yield a
/// type compatible with the IE's expected value type.
macro_rules! declare_var {
($name:expr, $optional:expr, $expr:expr) => {
match $expr {
Expr::If(if_expr) => {
let if_expr: &ExprIf = if_expr;
if if_expr.else_branch.is_some() {
if $optional {
quote!(let $name: Option<_> = #if_expr;)
} else {
quote!(let $name = #if_expr;)
}
} else {
let cond = &if_expr.cond;
let v = &if_expr.then_branch;
quote!(
let mut $name: Option<_> = if #cond {
Some(#v)
} else { None };
)
}
}
other => quote!(let $name = #other;),
}
};
($name:expr, $expr:expr) => { declare_var!($name, false, $expr) };
}
/// Generates tokens to add a length evaluated through the expression `$len` to a previously
/// declared mutable, local variable `frame_len`. If `$optional` evaluates to `true` the value
/// stored in `$name` is assumed to be of type `Option<V>` and its length will be added
/// conditionally based on the value's presence.
macro_rules! frame_len {
($name:expr, $optional:expr, $len:expr) => {
if $optional {
quote!(frame_len += $name.as_ref().map_or(0, |$name| $len);)
} else {
quote!(frame_len += $len;)
}
};
($name:expr, $len:expr) => {
frame_len! ($name, false, $len)
};
}
/// Executes an expression `$func`. If `$optional` evaluates to `true` the value stored in `$name`
/// is assumed to be of type `Option<V>`. In this case, `$func` is only executed if the value is
/// present. The expression can assume access to the Option's unwrapped value.
/// `$expr` represents the IE's value expression.
macro_rules! apply_on {
($name:expr, $optional:expr, $expr:expr, $func:expr) => {
if $optional {
quote!(if let Some($name) = $name {
$func;
})
} else {
match $expr {
Expr::If(if_expr) => {
if if_expr.else_branch.is_some() {
quote!($func;)
} else {
quote!(if let Some($name) = $name {
$func;
})
}
}
_ => quote!($func;),
}
}
};
($name:expr, $expr:expr, $func:expr) => {
apply_on! ($name, false, $expr, $func)
};
}
impl BufferWrite for IeDefinition {
fn gen_frame_len_tokens(&self) -> Result<proc_macro2::TokenStream> {
Ok(match self.type_ {
Ie::Ssid => frame_len!(ssid, IE_PREFIX_LEN + ssid.len()),
Ie::HtCaps => {
frame_len!(ht_caps, self.optional, IE_PREFIX_LEN + size_of::<ie::HtCapabilities>())
}
Ie::VhtCaps => frame_len!(
vht_caps,
self.optional,
IE_PREFIX_LEN + size_of::<ie::VhtCapabilities>()
),
// rsne::Rsne#len() carries the IE header already.
Ie::Rsne => frame_len!(rsne, self.optional, rsne.len()),
Ie::Rates => frame_len!(
rates,
IE_PREFIX_LEN + std::cmp::min(SUPPORTED_RATES_MAX_LEN, rates.len())
),
Ie::ExtendedRates { continue_rates } => {
if continue_rates {
quote!(if rates.len() > SUPPORTED_RATES_MAX_LEN {
frame_len += IE_PREFIX_LEN + rates.len() - SUPPORTED_RATES_MAX_LEN;
})
} else {
quote!(frame_len += IE_PREFIX_LEN + extended_supported_rates.len();)
}
}
Ie::BssMaxIdlePeriod => frame_len!(
bss_max_idle_period,
self.optional,
IE_PREFIX_LEN + size_of::<ie::BssMaxIdlePeriod>()
),
Ie::DsssParamSet => {
frame_len!(bss_max_idle_period, IE_PREFIX_LEN + size_of::<ie::DsssParamSet>())
}
Ie::Wpa1 => {
// IE + Vendor OUI + Vendor specific type + WPA1 body
frame_len!(wpa1, self.optional, IE_PREFIX_LEN + 4 + wpa1.len())
}
})
}
fn gen_write_to_buf_tokens(&self) -> Result<proc_macro2::TokenStream> {
Ok(match self.type_ {
Ie::Ssid => apply_on!(ssid, &self.value, ie::write_ssid(&mut w, &ssid)?),
Ie::Rates => quote!(
let rates_writer = ie::RatesWriter::try_new(&rates[..])?;
rates_writer.write_supported_rates(&mut w);
),
Ie::ExtendedRates { continue_rates } => {
if continue_rates {
quote!(rates_writer.write_ext_supported_rates(&mut w);)
} else {
// Extended supported rates should only be written if the maximum supported
// rates were specified.
quote!(
if rates.len() != SUPPORTED_RATES_MAX_LEN {
return Err(FrameWriteError::new_invalid_data(format!(
"attempt to write extended_supported_rates without specifying the \
maximum allowed supported_rates: {}", rates.len()
)).into());
}
ie::write_ext_supported_rates(&mut w, &extended_supported_rates[..])?;
)
}
}
Ie::HtCaps => apply_on!(
ht_caps,
self.optional,
&self.value,
ie::write_ht_capabilities(&mut w, &ht_caps)?
),
Ie::VhtCaps => apply_on!(
vht_caps,
self.optional,
&self.value,
ie::write_vht_capabilities(&mut w, &vht_caps)?
),
Ie::Rsne => apply_on!(rsne, self.optional, &self.value, ie::write_rsne(&mut w, &rsne)?),
Ie::BssMaxIdlePeriod => apply_on!(
bss_max_idle_period,
self.optional,
&self.value,
ie::write_bss_max_idle_period(&mut w, &bss_max_idle_period)?
),
Ie::DsssParamSet => {
apply_on!(dsss, &self.value, ie::write_dsss_param_set(&mut w, &dsss)?)
}
Ie::Wpa1 => apply_on!(wpa1, &self.value, ie::write_wpa1_ie(&mut w, &wpa1)?),
})
}
fn gen_var_declaration_tokens(&self) -> Result<proc_macro2::TokenStream> {
Ok(match self.type_ {
Ie::Ssid => declare_var!(ssid, self.optional, &self.value),
Ie::HtCaps => declare_var!(ht_caps, self.optional, &self.value),
Ie::VhtCaps => declare_var!(vht_caps, self.optional, &self.value),
Ie::Rates => declare_var!(rates, &self.value),
Ie::ExtendedRates { .. } => declare_var!(extended_supported_rates, &self.value),
Ie::Rsne => declare_var!(rsne, self.optional, &self.value),
Ie::BssMaxIdlePeriod => declare_var!(bss_max_idle_period, self.optional, &self.value),
Ie::DsssParamSet => declare_var!(dsss, &self.value),
Ie::Wpa1 => declare_var!(wpa1, &self.value),
})
}
}
impl Parse for IeDefinition {
fn parse(input: ParseStream) -> Result<Self> {
let name = input.parse::<Ident>()?;
let optional = input.peek(Token![?]);
if optional {
input.parse::<Token![?]>()?;
}
input.parse::<Token![:]>()?;
let value = input.parse::<Expr>()?;
match value {
Expr::Block(_)
| Expr::Call(_)
| Expr::If(_)
| Expr::Lit(_)
| Expr::MethodCall(_)
| Expr::Reference(_)
| Expr::Repeat(_)
| Expr::Struct(_)
| Expr::Tuple(_)
| Expr::Unary(_)
| Expr::Index(_)
| Expr::Path(_) => (),
other => {
return Err(Error::new(
name.span(),
format!("invalid expression for IE value: {:?}", other),
))
}
}
let type_ = match name.to_string().as_str() {
IE_SSID => {
if optional {
return Err(Error::new(name.span(), "`ssid` IE may never be optional"));
}
Ie::Ssid
}
IE_DSSS_PARAM_SET => {
if optional {
return Err(Error::new(
name.span(),
"`dsss_param_set` IE may never be optional",
));
}
Ie::DsssParamSet
}
IE_HT_CAPABILITIES => Ie::HtCaps,
IE_VHT_CAPABILITIES => Ie::VhtCaps,
IE_SUPPORTED_RATES | IE_SUPPORTED_EXTENDED_RATES if optional => {
return Err(Error::new(
name.span(),
"`supported_rates` and `extended_supported_rates` IE may never be optional",
));
}
IE_SUPPORTED_RATES => Ie::Rates,
IE_SUPPORTED_EXTENDED_RATES => {
let continue_rates = match &value {
Expr::Block(block) => block.block.stmts.is_empty(),
_ => false,
};
Ie::ExtendedRates { continue_rates }
}
IE_RSNE => Ie::Rsne,
IE_BSS_MAX_IDLE_PERIOD => Ie::BssMaxIdlePeriod,
IE_WPA1 => Ie::Wpa1,
unknown => return Err(Error::new(name.span(), format!("unknown IE: '{}'", unknown))),
};
Ok(IeDefinition { name, value, type_, optional })
}
}