Merge pull request #307 from azuwis/hf-14a-config

Add `hf 14a config` to deal with badly configured cards
This commit is contained in:
GameTec-live
2026-02-06 11:19:23 +01:00
committed by GitHub
8 changed files with 207 additions and 2 deletions
+1
View File
@@ -14,6 +14,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
- The transmission performance of USB has been improved (@xianglin1998)
- Added cmd for set mf1 config 'field_off_do_reset' (@xianglin1998)
- Fix Windows build (@suut)
- Added `hf 14a config` to deal with badly configured cards (@azuwis)
## [v2.1.0][2025-09-02]
- Added UV, formatter and linter. Contribution guidelines. (@GameTec-live)
+18
View File
@@ -574,6 +574,21 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u
return data_frame_make(cmd, status, resp_length, resp);
}
static data_frame_tx_t *cmd_processor_hf14a_get_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
hf14a_config_t *hc = get_hf14a_config();
return data_frame_make(cmd, STATUS_SUCCESS, sizeof(hf14a_config_t), (uint8_t *)hc);
}
static data_frame_tx_t *cmd_processor_hf14a_set_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
if (length != sizeof(hf14a_config_t)) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
hf14a_config_t hc;
memcpy(&hc, data, sizeof(hf14a_config_t));
set_hf14a_config(&hc);
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
}
static data_frame_tx_t *cmd_processor_mf1_manipulate_value_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
typedef struct {
uint8_t src_type;
@@ -1660,6 +1675,9 @@ static cmd_data_map_t m_data_cmd_map[] = {
{ DATA_CMD_HF14A_SET_FIELD_ON, before_reader_run, cmd_processor_hf14a_set_field_on, NULL },
{ DATA_CMD_HF14A_SET_FIELD_OFF, before_reader_run, cmd_processor_hf14a_set_field_off, NULL },
{ DATA_CMD_HF14A_GET_CONFIG, NULL, cmd_processor_hf14a_get_config, NULL },
{ DATA_CMD_HF14A_SET_CONFIG, NULL, cmd_processor_hf14a_set_config, NULL },
#endif
{ DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL },
+3
View File
@@ -76,6 +76,9 @@
#define DATA_CMD_HF14A_SET_FIELD_ON (2100)
#define DATA_CMD_HF14A_SET_FIELD_OFF (2101)
#define DATA_CMD_HF14A_GET_CONFIG (2200)
#define DATA_CMD_HF14A_SET_CONFIG (2201)
//
// ******************************************************************
@@ -39,6 +39,15 @@ static autotimer *g_timeout_auto_timer;
#define SPI_INSTANCE 0 /**< SPI instance index. */
static const nrf_drv_spi_t s_spiHandle = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE); // SPI instance
/*
Default HF 14a config is set to:
forcebcc = 0 (expect valid BCC)
forcecl2 = 0 (auto)
forcecl3 = 0 (auto)
forcerats = 0 (auto)
*/
static hf14a_config_t hf14aconfig = { 0, 0, 0, 0 };
#define ONCE_OPT __attribute__((optimize("O3")))
/**
@@ -690,6 +699,7 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) {
uint8_t status;
uint8_t do_cascade = 1;
uint8_t cascade_level = 0;
bool do_rats = false;
// OK we will select at least at cascade 1, lets see if first byte of UID was 0x88 in
// which case we need to make a cascade 2 request and select - this is a long UID
@@ -727,7 +737,12 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) {
uint8_t bcc = sel_uid[2] ^ sel_uid[3] ^ sel_uid[4] ^ sel_uid[5]; // calculate BCC
if (sel_uid[6] != bcc) {
NRF_LOG_INFO("BCC%d incorrect, got 0x%02x, expected 0x%02x\n", cascade_level, sel_uid[6], bcc);
return STATUS_HF_ERR_BCC;
if (hf14aconfig.forcebcc == 0) {
return STATUS_HF_ERR_BCC;
} else if (hf14aconfig.forcebcc == 1) {
sel_uid[6] = bcc;
} // else use card BCC
}
crc_14a_append(sel_uid, 7); // calculate and add CRC
@@ -745,6 +760,20 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) {
// If UID is 0X88 The beginning of the form shows that the UID is not complete
// In the next cycle, we need to make an increased level, return to the end of the anti -rushing collision, and complete the level
do_cascade = (((tag->sak & 0x04) /* && uid_resp[0] == 0x88 */) > 0);
if (cascade_level == 0) {
if (hf14aconfig.forcecl2 == 2) {
do_cascade = false;
} else if (hf14aconfig.forcecl2 == 1) {
do_cascade = true;
} // else 0==auto
} else if (cascade_level == 1) {
if (hf14aconfig.forcecl3 == 2) {
do_cascade = false;
} else if (hf14aconfig.forcecl3 == 1) {
do_cascade = true;
} // else 0==auto
}
if (do_cascade) {
// Remove first byte, 0x88 is not an UID byte, it CT, see page 3 of:
// http://www.nxp.com/documents/application_note/AN10927.pdf
@@ -761,7 +790,17 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) {
// Therefore + 1
tag->cascade = cascade_level + 1;
}
if (tag->sak & 0x20) {
if (hf14aconfig.forcerats == 2) {
do_rats = false;
NRF_LOG_INFO("Skipping RATS according to hf 14a config");
} else if (hf14aconfig.forcerats == 1) {
do_rats = true;
NRF_LOG_INFO("Forcing RATS according to hf 14a config");
} else {
do_rats = tag->sak & 0x20;
}
if (do_rats) {
// Tag supports 14443-4, sending RATS
uint16_t ats_size;
status = pcd_14a_reader_ats_request(tag->ats, &ats_size, 0xFF * 8);
@@ -1539,3 +1578,25 @@ uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc,
return status;
}
void set_hf14a_config(const hf14a_config_t *hc) {
if ((hc->forcebcc >= 0) && (hc->forcebcc <= 2)) {
hf14aconfig.forcebcc = hc->forcebcc;
}
if ((hc->forcecl2 >= 0) && (hc->forcecl2 <= 2)) {
hf14aconfig.forcecl2 = hc->forcecl2;
}
if ((hc->forcecl3 >= 0) && (hc->forcecl3 <= 2)) {
hf14aconfig.forcecl3 = hc->forcecl3;
}
if ((hc->forcerats >= 0) && (hc->forcerats <= 2)) {
hf14aconfig.forcerats = hc->forcerats;
}
}
hf14a_config_t *get_hf14a_config(void) {
return &hf14aconfig;
}
@@ -168,6 +168,17 @@ typedef struct {
uint8_t ats_len; // 14443-4 answer to select size
} PACKED picc_14a_tag_t;
// A struct used to send hf14a-configs
typedef struct {
int8_t forcebcc; // 0:expect valid BCC 1:force using computed BCC 2:force using card BCC
int8_t forcecl2; // 0:auto 1:force executing CL2 2:force skipping CL2
int8_t forcecl3; // 0:auto 1:force executing CL3 2:force skipping CL3
int8_t forcerats; // 0:auto 1:force executing RATS 2:force skipping RATS
} PACKED hf14a_config_t;
hf14a_config_t *get_hf14a_config(void);
void set_hf14a_config(const hf14a_config_t *hc);
#ifdef __cplusplus
extern "C" {
#endif
+83
View File
@@ -13,6 +13,7 @@ import serial.tools.list_ports
import threading
import struct
import queue
from enum import Enum
from multiprocessing import Pool, cpu_count
from typing import Union
from pathlib import Path
@@ -756,6 +757,88 @@ class HWVersion(DeviceRequiredUnit):
print(f' - Chameleon {model}, Version: {fw_version} ({git_version})')
@hf_14a.command('config')
class HF14AConfig(DeviceRequiredUnit):
class Config(Enum):
def __new__(cls, value, desc):
obj = object.__new__(cls)
obj._value_ = value
obj.desc = desc
return obj
@classmethod
def choices(cls):
return [elem.name for elem in cls]
@classmethod
def format(cls, index):
item = cls(index)
color = CG if index == 0 else CR
return f' - {cls.__name__.upper()} override: {color_string((color, item.name))} ( {item.desc} )'
@classmethod
def help(cls):
return ' / '.join([f'{elem.desc}' for elem in cls])
class Bcc(Config):
std = (0, "follow standard")
fix = (1, "fix bad BCC")
ignore = (2, "ignore bad BCC, always use card BCC")
class Cl2(Config):
std = (0, "follow standard")
force = (1, "always do CL2")
skip = (2, "always skip CL2")
class Cl3(Config):
std = (0, "follow standard")
force = (1, "always do CL3")
skip = (2, "always skip CL3")
class Rats(Config):
std = (0, "follow standard")
force = (1, "always do RATS")
skip = (2, "always skip RATS")
def args_parser(self) -> ArgumentParserNoExit:
parser = ArgumentParserNoExit()
parser.description = 'Configure 14a settings (use with caution)'
parser.add_argument('--std', action='store_true', help='Reset default configuration (follow standard)')
parser.add_argument('--bcc', type=str, choices=self.Bcc.choices(), help=self.Bcc.help())
parser.add_argument('--cl2', type=str, choices=self.Cl2.choices(), help=self.Cl2.help())
parser.add_argument('--cl3', type=str, choices=self.Cl3.choices(), help=self.Cl3.help())
parser.add_argument('--rats', type=str, choices=self.Rats.choices(), help=self.Rats.help())
return parser
def on_exec(self, args: argparse.Namespace):
change_requested = False
if args.std:
config = {'bcc': 0, 'cl2': 0, 'cl3': 0, 'rats': 0}
change_requested = True
else:
config = self.cmd.hf14a_get_config()
if args.bcc:
config['bcc'] = self.Bcc[args.bcc].value
change_requested = True
if args.cl2:
config['cl2'] = self.Cl2[args.cl2].value
change_requested = True
if args.cl3:
config['cl3'] = self.Cl3[args.cl3].value
change_requested = True
if args.rats:
config['rats'] = self.Rats[args.rats].value
change_requested = True
if change_requested:
self.cmd.hf14a_set_config(config)
config = self.cmd.hf14a_get_config()
print('HF 14a config')
print(self.Bcc.format(config['bcc']))
print(self.Cl2.format(config['cl2']))
print(self.Cl3.format(config['cl3']))
print(self.Rats.format(config['rats']))
@hf_14a.command('scan')
class HF14AScan(ReaderRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit:
+26
View File
@@ -425,6 +425,32 @@ class ChameleonCMD:
i += 14
return resp
@expect_response(Status.SUCCESS)
def hf14a_get_config(self):
"""
Get hf 14a config
:return:
"""
resp = self.device.send_cmd_sync(Command.HF14A_GET_CONFIG)
if resp.status == Status.SUCCESS:
bcc, cl2, cl3, rats = struct.unpack('!bbbb', resp.data)
resp.parsed = {'bcc': bcc,
'cl2': cl2,
'cl3': cl3,
'rats': rats}
return resp
@expect_response(Status.SUCCESS)
def hf14a_set_config(self, data):
"""
Set hf 14a config
:return:
"""
data = struct.pack('!bbbb', data['bcc'], data['cl2'], data['cl3'], data['rats'])
return self.device.send_cmd_sync(Command.HF14A_SET_CONFIG, data)
@expect_response(Status.LF_TAG_OK)
def em410x_scan(self):
"""
+2
View File
@@ -73,6 +73,8 @@ class Command(enum.IntEnum):
MF1_HARDNESTED_ACQUIRE = 2013
MF1_ENC_NESTED_ACQUIRE = 2014
MF1_CHECK_KEYS_ON_BLOCK = 2015
HF14A_GET_CONFIG = 2200
HF14A_SET_CONFIG = 2201
EM410X_SCAN = 3000
EM410X_WRITE_TO_T55XX = 3001