Skip to content

Commit f911bdd

Browse files
committed
fix(core): handle ACK bit on THP data messages
Both THP handshake and encrypted transport messages contain an ACK bit, which should not be ignored. [no changelog]
1 parent 6e45c37 commit f911bdd

File tree

3 files changed

+40
-17
lines changed

3 files changed

+40
-17
lines changed

core/src/trezor/wire/thp/alternating_bit_protocol.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ def is_ack_valid(cache: ChannelCache, ack_bit: int) -> bool:
1010
if not _is_ack_expected(cache):
1111
return False
1212

13-
if not _has_ack_correct_sync_bit(cache, ack_bit):
14-
return False
15-
16-
return True
13+
return _has_ack_correct_sync_bit(cache, ack_bit)
1714

1815

1916
def _is_ack_expected(cache: ChannelCache) -> bool:

core/src/trezor/wire/thp/channel.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ def is_channel_to_replace(self) -> bool:
217217

218218
async def recv_payload(
219219
self,
220-
expected_ctrl_byte: Callable[[int], bool] | None,
220+
expected_ctrl_byte: Callable[[int], bool],
221+
return_ack: bool = False,
221222
timeout_ms: int | None = None,
222223
) -> memoryview:
223224
"""
@@ -226,7 +227,7 @@ async def recv_payload(
226227
227228
Raise if the received control byte is an unexpected one.
228229
229-
If `expected_ctrl_byte` is `None`, returns after the first received ACK.
230+
If `return_ack` is set, return after receiving an expected ACK.
230231
"""
231232

232233
while True:
@@ -236,17 +237,32 @@ async def recv_payload(
236237

237238
# Synchronization process
238239
ctrl_byte = msg[0]
240+
if not (control_byte.is_ack(ctrl_byte) or control_byte.is_data(ctrl_byte)):
241+
if __debug__:
242+
self._log(
243+
"Unrelated control byte - ignoring ",
244+
utils.hexlify_if_bytes(msg),
245+
logger=log.warning,
246+
)
247+
continue
248+
239249
payload = msg[PacketHeader.INIT_LENGTH : -CHECKSUM_LENGTH]
240250
seq_bit = control_byte.get_seq_bit(ctrl_byte)
251+
ack_bit = control_byte.get_ack_bit(ctrl_byte)
241252

242-
# 1: Handle ACKs
243-
if control_byte.is_ack(ctrl_byte):
244-
handle_ack(self, control_byte.get_ack_bit(ctrl_byte))
245-
if expected_ctrl_byte is None:
246-
return payload
247-
continue
253+
# 0: Handle ACKs
254+
if received_expected_ack(self, ack_bit):
255+
if control_byte.is_ack(ctrl_byte):
256+
# ACK message is handled
257+
self.reassembler.reset()
248258

249-
if expected_ctrl_byte is None or not expected_ctrl_byte(ctrl_byte):
259+
if return_ack:
260+
# keep the reassembled payload (for future handling)
261+
return payload[:0]
262+
263+
# 1: Check expected control byte
264+
self.reassembler.reset()
265+
if not expected_ctrl_byte(ctrl_byte) or return_ack:
250266
if __debug__:
251267
self._log(
252268
"Unexpected control byte - ignoring ",
@@ -400,8 +416,12 @@ async def write_encrypted_payload(self, ctrl_byte: int, payload: bytes) -> None:
400416
# starting from 100ms till ~3.42s
401417
timeout_ms = round(10200 - 1010000 / (100 + i))
402418
try:
403-
# wait and return after receiving an ACK, or raise in case of an unexpected message.
404-
await self.recv_payload(expected_ctrl_byte=None, timeout_ms=timeout_ms)
419+
# wait and return after receiving an expected ACK.
420+
await self.recv_payload(
421+
expected_ctrl_byte=control_byte.is_encrypted_transport,
422+
return_ack=True,
423+
timeout_ms=timeout_ms,
424+
)
405425
except Timeout:
406426
if __debug__:
407427
log.warning(__name__, "Retransmit after %d ms", timeout_ms)
@@ -462,9 +482,9 @@ def send_ack(channel: Channel, ack_bit: int) -> Awaitable[None]:
462482
return channel.ctx.write_payload(header, b"")
463483

464484

465-
def handle_ack(ctx: Channel, ack_bit: int) -> None:
485+
def received_expected_ack(ctx: Channel, ack_bit: int) -> bool:
466486
if not ABP.is_ack_valid(ctx.channel_cache, ack_bit):
467-
return
487+
return False
468488
# ACK is expected and it has correct sync bit
469489
if __debug__:
470490
log.debug(
@@ -473,3 +493,4 @@ def handle_ack(ctx: Channel, ack_bit: int) -> None:
473493
iface=ctx.iface,
474494
)
475495
ABP.set_sending_allowed(ctx.channel_cache, True)
496+
return True

core/src/trezor/wire/thp/control_byte.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ def is_ack(ctrl_byte: int) -> bool:
4141
return ctrl_byte & _ACK_MASK == ACK_MESSAGE
4242

4343

44+
def is_data(ctrl_byte: int) -> bool:
45+
"""Return `True` for ACK, handshake and encrypted transport messages."""
46+
return ctrl_byte & 0xE0 == 0
47+
48+
4449
def is_continuation(ctrl_byte: int) -> bool:
4550
return ctrl_byte & _CONTINUATION_PACKET_MASK == CONTINUATION_PACKET
4651

0 commit comments

Comments
 (0)