fix(lf): download only the acquired sample count, not a fixed span

With the debug-frame fix the BigBuf download now returns clean sample frames (verified on
hardware: offsets 0..N, real envelope data, then ACK). But identify downloaded a fixed
15000 bytes while the acquisition captured 12288 samples (98304 bits / 8), so the tail was
BigBuf memory past the samples (heap / the 0xDEADBEEF stack canary) — which flagged as
"garbage" and poisoned the demod.

read_emitted now sizes the download to lf.read()'s reported count (firmware returns the
size in BITS; /8 = samples at 8 bits/sample); read_config downloads the readbl acquisition
span. No trailing memory -> the demod runs on clean samples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-14 17:34:23 -07:00
parent 938a571155
commit a06f4d12b8
2 changed files with 27 additions and 4 deletions

View File

@@ -58,24 +58,38 @@ def _download(device, count: int = _SAMPLE_COUNT) -> bytes:
return b""
def _acquired_bytes(read_result, default: int) -> int:
"""How many BigBuf bytes hold valid samples, from an ``lf.read()`` result. The firmware
reports the sample size in BITS (count * bits_per_sample); at 8 bits/sample that is one byte
per sample. Downloading exactly this avoids the BigBuf memory *past* the acquired samples
(heap / the 0xDEADBEEF stack canary) that otherwise poisons the demod."""
if isinstance(read_result, dict):
bits = read_result.get("samples", 0)
if isinstance(bits, int) and bits > 0:
return max(512, min(bits // 8, 60000))
return default
def read_emitted(device) -> dict | None:
"""Capture and decode whatever the tag is emitting in the field (no downlink) — the EM4100
ID a tag (native or T5577-emulated) streams continuously."""
try:
device.lf.read(samples=_SAMPLE_COUNT)
result = device.lf.read(samples=_SAMPLE_COUNT)
except Exception:
return None
return demod_samples(_download(device))
return demod_samples(_download(device, count=_acquired_bytes(result, _SAMPLE_COUNT)))
def read_config(device) -> dict | None:
"""Probe for a T55xx by reading config block 0 and demodulating the response. A clean,
sane config both proves the chip is a T5577 and says what it's programmed to emulate."""
sane config both proves the chip is a T5577 and says what it's programmed to emulate.
The block-0 read (DoPartialAcquisition) captures ~12000 samples and returns no count, so we
download that fixed span — enough for the short block response, without a memory tail."""
try:
device.lf.t55.readbl(0) # fills BigBuf with the block-0 response envelope
except Exception:
return None
return protocols.decode_t55xx_config(_download(device))
return protocols.decode_t55xx_config(_download(device, count=12000))
def identify_lf(device, emit=None) -> dict | None:

View File

@@ -362,3 +362,12 @@ class TestIdentifyLF:
def test_no_device(self):
assert identify_lf(None) is None
class TestAcquiredBytes:
def test_bit_count_to_bytes(self):
from pm3py.lf.capture import _acquired_bytes
assert _acquired_bytes({"samples": 98304}, 15000) == 12288 # 98304 bits / 8 = 12288 samples
assert _acquired_bytes({"samples": 0}, 15000) == 15000 # no count -> fallback
assert _acquired_bytes(None, 15000) == 15000
assert _acquired_bytes({"samples": 100}, 15000) == 512 # clamped up to a floor