feat(registers): NTAG5 STATUS_REG / CONFIG_REG self-describing views
Add Ntag5StatusReg and Ntag5ConfigReg (Register subclasses) and bind them as tag.status_reg / tag.config_reg on the NTAG 5 platform. Purely additive: every existing per-field accessor (disable_nfc, arbiter_mode, sram_enable, ...) stays, and the config_reg setter re-syncs the _arbiter_mode / _sram_enabled sibling caches the per-field setters keep, so the sim internals are untouched. - Ntag5StatusReg (0xA0): field/VCC/boot/lock/EEPROM/SRAM status bits - Ntag5ConfigReg (0xA1): disable_nfc, arbiter_mode (normal/mirror/ pass-through/PHDC), sram_enable, config_pt_transfer_dir Bit layout taken straight from the existing hand-rolled accessors, so the register view decodes the very same bytes (cross-checked in tests). 5 new tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -59,9 +59,10 @@ pm3py/
|
|||||||
transponders/ # tag/transponder models (extracted from sim/)
|
transponders/ # tag/transponder models (extracted from sim/)
|
||||||
bitfield.py # BitField descriptor + Register base — self-describing config/
|
bitfield.py # BitField descriptor + Register base — self-describing config/
|
||||||
# frame registers (T5577, EM4100, NTAG21x, Ultralight EV1/C, MFC
|
# frame registers (T5577, EM4100, NTAG21x, Ultralight EV1/C, MFC
|
||||||
# access bits, NTAG I2C NC_REG/NS_REG/REG_LOCK/PT_I2C). Fields are
|
# access bits, NTAG I2C NC_REG/NS_REG/REG_LOCK/PT_I2C, NTAG5
|
||||||
# documented attrs -> shell completion + repr decode table.
|
# STATUS_REG/CONFIG_REG). Fields are documented attrs -> shell
|
||||||
# Layouts cross-checked vs the firmware submodule + NXP datasheets.
|
# completion + repr decode table. Layouts cross-checked vs the
|
||||||
|
# firmware submodule + NXP datasheets.
|
||||||
hf/iso14443a/ # Tag14443A_3/4, MifareClassic, DESFire, NfcType2/4
|
hf/iso14443a/ # Tag14443A_3/4, MifareClassic, DESFire, NfcType2/4
|
||||||
# nxp/type2,ntag21x,ultralight,ntag_i2c
|
# nxp/type2,ntag21x,ultralight,ntag_i2c
|
||||||
# (NTAG210-216, Ultralight/C/EV1, NTAG I2C plus)
|
# (NTAG210-216, Ultralight/C/EV1, NTAG I2C plus)
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ 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.icode_slix2 import IcodeSlix2Tag
|
||||||
from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag
|
from pm3py.transponders.hf.iso15693.nxp.icode3 import Icode3Tag
|
||||||
from pm3py.transponders.hf.iso15693.nxp.icode_dna import IcodeDnaTag
|
from pm3py.transponders.hf.iso15693.nxp.icode_dna import IcodeDnaTag
|
||||||
from pm3py.transponders.hf.iso15693.nxp.ntag5_platform import Ntag5PlatformTag
|
from pm3py.transponders.hf.iso15693.nxp.ntag5_platform import (
|
||||||
|
Ntag5PlatformTag, Ntag5StatusReg, Ntag5ConfigReg,
|
||||||
|
)
|
||||||
from pm3py.transponders.hf.iso15693.nxp.ntag5_switch import Ntag5SwitchTag
|
from pm3py.transponders.hf.iso15693.nxp.ntag5_switch import Ntag5SwitchTag
|
||||||
from pm3py.transponders.hf.iso15693.nxp.ntag5_link import Ntag5LinkTag
|
from pm3py.transponders.hf.iso15693.nxp.ntag5_link import Ntag5LinkTag
|
||||||
from pm3py.transponders.hf.iso15693.nxp.ntag5_boost import Ntag5BoostTag
|
from pm3py.transponders.hf.iso15693.nxp.ntag5_boost import Ntag5BoostTag
|
||||||
@@ -111,6 +113,7 @@ __all__ = [
|
|||||||
"NxpIcodeTag", "IcodeSlixTag", "IcodeSlix2Tag",
|
"NxpIcodeTag", "IcodeSlixTag", "IcodeSlix2Tag",
|
||||||
"Icode3Tag", "IcodeDnaTag",
|
"Icode3Tag", "IcodeDnaTag",
|
||||||
"Ntag5PlatformTag", "Ntag5SwitchTag", "Ntag5LinkTag", "Ntag5BoostTag",
|
"Ntag5PlatformTag", "Ntag5SwitchTag", "Ntag5LinkTag", "Ntag5BoostTag",
|
||||||
|
"Ntag5StatusReg", "Ntag5ConfigReg",
|
||||||
"DesfireTag", "DesfireReader",
|
"DesfireTag", "DesfireReader",
|
||||||
"xEM", "xNT", "xM1", "FlexDF", "NExT", "MagicMifareClassicTag",
|
"xEM", "xNT", "xM1", "FlexDF", "NExT", "MagicMifareClassicTag",
|
||||||
"MutationFuzzer", "GrammarFuzzer",
|
"MutationFuzzer", "GrammarFuzzer",
|
||||||
|
|||||||
@@ -22,11 +22,52 @@ from .auth_aes import NxpAesAuth
|
|||||||
from pm3py.sim.frame import RFFrame
|
from pm3py.sim.frame import RFFrame
|
||||||
from .icode_slix2 import IcodeSlix2Tag
|
from .icode_slix2 import IcodeSlix2Tag
|
||||||
from .nxp_icode import NXP_MFG
|
from .nxp_icode import NXP_MFG
|
||||||
|
from pm3py.transponders.bitfield import BitField, Register
|
||||||
|
|
||||||
# NTAG 5 AREA_1 password identifiers (NTP5210/NTA5332 datasheets)
|
# NTAG 5 AREA_1 password identifiers (NTP5210/NTA5332 datasheets)
|
||||||
PWD_AREA1_READ = 0x40
|
PWD_AREA1_READ = 0x40
|
||||||
PWD_AREA1_WRITE = 0x80
|
PWD_AREA1_WRITE = 0x80
|
||||||
|
|
||||||
|
_PT_DIR = {0: "I2C->NFC", 1: "NFC->I2C"}
|
||||||
|
|
||||||
|
|
||||||
|
class Ntag5StatusReg(Register):
|
||||||
|
"""NTAG 5 STATUS_REG (session register 0xA0) — the live interface/EEPROM/SRAM status. Read
|
||||||
|
via READ CONFIG; most bits are hardware status (read-only). Bytes 0-3 map MSB-first, so
|
||||||
|
STATUS_REG byte 0 is bits 31:24 and byte 1 is bits 23:16."""
|
||||||
|
|
||||||
|
_width_bits = 32
|
||||||
|
_label = "NTAG5 STATUS_REG (0xA0)"
|
||||||
|
|
||||||
|
# byte 0
|
||||||
|
eeprom_wr_busy = BitField(31, doc="status: EEPROM write in progress")
|
||||||
|
eeprom_wr_error = BitField(30, doc="status: EEPROM write/erase error")
|
||||||
|
sram_data_ready = BitField(29, doc="status: SRAM data ready to be read")
|
||||||
|
synch_block_write = BitField(28, doc="status: synchronised block write in progress")
|
||||||
|
synch_block_read = BitField(27, doc="status: synchronised block read in progress")
|
||||||
|
pt_transfer_dir = BitField(26, values=_PT_DIR, doc="live pass-through data-flow direction")
|
||||||
|
vcc_supply_ok = BitField(25, doc="status: VCC (I2C-side) supply present")
|
||||||
|
nfc_field_ok = BitField(24, doc="status: NFC field present and sufficient")
|
||||||
|
# byte 1
|
||||||
|
vcc_boot_ok = BitField(23, doc="status: booted from VCC supply")
|
||||||
|
nfc_boot_ok = BitField(22, doc="status: booted from the NFC field")
|
||||||
|
i2c_if_locked = BitField(17, doc="status: memory arbitration locked to I2C")
|
||||||
|
nfc_if_locked = BitField(16, doc="status: memory arbitration locked to NFC")
|
||||||
|
|
||||||
|
|
||||||
|
class Ntag5ConfigReg(Register):
|
||||||
|
"""NTAG 5 CONFIG_REG (session register 0xA1) — the writable NFC/I2C behaviour config. Byte 0
|
||||||
|
is bits 31:24."""
|
||||||
|
|
||||||
|
_width_bits = 32
|
||||||
|
_label = "NTAG5 CONFIG_REG (0xA1)"
|
||||||
|
|
||||||
|
disable_nfc = BitField(29, doc="silence the NFC interface (everything except INVENTORY)")
|
||||||
|
arbiter_mode = BitField(27, 26, doc="NFC/I2C memory arbitration",
|
||||||
|
values={0: "normal", 1: "mirror", 2: "pass-through", 3: "PHDC"})
|
||||||
|
sram_enable = BitField(25, doc="gate all SRAM access")
|
||||||
|
config_pt_transfer_dir = BitField(24, values=_PT_DIR, doc="pass-through data-flow direction")
|
||||||
|
|
||||||
|
|
||||||
class Ntag5PlatformTag(NxpAesAuth, IcodeSlix2Tag):
|
class Ntag5PlatformTag(NxpAesAuth, IcodeSlix2Tag):
|
||||||
"""NTAG 5 platform base — shared by Switch, Link, and Boost.
|
"""NTAG 5 platform base — shared by Switch, Link, and Boost.
|
||||||
@@ -230,6 +271,32 @@ class Ntag5PlatformTag(NxpAesAuth, IcodeSlix2Tag):
|
|||||||
def config_pt_transfer_dir(self, value: bool):
|
def config_pt_transfer_dir(self, value: bool):
|
||||||
self._set_config_bit(0, 0, value)
|
self._set_config_bit(0, 0, value)
|
||||||
|
|
||||||
|
# ----- self-describing register views (STATUS_REG / CONFIG_REG) -----
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_reg(self) -> Ntag5StatusReg:
|
||||||
|
"""STATUS_REG (0xA0) decoded as a self-describing :class:`Ntag5StatusReg`."""
|
||||||
|
return Ntag5StatusReg(int.from_bytes(self._status_reg, "big"))
|
||||||
|
|
||||||
|
@status_reg.setter
|
||||||
|
def status_reg(self, value: "Ntag5StatusReg | int") -> None:
|
||||||
|
self._status_reg[:] = int(value).to_bytes(4, "big")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config_reg(self) -> Ntag5ConfigReg:
|
||||||
|
"""CONFIG_REG (0xA1) decoded as a self-describing :class:`Ntag5ConfigReg`. Assign one
|
||||||
|
back (``tag.config_reg = cfg``) to program it."""
|
||||||
|
return Ntag5ConfigReg(int.from_bytes(self._config_reg, "big"))
|
||||||
|
|
||||||
|
@config_reg.setter
|
||||||
|
def config_reg(self, value: "Ntag5ConfigReg | int") -> None:
|
||||||
|
self._config_reg[:] = int(value).to_bytes(4, "big")
|
||||||
|
# keep the sibling caches the per-field setters maintain in sync
|
||||||
|
if hasattr(self, "_arbiter_mode"):
|
||||||
|
self._arbiter_mode = (self._config_reg[0] >> 2) & 0x03
|
||||||
|
if hasattr(self, "_sram_enabled"):
|
||||||
|
self._sram_enabled = bool(self._config_reg[0] & 0x02)
|
||||||
|
|
||||||
# ----- Session register control methods -----
|
# ----- Session register control methods -----
|
||||||
|
|
||||||
def set_nfc_field(self, active: bool):
|
def set_nfc_field(self, active: bool):
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from pm3py.sim import (
|
|||||||
MifareAccessConditions, MifareClassicTag,
|
MifareAccessConditions, MifareClassicTag,
|
||||||
EM4100Code, EM4100Tag,
|
EM4100Code, EM4100Tag,
|
||||||
NtagI2CConfig, NtagI2CSession, NtagI2CPtI2C, NT3H2111, NT3H2211,
|
NtagI2CConfig, NtagI2CSession, NtagI2CPtI2C, NT3H2111, NT3H2211,
|
||||||
|
Ntag5StatusReg, Ntag5ConfigReg, Ntag5LinkTag,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -375,3 +376,58 @@ class TestNtagI2CRegisters:
|
|||||||
assert "NTAG I2C config registers" in text
|
assert "NTAG I2C config registers" in text
|
||||||
assert "transfer_dir" in text and "NFC->I2C" in text
|
assert "transfer_dir" in text and "NFC->I2C" in text
|
||||||
assert "sram_mirror_block" in text
|
assert "sram_mirror_block" in text
|
||||||
|
|
||||||
|
|
||||||
|
class TestNtag5Registers:
|
||||||
|
def test_config_reg_field_positions(self):
|
||||||
|
cfg = Ntag5ConfigReg(0)
|
||||||
|
cfg.arbiter_mode = "pass-through" # 0b10 at byte0 bits 3:2
|
||||||
|
cfg.sram_enable = True # byte0 bit1
|
||||||
|
cfg.disable_nfc = True # byte0 bit5
|
||||||
|
cfg.config_pt_transfer_dir = "NFC->I2C" # byte0 bit0
|
||||||
|
# byte0 = 0b0010_1011 = 0x2B (disable_nfc bit5 + arb=10 at bit3 + sram bit1 + ptdir bit0)
|
||||||
|
assert bytes(cfg)[0] == 0x2B
|
||||||
|
assert cfg.arbiter_mode == "pass-through"
|
||||||
|
assert cfg.disable_nfc is True
|
||||||
|
|
||||||
|
def test_status_reg_decode(self):
|
||||||
|
# byte0: nfc_field_ok(0)+vcc_supply_ok(1)+eeprom_wr_busy(7); byte1: nfc_if_locked(0)
|
||||||
|
st = Ntag5StatusReg(int.from_bytes(bytes([0b1000_0011, 0x01, 0, 0]), "big"))
|
||||||
|
assert st.nfc_field_ok is True
|
||||||
|
assert st.vcc_supply_ok is True
|
||||||
|
assert st.eeprom_wr_busy is True
|
||||||
|
assert st.sram_data_ready is False
|
||||||
|
assert st.nfc_if_locked is True
|
||||||
|
|
||||||
|
def test_view_agrees_with_existing_accessors(self):
|
||||||
|
tag = Ntag5LinkTag()
|
||||||
|
tag.arbiter_mode = 2 # existing per-field setter
|
||||||
|
tag.disable_nfc = True
|
||||||
|
tag.sram_enable = True
|
||||||
|
# the register view must decode the very same bytes
|
||||||
|
assert tag.config_reg.arbiter_mode == "pass-through"
|
||||||
|
assert tag.config_reg.disable_nfc is True
|
||||||
|
assert tag.config_reg.sram_enable is True
|
||||||
|
|
||||||
|
def test_assign_config_reg_updates_accessors_and_siblings(self):
|
||||||
|
tag = Ntag5LinkTag()
|
||||||
|
cfg = Ntag5ConfigReg(0)
|
||||||
|
cfg.arbiter_mode = "mirror" # 1
|
||||||
|
cfg.sram_enable = True
|
||||||
|
tag.config_reg = cfg
|
||||||
|
# existing property accessors (read straight off the bytes) must reflect it
|
||||||
|
assert tag.arbiter_mode == 1
|
||||||
|
assert tag.sram_enable is True
|
||||||
|
# and the sibling caches the individual setters maintain stay in sync
|
||||||
|
if hasattr(tag, "_arbiter_mode"):
|
||||||
|
assert tag._arbiter_mode == 1
|
||||||
|
if hasattr(tag, "_sram_enabled"):
|
||||||
|
assert tag._sram_enabled is True
|
||||||
|
|
||||||
|
def test_status_reg_binding_roundtrip(self):
|
||||||
|
tag = Ntag5LinkTag()
|
||||||
|
st = tag.status_reg
|
||||||
|
st.i2c_if_locked = True
|
||||||
|
tag.status_reg = st
|
||||||
|
assert tag.i2c_if_locked is True
|
||||||
|
assert tag.status_reg.i2c_if_locked is True
|
||||||
|
|||||||
Reference in New Issue
Block a user