From f7db6d0fb3a65006ac3a6eeaccb75e2610cf6b41 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 24 Sep 2023 00:42:58 +0200 Subject: [PATCH] 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 --- docs/protocol.md | 12 + firmware/application/src/app_cmd.c | 17 +- .../application/src/rfid/reader/hf/rc522.c | 224 +++++++++--------- .../application/src/rfid/reader/hf/rc522.h | 4 +- software/script/chameleon_cli_unit.py | 38 ++- software/script/chameleon_cmd.py | 37 ++- 6 files changed, 174 insertions(+), 158 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 018d477..85b4f1a 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -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 diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index b3879e6..d78199a 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -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, diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index a95b68d..569ba16 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -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; } \ No newline at end of file diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index 11926ab..899c148 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -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); diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 70e9b9d..cdc1555 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -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} ") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 085b86a..367e78f 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -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