mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-09-11 18:30:14 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc8cfb4e63 |
@@ -708,8 +708,3 @@ 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
|
||||
|
||||
+1
-9
@@ -3,14 +3,7 @@ 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]
|
||||
|
||||
## [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)
|
||||
- Hardware upgrade: Restarting the Ultra now only requires running each of the three RGB colors once, resolving previous firmware modification issues
|
||||
- Added commands to dump and clone Mifare tags
|
||||
- Fix bad missing tools warning (@suut)
|
||||
- Fix for FAST_READ command for nfc - mf0 tags
|
||||
@@ -26,7 +19,6 @@ 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)
|
||||
|
||||
@@ -18,6 +18,10 @@ 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).
|
||||
@@ -26,8 +30,6 @@ 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
|
||||
|
||||
@@ -47,4 +49,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)
|
||||
@@ -28,23 +28,16 @@ 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 \
|
||||
@@ -347,17 +340,12 @@ 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_jablotron_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_reader_generic.c \
|
||||
|
||||
INC_FOLDERS +=\
|
||||
${PROJ_DIR}/rfid/reader/ \
|
||||
|
||||
+12
-1391
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,6 @@
|
||||
#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"
|
||||
@@ -62,7 +61,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;
|
||||
@@ -137,11 +136,6 @@ 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
|
||||
@@ -156,27 +150,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
|
||||
@@ -204,9 +198,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) {
|
||||
@@ -673,11 +667,6 @@ 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);
|
||||
@@ -700,11 +689,6 @@ 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();
|
||||
@@ -859,16 +843,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();
|
||||
@@ -884,7 +868,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) {
|
||||
@@ -892,7 +876,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();
|
||||
|
||||
@@ -1040,17 +1024,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
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#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
|
||||
|
||||
@@ -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_COUNT][ADC_BUF_SIZE];
|
||||
static nrf_saadc_value_t adc_buf[ADC_BUF_SIZE][ADC_BUF_COUNT];
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,6 @@
|
||||
#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)
|
||||
|
||||
//
|
||||
// ******************************************************************
|
||||
@@ -69,8 +67,6 @@
|
||||
#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)
|
||||
@@ -82,7 +78,6 @@
|
||||
|
||||
#define DATA_CMD_HF14A_GET_CONFIG (2200)
|
||||
#define DATA_CMD_HF14A_SET_CONFIG (2201)
|
||||
#define DATA_CMD_HF14A_SNIFF (2020)
|
||||
|
||||
//
|
||||
// ******************************************************************
|
||||
@@ -98,21 +93,11 @@
|
||||
#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)
|
||||
|
||||
//
|
||||
// ******************************************************************
|
||||
@@ -163,8 +148,6 @@
|
||||
#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)
|
||||
//
|
||||
// ******************************************************************
|
||||
|
||||
@@ -177,31 +160,11 @@
|
||||
|
||||
//
|
||||
// ******************************************************************
|
||||
/* 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
|
||||
|
||||
@@ -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,36 +59,6 @@ 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 };
|
||||
@@ -356,11 +326,6 @@ 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) {
|
||||
@@ -377,11 +342,6 @@ 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();
|
||||
@@ -390,11 +350,9 @@ 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;
|
||||
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]);
|
||||
}
|
||||
// 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.");
|
||||
@@ -510,9 +468,7 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
|
||||
}
|
||||
// Incoming SELECT ALL for any cascade level
|
||||
if (szDataBits == 16 && p_data[1] == 0x20) {
|
||||
if (!m_sniff_passive) {
|
||||
nfc_tag_14a_tx_bytes(uid, 5, false);
|
||||
}
|
||||
nfc_tag_14a_tx_bytes(uid, 5, false);
|
||||
// NRF_LOG_INFO("[MFEMUL_SELECT] SEL Reply.");
|
||||
break;
|
||||
}
|
||||
@@ -526,14 +482,10 @@ 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;
|
||||
if (!m_sniff_passive) {
|
||||
nfc_tag_14a_tx_bytes(auto_coll_res->sak, 1, true);
|
||||
}
|
||||
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
|
||||
if (!m_sniff_passive) {
|
||||
nfc_tag_14a_tx_bytes(m_uid_incomplete_sak, 3, false);
|
||||
}
|
||||
nfc_tag_14a_tx_bytes(m_uid_incomplete_sak, 3, false);
|
||||
}
|
||||
} else {
|
||||
// IDLE, not our UID
|
||||
@@ -559,10 +511,6 @@ 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
|
||||
@@ -581,16 +529,8 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -615,20 +555,21 @@ 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);
|
||||
|
||||
/* 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);
|
||||
/* 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 */
|
||||
|
||||
// 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) {
|
||||
@@ -681,7 +622,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();
|
||||
}
|
||||
@@ -691,19 +632,7 @@ 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");
|
||||
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);
|
||||
}
|
||||
}
|
||||
// NRF_LOG_INFO("TX config is %d.\n", nrf_nfct_tx_frame_config_get(NRF_NFCT));
|
||||
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 512 /* must hold PCB + max APDU response */
|
||||
#define MAX_NFC_TX_BUFFER_SIZE 64
|
||||
|
||||
#define NFC_TAG_14A_CRC_LENGTH 2
|
||||
|
||||
@@ -36,11 +36,10 @@
|
||||
|
||||
// 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_PROPRIETARY, // Card is in proprietary state; all commands handled only by state_handler
|
||||
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_14a_state_t;
|
||||
|
||||
// UID of the length in the enumeration specification
|
||||
@@ -83,27 +82,6 @@ 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 (card→reader direction). Same signature as the RX sniff callback.
|
||||
* Install alongside nfc_tag_14a_set_sniff_cb() to capture both directions. */
|
||||
typedef void (*nfc_tag_14a_tx_sniff_cb_t)(const uint8_t *data, uint16_t szBits);
|
||||
|
||||
void nfc_tag_14a_set_tx_sniff_cb(nfc_tag_14a_tx_sniff_cb_t cb);
|
||||
void nfc_tag_14a_clear_tx_sniff_cb(void);
|
||||
|
||||
/* Passive sniff mode: when true, suppresses all CU anticollision responses
|
||||
* (ATQA, UID, SAK) so the CU does not collide with real cards in the field.
|
||||
* Enable before starting a sniff session, disable on completion. */
|
||||
void nfc_tag_14a_set_sniff_passive(bool passive);
|
||||
typedef void (*nfc_tag_14a_state_handler_t)(uint8_t *data, uint16_t szBits);
|
||||
typedef nfc_tag_14a_coll_res_reference_t *(*nfc_tag_14a_coll_handler_t)(void);
|
||||
|
||||
|
||||
@@ -1,446 +0,0 @@
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* @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,25 +805,26 @@ 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;
|
||||
@@ -863,7 +864,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);
|
||||
@@ -1006,10 +1007,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,15 +73,13 @@ 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: 1;
|
||||
uint8_t reserved1: 3;
|
||||
uint8_t reserved2;
|
||||
uint8_t reserved3;
|
||||
} nfc_tag_mf1_configure_t;
|
||||
@@ -149,7 +147,6 @@ 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);
|
||||
@@ -169,8 +166,5 @@ 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,15 +5,10 @@
|
||||
#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"
|
||||
@@ -26,6 +21,7 @@
|
||||
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;
|
||||
@@ -44,8 +40,7 @@ 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
|
||||
// Re-arm LPCOMP so the next field appearance triggers lpcomp_event_handler.
|
||||
NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk;
|
||||
NRF_LPCOMP->INTENSET = LPCOMP_INTENCLR_CROSS_Msk | LPCOMP_INTENCLR_UP_Msk | LPCOMP_INTENCLR_DOWN_Msk | LPCOMP_INTENCLR_READY_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");
|
||||
@@ -70,15 +65,12 @@ 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
|
||||
@@ -91,11 +83,8 @@ static void lpcomp_event_handler(nrf_lpcomp_event_t event) {
|
||||
set_slot_light_color(RGB_BLUE);
|
||||
TAG_FIELD_LED_ON()
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
|
||||
NRF_LOG_INFO("LF FIELD DETECTED");
|
||||
}
|
||||
@@ -115,15 +104,16 @@ static void pwm_handler(nrfx_pwm_evt_type_t event_type) {
|
||||
if (event_type != NRFX_PWM_EVT_STOPPED) {
|
||||
return;
|
||||
}
|
||||
// PWM has fully stopped — LF_MOD is released back to GPIO.
|
||||
// Now ANT_NO_MOD() and the settle delay are effective.
|
||||
|
||||
// after last broadcast, force NO_MOD on antenna to measure field.
|
||||
ANT_NO_MOD();
|
||||
bsp_delay_ms(2); // let peak detector drain: ~2 ms time constant on LF_RSSI
|
||||
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;
|
||||
if (is_lf_field_exists()) {
|
||||
// Field still present — play another finite burst then check again.
|
||||
nrfx_pwm_simple_playback(&m_broadcast, m_pwm_seq, 10, NRFX_PWM_FLAG_STOP);
|
||||
nrfx_lpcomp_disable();
|
||||
nrfx_pwm_simple_playback(&m_broadcast, m_pwm_seq, LF_125KHZ_BROADCAST_MAX, NRFX_PWM_FLAG_STOP);
|
||||
} else {
|
||||
// Field gone — clean up.
|
||||
lf_field_lost();
|
||||
}
|
||||
}
|
||||
@@ -135,13 +125,7 @@ static void pwm_init(void) {
|
||||
cfg.output_pins[i] = NRFX_PWM_PIN_NOT_USED;
|
||||
}
|
||||
cfg.irq_priority = APP_IRQ_PRIORITY_LOW;
|
||||
// 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.base_clock = 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;
|
||||
@@ -151,27 +135,6 @@ 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()) {
|
||||
@@ -184,7 +147,6 @@ static void lf_sense_disable(void) {
|
||||
nrfx_lpcomp_uninit();
|
||||
m_pwm_seq = NULL;
|
||||
m_is_lf_emulating = false;
|
||||
sd_clock_hfclk_release();
|
||||
}
|
||||
|
||||
static enum {
|
||||
@@ -243,15 +205,6 @@ 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();
|
||||
@@ -261,33 +214,6 @@ 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;
|
||||
}
|
||||
@@ -320,17 +246,6 @@ 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
|
||||
@@ -365,8 +280,7 @@ 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:
|
||||
@@ -389,18 +303,6 @@ 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
|
||||
@@ -411,37 +313,3 @@ 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,12 +7,8 @@
|
||||
|
||||
#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);
|
||||
@@ -20,14 +16,6 @@ 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
Reference in New Issue
Block a user