Skip to content

Ignore re-transmitted THP payloads on host #5578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion core/src/trezor/wire/thp/interface_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ async def _handle_broadcast(self, packet: bytes) -> None:
if ctrl_byte != CHANNEL_ALLOCATION_REQ:
raise ThpError("Unexpected ctrl_byte in a broadcast channel packet")

log.info(__name__, "got alloc: %s", utils.hexlify_if_bytes(packet))
channel_cache = channel_manager.create_new_channel(self._iface)
channel = self._load_channel(channel_cache)

Expand Down
8 changes: 7 additions & 1 deletion python/src/trezorlib/transport/thp/control_byte.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.

from typing import Optional

CODEC_V1 = 0x3F
CONTINUATION_PACKET = 0x80
HANDSHAKE_INIT_REQ = 0x00
Expand Down Expand Up @@ -51,7 +53,11 @@ def add_ack_bit_to_ctrl_byte(ctrl_byte: int, ack_bit: int) -> int:
raise Exception("Unexpected acknowledgement bit")


def get_seq_bit(ctrl_byte: int) -> int:
def get_seq_bit(ctrl_byte: int) -> Optional[int]:
if ctrl_byte & 0xE0:
# not all message types contain SEQ bit
return None

return (ctrl_byte & 0x10) >> 4


Expand Down
43 changes: 25 additions & 18 deletions python/src/trezorlib/transport/thp/protocol_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ def __init__(
prepare_channel_without_pairing: bool = True,
) -> None:
super().__init__(transport, mapping)
self._reset_sync_bits()
if prepare_channel_without_pairing:
self.trezor_state = self.prepare_channel_without_pairing(
credential=credential
)
# allow skipping unrelated response packets (e.g. in case of retransmissions)
self._do_channel_allocation(retries=MAX_RETRANSMISSION_COUNT)
self.trezor_state = self._do_handshake(credential=credential)

def get_channel(self) -> ProtocolV2Channel:
if not self._has_valid_channel:
Expand Down Expand Up @@ -127,12 +128,6 @@ def _read_message(self, message_type: type[MT], timeout: float | None = None) ->
assert isinstance(msg, message_type)
return msg

def prepare_channel_without_pairing(self, credential: bytes | None = None) -> int:
self._reset_sync_bits()
# allow skipping unrelated response packets (e.g. in case of retransmissions)
self._do_channel_allocation(retries=MAX_RETRANSMISSION_COUNT)
return self._do_handshake(credential=credential)

def _reset_sync_bits(self) -> None:
self.sync_bit_send = 0
self.sync_bit_receive = 0
Expand Down Expand Up @@ -350,13 +345,15 @@ def read_and_decrypt(
+ ")"
)

seq_bit = control_byte.get_seq_bit(header.ctrl_byte)
assert seq_bit is not None
LOG.debug(
"--> Get sequence bit %d %s %s",
control_byte.get_seq_bit(header.ctrl_byte),
seq_bit,
"from control byte",
hexlify(header.ctrl_byte.to_bytes(1, "big")).decode(),
)
self._send_ack_bit(bit=control_byte.get_seq_bit(header.ctrl_byte))
self._send_ack_bit(bit=seq_bit)

message = self._noise.decrypt(bytes(raw_payload))
session_id = message[0]
Expand All @@ -374,18 +371,28 @@ def _read_until_valid_crc_check(
if timeout is None:
timeout = self._DEFAULT_READ_TIMEOUT

is_valid = False
header, payload, chksum = thp_io.read(self.transport, timeout)
while not is_valid:
is_valid = checksum.is_valid(chksum, header.to_bytes_init() + payload)
if not is_valid:
while True:
header, payload, chksum = thp_io.read(self.transport, timeout)
if not checksum.is_valid(chksum, header.to_bytes_init() + payload):
LOG.error(
"Received a message with an invalid checksum:"
+ hexlify(header.to_bytes_init() + payload + chksum).decode()
)
header, payload, chksum = thp_io.read(self.transport, timeout)
continue

seq_bit = control_byte.get_seq_bit(header.ctrl_byte)
if seq_bit is not None:
if seq_bit != self.sync_bit_receive:
LOG.warning(
"Received unexpected message: sync bit=%d, expected=%d",
seq_bit,
self.sync_bit_receive,
)
continue

self.sync_bit_receive = 1 - self.sync_bit_receive

return header, payload
return header, payload

def _is_valid_channel_allocation_response(
self, header: MessageHeader, payload: bytes, original_nonce: bytes
Expand Down