Skip to content

Commit 1a014f9

Browse files
authored
Merge pull request #407 from matteoscrugli/feature/lf-idteck-psk1-emulation
Feature/lf: add IDTECK PSK1 tag emulation and T55xx clone
2 parents 013f1f5 + 1e78976 commit 1a014f9

18 files changed

Lines changed: 544 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
33
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...
44

55
## [unreleased][unreleased]
6+
- 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.
67
- Added PAC/Stanley LF protocol support: read, emulate and T55xx clone (@kevihiiin, @danieltwagner)
78
- Fix firmware application USB serial number (@taichunmin)
89
- Added ioProx LF protocol support (read, emulate and T55xx clone)

firmware/application/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ SRC_FILES += \
3535
$(PROJ_DIR)/rfid/nfctag/lf/utils/fskdemod.c \
3636
$(PROJ_DIR)/rfid/nfctag/lf/utils/circular_buffer.c \
3737
$(PROJ_DIR)/rfid/nfctag/lf/utils/manchester.c \
38+
$(PROJ_DIR)/rfid/nfctag/lf/utils/psk1.c \
3839
$(PROJ_DIR)/rfid/nfctag/lf/protocols/em410x.c \
3940
$(PROJ_DIR)/rfid/nfctag/lf/protocols/hidprox.c \
4041
$(PROJ_DIR)/rfid/nfctag/lf/protocols/pac.c \
4142
$(PROJ_DIR)/rfid/nfctag/lf/protocols/ioprox.c \
4243
$(PROJ_DIR)/rfid/nfctag/lf/protocols/viking.c \
4344
$(PROJ_DIR)/rfid/nfctag/lf/protocols/wiegand.c \
45+
$(PROJ_DIR)/rfid/nfctag/lf/protocols/idteck.c \
4446
$(PROJ_DIR)/utils/dataframe.c \
4547
$(PROJ_DIR)/utils/delayed_reset.c \
4648
$(PROJ_DIR)/utils/fds_util.c \

firmware/application/src/app_cmd.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,50 @@ static data_frame_tx_t *cmd_processor_ioprox_set_emu_id(uint16_t cmd, uint16_t s
11201120
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
11211121
}
11221122

