1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-25 16:08:32 +00:00

src/apps/debug: simplify debuglink, add more decision/state fields

- move data exported over debuglink into apps.debug
- move debug signals into apps.debug
- make pin/mnemonic dialogs testable
- streamline code style of apps.management.reset_device
- check __debug__ when debug app starts
This commit is contained in:
Jan Pochyla 2018-03-20 16:38:21 +01:00
parent f9c51af32f
commit 01bc12ec27
9 changed files with 108 additions and 103 deletions

View File

@ -1,13 +1,7 @@
from trezor import loop, ui, wire from trezor import ui, wire
from trezor.messages import ButtonRequestType, FailureType, wire_types from trezor.messages import ButtonRequestType, FailureType, wire_types
from trezor.messages.ButtonRequest import ButtonRequest from trezor.messages.ButtonRequest import ButtonRequest
from trezor.ui.confirm import CONFIRMED, ConfirmDialog, HoldToConfirmDialog from trezor.ui.confirm import CONFIRMED, ConfirmDialog, HoldToConfirmDialog
from apps.common import cache
# used to confirm/cancel the dialogs from outside of this module (i.e.
# through debug link)
if __debug__:
signal = cache.memory.setdefault('confirm_signal', loop.signal())
@ui.layout @ui.layout
@ -18,11 +12,7 @@ async def confirm(ctx, content, code=None, *args, **kwargs):
dialog = ConfirmDialog(content, *args, **kwargs) dialog = ConfirmDialog(content, *args, **kwargs)
if __debug__: return await ctx.wait(dialog) == CONFIRMED
waiter = ctx.wait(signal, dialog)
else:
waiter = ctx.wait(dialog)
return await waiter == CONFIRMED
@ui.layout @ui.layout
@ -33,11 +23,7 @@ async def hold_to_confirm(ctx, content, code=None, *args, **kwargs):
dialog = HoldToConfirmDialog(content, 'Hold to confirm', *args, **kwargs) dialog = HoldToConfirmDialog(content, 'Hold to confirm', *args, **kwargs)
if __debug__: return await ctx.wait(dialog) == CONFIRMED
waiter = ctx.wait(signal, dialog)
else:
waiter = ctx.wait(dialog)
return await waiter == CONFIRMED
async def require_confirm(*args, **kwargs): async def require_confirm(*args, **kwargs):

View File

@ -1,8 +1,11 @@
from trezor import res, ui from trezor import loop, res, ui
from trezor.messages import PinMatrixRequestType from trezor.messages import PinMatrixRequestType
from trezor.ui.confirm import CONFIRMED, ConfirmDialog from trezor.ui.confirm import CONFIRMED, ConfirmDialog
from trezor.ui.pin import PinMatrix from trezor.ui.pin import PinMatrix
if __debug__:
from apps.debug import input_signal
class PinCancelled(Exception): class PinCancelled(Exception):
pass pass
@ -42,13 +45,18 @@ async def request_pin(code=None, cancellable: bool=True) -> str:
matrix.onchange() matrix.onchange()
while True: while True:
if __debug__:
result = await loop.wait(dialog, input_signal)
if isinstance(result, str):
return result
else:
result = await dialog result = await dialog
if result == CONFIRMED: if result == CONFIRMED:
return matrix.pin return matrix.pin
elif result != CONFIRMED and matrix.pin: elif matrix.pin: # reset
matrix.change('') matrix.change('')
continue continue
else: else: # cancel
raise PinCancelled() raise PinCancelled()

View File

