Various bug fixes

This commit is contained in:
Niel Nielsen
2026-04-15 06:29:10 +02:00
parent 76c961ed59
commit 378c2b302f
3 changed files with 166 additions and 52 deletions
+107 -45
View File
@@ -21,6 +21,12 @@
#include "rc522.h"
#endif
#include "nfc_14a.h"
/* Forward declarations for functions added to nfc_14a.c/h in this PR.
* These are declared here to avoid build failure if nfc_14a.h is not yet
* updated on the build system. */
extern void nfc_tag_14a_set_tx_sniff_cb(void (*cb)(const uint8_t *, uint16_t));
extern void nfc_tag_14a_clear_tx_sniff_cb(void);
extern void nfc_tag_14a_set_sniff_passive(bool passive);
#include "nfc_14a_4.h"
#define NRF_LOG_MODULE_NAME app_cmd
@@ -1860,18 +1866,30 @@ static uint16_t m_sniff_buf_len = 0;
static bool m_sniff_active = false;
static uint16_t m_sniff_cb_count = 0; /* debug: total callback invocations */
/* Encode one frame into m_sniff_buf.
* Format: [szBits_be16][data...]
* Bit 15 of szBits: 0 = reader→card (RX), 1 = card→reader (TX).
* Real szBits always < 512 so bit15 is always free in genuine frames.
* Old parsers (bit15=0 for all frames) still work correctly. */
static void hf14a_sniff_store(const uint8_t *data, uint16_t szBits, bool is_tx) {
uint16_t szBytes = (szBits + 7) / 8;
if (m_sniff_buf_len + 2 + szBytes > HF_SNIFF_BUF_SIZE) return;
uint16_t hdr = szBits | (is_tx ? 0x8000u : 0x0000u);
m_sniff_buf[m_sniff_buf_len++] = (hdr >> 8) & 0xFF;
m_sniff_buf[m_sniff_buf_len++] = hdr & 0xFF;
memcpy(&m_sniff_buf[m_sniff_buf_len], data, szBytes);
m_sniff_buf_len += szBytes;
}
static void hf14a_sniff_frame_cb(const uint8_t *data, uint16_t szBits) {
m_sniff_cb_count++; /* count even if buffer full or inactive */
if (!m_sniff_active) return;
uint16_t szBytes = (szBits + 7) / 8;
/* Check space: 2 bytes header + data */
if (m_sniff_buf_len + 2 + szBytes > HF_SNIFF_BUF_SIZE) return;
/* Write bit count big-endian */
m_sniff_buf[m_sniff_buf_len++] = (szBits >> 8) & 0xFF;
m_sniff_buf[m_sniff_buf_len++] = szBits & 0xFF;
/* Write frame bytes */
memcpy(&m_sniff_buf[m_sniff_buf_len], data, szBytes);
m_sniff_buf_len += szBytes;
hf14a_sniff_store(data, szBits, false); /* reader→card */
}
static void hf14a_sniff_tx_frame_cb(const uint8_t *data, uint16_t szBits) {
if (!m_sniff_active) return;
hf14a_sniff_store(data, szBits, true); /* card→reader */
}
static data_frame_tx_t *cmd_processor_hf14a_sniff(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
@@ -1901,7 +1919,10 @@ static data_frame_tx_t *cmd_processor_hf14a_sniff(uint16_t cmd, uint16_t status,
m_sniff_buf_len = 0;
m_sniff_cb_count = 0;
m_sniff_active = true;
/* passive mode intentionally disabled: CU acts as the card so it must
* respond normally to the reader (ATQA/UID/SAK). TX sniff captures responses. */
nfc_tag_14a_set_sniff_cb(hf14a_sniff_frame_cb);
nfc_tag_14a_set_tx_sniff_cb(hf14a_sniff_tx_frame_cb);
/* Wait for duration, yielding each ms so USB stack stays alive.
* Feed watchdog every iteration — WDT timeout is 5000ms and the
@@ -1915,7 +1936,9 @@ static data_frame_tx_t *cmd_processor_hf14a_sniff(uint16_t cmd, uint16_t status,
/* Remove callback and restore normal sense state */
m_sniff_active = false;
/* (passive mode was not enabled, nothing to restore) */
nfc_tag_14a_clear_sniff_cb();
nfc_tag_14a_clear_tx_sniff_cb();
tag_emulation_sense_run(); /* restore slot-based sense state */
if (m_sniff_buf_len == 0) {
@@ -2416,46 +2439,85 @@ static data_frame_tx_t *cmd_processor_hf14a_4_emv_scan(uint16_t cmd, uint16_t st
}
if (flen_off + flen < 255) pi += flen_off + flen - 1; else break;
}
/* ---- Step 5: GPO with PDOL retry --------------------------------
* When the FCI is truncated (45b for long cards), we can't read the
* full PDOL. Try progressively larger PDOL sizes (all zeros) until the
* card accepts. pdol_len from FCI parsing is tried first. Common sizes
* cover most Mastercard/Visa variants. */
static const uint8_t gpo_try_pl[] = {0, 4, 8, 12, 18, 22, 26, 29, 34, 38, 44};
uint8_t gpo_buf[8 + 44]; /* PCB(1)+header(7)+max_PDOL(44)+Le(1)+CRC(2) fits abuf[64] */
/* ---- Step 5: GPO -----------------------------------------------
* Build GPO from parsed PDOL. pdol_len from FCI may be 0 if truncated.
* BUILD_GPO fills the PDOL data: TTQ = A0 00 00 00 for first 4 bytes
* (MSD+EMV contactless, offline, no DDA), rest zeros.
* TTQ=A0000000 is the lowest-security POS profile; Mastercard and Visa
* contactless cards respond to it even without a full terminal setup. */
static uint8_t gpo_buf[8 + 44]; /* static: keep off stack */
/* PDOL template: TTQ first 4 bytes, zeros after.
* TTQ A0000000: MSD+EMV contactless capable, offline, no CDA/DDA. */
static const uint8_t gpo_pdol_template[44] = {
0xA0, 0x00, 0x00, 0x00, /* TTQ (9F66): MSD+cEMV, offline, no DDA */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Amount Authorised (9F02) */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Amount Other (9F03) */
0x02, 0x08, /* Country Code (9F1A): Norway */
0x00, 0x00, 0x00, 0x00, 0x00, /* TVR (95) */
0x09, 0x78, /* Currency (5F2A): EUR */
0x25, 0x01, 0x01, /* Date (9A) */
0x00, /* Transaction Type (9C) */
0x00, 0x00, 0x00, 0x00, /* Unpredictable Number (9F37) */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* extra zeros */
0x00, 0x00, 0x00
};
uint8_t gpo_len = 0;
uint8_t *gpo_resp = NULL; uint16_t gpo_rlen = 0;
if (pdol_len > 44) pdol_len = 0; /* clamp to abuf-safe size */
{
bool gpo_ok = false;
for (uint8_t ti = 0; ti <= sizeof(gpo_try_pl) && !gpo_ok; ti++) {
uint8_t pl = (ti == 0) ? pdol_len
: gpo_try_pl[ti - 1];
/* skip sizes we already tried */
bool dup = false;
for (uint8_t si = 0; si < ti && !dup; si++)
dup = ((si == 0 ? pdol_len : gpo_try_pl[si-1]) == pl);
if (dup) continue;
gpo_len = 0;
gpo_buf[gpo_len++]=0x80; gpo_buf[gpo_len++]=0xA8;
gpo_buf[gpo_len++]=0x00; gpo_buf[gpo_len++]=0x00;
gpo_buf[gpo_len++]=(uint8_t)(pl + 2); /* Lc */
gpo_buf[gpo_len++]=0x83;
gpo_buf[gpo_len++]=pl;
memset(&gpo_buf[gpo_len], 0x00, pl); gpo_len += pl;
gpo_buf[gpo_len++]=0x00; /* Le */
if (!SEND_APDU(gpo_buf, gpo_len, &gpo_resp, &gpo_rlen)) continue;
/* Accept if response template (0x77/0x80) or SW 9000 */
if (gpo_rlen >= 2) {
uint8_t s1 = gpo_resp[gpo_rlen-2];
uint8_t s2 = gpo_resp[gpo_rlen-1];
if (gpo_resp[0] == 0x77 || gpo_resp[0] == 0x80 ||
(s1 == 0x90 && s2 == 0x00))
gpo_ok = true;
}
if (pdol_len > 44) pdol_len = 0;
#define BUILD_GPO(pl) do { \
gpo_len = 0; \
gpo_buf[gpo_len++]=0x80; gpo_buf[gpo_len++]=0xA8; \
gpo_buf[gpo_len++]=0x00; gpo_buf[gpo_len++]=0x00; \
gpo_buf[gpo_len++]=(uint8_t)((pl)+2); \
gpo_buf[gpo_len++]=0x83; gpo_buf[gpo_len++]=(pl); \
memcpy(&gpo_buf[gpo_len], gpo_pdol_template, \
(pl) <= sizeof(gpo_pdol_template) ? (pl) : sizeof(gpo_pdol_template)); \
if ((pl) > sizeof(gpo_pdol_template)) \
memset(&gpo_buf[gpo_len + sizeof(gpo_pdol_template)], 0, \
(pl) - sizeof(gpo_pdol_template)); \
gpo_len += (pl); \
gpo_buf[gpo_len++]=0x00; \
} while(0)
#define GPO_OK(rp,rl) ((rl)>=2 && \
((rp)[0]==0x77||(rp)[0]==0x80|| \
((rp)[(rl)-2]==0x90&&(rp)[(rl)-1]==0x00)))
/* Attempt 1: use PDOL length from FCI (may be 0 if truncated) */
BUILD_GPO(pdol_len);
if (!SEND_APDU(gpo_buf, gpo_len, &gpo_resp, &gpo_rlen) ||
!GPO_OK(gpo_resp, gpo_rlen)) {
/* Attempt 2: try 4 bytes (TTQ only — some Visa/MC accept this) */
if (pdol_len != 4) {
BUILD_GPO(4);
if (SEND_APDU(gpo_buf, gpo_len, &gpo_resp, &gpo_rlen) &&
GPO_OK(gpo_resp, gpo_rlen)) goto gpo_done;
}
if (!gpo_ok) goto done;
/* Attempt 3: try 29 bytes (common Mastercard/Visa PDOL size) */
if (pdol_len != 29) {
BUILD_GPO(29);
if (SEND_APDU(gpo_buf, gpo_len, &gpo_resp, &gpo_rlen) &&
GPO_OK(gpo_resp, gpo_rlen)) goto gpo_done;
}
/* Attempt 4: try 33 bytes (MC with Amount+Country+Currency fields) */
if (pdol_len != 33) {
BUILD_GPO(33);
if (SEND_APDU(gpo_buf, gpo_len, &gpo_resp, &gpo_rlen) &&
GPO_OK(gpo_resp, gpo_rlen)) goto gpo_done;
}
/* Attempts 5-7: additional MC sizes (+TermType, +DataAuth, +IssuerAppData) */
{ static const uint8_t extra_pl[] = {34, 36, 38};
for (uint8_t ei = 0; ei < sizeof(extra_pl); ei++) {
uint8_t pl = extra_pl[ei];
if (pl == pdol_len || pl == 4 || pl == 29 || pl == 33) continue;
BUILD_GPO(pl);
if (SEND_APDU(gpo_buf, gpo_len, &gpo_resp, &gpo_rlen) &&
GPO_OK(gpo_resp, gpo_rlen)) goto gpo_done;
}
}
goto done; /* all attempts failed */
}
gpo_done:
#undef BUILD_GPO
#undef GPO_OK
APPEND_PAIR(gpo_buf, gpo_len, gpo_resp, gpo_rlen);
num_apdus++;
uint8_t *afl = NULL; uint8_t afl_len = 0;
@@ -70,6 +70,25 @@ void nfc_tag_14a_set_sniff_cb(nfc_tag_14a_sniff_cb_t cb) {
void nfc_tag_14a_clear_sniff_cb(void) {
m_sniff_cb = NULL;
}
/* TX sniff: captures card→reader frames at TX_FRAMESTART */
static nfc_tag_14a_tx_sniff_cb_t m_tx_sniff_cb = NULL;
void nfc_tag_14a_set_tx_sniff_cb(nfc_tag_14a_tx_sniff_cb_t cb) {
m_tx_sniff_cb = cb;
}
void nfc_tag_14a_clear_tx_sniff_cb(void) {
m_tx_sniff_cb = NULL;
}
/* Passive sniff mode: suppress all tag TX responses so the CU does not
* participate in anticollision and avoids colliding with the real card. */
static bool m_sniff_passive = false;
void nfc_tag_14a_set_sniff_passive(bool passive) {
m_sniff_passive = passive;
}
static uint8_t m_nfc_tx_buffer[MAX_NFC_TX_BUFFER_SIZE] = { 0x00 };
// The N -secondary connection needs to use SAK, when the "third 'bit' in SAK is 1 is 1, the logo UID is incomplete
static uint8_t m_uid_incomplete_sak[] = { 0x04, 0xda, 0x17 };
@@ -366,9 +385,11 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
if (auto_coll_res != NULL) {
// The status machine is set to the preparation state, and the next operation is to enter the card selection link
m_tag_state_14a = NFC_TAG_STATE_14A_READY;
// After receiving the WUPA or REQA instruction, we need to reply to ATQA
nfc_tag_14a_tx_bytes(auto_coll_res->atqa, 2, false);
// NRF_LOG_INFO("ATQA reply: %02x%02x", auto_coll_res->atqa[0], auto_coll_res->atqa[1]);
if (!m_sniff_passive) {
// After receiving the WUPA or REQA instruction, we need to reply to ATQA
nfc_tag_14a_tx_bytes(auto_coll_res->atqa, 2, false);
// NRF_LOG_INFO("ATQA reply: %02x%02x", auto_coll_res->atqa[0], auto_coll_res->atqa[1]);
}
} else {
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
NRF_LOG_INFO("Auto anti-collision resource no exists.");
@@ -484,7 +505,9 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
}
// Incoming SELECT ALL for any cascade level
if (szDataBits == 16 && p_data[1] == 0x20) {
nfc_tag_14a_tx_bytes(uid, 5, false);
if (!m_sniff_passive) {
nfc_tag_14a_tx_bytes(uid, 5, false);
}
// NRF_LOG_INFO("[MFEMUL_SELECT] SEL Reply.");
break;
}
@@ -498,10 +521,14 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
if (cl_finished) {
// NRF_LOG_INFO("[MFEMUL_SELECT] m_tag_state_14a = MFEMUL_WORK");
m_tag_state_14a = NFC_TAG_STATE_14A_ACTIVE;
nfc_tag_14a_tx_bytes(auto_coll_res->sak, 1, true);
if (!m_sniff_passive) {
nfc_tag_14a_tx_bytes(auto_coll_res->sak, 1, true);
}
} else {
// It is necessary to continue the level, so we need to respond to a data that marks the incomplete UID in SAK
nfc_tag_14a_tx_bytes(m_uid_incomplete_sak, 3, false);
if (!m_sniff_passive) {
nfc_tag_14a_tx_bytes(m_uid_incomplete_sak, 3, false);
}
}
} else {
// IDLE, not our UID
@@ -651,7 +678,19 @@ void nfc_tag_14a_event_callback(nrfx_nfct_evt_t const *p_event) {
}
case NRFX_NFCT_EVT_TX_FRAMESTART: {
// NRF_LOG_INFO("TX start.\n");
// NRF_LOG_INFO("TX config is %d.\n", nrf_nfct_tx_frame_config_get(NRF_NFCT));
if (m_tx_sniff_cb != NULL) {
uint32_t amt = NRF_NFCT->TXD.AMOUNT;
uint16_t tx_bytes = (amt >> NFCT_TXD_AMOUNT_TXDATABYTES_Pos)
& (NFCT_TXD_AMOUNT_TXDATABYTES_Msk >> NFCT_TXD_AMOUNT_TXDATABYTES_Pos);
uint16_t tx_bits_rem = (amt >> NFCT_TXD_AMOUNT_TXDATABITS_Pos)
& (NFCT_TXD_AMOUNT_TXDATABITS_Msk >> NFCT_TXD_AMOUNT_TXDATABITS_Pos);
uint16_t tx_bits = (tx_bits_rem > 0)
? ((tx_bytes - 1) * 8 + tx_bits_rem)
: (tx_bytes * 8);
if (tx_bits > 0 && tx_bytes <= MAX_NFC_TX_BUFFER_SIZE) {
m_tx_sniff_cb(m_nfc_tx_buffer, tx_bits);
}
}
break;
}
case NRFX_NFCT_EVT_TX_FRAMEEND: {
@@ -90,6 +90,19 @@ typedef void (*nfc_tag_14a_sniff_cb_t)(const uint8_t *data, uint16_t szBits);
void nfc_tag_14a_set_sniff_cb(nfc_tag_14a_sniff_cb_t cb);
void nfc_tag_14a_clear_sniff_cb(void);
/* TX sniff callback — fires at TX_FRAMESTART with the frame the tag is about
* to send (cardreader direction). Same signature as the RX sniff callback.
* Install alongside nfc_tag_14a_set_sniff_cb() to capture both directions. */
typedef void (*nfc_tag_14a_tx_sniff_cb_t)(const uint8_t *data, uint16_t szBits);
void nfc_tag_14a_set_tx_sniff_cb(nfc_tag_14a_tx_sniff_cb_t cb);
void nfc_tag_14a_clear_tx_sniff_cb(void);
/* Passive sniff mode: when true, suppresses all CU anticollision responses
* (ATQA, UID, SAK) so the CU does not collide with real cards in the field.
* Enable before starting a sniff session, disable on completion. */
void nfc_tag_14a_set_sniff_passive(bool passive);
typedef void (*nfc_tag_14a_state_handler_t)(uint8_t *data, uint16_t szBits);
typedef nfc_tag_14a_coll_res_reference_t *(*nfc_tag_14a_coll_handler_t)(void);