async def read_any( self, expected_wire_types: Iterable[int]) -> protobuf.MessageType: if __debug__: log.debug( __name__, "%s:%x expect: %s", self.iface.iface_num(), self.sid, expected_wire_types, ) # Load the full message into a buffer, parse out type and data payload msg = await self.read_from_wire() # If we got a message with unexpected type, raise the message via # `UnexpectedMessageError` and let the session handler deal with it. if msg.type not in expected_wire_types: raise UnexpectedMessageError(msg) # find the protobuf type exptype = messages.get_type(msg.type) if __debug__: log.debug(__name__, "%s:%x read: %s", self.iface.iface_num(), self.sid, exptype) workflow.idle_timer.touch() # parse the message and return it return protobuf.load_message(msg.data, exptype)
async def read_any( self, expected_wire_types: Iterable[int]) -> protobuf.MessageType: reader = self.make_reader() if __debug__: log.debug( __name__, "%s:%x expect: %s", self.iface.iface_num(), self.sid, expected_wire_types, ) # Wait for the message header, contained in the first report. After # we receive it, we have a message type to match on. await reader.aopen() # If we got a message with unexpected type, raise the reader via # `UnexpectedMessageError` and let the session handler deal with it. if reader.type not in expected_wire_types: raise UnexpectedMessageError(reader) # find the protobuf type exptype = messages.get_type(reader.type) if __debug__: log.debug(__name__, "%s:%x read: %s", self.iface.iface_num(), self.sid, exptype) # parse the message and return it return await protobuf.load_message(reader, exptype)
async def protobuf_workflow(ctx: Context, reader: codec_v1.Reader, handler: Handler, *args: Any) -> None: from trezor.messages.Failure import Failure req = await protobuf.load_message(reader, messages.get_type(reader.type)) if __debug__: log.debug(__name__, "%s:%x request: %s", ctx.iface.iface_num(), ctx.sid, req) try: res = await handler(ctx, req, *args) except UnexpectedMessageError: # session handler takes care of this one raise except Error as exc: # respond with specific code and message await ctx.write(Failure(code=exc.code, message=exc.message)) raise except Exception as e: # respond with a generic code and message message = "Firmware error" if __debug__: message = "{}: {}".format(type(e), e) await ctx.write( Failure(code=FailureType.FirmwareError, message=message)) raise if res: # respond with a specific response await ctx.write(res)
async def read_any(self, allowed_types: Iterable[int]) -> MessageType: reader = self.make_reader() if __debug__: log.debug( __name__, "%s:%x expect: %s", self.iface.iface_num(), self.sid, allowed_types, ) await reader.aopen() # wait for the message header # if we got a message with unexpected type, raise the reader via # `UnexpectedMessageError` and let the session handler deal with it if reader.type not in allowed_types: raise UnexpectedMessageError(reader) # find the protobuf type exptype = messages.get_type(reader.type) if __debug__: log.debug(__name__, "%s:%x read: %s", self.iface.iface_num(), self.sid, exptype) # parse the message and return it return await protobuf.load_message(reader, exptype)
def get() -> protobuf.MessageType | None: stored_auth_type = storage.cache.get( storage.cache.APP_COMMON_AUTHORIZATION_TYPE) if not stored_auth_type: return None msg_wire_type = int.from_bytes(stored_auth_type, "big") msg_type = messages.get_type(msg_wire_type) buffer = storage.cache.get(storage.cache.APP_COMMON_AUTHORIZATION_DATA) reader = utils.BufferReader(buffer) return protobuf.load_message(reader, msg_type)
async def protobuf_workflow(ctx, reader, handler, *args): from trezor.messages.Failure import Failure req = await protobuf.load_message(reader, messages.get_type(reader.type)) try: res = await handler(ctx, req, *args) except UnexpectedMessageError: # session handler takes care of this one raise except Error as exc: # respond with specific code and message await ctx.write(Failure(code=exc.code, message=exc.message)) raise except Exception as exc: # respond with a generic code and message await ctx.write(Failure(code=FailureType.FirmwareError, message='Firmware error')) raise if res: # respond with a specific response await ctx.write(res)
async def read(self, types): ''' Wait for incoming message on this wire context and return it. Raises `UnexpectedMessageError` if the message type does not match one of `types`; and caller should always make sure to re-raise it. ''' reader = self.getreader() if __debug__: log.debug(__name__, '%s:%x read: %s', self.iface.iface_num(), self.sid, types) await reader.aopen() # wait for the message header # if we got a message with unexpected type, raise the reader via # `UnexpectedMessageError` and let the session handler deal with it if reader.type not in types: raise UnexpectedMessageError(reader) # look up the protobuf class and parse the message pbtype = messages.get_type(reader.type) return await protobuf.load_message(reader, pbtype)
async def read( self, expected_type: Type[protobuf.LoadedMessageType], field_cache: protobuf.FieldCache = None, ) -> protobuf.LoadedMessageType: if __debug__: log.debug( __name__, "%s:%x expect: %s", self.iface.iface_num(), self.sid, expected_type, ) # Load the full message into a buffer, parse out type and data payload msg = await self.read_from_wire() # If we got a message with unexpected type, raise the message via # `UnexpectedMessageError` and let the session handler deal with it. if msg.type != expected_type.MESSAGE_WIRE_TYPE: raise UnexpectedMessageError(msg) if __debug__: log.debug( __name__, "%s:%x read: %s", self.iface.iface_num(), self.sid, expected_type, ) workflow.idle_timer.touch() # look up the protobuf class and parse the message pbtype = messages.get_type(msg.type) return protobuf.load_message(msg.data, pbtype, field_cache) # type: ignore
async def handle_session(iface: WireInterface, session_id: int) -> None: ctx = Context(iface, session_id) next_reader = None # type: Optional[codec_v1.Reader] res_msg = None req_reader = None req_type = None req_msg = None while True: try: if next_reader is None: # We are not currently reading a message, so let's wait for one. # If the decoding fails, exception is raised and we try again # (with the same `Reader` instance, it's OK). Even in case of # de-synchronized wire communication, report with a message # header is eventually received, after a couple of tries. req_reader = ctx.make_reader() await req_reader.aopen() if __debug__: log.debug( __name__, "%s:%x receive: %s", iface.iface_num(), session_id, messages.get_type(req_reader.type), ) else: # We have a reader left over from earlier. We should process # this message instead of waiting for new one. req_reader = next_reader next_reader = None # Now we are in a middle of reading a message and we need to decide # what to do with it, based on its type from the message header. # From this point on, we should take care to read it in full and # send a response. # Take a mark of modules that are imported at this point, so we can # roll back and un-import any others. Should not raise. modules = utils.unimport_begin() # We need to find a handler for this message type. Should not # raise. handler = get_workflow_handler(req_reader) if handler is None: # If no handler is found, we can skip decoding and directly # respond with failure, but first, we should read the rest of # the message reports. Should not raise. await read_and_throw_away(req_reader) res_msg = unexpected_message() else: # We found a valid handler for this message type. # Workflow task, declared for the `workflow.on_close` call later. wf_task = None # type: Optional[loop.Task] # Here we make sure we always respond with a Failure response # in case of any errors. try: # Find a protobuf.MessageType subclass that describes this # message. Raises if the type is not found. req_type = messages.get_type(req_reader.type) # Try to decode the message according to schema from # `req_type`. Raises if the message is malformed. req_msg = await protobuf.load_message(req_reader, req_type) # At this point, message reports are all processed and # correctly parsed into `req_msg`. # Create the workflow task. wf_task = handler(ctx, req_msg) # Register the task into the workflow management system. workflow.on_start(wf_task) # Run the workflow task. Workflow can do more on-the-wire # communication inside, but it should eventually return a # response message, or raise an exception (a rather common # thing to do). Exceptions are handled in the code below. res_msg = await wf_task except UnexpectedMessageError as exc: # Workflow was trying to read a message from the wire, and # something unexpected came in. See Context.read() for # example, which expects some particular message and raises # UnexpectedMessageError if another one comes in. # In order not to lose the message, we pass on the reader # to get picked up by the workflow logic in the beginning of # the cycle, which processes it in the usual manner. # TODO: # We might handle only the few common cases here, like # Initialize and Cancel. next_reader = exc.reader res_msg = None except Exception as exc: # Either: # - the first workflow message had a type that has a # registered handler, but does not have a protobuf class # - the first workflow message was not a valid protobuf # - workflow raised some kind of an exception while running if __debug__: if isinstance(exc, ActionCancelled): log.debug(__name__, "cancelled: {}".format(exc.message)) else: log.exception(__name__, exc) res_msg = failure(exc) finally: # De-register the task from the workflow system, if we # registered it before. if wf_task is not None: workflow.on_close(wf_task) # If a default workflow is on, make sure we do not race # against the layout that is inside. # TODO: this is very hacky and complects wire with the ui if workflow.default_task is not None: await ui.wait_until_layout_is_running() if res_msg is not None: # Either the workflow returned a response, or we created one. # Write it on the wire. Possibly, the incoming message haven't # been read in full. We ignore this case here and let the rest # of the reports get processed while waiting for the message # header. # TODO: if the write fails, we do not unimport the loaded modules await ctx.write(res_msg) # Cleanup, so garbage collection triggered after un-importing can # pick up the trash. req_reader = None req_type = None req_msg = None res_msg = None handler = None wf_task = None # Unload modules imported by the workflow. Should not raise. utils.unimport_end(modules) except BaseException as exc: # The session handling should never exit, just log and continue. if __debug__: log.exception(__name__, exc)
async def _handle_single_message( ctx: Context, msg: codec_v1.Message, use_workflow: bool) -> codec_v1.Message | None: """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 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. If an unexpected message had arrived on the wire while the workflow was processing, the workflow is shut down with an `UnexpectedMessageError`. This is not considered an "error condition" to return over the wire -- instead the message is processed as if starting a new workflow. In such case, the `UnexpectedMessageError` is caught and the message is returned to the caller. It will then be processed in the next iteration of the message loop. """ if __debug__: try: msg_type = messages.get_type(msg.type).__name__ except KeyError: msg_type = "%d - unknown message type" % msg.type log.debug( __name__, "%s:%x receive: <%s>", ctx.iface.iface_num(), ctx.sid, msg_type, ) res_msg: protobuf.MessageType | None = None # We need to find a handler for this message type. Should not raise. handler = find_handler(ctx.iface, msg.type) if handler is None: # If no handler is found, we can skip decoding and directly # respond with failure. await ctx.write(unexpected_message()) return None # Here we make sure we always respond with a Failure response # in case of any errors. try: # Find a protobuf.MessageType subclass that describes this # message. Raises if the type is not found. req_type = messages.get_type(msg.type) # Try to decode the message according to schema from # `req_type`. Raises if the message is malformed. req_msg = _wrap_protobuf_load(msg.data, req_type) # Create the handler task. task = handler(ctx, req_msg) # Run the workflow task. Workflow can do more on-the-wire # communication inside, but it should eventually return a # response message, or raise an exception (a rather common # thing to do). Exceptions are handled in the code below. if use_workflow: # Spawn a workflow around the task. This ensures that concurrent # workflows are shut down. res_msg = await workflow.spawn(task) else: # For debug messages, ignore workflow processing and just await # results of the handler. res_msg = await task except UnexpectedMessageError as exc: # Workflow was trying to read a message from the wire, and # something unexpected came in. See Context.read() for # example, which expects some particular message and raises # UnexpectedMessageError if another one comes in. # In order not to lose the message, we return it to the caller. # TODO: # We might handle only the few common cases here, like # Initialize and Cancel. return exc.msg except BaseException as exc: # Either: # - the message had a type that has a registered handler, but does not have # a protobuf class # - the message was not valid protobuf # - workflow raised some kind of an exception while running # - something canceled the workflow from the outside if __debug__: if isinstance(exc, ActionCancelled): log.debug(__name__, "cancelled: {}".format(exc.message)) elif isinstance(exc, loop.TaskClosed): log.debug(__name__, "cancelled: loop task was closed") else: log.exception(__name__, exc) res_msg = failure(exc) if res_msg is not None: # perform the write outside the big try-except block, so that usb write # problem bubbles up await ctx.write(res_msg) return None