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

116 lines
4.4 KiB
Python

"""Tests for the ST25DV04K/16K/64K (ISO 15693 + I2C dynamic tag, RF side)."""
import asyncio
import struct
import pytest
from pm3py.sim.frame import RFFrame
from pm3py.transponders.hf.iso15693.st.st25dv import (
ST25DV04K, ST25DV16K, ST25DV64K, REG_ENDA1, REG_RFA2SS, DYN_MB_CTRL,
)
def run(coro):
return asyncio.new_event_loop().run_until_complete(coro)
def cmd(tag, payload: bytes):
return run(tag.handle_frame(RFFrame.from_bytes(payload)))
class TestIdentity:
@pytest.mark.parametrize("cls,prefix,blocks", [
(ST25DV04K, b"\xE0\x02\x24", 128),
(ST25DV16K, b"\xE0\x02\x26", 512),
(ST25DV64K, b"\xE0\x02\x26", 2048),
])
def test_uid_and_blocks(self, cls, prefix, blocks):
tag = cls()
assert tag._uid[0:3] == prefix
assert tag._num_blocks == blocks
def test_04k_sysinfo_has_memsize(self):
tag = ST25DV04K()
run(tag.power_on())
r = cmd(tag, bytes([0x02, 0x2B]))
assert r.data[1] == 0x0F
assert r.data[-1] == 0x24 # IC ref
assert r.data[-3] == 0x7F # 128 blocks - 1
def test_16k_sysinfo_omits_memsize(self):
tag = ST25DV16K()
run(tag.power_on())
r = cmd(tag, bytes([0x02, 0x2B]))
assert r.data[1] == 0x0B # DSFID+AFI+ICref, no memsize
assert r.data[-1] == 0x26
def test_ext_sysinfo_gives_memsize(self):
tag = ST25DV64K()
run(tag.power_on())
r = cmd(tag, bytes([0x02, 0x3B, 0x02, 0x0F]))
assert struct.unpack_from("<H", r.data, 12)[0] == 2047
class TestBlocks:
def test_read_write(self):
tag = ST25DV04K()
run(tag.power_on())
cmd(tag, bytes([0x02, 0x21, 0x08, 0xDE, 0xAD, 0xBE, 0xEF]))
assert cmd(tag, bytes([0x02, 0x20, 0x08])).data[1:5] == b"\xDE\xAD\xBE\xEF"
def test_extended_read_write_high_block(self):
tag = ST25DV16K()
run(tag.power_on())
cmd(tag, bytes([0x02, 0x31]) + struct.pack("<H", 300) + b"\x11\x22\x33\x44")
r = cmd(tag, bytes([0x02, 0x30]) + struct.pack("<H", 300))
assert r.data[1:5] == b"\x11\x22\x33\x44"
def test_fast_read_single(self):
tag = ST25DV04K()
run(tag.power_on())
cmd(tag, bytes([0x02, 0x21, 0x08, 1, 2, 3, 4]))
r = cmd(tag, bytes([0x02, 0xC0, 0x02, 0x08]))
assert r.data[1:5] == b"\x01\x02\x03\x04"
class TestPasswordConfig:
def test_present_password_and_write_config(self):
tag = ST25DV04K()
run(tag.power_on())
# write config denied without config session
assert cmd(tag, bytes([0x02, 0xA1, 0x02, REG_ENDA1, 0x03])).data[0] == 0x01
assert cmd(tag, bytes([0x02, 0xB3, 0x02, 0x00]) + bytes(8)).data[0] == 0x00
assert cmd(tag, bytes([0x02, 0xA1, 0x02, REG_ENDA1, 0x03])).data[0] == 0x00
assert cmd(tag, bytes([0x02, 0xA0, 0x02, REG_ENDA1])).data[1] == 0x03
def test_wrong_password(self):
tag = ST25DV04K(passwords={0: b"\x01" * 8, 1: bytes(8), 2: bytes(8), 3: bytes(8)})
run(tag.power_on())
assert cmd(tag, bytes([0x02, 0xB3, 0x02, 0x00]) + bytes(8)).data[0] == 0x01
def test_area_read_protection(self):
pw1 = b"\x01\x02\x03\x04\x05\x06\x07\x08"
tag = ST25DV04K(passwords={0: bytes(8), 1: pw1, 2: bytes(8), 3: bytes(8)})
run(tag.power_on())
cmd(tag, bytes([0x02, 0xB3, 0x02, 0x00]) + bytes(8)) # config session
cmd(tag, bytes([0x02, 0xA1, 0x02, REG_ENDA1, 0x00])) # Area1 = blocks 0-7
cmd(tag, bytes([0x02, 0xA1, 0x02, REG_RFA2SS, 0x09])) # A2 rwprot=10, pwd_ctrl=1
r = cmd(tag, bytes([0x02, 0x20, 20])) # block in Area2
assert r.data[0] == 0x01 and r.data[1] == 0x15
assert cmd(tag, bytes([0x02, 0xB3, 0x02, 0x01]) + pw1).data[0] == 0x00
assert cmd(tag, bytes([0x02, 0x20, 20])).data[0] == 0x00
class TestMailbox:
def test_mailbox_round_trip(self):
tag = ST25DV04K()
run(tag.power_on())
# mailbox disabled → error
assert cmd(tag, bytes([0x02, 0xAB, 0x02])).data[0] == 0x01
# enable via dynamic register MB_CTRL_Dyn b0
cmd(tag, bytes([0x02, 0xAE, 0x02, DYN_MB_CTRL, 0x01]))
msg = b"hello"
cmd(tag, bytes([0x02, 0xAA, 0x02, len(msg) - 1]) + msg)
assert cmd(tag, bytes([0x02, 0xAB, 0x02])).data[1] == len(msg) - 1
r = cmd(tag, bytes([0x02, 0xAC, 0x02, 0x00, len(msg) - 1]))
assert r.data[1:1 + len(msg)] == msg