feat(iso15): add inventory() with 1/16-slot parsing + AFI/DSFID filtering

inventory() parses both response formats: slots=1 -> single tag dict (or None); slots=16 -> list of tags from the iso15_inventory_response_t anti-collision struct, each CRC15-validated. Optional request-level AFI filter and client-side DSFID filter. One 16-slot round drops colliding slots (full tree-walk resolution is a follow-up); scan() stays the single-tag convenience.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-05 00:34:15 -07:00
parent 739e28e113
commit 137ec1e721
2 changed files with 115 additions and 1 deletions

View File

@@ -286,3 +286,43 @@ class TestSniffLineAnnotationOverflow:
"timestamp": 0, "duration": 0, "parity": b""}
line = format_sniff_line(entry, width=120, is_tty=False)
assert "CC" in line
def _inv_record(uid_display_hex, dsfid=0, flags=0):
from pm3py.core.protocol import crc15_bytes
body = bytes([flags, dsfid]) + bytes.fromhex(uid_display_hex)[::-1]
return body + crc15_bytes(body)
def test_15_inventory_single_slot():
t = AsyncMock()
iso = HF15Commands(t)
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO15693_COMMAND, status=0,
reason=0, ng=True, data=_inv_record("e004a2110b37c433"))
tag = asyncio.get_event_loop().run_until_complete(iso.inventory(slots=1))
assert tag == {"uid": "e004a2110b37c433", "dsfid": 0, "flags": 0}
# SLOT1 flag set in the request (raw[0] bit 0x20)
payload = t.send_ng.call_args.args[1]
assert payload[3] & 0x20 # raw[0] after 3-byte flags+len header
def test_15_inventory_16_slot_list_and_dsfid_filter():
t = AsyncMock()
iso = HF15Commands(t)
r0 = _inv_record("e004a2110b37c433")
r3 = _inv_record("e004a2110b3746e0", dsfid=5)
meta = bytearray(16 * 2)
meta[0:2] = bytes([1, len(r0)]) # slot 0 valid
meta[6:8] = bytes([1, len(r3)]) # slot 3 valid
meta[10:12] = bytes([2, 0]) # slot 5 collision
data = bytes([16]) + bytes(meta) + r0 + r3
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO15693_COMMAND, status=0,
reason=0, ng=True, data=data)
tags = asyncio.get_event_loop().run_until_complete(iso.inventory(slots=16))
assert [x["uid"] for x in tags] == ["e004a2110b37c433", "e004a2110b3746e0"]
assert tags[0]["slot"] == 0 and tags[1]["slot"] == 3
# client-side DSFID filter
t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO15693_COMMAND, status=0,
reason=0, ng=True, data=data)
only5 = asyncio.get_event_loop().run_until_complete(iso.inventory(slots=16, dsfid=5))
assert [x["uid"] for x in only5] == ["e004a2110b3746e0"]