From cb5d06da14c277147a90a4cf6c01dd1bd9fb435e Mon Sep 17 00:00:00 2001 From: michael Date: Thu, 19 Mar 2026 12:56:57 -0700 Subject: [PATCH] feat: set_uid() with validation and random generation for 15693 tags Tag15693.set_uid() now: - Accepts None (or no args) to generate a random valid UID with the correct IC-specific prefix (E0 + mfg + IC type) - Validates length (must be 8 bytes) - Warns on prefix mismatch (wrong IC type for product-specific tags) - Warns on non-NXP manufacturer code - Warns on missing E0 prefix --- pm3py/transponders/hf/iso15693/base.py | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pm3py/transponders/hf/iso15693/base.py b/pm3py/transponders/hf/iso15693/base.py index 1efef7b..9932ac5 100644 --- a/pm3py/transponders/hf/iso15693/base.py +++ b/pm3py/transponders/hf/iso15693/base.py @@ -128,6 +128,46 @@ class Tag15693(Transponder): random_bytes = os.urandom(8 - len(prefix)) return prefix + random_bytes + def set_uid(self, uid: bytes | str | None = None) -> None: + """Change the tag UID. Call sync() to push to firmware. + + uid=None generates a random valid UID with the correct IC prefix. + Warns if the UID prefix doesn't match the expected IC type. + """ + import warnings + if uid is None: + self._uid = self._generate_uid() + self._uid_dirty = True + return + uid = self._parse_uid(uid) + if len(uid) != 8: + raise ValueError(f"UID must be 8 bytes, got {len(uid)}") + prefix = self._uid_prefix + if len(prefix) > 2 and uid[:len(prefix)] != prefix: + expected = prefix.hex().upper() + actual = uid[:len(prefix)].hex().upper() + cls_name = type(self).__name__ + warnings.warn( + f"{cls_name}: UID prefix {actual} doesn't match expected {expected}. " + f"Phones/readers may not identify the IC type correctly. " + f"Use set_uid() with no args for a valid random UID.", + stacklevel=2, + ) + elif uid[:2] != b"\xE0\x04" and uid[:1] == b"\xE0": + warnings.warn( + f"UID manufacturer code {uid[1]:02X} is not NXP (04). " + f"Phones may not recognize this as an NXP tag.", + stacklevel=2, + ) + elif uid[:1] != b"\xE0": + warnings.warn( + f"UID must start with E0 (ISO 15693 IC manufacturer prefix). " + f"Got {uid[0]:02X}. This UID will not be recognized by readers.", + stacklevel=2, + ) + self._uid = uid + self._uid_dirty = True + def decode_trace(self, direction: int, payload: bytes) -> str | None: return decode_15693(direction, payload)