fix(lf): identify_lf fails clearly on a raw async device (not "coroutine has no len")
capture drives the device synchronously (as Proxmark3.sync()'s proxy exposes it). Passing a bare async Proxmark3() — whose methods return un-awaited coroutines, and which isn't even connected — blew up deep in the demod with "object of type 'coroutine' has no len()". Now identify_lf detects a coroutine-function device up front and raises an actionable TypeError pointing at Proxmark3.sync(). The mock-based tests returned plain values, so they never exercised the async method shape; added a test using a real coroutine function that reproduces and guards it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,8 @@ combines the two into "is it a T5577, and what is it (emulating)?".
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
|
||||||
from . import protocols
|
from . import protocols
|
||||||
|
|
||||||
# how many envelope samples to pull from BigBuf after a capture (a T55xx/EM frame is a few
|
# how many envelope samples to pull from BigBuf after a capture (a T55xx/EM frame is a few
|
||||||
@@ -120,6 +122,15 @@ def identify_lf(device, emit=None) -> dict | None:
|
|||||||
if device is None:
|
if device is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# capture drives the device *synchronously* (as Proxmark3.sync()'s proxy exposes it). A raw
|
||||||
|
# async Proxmark3() returns un-awaited coroutines here — fail with an actionable message
|
||||||
|
# instead of a cryptic "coroutine has no len()" deep in the demod.
|
||||||
|
if inspect.iscoroutinefunction(getattr(getattr(device, "lf", None), "read", None)):
|
||||||
|
raise TypeError(
|
||||||
|
"identify_lf needs a synchronous device — use Proxmark3.sync(), not Proxmark3(). "
|
||||||
|
"(A bare Proxmark3() is the async API and isn't connected.)"
|
||||||
|
)
|
||||||
|
|
||||||
# Force the device debug level to NONE for the duration: on debug-heavy firmware a raised
|
# Force the device debug level to NONE for the duration: on debug-heavy firmware a raised
|
||||||
# g_dbglevel makes LF acquisition emit Dbprintf frames over USB, which interleave with the
|
# g_dbglevel makes LF acquisition emit Dbprintf frames over USB, which interleave with the
|
||||||
# sample download and desync it. This is the device-side knob (not the C client's g_debugMode
|
# sample download and desync it. This is the device-side knob (not the C client's g_debugMode
|
||||||
|
|||||||
@@ -363,6 +363,20 @@ class TestIdentifyLF:
|
|||||||
def test_no_device(self):
|
def test_no_device(self):
|
||||||
assert identify_lf(None) is None
|
assert identify_lf(None) is None
|
||||||
|
|
||||||
|
def test_rejects_async_device(self):
|
||||||
|
# a raw async Proxmark3() exposes coroutine-function methods — must fail with a clear
|
||||||
|
# message pointing at Proxmark3.sync(), not blow up deep in the demod on an un-awaited
|
||||||
|
# coroutine (the MagicMock tests couldn't catch this since mocks return plain values)
|
||||||
|
dev = MagicMock()
|
||||||
|
async def _aread(*a, **k):
|
||||||
|
return {}
|
||||||
|
dev.lf.read = _aread
|
||||||
|
try:
|
||||||
|
identify_lf(dev)
|
||||||
|
assert False, "expected TypeError for an async device"
|
||||||
|
except TypeError as e:
|
||||||
|
assert "sync" in str(e).lower()
|
||||||
|
|
||||||
|
|
||||||
class TestCaptureValidation:
|
class TestCaptureValidation:
|
||||||
def test_looks_like_samples_rejects_memory(self):
|
def test_looks_like_samples_rejects_memory(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user