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#[derive(Debug)]
14pub struct Binary {
15 countries: Vec<BinaryCountry>,
18 wmmdbs: Vec<BinaryWmmDB>,
20 rules_db: Vec<BinaryRegRule>,
21 collections: Vec<BinaryCollection>,
23}
24
25impl Binary {
26 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 ®db.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(®db.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 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 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 name: String, pos: Option<u16>, }
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 result.rule_len = 16;
298
299 let cac_timeout = 0;
306
307 result.cac_timeout = Some(cac_timeout); if wwmdb_pos.is_some() {
310 result.rule_len += 4; 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#[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 db.write_file("/tmp/db.test").unwrap();
423 }
424}