diff --git a/pm3py/core/hf_iso15.py b/pm3py/core/hf_iso15.py index 9a51ce0..3beea31 100644 --- a/pm3py/core/hf_iso15.py +++ b/pm3py/core/hf_iso15.py @@ -204,6 +204,21 @@ class HF15Commands: success = resp.status == 0 and len(resp.data) > 0 and resp.data[0] == 0 return {"success": success, "block": block} + async def raw(self, iso_cmd: bytes | str, long_wait: bool = False, + timeout: float = 5.0) -> dict: + """Send a raw ISO15693 request frame and return the tag's response. + + ``iso_cmd`` is the request **without** the CRC (flags | command | [mfg] | [UID] | params); + the ISO15693 CRC is appended host-side. ``long_wait`` uses the write-length tag-ack timeout + (needed for WRITE/LOCK-class commands). The returned ``raw`` is the firmware-relayed tag + response (response-flags + data + its CRC).""" + if isinstance(iso_cmd, str): + iso_cmd = bytes.fromhex(iso_cmd.replace(" ", "")) + payload = _iso15_payload(iso_cmd, flags=_WRITE_FLAGS if long_wait else _READ_FLAGS) + resp = await self._t.send_ng(Cmd.HF_ISO15693_COMMAND, payload, timeout=timeout) + data = bytes(resp.data) + return {"status": resp.status, "raw": data, "data": data.hex()} + async def sniff(self, timeout: float = 60.0) -> dict: """Start ISO15693 sniff. Blocks until button press or timeout. diff --git a/tests/test_hf_15.py b/tests/test_hf_15.py index 07b7670..fdf804f 100644 --- a/tests/test_hf_15.py +++ b/tests/test_hf_15.py @@ -20,6 +20,24 @@ def test_15_scan(): assert "uid" in result +def test_15_raw(): + from pm3py.core.protocol import crc15_bytes + t = AsyncMock() + iso15 = HF15Commands(t) + resp_data = bytes([0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x96, 0x3A]) # flags + data + crc + t.send_ng.return_value = PM3Response(cmd=Cmd.HF_ISO15693_COMMAND, status=0, + reason=0, ng=True, data=resp_data) + # send a raw request frame (no CRC); it should be CRC'd + wrapped, and the tag reply returned + r = asyncio.get_event_loop().run_until_complete(iso15.raw(b"\x02\x20\x00")) + assert r["status"] == 0 and r["raw"] == resp_data and r["data"] == resp_data.hex() + payload = t.send_ng.call_args.args[1] # [flags(1)][len(2)][iso_cmd][crc15(2)] + assert payload[3:6] == b"\x02\x20\x00" # the frame, verbatim + assert payload[6:8] == crc15_bytes(b"\x02\x20\x00") # ISO15693 CRC appended host-side + # a hex string is accepted too + asyncio.get_event_loop().run_until_complete(iso15.raw("02 B2 04")) + assert t.send_ng.call_args.args[1][3:6] == b"\x02\xB2\x04" + + # ---- Tracelog parsing ---- def _make_tracelog_entry(timestamp, duration, is_response, data):