from enum import Enum from hashlib import sha256 import os.path from .tls import tls from .usb import usb from .db import db, subtype_to_string from .flash import write_enable, flush_changes, read_flash, write_flash_all from time import sleep from struct import pack, unpack from .table_types import SensorTypeInfo from binascii import hexlify, unhexlify from .util import assert_status, unhex from .hw_tables import dev_info_lookup from .blobs import reset_blob from . import timeslot as prg calib_data_path='calib-data.bin' def glow_start_scan(): cmd=unhexlify('3920bf0200ffff0000019900200000000099990000000000000000000000000020000000000000000000000000ffff000000990020000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') assert_status(tls.app(cmd)) def glow_end_enroll(): cmd=unhexlify('39f4010000f401000001ff002000000000ffff0000000000000000000000000020000000000000000000000000f401000000ff0020000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') assert_status(tls.app(cmd)) def cancel_capture(): usb.queue.put(b'') #sleep(0.2) #rsp=tls.app(b'\x04') #assert_status(rsp) usb.read_82() def wait_for_finger(): while True: b=usb.wait_int() if len(b) == 0: raise Exception('Cancelled') if b[0] == 2: break def get_prg_status(): return tls.app(unhexlify('5100000000')) def wait_till_finished(): while True: status = get_prg_status() if status[0] in [0, 7]: break sleep(0.2) def stop_prg(): return tls.app(unhexlify('5100200000')) def read_hw_reg32(addr): rsp=tls.cmd(pack(' 0: raise Exception('Garbage at the end of reply') return rc # hardcoded bit of cmd02 program. valid only for 009a # TODO: properly extract all these bits from the DLL hardcoded=unhex(''' 23000000200008000020008000000100320074000000008020200400242000005020773628200100302001003c208000082138000c210000482107004c210000582000005c20000060200000682005006c20014970200141742001887820018084202000942001809c200902a0200b19b4200300b8203b04bc201400c0200200c4200100c82002003300100000000080cc200000f503d0200000a1013200440000000080dc20e803e0206401e420d002e8200001f0200500f8200500fc200000b8203a00000804001408000008080000080800001408300008080000140831001c081a0032000c0000000080501101004c111e00340078010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010221710221710221610221610221601065010250101000007c8078c06ff0000000000004f80006d0300280307030990098db00b90880991858e08c1810b9190910ac1b8928a0993878a890b9388898908c88191890ac8889289099a818a890b9a88898908d08191890ad08892890802818a095a810a0288890b5a8808d98189890ad9908989095e8289890b5e88898908e18189890ae190898909648289890b648889096e8108e981890b6e880ae99091b9096f828a8f0b6f88918908f0818a890af090898909768289910b76b8918a08f88792910af8888a92097c81898a0b7c09018089890b01888991097f8189920b7f09088089920b088889920c07030307200402000000002f0004007000000029000400700000003500040080000000' ''') def bitpack(b): l=len(b) m=min(b) x=max(b) # maximum delta which we must encode x-=m # count useful bits u=0 while x > 0: x>>=1 u+=1 # convert to array of binary strings with each element exactly u characters long b=[bin(i-m+0x100)[-u:] for i in b] # combine chunks into one long text number with u*l binary digits and parse it as integer b=int(''.join(b[::-1]), 2) # convert back to bytes b=b.to_bytes((u*l+7)//8, 'little') return (u, m, b) class Line(): mask=None flags=None data=None v0=0 v1=0 v2=0 def clip(x): if x < -128: x=-128 if x > 127: x=127 return x & 0xff def scale(x): x -= 0x80 x = int(x*10/0x22) # TODO: scaling factor depends on a device return clip(x) def add(l, r): # Make signed l, r = unpack('bb', pack('BB', l, r)) return clip(l+r) def chunks(b, l): return [b[i:i+l] for i in range(0, len(b), l)] class CaptureMode(Enum): CALIBRATE=1 IDENTIFY=2 ENROLL=3 class Sensor(): calib_data=b'' def open(self, load_calib_data=True): self.device_info = identify_sensor() print('Opening sensor: %s' % self.device_info.name) self.type_info = SensorTypeInfo.get_by_type(self.device_info.type) if self.device_info.type == 0x199: self.lines_per_frame = 0xe0 # valid for 0x199, TODO: figure out where this number is coming from self.bytes_per_line = 0x78 self.key_calibration_line = 0x38 # (lines_per_calibration_data/2), but hardcoded for sensor type 0x199 else: raise Exception('Device %s is not supported (sensor type 0x%x)' % (self.device_info.name, self.device_info.type)) factory_bits = get_factory_bits(0x0e00) self.factory_calibration_values = factory_bits[3][4:] if load_calib_data and os.path.isfile(calib_data_path): with open(calib_data_path, 'rb') as f: self.calib_data = f.read() print('Calibration data loaded from a file.') else: self.calib_data = b'' print('Warning: no calibration data was loaded. Consider calibrating the sensor.') def save(self): with open(calib_data_path, 'wb') as f: f.write(self.calib_data) # This is the exact logic from the DLL. # If it looks broken that was probably intended. def patch_timeslot_table(self, b, inc_address, mult): b=bytearray(b) i=0 while i+3 < len(b): if b[i] & 0xf8 == 0x10: if b[i+2] > 1: b[i+2] *= mult if inc_address: b[i+1] += 1 i+=3 continue if b[i] == 0: i+=1 continue if b[i] == 7: i+=2 continue break return bytes(b) def patch_timeslot_again(self, b): b=bytearray(b) pc = 0 match=None # Look for the last Call in the script while pc < len(b): opcode, l, *operands = prg.decode_insn(b[pc:]) # End of Table, Return, End of Data if opcode == 1 or opcode == 2 or opcode == 4: break # Call if opcode == 11: match = operands[1] # destination address pc += l if match is None: return bytes(b) pc = match match = None # Look for the last Register Write to 0x8000203C while pc < len(b): opcode, l, *operands = prg.decode_insn(b[pc:]) # End of Table, Return, End of Data if opcode == 1 or opcode == 2 or opcode == 4: break # Write Register if opcode == 13 and operands[0] == 0x8000203c: match = pc pc += l if match is None: return bytes(b) # Hack the value to be taken from the factory calibration table right in the middle of a sensor b[match+1] = self.factory_calibration_values[self.key_calibration_line] return bytes(b) def average(self, raw_calib_data): frame_size = self.lines_per_frame * self.bytes_per_line interleave_lines = self.lines_per_frame // self.type_info.lines_per_calibration_data # 2, TODO: algo is quite different when it is 1 input_frames = 3 # len(raw_calib_data)//lines_per_frame//bytes_per_line, TODO: workout where it's really comming from # skip the first frame input_frames -= 1 base_address = frame_size frame=raw_calib_data[base_address:base_address+frame_size] # split into groups of lines frame=chunks(frame, interleave_lines*self.bytes_per_line) # split group of lines into lines frame=[chunks(f, self.bytes_per_line) for f in frame] # calculate averages across interleaved lines frame=[bytes([sum(i)//len(f) for i in zip(*f)]) for f in frame] return b''.join(frame) def process_calibration_results(self, cooked_data): frame=chunks(cooked_data, self.bytes_per_line) # apply scaling factors frame=[f[:8] + bytes(map(scale, f[8:])) for f in frame] frame=b''.join(frame) if len(self.calib_data) > 0: # Not the first calibration run. Combine results # split previous calibration info into lines lll=chunks(self.calib_data, self.bytes_per_line) # split next calibration info into lines rrr=chunks(frame, self.bytes_per_line) # Don't touch the first 8 bytes of each line, add everything else as signed characters, clipping the values combined=[ll[:8] + bytes([add(l, r) for l, r in zip(ll[8:],rr[8:])]) for ll, rr in zip(lll, rrr)] self.calib_data = bytes(b''.join(combined)) else: self.calib_data = frame def get_key_line(self): if len(self.calib_data) > 0: bytes_per_calibration_line=len(self.calib_data) // self.type_info.lines_per_calibration_data key_line_offset=8+bytes_per_calibration_line*self.key_calibration_line key_line=self.calib_data[key_line_offset:key_line_offset+self.type_info.line_width] key_line=bytes([i-1 if i == 5 else i for i in key_line]) else: key_line=b'\0'*self.type_info.line_width return key_line def build_cmd_02(self, mode): chunks=list(prg.split_chunks(hardcoded)) for c in chunks: # patch the 2D params. # The following is only needed on some hw versions below 6.5 as reported by cmd_01 #if c[0] == 0x2f: # c[1] = pack(' 0: bytes_per_calibration_line=len(self.calib_data) // self.type_info.lines_per_calibration_data for i in range(0, 112, 4): l=Line() lines += [l] l.mask=0xffffffff l.flags=i | (0x85 << 24) l.data=b'' for j in range(0, 112): p=8+j*bytes_per_calibration_line+i l.data += self.calib_data[p:p+4] #---------------- Line Update --------------- line_update = pack('> 0x14) <= 1]) chunks += [[0x30, line_update]] #---------------- Line Update Transform --------------- update_transform = b''.join([pack('> 0x14) > 1]) chunks += [[0x43, update_transform]] if mode == CaptureMode.CALIBRATE: req_lines = 3*self.lines_per_frame+1 # TODO: figure out how this is actually calculated else: req_lines = 0 return pack(' 0: tag, l = unpack(' 0: (t, l), x = unpack(' 1: raise Exception('Expected only one child record for finger') print('Recognised finger %02x (%s) from user %s' % (subtype, subtype_to_string(subtype), repr(usr.identity))) print('Template hash: %s' % hexlify(hsh).decode()) if len(finger_record.children) > 0: if finger_record.children[0]['type'] != 8: raise Exception('Expected data blob as a finger child') blob_id = finger_record.children[0]['dbid'] blob = db.get_record_value(blob_id).value tag, sz = unpack('