- Add docs/plans/README.md as the roadmap index cataloguing all design/plan docs. - Add the custom ISO14443-A command handling design + plan (L3 NTAG I2C SECTOR_SELECT / cross-sector reads + native auth; L4 static APDUs + WTX relay), produced from a multi-agent design workflow. - Backfill previously-uncommitted plan docs (trace-formatter, ndef-trace-decode, live-sniff, firmware-upstream-rebase, 14a-live-trace) and NTAG5_SECURITY.md. - Sync CLAUDE.md package structure with the committed transponder models. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.4 KiB
4.4 KiB
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")
- Response is never touched — table/precompiled, FPGA-timed via
EmSendCmd14443aRaw(SSP-clock alignment + FPGA delay-line). Tracing is architecturally incapable of affecting FDT. - 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.)
- 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_ngand the Miller software-UART receive share the ARM thread. - 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 <BH10sB20sBB16s16sB> 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: addCMD_HF_ISO14443A_SIM_TRACE(default id0x0339, next to the 15693 one at0x0336); reserve aFLAG_SIM_TRACEbit in the 14a simflagsword (default0x4000, a free high bit) so the host opts trace in/out — mirrors 15693'sflags & 0x02. - F2+F3
armsrc/iso14443a.c: copy theTRACE_PUSH/TRACE_FLUSHring block fromiso15693.c:2423butENTRY_MAX→ 128 (ISO-DEP I-blocks exceed 64B, needed for 14a-4) and ring → 16;trace_enabledfrom the new flag. Hook intoSimulateIso14443aTagEx(:1725): buffer received cmd after:1851(don't push); afterEmSendPrecompiledCmd(:2554) doTRACE_PUSH(0,cmd)thenTRACE_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: addCmd.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 mirroringstart_15693(raw pyserial + the existing_trace_readerthread) +trace/on_frame/quietkwargs.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_loopowns WTX; routeSIM_TRACEthrough 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_TRACEframes → assertTraceFormatter/on_frame/entries(mirrortest_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=64too 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)