예제 #1
0
파일: usb.py 프로젝트: jimmysong/firmware
    async def handle_download(self, offset, length, file_number):
        # let them read from where we store the signed txn
        # - filenumber can be 0 or 1: uploaded txn, or result
        from main import sf

        # limiting memory use here, should be MAX_BLK_LEN really
        length = min(length, MAX_BLK_LEN)

        assert 0 <= file_number < 2, 'bad fnum'
        assert 0 <= offset <= MAX_TXN_LEN, "bad offset"
        assert 1 <= length, 'len'

        # maintain a running SHA256 over what's sent
        if offset == 0:
            self.file_checksum = tcc.sha256()

        pos = (MAX_TXN_LEN * file_number) + offset

        resp = bytearray(4 + length)
        resp[0:4] = b'biny'
        sf.read(pos, memoryview(resp)[4:])

        self.file_checksum.update(memoryview(resp)[4:])

        return resp
예제 #2
0
    def find_spot(self, not_here=0):
        # search for a blank sector to use
        # - check randomly and pick first blank one (wear leveling, deniability)
        # - we will write and then erase old slot
        # - if "full", blow away a random one
        from main import sf

        options = [s for s in SLOTS if s != not_here]
        tcc.random.shuffle(options)

        buf = bytearray(16)
        for pos in options:
            sf.read(pos, buf)
            if set(buf) == {0xff}:
                # blank
                return sf, pos

        # No where to write! (probably a bug because we have lots of slots)
        # ... so pick a random slot and kill what it had
        #print("ERROR: nvram full?")

        victem = options[0]
        sf.sector_erase(victem)
        sf.wait_done()

        return sf, victem
예제 #3
0
def count_busy():
    from main import sf
    from nvstore import SLOTS

    busy = 0
    b = bytearray(4096)
    for pos in SLOTS:
        sf.read(pos, b)
        if len(set(b)) > 200:
            busy += 1
    return busy
예제 #4
0
    async def handle_upload(self, offset, total_size, data):
        from main import dis, sf

        # maintain a running SHA256 over what's received
        if offset == 0:
            self.file_checksum = tcc.sha256()

        assert offset % 256 == 0, 'alignment'
        assert offset + len(data) <= total_size <= MAX_UPLOAD_LEN, 'long'

        rb = bytearray(256)
        for pos in range(offset, offset + len(data), 256):
            if pos % 4096 == 0:
                # erase here
                sf.sector_erase(pos)

                dis.fullscreen("Receiving...")
                dis.progress_bar_show(offset / total_size)

                while sf.is_busy():
                    await sleep_ms(10)

            # write up to 256 bytes
            here = data[pos - offset:pos - offset + 256]
            sf.write(pos, here)

            # full page write: 0.6 to 3ms
            while sf.is_busy():
                await sleep_ms(1)

            # use actual read back for verify
            sf.read(pos, rb)
            self.file_checksum.update(rb[0:len(here)])

        if offset + len(data) >= total_size:
            # probably done
            dis.progress_bar_show(1.0)
            ux.restore_menu()

        return offset
예제 #5
0
async def test_sflash():
    dis.clear()
    dis.text(None, 18, 'Serial Flash')
    dis.show()

    #if ckcc.is_simulator(): return

    from main import sf
    from ustruct import pack
    import tcc

    msize = 1024 * 1024
    sf.chip_erase()

    for phase in [0, 1]:
        steps = 7 * 4
        for i in range(steps):
            dis.progress_bar(i / steps)
            dis.show()
            await sleep_ms(250)
            if not sf.is_busy(): break

        assert not sf.is_busy(), "sflash erase didn't finish"

        # leave chip blank
        if phase == 1: break

        buf = bytearray(32)
        for addr in range(0, msize, 1024):
            sf.read(addr, buf)
            assert set(buf) == {255}, "sflash not blank:" + repr(buf)

            rnd = tcc.sha256(pack('I', addr)).digest()
            sf.write(addr, rnd)
            sf.read(addr, buf)
            assert buf == rnd, "sflash write failed"

            dis.progress_bar_show(addr / msize)

        # check no aliasing, also right size part
        for addr in range(0, msize, 1024):
            expect = tcc.sha256(pack('I', addr)).digest()
            sf.read(addr, buf)
            assert buf == expect, "sflash readback failed"

            dis.progress_bar_show(addr / msize)
