feat: migrate sim framework from worktree to master

Migrates all 43 sim modules and 23 test files (~7.8k LOC, 686 tests)
from .worktrees/sim-framework/ into pm3py/sim/. Import fixes:
- 4 files: ..protocol/..transport → ..core.protocol/..core.transport
- trace_fmt.py: pm3py.hf_15 → pm3py.trace.ndef
- test_sim_pm3medium.py: flat imports → core.*
- test_sim_trace_fmt.py: flat imports → core.*
- Added sim Cmd entries to core/protocol.py (EML_SETMEM, SIM_TABLE_*)

751 tests passing (686 sim + 65 core).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-18 20:04:55 -07:00
parent 7fc2470b01
commit 668170457e
68 changed files with 16951 additions and 1 deletions

149
tests/test_sim_pm3medium.py Normal file
View File

@@ -0,0 +1,149 @@
"""Tests for PM3Medium — hardware-backed Medium using Proxmark3."""
import asyncio
import struct
import pytest
from unittest.mock import AsyncMock, MagicMock
from pm3py.sim.frame import RFFrame
from pm3py.sim.pm3medium import PM3ReaderMedium
from pm3py.core.transport import PM3Response
from pm3py.core.protocol import Cmd, PM3Status
def run(coro):
return asyncio.get_event_loop().run_until_complete(coro)
def _mock_transport():
t = AsyncMock()
return t
def _14a_scan_response(uid=b"\x01\x02\x03\x04", atqa=b"\x04\x00", sak=0x08):
"""Build a mock iso14a_card_select_t response."""
# iso14a_card_select_t: uid[10] + uidlen[1] + atqa[2] + sak[1] + ats_len[1] + ats[256]
data = uid + b"\x00" * (10 - len(uid))
data += bytes([len(uid)])
data += atqa
data += bytes([sak])
data += bytes([0]) # ats_len
data += b"\x00" * 256 # ats
# Select status comes from oldarg[0]: 1=OK+ATS, 2=OK no ATS
return PM3Response(cmd=Cmd.HF_ISO14443A_READER, status=PM3Status.SUCCESS,
reason=0, ng=False, data=data,
oldarg=(2, 0, 0))
def _14a_raw_response(data=b"\x90\x00"):
"""Build a mock raw APDU response."""
return PM3Response(cmd=Cmd.HF_ISO14443A_READER, status=PM3Status.SUCCESS,
reason=0, ng=False, data=data,
oldarg=(len(data), 0, 0))
def _15693_response(data=b"\x00"):
"""Build a mock ISO 15693 response."""
return PM3Response(cmd=Cmd.HF_ISO15693_COMMAND, status=PM3Status.SUCCESS,
reason=0, ng=True, data=data, oldarg=None)
# ---------------------------------------------------------------------------
# PM3ReaderMedium — 14443-A reader
# ---------------------------------------------------------------------------
class TestPM3ReaderMedium14443A:
def test_transmit_receive_14a_scan(self):
t = _mock_transport()
t.send_mix.return_value = _14a_scan_response()
medium = PM3ReaderMedium(t, protocol="14443a")
# Transmit REQA (triggers scan)
run(medium.transmit_reader(RFFrame.from_hex("26")))
resp = run(medium.receive_reader())
assert resp is not None
assert t.send_mix.called
def test_transmit_receive_14a_raw(self):
t = _mock_transport()
t.send_mix.return_value = _14a_raw_response(b"\x90\x00")
medium = PM3ReaderMedium(t, protocol="14443a")
# Send a raw APDU
apdu = b"\x00\xA4\x04\x00"
run(medium.transmit_reader(RFFrame.from_bytes(apdu)))
resp = run(medium.receive_reader())
assert resp is not None
assert resp.data == b"\x90\x00"
def test_dropfield(self):
t = _mock_transport()
t.send_ng_no_response = AsyncMock()
medium = PM3ReaderMedium(t, protocol="14443a")
run(medium.dropfield())
t.send_ng_no_response.assert_called_once()
# ---------------------------------------------------------------------------
# PM3ReaderMedium — 15693 reader
# ---------------------------------------------------------------------------
class TestPM3ReaderMedium15693:
def test_transmit_receive_15693_inventory(self):
t = _mock_transport()
uid_lsb = b"\x03\x1B\x6A\x5C\x50\x01\x04\xE0"
resp_data = b"\x00\x00" + uid_lsb # flags + dsfid + uid
t.send_ng.return_value = _15693_response(resp_data)
medium = PM3ReaderMedium(t, protocol="15693")
inv = bytes([0x26, 0x01, 0x00])
run(medium.transmit_reader(RFFrame.from_bytes(inv)))
resp = run(medium.receive_reader())
assert resp is not None
assert len(resp.data) >= 10
def test_transmit_receive_15693_read_block(self):
t = _mock_transport()
block_data = b"\x00\xDE\xAD\xBE\xEF" # flags + data
t.send_ng.return_value = _15693_response(block_data)
medium = PM3ReaderMedium(t, protocol="15693")
read_cmd = bytes([0x22, 0x20]) + b"\x01" * 8 + bytes([0x00])
run(medium.transmit_reader(RFFrame.from_bytes(read_cmd)))
resp = run(medium.receive_reader())
assert resp is not None
assert resp.data[1:5] == b"\xDE\xAD\xBE\xEF"
def test_timeout_returns_none(self):
t = _mock_transport()
t.send_ng.side_effect = TimeoutError()
medium = PM3ReaderMedium(t, protocol="15693")
inv = bytes([0x26, 0x01, 0x00])
run(medium.transmit_reader(RFFrame.from_bytes(inv)))
resp = run(medium.receive_reader())
assert resp is None
# ---------------------------------------------------------------------------
# PM3ReaderMedium — attach/detach (not applicable for hardware)
# ---------------------------------------------------------------------------
class TestPM3ReaderMediumLimitations:
def test_attach_raises(self):
"""Hardware medium doesn't support attaching software transponders."""
t = _mock_transport()
medium = PM3ReaderMedium(t, protocol="14443a")
with pytest.raises(NotImplementedError):
run(medium.attach(None))
def test_detach_raises(self):
t = _mock_transport()
medium = PM3ReaderMedium(t, protocol="14443a")
with pytest.raises(NotImplementedError):
run(medium.detach(0))