Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...

## [unreleased][unreleased]
- Added IDTECK LF protocol support: tag emulation (PSK1 RF/32) and T55xx clone. No reader path yet; PSK demodulation on the envelope-only receive chain is left for a follow-up.
- Added PAC/Stanley LF protocol support: read, emulate and T55xx clone (@kevihiiin, @danieltwagner)
- Fix firmware application USB serial number (@taichunmin)
- Added ioProx LF protocol support (read, emulate and T55xx clone)
Expand Down
2 changes: 2 additions & 0 deletions firmware/application/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ SRC_FILES += \
$(PROJ_DIR)/rfid/nfctag/lf/utils/fskdemod.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/circular_buffer.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/manchester.c \
$(PROJ_DIR)/rfid/nfctag/lf/utils/psk1.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/em410x.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/hidprox.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/pac.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/ioprox.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/viking.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/wiegand.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/idteck.c \
$(PROJ_DIR)/utils/dataframe.c \
$(PROJ_DIR)/utils/delayed_reset.c \
$(PROJ_DIR)/utils/fds_util.c \
Expand Down
47 changes: 47 additions & 0 deletions firmware/application/src/app_cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,50 @@ static data_frame_tx_t *cmd_processor_ioprox_set_emu_id(uint16_t cmd, uint16_t s
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
}

static data_frame_tx_t *cmd_processor_idteck_set_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
if (length != LF_IDTECK_TAG_ID_SIZE) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_IDTECK);
memcpy(buffer->buffer, data, LF_IDTECK_TAG_ID_SIZE);
tag_emulation_load_by_buffer(TAG_TYPE_IDTECK, false);
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
}

static data_frame_tx_t *cmd_processor_idteck_get_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
tag_slot_specific_type_t tag_types;
tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types);
if (tag_types.tag_lf != TAG_TYPE_IDTECK) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, data);
}
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_IDTECK);
return data_frame_make(cmd, STATUS_SUCCESS, LF_IDTECK_TAG_ID_SIZE, buffer->buffer);
}

#if defined(PROJECT_CHAMELEON_ULTRA)
// T55xx clone is only available on Chameleon Ultra; the Lite firmware
// has no LF reader hardware and does not compile the write_*_to_t55xx
// helpers in lf_reader_main.c.
static data_frame_tx_t *cmd_processor_idteck_write_to_t55xx(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
typedef struct {
uint8_t card_data[LF_IDTECK_TAG_ID_SIZE];
uint8_t new_key[4];
uint8_t old_keys[4];
} PACKED payload_t;

payload_t *payload = (payload_t *)data;

if (length < sizeof(payload_t) ||
(length - offsetof(payload_t, old_keys)) % sizeof(payload->old_keys) != 0) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}

uint8_t old_cnt = (length - offsetof(payload_t, old_keys)) / sizeof(payload->old_keys);
status = write_idteck_to_t55xx(payload->card_data, payload->new_key, payload->old_keys, old_cnt);
return data_frame_make(cmd, status, 0, NULL);
}
#endif

