Files
pm3py/tests/test_sim_st25tb.py
2026-07-05 09:53:52 -07:00

142 lines
4.8 KiB
Python

"""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")