wireless_regdb/
binary.rs

1use std::collections::HashMap;
2use std::fmt;
3
4use byteorder::{BigEndian, WriteBytesExt};
5use std::fs::OpenOptions;
6
7use anyhow::{anyhow, bail, Result};
8
9const MAGIC: u32 = 0x52474442;
10const VERSION: u32 = 20;
11
12/// Binary representation of the regulatory Database
13#[derive(Debug)]
14pub struct Binary {
15    // MAGIC (4)
16    // Version (4)
17    countries: Vec<BinaryCountry>,
18    // Padding (4)
19    wmmdbs: Vec<BinaryWmmDB>,
20    rules_db: Vec<BinaryRegRule>,
21    // Maybe Padding
22    collections: Vec<BinaryCollection>,
23}
24
25impl Binary {
26    /// Create a Binary representation of the Regulatory DB
27    ///
28    /// # Arguments
29    ///
30    /// * `regdb` - reference of a regulatory database
31    pub fn from_regdb(regdb: &super::RegDB) -> Result<Self> {
32        let mut countries: Vec<BinaryCountry> = Vec::new();
33        let mut wmmdbs: Vec<BinaryWmmDB> = Vec::new();
34        let mut wmmdb_pos: HashMap<String, usize> = HashMap::with_capacity(regdb.wmm_rules.len());
35
36        for n in regdb.countries.keys() {
37            if n.len() != 2 {
38                bail!("country name {} is not 2 characters", n);
39            }
40            let n = n.clone();
41            countries.push(BinaryCountry::new(n)?);
42        }
43        countries.sort();
44
45        for (n, w) in &regdb.wmm_rules {
46            let pos = (8 + countries.len() * 4 + 4 + wmmdbs.len() * 4 * 8) >> 2;
47            wmmdbs.push(BinaryWmmDB::new(w)?);
48            wmmdb_pos.insert(n.clone(), pos);
49        }
50
51        let mut pos = 8 + countries.len() * 4 + 4 + wmmdbs.len() * 4 * 8;
52
53        let mut rules = Vec::new();
54        for c in regdb.countries.values() {
55            for r in c.frequencies.values() {
56                rules.push(r);
57            }
58        }
59
60        rules.sort_unstable();
61        rules.dedup();
62
63        let mut reg_rules = HashMap::new();
64        let mut rules_db = Vec::new();
65        for r in rules {
66            assert!(!reg_rules.contains_key(r));
67
68            let wmmdb_pos = r
69                .wmmrule
70                .as_ref()
71                .map(|v| wmmdb_pos.get(v).copied())
72                .flatten();
73            let bin_rule = BinaryRegRule::new(r, wmmdb_pos)?;
74            let rule_size = bin_rule.size() as usize;
75            rules_db.push(bin_rule);
76
77            reg_rules.insert(r, pos);
78            pos += rule_size;
79        }
80
81        let mut coll = Self::create_collections(&regdb.countries);
82        coll.sort_unstable();
83        coll.dedup();
84
85        let mut collections = Vec::new();
86        for (r, d) in &coll {
87            for n in &mut countries {
88                let country = regdb
89                    .countries
90                    .get(&n.name)
91                    .ok_or_else(|| anyhow!("country {} not in db", n.name))?;
92                let mut c_freqs = country
93                    .frequencies
94                    .values()
95                    .collect::<Vec<&super::FrequencyBand>>();
96                c_freqs.sort_unstable();
97                c_freqs.dedup();
98                if &c_freqs == r && country.dfs == *d {
99                    n.pos = Some(pos as u16 >> 2);
100                }
101            }
102            let mut bin_coll = BinaryCollection::new(r.len() as u8, *d);
103            for r in r {
104                let pos = reg_rules.get(r).ok_or_else(|| anyhow!("rule not in db"))?;
105                let pos = *pos >> 2;
106                bin_coll.rules.push(pos as u16);
107            }
108
109            pos += bin_coll.len() as usize;
110            collections.push(bin_coll);
111        }
112
113        Ok(Self {
114            countries,
115            wmmdbs,
116            rules_db,
117            collections,
118        })
119    }
120
121    fn create_collections(
122        countries: &HashMap<String, super::Country>,
123    ) -> Vec<(Vec<&super::FrequencyBand>, super::DfsRegion)> {
124        let mut result = Vec::new();
125
126        for c in countries.values() {
127            let mut freqs = Vec::new();
128            for r in c.frequencies.values() {
129                freqs.push(r);
130            }
131
132            freqs.sort_unstable();
133            freqs.dedup();
134
135            result.push((freqs, c.dfs));
136        }
137
138        result
139    }
140
141    /// Write database to file
142    ///
143    /// # Arguments
144    ///
145    /// * `path` - path of the file
146    pub fn write_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
147        let file = OpenOptions::new()
148            .write(true)
149            .create(true)
150            .truncate(true)
151            .open(path)?;
152
153        self.write(file)
154    }
155
156    /// Write database to Writer
157    ///
158    /// # Arguments
159    ///
160    /// * `writer` - `std::io::Writer` to write database to
161    pub fn write<T: std::io::Write>(&self, mut writer: T) -> Result<()> {
162        writer.write_u32::<BigEndian>(MAGIC)?;
163        writer.write_u32::<BigEndian>(VERSION)?;
164
165        self.countries
166            .iter()
167            .map(|c| c.write(&mut writer))
168            .collect::<Result<()>>()?;
169
170        writer.write_u32::<BigEndian>(0)?;
171
172        self.wmmdbs
173            .iter()
174            .map(|r| r.write(&mut writer))
175            .collect::<Result<()>>()?;
176
177        self.rules_db
178            .iter()
179            .map(|r| r.write(&mut writer))
180            .collect::<Result<()>>()?;
181
182        self.collections
183            .iter()
184            .map(|r| r.write(&mut writer))
185            .collect::<Result<()>>()?;
186
187        Ok(())
188    }
189}
190
191struct BinaryCountry {
192    /// Has to have a size of 2 bytes
193    name: String, // str
194    pos: Option<u16>, // ptr
195}
196
197impl BinaryCountry {
198    pub fn new(name: String) -> Result<Self> {
199        if name.len() != 2 {
200            bail!("country name '{}' is not 2 bytes in size", name);
201        }
202
203        Ok(BinaryCountry { name, pos: None })
204    }
205
206    pub fn write<T: std::io::Write>(&self, writer: &mut T) -> Result<()> {
207        writer.write_all(self.name.as_bytes())?;
208        writer.write_u16::<BigEndian>(
209            self.pos
210                .ok_or_else(|| anyhow!("countries has no position"))?,
211        )?;
212
213        Ok(())
214    }
215}
216
217impl fmt::Debug for BinaryCountry {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        write!(f, "BinaryCountry ({})", &self.name)
220    }
221}
222
223impl PartialEq<BinaryCountry> for BinaryCountry {
224    fn eq(&self, other: &BinaryCountry) -> bool {
225        self.name == other.name
226    }
227}
228
229impl Ord for BinaryCountry {
230    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
231        self.name.cmp(&other.name)
232    }
233}
234
235impl PartialOrd for BinaryCountry {
236    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
237        Some(self.cmp(other))
238    }
239}
240
241impl Eq for BinaryCountry {}
242
243#[derive(Debug)]
244struct BinaryWmmDB(Vec<u8>);
245
246impl BinaryWmmDB {
247    pub fn new(wmmrule: &super::WmmRule) -> Result<Self> {
248        let mut result = BinaryWmmDB(Vec::new());
249
250        result.add_wmmrule(&wmmrule.vo_c)?;
251        result.add_wmmrule(&wmmrule.vi_c)?;
252        result.add_wmmrule(&wmmrule.be_c)?;
253        result.add_wmmrule(&wmmrule.bk_c)?;
254        result.add_wmmrule(&wmmrule.vo_ap)?;
255        result.add_wmmrule(&wmmrule.vi_ap)?;
256        result.add_wmmrule(&wmmrule.be_ap)?;
257        result.add_wmmrule(&wmmrule.bk_ap)?;
258
259        Ok(result)
260    }
261
262    fn add_wmmrule(&mut self, item: &super::WmmRuleItem) -> Result<()> {
263        let ecw = ((item.cw_min as f64 + 1.0_f64).log(2_f64) as u8) << 4
264            | ((item.cw_max as f64 + 1.0_f64).log(2f64) as u8);
265
266        self.0.write_u8(ecw)?;
267        self.0.write_u8(item.aifsn as u8)?;
268        self.0.write_u16::<BigEndian>(item.cot as u16)?;
269
270        Ok(())
271    }
272
273    pub fn write<T: std::io::Write>(&self, writer: &mut T) -> Result<()> {
274        writer.write_all(&self.0)?;
275
276        Ok(())
277    }
278}
279
280#[derive(Debug, Default)]
281struct BinaryRegRule {
282    rule_len: u8,
283    flags: u8,
284    power: u16,
285    from: u32,
286    to: u32,
287    maxbw: u32,
288    cac_timeout: Option<u16>,
289    wwmdb_pos: Option<u16>,
290}
291
292impl BinaryRegRule {
293    pub fn new(regrule: &super::FrequencyBand, wwmdb_pos: Option<usize>) -> Result<Self> {
294        let mut result = Self::default();
295
296        //TODO: assert(power.max_ant_gain == 0)??
297        result.rule_len = 16;
298
299        // this is copied from upstream, so allow it for future use
300        /*let cac_timeout = if (regrule.flags.to_u8() & 1 << 2) != 0 {
301            0 // upstream TODO
302        } else {
303            0
304        };*/
305        let cac_timeout = 0;
306
307        result.cac_timeout = Some(cac_timeout); // TODO: ??
308
309        if wwmdb_pos.is_some() {
310            result.rule_len += 4; // ??? cac timeout foo?
311            result.wwmdb_pos = wwmdb_pos.map(|v| v as u16);
312        }
313
314        result.flags = regrule.flags.to_u8();
315        result.from = (regrule.freqs.0 * 1000f64) as u32;
316        result.power = (regrule.power * 100f64) as u16;
317        result.to = (regrule.freqs.1 * 1000f64) as u32;
318        result.maxbw = (regrule.size * 1000f64) as u32;
319
320        Ok(result)
321    }
322
323    pub fn size(&self) -> u8 {
324        let padding = if self.rule_len % 4 == 0 {
325            0
326        } else {
327            4 - (self.rule_len % 4)
328        };
329        self.rule_len + padding
330    }
331
332    pub fn write<T: std::io::Write>(&self, writer: &mut T) -> Result<()> {
333        writer.write_u8(self.rule_len)?;
334        writer.write_u8(self.flags)?;
335
336        writer.write_u16::<BigEndian>(self.power)?;
337
338        writer.write_u32::<BigEndian>(self.from)?;
339        writer.write_u32::<BigEndian>(self.to)?;
340        writer.write_u32::<BigEndian>(self.maxbw)?;
341
342        if self.rule_len > 16 {
343            writer.write_u16::<BigEndian>(
344                self.cac_timeout
345                    .ok_or_else(|| anyhow!("no cac_timeout specified"))?,
346            )?;
347        }
348
349        if self.rule_len > 18 {
350            writer.write_u16::<BigEndian>(
351                self.wwmdb_pos
352                    .ok_or_else(|| anyhow!("no wwmdbPos specified"))?,
353            )?;
354        }
355
356        if self.rule_len % 4 == 0 {
357            return Ok(());
358        }
359        for _ in 0..(4 - (self.rule_len % 4)) {
360            writer.write_all(&[0])?;
361        }
362
363        Ok(())
364    }
365}
366
367#[derive(Debug)]
368struct BinaryCollection {
369    len: u8,
370    dfs: super::DfsRegion,
371    rules: Vec<u16>,
372}
373
374const SLEN: u8 = 3;
375
376// It holds a binary len, so will never be emtpy
377#[allow(clippy::len_without_is_empty)]
378impl BinaryCollection {
379    pub fn new(len: u8, dfs: super::DfsRegion) -> Self {
380        Self {
381            len,
382            dfs,
383            rules: Vec::new(),
384        }
385    }
386    pub fn len(&self) -> u8 {
387        let padding = if self.len % 2 != 0 { 2 } else { 0 };
388        4 + self.len * 2 + padding
389    }
390
391    pub fn write<T: std::io::Write>(&self, writer: &mut T) -> Result<()> {
392        writer.write_u8(SLEN)?;
393        writer.write_u8(self.len)?;
394        writer.write_u8(self.dfs as u8)?;
395        writer.write_u8(0)?;
396
397        for d in &self.rules {
398            writer.write_u16::<BigEndian>(*d)?;
399        }
400
401        if self.len % 2 != 0 {
402            writer.write_u16::<BigEndian>(0)?;
403        }
404
405        Ok(())
406    }
407}
408
409#[cfg(test)]
410mod test {
411    use super::Binary;
412    #[test]
413    fn write_empty_db() {
414        let db = Binary {
415            countries: Vec::new(),
416            wmmdbs: Vec::new(),
417            rules_db: Vec::new(),
418            collections: Vec::new(),
419        };
420
421        //use std::io::Cursor;
422        db.write_file("/tmp/db.test").unwrap();
423    }
424}