static data_frame_tx_t *cmd_processor_ioprox_get_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
tag_slot_specific_type_t tag_types;
tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types);
Expand Down Expand Up @@ -2646,6 +2690,7 @@ static cmd_data_map_t m_data_cmd_map[] = {
{ DATA_CMD_IOPROX_WRITE_TO_T55XX, before_reader_run, cmd_processor_ioprox_write_to_t55xx, NULL },
{ DATA_CMD_PAC_SCAN, before_reader_run, cmd_processor_pac_scan, NULL },
{ DATA_CMD_PAC_WRITE_TO_T55XX, before_reader_run, cmd_processor_pac_write_to_t55xx, NULL },
{ DATA_CMD_IDTECK_WRITE_TO_T55XX, before_reader_run, cmd_processor_idteck_write_to_t55xx, NULL },
{ DATA_CMD_LF_T55XX_WRITE, before_reader_run, cmd_processor_lf_t55xx_write, NULL },
{ DATA_CMD_ADC_GENERIC_READ, before_reader_run, cmd_processor_generic_read, NULL },

Expand Down Expand Up @@ -2714,6 +2759,8 @@ static cmd_data_map_t m_data_cmd_map[] = {
{ DATA_CMD_VIKING_GET_EMU_ID, NULL, cmd_processor_viking_get_emu_id, NULL },
{ DATA_CMD_PAC_SET_EMU_ID, NULL, cmd_processor_pac_set_emu_id, NULL },
{ DATA_CMD_PAC_GET_EMU_ID, NULL, cmd_processor_pac_get_emu_id, NULL },
{ DATA_CMD_IDTECK_SET_EMU_ID, NULL, cmd_processor_idteck_set_emu_id, NULL },
{ DATA_CMD_IDTECK_GET_EMU_ID, NULL, cmd_processor_idteck_get_emu_id, NULL },
/* ISO14443-4 T=CL emulation */
#if defined(PROJECT_CHAMELEON_ULTRA)
/* ISO14443-4 T=CL emulation */
Expand Down
3 changes: 3 additions & 0 deletions firmware/application/src/data_cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
#define DATA_CMD_IOPROX_DECODE_RAW (3012)
#define DATA_CMD_IOPROX_COMPOSE_ID (3013)
#define DATA_CMD_LF_T55XX_WRITE (3016)
#define DATA_CMD_IDTECK_WRITE_TO_T55XX (3017)

//
// ******************************************************************
Expand Down Expand Up @@ -187,6 +188,8 @@
#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_IDTECK_SET_EMU_ID (5010)
#define DATA_CMD_IDTECK_GET_EMU_ID (5011)

#define DATA_CMD_EM4X05_SCAN (3030)
#define DATA_CMD_EM4X05_READSNIFF (3032)
Expand Down
40 changes: 37 additions & 3 deletions firmware/application/src/rfid/nfctag/lf/lf_tag_em.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "nrfx_pwm.h"
#include "protocols/em410x.h"
#include "protocols/hidprox.h"
#include "protocols/idteck.h"
#include "protocols/ioprox.h"
#include "protocols/pac.h"
#include "protocols/viking.h"
Expand Down Expand Up @@ -128,7 +129,13 @@ static void pwm_init(void) {
cfg.output_pins[i] = NRFX_PWM_PIN_NOT_USED;
}
cfg.irq_priority = APP_IRQ_PRIORITY_LOW;
cfg.base_clock = NRF_PWM_CLK_125kHz;
// Base clock depends on the currently-loaded tag type. Legacy ASK/FSK
// protocols (EM410x, HID, ioProx, Viking, PAC) use 125kHz base so that
// their hardcoded counter_top values (8-64 range) produce the correct
// absolute timing. PSK1 protocols need finer resolution for the 16us
// subcarrier period, so pwm_init uses 1MHz base with counter_top=16.
// See tag_base_type.h IS_PSK1_TYPE for the list of qualifying types.
cfg.base_clock = IS_PSK1_TYPE(m_tag_type) ? NRF_PWM_CLK_1MHz : NRF_PWM_CLK_125kHz;
cfg.count_mode = NRF_PWM_MODE_UP;
cfg.load_mode = NRF_PWM_LOAD_WAVE_FORM;
cfg.step_mode = NRF_PWM_STEP_AUTO;
Expand All @@ -143,8 +150,12 @@ static void lf_sense_enable(void) {
// 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.
// ±40 ppm, which is also tight enough for differential PSK encodings
// (e.g. IDTECK) where what the reader decodes are bit-to-bit phase
// transitions, so absolute phase lock to the reader's carrier is not
// required. The tag-mode antenna taps on this board are envelope-only,
// which rules out coherent demodulation or phase-lock-based approaches,
// but does not preclude the differential-phase encodings supported here.
//
// Paired release in lf_sense_disable(). SD reference-counts HFXO requests,
// so this coexists with BLE. Both functions run from thread context
Expand Down Expand Up @@ -253,6 +264,15 @@ int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
return LF_PAC_TAG_ID_SIZE;
}

if (type == TAG_TYPE_IDTECK && buffer->length >= LF_IDTECK_TAG_ID_SIZE) {
m_tag_type = type;
void *codec = idteck.alloc();
m_pwm_seq = idteck.modulator(codec, buffer->buffer);
idteck.free(codec);
NRF_LOG_INFO("load lf idteck data finish.");
return LF_IDTECK_TAG_ID_SIZE;
}

NRF_LOG_ERROR("no valid data exists in buffer for tag type: %d.", type);
return 0;
}
Expand Down Expand Up @@ -385,3 +405,17 @@ bool lf_tag_pac_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
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));
}

/** @brief IDTECK data save callback. */
int lf_tag_idteck_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
return m_tag_type == TAG_TYPE_IDTECK ? LF_IDTECK_TAG_ID_SIZE : 0;
}

