fix(rawcli): writes take (location, data); multi-frame + CRC
- A write needs both a location and data. NTAG WRITE (0xA2) already did; the two-phase 0xA0 writes (Type 2 COMPAT_WRITE, MIFARE Classic WRITE) only took the page/block. They now take (page/block, data) and build a frame *list* — the command frame plus the 16-byte data frame. - Catalog build() may now return a list of frames; _handle_call sends each in sequence and traces every exchange (so both phases of a write show). - 14a catalog commands now append the ISO14443-A CRC (ISO14A_APPEND_CRC), which real tags require — manual raw hex is still sent verbatim. 3 new/updated tests (two-phase build + send, CRC flag on reads). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -40,13 +40,17 @@ def _connect(port):
|
||||
return None
|
||||
|
||||
|
||||
def _raw_exchange(device, payload: bytes, protocol: str = "hf14a") -> bytes | None:
|
||||
"""Issue one raw exchange over the identified protocol, returning the response bytes."""
|
||||
ISO14A_APPEND_CRC = 0x20
|
||||
|
||||
|
||||
def _raw_exchange(device, payload: bytes, protocol: str = "hf14a", crc: bool = False) -> bytes | None:
|
||||
"""Issue one raw exchange over the identified protocol, returning the response bytes. ``crc``
|
||||
appends the ISO14443-A CRC (catalog commands need it; manual raw hex is sent verbatim)."""
|
||||
if device is None:
|
||||
return None
|
||||
try:
|
||||
if protocol == "hf14a":
|
||||
resp = device.hf.iso14a.raw(payload)
|
||||
resp = device.hf.iso14a.raw(payload, flags=ISO14A_APPEND_CRC if crc else 0)
|
||||
elif protocol == "hf15":
|
||||
raw = getattr(device.hf.iso15, "raw", None)
|
||||
resp = raw(payload) if raw else None
|
||||
@@ -111,12 +115,15 @@ def _handle_call(state: RawSession, cmd, out=print) -> None:
|
||||
out(f"unknown command {cmd.name!r} for {catalog.name}; try 'help'")
|
||||
return
|
||||
try:
|
||||
payload = tc.build(*cmd.args)
|
||||
built = tc.build(*cmd.args)
|
||||
except (TypeError, ValueError) as exc:
|
||||
out(f"usage: {tc.usage()} ({exc})")
|
||||
return
|
||||
response = _raw_exchange(state.device, payload, protocol=catalog.protocol)
|
||||
out(render_exchange(payload, response, protocol=catalog.protocol, is_tty=True))
|
||||
frames = built if isinstance(built, list) else [built] # multi-frame (e.g. two-phase write)
|
||||
crc = catalog.protocol == "hf14a" # 14a commands need CRC to be accepted
|
||||
for frame in frames:
|
||||
response = _raw_exchange(state.device, frame, protocol=catalog.protocol, crc=crc)
|
||||
out(render_exchange(frame, response, protocol=catalog.protocol, is_tty=True))
|
||||
|
||||
|
||||
def _handle_tlv(state: RawSession, args, out=print) -> None:
|
||||
|
||||
@@ -68,8 +68,9 @@ TYPE2 = _catalog(
|
||||
_c("WRITE", ["page", "data"],
|
||||
lambda page, data: bytes([0xA2, _int(page)]) + _hex(data).ljust(4, b"\x00")[:4],
|
||||
"write 4 bytes <data> to <page> (0xA2)"),
|
||||
_c("COMPAT_WRITE", ["page"], lambda page: bytes([0xA0, _int(page)]),
|
||||
"compatibility write, two-phase (0xA0)"),
|
||||
_c("COMPAT_WRITE", ["page", "data"],
|
||||
lambda page, data: [bytes([0xA0, _int(page)]), _hex(data).ljust(16, b"\x00")[:16]],
|
||||
"compatibility write: 16-byte <data> to <page>, two-phase (0xA0)"),
|
||||
_c("HALT", [], lambda: bytes([0x50, 0x00]), "halt the tag (0x50 00)"),
|
||||
)
|
||||
|
||||
@@ -78,8 +79,9 @@ MFC = _catalog(
|
||||
"MIFARE Classic", "hf14a",
|
||||
_c("READ", ["block"], lambda block: bytes([0x30, _int(block)]),
|
||||
"read a 16-byte block (needs prior auth) (0x30)"),
|
||||
_c("WRITE", ["block"], lambda block: bytes([0xA0, _int(block)]),
|
||||
"start a block write, two-phase (0xA0)"),
|
||||
_c("WRITE", ["block", "data"],
|
||||
lambda block, data: [bytes([0xA0, _int(block)]), _hex(data).ljust(16, b"\x00")[:16]],
|
||||
"write a 16-byte block, two-phase (0xA0) — needs prior auth"),
|
||||
_c("HALT", [], lambda: bytes([0x50, 0x00]), "halt the card (0x50 00)"),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user