From: Johannes Berg <johannes.berg@intel.com>
Date: Mon, 9 Oct 2017 11:50:57 +0200
Subject: [PATCH] regdb: write firmware file format (version code 20)

TODO: clean up the Makefile stuff ...

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 create mode 100755 db2fw.py

--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,5 @@
 # Install prefix
 PREFIX ?= /usr
-CRDA_PATH ?= $(PREFIX)/lib/crda
-CRDA_KEY_PATH ?= $(CRDA_PATH)/pubkeys
 
 MANDIR ?= $(PREFIX)/share/man/
 
@@ -30,39 +28,47 @@ REGDB_AUTHOR ?= $(shell if [ -f $(DISTRO
 		fi)
 
 REGDB_PRIVKEY ?= ~/.wireless-regdb-$(REGDB_AUTHOR).key.priv.pem
-REGDB_PUBKEY ?= $(REGDB_AUTHOR).key.pub.pem
-
-REGDB_UPSTREAM_PUBKEY ?= sforshee.key.pub.pem
+REGDB_PUBCERT ?= $(REGDB_AUTHOR).x509.pem
 
 REGDB_CHANGED = $(shell $(SHA1SUM) -c --status sha1sum.txt >/dev/null 2>&1; \
         if [ $$? -ne 0 ]; then \
-                echo maintainer-clean $(REGDB_PUBKEY); \
+                echo maintainer-clean $(REGDB_PUBCERT); \
         fi)
 
 .PHONY: all clean mrproper install maintainer-clean install-distro-key
 
-all: $(REGDB_CHANGED) regulatory.bin sha1sum.txt
+all: $(REGDB_CHANGED) regulatory.db.p7s sha1sum.txt
 
 clean:
 	@rm -f *.pyc *.gz
 
 maintainer-clean: clean
-	@rm -f regulatory.bin
+	@rm -f regulatory.db regulatory.db.p7s
 
 mrproper: clean maintainer-clean
-	@echo Removed public key, regulatory.bin and compresed man pages
-	@rm -f $(REGDB_PUBKEY) .custom
+	@echo Removed public key, regulatory.db* and compressed man pages
+	@rm -f $(REGDB_PUBCERT) .custom
 
-regulatory.bin: db.txt $(REGDB_PRIVKEY) $(REGDB_PUBKEY)
-	@echo Generating $@ digitally signed by $(REGDB_AUTHOR)...
-	./db2bin.py regulatory.bin db.txt $(REGDB_PRIVKEY)
+regulatory.db: db.txt db2fw.py
+	@echo "Generating $@"
+	./db2fw.py regulatory.db db.txt
+
+regulatory.db.p7s: regulatory.db $(REGDB_PRIVKEY) $(REGDB_PUBCERT)
+	@echo "Signing regulatory.db (by $(REGDB_AUTHOR))..."
+	@openssl smime -sign \
+		-signer $(REGDB_PUBCERT) \
+		-inkey $(REGDB_PRIVKEY) \
+		-in $< -nosmimecap -binary \
+		-outform DER -out $@
 
 sha1sum.txt: db.txt
 	sha1sum $< > $@
 
-$(REGDB_PUBKEY): $(REGDB_PRIVKEY)
-	@echo "Generating public key for $(REGDB_AUTHOR)..."
-	openssl rsa -in $(REGDB_PRIVKEY) -out $(REGDB_PUBKEY) -pubout -outform PEM
+$(REGDB_PUBCERT): $(REGDB_PRIVKEY)
+	@echo "Generating certificate for $(REGDB_AUTHOR)..."
+	@openssl req -config regulatory.openssl.conf \
+		-key $(REGDB_PRIVKEY) -days 36500 -utf8 -nodes -batch \
+		-x509 -outform PEM -out $(REGDB_PUBCERT)
 	@echo $(REGDB_PUBKEY) > .custom
 
 
@@ -97,16 +103,7 @@ install-distro-key: maintainer-clean $(D
 #	make maintainer-clean
 #	make
 #	sudo make install
-install: regulatory.bin.5.gz
-	install -m 755 -d $(DESTDIR)/$(CRDA_PATH)
-	install -m 755 -d $(DESTDIR)/$(CRDA_KEY_PATH)
-	if [ -f .custom ]; then \
-		install -m 644 -t $(DESTDIR)/$(CRDA_KEY_PATH)/ $(shell cat .custom); \
-	fi
-	install -m 644 -t $(DESTDIR)/$(CRDA_KEY_PATH)/ $(REGDB_UPSTREAM_PUBKEY)
-	install -m 644 -t $(DESTDIR)/$(CRDA_PATH)/ regulatory.bin
+install: regulatory.db.5.gz
+	install -m 644 -t $(DESTDIR)/$(CRDA_PATH)/ regulatory.db
 	install -m 755 -d $(DESTDIR)/$(MANDIR)/man5/
-	install -m 644 -t $(DESTDIR)/$(MANDIR)/man5/ regulatory.bin.5.gz
-
-uninstall:
-	rm -rf $(DESTDIR)/$(CRDA_PATH)/
+	install -m 644 -t $(DESTDIR)/$(MANDIR)/man5/ regulatory.db.5.gz
--- a/README
+++ b/README
@@ -18,8 +18,8 @@ python module is used by the web viewer
 implemented as a MoinMoin macro (and used on http://wireless.kernel.org)
 to allow viewing the database for verification.
 
-The dbparse module is also used by db2bin.py, the `compiler', which
-compiles and signs the binary database.
+The dbparse module is also used by db2bin.py and db2fw.py, the `compilers'
+that compile the database to its binary formats.
 
 For more information, please see the CRDA git repository:
 
--- /dev/null
+++ b/db2fw.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+
+from cStringIO import StringIO
+import struct
+import hashlib
+from dbparse import DBParser
+import sys
+
+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.itervalues():
+        for rule in c.permissions:
+            result[rule] = 1
+    return result.keys()
+
+def create_collections(countries):
+    result = {}
+    for c in countries.itervalues():
+        result[(c.permissions, c.dfs_region)] = 1
+    return result.keys()
+
+
+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(file(sys.argv[2]))
+rules = create_rules(countries)
+rules.sort(cmp=lambda x, y: cmp(x.freqband, y.freqband))
+collections = create_collections(countries)
+collections.sort(cmp=lambda x, y: cmp(x[0][0].freqband, y[0][0].freqband))
+
+output = StringIO()
+
+# struct regdb_file_header
+be32(output, MAGIC)
+be32(output, VERSION)
+
+country_ptrs = {}
+countrynames = countries.keys()
+countrynames.sort()
+for alpha2 in countrynames:
+    coll = countries[alpha2]
+    output.write(struct.pack('>cc', str(alpha2[0]), str(alpha2[1])))
+    country_ptrs[alpha2] = PTR(output)
+output.write('\x00' * 4)
+
+reg_rules = {}
+flags = 0
+for reg_rule in rules:
+    freq_range, power_rule = reg_rule.freqband, reg_rule.power
+    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:
+        rule_len += 2
+    output.write(struct.pack('>BBHIII', rule_len, flags, power_rule.max_eirp * 100,
+                             freq_range.start * 1000, freq_range.end * 1000, freq_range.maxbw * 1000,
+                             ))
+    if cac_timeout:
+        output.write(struct.pack('>H', cac_timeout))
+    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], 'w')
+outfile.write(output.getvalue())
