feat(rawcli): command catalog + function-style commands (Phase 4)
- catalog.py: declarative per-transponder command sets (name, params, build(args)->bytes, help). Seeds: Type 2 (NTAG/UL: READ, FAST_READ, GET_VERSION, READ_SIG, READ_CNT, WRITE), MIFARE Classic (READ/WRITE/ HALT), ISO 15693 (INVENTORY, READ_BLOCK, WRITE_BLOCK, GET_SYSTEM_INFO). catalog_for(session) resolves by protocol + identified transponder. - app: function-style calls (READ(4) -> build -> raw exchange -> annotated trace); catalog-aware help (`help` lists the current tag's commands, `help READ` shows one). Bad args print usage. 9 new tests (catalog builds, resolver, call dispatch, help). Command knowledge is declarative + hardware-free-testable; the exchange is the user's device test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +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.entry import byte_space
|
||||
from pm3py.cli.rawcli.identify import identify, clear, name_14a
|
||||
from pm3py.cli.rawcli.catalog import TYPE2, ISO15, catalog_for, catalog_for as _cf
|
||||
from pm3py.cli.rawcli.app import dispatch
|
||||
|
||||
_ANSI = re.compile(r"\x1b\[[0-9;]*m")
|
||||
@@ -200,3 +201,67 @@ class TestControlCommands:
|
||||
dispatch(s, "close")
|
||||
assert "closed" in capsys.readouterr().out
|
||||
assert s.transponder is None
|
||||
|
||||
|
||||
class TestCatalog:
|
||||
def test_type2_builds(self):
|
||||
assert TYPE2.get("READ").build("4") == b"\x30\x04"
|
||||
assert TYPE2.get("read").build("0x04") == b"\x30\x04" # case + hex arg
|
||||
assert TYPE2.get("FAST_READ").build("0", "3") == b"\x3A\x00\x03"
|
||||
assert TYPE2.get("GET_VERSION").build() == b"\x60"
|
||||
assert TYPE2.get("WRITE").build("4", "01020304") == b"\xA2\x04\x01\x02\x03\x04"
|
||||
|
||||
def test_iso15_builds(self):
|
||||
assert ISO15.get("READ_BLOCK").build("4") == b"\x02\x20\x04"
|
||||
assert ISO15.get("INVENTORY").build() == b"\x26\x01\x00"
|
||||
|
||||
def test_resolver(self):
|
||||
s = RawSession()
|
||||
assert catalog_for(s) is None # nothing identified
|
||||
s.protocol, s.transponder = "hf14a", "NTAG215"
|
||||
assert catalog_for(s) is TYPE2
|
||||
s.transponder = "MIFARE Classic 1K"
|
||||
assert catalog_for(s).name == "MIFARE Classic"
|
||||
s.protocol, s.transponder = "hf15", "ISO15693"
|
||||
assert catalog_for(s) is ISO15
|
||||
|
||||
|
||||
class TestFunctionCalls:
|
||||
def _id(self, sak=0x00):
|
||||
return RawSession(device=_mock_device(
|
||||
scan14={"found": True, "sak": sak, "uid": b"\x04\x01\x02\x03"}))
|
||||
|
||||
def test_call_read_sends_correct_bytes(self, capsys):
|
||||
s = self._id()
|
||||
dispatch(s, "identify")
|
||||
s.device.hf.iso14a.raw.return_value = {"data": b"\xAA\xBB\xCC\xDD"}
|
||||
capsys.readouterr()
|
||||
dispatch(s, "READ(4)")
|
||||
out = _plain(capsys.readouterr().out)
|
||||
assert "30 04" in out # the built command hit the trace
|
||||
s.device.hf.iso14a.raw.assert_called_with(b"\x30\x04")
|
||||
|
||||
def test_call_without_identify(self, capsys):
|
||||
dispatch(RawSession(), "READ(4)")
|
||||
assert "identify" in capsys.readouterr().out
|
||||
|
||||
def test_unknown_call(self, capsys):
|
||||
s = self._id(); dispatch(s, "identify"); capsys.readouterr()
|
||||
dispatch(s, "FLY(4)")
|
||||
assert "unknown command" in capsys.readouterr().out
|
||||
|
||||
def test_bad_args_shows_usage(self, capsys):
|
||||
s = self._id(); dispatch(s, "identify"); capsys.readouterr()
|
||||
dispatch(s, "READ()") # missing page arg
|
||||
assert "usage: READ(page)" in capsys.readouterr().out
|
||||
|
||||
def test_help_lists_catalog(self, capsys):
|
||||
s = self._id(); dispatch(s, "identify"); capsys.readouterr()
|
||||
dispatch(s, "help")
|
||||
out = capsys.readouterr().out
|
||||
assert "READ(page)" in out and "GET_VERSION" in out
|
||||
|
||||
def test_help_command_detail(self, capsys):
|
||||
s = self._id(); dispatch(s, "identify"); capsys.readouterr()
|
||||
dispatch(s, "help READ")
|
||||
assert "0x30" in capsys.readouterr().out
|
||||
|
||||
Reference in New Issue
Block a user