remove no-ht20 flag
[platform/upstream/crda.git] / dbparse.py
1 #!/usr/bin/env python
2
3 import sys, math
4
5 flag_definitions = {
6     'NO-CCK':           1<<0,
7     'NO-OFDM':          1<<1,
8     'NO-INDOOR':        1<<2,
9     'NO-OUTDOOR':       1<<3,
10     'DFS':              1<<4,
11     'PTP-ONLY':         1<<5,
12     'PTMP-ONLY':        1<<6,
13     'PASSIVE-SCAN':     1<<7,
14     'NO-IBSS':          1<<8,
15     # hole at bit 9
16     'NO-HT40':          1<<10,
17     'EDGE-POWER-1':     1<<11,
18     'EDGE-POWER-2':     2<<11,
19     'EDGE-POWER-3':     3<<11,
20 }
21
22 class FreqBand(object):
23     def __init__(self, start, end, bw, comments=None):
24         self.start = start
25         self.end = end
26         self.maxbw = bw
27         self.comments = comments or []
28
29     def __cmp__(self, other):
30         s = self
31         o = other
32         if not isinstance(o, FreqBand):
33             return False
34         return cmp((s.start, s.end, s.maxbw), (o.start, o.end, o.maxbw))
35
36     def __hash__(self):
37         s = self
38         return hash((s.start, s.end, s.maxbw))
39
40     def __str__(self):
41         return '<FreqBand %.3f - %.3f @ %.3f>' % (
42                   self.start, self.end, self.maxbw)
43
44 class PowerRestriction(object):
45     def __init__(self, max_ant_gain, max_eirp, comments = None):
46         self.max_ant_gain = max_ant_gain
47         self.max_eirp = max_eirp
48         self.comments = comments or []
49
50     def __cmp__(self, other):
51         s = self
52         o = other
53         if not isinstance(o, PowerRestriction):
54             return False
55         return cmp((s.max_ant_gain, s.max_eirp),
56                    (o.max_ant_gain, o.max_eirp))
57
58     def __str__(self):
59         return '<PowerRestriction ...>'
60
61     def __hash__(self):
62         s = self
63         return hash((s.max_ant_gain, s.max_eirp))
64
65 class FlagError(Exception):
66     def __init__(self, flag):
67         self.flag = flag
68
69 class DuplicateEdgePowerError(Exception):
70     pass
71
72 class Permission(object):
73     def __init__(self, freqband, power, flags):
74         assert isinstance(freqband, FreqBand)
75         assert isinstance(power, PowerRestriction)
76         self.freqband = freqband
77         self.power = power
78         self.flags = 0
79         f = [e for e in flags if e.startswith('EDGE-POWER-')]
80         if len(f) > 1:
81             raise DuplicateEdgePowerError()
82         for flag in flags:
83             if not flag in flag_definitions:
84                 raise FlagError(flag)
85             self.flags |= flag_definitions[flag]
86         self.textflags = flags
87
88     def _as_tuple(self):
89         return (self.freqband, self.power, self.flags)
90
91     def __cmp__(self, other):
92         if not isinstance(other, Permission):
93             return False
94         return cmp(self._as_tuple(), other._as_tuple())
95
96     def __hash__(self):
97         return hash(self._as_tuple())
98
99 class Country(object):
100     def __init__(self, permissions=None, comments=None):
101         self._permissions = permissions or []
102         self.comments = comments or []
103
104     def add(self, perm):
105         assert isinstance(perm, Permission)
106         self._permissions.append(perm)
107         self._permissions.sort()
108
109     def __contains__(self, perm):
110         assert isinstance(perm, Permission)
111         return perm in self._permissions
112
113     def __str__(self):
114         r = ['(%s, %s)' % (str(b), str(p)) for b, p in self._permissions]
115         return '<Country (%s)>' % (', '.join(r))
116
117     def _get_permissions_tuple(self):
118         return tuple(self._permissions)
119     permissions = property(_get_permissions_tuple)
120
121 class SyntaxError(Exception):
122     pass
123
124 class DBParser(object):
125     def __init__(self, warn=None):
126         self._warn_callout = warn or sys.stderr.write
127
128     def _syntax_error(self, txt=None):
129         txt = txt and ' (%s)' % txt or ''
130         raise SyntaxError("Syntax error in line %d%s" % (self._lineno, txt))
131
132     def _warn(self, txt):
133         self._warn_callout("Warning (line %d): %s\n" % (self._lineno, txt))
134
135     def _parse_band_def(self, bname, banddef, dupwarn=True):
136         try:
137             freqs, bw = banddef.split('@')
138             bw = float(bw)
139         except ValueError:
140             bw = 20.0
141
142         try:
143             start, end = freqs.split('-')
144             start = float(start)
145             end = float(end)
146         except ValueError:
147             self._syntax_error("band must have frequency range")
148
149         b = FreqBand(start, end, bw, comments=self._comments)
150         self._comments = []
151         self._banddup[bname] = bname
152         if b in self._bandrev:
153             if dupwarn:
154                 self._warn('Duplicate band definition ("%s" and "%s")' % (
155                               bname, self._bandrev[b]))
156             self._banddup[bname] = self._bandrev[b]
157         self._bands[bname] = b
158         self._bandrev[b] = bname
159         self._bandline[bname] = self._lineno
160
161     def _parse_band(self, line):
162         try:
163             bname, line = line.split(':', 1)
164             if not bname:
165                 self._syntax_error("'band' keyword must be followed by name")
166         except ValueError:
167             self._syntax_error("band name must be followed by colon")
168
169         if bname in flag_definitions:
170             self._syntax_error("Invalid band name")
171
172         self._parse_band_def(bname, line)
173
174     def _parse_power(self, line):
175         try:
176             pname, line = line.split(':', 1)
177             if not pname:
178                 self._syntax_error("'power' keyword must be followed by name")
179         except ValueError:
180             self._syntax_error("power name must be followed by colon")
181
182         if pname in flag_definitions:
183             self._syntax_error("Invalid power name")
184
185         self._parse_power_def(pname, line)
186
187     def _parse_power_def(self, pname, line, dupwarn=True):
188         try:
189             (max_ant_gain,
190              max_eirp) = line.split(',')
191             if max_ant_gain == 'N/A':
192                 max_ant_gain = '0'
193             if max_eirp == 'N/A':
194                 max_eirp = '0'
195             max_ant_gain = float(max_ant_gain)
196             def conv_pwr(pwr):
197                 if pwr.endswith('mW'):
198                     pwr = float(pwr[:-2])
199                     return 10.0 * math.log10(pwr)
200                 else:
201                     return float(pwr)
202             max_eirp = conv_pwr(max_eirp)
203         except ValueError:
204             self._syntax_error("invalid power data")
205
206         p = PowerRestriction(max_ant_gain, max_eirp,
207                              comments=self._comments)
208         self._comments = []
209         self._powerdup[pname] = pname
210         if p in self._powerrev:
211             if dupwarn:
212                 self._warn('Duplicate power definition ("%s" and "%s")' % (
213                               pname, self._powerrev[p]))
214             self._powerdup[pname] = self._powerrev[p]
215         self._power[pname] = p
216         self._powerrev[p] = pname
217         self._powerline[pname] = self._lineno
218
219     def _parse_country(self, line):
220         try:
221             cname, line = line.split(':', 1)
222             if not cname:
223                 self._syntax_error("'country' keyword must be followed by name")
224             if line:
225                 self._syntax_error("extra data at end of country line")
226         except ValueError:
227             self._syntax_error("country name must be followed by colon")
228
229         if not cname in self._countries:
230             self._countries[cname] = Country(comments=self._comments)
231         self._comments = []
232         self._current_country = self._countries[cname]
233         self._current_country_name = cname
234
235     def _parse_country_item(self, line):
236         if line[0] == '(':
237             try:
238                 band, line = line[1:].split('),', 1)
239                 bname = 'UNNAMED %d' % self._lineno
240                 self._parse_band_def(bname, band, dupwarn=False)
241             except:
242                 self._syntax_error("Badly parenthesised band definition")
243         else:
244             try:
245                 bname, line = line.split(',', 1)
246                 if not bname:
247                     self._syntax_error("country definition must have band")
248                 if not line:
249                     self._syntax_error("country definition must have power")
250             except ValueError:
251                 self._syntax_error("country definition must have band and power")
252
253         if line[0] == '(':
254             items = line.split('),', 1)
255             if len(items) == 1:
256                 pname = items[0]
257                 line = ''
258                 if not pname[-1] == ')':
259                     self._syntax_error("Badly parenthesised power definition")
260                 pname = pname[:-1]
261                 flags = []
262             else:
263                 pname = items[0]
264                 flags = items[1].split(',')
265             power = pname[1:]
266             pname = 'UNNAMED %d' % self._lineno
267             self._parse_power_def(pname, power, dupwarn=False)
268         else:
269             line = line.split(',')
270             pname = line[0]
271             flags = line[1:]
272
273         if not bname in self._bands:
274             self._syntax_error("band does not exist")
275         if not pname in self._power:
276             self._syntax_error("power does not exist")
277         self._bands_used[bname] = True
278         self._power_used[pname] = True
279         # de-duplicate so binary database is more compact
280         bname = self._banddup[bname]
281         pname = self._powerdup[pname]
282         b = self._bands[bname]
283         p = self._power[pname]
284         try:
285             perm = Permission(b, p, flags)
286         except FlagError, e:
287             self._syntax_error("Invalid flag '%s'" % e.flag)
288         except DuplicateEdgePowerError:
289             self._syntax_error("More than one edge power value given!")
290         if perm in self._current_country:
291             self._warn('Rule "%s, %s" added to "%s" twice' % (
292                           bname, pname, self._current_country_name))
293         else:
294             self._current_country.add(perm)
295
296     def parse(self, f):
297         self._current_country = None
298         self._bands = {}
299         self._power = {}
300         self._countries = {}
301         self._bands_used = {}
302         self._power_used = {}
303         self._bandrev = {}
304         self._powerrev = {}
305         self._banddup = {}
306         self._powerdup = {}
307         self._bandline = {}
308         self._powerline = {}
309
310         self._comments = []
311
312         self._lineno = 0
313         for line in f:
314             self._lineno += 1
315             line = line.strip()
316             if line[0:1] == '#':
317                 self._comments.append(line[1:].strip())
318             line = line.replace(' ', '').replace('\t', '')
319             if not line:
320                 self._comments = []
321             line = line.split('#')[0]
322             if not line:
323                 continue
324             if line[0:4] == 'band':
325                 self._parse_band(line[4:])
326                 self._current_country = None
327                 self._comments = []
328             elif line[0:5] == 'power':
329                 self._parse_power(line[5:])
330                 self._current_country = None
331                 self._comments = []
332             elif line[0:7] == 'country':
333                 self._parse_country(line[7:])
334                 self._comments = []
335             elif self._current_country is not None:
336                 self._parse_country_item(line)
337                 self._comments = []
338             else:
339                 self._syntax_error("Expected band, power or country definition")
340
341         countries = {}
342         for k, v in self._countries.iteritems():
343             for k in k.split(','):
344                 countries[k] = v
345         bands = {}
346         for k, v in self._bands.iteritems():
347             if k in self._bands_used:
348                 bands[self._banddup[k]] = v
349                 continue
350             # we de-duplicated, but don't warn again about the dupes
351             if self._banddup[k] == k:
352                 self._lineno = self._bandline[k]
353                 self._warn('Unused band definition "%s"' % k)
354         power = {}
355         for k, v in self._power.iteritems():
356             if k in self._power_used:
357                 power[self._powerdup[k]] = v
358                 continue
359             # we de-duplicated, but don't warn again about the dupes
360             if self._powerdup[k] == k:
361                 self._lineno = self._powerline[k]
362                 self._warn('Unused power definition "%s"' % k)
363         return countries