fix(iso15): rdbl needs OPTION bit + CRC strip; wrbl needs LONG_WAIT
rdbl omitted the ISO15693 OPTION flag (0x40) so the tag returned [flags][data] without the lock byte the parser assumes (shifting data one byte) and included the 2-byte CRC in block_data. wrbl reused the read flags (no LONG_WAIT) so the firmware used the short RX timeout and the ~20ms write ack arrived too late -> false failure. rdbl: set OPTION (0x62/0x42), strip CRC (data[2:-2]). wrbl: CONNECT|READ_RESPONSE|LONG_WAIT (0x61). Mirrors C client CmdHF15Readblock / hf_15_write_blk. Pre-existing, independent of the rebase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,17 +21,22 @@ ISO15_READ_RESPONSE = 0x20
|
||||
ISO15_LONG_WAIT = 0x40
|
||||
|
||||
|
||||
def _iso15_payload(iso_cmd: bytes) -> bytes:
|
||||
# Default transport flags for read/inventory (C client CmdHF15Info/getUID).
|
||||
_READ_FLAGS = ISO15_CONNECT | ISO15_HIGH_SPEED | ISO15_READ_RESPONSE # 0x31
|
||||
# Write needs the long (~22ms) tag-ack timeout and does not set HIGH_SPEED
|
||||
# (mirrors the C client's hf_15_write_blk).
|
||||
_WRITE_FLAGS = ISO15_CONNECT | ISO15_READ_RESPONSE | ISO15_LONG_WAIT # 0x61
|
||||
|
||||
|
||||
def _iso15_payload(iso_cmd: bytes, flags: int = _READ_FLAGS) -> 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.
|
||||
flags. READ_RESPONSE (0x20) makes the firmware actually listen for the tag's answer;
|
||||
without it 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("<BH", flags, len(raw)) + raw
|
||||
|
||||
|
||||
@@ -79,16 +84,18 @@ class HF15Commands:
|
||||
|
||||
async def rdbl(self, block: int, uid: str | None = None) -> dict:
|
||||
"""Read a single block."""
|
||||
# OPTION (0x40) makes the tag return the block-security (lock) byte this parser
|
||||
# expects: addressed 0x22|0x40=0x62, unaddressed 0x02|0x40=0x42.
|
||||
if uid:
|
||||
uid_bytes = bytes.fromhex(uid)[::-1] # reverse for wire
|
||||
iso_cmd = bytes([0x22, 0x20]) + uid_bytes + bytes([block])
|
||||
iso_cmd = bytes([0x62, 0x20]) + uid_bytes + bytes([block])
|
||||
else:
|
||||
iso_cmd = bytes([0x02, 0x20, block]) # addressed without UID
|
||||
iso_cmd = bytes([0x42, 0x20, block]) # unaddressed
|
||||
|
||||
payload = _iso15_payload(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:
|
||||
if resp.status != 0 or len(resp.data) < 4:
|
||||
return {"success": False, "error": resp.status}
|
||||
|
||||
resp_flags = resp.data[0]
|
||||
@@ -96,7 +103,7 @@ class HF15Commands:
|
||||
return {"success": False, "error_flag": resp_flags}
|
||||
|
||||
lock_status = resp.data[1]
|
||||
block_data = resp.data[2:]
|
||||
block_data = resp.data[2:-2] # strip the 2-byte ISO15693 CRC the firmware relays
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
@@ -117,7 +124,7 @@ class HF15Commands:
|
||||
else:
|
||||
iso_cmd = bytes([0x02, 0x21, block]) + data
|
||||
|
||||
payload = _iso15_payload(iso_cmd)
|
||||
payload = _iso15_payload(iso_cmd, flags=_WRITE_FLAGS) # LONG_WAIT for the ~22ms write ack
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user