solo/tools/programmer.py
2018-12-03 23:01:51 -05:00

123 lines
3.2 KiB
Python

# Programs solo using the Solo bootloader
# Requires python-fido2, intelhex
import sys,os,time,struct
import array,struct,socket,json,base64
import tempfile
from binascii import hexlify
from fido2.hid import CtapHidDevice, CTAPHID
from fido2.client import Fido2Client, ClientError
from fido2.ctap import CtapError
from fido2.ctap1 import CTAP1
from intelhex import IntelHex
from sign_firmware import *
class SoloBootloader:
write = 0x40
done = 0x41
check = 0x42
erase = 0x43
version = 0x44
TAG = b'\x8C\x27\x90\xf6'
class Programmer():
def __init__(self,):
self.origin = 'https://example.org'
def find_device(self,):
dev = next(CtapHidDevice.list_devices(), None)
if not dev:
raise RuntimeError('No FIDO device found')
self.dev = dev
self.ctap1 = CTAP1(dev)
@staticmethod
def format_request(cmd,addr = 0,data = b'A'*16):
arr = b'\x00'*9
addr = struct.pack('<L', addr)
cmd = struct.pack('B', cmd)
length = struct.pack('B', len(data))
return cmd + addr[:3] + SoloBootloader.TAG + length + data
def exchange(self,cmd,addr=0,data=b'A'*16):
appid = b'A'*32
chal = b'B'*32
req = Programmer.format_request(cmd,addr,data)
res = self.ctap1.authenticate(chal,appid, req)
ret = res.signature[0]
if ret != CtapError.ERR.SUCCESS:
raise RuntimeError('Device returned non-success code %02x' % ret)
return res.signature[1:]
def version(self,):
data = self.exchange(SoloBootloader.version)
return data[0]
def write_flash(self,addr,data):
self.exchange(SoloBootloader.write,addr,data)
def verify_flash(self,sig):
"""
Tells device to check signature against application. If it passes,
the application will boot.
Exception raises if signature fails.
"""
self.exchange(SoloBootloader.done,0,sig)
def program_file(self,name):
data = json.loads(open(name,'r').read())
fw = base64.b64decode(from_websafe(data['firmware']).encode())
sig = base64.b64decode(from_websafe(data['signature']).encode())
ih = IntelHex()
tmp = tempfile.NamedTemporaryFile(delete=False)
tmp.write(fw)
tmp.seek(0)
tmp.close()
ih.fromfile(tmp.name, format='hex')
chunk = 240
seg = ih.segments()[0]
size = seg[1] - seg[0]
total = 0
t1 = time.time()*1000
for i in range(seg[0], seg[1], chunk):
s = i
e = min(i+chunk,seg[1])
data = ih.tobinarray(start=i,size = e-s)
self.write_flash(i,data)
total += chunk
progress = total/float(size)*100
sys.stdout.write('downloading %.2f%%...\r' % progress)
sys.stdout.write('downloading 100% \r\n')
t2 = time.time()*1000
print('time: %.2f s' % ((t2-t1)/1000.0))
print('Verifying...')
self.verify_flash(sig)
if __name__ == '__main__':
if len(sys.argv) != 2:
print('usage: %s <firmware.json>' % sys.argv[0])
sys.exit(1)
p = Programmer()
p.find_device()
print('version is ', p.version())
p.program_file(sys.argv[1])