# Trace Formatter Design ## Problem The sim session trace output is a raw `print()` with no color, no command decoding, no wrapping, and no leading newline (so it sometimes renders on the REPL prompt). The PM3 C client has rich colored, decoded trace output — we want that for our Python sim traces. ## Design ### New module: `pm3py/sim/trace_fmt.py` A `TraceFormatter` class that owns all rendering logic. `_trace_reader` in `sim_session.py` delegates to it instead of calling `print()` directly. ### Modes | Mode | Tag | Color | |------|-----|-------| | Sim (we are the tag) | `[Sim]` | Magenta (`\033[35m`) | | Reader (we are the reader) | `[Rdr]` | Cyan (`\033[36m`) | | Sniff (passive observer) | `[Snf]` | Default/white | ### Direction colors | Direction | Color | |-----------|-------| | Reader -> Tag (command) | Cyan (`\033[36m`) | | Tag -> Reader (response) | Yellow (`\033[33m`) | ### Additional colors | Element | Color | |---------|-------| | Command annotation | Green (`\033[32m`) | | Hex bytes | Dim (`\033[2m`) | Colors suppressed when `sys.stdout.isatty()` is False. ### Layout ``` \n[Sim] Reader -> Tag: 26 01 00 INVENTORY ^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^ mode prefix(colored) hex(dim) annotation(green) ``` Prefix column = fixed width (e.g., 24 chars). This is the indent for continuation lines. Hex bytes are space-separated (`de ad be ef`, not `deadbeef`). ### Wrapping When hex + annotation fit on one line: annotation appears inline after hex. When hex overflows terminal width: hex wraps at prefix column, annotation goes on its own line below (also at prefix column). ``` \n[Sim] Tag -> Reader: 00 de ad be ef 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fe a1 READ MULTIPLE BLOCK [0x00, 13 blocks] OK ``` ### Terminal width handling - Cache `os.get_terminal_size()` on init - Register `signal.SIGWINCH` handler (Unix) to update cached width - Fallback: re-read every 10 lines if signal registration fails - Default 80 columns if no TTY ### ISO 15693 command decode table Extracted from command byte (byte index 1 of frame): | Cmd | Name | Extra annotation | |-----|------|-----------------| | 0x01 | INVENTORY | mask_len if present | | 0x02 | STAY QUIET | -- | | 0x20 | READ SINGLE BLOCK | block number | | 0x21 | WRITE SINGLE BLOCK | block number + data length | | 0x23 | READ MULTIPLE BLOCK | start block, count | | 0x26 | RESET TO READY | -- | | 0x2B | GET SYSTEM INFO | -- | | 0x2C | GET MULTIPLE BLOCK SECURITY | -- | | 0xA1 | NXP READ CONFIG | reg index | | 0xA2 | NXP WRITE CONFIG | reg index, value | | 0xB2 | NXP GET RANDOM | -- | | 0xB3 | NXP SET PASSWORD | pwd_id | Response decoding: flags byte 0x00 = OK, 0x01 = ERROR + error code. Inventory responses show UID. Read responses show data. ### ISO 14443-A command decode table | Byte(s) | Name | |---------|------| | 0x26 | REQA | | 0x52 | WUPA | | 0x50 00 | HLTA | | 0x93 20 | ANTICOL CL1 | | 0x93 70 | SELECT CL1 | | 0x95 20/70 | ANTICOL/SELECT CL2 | | 0x97 20/70 | ANTICOL/SELECT CL3 | | 0xE0 | RATS | | 0x02/0x03 | I-BLOCK | | 0xA2/0xA3 | R-ACK | | 0xB2/0xB3 | R-NAK | | 0xC2/0xF2 | S(DESELECT)/S(WTX) | Response decoding: ATQA as `ATQA xx xx`, SAK as `SAK xx`, ATS as `ATS [len]`. ### API ```python class TraceFormatter: def __init__(self, mode: str = "sim"): """mode: "sim", "reader", or "sniff" """ def format(self, direction: int, payload: bytes) -> str: """Returns fully formatted, colored, wrapped string with leading \\n.""" def print(self, direction: int, payload: bytes) -> None: """format() + print to stdout.""" ``` Decoder functions: `decode_15693(direction, payload) -> str | None` and `decode_14443a(direction, payload) -> str | None`. ### Integration - `sim_session.py`: instantiate `TraceFormatter("sim")` in `start_15693()`, call `self._formatter.print()` in `_trace_reader()` - 14443-A trace path: decode table built now, wired up when firmware trace path lands - No changes to `replay.py` or `InteractiveReader.dump_trace()` ### Out of scope - Replay/recorder trace formatting - InteractiveReader dump formatting - Protocol decode beyond 15693 + 14443-A