142 lines
4.8 KiB
Python
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")
|