1123+
static data_frame_tx_t *cmd_processor_idteck_set_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
1124+
if (length != LF_IDTECK_TAG_ID_SIZE) {
1125+
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
1126+
}
1127+
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_IDTECK);
1128+
memcpy(buffer->buffer, data, LF_IDTECK_TAG_ID_SIZE);
1129+
tag_emulation_load_by_buffer(TAG_TYPE_IDTECK, false);
1130+
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
1131+
}
1132+
1133+
static data_frame_tx_t *cmd_processor_idteck_get_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
1134+
tag_slot_specific_type_t tag_types;
1135+
tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types);
1136+
if (tag_types.tag_lf != TAG_TYPE_IDTECK) {
1137+
return data_frame_make(cmd, STATUS_PAR_ERR, 0, data);
1138+
}
1139+
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_IDTECK);
1140+
return data_frame_make(cmd, STATUS_SUCCESS, LF_IDTECK_TAG_ID_SIZE, buffer->buffer);
1141+
}
1142+
1143+
#if defined(PROJECT_CHAMELEON_ULTRA)
1144+
// T55xx clone is only available on Chameleon Ultra; the Lite firmware
1145+
// has no LF reader hardware and does not compile the write_*_to_t55xx
1146+
// helpers in lf_reader_main.c.
1147+
static data_frame_tx_t *cmd_processor_idteck_write_to_t55xx(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
1148+
typedef struct {
1149+
uint8_t card_data[LF_IDTECK_TAG_ID_SIZE];
1150+
uint8_t new_key[4];
1151+
uint8_t old_keys[4];
1152+
} PACKED payload_t;
1153+
1154+
payload_t *payload = (payload_t *)data;
1155+
1156+
if (length < sizeof(payload_t) ||
1157+
(length - offsetof(payload_t, old_keys)) % sizeof(payload->old_keys) != 0) {
1158+
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
1159+
}
1160+
1161+
uint8_t old_cnt = (length - offsetof(payload_t, old_keys)) / sizeof(payload->old_keys);
1162+
status = write_idteck_to_t55xx(payload->card_data, payload->new_key, payload->old_keys, old_cnt);
1163+
return data_frame_make(cmd, status, 0, NULL);
1164+
}
1165+
#endif
1166+
11231167
static data_frame_tx_t *cmd_processor_ioprox_get_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
11241168
tag_slot_specific_type_t tag_types;
11251169
tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types);
@@ -2956,6 +3000,7 @@ static cmd_data_map_t m_data_cmd_map[] = {
29563000
{ DATA_CMD_IOPROX_WRITE_TO_T55XX, before_reader_run, cmd_processor_ioprox_write_to_t55xx, NULL },
29573001
{ DATA_CMD_PAC_SCAN, before_reader_run, cmd_processor_pac_scan, NULL },
29583002
{ DATA_CMD_PAC_WRITE_TO_T55XX, before_reader_run, cmd_processor_pac_write_to_t55xx, NULL },
3003+
{ DATA_CMD_IDTECK_WRITE_TO_T55XX, before_reader_run, cmd_processor_idteck_write_to_t55xx, NULL },
29593004
{ DATA_CMD_LF_T55XX_WRITE, before_reader_run, cmd_processor_lf_t55xx_write, NULL },
29603005
{ DATA_CMD_ADC_GENERIC_READ, before_reader_run, cmd_processor_generic_read, NULL },
29613006

@@ -3027,6 +3072,8 @@ static cmd_data_map_t m_data_cmd_map[] = {
30273072
{ DATA_CMD_VIKING_GET_EMU_ID, NULL, cmd_processor_viking_get_emu_id, NULL },
30283073
{ DATA_CMD_PAC_SET_EMU_ID, NULL, cmd_processor_pac_set_emu_id, NULL },
30293074
{ DATA_CMD_PAC_GET_EMU_ID, NULL, cmd_processor_pac_get_emu_id, NULL },
3075+
{ DATA_CMD_IDTECK_SET_EMU_ID, NULL, cmd_processor_idteck_set_emu_id, NULL },
3076+
{ DATA_CMD_IDTECK_GET_EMU_ID, NULL, cmd_processor_idteck_get_emu_id, NULL },
30303077
/* ISO14443-4 T=CL emulation */
30313078
#if defined(PROJECT_CHAMELEON_ULTRA)
30323079
/* ISO14443-4 T=CL emulation */

firmware/application/src/data_cmd.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
#define DATA_CMD_IOPROX_DECODE_RAW (3012)
111111
#define DATA_CMD_IOPROX_COMPOSE_ID (3013)
112112
#define DATA_CMD_LF_T55XX_WRITE (3016)
113+
#define DATA_CMD_IDTECK_WRITE_TO_T55XX (3018)
113114

114115
//
115116
// ******************************************************************
@@ -192,6 +193,8 @@
192193
#define DATA_CMD_PAC_GET_EMU_ID (5007)
193194
#define DATA_CMD_IOPROX_SET_EMU_ID (5008)
194195
#define DATA_CMD_IOPROX_GET_EMU_ID (5009)
196+
#define DATA_CMD_IDTECK_SET_EMU_ID (5012)
197+
#define DATA_CMD_IDTECK_GET_EMU_ID (5013)
195198

196199
#define DATA_CMD_EM4X05_SCAN (3030)
197200
#define DATA_CMD_EM4X05_READSNIFF (3032)

firmware/application/src/rfid/nfctag/lf/lf_tag_em.c

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "nrfx_pwm.h"
1111
#include "protocols/em410x.h"
1212
#include "protocols/hidprox.h"
13+
#include "protocols/idteck.h"
1314
#include "protocols/ioprox.h"
1415
#include "protocols/pac.h"
1516
#include "protocols/viking.h"
@@ -133,7 +134,13 @@ static void pwm_init(void) {
133134
cfg.output_pins[i] = NRFX_PWM_PIN_NOT_USED;
134135
}
135136
cfg.irq_priority = APP_IRQ_PRIORITY_LOW;
136-
cfg.base_clock = NRF_PWM_CLK_125kHz;
137+
// Base clock depends on the currently-loaded tag type. Legacy ASK/FSK
138+
// protocols (EM410x, HID, ioProx, Viking, PAC) use 125kHz base so that
139+
// their hardcoded counter_top values (8-64 range) produce the correct
140+
// absolute timing. PSK1 protocols need finer resolution for the 16us
141+
// subcarrier period, so pwm_init uses 1MHz base with counter_top=16.
142+
// See tag_base_type.h IS_PSK1_TYPE for the list of qualifying types.
143+
cfg.base_clock = IS_PSK1_TYPE(m_tag_type) ? NRF_PWM_CLK_1MHz : NRF_PWM_CLK_125kHz;
137144
cfg.count_mode = NRF_PWM_MODE_UP;
138145
cfg.load_mode = NRF_PWM_LOAD_WAVE_FORM;
139146
cfg.step_mode = NRF_PWM_STEP_AUTO;
@@ -148,8 +155,12 @@ static void lf_sense_enable(void) {
148155
// chip-to-chip spread that NRZ readers — which see cumulative error across
149156
// runs of same-polarity bits with no intra-run resync — reject even when
150157
// Manchester/FSK readers don't. Holding HFXO brings the PWM clock to
151-
// ±40 ppm. We can't lock to the reader's carrier (tag-mode antenna taps
152-
// on this board are envelope-only), so this is as good as it gets.
158+
// ±40 ppm, which is also tight enough for differential PSK encodings
159+
// (e.g. IDTECK) where what the reader decodes are bit-to-bit phase
160+
// transitions, so absolute phase lock to the reader's carrier is not
161+
// required. The tag-mode antenna taps on this board are envelope-only,
162+
// which rules out coherent demodulation or phase-lock-based approaches,
163+
// but does not preclude the differential-phase encodings supported here.
153164
//
154165
// Paired release in lf_sense_disable(). SD reference-counts HFXO requests,
155166
// so this coexists with BLE. Both functions run from thread context
@@ -258,6 +269,15 @@ int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
258269
return LF_PAC_TAG_ID_SIZE;
259270
}
260271

272+
if (type == TAG_TYPE_IDTECK && buffer->length >= LF_IDTECK_TAG_ID_SIZE) {
273+
m_tag_type = type;
274+
void *codec = idteck.alloc();
275+
m_pwm_seq = idteck.modulator(codec, buffer->buffer);
276+
idteck.free(codec);
277+
NRF_LOG_INFO("load lf idteck data finish.");
278+
return LF_IDTECK_TAG_ID_SIZE;
279+
}
280+
261281
NRF_LOG_ERROR("no valid data exists in buffer for tag type: %d.", type);
262282
return 0;
263283
}
@@ -391,3 +411,17 @@ bool lf_tag_pac_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
391411
uint8_t tag_id[8] = {'C', 'A', 'R', 'D', '0', '0', '0', '1'};
392412
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
393413
}
414+
415+
/** @brief IDTECK data save callback. */
416+
int lf_tag_idteck_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
417+
return m_tag_type == TAG_TYPE_IDTECK ? LF_IDTECK_TAG_ID_SIZE : 0;
418+
}
419+
420+
/** @brief IDTECK default frame: preamble "IDTK" + 32-bit placeholder card data. */
421+
bool lf_tag_idteck_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
422+
uint8_t tag_id[LF_IDTECK_TAG_ID_SIZE] = {
423+
0x49, 0x44, 0x54, 0x4B, // "IDTK" preamble (MSB first)
424+
0xDE, 0xAD, 0xBE, 0xEF, // default card data
425+
};
426+
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
427+
}

