1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-03 03:50:58 +00:00

refactor(core/wire): move restarting control to handle_single_message

that way it is possible to avoid restarting if we failed to find a
handler for a message (makes it faster with no ill effects because
"failed to find handler" (a) shouldn't happen and (b) doesn't cause any
significant fragmentation)
This commit is contained in:
matejcik 2024-05-30 12:08:18 +02:00 committed by Ioan Bizău
parent 8870869f93
commit cab6fd0799

View File

@ -89,20 +89,19 @@ if __debug__:
async def _handle_single_message( async def _handle_single_message(
ctx: context.Context, msg: codec_v1.Message, use_workflow: bool ctx: context.Context, msg: codec_v1.Message, use_workflow: bool
) -> codec_v1.Message | None: ) -> bool:
"""Handle a message that was loaded from USB by the caller. """Handle a message that was loaded from USB by the caller.
Find the appropriate handler, run it and write its result on the wire. In case Find the appropriate handler, run it and write its result on the wire. In case
a problem is encountered at any point, write the appropriate error on the wire. a problem is encountered at any point, write the appropriate error on the wire.
If the workflow finished normally or with an error, the return value is None. The return value indicates whether to override the default restarting behavior. If
`False` is returned, the caller is allowed to clear the loop and restart the
If an unexpected message had arrived on the wire while the workflow was processing, MicroPython machine (see `session.py`). This would lose all state and incurs a cost
the workflow is shut down with an `UnexpectedMessage` exception. This is not in terms of repeated startup time. When handling the message didn't cause any
considered an "error condition" to return over the wire -- instead the message significant fragmentation (e.g., if decoding the message was skipped), or if
is processed as if starting a new workflow. the type of message is supposed to be optimized and not disrupt the running state,
In such case, the `UnexpectedMessage` is caught and the message is returned this function will return `True`.
to the caller. It will then be processed in the next iteration of the message loop.
""" """
if __debug__: if __debug__:
try: try:
@ -126,7 +125,7 @@ async def _handle_single_message(
# Handlers are allowed to exception out. In that case, we can skip decoding # Handlers are allowed to exception out. In that case, we can skip decoding
# and return the error. # and return the error.
await ctx.write(failure(exc)) await ctx.write(failure(exc))
return None return True
if msg.type in workflow.ALLOW_WHILE_LOCKED: if msg.type in workflow.ALLOW_WHILE_LOCKED:
workflow.autolock_interrupts_workflow = False workflow.autolock_interrupts_workflow = False
@ -158,16 +157,21 @@ async def _handle_single_message(
# results of the handler. # results of the handler.
res_msg = await task res_msg = await task
except context.UnexpectedMessage as exc: except context.UnexpectedMessage:
# Workflow was trying to read a message from the wire, and # Workflow was trying to read a message from the wire, and
# something unexpected came in. See Context.read() for # something unexpected came in. See Context.read() for
# example, which expects some particular message and raises # example, which expects some particular message and raises
# UnexpectedMessage if another one comes in. # UnexpectedMessage if another one comes in.
# In order not to lose the message, we return it to the caller. #
# TODO: # We process the unexpected message by aborting the current workflow and
# We might handle only the few common cases here, like # possibly starting a new one, initiated by that message. (The main usecase
# Initialize and Cancel. # being, the host does not finish the workflow, we want other callers to
return exc.msg # be able to do their own thing.)
#
# The message is stored in the exception, which we re-raise for the caller
# to process. It is not a standard exception that should be logged and a result
# sent to the wire.
raise
except BaseException as exc: except BaseException as exc:
# Either: # Either:
@ -189,7 +193,9 @@ async def _handle_single_message(
# perform the write outside the big try-except block, so that usb write # perform the write outside the big try-except block, so that usb write
# problem bubbles up # problem bubbles up
await ctx.write(res_msg) await ctx.write(res_msg)
return None
# Look into `AVOID_RESTARTING_FOR` to see if this message should avoid restarting.
return msg.type in AVOID_RESTARTING_FOR
async def handle_session( async def handle_session(
@ -230,9 +236,16 @@ async def handle_session(
next_msg = None next_msg = None
try: try:
next_msg = await _handle_single_message( do_not_restart = await _handle_single_message(
ctx, msg, use_workflow=not is_debug_session ctx, msg, use_workflow=not is_debug_session
) )
except context.UnexpectedMessage as unexpected:
# The workflow was interrupted by an unexpected message. We need to
# process it as if it was a new message...
next_msg = unexpected.msg
# ...and we must not restart because that would lose the message.
do_not_restart = True
continue
except Exception as exc: except Exception as exc:
# Log and ignore. The session handler can only exit explicitly in the # Log and ignore. The session handler can only exit explicitly in the
# following finally block. # following finally block.
@ -246,8 +259,7 @@ async def handle_session(
# workflow running on wire. # workflow running on wire.
utils.unimport_end(modules) utils.unimport_end(modules)
if next_msg is None and msg.type not in AVOID_RESTARTING_FOR: if not do_not_restart:
# Shut down the loop if there is no next message waiting.
# Let the session be restarted from `main`. # Let the session be restarted from `main`.
loop.clear() loop.clear()
return # pylint: disable=lost-exception return # pylint: disable=lost-exception