94 lines
3.2 KiB
Python
94 lines
3.2 KiB
Python
"""ISO 15693 commands: hf.15.*"""
|
|
import struct
|
|
from .protocol import Cmd
|
|
from .transport import PM3Transport, PM3Error
|
|
|
|
# ISO15 PM3 flags
|
|
ISO15_CONNECT = 0x01
|
|
ISO15_NO_DISCONNECT = 0x02
|
|
ISO15_RAW = 0x04
|
|
ISO15_APPEND_CRC = 0x08
|
|
ISO15_READ_RESPONSE = 0x10
|
|
|
|
|
|
class HF15Commands:
|
|
"""ISO 15693 commands."""
|
|
|
|
def __init__(self, transport: PM3Transport):
|
|
self._t = transport
|
|
|
|
async def scan(self) -> dict:
|
|
"""Scan for ISO15693 tags using inventory command."""
|
|
# ISO15693 inventory: flags=0x06 (high data rate + inventory), cmd=0x01
|
|
iso_cmd = bytes([0x06, 0x01, 0x00]) # flags, inventory cmd, mask_len=0
|
|
flags = ISO15_CONNECT | ISO15_READ_RESPONSE | ISO15_APPEND_CRC
|
|
payload = struct.pack("<BH", flags, len(iso_cmd)) + iso_cmd
|
|
resp = await self._t.send_ng(Cmd.HF_ISO15693_COMMAND, payload, timeout=5.0)
|
|
|
|
if resp.status != 0 or len(resp.data) < 10:
|
|
return {"found": False, "uid": None}
|
|
|
|
resp_flags = resp.data[0]
|
|
dsfid = resp.data[1]
|
|
uid = resp.data[2:10]
|
|
|
|
return {
|
|
"found": True,
|
|
"uid": uid[::-1].hex(), # ISO15693 UIDs are reversed
|
|
"dsfid": dsfid,
|
|
"response_flags": resp_flags,
|
|
}
|
|
|
|
async def rdbl(self, block: int, uid: str | None = None) -> dict:
|
|
"""Read a single block."""
|
|
if uid:
|
|
uid_bytes = bytes.fromhex(uid)[::-1] # reverse for wire
|
|
iso_cmd = bytes([0x22, 0x20]) + uid_bytes + bytes([block])
|
|
else:
|
|
iso_cmd = bytes([0x02, 0x20, block]) # addressed without UID
|
|
|
|
flags = ISO15_CONNECT | ISO15_READ_RESPONSE | ISO15_APPEND_CRC
|
|
payload = struct.pack("<BH", flags, len(iso_cmd)) + iso_cmd
|
|
resp = await self._t.send_ng(Cmd.HF_ISO15693_COMMAND, payload, timeout=5.0)
|
|
|
|
if resp.status != 0 or len(resp.data) < 2:
|
|
return {"success": False, "error": resp.status}
|
|
|
|
resp_flags = resp.data[0]
|
|
if resp_flags != 0:
|
|
return {"success": False, "error_flag": resp_flags}
|
|
|
|
lock_status = resp.data[1]
|
|
block_data = resp.data[2:]
|
|
|
|
return {
|
|
"success": True,
|
|
"block": block,
|
|
"locked": bool(lock_status),
|
|
"data": block_data.hex(),
|
|
"raw": block_data,
|
|
}
|
|
|
|
async def wrbl(self, block: int, data: bytes | str, uid: str | None = None) -> dict:
|
|
"""Write a single block."""
|
|
if isinstance(data, str):
|
|
data = bytes.fromhex(data)
|
|
|
|
if uid:
|
|
uid_bytes = bytes.fromhex(uid)[::-1]
|
|
iso_cmd = bytes([0x22, 0x21]) + uid_bytes + bytes([block]) + data
|
|
else:
|
|
iso_cmd = bytes([0x02, 0x21, block]) + data
|
|
|
|
flags = ISO15_CONNECT | ISO15_READ_RESPONSE | ISO15_APPEND_CRC
|
|
payload = struct.pack("<BH", flags, len(iso_cmd)) + iso_cmd
|
|
resp = await self._t.send_ng(Cmd.HF_ISO15693_COMMAND, payload, timeout=5.0)
|
|
|
|
success = resp.status == 0 and len(resp.data) > 0 and resp.data[0] == 0
|
|
return {"success": success, "block": block}
|
|
|
|
async def sniff(self) -> dict:
|
|
"""Sniff ISO15693 traffic."""
|
|
resp = await self._t.send_ng(Cmd.HF_ISO15693_SNIFF, timeout=30.0)
|
|
return {"status": resp.status}
|