Skip to content

NFC fixes: EMV poller, missing sync read page, dropped unsupported protocols - #1

Open
AJ60 wants to merge 115 commits into
ssd1306_mcp23017from
fix/nfc-scanner-bor-v2
Open

NFC fixes: EMV poller, missing sync read page, dropped unsupported protocols#1
AJ60 wants to merge 115 commits into
ssd1306_mcp23017from
fix/nfc-scanner-bor-v2

Conversation

@AJ60

@AJ60 AJ60 commented Jun 27, 2026

Copy link
Copy Markdown
Owner

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.

UBYTE Optimizer and others added 30 commits April 3, 2026 14:01
- 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.
- 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.
…w SPI preset configuration for high edge mode
AJ60 and others added 29 commits May 18, 2026 02:10
…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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant