Author SHA1 Message Date
GameTec_live f349dbeeaa doc: changelog 2026-07-04 13:25:13 +02:00
GameTec-live dd5b11642d Merge pull request #317 from bob-zebedy/fix/pyinstaller-bin-path
fix: resolve binary tools detection after PyInstaller packaging
2026-07-04 13:15:14 +02:00
GameTec-live 2d9c8bcc9f Merge pull request #274 from unkernet/acl
Fix state machine behavior and access bits verification in MF1 emulation
2026-07-04 13:14:59 +02:00
GameTec-live 6e2a902d0e Merge pull request #404 from midlan/main
Add Jablotron LF tag support (read, emulate, write to T55xx)
2026-06-13 21:00:25 +02:00
unkernet 779bab265e Fix: mf1 encrypted HALT logic 2026-05-30 21:09:31 +07:00
unkernet ca6c299865 Merge branch 'main' into acl 2026-05-30 12:28:48 +07:00
Milan Davídek 6c20041add trigger CI (after Actions outage) 2026-05-26 15:05:59 +02:00
Milan Davídek 37454e9527 trigger CI 2026-05-26 14:29:08 +02:00
Milan DavídekandClaude Opus 4.7 a00abc5d40 docs(changelog): add Jablotron LF protocol entry
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:12:54 +02:00
Milan DavídekandClaude Opus 4.7 38689a82ba Move JABLOTRON_WRITE_TO_T55XX from 3017 to 3020
Keep SCAN(3019)/WRITE(3020) adjacent, matching the SCAN+WRITE
adjacency convention used by every other LF protocol
(EM410X 3000/3001, HIDPROX 3002/3003, VIKING 3004/3005,
IOPROX 3010/3011, PAC 3014/3015). Slot 3017 is now free for
future use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:01:58 +02:00
Milan DavídekandClaude Opus 4.7 52f37bff8d Merge upstream/main into Jablotron PR #404
Resolved conflicts from upstream feature additions (IDTECK PR #407,
LF_T55XX_WRITE PR #413, ISO14443-4 T=CL emulation, HF14A scan-keep,
etc.) by keeping both sides where independent.

Command ID note for reviewer:
- Maintainer's IDTECK shift commit (1e78976) stated "Jablotron retains
  the original slots 3016 and 3017", but upstream had already taken
  3016 for DATA_CMD_LF_T55XX_WRITE. Only 3017 was actually free.
- Resolution: JABLOTRON_WRITE_TO_T55XX kept at 3017 (honors intent).
  JABLOTRON_SCAN moved 3016 -> 3019 (next free slot).
- 5xxx range: JABLOTRON_SET/GET_EMU_ID = 5010/5011 unchanged
  (IDTECK shifted to 5012/5013 as planned).

Python files parse cleanly. No duplicate command IDs in data_cmd.h
(3xxx and 5xxx ranges checked).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 13:51:54 +02:00
GameTec-live 1a014f9656 Merge pull request #407 from matteoscrugli/feature/lf-idteck-psk1-emulation
Feature/lf: add IDTECK PSK1 tag emulation and T55xx clone
2026-05-15 14:00:46 +02:00
matteoscrugli 1e78976bd9 feat(lf): shift IDTECK command IDs to avoid collision with Jablotron PR #404
LupusE requested in the PR #407 review (2026-05-13) to move IDTECK
command IDs since PR #404 (Jablotron) also uses 3017 / 5010 / 5011.
Both PRs are open with the feature-freeze label; Jablotron retains
the original slots and IDTECK shifts up:

  DATA_CMD_IDTECK_WRITE_TO_T55XX: 3017 -> 3018
  DATA_CMD_IDTECK_SET_EMU_ID:     5010 -> 5012
  DATA_CMD_IDTECK_GET_EMU_ID:     5011 -> 5013

Files changed:
- firmware/application/src/data_cmd.h
- software/script/chameleon_enum.py

app_cmd.c references the constants by name only, no edit needed there.
2026-05-15 10:09:51 +02:00
matteoscrugli da8c788786 docs(changelog): add IDTECK LF protocol entry 2026-05-15 10:09:51 +02:00
matteoscrugli 729a7e56ea feat(cli): add lf idteck subgroup and extend lf clone with idteck type
Adds host-side CLI support for IDTECK:
- lf idteck econfig -s <slot> [--id <hex>]   set or read the emulated frame
- lf idteck write --id <hex>                 clone to a T55xx tag in reader mode
- lf clone -t idteck --id <hex>              same via the unified clone command
- hw slot list                                now renders Frame and Card ID
                                              for IDTECK slots

Input accepts 16 hex characters for the full 64-bit frame, or 8 hex
for the 32-bit payload (the fixed preamble 4944544B is auto-prepended).
A non-blocking informational note is emitted when the payload checksum
does not match the value computed from the card number, since some
readers validate this field and some do not.

Private helpers in chameleon_cli_unit.py (_idteck_compute_checksum,
_idteck_compose_frame, _idteck_frame_info) parse and compose IDTECK
frames and expose card-number-driven composition for a future
`lf idteck compose` command.
2026-05-15 10:09:51 +02:00
matteoscrugli 77e967c78b feat(lf): integrate IDTECK into firmware command and T55xx write paths
Exposes IDTECK to the host command protocol:
- DATA_CMD_IDTECK_SET_EMU_ID (5010) / GET_EMU_ID (5011) / WRITE_TO_T55XX (3017)
- Matching handlers in app_cmd.c for setting the emulated frame on
  the current LF slot, reading it back, and programming a T55xx tag

Adds write_idteck_to_t55xx in lf_reader_main (modeled on the other
per-protocol T55xx writers), wrapping idteck_t55xx_writer and the
shared write_t55xx helper.

After this commit the firmware is fully functional for IDTECK: a
host can set an emulated frame, read it back, or clone it to a T55xx.
The CLI wiring is added in the following commit.
2026-05-15 10:08:48 +02:00
matteoscrugli b8f070851c feat(lf): add IDTECK tag emulation (PSK1 RF/32)
Adds IDTECK as a new LF protocol for tag emulation. IDTECK is a PSK1
encoding at RF/32 with a 64-bit frame: a 32-bit fixed preamble
0x4944544B ("IDTK") followed by a 32-bit card payload (one-byte
checksum + 24-bit card number in byte-reversed layout, matching the
format used by the Proxmark3 client).

The modulator drives LF_MOD (load-modulation, same hardware path used
for FSK protocols like HID Prox) via the shared utils/psk1 helper,
producing a 62.5kHz subcarrier with a 180-degree phase flip at every
differential bit transition. Because PSK1 is differential the reader
decodes phase transitions between consecutive bits rather than
absolute phase, so carrier phase-lock is not required — a free-running
subcarrier from HFXO (±40ppm) stays within the tolerance of consumer
readers.

The 16us subcarrier period is below the counter_top minimum of 3 at
the legacy 125kHz PWM base clock used for ASK/FSK protocols. To avoid
rescaling every existing protocol, pwm_init now selects the base
clock based on the active tag type (predicate IS_PSK1_TYPE): 1MHz for
PSK1, 125kHz otherwise. Legacy protocols are untouched.

The comment in lf_sense_enable is updated to reflect that the absence
of carrier phase-lock (envelope-only tag-mode antenna taps) rules out
coherent demod but does not preclude differential-phase encodings
like the one introduced here.

T5577 cloning configuration uses the existing T5577_MODULATION_PSK1
symbol combined with RF/32 bitrate and 2 data blocks. Emulation read
is not added: the tag-emulation ADC path is 125kHz envelope-filtered,
so PSK demod would need a dedicated edge-timing decoder (left as a
follow-up).
2026-05-15 10:08:48 +02:00
matteoscrugli 5e2401587e feat(lf): add shared PSK1 wave-form helper for tag emulation
Factors out the PSK1 subcarrier generator into utils/psk1.{c,h}.
The helper takes a frame (MSB-first bytes), a bit count and a
destination wave-form buffer, and fills the buffer with PWM entries
expressing differential PSK1 as polarity flips at bit transitions.

No protocol uses this helper yet; it is introduced alone so that
individual PSK1 protocol files (starting with IDTECK in the next
commit) can plug into the same timing and encoding logic without
each re-implementing it.

The helper targets the 1MHz PWM base clock that will be selected by
pwm_init for PSK1 tag types; counter_top and duty constants are
defined accordingly.
2026-05-15 10:08:48 +02:00
GameTec-live 013f1f5ecd Merge pull request #420 from nieldk/PRNG-fix
feat(firmware): Add configurable PRNG type for MF1 emulator
2026-05-15 08:19:14 +02:00
Niel Nielsen 1bf8229d26 Add files via upload 2026-05-14 13:21:31 +02:00
Niel Nielsen 7e48d5427a Add files via upload 2026-05-13 21:22:03 +02:00
Niel Nielsen 1954faf3af Fix: Device does not go to sleep, #421
The fix in EVT_END_SEQ0:
	1.	ANT_NO_MOD() — silences LF_MOD so the local drive no longer charges the peak detector
	2.	bsp_delay_ms(2) — 2ms settle, slightly above the ~2ms time constant so the detector drains to reflect only the external field
	3.	is_lf_field_exists() — now sees the real field state
	4.	If field gone → nrfx_pwm_stop() → EVT_STOPPED → lf_field_lost() runs correctly
	5.	If field present → ANT_MOD() restores modulation for the next PWM sequence​​​​​​​​​​​​​​​​
2026-05-13 21:13:57 +02:00
Niel Nielsen cba4b84c4a FIX MFDEs version detection
Fixes version detection of MFDes info
2026-05-13 12:31:24 +02:00
Niel Nielsen b973385f8f Add full DESFire key check command with 3K3DES support
Adds hf des chk and hf des info commands for MIFARE DESFire EV1/EV2/EV3 key checking and card info enumeration.

Tested against: DESFire EV1 (SAK 0x20, 2KB storage) via Chameleon Ultra v2.1 USB
2026-05-12 11:21:24 +02:00
Niel Nielsen 5a3c0f4354 Add Chameleon Ultra for Sailfish OS to compatible apps 2026-05-11 14:31:24 +02:00
Niel Nielsen cef8b42c26 make_style as that is best practise 2026-05-11 12:36:51 +02:00
Niel Nielsen 0038fdd959 Add files via upload 2026-05-11 11:26:45 +02:00
Niel Nielsen 8351a3e206 Add files via upload 2026-05-11 11:26:13 +02:00
Niel Nielsen 4e8227d331 Add files via upload 2026-05-11 11:09:24 +02:00
Niel Nielsen 0c5e5ba7a2 Add files via upload 2026-05-11 10:51:00 +02:00
Niel Nielsen 6ac9993a41 fix(auth-trace): correct AR/AT prng step counts and add AR decryption display
- prng_successor steps for AR were 32 (should be 64) and AT were 64
  (should be 96), one full 32-bit word off in each case
- AR keystream (ks2) was computed but discarded; AR decrypted value
  was never shown
- Reorder output to show expected → encrypted → decrypted for AR then AT
2026-05-11 10:45:54 +02:00
Niel Nielsen 43f4d55c2b Update app_cmd.c 2026-05-11 10:25:50 +02:00
Niel Nielsen d3f9a521cb Update app_cmd.c 2026-05-11 09:50:23 +02:00
Niel Nielsen 5f8f29c8b4 Update data_cmd.h 2026-05-11 09:48:31 +02:00
Niel Nielsen 9d8c52fbdb Add sleep timeout commands for configuration 2026-05-11 09:45:32 +02:00
Niel Nielsen 68f929b23f Add files via upload 2026-05-10 20:40:39 +02:00
Niel Nielsen 173f8d472e Add files via upload 2026-05-10 20:39:58 +02:00
Niel Nielsen a64798f8a7 Add files via upload 2026-05-10 19:48:06 +02:00
Niel Nielsen 5b12dd4e7e Add files via upload 2026-05-10 19:47:07 +02:00
Niel Nielsen 269d9eb283 Add files via upload 2026-05-10 19:39:42 +02:00
Niel Nielsen 2701e64755 Add files via upload 2026-05-10 19:15:34 +02:00
Niel Nielsen c0fd09b737 Add files via upload 2026-05-10 19:13:59 +02:00
Niel Nielsen e2ce3f3a29 Add files via upload 2026-05-10 17:05:43 +02:00
Niel Nielsen 4bfc18ae6f Add files via upload 2026-05-10 17:05:06 +02:00
GameTec-live f06efdf815 Merge pull request #413 from nieldk/t55write
T55write
2026-05-08 17:26:51 +02:00
GameTec-live 1a769a0c4a Merge pull request #417 from azuwis/fix-hf-mf-eview
fix: hf mf eview param error due to chunk exceeding 32-block limit
2026-05-07 21:20:58 +02:00
Niel Nielsen 394781a45f Add files via upload 2026-05-07 20:08:23 +02:00
Niel Nielsen 36daf7038c Add files via upload 2026-05-07 20:07:23 +02:00
Niel Nielsen f8b0ae6085 FEAT: hf 14a auth-trace 2026-05-07 17:46:53 +02:00
Niel Nielsen 5c4cf13124 Merge branch 'RfidResearchGroup:main' into t55write 2026-05-07 13:54:35 +02:00
GameTec-live cce9d5b48d Merge pull request #419 from azuwis/fix-hf14a-raw
fix: hf14a_raw should return data bytes, not Response object
2026-05-07 11:24:48 +02:00
Zhong Jianxin d2c1f43a0e fix: hf14a_raw should return data bytes, not Response object
Callers treat the return value as bytes (len(), slicing), but hf14a_raw
was returning the Response object itself, causing TypeError.
2026-05-06 21:05:44 +08:00
Zhong Jianxin 874bb49485 fix: hf mf eview param error due to chunk exceeding 32-block limit
The firmware limits mf1_read_emu_block_data to at most 32 blocks per
request, but eview's chunk_count only honored data_max_length (256).
Added the same 32-block cap already used by esave.
2026-05-06 18:21:47 +08:00
Niel Nielsen bba432c579 Add files via upload 2026-05-01 20:14:24 +02:00
Niel Nielsen f7feda5dc9 Add files via upload 2026-05-01 18:45:52 +02:00
Niel Nielsen 8555f86c22 Merge branch 'RfidResearchGroup:main' into t55write 2026-05-01 17:52:45 +02:00
Niel Nielsen c63cc16bb5 Add files via upload 2026-05-01 17:50:32 +02:00
GameTec-live e4a6e74b45 Merge pull request #387 from naaraxi/main
Support for changing the wake time in the client
2026-05-01 16:30:25 +02:00
naaraxi 0460d9b95e Support for changing the wake time in the client 2026-05-01 14:36:52 +03:00
GameTec-live d7b8e63966 Merge pull request #406 from nieldk/t55write
hf 14a sniff improvements for nonce collection and crack, fence to catch missing or blocked mfkey binaries
2026-04-30 17:56:42 +02:00
Niel Nielsen 285d81b31e fix: restore executable permission to chameleon_cli_main.py 2026-04-28 20:36:26 +00:00
Niel Nielsen fc35ce41ba Merge branch 'RfidResearchGroup:main' into t55write 2026-04-28 22:08:46 +02:00
GameTec-live dc4c6fdbb0 Merge pull request #403 from DGinefra/main
Add iOS client "MCT Mifare Chameleon Tool" to compatible applications
2026-04-25 10:38:17 +02:00
GameTec-live 193ca010f0 Merge branch 'main' into fix/pyinstaller-bin-path 2026-04-25 08:44:55 +02:00
GameTec-live 763ea77cbd Merge pull request #379 from andrassmuk/fix/issue-378-mingw-pthread
fix: use native winpthreads for MinGW/MSYS2 Windows builds
2026-04-25 08:02:57 +02:00
Niel Nielsen de1d9f6c28 T55xx PAC clone
Add lf clone PAC command
2026-04-24 13:21:48 +02:00
Niel Nielsen ae345c6a59 Fix ADC buffer dimensions in ble_main.c 2026-04-24 11:41:59 +02:00
Niel Nielsen c3fd94ca8c hf 14a sniff, even more descriptive answers 2026-04-23 09:08:23 +02:00
Niel Nielsen 20d6136ee0 hf 14a sniff, more descriptive answers 2026-04-23 08:45:34 +02:00
Niel Nielsen 1e8c36f38c hf 14a sniff improvements for nonce collection and crack, fence to catch missing or blocked mfkey binaries 2026-04-23 07:58:57 +02:00
GameTec-live 75eb389fe9 Merge pull request #401 from nieldk/t55write
T55xx clone
2026-04-19 15:18:46 +02:00
Milan Davídek 5efa6b4ab0 Remove Jablotron placeholder comment from chameleon_enum 2026-04-15 15:54:19 +02:00
Niel Nielsen 4406788aef BUG: reverted bug that was reintroduced 2026-04-15 14:45:41 +02:00
Milan DavídekandClaude Sonnet 4.6 fcb6eb4718 Add Jablotron LF tag support (read, emulate, write to T55xx)
Jablotron uses differential biphase (inverted) at RF/64, 64-bit frames:
  bits  0-15: 0xFFFF preamble
  bits 16-55: 40-bit data (5 bytes), bit 16 must be 0
  bits 56-63: 8-bit checksum = (sum of data bytes) XOR 0x3A

Firmware:
  - rfid/nfctag/lf/protocols/jablotron.c  - encoder/decoder codec
  - rfid/nfctag/lf/utils/diphase.c        - inverted-biphase state machine
    (shared util, reusable by other diphase protocols)
  - rfid/reader/lf/lf_jablotron_data.c    - GPIO-interval reader path
  - app_cmd.c: JABLOTRON_SCAN, JABLOTRON_WRITE_TO_T55XX,
                JABLOTRON_SET_EMU_ID, JABLOTRON_GET_EMU_ID
  - tag_base_type.h: TAG_TYPE_JABLOTRON enum
  - t55xx.h: T5577_JABLOTRON_CONFIG (DIPHASE modulation, RF/64)
  - lf_tag_em.c: load callback, factory-default data, save callback

Python CLI (software/script/):
  - lf jablotron read               - scan a real tag
  - lf jablotron write --id         - clone onto T55xx
  - lf jablotron econfig -s N --id  - set emulator ID on a slot
  - hw slot list shows Jablotron ID and decimal card number

Python test (software/script/tests/test_jablotron_modulator.py):
  Pure-Python round-trip validator that reimplements the modulator and
  diphase decoder, expands PWM entries to an edge stream, and confirms
  the decoded data matches the input.  Regression guard for both the
  firmware's double-frame encoding and the single-frame variant.

Notable PWM design choices:
  - Constant-level diphase encoding uses the same PAC pattern:
    CC=0 for LOW, CC=counter_top+1 for HIGH.  counter_top=31 gives
    exactly 32 carrier cycles per half-bit at NRF_PWM_CLK_125kHz.
  - The 64-bit frame is encoded twice in the 256-entry PWM buffer with
    the internal level variable persisting between the two passes.
    This is required for clean PWM looping: a single 64-bit diphase
    frame with an odd number of zero bits ends at a level opposite the
    starting level, leaving no transition at the loop boundary where
    the reader expects one.  Encoding twice guarantees a continuous
    diphase stream regardless of the data's zero-count parity.

Reference: Proxmark3 cmdlfjablotron.c

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:15:55 +02:00
Niel Nielsen 378c2b302f Various bug fixes 2026-04-15 06:29:10 +02:00
Niel Nielsen 76c961ed59 Added Ultra/Lite guard 2026-04-14 09:45:02 +02:00
Niel Nielsen d70a0dd63f fix hf14a sniff 2026-04-14 09:32:35 +02:00
GameTec-live fb6480f355 doc: Update text in brackets to clarify debug / beta state and iOS only nature 2026-04-13 13:18:17 +02:00
Niel Nielsen 0ce680b5c7 Refactor LF clone command and update usage examples 2026-04-13 06:39:03 +02:00
Niel Nielsen 63a465ce9b Fix argument parsing for 'fc' in ioprox 2026-04-12 20:10:36 +02:00
DGinefra af8b8f3c5c Add iOS app "Mifare Chameleon Tool" to compatible applications
Hi,

I would like to add my iOS application "Mifare Chameleon Tool" to the list of compatible applications.

App Store link:
https://apps.apple.com/it/app/mifare-chameleon-tool/id6761231484

The app supports BLE communication with Chameleon Ultra.

Thanks!
2026-04-12 17:32:34 +02:00
DGinefra c45286d8d1 Add Mifare Chameleon Tool (iOS) to compatible apps 2026-04-12 17:31:32 +02:00
Niel Nielsen 1a09fbaf0e Merge branch 'RfidResearchGroup:main' into t55write 2026-04-08 13:11:02 +02:00
Niel Nielsen 12284d5f71 Fix: emv scan truncation 2026-04-08 12:36:12 +02:00
GameTec-live 6d30d33aef Merge pull request #357 from fmuk/pr/nfcimport-v2
feat: add Flipper Zero .nfc file importer for MFU/NTAG slots
2026-04-07 20:47:18 +02:00
Niel Nielsen e4dca3fcc4 align with RRG 2026-04-07 10:57:08 +02:00
Niel Nielsen 350a774d7c align with RRG 2026-04-07 10:47:41 +02:00
Niel Nielsen 67c1c36212 Clarify exit method behavior with comments
Added comments to clarify behavior of exit method.
2026-04-07 10:36:15 +02:00
Niel Nielsen bbfda3070d Fix: T55 write commands help 2026-04-07 10:36:15 +02:00
Niel Nielsen e16505e6a7 FEAT! Add T55 write commands 2026-04-07 10:36:06 +02:00
Niel Nielsen a3d3c1fc34 Remove conditional compilation for PROJECT_CHAMELEON_ULTRA 2026-04-07 10:29:12 +02:00
Niel Nielsen fcf0c31ca5 Fix syntax error in app_cmd.c 2026-04-07 10:29:12 +02:00
Niel Nielsen 9183ac40e4 Add PROJECT_CHAMELEON_ULTRA specific commands 2026-04-07 10:28:51 +02:00
Niel Nielsen c7e038cc61 Remove duplicate rc522.h include
Removed duplicate rc522.h include and adjusted spacing.
2026-04-07 10:25:13 +02:00
Niel Nielsen efa2ea2c7b protocol ISO 14443-4 and emv scan, loading json file from PM3rdv4 2026-04-07 10:23:58 +02:00
Fauzan Mirza dc950c4f60 fix: correct nfcimport class placement after merge 2026-04-07 00:52:07 +02:00
Fauzan Mirza 7931150412 Merge remote-tracking branch 'origin/main' into pr/nfcimport-v2 2026-04-07 00:49:42 +02:00
GameTec-live 93c1e150ab Merge pull request #361 from azuwis/esave
Fix `param error` of `hf mf esave`
2026-04-06 18:30:30 +02:00
GameTec-live 92505b0364 Merge pull request #362 from kevihiiin/pac-emulation
Add LF PAC/Stanley (125kHz) Support
2026-04-06 18:29:46 +02:00
Kevin Yuan eddbb31c05 Merge branch 'main' into pac-emulation 2026-04-06 16:43:41 +01:00
GameTec-live b77af1e779 Merge pull request #389 from Crazycurly/main
feat(cli): integrate HardNested attack into autopwn
2026-04-04 20:12:31 +02:00
GameTec-live a4b11e441a Merge pull request #388 from taichunmin/usb-serial-number
Fix firmware application USB serial number
2026-04-04 20:09:59 +02:00
Kevin Yuan 3924ad134b Merge branch 'main' into pac-emulation 2026-04-02 14:17:42 +01:00
GameTec-live 91f2e46bcb Merge pull request #397 from nieldk/feat/lf-data-analysis
feat(data): add LF capture analysis commands
2026-04-02 12:40:10 +02:00
GameTec-live 78e78eb883 Merge pull request #396 from nieldk/feat/hf14a-sniff
feat(hf): add ISO14443A reader frame capture (hf 14a sniff)
2026-04-02 12:32:26 +02:00
Niel Nielsen 890f316ca0 Merge branch 'main' into feat/lf-data-analysis 2026-04-02 12:09:01 +02:00
GameTec-live 652f341ff9 Merge pull request #399 from nieldk/feat/lf-raw-sniff-v2
feat(lf): add raw LF field ADC capture (lf sniff)
2026-04-02 11:47:06 +02:00
Niel Nielsen d0a8ade9e4 feat(lf): add raw LF field ADC capture (lf sniff) 2026-04-02 11:16:32 +02:00
GameTec-live 0ac25caedc Merge pull request #398 from RfidResearchGroup/revert-395-feat/lf-raw-sniff
Revert "feat(lf): add raw LF field ADC capture (lf sniff)"
2026-04-02 11:09:15 +02:00
GameTec-live 74e2dac27e Revert "feat(lf): add raw LF field ADC capture (lf sniff)" 2026-04-02 11:07:16 +02:00
Niel Nielsen 27697f9344 Merge branch 'main' into feat/lf-data-analysis 2026-04-02 10:58:20 +02:00
Benjamin Møller dd27081cdf Merge branch 'main' into feat/hf14a-sniff 2026-04-02 10:50:53 +02:00
GameTec-live 4f9cc9ec7c Merge pull request #395 from nieldk/feat/lf-raw-sniff
feat(lf): add raw LF field ADC capture (lf sniff)
2026-04-02 10:44:33 +02:00
Benjamin Møller 3f68690399 Merge branch 'main' into feat/lf-raw-sniff 2026-04-02 10:40:44 +02:00
GameTec-live 88f7fda526 Merge pull request #394 from nieldk/feat/lf-em4x05-reader
feat(lf): add EM4x05/EM4x69 reader (RTF gap protocol)
2026-04-02 09:00:33 +02:00
Niel Nielsen 29c407464b fix: make each PR self-contained with all required source files 2026-04-02 08:14:18 +02:00
Niel Nielsen f65acdd26d fix: make each PR self-contained with all required source files 2026-04-02 08:14:17 +02:00
Niel Nielsen 5daad00953 fix: make each PR self-contained with all required source files 2026-04-02 08:14:16 +02:00
Niel Nielsen 0b6bb28fc1 fix(data): guard Ultra-only includes and processors for Lite build 2026-04-02 08:07:15 +02:00
Niel Nielsen cff829e81c fix(hf): guard Ultra-only includes and processors for Lite build 2026-04-02 08:06:26 +02:00
Niel Nielsen dcad76bf38 fix(lf): guard Ultra-only includes and processors for Lite build 2026-04-02 08:05:33 +02:00
Niel Nielsen 4b88bf57b9 fix(lf): guard Ultra-only includes and processors for Lite build 2026-04-02 08:04:41 +02:00
Niel Nielsen ce932d2e8a feat(data): add LF capture analysis commands 2026-04-02 07:43:16 +02:00
Niel Nielsen 164d450f87 feat(hf): add ISO14443A reader frame capture (hf 14a sniff) 2026-04-02 07:42:26 +02:00
Niel Nielsen 264c2799a7 feat(lf): add raw LF field ADC capture (lf sniff) 2026-04-02 07:41:24 +02:00
Niel Nielsen e02918b867 feat(lf): add EM4x05/EM4x69 reader (RTF gap protocol) 2026-04-02 07:34:16 +02:00
Daniel Wagner a421e99648 request hfxo 2026-03-30 15:37:29 +01:00
Sam 6f4722a964 feat(cli): integrate hardnested attack into autopwn for HardNested vulnerable cards
When autopwn detects a HardNested vulnerable card (nt_level=2) with some known keys,
it now automatically attempts to recover remaining keys using the hardnested attack,
instead of only printing an advisory message. The implementation:

