Files
pm3py/tests/test_sim_memory.py
michael c1ab980036 refactor: remove all backward-compat shims from sim/
Deletes 24 shim files from sim/. All test imports now point directly
to canonical transponders/ and trace/ paths. sim/ contains only
infrastructure (15 files): frame, memory, transponder ABC, reader ABC,
medium, sim_session, table_compiler, replay, relay, fuzzer, pm3medium,
mcu_bridge, mcu_protocol, dual_session, __init__.

751 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 20:56:40 -07:00

211 lines
7.6 KiB
Python

"""Tests for DirtyByteArray, MemoryRegion, BlockAccess, Transponder regions."""
import pytest
from pm3py.sim.memory import DirtyByteArray, MemoryRegion, BlockAccess
from pm3py.sim.transponder import Transponder
from pm3py.sim.frame import RFFrame
class TestDirtyByteArray:
def test_starts_clean(self):
d = DirtyByteArray(16)
assert not d.dirty
def test_setitem_marks_dirty(self):
d = DirtyByteArray(16)
d[0] = 0xFF
assert d.dirty
def test_slice_setitem_marks_dirty(self):
d = DirtyByteArray(16)
d[0:4] = b"\xDE\xAD\xBE\xEF"
assert d.dirty
def test_clear_dirty(self):
d = DirtyByteArray(16)
d[0] = 0xFF
d.clear_dirty()
assert not d.dirty
def test_extend_marks_dirty(self):
d = DirtyByteArray(b"\x00")
d.extend(b"\xFF")
assert d.dirty
def test_from_bytes(self):
d = DirtyByteArray(b"\x01\x02\x03")
assert len(d) == 3
assert not d.dirty
def test_callback_on_dirty(self):
called = []
d = DirtyByteArray(16)
d.on_dirty = lambda: called.append(True)
d[0] = 0xFF
assert len(called) == 1
class TestBlockAccess:
def test_defaults_open(self):
a = BlockAccess()
assert a.read == "open"
assert a.write == "open"
assert a.read_key is None
def test_password_protected(self):
a = BlockAccess(read="password", read_key=1)
assert a.read == "password"
assert a.read_key == 1
class TestMemoryRegion:
def test_construction(self):
r = MemoryRegion(name="user", data=DirtyByteArray(112), block_size=4)
assert r.name == "user"
assert len(r.data) == 112
assert r.block_size == 4
def test_eml_offset_default_negative(self):
r = MemoryRegion(name="config", data=DirtyByteArray(8))
assert r.eml_offset == -1
def test_num_blocks(self):
r = MemoryRegion(name="user", data=DirtyByteArray(112), block_size=4)
assert r.num_blocks == 28
def test_access_for_block_default(self):
r = MemoryRegion(name="user", data=DirtyByteArray(16), block_size=4)
assert r.access_for_block(0).read == "open"
def test_access_for_block_with_map(self):
r = MemoryRegion(name="user", data=DirtyByteArray(16), block_size=4,
access_map=[
BlockAccess(),
BlockAccess(),
BlockAccess(read="password", read_key=1),
BlockAccess(read="password", read_key=1),
])
assert r.access_for_block(0).read == "open"
assert r.access_for_block(2).read == "password"
def test_dirty_propagates_from_data(self):
r = MemoryRegion(name="user", data=DirtyByteArray(16), block_size=4)
assert not r.data.dirty
r.data[0:4] = b"\xDE\xAD\xBE\xEF"
assert r.data.dirty
class StubTransponder(Transponder):
def __init__(self):
super().__init__()
self.regions["user"] = MemoryRegion(
name="user", data=DirtyByteArray(16), block_size=4, eml_offset=100)
self.regions["config"] = MemoryRegion(
name="config", data=DirtyByteArray(8), block_size=0, eml_offset=-1)
async def power_on(self): pass
async def power_off(self): pass
async def handle_frame(self, frame): return None
@property
def state(self): return "TEST"
class TestTransponderRegions:
def test_has_regions_dict(self):
t = StubTransponder()
assert "user" in t.regions
assert "config" in t.regions
def test_authenticate(self):
t = StubTransponder()
assert len(t._authenticated) == 0
t.authenticate(1)
assert 1 in t._authenticated
assert t._access_dirty
def test_deauthenticate(self):
t = StubTransponder()
t.authenticate(1)
t._access_dirty = False
t.deauthenticate(1)
assert 1 not in t._authenticated
assert t._access_dirty
def test_deauthenticate_all(self):
t = StubTransponder()
t.authenticate(1)
t.authenticate(2)
t.deauthenticate()
assert len(t._authenticated) == 0
class TestTag15693Regions:
def test_has_user_region(self):
from pm3py.transponders.hf.iso15693.base import Tag15693
tag = Tag15693(uid=bytes(8), block_size=4, num_blocks=28)
assert "user" in tag.regions
assert tag.regions["user"].block_size == 4
assert tag.regions["user"].eml_offset == 175
def test_memory_property_aliases_region(self):
from pm3py.transponders.hf.iso15693.base import Tag15693
tag = Tag15693(uid=bytes(8), block_size=4, num_blocks=28)
tag._memory[0:4] = b"\xDE\xAD\xBE\xEF"
assert tag.regions["user"].data[0:4] == b"\xDE\xAD\xBE\xEF"
assert tag.regions["user"].data.dirty
class TestIcodeSlix2AccessMap:
def test_protection_pointer_sets_access_map(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag
tag = IcodeSlix2Tag(uid=b"\xE0\x04\x02" + bytes(5), protection_pointer=4,
read_password=0xAABBCCDD)
user = tag.regions["user"]
assert user.access_for_block(0).read == "open"
assert user.access_for_block(3).read == "open"
assert user.access_for_block(4).read == "password"
assert user.access_for_block(4).read_key == 1
def test_no_protection_pointer_all_open(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag
tag = IcodeSlix2Tag(uid=b"\xE0\x04\x02" + bytes(5))
user = tag.regions["user"]
assert user.access_for_block(0).read == "open"
# access_map exists for block 79 counter, but all reads are open
assert user.access_for_block(78).read == "open"
assert user.access_for_block(79).write_mode == "counter"
class TestCompileFromRegions:
def test_open_blocks_have_data_entries(self):
from pm3py.transponders.hf.iso15693.base import Tag15693
from pm3py.sim.table_compiler import TableCompiler
tag = Tag15693(uid=bytes(8), block_size=4, num_blocks=4)
tag._memory[0:4] = b"\xDE\xAD\xBE\xEF"
table = TableCompiler.compile_from_regions(tag)
entry = table.lookup(bytes([0x02, 0x20, 0x00]))
assert entry is not None
assert entry.response[1:5] == b"\xDE\xAD\xBE\xEF"
def test_protected_blocks_without_auth(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag
from pm3py.sim.table_compiler import TableCompiler
tag = IcodeSlix2Tag(uid=b"\xE0\x04\x02" + bytes(5), protection_pointer=2,
read_password=0xAABBCCDD, block_size=4, num_blocks=4)
table = TableCompiler.compile_from_regions(tag)
# Block 0 (open) should have entry
assert table.lookup(bytes([0x02, 0x20, 0x00])) is not None
# Block 2 (protected) should have no entry (relay)
assert table.lookup(bytes([0x02, 0x20, 0x02])) is None
def test_protected_blocks_with_auth(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag, PWD_READ
from pm3py.sim.table_compiler import TableCompiler
tag = IcodeSlix2Tag(uid=b"\xE0\x04\x02" + bytes(5), protection_pointer=2,
read_password=0xAABBCCDD, block_size=4, num_blocks=4)
tag.authenticate(PWD_READ)
table = TableCompiler.compile_from_regions(tag)
# Block 2 now readable
entry = table.lookup(bytes([0x02, 0x20, 0x02]))
assert entry is not None
assert entry.response[0] == 0x00 # no error