/** @brief IDTECK default frame: preamble "IDTK" + 32-bit placeholder card data. */
bool lf_tag_idteck_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
uint8_t tag_id[LF_IDTECK_TAG_ID_SIZE] = {
0x49, 0x44, 0x54, 0x4B, // "IDTK" preamble (MSB first)
0xDE, 0xAD, 0xBE, 0xEF, // default card data
};
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
}
3 changes: 3 additions & 0 deletions firmware/application/src/rfid/nfctag/lf/lf_tag_em.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#define LF_HIDPROX_TAG_ID_SIZE 13
#define LF_VIKING_TAG_ID_SIZE 4
#define LF_PAC_TAG_ID_SIZE 8
#define LF_IDTECK_TAG_ID_SIZE 8

void lf_tag_125khz_sense_switch(bool enable);
int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
Expand All @@ -24,4 +25,6 @@ int lf_tag_viking_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffe
bool lf_tag_viking_data_factory(uint8_t slot, tag_specific_type_t tag_type);
int lf_tag_pac_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool lf_tag_pac_data_factory(uint8_t slot, tag_specific_type_t tag_type);
int lf_tag_idteck_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool lf_tag_idteck_data_factory(uint8_t slot, tag_specific_type_t tag_type);
bool is_lf_field_exists(void);
97 changes: 97 additions & 0 deletions firmware/application/src/rfid/nfctag/lf/protocols/idteck.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "idteck.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"
#include "utils/psk1.h"

// IDTECK: 64-bit PSK1 frame at RF/32. The 32-bit fixed preamble 0x4944544B
// ("IDTK") occupies the first four bytes of the frame; the remaining four
// bytes are the card payload (a one-byte checksum followed by a 24-bit card
// number in a byte-reversed layout, matching the format used by common
// IDTECK readers; see cmdlfidteck.c in the Proxmark3 client for details).

#define IDTECK_PWM_ENTRIES (IDTECK_BIT_COUNT * LF_PSK1_RF32_SUBCYCLES_PER_BIT)
#define IDTECK_T55XX_BLOCK_COUNT (3) // config word + 2 data blocks

static nrf_pwm_values_wave_form_t m_idteck_pwm_seq_vals[IDTECK_PWM_ENTRIES] = {};

static nrf_pwm_sequence_t m_idteck_pwm_seq = {
.values.p_wave_form = m_idteck_pwm_seq_vals,
.length = NRF_PWM_VALUES_LENGTH(m_idteck_pwm_seq_vals),
.repeats = 0,
.end_delay = 0,
};

static idteck_codec *idteck_alloc(void) {
idteck_codec *d = malloc(sizeof(idteck_codec));
memset(d->data, 0, IDTECK_DATA_SIZE);
return d;
}

static void idteck_free(idteck_codec *d) {
free(d);
}

static uint8_t *idteck_get_data(idteck_codec *d) {
return d->data;
}

// PSK demodulation is not implemented. The tag-emulation ADC path runs at
// 125kHz and is envelope-filtered, so recovering subcarrier phase for read
// would require a dedicated decoder (edge-timing based). These stubs keep the
// protocol struct complete; a future change can fill them in without touching
// the struct layout.
static void idteck_decoder_start(idteck_codec *d, uint8_t format) {
(void)d;
(void)format;
}

static bool idteck_decoder_feed(idteck_codec *d, uint16_t val) {
(void)d;
(void)val;
return false;
}

// buf is the 8-byte frame to transmit, MSB first on air. For standard IDTECK
// the first four bytes are the fixed preamble and the last four are the card
// payload; the CLI layer is responsible for composing them.
static const nrf_pwm_sequence_t *idteck_modulator(idteck_codec *d, uint8_t *buf) {
(void)d;

size_t n = lf_psk1_build_sequence(buf, IDTECK_BIT_COUNT,
m_idteck_pwm_seq_vals, IDTECK_PWM_ENTRIES);
m_idteck_pwm_seq.length = (uint16_t)(n * 4); // 4 uint16 fields per wave-form entry
return &m_idteck_pwm_seq;
}

const protocol idteck = {
.tag_type = TAG_TYPE_IDTECK,
.data_size = IDTECK_DATA_SIZE,
.alloc = (codec_alloc)idteck_alloc,
.free = (codec_free)idteck_free,
.get_data = (codec_get_data)idteck_get_data,
.modulator = (modulator)idteck_modulator,
.decoder =
{
.start = (decoder_start)idteck_decoder_start,
.feed = (decoder_feed)idteck_decoder_feed,
},
};

