fix(rawcli): identify false-positives on 15693/LF; retry 14a
With only a MIFARE Classic present, identify randomly reported ISO15693 (bogus UID) or "LF tag" — because a flaky-link 14a miss fell through to lenient checks: - 15693 accepted any `uid` even without `found`; now requires found AND a real E0-prefixed UID (a 14a miss leaves non-E0 garbage). - LF reported a tag whenever lf.search() returned its (always-truthy) sample-capture result; it can't confirm a tag without demod, so drop it from identify (reliable LF detection is a follow-up). - Retry the 14a scan (up to 3x) so a transient miss doesn't fall through. 3 tests (E0-gated 15693, bogus-UID + false-LF rejection). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -144,7 +144,13 @@ def identify(session, emit=None) -> dict:
|
|||||||
if dev is None:
|
if dev is None:
|
||||||
return {"found": False, "error": "no device connected"}
|
return {"found": False, "error": "no device connected"}
|
||||||
|
|
||||||
|
# ISO 14443-A — retry: the scan can miss before it selects on a flaky link, and a miss must
|
||||||
|
# not fall through to a bogus 15693/LF hit.
|
||||||
|
scan = {"found": False}
|
||||||
|
for _ in range(3):
|
||||||
scan = _try(lambda: dev.hf.iso14a.scan(), {"found": False})
|
scan = _try(lambda: dev.hf.iso14a.scan(), {"found": False})
|
||||||
|
if scan.get("found"):
|
||||||
|
break
|
||||||
if scan.get("found"):
|
if scan.get("found"):
|
||||||
session.field, session.protocol = "hf", "hf14a"
|
session.field, session.protocol = "hf", "hf14a"
|
||||||
# always emit ANSI; the app's printer renders it (and strips it off a non-terminal)
|
# always emit ANSI; the app's printer renders it (and strips it off a non-terminal)
|
||||||
@@ -159,21 +165,17 @@ def identify(session, emit=None) -> dict:
|
|||||||
"transponder": session.transponder, "uid": scan.get("uid"),
|
"transponder": session.transponder, "uid": scan.get("uid"),
|
||||||
"version": version.hex() if version else None}
|
"version": version.hex() if version else None}
|
||||||
|
|
||||||
|
# ISO 15693 — require found AND a real E0-prefixed UID, else a 14a miss reads as a bogus tag.
|
||||||
scan15 = _try(lambda: dev.hf.iso15.scan(), {"found": False})
|
scan15 = _try(lambda: dev.hf.iso15.scan(), {"found": False})
|
||||||
if scan15.get("found") or scan15.get("uid"):
|
uid15 = scan15.get("uid")
|
||||||
|
if scan15.get("found") and isinstance(uid15, str) and uid15.lower().startswith("e0"):
|
||||||
session.field, session.protocol = "hf", "hf15"
|
session.field, session.protocol = "hf", "hf15"
|
||||||
session.transponder = "ISO15693"
|
session.transponder = "ISO15693"
|
||||||
return {"found": True, "field": "hf", "protocol": "15693",
|
return {"found": True, "field": "hf", "protocol": "15693",
|
||||||
"transponder": "ISO15693", "uid": scan15.get("uid")}
|
"transponder": "ISO15693", "uid": uid15}
|
||||||
|
|
||||||
lf = _try(lambda: dev.lf.search(), None)
|
|
||||||
if lf:
|
|
||||||
session.field, session.protocol = "lf", "lf"
|
|
||||||
name = getattr(lf, "tag_type", None) or getattr(lf, "type", None) or "LF tag"
|
|
||||||
session.transponder = str(name)
|
|
||||||
return {"found": True, "field": "lf", "protocol": "lf",
|
|
||||||
"transponder": session.transponder, "raw": lf}
|
|
||||||
|
|
||||||
|
# LF: lf.search() only captures samples — it can't confirm a tag without demod, so it would
|
||||||
|
# always false-positive. Reliable LF identify (T55xx probe / EM4100 demod) is a follow-up.
|
||||||
return {"found": False}
|
return {"found": False}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -209,12 +209,25 @@ class TestIdentify:
|
|||||||
assert r["version"] == NTAG216_VERSION.hex()
|
assert r["version"] == NTAG216_VERSION.hex()
|
||||||
s.device.hf.iso14a.raw.assert_called_with(b"\x60", flags=0x20) # real GET_VERSION+CRC
|
s.device.hf.iso14a.raw.assert_called_with(b"\x60", flags=0x20) # real GET_VERSION+CRC
|
||||||
|
|
||||||
def test_15693_fallback(self):
|
def test_15693_when_e0_uid(self):
|
||||||
s = RawSession(device=_mock_device(scan15={"found": True, "uid": b"\xE0\x04"}))
|
s = RawSession(device=_mock_device(scan15={"found": True, "uid": "e004010203040506"}))
|
||||||
r = identify(s)
|
r = identify(s)
|
||||||
assert r["found"] and s.field == "hf" and s.protocol == "hf15"
|
assert r["found"] and s.field == "hf" and s.protocol == "hf15"
|
||||||
assert s.transponder == "ISO15693"
|
assert s.transponder == "ISO15693"
|
||||||
|
|
||||||
|
def test_15693_rejects_bogus_uid(self):
|
||||||
|
# a flaky 14a miss can leave a non-E0 "uid" — must NOT be reported as a 15693 tag
|
||||||
|
s = RawSession(device=_mock_device(scan15={"found": True, "uid": "0000000000003d42"}))
|
||||||
|
r = identify(s)
|
||||||
|
assert r["found"] is False and s.transponder is None
|
||||||
|
|
||||||
|
def test_no_false_lf(self):
|
||||||
|
# nothing on HF and lf.search() truthy must report not-found, not a bogus "LF tag"
|
||||||
|
dev = _mock_device()
|
||||||
|
dev.lf.search.return_value = object() # always-truthy search result
|
||||||
|
s = RawSession(device=dev)
|
||||||
|
assert identify(s)["found"] is False and s.transponder is None
|
||||||
|
|
||||||
def test_sak_names_from_an10833(self):
|
def test_sak_names_from_an10833(self):
|
||||||
assert name_14a({"sak": 0x18}) == "MIFARE Classic 4K"
|
assert name_14a({"sak": 0x18}) == "MIFARE Classic 4K"
|
||||||
assert name_14a({"sak": 0x20}).startswith("ISO14443-4")
|
assert name_14a({"sak": 0x20}).startswith("ISO14443-4")
|
||||||
|
|||||||
Reference in New Issue
Block a user