1use std::collections::HashMap;
17
18use std::iter::Peekable;
19use std::slice::Iter;
20
21pub(crate) mod binary;
22pub mod lexer;
23
24pub use crate::binary::Binary;
25pub use crate::lexer::TokType;
26
27use anyhow::{anyhow, bail, Result};
28use std::convert::TryFrom;
29
30#[derive(Debug, Default, PartialEq, Eq)]
32pub struct RegDB {
33 pub wmm_rules: HashMap<String, WmmRule>,
37 pub countries: HashMap<String, Country>,
39}
40
41impl RegDB {
42 pub fn from_lexer(lexer: Vec<TokType>) -> Result<Self> {
48 let mut ret = Self::default();
49
50 let mut it = lexer.iter().peekable();
51
52 while let Some(&v) = it.peek() {
53 match &v {
54 TokType::String(v) if v == "wmmrule" => {
55 it.next();
56 let name = it
57 .next()
58 .map(|x| x.get_string())
59 .ok_or_else(|| anyhow!("wmmrule does not contain a name"))??;
60 match it.next() {
61 Some(TokType::Colon) => (),
62 v => bail!("not a vailid wmmrule {}: {:?}", name, v),
63 }
64 ret.wmm_rules.insert(name, WmmRule::from_lexer(&mut it)?);
65 }
66 TokType::String(v) if v == "country" => {
67 it.next();
68 let name = match it.next() {
70 Some(TokType::String(v)) => v.to_string(),
71 Some(TokType::Int(v)) if *v == 0 => String::from("00"),
72 _ => bail!("country does not contain a name"),
73 };
74 match it.next() {
75 Some(TokType::Colon) => (),
76 v => bail!("not a vailid country {}: {:?}", name, v),
77 }
78
79 let dfs = match it.peek() {
80 Some(TokType::String(v)) => {
81 it.next();
82 Some(v.to_string())
83 }
84 _ => None,
85 };
86
87 ret.countries
88 .insert(name.clone(), Country::from_lexer(&name, &mut it, dfs)?);
89 }
90 v => bail!("not expected for a regulatory db: {:?}", v),
91 }
92 }
93
94 Ok(ret)
95 }
96}
97
98const WMMRULE_ITEMS: [&str; 8] = [
99 "vo_c", "vi_c", "be_c", "bk_c", "vo_ap", "vi_ap", "be_ap", "bk_ap",
100];
101
102#[derive(Debug, Default, PartialEq, Eq)]
104#[allow(non_snake_case)]
105pub struct WmmRule {
106 pub vo_c: WmmRuleItem,
107 pub vi_c: WmmRuleItem,
108 pub be_c: WmmRuleItem,
109 pub bk_c: WmmRuleItem,
110 pub vo_ap: WmmRuleItem,
111 pub vi_ap: WmmRuleItem,
112 pub be_ap: WmmRuleItem,
113 pub bk_ap: WmmRuleItem,
114}
115
116impl WmmRule {
117 fn from_lexer(it: &mut Peekable<Iter<TokType>>) -> Result<Self> {
118 let mut result = Self::default();
119 let mut set = Vec::new();
120
121 while let Some(&t) = it.peek() {
122 match &t {
123 TokType::String(x) if x == "country" || x == "wmmrule" => break,
124 TokType::String(_) => {
125 let name = it.next().unwrap().get_string().unwrap(); set.push(name.clone());
127 match it.next() {
128 Some(TokType::Colon) => (),
129 v => bail!("not a vailid wmmrule item {}: {:?}", name, v),
130 }
131 let ret = match name.as_str() {
132 "vo_c" => &mut result.vo_c,
133 "vi_c" => &mut result.vi_c,
134 "be_c" => &mut result.be_c,
135 "bk_c" => &mut result.bk_c,
136 "vo_ap" => &mut result.vo_ap,
137 "vi_ap" => &mut result.vi_ap,
138 "be_ap" => &mut result.be_ap,
139 "bk_ap" => &mut result.bk_ap,
140 v => bail!("not a valid wwmrule item {}: {:?}", name, v),
141 };
142
143 let mut set_item = Vec::new();
144 while let Some(&t) = it.peek() {
145 match &t {
146 TokType::String(_) => {
147 let name = it.next().unwrap().get_string().unwrap(); set_item.push(name.clone());
149 match it.next() {
150 Some(TokType::Equals) => (),
151 v => bail!("not a vailid wmmrule item {}: {:?}", name, v),
152 }
153 let ret = match name.as_str() {
154 "cw_min" => &mut ret.cw_min,
155 "cw_max" => &mut ret.cw_max,
156 "aifsn" => &mut ret.aifsn,
157 "cot" => &mut ret.cot,
158 v => bail!("not a valid wwmrule item {}: {:?}", name, v),
159 };
160
161 let value = it
162 .next()
163 .map(|x| x.get_int())
164 .ok_or_else(|| anyhow!("not a vailid wmmrule item"))??;
165 *ret = value;
166 match it.peek() {
167 Some(TokType::Comma) => {
168 it.next();
169 }
170 _ => break,
171 }
172 }
173 v => bail!("not expected for a wmmrule item: {:?}", v),
174 }
175 }
176
177 for x in &WMMRULEITEM_ITEMS {
178 if !set_item.contains(&x.to_string()) {
179 bail!("wmm rule item {} does not conain {}", name, x);
180 }
181 }
182 }
183 v => bail!("not expected for a wmmrule: {:?}", v),
184 }
185 }
186
187 for x in &WMMRULE_ITEMS {
188 if !set.contains(&x.to_string()) {
189 bail!("wmm rule does not conain {}", x);
190 }
191 }
192
193 Ok(result)
194 }
195}
196
197#[allow(non_snake_case)]
201#[derive(Debug, Default, PartialEq, Eq)]
202pub struct WmmRuleItem {
203 pub cw_min: usize,
204 pub cw_max: usize,
205 pub aifsn: usize,
206 pub cot: usize,
207}
208
209const WMMRULEITEM_ITEMS: [&str; 4] = ["cw_min", "cw_max", "aifsn", "cot"];
210
211#[derive(Debug, PartialEq, Eq, Default)]
213pub struct Country {
214 pub frequencies: HashMap<(String, String), FrequencyBand>,
215 pub dfs: DfsRegion,
216}
217
218impl Country {
219 fn from_lexer(
220 name: &str,
221 it: &mut Peekable<Iter<TokType>>,
222 dfs: Option<String>,
223 ) -> Result<Self> {
224 let mut result = Self::default();
225 result.dfs = dfs
226 .map(|v| DfsRegion::try_from(v.as_str()).ok())
227 .flatten()
228 .unwrap_or(DfsRegion::None);
229
230 let conv_pwr = |p: f64, mw: bool| -> f64 {
231 if mw {
232 10.0f64 * (p.log10())
233 } else {
234 p
235 }
236 };
237
238 while let Some(&t) = it.peek() {
239 match t {
240 TokType::LParen => {
241 it.next();
242 let from = Self::get_freq(it.next())?;
243 match it.next() {
244 Some(TokType::Minus) => (),
245 v => bail!("not a vailid country {}: {:?}", name, v),
246 }
247 let to = Self::get_freq(it.next())?;
248 match it.next() {
249 Some(TokType::At) => (),
250 v => bail!("not a vailid country {}: {:?}", name, v),
251 }
252 let freqs = Self::check_band(&from, &to)?;
254
255 let size = Self::get_freq(it.next())?.parse()?;
256 match it.next() {
257 Some(TokType::RParen) => (),
258 v => bail!("not a vailid country {}: {:?}", name, v),
259 }
260 match it.next() {
261 Some(TokType::Comma) => (),
262 v => bail!("not a vailid country {}: {:?}", name, v),
263 }
264 match it.next() {
265 Some(TokType::LParen) => (),
266 v => bail!("not a vailid country {}: {:?}", name, v),
267 }
268
269 let power = Self::get_freq(it.next())?.parse()?;
270 let power_unit = match it.peek() {
271 Some(TokType::String(x)) => {
272 it.next();
273 Some(x.to_string())
274 }
275 _ => None,
276 };
277 let power = conv_pwr(power, power_unit.is_some());
278
279 match it.next() {
280 Some(TokType::RParen) => (),
281 v => bail!("not a vailid country {}: {:?}", name, v),
282 }
283
284 match it.peek() {
285 Some(TokType::Comma) => (),
286 _ => {
287 result.frequencies.insert(
288 (from, to),
289 FrequencyBand::new(freqs, size, power, power_unit),
290 );
291 continue;
292 }
293 }
294
295 let mut flags = Vec::new();
296 let mut wmmrule = None;
297
298 while let Some(TokType::Comma) = it.peek() {
299 it.next();
300
301 let name = it
302 .next()
303 .map(|x| x.get_string())
304 .ok_or_else(|| anyhow!("wmmrule does not contain a name"))??;
305 if let Some(TokType::Equals) = it.peek() {
306 it.next();
307 let rule = it
308 .next()
309 .map(|x| x.get_string())
310 .ok_or_else(|| anyhow!("wmmrule does not contain a name"))??;
311 assert_eq!(name, "wmmrule"); assert!(wmmrule.is_none()); wmmrule = Some(rule);
314 } else {
316 flags.push(name);
317 }
318 }
319
320 result.frequencies.insert(
322 (from, to),
323 FrequencyBand {
324 freqs,
325 size,
326 power,
327 power_unit,
328 flags: Flags(flags),
329 wmmrule,
330 },
331 );
332 }
333 _ => break,
334 }
335 }
336
337 Ok(result)
338 }
339
340 fn get_freq(tok: Option<&TokType>) -> Result<String> {
341 match tok {
342 Some(TokType::Int(x)) => Ok(x.to_string()),
343 Some(TokType::String(x)) => Ok(x.to_string()),
344 _ => bail!("invalid country"),
345 }
346 }
347
348 fn check_band(from: &str, to: &str) -> Result<(f64, f64)> {
349 let from = from.parse::<f64>()?;
350 let to = to.parse::<f64>()?;
351 if to <= from {
352 bail!("freqency in wrong order {} < {}", to, from);
353 }
354
355 Ok((from, to))
356 }
357}
358
359#[repr(u8)]
361#[derive(Debug, PartialEq, Eq, Copy, Clone, Ord, PartialOrd)]
363pub enum DfsRegion {
364 None = 0,
365 FCC = 1,
366 ETSI = 2,
367 JP = 3,
368}
369
370impl TryFrom<&str> for DfsRegion {
371 type Error = anyhow::Error;
372
373 fn try_from(value: &str) -> Result<Self, Self::Error> {
374 match value {
375 "DFS-FCC" => Ok(DfsRegion::FCC),
376 "DFS-ETSI" => Ok(DfsRegion::ETSI),
377 "DFS-JP" => Ok(DfsRegion::JP),
378 v => bail!("{} is not a dfs region", v),
379 }
380 }
381}
382
383impl Default for DfsRegion {
384 fn default() -> Self {
385 DfsRegion::None
386 }
387}
388
389#[derive(Debug)]
391pub struct FrequencyBand {
392 pub freqs: (f64, f64),
393 pub size: f64,
394 pub power: f64,
395 pub power_unit: Option<String>,
396 pub flags: Flags, pub wmmrule: Option<String>, }
399
400impl FrequencyBand {
401 pub fn new(freqs: (f64, f64), size: f64, power: f64, power_unit: Option<String>) -> Self {
402 assert!(!freqs.0.is_nan());
403 assert!(!freqs.1.is_nan());
404 Self {
405 freqs,
406 size,
407 power,
408 power_unit,
409 flags: Flags(Vec::new()),
410 wmmrule: None,
411 }
412 }
413}
414
415impl std::hash::Hash for FrequencyBand {
416 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
417 ((self.freqs.0 * 1000f64) as usize).hash(state);
418 ((self.freqs.1 * 1000f64) as usize).hash(state);
419 ((self.power * 100f64) as usize).hash(state);
420 ((self.size * 1000f64) as usize).hash(state);
421 self.flags.to_u8().hash(state);
422 }
423}
424
425impl Ord for FrequencyBand {
426 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
427 match self.freqs.0.partial_cmp(&other.freqs.0) {
428 Some(std::cmp::Ordering::Equal) => match self.freqs.1.partial_cmp(&other.freqs.1) {
429 Some(v) if v == std::cmp::Ordering::Equal => {
430 match self.size.partial_cmp(&other.size) {
431 Some(std::cmp::Ordering::Equal) => {
432 match self.power.partial_cmp(&other.power) {
433 Some(std::cmp::Ordering::Equal) => {
434 match self.flags.to_u8().cmp(&other.flags.to_u8()) {
435 std::cmp::Ordering::Equal => {
436 self.wmmrule.cmp(&other.wmmrule)
437 }
438 v => v,
439 }
440 }
441 Some(v) => v,
442 None => unreachable!(), }
444 }
445 Some(v) => v,
446 None => unreachable!(),
447 }
448 }
449 Some(v) => v,
450 None => unreachable!(),
451 },
452 Some(v) => v,
453 None => unreachable!(),
454 }
455 }
456}
457
458impl PartialOrd for FrequencyBand {
459 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
460 Some(self.cmp(other))
461 }
462}
463
464impl PartialEq for FrequencyBand {
465 fn eq(&self, other: &Self) -> bool {
466 self.freqs.0 == other.freqs.0
467 && self.freqs.1 == other.freqs.1
468 && self.size == other.size
469 && self.power == other.power
470 && self.flags.to_u8() == other.flags.to_u8()
471 && self.wmmrule == other.wmmrule
472 }
473}
474
475#[derive(Debug, PartialEq, Eq, Default)]
477pub struct Flags(Vec<String>);
478
479impl Flags {
480 pub fn to_u8(&self) -> u8 {
481 let mut flags = 0;
482 for v in &self.0 {
483 match v.as_str() {
484 "NO-OFDM" => flags |= 1, "NO-OUTDOOR" => flags |= 1 << 1,
486 "DFS" => flags |= 1 << 2,
487 "NO-IR" => flags |= 1 << 3,
488 "AUTO-BW" => flags |= 1 << 4,
489 _ => (),
490 }
491 }
492 flags
493 }
494}
495
496impl Ord for Flags {
497 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
498 self.to_u8().cmp(&other.to_u8())
499 }
500}
501
502impl PartialOrd for Flags {
503 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
504 Some(self.cmp(other))
505 }
506}
507
508impl Eq for FrequencyBand {}
509
510impl std::ops::Deref for Flags {
511 type Target = Vec<String>;
512
513 fn deref(&self) -> &Self::Target {
514 &self.0
515 }
516}
517
518#[cfg(test)]
519mod test {
520 use super::{DfsRegion, HashMap, RegDB, TokType};
521 #[test]
522 fn wmmrule() {
523 #[rustfmt::skip]
524 let wmmrule = r#"
525wmmrule ETSI:
526 vo_c: cw_min=3, cw_max=7, aifsn=2, cot=2
527 vi_c: cw_min=7, cw_max=15, aifsn=2, cot=4
528 be_c: cw_min=15, cw_max=1023, aifsn=3, cot=6
529 bk_c: cw_min=15, cw_max=1023, aifsn=7, cot=6
530 vo_ap: cw_min=3, cw_max=7, aifsn=1, cot=2
531 vi_ap: cw_min=7, cw_max=15, aifsn=1, cot=4
532 be_ap: cw_min=15, cw_max=63, aifsn=3, cot=6
533 bk_ap: cw_min=15, cw_max=1023, aifsn=7, cot=6
534 "#;
535
536 let wmmrule = TokType::parse_str(wmmrule).unwrap();
537
538 let db = RegDB::from_lexer(wmmrule).unwrap();
539
540 let mut should = super::WmmRule::default();
541 should.vo_c = super::WmmRuleItem {
542 cw_min: 3,
543 cw_max: 7,
544 aifsn: 2,
545 cot: 2,
546 };
547 should.vi_c = super::WmmRuleItem {
548 cw_min: 7,
549 cw_max: 15,
550 aifsn: 2,
551 cot: 4,
552 };
553 should.be_c = super::WmmRuleItem {
554 cw_min: 15,
555 cw_max: 1023,
556 aifsn: 3,
557 cot: 6,
558 };
559 should.bk_c = super::WmmRuleItem {
560 cw_min: 15,
561 cw_max: 1023,
562 aifsn: 7,
563 cot: 6,
564 };
565 should.vo_ap = super::WmmRuleItem {
566 cw_min: 3,
567 cw_max: 7,
568 aifsn: 1,
569 cot: 2,
570 };
571 should.vi_ap = super::WmmRuleItem {
572 cw_min: 7,
573 cw_max: 15,
574 aifsn: 1,
575 cot: 4,
576 };
577 should.be_ap = super::WmmRuleItem {
578 cw_min: 15,
579 cw_max: 63,
580 aifsn: 3,
581 cot: 6,
582 };
583 should.bk_ap = super::WmmRuleItem {
584 cw_min: 15,
585 cw_max: 1023,
586 aifsn: 7,
587 cot: 6,
588 };
589
590 let mut should_hm = HashMap::new();
591 should_hm.insert("ETSI".to_string(), should);
592 let should = RegDB {
593 wmm_rules: should_hm,
594 countries: HashMap::new(),
595 };
596
597 assert_eq!(db, should);
598 }
599
600 #[test]
601 fn invalid_wmmrule() {
602 #[rustfmt::skip]
603 let wmmrule = r#"
604wmmrule ETSI:
605 vo_c: cw_min=3, cw_max=7, aifsn=2, cot=2
606 vi_c: cw_min=7, cw_max=15, aifsn=2, cot=4
607 be_c: cw_min=15, cw_max=1023, aifsn=3, cot=6
608 bk_c: cw_min=15, cw_max=1023, aifsn=7, cot=6
609 vo_ap: cw_min=3, cw_max=7, aifsn=1, cot=2
610 vi_ap: cw_min=7, cw_max=15, aifsn=1, cot=4
611 be_ap: cw_min=15, cw_max=63, aifsn=3, cot=6
612 "#;
613
614 let wmmrule = TokType::parse_str(wmmrule).unwrap();
615
616 let db = RegDB::from_lexer(wmmrule);
617
618 assert!(db.is_err());
619 }
620
621 #[test]
622 fn invalid_wmmrule_item() {
623 #[rustfmt::skip]
624 let wmmrule = r#"
625wmmrule ETSI:
626 vo_c: cw_min=3, cw_max=7, aifsn=2, cot=2
627 vi_c: cw_min=7, cw_max=15, aifsn=2, cot=4
628 be_c: cw_min=15, cw_max=1023, aifsn=3, cot=6
629 bk_c: cw_min=15, cw_max=1023, aifsn=7, cot=6
630 vo_ap: cw_min=3, cw_max=7, aifsn=1, cot=2
631 vi_ap: cw_min=7, cw_max=15, aifsn=1, cot=4
632 be_ap: cw_min=15, cw_max=63, aifsn=3
633 bk_ap: cw_min=15, cw_max=1023, aifsn=7, cot=6
634 "#;
635
636 let wmmrule = TokType::parse_str(wmmrule).unwrap();
637
638 let db = RegDB::from_lexer(wmmrule);
639
640 assert!(db.is_err());
641 }
642
643 #[test]
644 fn parse_db() {
645 let db = include_str!("./tests/db_2.txt");
646
647 let lexer = TokType::parse_str(db).unwrap();
648 let db = RegDB::from_lexer(lexer).unwrap();
649
650 assert_eq!(db.countries.len(), 2);
651 assert_eq!(db.wmm_rules.len(), 1);
652
653 assert!(db.countries.get("AD").is_some());
654
655 assert_eq!(db.countries.get("AD").unwrap().dfs, DfsRegion::ETSI);
656 assert_eq!(db.countries.get("00").unwrap().dfs, DfsRegion::None);
657
658 let ad = db.countries.get("AD").unwrap();
659 let freq = ad
660 .frequencies
661 .get(&("5150".to_string(), "5250".to_string()))
662 .unwrap();
663 assert_eq!(freq.flags.len(), 2);
664 assert!(freq.wmmrule.is_some());
665 assert_eq!(freq.wmmrule.as_ref().unwrap(), "ETSI");
666 }
667
668 #[test]
669 fn write_db() {
670 let db = include_str!("./../db.txt");
671
672 let lexer = TokType::parse_str(db).unwrap();
673 let db = RegDB::from_lexer(lexer).unwrap();
674
675 let db = super::binary::Binary::from_regdb(&db).unwrap();
676
677 db.write_file("/dev/null").unwrap(); }
679}