# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # utils.py - Misc utils. My favourite kind of source file. # import gc, sys, ustruct, ngu from ubinascii import unhexlify as a2b_hex from ubinascii import hexlify as b2a_hex from ubinascii import a2b_base64, b2a_base64 from uhashlib import sha256 B2A = lambda x: str(b2a_hex(x), 'ascii') class imported: # Context manager that temporarily imports # a list of modules. # LATER: doubtful this saves any memory when all the code is frozen. def __init__(self, *modules): self.modules = modules def __enter__(self): # import everything required rv = tuple(__import__(n) for n in self.modules) return rv[0] if len(self.modules) == 1 else rv def __exit__(self, exc_type, exc_value, traceback): for n in self.modules: if n in sys.modules: del sys.modules[n] # recovery that tasty memory. gc.collect() # class min_dramatic_pause: # # insure that something takes at least N ms # def __init__(self, min_time): # import utime # # self.min_time = min_time # self.start_time = utime.ticks_ms() # # def __enter__(self): # pass # # def __exit__(self, exc_type, exc_value, traceback): # import utime # # if exc_type is not None: return # # actual = utime.ticks_ms() - self.start_time # if actual < self.min_time: # utime.sleep_ms(self.min_time - actual) # def pretty_delay(n): # decode # of seconds into various ranges, need not be precise. if n < 120: return '%d seconds' % n n /= 60 if n < 60: return '%d minutes' % n n /= 60 if n < 48: return '%.1f hours' % n n /= 24 return 'about %d days' % n def pretty_short_delay(sec): # precise, shorter on screen display if sec >= 3600: return '%2dh %2dm %2ds' % (sec //3600, (sec//60) % 60, sec % 60) else: return '%2dm %2ds' % ((sec//60) % 60, sec % 60) def pop_count(i): # 32-bit population count for integers # from i = i - ((i >> 1) & 0x55555555) i = (i & 0x33333333) + ((i >> 2) & 0x33333333) return (((i + (i >> 4) & 0xF0F0F0F) * 0x1010101) & 0xffffffff) >> 24 def get_filesize(fn): # like os.path.getsize() import uos try: return uos.stat(fn)[6] except OSError: return 0 class HexWriter: # Emulate a file/stream but convert binary to hex as they write def __init__(self, fd): self.fd = fd self.pos = 0 self.checksum = sha256() def __enter__(self): self.fd.__enter__() return self def __exit__(self, *a, **k): self.fd.seek(0, 3) # go to end self.fd.write(b'\r\n') return self.fd.__exit__(*a, **k) def tell(self): return self.pos def write(self, b): self.checksum.update(b) self.pos += len(b) self.fd.write(b2a_hex(b)) def seek(self, offset, whence=0): assert whence == 0 # limited support self.pos = offset self.fd.seek((2*offset), 0) def read(self, ll): b = self.fd.read(ll*2) if not b: return b assert len(b)%2 == 0 self.pos += len(b)//2 return a2b_hex(b) def read_into(self, buf): b = self.read(len(buf)) buf[0:len(b)] = b return len(b) class Base64Writer: # Emulate a file/stream but convert binary to Base64 as they write def __init__(self, fd): self.fd = fd self.runt = b'' def __enter__(self): self.fd.__enter__() return self def __exit__(self, *a, **k): if self.runt: self.fd.write(b2a_base64(self.runt)) self.fd.write(b'\r\n') return self.fd.__exit__(*a, **k) def write(self, buf): if self.runt: buf = self.runt + buf rl = len(buf) % 3 self.runt = buf[-rl:] if rl else b'' if rl < len(buf): tmp = b2a_base64(buf[:(-rl if rl else None)]) # library puts in newlines!? assert tmp[-1:] == b'\n', tmp assert tmp[-2:-1] != b'=', tmp self.fd.write(tmp[:-1]) def swab32(n): # endian swap: 32 bits return ustruct.unpack('>I', ustruct.pack('= FW_HEADER_SIZE magic_value, timestamp, version_string, pk, fw_size, install_flags, hw_compat = \ unpack_from(FWH_PY_FORMAT, hdr)[0:7] assert magic_value == FW_HEADER_MAGIC, 'bad magic' assert fw_size == binary_size or fw_size == (binary_size-128), 'size problem' except Exception as exc: return "That does not look like a firmware " \ "file we would want to use: %s" % exc if hw_compat != 0: # check this hardware is compatible ok = False if hw_label == 'mk1': ok = (hw_compat & MK_1_OK) elif hw_label == 'mk2': ok = (hw_compat & MK_2_OK) elif hw_label == 'mk3': ok = (hw_compat & MK_3_OK) if not ok: return "New firmware doesn't support this version of Coldcard hardware (%s)."%hw_label water = callgate.get_highwater() if water[0] and timestamp < water: return "That downgrade is not supported." return None def clean_shutdown(style=0): # wipe SPI flash and shutdown (wiping main memory) import callgate from sflash import SF try: SF.wipe_most() except: pass callgate.show_logout(style) def call_later_ms(delay, cb, *args): import uasyncio async def doit(): await uasyncio.sleep_ms(delay) await cb(*args) uasyncio.create_task(doit()) def word_wrap(ln, w): while ln: sp = ln.rfind(' ', 0, w) if sp == -1: # bad-break the line sp = min(len(ln), w) nsp = sp if ln[nsp:nsp+1] == ' ': nsp += 1 else: nsp = sp+1 left = ln[0:sp] ln = ln[nsp:] if len(left) + 1 + len(ln) <= w: left = left + ' ' + ln ln = '' yield left # EOF