fix(transport): auto-resync after a read timeout (the "04 BB stale UID" desync)
On the flaky NG link a command's response can arrive *after* its read times out, and the next command's read then picks up that stale frame — observed in rawcli as every raw 14a command returning "04 BB" (the truncated UID from _ensure_selected's timed-out scan) instead of its own response. read_response/_read_any_response now set a _resync_needed flag on timeout, and the next send_frame drains any late/stale input before writing. Cheap — only fires after a timeout (rare on a healthy link) and no-ops when the stream is clean. Hardware-verified: identify -> NTAG216, READ(4) 6/6 correct, GET_VERSION correct, with the hardening in place. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -184,3 +184,30 @@ def test_read_response_skips_multiple_debug_then_reply():
|
||||
t._read_response_frame = _AM(side_effect=frames)
|
||||
resp = _run(t.read_response())
|
||||
assert resp.cmd == int(Cmd.PING) and resp.data == b"pong"
|
||||
|
||||
|
||||
def test_read_timeout_sets_resync_next_send_drains():
|
||||
# a read timeout flags a resync; the next send drains the late response before writing
|
||||
import asyncio as _a
|
||||
from unittest.mock import AsyncMock as _AM
|
||||
t = PM3Transport("/dev/null")
|
||||
t._reader = object()
|
||||
async def _timeout():
|
||||
raise _a.TimeoutError
|
||||
t._read_response_frame = _timeout
|
||||
try:
|
||||
_run(t.read_response(timeout=0.01))
|
||||
except Exception:
|
||||
pass
|
||||
assert t._resync_needed is True # timeout armed a resync
|
||||
|
||||
t._writer = _AM()
|
||||
t._writer.write = lambda f: None
|
||||
t._writer.drain = _AM()
|
||||
drained = []
|
||||
async def _fake_drain(quiet=0.08):
|
||||
drained.append(True)
|
||||
return 0
|
||||
t._drain_input = _fake_drain
|
||||
_run(t.send_frame(b"\x00"))
|
||||
assert drained == [True] and t._resync_needed is False # drained once, flag cleared
|
||||
|
||||
Reference in New Issue
Block a user