feat(rawcli): LF T5577 identify + clear stale result per identify

- identify clears the previous field/protocol/transponder up front, so
  consecutive identifies (esp. when swapping tags) never show stale data.
- LF identify: probe a T5577 by reading config block 0 (lf.t55.readbl(0)).
  A valid config confirms the T5577 and, decoded via T5577Config, names
  what it's emulating — "T5577 (EM4100 / EM4102)", "T5577 (HID Prox)", etc.
  (exact-word map, else modulation family). Tightly gated (status ok,
  non-trivial config, known modulation, sane max_block) so no-tag reads
  don't false-positive; lf.search() couldn't do this without demod.
- format_summary shows the config word for LF (no UID).

4 tests (T5577 emulation report, no-T5577, stale-clear).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-14 13:31:35 -07:00
parent 0775df4d54
commit 8726681e06
2 changed files with 98 additions and 5 deletions

View File

@@ -182,6 +182,7 @@ def _mock_device(scan14=None, scan15=None, version=None):
dev.hf.iso14a.scan.return_value = scan14 or {"found": False}
dev.hf.iso15.scan.return_value = scan15 or {"found": False}
dev.lf.search.return_value = None
dev.lf.t55.readbl.return_value = {"status": 1, "raw": b""} # no T55xx by default
dev.hf.iso14a.raw.return_value = {"raw": (version + b"\x99\x88") if version else None}
return dev
@@ -228,6 +229,32 @@ class TestIdentify:
s = RawSession(device=dev)
assert identify(s)["found"] is False and s.transponder is None
def test_lf_t5577_reports_emulation(self):
# T55xx block 0 = EM4100 config -> "T5577 (EM4100 / EM4102)"
dev = _mock_device()
dev.lf.t55.readbl.return_value = {"status": 0, "raw": bytes.fromhex("00148040")}
s = RawSession(device=dev)
r = identify(s)
assert r["found"] and s.field == "lf" and s.protocol == "lf"
assert s.transponder == "T5577 (EM4100 / EM4102)"
assert r["config"] == "00148040"
def test_lf_no_t5577_not_found(self):
dev = _mock_device()
dev.lf.t55.readbl.return_value = {"status": 1, "raw": b""} # no T55xx response
assert identify(RawSession(device=dev))["found"] is False
def test_identify_clears_stale_result(self):
# a second identify that finds nothing must clear the previous tag from the session
dev = _mock_device(scan14={"found": True, "sak": 0x08, "atqa": "0400", "uid": "b978423d"})
s = RawSession(device=dev)
identify(s)
assert s.transponder == "MIFARE Classic 1K"
dev.hf.iso14a.scan.return_value = {"found": False} # tag removed
dev.lf.t55.readbl.return_value = {"status": 1, "raw": b""}
identify(s)
assert s.transponder is None and s.field is None # no stale data
def test_sak_names_from_an10833(self):
assert name_14a({"sak": 0x18}) == "MIFARE Classic 4K"
assert name_14a({"sak": 0x20}).startswith("ISO14443-4")