Merge branch 'main' into patch-iso14443-4-blockvals

This commit is contained in:
Aaron Tulino
2026-07-28 10:32:47 -07:00
committed by GitHub
2 changed files with 119 additions and 90 deletions
@@ -55,15 +55,7 @@ static nfc_tag_14a_4_information_t *m_tag_information = NULL;
static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res;
/* T=CL session state */
static uint8_t m_block_num = 0;
static bool m_cid_supported = false;
static uint8_t m_cid = 0;
static uint8_t m_apdu_buf[NFC_14A_4_MAX_APDU];
static uint16_t m_apdu_len = 0;
static bool m_apdu_pending = false;
static uint8_t m_resp_buf[NFC_14A_4_MAX_APDU];
static uint16_t m_resp_len = 0;
static bool m_response_ready = false;
static nfc_tag_14a_4_tcl_state_t m_tcl_session_state;
/* TX scratch buffer */
static uint8_t m_tx_buf[NFC_14A_4_MAX_APDU + 4];
@@ -160,34 +152,35 @@ static bool find_static_response(const uint8_t *apdu, uint16_t apdu_len,
/* TX helpers */
/* ------------------------------------------------------------------ */
static void send_iblock(const uint8_t *data, uint16_t len) {
uint8_t pcb = 0x02 | (m_block_num & 0x01);
if (m_cid_supported) pcb |= PCB_CID_FOLLOWING;
static void send_iblock(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
uint16_t len = m_tcl_session_state->m_resp_len;
uint8_t pcb = 0x02 | (m_tcl_session_state->m_block_num & 0x01);
if (m_tcl_session_state->m_cid_supported) pcb |= PCB_CID_FOLLOWING;
uint8_t off = 0;
m_tx_buf[off++] = pcb;
if (m_cid_supported) m_tx_buf[off++] = m_cid & 0x0F;
if (m_tcl_session_state->m_cid_supported) m_tx_buf[off++] = m_tcl_session_state->m_cid & 0x0F;
if (len > NFC_14A_4_MAX_APDU) len = NFC_14A_4_MAX_APDU;
memcpy(&m_tx_buf[off], data, len);
memcpy(&m_tx_buf[off], m_tcl_session_state->m_resp_buf, len);
nfc_tag_14a_tx_bytes(m_tx_buf, off + len, true);
m_block_num ^= 1;
m_tcl_session_state->m_block_num ^= 1;
}
static void send_rack(void) {
uint8_t pcb = PCB_RBLOCK_VAL | (m_block_num & 0x01);
if (m_cid_supported) {
static void send_rack(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
uint8_t pcb = PCB_RBLOCK_VAL | (m_tcl_session_state->m_block_num & 0x01);
if (m_tcl_session_state->m_cid_supported) {
pcb |= PCB_CID_FOLLOWING;
uint8_t buf[2] = { pcb, m_cid & 0x0F };
uint8_t buf[2] = { pcb, m_tcl_session_state->m_cid & 0x0F };
nfc_tag_14a_tx_bytes(buf, 2, true);
} else {
nfc_tag_14a_tx_bytes(&pcb, 1, true);
}
}
static void send_wtx(void) {
static void send_wtx(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
uint8_t buf[3];
uint8_t off = 0;
buf[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_cid_supported) buf[off++] = m_cid & 0x0F;
buf[off++] = PCB_SBLOCK_WTX | (m_tcl_session_state->m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_tcl_session_state->m_cid_supported) buf[off++] = m_tcl_session_state->m_cid & 0x0F;
buf[off++] = WTX_VALUE;
nfc_tag_14a_tx_bytes(buf, off, true);
}
@@ -196,8 +189,18 @@ static void send_wtx(void) {
/* State handler (called from NFCT ISR on each received frame) */
/* ------------------------------------------------------------------ */
static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
if (szBytes == 0) return;
void nfc_tag_14a_4_base_respond(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
if (m_tcl_session_state->m_response_ready) {
m_tcl_session_state->m_response_ready = false;
send_iblock(m_tcl_session_state);
} else {
/* No response ready — keep reader alive with WTX */
send_wtx(m_tcl_session_state);
}
}
bool nfc_tag_14a_4_base_handler(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state, uint8_t *data, uint16_t szBytes) {
if (szBytes == 0) return false;
uint8_t pcb = data[0];
/* ---- S-block ---- */
@@ -206,37 +209,37 @@ static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
/* Echo DESELECT */
nfc_tag_14a_tx_bytes(data, szBytes, true);
nfc_tag_14a_4_reset_handler();
return;
return false;
}
if ((pcb & 0x3F) == (PCB_SBLOCK_WTX & 0x3F)) {
/* Reader sending WTX — echo back with our WTXM */
uint8_t wtxm = (szBytes > 1) ? data[szBytes - 1] & 0x3F : WTX_VALUE;
uint8_t resp[3];
uint8_t off = 0;
resp[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_cid_supported) resp[off++] = m_cid & 0x0F;
resp[off++] = PCB_SBLOCK_WTX | (m_tcl_session_state->m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_tcl_session_state->m_cid_supported) resp[off++] = m_tcl_session_state->m_cid & 0x0F;
resp[off++] = wtxm;
nfc_tag_14a_tx_bytes(resp, off, true);
/* If we now have a response ready, send it next I-block */
if (m_response_ready) {
m_response_ready = false;
send_iblock(m_resp_buf, m_resp_len);
if (m_tcl_session_state->m_response_ready) {
m_tcl_session_state->m_response_ready = false;
send_iblock(m_tcl_session_state);
}
return;
return false;
}
if ((pcb & PCB_PPS) == PCB_PPS) {
/* Echo back with our own PPS */
uint8_t resp[1] = { PCB_PPS };
nfc_tag_14a_tx_bytes(resp, 1, true);
return;
return false;
}
return;
return false;
}
/* ---- R-block ---- */
if (is_rblock(pcb)) {
send_rack();
return;
send_rack(m_tcl_session_state);
return false;
}
/* ---- I-block ---- */
@@ -248,14 +251,14 @@ static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
uint8_t offset = 1;
if (has_cid) {
m_cid_supported = true;
m_tcl_session_state->m_cid_supported = true;
offset++; /* skip CID byte */
}
if (has_nad) offset++;
if (offset >= szBytes) {
send_rack();
return;
send_rack(m_tcl_session_state);
return false;
}
uint16_t apdu_len = szBytes - offset;
@@ -263,95 +266,102 @@ static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
m_dbg_iblocks_rx++;
m_dbg_last_rx_pcb = pcb;
NRF_LOG_INFO("14A4 I-block #%d: reader_blk=%d m_block_num=%d apdu_len=%d",
m_dbg_iblocks_rx, reader_blknum, m_block_num, apdu_len);
NRF_LOG_INFO("14A4 I-block #%d: reader_blk=%d m_tcl_session_state->m_block_num=%d apdu_len=%d",
m_dbg_iblocks_rx, reader_blknum, m_tcl_session_state->m_block_num, apdu_len);
/* Block number check per ISO14443-4 §7.5.3.3:
* If block number matches expected, process new APDU.
* If block number does NOT match, it is a retransmit —
* resend the last response without re-processing. */
if (reader_blknum != (m_block_num & 0x01)) {
if (reader_blknum != (m_tcl_session_state->m_block_num & 0x01)) {
/* Retransmit: resend last response */
if (m_resp_len > 0) {
if (m_tcl_session_state->m_resp_len > 0) {
/* Restore block num to what we sent last time and resend */
m_block_num ^= 1; /* undo the increment from last send */
send_iblock(m_resp_buf, m_resp_len);
m_tcl_session_state->m_block_num ^= 1; /* undo the increment from last send */
send_iblock(m_tcl_session_state);
} else {
send_rack();
send_rack(m_tcl_session_state);
}
return;
return false;
}
memcpy(m_apdu_buf, &data[offset], apdu_len);
m_apdu_len = apdu_len;
m_apdu_pending = true;
m_response_ready = false;
memcpy(m_tcl_session_state->m_apdu_buf, &data[offset], apdu_len);
m_tcl_session_state->m_apdu_len = apdu_len;
m_tcl_session_state->m_apdu_pending = true;
m_tcl_session_state->m_response_ready = false;
if (more_chain) {
send_rack();
return;
send_rack(m_tcl_session_state);
return false;
}
/* APDU complete — check static table first, then WTX */
{
uint8_t *static_resp = NULL;
uint16_t static_len = 0;
bool _found = find_static_response(m_apdu_buf, apdu_len,
&static_resp, &static_len);
m_dbg_last_match = _found ? 1 : 0;
NRF_LOG_INFO("14A4 find_static: found=%d static_len=%d resp_count=%d",
_found, static_len, m_static_resp_count);
if (_found) {
m_dbg_iblocks_tx++;
memcpy(m_resp_buf, static_resp, static_len);
m_resp_len = static_len;
send_iblock(m_resp_buf, m_resp_len);
} else if (m_response_ready) {
m_response_ready = false;
send_iblock(m_resp_buf, m_resp_len);
} else {
/* No response ready — keep reader alive with WTX */
send_wtx();
}
}
return;
return true;
}
NRF_LOG_INFO("14A-4: unknown PCB 0x%02x", pcb);
return false;
}
/* ------------------------------------------------------------------ */
/* State handler (called from NFCT ISR on each received frame) */
/* ------------------------------------------------------------------ */
static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
if (!nfc_tag_14a_4_base_handler(&m_tcl_session_state, data, szBytes)) return;
/* APDU complete — check static table first, then WTX */
uint8_t *static_resp = NULL;
uint16_t static_len = 0;
bool _found = find_static_response(m_tcl_session_state.m_apdu_buf, m_tcl_session_state.m_apdu_len,
&static_resp, &static_len);
m_dbg_last_match = _found ? 1 : 0;
NRF_LOG_INFO("14A4 find_static: found=%d static_len=%d resp_count=%d",
_found, static_len, m_static_resp_count);
if (_found) {
m_dbg_iblocks_tx++;
memcpy(m_tcl_session_state.m_resp_buf, static_resp, static_len);
m_tcl_session_state.m_resp_len = static_len;
m_tcl_session_state.m_response_ready = true;
}
nfc_tag_14a_4_base_respond(&m_tcl_session_state);
}
/* ------------------------------------------------------------------ */
/* APDU relay API (for host-driven responses) */
/* ------------------------------------------------------------------ */
bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length) {
if (!m_apdu_pending) return false;
m_apdu_pending = false;
*length = m_apdu_len;
memcpy(buf, m_apdu_buf, m_apdu_len);
if (!m_tcl_session_state.m_apdu_pending) return false;
m_tcl_session_state.m_apdu_pending = false;
*length = m_tcl_session_state.m_apdu_len;
memcpy(buf, m_tcl_session_state.m_apdu_buf, m_tcl_session_state.m_apdu_len);
return true;
}
void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length) {
if (length > NFC_14A_4_MAX_APDU) length = NFC_14A_4_MAX_APDU;
memcpy(m_resp_buf, data, length);
m_resp_len = length;
m_response_ready = true;
memcpy(m_tcl_session_state.m_resp_buf, data, length);
m_tcl_session_state.m_resp_len = length;
m_tcl_session_state.m_response_ready = true;
}
/* ------------------------------------------------------------------ */
/* Reset handler */
/* ------------------------------------------------------------------ */
void nfc_tag_14a_4_reset_state(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
m_tcl_session_state->m_block_num = 0;
m_tcl_session_state->m_cid_supported = false;
m_tcl_session_state->m_cid = 0;
m_tcl_session_state->m_apdu_pending = false;
m_tcl_session_state->m_response_ready = false;
m_tcl_session_state->m_apdu_len = 0;
m_tcl_session_state->m_resp_len = 0;
}
void nfc_tag_14a_4_reset_handler(void) {
m_block_num = 0;
m_cid_supported = false;
m_cid = 0;
m_apdu_pending = false;
m_response_ready = false;
m_apdu_len = 0;
m_resp_len = 0;
nfc_tag_14a_4_reset_state(&m_tcl_session_state);
}
void nfc_tag_14a_4_get_debug_counters(uint8_t *rx, uint8_t *tx,
@@ -48,9 +48,27 @@ typedef struct __attribute__((packed)) {
}
nfc_tag_14a_4_information_t;
/* T=CL session state */
typedef struct __attribute__((packed)) {
uint8_t m_block_num;
bool m_cid_supported;
uint8_t m_cid;
uint8_t m_apdu_buf[NFC_14A_4_MAX_APDU];
uint16_t m_apdu_len;
bool m_apdu_pending;
uint8_t m_resp_buf[NFC_14A_4_MAX_APDU];
uint16_t m_resp_len;
bool m_response_ready;
}
nfc_tag_14a_4_tcl_state_t;
/* Anti-collision resource — used by get_coll_res_data in app_cmd.c */
nfc_tag_14a_coll_res_reference_t *nfc_tag_14a_4_get_coll_res(void);
/* Handles the low-level ISO14443-4 communication. Returns true when a complete APDU has been read and is ready for response. */
bool nfc_tag_14a_4_base_handler(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state, uint8_t *data, uint16_t szBytes);
void nfc_tag_14a_4_base_respond(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state);
/* tag_base_map callbacks */
int nfc_tag_14a_4_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
int nfc_tag_14a_4_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
@@ -66,6 +84,7 @@ bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length);
void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length);
/* Reset handler */
void nfc_tag_14a_4_reset_state(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state);
void nfc_tag_14a_4_reset_handler(void);
#endif /* NFC_14A_4_H */