예제 #6
0
    def load(self):
        # Search all slots for any we can read, decrypt that,
        # and pick the newest one (in unlikely case of dups)
        from main import sf

        # reset
        self.current = {}
        self.my_pos = 0
        self.is_dirty = 0

        # 4k, but last 32 bytes are a SHA (itself encrypted)
        global _tmp

        buf = bytearray(4)
        empty = 0
        for pos in SLOTS:
            gc.collect()

            sf.read(pos, buf)
            if buf[0] == buf[1] == buf[2] == buf[3] == 0xff:
                # erased (probably)
                empty += 1
                continue

            # check if first 2 bytes makes sense for JSON
            aes = self.get_aes(tcc.AES.Encrypt, pos)
            chk = aes.update(b'{"')

            if chk != buf[0:2]:
                # doesn't look like JSON meant for me
                continue

            # probably good, read it
            aes = self.get_aes(tcc.AES.Encrypt, pos)

            chk = tcc.sha256()
            expect = None

            with SFFile(pos, length=4096, pre_erased=True) as fd:
                for i in range(4096 / 32):
                    b = aes.update(fd.read(32))
                    if i != 127:
                        _tmp[i * 32:(i * 32) + 32] = b
                        chk.update(b)
                    else:
                        expect = b

            try:
                # verify checksum in last 32 bytes
                assert expect == chk.digest()

                # loads() can't work from a byte array, and converting to
                # bytes here would copy it; better to use file emulation.
                d = ujson.load(BytesIO(_tmp))
            except:
                # One in 65k or so chance to come here w/ garbage decoded, so
                # not an error.
                continue

            got_age = d.get('_age', 0)
            if got_age > self.current.get('_age', -1):
                # likely winner
                self.current = d
                self.my_pos = pos
                #print("NV: data @ %d w/ age=%d" % (pos, got_age))
            else:
                # stale data seen; clean it up.
                assert self.current['_age'] > 0
                print("NV: cleanup @ %d" % pos)
                sf.sector_erase(pos)
                sf.wait_done()

        # 4k is a large object, sigh, for us right now. cleanup
        gc.collect()

        # done, if we found something
        if self.my_pos:
            return

        # nothing found.
        self.my_pos = 0
        self.current = self.default_values()

        if empty == len(SLOTS):
            # Whole thing is blank. Bad for plausible deniability. Write 3 slots
            # with garbage. They will be wasted space until it fills.
            blks = list(SLOTS)
            tcc.random.shuffle(blks)

            for pos in blks[0:3]:
                for i in range(0, 4096, 256):
                    h = tcc.random.bytes(256)
                    sf.wait_done()
                    sf.write(pos + i, h)
예제 #7
0
# everything should be encrypted now
assert count_busy() == len(SLOTS)

# check we hide initial values
sf.chip_erase()
settings.load()
settings.save()
assert count_busy() == 4

# check checksum/age stuff works
settings.set('wrecked', 768)
settings.save()

b = bytearray(4096)
sf.read(settings.my_pos, b)
was_age = settings.get('_age')

settings.set('wrecked', 123)
settings.save()
assert settings.get('_age') == was_age + 1
was_pos = settings.my_pos

# write old data everywhere else
for pos in SLOTS:
    if pos != was_pos:
        for i in range(0, 4096, 256):
            sf.write(pos + i, b[i:i + 256])

settings.load()
assert was_pos == settings.my_pos