151 lines
6.3 KiB
Python
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
|