From a0fdc559c971a48a5ca58d0dc2ecdc1fce0be3fb Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 15 Jun 2021 17:54:09 +0300 Subject: [PATCH] [FL-662] Read Mifare Ultralight (#518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: add read mifare ultralight to menu * emv_decoder: add pragma once * nfc: add mifare ultralight reader * nfc: add mifare ultralight read draw * nfc: add mifare ultralight type checker * nfc: rework menu callback * mifare ultralight: change type names Co-authored-by: あく --- applications/nfc/nfc.c | 56 ++++++++---- applications/nfc/nfc_i.h | 1 + applications/nfc/nfc_types.h | 14 ++- applications/nfc/nfc_views.c | 53 +++++++++++ applications/nfc/nfc_views.h | 2 + applications/nfc/nfc_worker.c | 123 ++++++++++++++++++++++++++ applications/nfc/nfc_worker_i.h | 2 + lib/nfc_protocols/emv_decoder.h | 2 + lib/nfc_protocols/mifare_ultralight.c | 59 ++++++++++++ lib/nfc_protocols/mifare_ultralight.h | 56 ++++++++++++ 10 files changed, 351 insertions(+), 17 deletions(-) mode change 100644 => 100755 applications/nfc/nfc_views.c mode change 100644 => 100755 applications/nfc/nfc_worker.c create mode 100644 lib/nfc_protocols/mifare_ultralight.c create mode 100644 lib/nfc_protocols/mifare_ultralight.h diff --git a/applications/nfc/nfc.c b/applications/nfc/nfc.c index cecd15441..eb2c3f749 100755 --- a/applications/nfc/nfc.c +++ b/applications/nfc/nfc.c @@ -22,17 +22,7 @@ uint32_t nfc_view_exit(void* context) { void nfc_menu_callback(void* context, uint32_t index) { furi_assert(message_queue); NfcMessage message; - if(index == 0) { - message.type = NfcMessageTypeDetect; - } else if(index == 1) { - message.type = NfcMessageTypeReadEMV; - } else if(index == 2) { - message.type = NfcMessageTypeEmulateEMV; - } else if(index == 3) { - message.type = NfcMessageTypeEmulate; - } else if(index == 4) { - message.type = NfcMessageTypeField; - } + message.type = index; furi_check(osMessageQueuePut(message_queue, &message, 0, osWaitForever) == osOK); } @@ -52,11 +42,15 @@ Nfc* nfc_alloc() { // Menu nfc->submenu = submenu_alloc(); - submenu_add_item(nfc->submenu, "Detect", 0, nfc_menu_callback, nfc); - submenu_add_item(nfc->submenu, "Read EMV", 1, nfc_menu_callback, nfc); - submenu_add_item(nfc->submenu, "Emulate EMV", 2, nfc_menu_callback, nfc); - submenu_add_item(nfc->submenu, "Emulate", 3, nfc_menu_callback, nfc); - submenu_add_item(nfc->submenu, "Field", 4, nfc_menu_callback, nfc); + submenu_add_item(nfc->submenu, "Detect", NfcMessageTypeDetect, nfc_menu_callback, nfc); + submenu_add_item(nfc->submenu, "Read EMV", NfcMessageTypeReadEMV, nfc_menu_callback, nfc); + submenu_add_item( + nfc->submenu, "Emulate EMV", NfcMessageTypeEmulateEMV, nfc_menu_callback, nfc); + submenu_add_item(nfc->submenu, "Emulate", NfcMessageTypeEmulate, nfc_menu_callback, nfc); + submenu_add_item(nfc->submenu, "Field", NfcMessageTypeField, nfc_menu_callback, nfc); + submenu_add_item( + nfc->submenu, "Read MfUltralight", NfcMessageTypeReadMfUltralight, nfc_menu_callback, nfc); + View* submenu_view = submenu_get_view(nfc->submenu); view_set_previous_callback(submenu_view, nfc_view_exit); view_dispatcher_add_view(nfc->view_dispatcher, NfcViewMenu, submenu_view); @@ -98,6 +92,16 @@ Nfc* nfc_alloc() { view_set_previous_callback(nfc->view_field, nfc_view_stop); view_dispatcher_add_view(nfc->view_dispatcher, NfcViewField, nfc->view_field); + // Read Mifare Ultralight + nfc->view_read_mf_ultralight = view_alloc(); + view_set_context(nfc->view_read_mf_ultralight, nfc); + view_set_draw_callback(nfc->view_read_mf_ultralight, nfc_view_read_mf_ultralight_draw); + view_set_previous_callback(nfc->view_read_mf_ultralight, nfc_view_stop); + view_allocate_model( + nfc->view_read_mf_ultralight, ViewModelTypeLocking, sizeof(NfcViewReadModel)); + view_dispatcher_add_view( + nfc->view_dispatcher, NfcViewReadMfUltralight, nfc->view_read_mf_ultralight); + // Error nfc->view_error = view_alloc(); view_set_context(nfc->view_error, nfc); @@ -144,6 +148,10 @@ void nfc_free(Nfc* nfc) { view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewField); view_free(nfc->view_field); + // Read Mifare Ultralight + view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewReadMfUltralight); + view_free(nfc->view_read_mf_ultralight); + // Error view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewError); view_free(nfc->view_error); @@ -180,6 +188,7 @@ int32_t nfc_task(void* p) { NfcMessage message; while(1) { furi_check(osMessageQueueGet(message_queue, &message, NULL, osWaitForever) == osOK); + if(message.type == NfcMessageTypeDetect) { with_view_model( nfc->view_detect, (NfcViewReadModel * model) { @@ -200,6 +209,8 @@ int32_t nfc_task(void* p) { nfc_start(nfc, NfcViewEmulate, NfcWorkerStateEmulate); } else if(message.type == NfcMessageTypeField) { nfc_start(nfc, NfcViewField, NfcWorkerStateField); + } else if(message.type == NfcMessageTypeReadMfUltralight) { + nfc_start(nfc, NfcViewReadMfUltralight, NfcWorkerStateReadMfUltralight); } else if(message.type == NfcMessageTypeStop) { nfc_worker_stop(nfc->worker); } else if(message.type == NfcMessageTypeDeviceFound) { @@ -228,6 +239,19 @@ int32_t nfc_task(void* p) { model->found = false; return true; }); + } else if(message.type == NfcMessageTypeMfUlFound) { + with_view_model( + nfc->view_read_mf_ultralight, (NfcViewReadModel * model) { + model->found = true; + model->device = message.device; + return true; + }); + } else if(message.type == NfcMessageTypeMfUlNotFound) { + with_view_model( + nfc->view_read_mf_ultralight, (NfcViewReadModel * model) { + model->found = false; + return true; + }); } else if(message.type == NfcMessageTypeExit) { nfc_free(nfc); break; diff --git a/applications/nfc/nfc_i.h b/applications/nfc/nfc_i.h index 0ade0471b..3cac404c2 100644 --- a/applications/nfc/nfc_i.h +++ b/applications/nfc/nfc_i.h @@ -31,6 +31,7 @@ struct Nfc { View* view_emulate_emv; View* view_emulate; View* view_field; + View* view_read_mf_ultralight; View* view_cli; View* view_error; ViewDispatcher* view_dispatcher; diff --git a/applications/nfc/nfc_types.h b/applications/nfc/nfc_types.h index 4cf46e975..8787c38fb 100644 --- a/applications/nfc/nfc_types.h +++ b/applications/nfc/nfc_types.h @@ -44,8 +44,8 @@ typedef enum { NfcDeviceTypeNfcb, NfcDeviceTypeNfcf, NfcDeviceTypeNfcv, - NfcDeviceTypeNfcMifare, NfcDeviceTypeEMV, + NfcDeviceTypeMfUltralight, } NfcDeviceType; typedef struct { @@ -53,6 +53,12 @@ typedef struct { uint8_t number[8]; } EMVCard; +typedef struct { + uint8_t uid[7]; + uint8_t man_block[12]; + uint8_t otp[4]; +} MfUlCard; + typedef struct { NfcDeviceType type; union { @@ -61,6 +67,7 @@ typedef struct { rfalNfcfListenDevice nfcf; rfalNfcvListenDevice nfcv; EMVCard emv_card; + MfUlCard mf_ul_card; }; } NfcDevice; @@ -75,16 +82,19 @@ typedef enum { NfcWorkerStateEmulateEMV, NfcWorkerStateEmulate, NfcWorkerStateField, + NfcWorkerStateReadMfUltralight, // Transition NfcWorkerStateStop, } NfcWorkerState; typedef enum { + // From Menu NfcMessageTypeDetect, NfcMessageTypeReadEMV, NfcMessageTypeEmulateEMV, NfcMessageTypeEmulate, NfcMessageTypeField, + NfcMessageTypeReadMfUltralight, NfcMessageTypeStop, NfcMessageTypeExit, // From Worker @@ -92,6 +102,8 @@ typedef enum { NfcMessageTypeDeviceNotFound, NfcMessageTypeEMVFound, NfcMessageTypeEMVNotFound, + NfcMessageTypeMfUlFound, + NfcMessageTypeMfUlNotFound, } NfcMessageType; typedef struct { diff --git a/applications/nfc/nfc_views.c b/applications/nfc/nfc_views.c old mode 100644 new mode 100755 index c3c7feee1..769656b27 --- a/applications/nfc/nfc_views.c +++ b/applications/nfc/nfc_views.c @@ -150,6 +150,59 @@ void nfc_view_emulate_draw(Canvas* canvas, void* model) { canvas_draw_str(canvas, 2, 52, "SAK: 20 ATQA: 00/04"); } +void nfc_view_read_mf_ultralight_draw(Canvas* canvas, void* model) { + NfcViewReadModel* m = model; + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + char buffer[32]; + + if(m->found) { + canvas_draw_str(canvas, 0, 12, "Found Mifare Ultralight"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 22, "UID:"); + for(uint8_t i = 0; i < sizeof(m->device.mf_ul_card.uid); i++) { + snprintf( + buffer + (i * 2), sizeof(buffer) - (i * 2), "%02X", m->device.mf_ul_card.uid[i]); + } + buffer[sizeof(m->device.mf_ul_card.uid) * 2] = 0; + canvas_draw_str(canvas, 18, 22, buffer); + + uint8_t man_bl_size = sizeof(m->device.mf_ul_card.man_block); + canvas_draw_str(canvas, 2, 32, "Manufacturer block:"); + for(uint8_t i = 0; i < man_bl_size / 2; i++) { + snprintf( + buffer + (i * 2), + sizeof(buffer) - (i * 2), + "%02X", + m->device.mf_ul_card.man_block[i]); + } + buffer[man_bl_size] = 0; + canvas_draw_str(canvas, 2, 42, buffer); + + for(uint8_t i = 0; i < man_bl_size / 2; i++) { + snprintf( + buffer + (i * 2), + sizeof(buffer) - (i * 2), + "%02X", + m->device.mf_ul_card.man_block[man_bl_size / 2 + i]); + } + buffer[man_bl_size] = 0; + canvas_draw_str(canvas, 2, 52, buffer); + + canvas_draw_str(canvas, 2, 62, "OTP: "); + for(uint8_t i = 0; i < sizeof(m->device.mf_ul_card.otp); i++) { + snprintf( + buffer + (i * 2), sizeof(buffer) - (i * 2), "%02X", m->device.mf_ul_card.otp[i]); + } + buffer[sizeof(m->device.mf_ul_card.otp) * 2] = 0; + canvas_draw_str(canvas, 22, 62, buffer); + } else { + canvas_draw_str(canvas, 0, 12, "Searching"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 22, "Place card to the back"); + } +} + void nfc_view_field_draw(Canvas* canvas, void* model) { canvas_clear(canvas); canvas_set_font(canvas, FontPrimary); diff --git a/applications/nfc/nfc_views.h b/applications/nfc/nfc_views.h index 9a4dbc0cf..5ba990e7b 100644 --- a/applications/nfc/nfc_views.h +++ b/applications/nfc/nfc_views.h @@ -14,6 +14,7 @@ typedef enum { NfcViewEmulateEMV, NfcViewEmulate, NfcViewField, + NfcViewReadMfUltralight, NfcViewError, } NfcView; @@ -31,6 +32,7 @@ void nfc_view_read_emv_draw(Canvas* canvas, void* model); void nfc_view_emulate_emv_draw(Canvas* canvas, void* model); void nfc_view_emulate_draw(Canvas* canvas, void* model); +void nfc_view_read_mf_ultralight_draw(Canvas* canvas, void* model); void nfc_view_field_draw(Canvas* canvas, void* model); diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c old mode 100644 new mode 100755 index 78825789f..b2957608a --- a/applications/nfc/nfc_worker.c +++ b/applications/nfc/nfc_worker.c @@ -1,6 +1,7 @@ #include "nfc_worker_i.h" #include #include "nfc_protocols/emv_decoder.h" +#include "nfc_protocols/mifare_ultralight.h" #define NFC_WORKER_TAG "nfc worker" @@ -71,6 +72,8 @@ void nfc_worker_task(void* context) { nfc_worker_emulate(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateField) { nfc_worker_field(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateReadMfUltralight) { + nfc_worker_read_mf_ultralight(nfc_worker); } api_hal_nfc_deactivate(); nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); @@ -306,6 +309,126 @@ void nfc_worker_poll(NfcWorker* nfc_worker) { } } +void nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker) { + ReturnCode err; + rfalNfcDevice* dev_list; + uint8_t dev_cnt = 0; + uint8_t tx_buff[255] = {}; + uint16_t tx_len = 0; + uint8_t* rx_buff; + uint16_t* rx_len; + MfUltralightRead mf_ul_read; + + // Update screen before start searching + NfcMessage message = {.type = NfcMessageTypeMfUlNotFound}; + while(nfc_worker->state == NfcWorkerStateReadMfUltralight) { + furi_check( + osMessageQueuePut(nfc_worker->message_queue, &message, 0, osWaitForever) == osOK); + api_hal_nfc_deactivate(); + memset(&mf_ul_read, 0, sizeof(mf_ul_read)); + if(api_hal_nfc_detect(&dev_list, &dev_cnt, 100, false)) { + if(dev_list[0].type == RFAL_NFC_LISTEN_TYPE_NFCA && + mf_ul_check_card_type( + dev_list[0].dev.nfca.sensRes.anticollisionInfo, + dev_list[0].dev.nfca.sensRes.platformInfo, + dev_list[0].dev.nfca.selRes.sak)) { + // Get Mifare Ultralight version + FURI_LOG_I( + NFC_WORKER_TAG, "Found Mifare Ultralight tag. Trying to get tag version"); + tx_len = mf_ul_prepare_get_version(tx_buff); + err = api_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); + if(err == ERR_NONE) { + mf_ul_parse_get_version_response(rx_buff, &mf_ul_read); + FURI_LOG_I( + NFC_WORKER_TAG, + "Mifare Ultralight Type: %d, Pages: %d", + mf_ul_read.type, + mf_ul_read.pages_to_read); + } else if(err == ERR_TIMEOUT) { + FURI_LOG_W( + NFC_WORKER_TAG, + "Card doesn't respond to GET VERSION command. Reinit card and set default read parameters"); + err = ERR_NONE; + mf_ul_set_default_version(&mf_ul_read); + // Reinit device + api_hal_nfc_deactivate(); + if(!api_hal_nfc_detect(&dev_list, &dev_cnt, 100, false)) { + FURI_LOG_E(NFC_WORKER_TAG, "Lost connection. Restarting search"); + message.type = NfcMessageTypeMfUlNotFound; + continue; + } + } else { + FURI_LOG_E( + NFC_WORKER_TAG, + "Error getting Mifare Ultralight version. Error code: %d", + err); + message.type = NfcMessageTypeMfUlNotFound; + continue; + } + + // Dump Mifare Ultralight card + FURI_LOG_I(NFC_WORKER_TAG, "Trying to read pages"); + if(mf_ul_read.support_fast_read) { + // Read card with FAST_READ command + tx_len = mf_ul_prepare_fast_read(tx_buff, 0x00, mf_ul_read.pages_to_read - 1); + err = api_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); + if(err == ERR_NONE) { + FURI_LOG_I( + NFC_WORKER_TAG, + "Fast read pages %d - %d succeed", + 0, + mf_ul_read.pages_to_read - 1); + memcpy(mf_ul_read.dump, rx_buff, mf_ul_read.pages_to_read * 4); + mf_ul_read.pages_readed = mf_ul_read.pages_to_read; + } else { + FURI_LOG_E(NFC_WORKER_TAG, "Fast read failed"); + message.type = NfcMessageTypeMfUlNotFound; + continue; + } + } else { + // READ card with READ command (4 pages at a time) + for(uint8_t page = 0; page < mf_ul_read.pages_to_read; page += 4) { + tx_len = mf_ul_prepare_read(tx_buff, page); + err = api_hal_nfc_data_exchange(tx_buff, tx_len, &rx_buff, &rx_len, false); + if(err == ERR_NONE) { + FURI_LOG_I( + NFC_WORKER_TAG, "Read pages %d - %d succeed", page, page + 3); + memcpy(&mf_ul_read.dump[page * 4], rx_buff, 4 * 4); + mf_ul_read.pages_readed += 4; + } else { + FURI_LOG_W( + NFC_WORKER_TAG, "Read pages %d - %d failed", page, page + 3); + } + } + } + + // Fill message for nfc application + message.type = NfcMessageTypeMfUlFound; + memcpy( + message.device.mf_ul_card.uid, + dev_list[0].dev.nfca.nfcId1, + sizeof(message.device.mf_ul_card.uid)); + memcpy(message.device.mf_ul_card.man_block, mf_ul_read.dump, 4 * 3); + memcpy(message.device.mf_ul_card.otp, &mf_ul_read.dump[4 * 3], 4); + for(uint8_t i = 0; i < mf_ul_read.pages_readed * 4; i += 4) { + printf("Page %2d: ", i / 4); + for(uint8_t j = 0; j < 4; j++) { + printf("%02X ", mf_ul_read.dump[i + j]); + } + printf("\r\n"); + } + } else { + message.type = NfcMessageTypeMfUlNotFound; + FURI_LOG_W(NFC_WORKER_TAG, "Tag does not support Mifare Ultralight"); + } + } else { + message.type = NfcMessageTypeMfUlNotFound; + FURI_LOG_W(NFC_WORKER_TAG, "Can't find any tags"); + } + osDelay(100); + } +} + void nfc_worker_emulate(NfcWorker* nfc_worker) { while(nfc_worker->state == NfcWorkerStateEmulate) { if(api_hal_nfc_listen(100)) { diff --git a/applications/nfc/nfc_worker_i.h b/applications/nfc/nfc_worker_i.h index ba85f4b6f..a3ae0b488 100644 --- a/applications/nfc/nfc_worker_i.h +++ b/applications/nfc/nfc_worker_i.h @@ -38,3 +38,5 @@ void nfc_worker_poll(NfcWorker* nfc_worker); void nfc_worker_emulate(NfcWorker* nfc_worker); void nfc_worker_field(NfcWorker* nfc_worker); + +void nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker); diff --git a/lib/nfc_protocols/emv_decoder.h b/lib/nfc_protocols/emv_decoder.h index 31401f800..e19a543c8 100755 --- a/lib/nfc_protocols/emv_decoder.h +++ b/lib/nfc_protocols/emv_decoder.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include diff --git a/lib/nfc_protocols/mifare_ultralight.c b/lib/nfc_protocols/mifare_ultralight.c new file mode 100644 index 000000000..ef50f0cce --- /dev/null +++ b/lib/nfc_protocols/mifare_ultralight.c @@ -0,0 +1,59 @@ +#include "mifare_ultralight.h" + +bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { + return true; + } + return false; +} + +uint16_t mf_ul_prepare_get_version(uint8_t* dest) { + dest[0] = MF_UL_GET_VERSION_CMD; + return 1; +} + +void mf_ul_parse_get_version_response(uint8_t* buff, MfUltralightRead* mf_ul_read) { + MfUltralightVersion* version = (MfUltralightVersion*) buff; + if(version->storage_size == 0x0B || version->storage_size == 0x00) { + mf_ul_read->type = MfUltralightTypeUL11; + mf_ul_read->pages_to_read = 20; + mf_ul_read->support_fast_read = true; + } else if(version->storage_size == 0x0E) { + mf_ul_read->type = MfUltralightTypeUL21; + mf_ul_read->pages_to_read = 41; + mf_ul_read->support_fast_read = true; + } else if(version->storage_size == 0x0F) { + mf_ul_read->type = MfUltralightTypeNTAG213; + mf_ul_read->pages_to_read = 45; + mf_ul_read->support_fast_read = false; + } else if(version->storage_size == 0x11) { + mf_ul_read->type = MfUltralightTypeNTAG215; + mf_ul_read->pages_to_read = 135; + mf_ul_read->support_fast_read = false; + } else if(version->storage_size == 0x13) { + mf_ul_read->type = MfUltralightTypeNTAG216; + mf_ul_read->pages_to_read = 231; + mf_ul_read->support_fast_read = false; + } else { + mf_ul_set_default_version(mf_ul_read); + } +} + +void mf_ul_set_default_version(MfUltralightRead* mf_ul_read) { + mf_ul_read->type = MfUltralightTypeUnknown; + mf_ul_read->pages_to_read = 20; + mf_ul_read->support_fast_read = false; +} + +uint16_t mf_ul_prepare_read(uint8_t* dest, uint8_t start_page) { + dest[0] = MF_UL_READ_CMD; + dest[1] = start_page; + return 2; +} + +uint16_t mf_ul_prepare_fast_read(uint8_t* dest, uint8_t start_page, uint8_t end_page) { + dest[0] = MF_UL_FAST_READ_CMD; + dest[1] = start_page; + dest[2] = end_page; + return 3; +} diff --git a/lib/nfc_protocols/mifare_ultralight.h b/lib/nfc_protocols/mifare_ultralight.h new file mode 100644 index 000000000..435791916 --- /dev/null +++ b/lib/nfc_protocols/mifare_ultralight.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include + +#define MF_UL_GET_VERSION_CMD (0x60) +#define MF_UL_READ_CMD (0x30) +#define MF_UL_FAST_READ_CMD (0x3A) + +typedef enum { + MfUltralightTypeUnknown, + MfUltralightTypeUL11, + MfUltralightTypeUL21, + MfUltralightTypeNTAG213, + MfUltralightTypeNTAG215, + MfUltralightTypeNTAG216, +} MfUltralightType; + +typedef struct { + uint8_t header; + uint8_t vendor_id; + uint8_t prod_type; + uint8_t prod_subtype; + uint8_t prod_ver_major; + uint8_t prod_ver_minor; + uint8_t storage_size; + uint8_t protocol_type; +} MfUltralightVersion; + +typedef struct { + uint8_t sn0[3]; + uint8_t btBCC0; + uint8_t sn1[4]; + uint8_t btBCC1; + uint8_t internal; + uint8_t lock[2]; + uint8_t otp[4]; +} MfUltralightManufacturerBlock; + +typedef struct { + MfUltralightType type; + uint8_t pages_to_read; + uint8_t pages_readed; + bool support_fast_read; + uint8_t dump[255]; +} MfUltralightRead; + +bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +uint16_t mf_ul_prepare_get_version(uint8_t* dest); +void mf_ul_parse_get_version_response(uint8_t* buff, MfUltralightRead* mf_ul_read); +void mf_ul_set_default_version(MfUltralightRead* mf_ul_read); + +uint16_t mf_ul_prepare_read(uint8_t* dest, uint8_t start_page); +uint16_t mf_ul_prepare_fast_read(uint8_t* dest, uint8_t start_page, uint8_t end_page);