diff --git a/db2bin.py b/db2bin.py --- a/db2bin.py +++ b/db2bin.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from cStringIO import StringIO +from io import BytesIO, open import struct import hashlib from dbparse import DBParser @@ -10,21 +10,21 @@ VERSION = 19 if len(sys.argv) < 3: - print 'Usage: %s output-file input-file [key-file]' % sys.argv[0] + print('Usage: %s output-file input-file [key-file]' % sys.argv[0]) sys.exit(2) def create_rules(countries): result = {} - for c in countries.itervalues(): + for c in countries.values(): for rule in c.permissions: result[rule] = 1 - return result.keys() + return list(result) def create_collections(countries): result = {} - for c in countries.itervalues(): + for c in countries.values(): result[c.permissions] = 1 - return result.keys() + return list(result) def be32(output, val): @@ -49,21 +49,25 @@ return self._offset p = DBParser() -countries = p.parse(file(sys.argv[2])) +countries = p.parse(open(sys.argv[2], 'r', encoding='utf-8')) + +countrynames = list(countries) +countrynames.sort() + power = [] bands = [] -for c in countries.itervalues(): - for perm in c.permissions: +for alpha2 in countrynames: + for perm in countries[alpha2].permissions: if not perm.freqband in bands: bands.append(perm.freqband) if not perm.power in power: power.append(perm.power) rules = create_rules(countries) -rules.sort(cmp=lambda x, y: cmp(x.freqband, y.freqband)) +rules.sort() collections = create_collections(countries) -collections.sort(cmp=lambda x, y: cmp(x[0].freqband, y[0].freqband)) +collections.sort() -output = StringIO() +output = BytesIO() # struct regdb_file_header be32(output, MAGIC) @@ -104,19 +108,17 @@ # struct regdb_file_reg_rules_collection coll = list(coll) be32(output, len(coll)) - coll.sort(cmp=lambda x, y: cmp(x.freqband, y.freqband)) + coll.sort() for regrule in coll: be32(output, reg_rules[regrule]) # update country pointer now! reg_country_ptr.set() -countrynames = countries.keys() -countrynames.sort() for alpha2 in countrynames: coll = countries[alpha2] # struct regdb_file_reg_country - output.write(struct.pack('>ccxBI', str(alpha2[0]), str(alpha2[1]), coll.dfs_region, reg_rules_collections[coll.permissions])) + output.write(struct.pack('>2sxBI', alpha2, coll.dfs_region, reg_rules_collections[coll.permissions])) if len(sys.argv) > 3: @@ -141,5 +143,5 @@ else: siglen.set(0) -outfile = open(sys.argv[1], 'w') +outfile = open(sys.argv[1], 'wb') outfile.write(output.getvalue()) diff --git a/dbparse.py b/dbparse.py --- a/dbparse.py +++ b/dbparse.py @@ -1,6 +1,9 @@ #!/usr/bin/env python +from functools import total_ordering import sys, math +from math import ceil, log +from collections import defaultdict, OrderedDict # must match enum nl80211_reg_rule_flags @@ -25,6 +28,40 @@ 'DFS-JP': 3, } +@total_ordering + +class WmmRule(object): + + def __init__(self, vo_c, vi_c, be_c, bk_c, vo_ap, vi_ap, be_ap, bk_ap): + self.vo_c = vo_c + self.vi_c = vi_c + self.be_c = be_c + self.bk_c = bk_c + self.vo_ap = vo_ap + self.vi_ap = vi_ap + self.be_ap = be_ap + self.bk_ap = bk_ap + + def _as_tuple(self): + return (self.vo_c, self.vi_c, self.be_c, self.bk_c, + self.vo_ap, self.vi_ap, self.be_ap, self.bk_ap) + + def __eq__(self, other): + if other is None: + return False + return (self._as_tuple() == other._as_tuple()) + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + if other is None: + return False + return (self._as_tuple() < other._as_tuple()) + + def __hash__(self): + return hash(self._as_tuple()) + class FreqBand(object): def __init__(self, start, end, bw, comments=None): self.start = start @@ -32,41 +69,49 @@ self.maxbw = bw self.comments = comments or [] - def __cmp__(self, other): - s = self - o = other - if not isinstance(o, FreqBand): - return False - return cmp((s.start, s.end, s.maxbw), (o.start, o.end, o.maxbw)) + def _as_tuple(self): + return (self.start, self.end, self.maxbw) + + def __eq__(self, other): + return (self._as_tuple() == other._as_tuple()) + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return (self._as_tuple() < other._as_tuple()) def __hash__(self): - s = self - return hash((s.start, s.end, s.maxbw)) + return hash(self._as_tuple()) def __str__(self): return '' % ( self.start, self.end, self.maxbw) +@total_ordering class PowerRestriction(object): def __init__(self, max_ant_gain, max_eirp, comments = None): self.max_ant_gain = max_ant_gain self.max_eirp = max_eirp self.comments = comments or [] - def __cmp__(self, other): - s = self - o = other - if not isinstance(o, PowerRestriction): - return False - return cmp((s.max_ant_gain, s.max_eirp), - (o.max_ant_gain, o.max_eirp)) + def _as_tuple(self): + return (self.max_ant_gain, self.max_eirp) - def __str__(self): - return '' + def __eq__(self, other): + return (self._as_tuple() == other._as_tuple()) + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return (self._as_tuple() < other._as_tuple()) def __hash__(self): - s = self - return hash((s.max_ant_gain, s.max_eirp)) + return hash(self._as_tuple()) + + def __str__(self): + return '' class DFSRegionError(Exception): def __init__(self, dfs_region): @@ -76,12 +121,15 @@ def __init__(self, flag): self.flag = flag +@total_ordering class Permission(object): - def __init__(self, freqband, power, flags): + def __init__(self, freqband, power, flags, wmmrule): assert isinstance(freqband, FreqBand) assert isinstance(power, PowerRestriction) + assert isinstance(wmmrule, WmmRule) or wmmrule is None self.freqband = freqband self.power = power + self.wmmrule = wmmrule self.flags = 0 for flag in flags: if not flag in flag_definitions: @@ -90,26 +138,33 @@ self.textflags = flags def _as_tuple(self): - return (self.freqband, self.power, self.flags) + return (self.freqband, self.power, self.flags, self.wmmrule) - def __cmp__(self, other): - if not isinstance(other, Permission): - return False - return cmp(self._as_tuple(), other._as_tuple()) + def __eq__(self, other): + return (self._as_tuple() == other._as_tuple()) + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return (self._as_tuple() < other._as_tuple()) def __hash__(self): return hash(self._as_tuple()) + def __str__(self): + return str(self.freqband) + str(self.power) + str(self.wmmrule) + class Country(object): def __init__(self, dfs_region, permissions=None, comments=None): self._permissions = permissions or [] self.comments = comments or [] - self.dfs_region = 0 + self.dfs_region = 0 - if dfs_region: - if not dfs_region in dfs_regions: - raise DFSRegionError(dfs_region) - self.dfs_region = dfs_regions[dfs_region] + if dfs_region: + if not dfs_region in dfs_regions: + raise DFSRegionError(dfs_region) + self.dfs_region = dfs_regions[dfs_region] def add(self, perm): assert isinstance(perm, Permission) @@ -233,6 +288,61 @@ self._powerrev[p] = pname self._powerline[pname] = self._lineno + def _parse_wmmrule(self, line): + regions = line[:-1].strip() + if not regions: + self._syntax_error("'wmmrule' keyword must be followed by region") + + regions = regions.split(',') + + self._current_regions = {} + for region in regions: + if region in self._wmm_rules: + self._warn("region %s was added already to wmm rules" % region) + self._current_regions[region] = 1 + self._comments = [] + + def _validate_input(self, cw_min, cw_max, aifsn, cot): + if cw_min < 1: + self._syntax_error("Invalid cw_min value (%d)" % cw_min) + if cw_max < 1: + self._syntax_error("Invalid cw_max value (%d)" % cw_max) + if cw_min > cw_max: + self._syntax_error("Inverted contention window (%d - %d)" % + (cw_min, cw_max)) + if not (bin(cw_min + 1).count('1') == 1 and cw_min < 2**15): + self._syntax_error("Invalid cw_min value should be power of 2 - 1 (%d)" + % cw_min) + if not (bin(cw_max + 1).count('1') == 1 and cw_max < 2**15): + self._syntax_error("Invalid cw_max value should be power of 2 - 1 (%d)" + % cw_max) + if aifsn < 1: + self._syntax_error("Invalid aifsn value (%d)" % aifsn) + if cot < 0: + self._syntax_error("Invalid cot value (%d)" % cot) + + + def _validate_size(self, var, bytcnt): + return bytcnt < ceil(len(bin(var)[2:]) / 8.0) + + def _parse_wmmrule_item(self, line): + bytcnt = (2.0, 2.0, 1.0, 2.0) + try: + ac, cval = line.split(':') + if not ac: + self._syntax_error("wmm item must have ac prefix") + except ValueError: + self._syntax_error("access category must be followed by colon") + p = tuple([int(v.split('=', 1)[1]) for v in cval.split(',')]) + self._validate_input(*p) + for v, b in zip(p, bytcnt): + if self._validate_size(v, b): + self._syntax_error("unexpected input size expect %d got %d" + % (b, v)) + + for r in self._current_regions: + self._wmm_rules[r][ac] = p + def _parse_country(self, line): try: cname, cvals= line.split(':', 1) @@ -248,6 +358,7 @@ for cname in cnames: if len(cname) != 2: self._warn("country '%s' not alpha2" % cname) + cname = cname.encode('ascii') if not cname in self._countries: self._countries[cname] = Country(dfs_region, comments=self._comments) self._current_countries[cname] = self._countries[cname] @@ -290,6 +401,15 @@ line = line.split(',') pname = line[0] flags = line[1:] + w = None + if flags and 'wmmrule' in flags[-1]: + try: + region = flags.pop().split('=', 1)[1] + if region not in self._wmm_rules.keys(): + self._syntax_error("No wmm rule for %s" % region) + except IndexError: + self._syntax_error("flags is empty list or no region was found") + w = WmmRule(*self._wmm_rules[region].values()) if not bname in self._bands: self._syntax_error("band does not exist") @@ -303,10 +423,10 @@ b = self._bands[bname] p = self._power[pname] try: - perm = Permission(b, p, flags) - except FlagError, e: + perm = Permission(b, p, flags, w) + except FlagError as e: self._syntax_error("Invalid flag '%s'" % e.flag) - for cname, c in self._current_countries.iteritems(): + for cname, c in self._current_countries.items(): if perm in c: self._warn('Rule "%s, %s" added to "%s" twice' % ( bname, pname, cname)) @@ -315,6 +435,7 @@ def parse(self, f): self._current_countries = None + self._current_regions = None self._bands = {} self._power = {} self._countries = {} @@ -326,6 +447,7 @@ self._powerdup = {} self._bandline = {} self._powerline = {} + self._wmm_rules = defaultdict(lambda: OrderedDict()) self._comments = [] @@ -337,6 +459,7 @@ self._comments.append(line[1:].strip()) line = line.replace(' ', '').replace('\t', '') if not line: + self._current_regions = None self._comments = [] line = line.split('#')[0] if not line: @@ -344,23 +467,35 @@ if line[0:4] == 'band': self._parse_band(line[4:]) self._current_countries = None + self._current_regions = None self._comments = [] elif line[0:5] == 'power': self._parse_power(line[5:]) self._current_countries = None + self._current_regions = None self._comments = [] elif line[0:7] == 'country': self._parse_country(line[7:]) self._comments = [] + self._current_regions = None elif self._current_countries is not None: + self._current_regions = None self._parse_country_item(line) self._comments = [] + elif line[0:7] == 'wmmrule': + self._parse_wmmrule(line[7:]) + self._current_countries = None + self._comments = [] + elif self._current_regions is not None: + self._parse_wmmrule_item(line) + self._current_countries = None + self._comments = [] else: self._syntax_error("Expected band, power or country definition") countries = self._countries bands = {} - for k, v in self._bands.iteritems(): + for k, v in self._bands.items(): if k in self._bands_used: bands[self._banddup[k]] = v continue @@ -369,7 +504,7 @@ self._lineno = self._bandline[k] self._warn('Unused band definition "%s"' % k) power = {} - for k, v in self._power.iteritems(): + for k, v in self._power.items(): if k in self._power_used: power[self._powerdup[k]] = v continue diff --git /dev/null b/db2fw.py --- /dev/null +++ b/db2fw.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python + +from io import BytesIO, open +import struct +import hashlib +from dbparse import DBParser +import sys +from math import log + +MAGIC = 0x52474442 +VERSION = 20 + +if len(sys.argv) < 3: + print('Usage: %s output-file input-file' % sys.argv[0]) + sys.exit(2) + +def create_rules(countries): + result = {} + for c in countries.values(): + for rule in c.permissions: + result[rule] = 1 + return list(result) + +def create_collections(countries): + result = {} + for c in countries.values(): + result[(c.permissions, c.dfs_region)] = 1 + return list(result) + +def create_wmms(countries): + result = {} + for c in countries.values(): + for rule in c.permissions: + if rule.wmmrule is not None: + result[rule.wmmrule] = 1 + return list(result) + +def be32(output, val): + output.write(struct.pack('>I', val)) +def be16(output, val): + output.write(struct.pack('>H', val)) + +class PTR(object): + def __init__(self, output): + self._output = output + self._pos = output.tell() + be16(output, 0) + self._written = False + + def set(self, val=None): + if val is None: + val = self._output.tell() + assert val & 3 == 0 + self._offset = val + pos = self._output.tell() + self._output.seek(self._pos) + be16(self._output, val >> 2) + self._output.seek(pos) + self._written = True + + def get(self): + return self._offset + + @property + def written(self): + return self._written + +p = DBParser() +countries = p.parse(open(sys.argv[2], 'r', encoding='utf-8')) +rules = create_rules(countries) +rules.sort() +collections = create_collections(countries) +collections.sort() +wmms = create_wmms(countries) +wmms.sort() + +output = BytesIO() + +# struct regdb_file_header +be32(output, MAGIC) +be32(output, VERSION) + +country_ptrs = {} +countrynames = list(countries) +countrynames.sort() +for alpha2 in countrynames: + coll = countries[alpha2] + output.write(struct.pack('>2s', alpha2)) + country_ptrs[alpha2] = PTR(output) +output.write(b'\x00' * 4) + +wmmdb = {} +for w in wmms: + assert output.tell() & 3 == 0 + wmmdb[w] = output.tell() >> 2 + for r in w._as_tuple(): + ecw = int(log(r[0] + 1, 2)) << 4 | int(log(r[1] + 1, 2)) + ac = (ecw, r[2],r[3]) + output.write(struct.pack('>BBH', *ac)) + +reg_rules = {} +flags = 0 +for reg_rule in rules: + freq_range, power_rule, wmm_rule = reg_rule.freqband, reg_rule.power, reg_rule.wmmrule + reg_rules[reg_rule] = output.tell() + assert power_rule.max_ant_gain == 0 + flags = 0 + # convert to new rule flags + assert reg_rule.flags & ~0x899 == 0 + if reg_rule.flags & 1<<0: + flags |= 1<<0 + if reg_rule.flags & 1<<3: + flags |= 1<<1 + if reg_rule.flags & 1<<4: + flags |= 1<<2 + if reg_rule.flags & 1<<7: + flags |= 1<<3 + if reg_rule.flags & 1<<11: + flags |= 1<<4 + rule_len = 16 + cac_timeout = 0 # TODO + if not (flags & 1<<2): + cac_timeout = 0 + if cac_timeout or wmm_rule: + rule_len += 2 + if wmm_rule is not None: + rule_len += 2 + output.write(struct.pack('>BBHIII', rule_len, flags, int(power_rule.max_eirp * 100), + int(freq_range.start * 1000), int(freq_range.end * 1000), int(freq_range.maxbw * 1000), + )) + if rule_len > 16: + output.write(struct.pack('>H', cac_timeout)) + + if rule_len > 18: + be16(output, wmmdb[wmm_rule]) + + while rule_len % 4: + output.write('\0') + rule_len += 1 + +for coll in collections: + for alpha2 in countrynames: + if (countries[alpha2].permissions, countries[alpha2].dfs_region) == coll: + assert not country_ptrs[alpha2].written + country_ptrs[alpha2].set() + slen = 3 + output.write(struct.pack('>BBBx', slen, len(list(coll[0])), coll[1])) + coll = list(coll[0]) + for regrule in coll: + be16(output, reg_rules[regrule] >> 2) + if len(coll) % 2: + be16(output, 0) + +for alpha2 in countrynames: + assert country_ptrs[alpha2].written + +outfile = open(sys.argv[1], 'wb') +outfile.write(output.getvalue())