diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 4b6503b7289..c6304d53ca6 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -7,10 +7,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include @@ -22,6 +25,23 @@ #define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_device_test.nfc") #define NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_dict.nfc") +#define NFC_TEST_FLAG_WORKER_DONE (1) + +typedef enum { + NfcTestMfClassicSendFrameTestStateAuth, + NfcTestMfClassicSendFrameTestStateReadBlock, + + NfcTestMfClassicSendFrameTestStateFail, + NfcTestMfClassicSendFrameTestStateSuccess, +} NfcTestMfClassicSendFrameTestState; + +typedef struct { + NfcTestMfClassicSendFrameTestState state; + BitBuffer* tx_buf; + BitBuffer* rx_buf; + FuriThreadId thread_id; +} NfcTestMfClassicSendFrameTest; + typedef struct { Storage* storage; } NfcTest; @@ -435,6 +455,109 @@ static void mf_classic_value_block(void) { nfc_free(poller); } +NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* context) { + furi_check(event.poller); + furi_check(event.parent_event_data); + furi_check(context); + + NfcCommand command = NfcCommandContinue; + MfClassicPoller* instance = event.poller; + NfcTestMfClassicSendFrameTest* frame_test = context; + Iso14443_3aPollerEvent* iso3_event = event.parent_event_data; + + MfClassicError error = MfClassicErrorNone; + if(iso3_event->type == Iso14443_3aPollerEventTypeReady) { + if(frame_test->state == NfcTestMfClassicSendFrameTestStateAuth) { + MfClassicKey key = { + .data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }; + error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL); + frame_test->state = (error == MfClassicErrorNone) ? + NfcTestMfClassicSendFrameTestStateReadBlock : + NfcTestMfClassicSendFrameTestStateFail; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateReadBlock) { + do { + const uint8_t read_block_cmd[] = { + 0x30, + 0x01, + 0x8b, + 0xb9, + }; + bit_buffer_copy_bytes(frame_test->tx_buf, read_block_cmd, sizeof(read_block_cmd)); + + error = mf_classic_poller_send_encrypted_frame( + instance, frame_test->tx_buf, frame_test->rx_buf, 200000); + if(error != MfClassicErrorNone) break; + if(bit_buffer_get_size_bytes(frame_test->rx_buf) != 18) { + error = MfClassicErrorProtocol; + break; + } + + const uint8_t* rx_data = bit_buffer_get_data(frame_test->rx_buf); + const uint8_t rx_data_ref[16] = {0}; + if(memcmp(rx_data, rx_data_ref, sizeof(rx_data_ref)) != 0) { + error = MfClassicErrorProtocol; + break; + } + } while(false); + + frame_test->state = (error == MfClassicErrorNone) ? + NfcTestMfClassicSendFrameTestStateSuccess : + NfcTestMfClassicSendFrameTestStateFail; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateSuccess) { + command = NfcCommandStop; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateFail) { + command = NfcCommandStop; + } + } else { + frame_test->state = NfcTestMfClassicSendFrameTestStateFail; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_thread_flags_set(frame_test->thread_id, NFC_TEST_FLAG_WORKER_DONE); + } + + return command; +} + +MU_TEST(mf_classic_send_frame_test) { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + NfcPoller* mfc_poller = nfc_poller_alloc(poller, NfcProtocolMfClassic); + NfcTestMfClassicSendFrameTest context = { + .state = NfcTestMfClassicSendFrameTestStateAuth, + .thread_id = furi_thread_get_current_id(), + .tx_buf = bit_buffer_alloc(32), + .rx_buf = bit_buffer_alloc(32), + }; + nfc_poller_start_ex(mfc_poller, mf_classic_poller_send_frame_callback, &context); + + uint32_t flag = + furi_thread_flags_wait(NFC_TEST_FLAG_WORKER_DONE, FuriFlagWaitAny, FuriWaitForever); + mu_assert(flag == NFC_TEST_FLAG_WORKER_DONE, "Wrong thread flag"); + nfc_poller_stop(mfc_poller); + nfc_poller_free(mfc_poller); + + mu_assert( + context.state == NfcTestMfClassicSendFrameTestStateSuccess, "Wrong test state at the end"); + + bit_buffer_free(context.tx_buf); + bit_buffer_free(context.rx_buf); + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} + MU_TEST(mf_classic_dict_test) { Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_common_stat(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == FSE_OK) { @@ -538,11 +661,11 @@ MU_TEST_SUITE(nfc) { MU_RUN_TEST(mf_classic_1k_7b_file_test); MU_RUN_TEST(mf_classic_4k_4b_file_test); MU_RUN_TEST(mf_classic_4k_7b_file_test); - MU_RUN_TEST(mf_classic_reader); + MU_RUN_TEST(mf_classic_reader); MU_RUN_TEST(mf_classic_write); MU_RUN_TEST(mf_classic_value_block); - + MU_RUN_TEST(mf_classic_send_frame_test); MU_RUN_TEST(mf_classic_dict_test); nfc_test_free(); diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c index ef51d98e0b3..deba1bca284 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c @@ -34,6 +34,8 @@ static void nfc_scene_more_info_on_enter_mf_desfire(NfcApp* instance) { static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent event, void* context) { furi_assert(event.protocol == NfcProtocolMfDesfire); + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; const MfDesfirePollerEvent* mf_desfire_event = event.event_data; @@ -41,10 +43,12 @@ static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent even nfc_device_set_data( instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - return NfcCommandStop; + command = NfcCommandStop; + } else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) { + command = NfcCommandReset; } - return NfcCommandContinue; + return command; } static void nfc_scene_read_on_enter_mf_desfire(NfcApp* instance) { diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index 94b333f5523..f8eacd51a22 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -11,24 +11,29 @@ void nfc_render_mf_desfire_info( const uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); const uint32_t bytes_free = data->free_memory.is_present ? data->free_memory.bytes_free : 0; - furi_string_cat_printf(str, "\n%lu", bytes_total); + if(data->master_key_settings.is_free_directory_list) { + const uint32_t app_count = simple_array_get_count(data->applications); + uint32_t file_count = 0; + + for(uint32_t i = 0; i < app_count; ++i) { + const MfDesfireApplication* app = simple_array_cget(data->applications, i); + if(app->key_settings.is_free_directory_list) { + file_count += simple_array_get_count(app->file_ids); + } + } - if(data->version.sw_storage & 1) { - furi_string_push_back(str, '+'); + furi_string_cat_printf(str, "\n%lu Application%s", app_count, app_count != 1 ? "s" : ""); + furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + } else { + furi_string_cat_printf(str, "\nAuth required to read apps!"); } - furi_string_cat_printf(str, " bytes, %lu bytes free\n", bytes_free); - - const uint32_t app_count = simple_array_get_count(data->applications); - uint32_t file_count = 0; + furi_string_cat_printf(str, "\n%lu", bytes_total); - for(uint32_t i = 0; i < app_count; ++i) { - const MfDesfireApplication* app = simple_array_cget(data->applications, i); - file_count += simple_array_get_count(app->file_ids); + if(data->version.sw_storage & 1) { + furi_string_push_back(str, '+'); } - - furi_string_cat_printf(str, "%lu Application%s", app_count, app_count != 1 ? "s" : ""); - furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + furi_string_cat_printf(str, " bytes, %lu bytes free", bytes_free); if(format_type != NfcProtocolFormatTypeFull) return; @@ -101,17 +106,29 @@ void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriStri } void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str) { - furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); - furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); - furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); - furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); - furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + if(data->is_free_directory_list) { + furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); + furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); + furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); + furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); + furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + } else { + furi_string_cat_printf(str, "changeKeyID ??\n"); + furi_string_cat_printf(str, "configChangeable ??\n"); + furi_string_cat_printf(str, "freeCreateDelete ??\n"); + furi_string_cat_printf(str, "freeDirectoryList 0\n"); + furi_string_cat_printf(str, "masterChangeable ??\n"); + } if(data->flags) { furi_string_cat_printf(str, "flags %d\n", data->flags); } - furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); + if(data->is_free_directory_list) { + furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); + } else { + furi_string_cat_printf(str, "maxKeys ??\n"); + } } void nfc_render_mf_desfire_key_version( @@ -123,14 +140,16 @@ void nfc_render_mf_desfire_key_version( void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str) { const uint8_t* app_id = data->data; - furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[0], app_id[1], app_id[2]); + furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[2], app_id[1], app_id[0]); } void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str) { nfc_render_mf_desfire_key_settings(&data->key_settings, str); - for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { - nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + if(data->key_settings.is_free_directory_list) { + for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { + nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + } } } @@ -179,13 +198,16 @@ void nfc_render_mf_desfire_file_settings_data( } furi_string_cat_printf(str, "%s %s\n", type, comm); - furi_string_cat_printf( - str, - "r %d w %d rw %d c %d\n", - settings->access_rights >> 12 & 0xF, - settings->access_rights >> 8 & 0xF, - settings->access_rights >> 4 & 0xF, - settings->access_rights & 0xF); + + for(size_t i = 0; i < settings->access_rights_len; i++) { + furi_string_cat_printf( + str, + "r %d w %d rw %d c %d\n", + settings->access_rights[i] >> 12 & 0xF, + settings->access_rights[i] >> 8 & 0xF, + settings->access_rights[i] >> 4 & 0xF, + settings->access_rights[i] & 0xF); + } uint32_t record_count = 1; uint32_t record_size = 0; @@ -217,6 +239,20 @@ void nfc_render_mf_desfire_file_settings_data( break; } + bool is_auth_required = true; + for(size_t i = 0; i < settings->access_rights_len; i++) { + uint8_t read_rights = (settings->access_rights[i] >> 12) & 0x0f; + uint8_t read_write_rights = (settings->access_rights[i] >> 4) & 0x0f; + if((read_rights == 0x0e) || (read_write_rights == 0x0e)) { + is_auth_required = false; + break; + } + } + if(is_auth_required) { + furi_string_cat_printf(str, "Auth required to read file data\n"); + return; + } + if(simple_array_get_count(data->data) == 0) { return; } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c index 76834e3f4f7..3e111c723cd 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c @@ -35,7 +35,7 @@ void nfc_scene_mf_desfire_more_info_on_enter(void* context) { for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) { const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); furi_string_printf( - label, "App %02x%02x%02x", app_id->data[0], app_id->data[1], app_id->data[2]); + label, "App %02x%02x%02x", app_id->data[2], app_id->data[1], app_id->data[0]); submenu_add_item( submenu, furi_string_get_cstr(label), diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 19e52570175..518d029d078 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -315,6 +315,89 @@ MfClassicError mf_classic_poller_value_cmd( */ MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8_t block_num); +/** + * @brief Transmit and receive Iso14443_3a standard frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_standard_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Iso14443_3a frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Iso14443_3a frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * Custom parity bits must be set in the tx_buffer. The rx_buffer will contain + * the received data with the parity bits. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_custom_parity_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Mifare Classic encrypted frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the plain data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with decyphered received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_encrypted_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 2b01e74eedb..949ef8e66ee 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -465,3 +465,77 @@ MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8 return ret; } + +MfClassicError mf_classic_poller_send_standard_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = + iso14443_3a_poller_txrx(instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_custom_parity_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_encrypted_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + MfClassicError ret = MfClassicErrorNone; + do { + crypto1_encrypt(instance->crypto, NULL, tx_buffer, instance->tx_encrypted_buffer); + + Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + fwt_fc); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + + crypto1_decrypt(instance->crypto, instance->rx_encrypted_buffer, rx_buffer); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index 4c307538606..2818e9a24eb 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -21,8 +21,6 @@ extern "C" { #define MF_DESFIRE_CMD_GET_VALUE (0x6C) #define MF_DESFIRE_CMD_READ_RECORDS (0xBB) -#define MF_DESFIRE_FLAG_HAS_NEXT (0xAF) - #define MF_DESFIRE_MAX_KEYS (14) #define MF_DESFIRE_MAX_FILES (32) @@ -71,11 +69,6 @@ typedef struct { typedef uint8_t MfDesfireKeyVersion; -typedef struct { - MfDesfireKeySettings key_settings; - SimpleArray* key_versions; -} MfDesfireKeyConfiguration; - typedef enum { MfDesfireFileTypeStandard = 0, MfDesfireFileTypeBackup = 1, @@ -96,7 +89,8 @@ typedef uint16_t MfDesfireFileAccessRights; typedef struct { MfDesfireFileType type; MfDesfireFileCommunicationSettings comm; - MfDesfireFileAccessRights access_rights; + MfDesfireFileAccessRights access_rights[MF_DESFIRE_MAX_KEYS]; + uint8_t access_rights_len; union { struct { uint32_t size; @@ -136,6 +130,7 @@ typedef enum { MfDesfireErrorNotPresent, MfDesfireErrorProtocol, MfDesfireErrorTimeout, + MfDesfireErrorAuthentication, } MfDesfireError; typedef struct { diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index 646803e75b6..bfbbadadff1 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -1,5 +1,7 @@ #include "mf_desfire_i.h" +#define TAG "MfDesfire" + #define BITS_IN_BYTE (8U) #define MF_DESFIRE_FFF_VERSION_KEY \ @@ -175,59 +177,88 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer }; } MfDesfireFileSettingsLayout; + MfDesfireFileSettings file_settings_temp = {}; do { const size_t data_size = bit_buffer_get_size_bytes(buf); + const uint8_t* data_ptr = bit_buffer_get_data(buf); const size_t min_data_size = sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData); - const size_t max_data_size = - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue); - - if(data_size < min_data_size) break; - if(data_size <= max_data_size) { - MfDesfireFileSettingsLayout layout; - bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout)); - - data->type = layout.header.type; - data->comm = layout.header.comm; - data->access_rights = layout.header.access_rights; - - if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { - if(data_size != min_data_size) break; - - data->data.size = layout.data.size; - } else if(data->type == MfDesfireFileTypeValue) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue)) - break; - data->value.lo_limit = layout.value.lo_limit; - data->value.hi_limit = layout.value.hi_limit; - data->value.limited_credit_value = layout.value.limited_credit_value; - data->value.limited_credit_enabled = layout.value.limited_credit_enabled; - - } else if( - data->type == MfDesfireFileTypeLinearRecord || - data->type == MfDesfireFileTypeCyclicRecord) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord)) - break; + if(data_size < min_data_size) { + FURI_LOG_E( + TAG, "File settings size %zu less than minimum %zu", data_size, min_data_size); + break; + } - data->record.size = layout.record.size; - data->record.max = layout.record.max; - data->record.cur = layout.record.cur; + size_t bytes_processed = sizeof(MfDesfireFileSettingsHeader); + MfDesfireFileSettingsLayout layout = {}; + memcpy(&layout.header, data_ptr, sizeof(MfDesfireFileSettingsHeader)); + bool has_additional_access_rights = (layout.header.comm & 0x80) != 0; + + file_settings_temp.type = layout.header.type; + file_settings_temp.comm = layout.header.comm & 0x03; + file_settings_temp.access_rights_len = 1; + file_settings_temp.access_rights[0] = layout.header.access_rights; + + if(file_settings_temp.type == MfDesfireFileTypeStandard || + file_settings_temp.type == MfDesfireFileTypeBackup) { + memcpy( + &layout.data, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsData)); + file_settings_temp.data.size = layout.data.size; + bytes_processed += sizeof(MfDesfireFileSettingsData); + } else if(file_settings_temp.type == MfDesfireFileTypeValue) { + memcpy( + &layout.value, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsValue)); + file_settings_temp.value.lo_limit = layout.value.lo_limit; + file_settings_temp.value.hi_limit = layout.value.hi_limit; + file_settings_temp.value.limited_credit_value = layout.value.limited_credit_value; + file_settings_temp.value.limited_credit_enabled = layout.value.limited_credit_enabled; + bytes_processed += sizeof(MfDesfireFileSettingsValue); + } else if( + file_settings_temp.type == MfDesfireFileTypeLinearRecord || + file_settings_temp.type == MfDesfireFileTypeCyclicRecord) { + memcpy( + &layout.record, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsRecord)); + file_settings_temp.record.size = layout.record.size; + file_settings_temp.record.max = layout.record.max; + file_settings_temp.record.cur = layout.record.cur; + bytes_processed += sizeof(MfDesfireFileSettingsRecord); + } else { + FURI_LOG_W(TAG, "Unknown file type: %02x", file_settings_temp.type); + break; + } - } else { + if(has_additional_access_rights) { + uint8_t additional_access_rights_len = bit_buffer_get_byte(buf, bytes_processed); + FURI_LOG_D(TAG, "Has additional rights: %d", additional_access_rights_len); + if(data_size != bytes_processed + + additional_access_rights_len * sizeof(MfDesfireFileAccessRights) + + 1) { + FURI_LOG_W(TAG, "Unexpected command length: %zu", data_size); + for(size_t i = 0; i < bit_buffer_get_size_bytes(buf); i++) { + printf("%02X ", bit_buffer_get_byte(buf, i)); + } + printf("\r\n"); break; } - } else { - // TODO FL-3750: process HID Desfire command response here - // Set default fields for now - data->type = 0; - data->comm = 0; - data->access_rights = 0; - data->data.size = 0; + if(additional_access_rights_len > + MF_DESFIRE_MAX_KEYS * sizeof(MfDesfireFileAccessRights)) + break; + + memcpy( + &file_settings_temp.access_rights[1], + &data_ptr[bytes_processed], + additional_access_rights_len * sizeof(MfDesfireFileAccessRights)); + file_settings_temp.access_rights_len += additional_access_rights_len; } + *data = file_settings_temp; parsed = true; } while(false); @@ -392,18 +423,19 @@ bool mf_desfire_file_settings_load( break; furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY); + uint32_t access_rights_len = 0; + if(!flipper_format_get_value_count(ff, furi_string_get_cstr(key), &access_rights_len)) + break; + if((access_rights_len == 0) || ((access_rights_len % 2) != 0)) break; if(!flipper_format_read_hex( - ff, - furi_string_get_cstr(key), - (uint8_t*)&data->access_rights, - sizeof(MfDesfireFileAccessRights))) + ff, furi_string_get_cstr(key), (uint8_t*)&data->access_rights, access_rights_len)) break; + data->access_rights_len = access_rights_len / sizeof(MfDesfireFileAccessRights); if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1)) break; - } else if(data->type == MfDesfireFileTypeValue) { furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.hi_limit, 1)) @@ -641,8 +673,8 @@ bool mf_desfire_file_settings_save( if(!flipper_format_write_hex( ff, furi_string_get_cstr(key), - (const uint8_t*)&data->access_rights, - sizeof(MfDesfireFileAccessRights))) + (const uint8_t*)data->access_rights, + data->access_rights_len * sizeof(MfDesfireFileAccessRights))) break; if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { @@ -737,8 +769,11 @@ bool mf_desfire_application_save( if(i != key_version_count) break; const uint32_t file_count = simple_array_get_count(data->file_ids); - if(!mf_desfire_file_ids_save(simple_array_get_data(data->file_ids), file_count, prefix, ff)) - break; + if(file_count > 0) { + if(!mf_desfire_file_ids_save( + simple_array_get_data(data->file_ids), file_count, prefix, ff)) + break; + } for(i = 0; i < file_count; ++i) { const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h index 05381096d16..21250baace3 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h @@ -5,6 +5,52 @@ #define MF_DESFIRE_FFF_PICC_PREFIX "PICC" #define MF_DESFIRE_FFF_APP_PREFIX "Application" +// Successful operation +#define MF_DESFIRE_STATUS_OPERATION_OK (0x00) +// No changes done to backup files, CommitTransaction / AbortTransaction not necessary +#define MF_DESFIRE_STATUS_NO_CHANGES (0x0C) +// Insufficient NV-Memory to complete command +#define MF_DESFIRE_STATUS_OUT_OF_EEPROM_ERROR (0x0E) +// Command code not supported +#define MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE (0x1C) +// CRC or MAC does not match data Padding bytes not valid +#define MF_DESFIRE_STATUS_INTEGRITY_ERROR (0x1E) +// Invalid key number specified +#define MF_DESFIRE_STATUS_NO_SUCH_KEY (0x40) +// Length of command string invalid +#define MF_DESFIRE_STATUS_LENGTH_ERROR (0x7E) +// Current configuration / status does not allow the requested command +#define MF_DESFIRE_STATUS_PERMISSION_DENIED (0x9D) +// Value of the parameter(s) invalid +#define MF_DESFIRE_STATUS_PARAMETER_ERROR (0x9E) +// Requested AID not present on PICC +#define MF_DESFIRE_STATUS_APPLICATION_NOT_FOUND (0xA0) +// Unrecoverable error within application, application will be disabled +#define MF_DESFIRE_STATUS_APPL_INTEGRITY_ERROR (0xA1) +// Current authentication status does not allow the requested command +#define MF_DESFIRE_STATUS_AUTHENTICATION_ERROR (0xAE) +// Additional data frame is expected to be sent +#define MF_DESFIRE_STATUS_ADDITIONAL_FRAME (0xAF) +// Attempt to read/write data from/to beyond the file's/record's limits +// Attempt to exceed the limits of a value file. +#define MF_DESFIRE_STATUS_BOUNDARY_ERROR (0xBE) +// Unrecoverable error within PICC, PICC will be disabled +#define MF_DESFIRE_STATUS_PICC_INTEGRITY_ERROR (0xC1) +// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD +#define MF_DESFIRE_STATUS_COMMAND_ABORTED (0xCA) +// PICC was disabled by an unrecoverable error +#define MF_DESFIRE_STATUS_PICC_DISABLED_ERROR (0xCD) +// Number of Applications limited to 28, no additional CreateApplication possible +#define MF_DESFIRE_STATUS_COUNT_ERROR (0xCE) +// Creation of file/application failed because file/application with same number already exists +#define MF_DESFIRE_STATUS_DUBLICATE_ERROR (0xDE) +// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated +#define MF_DESFIRE_STATUS_EEPROM_ERROR (0xEE) +// Specified file number does not exist +#define MF_DESFIRE_STATUS_FILE_NOT_FOUND (0xF0) +// Unrecoverable error within file, file will be disabled +#define MF_DESFIRE_STATUS_FILE_INTEGRITY_ERROR (0xF1) + // SimpleArray configurations extern const SimpleArrayConfig mf_desfire_key_version_array_config; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index c9d8bbab603..fa8a7ae9b90 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -75,17 +75,23 @@ static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instan } static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->error = mf_desfire_poller_read_free_memory(instance, &instance->data->free_memory); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read free memory success"); instance->state = MfDesfirePollerStateReadMasterKeySettings; + } else if(instance->error == MfDesfireErrorNotPresent) { + FURI_LOG_D(TAG, "Read free memoty is unsupported"); + instance->state = MfDesfirePollerStateReadMasterKeySettings; + command = NfcCommandReset; } else { FURI_LOG_E(TAG, "Failed to read free memory"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); instance->state = MfDesfirePollerStateReadFailed; } - return NfcCommandContinue; + return command; } static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) { @@ -94,6 +100,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePo if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key settings success"); instance->state = MfDesfirePollerStateReadMasterKeyVersion; + } else if(instance->error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids"); + instance->data->master_key_settings.is_free_directory_list = false; + instance->data->master_key_settings.max_keys = 1; + instance->state = MfDesfirePollerStateReadMasterKeyVersion; } else { FURI_LOG_E(TAG, "Failed to read master key settings"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); @@ -110,7 +121,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePol instance->data->master_key_settings.max_keys); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key version success"); - instance->state = MfDesfirePollerStateReadApplicationIds; + if(instance->data->master_key_settings.is_free_directory_list) { + instance->state = MfDesfirePollerStateReadApplicationIds; + } else { + instance->state = MfDesfirePollerStateReadSuccess; + } } else { FURI_LOG_E(TAG, "Failed to read master key version"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); @@ -126,6 +141,9 @@ static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read application ids success"); instance->state = MfDesfirePollerStateReadApplications; + } else if(instance->error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Read application ids impossible without authentication"); + instance->state = MfDesfirePollerStateReadSuccess; } else { FURI_LOG_E(TAG, "Failed to read application ids"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 790f1171573..2b86318491f 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -19,6 +19,17 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error) { } } +MfDesfireError mf_desfire_process_status_code(uint8_t status_code) { + switch(status_code) { + case MF_DESFIRE_STATUS_OPERATION_OK: + return MfDesfireErrorNone; + case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: + return MfDesfireErrorAuthentication; + default: + return MfDesfireErrorProtocol; + } +} + MfDesfireError mf_desfire_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, @@ -42,7 +53,7 @@ MfDesfireError mf_desfire_send_chunks( } bit_buffer_reset(instance->tx_buffer); - bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_FLAG_HAS_NEXT); + bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME); if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); @@ -50,7 +61,8 @@ MfDesfireError mf_desfire_send_chunks( bit_buffer_reset(rx_buffer); } - while(bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_FLAG_HAS_NEXT)) { + while( + bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME)) { Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); @@ -71,6 +83,11 @@ MfDesfireError mf_desfire_send_chunks( } } while(false); + if(error == MfDesfireErrorNone) { + uint8_t err_code = bit_buffer_get_byte(instance->rx_buffer, 0); + error = mf_desfire_process_status_code(err_code); + } + return error; } @@ -110,7 +127,7 @@ MfDesfireError if(error != MfDesfireErrorNone) break; if(!mf_desfire_free_memory_parse(data, instance->result_buffer)) { - error = MfDesfireErrorProtocol; + error = MfDesfireErrorNotPresent; } } while(false); @@ -414,13 +431,25 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( simple_array_init(data, file_id_count); } - for(uint32_t i = 0; i < file_id_count; ++i) { + for(size_t i = 0; i < file_id_count; ++i) { const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); const MfDesfireFileSettings* file_settings_cur = simple_array_cget(file_settings, i); const MfDesfireFileType file_type = file_settings_cur->type; MfDesfireFileData* file_data = simple_array_get(data, i); + bool can_read_data = false; + for(size_t j = 0; j < file_settings_cur->access_rights_len; j++) { + uint8_t read_access = (file_settings_cur->access_rights[j] >> 12) & 0x0f; + uint8_t read_write_access = (file_settings_cur->access_rights[j] >> 4) & 0x0f; + can_read_data = (read_access == 0x0e) || (read_write_access == 0x0e); + if(can_read_data) break; + } + if(!can_read_data) { + FURI_LOG_D(TAG, "Can't read file %zu data without authentication", i); + continue; + } + if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) { error = mf_desfire_poller_read_file_data( instance, file_id, 0, file_settings_cur->data.size, file_data); @@ -432,8 +461,6 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( error = mf_desfire_poller_read_file_records( instance, file_id, 0, file_settings_cur->data.size, file_data); } - - if(error != MfDesfireErrorNone) break; } return error; @@ -448,22 +475,36 @@ MfDesfireError do { error = mf_desfire_poller_read_key_settings(instance, &data->key_settings); + if(error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids"); + data->key_settings.is_free_directory_list = false; + error = MfDesfireErrorNone; + break; + } if(error != MfDesfireErrorNone) break; error = mf_desfire_poller_read_key_versions( instance, data->key_versions, data->key_settings.max_keys); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read key version: %d", error); + break; + } error = mf_desfire_poller_read_file_ids(instance, data->file_ids); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read file ids: %d", error); + break; + } error = mf_desfire_poller_read_file_settings_multi( instance, data->file_ids, data->file_settings); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read file settings: %d", error); + break; + } error = mf_desfire_poller_read_file_data_multi( instance, data->file_ids, data->file_settings, data->file_data); - if(error != MfDesfireErrorNone) break; } while(false); @@ -484,11 +525,13 @@ MfDesfireError mf_desfire_poller_read_applications( simple_array_init(data, app_id_count); } - for(uint32_t i = 0; i < app_id_count; ++i) { + for(size_t i = 0; i < app_id_count; ++i) { do { + FURI_LOG_D(TAG, "Selecting app %zu", i); error = mf_desfire_poller_select_application(instance, simple_array_cget(app_ids, i)); if(error != MfDesfireErrorNone) break; + FURI_LOG_D(TAG, "Reading app %zu", i); MfDesfireApplication* current_app = simple_array_get(data, i); error = mf_desfire_poller_read_application(instance, current_app); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h index 1c80af36fb6..19e38bebbc7 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -47,6 +47,8 @@ struct MfDesfirePoller { MfDesfireError mf_desfire_process_error(Iso14443_4aError error); +MfDesfireError mf_desfire_process_status_code(uint8_t status_code); + const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance); #ifdef __cplusplus diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 6e597e7d05b..703bcc50cc1 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.7,, +Version,+,60.8,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 09b8b9ee821..c0edf9ffa51 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.7,, +Version,+,60.8,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2435,6 +2435,10 @@ Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, M Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" +Function,+,mf_classic_poller_send_custom_parity_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_encrypted_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_standard_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,mf_classic_poller_sync_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" Function,+,mf_classic_poller_sync_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" Function,+,mf_classic_poller_sync_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*"