fix(transport): skip async debug frames — the real cause of garbage LF reads
Debug-heavy firmware emits DEBUG_PRINT_STRING/INTEGERS/BYTES (0x0100-0x0102) frames asynchronously, independent of any command. pm3py's read_response returned the FIRST frame it saw, so a debug line landing mid-command was misparsed as the reply — observed on hardware as garbage LF sample counts (e.g. 1593903105), a config-SET that "timed out", and desynced BigBuf downloads (reading firmware memory / the 0xDEADBEEF stack canary). The C client routes debug frames to its logger and keeps reading for the real reply; pm3py did not. read_response and _read_any_response now loop past DEBUG_PRINT_* (and malformed) frames up to a bound, returning the actual command reply — for both NG replies and the OLD-frame bulk-download stream. This is what makes LF read/download reliable on this device. Tests: single + multiple interleaved debug frames are skipped to reach the real reply. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -151,3 +151,36 @@ def test_download_bulk_drains_stale_input_first():
|
||||
t.send_frame = tracking_send
|
||||
_run(t.download_bulk(Cmd.DOWNLOAD_BIGBUF, 0, 100, Cmd.DOWNLOADED_BIGBUF, Cmd.ACK))
|
||||
assert calls[0] == "drain" and calls[1] == "send" # drained BEFORE the request went out
|
||||
|
||||
|
||||
def _resp_frame(cmd, data=b"", status=0):
|
||||
# build a no-CRC NG response frame the way the device sends one
|
||||
length_ng = len(data) | 0x8000
|
||||
pre = struct.pack("<IHbbH", RESP_PREAMBLE_MAGIC, length_ng, status, 0, cmd)
|
||||
return pre + data + struct.pack("<H", RESP_POSTAMBLE_NOCRC)
|
||||
|
||||
|
||||
def test_read_response_skips_async_debug_frames():
|
||||
# a debug-print frame interleaved before the real reply must be skipped, not misread as data
|
||||
from unittest.mock import AsyncMock as _AM
|
||||
t = PM3Transport("/dev/null")
|
||||
t._reader = object() # truthy so read_response proceeds
|
||||
debug = _resp_frame(int(Cmd.DEBUG_PRINT_STRING), b"LF Sampling config q=95")
|
||||
reply = _resp_frame(int(Cmd.LF_ACQ_RAW_ADC), struct.pack("<I", 12288))
|
||||
t._read_response_frame = _AM(side_effect=[debug, reply])
|
||||
resp = _run(t.read_response())
|
||||
assert resp.cmd == int(Cmd.LF_ACQ_RAW_ADC)
|
||||
assert struct.unpack_from("<I", resp.data, 0)[0] == 12288 # the real sample count, not debug bytes
|
||||
|
||||
|
||||
def test_read_response_skips_multiple_debug_then_reply():
|
||||
from unittest.mock import AsyncMock as _AM
|
||||
t = PM3Transport("/dev/null")
|
||||
t._reader = object()
|
||||
frames = [_resp_frame(int(Cmd.DEBUG_PRINT_STRING), b"a"),
|
||||
_resp_frame(int(Cmd.DEBUG_PRINT_INTEGERS), struct.pack("<I", 999)),
|
||||
_resp_frame(int(Cmd.DEBUG_PRINT_BYTES), b"\x01\x02"),
|
||||
_resp_frame(int(Cmd.PING), b"pong")]
|
||||
t._read_response_frame = _AM(side_effect=frames)
|
||||
resp = _run(t.read_response())
|
||||
assert resp.cmd == int(Cmd.PING) and resp.data == b"pong"
|
||||
|
||||
Reference in New Issue
Block a user