Files
unleashed-firmware/lib/lfrfid/protocols/protocol_hid_generic.c
T
matthewb-wortheandClaude Opus 5 9effa4b44f RFID: enter cards by facility code and card number, read HID formats and Casi-Rusco badges (#1149)
Add Manually now asks whether to enter a card as FC/ID or as hex. FC/ID takes the
numbers printed on the card, one number input per field, and lays them out in the
protocol data itself: EM4100, HID H10301, ioProx XSF, AWID and Pyramid 26-bit, and
Gallagher. Parity and checksums are added by the firmware encoders as for hex-entered
data. Formats whose scrambling and checksums are not exported by the protocol library
go straight to hex as before.

HID H10302, H10304, H10306, AMAG S10401 and Corporate 1000 35-bit are read on top of
the firmware's Generic HIDProx protocol, whose data is the raw 44-bit field. The read
and saved-card screens add a line for every format the frame fits by length and parity;
a 37-bit frame fits more than one, so the second and later readings are prefixed "or".
Layouts follow the Proxmark3 wiegand_formats.c reference except S10401, whose even
parity covers bits 1-17 rather than 18: of 42 badges exported from a live access
system, all 42 pass that and only the 13 with bit 18 clear pass Proxmark3's.

Casi-Rusco C10106 badges transmit a plain EM4100 frame at RF/32, so the app reads the
badge id out of those 40 bits - a 19-bit credential beginning with 15, then a 19-bit
card field, less 66606 when its top bit is set. The frame carries no parity, so the
line is shown as the alternative reading and the credential range keeps it off almost
all other EM4100 cards.

The Generic HIDProx render now gives the frame length alone. Its "Data:" line repeated
the hex every caller already prints, and read the frame one nibble at a time from
offsets that assumed a full first nibble, so a frame whose length is not a multiple of
four lost bits after its first digit. A unit test covers every size header.

A number input whose range excludes 0 opened on an empty field that could not be
confirmed, because 0 is drawn as empty and the starting value was only clamped when it
was not 0. It is now clamped unconditionally, so the field is only ever empty when 0 is
in range, and an empty field reads back as 0 and is range-checked like any other value.

Co-authored-by: matthewb-worthe <135917560+matthewb-worthe@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 09:13:42 +03:00

258 lines
8.6 KiB
C

#include <furi.h>
#include <toolbox/protocols/protocol.h>
#include <lfrfid/tools/fsk_demod.h>
#include <lfrfid/tools/fsk_osc.h>
#include "lfrfid_protocols.h"
#include <bit_lib/bit_lib.h>
#define JITTER_TIME (20)
#define MIN_TIME (64 - JITTER_TIME)
#define MAX_TIME (80 + JITTER_TIME)
#define HID_DATA_SIZE 11
#define HID_PREAMBLE_SIZE 1
#define HID_PROTOCOL_SIZE_UNKNOWN 0
#define HID_ENCODED_DATA_SIZE (HID_PREAMBLE_SIZE + HID_DATA_SIZE + HID_PREAMBLE_SIZE)
#define HID_ENCODED_BIT_SIZE ((HID_PREAMBLE_SIZE + HID_DATA_SIZE) * 8)
#define HID_DECODED_DATA_SIZE (6)
#define HID_DECODED_BIT_SIZE ((HID_ENCODED_BIT_SIZE - HID_PREAMBLE_SIZE * 8) / 2)
#define HID_PREAMBLE 0x1D
typedef struct {
FSKDemod* fsk_demod;
} ProtocolHIDDecoder;
typedef struct {
FSKOsc* fsk_osc;
uint8_t encoded_index;
uint32_t pulse;
} ProtocolHIDEncoder;
typedef struct {
ProtocolHIDDecoder decoder;
ProtocolHIDEncoder encoder;
uint8_t encoded_data[HID_ENCODED_DATA_SIZE];
uint8_t data[HID_DECODED_DATA_SIZE];
} ProtocolHID;
ProtocolHID* protocol_hid_generic_alloc(void) {
ProtocolHID* protocol = malloc(sizeof(ProtocolHID));
protocol->decoder.fsk_demod = fsk_demod_alloc(MIN_TIME, 6, MAX_TIME, 5);
protocol->encoder.fsk_osc = fsk_osc_alloc(8, 10, 50);
return protocol;
}
void protocol_hid_generic_free(ProtocolHID* protocol) {
fsk_demod_free(protocol->decoder.fsk_demod);
fsk_osc_free(protocol->encoder.fsk_osc);
free(protocol);
}
uint8_t* protocol_hid_generic_get_data(ProtocolHID* protocol) {
return protocol->data;
}
void protocol_hid_generic_decoder_start(ProtocolHID* protocol) {
memset(protocol->encoded_data, 0, HID_ENCODED_DATA_SIZE);
}
static bool protocol_hid_generic_can_be_decoded(const uint8_t* data) {
// check preamble
if(data[0] != HID_PREAMBLE || data[HID_PREAMBLE_SIZE + HID_DATA_SIZE] != HID_PREAMBLE) {
return false;
}
// check for manchester encoding
for(size_t i = HID_PREAMBLE_SIZE; i < (HID_PREAMBLE_SIZE + HID_DATA_SIZE); i++) {
for(size_t n = 0; n < 4; n++) {
uint8_t bit_pair = (data[i] >> (n * 2)) & 0b11;
if(bit_pair == 0b11 || bit_pair == 0b00) {
return false;
}
}
}
return true;
}
static void protocol_hid_generic_decode(const uint8_t* from, uint8_t* to) {
size_t bit_index = 0;
for(size_t i = HID_PREAMBLE_SIZE; i < (HID_PREAMBLE_SIZE + HID_DATA_SIZE); i++) {
for(size_t n = 0; n < 4; n++) {
uint8_t bit_pair = (from[i] >> (6 - (n * 2))) & 0b11;
if(bit_pair == 0b01) {
bit_lib_set_bit(to, bit_index, 0);
} else if(bit_pair == 0b10) {
bit_lib_set_bit(to, bit_index, 1);
}
bit_index++;
}
}
}
/**
* Decodes size from the HID Proximity header:
* - If any of the first six bits is 1, the key is composed of the bits
* following the first 1
* - Otherwise, if the first six bits are 0:
* - If the seventh bit is 0, the key is composed of the remaining 37 bits.
* - If the seventh bit is 1, the size header continues until the next 1 bit,
* and the key is composed of however many bits remain.
*
* HID Proximity keys are 26 bits at minimum. If the header implies a key size
* under 26 bits, this function returns HID_PROTOCOL_SIZE_UNKNOWN.
*/
static uint8_t protocol_hid_generic_decode_protocol_size(ProtocolHID* protocol) {
for(size_t bit_index = 0; bit_index < 6; bit_index++) {
if(bit_lib_get_bit(protocol->data, bit_index)) {
return HID_DECODED_BIT_SIZE - bit_index - 1;
}
}
if(!bit_lib_get_bit(protocol->data, 6)) {
return 37;
}
size_t bit_index = 7;
uint8_t size = 36;
while(!bit_lib_get_bit(protocol->data, bit_index) && size >= 26) {
size--;
bit_index++;
}
return size < 26 ? HID_PROTOCOL_SIZE_UNKNOWN : size;
}
bool protocol_hid_generic_decoder_feed(ProtocolHID* protocol, bool level, uint32_t duration) {
bool value;
uint32_t count;
bool result = false;
fsk_demod_feed(protocol->decoder.fsk_demod, level, duration, &value, &count);
if(count > 0) {
for(size_t i = 0; i < count; i++) {
bit_lib_push_bit(protocol->encoded_data, HID_ENCODED_DATA_SIZE, value);
if(protocol_hid_generic_can_be_decoded(protocol->encoded_data)) {
protocol_hid_generic_decode(protocol->encoded_data, protocol->data);
result = true;
}
}
}
return result;
}
static void protocol_hid_generic_encode(ProtocolHID* protocol) {
protocol->encoded_data[0] = HID_PREAMBLE;
size_t bit_index = 0;
for(size_t i = 0; i < HID_DECODED_BIT_SIZE; i++) {
bool bit = bit_lib_get_bit(protocol->data, i);
if(bit) {
bit_lib_set_bit(protocol->encoded_data, 8 + bit_index, 1);
bit_lib_set_bit(protocol->encoded_data, 8 + bit_index + 1, 0);
} else {
bit_lib_set_bit(protocol->encoded_data, 8 + bit_index, 0);
bit_lib_set_bit(protocol->encoded_data, 8 + bit_index + 1, 1);
}
bit_index += 2;
}
}
bool protocol_hid_generic_encoder_start(ProtocolHID* protocol) {
protocol->encoder.encoded_index = 0;
protocol->encoder.pulse = 0;
protocol_hid_generic_encode(protocol);
return true;
}
LevelDuration protocol_hid_generic_encoder_yield(ProtocolHID* protocol) {
bool level = 0;
uint32_t duration = 0;
// if pulse is zero, we need to output high, otherwise we need to output low
if(protocol->encoder.pulse == 0) {
// get bit
uint8_t bit = bit_lib_get_bit(protocol->encoded_data, protocol->encoder.encoded_index);
// get pulse from oscillator
bool advance = fsk_osc_next(protocol->encoder.fsk_osc, bit, &duration);
if(advance) {
bit_lib_increment_index(protocol->encoder.encoded_index, HID_ENCODED_BIT_SIZE);
}
// duration diveded by 2 because we need to output high and low
duration = duration / 2;
protocol->encoder.pulse = duration;
level = true;
} else {
// output low half and reset pulse
duration = protocol->encoder.pulse;
protocol->encoder.pulse = 0;
level = false;
}
return level_duration_make(level, duration);
}
bool protocol_hid_generic_write_data(ProtocolHID* protocol, void* data) {
LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data;
bool result = false;
// Correct protocol data by redecoding
protocol_hid_generic_encoder_start(protocol);
protocol_hid_generic_decode(protocol->encoded_data, protocol->data);
protocol_hid_generic_encoder_start(protocol);
if(request->write_type == LFRFIDWriteTypeT5577) {
request->t5577.block[0] = LFRFID_T5577_MODULATION_FSK2a | LFRFID_T5577_BITRATE_RF_50 |
(3 << LFRFID_T5577_MAXBLOCK_SHIFT);
request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32);
request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32);
request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32);
request->t5577.blocks_to_write = 4;
result = true;
}
return result;
}
void protocol_hid_generic_render_data(ProtocolHID* protocol, FuriString* result) {
// the data itself is shown in hex by every caller, so only the frame length is added
const uint8_t protocol_size = protocol_hid_generic_decode_protocol_size(protocol);
if(protocol_size == HID_PROTOCOL_SIZE_UNKNOWN) {
furi_string_set(result, "Generic HID Proximity");
} else {
furi_string_printf(result, "%hhu-bit HID Proximity", protocol_size);
}
}
const ProtocolBase protocol_hid_generic = {
.name = "HIDProx",
.manufacturer = "Generic",
.data_size = HID_DECODED_DATA_SIZE,
.features = LFRFIDFeatureASK,
.validate_count = 6,
.alloc = (ProtocolAlloc)protocol_hid_generic_alloc,
.free = (ProtocolFree)protocol_hid_generic_free,
.get_data = (ProtocolGetData)protocol_hid_generic_get_data,
.decoder =
{
.start = (ProtocolDecoderStart)protocol_hid_generic_decoder_start,
.feed = (ProtocolDecoderFeed)protocol_hid_generic_decoder_feed,
},
.encoder =
{
.start = (ProtocolEncoderStart)protocol_hid_generic_encoder_start,
.yield = (ProtocolEncoderYield)protocol_hid_generic_encoder_yield,
},
.render_data = (ProtocolRenderData)protocol_hid_generic_render_data,
.render_brief_data = (ProtocolRenderData)protocol_hid_generic_render_data,
.write_data = (ProtocolWriteData)protocol_hid_generic_write_data,
};