Files
pm3py/CLAUDE.md
2026-03-16 21:23:34 -07:00

3.2 KiB

pm3py — Development Guide

What is this?

A pure-Python async library that speaks the Proxmark3 NG wire protocol directly over USB serial. Returns structured dicts, not text. No dependency on the C client binary.

Quick reference

# Run tests (no hardware needed, all mocked)
cd /home/work/pm3py
python -m pytest tests/ -v

# Install for development
pip install -e .

Architecture

  • protocol.py — Wire constants, CRC-16/A, Cmd enum, PM3Status enum. Source of truth for command IDs and frame formats.
  • transport.py — Frame encode/decode (encode_ng_frame, encode_mix_frame, decode_response_frame), PM3Transport async serial class. USB skips CRC (uses magic 0x3361), FPC UART uses CRC with byte-swapped wire format ((low << 8) | high).
  • client.pyProxmark3 class (async-first, Proxmark3.sync() for REPL), _SyncProxy, FirmwareInfo, port auto-detection.
  • hw.py — Hardware commands. LED API is platform-aware (Easy vs RDV4 have different color-to-pin mappings). PWM validation fetches capabilities on first use.
  • lf.py — LF commands + T55xxCommands + LFSearchResult. Full tag demod (EM410x, HID, etc.) is NOT implemented — that requires the C client's DSP stack.
  • hf.py — HF core (tune, search, sniff, dropfield).
  • hf_14a.py — ISO 14443-A (scan, raw, sniff, sim). Uses MIX frames.
  • hf_15.py — ISO 15693 (scan, rdbl, wrbl, sniff). Uses NG frames with ISO command payloads.
  • hf_mf.py — MIFARE Classic (rdbl, wrbl, rdsc, chk, sniff, sim, nested, cident). Read uses NG, write uses MIX.

Wire protocol essentials

  • NG frame (client→device): magic 0x61334d50 + length|0x8000 + cmd + payload + crc/nocrc
  • MIX frame: Same but ng bit unset, payload starts with 3x uint64 args (24 bytes)
  • Response frame: magic 0x62334d50 + length|ng + status + reason + cmd + payload + crc/nocrc
  • USB: no CRC (postamble = 0x3361 cmd / 0x3362 resp). C client sets send_with_crc_on_usb = false.
  • FPC UART: CRC-16/A with byte-swapped wire encoding

Key patterns

  • All command methods are async. The sync wrapper in _SyncProxy intercepts via __getattr__ and calls loop.run_until_complete().
  • Command classes hold a self._t reference to PM3Transport (or mock in tests).
  • Tests use AsyncMock for transport. Set hw._is_rdv4 = False to skip capabilities fetch in LED tests.
  • capabilities() response parsed at known byte/bit offsets from the C struct (version=7 format).

Platform differences (PM3 Easy vs RDV4)

Color Easy RDV4
green A (0x01) B (0x02)
red B (0x02) C (0x04)
orange C (0x04) A (0x01)
blue D (0x08) (none)
red2 (none) D (0x08)

PWM-capable: Easy = A,B. RDV4 = A,D.

Adding new commands

  1. Find the CMD_* constant in include/pm3_cmd.h and add to Cmd enum in protocol.py
  2. Check if the C client uses SendCommandNG (→ send_ng) or SendCommandMIX (→ send_mix)
  3. Check the payload struct in pm3_cmd.h and use struct.pack to build it
  4. Parse the response using struct.unpack_from on resp.data
  5. Return a dict with human-readable keys
  6. Write test with AsyncMock transport — no hardware needed