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:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user