diff --git a/pm3py/cli/rawcli/identify.py b/pm3py/cli/rawcli/identify.py index 379f5a4..5bfe1bb 100644 --- a/pm3py/cli/rawcli/identify.py +++ b/pm3py/cli/rawcli/identify.py @@ -144,7 +144,13 @@ def identify(session, emit=None) -> dict: if dev is None: return {"found": False, "error": "no device connected"} - scan = _try(lambda: dev.hf.iso14a.scan(), {"found": False}) + # 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}) + if scan.get("found"): + break if scan.get("found"): session.field, session.protocol = "hf", "hf14a" # 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"), "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}) - 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.transponder = "ISO15693" return {"found": True, "field": "hf", "protocol": "15693", - "transponder": "ISO15693", "uid": scan15.get("uid")} - - 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} + "transponder": "ISO15693", "uid": uid15} + # 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} diff --git a/tests/test_rawcli.py b/tests/test_rawcli.py index 9b07997..885834a 100644 --- a/tests/test_rawcli.py +++ b/tests/test_rawcli.py @@ -209,12 +209,25 @@ class TestIdentify: assert r["version"] == NTAG216_VERSION.hex() s.device.hf.iso14a.raw.assert_called_with(b"\x60", flags=0x20) # real GET_VERSION+CRC - def test_15693_fallback(self): - s = RawSession(device=_mock_device(scan15={"found": True, "uid": b"\xE0\x04"})) + def test_15693_when_e0_uid(self): + s = RawSession(device=_mock_device(scan15={"found": True, "uid": "e004010203040506"})) r = identify(s) assert r["found"] and s.field == "hf" and s.protocol == "hf15" 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): assert name_14a({"sak": 0x18}) == "MIFARE Classic 4K" assert name_14a({"sak": 0x20}).startswith("ISO14443-4")