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