Files
pm3py/pm3py/transponders/hf/iso15693/infineon/myd_vicinity.py
2026-07-05 09:53:52 -07:00

151 lines
6.3 KiB
Python

"""Infineon my-d vicinity — SRF55V02P / SRF55V10P (ISO 15693 / ISO 18000-3 M1).
Sourced from the Infineon my-d vicinity plain Extended Datasheet Rev 4.0.
8-byte UID with Infineon manufacturer code 0x05 (prefix E0 05 <chip-id>).
Notable differences from the NXP/ST 15693 parts:
- **No GET SYSTEM INFO (0x2B)** — identification is via the Chip-ID byte (UID
byte 5). A reader must not rely on 0x2B.
- Two memory views: ISO block mode (4-byte blocks) and a custom page mode
(8-byte pages) reached through the single custom opcode 0xA0. Plain variants
have no password/authentication (open access).
- Any user page can be a decrement-only value counter (custom Restricted Write).
This model implements the ISO block interface plus the custom Read/Write page
commands; the secure (SRF55VxxS) authentication protocol is not modelled.
"""
from __future__ import annotations
from pm3py.sim.frame import RFFrame
from ..base import Tag15693, FLAG_ADDRESS, FLAG_INVENTORY
INFINEON_MFG = 0x05
# Custom sub-commands (inside the 0xA0 custom frame)
SUB_READ = 0x10 # read one 8-byte page
SUB_WRITE = 0x30 # write one 8-byte page
SUB_WRITE_BYTE = 0x90 # write one byte (admin bytes included)
SUB_WRITE_REREAD = 0xB0 # write then return the page
SUB_RESTRICTED_WRITE = 0x00 # value-counter decrement
SUB_RESTRICTED_WRITE_REREAD = 0x80
class MydVicinity(Tag15693):
"""Infineon my-d vicinity plain. Concrete ICs set the memory size."""
_uid_prefix = b"\xE0\x05" # E0 + Infineon mfg 0x05; chip-id appended
_chip_id: int = 0x40 # UID byte 5 (also encodes EEPROM size)
def __init__(self, uid: bytes | str | None = None, dsfid: int = 0, afi: int = 0):
super().__init__(uid=uid, dsfid=dsfid, afi=afi,
block_size=4, num_blocks=self._num_blocks_cls,
ic_reference=0x00)
# Page-mode view: 8-byte data pages (custom addressing). Physical
# memory is shared with the block view (block N ↔ its bytes).
self._page_size = 8
async def handle_frame(self, frame: RFFrame) -> RFFrame | None:
if len(frame.data) < 2:
return None
cmd = frame.data[1]
if cmd == 0x2B:
# my-d vicinity does not implement Get System Info.
return self._make_error(0x01)
if cmd == 0xA0:
return self._handle_custom(frame)
return await super().handle_frame(frame)
# ----- custom page commands (opcode 0xA0, mfg 0x05) -----
def _handle_custom(self, frame: RFFrame) -> RFFrame | None:
data = frame.data
flags = data[0]
if len(data) < 3 or data[2] != INFINEON_MFG:
return None
addressed = bool(flags & FLAG_ADDRESS) and not (flags & FLAG_INVENTORY)
# flags, cmd(A0), mfg [+uid], then <sub-cmd, addr0(page), addr1(00), data...>
off = 3 + (8 if addressed else 0)
if addressed and (len(data) < 11 or bytes(reversed(data[3:11])) != self._uid):
return None
if len(data) < off + 3:
return self._make_error(0x0F)
sub = data[off]
page = data[off + 1]
params = data[off + 3:]
if sub == SUB_READ:
return self._read_page(page)
if sub == SUB_WRITE:
return self._write_page(page, params, reread=False)
if sub == SUB_WRITE_REREAD:
return self._write_page(page, params, reread=True)
if sub == SUB_WRITE_BYTE:
return self._write_byte(page, params)
if sub in (SUB_RESTRICTED_WRITE, SUB_RESTRICTED_WRITE_REREAD):
return self._restricted_write(page, params,
reread=(sub == SUB_RESTRICTED_WRITE_REREAD))
return self._make_error(0x01)
def _page_offset(self, page: int) -> int:
return page * self._page_size
def _num_pages(self) -> int:
return (self._num_blocks * self._block_size) // self._page_size
def _read_page(self, page: int) -> RFFrame | None:
if page >= self._num_pages():
return self._make_error(0x10)
off = self._page_offset(page)
return self._make_response(bytes(self._memory[off:off + self._page_size]))
def _write_page(self, page: int, data: bytes, reread: bool) -> RFFrame | None:
if page >= self._num_pages():
return self._make_error(0x10)
if len(data) < self._page_size:
return self._make_error(0x0F)
off = self._page_offset(page)
self._memory[off:off + self._page_size] = data[:self._page_size]
if reread:
return self._make_response(bytes(self._memory[off:off + self._page_size]))
return self._make_response(b"")
def _write_byte(self, page: int, params: bytes) -> RFFrame | None:
# params = byte-number(1) + value(1)
if len(params) < 2:
return self._make_error(0x0F)
byte_no, value = params[0], params[1]
if page >= self._num_pages() or byte_no > 0x09:
return self._make_error(0x10)
# Only the 8 data bytes live in _memory; admin bytes 8/9 are accepted
# but not stored in the block view.
if byte_no < 8:
self._memory[self._page_offset(page) + byte_no] = value
return self._make_response(b"")
def _restricted_write(self, page: int, params: bytes, reread: bool) -> RFFrame | None:
# Decrement-only value counter: accept only a strictly lower 32-bit value.
if page >= self._num_pages() or len(params) < 4:
return self._make_error(0x0F)
off = self._page_offset(page)
new_val = int.from_bytes(params[0:4], "little")
cur = int.from_bytes(self._memory[off:off + 4], "little")
if new_val >= cur and cur != 0:
return self._make_error(0x0F) # not lower → rejected
self._memory[off:off + 4] = new_val.to_bytes(4, "little")
if reread:
return self._make_response(bytes(self._memory[off:off + self._page_size]))
return self._make_response(b"")
class SRF55V02P(MydVicinity):
"""my-d vicinity SRF55V02P — 2.5 kbit, 56 ISO blocks (00h-37h)."""
_uid_prefix = b"\xE0\x05\x40"
_chip_id = 0x40
_num_blocks_cls = 56
class SRF55V10P(MydVicinity):
"""my-d vicinity SRF55V10P — 10 kbit, 248 ISO blocks (00h-F7h)."""
_uid_prefix = b"\xE0\x05\x00"
_chip_id = 0x00
_num_blocks_cls = 248