fix(rawcli): re-select per command + auto-CRC (as needed)
Live-hardware fixes: - Commands after identify returned nothing because the tag selection went stale (and a prior no-CRC frame could drop it). Re-select the tag before each command (silent — not traced) via _ensure_selected(). Note: this resets auth, so multi-step PWD_AUTH needs a later keep-session mode. - Auto-append the ISO14443-A CRC on 14a frames so a bare `60` works — except the frames that carry no CRC (REQA/WUPA short frames, 0x9x 0x20 anticollision), via _needs_crc(). Applies to catalog commands and manual raw alike. - Drop the now-redundant GET_VERSION padding trim (raw() trims properly). - help text: function-style commands are here, not "next phase". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,10 +24,10 @@ rawcli — raw command interface
|
|||||||
transponder show the identified transponder
|
transponder show the identified transponder
|
||||||
close drop the tag connection
|
close drop the tag connection
|
||||||
quit | exit | q leave rawcli
|
quit | exit | q leave rawcli
|
||||||
<hex ...> send raw bytes to the tag; the annotated exchange prints above
|
<hex/bin ...> send raw bytes to the tag (CRC auto-appended on 14a); trace prints above
|
||||||
|
|
||||||
Type hex as "30 04" or "3004"; Ctrl-/ toggles hex/binary entry (0x/0b). Function-style
|
Type hex as "30 04" or "3004"; Ctrl-/ toggles hex/binary entry (0x/0b). Function-style
|
||||||
transponder commands (READ(4), GET_DATA) land in the next phase."""
|
commands (READ(4), GET_VERSION()) come from the identified tag — listed below."""
|
||||||
|
|
||||||
|
|
||||||
def _connect(port):
|
def _connect(port):
|
||||||
@@ -43,6 +43,33 @@ def _connect(port):
|
|||||||
ISO14A_APPEND_CRC = 0x20
|
ISO14A_APPEND_CRC = 0x20
|
||||||
|
|
||||||
|
|
||||||
|
def _needs_crc(payload: bytes) -> bool:
|
||||||
|
"""Whether a 14a frame should get a CRC appended — everything except the frames ISO14443-A
|
||||||
|
sends without one: the 7-bit short frames REQA (0x26) / WUPA (0x52) and the ANTICOLLISION
|
||||||
|
frames (0x9x 0x20, which carry a BCC, not a CRC)."""
|
||||||
|
if not payload:
|
||||||
|
return False
|
||||||
|
b0 = payload[0]
|
||||||
|
if len(payload) == 1 and b0 in (0x26, 0x52):
|
||||||
|
return False
|
||||||
|
if len(payload) >= 2 and b0 in (0x93, 0x95, 0x97) and payload[1] == 0x20:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_selected(device, protocol: str) -> None:
|
||||||
|
"""Re-select the 14a tag before a command so a stale/dropped selection (or a prior bad frame)
|
||||||
|
can't leave the tag unresponsive. Silent — the re-select isn't traced. Note: this resets any
|
||||||
|
prior auth, so a PWD_AUTH must be issued together with the command it protects (a later
|
||||||
|
keep-session mode will address multi-step auth)."""
|
||||||
|
if device is None or protocol != "hf14a":
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
device.hf.iso14a.scan()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _raw_exchange(device, payload: bytes, protocol: str = "hf14a", crc: bool = False) -> bytes | None:
|
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``
|
"""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)."""
|
appends the ISO14443-A CRC (catalog commands need it; manual raw hex is sent verbatim)."""
|
||||||
@@ -100,7 +127,9 @@ def dispatch(state: RawSession, line: str, out=print) -> bool:
|
|||||||
|
|
||||||
if not cmd.payload:
|
if not cmd.payload:
|
||||||
return True
|
return True
|
||||||
response = _raw_exchange(state.device, cmd.payload, protocol=state.protocol)
|
_ensure_selected(state.device, state.protocol)
|
||||||
|
response = _raw_exchange(state.device, cmd.payload, protocol=state.protocol,
|
||||||
|
crc=state.protocol == "hf14a") # 14a frames need CRC; bare `60` works
|
||||||
out(render_exchange(cmd.payload, response, protocol=state.protocol, is_tty=True))
|
out(render_exchange(cmd.payload, response, protocol=state.protocol, is_tty=True))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -121,6 +150,7 @@ def _handle_call(state: RawSession, cmd, out=print) -> None:
|
|||||||
return
|
return
|
||||||
frames = built if isinstance(built, list) else [built] # multi-frame (e.g. two-phase write)
|
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
|
crc = catalog.protocol == "hf14a" # 14a commands need CRC to be accepted
|
||||||
|
_ensure_selected(state.device, catalog.protocol) # re-select once for the command
|
||||||
for frame in frames:
|
for frame in frames:
|
||||||
response = _raw_exchange(state.device, frame, protocol=catalog.protocol, crc=crc)
|
response = _raw_exchange(state.device, frame, protocol=catalog.protocol, crc=crc)
|
||||||
out(render_exchange(frame, response, protocol=catalog.protocol, is_tty=True))
|
out(render_exchange(frame, response, protocol=catalog.protocol, is_tty=True))
|
||||||
|
|||||||
@@ -186,8 +186,7 @@ def _probe_version(dev, fmt, emit) -> tuple[str | None, bytes | None]:
|
|||||||
if not raw:
|
if not raw:
|
||||||
return None, None
|
return None, None
|
||||||
version = bytes(raw[:8])
|
version = bytes(raw[:8])
|
||||||
display = bytes(raw).rstrip(b"\x00") or version # drop the firmware buffer padding
|
emit(fmt.format(1, bytes(raw)).strip("\n")) # raw() already trims to the received length
|
||||||
emit(fmt.format(1, display).strip("\n"))
|
|
||||||
model = decode_version(version)
|
model = decode_version(version)
|
||||||
if model:
|
if model:
|
||||||
emit(f" → {model}")
|
emit(f" → {model}")
|
||||||
|
|||||||
Reference in New Issue
Block a user