Big transponder support update
This commit is contained in:
153
tests/test_sim_st25ta.py
Normal file
153
tests/test_sim_st25ta.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""Tests for the ST25TA family (ST NFC Forum Type 4)."""
|
||||
import asyncio
|
||||
import pytest
|
||||
|
||||
from pm3py.sim.frame import RFFrame
|
||||
from pm3py.transponders.hf.iso14443a.base import _compute_bcc
|
||||
from pm3py.transponders.hf.iso14443a.st.st25ta import (
|
||||
ST25TA512B, ST25TA02KB, ST25TA16K, ST25TA64K,
|
||||
)
|
||||
|
||||
NDEF_MSG = b"\xD1\x01\x04\x54\x02enHi" # 9 bytes
|
||||
|
||||
|
||||
def run(coro):
|
||||
return asyncio.new_event_loop().run_until_complete(coro)
|
||||
|
||||
|
||||
class Session:
|
||||
"""Drives a Type 4 tag through activation + APDU exchange."""
|
||||
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
self.blk = 0
|
||||
run(tag.power_on())
|
||||
run(tag.handle_frame(RFFrame.from_hex("26")))
|
||||
uid = tag._uid
|
||||
ct = b"\x88" + uid[0:3]
|
||||
run(tag.handle_frame(RFFrame.from_bytes(b"\x93\x70" + ct + bytes([_compute_bcc(ct)]))))
|
||||
run(tag.handle_frame(RFFrame.from_bytes(b"\x95\x70" + uid[3:7] + bytes([_compute_bcc(uid[3:7])]))))
|
||||
self.ats = run(tag.handle_frame(RFFrame.from_bytes(b"\xE0\x50"))).data
|
||||
|
||||
def apdu(self, hexstr: str) -> bytes:
|
||||
pcb = 0x02 | self.blk
|
||||
self.blk ^= 1
|
||||
resp = run(self.tag.handle_frame(RFFrame.from_bytes(bytes([pcb]) + bytes.fromhex(hexstr))))
|
||||
return resp.data[1:]
|
||||
|
||||
def select_app(self):
|
||||
return self.apdu("00A4040007D276000085010100")
|
||||
|
||||
def select_file(self, fid: str):
|
||||
return self.apdu("00A4000C02" + fid)
|
||||
|
||||
def read(self, off: int, le: int) -> bytes:
|
||||
return self.apdu(f"00B0{off:04X}{le:02X}")
|
||||
|
||||
|
||||
class TestActivation:
|
||||
@pytest.mark.parametrize("cls,ats", [
|
||||
(ST25TA512B, b"\x05\x75\x80\x60\x02"),
|
||||
(ST25TA02KB, b"\x05\x75\x80\x60\x02"),
|
||||
(ST25TA16K, b"\x05\x78\x80\x90\x02"),
|
||||
])
|
||||
def test_ats(self, cls, ats):
|
||||
s = Session(cls())
|
||||
assert s.ats == ats
|
||||
|
||||
def test_uid_prefix(self):
|
||||
assert ST25TA02KB()._uid[0] == 0x02
|
||||
assert ST25TA02KB()._uid[1] == 0xE3
|
||||
|
||||
|
||||
class TestFileSystem:
|
||||
def test_cc_read(self):
|
||||
s = Session(ST25TA02KB())
|
||||
assert s.select_app() == b"\x90\x00"
|
||||
assert s.select_file("E103") == b"\x90\x00"
|
||||
cc = s.read(0, 15)
|
||||
assert cc[-2:] == b"\x90\x00"
|
||||
assert cc[0:4] == b"\x00\x0F\x20\x00"
|
||||
|
||||
def test_cc_per_variant(self):
|
||||
assert ST25TA512B()._cc_file[3:5] == b"\x00\x40"
|
||||
assert ST25TA02KB()._cc_file[3:5] == b"\x00\xFF"
|
||||
assert ST25TA16K()._cc_file[3:5] == b"\x00\xF6"
|
||||
assert ST25TA64K()._cc_file[11:13] == b"\x20\x00"
|
||||
|
||||
def test_ndef_read(self):
|
||||
s = Session(ST25TA02KB(ndef_message=NDEF_MSG))
|
||||
s.select_app()
|
||||
assert s.select_file("0001") == b"\x90\x00"
|
||||
assert s.read(0, 2)[0:2] == len(NDEF_MSG).to_bytes(2, "big")
|
||||
assert s.read(2, len(NDEF_MSG))[:-2] == NDEF_MSG
|
||||
|
||||
def test_ndef_update(self):
|
||||
s = Session(ST25TA02KB())
|
||||
s.select_app()
|
||||
s.select_file("0001")
|
||||
new = b"\x00\x03\xD0\x00\x00"
|
||||
assert s.apdu("00D60000%02X%s" % (len(new), new.hex())) == b"\x90\x00"
|
||||
assert s.read(0, len(new))[:-2] == new
|
||||
|
||||
def test_system_file_uid_and_icref(self):
|
||||
s = Session(ST25TA02KB())
|
||||
s.select_app()
|
||||
assert s.select_file("E101") == b"\x90\x00"
|
||||
assert s.read(8, 7)[:-2] == s.tag._uid
|
||||
assert s.read(0x11, 1)[0] == 0xE2
|
||||
|
||||
def test_select_unknown_file(self):
|
||||
s = Session(ST25TA02KB())
|
||||
s.select_app()
|
||||
assert s.select_file("E104") == b"\x6A\x82"
|
||||
|
||||
|
||||
class TestPassword:
|
||||
def test_read_protection(self):
|
||||
s = Session(ST25TA02KB(read_password=b"\x11" * 16))
|
||||
s.select_app()
|
||||
s.select_file("0001")
|
||||
assert s.read(0, 2) == b"\x69\x82"
|
||||
assert s.apdu("0020000110" + ("11" * 16)) == b"\x90\x00"
|
||||
assert s.read(0, 2)[-2:] == b"\x90\x00"
|
||||
|
||||
def test_wrong_password_retry_counter(self):
|
||||
s = Session(ST25TA02KB(read_password=b"\x11" * 16))
|
||||
s.select_app()
|
||||
s.select_file("0001")
|
||||
assert s.apdu("0020000110" + ("00" * 16)) == b"\x63\xC2"
|
||||
assert s.apdu("0020000110" + ("00" * 16)) == b"\x63\xC1"
|
||||
assert s.apdu("0020000110" + ("00" * 16)) == b"\x63\xC0"
|
||||
# blocked after 3 failures
|
||||
assert s.apdu("0020000110" + ("00" * 16)) == b"\x69\x85"
|
||||
|
||||
def test_write_protection(self):
|
||||
s = Session(ST25TA02KB(write_password=b"\x22" * 16))
|
||||
s.select_app()
|
||||
s.select_file("0001")
|
||||
assert s.apdu("00D6000003000000") == b"\x69\x82"
|
||||
assert s.apdu("0020000210" + ("22" * 16)) == b"\x90\x00"
|
||||
assert s.apdu("00D6000003000000") == b"\x90\x00"
|
||||
|
||||
def test_verify_query(self):
|
||||
s = Session(ST25TA02KB(read_password=b"\x11" * 16))
|
||||
s.select_app()
|
||||
s.select_file("0001")
|
||||
# Lc=0 query → password required
|
||||
assert s.apdu("00200001") == b"\x63\x00"
|
||||
|
||||
|
||||
class TestCounter:
|
||||
def test_event_counter_bumps_once_per_session(self):
|
||||
tag = ST25TA02KB()
|
||||
tag.enable_counter(on_write=False)
|
||||
s = Session(tag)
|
||||
s.select_app()
|
||||
s.select_file("0001")
|
||||
s.read(0, 2)
|
||||
s.read(0, 2)
|
||||
assert tag.event_counter == 1
|
||||
|
||||
def test_16k_has_no_counter(self):
|
||||
assert ST25TA16K()._has_counter is False
|
||||
Reference in New Issue
Block a user