Files
pm3py/pm3py/trace/decode_iso14b.py
2026-07-05 09:53:52 -07:00

99 lines
3.1 KiB
Python

"""ISO 14443 Type B (SRIX / ST25TB lineage) request/response decoders + CRC_B."""
from __future__ import annotations
# ST25TB / SRIX command opcodes
_14B_CMDS = {
0x08: "READ_BLOCK",
0x09: "WRITE_BLOCK",
0x0B: "GET_UID",
0x0C: "RESET_TO_INVENTORY",
0x0E: "SELECT",
0x0F: "COMPLETION",
}
def decode_14443b(direction: int, payload: bytes) -> str | None:
"""Decode an ISO 14443 Type B (ST25TB/SRIX) frame."""
if not payload:
return None
if direction == 0:
return _decode_request(payload)
return _decode_response(payload)
def _decode_request(payload: bytes) -> str | None:
b0 = payload[0]
# SRIX / ST25TB anticollision (opcode 0x06)
if b0 == 0x06 and len(payload) >= 2:
if payload[1] == 0x00:
return "INITIATE"
if payload[1] == 0x04:
return "PCALL16"
return f"ANTICOL 0x{payload[1]:02X}"
# SRIX slot marker: low nibble = 6, high nibble = slot 1..15
if (b0 & 0x0F) == 0x06 and (b0 >> 4) != 0:
return f"SLOT_MARKER {b0 >> 4}"
# Standard ISO 14443-3 Type B (REQB/WUPB/ATTRIB/HLTB)
if b0 == 0x05 and len(payload) >= 3:
return "WUPB" if (payload[2] & 0x08) else "REQB"
if (b0 & 0x0F) == 0x05 and (b0 >> 4) != 0:
return f"SLOT-MARKER {b0 >> 4}"
if b0 == 0x1D:
return "ATTRIB"
if b0 == 0x50 and len(payload) >= 5:
return "HLTB"
# SRIX / ST25TB memory + select opcodes (must precede the I-block fallback,
# since they share the PCB=00 bit pattern).
name = _14B_CMDS.get(b0)
if name is not None:
if b0 == 0x08 and len(payload) >= 2:
return f"READ_BLOCK #{payload[1]}"
if b0 == 0x09 and len(payload) >= 2:
return f"WRITE_BLOCK #{payload[1]}"
if b0 == 0x0E and len(payload) >= 2:
return f"SELECT chip=0x{payload[1]:02X}"
return name
# ISO-DEP I-block (PCB) — fallback for standard Type B Layer 4.
if b0 & 0xC0 == 0x00 and len(payload) > 1:
return f"I-BLOCK({b0 & 0x01})"
return None
def _decode_response(payload: bytes) -> str | None:
# ATQB: 50 | PUPI(4) | AppData(4) | ProtInfo(3) = 12 bytes
if len(payload) == 12 and payload[0] == 0x50:
return f"ATQB PUPI={payload[1:5].hex().upper()}"
if len(payload) == 1:
return f"CHIP_ID 0x{payload[0]:02X}"
if len(payload) == 4:
return f"BLOCK {payload.hex().upper()}"
if len(payload) == 8:
return f"UID {bytes(reversed(payload)).hex().upper()}"
# ISO-DEP I-block response
if payload[0] & 0xC0 == 0x00 and len(payload) > 1:
return f"I-BLOCK({payload[0] & 0x01})"
return None
def crc14b(data: bytes) -> bytes:
"""ISO/IEC 14443-3 Type B CRC (CRC_B), transmitted LSByte first.
Polynomial x^16 + x^12 + x^5 + 1, init 0xFFFF, result one's-complemented,
reflected. Matches the reference routine in the ST25TB datasheet appendix.
"""
crc = 0xFFFF
for b in data:
crc ^= b
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0x8408
else:
crc >>= 1
crc = (~crc) & 0xFFFF
return bytes([crc & 0xFF, (crc >> 8) & 0xFF])