mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
Merge pull request #307 from azuwis/hf-14a-config
Add `hf 14a config` to deal with badly configured cards
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user