From 3857cd7d5ff2691ee5b1ca7307cfd2306db66519 Mon Sep 17 00:00:00 2001 From: Kevin Wallace <184975+kevinwallace@users.noreply.github.com> Date: Wed, 23 Mar 2022 06:45:37 -0700 Subject: [PATCH] Nfc: add basic Mifare DESFire support (#1024) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix TextBox word wrap behavior * Wrap width is 120 pixels, not 140. (140 is larger than the screen!) * Glyph width already includes spacing; don't add 1 additional px * When starting a new line, include wrapped glyph width in new line_width. * Call canvas_set_font before text_box_insert_endline so that glyph width is calculated using correct font. Previous approach worked somewhat well using default TextBoxFontText but this version is more robust, particularly when using TextBoxFontHex. * Add basic Mifare DESFire reading, file/app browser * Fix build with APP_ARCHIVE=0 * Add bool type to flipper_format * Add ability to save and load DESFire card data * Skip over NfcSceneDeviceInfo when viewing saved DESFire info * mf_df_clear: don't leak master key settings key versions * When opening a DESFire card from Archive, retain UID emulation behavior * rm unnecessary \r\n * show Popup instead of leaving view in bad state * Move NfcReaderRequestData out of union This makes it safe to emulate DESFire/EMV without clobbering card data. * Display saved DESFire cards via NfcSceneDeviceInfo * Display and save file metadata even when contents are missing This can happen when a file doesn't allow unauthenticated reads (see the call to mf_df_parse_read_data_response in nfc_worker.c). Co-authored-by: Kevin Wallace Co-authored-by: あく Co-authored-by: gornekich --- .../desktop/scenes/desktop_scene_main.c | 2 + applications/gui/modules/text_box.c | 20 +- applications/nfc/nfc_device.c | 396 ++++++++++++++++ applications/nfc/nfc_device.h | 8 +- applications/nfc/nfc_types.c | 2 + applications/nfc/nfc_worker.c | 248 ++++++++++ applications/nfc/nfc_worker.h | 1 + applications/nfc/nfc_worker_i.h | 2 + applications/nfc/scenes/nfc_scene_card_menu.c | 2 + applications/nfc/scenes/nfc_scene_config.h | 5 + .../nfc/scenes/nfc_scene_device_info.c | 27 +- .../nfc/scenes/nfc_scene_mifare_desfire_app.c | 119 +++++ .../scenes/nfc_scene_mifare_desfire_data.c | 108 +++++ .../scenes/nfc_scene_mifare_desfire_menu.c | 52 ++ .../scenes/nfc_scene_read_mifare_desfire.c | 56 +++ .../nfc_scene_read_mifare_desfire_success.c | 106 +++++ .../nfc/scenes/nfc_scene_save_success.c | 4 + .../nfc/scenes/nfc_scene_saved_menu.c | 17 +- .../nfc/scenes/nfc_scene_scripts_menu.c | 12 + .../flipper_format/flipper_format_test.c | 29 ++ lib/flipper_format/flipper_format.c | 63 +++ lib/flipper_format/flipper_format.h | 55 +++ lib/flipper_format/flipper_format_stream.c | 9 + lib/flipper_format/flipper_format_stream.h | 1 + lib/nfc_protocols/mifare_desfire.c | 447 ++++++++++++++++++ lib/nfc_protocols/mifare_desfire.h | 164 +++++++ 26 files changed, 1941 insertions(+), 14 deletions(-) mode change 100755 => 100644 applications/nfc/nfc_device.c mode change 100755 => 100644 applications/nfc/nfc_worker.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_device_info.c create mode 100644 applications/nfc/scenes/nfc_scene_mifare_desfire_app.c create mode 100644 applications/nfc/scenes/nfc_scene_mifare_desfire_data.c create mode 100644 applications/nfc/scenes/nfc_scene_mifare_desfire_menu.c create mode 100644 applications/nfc/scenes/nfc_scene_read_mifare_desfire.c create mode 100644 applications/nfc/scenes/nfc_scene_read_mifare_desfire_success.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_save_success.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_saved_menu.c create mode 100644 lib/nfc_protocols/mifare_desfire.c create mode 100644 lib/nfc_protocols/mifare_desfire.h diff --git a/applications/desktop/scenes/desktop_scene_main.c b/applications/desktop/scenes/desktop_scene_main.c index 8131d0c3..7ba09e0f 100644 --- a/applications/desktop/scenes/desktop_scene_main.c +++ b/applications/desktop/scenes/desktop_scene_main.c @@ -48,6 +48,7 @@ static void desktop_scene_main_interact_animation_callback(void* context) { desktop->view_dispatcher, DesktopAnimationEventInteractAnimation); } +#ifdef APP_ARCHIVE static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) { furi_assert(desktop); furi_assert(flipper_app); @@ -65,6 +66,7 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl furi_thread_start(desktop->scene_thread); } +#endif void desktop_scene_main_callback(DesktopEvent event, void* context) { Desktop* desktop = (Desktop*)context; diff --git a/applications/gui/modules/text_box.c b/applications/gui/modules/text_box.c index e2a99c22..a7d0d763 100755 --- a/applications/gui/modules/text_box.c +++ b/applications/gui/modules/text_box.c @@ -57,17 +57,18 @@ static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) { const char* str = model->text; size_t line_num = 0; - const size_t text_width = 140; + const size_t text_width = 120; while(str[i] != '\0') { char symb = str[i++]; if(symb != '\n') { - line_width += canvas_glyph_width(canvas, symb) + 1; - if(line_width > text_width) { + size_t glyph_width = canvas_glyph_width(canvas, symb); + if(line_width + glyph_width > text_width) { line_num++; line_width = 0; string_push_back(model->text_formatted, '\n'); } + line_width += glyph_width; } else { line_num++; line_width = 0; @@ -94,18 +95,19 @@ static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) { static void text_box_view_draw_callback(Canvas* canvas, void* _model) { TextBoxModel* model = _model; - if(!model->formatted) { - text_box_insert_endline(canvas, model); - model->formatted = true; - } - canvas_clear(canvas); - elements_slightly_rounded_frame(canvas, 0, 0, 124, 64); if(model->font == TextBoxFontText) { canvas_set_font(canvas, FontSecondary); } else if(model->font == TextBoxFontHex) { canvas_set_font(canvas, FontKeyboard); } + + if(!model->formatted) { + text_box_insert_endline(canvas, model); + model->formatted = true; + } + + elements_slightly_rounded_frame(canvas, 0, 0, 124, 64); elements_multiline_text(canvas, 3, 11, model->text_pos); elements_scrollbar(canvas, model->scroll_pos, model->scroll_num); } diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c old mode 100755 new mode 100644 index 1f4c35cc..477737cc --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -16,6 +16,7 @@ NfcDevice* nfc_device_alloc() { void nfc_device_free(NfcDevice* nfc_dev) { furi_assert(nfc_dev); + nfc_device_clear(nfc_dev); furi_record_close("storage"); furi_record_close("dialogs"); free(nfc_dev); @@ -28,6 +29,8 @@ void nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) { string_set_str(format_string, "Bank card"); } else if(dev->format == NfcDeviceSaveFormatMifareUl) { string_set_str(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true)); + } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { + string_set_str(format_string, "Mifare DESFire"); } else { string_set_str(format_string, "Unknown"); } @@ -53,6 +56,11 @@ bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) { return true; } } + if(string_start_with_str_p(format_string, "Mifare DESFire")) { + dev->format = NfcDeviceSaveFormatMifareDesfire; + dev->dev_data.nfc_data.protocol = NfcDeviceProtocolMifareDesfire; + return true; + } return false; } @@ -154,6 +162,383 @@ bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { return parsed; } +static bool nfc_device_save_mifare_df_key_settings( + FlipperFormat* file, + MifareDesfireKeySettings* ks, + const char* prefix) { + bool saved = false; + string_t key; + string_init(key); + + do { + string_printf(key, "%s Change Key ID", prefix); + if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->change_key_id, 1)) break; + string_printf(key, "%s Config Changeable", prefix); + if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->config_changeable, 1)) + break; + string_printf(key, "%s Free Create Delete", prefix); + if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->free_create_delete, 1)) + break; + string_printf(key, "%s Free Directory List", prefix); + if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->free_directory_list, 1)) + break; + string_printf(key, "%s Key Changeable", prefix); + if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) + break; + string_printf(key, "%s Max Keys", prefix); + if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; + for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { + string_printf(key, "%s Key %d Version", prefix, kv->id); + if(!flipper_format_write_hex(file, string_get_cstr(key), &kv->version, 1)) break; + } + saved = true; + } while(false); + + string_clear(key); + return saved; +} + +bool nfc_device_load_mifare_df_key_settings( + FlipperFormat* file, + MifareDesfireKeySettings* ks, + const char* prefix) { + bool parsed = false; + string_t key; + string_init(key); + + do { + string_printf(key, "%s Change Key ID", prefix); + if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->change_key_id, 1)) break; + string_printf(key, "%s Config Changeable", prefix); + if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->config_changeable, 1)) break; + string_printf(key, "%s Free Create Delete", prefix); + if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->free_create_delete, 1)) + break; + string_printf(key, "%s Free Directory List", prefix); + if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->free_directory_list, 1)) + break; + string_printf(key, "%s Key Changeable", prefix); + if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) + break; + string_printf(key, "%s Max Keys", prefix); + if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; + MifareDesfireKeyVersion** kv_head = &ks->key_version_head; + for(int key_id = 0; key_id < ks->max_keys; key_id++) { + string_printf(key, "%s Key %d Version", prefix, key_id); + uint8_t version; + if(flipper_format_read_hex(file, string_get_cstr(key), &version, 1)) { + MifareDesfireKeyVersion* kv = malloc(sizeof(MifareDesfireKeyVersion)); + memset(kv, 0, sizeof(MifareDesfireKeyVersion)); + kv->id = key_id; + kv->version = version; + *kv_head = kv; + kv_head = &kv->next; + } + } + parsed = true; + } while(false); + + string_clear(key); + return parsed; +} + +static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { + bool saved = false; + string_t prefix, key; + string_init_printf(prefix, "Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + string_init(key); + uint8_t* tmp = NULL; + + do { + if(app->key_settings) { + if(!nfc_device_save_mifare_df_key_settings( + file, app->key_settings, string_get_cstr(prefix))) + break; + } + uint32_t n_files = 0; + for(MifareDesfireFile* f = app->file_head; f; f = f->next) { + n_files++; + } + tmp = malloc(n_files); + int i = 0; + for(MifareDesfireFile* f = app->file_head; f; f = f->next) { + tmp[i++] = f->id; + } + string_printf(key, "%s File IDs", string_get_cstr(prefix)); + if(!flipper_format_write_hex(file, string_get_cstr(key), tmp, n_files)) break; + bool saved_files = true; + for(MifareDesfireFile* f = app->file_head; f; f = f->next) { + saved_files = false; + string_printf(key, "%s File %d Type", string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, string_get_cstr(key), &f->type, 1)) break; + string_printf( + key, "%s File %d Communication Settings", string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, string_get_cstr(key), &f->comm, 1)) break; + string_printf(key, "%s File %d Access Rights", string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex( + file, string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) + break; + uint16_t size = 0; + if(f->type == MifareDesfireFileTypeStandard || + f->type == MifareDesfireFileTypeBackup) { + size = f->settings.data.size; + string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.data.size, 1)) + break; + } else if(f->type == MifareDesfireFileTypeValue) { + string_printf(key, "%s File %d Hi Limit", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.value.hi_limit, 1)) + break; + string_printf(key, "%s File %d Lo Limit", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.value.lo_limit, 1)) + break; + string_printf( + key, "%s File %d Limited Credit Value", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) + break; + string_printf( + key, "%s File %d Limited Credit Enabled", string_get_cstr(prefix), f->id); + if(!flipper_format_write_bool( + file, string_get_cstr(key), &f->settings.value.limited_credit_enabled, 1)) + break; + size = 4; + } else if( + f->type == MifareDesfireFileTypeLinearRecord || + f->type == MifareDesfireFileTypeCyclicRecord) { + string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.record.size, 1)) + break; + string_printf(key, "%s File %d Max", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.record.max, 1)) + break; + string_printf(key, "%s File %d Cur", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.record.cur, 1)) + break; + size = f->settings.record.size * f->settings.record.cur; + } + if(f->contents) { + string_printf(key, "%s File %d", string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, string_get_cstr(key), f->contents, size)) break; + } + saved_files = true; + } + if(!saved_files) { + break; + } + saved = true; + } while(false); + + free(tmp); + string_clear(prefix); + string_clear(key); + return saved; +} + +bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { + bool parsed = false; + string_t prefix, key; + string_init_printf(prefix, "Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + string_init(key); + uint8_t* tmp = NULL; + MifareDesfireFile* f = NULL; + + do { + app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); + memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); + if(!nfc_device_load_mifare_df_key_settings( + file, app->key_settings, string_get_cstr(prefix))) { + free(app->key_settings); + app->key_settings = NULL; + break; + } + string_printf(key, "%s File IDs", string_get_cstr(prefix)); + uint32_t n_files; + if(!flipper_format_get_value_count(file, string_get_cstr(key), &n_files)) break; + tmp = malloc(n_files); + if(!flipper_format_read_hex(file, string_get_cstr(key), tmp, n_files)) break; + MifareDesfireFile** file_head = &app->file_head; + bool parsed_files = true; + for(int i = 0; i < n_files; i++) { + parsed_files = false; + f = malloc(sizeof(MifareDesfireFile)); + memset(f, 0, sizeof(MifareDesfireFile)); + f->id = tmp[i]; + string_printf(key, "%s File %d Type", string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, string_get_cstr(key), &f->type, 1)) break; + string_printf( + key, "%s File %d Communication Settings", string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, string_get_cstr(key), &f->comm, 1)) break; + string_printf(key, "%s File %d Access Rights", string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) + break; + if(f->type == MifareDesfireFileTypeStandard || + f->type == MifareDesfireFileTypeBackup) { + string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.data.size, 1)) + break; + } else if(f->type == MifareDesfireFileTypeValue) { + string_printf(key, "%s File %d Hi Limit", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.value.hi_limit, 1)) + break; + string_printf(key, "%s File %d Lo Limit", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.value.lo_limit, 1)) + break; + string_printf( + key, "%s File %d Limited Credit Value", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) + break; + string_printf( + key, "%s File %d Limited Credit Enabled", string_get_cstr(prefix), f->id); + if(!flipper_format_read_bool( + file, string_get_cstr(key), &f->settings.value.limited_credit_enabled, 1)) + break; + } else if( + f->type == MifareDesfireFileTypeLinearRecord || + f->type == MifareDesfireFileTypeCyclicRecord) { + string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.record.size, 1)) + break; + string_printf(key, "%s File %d Max", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.record.max, 1)) + break; + string_printf(key, "%s File %d Cur", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.record.cur, 1)) + break; + } + string_printf(key, "%s File %d", string_get_cstr(prefix), f->id); + if(flipper_format_key_exist(file, string_get_cstr(key))) { + uint32_t size; + if(!flipper_format_get_value_count(file, string_get_cstr(key), &size)) break; + f->contents = malloc(size); + if(!flipper_format_read_hex(file, string_get_cstr(key), f->contents, size)) break; + } + *file_head = f; + file_head = &f->next; + f = NULL; + parsed_files = true; + } + if(!parsed_files) { + break; + } + parsed = true; + } while(false); + + if(f) { + free(f->contents); + free(f); + } + free(tmp); + string_clear(prefix); + string_clear(key); + return parsed; +} + +static bool nfc_device_save_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + MifareDesfireData* data = &dev->dev_data.mf_df_data; + uint8_t* tmp = NULL; + + do { + if(!flipper_format_write_comment_cstr(file, "Mifare DESFire specific data")) break; + if(!flipper_format_write_hex( + file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version))) + break; + if(data->free_memory) { + if(!flipper_format_write_uint32(file, "PICC Free Memory", &data->free_memory->bytes, 1)) + break; + } + if(data->master_key_settings) { + if(!nfc_device_save_mifare_df_key_settings(file, data->master_key_settings, "PICC")) + break; + } + uint32_t n_apps = 0; + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + n_apps++; + } + if(!flipper_format_write_uint32(file, "Application Count", &n_apps, 1)) break; + tmp = malloc(n_apps * 3); + int i = 0; + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + memcpy(tmp + i, app->id, 3); + i += 3; + } + if(!flipper_format_write_hex(file, "Application IDs", tmp, n_apps * 3)) break; + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + if(!nfc_device_save_mifare_df_app(file, app)) break; + } + saved = true; + } while(false); + + free(tmp); + return saved; +} + +bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + MifareDesfireData* data = &dev->dev_data.mf_df_data; + memset(data, 0, sizeof(MifareDesfireData)); + uint8_t* tmp = NULL; + + do { + if(!flipper_format_read_hex( + file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version))) + break; + if(flipper_format_key_exist(file, "PICC Free Memory")) { + data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); + memset(data->free_memory, 0, sizeof(MifareDesfireFreeMemory)); + if(!flipper_format_read_uint32( + file, "PICC Free Memory", &data->free_memory->bytes, 1)) { + free(data->free_memory); + break; + } + } + data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); + memset(data->master_key_settings, 0, sizeof(MifareDesfireKeySettings)); + if(!nfc_device_load_mifare_df_key_settings(file, data->master_key_settings, "PICC")) { + free(data->master_key_settings); + data->master_key_settings = NULL; + break; + } + uint32_t n_apps; + if(!flipper_format_read_uint32(file, "Application Count", &n_apps, 1)) break; + tmp = malloc(n_apps * 3); + if(!flipper_format_read_hex(file, "Application IDs", tmp, n_apps * 3)) break; + bool parsed_apps = true; + MifareDesfireApplication** app_head = &data->app_head; + for(int i = 0; i < n_apps; i++) { + MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication)); + memset(app, 0, sizeof(MifareDesfireApplication)); + memcpy(app->id, &tmp[i * 3], 3); + if(!nfc_device_load_mifare_df_app(file, app)) { + free(app); + parsed_apps = false; + break; + } + *app_head = app; + app_head = &app->next; + } + if(!parsed_apps) break; + parsed = true; + } while(false); + + free(tmp); + return parsed; +} + static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) { bool saved = false; NfcEmvData* data = &dev->dev_data.emv_data; @@ -263,6 +648,8 @@ static bool nfc_device_save_file( // Save more data if necessary if(dev->format == NfcDeviceSaveFormatMifareUl) { if(!nfc_device_save_mifare_ul_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { + if(!nfc_device_save_mifare_df_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatBankCard) { if(!nfc_device_save_bank_card_data(file, dev)) break; } @@ -327,6 +714,8 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) { // Parse other data if(dev->format == NfcDeviceSaveFormatMifareUl) { if(!nfc_device_load_mifare_ul_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { + if(!nfc_device_load_mifare_df_data(file, dev)) break; } else if(dev->format == NfcDeviceSaveFormatBankCard) { if(!nfc_device_load_bank_card_data(file, dev)) break; } @@ -389,9 +778,16 @@ bool nfc_file_select(NfcDevice* dev) { return res; } +void nfc_device_data_clear(NfcDeviceData* dev_data) { + if(dev_data->nfc_data.protocol == NfcDeviceProtocolMifareDesfire) { + mf_df_clear(&dev_data->mf_df_data); + } +} + void nfc_device_clear(NfcDevice* dev) { furi_assert(dev); + nfc_device_data_clear(&dev->dev_data); memset(&dev->dev_data, 0, sizeof(dev->dev_data)); dev->format = NfcDeviceSaveFormatUid; } diff --git a/applications/nfc/nfc_device.h b/applications/nfc/nfc_device.h index b26e6bf9..cfc882f9 100644 --- a/applications/nfc/nfc_device.h +++ b/applications/nfc/nfc_device.h @@ -6,6 +6,7 @@ #include #include "mifare_ultralight.h" +#include "mifare_desfire.h" #define NFC_DEV_NAME_MAX_LEN 22 #define NFC_FILE_NAME_MAX_LEN 120 @@ -26,12 +27,14 @@ typedef enum { NfcDeviceProtocolUnknown, NfcDeviceProtocolEMV, NfcDeviceProtocolMifareUl, + NfcDeviceProtocolMifareDesfire, } NfcProtocol; typedef enum { NfcDeviceSaveFormatUid, NfcDeviceSaveFormatBankCard, NfcDeviceSaveFormatMifareUl, + NfcDeviceSaveFormatMifareDesfire, } NfcDeviceSaveFormat; typedef struct { @@ -62,10 +65,11 @@ typedef struct { typedef struct { NfcDeviceCommonData nfc_data; + NfcReaderRequestData reader_data; union { NfcEmvData emv_data; MifareUlData mf_ul_data; - NfcReaderRequestData reader_data; + MifareDesfireData mf_df_data; }; } NfcDeviceData; @@ -93,6 +97,8 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path); bool nfc_file_select(NfcDevice* dev); +void nfc_device_data_clear(NfcDeviceData* dev); + void nfc_device_clear(NfcDevice* dev); bool nfc_device_delete(NfcDevice* dev); diff --git a/applications/nfc/nfc_types.c b/applications/nfc/nfc_types.c index 99e17a90..6a2d8fad 100644 --- a/applications/nfc/nfc_types.c +++ b/applications/nfc/nfc_types.c @@ -53,6 +53,8 @@ const char* nfc_guess_protocol(NfcProtocol protocol) { return "EMV bank card"; } else if(protocol == NfcDeviceProtocolMifareUl) { return "Mifare Ultral/NTAG"; + } else if(protocol == NfcDeviceProtocolMifareDesfire) { + return "Mifare DESFire"; } else { return "Unrecognized"; } diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c old mode 100755 new mode 100644 index 25c38832..ce865a7f --- 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_desfire.h" #include "nfc_protocols/mifare_ultralight.h" #define TAG "NfcWorker" @@ -94,6 +95,8 @@ int32_t nfc_worker_task(void* context) { nfc_worker_read_mifare_ul(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateEmulateMifareUl) { nfc_worker_emulate_mifare_ul(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateReadMifareDesfire) { + nfc_worker_read_mifare_desfire(nfc_worker); } else if(nfc_worker->state == NfcWorkerStateField) { nfc_worker_field(nfc_worker); } @@ -108,6 +111,7 @@ void nfc_worker_detect(NfcWorker* nfc_worker) { rfalNfcDevice* dev_list; rfalNfcDevice* dev; uint8_t dev_cnt; + nfc_device_data_clear(nfc_worker->dev_data); NfcDeviceCommonData* result = &nfc_worker->dev_data->nfc_data; while(nfc_worker->state == NfcWorkerStateDetect) { @@ -126,6 +130,11 @@ void nfc_worker_detect(NfcWorker* nfc_worker) { dev->dev.nfca.sensRes.platformInfo, dev->dev.nfca.selRes.sak)) { result->protocol = NfcDeviceProtocolMifareUl; + } else if(mf_df_check_card_type( + dev->dev.nfca.sensRes.anticollisionInfo, + dev->dev.nfca.sensRes.platformInfo, + dev->dev.nfca.selRes.sak)) { + result->protocol = NfcDeviceProtocolMifareDesfire; } else if(dev->rfInterface == RFAL_NFC_INTERFACE_ISODEP) { result->protocol = NfcDeviceProtocolEMV; } else { @@ -192,6 +201,7 @@ void nfc_worker_read_emv_app(NfcWorker* nfc_worker) { uint8_t* rx_buff; uint16_t* rx_len; NfcDeviceData* result = nfc_worker->dev_data; + nfc_device_data_clear(result); while(nfc_worker->state == NfcWorkerStateReadEMVApp) { memset(&emv_app, 0, sizeof(emv_app)); @@ -253,6 +263,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { uint8_t* rx_buff; uint16_t* rx_len; NfcDeviceData* result = nfc_worker->dev_data; + nfc_device_data_clear(result); while(nfc_worker->state == NfcWorkerStateReadEMV) { memset(&emv_app, 0, sizeof(emv_app)); @@ -516,6 +527,7 @@ void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker) { uint16_t* rx_len; MifareUlDevice mf_ul_read; NfcDeviceData* result = nfc_worker->dev_data; + nfc_device_data_clear(result); while(nfc_worker->state == NfcWorkerStateReadMifareUl) { furi_hal_nfc_deactivate(); @@ -658,6 +670,242 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { } } +ReturnCode nfc_exchange_full( + uint8_t* tx_buff, + uint16_t tx_len, + uint8_t* rx_buff, + uint16_t rx_cap, + uint16_t* rx_len) { + ReturnCode err; + uint8_t* part_buff; + uint16_t* part_len; + + err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &part_buff, &part_len, false); + if(*part_len > rx_cap) { + return ERR_OVERRUN; + } + memcpy(rx_buff, part_buff, *part_len); + *rx_len = *part_len; + while(err == ERR_NONE && rx_buff[0] == 0xAF) { + err = furi_hal_nfc_data_exchange(rx_buff, 1, &part_buff, &part_len, false); + if(*part_len > rx_cap - *rx_len) { + return ERR_OVERRUN; + } + if(*part_len == 0) { + return ERR_PROTO; + } + memcpy(rx_buff + *rx_len, part_buff + 1, *part_len - 1); + *rx_buff = *part_buff; + *rx_len += *part_len - 1; + } + + return err; +} + +void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { + ReturnCode err; + rfalNfcDevice* dev_list; + uint8_t dev_cnt = 0; + uint8_t tx_buff[64] = {}; + uint16_t tx_len = 0; + uint8_t rx_buff[512] = {}; + uint16_t rx_len; + NfcDeviceData* result = nfc_worker->dev_data; + nfc_device_data_clear(result); + MifareDesfireData* data = &result->mf_df_data; + + while(nfc_worker->state == NfcWorkerStateReadMifareDesfire) { + furi_hal_nfc_deactivate(); + if(!furi_hal_nfc_detect(&dev_list, &dev_cnt, 300, false)) { + osDelay(100); + continue; + } + memset(data, 0, sizeof(MifareDesfireData)); + if(dev_list[0].type != RFAL_NFC_LISTEN_TYPE_NFCA || + !mf_df_check_card_type( + dev_list[0].dev.nfca.sensRes.anticollisionInfo, + dev_list[0].dev.nfca.sensRes.platformInfo, + dev_list[0].dev.nfca.selRes.sak)) { + FURI_LOG_D(TAG, "Tag is not DESFire"); + osDelay(100); + continue; + } + + FURI_LOG_D(TAG, "Found DESFire tag"); + + // Fill non-DESFire result data + result->nfc_data.uid_len = dev_list[0].dev.nfca.nfcId1Len; + result->nfc_data.atqa[0] = dev_list[0].dev.nfca.sensRes.anticollisionInfo; + result->nfc_data.atqa[1] = dev_list[0].dev.nfca.sensRes.platformInfo; + result->nfc_data.sak = dev_list[0].dev.nfca.selRes.sak; + result->nfc_data.device = NfcDeviceNfca; + result->nfc_data.protocol = NfcDeviceProtocolMifareDesfire; + memcpy(result->nfc_data.uid, dev_list[0].dev.nfca.nfcId1, result->nfc_data.uid_len); + + // Get DESFire version + tx_len = mf_df_prepare_get_version(tx_buff); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err != ERR_NONE) { + FURI_LOG_W(TAG, "Bad exchange getting version, err: %d", err); + continue; + } + if(!mf_df_parse_get_version_response(rx_buff, rx_len, &data->version)) { + FURI_LOG_W(TAG, "Bad DESFire GET_VERSION response"); + continue; + } + + tx_len = mf_df_prepare_get_free_memory(tx_buff); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err == ERR_NONE) { + data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); + memset(data->free_memory, 0, sizeof(MifareDesfireFreeMemory)); + if(!mf_df_parse_get_free_memory_response(rx_buff, rx_len, data->free_memory)) { + FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)"); + free(data->free_memory); + data->free_memory = NULL; + } + } + + tx_len = mf_df_prepare_get_key_settings(tx_buff); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err != ERR_NONE) { + FURI_LOG_D(TAG, "Bad exchange getting key settings, err: %d", err); + } else { + data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); + memset(data->master_key_settings, 0, sizeof(MifareDesfireKeySettings)); + if(!mf_df_parse_get_key_settings_response(rx_buff, rx_len, data->master_key_settings)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); + free(data->master_key_settings); + data->master_key_settings = NULL; + } + + MifareDesfireKeyVersion** key_version_head = + &data->master_key_settings->key_version_head; + for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) { + tx_len = mf_df_prepare_get_key_version(tx_buff, key_id); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err != ERR_NONE) { + FURI_LOG_W(TAG, "Bad exchange getting key version, err: %d", err); + continue; + } + MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); + memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); + key_version->id = key_id; + if(!mf_df_parse_get_key_version_response(rx_buff, rx_len, key_version)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); + free(key_version); + continue; + } + *key_version_head = key_version; + key_version_head = &key_version->next; + } + } + + tx_len = mf_df_prepare_get_application_ids(tx_buff); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err != ERR_NONE) { + FURI_LOG_W(TAG, "Bad exchange getting application IDs, err: %d", err); + } else { + if(!mf_df_parse_get_application_ids_response(rx_buff, rx_len, &data->app_head)) { + FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response"); + } + } + + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + tx_len = mf_df_prepare_select_application(tx_buff, app->id); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(!mf_df_parse_select_application_response(rx_buff, rx_len)) { + FURI_LOG_W(TAG, "Bad exchange selecting application, err: %d", err); + continue; + } + tx_len = mf_df_prepare_get_key_settings(tx_buff); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err != ERR_NONE) { + FURI_LOG_W(TAG, "Bad exchange getting key settings, err: %d", err); + } else { + app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); + memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); + if(!mf_df_parse_get_key_settings_response(rx_buff, rx_len, app->key_settings)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); + free(app->key_settings); + app->key_settings = NULL; + } + + MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head; + for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) { + tx_len = mf_df_prepare_get_key_version(tx_buff, key_id); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err != ERR_NONE) { + FURI_LOG_W(TAG, "Bad exchange getting key version, err: %d", err); + continue; + } + MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); + memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); + key_version->id = key_id; + if(!mf_df_parse_get_key_version_response(rx_buff, rx_len, key_version)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); + free(key_version); + continue; + } + *key_version_head = key_version; + key_version_head = &key_version->next; + } + } + + tx_len = mf_df_prepare_get_file_ids(tx_buff); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err != ERR_NONE) { + FURI_LOG_W(TAG, "Bad exchange getting file IDs, err: %d", err); + } else { + if(!mf_df_parse_get_file_ids_response(rx_buff, rx_len, &app->file_head)) { + FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response"); + } + } + + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + tx_len = mf_df_prepare_get_file_settings(tx_buff, file->id); + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err != ERR_NONE) { + FURI_LOG_W(TAG, "Bad exchange getting file settings, err: %d", err); + continue; + } + if(!mf_df_parse_get_file_settings_response(rx_buff, rx_len, file)) { + FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response"); + continue; + } + switch(file->type) { + case MifareDesfireFileTypeStandard: + case MifareDesfireFileTypeBackup: + tx_len = mf_df_prepare_read_data(tx_buff, file->id, 0, 0); + break; + case MifareDesfireFileTypeValue: + tx_len = mf_df_prepare_get_value(tx_buff, file->id); + break; + case MifareDesfireFileTypeLinearRecord: + case MifareDesfireFileTypeCyclicRecord: + tx_len = mf_df_prepare_read_records(tx_buff, file->id, 0, 0); + break; + } + err = nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); + if(err != ERR_NONE) { + FURI_LOG_W(TAG, "Bad exchange reading file %d, err: %d", file->id, err); + continue; + } + if(!mf_df_parse_read_data_response(rx_buff, rx_len, file)) { + FURI_LOG_W(TAG, "Bad response reading file %d", file->id); + continue; + } + } + } + + // Notify caller and exit + if(nfc_worker->callback) { + nfc_worker->callback(nfc_worker->context); + } + break; + } +} + void nfc_worker_field(NfcWorker* nfc_worker) { furi_hal_nfc_field_on(); while(nfc_worker->state == NfcWorkerStateField) { diff --git a/applications/nfc/nfc_worker.h b/applications/nfc/nfc_worker.h index 519ec7a2..1fcab122 100755 --- a/applications/nfc/nfc_worker.h +++ b/applications/nfc/nfc_worker.h @@ -18,6 +18,7 @@ typedef enum { NfcWorkerStateField, NfcWorkerStateReadMifareUl, NfcWorkerStateEmulateMifareUl, + NfcWorkerStateReadMifareDesfire, // Transition NfcWorkerStateStop, } NfcWorkerState; diff --git a/applications/nfc/nfc_worker_i.h b/applications/nfc/nfc_worker_i.h index 47be09b9..14137cd8 100755 --- a/applications/nfc/nfc_worker_i.h +++ b/applications/nfc/nfc_worker_i.h @@ -45,4 +45,6 @@ void nfc_worker_field(NfcWorker* nfc_worker); void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker); +void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker); + void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker); diff --git a/applications/nfc/scenes/nfc_scene_card_menu.c b/applications/nfc/scenes/nfc_scene_card_menu.c index 638b7192..2b6680a4 100755 --- a/applications/nfc/scenes/nfc_scene_card_menu.c +++ b/applications/nfc/scenes/nfc_scene_card_menu.c @@ -50,6 +50,8 @@ bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) { nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexRunApp); if(nfc->dev->dev_data.nfc_data.protocol == NfcDeviceProtocolMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUl); + } else if(nfc->dev->dev_data.nfc_data.protocol == NfcDeviceProtocolMifareDesfire) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfire); } else if(nfc->dev->dev_data.nfc_data.protocol == NfcDeviceProtocolEMV) { scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvApp); } diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/nfc/scenes/nfc_scene_config.h index f4ccdf0c..e2e20d1f 100755 --- a/applications/nfc/scenes/nfc_scene_config.h +++ b/applications/nfc/scenes/nfc_scene_config.h @@ -19,6 +19,11 @@ ADD_SCENE(nfc, mifare_ul_menu, MifareUlMenu) ADD_SCENE(nfc, emulate_mifare_ul, EmulateMifareUl) ADD_SCENE(nfc, read_emv_app, ReadEmvApp) ADD_SCENE(nfc, read_emv_app_success, ReadEmvAppSuccess) +ADD_SCENE(nfc, read_mifare_desfire, ReadMifareDesfire) +ADD_SCENE(nfc, read_mifare_desfire_success, ReadMifareDesfireSuccess) +ADD_SCENE(nfc, mifare_desfire_menu, MifareDesfireMenu) +ADD_SCENE(nfc, mifare_desfire_data, MifareDesfireData) +ADD_SCENE(nfc, mifare_desfire_app, MifareDesfireApp) ADD_SCENE(nfc, device_info, DeviceInfo) ADD_SCENE(nfc, delete, Delete) ADD_SCENE(nfc, delete_success, DeleteSuccess) diff --git a/applications/nfc/scenes/nfc_scene_device_info.c b/applications/nfc/scenes/nfc_scene_device_info.c old mode 100755 new mode 100644 index f0f326f6..e9b6975f --- a/applications/nfc/scenes/nfc_scene_device_info.c +++ b/applications/nfc/scenes/nfc_scene_device_info.c @@ -32,7 +32,7 @@ void nfc_scene_device_info_on_enter(void* context) { // Setup Custom Widget view widget_add_text_box_element( - nfc->widget, 0, 0, 128, 22, AlignCenter, AlignCenter, nfc->dev->dev_name); + nfc->widget, 0, 0, 128, 22, AlignCenter, AlignTop, nfc->dev->dev_name); widget_add_button_element( nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_device_info_widget_callback, nfc); widget_add_button_element( @@ -64,7 +64,8 @@ void nfc_scene_device_info_on_enter(void* context) { widget_add_string_element(nfc->widget, 64, 21, AlignCenter, AlignTop, FontSecondary, uid_str); const char* protocol_name = NULL; - if(data->protocol == NfcDeviceProtocolEMV) { + if(data->protocol == NfcDeviceProtocolEMV || + data->protocol == NfcDeviceProtocolMifareDesfire) { protocol_name = nfc_guess_protocol(data->protocol); } else if(data->protocol == NfcDeviceProtocolMifareUl) { protocol_name = nfc_mf_ul_type(nfc->dev->dev_data.mf_ul_data.type, false); @@ -101,6 +102,25 @@ void nfc_scene_device_info_on_enter(void* context) { nfc->text_box_store, "%02X%02X ", mf_ul_data->data[i], mf_ul_data->data[i + 1]); } text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareDesfire) { + MifareDesfireData* mf_df_data = &nfc->dev->dev_data.mf_df_data; + uint16_t n_apps = 0; + uint16_t n_files = 0; + for(MifareDesfireApplication* app = mf_df_data->app_head; app; app = app->next) { + n_apps++; + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + n_files++; + } + } + nfc_text_store_set( + nfc, + "%d application%s, %d file%s", + n_apps, + n_apps == 1 ? "" : "s", + n_files, + n_files == 1 ? "" : "s"); + widget_add_string_element( + nfc->widget, 64, 17, AlignCenter, AlignBottom, FontSecondary, nfc->text_store); } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) { NfcEmvData* emv_data = &nfc->dev->dev_data.emv_data; BankCard* bank_card = nfc->bank_card; @@ -162,6 +182,9 @@ bool nfc_scene_device_info_on_event(void* context, SceneManagerEvent event) { nfc->scene_manager, NfcSceneDeviceInfo, NfcSceneDeviceInfoData); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewBankCard); consumed = true; + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareDesfire) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareDesfireData); + consumed = true; } } else if(state == NfcSceneDeviceInfoData && event.event == NfcCustomEventViewExit) { scene_manager_set_scene_state( diff --git a/applications/nfc/scenes/nfc_scene_mifare_desfire_app.c b/applications/nfc/scenes/nfc_scene_mifare_desfire_app.c new file mode 100644 index 00000000..e68d46d1 --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_mifare_desfire_app.c @@ -0,0 +1,119 @@ +#include "../nfc_i.h" + +#define TAG "NfcSceneMifareDesfireApp" + +enum SubmenuIndex { + SubmenuIndexAppInfo, + SubmenuIndexDynamic, // dynamic indexes start here +}; + +MifareDesfireApplication* nfc_scene_mifare_desfire_app_get_app(Nfc* nfc) { + uint32_t app_idx = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireApp) >> 1; + MifareDesfireApplication* app = nfc->dev->dev_data.mf_df_data.app_head; + for(int i = 0; i < app_idx && app; i++) { + app = app->next; + } + return app; +} + +void nfc_scene_mifare_desfire_app_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = (Nfc*)context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_mifare_desfire_app_on_enter(void* context) { + Nfc* nfc = (Nfc*)context; + Submenu* submenu = nfc->submenu; + MifareDesfireApplication* app = nfc_scene_mifare_desfire_app_get_app(nfc); + if(!app) { + popup_set_icon(nfc->popup, 5, 5, &I_WarningDolphin_45x42); + popup_set_header(nfc->popup, "Internal Error!", 55, 12, AlignLeft, AlignBottom); + popup_set_text( + nfc->popup, + "No app selected.\nThis should\nnever happen,\nplease file a bug.", + 55, + 15, + AlignLeft, + AlignTop); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + FURI_LOG_E(TAG, "Bad state. No app selected?"); + return; + } + + text_box_set_font(nfc->text_box, TextBoxFontHex); + + submenu_add_item( + submenu, + "App info", + SubmenuIndexAppInfo, + nfc_scene_mifare_desfire_app_submenu_callback, + nfc); + + uint16_t cap = NFC_TEXT_STORE_SIZE; + char* buf = nfc->text_store; + int idx = SubmenuIndexDynamic; + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + int size = snprintf(buf, cap, "File %d", file->id); + if(size < 0 || size >= cap) { + FURI_LOG_W( + TAG, + "Exceeded NFC_TEXT_STORE_SIZE when preparing file id strings; menu truncated"); + break; + } + char* label = buf; + cap -= size + 1; + buf += size + 1; + submenu_add_item( + submenu, label, idx++, nfc_scene_mifare_desfire_app_submenu_callback, nfc); + } + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mifare_desfire_app_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = (Nfc*)context; + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireApp); + + if(event.type == SceneManagerEventTypeCustom) { + MifareDesfireApplication* app = nfc_scene_mifare_desfire_app_get_app(nfc); + TextBox* text_box = nfc->text_box; + string_reset(nfc->text_box_store); + if(event.event == SubmenuIndexAppInfo) { + mf_df_cat_application_info(app, nfc->text_box_store); + } else { + uint16_t index = event.event - SubmenuIndexDynamic; + MifareDesfireFile* file = app->file_head; + for(int i = 0; file && i < index; i++) { + file = file->next; + } + if(!file) { + return false; + } + mf_df_cat_file(file, nfc->text_box_store); + } + text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMifareDesfireApp, state | 1); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + return true; + } else if(event.type == SceneManagerEventTypeBack) { + if(state & 1) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareDesfireApp, state & ~1); + return true; + } + } + + return false; +} + +void nfc_scene_mifare_desfire_app_on_exit(void* context) { + Nfc* nfc = (Nfc*)context; + + text_box_reset(nfc->text_box); + string_reset(nfc->text_box_store); + + submenu_reset(nfc->submenu); +} diff --git a/applications/nfc/scenes/nfc_scene_mifare_desfire_data.c b/applications/nfc/scenes/nfc_scene_mifare_desfire_data.c new file mode 100644 index 00000000..c34d7af1 --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_mifare_desfire_data.c @@ -0,0 +1,108 @@ +#include "../nfc_i.h" + +#define TAG "NfcSceneMifareDesfireData" + +enum { + MifareDesfireDataStateMenu, + MifareDesfireDataStateItem, // MUST be last, states >= this correspond with submenu index +}; + +enum SubmenuIndex { + SubmenuIndexCardInfo, + SubmenuIndexDynamic, // dynamic indexes start here +}; + +void nfc_scene_mifare_desfire_data_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = (Nfc*)context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_mifare_desfire_data_on_enter(void* context) { + Nfc* nfc = (Nfc*)context; + Submenu* submenu = nfc->submenu; + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireData); + MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data; + + text_box_set_font(nfc->text_box, TextBoxFontHex); + + submenu_add_item( + submenu, + "Card info", + SubmenuIndexCardInfo, + nfc_scene_mifare_desfire_data_submenu_callback, + nfc); + + uint16_t cap = NFC_TEXT_STORE_SIZE; + char* buf = nfc->text_store; + int idx = SubmenuIndexDynamic; + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + int size = snprintf(buf, cap, "App %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + if(size < 0 || size >= cap) { + FURI_LOG_W( + TAG, "Exceeded NFC_TEXT_STORE_SIZE when preparing app id strings; menu truncated"); + break; + } + char* label = buf; + cap -= size + 1; + buf += size + 1; + submenu_add_item( + submenu, label, idx++, nfc_scene_mifare_desfire_data_submenu_callback, nfc); + } + + if(state >= MifareDesfireDataStateItem) { + submenu_set_selected_item( + nfc->submenu, state - MifareDesfireDataStateItem + SubmenuIndexDynamic); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareDesfireData, MifareDesfireDataStateMenu); + } + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mifare_desfire_data_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = (Nfc*)context; + uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireData); + MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data; + + if(event.type == SceneManagerEventTypeCustom) { + TextBox* text_box = nfc->text_box; + string_reset(nfc->text_box_store); + if(event.event == SubmenuIndexCardInfo) { + mf_df_cat_card_info(data, nfc->text_box_store); + text_box_set_text(text_box, string_get_cstr(nfc->text_box_store)); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneMifareDesfireData, + MifareDesfireDataStateItem + SubmenuIndexCardInfo); + return true; + } else { + uint16_t index = event.event - SubmenuIndexDynamic; + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareDesfireData, MifareDesfireDataStateItem + index); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareDesfireApp, index << 1); + scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareDesfireApp); + return true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state >= MifareDesfireDataStateItem) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareDesfireData, MifareDesfireDataStateMenu); + return true; + } + } + + return false; +} + +void nfc_scene_mifare_desfire_data_on_exit(void* context) { + Nfc* nfc = (Nfc*)context; + + text_box_reset(nfc->text_box); + string_reset(nfc->text_box_store); + + submenu_reset(nfc->submenu); +} diff --git a/applications/nfc/scenes/nfc_scene_mifare_desfire_menu.c b/applications/nfc/scenes/nfc_scene_mifare_desfire_menu.c new file mode 100644 index 00000000..1741e123 --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_mifare_desfire_menu.c @@ -0,0 +1,52 @@ +#include "../nfc_i.h" + +enum SubmenuIndex { + SubmenuIndexSave, +}; + +void nfc_scene_mifare_desfire_menu_submenu_callback(void* context, uint32_t index) { + Nfc* nfc = (Nfc*)context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, index); +} + +void nfc_scene_mifare_desfire_menu_on_enter(void* context) { + Nfc* nfc = (Nfc*)context; + Submenu* submenu = nfc->submenu; + + submenu_add_item( + submenu, + "Name and save", + SubmenuIndexSave, + nfc_scene_mifare_desfire_menu_submenu_callback, + nfc); + submenu_set_selected_item( + nfc->submenu, + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareDesfireMenu)); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mifare_desfire_menu_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = (Nfc*)context; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSave) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneMifareDesfireMenu, SubmenuIndexSave); + nfc->dev->format = NfcDeviceSaveFormatMifareDesfire; + // Clear device name + nfc_device_set_name(nfc->dev, ""); + scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); + return true; + } + } + + return false; +} + +void nfc_scene_mifare_desfire_menu_on_exit(void* context) { + Nfc* nfc = (Nfc*)context; + + submenu_reset(nfc->submenu); +} diff --git a/applications/nfc/scenes/nfc_scene_read_mifare_desfire.c b/applications/nfc/scenes/nfc_scene_read_mifare_desfire.c new file mode 100644 index 00000000..df21080e --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_read_mifare_desfire.c @@ -0,0 +1,56 @@ +#include "../nfc_i.h" +#include + +void nfc_read_mifare_desfire_worker_callback(void* context) { + Nfc* nfc = (Nfc*)context; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit); +} + +void nfc_scene_read_mifare_desfire_on_enter(void* context) { + Nfc* nfc = (Nfc*)context; + DOLPHIN_DEED(DolphinDeedNfcRead); + + // Setup view + Popup* popup = nfc->popup; + popup_set_header(popup, "Reading\nDESFire", 70, 34, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); + // Start worker + nfc_worker_start( + nfc->worker, + NfcWorkerStateReadMifareDesfire, + &nfc->dev->dev_data, + nfc_read_mifare_desfire_worker_callback, + nfc); +} + +bool nfc_scene_read_mifare_desfire_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = (Nfc*)context; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventWorkerExit) { + notification_message(nfc->notifications, &sequence_success); + scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfireSuccess); + return true; + } + } else if(event.type == SceneManagerEventTypeTick) { + notification_message(nfc->notifications, &sequence_blink_blue_10); + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + return true; + } + return false; +} + +void nfc_scene_read_mifare_desfire_on_exit(void* context) { + Nfc* nfc = (Nfc*)context; + + // Stop worker + nfc_worker_stop(nfc->worker); + + // Clear view + Popup* popup = nfc->popup; + popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); + popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 0, NULL); +} diff --git a/applications/nfc/scenes/nfc_scene_read_mifare_desfire_success.c b/applications/nfc/scenes/nfc_scene_read_mifare_desfire_success.c new file mode 100644 index 00000000..995869bf --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_read_mifare_desfire_success.c @@ -0,0 +1,106 @@ +#include "../nfc_i.h" +#include + +#define NFC_SCENE_READ_SUCCESS_SHIFT " " + +enum { + ReadMifareDesfireSuccessStateShowUID, + ReadMifareDesfireSuccessStateShowData, +}; + +void nfc_scene_read_mifare_desfire_success_dialog_callback(DialogExResult result, void* context) { + Nfc* nfc = (Nfc*)context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); +} + +void nfc_scene_read_mifare_desfire_success_on_enter(void* context) { + Nfc* nfc = (Nfc*)context; + + MifareDesfireData* data = &nfc->dev->dev_data.mf_df_data; + DialogEx* dialog_ex = nfc->dialog_ex; + dialog_ex_set_left_button_text(dialog_ex, "Back"); + dialog_ex_set_center_button_text(dialog_ex, "Data"); + dialog_ex_set_right_button_text(dialog_ex, "More"); + dialog_ex_set_icon(dialog_ex, 8, 16, &I_Medium_chip_22x21); + + uint16_t n_apps = 0; + uint16_t n_files = 0; + + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + n_apps++; + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + n_files++; + } + } + + nfc_text_store_set( + nfc, + "UID: %02X %02X %02X %02X %02X %02X %02X\n" NFC_SCENE_READ_SUCCESS_SHIFT + "%d%s bytes\n" NFC_SCENE_READ_SUCCESS_SHIFT "%d bytes free\n" + "%d application%s, %d file%s", + data->version.uid[0], + data->version.uid[1], + data->version.uid[2], + data->version.uid[3], + data->version.uid[4], + data->version.uid[5], + data->version.uid[6], + 1 << (data->version.sw_storage >> 1), + (data->version.sw_storage & 1) ? "+" : "", + data->free_memory ? data->free_memory->bytes : 0, + n_apps, + n_apps == 1 ? "" : "s", + n_files, + n_files == 1 ? "" : "s"); + dialog_ex_set_text(dialog_ex, nfc->text_store, 8, 6, AlignLeft, AlignTop); + dialog_ex_set_context(dialog_ex, nfc); + dialog_ex_set_result_callback( + dialog_ex, nfc_scene_read_mifare_desfire_success_dialog_callback); + + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneReadMifareDesfireSuccess, + ReadMifareDesfireSuccessStateShowUID); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); +} + +bool nfc_scene_read_mifare_desfire_success_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + uint32_t state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadMifareDesfireSuccess); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(state == ReadMifareDesfireSuccessStateShowUID && event.event == DialogExResultLeft) { + scene_manager_previous_scene(nfc->scene_manager); + consumed = true; + } else if( + state == ReadMifareDesfireSuccessStateShowUID && event.event == DialogExResultCenter) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareDesfireData); + consumed = true; + } else if(state == ReadMifareDesfireSuccessStateShowUID && event.event == DialogExResultRight) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareDesfireMenu); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state == ReadMifareDesfireSuccessStateShowData) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneReadMifareDesfireSuccess, + ReadMifareDesfireSuccessStateShowUID); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_read_mifare_desfire_success_on_exit(void* context) { + Nfc* nfc = (Nfc*)context; + + // Clean dialog + DialogEx* dialog_ex = nfc->dialog_ex; + dialog_ex_reset(dialog_ex); +} diff --git a/applications/nfc/scenes/nfc_scene_save_success.c b/applications/nfc/scenes/nfc_scene_save_success.c old mode 100755 new mode 100644 index f8195d8c..b1a84003 --- a/applications/nfc/scenes/nfc_scene_save_success.c +++ b/applications/nfc/scenes/nfc_scene_save_success.c @@ -33,6 +33,10 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { consumed = scene_manager_search_and_switch_to_another_scene( nfc->scene_manager, NfcSceneFileSelect); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMifareDesfireMenu)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMifareDesfireMenu); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); diff --git a/applications/nfc/scenes/nfc_scene_saved_menu.c b/applications/nfc/scenes/nfc_scene_saved_menu.c old mode 100755 new mode 100644 index 5da82572..201132c1 --- a/applications/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/nfc/scenes/nfc_scene_saved_menu.c @@ -18,9 +18,22 @@ void nfc_scene_saved_menu_on_enter(void* context) { Nfc* nfc = (Nfc*)context; Submenu* submenu = nfc->submenu; - if(nfc->dev->format != NfcDeviceSaveFormatBankCard) { + if(nfc->dev->format == NfcDeviceSaveFormatUid || + nfc->dev->format == NfcDeviceSaveFormatMifareDesfire || + nfc->dev->format == NfcDeviceSaveFormatBankCard) { submenu_add_item( - submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); + submenu, + "Emulate UID", + SubmenuIndexEmulate, + nfc_scene_saved_menu_submenu_callback, + nfc); + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { + submenu_add_item( + submenu, + "Emulate Ultralight", + SubmenuIndexEmulate, + nfc_scene_saved_menu_submenu_callback, + nfc); } submenu_add_item( submenu, "Edit UID and name", SubmenuIndexEdit, nfc_scene_saved_menu_submenu_callback, nfc); diff --git a/applications/nfc/scenes/nfc_scene_scripts_menu.c b/applications/nfc/scenes/nfc_scene_scripts_menu.c index 367b5c05..5df2c7d2 100755 --- a/applications/nfc/scenes/nfc_scene_scripts_menu.c +++ b/applications/nfc/scenes/nfc_scene_scripts_menu.c @@ -3,6 +3,7 @@ enum SubmenuIndex { SubmenuIndexBankCard, SubmenuIndexMifareUltralight, + SubmenuIndexMifareDesfire, }; void nfc_scene_scripts_menu_submenu_callback(void* context, uint32_t index) { @@ -27,6 +28,12 @@ void nfc_scene_scripts_menu_on_enter(void* context) { SubmenuIndexMifareUltralight, nfc_scene_scripts_menu_submenu_callback, nfc); + submenu_add_item( + submenu, + "Read Mifare DESFire", + SubmenuIndexMifareDesfire, + nfc_scene_scripts_menu_submenu_callback, + nfc); submenu_set_selected_item( nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneScriptsMenu)); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); @@ -46,6 +53,11 @@ bool nfc_scene_scripts_menu_on_event(void* context, SceneManagerEvent event) { nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIndexMifareUltralight); scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUl); return true; + } else if(event.event == SubmenuIndexMifareDesfire) { + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIndexMifareDesfire); + scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfire); + return true; } } diff --git a/applications/tests/flipper_format/flipper_format_test.c b/applications/tests/flipper_format/flipper_format_test.c index 62171817..f83360b4 100644 --- a/applications/tests/flipper_format/flipper_format_test.c +++ b/applications/tests/flipper_format/flipper_format_test.c @@ -26,6 +26,10 @@ static const char* test_float_key = "Float data"; static const float test_float_data[] = {1.5f, 1000.0f}; static const float test_float_updated_data[] = {1.2f}; +static const char* test_bool_key = "Bool data"; +static const bool test_bool_data[] = {true, false}; +static const bool test_bool_updated_data[] = {false, true, true}; + static const char* test_hex_key = "Hex data"; static const uint8_t test_hex_data[] = {0xDE, 0xAD, 0xBE}; static const uint8_t test_hex_updated_data[] = {0xFE, 0xCA}; @@ -38,6 +42,7 @@ static const char* test_data_nix = "Filetype: Flipper File test\n" "Int32 data: 1234 -6345 7813 0\n" "Uint32 data: 1234 0 5678 9098 7654321\n" "Float data: 1.5 1000.0\n" + "Bool data: true false\n" "Hex data: DE AD BE"; #define READ_TEST_WIN "ff_win.test" @@ -48,6 +53,7 @@ static const char* test_data_win = "Filetype: Flipper File test\r\n" "Int32 data: 1234 -6345 7813 0\r\n" "Uint32 data: 1234 0 5678 9098 7654321\r\n" "Float data: 1.5 1000.0\r\n" + "Bool data: true false\r\n" "Hex data: DE AD BE"; #define READ_TEST_FLP "ff_flp.test" @@ -129,6 +135,11 @@ static bool test_read(const char* file_name) { if(memcmp(scratchpad, test_float_data, sizeof(float) * COUNT_OF(test_float_data)) != 0) break; + if(!flipper_format_get_value_count(file, test_bool_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_bool_data)) break; + if(!flipper_format_read_bool(file, test_bool_key, scratchpad, uint32_value)) break; + if(memcmp(scratchpad, test_bool_data, sizeof(bool) * COUNT_OF(test_bool_data)) != 0) break; + if(!flipper_format_get_value_count(file, test_hex_key, &uint32_value)) break; if(uint32_value != COUNT_OF(test_hex_data)) break; if(!flipper_format_read_hex(file, test_hex_key, scratchpad, uint32_value)) break; @@ -195,6 +206,15 @@ static bool test_read_updated(const char* file_name) { sizeof(float) * COUNT_OF(test_float_updated_data)) != 0) break; + if(!flipper_format_get_value_count(file, test_bool_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_bool_updated_data)) break; + if(!flipper_format_read_bool(file, test_bool_key, scratchpad, uint32_value)) break; + if(memcmp( + scratchpad, + test_bool_updated_data, + sizeof(bool) * COUNT_OF(test_bool_updated_data)) != 0) + break; + if(!flipper_format_get_value_count(file, test_hex_key, &uint32_value)) break; if(uint32_value != COUNT_OF(test_hex_updated_data)) break; if(!flipper_format_read_hex(file, test_hex_key, scratchpad, uint32_value)) break; @@ -235,6 +255,9 @@ static bool test_write(const char* file_name) { if(!flipper_format_write_float( file, test_float_key, test_float_data, COUNT_OF(test_float_data))) break; + if(!flipper_format_write_bool( + file, test_bool_key, test_bool_data, COUNT_OF(test_bool_data))) + break; if(!flipper_format_write_hex(file, test_hex_key, test_hex_data, COUNT_OF(test_hex_data))) break; result = true; @@ -299,6 +322,9 @@ static bool test_update(const char* file_name) { if(!flipper_format_update_float( file, test_float_key, test_float_updated_data, COUNT_OF(test_float_updated_data))) break; + if(!flipper_format_update_bool( + file, test_bool_key, test_bool_updated_data, COUNT_OF(test_bool_updated_data))) + break; if(!flipper_format_update_hex( file, test_hex_key, test_hex_updated_data, COUNT_OF(test_hex_updated_data))) break; @@ -328,6 +354,9 @@ static bool test_update_backward(const char* file_name) { if(!flipper_format_update_float( file, test_float_key, test_float_data, COUNT_OF(test_float_data))) break; + if(!flipper_format_update_bool( + file, test_bool_key, test_bool_data, COUNT_OF(test_bool_data))) + break; if(!flipper_format_update_hex(file, test_hex_key, test_hex_data, COUNT_OF(test_hex_data))) break; diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index dd3676c5..01f54632 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -246,6 +246,36 @@ bool flipper_format_write_int32( return result; } +bool flipper_format_read_bool( + FlipperFormat* flipper_format, + const char* key, + bool* data, + const uint16_t data_size) { + return flipper_format_stream_read_value_line( + flipper_format->stream, + key, + FlipperStreamValueBool, + data, + data_size, + flipper_format->strict_mode); +} + +bool flipper_format_write_bool( + FlipperFormat* flipper_format, + const char* key, + const bool* data, + const uint16_t data_size) { + furi_assert(flipper_format); + FlipperStreamWriteData write_data = { + .key = key, + .type = FlipperStreamValueBool, + .data = data, + .data_size = data_size, + }; + bool result = flipper_format_stream_write_value_line(flipper_format->stream, &write_data); + return result; +} + bool flipper_format_read_float( FlipperFormat* flipper_format, const char* key, @@ -391,6 +421,22 @@ bool flipper_format_update_int32( return result; } +bool flipper_format_update_bool( + FlipperFormat* flipper_format, + const char* key, + const bool* data, + const uint16_t data_size) { + FlipperStreamWriteData write_data = { + .key = key, + .type = FlipperStreamValueBool, + .data = data, + .data_size = data_size, + }; + bool result = flipper_format_stream_delete_key_and_write( + flipper_format->stream, &write_data, flipper_format->strict_mode); + return result; +} + bool flipper_format_update_float( FlipperFormat* flipper_format, const char* key, @@ -489,6 +535,23 @@ bool flipper_format_insert_or_update_int32( return result; } +bool flipper_format_insert_or_update_bool( + FlipperFormat* flipper_format, + const char* key, + const bool* data, + const uint16_t data_size) { + bool result = false; + + if(!flipper_format_key_exist(flipper_format, key)) { + flipper_format_seek_to_end(flipper_format); + result = flipper_format_write_bool(flipper_format, key, data, data_size); + } else { + result = flipper_format_update_bool(flipper_format, key, data, data_size); + } + + return result; +} + bool flipper_format_insert_or_update_float( FlipperFormat* flipper_format, const char* key, diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index a67c7a47..0f3c299d 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -329,6 +329,34 @@ bool flipper_format_write_int32( const int32_t* data, const uint16_t data_size); +/** + * Read array of bool by key + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_format_read_bool( + FlipperFormat* flipper_format, + const char* key, + bool* data, + const uint16_t data_size); + +/** + * Write key and array of bool + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_format_write_bool( + FlipperFormat* flipper_format, + const char* key, + const bool* data, + const uint16_t data_size); + /** * Read array of float by key * @param flipper_format Pointer to a FlipperFormat instance @@ -456,6 +484,19 @@ bool flipper_format_update_int32( const int32_t* data, const uint16_t data_size); +/** + * Updates the value of the first matching key to a bool array value. Sets the RW pointer to a position at the end of inserted data. + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_format_update_bool( + FlipperFormat* flipper_format, + const char* key, + const bool* data, + const uint16_t data_size); + /** * Updates the value of the first matching key to a float array value. Sets the RW pointer to a position at the end of inserted data. * @param flipper_format Pointer to a FlipperFormat instance @@ -537,6 +578,20 @@ bool flipper_format_insert_or_update_int32( const int32_t* data, const uint16_t data_size); +/** + * Updates the value of the first matching key to a bool array value, or adds the key and value if the key did not exist. + * Sets the RW pointer to a position at the end of inserted data. + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_format_insert_or_update_bool( + FlipperFormat* flipper_format, + const char* key, + const bool* data, + const uint16_t data_size); + /** * Updates the value of the first matching key to a float array value, or adds the key and value if the key did not exist. * Sets the RW pointer to a position at the end of inserted data. diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index 44084b05..bc12cd17 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -285,6 +285,10 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa const uint32_t* data = write_data->data; string_printf(value, "%" PRId32, data[i]); }; break; + case FlipperStreamValueBool: { + const bool* data = write_data->data; + string_printf(value, data[i] ? "true" : "false"); + }; break; default: furi_crash("Unknown FF type"); } @@ -372,6 +376,11 @@ bool flipper_format_stream_read_value_line( uint32_t* data = _data; scan_values = sscanf(string_get_cstr(value), "%" PRId32, &data[i]); }; break; + case FlipperStreamValueBool: { + bool* data = _data; + data[i] = !string_cmpi_str(value, "true"); + scan_values = 1; + }; break; default: furi_crash("Unknown FF type"); } diff --git a/lib/flipper_format/flipper_format_stream.h b/lib/flipper_format/flipper_format_stream.h index 6bf50c57..dc9fb5b3 100644 --- a/lib/flipper_format/flipper_format_stream.h +++ b/lib/flipper_format/flipper_format_stream.h @@ -15,6 +15,7 @@ typedef enum { FlipperStreamValueFloat, FlipperStreamValueInt32, FlipperStreamValueUint32, + FlipperStreamValueBool, } FlipperStreamValue; typedef struct { diff --git a/lib/nfc_protocols/mifare_desfire.c b/lib/nfc_protocols/mifare_desfire.c new file mode 100644 index 00000000..6a9c13b6 --- /dev/null +++ b/lib/nfc_protocols/mifare_desfire.c @@ -0,0 +1,447 @@ +#include "mifare_desfire.h" +#include +#include + +void mf_df_clear(MifareDesfireData* data) { + free(data->free_memory); + if(data->master_key_settings) { + MifareDesfireKeyVersion* key_version = data->master_key_settings->key_version_head; + while(key_version) { + MifareDesfireKeyVersion* next_key_version = key_version->next; + free(key_version); + key_version = next_key_version; + } + } + free(data->master_key_settings); + MifareDesfireApplication* app = data->app_head; + while(app) { + MifareDesfireApplication* next_app = app->next; + if(app->key_settings) { + MifareDesfireKeyVersion* key_version = app->key_settings->key_version_head; + while(key_version) { + MifareDesfireKeyVersion* next_key_version = key_version->next; + free(key_version); + key_version = next_key_version; + } + } + free(app->key_settings); + MifareDesfireFile* file = app->file_head; + while(file) { + MifareDesfireFile* next_file = file->next; + free(file->contents); + free(file); + file = next_file; + } + free(app); + app = next_app; + } + data->free_memory = NULL; + data->master_key_settings = NULL; + data->app_head = NULL; +} + +void mf_df_cat_data(MifareDesfireData* data, string_t out) { + mf_df_cat_card_info(data, out); + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + mf_df_cat_application(app, out); + } +} + +void mf_df_cat_card_info(MifareDesfireData* data, string_t out) { + mf_df_cat_version(&data->version, out); + if(data->free_memory) { + mf_df_cat_free_mem(data->free_memory, out); + } + if(data->master_key_settings) { + mf_df_cat_key_settings(data->master_key_settings, out); + } +} + +void mf_df_cat_version(MifareDesfireVersion* version, string_t out) { + string_cat_printf( + out, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + version->uid[0], + version->uid[1], + version->uid[2], + version->uid[3], + version->uid[4], + version->uid[5], + version->uid[6]); + string_cat_printf( + out, + "hw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + version->hw_vendor, + version->hw_type, + version->hw_subtype, + version->hw_major, + version->hw_minor, + version->hw_storage, + version->hw_proto); + string_cat_printf( + out, + "sw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + version->sw_vendor, + version->sw_type, + version->sw_subtype, + version->sw_major, + version->sw_minor, + version->sw_storage, + version->sw_proto); + string_cat_printf( + out, + "batch %02x:%02x:%02x:%02x:%02x\n" + "week %d year %d\n", + version->batch[0], + version->batch[1], + version->batch[2], + version->batch[3], + version->batch[4], + version->prod_week, + version->prod_year); +} + +void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, string_t out) { + string_cat_printf(out, "freeMem %d\n", free_mem->bytes); +} + +void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out) { + string_cat_printf(out, "changeKeyID %d\n", ks->change_key_id); + string_cat_printf(out, "configChangeable %d\n", ks->config_changeable); + string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete); + string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list); + string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable); + string_cat_printf(out, "maxKeys %d\n", ks->max_keys); + for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { + string_cat_printf(out, "key %d version %d\n", kv->id, kv->version); + } +} + +void mf_df_cat_application_info(MifareDesfireApplication* app, string_t out) { + string_cat_printf(out, "Application %02x%02x%02x\n", app->id[0], app->id[1], app->id[2]); + if(app->key_settings) { + mf_df_cat_key_settings(app->key_settings, out); + } +} + +void mf_df_cat_application(MifareDesfireApplication* app, string_t out) { + mf_df_cat_application_info(app, out); + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + mf_df_cat_file(file, out); + } +} + +void mf_df_cat_file(MifareDesfireFile* file, string_t out) { + char* type = "unknown"; + switch(file->type) { + case MifareDesfireFileTypeStandard: + type = "standard"; + break; + case MifareDesfireFileTypeBackup: + type = "backup"; + break; + case MifareDesfireFileTypeValue: + type = "value"; + break; + case MifareDesfireFileTypeLinearRecord: + type = "linear"; + break; + case MifareDesfireFileTypeCyclicRecord: + type = "cyclic"; + break; + } + char* comm = "unknown"; + switch(file->comm) { + case MifareDesfireFileCommunicationSettingsPlaintext: + comm = "plain"; + break; + case MifareDesfireFileCommunicationSettingsAuthenticated: + comm = "auth"; + break; + case MifareDesfireFileCommunicationSettingsEnciphered: + comm = "enciphered"; + break; + } + string_cat_printf(out, "File %d\n", file->id); + string_cat_printf(out, "%s %s\n", type, comm); + string_cat_printf( + out, + "r %d w %d rw %d c %d\n", + file->access_rights >> 12 & 0xF, + file->access_rights >> 8 & 0xF, + file->access_rights >> 4 & 0xF, + file->access_rights & 0xF); + uint16_t size = 0; + uint16_t num = 1; + switch(file->type) { + case MifareDesfireFileTypeStandard: + case MifareDesfireFileTypeBackup: + size = file->settings.data.size; + string_cat_printf(out, "size %d\n", size); + break; + case MifareDesfireFileTypeValue: + size = 4; + string_cat_printf( + out, "lo %d hi %d\n", file->settings.value.lo_limit, file->settings.value.hi_limit); + string_cat_printf( + out, + "limit %d enabled %d\n", + file->settings.value.limited_credit_value, + file->settings.value.limited_credit_enabled); + break; + case MifareDesfireFileTypeLinearRecord: + case MifareDesfireFileTypeCyclicRecord: + size = file->settings.record.size; + num = file->settings.record.cur; + string_cat_printf(out, "size %d\n", size); + string_cat_printf(out, "num %d max %d\n", num, file->settings.record.max); + break; + } + uint8_t* data = file->contents; + if(data) { + for(int rec = 0; rec < num; rec++) { + for(int ch = 0; ch < size; ch++) { + string_cat_printf(out, "%02x", data[rec * size + ch]); + } + string_cat_printf(out, " \n"); + } + } +} + +bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + return ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20; +} + +uint16_t mf_df_prepare_get_version(uint8_t* dest) { + dest[0] = MF_DF_GET_VERSION; + return 1; +} + +bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len < sizeof(MifareDesfireVersion)) { + return false; + } + memcpy(out, buf, sizeof(MifareDesfireVersion)); + return true; +} + +uint16_t mf_df_prepare_get_free_memory(uint8_t* dest) { + dest[0] = MF_DF_GET_FREE_MEMORY; + return 1; +} + +bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len != 3) { + return false; + } + out->bytes = buf[0] | (buf[1] << 8) | (buf[2] << 16); + return true; +} + +uint16_t mf_df_prepare_get_key_settings(uint8_t* dest) { + dest[0] = MF_DF_GET_KEY_SETTINGS; + return 1; +} + +bool mf_df_parse_get_key_settings_response( + uint8_t* buf, + uint16_t len, + MifareDesfireKeySettings* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len < 2) { + return false; + } + out->change_key_id = buf[0] >> 4; + out->config_changeable = (buf[0] & 0x8) != 0; + out->free_create_delete = (buf[0] & 0x4) != 0; + out->free_directory_list = (buf[0] & 0x2) != 0; + out->master_key_changeable = (buf[0] & 0x1) != 0; + out->max_keys = buf[1]; + return true; +} + +uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id) { + dest[0] = MF_DF_GET_KEY_VERSION; + dest[1] = key_id; + return 2; +} + +bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out) { + if(len != 2 || *buf) { + return false; + } + out->version = buf[1]; + return true; +} + +uint16_t mf_df_prepare_get_application_ids(uint8_t* dest) { + dest[0] = MF_DF_GET_APPLICATION_IDS; + return 1; +} + +bool mf_df_parse_get_application_ids_response( + uint8_t* buf, + uint16_t len, + MifareDesfireApplication** app_head) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len % 3 != 0) { + return false; + } + while(len) { + MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication)); + memset(app, 0, sizeof(MifareDesfireApplication)); + memcpy(app->id, buf, 3); + len -= 3; + buf += 3; + *app_head = app; + app_head = &app->next; + } + return true; +} + +uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]) { + dest[0] = MF_DF_SELECT_APPLICATION; + dest[1] = id[0]; + dest[2] = id[1]; + dest[3] = id[2]; + return 4; +} + +bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len) { + return len == 1 && !*buf; +} + +uint16_t mf_df_prepare_get_file_ids(uint8_t* dest) { + dest[0] = MF_DF_GET_FILE_IDS; + return 1; +} + +bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + while(len) { + MifareDesfireFile* file = malloc(sizeof(MifareDesfireFile)); + memset(file, 0, sizeof(MifareDesfireFile)); + file->id = *buf; + len--; + buf++; + *file_head = file; + file_head = &file->next; + } + return true; +} + +uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id) { + dest[0] = MF_DF_GET_FILE_SETTINGS; + dest[1] = file_id; + return 2; +} + +bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { + if(len < 5 || *buf) { + return false; + } + len--; + buf++; + out->type = buf[0]; + out->comm = buf[1]; + out->access_rights = buf[2] | (buf[3] << 8); + switch(out->type) { + case MifareDesfireFileTypeStandard: + case MifareDesfireFileTypeBackup: + if(len != 7) { + return false; + } + out->settings.data.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); + break; + case MifareDesfireFileTypeValue: + if(len != 17) { + return false; + } + out->settings.value.lo_limit = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); + out->settings.value.hi_limit = buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24); + out->settings.value.limited_credit_value = buf[12] | (buf[13] << 8) | (buf[14] << 16) | + (buf[15] << 24); + out->settings.value.limited_credit_enabled = buf[16]; + break; + case MifareDesfireFileTypeLinearRecord: + case MifareDesfireFileTypeCyclicRecord: + if(len != 13) { + return false; + } + out->settings.record.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); + out->settings.record.max = buf[7] | (buf[8] << 8) | (buf[9] << 16); + out->settings.record.cur = buf[10] | (buf[11] << 8) | (buf[12] << 16); + break; + default: + return false; + } + return true; +} + +uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { + dest[0] = MF_DF_READ_DATA; + dest[1] = file_id; + dest[2] = offset; + dest[3] = offset >> 8; + dest[4] = offset >> 16; + dest[5] = len; + dest[6] = len >> 8; + dest[7] = len >> 16; + return 8; +} + +uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id) { + dest[0] = MF_DF_GET_VALUE; + dest[1] = file_id; + return 2; +} + +uint16_t + mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { + dest[0] = MF_DF_READ_RECORDS; + dest[1] = file_id; + dest[2] = offset; + dest[3] = offset >> 8; + dest[4] = offset >> 16; + dest[5] = len; + dest[6] = len >> 8; + dest[7] = len >> 16; + return 8; +} + +bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + out->contents = malloc(len); + memcpy(out->contents, buf, len); + return true; +} \ No newline at end of file diff --git a/lib/nfc_protocols/mifare_desfire.h b/lib/nfc_protocols/mifare_desfire.h new file mode 100644 index 00000000..809f1782 --- /dev/null +++ b/lib/nfc_protocols/mifare_desfire.h @@ -0,0 +1,164 @@ +#pragma once + +#include +#include +#include + +#define MF_DF_GET_VERSION (0x60) +#define MF_DF_GET_FREE_MEMORY (0x6E) +#define MF_DF_GET_KEY_SETTINGS (0x45) +#define MF_DF_GET_KEY_VERSION (0x64) +#define MF_DF_GET_APPLICATION_IDS (0x6A) +#define MF_DF_SELECT_APPLICATION (0x5A) +#define MF_DF_GET_FILE_IDS (0x6F) +#define MF_DF_GET_FILE_SETTINGS (0xF5) + +#define MF_DF_READ_DATA (0xBD) +#define MF_DF_GET_VALUE (0x6C) +#define MF_DF_READ_RECORDS (0xBB) + +typedef struct { + uint8_t hw_vendor; + uint8_t hw_type; + uint8_t hw_subtype; + uint8_t hw_major; + uint8_t hw_minor; + uint8_t hw_storage; + uint8_t hw_proto; + + uint8_t sw_vendor; + uint8_t sw_type; + uint8_t sw_subtype; + uint8_t sw_major; + uint8_t sw_minor; + uint8_t sw_storage; + uint8_t sw_proto; + + uint8_t uid[7]; + uint8_t batch[5]; + uint8_t prod_week; + uint8_t prod_year; +} MifareDesfireVersion; + +typedef struct { + uint32_t bytes; +} MifareDesfireFreeMemory; // EV1+ only + +typedef struct MifareDesfireKeyVersion { + uint8_t id; + uint8_t version; + struct MifareDesfireKeyVersion* next; +} MifareDesfireKeyVersion; + +typedef struct { + uint8_t change_key_id; + bool config_changeable; + bool free_create_delete; + bool free_directory_list; + bool master_key_changeable; + uint8_t max_keys; + MifareDesfireKeyVersion* key_version_head; +} MifareDesfireKeySettings; + +typedef enum { + MifareDesfireFileTypeStandard = 0, + MifareDesfireFileTypeBackup = 1, + MifareDesfireFileTypeValue = 2, + MifareDesfireFileTypeLinearRecord = 3, + MifareDesfireFileTypeCyclicRecord = 4, +} MifareDesfireFileType; + +typedef enum { + MifareDesfireFileCommunicationSettingsPlaintext = 0, + MifareDesfireFileCommunicationSettingsAuthenticated = 1, + MifareDesfireFileCommunicationSettingsEnciphered = 3, +} MifareDesfireFileCommunicationSettings; + +typedef struct MifareDesfireFile { + uint8_t id; + MifareDesfireFileType type; + MifareDesfireFileCommunicationSettings comm; + uint16_t access_rights; + union { + struct { + uint32_t size; + } data; + struct { + uint32_t lo_limit; + uint32_t hi_limit; + uint32_t limited_credit_value; + bool limited_credit_enabled; + } value; + struct { + uint32_t size; + uint32_t max; + uint32_t cur; + } record; + } settings; + uint8_t* contents; + + struct MifareDesfireFile* next; +} MifareDesfireFile; + +typedef struct MifareDesfireApplication { + uint8_t id[3]; + MifareDesfireKeySettings* key_settings; + MifareDesfireFile* file_head; + + struct MifareDesfireApplication* next; +} MifareDesfireApplication; + +typedef struct { + MifareDesfireVersion version; + MifareDesfireFreeMemory* free_memory; + MifareDesfireKeySettings* master_key_settings; + MifareDesfireApplication* app_head; +} MifareDesfireData; + +void mf_df_clear(MifareDesfireData* data); + +void mf_df_cat_data(MifareDesfireData* data, string_t out); +void mf_df_cat_card_info(MifareDesfireData* data, string_t out); +void mf_df_cat_version(MifareDesfireVersion* version, string_t out); +void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, string_t out); +void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, string_t out); +void mf_df_cat_application_info(MifareDesfireApplication* app, string_t out); +void mf_df_cat_application(MifareDesfireApplication* app, string_t out); +void mf_df_cat_file(MifareDesfireFile* file, string_t out); + +bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +uint16_t mf_df_prepare_get_version(uint8_t* dest); +bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out); + +uint16_t mf_df_prepare_get_free_memory(uint8_t* dest); +bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out); + +uint16_t mf_df_prepare_get_key_settings(uint8_t* dest); +bool mf_df_parse_get_key_settings_response( + uint8_t* buf, + uint16_t len, + MifareDesfireKeySettings* out); + +uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id); +bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out); + +uint16_t mf_df_prepare_get_application_ids(uint8_t* dest); +bool mf_df_parse_get_application_ids_response( + uint8_t* buf, + uint16_t len, + MifareDesfireApplication** app_head); + +uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]); +bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len); + +uint16_t mf_df_prepare_get_file_ids(uint8_t* dest); +bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head); + +uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id); +bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); + +uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); +uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id); +uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); +bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out);