@ -1,69 +1,42 @@
import micropython from trezor import loop, utils
import gc from trezor.messages import wire_types
from uctypes import bytes_at, bytearray_at
from trezor import loop
from trezor.wire import register, protobuf_workflow
from trezor.messages.wire_types import \
DebugLinkDecision, DebugLinkGetState, DebugLinkStop, \
DebugLinkMemoryRead, DebugLinkMemoryWrite, DebugLinkFlashErase
from trezor.messages.DebugLinkMemory import DebugLinkMemory
from trezor.messages.DebugLinkState import DebugLinkState from trezor.messages.DebugLinkState import DebugLinkState
from trezor.ui.confirm import CONFIRMED, CANCELLED from trezor.ui import confirm, swipe
from trezor.wire import register, protobuf_workflow
from apps.common.confirm import signal
from apps.common import storage from apps.common import storage
from apps.management import reset_device
if not __debug__:
utils.halt("debug mode inactive")
reset_internal_entropy = None
reset_current_words = None
reset_word_index = None
confirm_signal = loop.signal()
swipe_signal = loop.signal()
input_signal = loop.signal()
async def dispatch_DebugLinkDecision(ctx, msg): async def dispatch_DebugLinkDecision(ctx, msg):
signal.send(CONFIRMED if msg.yes_no else CANCELLED) if msg.yes_no is not None:
confirm_signal.send(confirm.CONFIRMED if msg.yes_no else confirm.CANCELLED)
if msg.up_down is not None:
swipe_signal.send(swipe.SWIPE_DOWN if msg.up_down else swipe.SWIPE_UP)
if msg.input is not None:
input_signal.send(msg.input)
async def dispatch_DebugLinkGetState(ctx, msg): async def dispatch_DebugLinkGetState(ctx, msg):
m = DebugLinkState() m = DebugLinkState()
m.mnemonic = storage.get_mnemonic() m.mnemonic = storage.get_mnemonic()
m.passphrase_protection = storage.has_passphrase() m.passphrase_protection = storage.has_passphrase()
m.reset_entropy = reset_device.internal_entropy m.reset_word_pos = reset_word_index
m.reset_word = reset_device.current_word m.reset_entropy = reset_internal_entropy
if reset_current_words:
m.reset_word = ' '.join(reset_current_words)
return m return m
async def dispatch_DebugLinkStop(ctx, msg):
pass
async def dispatch_DebugLinkMemoryRead(ctx, msg):
m = DebugLinkMemory()
m.memory = bytes_at(msg.address, msg.length)
return m
async def dispatch_DebugLinkMemoryWrite(ctx, msg):
l = len(msg.memory)
data = bytearray_at(msg.address, l)
data[0:l] = msg.memory
async def dispatch_DebugLinkFlashErase(ctx, msg):
# TODO: erase(msg.sector)
pass
async def memory_stats(interval):
sleep = loop.sleep(interval * 1000 * 1000)
while True:
micropython.mem_info()
gc.collect()
await sleep
def boot(): def boot():
register(DebugLinkDecision, protobuf_workflow, dispatch_DebugLinkDecision) register(wire_types.DebugLinkDecision, protobuf_workflow, dispatch_DebugLinkDecision)
register(DebugLinkGetState, protobuf_workflow, dispatch_DebugLinkGetState) register(wire_types.DebugLinkGetState, protobuf_workflow, dispatch_DebugLinkGetState)
register(DebugLinkStop, protobuf_workflow, dispatch_DebugLinkStop)
register(DebugLinkMemoryRead, protobuf_workflow, dispatch_DebugLinkMemoryRead)
register(DebugLinkMemoryWrite, protobuf_workflow, dispatch_DebugLinkMemoryWrite)
register(DebugLinkFlashErase, protobuf_workflow, dispatch_DebugLinkFlashErase)
# loop.schedule(memory_stats(10))

View File

