Files
pm3py/tests/test_sim_icode_slix.py
michael c1ab980036 refactor: remove all backward-compat shims from sim/
Deletes 24 shim files from sim/. All test imports now point directly
to canonical transponders/ and trace/ paths. sim/ contains only
infrastructure (15 files): frame, memory, transponder ABC, reader ABC,
medium, sim_session, table_compiler, replay, relay, fuzzer, pm3medium,
mcu_bridge, mcu_protocol, dual_session, __init__.

751 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 20:56:40 -07:00

342 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Tests for ICODE SLIX (SL2S2002) transponder model."""
import asyncio
import struct
import pytest
from pm3py.sim.frame import RFFrame
from pm3py.transponders.hf.iso15693.nxp.nxp_icode import (
CMD_SET_EAS, CMD_RESET_EAS, CMD_LOCK_EAS, CMD_EAS_ALARM,
CMD_PASSWORD_PROTECT_EAS_AFI, CMD_WRITE_EAS_ID,
CMD_INVENTORY_READ, CMD_FAST_INVENTORY_READ,
)
def run(coro):
return asyncio.get_event_loop().run_until_complete(coro)
# ---------------------------------------------------------------------------
# Helper: GET_RANDOM + XOR password
# ---------------------------------------------------------------------------
def _xor_password(tag, pwd_value: int) -> bytes:
"""Get random from tag, return XOR'd password bytes."""
resp = run(tag.handle_frame(RFFrame.from_bytes(bytes([0x02, 0xB2, 0x04]))))
r = resp.data[1:3]
pwd = struct.pack("<I", pwd_value)
return bytes([pwd[0] ^ r[0], pwd[1] ^ r[1], pwd[2] ^ r[0], pwd[3] ^ r[1]])
def _authenticate(tag, pwd_id: int, pwd_value: int):
"""GET_RANDOM + SET_PASSWORD for a given password ID."""
xored = _xor_password(tag, pwd_value)
cmd = bytes([0x02, 0xB3, 0x04, pwd_id]) + xored
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00, f"auth failed for pwd_id {pwd_id:#x}"
PWD_EAS_AFI = 0x10
# ---------------------------------------------------------------------------
# Basic SLIX properties
# ---------------------------------------------------------------------------
class TestIcodeSlixBasics:
def test_import(self):
"""IcodeSlixTag can be imported."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
def test_inherits_nxp_icode(self):
"""SLIX inherits from NxpIcodeTag."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
from pm3py.transponders.hf.iso15693.nxp.nxp_icode import NxpIcodeTag
assert issubclass(IcodeSlixTag, NxpIcodeTag)
def test_default_28_blocks(self):
"""SLIX defaults to 28 blocks × 4 bytes."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
assert tag._num_blocks == 28
assert tag._block_size == 4
def test_responds_to_inventory(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
inv = bytes([0x26, 0x01, 0x00])
resp = run(tag.handle_frame(RFFrame.from_bytes(inv)))
assert resp is not None
def test_read_write_block(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
# Write block 5
cmd = bytes([0x02, 0x21, 0x05, 0xAA, 0xBB, 0xCC, 0xDD])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
# Read it back
cmd = bytes([0x02, 0x20, 0x05])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[1:5] == bytes([0xAA, 0xBB, 0xCC, 0xDD])
# ---------------------------------------------------------------------------
# Single password (EAS/AFI only)
# ---------------------------------------------------------------------------
class TestIcodeSlixPassword:
def test_set_password_eas_afi(self):
"""SET_PASSWORD with correct EAS/AFI password succeeds."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4),
eas_afi_password=0xAABBCCDD)
run(tag.power_on())
_authenticate(tag, PWD_EAS_AFI, 0xAABBCCDD)
def test_set_password_wrong(self):
"""SET_PASSWORD with wrong password fails."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4),
eas_afi_password=0xAABBCCDD)
run(tag.power_on())
xored = _xor_password(tag, 0x00000000)
cmd = bytes([0x02, 0xB3, 0x04, PWD_EAS_AFI]) + xored
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] & 0x01
def test_write_password(self):
"""WRITE_PASSWORD changes the EAS/AFI password."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4),
eas_afi_password=0xAABBCCDD)
run(tag.power_on())
_authenticate(tag, PWD_EAS_AFI, 0xAABBCCDD)
new_xored = _xor_password(tag, 0x11223344)
cmd = bytes([0x02, 0xB4, 0x04, PWD_EAS_AFI]) + new_xored
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
# New password works
_authenticate(tag, PWD_EAS_AFI, 0x11223344)
def test_lock_password(self):
"""LOCK_PASSWORD prevents WRITE_PASSWORD."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4),
eas_afi_password=0xAABBCCDD)
run(tag.power_on())
_authenticate(tag, PWD_EAS_AFI, 0xAABBCCDD)
cmd = bytes([0x02, 0xB5, 0x04, PWD_EAS_AFI])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
# WRITE_PASSWORD now fails
_authenticate(tag, PWD_EAS_AFI, 0xAABBCCDD)
new_xored = _xor_password(tag, 0x11223344)
cmd = bytes([0x02, 0xB4, 0x04, PWD_EAS_AFI]) + new_xored
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] & 0x01
def test_no_privacy_password(self):
"""SLIX has no privacy — only EAS/AFI password accepted."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4),
eas_afi_password=0xAABBCCDD)
run(tag.power_on())
# Try to auth with privacy pwd_id — should fail
xored = _xor_password(tag, 0xAABBCCDD)
cmd = bytes([0x02, 0xB3, 0x04, 0x04]) + xored # pwd_id=0x04 (privacy)
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] & 0x01
# ---------------------------------------------------------------------------
# EAS subsystem
# ---------------------------------------------------------------------------
class TestIcodeSlixEas:
def test_set_eas(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
cmd = bytes([0x02, CMD_SET_EAS, 0x04])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
assert tag.eas_enabled
def test_reset_eas(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
tag.set_eas(True)
cmd = bytes([0x02, CMD_RESET_EAS, 0x04])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
assert not tag.eas_enabled
def test_eas_alarm(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
tag.set_eas(True)
cmd = bytes([0x02, CMD_EAS_ALARM, 0x04])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp is not None
assert resp.data[0] == 0x00
assert len(resp.data) >= 33
def test_eas_alarm_silent_when_disabled(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
cmd = bytes([0x02, CMD_EAS_ALARM, 0x04])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp is None
def test_lock_eas(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
tag.set_eas(True)
cmd = bytes([0x02, CMD_LOCK_EAS, 0x04])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
# Can't change now
cmd = bytes([0x02, CMD_RESET_EAS, 0x04])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] & 0x01
def test_password_protect_eas_afi(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4),
eas_afi_password=0xAABBCCDD)
run(tag.power_on())
_authenticate(tag, PWD_EAS_AFI, 0xAABBCCDD)
cmd = bytes([0x02, CMD_PASSWORD_PROTECT_EAS_AFI, 0x04])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
assert tag._eas_password_protected
def test_write_eas_id(self):
"""WRITE EAS ID is SLIX2-only (EAS Selective per AN11809)."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag
tag = IcodeSlix2Tag(uid=b"\xE0\x04\x02" + bytes(5),
eas_afi_password=0xAABBCCDD)
run(tag.power_on())
_authenticate(tag, PWD_EAS_AFI, 0xAABBCCDD)
cmd = bytes([0x02, CMD_WRITE_EAS_ID, 0x04, 0x34, 0x12])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
assert tag._eas_id == 0x1234
def test_slix_rejects_write_eas_id(self):
"""SLIX (SL2S2002) does not support WRITE EAS ID (0xA7)."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
cmd = bytes([0x02, CMD_WRITE_EAS_ID, 0x04, 0x34, 0x12])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp is None
# ---------------------------------------------------------------------------
# INVENTORY READ
# ---------------------------------------------------------------------------
class TestIcodeSlixInventoryRead:
def test_inventory_read(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10\x03\x04\x05\x06")
run(tag.power_on())
cmd = bytes([0x06, CMD_INVENTORY_READ, 0x04, 0x00, 0x00, 0x00])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp is not None
assert resp.data[0] == 0x00
def test_fast_inventory_read(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10\x03\x04\x05\x06")
run(tag.power_on())
cmd = bytes([0x06, CMD_FAST_INVENTORY_READ, 0x04, 0x00, 0x00, 0x00])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp is not None
assert resp.data[0] == 0x00
# ---------------------------------------------------------------------------
# SLIX2 still works after refactoring
# ---------------------------------------------------------------------------
class TestSlix2StillWorks:
"""Verify SLIX2 inherits from SLIX and retains all features."""
def test_slix2_inherits_slix(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag
assert issubclass(IcodeSlix2Tag, IcodeSlixTag)
def test_slix2_privacy_still_works(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag
tag = IcodeSlix2Tag(uid=b"\xE0\x04\x02" + bytes(5),
privacy_password=0xDEADBEEF)
run(tag.power_on())
tag.enter_privacy_mode()
assert tag.privacy_mode
def test_slix2_destroy_still_works(self):
from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag
tag = IcodeSlix2Tag(uid=b"\xE0\x04\x02" + bytes(5),
destroy_password=0xDEAD1234)
run(tag.power_on())
xored = _xor_password(tag, 0xDEAD1234)
cmd = bytes([0x02, 0xB9, 0x04]) + xored
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
# Dead
inv = bytes([0x26, 0x01, 0x00])
assert run(tag.handle_frame(RFFrame.from_bytes(inv))) is None
def test_slix2_eas_inherited_from_slix(self):
"""SLIX2 EAS works via inheritance from SLIX."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag
tag = IcodeSlix2Tag(uid=b"\xE0\x04\x02" + bytes(5))
run(tag.power_on())
cmd = bytes([0x02, CMD_SET_EAS, 0x04])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp.data[0] == 0x00
assert tag.eas_enabled
# ---------------------------------------------------------------------------
# SLIX should NOT have SLIX2-only features
# ---------------------------------------------------------------------------
class TestSlixLacksPrivacy:
def test_no_enable_privacy(self):
"""SLIX does not respond to ENABLE PRIVACY (0xBA)."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
cmd = bytes([0x02, 0xBA, 0x04, 0x00, 0x00, 0x00, 0x00])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
# Should be None (unhandled) or error
assert resp is None or resp.data[0] & 0x01
def test_no_destroy(self):
"""SLIX does not respond to DESTROY (0xB9)."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
cmd = bytes([0x02, 0xB9, 0x04, 0x00, 0x00, 0x00, 0x00])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp is None or resp.data[0] & 0x01
def test_no_protect_page(self):
"""SLIX does not respond to PROTECT PAGE (0xB6)."""
from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag
tag = IcodeSlixTag(uid=b"\xE0\x04\x01\x10" + bytes(4))
run(tag.power_on())
cmd = bytes([0x02, 0xB6, 0x04, 0x08, 0x01])
resp = run(tag.handle_frame(RFFrame.from_bytes(cmd)))
assert resp is None or resp.data[0] & 0x01