"""Tests for the ISO 14443 Type B stack + ST25TB family (SRIX lineage).""" import asyncio import pytest from pm3py.sim.frame import RFFrame from pm3py.transponders.hf.iso14443b.st.st25tb import ( ST25TB512AC, ST25TB512AT, ST25TB02K, ST25TB04K, ) from pm3py.trace.decode_iso14b import decode_14443b, crc14b def run(coro): return asyncio.new_event_loop().run_until_complete(coro) def frame(tag, payload: bytes): return run(tag.handle_frame(RFFrame.from_bytes(payload))) def selected(cls, uid=None): tag = cls(uid=uid) run(tag.power_on()) cid = frame(tag, b"\x06\x00").data[0] frame(tag, bytes([0x0E, cid])) assert tag.state == "SELECTED" return tag class TestAnticollision: def test_initiate_moves_to_inventory(self): tag = ST25TB02K() run(tag.power_on()) assert tag.state == "READY" r = frame(tag, b"\x06\x00") assert len(r.data) == 1 assert tag.state == "INVENTORY" def test_memory_commands_need_select(self): tag = ST25TB02K() run(tag.power_on()) frame(tag, b"\x06\x00") # INVENTORY, not yet selected assert frame(tag, b"\x08\x07") is None def test_select_and_get_uid(self): tag = selected(ST25TB02K, uid=bytes([0xD0, 0x02, 0x3F, 1, 2, 3, 4, 5])) r = frame(tag, b"\x0B") assert r.data == tag._uid[::-1] # LSByte first def test_wrong_chip_id_not_selected(self): tag = ST25TB02K() run(tag.power_on()) cid = frame(tag, b"\x06\x00").data[0] assert frame(tag, bytes([0x0E, (cid + 1) & 0xFF])) is None assert tag.state != "SELECTED" def test_completion_deactivates(self): tag = selected(ST25TB02K) assert frame(tag, b"\x0F") is None assert tag.state == "DEACTIVATED" def test_reset_to_inventory(self): tag = selected(ST25TB02K) frame(tag, b"\x0C") assert tag.state == "INVENTORY" class TestIdentity: @pytest.mark.parametrize("cls,pc,blocks", [ (ST25TB512AC, 0x1B, 16), (ST25TB512AT, 0x33, 16), (ST25TB02K, 0x3F, 64), (ST25TB04K, 0x1F, 128), ]) def test_uid_and_size(self, cls, pc, blocks): tag = cls() assert tag._uid[0:3] == bytes([0xD0, 0x02, pc]) assert tag._num_blocks == blocks class TestMemory: def test_write_read_user_block(self): tag = selected(ST25TB04K) frame(tag, b"\x09\x10\xDE\xAD\xBE\xEF") # no response expected assert frame(tag, b"\x08\x10").data == b"\xDE\xAD\xBE\xEF" def test_write_block_has_no_response(self): tag = selected(ST25TB02K) assert frame(tag, b"\x09\x10\x01\x02\x03\x04") is None def test_system_block_255(self): tag = selected(ST25TB02K) assert len(frame(tag, b"\x08\xFF").data) == 4 def test_invalid_block_no_response(self): tag = selected(ST25TB02K) # 64 blocks assert frame(tag, b"\x08\x64") is None # block 100 invalid class TestCounters: def test_factory_values(self): tag = selected(ST25TB02K) assert frame(tag, b"\x08\x05").data == (0xFFFFFFFE).to_bytes(4, "little") assert frame(tag, b"\x08\x06").data == (0xFFFFFFFF).to_bytes(4, "little") def test_decrement_only(self): tag = selected(ST25TB02K) frame(tag, bytes([0x09, 0x05]) + (0x1000).to_bytes(4, "little")) assert int.from_bytes(frame(tag, b"\x08\x05").data, "little") == 0x1000 # higher value rejected frame(tag, bytes([0x09, 0x05]) + (0x2000).to_bytes(4, "little")) assert int.from_bytes(frame(tag, b"\x08\x05").data, "little") == 0x1000 class TestOTP: def test_otp_bit_clear_only(self): tag = selected(ST25TB02K) assert frame(tag, b"\x08\x00").data == b"\xFF\xFF\xFF\xFF" # ships all-1s frame(tag, b"\x09\x00\x0F\x00\x00\x00") assert frame(tag, b"\x08\x00").data == b"\x0F\x00\x00\x00" # cannot set bits back frame(tag, b"\x09\x00\xFF\xFF\xFF\xFF") assert frame(tag, b"\x08\x00").data == b"\x0F\x00\x00\x00" def test_at_variant_blocks_are_plain_eeprom(self): tag = selected(ST25TB512AT) frame(tag, b"\x09\x00\x0F\xF0\x00\x00") assert frame(tag, b"\x08\x00").data == b"\x0F\xF0\x00\x00" class TestDecode: def test_request_decode(self): assert decode_14443b(0, b"\x06\x00") == "INITIATE" assert decode_14443b(0, b"\x06\x04") == "PCALL16" assert decode_14443b(0, b"\x26") == "SLOT_MARKER 2" assert decode_14443b(0, b"\x0E\x5A") == "SELECT chip=0x5A" assert decode_14443b(0, b"\x08\x07") == "READ_BLOCK #7" assert decode_14443b(0, b"\x09\x07") == "WRITE_BLOCK #7" def test_crc_b(self): # ST25TB datasheet reference vector. assert crc14b(bytes.fromhex("0A123456")) == bytes.fromhex("2CF6")