firmware/application/src/rfid/nfctag/lf/lf_tag_em.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#define LF_HIDPROX_TAG_ID_SIZE 13
1212
#define LF_VIKING_TAG_ID_SIZE 4
1313
#define LF_PAC_TAG_ID_SIZE 8
14+
#define LF_IDTECK_TAG_ID_SIZE 8
1415

1516
void lf_tag_125khz_sense_switch(bool enable);
1617
int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
@@ -24,4 +25,6 @@ int lf_tag_viking_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffe
2425
bool lf_tag_viking_data_factory(uint8_t slot, tag_specific_type_t tag_type);
2526
int lf_tag_pac_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
2627
bool lf_tag_pac_data_factory(uint8_t slot, tag_specific_type_t tag_type);
28+
int lf_tag_idteck_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
29+
bool lf_tag_idteck_data_factory(uint8_t slot, tag_specific_type_t tag_type);
2730
bool is_lf_field_exists(void);
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#include "idteck.h"
2+
3+
#include <stdlib.h>
4+
#include <string.h>
5+
6+
#include "nordic_common.h"
7+
#include "nrf_pwm.h"
8+
#include "protocols.h"
9+
#include "t55xx.h"
10+
#include "tag_base_type.h"
11+
#include "utils/psk1.h"
12+
13+
// IDTECK: 64-bit PSK1 frame at RF/32. The 32-bit fixed preamble 0x4944544B
14+
// ("IDTK") occupies the first four bytes of the frame; the remaining four
15+
// bytes are the card payload (a one-byte checksum followed by a 24-bit card
16+
// number in a byte-reversed layout, matching the format used by common
17+
// IDTECK readers; see cmdlfidteck.c in the Proxmark3 client for details).
18+
19+
#define IDTECK_PWM_ENTRIES (IDTECK_BIT_COUNT * LF_PSK1_RF32_SUBCYCLES_PER_BIT)
20+
#define IDTECK_T55XX_BLOCK_COUNT (3) // config word + 2 data blocks
21+
22+
static nrf_pwm_values_wave_form_t m_idteck_pwm_seq_vals[IDTECK_PWM_ENTRIES] = {};
23+
24+
static nrf_pwm_sequence_t m_idteck_pwm_seq = {
25+
.values.p_wave_form = m_idteck_pwm_seq_vals,
26+
.length = NRF_PWM_VALUES_LENGTH(m_idteck_pwm_seq_vals),
27+
.repeats = 0,
28+
.end_delay = 0,
29+
};
30+
31+
static idteck_codec *idteck_alloc(void) {
32+
idteck_codec *d = malloc(sizeof(idteck_codec));
33+
memset(d->data, 0, IDTECK_DATA_SIZE);
34+
return d;
35+
}
36+
37+
static void idteck_free(idteck_codec *d) {
38+
free(d);
39+
}
40+
41+
static uint8_t *idteck_get_data(idteck_codec *d) {
42+
return d->data;
43+
}
44+
45+
// PSK demodulation is not implemented. The tag-emulation ADC path runs at
46+
// 125kHz and is envelope-filtered, so recovering subcarrier phase for read
47+
// would require a dedicated decoder (edge-timing based). These stubs keep the
48+
// protocol struct complete; a future change can fill them in without touching
49+
// the struct layout.
50+
static void idteck_decoder_start(idteck_codec *d, uint8_t format) {
51+
(void)d;
52+
(void)format;
53+
}
54+
55+
static bool idteck_decoder_feed(idteck_codec *d, uint16_t val) {
56+
(void)d;
57+
(void)val;
58+
return false;
59+
}
60+
61+
// buf is the 8-byte frame to transmit, MSB first on air. For standard IDTECK
62+
// the first four bytes are the fixed preamble and the last four are the card
63+
// payload; the CLI layer is responsible for composing them.
64+
static const nrf_pwm_sequence_t *idteck_modulator(idteck_codec *d, uint8_t *buf) {
65+
(void)d;
66+
67+
size_t n = lf_psk1_build_sequence(buf, IDTECK_BIT_COUNT,
68+
m_idteck_pwm_seq_vals, IDTECK_PWM_ENTRIES);
69+
m_idteck_pwm_seq.length = (uint16_t)(n * 4); // 4 uint16 fields per wave-form entry
70+
return &m_idteck_pwm_seq;
71+
}
72+
73+
const protocol idteck = {
74+
.tag_type = TAG_TYPE_IDTECK,
75+
.data_size = IDTECK_DATA_SIZE,
76+
.alloc = (codec_alloc)idteck_alloc,
77+
.free = (codec_free)idteck_free,
78+
.get_data = (codec_get_data)idteck_get_data,
79+
.modulator = (modulator)idteck_modulator,
80+
.decoder =
81+
{
82+
.start = (decoder_start)idteck_decoder_start,
83+
.feed = (decoder_feed)idteck_decoder_feed,
84+
},
85+
};
86+
87+
// T5577 writer: block 0 holds the PSK1 RF/32 configuration, blocks 1-2 hold
88+
// the 64-bit frame big-endian.
89+
uint8_t idteck_t55xx_writer(uint8_t *uid, uint32_t *blks) {
90+
uint32_t hi = 0, lo = 0;
91+
for (int i = 0; i < 4; i++) hi = (hi << 8) | uid[i];
92+
for (int i = 4; i < 8; i++) lo = (lo << 8) | uid[i];
93+
blks[0] = T5577_IDTECK_CONFIG;
94+
blks[1] = hi;
95+
blks[2] = lo;
96+
return IDTECK_T55XX_BLOCK_COUNT;
97+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
3+
#include "protocols.h"
4+
5+
// IDTECK frame layout: 32-bit fixed preamble + 32-bit card payload.
6+
#define IDTECK_DATA_SIZE (8) // 8 bytes = 64 bits on air
7+
#define IDTECK_BIT_COUNT (64)
8+
#define IDTECK_PREAMBLE (0x4944544BU) // ASCII "IDTK", MSB first on air
9+
10+
typedef struct {
11+
uint8_t data[IDTECK_DATA_SIZE];
12+
} idteck_codec;
13+
14+
extern const protocol idteck;
15+
16+
uint8_t idteck_t55xx_writer(uint8_t *uid, uint32_t *blks);

firmware/application/src/rfid/nfctag/lf/protocols/t55xx.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ extern "C" {
8181
T5577_PWD | \
8282
(4 << T5577_MAXBLOCK_SHIFT))
8383

84+
// IDTECK: PSK1 at RF/32, subcarrier = carrier/2 (RF_2), 2 data blocks (64-bit frame).
85+
#define T5577_IDTECK_CONFIG ( \
86+
T5577_BITRATE_RF_32 | \
87+
T5577_MODULATION_PSK1 | \
88+
T5577_PSKCF_RF_2 | \
89+
T5577_PWD | \
90+
(2 << T5577_MAXBLOCK_SHIFT))
91+
8492
#if defined(PROJECT_CHAMELEON_ULTRA)
8593
void t55xx_write_data(uint32_t passwd, uint32_t *blks, uint8_t blk_count);
8694
void t55xx_reset_passwd(uint32_t old_passwd, uint32_t new_passwd);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include "psk1.h"
2+
3+
// Extract bit at index `bit_idx` from a MSB-first bit stream stored in
4+
// frame_bytes. bit_idx=0 is the MSB of frame_bytes[0].
5+
static inline bool read_bit_msb_first(const uint8_t *frame_bytes, size_t bit_idx) {
6+
return (frame_bytes[bit_idx / 8] >> (7 - (bit_idx % 8))) & 1U;
7+
}
8+
9+
// Extract the last bit of the frame (LSB of the last byte that contains a
10+
// bit). For frames whose bit_count is not a multiple of 8 this still points
11+
// at the final transmitted bit.
12+
static inline bool read_last_bit(const uint8_t *frame_bytes, size_t bit_count) {
13+
size_t last_idx = bit_count - 1;
14+
return read_bit_msb_first(frame_bytes, last_idx);
15+
}
16+
17+
size_t lf_psk1_build_sequence(const uint8_t *frame_bytes,
18+
size_t bit_count,
19+
nrf_pwm_values_wave_form_t *out_buf,
20+
size_t out_capacity) {
21+
size_t required = bit_count * LF_PSK1_RF32_SUBCYCLES_PER_BIT;
22+
if (required > out_capacity) {
23+
return 0;
24+
}
25+
26+
bool phase = false;
27+
bool last_bit = read_last_bit(frame_bytes, bit_count);
28+
29+
size_t k = 0;
30+
for (size_t bit_idx = 0; bit_idx < bit_count; bit_idx++) {
31+
bool cur_bit = read_bit_msb_first(frame_bytes, bit_idx);
32+
if (cur_bit != last_bit) {
33+
phase = !phase;
34+
}
35+
last_bit = cur_bit;
36+
37+
uint16_t pol = phase ? (1U << 15) : 0;
38+
for (size_t c = 0; c < LF_PSK1_RF32_SUBCYCLES_PER_BIT; c++) {
39+
out_buf[k].channel_0 = pol | LF_PSK1_SUBCARRIER_DUTY;
40+
out_buf[k].channel_1 = 0;
41+
out_buf[k].channel_2 = 0;
42+
out_buf[k].counter_top = LF_PSK1_SUBCARRIER_TOP;
43+
k++;
44+
}
45+
}
46+
47+
return k;
48+
}

0 commit comments

Comments
 (0)