NFC fixes: EMV poller, missing sync read page, dropped unsupported protocols - #1
NFC fixes: EMV poller, missing sync read page, dropped unsupported protocols#1AJ60 wants to merge 115 commits into
Conversation
- Introduced furi_hal_pcf8574.c and furi_hal_pcf8574.h for PCF8574 I2C GPIO expander support. - Updated furi_hal.c to initialize PN532 and PCF8574 during HAL initialization. - Removed MCP23017 dependencies from furi_hal_light.c, furi_hal_vibro.c, and furi_hal_speaker.c, replacing them with PCF8574 functions. - Simplified power management in furi_hal_power.c by removing INA219 support, relying solely on ADC for power measurements. - Added furi_hal_pn532.c and furi_hal_pn532.h for PN532 NFC module communication, including initialization and command handling. - Updated furi_hal_speaker.c to control the speaker via PCF8574 instead of TIM peripherals. - Adjusted furi_hal_light.c to remove MCP23017 references and handle light settings through PCF8574.
…and improve error logging
…mprove error logging
…h PCF8574, and adjust interrupt handling
- Introduced furi_hal_nfc_pn532.c and furi_hal_nfc_pn532.h to implement the PN532 NFC backend. - Updated furi_hal_nfc_iso14443a.c to check for PN532 activity and delegate NFC operations accordingly. - Enhanced furi_hal_pn532.c with new command handling and response parsing for data exchange. - Implemented functions for polling ISO14443A targets and handling data transactions with the PN532. - Added event queue management for NFC events in the PN532 backend. - Improved error handling and response validation in the PN532 communication functions.
…build validation scripts
…finitions for display control
…w SPI preset configuration for high edge mode
… cache across phase transitions
…am buffer send/receive, RX overflow log
…docs
Phase 3 (M3-M7) + Phase 4 (L1-L5) batch commit. All remaining code fixes:
M3 - FAP metadata cache (archive_browser.c):
- 64-entry LRU cache with 5s TTL for FAP name/icon lookups
- strcmp path-based cache keys, directory-change flush
- fap_cache_get/set/flush helpers, integrated into archive_get_fap_meta()
- Cache flushed in archive_list_load_cb() when directory changes
M4 - RFID dead code removal (furi_hal_rfid.c):
- Removed unused RFID_DMA_* macros (lines 42-47)
- Removed commented-out furi_hal_rfid_dma_isr() (lines 322-337)
- Removed dead code blocks from furi_hal_rfid_tim_emulate_dma_start/stop
M6 - Storage file handle comment (storage_ext.c):
- Replaced TODO FL-3522 with explanatory comment about FatFS
f_mount(NULL,path,0) not closing open handles (acceptable on card removal)
M7 - Loader double-start comment (loader.c):
- Replaced TODO FL-3522 with clear explanation of double-start
prevention and which code paths can trigger it
L1 - Schrader GG4 battery (schrader_gg4.c):
- Removed "TODO locate and fix", kept explanatory comment that
Schrader GG4 has no battery status bit in 32-bit data frame
L2 - FAAC SLH bypass (faac_slh.c):
- Both instances (gen_data + decrypt): replaced "Stupid bypass" TODO
with proper comment + FURI_LOG_D(TAG,"No custom button set...")
- Second instance (decrypt) was missing FURI_LOG_D, now added
L3 - NTAG4xx doc (ntag4xx.c):
- Replaced TODO with doc comment explaining NTAG426Q DNA fallback
to NTAG424DNA is safe (426Q rare, behaves identically)
L4 - DFU signature check (dfu_file.c):
- Added signature check using ucDfuSignature_U/F/D individual bytes
(matches DfuSuffix struct layout), logs error on mismatch
L5 - mjs NaN endianness (mjs_string.c):
- Added #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ guard for
GET_VAL_NAN_PAYLOAD macro (STM32WB55 is little-endian, guard for
future big-endian ports)
Also updated BUGS.md and BUG_FIX_PLAN.md to reflect all bugs as resolved
(except H7 ISO14443-4 full chaining which remains partially done - only
R-block ACK/NACK in PWT extension decode is implemented).
Build: firmware.elf links successfully. Updater.elf pre-existing link error.
Port ESP32 hybrid auth to MF Classic poller: InDataExchange-first auth for non-nested/non-backdoor, plaintext read/write when PN532-authed, Crypto1 fallback on failure. Genuine PN532 gets faster auth + I/O; clone falls back to manual Crypto1. - HAL: add furi_hal_nfc_pn532_mf_read_block/write_block, remove dup mf_auth - Poller: branch read/write/halt on pn532_mf_authed flag - ISO15693: add get_count guard to set_block_locked + append_block_security - Hardnested: add furi_thread_flags_get abort to 3 inner loops - Remove unconditional NTAG203 hex dump from mf_ultralight_poller_i.c - api_symbols.csv: 4 new HAL functions marked + Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
…uction PN532 doesn't support listener mode, so ~77KB of listener protocol code was dead weight. Added #ifndef PN532_ENABLED guards around listener alloc/start/stop calls in 14 NFC files. Added PN532_ENABLED CPPDEF in firmwareopts.scons for IS_BASE_FIRMWARE builds. Co-Authored-By: OpenClaude (mimo-v2-pro) <openclaude@gitlawb.com>
Previous batch was missing #else UNUSED(instance) causing -Werror=unused-parameter on COMPACT builds. Co-Authored-By: OpenClaude (mimo-v2-pro) <openclaude@gitlawb.com>
Implement TgInitAsTarget emulation on PN532 via furi_hal_nfc_pn532. Add Type B, Jewel/Topaz, P2P protocol registration in scanner. Enhance low-level PN532 driver for bidirectional exchange.
Remove PN532_ENABLED preprocessor guards that stubbed out all listener/emulation functionality across 12 protocol support files and the CLI emulate command. Emulation on PN532 is now functional via TgInitAsTarget. Guard ST25R3916-only NFCA listener code under PN532_ENABLED.
New self-contained Type 4 Tag emulation handling SELECT, READ BINARY, and UPDATE BINARY APDUs for NDEF messages. Supports CC file, NDEF file selection by AID or file ID. Embedded in Type 4 Tag listener as fallback APDU processor when existing handler chain doesn't match a command. Default NDEF record: 'Hello from Flipper!' text record.
Add receive-side I-block accumulation loop in both 4a and 4b poller send_block functions. Send R(ACK) for each fragment and loop until chaining completes. Fix PCB comparison in layer decode to mask out CHAIN bit on initial match and accept any I-block when chain_active. Add iso14443_4_layer_encode_r_ack API for R-block construction.
Add hybrid authentication (nested + hardnested fallback) to MF Classic poller. Implement backdoor state caching across dict attack phase transitions to avoid redundant key searches. Add bounded yield on poller stop to prevent watchdog timeouts during long dict attacks. Enhance keys_dict with improved iteration and caching.
Fix use-after-free in nfc_protocol_support by setting poller=NULL after poller_free. Improve I2C scan scene text rendering by switching to TextBoxFontText and building display string in FuriString buffer. Set st25tb and srix protocol support as non-embedded for PN532 build.
- Update SConscripts for drivers and NFC library - Update FAP build tooling (fastfap, appmanifest, fbt_extapps) - Update firmware options, target config, API symbols, and board config - Remove commented-out DMA ISR from RFID HAL - Update log level define in furi/core/log.h - Tweak .gitignore for build artifacts - Remove debug apps from PN532 build (unused targets) - Add _build_release.cmd build helper
Replace manual NULL check with furi_check() and add allocation safety checks for data, iso14443_4_layer, tx_buffer, rx_buffer. Part of C3 NULL-check fix series.
- SRIX write: use SrixPollerModeRequest via event data instead of direct poller struct access (SrixPoller is opaque from protocol support) - Remove stale iso15693_3/slix include from ndef.c parser - Remove stale iso15693_3 apdu/raw handler refs from application.fam - Update NFC_PROTOCOL_AUDIT.md with WONTFIX for dynamic block sizing - Add MAD function cluster names to render
Warns developer if log level > Info during NFC scan, since debug builds with verbose logging will OOM crash from FURI_LOG_D allocs. Non-blocking — proceeds with scan after warning.
351 files, +13317/-9441 NFC protocol cleanup (PN532 hardware has no native support for 3 protocols): - Remove: iso15693_3, slix, st25tb source dirs - Remove: helper/protocol_support, scene, CLI handlers for removed protocols - Add: jewel protocol (info+moreinfo+write READ_ALL stub) - Add: felica_system scene, T4T NDEF scene, jewel supported-card parser PN532 driver expansion: - furi_hal_pn532.c +1190 LOC: error code enum (29), wakeup retry (L5), I2C retry on ACK fail, SAM config - furi_hal_nfc_pn532.c +733: tech glue, listener I-block PCB toggle, NTAG/UL CRC double-encode fix - furi_hal_pn532.h +510: 29 error codes - nfc_scanner.c +374: multi-protocol probe (A/B/FeliCa/Jewel) NFC protocol fixes (NFC audit 2026-05-29, 113 findings, 25 landed): - MF Classic: hybrid auth, dict cache, retry cap, mask reset, 7-byte UID - MIFARE Ultralight: pages_read guard, 3DES key check, fast_read cap - Type 4 Tag: VLA fix, write buffer validation - EMV: graceful break, APDU bounds, AID parsing - DESFire: infinite loop fix, event type - FeliCa: 32-bit unaligned access fix, activation retry cap - SRIX: type field set, event-driven write - Jewel: defensive check - ISO 14443-3B: state init, activate() valid data - ISO 14443-4A: ATS compare, WTXM=0 clamp - furi_check guards on 22 malloc sites (malloc-checks script + manual) MfClassic 4KB heap guard, furi_check on bit_buffer allocs. Log spam demoted to DEBUG in hot paths. Documentation: - HANDOVER.md (untracked) - NFC_ARCHITECTURE.md (untracked) - BUG_FIX_PLAN.md (untracked) - documentation/NFCProtocolMatrix.md (untracked) - documentation/RAM_COMPARISON.md (untracked) Tools (untracked): - scripts/add_malloc_checks.py: bulk furi_check injection - scripts/check_nfc_feature_flags.py: PN532_ENABLED guard audit Test (untracked): - applications/debug/unit_tests/tests/nfc/test_pn532_auth_property.c Cleanup: - Delete AGENT_PROJECT_STRUCTURE_DOC.md (332 lines, superseded by AGENTS.md) - .gitignore: add 'nul' (Windows reserved name) - Format fixes: black, clang-format - Free flash: 863,416 B (843.2 KB), free 16.8 KB Build: TARGET_HW=7 DEBUG=0 COMPACT=1 copro_dist updater_package fap_dist -> green Lint: black + clang-format clean, imglint broken pre-existing (Windows path bug)
G1 of H7 ISO-DEP residual gaps. ISO/IEC 14443-4:2016 §7.1.6.2: when a chained I-block fragment arrives corrupted, poller must emit R(NAK) to request card to retransmit the last block. Without it, the chain loop silently dropped bad frames and relied on FWT timeout. Changes: - lib/nfc/helpers/iso14443_4_layer.c:494 add iso14443_4_layer_encode_r_nack (mirrors encode_r_ack, sets NACK bit at PCB offset 4) - lib/nfc/helpers/iso14443_4_layer.h:90 declaration - targets/f7/furi_hal/furi_hal_nfc_pn532.c:1215-1271 chain loop: - Track fragment_retry_count; on err/empty, retry with R(NAK)=0xB2|bit0 - Clear err before continue (preserve while-condition correctness) - Abandon after 3 failed retries -> FuriHalNfcErrorCommunicationTimeout Block-num toggle remains single at line 1287 (post-loop, not per-fragment). Overflow check and successful-path memcpy unchanged. Build: TARGET_HW=7 DEBUG=0 COMPACT=1 copro_dist updater_package fap_dist firmware.bin: 863,416 -> 863,584 B (+168 B), free 16.7 KB
G4 of H7 ISO-DEP residual gaps. Listener side of ISO-14443-4 chaining accumulated fragments from malicious/unbounded readers into chaining_buffer (4 KB alloc) with no size check. Poller side (decode_response) has a guard at iso14443_4_layer.c:223; listener side did not. Reader could stream chained I-blocks forever -> unbounded heap growth. Changes: - lib/nfc/helpers/iso14443_4_layer.h:53 add Iso14443_4LayerResultError enum value (1<<4) for protocol-level error - lib/nfc/helpers/iso14443_4_layer.c:362 overflow check before bit_buffer_append_bytes; on overflow log error, reset instance, return Iso14443_4LayerResultError - lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c:123 handle Error flag: reset listener, fire Halted event, NfcCommandSleep (mirrors Halt handler) Build: TARGET_HW=7 DEBUG=0 COMPACT=1 copro_dist updater_package fap_dist firmware.bin: 863,584 -> 863,824 B (+240 B), free 16.4 KB
G7 of H7 residual gaps. BUG_FIX_PLAN.md had three stale 'partial' / 'deferred' claims about H7 ISO-DEP chaining that contradicted the actual code (full chain accumulation, R(ACK) emit/parse, R(NAK) retransmit, WTX, and overflow guards all present). Updates: - Header status: 'partially implemented ... deferred' -> 'fully implemented' with note on G1, G4 residual fixes landed 2026-06-02 - Phase 2.7 (H7) line: drop 'partial: R-block ACK/NACK in PWT decode only' annotation - Trailing paragraph: 'H7 full chaining ... deferred' replaced with residual gap summary and commit references G3 (4B chain support) and G5 (last_tx_data lifecycle) explicitly closed with rationale: G3 needs 4B test card, G5 verified non-bug (null guard at iso14443_4_layer.c:432 + reset() lifecycle correct). No code changes; docs only.
scripts/imglint.py:66 _gather_images() called images.remove(filepath) unconditionally when processing an excluded folder. If the exclude folder was walked before the matching include folder (or if the file wasn't added to the list for any other reason), Python raised 'ValueError: list.remove(x): x not in list' and the whole lint crashed. This was reproducible on any dirty tree that added new PNGs under applications/ or assets/, because the lint ran on the working copy including untracked files. Fix: guard the remove with 'if filepath in images'. Defensive check; no behavior change for the working case where the file was previously added by the matching include folder. Verified: ./fbt lint_img no longer crashes with ValueError. Reports remaining legitimate issues (e.g. xremote RGB PNGs in external FAP) without the spurious tool failure.
CC1101 errata SWRZ012: the RXFIFO_OVERFLOW bit in CC1101 RXBYTES (0x3B) is unreliable (latches + can spuriously assert on read). TI recommends MARCSTATE (status byte from SNOP) == 0b110 as the reliable detector. Old code at furi_hal_subghz.c:276 read the broken bit and ignored it, leaving a 3-year-old 'TODO: Find reason why RXFIFO_OVERFLOW doesnt work correctly' comment. Symptom: heavy traffic / band hopping silently dropped RX data. Workaround in 95d1127 flushed on overrun but did not address root cause. Changes: - targets/f7/furi_hal/furi_hal_subghz.c:283 add furi_hal_subghz_check_rx_fifo_overflow() that reads MARCSTATE, logs warning, calls cc1101_flush_rx() on overflow, returns bool - targets/f7/furi_hal/furi_hal_subghz.h:95 declaration with errata reference - applications/drivers/subghz/cc1101_ext/cc1101_ext.c:406 mirror in ext-module driver: subghz_device_cc1101_ext_check_rx_fifo_overflow (not exported; device API only) - targets/f7/api_symbols.csv:1890 add new public symbol (alphabetical between async_rx_hop and check_tx) The broken 'TODO' comment at furi_hal_subghz.c:276 and cc1101_ext.c:399 removed. furi_hal_subghz_rx_pipe_not_empty() behavior unchanged. Build: TARGET_HW=7 DEBUG=0 COMPACT=1 copro_dist updater_package fap_dist firmware.bin: 863,824 -> 864,000 B (+176 B), free 16.2 KB Note: caller integration in subghz_txrx.c worker tick is not included; helper is exported for callers to wire. Follow-up commit.
5 doc files updated with 2026-06-02 static analysis (161,512 B free heap at boot in release build) and clarification that the OOM crash is conditional on user action, not a PN532 issue: - AGENTS.md:516 merge redundant OOM entries (L516+L519); add conditional wording + 2026-06-02 static analysis (157.7 KB free heap); consolidate heap guards - documentation/RAM_COMPARISON.md:65,82,90 add rows (e),(f) to §5 Root Cause; add default-level + static-analysis paragraph to §6; add 3 rows to §7 (FURI_LOG_D/T compile-out, runtime cap, early-break filter) - NFC_ARCHITECTURE.md:958 add heap row to §10.3 + new §10.4 Memory profile subsection (boot heap, log state, conditional OOM) - HANDOVER.md:90 add Memory & Logging Notes block to Known Limitations (conditional OOM + 157.7 KB free heap) - BUGS.md:62 add MITIGATED section (M-LOG) with full root cause + 3 mitigations; remove stale H7 PARTIALLY FIXED entry (closed in commit 83da39e) +56/-13 LOC, no code changes, no behavior change. Local commit only.
- Introduced a new log file `user_logs_utf8.txt` to capture detailed logs related to NFC read crashes. - Logs include various states and events during NFC operations, including timeouts and mode settings. - Captured additional metadata with timestamps for better tracking of incidents.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5293b91825
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -55,7 +55,7 @@ static NfcCommand nfc_scene_read_poller_callback_mf_plus(NfcGenericEvent event, | |||
| view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); | |||
| command = NfcCommandStop; | |||
| } else if(mf_plus_event->type == MfPlusPollerEventTypeReadFailed) { | |||
| command = NfcCommandReset; | |||
| command = NfcCommandStop; | |||
There was a problem hiding this comment.
Surface MF Plus read failures before stopping
When a MIFARE Plus read fails, this path now returns NfcCommandStop without sending NfcCustomEventPollerFailure or NfcCustomEventPollerReadFailed. In the read scene, the generic handler only leaves the loading/read UI in response to those custom events, so a detected MF Plus card that fails during read stops the poller but leaves the user stuck on the read screen instead of retrying or showing failure.
Useful? React with 👍 / 👎.
| NfcCommand command = instance->callback(instance->general_event, instance->context); | ||
| instance->state = EmvPollerStateIdle; | ||
| return command; | ||
| instance->callback(instance->general_event, instance->context); |
There was a problem hiding this comment.
Mark EMV failure events before invoking callback
For EMV state-machine failures such as PPSE selection failing, this callback is invoked without setting instance->emv_event.type to EmvPollerEventTypeReadFailed; the new app-side failure branch relies on that type to send NfcCustomEventPollerFailure. With the type left as stale/uninitialized malloc data, failures can be reported as success or ignored, so the intended "surface the error" path is unreliable.
Useful? React with 👍 / 👎.
| static uint8_t pcf8574_state = 0xFF; | ||
| static bool pcf8574_ready = false; | ||
| static uint8_t pcf8574_addr = PCF8574_I2C_ADDR; | ||
| #define PCF8574_I2C_TIMEOUT_MS 50 /* 50ms proven adequate for single-byte R/W on shared I2C1 */ |
There was a problem hiding this comment.
Restore the shared I2C1 timeout floor
This PCF8574 timeout is used for all expander reads and writes on the same I2C1 bus as the PN532 and OLED; under display/NFC bus contention, 50ms can falsely mark button/vibro/buzzer transactions as failed. The DIY board notes require a 150ms floor for regular I2C1 transactions, so this should use at least 150ms rather than the new 50ms value.
Useful? React with 👍 / 👎.
Fixes for DIY board NFC scanner:\n\n- Fixed EMV poller error handling to prevent hang when callback returns error\n- Fixed missing \mf_ultralight_poller_sync_read_page\ declaration and implementation\n- Fixed missing \mf_plus_poller_sync.h\ in SDK headers\n- Fixed invalid icon references in MfPlus verify scene\n- Dropped unsupported ISO15693-3, Slix, and ST25TB protocols from PN532 scanner\n\nCloses NFC issues 11 and 15.