// T5577 writer: block 0 holds the PSK1 RF/32 configuration, blocks 1-2 hold
// the 64-bit frame big-endian.
uint8_t idteck_t55xx_writer(uint8_t *uid, uint32_t *blks) {
uint32_t hi = 0, lo = 0;
for (int i = 0; i < 4; i++) hi = (hi << 8) | uid[i];
for (int i = 4; i < 8; i++) lo = (lo << 8) | uid[i];
blks[0] = T5577_IDTECK_CONFIG;
blks[1] = hi;
blks[2] = lo;
return IDTECK_T55XX_BLOCK_COUNT;
}
16 changes: 16 additions & 0 deletions firmware/application/src/rfid/nfctag/lf/protocols/idteck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include "protocols.h"

// IDTECK frame layout: 32-bit fixed preamble + 32-bit card payload.
#define IDTECK_DATA_SIZE (8) // 8 bytes = 64 bits on air
#define IDTECK_BIT_COUNT (64)
#define IDTECK_PREAMBLE (0x4944544BU) // ASCII "IDTK", MSB first on air

typedef struct {
uint8_t data[IDTECK_DATA_SIZE];
} idteck_codec;

extern const protocol idteck;

uint8_t idteck_t55xx_writer(uint8_t *uid, uint32_t *blks);
8 changes: 8 additions & 0 deletions firmware/application/src/rfid/nfctag/lf/protocols/t55xx.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ extern "C" {
T5577_PWD | \
(4 << T5577_MAXBLOCK_SHIFT))

// IDTECK: PSK1 at RF/32, subcarrier = carrier/2 (RF_2), 2 data blocks (64-bit frame).
#define T5577_IDTECK_CONFIG ( \
T5577_BITRATE_RF_32 | \
T5577_MODULATION_PSK1 | \
T5577_PSKCF_RF_2 | \
T5577_PWD | \
(2 << T5577_MAXBLOCK_SHIFT))

#if defined(PROJECT_CHAMELEON_ULTRA)
void t55xx_write_data(uint32_t passwd, uint32_t *blks, uint8_t blk_count);
void t55xx_reset_passwd(uint32_t old_passwd, uint32_t new_passwd);
Expand Down
48 changes: 48 additions & 0 deletions firmware/application/src/rfid/nfctag/lf/utils/psk1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "psk1.h"

// Extract bit at index `bit_idx` from a MSB-first bit stream stored in
// frame_bytes. bit_idx=0 is the MSB of frame_bytes[0].
static inline bool read_bit_msb_first(const uint8_t *frame_bytes, size_t bit_idx) {
return (frame_bytes[bit_idx / 8] >> (7 - (bit_idx % 8))) & 1U;
}

// Extract the last bit of the frame (LSB of the last byte that contains a
// bit). For frames whose bit_count is not a multiple of 8 this still points
// at the final transmitted bit.
static inline bool read_last_bit(const uint8_t *frame_bytes, size_t bit_count) {
size_t last_idx = bit_count - 1;
return read_bit_msb_first(frame_bytes, last_idx);
}

size_t lf_psk1_build_sequence(const uint8_t *frame_bytes,
size_t bit_count,
nrf_pwm_values_wave_form_t *out_buf,
size_t out_capacity) {
size_t required = bit_count * LF_PSK1_RF32_SUBCYCLES_PER_BIT;
if (required > out_capacity) {
return 0;
}

bool phase = false;
bool last_bit = read_last_bit(frame_bytes, bit_count);

size_t k = 0;
for (size_t bit_idx = 0; bit_idx < bit_count; bit_idx++) {
bool cur_bit = read_bit_msb_first(frame_bytes, bit_idx);
if (cur_bit != last_bit) {
phase = !phase;
}
last_bit = cur_bit;

uint16_t pol = phase ? (1U << 15) : 0;
for (size_t c = 0; c < LF_PSK1_RF32_SUBCYCLES_PER_BIT; c++) {
out_buf[k].channel_0 = pol | LF_PSK1_SUBCARRIER_DUTY;
out_buf[k].channel_1 = 0;
out_buf[k].channel_2 = 0;
out_buf[k].counter_top = LF_PSK1_SUBCARRIER_TOP;
k++;
}
}

return k;
}
Loading
Loading