Nfc: add basic Mifare DESFire support (#1024)
* 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 <git+flipperzero@kevin.wallace.seattle.wa.us> Co-authored-by: あく <alleteam@gmail.com> Co-authored-by: gornekich <n.gorbadey@gmail.com>
This commit is contained in:
		
							parent
							
								
									d075e00ae1
								
							
						
					
					
						commit
						3857cd7d5f
					
				| @ -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; | ||||
|  | ||||
| @ -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); | ||||
| } | ||||
|  | ||||
							
								
								
									
										396
									
								
								applications/nfc/nfc_device.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										396
									
								
								applications/nfc/nfc_device.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -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; | ||||
| } | ||||
|  | ||||
| @ -6,6 +6,7 @@ | ||||
| #include <dialogs/dialogs.h> | ||||
| 
 | ||||
| #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); | ||||
|  | ||||
| @ -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"; | ||||
|     } | ||||
|  | ||||
							
								
								
									
										248
									
								
								applications/nfc/nfc_worker.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										248
									
								
								applications/nfc/nfc_worker.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -1,6 +1,7 @@ | ||||
| #include "nfc_worker_i.h" | ||||
| #include <furi_hal.h> | ||||
| #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) { | ||||
|  | ||||
| @ -18,6 +18,7 @@ typedef enum { | ||||
|     NfcWorkerStateField, | ||||
|     NfcWorkerStateReadMifareUl, | ||||
|     NfcWorkerStateEmulateMifareUl, | ||||
|     NfcWorkerStateReadMifareDesfire, | ||||
|     // Transition
 | ||||
|     NfcWorkerStateStop, | ||||
| } NfcWorkerState; | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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); | ||||
|             } | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
							
								
								
									
										27
									
								
								applications/nfc/scenes/nfc_scene_device_info.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										27
									
								
								applications/nfc/scenes/nfc_scene_device_info.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -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( | ||||
|  | ||||
							
								
								
									
										119
									
								
								applications/nfc/scenes/nfc_scene_mifare_desfire_app.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								applications/nfc/scenes/nfc_scene_mifare_desfire_app.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
| } | ||||
							
								
								
									
										108
									
								
								applications/nfc/scenes/nfc_scene_mifare_desfire_data.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								applications/nfc/scenes/nfc_scene_mifare_desfire_data.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
| } | ||||
							
								
								
									
										52
									
								
								applications/nfc/scenes/nfc_scene_mifare_desfire_menu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								applications/nfc/scenes/nfc_scene_mifare_desfire_menu.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
| } | ||||
							
								
								
									
										56
									
								
								applications/nfc/scenes/nfc_scene_read_mifare_desfire.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								applications/nfc/scenes/nfc_scene_read_mifare_desfire.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| #include "../nfc_i.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
							
								
								
									
										106
									
								
								applications/nfc/scenes/nfc_scene_read_mifare_desfire_success.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								applications/nfc/scenes/nfc_scene_read_mifare_desfire_success.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,106 @@ | ||||
| #include "../nfc_i.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| #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); | ||||
| } | ||||
							
								
								
									
										4
									
								
								applications/nfc/scenes/nfc_scene_save_success.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										4
									
								
								applications/nfc/scenes/nfc_scene_save_success.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -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); | ||||
|  | ||||
							
								
								
									
										17
									
								
								applications/nfc/scenes/nfc_scene_saved_menu.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										17
									
								
								applications/nfc/scenes/nfc_scene_saved_menu.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -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); | ||||
|  | ||||
| @ -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; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
| @ -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"); | ||||
|                     } | ||||
|  | ||||
| @ -15,6 +15,7 @@ typedef enum { | ||||
|     FlipperStreamValueFloat, | ||||
|     FlipperStreamValueInt32, | ||||
|     FlipperStreamValueUint32, | ||||
|     FlipperStreamValueBool, | ||||
| } FlipperStreamValue; | ||||
| 
 | ||||
| typedef struct { | ||||
|  | ||||
							
								
								
									
										447
									
								
								lib/nfc_protocols/mifare_desfire.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								lib/nfc_protocols/mifare_desfire.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,447 @@ | ||||
| #include "mifare_desfire.h" | ||||
| #include <furi.h> | ||||
| #include <furi_hal_nfc.h> | ||||
| 
 | ||||
| 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; | ||||
| } | ||||
							
								
								
									
										164
									
								
								lib/nfc_protocols/mifare_desfire.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								lib/nfc_protocols/mifare_desfire.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,164 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <m-string.h> | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #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); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Kevin Wallace
						Kevin Wallace