"""Tests for ICODE 3 (SL2S3003) 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_READ_CONFIG, CMD_WRITE_CONFIG, CMD_READ_TT, ) def run(coro): return asyncio.get_event_loop().run_until_complete(coro) def _xor_password(tag, pwd_value: int) -> bytes: resp = run(tag.handle_frame(RFFrame.from_bytes(bytes([0x02, 0xB2, 0x04])))) r = resp.data[1:3] pwd = struct.pack("= 5 # flags + 4 bytes def test_write_config(self): """WRITE CONFIG writes to config memory.""" from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag tag = Icode3Tag(uid=b"\xE0\x04\x01\x20" + bytes(4)) run(tag.power_on()) # Write to config block 18 (EAS ID) cmd = bytes([0x02, CMD_WRITE_CONFIG, 0x04, 18, 0x34, 0x12, 0x00, 0x00]) resp = run(tag.handle_frame(RFFrame.from_bytes(cmd))) assert resp is not None assert resp.data[0] == 0x00 # Read it back cmd = bytes([0x02, CMD_READ_CONFIG, 0x04, 18, 0x00]) resp = run(tag.handle_frame(RFFrame.from_bytes(cmd))) assert resp.data[1:3] == bytes([0x34, 0x12]) def test_read_config_passwords_masked(self): """READ CONFIG masks password blocks with 0x00.""" from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag tag = Icode3Tag(uid=b"\xE0\x04\x01\x20" + bytes(4), read_password=0xDEADBEEF) run(tag.power_on()) # Read config block 42 (READ_PWD) — should be masked cmd = bytes([0x02, CMD_READ_CONFIG, 0x04, 42, 0x00]) resp = run(tag.handle_frame(RFFrame.from_bytes(cmd))) assert resp is not None assert resp.data[0] == 0x00 assert resp.data[1:5] == bytes(4) # masked with 0x00 def test_write_config_requires_auth_when_protected(self): """WRITE CONFIG fails without config password when protected.""" from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag tag = Icode3Tag(uid=b"\xE0\x04\x01\x20" + bytes(4), config_password=0xAABBCCDD) run(tag.power_on()) tag._config_password_protected = True cmd = bytes([0x02, CMD_WRITE_CONFIG, 0x04, 18, 0x34, 0x12, 0x00, 0x00]) resp = run(tag.handle_frame(RFFrame.from_bytes(cmd))) assert resp.data[0] & 0x01 def test_read_config_multiple_blocks(self): """READ CONFIG can read multiple blocks.""" from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag tag = Icode3Tag(uid=b"\xE0\x04\x01\x20" + bytes(4)) run(tag.power_on()) # Read 3 blocks starting at block 16 cmd = bytes([0x02, CMD_READ_CONFIG, 0x04, 16, 0x02]) resp = run(tag.handle_frame(RFFrame.from_bytes(cmd))) assert resp is not None assert resp.data[0] == 0x00 assert len(resp.data) >= 13 # flags + 3 * 4 bytes # --------------------------------------------------------------------------- # READ TT (0xC4) — TagTamper # --------------------------------------------------------------------------- class TestIcode3TagTamper: def test_read_tt_closed(self): """READ TT returns closed status by default.""" from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag tag = Icode3Tag(uid=b"\xE0\x04\x01\x20" + bytes(4), tag_tamper=True) run(tag.power_on()) cmd = bytes([0x02, CMD_READ_TT, 0x04]) resp = run(tag.handle_frame(RFFrame.from_bytes(cmd))) assert resp is not None assert resp.data[0] == 0x00 # actual_status=0x43('C'), stored_status=0x43('C') assert resp.data[1] == 0x43 # 'C' = Closed assert resp.data[2] == 0x43 def test_read_tt_open(self): """READ TT returns open status when tampered.""" from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag tag = Icode3Tag(uid=b"\xE0\x04\x01\x20" + bytes(4), tag_tamper=True) run(tag.power_on()) tag._tt_status_actual = 0x4F # 'O' = Open cmd = bytes([0x02, CMD_READ_TT, 0x04]) resp = run(tag.handle_frame(RFFrame.from_bytes(cmd))) assert resp.data[1] == 0x4F # 'O' def test_read_tt_not_supported_without_flag(self): """READ TT returns error when tag_tamper=False.""" from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag tag = Icode3Tag(uid=b"\xE0\x04\x01\x20" + bytes(4), tag_tamper=False) run(tag.power_on()) cmd = bytes([0x02, CMD_READ_TT, 0x04]) resp = run(tag.handle_frame(RFFrame.from_bytes(cmd))) assert resp is None or resp.data[0] & 0x01 # --------------------------------------------------------------------------- # GET NXP SYSTEM INFO feature flags # --------------------------------------------------------------------------- class TestIcode3SystemInfo: def test_feature_flags(self): """GET NXP SYSTEM INFO returns ICODE 3 feature flags.""" from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag tag = Icode3Tag(uid=b"\xE0\x04\x01\x20" + bytes(4)) run(tag.power_on()) cmd = bytes([0x02, 0xAB, 0x04]) resp = run(tag.handle_frame(RFFrame.from_bytes(cmd))) assert resp is not None assert resp.data[0] == 0x00 # Feature flags should indicate counter, privacy, destroy, etc. feature_flags = struct.unpack_from("