use std::collections::HashMap;
use std::iter::Peekable;
use std::slice::Iter;
pub(crate) mod binary;
pub mod lexer;
pub use crate::binary::Binary;
pub use crate::lexer::TokType;
use anyhow::{anyhow, bail, Result};
use std::convert::TryFrom;
#[derive(Debug, Default, PartialEq, Eq)]
pub struct RegDB {
pub wmm_rules: HashMap<String, WmmRule>,
pub countries: HashMap<String, Country>,
}
impl RegDB {
pub fn from_lexer(lexer: Vec<TokType>) -> Result<Self> {
let mut ret = Self::default();
let mut it = lexer.iter().peekable();
while let Some(&v) = it.peek() {
match &v {
TokType::String(v) if v == "wmmrule" => {
it.next();
let name = it
.next()
.map(|x| x.get_string())
.ok_or_else(|| anyhow!("wmmrule does not contain a name"))??;
match it.next() {
Some(TokType::Colon) => (),
v => bail!("not a vailid wmmrule {}: {:?}", name, v),
}
ret.wmm_rules.insert(name, WmmRule::from_lexer(&mut it)?);
}
TokType::String(v) if v == "country" => {
it.next();
let name = match it.next() {
Some(TokType::String(v)) => v.to_string(),
Some(TokType::Int(v)) if *v == 0 => String::from("00"),
_ => bail!("country does not contain a name"),
};
match it.next() {
Some(TokType::Colon) => (),
v => bail!("not a vailid country {}: {:?}", name, v),
}
let dfs = match it.peek() {
Some(TokType::String(v)) => {
it.next();
Some(v.to_string())
}
_ => None,
};
ret.countries
.insert(name.clone(), Country::from_lexer(&name, &mut it, dfs)?);
}
v => bail!("not expected for a regulatory db: {:?}", v),
}
}
Ok(ret)
}
}
const WMMRULE_ITEMS: [&str; 8] = [
"vo_c", "vi_c", "be_c", "bk_c", "vo_ap", "vi_ap", "be_ap", "bk_ap",
];
#[derive(Debug, Default, PartialEq, Eq)]
#[allow(non_snake_case)]
pub struct WmmRule {
pub vo_c: WmmRuleItem,
pub vi_c: WmmRuleItem,
pub be_c: WmmRuleItem,
pub bk_c: WmmRuleItem,
pub vo_ap: WmmRuleItem,
pub vi_ap: WmmRuleItem,
pub be_ap: WmmRuleItem,
pub bk_ap: WmmRuleItem,
}
impl WmmRule {
fn from_lexer(it: &mut Peekable<Iter<TokType>>) -> Result<Self> {
let mut result = Self::default();
let mut set = Vec::new();
while let Some(&t) = it.peek() {
match &t {
TokType::String(x) if x == "country" || x == "wmmrule" => break,
TokType::String(_) => {
let name = it.next().unwrap().get_string().unwrap(); set.push(name.clone());
match it.next() {
Some(TokType::Colon) => (),
v => bail!("not a vailid wmmrule item {}: {:?}", name, v),
}
let ret = match name.as_str() {
"vo_c" => &mut result.vo_c,
"vi_c" => &mut result.vi_c,
"be_c" => &mut result.be_c,
"bk_c" => &mut result.bk_c,
"vo_ap" => &mut result.vo_ap,
"vi_ap" => &mut result.vi_ap,
"be_ap" => &mut result.be_ap,
"bk_ap" => &mut result.bk_ap,
v => bail!("not a valid wwmrule item {}: {:?}", name, v),
};
let mut set_item = Vec::new();
while let Some(&t) = it.peek() {
match &t {
TokType::String(_) => {
let name = it.next().unwrap().get_string().unwrap(); set_item.push(name.clone());
match it.next() {
Some(TokType::Equals) => (),
v => bail!("not a vailid wmmrule item {}: {:?}", name, v),
}
let ret = match name.as_str() {
"cw_min" => &mut ret.cw_min,
"cw_max" => &mut ret.cw_max,
"aifsn" => &mut ret.aifsn,
"cot" => &mut ret.cot,
v => bail!("not a valid wwmrule item {}: {:?}", name, v),
};
let value = it
.next()
.map(|x| x.get_int())
.ok_or_else(|| anyhow!("not a vailid wmmrule item"))??;
*ret = value;
match it.peek() {
Some(TokType::Comma) => {
it.next();
}
_ => break,
}
}
v => bail!("not expected for a wmmrule item: {:?}", v),
}
}
for x in &WMMRULEITEM_ITEMS {
if !set_item.contains(&x.to_string()) {
bail!("wmm rule item {} does not conain {}", name, x);
}
}
}
v => bail!("not expected for a wmmrule: {:?}", v),
}
}
for x in &WMMRULE_ITEMS {
if !set.contains(&x.to_string()) {
bail!("wmm rule does not conain {}", x);
}
}
Ok(result)
}
}
#[allow(non_snake_case)]
#[derive(Debug, Default, PartialEq, Eq)]
pub struct WmmRuleItem {
pub cw_min: usize,
pub cw_max: usize,
pub aifsn: usize,
pub cot: usize,
}
const WMMRULEITEM_ITEMS: [&str; 4] = ["cw_min", "cw_max", "aifsn", "cot"];
#[derive(Debug, PartialEq, Eq, Default)]
pub struct Country {
pub frequencies: HashMap<(String, String), FrequencyBand>,
pub dfs: DfsRegion,
}
impl Country {
fn from_lexer(
name: &str,
it: &mut Peekable<Iter<TokType>>,
dfs: Option<String>,
) -> Result<Self> {
let mut result = Self::default();
result.dfs = dfs
.map(|v| DfsRegion::try_from(v.as_str()).ok())
.flatten()
.unwrap_or(DfsRegion::None);
let conv_pwr = |p: f64, mw: bool| -> f64 {
if mw {
10.0f64 * (p.log10())
} else {
p
}
};
while let Some(&t) = it.peek() {
match t {
TokType::LParen => {
it.next();
let from = Self::get_freq(it.next())?;
match it.next() {
Some(TokType::Minus) => (),
v => bail!("not a vailid country {}: {:?}", name, v),
}
let to = Self::get_freq(it.next())?;
match it.next() {
Some(TokType::At) => (),
v => bail!("not a vailid country {}: {:?}", name, v),
}
let freqs = Self::check_band(&from, &to)?;
let size = Self::get_freq(it.next())?.parse()?;
match it.next() {
Some(TokType::RParen) => (),
v => bail!("not a vailid country {}: {:?}", name, v),
}
match it.next() {
Some(TokType::Comma) => (),
v => bail!("not a vailid country {}: {:?}", name, v),
}
match it.next() {
Some(TokType::LParen) => (),
v => bail!("not a vailid country {}: {:?}", name, v),
}
let power = Self::get_freq(it.next())?.parse()?;
let power_unit = match it.peek() {
Some(TokType::String(x)) => {
it.next();
Some(x.to_string())
}
_ => None,
};
let power = conv_pwr(power, power_unit.is_some());
match it.next() {
Some(TokType::RParen) => (),
v => bail!("not a vailid country {}: {:?}", name, v),
}
match it.peek() {
Some(TokType::Comma) => (),
_ => {
result.frequencies.insert(
(from, to),
FrequencyBand::new(freqs, size, power, power_unit),
);
continue;
}
}
let mut flags = Vec::new();
let mut wmmrule = None;
while let Some(TokType::Comma) = it.peek() {
it.next();
let name = it
.next()
.map(|x| x.get_string())
.ok_or_else(|| anyhow!("wmmrule does not contain a name"))??;
if let Some(TokType::Equals) = it.peek() {
it.next();
let rule = it
.next()
.map(|x| x.get_string())
.ok_or_else(|| anyhow!("wmmrule does not contain a name"))??;
assert_eq!(name, "wmmrule"); assert!(wmmrule.is_none()); wmmrule = Some(rule);
} else {
flags.push(name);
}
}
result.frequencies.insert(
(from, to),
FrequencyBand {
freqs,
size,
power,
power_unit,
flags: Flags(flags),
wmmrule,
},
);
}
_ => break,
}
}
Ok(result)
}
fn get_freq(tok: Option<&TokType>) -> Result<String> {
match tok {
Some(TokType::Int(x)) => Ok(x.to_string()),
Some(TokType::String(x)) => Ok(x.to_string()),
_ => bail!("invalid country"),
}
}
fn check_band(from: &str, to: &str) -> Result<(f64, f64)> {
let from = from.parse::<f64>()?;
let to = to.parse::<f64>()?;
if to <= from {
bail!("freqency in wrong order {} < {}", to, from);
}
Ok((from, to))
}
}
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Ord, PartialOrd)]
pub enum DfsRegion {
None = 0,
FCC = 1,
ETSI = 2,
JP = 3,
}
impl TryFrom<&str> for DfsRegion {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"DFS-FCC" => Ok(DfsRegion::FCC),
"DFS-ETSI" => Ok(DfsRegion::ETSI),
"DFS-JP" => Ok(DfsRegion::JP),
v => bail!("{} is not a dfs region", v),
}
}
}
impl Default for DfsRegion {
fn default() -> Self {
DfsRegion::None
}
}
#[derive(Debug)]
pub struct FrequencyBand {
pub freqs: (f64, f64),
pub size: f64,
pub power: f64,
pub power_unit: Option<String>,
pub flags: Flags, pub wmmrule: Option<String>, }
impl FrequencyBand {
pub fn new(freqs: (f64, f64), size: f64, power: f64, power_unit: Option<String>) -> Self {
assert!(!freqs.0.is_nan());
assert!(!freqs.1.is_nan());
Self {
freqs,
size,
power,
power_unit,
flags: Flags(Vec::new()),
wmmrule: None,
}
}
}
impl std::hash::Hash for FrequencyBand {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
((self.freqs.0 * 1000f64) as usize).hash(state);
((self.freqs.1 * 1000f64) as usize).hash(state);
((self.power * 100f64) as usize).hash(state);
((self.size * 1000f64) as usize).hash(state);
self.flags.to_u8().hash(state);
}
}
impl Ord for FrequencyBand {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.freqs.0.partial_cmp(&other.freqs.0) {
Some(std::cmp::Ordering::Equal) => match self.freqs.1.partial_cmp(&other.freqs.1) {
Some(v) if v == std::cmp::Ordering::Equal => {
match self.size.partial_cmp(&other.size) {
Some(std::cmp::Ordering::Equal) => {
match self.power.partial_cmp(&other.power) {
Some(std::cmp::Ordering::Equal) => {
match self.flags.to_u8().cmp(&other.flags.to_u8()) {
std::cmp::Ordering::Equal => {
self.wmmrule.cmp(&other.wmmrule)
}
v => v,
}
}
Some(v) => v,
None => unreachable!(), }
}
Some(v) => v,
None => unreachable!(),
}
}
Some(v) => v,
None => unreachable!(),
},
Some(v) => v,
None => unreachable!(),
}
}
}
impl PartialOrd for FrequencyBand {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for FrequencyBand {
fn eq(&self, other: &Self) -> bool {
self.freqs.0 == other.freqs.0
&& self.freqs.1 == other.freqs.1
&& self.size == other.size
&& self.power == other.power
&& self.flags.to_u8() == other.flags.to_u8()
&& self.wmmrule == other.wmmrule
}
}
#[derive(Debug, PartialEq, Eq, Default)]
pub struct Flags(Vec<String>);
impl Flags {
pub fn to_u8(&self) -> u8 {
let mut flags = 0;
for v in &self.0 {
match v.as_str() {
"NO-OFDM" => flags |= 1, "NO-OUTDOOR" => flags |= 1 << 1,
"DFS" => flags |= 1 << 2,
"NO-IR" => flags |= 1 << 3,
"AUTO-BW" => flags |= 1 << 4,
_ => (),
}
}
flags
}
}
impl Ord for Flags {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.to_u8().cmp(&other.to_u8())
}
}
impl PartialOrd for Flags {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Eq for FrequencyBand {}
impl std::ops::Deref for Flags {
type Target = Vec<String>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod test {
use super::{DfsRegion, HashMap, RegDB, TokType};
#[test]
fn wmmrule() {
#[rustfmt::skip]
let wmmrule = r#"
wmmrule ETSI:
vo_c: cw_min=3, cw_max=7, aifsn=2, cot=2
vi_c: cw_min=7, cw_max=15, aifsn=2, cot=4
be_c: cw_min=15, cw_max=1023, aifsn=3, cot=6
bk_c: cw_min=15, cw_max=1023, aifsn=7, cot=6
vo_ap: cw_min=3, cw_max=7, aifsn=1, cot=2
vi_ap: cw_min=7, cw_max=15, aifsn=1, cot=4
be_ap: cw_min=15, cw_max=63, aifsn=3, cot=6
bk_ap: cw_min=15, cw_max=1023, aifsn=7, cot=6
"#;
let wmmrule = TokType::parse_str(wmmrule).unwrap();
let db = RegDB::from_lexer(wmmrule).unwrap();
let mut should = super::WmmRule::default();
should.vo_c = super::WmmRuleItem {
cw_min: 3,
cw_max: 7,
aifsn: 2,
cot: 2,
};
should.vi_c = super::WmmRuleItem {
cw_min: 7,
cw_max: 15,
aifsn: 2,
cot: 4,
};
should.be_c = super::WmmRuleItem {
cw_min: 15,
cw_max: 1023,
aifsn: 3,
cot: 6,
};
should.bk_c = super::WmmRuleItem {
cw_min: 15,
cw_max: 1023,
aifsn: 7,
cot: 6,
};
should.vo_ap = super::WmmRuleItem {
cw_min: 3,
cw_max: 7,
aifsn: 1,
cot: 2,
};
should.vi_ap = super::WmmRuleItem {
cw_min: 7,
cw_max: 15,
aifsn: 1,
cot: 4,
};
should.be_ap = super::WmmRuleItem {
cw_min: 15,
cw_max: 63,
aifsn: 3,
cot: 6,
};
should.bk_ap = super::WmmRuleItem {
cw_min: 15,
cw_max: 1023,
aifsn: 7,
cot: 6,
};
let mut should_hm = HashMap::new();
should_hm.insert("ETSI".to_string(), should);
let should = RegDB {
wmm_rules: should_hm,
countries: HashMap::new(),
};
assert_eq!(db, should);
}
#[test]
fn invalid_wmmrule() {
#[rustfmt::skip]
let wmmrule = r#"
wmmrule ETSI:
vo_c: cw_min=3, cw_max=7, aifsn=2, cot=2
vi_c: cw_min=7, cw_max=15, aifsn=2, cot=4
be_c: cw_min=15, cw_max=1023, aifsn=3, cot=6
bk_c: cw_min=15, cw_max=1023, aifsn=7, cot=6
vo_ap: cw_min=3, cw_max=7, aifsn=1, cot=2
vi_ap: cw_min=7, cw_max=15, aifsn=1, cot=4
be_ap: cw_min=15, cw_max=63, aifsn=3, cot=6
"#;
let wmmrule = TokType::parse_str(wmmrule).unwrap();
let db = RegDB::from_lexer(wmmrule);
assert!(db.is_err());
}
#[test]
fn invalid_wmmrule_item() {
#[rustfmt::skip]
let wmmrule = r#"
wmmrule ETSI:
vo_c: cw_min=3, cw_max=7, aifsn=2, cot=2
vi_c: cw_min=7, cw_max=15, aifsn=2, cot=4
be_c: cw_min=15, cw_max=1023, aifsn=3, cot=6
bk_c: cw_min=15, cw_max=1023, aifsn=7, cot=6
vo_ap: cw_min=3, cw_max=7, aifsn=1, cot=2
vi_ap: cw_min=7, cw_max=15, aifsn=1, cot=4
be_ap: cw_min=15, cw_max=63, aifsn=3
bk_ap: cw_min=15, cw_max=1023, aifsn=7, cot=6
"#;
let wmmrule = TokType::parse_str(wmmrule).unwrap();
let db = RegDB::from_lexer(wmmrule);
assert!(db.is_err());
}
#[test]
fn parse_db() {
let db = include_str!("./tests/db_2.txt");
let lexer = TokType::parse_str(db).unwrap();
let db = RegDB::from_lexer(lexer).unwrap();
assert_eq!(db.countries.len(), 2);
assert_eq!(db.wmm_rules.len(), 1);
assert!(db.countries.get("AD").is_some());
assert_eq!(db.countries.get("AD").unwrap().dfs, DfsRegion::ETSI);
assert_eq!(db.countries.get("00").unwrap().dfs, DfsRegion::None);
let ad = db.countries.get("AD").unwrap();
let freq = ad
.frequencies
.get(&("5150".to_string(), "5250".to_string()))
.unwrap();
assert_eq!(freq.flags.len(), 2);
assert!(freq.wmmrule.is_some());
assert_eq!(freq.wmmrule.as_ref().unwrap(), "ETSI");
}
#[test]
fn write_db() {
let db = include_str!("./../db.txt");
let lexer = TokType::parse_str(db).unwrap();
let db = RegDB::from_lexer(lexer).unwrap();
let db = super::binary::Binary::from_regdb(&db).unwrap();
db.write_file("/dev/null").unwrap(); }
}