- Iterates over each missing key slot, picking a known key before each attempt
  (allows newly recovered keys to be reused for subsequent targets)
- Invokes hardnested.recover_key() with standard parameters (200 max runs, 3 max attempts)
- After each found key, checks if it is reusable for other sectors
- Falls back to senested attack if hardnested does not recover all keys

This matches the existing behavior for nested and static-encrypted-nested attacks.
2026-03-25 16:30:48 +08:00
Kevin Yuan 9e58461f9a Potential fix: Compare glitch in PWM module 2026-03-24 17:04:18 +00:00
Kevin Yuan ac859f7531 Add PAC/Stanley LF entry 2026-03-24 15:44:12 +00:00
Kevin Yuan f5d721bbfd PAC/Stanley CLI: replace --id with --cn/--raw (PM3 parity)
Split the single --id argument into --cn (8 ASCII chars) and --raw
(32 hex char T55XX bitstream, directly compatible with PM3 raw output).
Add Python-side PAC bitstream encoder/decoder for raw format support.
Output now shows CN and Raw labels matching PM3's format.

Add NRF_LOG module registration to pac.c for debug logging,
consistent with other protocol implementations.

Reassign PAC command IDs (3014/3015) to avoid collision with ioProx
(3010/3011) after rebase onto upstream/main.
2026-03-24 15:04:41 +00:00
Kevin Yuan 69327ded7d Clean up PAC/Stanley CLI: remove debug command, accept ASCII IDs, handle unknown tag types gracefully
- Remove lf pac debug command (development-only)
- Accept both 16-hex and 8-ASCII card ID formats with 7-bit validation
- Add T55xx write command under lf pac write
- Handle unknown TagSpecificType values in slot list without crashing
- Auto-initialize slot data when setting tag type
- Simplify pac_write_to_t55xx by removing unused key parameters
2026-03-24 14:41:22 +00:00
Kevin Yuan ccf4510c1c Improve PAC/Stanley NRZ reader reliability
Three fixes that together bring rapid-fire read reliability from ~20%
to 100%:

- Add MIN_SPIKE_CAP floor (8000) to prevent spike_cap from clipping
  NRZ high when prescan correctly captures NRZ low. Without this,
  spike_cap = raw_min*3 ≈ 2820 collapses the signal range.

- Reorder carrier-before-SAADC in pac_read(): start the 125kHz field
  and wait 10ms before enabling ADC sampling, so prescan calibration
  sees real NRZ signal levels rather than T55XX power-on-reset noise.

- Add auto-recalibration: if no valid frame is found after 20480
  Phase 3 samples (~164ms, ~5 frame periods), reset the decoder to
  Phase 1 and re-calibrate from fresh samples. This gives ~3
  calibration attempts per 500ms scan window instead of just one.

Tested with Proxmark3 sim (15 consecutive rapid-fire reads, 100%) and
T55XX tag (write-read roundtrip + 15x rapid-fire, 100%).
2026-03-24 14:38:46 +00:00
Kevin Yuan 8442bea4c1 Add PAC/Stanley T55XX write support
Add pac_t55xx_writer() for encoding PAC card data into T55XX blocks,
along with the T5577_PAC_CONFIG (NRZ/Direct, RF/32, password-protected,
4 data blocks). Wire DATA_CMD_PAC_WRITE_TO_T55XX (3011) through the
command processor, dispatch table, and Python client.
2026-03-24 14:38:46 +00:00
Kevin Yuan 17ff2abf60 Replace moving average with PM3-style per-sample thresholding and fix integer overflows
Replace the 32-sample moving average + hysteresis demodulation with
Proxmark3-inspired per-sample thresholding and dead zone. This
eliminates ~16 samples of group delay per edge, reducing timing
jitter from ~11 samples to ~2-3 samples.

The new approach:
- Prescan: track raw_min, compute spike_cap (unchanged)
- Warmup: track min/max of clipped samples directly (not averaged)
- Detection: per-sample dead zone classification — sample >= high
  threshold → 1, sample <= low threshold → 0, between → keep
  previous state. Thresholds set at 75% fuzz of signal range.

Removes the avg_buf[32] circular buffer, avg_sum, avg_idx, and
sum-unit threshold/hysteresis state. Struct is 72 bytes smaller.

Widen integer types to prevent overflow UB:
- sample_count: uint16_t -> uint32_t (overflows at 524ms)
- interval, nbits: uint16_t -> uint32_t (matching sample_count width)
2026-03-24 14:38:46 +00:00
Kevin Yuan 2fd1a260cf Add PAC/Stanley LF tag emulation support
Implements NRZ/Direct modulation at RF/32 for PAC/Stanley tag emulation.
The modulator encodes 8-byte ASCII card IDs into 128-bit NRZ frames
(0xFF sync + 12 UART frames) and generates PWM waveforms using constant
output levels (compare=counter_top for HIGH, compare=0 for LOW).

Firmware: modulator in pac.c, load/save/factory callbacks in lf_tag_em,
tag_emulation registration, SET/GET_EMU_ID commands (5006/5007).
CLI: pac_set/get_emu_id methods, 'lf pac econfig' command, hw slot list
display for PAC tags.
2026-03-24 14:38:46 +00:00
Kevin Yuan c494a2cc81 Add PAC/Stanley LF tag reading support
Implements NRZ/Direct modulation decoder for PAC/Stanley 125kHz cards
using SAADC ADC sampling with spike-aware threshold calibration.
The LC antenna produces brief high-amplitude transients at NRZ transitions
which are clipped before the moving-average filter to isolate the actual
data levels.
2026-03-24 14:37:25 +00:00
taichunmin c51051b30e Fix firmware application USB serial number 2026-03-24 00:44:36 +08:00
Fauzan MirzaandClaude Opus 4.6 acb8959117 docs: add nfcimport entry to CHANGELOG
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 23:52:35 +01:00
Fauzan MirzaandClaude Opus 4.6 193f66acdd feat: add --amiibo flag to hf mfu nfcimport for PWD/PACK derivation
Real NTAG 215 chips never reveal the stored password over NFC, so
Flipper .nfc dumps always have zeros for pages 133-134 (PWD/PACK).
This causes readers to reject the emulated tag when they attempt
PWD_AUTH as part of their amiibo validation flow.

The --amiibo flag derives the correct PWD from the UID using the
well-known XOR algorithm and sets PACK to the standard 0x8080,
enabling proper authentication with Nintendo devices.

Usage: hf mfu nfcimport -f Kirby.nfc -s 6 --amiibo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 23:52:13 +01:00
Fauzan MirzaandClaude Opus 4.6 a5847c75ef feat: add Flipper Zero .nfc file importer for MFU/NTAG slots
Add `hf mfu nfcimport` command to import Flipper Zero .nfc files
directly into ChameleonUltra emulator slots. Supports NTAG 210/212/
213/215/216, Mifare Ultralight, Ultralight C, and Ultralight EV1.

The importer parses the Flipper .nfc format and configures the slot
with the correct tag type, anti-collision data (UID/ATQA/SAK),
GET_VERSION response, READ_SIG signature, counter values, and full
page data.

Handles NTAG counter index mapping (Flipper's NFC counter index 2
maps to firmware internal index 0) and gracefully skips unsupported
counters with a warning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 23:52:13 +01:00
GameTec-live e5d615d512 Merge pull request #367 from bernadic/feat/ioprox
feat(lf): add ioProx support (read, emulate, clone)
2026-03-20 18:46:07 +01:00
andrassmuk 8e28d1a40e fix: use native winpthreads for MinGW/MSYS2 builds on Windows
The pthreads4w dependency uses MSVC-specific architecture detection
(_M_X64, _M_IX86 macros) which fails under MinGW/MSYS2/ProxSpace
with "unknown not supported in version.rc".

MinGW-w64 ships with winpthreads, so only MSVC builds need pthreads4w.

Fixes #378
2026-03-19 15:06:33 +01:00
Jozef Bernadic e4d70d1417 fix(cmd): resolve ioProx command ID conflict with #362 2026-03-19 08:25:59 +01:00
Jozef Bernadic 76bb091247 docs(changelog): add ioProx entry 2026-03-03 16:58:34 +01:00
Jozef Bernadic 1b6701661d feat(cli): add ioProx commands 2026-03-03 16:24:26 +01:00
Jozef Bernadic 202c6a677e feat(lf): integrate ioProx into LF reader, emulation and T55xx writer pipeline 2026-03-03 16:24:26 +01:00
Jozef Bernadic 36645932de feat(lf): add ioProx support (reader, emulation and T55xx writer) 2026-03-03 16:24:08 +01:00
Jozef Bernadic e1a2f698f4 chore: ignore local IDE files and build artifacts 2026-03-03 15:16:27 +01:00
Zhong Jianxin 9d483cdc5e Fix param error of hf mf esave
Step to reproduce:

```
[USB] chameleon --> hf mf esave -f test.bin
API request fail, param error
```

Commit d95112f821 change
NETDATA_MAX_DATA_LENGTH from 512 to 4096, this increase max block count
to 256, while [cmd_processor_mf1_read_emu_block_data][1] hardcode max
block count to 32

[1]: https://github.com/RfidResearchGroup/ChameleonUltra/blob/b108c84af9b473c840ddcae6f769502adb6c5aa5/firmware/application/src/app_cmd.c#L1088
2026-02-17 09:52:59 +08:00
GameTec-live 1bfd01b45a Merge branch 'main' into fix/pyinstaller-bin-path 2026-02-02 19:40:56 +01:00
Bob 301fdb2f57 fix: resolve binary tools detection after PyInstaller packaging
This fixes the warning about missing tools after PyInstaller

packaging by correctly detecting the temporary

extraction directory (sys._MEIPASS) in frozen environments.

- New get_resource_dir() in chameleon_utils.py

- Import default_cwd in chameleon_cli_unit.py

