Migrates all 43 sim modules and 23 test files (~7.8k LOC, 686 tests) from .worktrees/sim-framework/ into pm3py/sim/. Import fixes: - 4 files: ..protocol/..transport → ..core.protocol/..core.transport - trace_fmt.py: pm3py.hf_15 → pm3py.trace.ndef - test_sim_pm3medium.py: flat imports → core.* - test_sim_trace_fmt.py: flat imports → core.* - Added sim Cmd entries to core/protocol.py (EML_SETMEM, SIM_TABLE_*) 751 tests passing (686 sim + 65 core). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
87 lines
3.0 KiB
Python
87 lines
3.0 KiB
Python
"""NXP ICODE/NTAG5 platform — shared NXP custom commands."""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import struct
|
|
|
|
from .frame import RFFrame
|
|
from .trace_fmt import decode_15693_nxp
|
|
from .type5 import NfcType5Tag
|
|
|
|
# NXP manufacturer code
|
|
NXP_MFG = 0x04
|
|
|
|
# NXP custom commands per SL2S2602 datasheet
|
|
CMD_INVENTORY_READ = 0xA0
|
|
CMD_FAST_INVENTORY_READ = 0xA1
|
|
CMD_SET_EAS = 0xA2
|
|
CMD_RESET_EAS = 0xA3
|
|
CMD_LOCK_EAS = 0xA4
|
|
CMD_EAS_ALARM = 0xA5
|
|
CMD_PASSWORD_PROTECT_EAS_AFI = 0xA6
|
|
CMD_WRITE_EAS_ID = 0xA7
|
|
CMD_GET_NXP_SYSTEM_INFO = 0xAB
|
|
CMD_GET_RANDOM = 0xB2
|
|
CMD_SET_PASSWORD = 0xB3
|
|
CMD_WRITE_PASSWORD = 0xB4
|
|
CMD_LOCK_PASSWORD = 0xB5
|
|
CMD_PROTECT_PAGE = 0xB6
|
|
CMD_LOCK_PAGE_PROTECTION = 0xB7
|
|
CMD_DESTROY = 0xB9
|
|
CMD_ENABLE_PRIVACY = 0xBA
|
|
CMD_64BIT_PASSWORD_PROTECTION = 0xBB
|
|
CMD_STAY_QUIET_PERSISTENT = 0xBC
|
|
CMD_READ_SIGNATURE = 0xBD
|
|
CMD_READ_CONFIG = 0xC0
|
|
CMD_WRITE_CONFIG = 0xC1
|
|
CMD_READ_TT = 0xC4
|
|
|
|
|
|
class NxpIcodeTag(NfcType5Tag):
|
|
"""NXP ICODE/NTAG5 shared platform features.
|
|
|
|
Provides: originality signature, NXP system info, GET_RANDOM, SET_PASSWORD.
|
|
Base for ICODE SLIX, SLIX2, DNA, NTAG 5 Link, etc.
|
|
"""
|
|
_uid_prefix = b"\xE0\x04" # E0 + NXP manufacturer code
|
|
|
|
def __init__(self, uid: bytes, signature: bytes | None = None, **kwargs):
|
|
super().__init__(uid=uid, **kwargs)
|
|
# 32-byte ECC originality signature (from factory, read-only)
|
|
self._signature = signature or bytes(32)
|
|
|
|
def decode_trace(self, direction: int, payload: bytes) -> str | None:
|
|
return decode_15693_nxp(direction, payload) or super().decode_trace(direction, payload)
|
|
|
|
def _handle_custom_command(self, cmd: int, frame: RFFrame) -> RFFrame | None:
|
|
"""Handle NXP custom commands (0xA0-0xDF)."""
|
|
# Verify NXP manufacturer code
|
|
if len(frame.data) < 3 or frame.data[2] != NXP_MFG:
|
|
return None
|
|
|
|
match cmd:
|
|
case 0xB2: # GET RANDOM NUMBER
|
|
return self._handle_get_random(frame)
|
|
case 0xB3: # SET PASSWORD
|
|
return self._nxp_handle_set_password(frame)
|
|
|
|
return None
|
|
|
|
def _handle_get_nxp_system_info(self, frame: RFFrame) -> RFFrame | None:
|
|
"""GET NXP SYSTEM INFO (0xAB): returns PP byte, PP conditions, lock bits, feature flags."""
|
|
# Base implementation — subclasses override with actual PP/feature data
|
|
# Response: flags(1) + PP(1) + PP_cond(1) + lock_bits(1) + feature_flags(4)
|
|
return RFFrame.from_bytes(bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
|
|
|
|
def _handle_get_random(self, frame: RFFrame) -> RFFrame | None:
|
|
random_bytes = os.urandom(2)
|
|
return RFFrame.from_bytes(bytes([0x00]) + random_bytes)
|
|
|
|
def _handle_read_signature(self, frame: RFFrame) -> RFFrame | None:
|
|
"""READ SIGNATURE (0xBD): returns 32-byte ECC originality signature."""
|
|
return RFFrame.from_bytes(bytes([0x00]) + self._signature)
|
|
|
|
def _nxp_handle_set_password(self, frame: RFFrame) -> RFFrame | None:
|
|
"""Base SET_PASSWORD handler. Override in subclasses for specific password types."""
|
|
return None
|