Files
pm3py/docs/plans/2026-03-17-stateful-table-firmware-plan.md
michael 2a56837935 docs: stateful response table firmware implementation plan
9 tasks: command IDs, iso15_tag_t extensions, sim_crypto, sim_table
header + implementation, appmain dispatch, native handler access
control + random UID, table fallback in default case, full build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 17:00:42 -07:00

796 lines
26 KiB
Markdown

# Stateful Response Table Firmware Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add a stateful response table engine to PM3 firmware for custom/vendor 15693 commands, with access control in the native handler and shared AES-CMAC crypto.
**Architecture:** New `sim_table.c/h` provides table storage, lookup with group-based state machine, and action execution (static response, EML read/write, AES-CMAC). The existing `SimTagIso15693()` native handler gains per-block access control and falls through to the table for unrecognized commands. Shared `sim_crypto.c/h` wraps existing `ulaes_cmac()`.
**Tech Stack:** C (ARM7 firmware), existing PM3 build system, existing `cmac_calc.c` for AES
**Working directory:** `/home/work/proxmark3/` on branch `sim-relay`
**Build command:** `make armsrc/obj/fullimage.elf` (firmware only, no client)
**Important:** All commits use `git commit --no-gpg-sign`. Firmware has no unit test framework — we verify by successful compilation. Python-side tests come later in a separate plan.
---
### Task 1: Command IDs
**Files:**
- Modify: `/home/work/proxmark3/include/pm3_cmd.h`
**Step 1: Add table command IDs**
After the `CMD_HF_SEOS_SIMULATE` line (~930), add:
```c
// Sim response table management
#define CMD_SIM_TABLE_UPLOAD 0x0950 // Host→FW: upload table (chunked)
#define CMD_SIM_TABLE_CLEAR 0x0951 // Host→FW: clear table
#define CMD_SIM_TABLE_UPDATE 0x0952 // Host→FW: add/modify single entry
```
**Step 2: Verify build**
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
Expected: Build succeeds
**Step 3: Commit**
```bash
cd /home/work/proxmark3
git add include/pm3_cmd.h
git commit --no-gpg-sign -m "feat: add CMD_SIM_TABLE_* command IDs"
```
---
### Task 2: iso15_tag_t extensions
**Files:**
- Modify: `/home/work/proxmark3/include/iso15.h`
**Step 1: Add new fields to iso15_tag_t**
Add these fields before the closing `} PACKED iso15_tag_t;` (after `trace_enabled`):
```c
// Stateful table support
uint8_t random_uid; // if nonzero, inventory returns random UID
uint8_t auth_state; // bitmask: authenticated passwords
// bit 0: read, bit 1: write, bit 2: privacy, bit 3: destroy
uint8_t lock_mode[ISO15693_TAG_MAX_PAGES];
// per-block access control:
// 0x00 = open
// 0x01 = permanently locked
// 0x02 = read needs auth (check auth_state bit 0)
// 0x04 = write needs auth (check auth_state bit 1)
// 0x06 = read+write need auth
```
**Step 2: Verify build**
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
Expected: Build succeeds
**Step 3: Commit**
```bash
cd /home/work/proxmark3
git add include/iso15.h
git commit --no-gpg-sign -m "feat: extend iso15_tag_t with auth_state, lock_mode, random_uid"
```
---
### Task 3: sim_crypto module
**Files:**
- Create: `/home/work/proxmark3/armsrc/sim_crypto.h`
- Create: `/home/work/proxmark3/armsrc/sim_crypto.c`
- Modify: `/home/work/proxmark3/armsrc/Makefile`
**Step 1: Create sim_crypto.h**
```c
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Shared crypto primitives for card simulation
//-----------------------------------------------------------------------------
#ifndef __SIM_CRYPTO_H
#define __SIM_CRYPTO_H
#include "common.h"
// AES-CMAC: compute MAC over msg using 16-byte key
// mac_out receives up to mac_len bytes of the 16-byte MAC
// Returns true on success
bool sim_aes_cmac(const uint8_t key[16], const uint8_t *msg,
uint8_t msg_len, uint8_t *mac_out, uint8_t mac_len);
#endif // __SIM_CRYPTO_H
```
**Step 2: Create sim_crypto.c**
```c
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Shared crypto primitives for card simulation
//-----------------------------------------------------------------------------
#include "sim_crypto.h"
#include "cmac_calc.h"
#include <string.h>
bool sim_aes_cmac(const uint8_t key[16], const uint8_t *msg,
uint8_t msg_len, uint8_t *mac_out, uint8_t mac_len) {
if (mac_len > 16) mac_len = 16;
uint8_t full_mac[16];
ulaes_cmac(key, 16, msg, msg_len, full_mac);
memcpy(mac_out, full_mac, mac_len);
return true;
}
```
**Step 3: Add to Makefile**
In `armsrc/Makefile`, find the `SRC_ASM` or `SRC_FIRM` source list (where `cmac_calc.c` appears). Add `sim_crypto.c \` on the line after `cmac_calc.c \`.
**Step 4: Verify build**
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
Expected: Build succeeds
**Step 5: Commit**
```bash
cd /home/work/proxmark3
git add armsrc/sim_crypto.h armsrc/sim_crypto.c armsrc/Makefile
git commit --no-gpg-sign -m "feat: add sim_crypto module (AES-CMAC wrapper)"
```
---
### Task 4: sim_table header
**Files:**
- Create: `/home/work/proxmark3/armsrc/sim_table.h`
**Step 1: Create sim_table.h**
```c
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Stateful response table for card simulation
//-----------------------------------------------------------------------------
#ifndef __SIM_TABLE_H
#define __SIM_TABLE_H
#include "common.h"
#include "iso15.h"
#define SIM_TABLE_MAX_MATCH 32
#define SIM_TABLE_MAX_RESPONSE 64
#define SIM_TABLE_MAX_ENTRIES 300
// EML action types
#define SIM_EML_NONE 0 // static response only
#define SIM_EML_READ 1 // read from EML into response
#define SIM_EML_WRITE 2 // write command data to EML
#define SIM_EML_AES_CMAC 3 // compute AES-CMAC, key from EML, challenge from cmd
// Match modes
#define SIM_MATCH_EXACT 0
#define SIM_MATCH_PREFIX 1
// Entry flags
#define SIM_FLAG_APPEND_CRC 0x01
#define SIM_FLAG_CONSUME 0x02 // one-shot: disable entry after use
typedef struct {
// Match
uint8_t match[SIM_TABLE_MAX_MATCH];
uint8_t match_len;
uint8_t match_mode; // SIM_MATCH_EXACT or SIM_MATCH_PREFIX
// Response
uint8_t response[SIM_TABLE_MAX_RESPONSE];
uint8_t response_len;
uint8_t response_flags; // SIM_FLAG_APPEND_CRC
// EML actions
uint8_t eml_action; // SIM_EML_*
uint16_t eml_offset; // EML byte offset for read/write/key
uint8_t eml_len; // bytes to read/write / MAC output length
uint8_t eml_resp_insert; // position in response to insert data (read/CMAC)
uint8_t cmd_data_offset; // position in command for write data / challenge
uint8_t cmd_data_len; // length of data to write / challenge length
// State
uint8_t group; // group this entry belongs to (0 = always active)
uint32_t activate_groups; // bitmask: groups to activate on match
uint32_t deactivate_groups; // bitmask: groups to deactivate on match
uint8_t set_auth; // auth_state bits to SET on match
uint8_t clear_auth; // auth_state bits to CLEAR on match
// Flags
uint8_t flags; // SIM_FLAG_CONSUME etc.
uint8_t _pad[1]; // alignment
} PACKED sim_table_entry_t; // ~128 bytes
// Initialize table in BigBuf with initial active group mask
void sim_table_init(uint32_t initial_groups);
// Clear all entries, reset state
void sim_table_clear(void);
// Add entry from serialized data (128 bytes)
// Returns true if entry was added
bool sim_table_add(const uint8_t *data, uint16_t len);
// Current entry count
uint16_t sim_table_count(void);
// Find first matching entry for command
// Returns NULL if no match
sim_table_entry_t *sim_table_lookup(const uint8_t *cmd, uint8_t cmd_len);
// Execute matched entry: build response, apply state changes
// tag pointer needed for EML access and auth_state updates
void sim_table_execute(sim_table_entry_t *e,
const uint8_t *cmd, uint8_t cmd_len,
uint8_t *resp_buf, uint16_t *resp_len,
iso15_tag_t *tag);
// Get current active groups mask
uint32_t sim_table_get_active_groups(void);
#endif // __SIM_TABLE_H
```
**Step 2: Verify build**
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
Expected: Build succeeds (header only, no .c yet)
**Step 3: Commit**
```bash
cd /home/work/proxmark3
git add armsrc/sim_table.h
git commit --no-gpg-sign -m "feat: add sim_table.h — stateful response table header"
```
---
### Task 5: sim_table implementation
**Files:**
- Create: `/home/work/proxmark3/armsrc/sim_table.c`
- Modify: `/home/work/proxmark3/armsrc/Makefile`
**Step 1: Create sim_table.c**
```c
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Stateful response table for card simulation
//-----------------------------------------------------------------------------
#include "sim_table.h"
#include "sim_crypto.h"
#include "BigBuf.h"
#include "dbprint.h"
#include <string.h>
static struct {
sim_table_entry_t *entries;
uint16_t count;
uint16_t max_entries;
uint32_t active_groups;
} g_table;
void sim_table_init(uint32_t initial_groups) {
// Allocate table from BigBuf
// We use a dedicated region at the end of BigBuf
uint32_t table_size = SIM_TABLE_MAX_ENTRIES * sizeof(sim_table_entry_t);
g_table.entries = (sim_table_entry_t *)BigBuf_calloc(table_size);
g_table.count = 0;
g_table.max_entries = SIM_TABLE_MAX_ENTRIES;
g_table.active_groups = initial_groups;
if (g_dbglevel >= DBG_INFO)
Dbprintf("sim_table_init: %d entries max, initial groups=0x%08X",
g_table.max_entries, initial_groups);
}
void sim_table_clear(void) {
if (g_table.entries) {
memset(g_table.entries, 0, g_table.count * sizeof(sim_table_entry_t));
}
g_table.count = 0;
g_table.active_groups = 0;
}
bool sim_table_add(const uint8_t *data, uint16_t len) {
if (g_table.entries == NULL) return false;
if (g_table.count >= g_table.max_entries) return false;
if (len < sizeof(sim_table_entry_t)) return false;
memcpy(&g_table.entries[g_table.count], data, sizeof(sim_table_entry_t));
g_table.count++;
return true;
}
uint16_t sim_table_count(void) {
return g_table.count;
}
uint32_t sim_table_get_active_groups(void) {
return g_table.active_groups;
}
sim_table_entry_t *sim_table_lookup(const uint8_t *cmd, uint8_t cmd_len) {
for (uint16_t i = 0; i < g_table.count; i++) {
sim_table_entry_t *e = &g_table.entries[i];
// Skip empty entries (consumed one-shots)
if (e->match_len == 0) continue;
// Skip if entry's group is not active (group 0 = always active)
if (e->group != 0 && !(g_table.active_groups & (1u << e->group))) {
continue;
}
// Match
bool matched = false;
if (e->match_mode == SIM_MATCH_EXACT) {
if (cmd_len == e->match_len &&
memcmp(cmd, e->match, e->match_len) == 0) {
matched = true;
}
} else if (e->match_mode == SIM_MATCH_PREFIX) {
if (cmd_len >= e->match_len &&
memcmp(cmd, e->match, e->match_len) == 0) {
matched = true;
}
}
if (!matched) continue;
// Match found — apply state changes
if (e->activate_groups)
g_table.active_groups |= e->activate_groups;
if (e->deactivate_groups)
g_table.active_groups &= ~e->deactivate_groups;
// Consume if one-shot
if (e->flags & SIM_FLAG_CONSUME) {
e->match_len = 0; // mark as empty
}
return e;
}
return NULL;
}
void sim_table_execute(sim_table_entry_t *e,
const uint8_t *cmd, uint8_t cmd_len,
uint8_t *resp_buf, uint16_t *resp_len,
iso15_tag_t *tag) {
// Apply auth state changes
if (e->set_auth) tag->auth_state |= e->set_auth;
if (e->clear_auth) tag->auth_state &= ~e->clear_auth;
// Start with static response
memcpy(resp_buf, e->response, e->response_len);
*resp_len = e->response_len;
switch (e->eml_action) {
case SIM_EML_NONE:
break;
case SIM_EML_READ: {
// Read from tag EML data into response
uint16_t off = e->eml_offset;
uint8_t len = e->eml_len;
uint8_t ins = e->eml_resp_insert;
if (off + len <= sizeof(tag->data)) {
memcpy(resp_buf + ins, tag->data + off, len);
if (ins + len > *resp_len)
*resp_len = ins + len;
}
break;
}
case SIM_EML_WRITE: {
// Write command data to tag EML
uint8_t src_off = e->cmd_data_offset;
uint8_t len = e->eml_len;
uint16_t dst_off = e->eml_offset;
if (src_off + len <= cmd_len && dst_off + len <= sizeof(tag->data)) {
memcpy(tag->data + dst_off, cmd + src_off, len);
}
break;
}
case SIM_EML_AES_CMAC: {
// Key from EML, challenge from command, MAC into response
uint8_t key[16];
uint16_t key_off = e->eml_offset;
if (key_off + 16 > sizeof(tag->data)) break;
memcpy(key, tag->data + key_off, 16);
uint8_t challenge_off = e->cmd_data_offset;
uint8_t challenge_len = e->cmd_data_len;
if (challenge_off + challenge_len > cmd_len) break;
uint8_t mac_len = e->eml_len;
uint8_t mac_ins = e->eml_resp_insert;
sim_aes_cmac(key, cmd + challenge_off, challenge_len,
resp_buf + mac_ins, mac_len);
if (mac_ins + mac_len > *resp_len)
*resp_len = mac_ins + mac_len;
break;
}
}
}
```
**Step 2: Add to Makefile**
Add `sim_table.c \` after the `sim_crypto.c \` line added in Task 3.
**Step 3: Verify build**
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
Expected: Build succeeds
**Step 4: Commit**
```bash
cd /home/work/proxmark3
git add armsrc/sim_table.c armsrc/Makefile
git commit --no-gpg-sign -m "feat: add sim_table.c — stateful response table engine"
```
---
### Task 6: Command dispatch in appmain.c
**Files:**
- Modify: `/home/work/proxmark3/armsrc/appmain.c`
**Step 1: Add include**
Near the top of `appmain.c`, with the other includes, add:
```c
#include "sim_table.h"
```
**Step 2: Add dispatch cases**
Find the `CMD_HF_SEOS_SIMULATE` case (around line 1571). After it, add:
```c
case CMD_SIM_TABLE_UPLOAD: {
// First chunk: init table, then add entries
uint32_t initial_groups = 0xFFFFFFFF; // all groups active by default
if (packet->length >= 4) {
// First 4 bytes = initial_groups mask
memcpy(&initial_groups, packet->data.asBytes, 4);
}
sim_table_init(initial_groups);
// Remaining bytes = entries
uint16_t data_off = 4;
while (data_off + sizeof(sim_table_entry_t) <= packet->length) {
sim_table_add(packet->data.asBytes + data_off, sizeof(sim_table_entry_t));
data_off += sizeof(sim_table_entry_t);
}
reply_ng(CMD_SIM_TABLE_UPLOAD, PM3_SUCCESS, NULL, 0);
break;
}
case CMD_SIM_TABLE_CLEAR: {
sim_table_clear();
reply_ng(CMD_SIM_TABLE_CLEAR, PM3_SUCCESS, NULL, 0);
break;
}
case CMD_SIM_TABLE_UPDATE: {
// Add single entry (or chunk of entries)
uint16_t off = 0;
while (off + sizeof(sim_table_entry_t) <= packet->length) {
sim_table_add(packet->data.asBytes + off, sizeof(sim_table_entry_t));
off += sizeof(sim_table_entry_t);
}
reply_ng(CMD_SIM_TABLE_UPDATE, PM3_SUCCESS, NULL, 0);
break;
}
```
**Step 3: Verify build**
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
Expected: Build succeeds
**Step 4: Commit**
```bash
cd /home/work/proxmark3
git add armsrc/appmain.c
git commit --no-gpg-sign -m "feat: dispatch CMD_SIM_TABLE_* in appmain"
```
---
### Task 7: Native handler access control + random UID
**Files:**
- Modify: `/home/work/proxmark3/armsrc/iso15693.c`
**Step 1: Add includes**
Near the top of `iso15693.c`, add:
```c
#include "sim_table.h"
```
**Step 2: Initialize new fields in SimTagIso15693**
In `SimTagIso15693()`, after the existing field initializations (around line 2193, after `tag->trace_enabled`), add:
```c
tag->random_uid = 0;
tag->auth_state = 0;
memset(tag->lock_mode, 0, sizeof(tag->lock_mode));
```
**Step 3: Add random UID to inventory response**
Find the inventory response block (around line 2412-2416):
```c
// No error: Answer
recv[0] = ISO15_NOERROR;
recv[1] = tag->dsfid;
memcpy(&recv[2], tag->uid, 8);
recvLen = 10;
```
Replace with:
```c
// No error: Answer
recv[0] = ISO15_NOERROR;
recv[1] = tag->dsfid;
if (tag->random_uid) {
// Privacy mode: return random UID
for (int i = 0; i < 8; i++)
recv[2 + i] = prand() & 0xFF;
} else {
memcpy(&recv[2], tag->uid, 8);
}
recvLen = 10;
```
Also do the same for the second inventory path (around line 2450-2453 in the `switch(cmd[1])` case `ISO15693_INVENTORY`):
```c
case ISO15693_INVENTORY:
if (g_dbglevel >= DBG_DEBUG) Dbprintf("Inventory cmd");
recv[0] = ISO15_NOERROR;
recv[1] = tag->dsfid;
if (tag->random_uid) {
for (int i = 0; i < 8; i++)
recv[2 + i] = prand() & 0xFF;
} else {
memcpy(&recv[2], tag->uid, 8);
}
recvLen = 10;
break;
```
**Step 4: Add access control to READBLOCK**
Find the `ISO15693_READBLOCK` case (around line 2459). After the `pageNum` extraction and before the existing bounds check, add access control:
```c
case ISO15693_READBLOCK:
if (g_dbglevel >= DBG_DEBUG) Dbprintf("ReadBlock cmd");
pageNum = cmd[cmdCpt++];
// Access control check
if (pageNum < ISO15693_TAG_MAX_PAGES) {
uint8_t lm = tag->lock_mode[pageNum];
if (lm == 0x01) {
error = ISO15_ERROR_BLOCK_LOCKED;
break;
}
if ((lm & 0x02) && !(tag->auth_state & 0x01)) {
error = ISO15_ERROR_BLOCK_UNAVAILABLE;
break;
}
}
if (pageNum >= tag->pagesCount)
error = ISO15_ERROR_BLOCK_UNAVAILABLE;
else {
// ... existing read logic unchanged ...
```
**Step 5: Add access control to WRITEBLOCK**
Find the `ISO15693_WRITEBLOCK` case (around line 2476). After `pageNum` extraction, add:
```c
case ISO15693_WRITEBLOCK:
if (g_dbglevel >= DBG_DEBUG) Dbprintf("WriteBlock cmd");
pageNum = cmd[cmdCpt++];
// Access control check
if (pageNum < ISO15693_TAG_MAX_PAGES) {
uint8_t lm = tag->lock_mode[pageNum];
if (lm == 0x01) {
error = ISO15_ERROR_BLOCK_LOCKED;
break;
}
if ((lm & 0x04) && !(tag->auth_state & 0x02)) {
error = ISO15_ERROR_BLOCK_UNAVAILABLE;
break;
}
}
if (pageNum >= tag->pagesCount)
error = ISO15_ERROR_BLOCK_UNAVAILABLE;
else {
// ... existing write logic unchanged ...
```
**Step 6: Verify build**
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
Expected: Build succeeds
**Step 7: Commit**
```bash
cd /home/work/proxmark3
git add armsrc/iso15693.c
git commit --no-gpg-sign -m "feat: add access control + random UID to 15693 sim handler"
```
---
### Task 8: Table fallback in default case
**Files:**
- Modify: `/home/work/proxmark3/armsrc/iso15693.c`
**Step 1: Replace default case with table lookup + relay fallback**
Find the `default:` case in the command switch (around line 2629). Replace the entire case body:
```c
default:
// --- Table lookup for custom/vendor commands ---
if (sim_table_count() > 0) {
sim_table_entry_t *entry = sim_table_lookup(cmd, cmd_len);
if (entry) {
sim_table_execute(entry, cmd, cmd_len, recv, &recvLen, tag);
if (g_dbglevel >= DBG_DEBUG)
Dbprintf("Table match for CMD 0x%02X, resp %d bytes",
cmd[1], recvLen);
break;
}
}
// No table match — fall through to relay
if (tag->relay_resp_ready &&
tag->relay_cmd_len == cmd_len &&
memcmp(tag->relay_cmd, cmd, cmd_len) == 0) {
memcpy(recv, tag->relay_resp, tag->relay_resp_len);
recvLen = tag->relay_resp_len;
tag->relay_resp_ready = false;
if (g_dbglevel >= DBG_DEBUG)
Dbprintf("Serving cached relay response for CMD 0x%02X", cmd[1]);
} else {
memcpy(tag->relay_cmd, cmd, MIN(cmd_len, sizeof(tag->relay_cmd)));
tag->relay_cmd_len = cmd_len;
tag->relay_pending = true;
tag->relay_resp_ready = false;
reply_ng(CMD_HF_ISO15693_SIM_RELAY, PM3_SUCCESS, cmd, cmd_len);
if (g_dbglevel >= DBG_DEBUG)
Dbprintf("Relayed CMD 0x%02X to host (%d bytes)", cmd[1], cmd_len);
recvLen = 0;
}
break;
```
**Step 2: Verify build**
Run: `cd /home/work/proxmark3 && make armsrc/obj/fullimage.elf -j`
Expected: Build succeeds
**Step 3: Commit**
```bash
cd /home/work/proxmark3
git add armsrc/iso15693.c
git commit --no-gpg-sign -m "feat: table lookup before relay fallback in 15693 sim default case"
```
---
### Task 9: Verify full build + flash size
**Step 1: Clean build**
Run: `cd /home/work/proxmark3 && make clean && make -j`
Expected: Full build succeeds (client + firmware)
**Step 2: Check firmware size**
Run: `cd /home/work/proxmark3 && arm-none-eabi-size armsrc/obj/fullimage.elf`
Expected: text section fits in 512KB flash. Note the size for comparison with upstream.
**Step 3: Commit (if any Makefile adjustments needed)**
If build revealed issues, fix and commit. Otherwise, this task is just verification.