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

65 lines
3.2 KiB
Markdown

# 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
```bash
# 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.py``Proxmark3` 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