@ -18,14 +18,10 @@ from apps.common.confirm import require_confirm
from apps.management.change_pin import request_pin_confirm from apps.management.change_pin import request_pin_confirm
if __debug__: if __debug__:
internal_entropy = None from apps import debug
current_word = None
async def reset_device(ctx, msg): async def reset_device(ctx, msg):
if __debug__:
global internal_entropy
# validate parameters and device state # validate parameters and device state
if msg.strength not in (128, 192, 256): if msg.strength not in (128, 192, 256):
raise wire.FailureError( raise wire.FailureError(
@ -36,31 +32,29 @@ async def reset_device(ctx, msg):
FailureType.UnexpectedMessage, FailureType.UnexpectedMessage,
'Already initialized') 'Already initialized')
if msg.pin_protection:
# request new PIN # request new PIN
if msg.pin_protection:
newpin = await request_pin_confirm(ctx) newpin = await request_pin_confirm(ctx)
else: else:
# new PIN is empty
newpin = '' newpin = ''
# generate and display internal entropy # generate and display internal entropy
internal_entropy = random.bytes(32) internal_ent = random.bytes(32)
if __debug__:
debug.reset_internal_entropy = internal_ent
if msg.display_random: if msg.display_random:
await show_entropy(ctx, internal_entropy) await show_entropy(ctx, internal_ent)
# request external entropy and compute mnemonic # request external entropy and compute mnemonic
ack = await ctx.call(EntropyRequest(), wire_types.EntropyAck) ent_ack = await ctx.call(EntropyRequest(), wire_types.EntropyAck)
mnemonic = generate_mnemonic( mnemonic = generate_mnemonic(msg.strength, internal_ent, ent_ack.entropy)
msg.strength, internal_entropy, ack.entropy)
if msg.skip_backup: if not msg.skip_backup:
# let user backup the mnemonic later # require confirmation of the mnemonic safety
pass
else:
# warn user about mnemonic safety
await show_warning(ctx) await show_warning(ctx)
while True:
# show mnemonic and require confirmation of a random word # show mnemonic and require confirmation of a random word
while True:
await show_mnemonic(ctx, mnemonic) await show_mnemonic(ctx, mnemonic)
if await check_mnemonic(ctx, mnemonic): if await check_mnemonic(ctx, mnemonic):
break break
@ -77,11 +71,11 @@ async def reset_device(ctx, msg):
storage.load_mnemonic( storage.load_mnemonic(
mnemonic=mnemonic, needs_backup=msg.skip_backup) mnemonic=mnemonic, needs_backup=msg.skip_backup)
# show success message # show success message. if we skipped backup, it's possible that homescreen
# is still running, uninterrupted. restart it to pick up new label.
if not msg.skip_backup: if not msg.skip_backup:
await show_success(ctx) await show_success(ctx)
else: else:
# trigger reload of homescreen
workflow.restartdefault() workflow.restartdefault()
return Success(message='Initialized') return Success(message='Initialized')
@ -142,7 +136,7 @@ async def show_success(ctx):
cancel=None) cancel=None)
async def show_entropy(ctx, entropy: int): async def show_entropy(ctx, entropy: bytes):
estr = hexlify(entropy).decode() estr = hexlify(entropy).decode()
lines = chunks(estr, 16) lines = chunks(estr, 16)
content = Text('Internal entropy', ui.ICON_RESET, ui.MONO, *lines) content = Text('Internal entropy', ui.ICON_RESET, ui.MONO, *lines)
@ -165,6 +159,9 @@ async def show_mnemonic(ctx, mnemonic: str):
@ui.layout @ui.layout
async def show_mnemonic_page(page: int, page_count: int, pages: list): async def show_mnemonic_page(page: int, page_count: int, pages: list):
if __debug__:
debug.reset_current_words = [word for _, word in pages[page]]
lines = ['%2d. %s' % (wi + 1, word) for wi, word in pages[page]] lines = ['%2d. %s' % (wi + 1, word) for wi, word in pages[page]]
content = Text('Recovery seed', ui.ICON_RESET, ui.MONO, *lines) content = Text('Recovery seed', ui.ICON_RESET, ui.MONO, *lines)
content = Scrollpage(content, page, page_count) content = Scrollpage(content, page, page_count)
@ -194,6 +191,9 @@ async def check_mnemonic(ctx, mnemonic: str) -> bool:
@ui.layout @ui.layout
async def check_word(ctx, words: list, index: int): async def check_word(ctx, words: list, index: int):
if __debug__:
debug.reset_word_index = index
keyboard = MnemonicKeyboard('Type the %s word:' % format_ordinal(index + 1)) keyboard = MnemonicKeyboard('Type the %s word:' % format_ordinal(index + 1))
result = await ctx.wait(keyboard) result = await ctx.wait(keyboard)
return result == words[index] return result == words[index]

View File

@ -4,14 +4,20 @@ import protobuf as p
class DebugLinkDecision(p.MessageType): class DebugLinkDecision(p.MessageType):
FIELDS = { FIELDS = {
1: ('yes_no', p.BoolType, 0), # required 1: ('yes_no', p.BoolType, 0),
2: ('up_down', p.BoolType, 0),
3: ('input', p.UnicodeType, 0),
} }
MESSAGE_WIRE_TYPE = 100 MESSAGE_WIRE_TYPE = 100
def __init__( def __init__(
self, self,
yes_no: bool = None, yes_no: bool = None,
up_down: bool = None,
input: str = None,
**kwargs, **kwargs,
): ):
self.yes_no = yes_no self.yes_no = yes_no
self.up_down = up_down
self.input = input
p.MessageType.__init__(self, **kwargs) p.MessageType.__init__(self, **kwargs)

View File

@ -15,6 +15,7 @@ class DebugLinkState(p.MessageType):
8: ('reset_entropy', p.BytesType, 0), 8: ('reset_entropy', p.BytesType, 0),
9: ('recovery_fake_word', p.UnicodeType, 0), 9: ('recovery_fake_word', p.UnicodeType, 0),
10: ('recovery_word_pos', p.UVarintType, 0), 10: ('recovery_word_pos', p.UVarintType, 0),
11: ('reset_word_pos', p.UVarintType, 0),
} }
MESSAGE_WIRE_TYPE = 102 MESSAGE_WIRE_TYPE = 102
@ -30,6 +31,7 @@ class DebugLinkState(p.MessageType):
reset_entropy: bytes = None, reset_entropy: bytes = None,
recovery_fake_word: str = None, recovery_fake_word: str = None,
recovery_word_pos: int = None, recovery_word_pos: int = None,
reset_word_pos: int = None,
**kwargs, **kwargs,
): ):
self.layout = layout self.layout = layout
@ -42,4 +44,5 @@ class DebugLinkState(p.MessageType):
self.reset_entropy = reset_entropy self.reset_entropy = reset_entropy
self.recovery_fake_word = recovery_fake_word self.recovery_fake_word = recovery_fake_word
self.recovery_word_pos = recovery_word_pos self.recovery_word_pos = recovery_word_pos
self.reset_word_pos = reset_word_pos
p.MessageType.__init__(self, **kwargs) p.MessageType.__init__(self, **kwargs)

