feat(rawcli): annotate read/write output with datasheet memory regions

A READ now names the memory locations the pages cover, per the tag's datasheet layout,
grouping consecutive same-role pages:

  READ(0)  -> 00-02 → UID / serial number  ·  03 → Capability Container (CC)
  READ(4)  -> 04-07 → user memory
  READ(0xE3) -> E3 → CFG0 · E4 → CFG1 · E5 → PWD · E6 → PACK

TagCommand gains a pages() callable (which page/block numbers a command touches: READ = 4
pages from <page>, FAST_READ = <start>..<end>, WRITE = one page). memory.region_annotation()
groups those into "lo-hi → role" via the existing page_role layouts; _handle_call prints it
after the exchange. No layout for the tag (e.g. MIFARE Classic) -> no annotation.

Hardware-verified on NTAG216 (READ 0/4/0xE3, FAST_READ). Tests for the grouping + the
dispatch path. 1264 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-15 16:18:36 -07:00
parent a501759c43
commit 260cb8eb9e
4 changed files with 68 additions and 6 deletions

View File

@@ -392,6 +392,21 @@ class TestFunctionCalls:
assert calls[0].args[0] == b"\xA0\x04"
assert calls[1].args[0] == b"\x01\x02\x03\x04" + b"\x00" * 12
def test_read_annotates_memory_region(self, capsys):
# READ output names the datasheet regions the pages cover (the user's ask)
s = RawSession(device=_mock_device(
scan14={"found": True, "sak": 0x00, "atqa": "4400", "uid": "04a1b2c3d4e5f6"},
version=NTAG216_VERSION))
dispatch(s, "identify")
assert s.transponder == "NTAG216"
s.device.hf.iso14a.raw.return_value = {"raw": b"\x03\x00\xFE\x00" * 4}
capsys.readouterr()
dispatch(s, "READ(4)")
assert "04-07 → user memory" in _plain(capsys.readouterr().out)
dispatch(s, "READ(0)")
o = _plain(capsys.readouterr().out)
assert "UID / serial number" in o and "Capability Container" in o
def test_call_without_identify(self, capsys):
dispatch(RawSession(), "READ(4)")
assert "identify" in capsys.readouterr().out
@@ -524,6 +539,15 @@ class TestMemoryHint:
from pm3py.cli.rawcli.memory import input_hint
assert input_hint(RawSession(), "READ(4)") is None
def test_region_annotation(self):
from pm3py.cli.rawcli.memory import region_annotation
assert region_annotation("NTAG216", [0, 1, 2, 3]) == \
"00-02 → UID / serial number · 03 → Capability Container (CC)"
assert region_annotation("NTAG216", [4, 5, 6, 7]) == "04-07 → user memory"
assert "PWD" in region_annotation("NTAG213", [0x2B])
assert region_annotation("MIFARE Classic 1K", [4]) is None # no layout -> no annotation
assert region_annotation("NTAG216", []) is None
def test_t5577_block_roles(self):
from pm3py.cli.rawcli.memory import page_role, input_hint
tp = "T5577 (EM4100 20260716FF)"