feat: add Indala 224-bit (long format) RFID protocol support

OFW PR 4343 by kuzaxak
This commit is contained in:
MX
2026-03-07 23:53:22 +03:00
parent f7da751d5f
commit aa75974395
5 changed files with 419 additions and 0 deletions
@@ -526,6 +526,134 @@ MU_TEST(test_lfrfid_protocol_fdxb_read_simple) {
protocol_dict_free(dict);
}
// Indala224: 224-bit PSK2 frame, no known FC/CN descramble,
// test data uses the Proxmark3-verified reference (lf indala reader output)
#define INDALA224_TEST_DATA_SIZE 28
#define INDALA224_TEST_DATA \
{0x80, 0x00, 0x00, 0x01, 0xB2, 0x35, 0x23, 0xA6, 0xC2, 0xE3, 0x1E, 0xBA, 0x3C, 0xBE, \
0xE4, 0xAF, 0xB3, 0xC6, 0xAD, 0x1F, 0xCF, 0x64, 0x93, 0x93, 0x92, 0x8C, 0x14, 0xE5}
#define INDALA224_BITS_PER_FRAME 224
#define INDALA224_US_PER_BIT 255
#define INDALA224_FRAMES_TO_DECODE 3
MU_TEST(test_lfrfid_protocol_indala224_roundtrip) {
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
mu_assert_int_eq(
INDALA224_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolIndala224));
mu_assert_string_eq("Indala224", protocol_dict_get_name(dict, LFRFIDProtocolIndala224));
mu_assert_string_eq("Motorola", protocol_dict_get_manufacturer(dict, LFRFIDProtocolIndala224));
const uint8_t data[INDALA224_TEST_DATA_SIZE] = INDALA224_TEST_DATA;
// Encode: extract bit-level polarity from encoder output.
// PSK encoder yields carrier-level oscillation (duration=1 per half-cycle).
// PulseGlue produces ~16us outputs which are below the decoder's 127us threshold,
// so we extract the bit-level polarity directly: sample the encoder's phase state
// at the start of each bit period (every 32 yields = 16 carrier cycles).
protocol_dict_set_data(dict, LFRFIDProtocolIndala224, data, INDALA224_TEST_DATA_SIZE);
mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolIndala224));
const size_t total_bits = INDALA224_BITS_PER_FRAME * INDALA224_FRAMES_TO_DECODE;
bool* bit_levels = malloc(total_bits);
mu_check(bit_levels != NULL);
for(size_t i = 0; i < total_bits; i++) {
LevelDuration ld = protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224);
bit_levels[i] = level_duration_get_level(ld);
// Skip remaining 31 yields for this bit period
for(size_t skip = 0; skip < 31; skip++) {
protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224);
}
}
// Decode: convert bit-level polarities to run-length timing pairs
// and feed directly to decoder (simulates hardware PSK demodulator output).
protocol_dict_decoders_start(dict);
ProtocolId protocol = PROTOCOL_NO;
size_t i = 0;
while(i < total_bits) {
bool cur = bit_levels[i];
size_t run = 1;
while(i + run < total_bits && bit_levels[i + run] == cur) {
run++;
}
uint32_t duration = (uint32_t)(run * INDALA224_US_PER_BIT);
protocol = protocol_dict_decoders_feed(dict, cur, duration);
if(protocol != PROTOCOL_NO) break;
i += run;
}
free(bit_levels);
mu_assert_int_eq(LFRFIDProtocolIndala224, protocol);
uint8_t received_data[INDALA224_TEST_DATA_SIZE] = {0};
protocol_dict_get_data(dict, protocol, received_data, INDALA224_TEST_DATA_SIZE);
mu_assert_mem_eq(data, received_data, INDALA224_TEST_DATA_SIZE);
protocol_dict_free(dict);
}
// Indala224 phase-alternating test: data with odd number of 1-bits
// causes PSK2 carrier phase to invert between consecutive frames.
// Decoder must accept inverted preamble at the second frame boundary.
#define INDALA224_ALT_TEST_DATA \
{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
MU_TEST(test_lfrfid_protocol_indala224_alternating_phase) {
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
const uint8_t data[INDALA224_TEST_DATA_SIZE] = INDALA224_ALT_TEST_DATA;
protocol_dict_set_data(dict, LFRFIDProtocolIndala224, data, INDALA224_TEST_DATA_SIZE);
mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolIndala224));
const size_t total_bits = INDALA224_BITS_PER_FRAME * INDALA224_FRAMES_TO_DECODE;
bool* bit_levels = malloc(total_bits);
mu_check(bit_levels != NULL);
for(size_t i = 0; i < total_bits; i++) {
LevelDuration ld = protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224);
bit_levels[i] = level_duration_get_level(ld);
for(size_t skip = 0; skip < 31; skip++) {
protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224);
}
}
// Verify phase alternation: frame 1 and frame 2 start with opposite polarity
mu_check(bit_levels[0] != bit_levels[INDALA224_BITS_PER_FRAME]);
protocol_dict_decoders_start(dict);
ProtocolId protocol = PROTOCOL_NO;
size_t i = 0;
while(i < total_bits) {
bool cur = bit_levels[i];
size_t run = 1;
while(i + run < total_bits && bit_levels[i + run] == cur) {
run++;
}
uint32_t duration = (uint32_t)(run * INDALA224_US_PER_BIT);
protocol = protocol_dict_decoders_feed(dict, cur, duration);
if(protocol != PROTOCOL_NO) break;
i += run;
}
free(bit_levels);
mu_assert_int_eq(LFRFIDProtocolIndala224, protocol);
uint8_t received_data[INDALA224_TEST_DATA_SIZE] = {0};
protocol_dict_get_data(dict, protocol, received_data, INDALA224_TEST_DATA_SIZE);
mu_assert_mem_eq(data, received_data, INDALA224_TEST_DATA_SIZE);
protocol_dict_free(dict);
}
MU_TEST_SUITE(test_lfrfid_protocols_suite) {
MU_RUN_TEST(test_lfrfid_protocol_em_read_simple);
MU_RUN_TEST(test_lfrfid_protocol_em_emulate_simple);
@@ -538,6 +666,9 @@ MU_TEST_SUITE(test_lfrfid_protocols_suite) {
MU_RUN_TEST(test_lfrfid_protocol_inadala26_emulate_simple);
MU_RUN_TEST(test_lfrfid_protocol_indala224_roundtrip);
MU_RUN_TEST(test_lfrfid_protocol_indala224_alternating_phase);
MU_RUN_TEST(test_lfrfid_protocol_fdxb_read_simple);
MU_RUN_TEST(test_lfrfid_protocol_fdxb_emulate_simple);
}
+2
View File
@@ -4,6 +4,7 @@
#include "protocol_h10301.h"
#include "protocol_idteck.h"
#include "protocol_indala26.h"
#include "protocol_indala224.h"
#include "protocol_io_prox_xsf.h"
#include "protocol_awid.h"
#include "protocol_fdx_a.h"
@@ -30,6 +31,7 @@ const ProtocolBase* const lfrfid_protocols[] = {
[LFRFIDProtocolH10301] = &protocol_h10301,
[LFRFIDProtocolIdteck] = &protocol_idteck,
[LFRFIDProtocolIndala26] = &protocol_indala26,
[LFRFIDProtocolIndala224] = &protocol_indala224,
[LFRFIDProtocolIOProxXSF] = &protocol_io_prox_xsf,
[LFRFIDProtocolAwid] = &protocol_awid,
[LFRFIDProtocolFDXA] = &protocol_fdx_a,
+1
View File
@@ -16,6 +16,7 @@ typedef enum {
LFRFIDProtocolH10301,
LFRFIDProtocolIdteck,
LFRFIDProtocolIndala26,
LFRFIDProtocolIndala224,
LFRFIDProtocolIOProxXSF,
LFRFIDProtocolAwid,
LFRFIDProtocolFDXA,
+281
View File
@@ -0,0 +1,281 @@
#include <furi.h>
#include <toolbox/protocols/protocol.h>
#include <bit_lib/bit_lib.h>
#include "lfrfid_protocols.h"
#define INDALA224_PREAMBLE_BIT_SIZE (30)
#define INDALA224_PREAMBLE_DATA_SIZE (4)
#define INDALA224_ENCODED_BIT_SIZE (224)
#define INDALA224_ENCODED_DATA_SIZE \
(((INDALA224_ENCODED_BIT_SIZE) / 8) + INDALA224_PREAMBLE_DATA_SIZE)
#define INDALA224_ENCODED_DATA_LAST ((INDALA224_ENCODED_BIT_SIZE) / 8)
#define INDALA224_DECODED_BIT_SIZE (224)
#define INDALA224_DECODED_DATA_SIZE (28)
#define INDALA224_US_PER_BIT (255)
#define INDALA224_ENCODER_PULSES_PER_BIT (16)
typedef struct {
uint8_t data_index;
uint8_t bit_clock_index;
bool current_polarity;
bool pulse_phase;
} ProtocolIndala224Encoder;
typedef struct {
uint8_t encoded_data[INDALA224_ENCODED_DATA_SIZE];
uint8_t negative_encoded_data[INDALA224_ENCODED_DATA_SIZE];
uint8_t data[INDALA224_DECODED_DATA_SIZE];
ProtocolIndala224Encoder encoder;
} ProtocolIndala224;
ProtocolIndala224* protocol_indala224_alloc(void) {
ProtocolIndala224* protocol = malloc(sizeof(ProtocolIndala224));
return protocol;
}
void protocol_indala224_free(ProtocolIndala224* protocol) {
free(protocol);
}
uint8_t* protocol_indala224_get_data(ProtocolIndala224* protocol) {
return protocol->data;
}
void protocol_indala224_decoder_start(ProtocolIndala224* protocol) {
memset(protocol->encoded_data, 0, INDALA224_ENCODED_DATA_SIZE);
memset(protocol->negative_encoded_data, 0, INDALA224_ENCODED_DATA_SIZE);
}
static bool protocol_indala224_check_preamble(uint8_t* data, size_t bit_index) {
// Normal preamble: 1 followed by 29 zeros
if(bit_lib_get_bits(data, bit_index, 8) != 0b10000000) return false;
if(bit_lib_get_bits(data, bit_index + 8, 8) != 0) return false;
if(bit_lib_get_bits(data, bit_index + 16, 8) != 0) return false;
if(bit_lib_get_bits(data, bit_index + 24, 6) != 0) return false;
return true;
}
static bool protocol_indala224_check_inverted_preamble(uint8_t* data, size_t bit_index) {
// Inverted preamble: 0 followed by 29 ones (phase-alternating PSK2 cards)
if(bit_lib_get_bits(data, bit_index, 8) != 0b01111111) return false;
if(bit_lib_get_bits(data, bit_index + 8, 8) != 0xFF) return false;
if(bit_lib_get_bits(data, bit_index + 16, 8) != 0xFF) return false;
if(bit_lib_get_bits(data, bit_index + 24, 6) != 0b111111) return false;
return true;
}
static bool protocol_indala224_can_be_decoded(uint8_t* data) {
if(!protocol_indala224_check_preamble(data, 0)) return false;
// Second frame may have same or inverted preamble (PSK2 phase alternation)
if(!protocol_indala224_check_preamble(data, INDALA224_ENCODED_BIT_SIZE) &&
!protocol_indala224_check_inverted_preamble(data, INDALA224_ENCODED_BIT_SIZE)) {
return false;
}
return true;
}
static bool protocol_indala224_decoder_feed_internal(bool polarity, uint32_t time, uint8_t* data) {
time += (INDALA224_US_PER_BIT / 2);
size_t bit_count = (time / INDALA224_US_PER_BIT);
bool result = false;
if(bit_count < INDALA224_ENCODED_BIT_SIZE) {
for(size_t i = 0; i < bit_count; i++) {
bit_lib_push_bit(data, INDALA224_ENCODED_DATA_SIZE, polarity);
if(protocol_indala224_can_be_decoded(data)) {
result = true;
break;
}
}
}
return result;
}
static void protocol_indala224_decoder_save(uint8_t* data_to, const uint8_t* data_from) {
// PSK2 differential decode: the shift register contains phase values, not data.
// T5577 PSK2 encodes data as phase transitions: data[n] = phase[n] XOR phase[n+1].
// Bit 224 (start of second frame) provides phase[n+1] for the last data bit.
for(size_t i = 0; i < INDALA224_DECODED_BIT_SIZE; i++) {
bool phase_current = bit_lib_get_bit(data_from, i);
bool phase_next = bit_lib_get_bit(data_from, i + 1);
bit_lib_set_bit(data_to, i, phase_current ^ phase_next);
}
}
bool protocol_indala224_decoder_feed(ProtocolIndala224* protocol, bool level, uint32_t duration) {
bool result = false;
if(duration > (INDALA224_US_PER_BIT / 2)) {
if(protocol_indala224_decoder_feed_internal(level, duration, protocol->encoded_data)) {
protocol_indala224_decoder_save(protocol->data, protocol->encoded_data);
FURI_LOG_D("Indala224", "Positive");
result = true;
return result;
}
if(protocol_indala224_decoder_feed_internal(
!level, duration, protocol->negative_encoded_data)) {
protocol_indala224_decoder_save(protocol->data, protocol->negative_encoded_data);
FURI_LOG_D("Indala224", "Negative");
result = true;
return result;
}
}
return result;
}
bool protocol_indala224_encoder_start(ProtocolIndala224* protocol) {
// Store raw data for PSK2 emulation - the yield function handles PSK2 modulation.
memcpy(protocol->encoded_data, protocol->data, INDALA224_DECODED_DATA_SIZE);
protocol->encoder.data_index = 0;
protocol->encoder.current_polarity = true;
protocol->encoder.pulse_phase = true;
protocol->encoder.bit_clock_index = 0;
return true;
}
LevelDuration protocol_indala224_encoder_yield(ProtocolIndala224* protocol) {
LevelDuration level_duration;
ProtocolIndala224Encoder* encoder = &protocol->encoder;
if(encoder->pulse_phase) {
level_duration = level_duration_make(encoder->current_polarity, 1);
encoder->pulse_phase = false;
} else {
level_duration = level_duration_make(!encoder->current_polarity, 1);
encoder->pulse_phase = true;
encoder->bit_clock_index++;
if(encoder->bit_clock_index >= INDALA224_ENCODER_PULSES_PER_BIT) {
encoder->bit_clock_index = 0;
// PSK2: carrier phase flips when data bit is 1
bool current_bit = bit_lib_get_bit(protocol->encoded_data, encoder->data_index);
if(current_bit) {
encoder->current_polarity = !encoder->current_polarity;
}
bit_lib_increment_index(encoder->data_index, INDALA224_ENCODED_BIT_SIZE);
}
}
return level_duration;
}
static void protocol_indala224_render_data_internal(
ProtocolIndala224* protocol,
FuriString* result,
bool brief) {
if(brief) {
furi_string_printf(
result,
"Raw: %02X%02X%02X%02X\n"
" %02X%02X%02X%02X...",
protocol->data[0],
protocol->data[1],
protocol->data[2],
protocol->data[3],
protocol->data[4],
protocol->data[5],
protocol->data[6],
protocol->data[7]);
} else {
furi_string_printf(
result,
"Raw: %02X%02X%02X%02X %02X%02X%02X%02X\n"
"%02X%02X%02X%02X %02X%02X%02X%02X\n"
"%02X%02X%02X%02X %02X%02X%02X%02X\n"
"%02X%02X%02X%02X",
protocol->data[0],
protocol->data[1],
protocol->data[2],
protocol->data[3],
protocol->data[4],
protocol->data[5],
protocol->data[6],
protocol->data[7],
protocol->data[8],
protocol->data[9],
protocol->data[10],
protocol->data[11],
protocol->data[12],
protocol->data[13],
protocol->data[14],
protocol->data[15],
protocol->data[16],
protocol->data[17],
protocol->data[18],
protocol->data[19],
protocol->data[20],
protocol->data[21],
protocol->data[22],
protocol->data[23],
protocol->data[24],
protocol->data[25],
protocol->data[26],
protocol->data[27]);
}
}
void protocol_indala224_render_data(ProtocolIndala224* protocol, FuriString* result) {
protocol_indala224_render_data_internal(protocol, result, false);
}
void protocol_indala224_render_brief_data(ProtocolIndala224* protocol, FuriString* result) {
protocol_indala224_render_data_internal(protocol, result, true);
}
bool protocol_indala224_write_data(ProtocolIndala224* protocol, void* data) {
LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data;
bool result = false;
if(request->write_type == LFRFIDWriteTypeT5577) {
// Config: PSK2, RF/32, 7 data blocks (matches Proxmark3 T55X7_INDALA_224_CONFIG_BLOCK)
// T5577 data blocks contain raw data bits; the chip handles PSK2 modulation.
request->t5577.block[0] = LFRFID_T5577_BITRATE_RF_32 | LFRFID_T5577_MODULATION_PSK2 |
(7 << LFRFID_T5577_MAXBLOCK_SHIFT);
request->t5577.block[1] = bit_lib_get_bits_32(protocol->data, 0, 32);
request->t5577.block[2] = bit_lib_get_bits_32(protocol->data, 32, 32);
request->t5577.block[3] = bit_lib_get_bits_32(protocol->data, 64, 32);
request->t5577.block[4] = bit_lib_get_bits_32(protocol->data, 96, 32);
request->t5577.block[5] = bit_lib_get_bits_32(protocol->data, 128, 32);
request->t5577.block[6] = bit_lib_get_bits_32(protocol->data, 160, 32);
request->t5577.block[7] = bit_lib_get_bits_32(protocol->data, 192, 32);
request->t5577.blocks_to_write = 8;
result = true;
}
return result;
}
const ProtocolBase protocol_indala224 = {
.name = "Indala224",
.manufacturer = "Motorola",
.data_size = INDALA224_DECODED_DATA_SIZE,
.features = LFRFIDFeaturePSK,
.validate_count = 6,
.alloc = (ProtocolAlloc)protocol_indala224_alloc,
.free = (ProtocolFree)protocol_indala224_free,
.get_data = (ProtocolGetData)protocol_indala224_get_data,
.decoder =
{
.start = (ProtocolDecoderStart)protocol_indala224_decoder_start,
.feed = (ProtocolDecoderFeed)protocol_indala224_decoder_feed,
},
.encoder =
{
.start = (ProtocolEncoderStart)protocol_indala224_encoder_start,
.yield = (ProtocolEncoderYield)protocol_indala224_encoder_yield,
},
.render_data = (ProtocolRenderData)protocol_indala224_render_data,
.render_brief_data = (ProtocolRenderData)protocol_indala224_render_brief_data,
.write_data = (ProtocolWriteData)protocol_indala224_write_data,
};
@@ -0,0 +1,4 @@
#pragma once
#include <toolbox/protocols/protocol.h>
extern const ProtocolBase protocol_indala224;