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
|
||||
close drop the tag connection
|
||||
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
|
||||
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):
|
||||
@@ -43,6 +43,33 @@ def _connect(port):
|
||||
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:
|
||||
"""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)."""
|
||||
@@ -100,7 +127,9 @@ def dispatch(state: RawSession, line: str, out=print) -> bool:
|
||||
|
||||
if not cmd.payload:
|
||||
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))
|
||||
return True
|
||||
|
||||
@@ -121,6 +150,7 @@ def _handle_call(state: RawSession, cmd, out=print) -> None:
|
||||
return
|
||||
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
|
||||
_ensure_selected(state.device, catalog.protocol) # re-select once for the command
|
||||
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))
|
||||
|
||||
@@ -186,8 +186,7 @@ def _probe_version(dev, fmt, emit) -> tuple[str | None, bytes | None]:
|
||||
if not raw:
|
||||
return None, None
|
||||
version = bytes(raw[:8])
|
||||
display = bytes(raw).rstrip(b"\x00") or version # drop the firmware buffer padding
|
||||
emit(fmt.format(1, display).strip("\n"))
|
||||
emit(fmt.format(1, bytes(raw)).strip("\n")) # raw() already trims to the received length
|
||||
model = decode_version(version)
|
||||
if model:
|
||||
emit(f" → {model}")
|
||||
|
||||
Reference in New Issue
Block a user