hw 14a raw: closer to pm3 syntax, removed bit_frame,...

Now data length is always in bits
Option -o => -a and only needed to turn field on without select or data
Reorganize pcd_14a_reader_raw_cmd
Some more checks
This commit is contained in:
Philippe Teuwen
2023-09-24 01:02:30 +02:00
parent 234b22c48a
commit f7db6d0fb3
6 changed files with 174 additions and 158 deletions
+12
View File
@@ -251,6 +251,17 @@ Notes:
* Command: 24 bytes: `type|block|key[6]|block_data[16]`. Key as 6 bytes.
* Response: no data
* CLI: cf `hf mf wrbl`
### 2010: HF14A_RAW
* Command: : 5+N bytes: `options|resp_timeout_ms[2]|bitlen[2]` followed by data to be transmitted, with `options` a 1-byte BigEndian bitfield, so starting from MSB:
* `activate_rf_field`:1
* `wait_response`:1
* `append_crc`:1
* `auto_select`:1
* `keep_rf_field`:1
* `check_response_crc`:1
* `reserved`:2
* Response: data sent by the card
* CLI: cf `hf 14a raw`
### 3000: EM410X_SCAN
* Command: no data
* Response: 5 bytes. `id[5]`. ID as 5 bytes.
@@ -356,6 +367,7 @@ Be verbose, explicit and reuse conventions, in order to enhance code maintainabi
### Guideline: Structs
- Define C `struct` for cmd/resp data greater than a single byte, use and abuse of `struct.pack`/`struct.unpack` in Python. So one can understand the payload format at a simple glimpse. Exceptions to `C` struct are when the formats are of variable length (but Python `struct` is still flexible enough to cope with such formats!)
- Avoid hardcoding offsets, use `sizeof()`, `offsetof(struct, field)` in C and `struct.calcsize()` in Python
- For complex bitfield structs, exceptionally you can use ctypes in Python. Beware ctypes.BigEndianStructure bitfield will be parsed in the firmware in the reverse order, from LSB to MSB.
### Guideline: Status
If single byte of data to return, still use a 1-byte `data`, not `status`. Standard response status is `STATUS_DEVICE_SUCCESS` for general commands, `HF_TAG_OK` for HF commands and `LF_TAG_OK` for LF commands. If the response status is different than those, the response data is empty. Response status are generic and cover things like tag disappearance or tag non-conformities with the ISO standard. If a command needs more specific response status, it is added in the first byte of the data, to avoid cluttering the 1-byte general status enum with command-specific statuses. See e.g. [MF1_DARKSIDE_ACQUIRE](#2004-mf1_darkside_acquire).
### Guideline: unambiguous types
+7 -10
View File
@@ -404,21 +404,20 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u
uint16_t resp_length = 0;
typedef struct {
struct { // MSB -> LSB
uint8_t reserved : 1;
struct { // LSB -> MSB
uint8_t reserved : 2;
uint8_t check_response_crc : 1;
uint8_t keep_rf_field : 1;
uint8_t auto_select : 1;
uint8_t bit_frame : 1;
uint8_t append_crc : 1;
uint8_t wait_response : 1;
uint8_t open_rf_field : 1;
uint8_t activate_rf_field : 1;
} options;
// U16NTOHS
uint16_t resp_timeout;
uint16_t data_length;
uint16_t data_bitlength;
uint8_t data_buffer[0]; // We can have a lot of data or no data. struct just to compute offsets with min options.
} PACKED payload_t;
@@ -427,27 +426,25 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
NRF_LOG_INFO("open_rf_field = %d", payload->options.open_rf_field);
NRF_LOG_INFO("activate_rf_field = %d", payload->options.activate_rf_field);
NRF_LOG_INFO("wait_response = %d", payload->options.wait_response);
NRF_LOG_INFO("append_crc = %d", payload->options.append_crc);
NRF_LOG_INFO("bit_frame = %d", payload->options.bit_frame);
NRF_LOG_INFO("auto_select = %d", payload->options.auto_select);
NRF_LOG_INFO("keep_rf_field = %d", payload->options.keep_rf_field);
NRF_LOG_INFO("check_response_crc = %d", payload->options.check_response_crc);
NRF_LOG_INFO("reserved = %d", payload->options.reserved);
status = pcd_14a_reader_raw_cmd(
payload->options.open_rf_field,
payload->options.activate_rf_field,
payload->options.wait_response,
payload->options.append_crc,
payload->options.bit_frame,
payload->options.auto_select,
payload->options.keep_rf_field,
payload->options.check_response_crc,
U16NTOHS(payload->resp_timeout),
U16NTOHS(payload->data_length),
U16NTOHS(payload->data_bitlength),
payload->data_buffer,
resp,
+113 -111
View File
@@ -1132,7 +1132,6 @@ 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 :bitsFrame : Is it necessary to send bit frames, which are mutually exclusive with the appendCrc parameter.
* @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.
@@ -1143,122 +1142,125 @@ inline void pcd_14a_reader_crc_computer(uint8_t use522CalcCRC) {
* @retval : Execution Status
*
*/
uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool bitsFrame, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout,
uint16_t szDataSend, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax) {
// Status code, default is OK.
uint8_t status = HF_TAG_OK;
// Reset recv length.
*pszDataRecv = 0;
// Old response timeout.
uint16_t oldWaitRespTimeout;
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;
// Reset recv length.
*pszDataRecv = 0;
if (openRFField) { // Open rf filed?
// If additional CRC is required, first add the CRC to the tail.
if (appendCrc) {
if (szDataSendBits == 0) {
NRF_LOG_INFO("Adding CRC but missing data");
return STATUS_PAR_ERR;
}
if (szDataSendBits % 8) {
NRF_LOG_INFO("Adding CRC incompatible with partial bytes");
return STATUS_PAR_ERR;
}
if (szDataSendBits > ((DEF_FIFO_LENGTH - DEF_CRC_LENGTH) * 8)) {
// Note: Adding CRC requires at least two bytes of free space. If the transmitted data is already greater than or equal to 64, an error needs to be returned
NRF_LOG_INFO("Adding CRC requires data length less than or equal to 62.");
return STATUS_PAR_ERR;
}
// Calculate and append CRC byte data to the buffer
crc_14a_append(pDataSend, szDataSendBits / 8);
// CRC is also sent as part of the data, so the total length needs to be added to the CRC length here
szDataSendBits += DEF_CRC_LENGTH * 8;
}
if (autoSelect || szDataSendBits) {
// override openRFField if we need to select or to send data
openRFField = true;
}
if (openRFField && ! g_is_reader_antenna_on) { // Open rf field?
pcd_14a_reader_reset();
pcd_14a_reader_antenna_on();
bsp_delay_ms(8);
}
// Is there any data that needs to be sent
if (szDataSend > 0) {
// If additional CRC is required, first add the CRC to the tail.
if (appendCrc) {
// Note: Adding CRC requires at least two bytes of free space. If the transmitted data is already greater than or equal to 64, an error needs to be returned
if (szDataSend > (DEF_FIFO_LENGTH - DEF_CRC_LENGTH)) {
NRF_LOG_INFO("Adding CRC requires data length less than or equal to 62.");
status = STATUS_PAR_ERR;
} else {
// Calculate and append CRC byte data to the buffer
crc_14a_append(pDataSend, szDataSend);
// CRC is also sent as part of the data, so the total length needs to be added to the CRC length here
szDataSend += DEF_CRC_LENGTH;
}
}
// Ensure that the previous operation is normal
if (status == HF_TAG_OK) {
// Determine if card selection is necessary first based on needs
if (autoSelect) {
picc_14a_tag_t ti;
status = pcd_14a_reader_scan_once(&ti);
// Determine whether the card search was successful
if (status != HF_TAG_OK) {
return status;
}
}
// If there is no need to receive data, the data receiving cache needs to be empty, otherwise a specified timeout value needs to be set
if (waitResp) {
// Caching old timeout values
oldWaitRespTimeout = g_com_timeout_ms;
// Then set the new values in
g_com_timeout_ms = waitRespTimeout;
} else {
pDataRecv = NULL;
}
// Ensure that the previous operation is normal
if (status == HF_TAG_OK) {
if (bitsFrame) {
status = pcd_14a_reader_bits_transfer(
pDataSend,
szDataSend,
NULL,
pDataRecv,
NULL,
pszDataRecv,
szDataRecvBitMax
);
} else {
status = pcd_14a_reader_bytes_transfer(
PCD_TRANSCEIVE,
pDataSend,
szDataSend,
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)
// Calculate and store CRC
uint8_t crc_buff[DEF_CRC_LENGTH] = { 0x00 };
crc_14a_calculate(pDataRecv, finalRecvBytes - DEF_CRC_LENGTH, crc_buff);
// Verify CRC
if (pDataRecv[finalRecvBytes - 2] != crc_buff[0] || pDataRecv[finalRecvBytes - 1] != crc_buff[1]) {
// We have found an error in CRC verification and need to inform the upper computer!
*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,
// we will return the data without CRC
*pszDataRecv = finalRecvBytes - DEF_CRC_LENGTH;
}
} else {
// The data is insufficient to support the length of the CRC, so it is returned as is
*pszDataRecv = 0;
}
} else {
// Do not verify CRC, all data is returned as is
*pszDataRecv = finalRecvBytes;
}
// We need to recover the timeout value
g_com_timeout_ms = oldWaitRespTimeout;
} else {
*pszDataRecv = 0;
}
if (autoSelect) {
picc_14a_tag_t ti;
status = pcd_14a_reader_scan_once(&ti);
// Determine whether the card search was successful
if (status != HF_TAG_OK) {
pcd_14a_reader_antenna_off();
return status;
}
}
// Finally, keep the field open as needed
if (!keepField) {
pcd_14a_reader_antenna_off();
}
return status;
// Is there any data that needs to be sent
if (szDataSendBits) {
// If there is no need to receive data, the data receiving cache needs to be empty, otherwise a specified timeout value needs to be set
// Caching old timeout values
uint16_t oldWaitRespTimeout = g_com_timeout_ms;
if (waitResp) {
// Then set the new values in
g_com_timeout_ms = waitRespTimeout;
} else {
pDataRecv = NULL;
}
if (szDataSendBits % 8) {
status = pcd_14a_reader_bits_transfer(
pDataSend,
szDataSendBits,
NULL,
pDataRecv,
NULL,
pszDataRecv,
szDataRecvBitMax
);
} else {
status = pcd_14a_reader_bytes_transfer(
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)
// Calculate and store CRC
uint8_t crc_buff[DEF_CRC_LENGTH] = { 0x00 };
crc_14a_calculate(pDataRecv, finalRecvBytes - DEF_CRC_LENGTH, crc_buff);
// Verify CRC
if (pDataRecv[finalRecvBytes - 2] != crc_buff[0] || pDataRecv[finalRecvBytes - 1] != crc_buff[1]) {
// We have found an error in CRC verification and need to inform the upper computer!
*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,
// we will return the data without CRC
*pszDataRecv = finalRecvBytes - DEF_CRC_LENGTH;
}
} else {
// The data is insufficient to support the length of the CRC, so it is returned as is
*pszDataRecv = 0;
}
} else {
// Do not verify CRC, all data is returned as is
*pszDataRecv = finalRecvBytes;
}
// We need to recover the timeout value
g_com_timeout_ms = oldWaitRespTimeout;
} else {
*pszDataRecv = 0;
}
}
// Finally, keep the field open as needed
if (!keepField) {
pcd_14a_reader_antenna_off();
}
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 bitsFrame, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout,
uint16_t szDataSend, 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);
+25 -13
View File
@@ -1589,26 +1589,34 @@ class HF14ARaw(ReaderRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit or None:
parser = ArgumentParserNoExit()
parser.add_argument('-r', '--response', help="do not read response", action='store_true', default=False,)
parser.add_argument('-c', '--crc', help="calculate and append CRC", 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('-o', '--open-rf', help="active signal field ON", action='store_true', default=False,)
parser.add_argument('-s', '--select-tag', help="Select the tag before executing the command", action='store_true', default=False,)
parser.add_argument('-b', '--bits', type=int, help="number of bits to send. Useful for send partial byte")
parser.add_argument('-t', '--timeout', type=int, help="timeout in ms", default=100)
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('-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' \
# ' hf 14a raw -b 7 -d 40 -k\n' \
# ' hf 14a raw -d 43 -k\n' \
# ' hf 14a raw -d 3000 -c\n' \
# ' hf 14a raw -sc -d 6000\n'
return parser
def on_exec(self, args: argparse.Namespace):
options = {
'open_rf_field': self.bool_to_bit(args.open_rf),
'wait_response': self.bool_to_bit(args.response == False),
'activate_rf_field': self.bool_to_bit(args.activate_rf),
'wait_response': self.bool_to_bit(not args.response),
'append_crc': self.bool_to_bit(args.crc),
'bit_frame': self.bool_to_bit(args.bits is not None),
'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),
}
data: str = args.data
if data is not None:
@@ -1624,6 +1632,10 @@ class HF14ARaw(ReaderRequiredUnit):
return
else:
data_bytes = []
if args.bits is not None and args.crc:
print(f" [!] {CR}--bits and --crc are mutually exclusive{C0}")
return
# Exec 14a raw cmd.
resp = self.cmd.hf14a_raw(options, args.timeout, data_bytes, args.bits)
if resp.status == chameleon_status.Device.HF_TAG_OK:
@@ -1632,9 +1644,9 @@ class HF14ARaw(ReaderRequiredUnit):
# print head
" - " +
# print data
' '.join([ hex(byte).replace('0x', '').rjust(2, '0') for byte in resp.data ])
' '.join([hex(byte).replace('0x', '').rjust(2, '0') for byte in resp.data])
)
else:
print(F" [*] {CY}No data response{C0}")
print(F" [*] {CY}No response{C0}")
else:
print(f" [!] {CR}{chameleon_status.message[resp.status]}{C0} ")
+15 -22
View File
@@ -617,7 +617,7 @@ class ChameleonCMD:
resp.data = resp.status == chameleon_status.Device.HF_TAG_OK
return resp
def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bit_owned_by_the_last_byte=None):
def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bitlen=None):
"""
Send raw cmd to 14a tag
:param options:
@@ -629,40 +629,35 @@ class ChameleonCMD:
class CStruct(ctypes.BigEndianStructure):
_fields_ = [
("open_rf_field", ctypes.c_uint8, 1),
("activate_rf_field", ctypes.c_uint8, 1),
("wait_response", ctypes.c_uint8, 1),
("append_crc", ctypes.c_uint8, 1),
("bit_frame", ctypes.c_uint8, 1),
("auto_select", ctypes.c_uint8, 1),
("keep_rf_field", ctypes.c_uint8, 1),
("check_response_crc", ctypes.c_uint8, 1),
("reserved", ctypes.c_uint8, 1),
("reserved", ctypes.c_uint8, 2),
]
cs = CStruct()
cs.open_rf_field = options['open_rf_field']
cs.activate_rf_field = options['activate_rf_field']
cs.wait_response = options['wait_response']
cs.append_crc = options['append_crc']
cs.bit_frame = options['bit_frame']
cs.auto_select = options['auto_select']
cs.keep_rf_field = options['keep_rf_field']
cs.check_response_crc = options['check_response_crc']
if options['bit_frame'] == 1:
bits_or_bytes = len(data) * 8 # bits = bytes * 8(bit)
if bit_owned_by_the_last_byte is not None and bit_owned_by_the_last_byte != 8:
bits_or_bytes = bits_or_bytes - (8 - bit_owned_by_the_last_byte)
if bitlen is None:
bitlen = len(data) * 8 # bits = bytes * 8(bit)
else:
bits_or_bytes = len(data) # bytes length
if len(data) > 0:
data = struct.pack(f'!BHH{len(data)}s', bytes(cs)[0], resp_timeout_ms, bits_or_bytes, bytearray(data))
else:
data = struct.pack(f'!BHH', bytes(cs)[0], resp_timeout_ms, 0)
if len(data) == 0:
raise ValueError(f'bitlen={bitlen} but missing data')
if not ((len(data) - 1) * 8 < bitlen <= len(data) * 8):
raise ValueError(f'bitlen={bitlen} incompatible with provided data ({len(data)} bytes), '
f'must be between {((len(data) - 1) * 8 )+1} and {len(data) * 8} included')
data = bytes(cs)+struct.pack(f'!HH{len(data)}s', resp_timeout_ms, bitlen, bytearray(data))
return self.device.send_cmd_sync(DATA_CMD_HF14A_RAW, data, timeout=(resp_timeout_ms / 1000) + 1)
@expect_response(chameleon_status.Device.HF_TAG_OK)
def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target):
"""
@@ -1228,26 +1223,24 @@ def test_fn():
cml.set_device_reader_mode()
options = {
'open_rf_field': 1,
'activate_rf_field': 1,
'wait_response': 1,
'append_crc': 0,
'bit_frame': 1,
'auto_select': 0,
'keep_rf_field': 1,
'check_response_crc': 0,
}
# unlock 1
resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bit_owned_by_the_last_byte=7)
resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bitlen=7)
if resp.status == 0x00 and resp.data[0] == 0x0a:
print("Gen1A unlock 1 success")
# unlock 2
options['bit_frame'] = 0
resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x43])
if resp.status == 0x00 and resp.data[0] == 0x0a:
print("Gen1A unlock 2 success")
print("Start dump gen1a memeory...")
print("Start dump gen1a memory...")
block = 0
while block < 64:
# Tag read block cmd