feat: migrate sim framework from worktree to master
Migrates all 43 sim modules and 23 test files (~7.8k LOC, 686 tests) from .worktrees/sim-framework/ into pm3py/sim/. Import fixes: - 4 files: ..protocol/..transport → ..core.protocol/..core.transport - trace_fmt.py: pm3py.hf_15 → pm3py.trace.ndef - test_sim_pm3medium.py: flat imports → core.* - test_sim_trace_fmt.py: flat imports → core.* - Added sim Cmd entries to core/protocol.py (EML_SETMEM, SIM_TABLE_*) 751 tests passing (686 sim + 65 core). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
189
tests/test_sim_ndef.py
Normal file
189
tests/test_sim_ndef.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""Tests for NDEF Type 2 and Type 4 tag models."""
|
||||
import asyncio
|
||||
import pytest
|
||||
|
||||
from pm3py.sim.frame import RFFrame
|
||||
from pm3py.sim.medium import SoftwareMedium
|
||||
from pm3py.sim.iso14443a import Reader14443A, _compute_bcc
|
||||
from pm3py.sim.ndef import NfcType2Tag, NfcType4Tag
|
||||
|
||||
|
||||
def run(coro):
|
||||
return asyncio.get_event_loop().run_until_complete(coro)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# NFC Type 2 Tag (extends Tag14443A_3)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestNfcType2Tag:
|
||||
def test_atqa_sak(self):
|
||||
tag = NfcType2Tag(uid=b"\x04\x01\x02\x03\x04\x05\x06", ndef_message=b"")
|
||||
run(tag.power_on())
|
||||
resp = run(tag.handle_frame(RFFrame.from_hex("26")))
|
||||
assert resp.data == b"\x44\x00" # NTAG ATQA
|
||||
|
||||
# SELECT (7-byte UID needs cascade)
|
||||
uid = b"\x04\x01\x02\x03\x04\x05\x06"
|
||||
ct_uid = b"\x88" + uid[0:3]
|
||||
bcc1 = _compute_bcc(ct_uid)
|
||||
run(tag.handle_frame(RFFrame.from_bytes(b"\x93\x70" + ct_uid + bytes([bcc1]))))
|
||||
cl2_uid = uid[3:7]
|
||||
bcc2 = _compute_bcc(cl2_uid)
|
||||
resp = run(tag.handle_frame(RFFrame.from_bytes(b"\x95\x70" + cl2_uid + bytes([bcc2]))))
|
||||
assert resp.data[0] & 0x20 == 0 # SAK bit 5 = 0 (no ISO-DEP)
|
||||
|
||||
def test_read_returns_4_pages(self):
|
||||
"""READ command returns 16 bytes (4 pages of 4 bytes)."""
|
||||
tag = NfcType2Tag(uid=b"\x04\x01\x02\x03\x04\x05\x06",
|
||||
ndef_message=b"\xD1\x01\x04\x54\x02enHi")
|
||||
run(tag.power_on())
|
||||
self._select(tag)
|
||||
|
||||
# READ page 0
|
||||
resp = run(tag.handle_frame(RFFrame.from_bytes(b"\x30\x00")))
|
||||
assert resp is not None
|
||||
assert len(resp.data) == 16 # 4 pages
|
||||
|
||||
def test_cc_block_at_page_3(self):
|
||||
"""Page 3 is the capability container."""
|
||||
tag = NfcType2Tag(uid=b"\x04\x01\x02\x03\x04\x05\x06",
|
||||
ndef_message=b"\xD1\x01\x04\x54\x02enHi",
|
||||
total_pages=45) # NTAG213-like
|
||||
run(tag.power_on())
|
||||
self._select(tag)
|
||||
|
||||
# READ page 3 (CC is in pages 3)
|
||||
resp = run(tag.handle_frame(RFFrame.from_bytes(b"\x30\x03")))
|
||||
cc = resp.data[0:4]
|
||||
assert cc[0] == 0xE1 # NDEF magic
|
||||
assert cc[1] == 0x10 # version 1.0
|
||||
assert cc[2] > 0 # size (in 8-byte units)
|
||||
assert cc[3] == 0x00 # read/write access
|
||||
|
||||
def test_ndef_data_in_pages_4_plus(self):
|
||||
"""NDEF message stored starting at page 4 as TLV."""
|
||||
msg = b"\xD1\x01\x04\x54\x02enHi"
|
||||
tag = NfcType2Tag(uid=b"\x04\x01\x02\x03\x04\x05\x06", ndef_message=msg)
|
||||
run(tag.power_on())
|
||||
self._select(tag)
|
||||
|
||||
# READ page 4
|
||||
resp = run(tag.handle_frame(RFFrame.from_bytes(b"\x30\x04")))
|
||||
# TLV: type=0x03 (NDEF), length, message...
|
||||
assert resp.data[0] == 0x03 # NDEF TLV type
|
||||
assert resp.data[1] == len(msg)
|
||||
|
||||
def test_write_page(self):
|
||||
"""WRITE command writes 4 bytes to a page."""
|
||||
tag = NfcType2Tag(uid=b"\x04\x01\x02\x03\x04\x05\x06", ndef_message=b"")
|
||||
run(tag.power_on())
|
||||
self._select(tag)
|
||||
|
||||
# WRITE page 4
|
||||
resp = run(tag.handle_frame(RFFrame.from_bytes(b"\xA2\x04\xDE\xAD\xBE\xEF")))
|
||||
assert resp is not None # ACK
|
||||
|
||||
# Read back
|
||||
resp = run(tag.handle_frame(RFFrame.from_bytes(b"\x30\x04")))
|
||||
assert resp.data[0:4] == b"\xDE\xAD\xBE\xEF"
|
||||
|
||||
def _select(self, tag):
|
||||
uid = tag._uid
|
||||
run(tag.handle_frame(RFFrame.from_hex("26")))
|
||||
ct_uid = b"\x88" + uid[0:3]
|
||||
bcc1 = _compute_bcc(ct_uid)
|
||||
run(tag.handle_frame(RFFrame.from_bytes(b"\x93\x70" + ct_uid + bytes([bcc1]))))
|
||||
cl2_uid = uid[3:7]
|
||||
bcc2 = _compute_bcc(cl2_uid)
|
||||
run(tag.handle_frame(RFFrame.from_bytes(b"\x95\x70" + cl2_uid + bytes([bcc2]))))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# NFC Type 4 Tag (extends Tag14443A_4)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestNfcType4Tag:
|
||||
def test_sak_indicates_isodep(self):
|
||||
tag = NfcType4Tag(uid=b"\x04\x01\x02\x03", ndef_message=b"")
|
||||
run(tag.power_on())
|
||||
run(tag.handle_frame(RFFrame.from_hex("26")))
|
||||
uid = b"\x04\x01\x02\x03"
|
||||
bcc = _compute_bcc(uid)
|
||||
resp = run(tag.handle_frame(RFFrame.from_bytes(b"\x93\x70" + uid + bytes([bcc]))))
|
||||
assert resp.data[0] & 0x20 != 0 # SAK bit 5 = 1
|
||||
|
||||
def test_rats_returns_ats(self):
|
||||
tag = NfcType4Tag(uid=b"\x04\x01\x02\x03", ndef_message=b"")
|
||||
run(tag.power_on())
|
||||
self._select_and_rats(tag)
|
||||
assert tag.state == "PROTOCOL"
|
||||
|
||||
def test_select_ndef_application(self):
|
||||
"""SELECT NDEF application by AID D2760000850101."""
|
||||
tag = NfcType4Tag(uid=b"\x04\x01\x02\x03",
|
||||
ndef_message=b"\xD1\x01\x04\x54\x02enHi")
|
||||
run(tag.power_on())
|
||||
self._select_and_rats(tag)
|
||||
|
||||
# SELECT AID via I-block
|
||||
select_apdu = b"\x00\xA4\x04\x00\x07\xD2\x76\x00\x00\x85\x01\x01"
|
||||
iblock = b"\x02" + select_apdu
|
||||
resp = run(tag.handle_frame(RFFrame.from_bytes(iblock)))
|
||||
assert resp is not None
|
||||
# Should get 9000 (success)
|
||||
assert resp.data[-2:] == b"\x90\x00" or resp.data[1:3] == b"\x90\x00"
|
||||
|
||||
def test_select_cc_file(self):
|
||||
"""SELECT CC file (E103) then READ BINARY."""
|
||||
tag = NfcType4Tag(uid=b"\x04\x01\x02\x03",
|
||||
ndef_message=b"\xD1\x01\x04\x54\x02enHi")
|
||||
run(tag.power_on())
|
||||
self._select_and_rats(tag)
|
||||
|
||||
# Select NDEF app
|
||||
self._send_apdu(tag, b"\x00\xA4\x04\x00\x07\xD2\x76\x00\x00\x85\x01\x01")
|
||||
|
||||
# Select CC file (E103)
|
||||
resp = self._send_apdu(tag, b"\x00\xA4\x00\x0C\x02\xE1\x03")
|
||||
assert resp is not None
|
||||
|
||||
# READ BINARY offset=0, length=15
|
||||
resp = self._send_apdu(tag, b"\x00\xB0\x00\x00\x0F")
|
||||
assert resp is not None
|
||||
# CC file starts with CCLEN(2) + version + ...
|
||||
payload = resp[:-2] # strip SW
|
||||
assert len(payload) >= 7
|
||||
|
||||
def test_read_ndef_file(self):
|
||||
"""SELECT NDEF file (E104) then READ BINARY to get NDEF message."""
|
||||
msg = b"\xD1\x01\x04\x54\x02enHi"
|
||||
tag = NfcType4Tag(uid=b"\x04\x01\x02\x03", ndef_message=msg)
|
||||
run(tag.power_on())
|
||||
self._select_and_rats(tag)
|
||||
|
||||
# Select NDEF app
|
||||
self._send_apdu(tag, b"\x00\xA4\x04\x00\x07\xD2\x76\x00\x00\x85\x01\x01")
|
||||
# Select NDEF file (E104)
|
||||
self._send_apdu(tag, b"\x00\xA4\x00\x0C\x02\xE1\x04")
|
||||
# READ BINARY — first 2 bytes are NDEF length
|
||||
resp = self._send_apdu(tag, b"\x00\xB0\x00\x00\x20")
|
||||
payload = resp[:-2]
|
||||
nlen = (payload[0] << 8) | payload[1]
|
||||
assert nlen == len(msg)
|
||||
assert payload[2:2 + nlen] == msg
|
||||
|
||||
def _select_and_rats(self, tag):
|
||||
run(tag.handle_frame(RFFrame.from_hex("26")))
|
||||
uid = tag._uid[:4]
|
||||
bcc = _compute_bcc(uid)
|
||||
run(tag.handle_frame(RFFrame.from_bytes(b"\x93\x70" + uid + bytes([bcc]))))
|
||||
run(tag.handle_frame(RFFrame.from_hex("E050")))
|
||||
|
||||
def _send_apdu(self, tag, apdu: bytes) -> bytes:
|
||||
"""Send APDU via I-block, return response payload (including SW)."""
|
||||
pcb = 0x02
|
||||
resp = run(tag.handle_frame(RFFrame.from_bytes(bytes([pcb]) + apdu)))
|
||||
if resp is None:
|
||||
return b""
|
||||
return resp.data[1:] # strip PCB
|
||||
Reference in New Issue
Block a user