# pm3py Package Refactor Design **Date:** 2026-03-18 **Status:** Design complete, ready for implementation ## Motivation pm3py has grown from a flat wire-protocol library into a full ecosystem with transponder models, sim sessions, sniff infrastructure, MCU bridges, and AES crypto. The current flat layout (`pm3py/hf_14a.py`, `pm3py/hf_15.py`, etc.) doesn't scale — sniff infrastructure is tangled with reader commands, naming is inconsistent (`hf.a14` vs `hf.iso15`), and there's no clear home for transponder models or higher-level workflows. ## Design Decisions ### 1. Single package, sub-packages (not namespace packages) Everything stays under `pm3py/`. One `pip install pm3py`. No separate installable packages — this is one tool with one hardware target. Split later if a genuine need arises. ### 2. Hybrid client model - **Core commands** stay on the `Proxmark3` client — `pm3.hf.iso15.rdbl(4)`. Feels like the PM3 CLI. - **Sim and Sniff** are standalone session classes that take the transport (not the client). They have lifecycle (start/stop/download) that doesn't fit the stateless command pattern. - **Reader modes** are higher-level classes that compose core commands. They take a `Proxmark3` instance. ### 3. Core preserves PM3 CLI feel Low-level commands (`scan`, `rdbl`, `wrbl`, `sniff`) stay in `core/`. Reader modes are a layer on top. Someone who just wants `pm3.hf.iso15.rdbl(4)` shouldn't have to think about "modes." ### 4. Naming follows PM3 conventions - `hf_14a.py` → `hf_iso14a.py` - `hf_15.py` → `hf_iso15.py` - `hf_mf.py` → `hf_mfc.py` - Client attributes: `hf.a14` → `hf.iso14a`, `hf.mf` → `hf.mfc`, `hf.iso15` stays ### 5. Sniff extracted from core `hf_15.py` is currently ~60% sniff infrastructure (trace parsing, 15693 decoders, NDEF annotation, ANSI formatting) and ~40% reader commands. The sniff code becomes its own sub-package. Core keeps only thin `.sniff()` firmware commands. ### 6. Transponder hierarchy: frequency → standard → manufacturer → transponder For HF, the ISO standard is the second level. For LF, where there's typically no standard, skip straight to manufacturer. ### 7. Reader hierarchy: frequency → manufacturer Reader modes organized by frequency, then by reader IC manufacturer. ## Package Structure ``` pm3py/ __init__.py # re-exports from core for backward compat core/ __init__.py # re-exports Proxmark3, PM3Error, PM3Response, Cmd, PM3Status protocol.py # wire constants, CRC, Cmd enum, PM3Status transport.py # frame encode/decode, PM3Transport client.py # Proxmark3 class, _SyncProxy, FirmwareInfo hw.py # HWCommands hf.py # HFCommands — tune, search, dropfield hf_iso14a.py # HF14ACommands — scan, raw hf_iso15.py # HF15Commands — scan, rdbl, wrbl (sniff removed) hf_mfc.py # HFMFCommands — rdbl, wrbl, rdsc, chk, nested, cident lf.py # LFCommands, T55xxCommands, LFSearchResult sniff/ __init__.py session.py # SniffSession — start/stop/download per protocol trace.py # parse_tracelog() — protocol-agnostic trace buffer parsing decode_iso15.py # ISO 15693 request/response decoders, command tables decode_iso14a.py # ISO 14443-A decoder (stub/future) ndef.py # NDEF TLV/record decode for trace annotation format.py # ANSI color formatting, format_sniff_line, wrapping sim/ __init__.py # sim session, table compiler, relay — moved from worktree reader/ __init__.py hf/ __init__.py nxp/ # NXP reader ICs (CLRC663, PN5xx) st/ # ST reader ICs lf/ __init__.py modes/ __init__.py inventory.py # multi-tag scan, anti-collision workflows programming.py # bulk read/write/clone access.py # Wiegand/OSDP credential operations transponders/ __init__.py hf/ __init__.py iso14443a3/ __init__.py nxp/ __init__.py mifare_classic.py mifare_ultralight.py ntag.py # NTAG 213/215/216 iso14443a4/ __init__.py nxp/ __init__.py desfire.py iso15693/ __init__.py common.py # base ISO 15693 system info, block layout ndef.py # NFC Forum Type 5 NDEF models nxp/ __init__.py icode_slix2.py ntag5.py # NTAG 5 (ISO 15693 / NFC Type 5) st/ __init__.py st25tv.py lf/ __init__.py atmel/ __init__.py t55xx.py em/ __init__.py em4100.py em4x05.py ``` ## Import Paths ### Core commands (PM3 CLI feel, unchanged) ```python from pm3py import Proxmark3 async with Proxmark3() as pm3: await pm3.hw.ping() await pm3.hf.iso14a.scan() await pm3.hf.iso15.rdbl(4) await pm3.hf.mfc.rdbl(0) await pm3.lf.t55.readbl(0) await pm3.hf.tune() await pm3.hf.dropfield() ``` ### Sniff and Sim (standalone session classes) ```python from pm3py.sniff import SniffSession from pm3py.sim import SimSession sniff = SniffSession(pm3._transport) entries = await sniff.iso15(timeout=60.0) sim = SimSession(pm3._transport) await sim.start(tag_model) ``` ### Reader modes (compose core commands) ```python from pm3py.reader import InventoryMode inv = InventoryMode(pm3) tags = await inv.scan_all_15() ``` ### Transponder models (independent of hardware) ```python from pm3py.transponders.hf.iso14443a3.nxp import MifareClassic1K from pm3py.transponders.hf.iso15693.nxp import IcodeSlix2 from pm3py.transponders.hf.iso15693 import ndef ``` ## Backward Compatibility - `pm3py/__init__.py` keeps re-exporting `Proxmark3`, `PM3Error`, `PM3Response`, `Cmd`, `PM3Status` - Existing `from pm3py import Proxmark3` continues to work - **Breaking changes:** attribute renames on client only: `hf.a14` → `hf.iso14a`, `hf.mf` → `hf.mfc` ## Migration Phases ### Phase 1 — Create `core/`, normalize names, fix imports - Create `pm3py/core/` and move protocol, transport, client, hw, hf, lf - Rename during move: `hf_14a.py` → `hf_iso14a.py`, `hf_15.py` → `hf_iso15.py`, `hf_mf.py` → `hf_mfc.py` - Normalize client attributes: `hf.a14` → `hf.iso14a`, `hf.mf` → `hf.mfc` - `pm3py/__init__.py` re-exports from `core` - Move and update tests. All green before proceeding. ### Phase 2 — Extract `sniff/` - Pull trace parsing, 15693 decoders, NDEF annotation, ANSI formatting out of `hf_iso15.py` into `sniff/` - Core `hf_iso15.py` keeps only thin `.sniff()` firmware command - `sniff_decoded()` → `SniffSession.iso15()` ### Phase 3 — Scaffold `transponders/` - Create the `hf/iso14443a3/`, `hf/iso14443a4/`, `hf/iso15693/`, `lf/atmel/`, `lf/em/` tree - Start with `hf/iso15693/nxp/icode_slix2.py` and `hf/iso14443a3/nxp/mifare_classic.py` — these have existing models in the sim worktree ### Phase 4 — Scaffold `reader/` and `sim/` - `reader/` with `hf/`, `lf/`, `modes/` stubs - `sim/` — migrate from worktree Each phase is one PR, tests green before merge.