mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -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
|
# End of https://www.toptal.com/developers/gitignore/api/visualstudio,c++,c,python,visualstudiocode,macos,windows
|
||||||
software/script/tests/nonces.bin
|
software/script/tests/nonces.bin
|
||||||
software/script/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
-4
@@ -3,10 +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...
|
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]
|
## [unreleased][unreleased]
|
||||||
- Added PAC/Stanley LF protocol support: read, emulate and T55xx clone (@kevihiiin, @danieltwagner)
|
- Hardware upgrade: Restarting the Ultra now only requires running each of the three RGB colors once, resolving previous firmware modification issues
|
||||||
- Fix firmware application USB serial number (@taichunmin)
|
|
||||||
- Added ioProx LF protocol support (read, emulate and T55xx clone)
|
|
||||||
- Added `hf mfu nfcimport` to import Flipper Zero `.nfc` files into MFU/NTAG emulator slots, with `--amiibo` flag for automatic PWD/PACK derivation (@fmuk)
|
|
||||||
- Added commands to dump and clone Mifare tags
|
- Added commands to dump and clone Mifare tags
|
||||||
- Fix bad missing tools warning (@suut)
|
- Fix bad missing tools warning (@suut)
|
||||||
- Fix for FAST_READ command for nfc - mf0 tags
|
- Fix for FAST_READ command for nfc - mf0 tags
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ Guangdong, China: [MTools Tec](https://shop.mtoolstec.com/)
|
|||||||
|
|
||||||
Lazada One, Singapore: [Aliexpress by RRG](https://proxgrind.aliexpress.com/store/1101312023)
|
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 ?
|
# What is it and how to use ?
|
||||||
|
|
||||||
Read the [available documentation](https://github.com/RfidResearchGroup/ChameleonUltra/wiki).
|
Read the [available documentation](https://github.com/RfidResearchGroup/ChameleonUltra/wiki).
|
||||||
@@ -26,7 +30,6 @@ Read the [available documentation](https://github.com/RfidResearchGroup/Chameleo
|
|||||||
|
|
||||||
* [ChameleonUltraGUI](https://github.com/GameTec-live/ChameleonUltraGUI)
|
* [ChameleonUltraGUI](https://github.com/GameTec-live/ChameleonUltraGUI)
|
||||||
* [MTools BLE](https://github.com/RfidResearchGroup/ChameleonUltra/wiki/mtoolsble)
|
* [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)
|
|
||||||
|
|
||||||
# Videos
|
# Videos
|
||||||
|
|
||||||
@@ -46,4 +49,4 @@ Where do you find the community?
|
|||||||
* Devices/chameleon-ultra for usage discussions
|
* Devices/chameleon-ultra for usage discussions
|
||||||
* [GameTec_live discord server](https://discord.gg/DJ2A4wxncK)
|
* [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,7 +28,6 @@ SRC_FILES += \
|
|||||||
$(PROJ_DIR)/rfid/nfctag/tag_persistence.c \
|
$(PROJ_DIR)/rfid/nfctag/tag_persistence.c \
|
||||||
$(PROJ_DIR)/rfid/nfctag/hf/crypto1_helper.c \
|
$(PROJ_DIR)/rfid/nfctag/hf/crypto1_helper.c \
|
||||||
$(PROJ_DIR)/rfid/nfctag/hf/nfc_14a.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_mf1.c \
|
||||||
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf0_ntag.c \
|
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf0_ntag.c \
|
||||||
$(PROJ_DIR)/rfid/nfctag/lf/lf_tag_em.c \
|
$(PROJ_DIR)/rfid/nfctag/lf/lf_tag_em.c \
|
||||||
@@ -37,8 +36,6 @@ SRC_FILES += \
|
|||||||
$(PROJ_DIR)/rfid/nfctag/lf/utils/manchester.c \
|
$(PROJ_DIR)/rfid/nfctag/lf/utils/manchester.c \
|
||||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/em410x.c \
|
$(PROJ_DIR)/rfid/nfctag/lf/protocols/em410x.c \
|
||||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/hidprox.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/viking.c \
|
||||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/wiegand.c \
|
$(PROJ_DIR)/rfid/nfctag/lf/protocols/wiegand.c \
|
||||||
$(PROJ_DIR)/utils/dataframe.c \
|
$(PROJ_DIR)/utils/dataframe.c \
|
||||||
@@ -343,16 +340,12 @@ ifeq (${CURRENT_DEVICE_TYPE}, ${CHAMELEON_ULTRA})
|
|||||||
$(PROJ_DIR)/rfid/reader/hf/rc522.c \
|
$(PROJ_DIR)/rfid/reader/hf/rc522.c \
|
||||||
$(PROJ_DIR)/rfid/reader/lf/lf_125khz_radio.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_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_data.c \
|
||||||
$(PROJ_DIR)/rfid/reader/lf/lf_reader_main.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_t55xx_data.c \
|
||||||
$(PROJ_DIR)/rfid/reader/lf/lf_hidprox_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_viking_data.c \
|
||||||
|
$(PROJ_DIR)/rfid/reader/lf/lf_reader_generic.c \
|
||||||
|
|
||||||
INC_FOLDERS +=\
|
INC_FOLDERS +=\
|
||||||
${PROJ_DIR}/rfid/reader/ \
|
${PROJ_DIR}/rfid/reader/ \
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -479,7 +479,7 @@ static void check_wakeup_src(void) {
|
|||||||
light_up_by_slot();
|
light_up_by_slot();
|
||||||
|
|
||||||
// If no operation follows, wait for the timeout and then deep hibernate
|
// If no operation follows, wait for the timeout and then deep hibernate
|
||||||
sleep_timer_start(settings_get_sleep_timeout());
|
sleep_timer_start(SLEEP_DELAY_MS_BUTTON_WAKEUP);
|
||||||
} else if ((m_reset_source & (NRF_POWER_RESETREAS_NFC_MASK | NRF_POWER_RESETREAS_LPCOMP_MASK)) ||
|
} else if ((m_reset_source & (NRF_POWER_RESETREAS_NFC_MASK | NRF_POWER_RESETREAS_LPCOMP_MASK)) ||
|
||||||
(m_gpregret_val & RESET_ON_LF_FIELD_EXISTS_Msk)) {
|
(m_gpregret_val & RESET_ON_LF_FIELD_EXISTS_Msk)) {
|
||||||
NRF_LOG_INFO("WakeUp from rfid field");
|
NRF_LOG_INFO("WakeUp from rfid field");
|
||||||
@@ -667,11 +667,6 @@ static void btn_fn_copy_lf(uint8_t slot, tag_specific_type_t type) {
|
|||||||
size = LF_HIDPROX_TAG_ID_SIZE;
|
size = LF_HIDPROX_TAG_ID_SIZE;
|
||||||
data = id_buffer;
|
data = id_buffer;
|
||||||
break;
|
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:
|
||||||
case TAG_TYPE_EM410X_ELECTRA: {
|
case TAG_TYPE_EM410X_ELECTRA: {
|
||||||
status = scan_em410x(id_buffer);
|
status = scan_em410x(id_buffer);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
#define STATUS_LF_TAG_OK (0x40) // Some of the low -frequency cards are successful!
|
#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_NO_FOUND (0x41) // Can't search for valid LF tags
|
||||||
#define STATUS_LF_TAG_LOGIN_REQUIRED (0x42) // Tag requires LOGIN before read
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
// other status
|
// other status
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ BLE_ADVERTISING_DEF(m_advertising);
|
|||||||
|
|
||||||
uint16_t batt_lvl_in_milli_volts = 0;
|
uint16_t batt_lvl_in_milli_volts = 0;
|
||||||
uint8_t percentage_batt_lvl = 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_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. */
|
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;
|
lf_adc_callback_t m_lf_adc_callback = NULL;
|
||||||
@@ -806,4 +806,4 @@ void unregister_lf_adc_callback(void) {
|
|||||||
nrfx_saadc_uninit();
|
nrfx_saadc_uninit();
|
||||||
adc_configure();
|
adc_configure();
|
||||||
m_lf_adc_callback = NULL;
|
m_lf_adc_callback = NULL;
|
||||||
}
|
}
|
||||||
@@ -46,8 +46,6 @@
|
|||||||
#define DATA_CMD_GET_BLE_PAIRING_ENABLE (1036)
|
#define DATA_CMD_GET_BLE_PAIRING_ENABLE (1036)
|
||||||
#define DATA_CMD_SET_BLE_PAIRING_ENABLE (1037)
|
#define DATA_CMD_SET_BLE_PAIRING_ENABLE (1037)
|
||||||
#define DATA_CMD_GET_ALL_SLOT_NICKS (1038)
|
#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_READ_ONE_BLOCK (2008)
|
||||||
#define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009)
|
#define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009)
|
||||||
#define DATA_CMD_HF14A_RAW (2010)
|
#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_MANIPULATE_VALUE_BLOCK (2011)
|
||||||
#define DATA_CMD_MF1_CHECK_KEYS_OF_SECTORS (2012)
|
#define DATA_CMD_MF1_CHECK_KEYS_OF_SECTORS (2012)
|
||||||
#define DATA_CMD_MF1_HARDNESTED_ACQUIRE (2013)
|
#define DATA_CMD_MF1_HARDNESTED_ACQUIRE (2013)
|
||||||
@@ -82,7 +78,6 @@
|
|||||||
|
|
||||||
#define DATA_CMD_HF14A_GET_CONFIG (2200)
|
#define DATA_CMD_HF14A_GET_CONFIG (2200)
|
||||||
#define DATA_CMD_HF14A_SET_CONFIG (2201)
|
#define DATA_CMD_HF14A_SET_CONFIG (2201)
|
||||||
#define DATA_CMD_HF14A_SNIFF (2020)
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// ******************************************************************
|
// ******************************************************************
|
||||||
@@ -98,18 +93,11 @@
|
|||||||
#define DATA_CMD_EM410X_ELECTRA_WRITE_TO_T55XX (3006)
|
#define DATA_CMD_EM410X_ELECTRA_WRITE_TO_T55XX (3006)
|
||||||
#define DATA_CMD_HIDPROX_SCAN (3002)
|
#define DATA_CMD_HIDPROX_SCAN (3002)
|
||||||
#define DATA_CMD_HIDPROX_WRITE_TO_T55XX (3003)
|
#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_SCAN (3004)
|
||||||
#define DATA_CMD_VIKING_WRITE_TO_T55XX (3005)
|
#define DATA_CMD_VIKING_WRITE_TO_T55XX (3005)
|
||||||
#define DATA_CMD_ADC_GENERIC_READ (3009)
|
#define DATA_CMD_ADC_GENERIC_READ (3009)
|
||||||
#define DATA_CMD_GENERIC_READ (3007)
|
#define DATA_CMD_GENERIC_READ (3007)
|
||||||
#define DATA_CMD_CORR_GENERIC_READ (3008)
|
#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)
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// ******************************************************************
|
// ******************************************************************
|
||||||
@@ -172,27 +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_SET_EMU_ID (5000)
|
||||||
#define DATA_CMD_EM410X_GET_EMU_ID (5001)
|
#define DATA_CMD_EM410X_GET_EMU_ID (5001)
|
||||||
#define DATA_CMD_HIDPROX_SET_EMU_ID (5002)
|
#define DATA_CMD_HIDPROX_SET_EMU_ID (5002)
|
||||||
#define DATA_CMD_HIDPROX_GET_EMU_ID (5003)
|
#define DATA_CMD_HIDPROX_GET_EMU_ID (5003)
|
||||||
#define DATA_CMD_VIKING_SET_EMU_ID (5004)
|
#define DATA_CMD_VIKING_SET_EMU_ID (5004)
|
||||||
#define DATA_CMD_VIKING_GET_EMU_ID (5005)
|
#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_EM4X05_SCAN (3030)
|
|
||||||
#define DATA_CMD_EM4X05_READSNIFF (3032)
|
|
||||||
#define DATA_CMD_LF_SNIFF (3031)
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -59,36 +59,6 @@ const uint16_t ats_fsdi_table[] = {
|
|||||||
static volatile bool m_is_responded = false;
|
static volatile bool m_is_responded = false;
|
||||||
// Receiving buffer
|
// Receiving buffer
|
||||||
static uint8_t m_nfc_rx_buffer[MAX_NFC_RX_BUFFER_SIZE] = { 0x00 };
|
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 };
|
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
|
// 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 };
|
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
|
// Because of this error receiving event caused by this possible interference
|
||||||
return;
|
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
|
// Manually draw frame, separate data and strange school inspection
|
||||||
#if !NFC_TAG_14A_RX_PARITY_AUTO_DEL_ENABLE
|
#if !NFC_TAG_14A_RX_PARITY_AUTO_DEL_ENABLE
|
||||||
if (szDataBits >= 9) {
|
if (szDataBits >= 9) {
|
||||||
@@ -385,11 +350,9 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
|
|||||||
if (auto_coll_res != NULL) {
|
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
|
// 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;
|
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
|
||||||
// After receiving the WUPA or REQA instruction, we need to reply to ATQA
|
nfc_tag_14a_tx_bytes(auto_coll_res->atqa, 2, false);
|
||||||
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]);
|
||||||
// NRF_LOG_INFO("ATQA reply: %02x%02x", auto_coll_res->atqa[0], auto_coll_res->atqa[1]);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
|
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
|
||||||
NRF_LOG_INFO("Auto anti-collision resource no exists.");
|
NRF_LOG_INFO("Auto anti-collision resource no exists.");
|
||||||
@@ -505,9 +468,7 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
|
|||||||
}
|
}
|
||||||
// Incoming SELECT ALL for any cascade level
|
// Incoming SELECT ALL for any cascade level
|
||||||
if (szDataBits == 16 && p_data[1] == 0x20) {
|
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.");
|
// NRF_LOG_INFO("[MFEMUL_SELECT] SEL Reply.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -521,14 +482,10 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
|
|||||||
if (cl_finished) {
|
if (cl_finished) {
|
||||||
// NRF_LOG_INFO("[MFEMUL_SELECT] m_tag_state_14a = MFEMUL_WORK");
|
// NRF_LOG_INFO("[MFEMUL_SELECT] m_tag_state_14a = MFEMUL_WORK");
|
||||||
m_tag_state_14a = NFC_TAG_STATE_14A_ACTIVE;
|
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 {
|
} else {
|
||||||
// It is necessary to continue the level, so we need to respond to a data that marks the incomplete UID in SAK
|
// 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 {
|
} else {
|
||||||
// IDLE, not our UID
|
// IDLE, not our UID
|
||||||
@@ -554,10 +511,6 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
|
|||||||
}
|
}
|
||||||
// RATS instruction
|
// RATS instruction
|
||||||
if (p_data[0] == NFC_TAG_14A_CMD_RATS && nfc_tag_14a_checks_crc(p_data, 4)) {
|
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
|
// Make sure the sub -packaging opens the support of ATS
|
||||||
if (auto_coll_res->ats->length > 0) {
|
if (auto_coll_res->ats->length > 0) {
|
||||||
// Take out FSD and return according to the maximum FSD
|
// Take out FSD and return according to the maximum FSD
|
||||||
@@ -602,10 +555,11 @@ static inline void nrf_nfct_reset(void) {
|
|||||||
// Use Window Grid frame delay mode.
|
// Use Window Grid frame delay mode.
|
||||||
nrf_nfct_frame_delay_mode_set(NRF_NFCT_FRAME_DELAY_MODE_WINDOWGRID);
|
nrf_nfct_frame_delay_mode_set(NRF_NFCT_FRAME_DELAY_MODE_WINDOWGRID);
|
||||||
|
|
||||||
/* Use SDD00001 per ISO14443-3 standard.
|
/* Begin: Workaround for anomaly 25 */
|
||||||
* Note: SDD00100 was previously used for Windows Phone compatibility
|
/* Workaround for wrong SENSRES values require using SDD00001, but here SDD00100 is used
|
||||||
* but breaks standard readers (including Proxmark3). SDD00001 is correct. */
|
because it is required to operate with Windows Phone */
|
||||||
nrf_nfct_sensres_bit_frame_sdd_set(NRF_NFCT_SENSRES_BIT_FRAME_SDD_00001);
|
nrf_nfct_sensres_bit_frame_sdd_set(NRF_NFCT_SENSRES_BIT_FRAME_SDD_00100);
|
||||||
|
/* End: Workaround for anomaly 25 */
|
||||||
|
|
||||||
// Restore interrupts.
|
// Restore interrupts.
|
||||||
nrf_nfct_int_enable(int_enabled);
|
nrf_nfct_int_enable(int_enabled);
|
||||||
@@ -678,19 +632,7 @@ void nfc_tag_14a_event_callback(nrfx_nfct_evt_t const *p_event) {
|
|||||||
}
|
}
|
||||||
case NRFX_NFCT_EVT_TX_FRAMESTART: {
|
case NRFX_NFCT_EVT_TX_FRAMESTART: {
|
||||||
// NRF_LOG_INFO("TX start.\n");
|
// NRF_LOG_INFO("TX start.\n");
|
||||||
if (m_tx_sniff_cb != NULL) {
|
// NRF_LOG_INFO("TX config is %d.\n", nrf_nfct_tx_frame_config_get(NRF_NFCT));
|
||||||
uint32_t amt = NRF_NFCT->TXD.AMOUNT;
|
|
||||||
uint16_t tx_bytes = (amt >> NFCT_TXD_AMOUNT_TXDATABYTES_Pos)
|
|
||||||
& (NFCT_TXD_AMOUNT_TXDATABYTES_Msk >> NFCT_TXD_AMOUNT_TXDATABYTES_Pos);
|
|
||||||
uint16_t tx_bits_rem = (amt >> NFCT_TXD_AMOUNT_TXDATABITS_Pos)
|
|
||||||
& (NFCT_TXD_AMOUNT_TXDATABITS_Msk >> NFCT_TXD_AMOUNT_TXDATABITS_Pos);
|
|
||||||
uint16_t tx_bits = (tx_bits_rem > 0)
|
|
||||||
? ((tx_bytes - 1) * 8 + tx_bits_rem)
|
|
||||||
: (tx_bytes * 8);
|
|
||||||
if (tx_bits > 0 && tx_bytes <= MAX_NFC_TX_BUFFER_SIZE) {
|
|
||||||
m_tx_sniff_cb(m_nfc_tx_buffer, tx_bits);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NRFX_NFCT_EVT_TX_FRAMEEND: {
|
case NRFX_NFCT_EVT_TX_FRAMEEND: {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "tag_emulation.h"
|
#include "tag_emulation.h"
|
||||||
|
|
||||||
#define MAX_NFC_RX_BUFFER_SIZE 257
|
#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
|
#define NFC_TAG_14A_CRC_LENGTH 2
|
||||||
|
|
||||||
@@ -82,27 +82,6 @@ typedef struct {
|
|||||||
|
|
||||||
// Communication reception function that needs to be implemented
|
// Communication reception function that needs to be implemented
|
||||||
typedef void (*nfc_tag_14a_reset_handler_t)(void);
|
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 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);
|
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,71 +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);
|
|
||||||
@@ -5,13 +5,10 @@
|
|||||||
#include "bsp_delay.h"
|
#include "bsp_delay.h"
|
||||||
#include "fds_util.h"
|
#include "fds_util.h"
|
||||||
#include "nrf_gpio.h"
|
#include "nrf_gpio.h"
|
||||||
#include "nrf_soc.h"
|
|
||||||
#include "nrfx_lpcomp.h"
|
#include "nrfx_lpcomp.h"
|
||||||
#include "nrfx_pwm.h"
|
#include "nrfx_pwm.h"
|
||||||
#include "protocols/em410x.h"
|
#include "protocols/em410x.h"
|
||||||
#include "protocols/hidprox.h"
|
#include "protocols/hidprox.h"
|
||||||
#include "protocols/ioprox.h"
|
|
||||||
#include "protocols/pac.h"
|
|
||||||
#include "protocols/viking.h"
|
#include "protocols/viking.h"
|
||||||
#include "syssleep.h"
|
#include "syssleep.h"
|
||||||
#include "tag_emulation.h"
|
#include "tag_emulation.h"
|
||||||
@@ -24,6 +21,7 @@
|
|||||||
NRF_LOG_MODULE_REGISTER();
|
NRF_LOG_MODULE_REGISTER();
|
||||||
|
|
||||||
#define ANT_NO_MOD() nrf_gpio_pin_clear(LF_MOD)
|
#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
|
// Whether the USB light effect is allowed to enable
|
||||||
extern bool g_usb_led_marquee_enable;
|
extern bool g_usb_led_marquee_enable;
|
||||||
@@ -42,8 +40,7 @@ static void lf_field_lost(void) {
|
|||||||
g_is_tag_emulating = false; // Reset the flag in the emulation
|
g_is_tag_emulating = false; // Reset the flag in the emulation
|
||||||
m_is_lf_emulating = false;
|
m_is_lf_emulating = false;
|
||||||
TAG_FIELD_LED_OFF() // Make sure the indicator light of the LF field status
|
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_INTENCLR_CROSS_Msk | LPCOMP_INTENCLR_UP_Msk | LPCOMP_INTENCLR_DOWN_Msk | LPCOMP_INTENCLR_READY_Msk;
|
||||||
NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk;
|
|
||||||
// call sleep_timer_start *after* unsetting g_is_tag_emulating
|
// 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
|
sleep_timer_start(SLEEP_DELAY_MS_FIELD_125KHZ_LOST); // Start the timer to enter the sleep
|
||||||
NRF_LOG_INFO("LF FIELD LOST");
|
NRF_LOG_INFO("LF FIELD LOST");
|
||||||
@@ -68,15 +65,12 @@ bool is_lf_field_exists(void) {
|
|||||||
* priority is set to APP_IRQ_PRIORITY_HIGH).
|
* priority is set to APP_IRQ_PRIORITY_HIGH).
|
||||||
*/
|
*/
|
||||||
static void lpcomp_event_handler(nrf_lpcomp_event_t event) {
|
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) {
|
if (m_is_lf_emulating || event != NRF_LPCOMP_EVENT_UP) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep_timer_stop(); // turn off dormant delay
|
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();
|
nrfx_lpcomp_disable();
|
||||||
|
|
||||||
// set the emulation status logo bit
|
// set the emulation status logo bit
|
||||||
@@ -89,9 +83,8 @@ static void lpcomp_event_handler(nrf_lpcomp_event_t event) {
|
|||||||
set_slot_light_color(RGB_BLUE);
|
set_slot_light_color(RGB_BLUE);
|
||||||
TAG_FIELD_LED_ON()
|
TAG_FIELD_LED_ON()
|
||||||
|
|
||||||
// Loop continuously — no stop/restart gaps between sequence plays.
|
// use precise hardware timer to broadcast card id
|
||||||
// Field-loss is detected in pwm_handler via EVT_END_SEQ0.
|
nrfx_pwm_simple_playback(&m_broadcast, m_pwm_seq, LF_125KHZ_BROADCAST_MAX, NRFX_PWM_FLAG_STOP);
|
||||||
nrfx_pwm_simple_playback(&m_broadcast, m_pwm_seq, 1, NRFX_PWM_FLAG_LOOP);
|
|
||||||
|
|
||||||
NRF_LOG_INFO("LF FIELD DETECTED");
|
NRF_LOG_INFO("LF FIELD DETECTED");
|
||||||
}
|
}
|
||||||
@@ -108,23 +101,21 @@ static void lpcomp_init(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void pwm_handler(nrfx_pwm_evt_type_t event_type) {
|
static void pwm_handler(nrfx_pwm_evt_type_t event_type) {
|
||||||
if (event_type == NRFX_PWM_EVT_END_SEQ0) {
|
|
||||||
// Fired at end of each loop iteration — check field without stopping PWM.
|
|
||||||
// Mask UP interrupt while sampling to prevent re-entrancy.
|
|
||||||
NRF_LPCOMP->INTENCLR = LPCOMP_INTENCLR_UP_Msk;
|
|
||||||
if (!is_lf_field_exists()) {
|
|
||||||
// Field gone — stop the loop; pwm_handler will get EVT_STOPPED next.
|
|
||||||
nrfx_pwm_stop(&m_broadcast, false);
|
|
||||||
}
|
|
||||||
// Re-enable will happen either in lf_field_lost (via INTENSET) or stays
|
|
||||||
// suppressed while PWM keeps looping (we only need it after field_lost).
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event_type != NRFX_PWM_EVT_STOPPED) {
|
if (event_type != NRFX_PWM_EVT_STOPPED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// after last broadcast, force NO_MOD on antenna to measure field.
|
||||||
ANT_NO_MOD();
|
ANT_NO_MOD();
|
||||||
lf_field_lost();
|
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()) {
|
||||||
|
nrfx_lpcomp_disable();
|
||||||
|
nrfx_pwm_simple_playback(&m_broadcast, m_pwm_seq, LF_125KHZ_BROADCAST_MAX, NRFX_PWM_FLAG_STOP);
|
||||||
|
} else {
|
||||||
|
lf_field_lost();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pwm_init(void) {
|
static void pwm_init(void) {
|
||||||
@@ -144,23 +135,6 @@ static void pwm_init(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void lf_sense_enable(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. We can't lock to the reader's carrier (tag-mode antenna taps
|
|
||||||
// on this board are envelope-only), so this is as good as it gets.
|
|
||||||
//
|
|
||||||
// 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();
|
lpcomp_init();
|
||||||
pwm_init(); // use precise hardware pwm to broadcast card id
|
pwm_init(); // use precise hardware pwm to broadcast card id
|
||||||
if (is_lf_field_exists()) {
|
if (is_lf_field_exists()) {
|
||||||
@@ -173,7 +147,6 @@ static void lf_sense_disable(void) {
|
|||||||
nrfx_lpcomp_uninit();
|
nrfx_lpcomp_uninit();
|
||||||
m_pwm_seq = NULL;
|
m_pwm_seq = NULL;
|
||||||
m_is_lf_emulating = false;
|
m_is_lf_emulating = false;
|
||||||
sd_clock_hfclk_release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum {
|
static enum {
|
||||||
@@ -232,15 +205,6 @@ int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
|
|||||||
return LF_HIDPROX_TAG_ID_SIZE;
|
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) {
|
if (type == TAG_TYPE_VIKING && buffer->length >= LF_VIKING_TAG_ID_SIZE) {
|
||||||
m_tag_type = type;
|
m_tag_type = type;
|
||||||
void *codec = viking.alloc();
|
void *codec = viking.alloc();
|
||||||
@@ -250,15 +214,6 @@ int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
|
|||||||
return LF_VIKING_TAG_ID_SIZE;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
NRF_LOG_ERROR("no valid data exists in buffer for tag type: %d.", type);
|
NRF_LOG_ERROR("no valid data exists in buffer for tag type: %d.", type);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -291,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;
|
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
|
/** @brief Id card deposit card number before callback
|
||||||
* @param type Refined tag type
|
* @param type Refined tag type
|
||||||
* @param buffer Data buffer
|
* @param buffer Data buffer
|
||||||
@@ -359,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));
|
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
|
/** @brief Id card deposit card number before callback
|
||||||
* @param slot Card slot number
|
* @param slot Card slot number
|
||||||
* @param tag_type Refined tag type
|
* @param tag_type Refined tag type
|
||||||
@@ -381,13 +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};
|
uint8_t tag_id[4] = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||||
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
|
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));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,10 +7,8 @@
|
|||||||
|
|
||||||
#define LF_EM410X_TAG_ID_SIZE 5
|
#define LF_EM410X_TAG_ID_SIZE 5
|
||||||
#define LF_EM410X_ELECTRA_TAG_ID_SIZE 13
|
#define LF_EM410X_ELECTRA_TAG_ID_SIZE 13
|
||||||
#define LF_IOPROX_TAG_ID_SIZE 16
|
|
||||||
#define LF_HIDPROX_TAG_ID_SIZE 13
|
#define LF_HIDPROX_TAG_ID_SIZE 13
|
||||||
#define LF_VIKING_TAG_ID_SIZE 4
|
#define LF_VIKING_TAG_ID_SIZE 4
|
||||||
#define LF_PAC_TAG_ID_SIZE 8
|
|
||||||
|
|
||||||
void lf_tag_125khz_sense_switch(bool enable);
|
void lf_tag_125khz_sense_switch(bool enable);
|
||||||
int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
|
int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
|
||||||
@@ -18,10 +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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
|
||||||
bool is_lf_field_exists(void);
|
bool is_lf_field_exists(void);
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
#define DEMOD_BUFFER_SIZE (32)
|
#define DEMOD_BUFFER_SIZE (32)
|
||||||
#define HIDPROX_RAW_SIZE (96)
|
#define HIDPROX_RAW_SIZE (96)
|
||||||
|
|
||||||
// NOTE: These LF_FSK2a_* defines are intentionally local to this .c file
|
|
||||||
// to allow per-protocol timing tuning (do not move to a shared header).
|
|
||||||
#define LF_FSK2a_PWM_LO_FREQ_LOOP (5)
|
#define LF_FSK2a_PWM_LO_FREQ_LOOP (5)
|
||||||
#define LF_FSK2a_PWM_LO_FREQ_TOP_VALUE (10)
|
#define LF_FSK2a_PWM_LO_FREQ_TOP_VALUE (10)
|
||||||
#define LF_FSK2a_PWM_HI_FREQ_LOOP (6)
|
#define LF_FSK2a_PWM_HI_FREQ_LOOP (6)
|
||||||
@@ -49,7 +47,7 @@ void hidprox_decoder_start(hidprox_codec *d, uint8_t format_hint) {
|
|||||||
hidprox_codec *hidprox_codec_alloc(void) {
|
hidprox_codec *hidprox_codec_alloc(void) {
|
||||||
hidprox_codec *d = malloc(sizeof(hidprox_codec));
|
hidprox_codec *d = malloc(sizeof(hidprox_codec));
|
||||||
d->card = NULL;
|
d->card = NULL;
|
||||||
d->modem = fsk_alloc(FSK_BITRATE_HID);
|
d->modem = fsk_alloc();
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,413 +0,0 @@
|
|||||||
#include "ioprox.h"
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "fskdemod.h"
|
|
||||||
#include "t55xx.h"
|
|
||||||
#include "tag_base_type.h"
|
|
||||||
|
|
||||||
#define IOPROX_SOF (0x1d)
|
|
||||||
#define IOPROX_T55XX_BLOCK_COUNT (3)
|
|
||||||
#define DEMOD_BUFFER_SIZE (32)
|
|
||||||
#define IOPROX_RAW_SIZE (96)
|
|
||||||
|
|
||||||
// NOTE: These LF_FSK2a_* defines are intentionally local to this .c file
|
|
||||||
// to allow per-protocol timing tuning (do not move to a shared header).
|
|
||||||
#define LF_FSK2a_PWM_LO_FREQ_LOOP (6)
|
|
||||||
#define LF_FSK2a_PWM_LO_FREQ_TOP_VALUE (11)
|
|
||||||
#define LF_FSK2a_PWM_HI_FREQ_LOOP (8)
|
|
||||||
#define LF_FSK2a_PWM_HI_FREQ_TOP_VALUE (8)
|
|
||||||
|
|
||||||
static nrf_pwm_values_wave_form_t m_ioprox_pwm_seq_vals[IOPROX_RAW_SIZE * 6] = {};
|
|
||||||
|
|
||||||
nrf_pwm_sequence_t m_ioprox_pwm_seq = {
|
|
||||||
.values.p_wave_form = m_ioprox_pwm_seq_vals,
|
|
||||||
.length = NRF_PWM_VALUES_LENGTH(m_ioprox_pwm_seq_vals),
|
|
||||||
.repeats = 0,
|
|
||||||
.end_delay = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
void ioprox_reset_bits(ioprox_codec_t *d) {
|
|
||||||
d->bit_len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void push_bit(ioprox_codec_t *d, uint8_t bit)
|
|
||||||
{
|
|
||||||
if (d->bit_len < IOPROX_MAX_BITS) {
|
|
||||||
d->bits[d->bit_len++] = bit;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Buffer full: drop the oldest bit and append the new one
|
|
||||||
memmove(d->bits, d->bits + 1, IOPROX_MAX_BITS - 1);
|
|
||||||
d->bits[IOPROX_MAX_BITS - 1] = bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint8_t get_bit_inv(const uint8_t *bits, uint16_t pos, bool inv)
|
|
||||||
{
|
|
||||||
uint8_t b = bits[pos] & 1u;
|
|
||||||
return inv ? (uint8_t)(b ^ 1u) : b;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a bit array to a 32-bit big-endian integer.
|
|
||||||
static inline uint32_t bytebits_to_byte(const uint8_t *bits, uint16_t len)
|
|
||||||
{
|
|
||||||
uint32_t val = 0;
|
|
||||||
for (uint16_t i = 0; i < len; i++) {
|
|
||||||
val = (val << 1) | (bits[i] & 1u);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads 8 bits MSB-first from bits[start_pos], optionally inverting each bit.
|
|
||||||
static inline uint8_t bytebits_to_u8_msb_inv(const uint8_t *bits, uint16_t start_pos, bool inv)
|
|
||||||
{
|
|
||||||
uint8_t v = 0;
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
v = (uint8_t)((v << 1) | get_bit_inv(bits, (uint16_t)(start_pos + i), inv));
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unpacks a raw 8-byte card frame into the codec bit buffer (MSB-first).
|
|
||||||
bool ioprox_raw8_to_bits(const uint8_t *raw8, ioprox_codec_t *d) {
|
|
||||||
d->bit_len = 0;
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
uint8_t byte = raw8[i];
|
|
||||||
for (int j = 0; j < 8; j++) {
|
|
||||||
d->bits[d->bit_len++] = (byte >> (7 - j)) & 0x01;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ioProx checksum: 0xFF - (sum(b1..b5) & 0xFF)
|
|
||||||
static inline uint8_t ioprox_checksum5(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4, uint8_t b5)
|
|
||||||
{
|
|
||||||
uint16_t sum = (uint16_t)b1 + b2 + b3 + b4 + b5;
|
|
||||||
return (uint8_t)(0xFFu - (uint8_t)(sum & 0xFFu));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if 10 starting bits form a valid ioProx preamble.
|
|
||||||
// Preamble is 9 zeros followed by 1 one (inverted when inv=true).
|
|
||||||
static bool preamble_match(const uint8_t *d, uint16_t off, bool inv)
|
|
||||||
{
|
|
||||||
for (int k = 0; k < 9; k++) {
|
|
||||||
if ((d[off + k] & 1u) != (inv ? 1u : 0u)) return false;
|
|
||||||
}
|
|
||||||
return ((d[off + 9] & 1u) == (inv ? 0u : 1u));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decodes a 64-bit ioProx frame starting at bit index idx into d->data.
|
|
||||||
// Returns true only if the checksum passes.
|
|
||||||
//
|
|
||||||
// Frame layout (8+1 bit-framing, 7 groups):
|
|
||||||
// b0: SOF byte (0x00)
|
|
||||||
// b1: 0xF0 (fixed header)
|
|
||||||
// b2: facility code
|
|
||||||
// b3: version
|
|
||||||
// b4: card number high byte
|
|
||||||
// b5: card number low byte
|
|
||||||
// b6: checksum (0xFF - sum(b1..b5))
|
|
||||||
//
|
|
||||||
// Output d->data layout (16 bytes):
|
|
||||||
// [0] version
|
|
||||||
// [1] facility code
|
|
||||||
// [2-3] card number (big-endian)
|
|
||||||
// [4-11] raw8 frame bytes (for debugging and storage)
|
|
||||||
// [12-15] reserved (0x00)
|
|
||||||
static bool decode_and_pack(ioprox_codec_t *d, uint16_t idx, bool inv)
|
|
||||||
{
|
|
||||||
uint8_t b1, b2, b3, b4, b5, b6;
|
|
||||||
uint16_t number;
|
|
||||||
uint32_t raw_block1;
|
|
||||||
uint32_t raw_block2;
|
|
||||||
|
|
||||||
b1 = bytebits_to_u8_msb_inv(d->bits, (uint16_t)(idx + 9), inv);
|
|
||||||
b2 = bytebits_to_u8_msb_inv(d->bits, (uint16_t)(idx + 18), inv);
|
|
||||||
b3 = bytebits_to_u8_msb_inv(d->bits, (uint16_t)(idx + 27), inv);
|
|
||||||
b4 = bytebits_to_u8_msb_inv(d->bits, (uint16_t)(idx + 36), inv);
|
|
||||||
b5 = bytebits_to_u8_msb_inv(d->bits, (uint16_t)(idx + 45), inv);
|
|
||||||
b6 = bytebits_to_u8_msb_inv(d->bits, (uint16_t)(idx + 54), inv);
|
|
||||||
|
|
||||||
if (ioprox_checksum5(b1, b2, b3, b4, b5) != b6) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
number = (uint16_t)(((uint16_t)b4 << 8) | b5);
|
|
||||||
raw_block1 = bytebits_to_byte(d->bits + idx, 32);
|
|
||||||
raw_block2 = bytebits_to_byte(d->bits + idx + 32, 32);
|
|
||||||
|
|
||||||
memset(d->data, 0, sizeof(d->data));
|
|
||||||
|
|
||||||
d->data[0] = b3; // version
|
|
||||||
d->data[1] = b2; // facility code
|
|
||||||
d->data[2] = (uint8_t)(number >> 8); // card number high byte
|
|
||||||
d->data[3] = (uint8_t)(number & 0xFF); // card number low byte
|
|
||||||
|
|
||||||
// Raw frame bytes (human-readable, stable across re-reads)
|
|
||||||
d->data[4] = (uint8_t)(raw_block1 >> 24);
|
|
||||||
d->data[5] = (uint8_t)(raw_block1 >> 16);
|
|
||||||
d->data[6] = (uint8_t)(raw_block1 >> 8);
|
|
||||||
d->data[7] = (uint8_t)(raw_block1);
|
|
||||||
d->data[8] = (uint8_t)(raw_block2 >> 24);
|
|
||||||
d->data[9] = (uint8_t)(raw_block2 >> 16);
|
|
||||||
d->data[10] = (uint8_t)(raw_block2 >> 8);
|
|
||||||
d->data[11] = (uint8_t)(raw_block2);
|
|
||||||
// d->data[12..15] zeroed by memset above
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decodes a raw 8-byte ioProx card frame into the 16-byte output buffer.
|
|
||||||
// Returns true if the frame checksum is valid.
|
|
||||||
bool ioprox_decode_raw_to_data(const uint8_t *raw8, uint8_t *output) {
|
|
||||||
ioprox_codec_t codec;
|
|
||||||
memset(&codec, 0, sizeof(codec));
|
|
||||||
ioprox_raw8_to_bits(raw8, &codec);
|
|
||||||
if (decode_and_pack(&codec, 0, false)) {
|
|
||||||
memcpy(output, codec.data, 16);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes 8 bits of v MSB-first into bits[] starting at position pos.
|
|
||||||
static void write_bits_msb(uint8_t *bits, uint16_t pos, uint8_t v)
|
|
||||||
{
|
|
||||||
for (uint8_t i = 0; i < 8; i++) {
|
|
||||||
bits[pos + i] = (v >> (7 - i)) & 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encodes ioProx card parameters into the 16-byte output buffer.
|
|
||||||
// The encoded frame uses 8+1 bit framing (8 data bits + 1 separator per group).
|
|
||||||
// Returns false if output pointer is NULL.
|
|
||||||
bool ioprox_encode_params_to_data(uint8_t version, uint8_t facility, uint16_t number, uint8_t *output)
|
|
||||||
{
|
|
||||||
if (!output) return false;
|
|
||||||
|
|
||||||
uint8_t b0 = 0x00;
|
|
||||||
uint8_t b1 = 0xF0;
|
|
||||||
uint8_t b2 = facility;
|
|
||||||
uint8_t b3 = version;
|
|
||||||
uint8_t b4 = (uint8_t)(number >> 8);
|
|
||||||
uint8_t b5 = (uint8_t)(number & 0xFF);
|
|
||||||
uint8_t b6 = ioprox_checksum5(b1, b2, b3, b4, b5);
|
|
||||||
|
|
||||||
uint8_t bits[64] = {0};
|
|
||||||
|
|
||||||
// Pack 7 data bytes using 8+1 framing (data bits + separator)
|
|
||||||
write_bits_msb(bits, 0, b0); bits[ 8] = 0;
|
|
||||||
write_bits_msb(bits, 9, b1); bits[17] = 1;
|
|
||||||
write_bits_msb(bits, 18, b2); bits[26] = 1;
|
|
||||||
write_bits_msb(bits, 27, b3); bits[35] = 1;
|
|
||||||
write_bits_msb(bits, 36, b4); bits[44] = 1;
|
|
||||||
write_bits_msb(bits, 45, b5); bits[53] = 1;
|
|
||||||
write_bits_msb(bits, 54, b6);
|
|
||||||
bits[62] = 1;
|
|
||||||
bits[63] = 1;
|
|
||||||
|
|
||||||
// Decoded fields
|
|
||||||
output[0] = b3; // version
|
|
||||||
output[1] = b2; // facility code
|
|
||||||
output[2] = b4; // card number high byte
|
|
||||||
output[3] = b5; // card number low byte
|
|
||||||
|
|
||||||
// Raw bitstream packed into bytes [4..11]
|
|
||||||
memset(output + 4, 0, 8);
|
|
||||||
for (int i = 0; i < 64; i++) {
|
|
||||||
if (bits[i]) {
|
|
||||||
output[4 + (i / 8)] |= (uint8_t)(1u << (7 - (i % 8)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scans the tail of the bit buffer for a valid ioProx frame.
|
|
||||||
// To keep CPU load low, only the last ~5 frames (320 bits) are scanned.
|
|
||||||
// Returns true if a valid frame (checksum OK) was decoded into d->data.
|
|
||||||
static bool scan_tail(ioprox_codec_t *d)
|
|
||||||
{
|
|
||||||
// Need at least 128 bits to verify two consecutive 64-bit frames
|
|
||||||
if (d->bit_len < 128) return false;
|
|
||||||
|
|
||||||
// Limit scan to the last 320 bits (~5 frames) for performance
|
|
||||||
uint16_t start_from = (d->bit_len > 320) ? (uint16_t)(d->bit_len - 320) : 0;
|
|
||||||
|
|
||||||
for (uint16_t i = start_from; (uint16_t)(i + 64 + 10) <= d->bit_len; i++) {
|
|
||||||
for (int inv_i = 0; inv_i <= 1; inv_i++) {
|
|
||||||
bool inv = (inv_i == 1);
|
|
||||||
|
|
||||||
// Require two consecutive preambles 64 bits apart (Proxmark-style sync check)
|
|
||||||
if (!preamble_match(d->bits, i, inv)) continue;
|
|
||||||
if (!preamble_match(d->bits, i + 64, inv)) continue;
|
|
||||||
|
|
||||||
// Try small phase offsets to tolerate minor bit-alignment jitter
|
|
||||||
for (int8_t phase = -2; phase <= 2; phase++) {
|
|
||||||
int32_t idx_i = (int32_t)i + (int32_t)phase;
|
|
||||||
|
|
||||||
if (idx_i < 0) continue;
|
|
||||||
if (idx_i + 64 > d->bit_len) continue;
|
|
||||||
|
|
||||||
uint16_t idx = (uint16_t)idx_i;
|
|
||||||
|
|
||||||
// Validate separator/stop bits within the frame
|
|
||||||
int bad = 0;
|
|
||||||
if (((d->bits[idx + 8] & 1u) ^ inv) != 0) bad++;
|
|
||||||
if (((d->bits[idx + 17] & 1u) ^ inv) != 1) bad++;
|
|
||||||
if (((d->bits[idx + 26] & 1u) ^ inv) != 1) bad++;
|
|
||||||
if (((d->bits[idx + 35] & 1u) ^ inv) != 1) bad++;
|
|
||||||
if (((d->bits[idx + 44] & 1u) ^ inv) != 1) bad++;
|
|
||||||
if (((d->bits[idx + 53] & 1u) ^ inv) != 1) bad++;
|
|
||||||
if (((d->bits[idx + 62] & 1u) ^ inv) != 1) bad++;
|
|
||||||
if (((d->bits[idx + 63] & 1u) ^ inv) != 1) bad++;
|
|
||||||
|
|
||||||
// Tolerate up to 2 bad bits to handle noise
|
|
||||||
if (bad > 2) continue;
|
|
||||||
|
|
||||||
if (decode_and_pack(d, idx, inv)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Protocol callbacks ---
|
|
||||||
|
|
||||||
static void *ioprox_codec_alloc(void)
|
|
||||||
{
|
|
||||||
ioprox_codec_t *d = (ioprox_codec_t *)malloc(sizeof(ioprox_codec_t));
|
|
||||||
if (!d) return NULL;
|
|
||||||
memset(d, 0, sizeof(*d));
|
|
||||||
d->modem = fsk_alloc(FSK_BITRATE_IOPROX);
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ioprox_codec_free(void *codec)
|
|
||||||
{
|
|
||||||
ioprox_codec_t *d = (ioprox_codec_t *)codec;
|
|
||||||
if (!d) return;
|
|
||||||
if (d->modem) {
|
|
||||||
fsk_free(d->modem);
|
|
||||||
d->modem = NULL;
|
|
||||||
}
|
|
||||||
free(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t *ioprox_get_data(void *codec)
|
|
||||||
{
|
|
||||||
ioprox_codec_t *d = (ioprox_codec_t *)codec;
|
|
||||||
return d->data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ioprox_decoder_start(void *codec, uint8_t format_hint)
|
|
||||||
{
|
|
||||||
(void)format_hint;
|
|
||||||
ioprox_codec_t *d = (ioprox_codec_t *)codec;
|
|
||||||
d->bit_len = 0;
|
|
||||||
memset(d->bits, 0, sizeof(d->bits));
|
|
||||||
memset(d->data, 0, sizeof(d->data));
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ioprox_decoder_feed(void *codec, uint16_t val)
|
|
||||||
{
|
|
||||||
ioprox_codec_t *d = (ioprox_codec_t *)codec;
|
|
||||||
if (!d || !d->modem) return false;
|
|
||||||
|
|
||||||
bool bit = false;
|
|
||||||
if (!fsk_feed(d->modem, val, &bit)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
push_bit(d, (uint8_t)(bit ? 1u : 0u));
|
|
||||||
|
|
||||||
if (d->bit_len >= 128) {
|
|
||||||
if (scan_tail(d)) {
|
|
||||||
ioprox_reset_bits(d);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discard oldest 64 bits when buffer grows too large to prevent stale noise buildup
|
|
||||||
if (d->bit_len >= 512) {
|
|
||||||
memmove(d->bits, d->bits + 64, 512 - 64);
|
|
||||||
d->bit_len -= 64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void ioprox_emit_bit(int *k, bool bit)
|
|
||||||
{
|
|
||||||
if (!bit) {
|
|
||||||
for (int j = 0; j < LF_FSK2a_PWM_HI_FREQ_LOOP; j++) {
|
|
||||||
m_ioprox_pwm_seq_vals[*k].channel_0 = LF_FSK2a_PWM_HI_FREQ_TOP_VALUE / 2;
|
|
||||||
m_ioprox_pwm_seq_vals[*k].counter_top = LF_FSK2a_PWM_HI_FREQ_TOP_VALUE;
|
|
||||||
(*k)++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int j = 0; j < LF_FSK2a_PWM_LO_FREQ_LOOP; j++) {
|
|
||||||
m_ioprox_pwm_seq_vals[*k].channel_0 = LF_FSK2a_PWM_LO_FREQ_TOP_VALUE / 2;
|
|
||||||
m_ioprox_pwm_seq_vals[*k].counter_top = LF_FSK2a_PWM_LO_FREQ_TOP_VALUE;
|
|
||||||
(*k)++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FSK2a modulator: converts the 8 raw card bytes into a PWM sequence for LF transmission.
|
|
||||||
const nrf_pwm_sequence_t *ioprox_modulator(ioprox_codec_t *d, uint8_t *buf)
|
|
||||||
{
|
|
||||||
(void)d;
|
|
||||||
|
|
||||||
// Raw card data starts at buf[4] (bytes 0-3 are decoded fields)
|
|
||||||
uint8_t *raw = &buf[4];
|
|
||||||
|
|
||||||
int k = 0;
|
|
||||||
|
|
||||||
// Emit 64 bits MSB-first
|
|
||||||
for (int bi = 0; bi < 8; bi++) {
|
|
||||||
uint8_t v = raw[bi];
|
|
||||||
for (int bit = 7; bit >= 0; bit--) {
|
|
||||||
ioprox_emit_bit(&k, ((v >> bit) & 1u) != 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ioprox_pwm_seq.length = (uint16_t)(k * 4);
|
|
||||||
return &m_ioprox_pwm_seq;
|
|
||||||
}
|
|
||||||
|
|
||||||
const protocol ioprox = {
|
|
||||||
.tag_type = TAG_TYPE_IOPROX,
|
|
||||||
.data_size = IOPROX_DATA_SIZE,
|
|
||||||
.alloc = (codec_alloc)ioprox_codec_alloc,
|
|
||||||
.free = (codec_free)ioprox_codec_free,
|
|
||||||
.get_data = (codec_get_data)ioprox_get_data,
|
|
||||||
.modulator = (modulator)ioprox_modulator,
|
|
||||||
.decoder = {
|
|
||||||
.start = (decoder_start)ioprox_decoder_start,
|
|
||||||
.feed = (decoder_feed)ioprox_decoder_feed,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Packs the raw card bitstream into T5577 blocks for writing.
|
|
||||||
// Block 0: T5577 config word for ioProx (FSK2a, RF/64, max block 2)
|
|
||||||
// Block 1: first 4 raw bytes
|
|
||||||
// Block 2: last 4 raw bytes
|
|
||||||
uint8_t ioprox_t55xx_writer(uint8_t *buf, uint32_t *blks) {
|
|
||||||
uint8_t *raw = &buf[4];
|
|
||||||
|
|
||||||
blks[0] = T5577_IOPROX_CONFIG;
|
|
||||||
blks[1] = ((uint32_t)raw[0] << 24) |
|
|
||||||
((uint32_t)raw[1] << 16) |
|
|
||||||
((uint32_t)raw[2] << 8) |
|
|
||||||
((uint32_t)raw[3]);
|
|
||||||
blks[2] = ((uint32_t)raw[4] << 24) |
|
|
||||||
((uint32_t)raw[5] << 16) |
|
|
||||||
((uint32_t)raw[6] << 8) |
|
|
||||||
((uint32_t)raw[7]);
|
|
||||||
|
|
||||||
return IOPROX_T55XX_BLOCK_COUNT;
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "protocols.h"
|
|
||||||
#include "fskdemod.h"
|
|
||||||
|
|
||||||
// 16-byte payload (data frame for emulation and CLI):
|
|
||||||
// 0: Version
|
|
||||||
// 1: Facility Code
|
|
||||||
// 2-3: Card Number (Big-endian, uint16)
|
|
||||||
// 4-11: Raw8 (8 bytes of raw card data)
|
|
||||||
// 12-15: Reserved (0x00000000)
|
|
||||||
|
|
||||||
#define IOPROX_DATA_SIZE 16
|
|
||||||
#define IOPROX_MAX_BITS 256
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
fsk_t *modem;
|
|
||||||
|
|
||||||
uint8_t bits[IOPROX_MAX_BITS];
|
|
||||||
uint16_t bit_len;
|
|
||||||
|
|
||||||
uint8_t data[IOPROX_DATA_SIZE];
|
|
||||||
} ioprox_codec_t;
|
|
||||||
|
|
||||||
extern const protocol ioprox;
|
|
||||||
|
|
||||||
uint8_t ioprox_t55xx_writer(uint8_t *buf, uint32_t *blks);
|
|
||||||
bool ioprox_decode_raw_to_data(const uint8_t *raw8, uint8_t *output);
|
|
||||||
bool ioprox_encode_params_to_data(uint8_t ver, uint8_t fc, uint16_t cn, uint8_t *out);
|
|
||||||
@@ -1,400 +0,0 @@
|
|||||||
#include "pac.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "nordic_common.h"
|
|
||||||
#include "nrf_pwm.h"
|
|
||||||
#include "protocols.h"
|
|
||||||
#include "t55xx.h"
|
|
||||||
#include "tag_base_type.h"
|
|
||||||
|
|
||||||
#define NRF_LOG_MODULE_NAME pac_protocol
|
|
||||||
#include "nrf_log.h"
|
|
||||||
#include "nrf_log_ctrl.h"
|
|
||||||
#include "nrf_log_default_backends.h"
|
|
||||||
NRF_LOG_MODULE_REGISTER();
|
|
||||||
|
|
||||||
#define PAC_DATA_SIZE 8 // 8-byte ASCII card ID
|
|
||||||
|
|
||||||
// NRZ at RF/32: 32 carrier cycles per bit.
|
|
||||||
// With SAADC sampling at 1 sample per carrier cycle, 32 samples = 1 bit.
|
|
||||||
#define PAC_RF_PER_BIT 32
|
|
||||||
#define PAC_HALF_BIT 16 // Half-bit for rounding interval → nbits
|
|
||||||
#define PAC_MAX_BITS_RUN 20 // Max consecutive same-polarity bits we accept
|
|
||||||
|
|
||||||
// PAC frame is exactly 128 bits on T55xx (4 blocks Ă— 32 bits):
|
|
||||||
// 8-bit sync marker (0xFF) + 12 Ă— 10-bit UART frames = 128 bits
|
|
||||||
#define PAC_FRAME_BITS 128
|
|
||||||
#define PAC_PREAMBLE_BITS 19
|
|
||||||
|
|
||||||
// Preamble: 1111111100100000010 (19 bits) = 0x7F902
|
|
||||||
#define PAC_PREAMBLE 0x7F902UL
|
|
||||||
#define PAC_PREAMBLE_INV 0x006FDUL // Bitwise inverse masked to 19 bits
|
|
||||||
|
|
||||||
#define PAC_UART_FRAME_BITS 10
|
|
||||||
#define PAC_PAYLOAD_BYTES 12 // STX + '2' + '0' + 8 card ID + XOR checksum
|
|
||||||
#define PAC_STX 0x02
|
|
||||||
|
|
||||||
// ADC demodulation: spike-clipping + threshold with dead zone.
|
|
||||||
//
|
|
||||||
// Signal has 3 amplitude zones:
|
|
||||||
// NRZ low (~500-2000 ADC) — tag load modulation "0" state
|
|
||||||
// NRZ high (~4000-6000) — tag load modulation "1" state
|
|
||||||
// Spikes (~10k-14k) — LC ringing at NRZ transitions (8-20 cycles wide)
|
|
||||||
//
|
|
||||||
// Prescan (128 samples) finds the NRZ floor (raw_min), then spike_cap
|
|
||||||
// = max(raw_min * SPIKE_MULT, MIN_SPIKE_CAP) clips spikes while preserving
|
|
||||||
// the NRZ high level. MIN_SPIKE_CAP ensures spike_cap is never below the
|
|
||||||
// NRZ high level, even when raw_min correctly captures NRZ low.
|
|
||||||
#define PAC_PRESCAN_SAMPLES 128 // ~1ms: raw min detection
|
|
||||||
#define PAC_WARMUP_SAMPLES 600 // ~5ms: threshold calibration on clipped signal
|
|
||||||
#define PAC_SPIKE_MULT 3 // Clip at 3Ă— floor
|
|
||||||
#define PAC_MIN_SPIKE_CAP 8000 // Floor: preserves NRZ high (~5000) always
|
|
||||||
#define PAC_THRESH_FUZZ 75 // Dead zone: 25%-75% of clipped range
|
|
||||||
// Auto-recalibrate if no frame found within this many Phase 3 samples.
|
|
||||||
// ~5 frame periods = 5 Ă— 128 bits Ă— 32 samples/bit = 20480 samples (~164ms).
|
|
||||||
// Gives ~3 calibration attempts in a 500ms scan window.
|
|
||||||
#define PAC_RECAL_SAMPLES 20480
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
// NRZ shift register (128 bits)
|
|
||||||
uint64_t raw_hi; // upper 64 bits
|
|
||||||
uint64_t raw_lo; // lower 64 bits
|
|
||||||
bool polarity; // current NRZ level (toggled on each edge)
|
|
||||||
uint16_t bit_count; // total bits shifted in (capped at PAC_FRAME_BITS)
|
|
||||||
uint8_t card_id[PAC_DATA_SIZE];
|
|
||||||
|
|
||||||
// ADC → NRZ demodulation state (spike-clip + threshold with dead zone)
|
|
||||||
uint32_t total_samples; // total samples processed
|
|
||||||
int16_t raw_min; // minimum raw sample seen (for spike cap)
|
|
||||||
int32_t spike_cap; // clip level
|
|
||||||
int16_t clip_max; // max of clipped samples during warmup
|
|
||||||
int16_t clip_min; // min of clipped samples during warmup
|
|
||||||
int16_t thresh_high; // above this → bit=1
|
|
||||||
int16_t thresh_low; // below this → bit=0, between → keep previous
|
|
||||||
bool adc_state; // current demodulated binary level
|
|
||||||
bool has_signal; // true after first threshold crossing
|
|
||||||
uint32_t sample_count; // samples since last transition
|
|
||||||
uint32_t decode_samples; // Phase 3 samples since last calibration
|
|
||||||
} pac_codec;
|
|
||||||
|
|
||||||
// Shift one bit into the 128-bit register.
|
|
||||||
static void shift_bit(pac_codec *d, bool bit) {
|
|
||||||
d->raw_hi = (d->raw_hi << 1) | (d->raw_lo >> 63);
|
|
||||||
d->raw_lo = (d->raw_lo << 1) | (bit ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract a single bit from the 128-bit register.
|
|
||||||
// Position 0 = MSB of raw_hi (oldest), position 127 = LSB of raw_lo (newest).
|
|
||||||
static bool get_bit(pac_codec *d, uint16_t pos) {
|
|
||||||
if (pos < 64) {
|
|
||||||
return (d->raw_hi >> (63 - pos)) & 1;
|
|
||||||
}
|
|
||||||
return (d->raw_lo >> (127 - pos)) & 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode a 10-bit UART frame at bit position 'start'.
|
|
||||||
// Frame: start(0) + 7 data bits LSB-first + odd parity + stop(1).
|
|
||||||
static int decode_uart_byte(pac_codec *d, uint16_t start, bool inverted) {
|
|
||||||
#define RD(pos) (inverted ? !get_bit(d, (pos)) : get_bit(d, (pos)))
|
|
||||||
|
|
||||||
if (RD(start)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t byte_val = 0;
|
|
||||||
uint8_t ones = 0;
|
|
||||||
for (int i = 0; i < 7; i++) {
|
|
||||||
if (RD(start + 1 + i)) {
|
|
||||||
byte_val |= (1 << i);
|
|
||||||
ones++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RD(start + 8)) {
|
|
||||||
ones++;
|
|
||||||
}
|
|
||||||
if ((ones & 1) == 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!RD(start + 9)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef RD
|
|
||||||
return byte_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the 128-bit register contains a valid PAC frame.
|
|
||||||
static bool try_decode_frame(pac_codec *d, bool inverted) {
|
|
||||||
uint32_t preamble = 0;
|
|
||||||
for (int i = 0; i < PAC_PREAMBLE_BITS; i++) {
|
|
||||||
preamble = (preamble << 1) | (get_bit(d, i) ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t expected = inverted ? PAC_PREAMBLE_INV : PAC_PREAMBLE;
|
|
||||||
if (preamble != expected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t decoded[PAC_PAYLOAD_BYTES];
|
|
||||||
for (int i = 0; i < PAC_PAYLOAD_BYTES; i++) {
|
|
||||||
uint16_t frame_start = 8 + i * PAC_UART_FRAME_BITS;
|
|
||||||
int val = decode_uart_byte(d, frame_start, inverted);
|
|
||||||
if (val < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
decoded[i] = (uint8_t)val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decoded[0] != PAC_STX) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t xor_check = 0;
|
|
||||||
for (int i = 3; i < 3 + PAC_DATA_SIZE; i++) {
|
|
||||||
xor_check ^= decoded[i];
|
|
||||||
}
|
|
||||||
if (xor_check != decoded[11]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(d->card_id, &decoded[3], PAC_DATA_SIZE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process a demodulated NRZ edge interval (in samples = carrier cycles).
|
|
||||||
static bool pac_process_interval(pac_codec *d, uint32_t interval) {
|
|
||||||
uint32_t nbits = (interval + PAC_HALF_BIT) / PAC_RF_PER_BIT;
|
|
||||||
|
|
||||||
if (nbits < 1 || nbits > PAC_MAX_BITS_RUN) {
|
|
||||||
d->raw_hi = 0;
|
|
||||||
d->raw_lo = 0;
|
|
||||||
d->polarity = false;
|
|
||||||
d->bit_count = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < nbits; i++) {
|
|
||||||
shift_bit(d, d->polarity);
|
|
||||||
if (d->bit_count < PAC_FRAME_BITS) {
|
|
||||||
d->bit_count++;
|
|
||||||
}
|
|
||||||
if (d->bit_count >= PAC_FRAME_BITS) {
|
|
||||||
if (try_decode_frame(d, false) || try_decode_frame(d, true)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d->polarity = !d->polarity;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static pac_codec *pac_alloc(void) {
|
|
||||||
pac_codec *codec = malloc(sizeof(pac_codec));
|
|
||||||
return codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pac_free(pac_codec *d) {
|
|
||||||
free(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t *pac_get_data(pac_codec *d) {
|
|
||||||
return d->card_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pac_decoder_start(pac_codec *d, uint8_t format) {
|
|
||||||
memset(d, 0, sizeof(pac_codec));
|
|
||||||
d->raw_min = 32767; // INT16_MAX: first sample updates it
|
|
||||||
d->spike_cap = 0x7FFFFFFF; // INT32_MAX: no capping until prescan completes
|
|
||||||
d->clip_max = -32768; // INT16_MIN: first clipped sample updates it
|
|
||||||
d->clip_min = 32767; // INT16_MAX: first clipped sample updates it
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feed a raw ADC sample (one per carrier cycle at 125kHz).
|
|
||||||
// Spike-clipping + threshold with dead zone (PM3 nrzRawDemod style).
|
|
||||||
static bool pac_decoder_feed(pac_codec *d, uint16_t raw_sample) {
|
|
||||||
int16_t sample = (int16_t)raw_sample;
|
|
||||||
d->total_samples++;
|
|
||||||
|
|
||||||
// Phase 1: Prescan — track raw minimum to find the NRZ floor.
|
|
||||||
if (d->total_samples <= PAC_PRESCAN_SAMPLES) {
|
|
||||||
if (sample < d->raw_min && sample > 0) {
|
|
||||||
d->raw_min = sample;
|
|
||||||
}
|
|
||||||
if (d->total_samples == PAC_PRESCAN_SAMPLES) {
|
|
||||||
d->spike_cap = (int32_t)d->raw_min * PAC_SPIKE_MULT;
|
|
||||||
if (d->spike_cap < PAC_MIN_SPIKE_CAP) {
|
|
||||||
d->spike_cap = PAC_MIN_SPIKE_CAP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip spikes: LC ringing transients are replaced with the cap level.
|
|
||||||
if (sample > d->spike_cap) {
|
|
||||||
sample = d->spike_cap;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t warmup_samples = d->total_samples - PAC_PRESCAN_SAMPLES;
|
|
||||||
|
|
||||||
// Phase 2: Warmup — track min/max of clipped samples to find NRZ levels.
|
|
||||||
if (warmup_samples <= PAC_WARMUP_SAMPLES) {
|
|
||||||
if (sample > d->clip_max) d->clip_max = sample;
|
|
||||||
if (sample < d->clip_min) d->clip_min = sample;
|
|
||||||
if (warmup_samples == PAC_WARMUP_SAMPLES) {
|
|
||||||
int16_t range = d->clip_max - d->clip_min;
|
|
||||||
d->thresh_high = d->clip_min + (range * PAC_THRESH_FUZZ) / 100;
|
|
||||||
d->thresh_low = d->clip_min + (range * (100 - PAC_THRESH_FUZZ)) / 100;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 3: Per-sample threshold with dead zone.
|
|
||||||
// Auto-recalibrate if no frame found after enough decode samples —
|
|
||||||
// the one-shot calibration may have captured an unlucky NRZ segment.
|
|
||||||
d->decode_samples++;
|
|
||||||
if (d->decode_samples >= PAC_RECAL_SAMPLES) {
|
|
||||||
pac_decoder_start(d, 0);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
d->sample_count++;
|
|
||||||
|
|
||||||
bool new_state = d->adc_state;
|
|
||||||
if (sample >= d->thresh_high) {
|
|
||||||
new_state = true;
|
|
||||||
} else if (sample <= d->thresh_low) {
|
|
||||||
new_state = false;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!d->has_signal) {
|
|
||||||
d->has_signal = true;
|
|
||||||
d->adc_state = new_state;
|
|
||||||
d->sample_count = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new_state == d->adc_state) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transition detected — process the interval
|
|
||||||
uint32_t interval = d->sample_count;
|
|
||||||
d->sample_count = 0;
|
|
||||||
d->adc_state = new_state;
|
|
||||||
|
|
||||||
return pac_process_interval(d, interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Modulator (emulation) ---
|
|
||||||
|
|
||||||
static nrf_pwm_values_wave_form_t m_pac_pwm_seq_vals[PAC_FRAME_BITS] = {};
|
|
||||||
|
|
||||||
static const nrf_pwm_sequence_t m_pac_pwm_seq = {
|
|
||||||
.values.p_wave_form = m_pac_pwm_seq_vals,
|
|
||||||
.length = NRF_PWM_VALUES_LENGTH(m_pac_pwm_seq_vals),
|
|
||||||
.repeats = 0,
|
|
||||||
.end_delay = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build the 128-bit NRZ bitstream from 8-byte card ID.
|
|
||||||
// Frame: 0xFF sync (8 bits) + 12 Ă— 10-bit UART frames = 128 bits.
|
|
||||||
// UART frame: start(0) + 7 data bits LSB-first + odd parity + stop(1).
|
|
||||||
// Payload bytes: STX(0x02), '2', '0', card_id[0..7], XOR checksum.
|
|
||||||
static void pac_build_bitstream(const uint8_t *card_id, uint8_t *bits_out) {
|
|
||||||
uint8_t payload[PAC_PAYLOAD_BYTES];
|
|
||||||
payload[0] = PAC_STX;
|
|
||||||
payload[1] = '2';
|
|
||||||
payload[2] = '0';
|
|
||||||
memcpy(&payload[3], card_id, PAC_DATA_SIZE);
|
|
||||||
|
|
||||||
// XOR checksum over card ID bytes (indices 3..10)
|
|
||||||
uint8_t xor_check = 0;
|
|
||||||
for (int i = 3; i < 3 + PAC_DATA_SIZE; i++) {
|
|
||||||
xor_check ^= payload[i];
|
|
||||||
}
|
|
||||||
payload[11] = xor_check;
|
|
||||||
|
|
||||||
int bit_pos = 0;
|
|
||||||
|
|
||||||
// 8-bit sync marker: 0xFF (all ones)
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
bits_out[bit_pos++] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 12 UART frames
|
|
||||||
for (int f = 0; f < PAC_PAYLOAD_BYTES; f++) {
|
|
||||||
uint8_t byte_val = payload[f];
|
|
||||||
|
|
||||||
// Start bit (0)
|
|
||||||
bits_out[bit_pos++] = 0;
|
|
||||||
|
|
||||||
// 7 data bits, LSB first
|
|
||||||
uint8_t ones = 0;
|
|
||||||
for (int i = 0; i < 7; i++) {
|
|
||||||
uint8_t bit = (byte_val >> i) & 1;
|
|
||||||
bits_out[bit_pos++] = bit;
|
|
||||||
ones += bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Odd parity: set so total ones (data + parity) is odd
|
|
||||||
uint8_t parity = (ones & 1) ? 0 : 1;
|
|
||||||
bits_out[bit_pos++] = parity;
|
|
||||||
|
|
||||||
// Stop bit (1)
|
|
||||||
bits_out[bit_pos++] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const nrf_pwm_sequence_t *pac_modulator(pac_codec *d, uint8_t *buf) {
|
|
||||||
uint8_t bits[PAC_FRAME_BITS];
|
|
||||||
pac_build_bitstream(buf, bits);
|
|
||||||
|
|
||||||
// NRZ: output must be CONSTANT within each bit period (no mid-bit transition).
|
|
||||||
// Per nRF52840 PS: compare >= counter_top → pin held HIGH; compare = 0 → pin held LOW.
|
|
||||||
// Use compare = counter_top + 1 (not counter_top) to avoid the compare == counter_top
|
|
||||||
// boundary where a 1-tick output glitch may occur due to simultaneous compare-match
|
|
||||||
// and counter-wrap. Real PAC readers with hardware edge detection are sensitive to this;
|
|
||||||
// PM3's software NRZ demod is not (it averages over the bit period).
|
|
||||||
for (int i = 0; i < PAC_FRAME_BITS; i++) {
|
|
||||||
m_pac_pwm_seq_vals[i].channel_0 = bits[i] ? (PAC_RF_PER_BIT + 1) : 0;
|
|
||||||
m_pac_pwm_seq_vals[i].counter_top = PAC_RF_PER_BIT;
|
|
||||||
}
|
|
||||||
return &m_pac_pwm_seq;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define PAC_T55XX_BLOCK_COUNT 5 // 1 config + 4 data blocks
|
|
||||||
|
|
||||||
uint8_t pac_t55xx_writer(uint8_t *data, uint32_t *blks) {
|
|
||||||
uint8_t bits[PAC_FRAME_BITS];
|
|
||||||
pac_build_bitstream(data, bits);
|
|
||||||
|
|
||||||
blks[0] = T5577_PAC_CONFIG;
|
|
||||||
for (int b = 0; b < 4; b++) {
|
|
||||||
uint32_t word = 0;
|
|
||||||
for (int i = 0; i < 32; i++) {
|
|
||||||
word = (word << 1) | bits[b * 32 + i];
|
|
||||||
}
|
|
||||||
blks[b + 1] = word;
|
|
||||||
}
|
|
||||||
return PAC_T55XX_BLOCK_COUNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const protocol pac = {
|
|
||||||
.tag_type = TAG_TYPE_PAC,
|
|
||||||
.data_size = PAC_DATA_SIZE,
|
|
||||||
.alloc = (codec_alloc)pac_alloc,
|
|
||||||
.free = (codec_free)pac_free,
|
|
||||||
.get_data = (codec_get_data)pac_get_data,
|
|
||||||
.modulator = (modulator)pac_modulator,
|
|
||||||
.decoder =
|
|
||||||
{
|
|
||||||
.start = (decoder_start)pac_decoder_start,
|
|
||||||
.feed = (decoder_feed)pac_decoder_feed,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "protocols.h"
|
|
||||||
|
|
||||||
extern const protocol pac;
|
|
||||||
uint8_t pac_t55xx_writer(uint8_t *data, uint32_t *blks);
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user