What changed, and why it matters
This is a code cleanup (refactor) that reorganizes how the Trezor hardware wallet handles its newer THP (Trezor Host Protocol) communication. It moves logic from a loose module into a class called ThpContext, similar to how the older protocol is already structured. There is no indication in the commit that this fixes a security bug; it is described as an internal restructuring with no user-facing changelog entry.
No security action required. Treat as normal code-quality review; verify that THP tests still pass and that channel/interface binding behaves correctly on real hardware after the refactor.
Security signals we found
Refactor only: no changelog, no CVE, no security claim in commit message
Existing security checks (checksum validation, channel allocation, unallocated channel errors, codec_v1 rejection) are retained in ThpContext
Channel-to-interface binding is simplified by storing interface number directly in cache
Debug-only helper find_thp_channel added for tests, gated by __debug__
Evidence from the diff
The commit replaces the procedural thp_main.thp_main_loop and global _CHANNELS dictionary with a ThpContext class that owns an interface’s channels, packet read/write waits, broadcast handling, codec_v1 fallback responses, and payload reassembly dispatch. Channel cache now stores iface.iface_num() directly instead of an encoded interface blob, removing interface_manager encode/decode helpers. The debug-only find_thp_channel helper is moved to trezor.wire. The change is structural: security-critical checks (checksum validation, channel allocation, unallocated-channel error responses) are preserved, and no new parsing or trust boundary is introduced.
Changed components
core/src/trezor/wire/thp/thp_main.py (removed)core/src/trezor/wire/thp/interface_context.py (new ThpContext)core/src/trezor/wire/thp/channel.pycore/src/trezor/wire/thp/channel_manager.pycore/src/trezor/wire/__init__.pycore/src/storage/cache_thp.pycore/src/apps/debug/__init__.pycore/tests/test_trezor.wire.thp.pycore/tests/thp_common.pyInspect captured patch +230 / −310
diff --git a/core/embed/upymod/qstrdefsport.h b/core/embed/upymod/qstrdefsport.h
index 350a8ee50..88611f26f 100644
--- a/core/embed/upymod/qstrdefsport.h
+++ b/core/embed/upymod/qstrdefsport.h
@@ -411,6 +411,7 @@ Q(control_byte)
Q(cpace)
Q(credential_manager)
Q(crypto)
+Q(interface_context)
Q(interface_manager)
Q(memory_manager)
Q(pairing)
@@ -421,7 +422,6 @@ Q(session_manager)
Q(storage.cache_thp)
Q(storage.cache_thp_keys)
Q(thp)
-Q(thp_main)
Q(transmission_loop)
Q(trezor.enums.ThpMessageType)
Q(trezor.enums.ThpPairingMethod)
@@ -433,13 +433,13 @@ Q(trezor.wire.thp.checksum)
Q(trezor.wire.thp.control_byte)
Q(trezor.wire.thp.cpace)
Q(trezor.wire.thp.crypto)
+Q(trezor.wire.thp.interface_context)
Q(trezor.wire.thp.interface_manager)
Q(trezor.wire.thp.memory_manager)
Q(trezor.wire.thp.pairing_context)
Q(trezor.wire.thp.received_message_handler)
Q(trezor.wire.thp.session_context)
Q(trezor.wire.thp.session_manager)
-Q(trezor.wire.thp.thp_main)
Q(trezor.wire.thp.transmission_loop)
Q(trezor.wire.thp.ui)
Q(trezor.wire.thp.writer)
diff --git a/core/src/apps/debug/__init__.py b/core/src/apps/debug/__init__.py
index 5d6aeb2ca..d57431417 100644
--- a/core/src/apps/debug/__init__.py
+++ b/core/src/apps/debug/__init__.py
@@ -287,20 +287,15 @@ if __debug__:
if msg.channel_id is None:
raise RuntimeError("Invalid DebugLinkGetPairingInfo message")
- from trezor.wire.thp.channel import Channel
+ from trezor.wire import find_thp_channel
from trezor.wire.thp.pairing_context import PairingContext
- from trezor.wire.thp.thp_main import _CHANNELS
- channel_id = int.from_bytes(msg.channel_id, "big")
- channel: Channel | None = None
- ctx: PairingContext | None = None
- try:
- channel = _CHANNELS[channel_id]
- ctx = channel.connection_context
- except KeyError:
- pass
+ channel = find_thp_channel(msg.channel_id)
+ if channel is None:
+ raise RuntimeError("Channel not found")
- if ctx is None or not isinstance(ctx, PairingContext):
+ ctx = channel.connection_context
+ if not isinstance(ctx, PairingContext):
raise RuntimeError("Trezor is not in pairing mode")
ctx.nfc_secret_host = msg.nfc_secret_host
diff --git a/core/src/storage/cache_thp.py b/core/src/storage/cache_thp.py
index 2d8305d64..5a9c9a007 100644
--- a/core/src/storage/cache_thp.py
+++ b/core/src/storage/cache_thp.py
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
from storage.cache_common import (
CHANNEL_HOST_STATIC_PUBKEY,
CHANNEL_ID,
+ CHANNEL_IFACE,
CHANNEL_STATE,
CHANNEL_SYNC,
SESSION_ID,
@@ -13,7 +14,7 @@ from storage.cache_common import (
)
if TYPE_CHECKING:
- from typing import Tuple
+ from typing import Iterable, Tuple
pass
@@ -182,12 +183,13 @@ def update_session_last_used(channel_id: bytes, session_id: bytes) -> None:
return
-def get_all_allocated_channels() -> list[ChannelCache]:
- _list: list[ChannelCache] = []
+def iter_allocated_channels(iface_num: int) -> Iterable[ChannelCache]:
for channel in _CHANNELS:
- if channel.get_int(CHANNEL_STATE, _UNALLOCATED_STATE) != _UNALLOCATED_STATE:
- _list.append(channel)
- return _list
+ state = channel.get_int(CHANNEL_STATE, _UNALLOCATED_STATE)
+ if state == _UNALLOCATED_STATE:
+ continue
+ if channel.get_int(CHANNEL_IFACE) == iface_num:
+ yield channel
def get_allocated_session(
diff --git a/core/src/trezor/wire/__init__.py b/core/src/trezor/wire/__init__.py
index 9b6bb13e0..c6e2e8f76 100644
--- a/core/src/trezor/wire/__init__.py
+++ b/core/src/trezor/wire/__init__.py
@@ -5,7 +5,7 @@ Handles on-the-wire communication with a host computer. The communication is:
- Request / response.
- Protobuf-encoded, see `protobuf.py`.
-- Wrapped in a simple envelope format, see `trezor/wire/codec/codec_v1.py` or `trezor/wire/thp/thp_main.py`.
+- Wrapped in a simple envelope format, see `trezor/wire/codec/codec_v1.py` or `trezor/wire/thp/context.py`.
- Transferred over USB interface, or UDP in case of Unix emulation.
This module:
@@ -31,7 +31,8 @@ from .. import workflow
from . import message_handler, protocol_common
if utils.USE_THP:
- from .thp import thp_main
+ from .thp import received_message_handler
+ from .thp.interface_context import ThpContext
else:
from .codec.codec_context import CodecContext
@@ -49,6 +50,8 @@ if TYPE_CHECKING:
from trezorio import WireInterface
from typing import Any, Callable, Coroutine, Generic, TypeVar
+ from trezor.wire.thp.channel import Channel
+
T = TypeVar("T")
Msg = TypeVar("Msg", bound=protobuf.MessageType)
HandlerTask = Coroutine[Any, Any, protobuf.MessageType]
@@ -83,30 +86,30 @@ if utils.USE_THP:
# in more stable area of memory
from .thp import memory_manager # noqa: F401
- async def handle_session(iface: WireInterface) -> None:
+ if __debug__:
+ _THP_CHANNELS = []
- # Take a mark of modules that are imported at this point, so we can
- # roll back and un-import any others.
- modules = utils.unimport_begin()
+ def find_thp_channel(channel_id: bytes) -> Channel | None:
+ """Used by `DebugLinkGetPairingInfo` (only for tests)."""
+ key = int.from_bytes(channel_id, "big")
+ for channels in _THP_CHANNELS:
+ result = channels.get(key)
+ if result is not None:
+ return result
+ return None
+
+ async def handle_session(iface: WireInterface) -> None:
+ ctx = ThpContext.load_from_cache(iface)
+ if __debug__:
+ _THP_CHANNELS.append(ctx._channels)
while True:
try:
- await thp_main.thp_main_loop(iface)
- except Exception as exc:
- # Log and try again.
- if __debug__:
- log.exception(__name__, exc, iface=iface)
- finally:
- # Unload modules imported by the workflow. Should not raise.
- if __debug__:
- log.debug(
- __name__,
- "utils.unimport_end(modules) and loop.clear()",
- iface=iface,
- )
- utils.unimport_end(modules)
- loop.clear()
- return # pylint: disable=lost-exception
+ (channel, message) = await ctx.get_next_message()
+ await received_message_handler.handle_received_message(channel, message)
+ except Exception:
+ loop.clear() # restart event loop in case of error
+ raise # the traceback will be printed by `loop._step()`
else:
diff --git a/core/src/trezor/wire/thp/channel.py b/core/src/trezor/wire/thp/channel.py
index caf8c6d7e..873ea58c4 100644
--- a/core/src/trezor/wire/thp/channel.py
+++ b/core/src/trezor/wire/thp/channel.py
@@ -22,22 +22,9 @@ from storage.cache_thp import (
from trezor import loop, protobuf, utils, workflow
from trezor.wire.errors import WireBufferError
-from . import (
- ENCRYPTED,
- ChannelState,
- PacketHeader,
- ThpDecryptionError,
- ThpError,
- ThpErrorType,
-)
+from . import ENCRYPTED, ChannelState, PacketHeader, ThpDecryptionError, ThpError
from . import alternating_bit_protocol as ABP
-from . import (
- control_byte,
- crypto,
- interface_manager,
- memory_manager,
- received_message_handler,
-)
+from . import control_byte, crypto, memory_manager
from .checksum import CHECKSUM_LENGTH
from .transmission_loop import TransmissionLoop
from .writer import (
@@ -54,11 +41,11 @@ if __debug__:
from . import state_to_str
if TYPE_CHECKING:
- from trezorio import WireInterface
from typing import Any, Awaitable
from trezor.messages import ThpPairingCredential
+ from .interface_context import ThpContext
from .pairing_context import PairingContext
from .session_context import GenericSessionContext
@@ -76,7 +63,7 @@ class Reassembler:
"""
Process current packet, returning the payload buffer on success.
- May raise WireBufferError if there is a concurrent payload reassembly in progress.
+ May raise `WireBufferError` if there is a concurrent payload reassembly in progress.
"""
ctrl_byte = packet[0]
if control_byte.is_continuation(ctrl_byte):
@@ -121,13 +108,12 @@ class Channel:
THP protocol encrypted communication channel.
"""
- def __init__(self, channel_cache: ChannelCache) -> None:
+ def __init__(self, channel_cache: ChannelCache, ctx: ThpContext) -> None:
+ assert ctx._iface.iface_num() == channel_cache.get_int(CHANNEL_IFACE)
# Channel properties
self.channel_id: bytes = channel_cache.channel_id
- channel_iface = channel_cache.get(CHANNEL_IFACE)
- assert channel_iface is not None
- self.iface: WireInterface = interface_manager.decode_iface(channel_iface)
+ self.iface = ctx._iface
if __debug__:
self._log("channel initialization")
self.channel_cache: ChannelCache = channel_cache
@@ -198,18 +184,14 @@ class Channel:
# READ and DECRYPT
- def receive_packet(self, packet: utils.BufferType) -> Awaitable[None] | None:
+ def handle_packet(self, packet: utils.BufferType) -> memoryview | None:
+ if self.get_channel_state() == ChannelState.UNALLOCATED:
+ return None
try:
- buffer = self.reassembler.get_next_message(memoryview(packet))
+ return self.reassembler.get_next_message(memoryview(packet))
except WireBufferError:
self.reassembler.reset()
- return self.write_error(ThpErrorType.TRANSPORT_BUSY)
-
- if buffer is None:
- return None
-
- self._log("buffer: ", hexlify_if_bytes(buffer))
- return received_message_handler.handle_received_message(self, buffer)
+ raise
def decrypt_buffer(
self, message_length: int, offset: int = INIT_HEADER_LENGTH
diff --git a/core/src/trezor/wire/thp/channel_manager.py b/core/src/trezor/wire/thp/channel_manager.py
index b8b7fc0f2..56eee54b3 100644
--- a/core/src/trezor/wire/thp/channel_manager.py
+++ b/core/src/trezor/wire/thp/channel_manager.py
@@ -1,52 +1,21 @@
from typing import TYPE_CHECKING
from storage import cache_thp
-from storage.cache_common import CHANNEL_IFACE
+from storage.cache_common import CHANNEL_IFACE, CHANNEL_STATE
-from . import ChannelState, interface_manager
-from .channel import Channel
+from . import ChannelState
if TYPE_CHECKING:
from trezorio import WireInterface
from storage.cache_thp import ChannelCache
-if __debug__:
- from trezor import log
-CHANNELS_LOADED: bool = False
-
-
-def create_new_channel(iface: WireInterface) -> Channel:
+def create_new_channel(iface: WireInterface) -> ChannelCache:
"""
Creates a new channel for the interface `iface`.
"""
channel_cache: ChannelCache = cache_thp.get_new_channel()
- channel_cache.set(CHANNEL_IFACE, interface_manager.encode_iface(iface))
- channel = Channel(channel_cache)
- channel.set_channel_state(ChannelState.TH1)
- return channel
-
-
-def load_cached_channels(
- channels_dict: dict[int, Channel], iface: WireInterface
-) -> None:
- """
- Returns all allocated channels from cache.
- """
- global CHANNELS_LOADED
-
- if CHANNELS_LOADED:
- if __debug__:
- log.debug(
- __name__, "Channels already loaded, process skipped.", iface=iface
- )
- return
-
- cached_channels = cache_thp.get_all_allocated_channels()
- for channel in cached_channels:
- channel_id = int.from_bytes(channel.channel_id, "big")
- channels_dict[channel_id] = Channel(channel)
- if __debug__:
- log.debug(__name__, "Channels loaded from cache.", iface=iface)
- CHANNELS_LOADED = True
+ channel_cache.set_int(CHANNEL_IFACE, iface.iface_num())
+ channel_cache.set_int(CHANNEL_STATE, ChannelState.TH1)
+ return channel_cache
diff --git a/core/src/trezor/wire/thp/interface_context.py b/core/src/trezor/wire/thp/interface_context.py
new file mode 100644
index 000000000..cc54520af
--- /dev/null
+++ b/core/src/trezor/wire/thp/interface_context.py
@@ -0,0 +1,156 @@
+import ustruct
+from micropython import const
+from typing import TYPE_CHECKING
+
+from storage.cache_thp import (
+ BROADCAST_CHANNEL_ID,
+ ChannelCache,
+ iter_allocated_channels,
+)
+from trezor import io, loop
+
+from ..errors import WireBufferError
+from . import (
+ CHANNEL_ALLOCATION_REQ,
+ CODEC_V1,
+ PacketHeader,
+ ThpError,
+ ThpErrorType,
+ channel_manager,
+ checksum,
+ control_byte,
+ get_channel_allocation_response,
+)
+from .channel import Channel
+from .checksum import CHECKSUM_LENGTH
+from .writer import INIT_HEADER_LENGTH, write_payload_to_wire_and_add_checksum
+
+if __debug__:
+ from trezor import log
+
+if TYPE_CHECKING:
+ from trezorio import WireInterface
+
+_CID_REQ_PAYLOAD_LENGTH = const(12)
+
+# Codec_v1 magic constant:
+# "?##" + Failure message type + msg_size + msg_data (code = "Failure_InvalidProtocol")
+CODEC_V1_ERROR_MESSAGE = b"\x3f\x23\x23\x00\x03\x00\x00\x00\x14\x08\x11"
+
+
+class ThpContext:
+ """
+ This class allows fetching multi-packet THP payloads from a given interface.
+ It also handles and responds to low-level single packet THP messages, creating new channels if needed.
+ """
+
+ @classmethod
+ def load_from_cache(cls, iface: WireInterface) -> "ThpContext":
+ ctx = cls(iface)
+ for channel_cache in iter_allocated_channels(iface.iface_num()):
+ ctx._load_channel(channel_cache)
+ return ctx
+
+ def _load_channel(self, cache: ChannelCache) -> Channel:
+ channel_id = int.from_bytes(cache.channel_id, "big")
+ assert channel_id not in self._channels
+ self._channels[channel_id] = channel = Channel(cache, self)
+ return channel
+
+ def __init__(self, iface: WireInterface) -> None:
+ self._iface = iface
+ self._read = loop.wait(iface.iface_num() | io.POLL_READ)
+ self._write = loop.wait(iface.iface_num() | io.POLL_WRITE)
+ self._channels: dict[int, Channel] = {}
+
+ async def get_next_message(self) -> tuple[Channel, memoryview]:
+ packet = bytearray(self._iface.RX_PACKET_LEN)
+ while True:
+ packet_len = await self._read
+ assert packet_len is not None
+ assert packet_len == len(packet)
+ self._iface.read(packet, 0)
+
+ if _get_ctrl_byte(packet) == CODEC_V1:
+ await self._handle_codec_v1(packet)
+ continue
+
+ cid = ustruct.unpack(">BH", packet)[1]
+
+ if cid == BROADCAST_CHANNEL_ID:
+ await self._handle_broadcast(packet)
+ continue
+
+ channel = self._channels.get(cid)
+ if channel is None:
+ await self._handle_unallocated(cid, packet)
+ continue
+
+ try:
+ message = channel.handle_packet(packet)
+ if message is not None:
+ # `message` must be handled ASAP without blocking,
+ # since it may point to the global read buffer.
+ return channel, message
+ except WireBufferError:
+ await channel.write_error(ThpErrorType.TRANSPORT_BUSY)
+ continue
+
+ async def write_packet(self, packet: bytes) -> None:
+ assert len(packet) == self._iface.TX_PACKET_LEN
+ while True:
+ await self._write
+ n_written = self._iface.write(packet)
+ if n_written == len(packet):
+ return
+ assert n_written == 0
+
+ async def _handle_codec_v1(self, packet: bytes) -> None:
+ # If the received packet is not an initial codec_v1 packet, do not send error message
+ if packet[1:3] == b"##":
+ buf = CODEC_V1_ERROR_MESSAGE
+ buf += bytes(self._iface.TX_PACKET_LEN - len(buf))
+ await self.write_packet(buf)
+
+ async def _handle_broadcast(self, packet: bytes) -> None:
+ if _get_ctrl_byte(packet) != CHANNEL_ALLOCATION_REQ:
+ raise ThpError("Unexpected ctrl_byte in a broadcast channel packet")
+
+ data = packet[: INIT_HEADER_LENGTH + _CID_REQ_PAYLOAD_LENGTH]
+ if not checksum.is_valid(data[-CHECKSUM_LENGTH:], data[:-CHECKSUM_LENGTH]):
+ raise ThpError("Checksum is not valid")
+
+ length, nonce = ustruct.unpack(">H8s", packet[3:])
+ if length != _CID_REQ_PAYLOAD_LENGTH:
+ raise ThpError("Invalid length in broadcast channel packet")
+
+ channel_cache = channel_manager.create_new_channel(self._iface)
+ channel = self._load_channel(channel_cache)
+
+ response_data = get_channel_allocation_response(
+ nonce, channel.channel_id, self._iface
+ )
+ response_header = PacketHeader.get_channel_allocation_response_header(
+ len(response_data) + CHECKSUM_LENGTH,
+ )
+ if __debug__:
+ log.debug(
+ __name__,
+ "New channel allocated with id %d",
+ channel.get_channel_id_int(),
+ iface=self._iface,
+ )
+ await write_payload_to_wire_and_add_checksum(
+ self._iface, response_header, response_data
+ )
+
+ async def _handle_unallocated(self, cid: int, packet: bytes) -> None:
+ if control_byte.is_continuation(_get_ctrl_byte(packet)):
+ return
+ data = (ThpErrorType.UNALLOCATED_CHANNEL).to_bytes(1, "big")
+ header = PacketHeader.get_error_header(cid, len(data) + CHECKSUM_LENGTH)
+ await write_payload_to_wire_and_add_checksum(self._iface, header, data)
+
+
+def _get_ctrl_byte(packet: bytes) -> int:
+ return packet[0]
diff --git a/core/src/trezor/wire/thp/thp_main.py b/core/src/trezor/wire/thp/thp_main.py
deleted file mode 100644
index de5f4174b..000000000
--- a/core/src/trezor/wire/thp/thp_main.py
+++ /dev/null
@@ -1,173 +0,0 @@
-import ustruct
-from micropython import const
-from typing import TYPE_CHECKING
-
-from storage.cache_thp import BROADCAST_CHANNEL_ID
-from trezor import io, loop, utils
-
-from . import (
- CHANNEL_ALLOCATION_REQ,
- CODEC_V1,
- ChannelState,
- PacketHeader,
- ThpError,
- ThpErrorType,
- channel_manager,
- checksum,
- control_byte,
- get_channel_allocation_response,
- writer,
-)
-from .channel import Channel
-from .checksum import CHECKSUM_LENGTH
-from .writer import (
- INIT_HEADER_LENGTH,
- MAX_PAYLOAD_LEN,
- write_payload_to_wire_and_add_checksum,
-)
-
-if __debug__:
- from trezor import log
-
-if TYPE_CHECKING:
- from trezorio import WireInterface
-
-_CID_REQ_PAYLOAD_LENGTH = const(12)
-_CHANNELS: dict[int, Channel] = {}
-
-
-async def thp_main_loop(iface: WireInterface) -> None:
- global _CHANNELS
- channel_manager.load_cached_channels(_CHANNELS, iface)
-
- read = loop.wait(iface.iface_num() | io.POLL_READ)
- packet = bytearray(iface.RX_PACKET_LEN)
- try:
- while True:
- try:
- if __debug__:
- log.debug(__name__, "thp_main_loop", iface=iface)
- packet_len = await read
- assert packet_len == len(packet)
- iface.read(packet, 0)
-
- if _get_ctrl_byte(packet) == CODEC_V1:
- await _handle_codec_v1(iface, packet)
- continue
-
- cid = ustruct.unpack(">BH", packet)[1]
-
- if cid == BROADCAST_CHANNEL_ID:
- await _handle_broadcast(iface, packet)
- continue
-
- if cid in _CHANNELS:
- await _handle_allocated(iface, cid, packet)
- else:
- await _handle_unallocated(iface, cid, packet)
-
- except ThpError as e:
- if __debug__:
- log.exception(__name__, e, iface=iface)
- finally:
- channel_manager.CHANNELS_LOADED = False
-
-
-async def _handle_codec_v1(iface: WireInterface, packet: bytes) -> None:
- # If the received packet is not an initial codec_v1 packet, do not send error message
- if not packet[1:3] == b"##":
- return
- if __debug__:
- log.debug(__name__, "Received codec_v1 message, returning error", iface=iface)
- error_message = _get_codec_v1_error_message()
- await writer.write_packet_to_wire(iface, error_message)
-
-
-async def _handle_broadcast(iface: WireInterface, packet: utils.BufferType) -> None:
- if _get_ctrl_byte(packet) != CHANNEL_ALLOCATION_REQ:
- raise ThpError("Unexpected ctrl_byte in a broadcast channel packet")
- if __debug__:
- log.debug(
- __name__, "Received valid message on the broadcast channel", iface=iface
- )
-
- length, nonce = ustruct.unpack(">H8s", packet[3:])
- payload = _get_buffer_for_payload(length, packet[5:], _CID_REQ_PAYLOAD_LENGTH)
- if not checksum.is_valid(
- payload[-4:],
- packet[: _CID_REQ_PAYLOAD_LENGTH + INIT_HEADER_LENGTH - CHECKSUM_LENGTH],
- ):
- raise ThpError("Checksum is not valid")
-
- new_channel: Channel = channel_manager.create_new_channel(iface)
- cid = int.from_bytes(new_channel.channel_id, "big")
- _CHANNELS[cid] = new_channel
-
- response_data = get_channel_allocation_response(
- nonce, new_channel.channel_id, iface
- )
- response_header = PacketHeader.get_channel_allocation_response_header(
- len(response_data) + CHECKSUM_LENGTH,
- )
- if __debug__:
- log.debug(__name__, "New channel allocated with id %d", cid, iface=iface)
-
- await write_payload_to_wire_and_add_checksum(iface, response_header, response_data)
-
-
-async def _handle_allocated(
- iface: WireInterface, cid: int, packet: utils.BufferType
-) -> None:
- channel = _CHANNELS[cid]
- if channel is None:
- await _handle_unallocated(iface, cid, packet)
- raise ThpError("Invalid state of a channel")
- if channel.iface is not iface:
- # TODO send error message to wire
- raise ThpError("Channel has different WireInterface")
-
- if channel.get_channel_state() != ChannelState.UNALLOCATED:
- x = channel.receive_packet(packet)
- if x is not None:
- await x
-
-
-async def _handle_unallocated(iface: WireInterface, cid: int, packet: bytes) -> None:
- if control_byte.is_continuation(_get_ctrl_byte(packet)):
- return
- data = (ThpErrorType.UNALLOCATED_CHANNEL).to_bytes(1, "big")
- header = PacketHeader.get_error_header(cid, len(data) + CHECKSUM_LENGTH)
- await write_payload_to_wire_and_add_checksum(iface, header, data)
-
-
-def _get_buffer_for_payload(
- payload_length: int,
- existing_buffer: utils.BufferType,
- max_length: int = MAX_PAYLOAD_LEN,
-) -> utils.BufferType:
- if payload_length > max_length:
- raise ThpError("Message too large")
- if payload_length > len(existing_buffer):
- try:
- new_buffer = bytearray(payload_length)
- except MemoryError:
- raise ThpError("Message too large")
- return new_buffer
- return _reuse_existing_buffer(payload_length, existing_buffer)
-
-
-def _reuse_existing_buffer(
- payload_length: int, existing_buffer: utils.BufferType
-) -> utils.BufferType:
- return memoryview(existing_buffer)[:payload_length]
-
-
-def _get_ctrl_byte(packet: bytes) -> int:
- return packet[0]
-
-
-def _get_codec_v1_error_message() -> bytes:
- # Codec_v1 magic constant "?##" + Failure message type + msg_size
- # + msg_data (code = "Failure_InvalidProtocol") + padding to 64 B
- ERROR_MSG = b"\x3f\x23\x23\x00\x03\x00\x00\x00\x14\x08\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- return ERROR_MSG
diff --git a/core/tests/test_trezor.wire.thp.py b/core/tests/test_trezor.wire.thp.py
index ec5da789e..ea165a9c2 100644
--- a/core/tests/test_trezor.wire.thp.py
+++ b/core/tests/test_trezor.wire.thp.py
@@ -5,7 +5,8 @@ from trezor import io
if utils.USE_THP:
import thp_common
- from trezor.wire.thp import interface_manager, memory_manager, thp_main
+ from trezor.wire import handle_session as thp_main_loop
+ from trezor.wire.thp import memory_manager
@unittest.skipUnless(utils.USE_THP, "only needed for THP")
@@ -14,18 +15,16 @@ class TestTrezorHostProtocol(unittest.TestCase):
def __init__(self):
if __debug__:
thp_common.suppress_debug_log()
- interface_manager.encode_iface = thp_common.dummy_encode_iface
super().__init__()
def setUp(self):
self.interface = MockHID()
memory_manager.READ_BUFFER = bytearray(64)
memory_manager.WRITE_BUFFER = bytearray(256)
- interface_manager.decode_iface = thp_common.dummy_decode_iface
def test_codec_message(self):
self.assertEqual(len(self.interface.data), 0)
- gen = thp_main.thp_main_loop(self.interface)
+ gen = thp_main_loop(self.interface)
gen.send(None)
# There should be a failiure response to received init packet (starts with "?##")
@@ -49,7 +48,7 @@ class TestTrezorHostProtocol(unittest.TestCase):
self.assertEqual(len(self.interface.data), 1)
def test_message_on_unallocated_channel(self):
- gen = thp_main.thp_main_loop(self.interface)
+ gen = thp_main_loop(self.interface)
query = gen.send(None)
self.assertObjectEqual(query, self.interface.wait_object(io.POLL_READ))
message_to_channel_789a = (
diff --git a/core/tests/thp_common.py b/core/tests/thp_common.py
index 3fba09e2d..22427180f 100644
--- a/core/tests/thp_common.py
+++ b/core/tests/thp_common.py
@@ -6,40 +6,27 @@ if utils.USE_THP:
from mock_wire_interface import MockHID
from storage import cache_thp
- from storage.cache_common import CHANNEL_IFACE
from trezor.wire import context
- from trezor.wire.thp import ChannelState, interface_manager
from trezor.wire.thp.channel import Channel
+ from trezor.wire.thp.channel_manager import create_new_channel
+ from trezor.wire.thp.interface_context import ThpContext
from trezor.wire.thp.session_context import SessionContext
- _MOCK_INTERFACE_HID = b"\x00"
-
if TYPE_CHECKING:
from trezor.wire import WireInterface
- def dummy_decode_iface(cached_iface: bytes):
- return MockHID()
-
- def dummy_encode_iface(iface: WireInterface):
- return _MOCK_INTERFACE_HID
-
- def get_new_channel(channel_iface: WireInterface | None = None) -> Channel:
- interface_manager.decode_iface = dummy_decode_iface
- channel_cache = cache_thp.get_new_channel()
- channel_cache.set(CHANNEL_IFACE, _MOCK_INTERFACE_HID)
- channel = Channel(channel_cache)
- channel.set_channel_state(ChannelState.TH1)
- if channel_iface is not None:
- channel.iface = channel_iface
- return channel
-
def prepare_context() -> None:
- channel = get_new_channel()
+ mock_iface = MockHID()
+ channel_cache = create_new_channel(mock_iface)
session_cache = cache_thp.create_or_replace_session(
- channel.channel_cache, session_id=b"\x01"
+ channel_cache, session_id=b"\x01"
)
- session_ctx = SessionContext(channel, session_cache)
- context.CURRENT_CONTEXT = session_ctx
+ channel = Channel(channel_cache, ThpContext.load_from_cache(mock_iface))
+ context.CURRENT_CONTEXT = SessionContext(channel, session_cache)
+
+ def get_new_channel(iface: WireInterface) -> Channel:
+ channel_cache = create_new_channel(iface)
+ return Channel(channel_cache, ThpContext(iface))
if __debug__:
Why this scored 11/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.