- Simplify check_tools() to use imported default_cwd
2025-11-01 15:27:54 +08:00
unkernet 6ceefb39d7 MF1: Enforce block-level access control for READ/WRITE operations
- Introduced `blockToSector()` and `sectorToBlock()` helpers to map between blocks and sectors.
- Added `AuthenticatedSector` to track which sector is currently authenticated.
- Fix access control table `abTrailerAccessConditions`: fixed swapped entries for access conditions.
- Prevent authentication with Key B if it is readable using Key A.
- READ and WRITE commands now verify permissions; unauthorized access are rejected.
- On trailer writes, preserved protected fields (Key A, access bits, Key B) based on access conditions if write is not permitted.
2025-08-10 09:49:58 +07:00
unkernet 284cef624d MF1: Unify state reset logic
- Ensured state machine reset on CRC/parity errors and invalid operations across command handlers.
- Set `NFC_TAG_STATE_14A_PROPRIETARY` upon successful authentication to route subsequent commands only into the MF1 handler.
2025-08-10 07:11:22 +07:00
84 changed files with 10213 additions and 519 deletions
+5
View File
@@ -708,3 +708,8 @@ FodyWeavers.xsd
# End of https://www.toptal.com/developers/gitignore/api/visualstudio,c++,c,python,visualstudiocode,macos,windows
software/script/tests/nonces.bin
software/script/nonces.bin
.vscode/settings.json
.vscode/tasks.json
firmware/compile_commands.json
firmware/application/compile_commands.json
software/src/target_arch_detect.c
+9 -1
View File
@@ -3,7 +3,14 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased]
- Hardware upgrade: Restarting the Ultra now only requires running each of the three RGB colors once, resolving previous firmware modification issues
## [v2.2.0][2026-07-04]
- Added Jablotron LF protocol support: read, emulate and T55xx clone (@midlan)
- Added IDTECK LF protocol support: tag emulation (PSK1 RF/32) and T55xx clone. No reader path yet; PSK demodulation on the envelope-only receive chain is left for a follow-up.
- Added PAC/Stanley LF protocol support: read, emulate and T55xx clone (@kevihiiin, @danieltwagner)
- Fix firmware application USB serial number (@taichunmin)
- Added ioProx LF protocol support (read, emulate and T55xx clone)
- Added `hf mfu nfcimport` to import Flipper Zero `.nfc` files into MFU/NTAG emulator slots, with `--amiibo` flag for automatic PWD/PACK derivation (@fmuk)
- Added commands to dump and clone Mifare tags
- Fix bad missing tools warning (@suut)
- Fix for FAST_READ command for nfc - mf0 tags
@@ -19,6 +26,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
- Fix Windows build (@suut)
- Added `hf 14a config` to deal with badly configured cards (@azuwis)
- New Symmetrical LED Animation Mode and Improved Minimal Mode (@WillyJL)
- Fix MF1 state reset logic and access control conditions (@unkernet)
## [v2.1.0][2025-09-02]
- Added UV, formatter and linter. Contribution guidelines. (@GameTec-live)
+3 -5
View File
@@ -18,10 +18,6 @@ Guangdong, China: [MTools Tec](https://shop.mtoolstec.com/)
Lazada One, Singapore: [Aliexpress by RRG](https://proxgrind.aliexpress.com/store/1101312023)
# Hardware Upgrade Notice
**Important:** The Chameleon Ultra hardware has been upgraded! Restarting the device now only requires running each of the three RGB colors once (equivalent to a restart). This resolves previous issues where firmware modifications could cause the device to malfunction.
# What is it and how to use ?
Read the [available documentation](https://github.com/RfidResearchGroup/ChameleonUltra/wiki).
@@ -30,6 +26,8 @@ Read the [available documentation](https://github.com/RfidResearchGroup/Chameleo
* [ChameleonUltraGUI](https://github.com/GameTec-live/ChameleonUltraGUI)
* [MTools BLE](https://github.com/RfidResearchGroup/ChameleonUltra/wiki/mtoolsble)
* [Mifare Chameleon Tool (iOS only, Beta)](https://apps.apple.com/it/app/mifare-chameleon-tool/id6761231484)
* [Chameleon Ultra (Sailfish OS only)](https://sailfishos-chum.github.io/apps/harbour-chameleon-ultra)
# Videos
@@ -49,4 +47,4 @@ Where do you find the community?
* Devices/chameleon-ultra for usage discussions
* [GameTec_live discord server](https://discord.gg/DJ2A4wxncK)
###### Searching for the docs repo? Find it [here](https://github.com/RfidResearchGroup/ChameleonUltraDocs)
###### Searching for the docs repo? Find it [here](https://github.com/RfidResearchGroup/ChameleonUltraDocs)
+13 -1
View File
@@ -28,16 +28,23 @@ SRC_FILES += \
$(PROJ_DIR)/rfid/nfctag/tag_persistence.c \
$(PROJ_DIR)/rfid/nfctag/hf/crypto1_helper.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_14a.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_14a_4.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf1.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf0_ntag.c \
$(PROJ_DIR)/rfid/nfctag/lf/lf_tag_em.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/fskdemod.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/circular_buffer.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/manchester.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/psk1.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/em410x.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/hidprox.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/pac.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/ioprox.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/viking.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/jablotron.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/diphase.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/wiegand.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/idteck.c \
$(PROJ_DIR)/utils/dataframe.c \
$(PROJ_DIR)/utils/delayed_reset.c \
$(PROJ_DIR)/utils/fds_util.c \
@@ -340,12 +347,17 @@ ifeq (${CURRENT_DEVICE_TYPE}, ${CHAMELEON_ULTRA})
$(PROJ_DIR)/rfid/reader/hf/rc522.c \
$(PROJ_DIR)/rfid/reader/lf/lf_125khz_radio.c \
$(PROJ_DIR)/rfid/reader/lf/lf_em410x_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_em4x05_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_gap.c \
$(PROJ_DIR)/rfid/reader/lf/lf_reader_generic.c \
$(PROJ_DIR)/rfid/reader/lf/lf_reader_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_reader_main.c \
$(PROJ_DIR)/rfid/reader/lf/lf_t55xx_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_hidprox_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_pac_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_ioprox_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_viking_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_reader_generic.c \
$(PROJ_DIR)/rfid/reader/lf/lf_jablotron_data.c \
INC_FOLDERS +=\
${PROJ_DIR}/rfid/reader/ \
File diff suppressed because it is too large Load Diff
+34 -18
View File
@@ -12,6 +12,7 @@
#include "nrf_delay.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_rng.h"
#include "nfc_mf1.h" // for nfc_tag_mf1_prng_seed
#include "nrf_power.h"
#include "nrf_pwr_mgmt.h"
#include "nrfx_nfct.h"
@@ -61,7 +62,7 @@ static bool m_is_a_btn_release = false;
static bool m_system_off_processing = false;
// NFC field generator state
volatile bool m_is_field_on = false;
volatile bool m_is_field_on = false;
// cpu reset reason
static uint32_t m_reset_source;
@@ -136,6 +137,11 @@ void rng_drv_and_srand_init(void) {
// Finally initialize the srand seeds in the c standard library
srand(rand_int);
// Seed the MFC LFSR PRNG with the same hardware random value.
// This makes nonce generation follow the real Mifare Classic LFSR pattern
// so readers that fingerprint PRNG type (e.g. Eltis) accept the emulated card.
nfc_tag_mf1_prng_seed(rand_int);
}
/**@brief Initialize GPIO matrix library
@@ -150,27 +156,27 @@ static void gpio_te_init(void) {
static void field_generator_rainbow_loop(void) {
static uint8_t color_index = 0;
static uint32_t last_update = 0;
if (!m_is_field_on) return;
uint32_t now = app_timer_cnt_get();
if (app_timer_cnt_diff_compute(now, last_update) < APP_TIMER_TICKS(100)) {
return;
}
last_update = now;
// Rainbow colors
const uint8_t colors[] = {RGB_RED, RGB_YELLOW, RGB_GREEN, RGB_CYAN, RGB_BLUE, RGB_MAGENTA};
set_slot_light_color(colors[color_index]);
uint32_t *led_pins = hw_get_led_array();
// Light up all LEDs with current color
for (int i = 0; i < RGB_LIST_NUM; i++) {
nrf_gpio_pin_set(led_pins[i]);
}
color_index = (color_index + 1) % 6;
}
#endif
@@ -198,9 +204,9 @@ static void timer_button_event_handle(void *arg) {
NRF_LOG_INFO("BUTTON press during shutdown");
return;
}
nrf_drv_gpiote_pin_t pin = *(nrf_drv_gpiote_pin_t *)arg;
// Check here if the current GPIO is at the pressed level
if (nrf_gpio_pin_read(pin) == 1) {
if (pin == BUTTON_1) {
@@ -667,6 +673,11 @@ static void btn_fn_copy_lf(uint8_t slot, tag_specific_type_t type) {
size = LF_HIDPROX_TAG_ID_SIZE;
data = id_buffer;
break;
case TAG_TYPE_IOPROX:
status = scan_ioprox(id_buffer, 0);
size = LF_IOPROX_TAG_ID_SIZE;
data = id_buffer;
break;
case TAG_TYPE_EM410X:
case TAG_TYPE_EM410X_ELECTRA: {
status = scan_em410x(id_buffer);
@@ -689,6 +700,11 @@ static void btn_fn_copy_lf(uint8_t slot, tag_specific_type_t type) {
size = LF_VIKING_TAG_ID_SIZE;
data = id_buffer;
break;
case TAG_TYPE_JABLOTRON:
status = scan_jablotron(id_buffer);
size = LF_JABLOTRON_TAG_ID_SIZE;
data = id_buffer;
break;
default:
NRF_LOG_ERROR("Unsupported LF tag type")
offline_status_error();
@@ -843,16 +859,16 @@ static void run_button_function_by_settings(settings_button_function_t sbf) {
nrf_gpio_pin_set(READER_POWER); // reader power enable
nrf_gpio_cfg_output(HF_ANT_SEL);
nrf_gpio_pin_clear(HF_ANT_SEL); // hf ant switch to reader mode
pcd_14a_reader_init();
bsp_delay_ms(10);
}
pcd_14a_reader_reset();
pcd_14a_reader_antenna_on();
m_is_field_on = true;
NRF_LOG_INFO("NFC field ON");
// Set initial rainbow state
set_slot_light_color(RGB_RED);
uint32_t *led_pins = hw_get_led_array();
@@ -868,7 +884,7 @@ static void run_button_function_by_settings(settings_button_function_t sbf) {
pcd_14a_reader_antenna_off();
m_is_field_on = false;
NRF_LOG_INFO("NFC field OFF");
// If we're not in reader mode, clean up the hardware
device_mode_t current_mode = get_device_mode();
if (current_mode != DEVICE_MODE_READER) {
@@ -876,7 +892,7 @@ static void run_button_function_by_settings(settings_button_function_t sbf) {
nrf_gpio_pin_clear(READER_POWER); // reader power disable
nrf_gpio_pin_set(HF_ANT_SEL); // hf ant switch back to tag mode
}
// Restore normal LED
light_up_by_slot();
@@ -1024,17 +1040,17 @@ int main(void) {
lesc_event_process();
// Button event process
button_press_process();
#if defined(PROJECT_CHAMELEON_ULTRA)
// Field generator rainbow animation
field_generator_rainbow_loop();
#endif
// Led blink at usb status (only if field generator is off)
if (!m_is_field_on) {
blink_usb_led_status();
}
// Data pack process
data_frame_process();
// Log print process
+1
View File
@@ -19,6 +19,7 @@
/////////////////////////////////////////////////////////////////////
#define STATUS_LF_TAG_OK (0x40) // Some of the low -frequency cards are successful!
#define STATUS_LF_TAG_NO_FOUND (0x41) // Can't search for valid LF tags
#define STATUS_LF_TAG_LOGIN_REQUIRED (0x42) // Tag requires LOGIN before read
/////////////////////////////////////////////////////////////////////
// other status
+7 -7
View File
@@ -90,7 +90,7 @@ BLE_ADVERTISING_DEF(m_advertising);
uint16_t batt_lvl_in_milli_volts = 0;
uint8_t percentage_batt_lvl = 0;
static nrf_saadc_value_t adc_buf[ADC_BUF_SIZE][ADC_BUF_COUNT];
static nrf_saadc_value_t adc_buf[ADC_BUF_COUNT][ADC_BUF_SIZE];
static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID; /**< Handle of the current connection. */
static uint16_t m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3; /**< Maximum length of data (in bytes) that can be transmitted to the peer by the Nordic UART service module. */
lf_adc_callback_t m_lf_adc_callback = NULL;
@@ -736,11 +736,11 @@ static void battery_level_meas_timeout_handler(void *p_context) {
// if battery service is notification enable, we can send msg to device.
err_code = ble_bas_battery_level_update(&m_bas, percentage_batt_lvl, BLE_CONN_HANDLE_ALL);
if ((err_code != NRF_SUCCESS) &&
(err_code != NRF_ERROR_INVALID_STATE) &&
(err_code != NRF_ERROR_RESOURCES) &&
(err_code != NRF_ERROR_BUSY) &&
(err_code != NRF_ERROR_FORBIDDEN) &&
(err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)) {
(err_code != NRF_ERROR_INVALID_STATE) &&
(err_code != NRF_ERROR_RESOURCES) &&
(err_code != NRF_ERROR_BUSY) &&
(err_code != NRF_ERROR_FORBIDDEN) &&
(err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)) {
APP_ERROR_HANDLER(err_code);
}
@@ -806,4 +806,4 @@ void unregister_lf_adc_callback(void) {
nrfx_saadc_uninit();
adc_configure();
m_lf_adc_callback = NULL;
}
}
+37
View File
@@ -46,6 +46,8 @@
#define DATA_CMD_GET_BLE_PAIRING_ENABLE (1036)
#define DATA_CMD_SET_BLE_PAIRING_ENABLE (1037)
#define DATA_CMD_GET_ALL_SLOT_NICKS (1038)
#define DATA_CMD_GET_SLEEP_TIMEOUT (1039)
#define DATA_CMD_SET_SLEEP_TIMEOUT (1040)
//
// ******************************************************************
@@ -67,6 +69,8 @@
#define DATA_CMD_MF1_READ_ONE_BLOCK (2008)
#define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009)
#define DATA_CMD_HF14A_RAW (2010)
#define DATA_CMD_HF14A_SCAN_KEEP (2016) /* scan+RATS, keep field alive for APDU exchange */
#define DATA_CMD_HF14A_AUTH_TRACE (2017) /* full anticoll + Crypto1 auth, every frame returned for inspection */
#define DATA_CMD_MF1_MANIPULATE_VALUE_BLOCK (2011)
#define DATA_CMD_MF1_CHECK_KEYS_OF_SECTORS (2012)
#define DATA_CMD_MF1_HARDNESTED_ACQUIRE (2013)
@@ -78,6 +82,7 @@
#define DATA_CMD_HF14A_GET_CONFIG (2200)
#define DATA_CMD_HF14A_SET_CONFIG (2201)
#define DATA_CMD_HF14A_SNIFF (2020)
//
// ******************************************************************
@@ -93,11 +98,21 @@
#define DATA_CMD_EM410X_ELECTRA_WRITE_TO_T55XX (3006)
#define DATA_CMD_HIDPROX_SCAN (3002)
#define DATA_CMD_HIDPROX_WRITE_TO_T55XX (3003)
#define DATA_CMD_PAC_SCAN (3014)
#define DATA_CMD_PAC_WRITE_TO_T55XX (3015)
#define DATA_CMD_VIKING_SCAN (3004)
#define DATA_CMD_VIKING_WRITE_TO_T55XX (3005)
#define DATA_CMD_ADC_GENERIC_READ (3009)
#define DATA_CMD_GENERIC_READ (3007)
#define DATA_CMD_CORR_GENERIC_READ (3008)
#define DATA_CMD_IOPROX_SCAN (3010)
#define DATA_CMD_IOPROX_WRITE_TO_T55XX (3011)
#define DATA_CMD_IOPROX_DECODE_RAW (3012)
#define DATA_CMD_IOPROX_COMPOSE_ID (3013)
#define DATA_CMD_LF_T55XX_WRITE (3016)
#define DATA_CMD_IDTECK_WRITE_TO_T55XX (3018)
#define DATA_CMD_JABLOTRON_SCAN (3019)
#define DATA_CMD_JABLOTRON_WRITE_TO_T55XX (3020)
//
// ******************************************************************
@@ -148,6 +163,8 @@
#define DATA_CMD_MF0_NTAG_GET_EMULATOR_CONFIG (4037)
#define DATA_CMD_MF1_SET_FIELD_OFF_DO_RESET (4038)
#define DATA_CMD_MF1_GET_FIELD_OFF_DO_RESET (4039)
#define DATA_CMD_MF1_GET_PRNG_TYPE (4040) // 0=static 1=weak(LFSR) 2=hard(rand)
#define DATA_CMD_MF1_SET_PRNG_TYPE (4041)
//
// ******************************************************************
@@ -160,11 +177,31 @@
//
// ******************************************************************
/* ISO14443-4 T=CL emulation commands */
#define DATA_CMD_HF14A_4_APDU_RECV (6000) /* non-blocking poll: firmware->host APDU */
#define DATA_CMD_HF14A_4_APDU_SEND (6001) /* host->firmware APDU response */
#define DATA_CMD_HF14A_4_SET_ANTI_COLL (6002) /* set UID/ATQA/SAK/ATS */
#define DATA_CMD_HF14A_4_STATIC_RESP (6003) /* add/clear static APDU response pair */
#define DATA_CMD_HF14A_4_READER_APDU (6004) /* select+RATS+send APDU, keep field */
#define DATA_CMD_HF14A_4_EMV_SCAN (6005) /* full EMV scan in one call */
#define DATA_CMD_EM410X_SET_EMU_ID (5000)
#define DATA_CMD_EM410X_GET_EMU_ID (5001)
#define DATA_CMD_HIDPROX_SET_EMU_ID (5002)
#define DATA_CMD_HIDPROX_GET_EMU_ID (5003)
#define DATA_CMD_VIKING_SET_EMU_ID (5004)
#define DATA_CMD_VIKING_GET_EMU_ID (5005)
#define DATA_CMD_PAC_SET_EMU_ID (5006)
#define DATA_CMD_PAC_GET_EMU_ID (5007)
#define DATA_CMD_IOPROX_SET_EMU_ID (5008)
#define DATA_CMD_IOPROX_GET_EMU_ID (5009)
#define DATA_CMD_JABLOTRON_SET_EMU_ID (5010)
#define DATA_CMD_JABLOTRON_GET_EMU_ID (5011)
#define DATA_CMD_IDTECK_SET_EMU_ID (5012)
#define DATA_CMD_IDTECK_GET_EMU_ID (5013)
#define DATA_CMD_EM4X05_SCAN (3030)
#define DATA_CMD_EM4X05_READSNIFF (3032)
#define DATA_CMD_LF_SNIFF (3031)
#endif
+1 -1
View File
@@ -34,4 +34,4 @@ const uint8_t byte_mirror[256] = {
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
};
};
@@ -59,6 +59,36 @@ const uint16_t ats_fsdi_table[] = {
static volatile bool m_is_responded = false;
// Receiving buffer
static uint8_t m_nfc_rx_buffer[MAX_NFC_RX_BUFFER_SIZE] = { 0x00 };
/* Optional sniff callback — fires for every received frame */
static nfc_tag_14a_sniff_cb_t m_sniff_cb = NULL;
void nfc_tag_14a_set_sniff_cb(nfc_tag_14a_sniff_cb_t cb) {
m_sniff_cb = 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 };
@@ -326,6 +356,11 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
// Because of this error receiving event caused by this possible interference
return;
}
/* Sniff hook — fire before any tag response logic */
if (m_sniff_cb != NULL) {
m_sniff_cb(p_data, szDataBits);
}
// Manually draw frame, separate data and strange school inspection
#if !NFC_TAG_14A_RX_PARITY_AUTO_DEL_ENABLE
if (szDataBits >= 9) {
@@ -342,6 +377,11 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
// The trigger conditions are: REQA response in non -Halt mode
// Temporary through: Wupa response in non -choice state, no matter what state is in the state, you can use the Wupa instruction to wake up
if ((szDataBits == 7) && ((isREQA && m_tag_state_14a != NFC_TAG_STATE_14A_HALTED) || isWUPA)) {
// Received 7-bit command (REQA or WUPA) while the tag is active — reset state machine
if (m_tag_state_14a != NFC_TAG_STATE_14A_IDLE && m_tag_state_14a != NFC_TAG_STATE_14A_HALTED) {
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
return;
}
// The receiver of the 14A communication is notified, the internal state machine is reset
if (m_tag_handler.cb_reset != NULL) {
m_tag_handler.cb_reset();
@@ -350,9 +390,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.");
@@ -468,7 +510,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;
}
@@ -482,10 +526,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
@@ -511,6 +559,10 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
}
// RATS instruction
if (p_data[0] == NFC_TAG_14A_CMD_RATS && nfc_tag_14a_checks_crc(p_data, 4)) {
// Reset T=CL layer state for the new session
if (m_tag_handler.cb_reset != NULL) {
m_tag_handler.cb_reset();
}
// Make sure the sub -packaging opens the support of ATS
if (auto_coll_res->ats->length > 0) {
// Take out FSD and return according to the maximum FSD
@@ -529,8 +581,16 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
// No processing is successful, it may be some other data. You need to re-post processing
if (m_tag_handler.cb_state != NULL) { //Activation status, transfer the message to other registered processor processing
m_tag_handler.cb_state(p_data, szDataBits);
break;
}
break;
}
case NFC_TAG_STATE_14A_PROPRIETARY: {
if (m_tag_handler.cb_state != NULL) {
m_tag_handler.cb_state(p_data, szDataBits);
} else {
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
}
break;
}
}
}
@@ -555,21 +615,20 @@ static inline void nrf_nfct_reset(void) {
// Use Window Grid frame delay mode.
nrf_nfct_frame_delay_mode_set(NRF_NFCT_FRAME_DELAY_MODE_WINDOWGRID);
/* Begin: Workaround for anomaly 25 */
/* Workaround for wrong SENSRES values require using SDD00001, but here SDD00100 is used
because it is required to operate with Windows Phone */
nrf_nfct_sensres_bit_frame_sdd_set(NRF_NFCT_SENSRES_BIT_FRAME_SDD_00100);
/* End: Workaround for anomaly 25 */
/* Use SDD00001 per ISO14443-3 standard.
* Note: SDD00100 was previously used for Windows Phone compatibility
* but breaks standard readers (including Proxmark3). SDD00001 is correct. */
nrf_nfct_sensres_bit_frame_sdd_set(NRF_NFCT_SENSRES_BIT_FRAME_SDD_00001);
// Restore interrupts.
nrf_nfct_int_enable(int_enabled);
// Disable interrupts associated with data exchange.
nrf_nfct_int_disable(NRF_NFCT_INT_RXFRAMESTART_MASK |
NRF_NFCT_INT_RXFRAMEEND_MASK |
NRF_NFCT_INT_RXERROR_MASK |
NRF_NFCT_INT_TXFRAMESTART_MASK |
NRF_NFCT_INT_TXFRAMEEND_MASK);
nrf_nfct_int_disable(NRF_NFCT_INT_RXFRAMESTART_MASK |
NRF_NFCT_INT_RXFRAMEEND_MASK |
NRF_NFCT_INT_RXERROR_MASK |
NRF_NFCT_INT_TXFRAMESTART_MASK |
NRF_NFCT_INT_TXFRAMEEND_MASK);
}
static inline void nfc_fdt_reset(void) {
@@ -622,7 +681,7 @@ void nfc_tag_14a_event_callback(nrfx_nfct_evt_t const *p_event) {
if (reset_if_field_lost) {
// Fix a bug where certain special conditions prevent triggering TX start events and actually transmit incorrect data to the card reader.
// After more more more testing, I found that simply going into sleep mode and restarting can restore work.
// After more more more testing, I found that simply going into sleep mode and restarting can restore work.
// Therefore, I suspect that there may be some issues with the NFC peripheral that require a reset to resolve.
nrf_nfct_reset();
}
@@ -632,7 +691,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: {
@@ -4,7 +4,7 @@
#include "tag_emulation.h"
#define MAX_NFC_RX_BUFFER_SIZE 257
#define MAX_NFC_TX_BUFFER_SIZE 64
#define MAX_NFC_TX_BUFFER_SIZE 512 /* must hold PCB + max APDU response */
#define NFC_TAG_14A_CRC_LENGTH 2
@@ -36,10 +36,11 @@
// ISO14443-A Universal state machine
typedef enum {
NFC_TAG_STATE_14A_IDLE, // Leisure, you can wait for any instructions
NFC_TAG_STATE_14A_READY, // Select card status, currently the standard 14A anti -rushing collision
NFC_TAG_STATE_14A_ACTIVE, // Select cards or other instructions to enter the working status, which can receive all data
NFC_TAG_STATE_14A_HALTED, // The label stops working status and can only be awakened by Halt or other special instructions (non -labels)
NFC_TAG_STATE_14A_IDLE, // Leisure, you can wait for any instructions
NFC_TAG_STATE_14A_READY, // Select card status, currently the standard 14A anti -rushing collision
NFC_TAG_STATE_14A_ACTIVE, // Select cards or other instructions to enter the working status, which can receive all data
NFC_TAG_STATE_14A_HALTED, // The label stops working status and can only be awakened by Halt or other special instructions (non -labels)
NFC_TAG_STATE_14A_PROPRIETARY, // Card is in proprietary state; all commands handled only by state_handler
} nfc_tag_14a_state_t;
// UID of the length in the enumeration specification
@@ -82,6 +83,27 @@ typedef struct {
// Communication reception function that needs to be implemented
typedef void (*nfc_tag_14a_reset_handler_t)(void);
/* Sniff callback — called for every received frame before the tag handler.
* data : raw frame bytes (after parity strip)
* szBits : number of bits received */
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);
@@ -0,0 +1,446 @@
/**
* @file nfc_14a_4.c
* @brief ISO14443-4 T=CL emulation for ChameleonUltra
*
* Implements a full ISO14443-4 tag emulator with a static APDU response
* table. The table is populated by the host before field activation, so
* the firmware can respond to an EMV reader autonomously without any USB
* communication while the RF field is active.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <string.h>
#include "nfc_14a_4.h"
#include "nfc_14a.h"
#include "tag_emulation.h"
#include "tag_persistence.h"
#include "fds_util.h"
#include "nrf_log.h"
/* ------------------------------------------------------------------ */
/* PCB byte constants (ISO14443-4 §7) */
/* ------------------------------------------------------------------ */
#define PCB_IBLOCK_MASK 0xC0
#define PCB_IBLOCK_VAL 0x00
#define PCB_RBLOCK_MASK 0xE0
#define PCB_RBLOCK_VAL 0x80 /* R(ACK) = 0xA2/0xA3, R(NAK) = 0xB2/0xB3 */
#define PCB_SBLOCK_MASK 0xC0
#define PCB_SBLOCK_VAL 0xC0
#define PCB_BLOCK_NUM 0x01
#define PCB_CID_FOLLOWING 0x10 /* bit4: CID follows */
#define PCB_NAD_FOLLOWING 0x08 /* bit3: NAD follows */
#define PCB_CHAIN 0x20 /* bit5: chaining flag per ISO14443-4 Table 3 */
#define PCB_SBLOCK_WTX 0x30
#define PCB_SBLOCK_DESELECT 0xC2
#define WTX_VALUE 0x3B /* WTXM=59 (~3s extra wait) */
static inline bool is_iblock(uint8_t pcb) {
return (pcb & PCB_IBLOCK_MASK) == PCB_IBLOCK_VAL;
}
static inline bool is_rblock(uint8_t pcb) {
/* R-block: bit7=1, bit6=0, bit2=1, bit1=0 (mask 0xC6, value 0x82) */
return (pcb & 0xC6) == 0x82;
}
static inline bool is_sblock(uint8_t pcb) {
return (pcb & PCB_SBLOCK_MASK) == PCB_SBLOCK_VAL;
}
/* ------------------------------------------------------------------ */
/* Module state */
/* ------------------------------------------------------------------ */
static nfc_tag_14a_4_information_t *m_tag_information = NULL;
/* Shadow coll-res references into m_tag_information */
static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res;
/* T=CL session state */
static uint8_t m_block_num = 0;
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 */
static uint8_t m_tx_buf[NFC_14A_4_MAX_APDU + 4];
/* Debug counters — readable via hf 14a debug */
static uint8_t m_dbg_iblocks_rx = 0; /* I-blocks received */
static uint8_t m_dbg_iblocks_tx = 0; /* I-blocks sent */
static uint8_t m_dbg_last_rx_pcb = 0; /* PCB of last received I-block */
static uint8_t m_dbg_last_match = 0; /* last find_static_response result */
/* Static APDU response table (RAM copy, populated from m_tag_information) */
static nfc_tag_14a_4_static_response_t m_static_resp[NFC_14A_4_MAX_STATIC_RESPONSES];
static uint8_t m_static_resp_count = 0;
/* Large response overflow (RAM only, > NFC_14A_4_MAX_STATIC_RESP_LEN bytes).
* NOT persisted to flash. Must reload via emv load after power cycle. */
typedef struct {
uint8_t cmd[NFC_14A_4_MAX_STATIC_CMD_LEN];
uint8_t cmd_len;
uint8_t resp[NFC_14A_4_MAX_LARGE_RESP_LEN];
uint16_t resp_len;
} nfc_tag_14a_4_large_response_t;
static nfc_tag_14a_4_large_response_t m_large_resp[NFC_14A_4_MAX_LARGE_RESPONSES];
static uint8_t m_large_resp_count = 0;
/* ------------------------------------------------------------------ */
/* Static response table */
/* ------------------------------------------------------------------ */
void nfc_tag_14a_4_add_static_response(const uint8_t *cmd, uint8_t cmd_len,
const uint8_t *resp, uint16_t resp_len) {
if (cmd_len > NFC_14A_4_MAX_STATIC_CMD_LEN) cmd_len = NFC_14A_4_MAX_STATIC_CMD_LEN;
if (resp_len > NFC_14A_4_MAX_STATIC_RESP_LEN) {
/* Large response: RAM-only overflow table */
if (m_large_resp_count >= NFC_14A_4_MAX_LARGE_RESPONSES) return;
if (resp_len > NFC_14A_4_MAX_LARGE_RESP_LEN) resp_len = NFC_14A_4_MAX_LARGE_RESP_LEN;
nfc_tag_14a_4_large_response_t *le = &m_large_resp[m_large_resp_count++];
le->cmd_len = cmd_len;
le->resp_len = resp_len;
memcpy(le->cmd, cmd, cmd_len);
memcpy(le->resp, resp, resp_len);
return;
}
/* Normal response: flash-backed table */
if (m_static_resp_count >= NFC_14A_4_MAX_STATIC_RESPONSES) return;
nfc_tag_14a_4_static_response_t *e = &m_static_resp[m_static_resp_count++];
e->cmd_len = cmd_len;
e->resp_len = (uint8_t)resp_len;
memcpy(e->cmd, cmd, cmd_len);
memcpy(e->resp, resp, resp_len);
if (m_tag_information &&
m_tag_information->static_resp_count < NFC_14A_4_MAX_STATIC_RESPONSES) {
memcpy(&m_tag_information->static_resp[m_tag_information->static_resp_count++],
e, sizeof(*e));
}
}
void nfc_tag_14a_4_clear_static_responses(void) {
m_static_resp_count = 0;
m_large_resp_count = 0;
if (m_tag_information) {
m_tag_information->static_resp_count = 0;
}
}
static bool find_static_response(const uint8_t *apdu, uint16_t apdu_len,
uint8_t **resp_out, uint16_t *resp_len_out) {
/* Flash-backed table */
for (uint8_t i = 0; i < m_static_resp_count; i++) {
nfc_tag_14a_4_static_response_t *e = &m_static_resp[i];
if (apdu_len >= e->cmd_len &&
memcmp(apdu, e->cmd, e->cmd_len) == 0) {
*resp_out = e->resp;
*resp_len_out = e->resp_len;
return true;
}
}
/* RAM-only large response table */
for (uint8_t i = 0; i < m_large_resp_count; i++) {
nfc_tag_14a_4_large_response_t *e = &m_large_resp[i];
if (apdu_len >= e->cmd_len &&
memcmp(apdu, e->cmd, e->cmd_len) == 0) {
*resp_out = e->resp;
*resp_len_out = e->resp_len;
return true;
}
}
return false;
}
/* ------------------------------------------------------------------ */
/* TX helpers */
/* ------------------------------------------------------------------ */
static void send_iblock(const uint8_t *data, uint16_t len) {
uint8_t pcb = 0x02 | (m_block_num & 0x01);
if (m_cid_supported) pcb |= PCB_CID_FOLLOWING;
uint8_t off = 0;
m_tx_buf[off++] = pcb;
if (m_cid_supported) m_tx_buf[off++] = m_cid & 0x0F;
if (len > NFC_14A_4_MAX_APDU) len = NFC_14A_4_MAX_APDU;
memcpy(&m_tx_buf[off], data, len);
nfc_tag_14a_tx_bytes(m_tx_buf, off + len, true);
m_block_num ^= 1;
}
static void send_rack(void) {
uint8_t pcb = 0xA2 | (m_block_num & 0x01);
if (m_cid_supported) {
pcb |= PCB_CID_FOLLOWING;
uint8_t buf[2] = { pcb, m_cid & 0x0F };
nfc_tag_14a_tx_bytes(buf, 2, true);
} else {
nfc_tag_14a_tx_bytes(&pcb, 1, true);
}
}
static void send_wtx(void) {
uint8_t buf[3];
uint8_t off = 0;
buf[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_cid_supported) buf[off++] = m_cid & 0x0F;
buf[off++] = WTX_VALUE;
nfc_tag_14a_tx_bytes(buf, off, true);
}
/* ------------------------------------------------------------------ */
/* 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 (szBytes == 0) return;
uint8_t pcb = data[0];
/* ---- S-block ---- */
if (is_sblock(pcb)) {
if ((pcb & 0xF7) == PCB_SBLOCK_DESELECT) {
/* Echo DESELECT */
nfc_tag_14a_tx_bytes(data, szBytes, true);
nfc_tag_14a_4_reset_handler();
return;
}
if ((pcb & 0x3F) == (PCB_SBLOCK_WTX & 0x3F)) {
/* Reader sending WTX — echo back with our WTXM */
uint8_t wtxm = (szBytes > 1) ? data[szBytes - 1] & 0x3F : WTX_VALUE;
uint8_t resp[3];
uint8_t off = 0;
resp[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_cid_supported) resp[off++] = m_cid & 0x0F;
resp[off++] = wtxm;
nfc_tag_14a_tx_bytes(resp, off, true);
/* If we now have a response ready, send it next I-block */
if (m_response_ready) {
m_response_ready = false;
send_iblock(m_resp_buf, m_resp_len);
}
return;
}
return;
}
/* ---- R-block ---- */
if (is_rblock(pcb)) {
send_rack();
return;
}
/* ---- I-block ---- */
if (is_iblock(pcb)) {
uint8_t reader_blknum = pcb & PCB_BLOCK_NUM;
bool has_cid = (pcb & PCB_CID_FOLLOWING) != 0;
bool has_nad = (pcb & PCB_NAD_FOLLOWING) != 0;
bool more_chain = (pcb & PCB_CHAIN) != 0;
uint8_t offset = 1;
if (has_cid) {
/* CID acknowledged but not used in responses (keeps protocol simpler) */
m_cid_supported = false;
offset++; /* skip CID byte */
}
if (has_nad) offset++;
if (offset >= szBytes) {
send_rack();
return;
}
uint16_t apdu_len = szBytes - offset;
if (apdu_len > NFC_14A_4_MAX_APDU) apdu_len = NFC_14A_4_MAX_APDU;
m_dbg_iblocks_rx++;
m_dbg_last_rx_pcb = pcb;
NRF_LOG_INFO("14A4 I-block #%d: reader_blk=%d m_block_num=%d apdu_len=%d",
m_dbg_iblocks_rx, reader_blknum, m_block_num, apdu_len);
/* Block number check per ISO14443-4 §7.5.3.3:
* If block number matches expected, process new APDU.
* If block number does NOT match, it is a retransmit
* resend the last response without re-processing. */
if (reader_blknum != (m_block_num & 0x01)) {
/* Retransmit: resend last response */
if (m_resp_len > 0) {
/* Restore block num to what we sent last time and resend */
m_block_num ^= 1; /* undo the increment from last send */
send_iblock(m_resp_buf, m_resp_len);
} else {
send_rack();
}
return;
}
memcpy(m_apdu_buf, &data[offset], apdu_len);
m_apdu_len = apdu_len;
m_apdu_pending = true;
m_response_ready = false;
if (more_chain) {
send_rack();
return;
}
/* APDU complete — check static table first, then WTX */
{
uint8_t *static_resp = NULL;
uint16_t static_len = 0;
bool _found = find_static_response(m_apdu_buf, apdu_len,
&static_resp, &static_len);
m_dbg_last_match = _found ? 1 : 0;
NRF_LOG_INFO("14A4 find_static: found=%d static_len=%d resp_count=%d",
_found, static_len, m_static_resp_count);
if (_found) {
m_dbg_iblocks_tx++;
memcpy(m_resp_buf, static_resp, static_len);
m_resp_len = static_len;
send_iblock(m_resp_buf, m_resp_len);
} 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);
}
/* ------------------------------------------------------------------ */
/* APDU relay API (for host-driven responses) */
/* ------------------------------------------------------------------ */
bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length) {
if (!m_apdu_pending) return false;
m_apdu_pending = false;
*length = m_apdu_len;
memcpy(buf, m_apdu_buf, m_apdu_len);
return true;
}
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;
memcpy(m_resp_buf, data, length);
m_resp_len = length;
m_response_ready = true;
}
/* ------------------------------------------------------------------ */
/* Reset handler */
/* ------------------------------------------------------------------ */
void nfc_tag_14a_4_reset_handler(void) {
m_block_num = 0;
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,
uint8_t *last_pcb, uint8_t *last_match) {
*rx = m_dbg_iblocks_rx;
*tx = m_dbg_iblocks_tx;
*last_pcb = m_dbg_last_rx_pcb;
*last_match = m_dbg_last_match;
}
/* ------------------------------------------------------------------ */
/* Anti-collision resource */
/* ------------------------------------------------------------------ */
nfc_tag_14a_coll_res_reference_t *nfc_tag_14a_4_get_coll_res(void) {
if (m_tag_information == NULL) return NULL;
m_shadow_coll_res.sak = m_tag_information->res_coll.sak;
m_shadow_coll_res.atqa = m_tag_information->res_coll.atqa;
m_shadow_coll_res.uid = m_tag_information->res_coll.uid;
m_shadow_coll_res.size = &m_tag_information->res_coll.size;
m_shadow_coll_res.ats = &m_tag_information->res_coll.ats;
return &m_shadow_coll_res;
}
/* ------------------------------------------------------------------ */
/* Data load / save / factory callbacks */
/* ------------------------------------------------------------------ */
int nfc_tag_14a_4_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
int info_size = sizeof(nfc_tag_14a_4_information_t);
if (buffer->length < info_size) {
NRF_LOG_ERROR("14A-4 loadcb: buffer too small (%d < %d)",
buffer->length, info_size);
return info_size;
}
m_tag_information = (nfc_tag_14a_4_information_t *)buffer->buffer;
/* Populate RAM static table from persisted slot data */
m_static_resp_count = m_tag_information->static_resp_count;
if (m_static_resp_count > NFC_14A_4_MAX_STATIC_RESPONSES)
m_static_resp_count = NFC_14A_4_MAX_STATIC_RESPONSES;
memcpy(m_static_resp, m_tag_information->static_resp,
m_static_resp_count * sizeof(nfc_tag_14a_4_static_response_t));
nfc_tag_14a_handler_t handler = {
.get_coll_res = nfc_tag_14a_4_get_coll_res,
.cb_state = nfc_tag_14a_4_state_handler,
.cb_reset = nfc_tag_14a_4_reset_handler,
};
nfc_tag_14a_set_handler(&handler);
NRF_LOG_INFO("14A-4 loadcb OK: SAK=%02x uid_sz=%d static_resp=%d",
m_tag_information->res_coll.sak[0],
m_tag_information->res_coll.size,
m_static_resp_count);
return info_size;
}
int nfc_tag_14a_4_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
return sizeof(nfc_tag_14a_4_information_t);
}
bool nfc_tag_14a_4_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
if (tag_type != TAG_TYPE_HF14A_4) return false;
/* Build factory defaults on stack and write directly to FDS
* (same pattern as nfc_tag_mf1_data_factory). */
nfc_tag_14a_4_information_t info;
memset(&info, 0, sizeof(info));
/* Placeholder 7-byte NXP-style UID */
info.res_coll.size = NFC_TAG_14A_UID_DOUBLE_SIZE;
info.res_coll.atqa[0] = 0x04;
info.res_coll.atqa[1] = 0x00;
info.res_coll.sak[0] = 0x20; /* ISO14443-4 */
info.res_coll.uid[0] = 0x04;
info.res_coll.uid[1] = 0x01;
info.res_coll.uid[2] = 0x02;
info.res_coll.uid[3] = 0x03;
info.res_coll.uid[4] = 0x04;
info.res_coll.uid[5] = 0x05;
info.res_coll.uid[6] = 0x06;
static const uint8_t default_ats[] = {
0x10, 0x78, 0x80, 0x70, 0x02, 0x00,
0x31, 0xC1, 0x64, 0x09, 0x97, 0x61,
0x26, 0x00, 0x90, 0x00
};
info.res_coll.ats.length = sizeof(default_ats);
memcpy(info.res_coll.ats.data, default_ats, sizeof(default_ats));
info.static_resp_count = 0;
fds_slot_record_map_t map_info;
get_fds_map_by_slot_sense_type_for_dump(slot, TAG_SENSE_HF, &map_info);
bool ret = fds_write_sync(map_info.id, map_info.key, sizeof(info), &info);
NRF_LOG_INFO("14A-4 factory slot %d: %s", slot, ret ? "OK" : "FAIL");
return ret;
}
@@ -0,0 +1,73 @@
/**
* @file nfc_14a_4.h
* @brief ISO14443-4 T=CL emulation for ChameleonUltra
*
* Implements a full ISO14443-4 tag emulator:
* - I-blocks (information, chaining, CID)
* - R-blocks (ACK/NAK retransmit)
* - S-blocks (WTX to keep reader alive, DESELECT)
* - Static APDU response table (pre-loaded before field, no USB needed
* during field exchange)
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef NFC_14A_4_H
#define NFC_14A_4_H
#include "nfc_14a.h"
#include "tag_emulation.h"
/* Maximum APDU size (FSCI=8 → FSC=256, minus PCB+CRC = 253) */
#define NFC_14A_4_MAX_APDU 260 /* max APDU in RAM; flash entries capped at 253 */
/* Static APDU response table — up to 12 pre-configured command/response pairs.
* Loaded before field activation; firmware responds autonomously without USB. */
#define NFC_14A_4_MAX_STATIC_RESPONSES 12
#define NFC_14A_4_MAX_LARGE_RESPONSES 4 /* RAM-only, for resp > 253 bytes */
#define NFC_14A_4_MAX_LARGE_RESP_LEN 260 /* max large response size */
#define NFC_14A_4_MAX_STATIC_CMD_LEN 16
#define NFC_14A_4_MAX_STATIC_RESP_LEN 253 /* max bytes in flash-backed slot */
typedef struct __attribute__((packed)) {
uint8_t cmd_len;
uint8_t cmd[NFC_14A_4_MAX_STATIC_CMD_LEN];
uint8_t resp_len;
uint8_t resp[NFC_14A_4_MAX_STATIC_RESP_LEN];
}
nfc_tag_14a_4_static_response_t;
/**
* Per-slot persistent data layout stored in FDS flash.
* Anti-collision response (UID/ATQA/SAK/ATS) plus the static response table.
*/
typedef struct __attribute__((packed)) {
nfc_tag_14a_coll_res_entity_t res_coll;
uint8_t static_resp_count;
nfc_tag_14a_4_static_response_t static_resp[NFC_14A_4_MAX_STATIC_RESPONSES];
}
nfc_tag_14a_4_information_t;
/* 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);
/* 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_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool nfc_tag_14a_4_data_factory(uint8_t slot, tag_specific_type_t tag_type);
/* Static response table management (called before hw mode -e) */
void nfc_tag_14a_4_add_static_response(const uint8_t *cmd, uint8_t cmd_len,
const uint8_t *resp, uint16_t resp_len);
void nfc_tag_14a_4_clear_static_responses(void);
/* APDU relay — host-driven responses */
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);
/* Reset handler */
void nfc_tag_14a_4_reset_handler(void);
#endif /* NFC_14A_4_H */
void nfc_tag_14a_4_get_debug_counters(uint8_t *rx, uint8_t *tx, uint8_t *last_pcb, uint8_t *last_match);
@@ -696,7 +696,7 @@ static bool check_ro_lock_on_page(int block_num) {
//the BL bit only freezes the lock bytes !
return (m_tag_information->memory[2][2] & 8) != 0;
default:
return (m_tag_information->memory[2][2] & 9) != 0;
return (m_tag_information->memory[2][2] & 9) != 0;
}
// bits 0 and 3
} else if (block_num <= MF0ICU1_PAGES) {
@@ -805,26 +805,25 @@ static bool check_ro_lock_on_page(int block_num) {
// the lock configuration. We only check the actual lock bits (L0-L15) in bytes 0-1.
return locked_small_range;
default:
return locked_small_range | locked_large_range;
return locked_small_range | locked_large_range;
}
} else {
//Check the block locking bits to see if we can touch the dynamic locks bytes for NTAG tags
if(block_num == user_memory_end)
{
if (block_num == user_memory_end) {
switch (m_tag_type) {
case TAG_TYPE_NTAG_213:
case TAG_TYPE_NTAG_215:
case TAG_TYPE_NTAG_216: {
uint8_t block_bytes = m_tag_information->memory[user_memory_end][2];
uint16_t block_world = 0;
// Each bit in block_bytes maps to 2 bits in block_world
for (int i = 0; i < 8; i++) {
if (block_bytes & (0x01 << i)) {
block_world |= (0x0003 << (i * 2));
}
}
p_lock_bytes = m_tag_information->memory[user_memory_end];
uint16_t lock_word = (((uint16_t)p_lock_bytes[1]) << 8) | (uint16_t)p_lock_bytes[0];
return (lock_word & block_world) != 0;
@@ -864,7 +863,7 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) {
default:
out_of_bounds = block_num >= block_max;
break;
}
}
// Reject out-of-bounds writes (except config pages)
if (out_of_bounds) {
NRF_LOG_ERROR("Write failed: block_num %08x >= block_max %08x", block_num, block_max);
@@ -1007,10 +1006,10 @@ static void handle_pwd_auth_command(uint8_t *p_data) {
if (m_tag_information->config.detection_enable && m_auth_log.count < MF0_NTAG_AUTH_LOG_MAX) {
memcpy(m_auth_log.logs[m_auth_log.count].pwd, &p_data[1], 4);
m_auth_log.count++;
NRF_LOG_INFO("NTAG password: %02x%02x%02x%02x",
NRF_LOG_INFO("NTAG password: %02x%02x%02x%02x",
p_data[1], p_data[2], p_data[3], p_data[4]);
}
if (pwd != supplied_pwd) {
if (auth_lim) {
cnt_data[MF0_NTAG_AUTHLIM_OFF_IN_CTR] &= ~MF0_NTAG_AUTHLIM_MASK_IN_CTR;
File diff suppressed because it is too large Load Diff
@@ -73,13 +73,15 @@ typedef struct {
uint8_t mode_gen2_magic: 1;
/**
* Should the NFC peripheral be reset after losing the RF field?
* This configuration can fix the issue where some card readers cause the CU to enter a strange state of no response/incorrect response.
* This configuration can fix the issue where some card readers cause the CU to enter a strange state of no response/incorrect response.
* Once in this state, the device must be restarted to resolve the issue.
* Alternatively, enabling this configuration for resetting the NFC after leaving the rf field can also solve the aforementioned problem.
*/
uint8_t field_off_do_reset: 1;
// PRNG type: 0=static 1=weak/LFSR(default) 2=hard/rand
uint8_t prng_type: 2;
// reserved
uint8_t reserved1: 3;
uint8_t reserved1: 1;
uint8_t reserved2;
uint8_t reserved3;
} nfc_tag_mf1_configure_t;
@@ -147,6 +149,7 @@ typedef struct {
nfc_tag_mf1_auth_log_t *mf1_get_auth_log(uint32_t *count);
void nfc_tag_mf1_reset_handler();
int nfc_tag_mf1_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
int nfc_tag_mf1_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool nfc_tag_mf1_data_factory(uint8_t slot, tag_specific_type_t tag_type);
@@ -166,5 +169,8 @@ void nfc_tag_mf1_set_write_mode(nfc_tag_mf1_write_mode_t write_mode);
nfc_tag_mf1_write_mode_t nfc_tag_mf1_get_write_mode(void);
void nfc_tag_mf1_set_field_off_do_reset(bool enable);
bool nfc_tag_mf1_is_field_off_do_reset(void);
void nfc_tag_mf1_prng_seed(uint32_t seed); // seed MFC LFSR PRNG from hardware RNG
void nfc_tag_mf1_set_prng_type(uint8_t type); // 0=static 1=weak(LFSR) 2=hard(rand)
uint8_t nfc_tag_mf1_get_prng_type(void);
#endif
@@ -5,10 +5,15 @@
#include "bsp_delay.h"
#include "fds_util.h"
#include "nrf_gpio.h"
#include "nrf_soc.h"
#include "nrfx_lpcomp.h"
#include "nrfx_pwm.h"
#include "protocols/em410x.h"
#include "protocols/hidprox.h"
#include "protocols/idteck.h"
#include "protocols/ioprox.h"
#include "protocols/jablotron.h"
#include "protocols/pac.h"
#include "protocols/viking.h"
#include "syssleep.h"
#include "tag_emulation.h"
@@ -21,7 +26,6 @@
NRF_LOG_MODULE_REGISTER();
#define ANT_NO_MOD() nrf_gpio_pin_clear(LF_MOD)
#define LF_125KHZ_BROADCAST_MAX (10)
// Whether the USB light effect is allowed to enable
extern bool g_usb_led_marquee_enable;
@@ -40,7 +44,8 @@ static void lf_field_lost(void) {
g_is_tag_emulating = false; // Reset the flag in the emulation
m_is_lf_emulating = false;
TAG_FIELD_LED_OFF() // Make sure the indicator light of the LF field status
NRF_LPCOMP->INTENSET = LPCOMP_INTENCLR_CROSS_Msk | LPCOMP_INTENCLR_UP_Msk | LPCOMP_INTENCLR_DOWN_Msk | LPCOMP_INTENCLR_READY_Msk;
// Re-arm LPCOMP so the next field appearance triggers lpcomp_event_handler.
NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk;
// call sleep_timer_start *after* unsetting g_is_tag_emulating
sleep_timer_start(SLEEP_DELAY_MS_FIELD_125KHZ_LOST); // Start the timer to enter the sleep
NRF_LOG_INFO("LF FIELD LOST");
@@ -65,12 +70,15 @@ bool is_lf_field_exists(void) {
* priority is set to APP_IRQ_PRIORITY_HIGH).
*/
static void lpcomp_event_handler(nrf_lpcomp_event_t event) {
// Only when the lf -frequency emulation is not launched, and the analog card is started
// Only when the lf-frequency emulation is not launched, and the analog card is started
if (m_is_lf_emulating || event != NRF_LPCOMP_EVENT_UP) {
return;
}
sleep_timer_stop(); // turn off dormant delay
// Disable LPCOMP during emulation — LF_RSSI fluctuates during load
// modulation and would trigger spurious DOWN events with DETECT_CROSS.
// Field-loss is checked periodically via EVT_END_SEQ0 in pwm_handler.
nrfx_lpcomp_disable();
// set the emulation status logo bit
@@ -83,8 +91,11 @@ static void lpcomp_event_handler(nrf_lpcomp_event_t event) {
set_slot_light_color(RGB_BLUE);
TAG_FIELD_LED_ON()
// use precise hardware timer to broadcast card id
nrfx_pwm_simple_playback(&m_broadcast, m_pwm_seq, LF_125KHZ_BROADCAST_MAX, NRFX_PWM_FLAG_STOP);
// Play a finite burst then stop — field check happens in EVT_STOPPED after
// PWM has fully released LF_MOD, so ANT_NO_MOD() and the settle delay are
// effective. NRFX_PWM_FLAG_LOOP kept the pin owned by the peripheral,
// making the field check always read "present" due to self-drive on LF_RSSI.
nrfx_pwm_simple_playback(&m_broadcast, m_pwm_seq, 10, NRFX_PWM_FLAG_STOP);
NRF_LOG_INFO("LF FIELD DETECTED");
}
@@ -104,16 +115,15 @@ static void pwm_handler(nrfx_pwm_evt_type_t event_type) {
if (event_type != NRFX_PWM_EVT_STOPPED) {
return;
}
// after last broadcast, force NO_MOD on antenna to measure field.
// PWM has fully stopped — LF_MOD is released back to GPIO.
// Now ANT_NO_MOD() and the settle delay are effective.
ANT_NO_MOD();
bsp_delay_ms(1);
// We don't need any events, but only need to detect the state of the field
NRF_LPCOMP->INTENCLR = LPCOMP_INTENCLR_CROSS_Msk | LPCOMP_INTENCLR_UP_Msk | LPCOMP_INTENCLR_DOWN_Msk | LPCOMP_INTENCLR_READY_Msk;
bsp_delay_ms(2); // let peak detector drain: ~2 ms time constant on LF_RSSI
if (is_lf_field_exists()) {
nrfx_lpcomp_disable();
nrfx_pwm_simple_playback(&m_broadcast, m_pwm_seq, LF_125KHZ_BROADCAST_MAX, NRFX_PWM_FLAG_STOP);
// Field still present — play another finite burst then check again.
nrfx_pwm_simple_playback(&m_broadcast, m_pwm_seq, 10, NRFX_PWM_FLAG_STOP);
} else {
// Field gone — clean up.
lf_field_lost();
}
}
@@ -125,7 +135,13 @@ static void pwm_init(void) {
cfg.output_pins[i] = NRFX_PWM_PIN_NOT_USED;
}
cfg.irq_priority = APP_IRQ_PRIORITY_LOW;
cfg.base_clock = NRF_PWM_CLK_125kHz;
// Base clock depends on the currently-loaded tag type. Legacy ASK/FSK
// protocols (EM410x, HID, ioProx, Viking, PAC) use 125kHz base so that
// their hardcoded counter_top values (8-64 range) produce the correct
// absolute timing. PSK1 protocols need finer resolution for the 16us
// subcarrier period, so pwm_init uses 1MHz base with counter_top=16.
// See tag_base_type.h IS_PSK1_TYPE for the list of qualifying types.
cfg.base_clock = IS_PSK1_TYPE(m_tag_type) ? NRF_PWM_CLK_1MHz : NRF_PWM_CLK_125kHz;
cfg.count_mode = NRF_PWM_MODE_UP;
cfg.load_mode = NRF_PWM_LOAD_WAVE_FORM;
cfg.step_mode = NRF_PWM_STEP_AUTO;
@@ -135,6 +151,27 @@ static void pwm_init(void) {
}
static void lf_sense_enable(void) {
// PWM bit timing divides HFCLK by a fixed ratio. On HFINT (64 MHz RC,
// ±1.5% at 25°C after factory trim, wider over temperature) this gives a
// chip-to-chip spread that NRZ readers — which see cumulative error across
// runs of same-polarity bits with no intra-run resync — reject even when
// Manchester/FSK readers don't. Holding HFXO brings the PWM clock to
// ±40 ppm, which is also tight enough for differential PSK encodings
// (e.g. IDTECK) where what the reader decodes are bit-to-bit phase
// transitions, so absolute phase lock to the reader's carrier is not
// required. The tag-mode antenna taps on this board are envelope-only,
// which rules out coherent demodulation or phase-lock-based approaches,
// but does not preclude the differential-phase encodings supported here.
//
// Paired release in lf_sense_disable(). SD reference-counts HFXO requests,
// so this coexists with BLE. Both functions run from thread context
// (tag_mode_enter/tag_emulation_sense_end) where SVCs are safe.
sd_clock_hfclk_request();
uint32_t hfclk_running = 0;
while (!hfclk_running) {
sd_clock_hfclk_is_running(&hfclk_running);
}
lpcomp_init();
pwm_init(); // use precise hardware pwm to broadcast card id
if (is_lf_field_exists()) {
@@ -147,6 +184,7 @@ static void lf_sense_disable(void) {
nrfx_lpcomp_uninit();
m_pwm_seq = NULL;
m_is_lf_emulating = false;
sd_clock_hfclk_release();
}
static enum {
@@ -205,6 +243,15 @@ int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
return LF_HIDPROX_TAG_ID_SIZE;
}
if (type == TAG_TYPE_IOPROX && buffer->length >= LF_IOPROX_TAG_ID_SIZE) {
m_tag_type = type;
void *codec = ioprox.alloc();
m_pwm_seq = ioprox.modulator(codec, buffer->buffer);
ioprox.free(codec);
NRF_LOG_INFO("load lf ioprox data finish.");
return LF_IOPROX_TAG_ID_SIZE;
}
if (type == TAG_TYPE_VIKING && buffer->length >= LF_VIKING_TAG_ID_SIZE) {
m_tag_type = type;
void *codec = viking.alloc();
@@ -214,6 +261,33 @@ int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
return LF_VIKING_TAG_ID_SIZE;
}
if (type == TAG_TYPE_PAC && buffer->length >= LF_PAC_TAG_ID_SIZE) {
m_tag_type = type;
void *codec = pac.alloc();
m_pwm_seq = pac.modulator(codec, buffer->buffer);
pac.free(codec);
NRF_LOG_INFO("load lf pac data finish.");
return LF_PAC_TAG_ID_SIZE;
}
if (type == TAG_TYPE_JABLOTRON && buffer->length >= LF_JABLOTRON_TAG_ID_SIZE) {
m_tag_type = type;
void *codec = jablotron.alloc();
m_pwm_seq = jablotron.modulator(codec, buffer->buffer);
jablotron.free(codec);
NRF_LOG_INFO("load lf jablotron data finish.");
return LF_JABLOTRON_TAG_ID_SIZE;
}
if (type == TAG_TYPE_IDTECK && buffer->length >= LF_IDTECK_TAG_ID_SIZE) {
m_tag_type = type;
void *codec = idteck.alloc();
m_pwm_seq = idteck.modulator(codec, buffer->buffer);
idteck.free(codec);
NRF_LOG_INFO("load lf idteck data finish.");
return LF_IDTECK_TAG_ID_SIZE;
}
NRF_LOG_ERROR("no valid data exists in buffer for tag type: %d.", type);
return 0;
}
@@ -246,6 +320,17 @@ int lf_tag_hidprox_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buff
return m_tag_type == TAG_TYPE_HID_PROX ? LF_HIDPROX_TAG_ID_SIZE : 0;
}
/** @brief Id card deposit card number before callback
* @param type Refined tag type
* @param buffer Data buffer
* @return The length of the data that needs to be saved is that it does not save when 0
*/
int lf_tag_ioprox_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
// Make sure to load this tag before allowing saving
// Just save the original card package directly
return m_tag_type == TAG_TYPE_IOPROX ? LF_IOPROX_TAG_ID_SIZE : 0;
}
/** @brief Id card deposit card number before callback
* @param type Refined tag type
* @param buffer Data buffer
@@ -280,7 +365,8 @@ bool lf_tag_data_factory(uint8_t slot, tag_specific_type_t tag_type, uint8_t *ta
bool lf_tag_em410x_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
static const uint8_t tag_id_base[LF_EM410X_TAG_ID_SIZE] = {0xDE, 0xAD, 0xBE, 0xEF, 0x88};
static const uint8_t tag_id_electra[LF_EM410X_ELECTRA_TAG_ID_SIZE] = {0xDE, 0xAD, 0xBE, 0xEF, 0x88,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
switch (tag_type) {
case TAG_TYPE_EM410X_ELECTRA:
@@ -303,6 +389,18 @@ bool lf_tag_hidprox_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
}
/** @brief Id card deposit card number before callback
* @param slot Card slot number
* @param tag_type Refined tag type
* @return Whether the format is successful, if the formatting is successful, it will return to True, otherwise False will be returned
*/
bool lf_tag_ioprox_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
uint8_t tag_id[16] = {
0x01, 0xAA, 0x30, 0x39, 0x00, 0x78, 0x6A, 0xA0, 0x33, 0x09, 0xCF, 0xEF, 0x00, 0x00, 0x00, 0x00
};
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
}
/** @brief Id card deposit card number before callback
* @param slot Card slot number
* @param tag_type Refined tag type
@@ -313,3 +411,37 @@ bool lf_tag_viking_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
uint8_t tag_id[4] = {0xDE, 0xAD, 0xBE, 0xEF};
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
}
int lf_tag_pac_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
return m_tag_type == TAG_TYPE_PAC ? LF_PAC_TAG_ID_SIZE : 0;
}
bool lf_tag_pac_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
// default id: 8 ASCII bytes
uint8_t tag_id[8] = {'C', 'A', 'R', 'D', '0', '0', '0', '1'};
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
}
int lf_tag_jablotron_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
return m_tag_type == TAG_TYPE_JABLOTRON ? LF_JABLOTRON_TAG_ID_SIZE : 0;
}
bool lf_tag_jablotron_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
// default id: 5 bytes (top bit must be 0)
uint8_t tag_id[5] = {0x01, 0xB6, 0x69, 0x00, 0x00};
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
}
/** @brief IDTECK data save callback. */
int lf_tag_idteck_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
return m_tag_type == TAG_TYPE_IDTECK ? LF_IDTECK_TAG_ID_SIZE : 0;
}
/** @brief IDTECK default frame: preamble "IDTK" + 32-bit placeholder card data. */
bool lf_tag_idteck_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
uint8_t tag_id[LF_IDTECK_TAG_ID_SIZE] = {
0x49, 0x44, 0x54, 0x4B, // "IDTK" preamble (MSB first)
0xDE, 0xAD, 0xBE, 0xEF, // default card data
};
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
}
@@ -7,8 +7,12 @@
#define LF_EM410X_TAG_ID_SIZE 5
#define LF_EM410X_ELECTRA_TAG_ID_SIZE 13
#define LF_IOPROX_TAG_ID_SIZE 16
#define LF_HIDPROX_TAG_ID_SIZE 13
#define LF_VIKING_TAG_ID_SIZE 4
#define LF_PAC_TAG_ID_SIZE 8
#define LF_JABLOTRON_TAG_ID_SIZE 5
#define LF_IDTECK_TAG_ID_SIZE 8
void lf_tag_125khz_sense_switch(bool enable);
int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
@@ -16,6 +20,14 @@ int lf_tag_em410x_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffe
bool lf_tag_em410x_data_factory(uint8_t slot, tag_specific_type_t tag_type);
int lf_tag_hidprox_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool lf_tag_hidprox_data_factory(uint8_t slot, tag_specific_type_t tag_type);
int lf_tag_ioprox_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool lf_tag_ioprox_data_factory(uint8_t slot, tag_specific_type_t tag_type);
int lf_tag_viking_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool lf_tag_viking_data_factory(uint8_t slot, tag_specific_type_t tag_type);
int lf_tag_pac_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool lf_tag_pac_data_factory(uint8_t slot, tag_specific_type_t tag_type);
int lf_tag_jablotron_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool lf_tag_jablotron_data_factory(uint8_t slot, tag_specific_type_t tag_type);
int lf_tag_idteck_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool lf_tag_idteck_data_factory(uint8_t slot, tag_specific_type_t tag_type);
bool is_lf_field_exists(void);
@@ -358,10 +358,10 @@ const protocol em410x_electra = {
.get_data = (codec_get_data)em410x_get_data,
.modulator = (modulator)em410x_electra_modulator,
.decoder =
{
.start = (decoder_start)em410x_electra_decoder_start,
.feed = (decoder_feed)em410x_electra_decoder_feed,
},
{
.start = (decoder_start)em410x_electra_decoder_start,
.feed = (decoder_feed)em410x_electra_decoder_feed,
},
};
// EM-Micro, EM410x/64 (std)
@@ -373,10 +373,10 @@ const protocol em410x_64 = {
.get_data = (codec_get_data)em410x_get_data,
.modulator = (modulator)em410x_modulator,
.decoder =
{
.start = (decoder_start)em410x_decoder_start,
.feed = (decoder_feed)em410x_decoder_feed,
},
{
.start = (decoder_start)em410x_decoder_start,
.feed = (decoder_feed)em410x_decoder_feed,
},
};
// EM-Micro, EM410x/32
@@ -388,10 +388,10 @@ const protocol em410x_32 = {
.get_data = (codec_get_data)em410x_get_data,
.modulator = (modulator)em410x_modulator,
.decoder =
{
.start = (decoder_start)em410x_decoder_start,
.feed = (decoder_feed)em410x_decoder_feed,
},
{
.start = (decoder_start)em410x_decoder_start,
.feed = (decoder_feed)em410x_decoder_feed,
},
};
// EM-Micro, EM410x/16
@@ -403,10 +403,10 @@ const protocol em410x_16 = {
.get_data = (codec_get_data)em410x_get_data,
.modulator = (modulator)em410x_modulator,
.decoder =
{
.start = (decoder_start)em410x_decoder_start,
.feed = (decoder_feed)em410x_decoder_feed,
},
{
.start = (decoder_start)em410x_decoder_start,
.feed = (decoder_feed)em410x_decoder_feed,
},
};
// Encode EM410X card number to T55xx blocks.

Some files were not shown because too many files have changed in this diff Show More