This commit is contained in:
Philippe Teuwen
2023-09-25 00:51:43 +02:00
parent e23ad695e6
commit 372cfead0c
8 changed files with 99 additions and 91 deletions
+16 -16
View File
@@ -392,7 +392,7 @@ static data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t
static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
// Response Buffer
uint8_t resp[DEF_FIFO_LENGTH] = { 0x00 };
uint8_t resp[DEF_FIFO_LENGTH] = { 0x00 };
uint16_t resp_length = 0;
typedef struct {
@@ -427,22 +427,22 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u
NRF_LOG_INFO("reserved = %d", payload->options.reserved);
status = pcd_14a_reader_raw_cmd(
payload->options.activate_rf_field,
payload->options.wait_response,
payload->options.append_crc,
payload->options.auto_select,
payload->options.keep_rf_field,
payload->options.check_response_crc,
payload->options.activate_rf_field,
payload->options.wait_response,
payload->options.append_crc,
payload->options.auto_select,
payload->options.keep_rf_field,
payload->options.check_response_crc,
U16NTOHS(payload->resp_timeout),
U16NTOHS(payload->resp_timeout),
U16NTOHS(payload->data_bitlength),
payload->data_buffer,
U16NTOHS(payload->data_bitlength),
payload->data_buffer,
resp,
&resp_length,
U8ARR_BIT_LEN(resp)
);
resp,
&resp_length,
U8ARR_BIT_LEN(resp)
);
return data_frame_make(cmd, status, resp_length, resp);
}
@@ -552,7 +552,7 @@ static data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t sta
uint8_t slot_now = payload->slot_index;
tag_emulation_slot_set_enable(slot_now, payload->sense_type, payload->enabled);
if ((!payload->enabled) &&
(!tag_emulation_slot_is_enabled(slot_now, payload->sense_type == TAG_SENSE_HF ? TAG_SENSE_LF : TAG_SENSE_HF))) {
(!tag_emulation_slot_is_enabled(slot_now, payload->sense_type == TAG_SENSE_HF ? TAG_SENSE_LF : TAG_SENSE_HF))) {
// HF and LF disabled, need to change slot
uint8_t slot_prev = tag_emulation_slot_find_next(slot_now);
NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev);
@@ -863,7 +863,7 @@ static data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t s
payload[slot].enabled_hf = tag_emulation_slot_is_enabled(slot, TAG_SENSE_HF);
payload[slot].enabled_lf = tag_emulation_slot_is_enabled(slot, TAG_SENSE_LF);
}
return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t*)&payload);
return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload);
}
static data_frame_tx_t *cmd_processor_get_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
@@ -109,8 +109,8 @@ typedef enum {
TAG_TYPE_NTAG_216
typedef struct {
tag_specific_type_t tag_hf;
tag_specific_type_t tag_lf;
tag_specific_type_t tag_hf;
tag_specific_type_t tag_lf;
} tag_slot_specific_type_t;
@@ -39,10 +39,10 @@ static tag_specific_type_t tag_specific_type_hf_values[] = { TAG_SPECIFIC_TYPE_H
bool is_tag_specific_type_valid(tag_specific_type_t tag_type) {
bool valid = false;
for (uint16_t i=0; i < ARRAYLEN(tag_specific_type_lf_values); i++) {
for (uint16_t i = 0; i < ARRAYLEN(tag_specific_type_lf_values); i++) {
valid |= (tag_type == tag_specific_type_lf_values[i]);
}
for (uint16_t i=0; i < ARRAYLEN(tag_specific_type_hf_values); i++) {
for (uint16_t i = 0; i < ARRAYLEN(tag_specific_type_hf_values); i++) {
valid |= (tag_type == tag_specific_type_hf_values[i]);
}
return valid;
@@ -398,7 +398,7 @@ void tag_emulation_sense_switch(tag_sense_type_t type, bool enable) {
break;
case TAG_SENSE_HF:
if (enable && (slotConfig.slots[slot].enabled_hf) &&
(slotConfig.slots[slot].tag_hf != TAG_TYPE_UNDEFINED)) {
(slotConfig.slots[slot].tag_hf != TAG_TYPE_UNDEFINED)) {
nfc_tag_14a_sense_switch(true);
} else {
nfc_tag_14a_sense_switch(false);
@@ -406,7 +406,7 @@ void tag_emulation_sense_switch(tag_sense_type_t type, bool enable) {
break;
case TAG_SENSE_LF:
if (enable && (slotConfig.slots[slot].enabled_lf) &&
(slotConfig.slots[slot].tag_lf != TAG_TYPE_UNDEFINED)) {
(slotConfig.slots[slot].tag_lf != TAG_TYPE_UNDEFINED)) {
lf_tag_125khz_sense_switch(true);
} else {
lf_tag_125khz_sense_switch(false);
@@ -425,32 +425,32 @@ static void tag_emulation_migrate_slot_config_v0_to_v8(void) {
// Populate new slotConfig struct
slotConfig.version = TAG_SLOT_CONFIG_CURRENT_VERSION;
slotConfig.active_slot = tmpbuf[0];
for (uint8_t i = 0; i< ARRAYLEN(slotConfig.slots); i++) {
bool enabled = tmpbuf[4+(i*4)] & 1;
for (uint8_t i = 0; i < ARRAYLEN(slotConfig.slots); i++) {
bool enabled = tmpbuf[4 + (i * 4)] & 1;
slotConfig.slots[i].tag_hf = tmpbuf[4+(i*4)+2];
for (uint8_t j=0; j < ARRAYLEN(tag_specific_type_old2new_hf_values); j++) {
slotConfig.slots[i].tag_hf = tmpbuf[4 + (i * 4) + 2];
for (uint8_t j = 0; j < ARRAYLEN(tag_specific_type_old2new_hf_values); j++) {
if (slotConfig.slots[i].tag_hf == tag_specific_type_old2new_hf_values[j][0]) {
slotConfig.slots[i].tag_hf = tag_specific_type_old2new_hf_values[j][1];
}
}
slotConfig.slots[i].enabled_hf = slotConfig.slots[i].tag_hf != TAG_TYPE_UNDEFINED ? enabled : false;
NRF_LOG_INFO("Slot %i HF: %02X->%04X enabled:%i", i, tmpbuf[4+(i*4)+2], slotConfig.slots[i].tag_hf, slotConfig.slots[i].enabled_hf);
NRF_LOG_INFO("Slot %i HF: %02X->%04X enabled:%i", i, tmpbuf[4 + (i * 4) + 2], slotConfig.slots[i].tag_hf, slotConfig.slots[i].enabled_hf);
slotConfig.slots[i].tag_lf = tmpbuf[4+(i*4)+3];
for (uint8_t j=0; j < ARRAYLEN(tag_specific_type_old2new_lf_values); j++) {
slotConfig.slots[i].tag_lf = tmpbuf[4 + (i * 4) + 3];
for (uint8_t j = 0; j < ARRAYLEN(tag_specific_type_old2new_lf_values); j++) {
if (slotConfig.slots[i].tag_lf == tag_specific_type_old2new_lf_values[j][0]) {
slotConfig.slots[i].tag_lf = tag_specific_type_old2new_lf_values[j][1];
}
}
slotConfig.slots[i].enabled_lf = slotConfig.slots[i].tag_lf != TAG_TYPE_UNDEFINED ? enabled : false;
NRF_LOG_INFO("Slot %i LF: %02X->%04X enabled:%i", i, tmpbuf[4+(i*4)+3], slotConfig.slots[i].tag_lf, slotConfig.slots[i].enabled_lf);
NRF_LOG_INFO("Slot %i LF: %02X->%04X enabled:%i", i, tmpbuf[4 + (i * 4) + 3], slotConfig.slots[i].tag_lf, slotConfig.slots[i].enabled_lf);
}
}
static void tag_emulation_migrate_slot_config(void) {
switch(slotConfig.version) {
switch (slotConfig.version) {
case 0:
case 1:
case 2:
@@ -627,7 +627,7 @@ uint8_t tag_emulation_slot_find_next(uint8_t slot_now) {
uint8_t start_slot = (slot_now + 1 == TAG_MAX_SLOT_NUM) ? 0 : slot_now + 1;
for (uint8_t i = start_slot;;) {
if (i == slot_now) return slot_now; // No other activated card slots were found after a loop
if (slotConfig.slots[i].enabled_hf || slotConfig.slots[i].enabled_lf ) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity
if (slotConfig.slots[i].enabled_hf || slotConfig.slots[i].enabled_lf) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity
i++;
if (i == TAG_MAX_SLOT_NUM) { // Continue the next cycle
i = 0;
@@ -686,7 +686,7 @@ void tag_emulation_change_type(uint8_t slot, tag_specific_type_t tag_type) {
void tag_emulation_factory_init(void) {
fds_slot_record_map_t map_info;
// Initialized a dual -frequency card in the card slot, if there is no historical record, it is a new state of factory.
// Initialized a dual -frequency card in the card slot, if there is no historical record, it is a new state of factory.
if (slotConfig.slots[0].enabled_hf && slotConfig.slots[0].tag_hf == TAG_TYPE_MIFARE_1024) {
// Initialize a high -frequency M1 card in the card slot 1, if it does not exist.
get_fds_map_by_slot_sense_type_for_dump(0, TAG_SENSE_HF, &map_info);
+30 -30
View File
@@ -1129,20 +1129,20 @@ inline void pcd_14a_reader_crc_computer(uint8_t use522CalcCRC) {
}
/**
* @brief : The hf 14a raw command implementation function can be used to send the 14A command with the specified configuration parameters.
* @param :waitResp : Wait for tag response
* @param :appendCrc : Do you want to add CRC before sending
* @param :autoSelect : Automatically select card before sending data
* @param :keepField : Do you want to keep the RF field on after sending
* @param :checkCrc : Is CRC verified after receiving data? If CRC verification is enabled, CRC bytes will be automatically removed after verification is completed.
* @param :waitRespTimeout : If waitResp is enabled, this parameter will be the timeout value to wait for the tag to respond
* @param :szDataSend : The number of bytes or bits of data to be sent
* @param :pDataSend : Pointer to the buffer of the data to be sent
* @brief : The hf 14a raw command implementation function can be used to send the 14A command with the specified configuration parameters.
* @param :waitResp : Wait for tag response
* @param :appendCrc : Do you want to add CRC before sending
* @param :autoSelect : Automatically select card before sending data
* @param :keepField : Do you want to keep the RF field on after sending
* @param :checkCrc : Is CRC verified after receiving data? If CRC verification is enabled, CRC bytes will be automatically removed after verification is completed.
* @param :waitRespTimeout : If waitResp is enabled, this parameter will be the timeout value to wait for the tag to respond
* @param :szDataSend : The number of bytes or bits of data to be sent
* @param :pDataSend : Pointer to the buffer of the data to be sent
*
* @retval : Execution Status
* @retval : Execution Status
*
*/
uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout,
uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout,
uint16_t szDataSendBits, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax) {
// Status code, default is OK.
uint8_t status = HF_TAG_OK;
@@ -1203,32 +1203,32 @@ uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc,
}
if (szDataSendBits % 8) {
status = pcd_14a_reader_bits_transfer(
pDataSend,
szDataSendBits,
NULL,
pDataRecv,
NULL,
pszDataRecv,
szDataRecvBitMax
);
pDataSend,
szDataSendBits,
NULL,
pDataRecv,
NULL,
pszDataRecv,
szDataRecvBitMax
);
} else {
status = pcd_14a_reader_bytes_transfer(
PCD_TRANSCEIVE,
pDataSend,
szDataSendBits / 8,
pDataRecv,
pszDataRecv,
szDataRecvBitMax
);
PCD_TRANSCEIVE,
pDataSend,
szDataSendBits / 8,
pDataRecv,
pszDataRecv,
szDataRecvBitMax
);
}
// If we need to receive data, we need to perform further operations on the data based on the remaining configuration after receiving it
if (waitResp) {
// Number of bits to bytes
uint8_t finalRecvBytes = (*pszDataRecv / 8) + (*pszDataRecv % 8 > 0 ? 1 : 0);
// If CRC verification is required, we need to perform CRC calculation
if (checkCrc) {
if (finalRecvBytes >= 3) { // Ensure at least three bytes (one byte of data+two bytes of CRC)
if (finalRecvBytes >= 3) { // Ensure at least three bytes (one byte of data+two bytes of CRC)
// Calculate and store CRC
uint8_t crc_buff[DEF_CRC_LENGTH] = { 0x00 };
crc_14a_calculate(pDataRecv, finalRecvBytes - DEF_CRC_LENGTH, crc_buff);
@@ -1238,7 +1238,7 @@ uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc,
*pszDataRecv = 0;
status = HF_ERR_CRC;
} else {
// If the CRC needs to be verified by the device and the device determines that the CRC is normal,
// If the CRC needs to be verified by the device and the device determines that the CRC is normal,
// we will return the data without CRC
*pszDataRecv = finalRecvBytes - DEF_CRC_LENGTH;
}
@@ -1263,4 +1263,4 @@ uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc,
}
return status;
}
}
@@ -220,8 +220,8 @@ uint8_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *pData);
uint8_t pcd_14a_reader_halt_tag(void);
void pcd_14a_reader_fast_halt_tag(void);
uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout,
uint16_t szDataSendBits, uint8_t* pDataSend, uint8_t* pDataRecv, uint16_t* pszDataRecv, uint16_t szDataRecvBitMax);
uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout,
uint16_t szDataSendBits, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax);
// UID & UFUID tag operation
uint8_t pcd_14a_reader_gen1a_unlock(void);
+2 -2
View File
@@ -479,8 +479,8 @@ void ledblink6(void) {
} else {
ledblink6_step = 0;
//if (++ledblink6_color == RGB_WHITE) ledblink6_color = RGB_RED;
uint8_t new_color=rand()%6;
for (; new_color == ledblink6_color; new_color=rand()%6);
uint8_t new_color = rand() % 6;
for (; new_color == ledblink6_color; new_color = rand() % 6);
ledblink6_color = new_color;
}
}
+18 -11
View File
@@ -975,6 +975,7 @@ class HFMFInfo(DeviceRequiredUnit):
def on_exec(self, args: argparse.Namespace):
return self.scan()
@hf_mfu.command('rdpg', 'MIFARE Ultralight read one page')
class HFMFURDPG(BaseMFUAuthOpera):
# hf mfu rdpg -p 2
@@ -1006,6 +1007,7 @@ class HFMFURDPG(BaseMFUAuthOpera):
resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, param.page))
print(f" - Data: {resp[:4].hex()}")
@hf_mfu.command('dump', 'MIFARE Ultralight dump pages')
class HFMFUDUMP(BaseMFUAuthOpera):
# hf mfu dump [-p start_page] [-q number_pages] [-f output_file]
@@ -1160,12 +1162,12 @@ class HWSlotList(DeviceRequiredUnit):
def get_slot_name(self, slot, sense):
try:
name = self.cmd.get_slot_tag_nick(slot, sense).decode(encoding="utf8")
return {'baselen':len(name), 'metalen':len(CC+C0), 'name':f'{CC}{name}{C0}'}
return {'baselen': len(name), 'metalen': len(CC+C0), 'name': f'{CC}{name}{C0}'}
except UnexpectedResponseError:
return {'baselen':0, 'metalen':0, 'name':f''}
return {'baselen': 0, 'metalen': 0, 'name': f''}
except UnicodeDecodeError:
name = "UTF8 Err"
return {'baselen':len(name), 'metalen':len(CC+C0), 'name':f'{CC}{name}{C0}'}
return {'baselen': len(name), 'metalen': len(CC+C0), 'name': f'{CC}{name}{C0}'}
# hw slot list
def on_exec(self, args: argparse.Namespace):
@@ -1180,7 +1182,7 @@ class HWSlotList(DeviceRequiredUnit):
lfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF)
m = max(hfn['baselen'], lfn['baselen'])
maxnamelength = m if m > maxnamelength else maxnamelength
slotnames.append({'hf':hfn, 'lf':lfn})
slotnames.append({'hf': hfn, 'lf': lfn})
for slot in chameleon_cmd.SlotNumber:
fwslot = chameleon_cmd.SlotNumber.to_fw(slot)
hf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot]['hf'])
@@ -1222,6 +1224,7 @@ class HWSlotList(DeviceRequiredUnit):
else:
print("undef")
@hw_slot.command('change', 'Set emulation tag slot activated.')
class HWSlotSet(SlotIndexRequireUnit):
def args_parser(self) -> ArgumentParserNoExit or None:
@@ -1319,6 +1322,7 @@ class HWSlotEnableSet(SlotIndexRequireUnit, SenseTypeRequireUnit):
self.cmd.set_slot_enable(slot_num, sense_type, enable)
print(f' - Set slot {slot_num} {"LF" if sense_type==chameleon_cmd.TagSenseType.TAG_SENSE_LF else "HF"} {"enable" if enable else "disable"} success.')
@lf_em_sim.command('set', 'Set simulated em410x card id')
class LFEMSimSet(LFEMCardRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit or None:
@@ -1690,19 +1694,23 @@ class HWRaw(DeviceRequiredUnit):
class HF14ARaw(ReaderRequiredUnit):
def bool_to_bit(self, value):
return 1 if value else 0
return 1 if value else 0
def args_parser(self) -> ArgumentParserNoExit or None:
parser = ArgumentParserNoExit()
parser.add_argument('-a', '--activate-rf', help="Active signal field ON without select", action='store_true', default=False,)
parser.add_argument('-s', '--select-tag', help="Active signal field ON with select", action='store_true', default=False,)
parser.add_argument('-a', '--activate-rf', help="Active signal field ON without select",
action='store_true', default=False,)
parser.add_argument('-s', '--select-tag', help="Active signal field ON with select",
action='store_true', default=False,)
# TODO: parser.add_argument('-3', '--type3-select-tag', help="Active signal field ON with ISO14443-3 select (no RATS)", action='store_true', default=False,)
parser.add_argument('-d', '--data', type=str, help="Data to be sent")
parser.add_argument('-b', '--bits', type=int, help="Number of bits to send. Useful for send partial byte")
parser.add_argument('-c', '--crc', help="Calculate and append CRC", action='store_true', default=False,)
parser.add_argument('-r', '--response', help="Do not read response", action='store_true', default=False,)
parser.add_argument('-cc', '--crc-clear', help="Verify and clear CRC of received data", action='store_true', default=False,)
parser.add_argument('-k', '--keep-rf', help="Keep signal field ON after receive", action='store_true', default=False,)
parser.add_argument('-cc', '--crc-clear', help="Verify and clear CRC of received data",
action='store_true', default=False,)
parser.add_argument('-k', '--keep-rf', help="Keep signal field ON after receive",
action='store_true', default=False,)
parser.add_argument('-t', '--timeout', type=int, help="Timeout in ms", default=100)
# TODO: need support for carriage returns in parser, why are they mangled?
# parser.description = 'Examples:\n' \
@@ -1712,7 +1720,6 @@ class HF14ARaw(ReaderRequiredUnit):
# ' hf 14a raw -sc -d 6000\n'
return parser
def on_exec(self, args: argparse.Namespace):
options = {
'activate_rf_field': self.bool_to_bit(args.activate_rf),
@@ -1721,7 +1728,7 @@ class HF14ARaw(ReaderRequiredUnit):
'auto_select': self.bool_to_bit(args.select_tag),
'keep_rf_field': self.bool_to_bit(args.keep_rf),
'check_response_crc': self.bool_to_bit(args.crc_clear),
#'auto_type3_select': self.bool_to_bit(args.type3-select-tag),
# 'auto_type3_select': self.bool_to_bit(args.type3-select-tag),
}
data: str = args.data
if data is not None:
+14 -13
View File
@@ -164,7 +164,7 @@ class TagSpecificType(enum.IntEnum):
###### LF ######
#### ASK Tag-Talk-First 100
#### ASK Tag-Talk-First 100 ####
# EM410x
TAG_TYPE_EM410X = 100,
# FDX-B
@@ -177,18 +177,18 @@ class TagSpecificType(enum.IntEnum):
# Noralsy
# Jablotron
#### FSK Tag-Talk-First 200
#### FSK Tag-Talk-First 200 ####
# HID Prox
# ioProx
# AWID
# Paradox
#### PSK Tag-Talk-First 300
#### PSK Tag-Talk-First 300 ####
# Indala
# Keri
# NexWatch
#### Reader-Talk-First 400
#### Reader-Talk-First 400 ####
# T5577
# EM4x05/4x69
# EM4x50/4x70
@@ -198,28 +198,28 @@ class TagSpecificType(enum.IntEnum):
###### HF ######
# MIFARE Classic series 1000
#### MIFARE Classic series 1000 ####
TAG_TYPE_MIFARE_Mini = 1000,
TAG_TYPE_MIFARE_1024 = 1001,
TAG_TYPE_MIFARE_2048 = 1002,
TAG_TYPE_MIFARE_4096 = 1003,
# MFUL / NTAG series 1100
#### MFUL / NTAG series 1100 ####
TAG_TYPE_NTAG_213 = 1100,
TAG_TYPE_NTAG_215 = 1101,
TAG_TYPE_NTAG_216 = 1102,
# MIFARE Plus series 1200
# DESFire series 1300
#### MIFARE Plus series 1200 ####
#### DESFire series 1300 ####
# ST25TA series 2000
#### ST25TA series 2000 ####
# HF14A-4 series 3000
#### HF14A-4 series 3000 ####
@staticmethod
def list(exclude_meta=True):
return [t for t in TagSpecificType
if (t > TagSpecificType.OLD_TAG_TYPES_END and
if (t > TagSpecificType.OLD_TAG_TYPES_END and
t != TagSpecificType.TAG_TYPES_LF_END)
or not exclude_meta]
or not exclude_meta]
@staticmethod
def list_hf():
@@ -628,7 +628,7 @@ class ChameleonCMD:
:param bit_owned_by_the_last_byte:
:return:
"""
class CStruct(ctypes.BigEndianStructure):
_fields_ = [
("activate_rf_field", ctypes.c_uint8, 1),
@@ -1274,5 +1274,6 @@ def test_fn():
# disconnect
dev.close()
if __name__ == '__main__':
test_fn()