"""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("= 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