From f9e56e32ef769b6c844f5ed55138c0b3aeae8a92 Mon Sep 17 00:00:00 2001 From: michael Date: Tue, 14 Jul 2026 09:01:52 -0700 Subject: [PATCH] feat(registers): ICODE feature-flags decoder (GET NXP SYSTEM INFO) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add IcodeFeatureFlags (Register) for the 16-bit ICODE feature word reported by GET NXP SYSTEM INFO (ICODE 3 Table 113; DNA/SLIX2 report a subset). Read-only capability decoder — decode a captured word to see what a tag supports. Fields mirror the existing _FEATURE_* constants one-to-one (asserted in a test). 3 new tests. Co-Authored-By: Claude Opus 4.8 --- pm3py/sim/__init__.py | 4 +-- pm3py/transponders/hf/iso15693/nxp/icode3.py | 28 ++++++++++++++++++++ tests/test_bitfield.py | 24 +++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/pm3py/sim/__init__.py b/pm3py/sim/__init__.py index c77ec2d..b699fe5 100644 --- a/pm3py/sim/__init__.py +++ b/pm3py/sim/__init__.py @@ -46,7 +46,7 @@ from pm3py.transponders.hf.iso15693.type5 import NfcType5Tag, ndef_text, ndef_ur from pm3py.transponders.hf.iso15693.nxp.nxp_icode import NxpIcodeTag from pm3py.transponders.hf.iso15693.nxp.icode_slix import IcodeSlixTag from pm3py.transponders.hf.iso15693.nxp.icode_slix2 import IcodeSlix2Tag -from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag +from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag, IcodeFeatureFlags from pm3py.transponders.hf.iso15693.nxp.icode_dna import IcodeDnaTag from pm3py.transponders.hf.iso15693.nxp.auth_aes import NxpKeyPrivileges from pm3py.transponders.hf.iso15693.nxp.ntag5_platform import ( @@ -115,7 +115,7 @@ __all__ = [ "Icode3Tag", "IcodeDnaTag", "Ntag5PlatformTag", "Ntag5SwitchTag", "Ntag5LinkTag", "Ntag5BoostTag", "Ntag5StatusReg", "Ntag5ConfigReg", - "NxpKeyPrivileges", + "NxpKeyPrivileges", "IcodeFeatureFlags", "DesfireTag", "DesfireReader", "xEM", "xNT", "xM1", "FlexDF", "NExT", "MagicMifareClassicTag", "MutationFuzzer", "GrammarFuzzer", diff --git a/pm3py/transponders/hf/iso15693/nxp/icode3.py b/pm3py/transponders/hf/iso15693/nxp/icode3.py index d1a8ade..f48402c 100644 --- a/pm3py/transponders/hf/iso15693/nxp/icode3.py +++ b/pm3py/transponders/hf/iso15693/nxp/icode3.py @@ -16,6 +16,7 @@ import os import struct from pm3py.sim.frame import RFFrame +from pm3py.transponders.bitfield import BitField, Register from .icode_slix2 import IcodeSlix2Tag, PWD_READ, PWD_WRITE from .nxp_icode import NXP_MFG @@ -44,6 +45,33 @@ _FEATURE_WRITE_CID = 1 << 14 _FEATURE_HIGH_DATA_RATES = 1 << 15 +class IcodeFeatureFlags(Register): + """The NXP ICODE feature-flags word reported by GET NXP SYSTEM INFO (ICODE 3 Table 113; + ICODE DNA / SLIX2 report a subset). Read-only capability bits — decode a word you captured + to see what a tag supports. 16-bit little-endian on the wire, but treated here as one word + (bit N is feature flag N).""" + + _width_bits = 16 + _label = "ICODE feature flags" + + um_pp = BitField(0, doc="user-memory password protection") + counter = BitField(1, doc="single-block read counter") + eas_id = BitField(2, doc="EAS ID") + eas_pp = BitField(3, doc="EAS password protection") + afi_pp = BitField(4, doc="AFI password protection") + inventory_read_ext = BitField(5, doc="extended INVENTORY READ") + eas_ir = BitField(6, doc="EAS in INVENTORY READ") + cid = BitField(7, doc="configurable customer ID") + originality_sig = BitField(8, doc="ECC originality signature") + tagtamper = BitField(9, doc="tag-tamper feature") + p_quiet = BitField(10, doc="persistent QUIET state") + nfc_mirror = BitField(11, doc="NFC ASCII mirror") + privacy = BitField(12, doc="privacy mode") + destroy = BitField(13, doc="DESTROY command") + write_cid = BitField(14, doc="writeable customer ID") + high_data_rates = BitField(15, doc="high data-rate support") + + class Icode3Tag(IcodeSlix2Tag): """ICODE 3 transponder (SL2S3003 / SL2S3003TT). diff --git a/tests/test_bitfield.py b/tests/test_bitfield.py index ad37f7d..b387bf3 100644 --- a/tests/test_bitfield.py +++ b/tests/test_bitfield.py @@ -10,6 +10,7 @@ from pm3py.sim import ( NtagI2CConfig, NtagI2CSession, NtagI2CPtI2C, NT3H2111, NT3H2211, Ntag5StatusReg, Ntag5ConfigReg, Ntag5LinkTag, NxpKeyPrivileges, IcodeDnaTag, + IcodeFeatureFlags, ) @@ -467,3 +468,26 @@ class TestNxpKeyPrivileges: text = repr(NxpKeyPrivileges.build(read=True, destroy=True)) assert "NXP key privileges" in text assert "read" in text and "destroy" in text + + +class TestIcodeFeatureFlags: + def test_bits_match_module_constants(self): + import pm3py.transponders.hf.iso15693.nxp.icode3 as ic3 + # every declared field must line up with the _FEATURE_* constant of the same name + for name, bf in IcodeFeatureFlags.fields(): + const = getattr(ic3, f"_FEATURE_{name.upper()}") + assert (1 << bf.lo) == const, name + + def test_decode(self): + # um_pp(0) + counter(1) + originality_sig(8) + high_data_rates(15) + w = (1 << 0) | (1 << 1) | (1 << 8) | (1 << 15) + f = IcodeFeatureFlags(w) + assert f.um_pp is True and f.counter is True + assert f.originality_sig is True and f.high_data_rates is True + assert f.privacy is False + assert int(f) == w + + def test_repr(self): + text = repr(IcodeFeatureFlags(0x0001)) + assert "ICODE feature flags" in text + assert "um_pp" in text