"""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.sim.iso15693 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.sim.iso15693 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.sim.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.sim.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.sim.iso15693 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.sim.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.sim.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