From 378c2b302fb97b71cdc0de92205956d0e4c6f272 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Wed, 15 Apr 2026 06:29:10 +0200 Subject: [PATCH] Various bug fixes --- firmware/application/src/app_cmd.c | 152 ++++++++++++------ .../application/src/rfid/nfctag/hf/nfc_14a.c | 53 +++++- .../application/src/rfid/nfctag/hf/nfc_14a.h | 13 ++ 3 files changed, 166 insertions(+), 52 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 322f928..ade7f54 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -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; diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c index 50aa32b..c7b9ca6 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c @@ -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: { diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_14a.h b/firmware/application/src/rfid/nfctag/hf/nfc_14a.h index cd1702b..7bbf7fb 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_14a.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_14a.h @@ -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 (card→reader 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);