diff --git a/pm3py/core/hf_iso15.py b/pm3py/core/hf_iso15.py index 40e3c2d..090b4ed 100644 --- a/pm3py/core/hf_iso15.py +++ b/pm3py/core/hf_iso15.py @@ -1,6 +1,6 @@ """ISO 15693 commands: hf.iso15.*""" import struct -from .protocol import Cmd +from .protocol import Cmd, crc15_bytes from .transport import PM3Transport, PM3Error # Re-export trace symbols for backward compat with existing test imports @@ -11,12 +11,28 @@ from ..trace import ( format_sniff_line, ) -# ISO15 PM3 flags +# ISO15 PM3 transport flags — values MUST match firmware include/iso15.h ISO15_CONNECT = 0x01 ISO15_NO_DISCONNECT = 0x02 ISO15_RAW = 0x04 -ISO15_APPEND_CRC = 0x08 -ISO15_READ_RESPONSE = 0x10 +ISO15_APPEND_CRC = 0x08 # legacy: ARM firmware ignores it — CRC is added host-side +ISO15_HIGH_SPEED = 0x10 +ISO15_READ_RESPONSE = 0x20 +ISO15_LONG_WAIT = 0x40 + + +def _iso15_payload(iso_cmd: bytes) -> bytes: + """Build a HF_ISO15693_COMMAND payload from a raw ISO15693 command. + + Appends the ISO15693 CRC host-side (the ARM firmware ignores ISO15_APPEND_CRC — + the client must add it, like the C client's AddCrc15) and prefixes the PM3 transport + flags. HIGH_SPEED + READ_RESPONSE make the firmware receive at high data rate and + actually listen for the tag's answer — without READ_RESPONSE (0x20) the firmware + passes a NULL rx buffer and replies empty. + """ + raw = iso_cmd + crc15_bytes(iso_cmd) + flags = ISO15_CONNECT | ISO15_HIGH_SPEED | ISO15_READ_RESPONSE + return struct.pack(" dict: + """Diagnostic: send a raw ISO15693 inventory with a caller-chosen request-flags + byte and return the raw device response (no parsing). Use to isolate 1-slot vs + 16-slot behavior: inv_flags=0x26 → 1 slot (SLOT1 set), 0x06 → 16 slots. + """ + iso_cmd = bytes([inv_flags, 0x01, 0x00]) # inv flags, INVENTORY cmd, mask_len=0 + payload = _iso15_payload(iso_cmd) + resp = await self._t.send_ng(Cmd.HF_ISO15693_COMMAND, payload, timeout=5.0) + return {"inv_flags": hex(inv_flags), "status": resp.status, + "len": len(resp.data), "data": resp.data.hex()} + async def rdbl(self, block: int, uid: str | None = None) -> dict: """Read a single block.""" if uid: @@ -59,8 +85,7 @@ class HF15Commands: else: iso_cmd = bytes([0x02, 0x20, block]) # addressed without UID - flags = ISO15_CONNECT | ISO15_READ_RESPONSE | ISO15_APPEND_CRC - payload = struct.pack(" 0 and resp.data[0] == 0 diff --git a/pm3py/core/protocol.py b/pm3py/core/protocol.py index 79ba814..e866b05 100644 --- a/pm3py/core/protocol.py +++ b/pm3py/core/protocol.py @@ -68,6 +68,23 @@ def crc16_a(data: bytes) -> int: return crc +def crc15(data: bytes) -> int: + """ISO 15693 / NFC Type 5 CRC (CRC-16/X-25): poly=0x8408 (reflected), init=0xFFFF, + refin/refout=true, xorout=0xFFFF. Check value crc15(b"123456789") == 0x906E.""" + crc = 0xFFFF + for b in data: + crc ^= b + for _ in range(8): + crc = (crc >> 1) ^ 0x8408 if (crc & 1) else (crc >> 1) + return (~crc) & 0xFFFF + + +def crc15_bytes(data: bytes) -> bytes: + """ISO 15693 CRC as appended on the wire (LSB byte first).""" + c = crc15(data) + return bytes([c & 0xFF, (c >> 8) & 0xFF]) + + # --- Error codes --- class PM3Status(IntEnum): SUCCESS = 0 diff --git a/tests/test_crc.py b/tests/test_crc.py index 4f1beb9..051ea83 100644 --- a/tests/test_crc.py +++ b/tests/test_crc.py @@ -1,4 +1,4 @@ -from pm3py.core.protocol import crc16_a +from pm3py.core.protocol import crc16_a, crc15, crc15_bytes def test_crc16_a_empty(): assert crc16_a(b"") == 0x6363 # init 0xC6C6 reflected = 0x6363 @@ -15,3 +15,20 @@ def test_crc16_a_ping_preamble(): crc = crc16_a(preamble) assert isinstance(crc, int) assert 0 <= crc <= 0xFFFF + + +def test_crc15_known_vector(): + # ISO15693 / NFC Type 5 CRC == CRC-16/X-25; standard check string -> 0x906E + assert crc15(b"123456789") == 0x906E + +def test_crc15_residue(): + # Appending crc15 (LSB-first) then re-running the raw X-25 loop over the full + # frame yields the X-25 residue 0xF0B8 — the property firmware CheckCrc15 uses. + data = bytes([0x26, 0x01, 0x00]) + frame = data + crc15_bytes(data) + crc = 0xFFFF + for byte in frame: + crc ^= byte + for _ in range(8): + crc = (crc >> 1) ^ 0x8408 if (crc & 1) else (crc >> 1) + assert crc == 0xF0B8