feat(rawcli): ICODE SLIX2 datasheet catalog
SLIX2 is the SLIX command set plus three SLIX2-only commands and a larger 80-block memory: READ_SIGNATURE (0xBD, the ECC originality signature identify already probes), PASSWORD_PROTECTION_64BIT (0xBB), STAY_QUIET_PERSISTENT (0xBC). Built by reusing the SLIX catalog's commands, so it stays a strict superset; catalog_for routes an identified "ICODE SLIX2" to it (SLIX2 checked before the SLIX/ICODE fallback). Verified live through the real completer + PromptSession: command-name completion surfaces the SLIX2 commands, raw-opcode completion maps 0xBD/0xBB/0xBC back to them, and entry accepts end to end. Unit test covers the delta + dispatch. Follow-ups noted in docs/rawcli-todo.md: NTAG5 (needs a finer identify probe first, since it shares E0 04 with SLIX), and INVENTORY_READ/FAST_INVENTORY_READ for the SLIX catalogs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,16 +41,26 @@ rendered dropdown on a real terminal is the only thing left unautomated.
|
|||||||
authenticate step is the reader's job before these.
|
authenticate step is the reader's job before these.
|
||||||
- Autocomplete must work in hex + binary (see the bug above).
|
- Autocomplete must work in hex + binary (see the bug above).
|
||||||
|
|
||||||
## After MFC — 15693 vicinity chips: ICODE SLIX, ICODE SLIX2, NTAG5
|
## After MFC — 15693 vicinity chips: ICODE SLIX ✅, ICODE SLIX2 ✅, NTAG5 (next)
|
||||||
|
|
||||||
1. **15693 header builder** (shared foundation). Replace the ad-hoc `bytes([0x02, cmd, …])` with a
|
1. **15693 header builder** ✅ — `iso15_frame()` (flags + command + NXP mfg 0x04), shared by all
|
||||||
real request-header builder: flags (data rate, addressed-vs-nonaddressed + 8-byte UID, option,
|
15693 catalogs.
|
||||||
protocol-extension) + command + NXP **manufacturer code 0x04** for ICODE/NTAG5 custom commands.
|
2. **Per-chip identify** — `identify.name_iso15` distinguishes SLIX vs SLIX2 (READ_SIGNATURE 0xBD
|
||||||
2. **Per-chip identify.** Today identify stamps `"ISO15693"` generically; resolve the actual IC
|
probe). **NTAG5 and ICODE DNA still fall through to "ICODE SLIX"** (they share `E0 04`); NTAG5
|
||||||
(UID mfg byte `E0 04…` + GET_SYSTEM_INFO / IC-reference) so catalog + map key off SLIX2 / NTAG5.
|
needs a finer probe (IC-reference / GET_SYSTEM_INFO) before its catalog can be routed.
|
||||||
3. **Catalogs + memory maps** per chip (blocks, config, counters, EAS/AFI, password/AES) — sourced
|
3. **Catalogs** — `_SLIX` (plain) and `_SLIX2` (`_SLIX` + READ_SIGNATURE 0xBD /
|
||||||
from the models in `transponders/hf/iso15693/nxp/` (`icode_slix`, `icode_slix2`, `ntag5_*`).
|
PASSWORD_PROTECTION_64BIT 0xBB / STAY_QUIET_PERSISTENT 0xBC, 80 blocks) done, dispatched by
|
||||||
4. Same autocomplete UX (hex + binary).
|
`catalog_for`, verified live. **NTAG5 catalog** (READ/WRITE_CONFIG, the NTAG5 config registers,
|
||||||
|
password/AES) still to build — source from `transponders/hf/iso15693/nxp/ntag5_*`.
|
||||||
|
4. Autocomplete UX ✅ (hex + binary, command names + opcode-by-value; the byte-line completion bug
|
||||||
|
is fixed).
|
||||||
|
|
||||||
|
**Completeness gap (both SLIX catalogs):** `INVENTORY_READ` (0xA0) / `FAST_INVENTORY_READ` (0xA1)
|
||||||
|
are in the datasheet + the models but not yet in the catalogs — they carry an inventory request
|
||||||
|
body (AFI/mask/block range), so they need a small dedicated builder, not a bare opcode.
|
||||||
|
|
||||||
|
**Hardware verify pending:** SLIX2 catalog was verified only against unit tests + a live-session
|
||||||
|
drive (no SLIX2 tag on the reader). Confirm on a real SLIX2 when one is available.
|
||||||
|
|
||||||
## Also queued
|
## Also queued
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,19 @@ _SLIX = _catalog(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ICODE SLIX2 (SL2S2602): everything the SLIX has, plus three SLIX2-only commands and a larger
|
||||||
|
# 80-block memory. READ_SIGNATURE (0xBD) is the one identify probes to tell SLIX2 from SLIX.
|
||||||
|
_SLIX2 = _catalog(
|
||||||
|
"ICODE SLIX2", "hf15", *_SLIX.commands.values(),
|
||||||
|
_c("READ_SIGNATURE", [], lambda: iso15_frame(0xBD, mfg=NXP_MFG),
|
||||||
|
"read the 32-byte ECC originality signature — SLIX2 only (0xBD)", opcode=0xBD),
|
||||||
|
_c("PASSWORD_PROTECTION_64BIT", [], lambda: iso15_frame(0xBB, mfg=NXP_MFG),
|
||||||
|
"combine the read+write passwords into 64-bit protection — SLIX2 only (0xBB)", opcode=0xBB),
|
||||||
|
_c("STAY_QUIET_PERSISTENT", [], lambda: iso15_frame(0xBC, mfg=NXP_MFG),
|
||||||
|
"persistent quiet that survives power cycles — SLIX2 only (0xBC)", opcode=0xBC),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---- LF T5577 / ATA5577 (block read/write over the T55xx downlink; executes via lf.t55) ----
|
# ---- LF T5577 / ATA5577 (block read/write over the T55xx downlink; executes via lf.t55) ----
|
||||||
# build() exists only to expose the downlink opcode for hex/binary completion; execution goes
|
# build() exists only to expose the downlink opcode for hex/binary completion; execution goes
|
||||||
# through run() (the device method), since LF isn't a raw-byte exchange like 14a.
|
# through run() (the device method), since LF isn't a raw-byte exchange like 14a.
|
||||||
@@ -420,6 +433,8 @@ def catalog_for(session) -> Catalog | None:
|
|||||||
"""Resolve the command catalog for the session's identified transponder, or None."""
|
"""Resolve the command catalog for the session's identified transponder, or None."""
|
||||||
name = (session.transponder or "").upper()
|
name = (session.transponder or "").upper()
|
||||||
if session.protocol == "hf15":
|
if session.protocol == "hf15":
|
||||||
|
if "SLIX2" in name:
|
||||||
|
return _SLIX2
|
||||||
return _SLIX if "SLIX" in name or "ICODE" in name else ISO15
|
return _SLIX if "SLIX" in name or "ICODE" in name else ISO15
|
||||||
if session.protocol == "hf14a":
|
if session.protocol == "hf14a":
|
||||||
if not session.transponder:
|
if not session.transponder:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from pm3py.cli.rawcli.trace_view import render_exchange
|
|||||||
from pm3py.cli.rawcli.parser import parse_token, parse_bytes, parse_line
|
from pm3py.cli.rawcli.parser import parse_token, parse_bytes, parse_line
|
||||||
from pm3py.cli.rawcli.entry import type_digit, is_byte_line, reconcile_bases
|
from pm3py.cli.rawcli.entry import type_digit, is_byte_line, reconcile_bases
|
||||||
from pm3py.cli.rawcli.identify import identify, clear, name_14a, format_summary
|
from pm3py.cli.rawcli.identify import identify, clear, name_14a, format_summary
|
||||||
from pm3py.cli.rawcli.catalog import (TYPE2, ISO15, T5577, LF_READONLY, MFC, _SLIX,
|
from pm3py.cli.rawcli.catalog import (TYPE2, ISO15, T5577, LF_READONLY, MFC, _SLIX, _SLIX2,
|
||||||
iso15_frame, iso15_flags_decode, catalog_for, catalog_for as _cf)
|
iso15_frame, iso15_flags_decode, catalog_for, catalog_for as _cf)
|
||||||
from pm3py.cli.rawcli.tlv import parse_tlv, format_tlv
|
from pm3py.cli.rawcli.tlv import parse_tlv, format_tlv
|
||||||
from pm3py.cli.rawcli.completer import RawCompleter
|
from pm3py.cli.rawcli.completer import RawCompleter
|
||||||
@@ -458,6 +458,20 @@ class TestCatalog:
|
|||||||
s = RawSession(); s.protocol, s.transponder = "hf15", "ICODE SLIX"
|
s = RawSession(); s.protocol, s.transponder = "hf15", "ICODE SLIX"
|
||||||
assert catalog_for(s) is _SLIX
|
assert catalog_for(s) is _SLIX
|
||||||
|
|
||||||
|
def test_slix2_catalog(self):
|
||||||
|
# SLIX2 = the full SLIX datasheet set plus three SLIX2-only commands (0xBD/0xBB/0xBC)
|
||||||
|
assert set(_SLIX.commands) <= set(_SLIX2.commands) # superset of SLIX
|
||||||
|
for name, op in [("READ_SIGNATURE", 0xBD), ("PASSWORD_PROTECTION_64BIT", 0xBB),
|
||||||
|
("STAY_QUIET_PERSISTENT", 0xBC)]:
|
||||||
|
assert name not in _SLIX.commands # SLIX2-only
|
||||||
|
assert _SLIX2.get(name).opcode() == op
|
||||||
|
assert _SLIX2.by_opcode(op).name == name # raw opcode maps back
|
||||||
|
assert _SLIX2.get("READ_SIGNATURE").build() == b"\x02\xBD\x04" # NXP mfg inserted
|
||||||
|
# dispatch: SLIX2 -> _SLIX2, plain SLIX still -> _SLIX (SLIX2 test comes first)
|
||||||
|
s = RawSession(); s.protocol = "hf15"
|
||||||
|
s.transponder = "ICODE SLIX2"; assert catalog_for(s) is _SLIX2
|
||||||
|
s.transponder = "ICODE SLIX"; assert catalog_for(s) is _SLIX
|
||||||
|
|
||||||
def test_resolver(self):
|
def test_resolver(self):
|
||||||
s = RawSession()
|
s = RawSession()
|
||||||
assert catalog_for(s) is None # nothing identified
|
assert catalog_for(s) is None # nothing identified
|
||||||
|
|||||||
Reference in New Issue
Block a user