Author SHA1 Message Date
ambroserrg fc8cfb4e63 Update documentation for hardware upgrade
Added notices to README.md and CHANGELOG.md about the hardware upgrade
that allows restarting the Ultra by running each RGB color once,
resolving previous firmware modification issues.
2026-03-10 10:29:12 +08:00
84 changed files with 514 additions and 10208 deletions
-5
View File
@@ -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
View File
@@ -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)
+5 -3
View File
@@ -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)
+1 -13
View File
@@ -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/ \
File diff suppressed because it is too large Load Diff
+18 -34
View File
@@ -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
-1
View File
@@ -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
+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_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;
}
}
-37
View File
@@ -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
+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,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