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; static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res;
/* T=CL session state */ /* T=CL session state */
static uint8_t m_block_num = 0; static nfc_tag_14a_4_tcl_state_t m_tcl_session_state;
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;
/* TX scratch buffer */ /* TX scratch buffer */
static uint8_t m_tx_buf[NFC_14A_4_MAX_APDU + 4]; 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 */ /* TX helpers */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
static void send_iblock(const uint8_t *data, uint16_t len) { static void send_iblock(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
uint8_t pcb = 0x02 | (m_block_num & 0x01); uint16_t len = m_tcl_session_state->m_resp_len;
if (m_cid_supported) pcb |= PCB_CID_FOLLOWING; 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; uint8_t off = 0;
m_tx_buf[off++] = pcb; 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; 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); 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) { static void send_rack(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
uint8_t pcb = PCB_RBLOCK_VAL | (m_block_num & 0x01); uint8_t pcb = PCB_RBLOCK_VAL | (m_tcl_session_state->m_block_num & 0x01);
if (m_cid_supported) { if (m_tcl_session_state->m_cid_supported) {
pcb |= PCB_CID_FOLLOWING; 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); nfc_tag_14a_tx_bytes(buf, 2, true);
} else { } else {
nfc_tag_14a_tx_bytes(&pcb, 1, true); 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 buf[3];
uint8_t off = 0; uint8_t off = 0;
buf[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0); buf[off++] = PCB_SBLOCK_WTX | (m_tcl_session_state->m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_cid_supported) buf[off++] = m_cid & 0x0F; if (m_tcl_session_state->m_cid_supported) buf[off++] = m_tcl_session_state->m_cid & 0x0F;
buf[off++] = WTX_VALUE; buf[off++] = WTX_VALUE;
nfc_tag_14a_tx_bytes(buf, off, true); 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) */ /* State handler (called from NFCT ISR on each received frame) */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) { void nfc_tag_14a_4_base_respond(nfc_tag_14a_4_tcl_state_t *m_tcl_session_state) {
if (szBytes == 0) return; 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]; uint8_t pcb = data[0];
/* ---- S-block ---- */ /* ---- S-block ---- */
@@ -206,37 +209,37 @@ static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
/* Echo DESELECT */ /* Echo DESELECT */
nfc_tag_14a_tx_bytes(data, szBytes, true); nfc_tag_14a_tx_bytes(data, szBytes, true);
nfc_tag_14a_4_reset_handler(); nfc_tag_14a_4_reset_handler();
return; return false;
} }
if ((pcb & 0x3F) == (PCB_SBLOCK_WTX & 0x3F)) { if ((pcb & 0x3F) == (PCB_SBLOCK_WTX & 0x3F)) {
/* Reader sending WTX — echo back with our WTXM */ /* Reader sending WTX — echo back with our WTXM */
uint8_t wtxm = (szBytes > 1) ? data[szBytes - 1] & 0x3F : WTX_VALUE; uint8_t wtxm = (szBytes > 1) ? data[szBytes - 1] & 0x3F : WTX_VALUE;
uint8_t resp[3]; uint8_t resp[3];
uint8_t off = 0; uint8_t off = 0;
resp[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0); resp[off++] = PCB_SBLOCK_WTX | (m_tcl_session_state->m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_cid_supported) resp[off++] = m_cid & 0x0F; if (m_tcl_session_state->m_cid_supported) resp[off++] = m_tcl_session_state->m_cid & 0x0F;
resp[off++] = wtxm; resp[off++] = wtxm;
nfc_tag_14a_tx_bytes(resp, off, true); nfc_tag_14a_tx_bytes(resp, off, true);
/* If we now have a response ready, send it next I-block */ /* If we now have a response ready, send it next I-block */
if (m_response_ready) { if (m_tcl_session_state->m_response_ready) {
m_response_ready = false; m_tcl_session_state->m_response_ready = false;
send_iblock(m_resp_buf, m_resp_len); send_iblock(m_tcl_session_state);
} }
return; return false;
} }
if ((pcb & PCB_PPS) == PCB_PPS) { if ((pcb & PCB_PPS) == PCB_PPS) {
/* Echo back with our own PPS */ /* Echo back with our own PPS */
uint8_t resp[1] = { PCB_PPS }; uint8_t resp[1] = { PCB_PPS };
nfc_tag_14a_tx_bytes(resp, 1, true); nfc_tag_14a_tx_bytes(resp, 1, true);
return; return false;
} }
return; return false;
} }
/* ---- R-block ---- */ /* ---- R-block ---- */
if (is_rblock(pcb)) { if (is_rblock(pcb)) {
send_rack(); send_rack(m_tcl_session_state);
return; return false;
} }
/* ---- I-block ---- */ /* ---- I-block ---- */
@@ -248,14 +251,14 @@ static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
uint8_t offset = 1; uint8_t offset = 1;
if (has_cid) { if (has_cid) {
m_cid_supported = true; m_tcl_session_state->m_cid_supported = true;
offset++; /* skip CID byte */ offset++; /* skip CID byte */
} }
if (has_nad) offset++; if (has_nad) offset++;
if (offset >= szBytes) { if (offset >= szBytes) {
send_rack(); send_rack(m_tcl_session_state);
return; return false;
} }
uint16_t apdu_len = szBytes - offset; 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_iblocks_rx++;
m_dbg_last_rx_pcb = pcb; m_dbg_last_rx_pcb = pcb;
NRF_LOG_INFO("14A4 I-block #%d: reader_blk=%d m_block_num=%d apdu_len=%d", 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_block_num, apdu_len); 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: /* Block number check per ISO14443-4 §7.5.3.3:
* If block number matches expected, process new APDU. * If block number matches expected, process new APDU.
* If block number does NOT match, it is a retransmit — * If block number does NOT match, it is a retransmit —
* resend the last response without re-processing. */ * 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 */ /* 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 */ /* Restore block num to what we sent last time and resend */
m_block_num ^= 1; /* undo the increment from last send */ m_tcl_session_state->m_block_num ^= 1; /* undo the increment from last send */
send_iblock(m_resp_buf, m_resp_len); send_iblock(m_tcl_session_state);
} else { } else {
send_rack(); send_rack(m_tcl_session_state);
} }
return; return false;
} }
memcpy(m_apdu_buf, &data[offset], apdu_len); memcpy(m_tcl_session_state->m_apdu_buf, &data[offset], apdu_len);
m_apdu_len = apdu_len; m_tcl_session_state->m_apdu_len = apdu_len;
m_apdu_pending = true; m_tcl_session_state->m_apdu_pending = true;
m_response_ready = false; m_tcl_session_state->m_response_ready = false;
if (more_chain) { if (more_chain) {
send_rack(); send_rack(m_tcl_session_state);
return; return false;
} }
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 */ /* APDU complete — check static table first, then WTX */
{
uint8_t *static_resp = NULL; uint8_t *static_resp = NULL;
uint16_t static_len = 0; uint16_t static_len = 0;
bool _found = find_static_response(m_apdu_buf, apdu_len, bool _found = find_static_response(m_tcl_session_state.m_apdu_buf, m_tcl_session_state.m_apdu_len,
&static_resp, &static_len); &static_resp, &static_len);
m_dbg_last_match = _found ? 1 : 0; m_dbg_last_match = _found ? 1 : 0;
NRF_LOG_INFO("14A4 find_static: found=%d static_len=%d resp_count=%d", NRF_LOG_INFO("14A4 find_static: found=%d static_len=%d resp_count=%d",
_found, static_len, m_static_resp_count); _found, static_len, m_static_resp_count);
if (_found) { if (_found) {
m_dbg_iblocks_tx++; m_dbg_iblocks_tx++;
memcpy(m_resp_buf, static_resp, static_len); memcpy(m_tcl_session_state.m_resp_buf, static_resp, static_len);
m_resp_len = static_len; m_tcl_session_state.m_resp_len = static_len;
send_iblock(m_resp_buf, m_resp_len); m_tcl_session_state.m_response_ready = true;
} 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;
} }
NRF_LOG_INFO("14A-4: unknown PCB 0x%02x", pcb); nfc_tag_14a_4_base_respond(&m_tcl_session_state);
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* APDU relay API (for host-driven responses) */ /* APDU relay API (for host-driven responses) */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length) { bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length) {
if (!m_apdu_pending) return false; if (!m_tcl_session_state.m_apdu_pending) return false;
m_apdu_pending = false; m_tcl_session_state.m_apdu_pending = false;
*length = m_apdu_len; *length = m_tcl_session_state.m_apdu_len;
memcpy(buf, m_apdu_buf, m_apdu_len); memcpy(buf, m_tcl_session_state.m_apdu_buf, m_tcl_session_state.m_apdu_len);
return true; return true;
} }
void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length) { 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; if (length > NFC_14A_4_MAX_APDU) length = NFC_14A_4_MAX_APDU;
memcpy(m_resp_buf, data, length); memcpy(m_tcl_session_state.m_resp_buf, data, length);
m_resp_len = length; m_tcl_session_state.m_resp_len = length;
m_response_ready = true; m_tcl_session_state.m_response_ready = true;
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Reset handler */ /* 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) { void nfc_tag_14a_4_reset_handler(void) {
m_block_num = 0; nfc_tag_14a_4_reset_state(&m_tcl_session_state);
m_cid_supported = false;
m_cid = 0;
m_apdu_pending = false;
m_response_ready = false;
m_apdu_len = 0;
m_resp_len = 0;
} }
void nfc_tag_14a_4_get_debug_counters(uint8_t *rx, uint8_t *tx, 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; 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 */ /* 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); 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 */ /* 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_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); 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); void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length);
/* Reset handler */ /* 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); void nfc_tag_14a_4_reset_handler(void);
#endif /* NFC_14A_4_H */ #endif /* NFC_14A_4_H */