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:
michael
2026-07-04 23:03:06 -07:00
parent f2b23f768b
commit 01357e4e78

View File

@@ -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