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_link import Ntag5LinkTag
|
||||||
from pm3py.transponders.hf.iso15693.nxp.ntag5_boost import Ntag5BoostTag
|
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.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 (
|
from pm3py.transponders.hf.iso15693.infineon.myd_vicinity import (
|
||||||
MydVicinity, SRF55V02P, SRF55V10P,
|
MydVicinity, SRF55V02P, SRF55V10P,
|
||||||
)
|
)
|
||||||
@@ -103,7 +105,7 @@ __all__ = [
|
|||||||
"ST25TA", "ST25TA512B", "ST25TA02KB", "ST25TA16K", "ST25TA64K",
|
"ST25TA", "ST25TA512B", "ST25TA02KB", "ST25TA16K", "ST25TA64K",
|
||||||
"OptigaAuthenticateNBT",
|
"OptigaAuthenticateNBT",
|
||||||
"ST25TV", "ST25TV512C", "ST25TV02KC",
|
"ST25TV", "ST25TV512C", "ST25TV02KC",
|
||||||
"ST25DV", "ST25DV04K", "ST25DV16K", "ST25DV64K",
|
"ST25DV", "ST25DV04K", "ST25DV16K", "ST25DV64K", "St25dvRfAreaSS",
|
||||||
"MydVicinity", "SRF55V02P", "SRF55V10P",
|
"MydVicinity", "SRF55V02P", "SRF55V10P",
|
||||||
"Tag14443B", "StateB", "Tag14443B_4", "StateB4",
|
"Tag14443B", "StateB", "Tag14443B_4", "StateB4",
|
||||||
"ST25TB", "ST25TB512AC", "ST25TB512AT", "ST25TB02K", "ST25TB04K",
|
"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 pm3py.trace.decode_iso15 import decode_15693_st
|
||||||
from ..type5 import NfcType5Tag
|
from ..type5 import NfcType5Tag
|
||||||
from ..base import FLAG_ADDRESS, FLAG_INVENTORY
|
from ..base import FLAG_ADDRESS, FLAG_INVENTORY
|
||||||
|
from pm3py.transponders.bitfield import BitField, Register
|
||||||
|
|
||||||
ST_MFG = 0x02
|
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)
|
# Custom command codes (subset modelled)
|
||||||
CMD_READ_CFG = 0xA0
|
CMD_READ_CFG = 0xA0
|
||||||
CMD_WRITE_CFG = 0xA1
|
CMD_WRITE_CFG = 0xA1
|
||||||
@@ -299,8 +322,19 @@ class ST25DV(NfcType5Tag):
|
|||||||
return 3
|
return 3
|
||||||
return 4
|
return 4
|
||||||
|
|
||||||
|
_RFASS_REG = {1: REG_RFA1SS, 2: REG_RFA2SS, 3: REG_RFA3SS, 4: REG_RFA4SS}
|
||||||
|
|
||||||
def _rfaiss(self, area: int) -> int:
|
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:
|
def _session_for_area(self, area: int) -> int:
|
||||||
pwd_ctrl = self._rfaiss(area) & 0x03
|
pwd_ctrl = self._rfaiss(area) & 0x03
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from pm3py.sim import (
|
|||||||
Ntag5StatusReg, Ntag5ConfigReg, Ntag5LinkTag,
|
Ntag5StatusReg, Ntag5ConfigReg, Ntag5LinkTag,
|
||||||
NxpKeyPrivileges, IcodeDnaTag,
|
NxpKeyPrivileges, IcodeDnaTag,
|
||||||
IcodeFeatureFlags,
|
IcodeFeatureFlags,
|
||||||
|
St25dvRfAreaSS, ST25DV04K,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -491,3 +492,30 @@ class TestIcodeFeatureFlags:
|
|||||||
text = repr(IcodeFeatureFlags(0x0001))
|
text = repr(IcodeFeatureFlags(0x0001))
|
||||||
assert "ICODE feature flags" in text
|
assert "ICODE feature flags" in text
|
||||||
assert "um_pp" 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