feat(rawcli): identify + persistent connection (Phase 3)
- identify.py: probe HF (14a -> 15693) then LF; set session field / protocol / transponder. The 14a scan uses NO_DISCONNECT so the tag stays selected (the persistent connection). SAK -> coarse family name for the breadcrumb. - session: add `protocol` (drives raw-exchange routing) - app: connect via Proxmark3.sync() (scan/raw become plain calls); wire identify / transponder / close control commands; protocol-aware _raw_exchange; breadcrumb fills in field + transponder after identify 8 new tests (identify 14a/15693/none, clear, control commands). Device probes are mocked; live identify is the user's hardware test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
"""rawcli Phase 1 — session state, breadcrumb, trace rendering, CLI dispatch. Hardware-free."""
|
||||
import re
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from pm3py.cli.main import build_parser, main
|
||||
@@ -8,6 +10,7 @@ from pm3py.cli.rawcli.session import RawSession
|
||||
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.app import dispatch
|
||||
|
||||
_ANSI = re.compile(r"\x1b\[[0-9;]*m")
|
||||
@@ -138,3 +141,62 @@ class TestDispatch:
|
||||
def test_bad_hex_reports(self, capsys):
|
||||
assert dispatch(RawSession(), "0b0011") is True
|
||||
assert "whole number of bytes" in capsys.readouterr().out
|
||||
|
||||
|
||||
def _mock_device(scan14=None, scan15=None):
|
||||
dev = MagicMock()
|
||||
dev.hf.iso14a.scan.return_value = scan14 or {"found": False}
|
||||
dev.hf.iso15.scan.return_value = scan15 or {"found": False}
|
||||
dev.lf.search.return_value = None
|
||||
return dev
|
||||
|
||||
|
||||
class TestIdentify:
|
||||
def test_no_device(self):
|
||||
s = RawSession()
|
||||
r = identify(s)
|
||||
assert r["found"] is False and s.field is None
|
||||
|
||||
def test_14a_sets_field_and_name(self):
|
||||
s = RawSession(device=_mock_device(
|
||||
scan14={"found": True, "sak": 0x08, "uid": b"\x01\x02\x03\x04"}))
|
||||
r = identify(s)
|
||||
assert r["found"] and s.field == "hf" and s.protocol == "hf14a"
|
||||
assert s.transponder == "MIFARE Classic 1K"
|
||||
assert r["uid"] == b"\x01\x02\x03\x04"
|
||||
|
||||
def test_15693_fallback(self):
|
||||
s = RawSession(device=_mock_device(scan15={"found": True, "uid": b"\xE0\x04"}))
|
||||
r = identify(s)
|
||||
assert r["found"] and s.field == "hf" and s.protocol == "hf15"
|
||||
assert s.transponder == "ISO15693"
|
||||
|
||||
def test_name_unknown_sak(self):
|
||||
assert name_14a({"sak": 0x99}) == "ISO14443-A (SAK 99)"
|
||||
|
||||
def test_clear_forgets_tag(self):
|
||||
s = RawSession(device=_mock_device(scan14={"found": True, "sak": 0x08}))
|
||||
identify(s)
|
||||
clear(s)
|
||||
assert s.field is None and s.transponder is None and s.protocol == "hf14a"
|
||||
|
||||
|
||||
class TestControlCommands:
|
||||
def test_identify_command(self, capsys):
|
||||
s = RawSession(device=_mock_device(
|
||||
scan14={"found": True, "sak": 0x00, "uid": b"\x04\x11\x22\x33"}))
|
||||
dispatch(s, "identify")
|
||||
out = capsys.readouterr().out
|
||||
assert "MIFARE UL/NTAG" in out and "04112233" in out
|
||||
assert s.breadcrumb() == "[raw / hf / MIFARE UL/NTAG / 0x]"
|
||||
|
||||
def test_transponder_when_none(self, capsys):
|
||||
dispatch(RawSession(), "transponder")
|
||||
assert "run 'identify'" in capsys.readouterr().out
|
||||
|
||||
def test_close(self, capsys):
|
||||
s = RawSession(device=_mock_device(scan14={"found": True, "sak": 0x08}))
|
||||
dispatch(s, "identify")
|
||||
dispatch(s, "close")
|
||||
assert "closed" in capsys.readouterr().out
|
||||
assert s.transponder is None
|
||||
|
||||
Reference in New Issue
Block a user