65 lines
3.2 KiB
Markdown
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
|