View File

@ -4,6 +4,9 @@ from trezor.ui import Widget
from trezor.ui.button import BTN_ACTIVE, BTN_CLICKED, BTN_STARTED, Button from trezor.ui.button import BTN_ACTIVE, BTN_CLICKED, BTN_STARTED, Button
from trezor.ui.loader import Loader from trezor.ui.loader import Loader
if __debug__:
from apps.debug import confirm_signal
CONFIRMED = const(1) CONFIRMED = const(1)
CANCELLED = const(2) CANCELLED = const(2)
DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM)
@ -11,8 +14,12 @@ DEFAULT_CANCEL = res.load(ui.ICON_CANCEL)
class ConfirmDialog(Widget): class ConfirmDialog(Widget):
def __init__(self,
def __init__(self, content, confirm=DEFAULT_CONFIRM, cancel=DEFAULT_CANCEL, confirm_style=ui.BTN_CONFIRM, cancel_style=ui.BTN_CANCEL): content,
confirm=DEFAULT_CONFIRM,
cancel=DEFAULT_CANCEL,
confirm_style=ui.BTN_CONFIRM,
cancel_style=ui.BTN_CANCEL):
self.content = content self.content = content
if cancel is not None: if cancel is not None:
self.confirm = Button( self.confirm = Button(
@ -37,6 +44,9 @@ class ConfirmDialog(Widget):
return CANCELLED return CANCELLED
async def __iter__(self): async def __iter__(self):
if __debug__:
return await loop.wait(super().__iter__(), self.content, confirm_signal)
else:
return await loop.wait(super().__iter__(), self.content) return await loop.wait(super().__iter__(), self.content)
@ -81,5 +91,8 @@ class HoldToConfirmDialog(Widget):
else: else:
content_loop = self.content content_loop = self.content
confirm_loop = super().__iter__() # default loop (render on touch) confirm_loop = super().__iter__() # default loop (render on touch)
if __debug__:
result = await loop.wait(content_loop, confirm_loop, confirm_signal)
else:
result = await loop.wait(content_loop, confirm_loop) result = await loop.wait(content_loop, confirm_loop)
return result return result

View File

@ -3,6 +3,9 @@ from trezor.crypto import bip39
from trezor.ui import display from trezor.ui import display
from trezor.ui.button import BTN_CLICKED, ICON, Button from trezor.ui.button import BTN_CLICKED, ICON, Button
if __debug__:
from apps.debug import input_signal
MNEMONIC_KEYS = ('abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz') MNEMONIC_KEYS = ('abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz')
@ -145,6 +148,12 @@ class MnemonicKeyboard(ui.Widget):
btn.disable() btn.disable()
async def __iter__(self): async def __iter__(self):
if __debug__:
return await loop.wait(self.edit_loop(), input_signal)
else:
return await self.edit_loop()
async def edit_loop(self):
timeout = loop.sleep(1000 * 1000 * 1) timeout = loop.sleep(1000 * 1000 * 1)
touch = loop.select(io.TOUCH) touch = loop.select(io.TOUCH)
wait_timeout = loop.wait(touch, timeout) wait_timeout = loop.wait(touch, timeout)

View File

@ -1,6 +1,9 @@
from micropython import const from micropython import const
from trezor import loop, ui, res from trezor import loop, ui, res
from .swipe import Swipe, SWIPE_UP, SWIPE_DOWN, SWIPE_VERTICAL from trezor.ui.swipe import Swipe, SWIPE_UP, SWIPE_DOWN, SWIPE_VERTICAL
if __debug__:
from apps.debug import swipe_signal
async def change_page(page, page_count): async def change_page(page, page_count):
@ -11,7 +14,11 @@ async def change_page(page, page_count):
d = SWIPE_DOWN d = SWIPE_DOWN
else: else:
d = SWIPE_VERTICAL d = SWIPE_VERTICAL
s = await Swipe(directions=d) swipe = Swipe(directions=d)
if __debug__:
s = await loop.wait(swipe, swipe_signal)
else:
s = await swipe
if s == SWIPE_UP: if s == SWIPE_UP:
return page + 1 # scroll down return page + 1 # scroll down
elif s == SWIPE_DOWN: elif s == SWIPE_DOWN: