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