# Live trace for ISO14443-A sim (14a-3, extensible to 14a-4) Add a live reader↔tag trace to the ISO14443-A card simulation, mirroring the working ISO15693 sim trace, designed so 14a-4 (ISO-DEP / Layer 4) reuses the same firmware mechanism. Backed by the feasibility eval (verdict: **feasible with care** — 86µs FDT does not break it). ## Design invariants (why 86µs is not "too fast") 1. **Response is never touched** — table/precompiled, FPGA-timed via `EmSendCmd14443aRaw` (SSP-clock alignment + FPGA delay-line). Tracing is architecturally incapable of affecting FDT. 2. **Both trace pushes deferred to *after* transmit** → zero added work inside the 86µs window. (A push is a ≤128B memcpy ≈ <200 cycles / <4.2µs anyway.) 3. **Flush ≤1 ring entry per idle iteration** (NOT 15693's bulk-drain) — the only USB-touching part must never overlap a reader-command edge, because `reply_ng` and the Miller software-UART receive share the ARM thread. 4. **Ring + drop-on-full** → lossy under anticollision bursts, but the sim never stalls. RF timing is preserved; trace is merely lossy in tight gaps. ## P0 — Prerequisite (standalone bug): fix `start_14a` sim-start payload `sim_session.py:101` builds `payload = atqa + sak + uid` then calls `send_ng_no_response(HF_ISO14443A_SIMULATE)` with **no payload** (`:123` TODO). The sim never starts correctly today. Fix: send the proper `HF_ISO14443A_SIMULATE` NG struct — the exact `` layout already used by the fixed `hf.iso14a.sim()` (tagtype, flags, uid[10], exitAfter, rats…). Land first; 14a sim is unusable without it, trace or not. ## Firmware (atomic single-file commits + build) - **F1 `include/pm3_cmd.h`:** add `CMD_HF_ISO14443A_SIM_TRACE` (default id `0x0339`, next to the 15693 one at `0x0336`); reserve a `FLAG_SIM_TRACE` bit in the 14a sim `flags` word (default `0x4000`, a free high bit) so the host opts trace in/out — mirrors 15693's `flags & 0x02`. - **F2+F3 `armsrc/iso14443a.c`:** copy the `TRACE_PUSH`/`TRACE_FLUSH` ring block from `iso15693.c:2423` but **`ENTRY_MAX` → 128** (ISO-DEP I-blocks exceed 64B, needed for 14a-4) and ring → 16; `trace_enabled` from the new flag. Hook into `SimulateIso14443aTagEx` (`:1725`): buffer received cmd after `:1851` (don't push); after `EmSendPrecompiledCmd` (`:2554`) do `TRACE_PUSH(0,cmd)` then `TRACE_PUSH(1,response)`; `TRACE_FLUSH_ONE()` at loop top (`:1844`); full drain on loop exit. - **F4:** build `fullimage PLATFORM=PM3GENERIC` — must be clean. ## pm3py (reuses the generic scriptable-trace plumbing) - **Y1 `protocol.py`:** add `Cmd.HF_ISO14443A_SIM_TRACE`. - **Y2 `sim_session.py:284`:** generalize the trace-reader guard (`cmd == CMD_HF_ISO15693_SIM_TRACE`) into a `{cmd: (protocol, decoder)}` dispatch; add the 14a branch (`dir=data[0]&0x0F`, `crc_fail=data[0]&0x80`, `protocol=1`, `decode_14443a`). - **Y3 `start_14a`:** synchronous trace path mirroring `start_15693` (raw pyserial + the existing `_trace_reader` thread) + `trace`/`on_frame`/`quiet` kwargs. `on_frame`/`quiet`/`entries` + `TraceFormatter(mode='sim')` unchanged. ## I/O model decision (the one real design fork) - **14a-3:** sync trace path (no WTX relay at Layer 3) → avoids dual-reader-on- one-fd contention. - **14a-4 (later):** the async `_relay_loop` owns WTX; route `SIM_TRACE` through *it* (single async reader), not a second thread. Firmware F1–F3 are identical for both layers — only the pm3py consumer differs. WTX creates ms-scale idle windows ideal for flushing. ## Tests - **T1:** mocked-transport unit test — synthetic `CMD_HF_ISO14443A_SIM_TRACE` frames → assert `TraceFormatter`/`on_frame`/`entries` (mirror `test_trace_scripting.py`). - **T2 (bench):** phone doing anticollision + SELECT + RATS → verify (a) card never drops, (b) frames stream, tolerating loss during anticollision. ## Risks (+ mitigations) - Ring fill under heavy Layer-3 polling → lossy by design; ring=16, accept loss in anticollision (uninteresting traffic). - Flush overlapping a reader edge → single-entry-per-idle at loop top only; never bulk-drain post-transmit. A missed command = reader retry, not teardown. - `ENTRY_MAX=64` too small for ISO-DEP → 128 (RAM ≈ 1KB static; check budget). - pm3py dual reader on one fd → 14a-3 sync only; 14a-4 converges to async relay. ## Sequence `P0 → F1 → F2+F3 → F4 → Y1 → Y2 → Y3 → T1 → (T2 on bench)`