feat(registers): ST25DV RF_Ai_SS area-security register

Add St25dvRfAreaSS (Register) for the ST25DV per-area RF security
registers (RFA1SS..RFA4SS): pwd_ctrl (which RF password gates the area)
and rw_protection (read/write protection level). Bound via
tag.rf_area_ss(area) / tag.set_rf_area_ss(area, value). Modelled from
the model's own tested protection logic (single source of truth) and
cross-checked against _read_allowed / _write_allowed. 3 new tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-14 09:05:55 -07:00
parent f9e56e32ef
commit 130bcd1a90
3 changed files with 67 additions and 3 deletions

View File

@@ -56,7 +56,9 @@ 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_boost import Ntag5BoostTag
from pm3py.transponders.hf.iso15693.st.st25tv import ST25TV, ST25TV512C, ST25TV02KC
from pm3py.transponders.hf.iso15693.st.st25dv import ST25DV, ST25DV04K, ST25DV16K, ST25DV64K
from pm3py.transponders.hf.iso15693.st.st25dv import (
ST25DV, ST25DV04K, ST25DV16K, ST25DV64K, St25dvRfAreaSS,
)
from pm3py.transponders.hf.iso15693.infineon.myd_vicinity import (
MydVicinity, SRF55V02P, SRF55V10P,
)
@@ -103,7 +105,7 @@ __all__ = [
"ST25TA", "ST25TA512B", "ST25TA02KB", "ST25TA16K", "ST25TA64K",
"OptigaAuthenticateNBT",
"ST25TV", "ST25TV512C", "ST25TV02KC",
"ST25DV", "ST25DV04K", "ST25DV16K", "ST25DV64K",
"ST25DV", "ST25DV04K", "ST25DV16K", "ST25DV64K", "St25dvRfAreaSS",
"MydVicinity", "SRF55V02P", "SRF55V10P",
"Tag14443B", "StateB", "Tag14443B_4", "StateB4",
"ST25TB", "ST25TB512AC", "ST25TB512AT", "ST25TB02K", "ST25TB04K",

View File

@@ -17,9 +17,32 @@ from pm3py.sim.frame import RFFrame
from pm3py.trace.decode_iso15 import decode_15693_st
from ..type5 import NfcType5Tag
from ..base import FLAG_ADDRESS, FLAG_INVENTORY
from pm3py.transponders.bitfield import BitField, Register
ST_MFG = 0x02
# RF_Ai_SS read/write protection level (bits 3:2). Labels describe areas 2-4; on Area 1 read
# stays open except at level 3 (an ST25DV quirk — see St25dvRfAreaSS).
_ST25DV_RW = {
0: "open (read+write)",
1: "read open / write needs password",
2: "read+write need password",
3: "read needs password / write locked",
}
_ST25DV_PWD = {0: "none", 1: "RF_PWD_1", 2: "RF_PWD_2", 3: "RF_PWD_3"}
class St25dvRfAreaSS(Register):
"""ST25DV RF_Ai_SS — one of the four RF-area security registers (RFA1SS..RFA4SS). Selects
which RF password gates the area and its read/write protection level. On **Area 1**, read
stays open unless the level is 3; on Areas 2-4 level >= 2 protects read too."""
_width_bits = 8
_label = "ST25DV RF_Ai_SS"
pwd_ctrl = BitField(1, 0, values=_ST25DV_PWD, doc="which RF password protects this area")
rw_protection = BitField(3, 2, values=_ST25DV_RW, doc="RF read/write protection level")
# Custom command codes (subset modelled)
CMD_READ_CFG = 0xA0
CMD_WRITE_CFG = 0xA1
@@ -299,8 +322,19 @@ class ST25DV(NfcType5Tag):
return 3
return 4
_RFASS_REG = {1: REG_RFA1SS, 2: REG_RFA2SS, 3: REG_RFA3SS, 4: REG_RFA4SS}
def _rfaiss(self, area: int) -> int:
return self._cfg[{1: REG_RFA1SS, 2: REG_RFA2SS, 3: REG_RFA3SS, 4: REG_RFA4SS}[area]]
return self._cfg[self._RFASS_REG[area]]
def rf_area_ss(self, area: int) -> St25dvRfAreaSS:
"""RF_Ai_SS security register for memory area ``area`` (1-4) as a self-describing
:class:`St25dvRfAreaSS`."""
return St25dvRfAreaSS(self._cfg[self._RFASS_REG[area]])
def set_rf_area_ss(self, area: int, value: "St25dvRfAreaSS | int") -> None:
"""Program the RF_Ai_SS register for memory area ``area`` (1-4)."""
self._cfg[self._RFASS_REG[area]] = int(value) & 0xFF
def _session_for_area(self, area: int) -> int:
pwd_ctrl = self._rfaiss(area) & 0x03

View File

@@ -11,6 +11,7 @@ from pm3py.sim import (
Ntag5StatusReg, Ntag5ConfigReg, Ntag5LinkTag,
NxpKeyPrivileges, IcodeDnaTag,
IcodeFeatureFlags,
St25dvRfAreaSS, ST25DV04K,
)
@@ -491,3 +492,30 @@ class TestIcodeFeatureFlags:
text = repr(IcodeFeatureFlags(0x0001))
assert "ICODE feature flags" in text
assert "um_pp" in text
class TestSt25dvRfAreaSS:
def test_fields(self):
r = St25dvRfAreaSS(0)
r.rw_protection = 2 # bits 3:2
r.pwd_ctrl = 1 # bits 1:0
assert int(r) == (2 << 2) | 1 # 0x09
assert r.rw_protection == "read+write need password"
assert r.pwd_ctrl == "RF_PWD_1"
def test_binding_matches_protection_logic(self):
tag = ST25DV04K()
r = St25dvRfAreaSS(0)
r.rw_protection = 3 # write locked
tag.set_rf_area_ss(1, r)
assert tag.rf_area_ss(1).rw_protection == "read needs password / write locked"
# the tag's existing protection logic must see the very same bits
assert tag._write_allowed(4) is False # level 3 -> write forbidden
assert tag._read_allowed(4) is False # level 3, no session -> read denied
def test_open_area(self):
tag = ST25DV04K()
tag.set_rf_area_ss(1, St25dvRfAreaSS(0)) # level 0 = fully open
assert tag.rf_area_ss(1).rw_protection == "open (read+write)"
assert tag._write_allowed(4) is True
assert tag._read_allowed(4) is True