 890c9e87ce
			
		
	
	
		890c9e87ce
		
			
		
	
	
	
	
		
			
			* examples: plugins: utilize fal_embedded * libs: removed fnv1a_hash * furi: added FURI_PACKED; apps, libs: changed to use FURI_PACKED * lib: mbedtls: using custom config * lib: toolbox: removed md5, switched to mbedtls * targets: f18: link fix * lib: added mbedtls_cfg.h * apps: nfc: explicit dependency on libmbedtls * u2f: reworking to mbedtls * u2f: replaced sha256 & hmac with mbedtls * u2f: functional rework using mbedtls * libs: dropped micro-ecc * u2f: dropped old implementation * toolbox: removed sha256 impl * mcheck() for mbedtls * libs: removed libmisc; split into smaller libs * apps: debug: fixed display_test * apps: include cleanups * fbt: fixed VERSIONCOMSTR * furi: added FURI_CHECK_RETURN * lib: removed qrcode * cleanup * fbt: lint_py+format_py: fixed excessive command length * api: Removed bzero from f7 * api: Removed bzero from f18 * Bump API Symbols Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
		
			
				
	
	
		
			791 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			791 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "mf_desfire_i.h"
 | |
| 
 | |
| #define BITS_IN_BYTE (8U)
 | |
| 
 | |
| #define MF_DESFIRE_FFF_VERSION_KEY \
 | |
|     MF_DESFIRE_FFF_PICC_PREFIX " " \
 | |
|                                "Version"
 | |
| #define MF_DESFIRE_FFF_FREE_MEM_KEY \
 | |
|     MF_DESFIRE_FFF_PICC_PREFIX " "  \
 | |
|                                "Free Memory"
 | |
| 
 | |
| #define MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY "Change Key ID"
 | |
| #define MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY "Config Changeable"
 | |
| #define MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY "Free Create Delete"
 | |
| #define MF_DESFIRE_FFF_FREE_DIR_LIST_KEY "Free Directory List"
 | |
| #define MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY "Key Changeable"
 | |
| #define MF_DESFIRE_FFF_FLAGS_KEY "Flags"
 | |
| #define MF_DESFIRE_FFF_MAX_KEYS_KEY "Max Keys"
 | |
| 
 | |
| #define MF_DESFIRE_FFF_KEY_SUB_PREFIX "Key"
 | |
| #define MF_DESFIRE_FFF_KEY_VERSION_KEY "Version"
 | |
| 
 | |
| #define MF_DESFIRE_FFF_APPLICATION_COUNT_KEY \
 | |
|     MF_DESFIRE_FFF_APP_PREFIX " "            \
 | |
|                               "Count"
 | |
| #define MF_DESFIRE_FFF_APPLICATION_IDS_KEY \
 | |
|     MF_DESFIRE_FFF_APP_PREFIX " "          \
 | |
|                               "IDs"
 | |
| 
 | |
| #define MF_DESFIRE_FFF_FILE_SUB_PREFIX "File"
 | |
| #define MF_DESFIRE_FFF_FILE_IDS_KEY    \
 | |
|     MF_DESFIRE_FFF_FILE_SUB_PREFIX " " \
 | |
|                                    "IDs"
 | |
| #define MF_DESFIRE_FFF_FILE_TYPE_KEY "Type"
 | |
| #define MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY "Communication Settings"
 | |
| #define MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY "Access Rights"
 | |
| 
 | |
| #define MF_DESFIRE_FFF_FILE_SIZE_KEY "Size"
 | |
| 
 | |
| #define MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY "Hi Limit"
 | |
| #define MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY "Lo Limit"
 | |
| #define MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY "Limited Credit Value"
 | |
| #define MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY "Limited Credit Enabled"
 | |
| 
 | |
| #define MF_DESFIRE_FFF_FILE_MAX_KEY "Max"
 | |
| #define MF_DESFIRE_FFF_FILE_CUR_KEY "Cur"
 | |
| 
 | |
| bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) {
 | |
|     const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireVersion);
 | |
| 
 | |
|     if(can_parse) {
 | |
|         bit_buffer_write_bytes(buf, data, sizeof(MfDesfireVersion));
 | |
|     }
 | |
| 
 | |
|     return can_parse;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) {
 | |
|     typedef struct FURI_PACKED {
 | |
|         uint32_t bytes_free : 3 * BITS_IN_BYTE;
 | |
|     } MfDesfireFreeMemoryLayout;
 | |
| 
 | |
|     const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireFreeMemoryLayout);
 | |
| 
 | |
|     if(can_parse) {
 | |
|         MfDesfireFreeMemoryLayout layout;
 | |
|         bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFreeMemoryLayout));
 | |
|         data->bytes_free = layout.bytes_free;
 | |
|     }
 | |
| 
 | |
|     data->is_present = can_parse;
 | |
| 
 | |
|     return can_parse;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) {
 | |
|     typedef struct FURI_PACKED {
 | |
|         bool is_master_key_changeable : 1;
 | |
|         bool is_free_directory_list : 1;
 | |
|         bool is_free_create_delete : 1;
 | |
|         bool is_config_changeable : 1;
 | |
|         uint8_t change_key_id : 4;
 | |
|         uint8_t max_keys : 4;
 | |
|         uint8_t flags : 4;
 | |
|     } MfDesfireKeySettingsLayout;
 | |
| 
 | |
|     const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout);
 | |
| 
 | |
|     if(can_parse) {
 | |
|         MfDesfireKeySettingsLayout layout;
 | |
|         bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireKeySettingsLayout));
 | |
| 
 | |
|         data->is_master_key_changeable = layout.is_master_key_changeable;
 | |
|         data->is_free_directory_list = layout.is_free_directory_list;
 | |
|         data->is_free_create_delete = layout.is_free_create_delete;
 | |
|         data->is_config_changeable = layout.is_config_changeable;
 | |
| 
 | |
|         data->change_key_id = layout.change_key_id;
 | |
|         data->max_keys = layout.max_keys;
 | |
|         data->flags = layout.flags;
 | |
|     }
 | |
| 
 | |
|     return can_parse;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf) {
 | |
|     const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion);
 | |
| 
 | |
|     if(can_parse) {
 | |
|         bit_buffer_write_bytes(buf, data, sizeof(MfDesfireKeyVersion));
 | |
|     }
 | |
| 
 | |
|     return can_parse;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_application_id_parse(
 | |
|     MfDesfireApplicationId* data,
 | |
|     uint32_t index,
 | |
|     const BitBuffer* buf) {
 | |
|     const bool can_parse =
 | |
|         bit_buffer_get_size_bytes(buf) >=
 | |
|         (index * sizeof(MfDesfireApplicationId) + sizeof(MfDesfireApplicationId));
 | |
| 
 | |
|     if(can_parse) {
 | |
|         bit_buffer_write_bytes_mid(
 | |
|             buf, data, index * sizeof(MfDesfireApplicationId), sizeof(MfDesfireApplicationId));
 | |
|     }
 | |
| 
 | |
|     return can_parse;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_id_parse(MfDesfireFileId* data, uint32_t index, const BitBuffer* buf) {
 | |
|     const bool can_parse = bit_buffer_get_size_bytes(buf) >=
 | |
|                            (index * sizeof(MfDesfireFileId) + sizeof(MfDesfireFileId));
 | |
|     if(can_parse) {
 | |
|         bit_buffer_write_bytes_mid(
 | |
|             buf, data, index * sizeof(MfDesfireFileId), sizeof(MfDesfireFileId));
 | |
|     }
 | |
| 
 | |
|     return can_parse;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer* buf) {
 | |
|     bool parsed = false;
 | |
| 
 | |
|     typedef struct FURI_PACKED {
 | |
|         uint8_t type;
 | |
|         uint8_t comm;
 | |
|         uint16_t access_rights;
 | |
|     } MfDesfireFileSettingsHeader;
 | |
| 
 | |
|     typedef struct FURI_PACKED {
 | |
|         uint32_t size : 3 * BITS_IN_BYTE;
 | |
|     } MfDesfireFileSettingsData;
 | |
| 
 | |
|     typedef struct FURI_PACKED {
 | |
|         uint32_t lo_limit;
 | |
|         uint32_t hi_limit;
 | |
|         uint32_t limited_credit_value;
 | |
|         uint8_t limited_credit_enabled;
 | |
|     } MfDesfireFileSettingsValue;
 | |
| 
 | |
|     typedef struct FURI_PACKED {
 | |
|         uint32_t size : 3 * BITS_IN_BYTE;
 | |
|         uint32_t max : 3 * BITS_IN_BYTE;
 | |
|         uint32_t cur : 3 * BITS_IN_BYTE;
 | |
|     } MfDesfireFileSettingsRecord;
 | |
| 
 | |
|     typedef struct FURI_PACKED {
 | |
|         MfDesfireFileSettingsHeader header;
 | |
|         union {
 | |
|             MfDesfireFileSettingsData data;
 | |
|             MfDesfireFileSettingsValue value;
 | |
|             MfDesfireFileSettingsRecord record;
 | |
|         };
 | |
|     } MfDesfireFileSettingsLayout;
 | |
| 
 | |
|     do {
 | |
|         const size_t data_size = bit_buffer_get_size_bytes(buf);
 | |
|         const size_t min_data_size =
 | |
|             sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData);
 | |
| 
 | |
|         if(data_size < min_data_size) break;
 | |
| 
 | |
|         MfDesfireFileSettingsLayout layout;
 | |
|         bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout));
 | |
| 
 | |
|         data->type = layout.header.type;
 | |
|         data->comm = layout.header.comm;
 | |
|         data->access_rights = layout.header.access_rights;
 | |
| 
 | |
|         if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
 | |
|             if(data_size != min_data_size) break;
 | |
| 
 | |
|             data->data.size = layout.data.size;
 | |
| 
 | |
|         } else if(data->type == MfDesfireFileTypeValue) {
 | |
|             if(data_size !=
 | |
|                sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue))
 | |
|                 break;
 | |
| 
 | |
|             data->value.lo_limit = layout.value.lo_limit;
 | |
|             data->value.hi_limit = layout.value.hi_limit;
 | |
|             data->value.limited_credit_value = layout.value.limited_credit_value;
 | |
|             data->value.limited_credit_enabled = layout.value.limited_credit_enabled;
 | |
| 
 | |
|         } else if(
 | |
|             data->type == MfDesfireFileTypeLinearRecord ||
 | |
|             data->type == MfDesfireFileTypeCyclicRecord) {
 | |
|             if(data_size !=
 | |
|                sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord))
 | |
|                 break;
 | |
| 
 | |
|             data->record.size = layout.record.size;
 | |
|             data->record.max = layout.record.max;
 | |
|             data->record.cur = layout.record.cur;
 | |
| 
 | |
|         } else {
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         parsed = true;
 | |
|     } while(false);
 | |
| 
 | |
|     return parsed;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_data_parse(MfDesfireFileData* data, const BitBuffer* buf) {
 | |
|     const size_t data_size = bit_buffer_get_size_bytes(buf);
 | |
| 
 | |
|     if(data_size > 0) {
 | |
|         simple_array_init(data->data, data_size);
 | |
|         bit_buffer_write_bytes(buf, simple_array_get_data(data->data), data_size);
 | |
|     }
 | |
| 
 | |
|     // Success no matter whether there is data or not
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void mf_desfire_file_data_init(MfDesfireFileData* data) {
 | |
|     data->data = simple_array_alloc(&simple_array_config_uint8_t);
 | |
| }
 | |
| 
 | |
| void mf_desfire_application_init(MfDesfireApplication* data) {
 | |
|     data->key_versions = simple_array_alloc(&mf_desfire_key_version_array_config);
 | |
|     data->file_ids = simple_array_alloc(&mf_desfire_file_id_array_config);
 | |
|     data->file_settings = simple_array_alloc(&mf_desfire_file_settings_array_config);
 | |
|     data->file_data = simple_array_alloc(&mf_desfire_file_data_array_config);
 | |
| }
 | |
| 
 | |
| void mf_desfire_file_data_reset(MfDesfireFileData* data) {
 | |
|     simple_array_free(data->data);
 | |
|     memset(data, 0, sizeof(MfDesfireFileData));
 | |
| }
 | |
| 
 | |
| void mf_desfire_application_reset(MfDesfireApplication* data) {
 | |
|     simple_array_free(data->key_versions);
 | |
|     simple_array_free(data->file_ids);
 | |
|     simple_array_free(data->file_settings);
 | |
|     simple_array_free(data->file_data);
 | |
|     memset(data, 0, sizeof(MfDesfireApplication));
 | |
| }
 | |
| 
 | |
| void mf_desfire_file_data_copy(MfDesfireFileData* data, const MfDesfireFileData* other) {
 | |
|     simple_array_copy(data->data, other->data);
 | |
| }
 | |
| 
 | |
| void mf_desfire_application_copy(MfDesfireApplication* data, const MfDesfireApplication* other) {
 | |
|     data->key_settings = other->key_settings;
 | |
|     simple_array_copy(data->key_versions, other->key_versions);
 | |
|     simple_array_copy(data->file_ids, other->file_ids);
 | |
|     simple_array_copy(data->file_settings, other->file_settings);
 | |
|     simple_array_copy(data->file_data, other->file_data);
 | |
| }
 | |
| 
 | |
| bool mf_desfire_version_load(MfDesfireVersion* data, FlipperFormat* ff) {
 | |
|     return flipper_format_read_hex(
 | |
|         ff, MF_DESFIRE_FFF_VERSION_KEY, (uint8_t*)data, sizeof(MfDesfireVersion));
 | |
| }
 | |
| 
 | |
| bool mf_desfire_free_memory_load(MfDesfireFreeMemory* data, FlipperFormat* ff) {
 | |
|     data->is_present = flipper_format_key_exist(ff, MF_DESFIRE_FFF_FREE_MEM_KEY);
 | |
|     return data->is_present ?
 | |
|                flipper_format_read_uint32(ff, MF_DESFIRE_FFF_FREE_MEM_KEY, &data->bytes_free, 1) :
 | |
|                true;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_key_settings_load(
 | |
|     MfDesfireKeySettings* data,
 | |
|     const char* prefix,
 | |
|     FlipperFormat* ff) {
 | |
|     bool success = false;
 | |
| 
 | |
|     FuriString* key = furi_string_alloc();
 | |
| 
 | |
|     do {
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY);
 | |
|         if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->change_key_id, 1)) break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY);
 | |
|         if(!flipper_format_read_bool(ff, furi_string_get_cstr(key), &data->is_config_changeable, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY);
 | |
|         if(!flipper_format_read_bool(
 | |
|                ff, furi_string_get_cstr(key), &data->is_free_create_delete, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_DIR_LIST_KEY);
 | |
|         if(!flipper_format_read_bool(
 | |
|                ff, furi_string_get_cstr(key), &data->is_free_directory_list, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY);
 | |
|         if(!flipper_format_read_bool(
 | |
|                ff, furi_string_get_cstr(key), &data->is_master_key_changeable, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FLAGS_KEY);
 | |
|         if(flipper_format_key_exist(ff, furi_string_get_cstr(key))) {
 | |
|             if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->flags, 1)) break;
 | |
|         }
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_MAX_KEYS_KEY);
 | |
|         if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->max_keys, 1)) break;
 | |
| 
 | |
|         success = true;
 | |
|     } while(false);
 | |
| 
 | |
|     furi_string_free(key);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_key_version_load(
 | |
|     MfDesfireKeyVersion* data,
 | |
|     const char* prefix,
 | |
|     uint32_t index,
 | |
|     FlipperFormat* ff) {
 | |
|     FuriString* key = furi_string_alloc_printf(
 | |
|         "%s %s %lu %s",
 | |
|         prefix,
 | |
|         MF_DESFIRE_FFF_KEY_SUB_PREFIX,
 | |
|         index,
 | |
|         MF_DESFIRE_FFF_KEY_VERSION_KEY);
 | |
|     const bool success = flipper_format_read_hex(ff, furi_string_get_cstr(key), data, 1);
 | |
|     furi_string_free(key);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_count_load(uint32_t* data, const char* prefix, FlipperFormat* ff) {
 | |
|     FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY);
 | |
|     const bool success = flipper_format_get_value_count(ff, furi_string_get_cstr(key), data);
 | |
|     furi_string_free(key);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_ids_load(
 | |
|     MfDesfireFileId* data,
 | |
|     uint32_t count,
 | |
|     const char* prefix,
 | |
|     FlipperFormat* ff) {
 | |
|     FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY);
 | |
|     const bool success = flipper_format_read_hex(ff, furi_string_get_cstr(key), data, count);
 | |
|     furi_string_free(key);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_settings_load(
 | |
|     MfDesfireFileSettings* data,
 | |
|     const char* prefix,
 | |
|     FlipperFormat* ff) {
 | |
|     bool success = false;
 | |
| 
 | |
|     FuriString* key = furi_string_alloc();
 | |
| 
 | |
|     do {
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_TYPE_KEY);
 | |
|         if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), (uint8_t*)&data->type, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY);
 | |
|         if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), (uint8_t*)&data->comm, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY);
 | |
|         if(!flipper_format_read_hex(
 | |
|                ff,
 | |
|                furi_string_get_cstr(key),
 | |
|                (uint8_t*)&data->access_rights,
 | |
|                sizeof(MfDesfireFileAccessRights)))
 | |
|             break;
 | |
| 
 | |
|         if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY);
 | |
|             if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1))
 | |
|                 break;
 | |
| 
 | |
|         } else if(data->type == MfDesfireFileTypeValue) {
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY);
 | |
|             if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.hi_limit, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY);
 | |
|             if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.lo_limit, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY);
 | |
|             if(!flipper_format_read_uint32(
 | |
|                    ff, furi_string_get_cstr(key), &data->value.limited_credit_value, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY);
 | |
|             if(!flipper_format_read_bool(
 | |
|                    ff, furi_string_get_cstr(key), &data->value.limited_credit_enabled, 1))
 | |
|                 break;
 | |
|         } else if(
 | |
|             data->type == MfDesfireFileTypeLinearRecord ||
 | |
|             data->type == MfDesfireFileTypeCyclicRecord) {
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY);
 | |
|             if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.size, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_MAX_KEY);
 | |
|             if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.max, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY);
 | |
|             if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1))
 | |
|                 break;
 | |
|         }
 | |
| 
 | |
|         success = true;
 | |
|     } while(false);
 | |
| 
 | |
|     furi_string_free(key);
 | |
| 
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_data_load(MfDesfireFileData* data, const char* prefix, FlipperFormat* ff) {
 | |
|     bool success = false;
 | |
|     do {
 | |
|         if(!flipper_format_key_exist(ff, prefix)) {
 | |
|             success = true;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         uint32_t data_size;
 | |
|         if(!flipper_format_get_value_count(ff, prefix, &data_size)) break;
 | |
| 
 | |
|         simple_array_init(data->data, data_size);
 | |
| 
 | |
|         if(!flipper_format_read_hex(ff, prefix, simple_array_get_data(data->data), data_size))
 | |
|             break;
 | |
| 
 | |
|         success = true;
 | |
|     } while(false);
 | |
| 
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_application_count_load(uint32_t* data, FlipperFormat* ff) {
 | |
|     return flipper_format_read_uint32(ff, MF_DESFIRE_FFF_APPLICATION_COUNT_KEY, data, 1);
 | |
| }
 | |
| 
 | |
| bool mf_desfire_application_ids_load(
 | |
|     MfDesfireApplicationId* data,
 | |
|     uint32_t count,
 | |
|     FlipperFormat* ff) {
 | |
|     return flipper_format_read_hex(
 | |
|         ff, MF_DESFIRE_FFF_APPLICATION_IDS_KEY, data->data, count * sizeof(MfDesfireApplicationId));
 | |
| }
 | |
| 
 | |
| bool mf_desfire_application_load(MfDesfireApplication* data, const char* prefix, FlipperFormat* ff) {
 | |
|     FuriString* sub_prefix = furi_string_alloc();
 | |
|     bool success = false;
 | |
| 
 | |
|     do {
 | |
|         if(!mf_desfire_key_settings_load(&data->key_settings, prefix, ff)) break;
 | |
| 
 | |
|         const uint32_t key_version_count = data->key_settings.max_keys;
 | |
|         simple_array_init(data->key_versions, key_version_count);
 | |
| 
 | |
|         uint32_t i;
 | |
|         for(i = 0; i < key_version_count; ++i) {
 | |
|             if(!mf_desfire_key_version_load(simple_array_get(data->key_versions, i), prefix, i, ff))
 | |
|                 break;
 | |
|         }
 | |
| 
 | |
|         if(i != key_version_count) break;
 | |
| 
 | |
|         uint32_t file_count;
 | |
|         if(!mf_desfire_file_count_load(&file_count, prefix, ff)) break;
 | |
| 
 | |
|         simple_array_init(data->file_ids, file_count);
 | |
|         if(!mf_desfire_file_ids_load(simple_array_get_data(data->file_ids), file_count, prefix, ff))
 | |
|             break;
 | |
| 
 | |
|         simple_array_init(data->file_settings, file_count);
 | |
|         simple_array_init(data->file_data, file_count);
 | |
| 
 | |
|         for(i = 0; i < file_count; ++i) {
 | |
|             const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i);
 | |
|             furi_string_printf(
 | |
|                 sub_prefix, "%s %s %u", prefix, MF_DESFIRE_FFF_FILE_SUB_PREFIX, *file_id);
 | |
| 
 | |
|             MfDesfireFileSettings* file_settings = simple_array_get(data->file_settings, i);
 | |
|             if(!mf_desfire_file_settings_load(file_settings, furi_string_get_cstr(sub_prefix), ff))
 | |
|                 break;
 | |
| 
 | |
|             MfDesfireFileData* file_data = simple_array_get(data->file_data, i);
 | |
|             if(!mf_desfire_file_data_load(file_data, furi_string_get_cstr(sub_prefix), ff)) break;
 | |
|         }
 | |
| 
 | |
|         if(i != file_count) break;
 | |
| 
 | |
|         success = true;
 | |
|     } while(false);
 | |
| 
 | |
|     furi_string_free(sub_prefix);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_version_save(const MfDesfireVersion* data, FlipperFormat* ff) {
 | |
|     return flipper_format_write_hex(
 | |
|         ff, MF_DESFIRE_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(MfDesfireVersion));
 | |
| }
 | |
| 
 | |
| bool mf_desfire_free_memory_save(const MfDesfireFreeMemory* data, FlipperFormat* ff) {
 | |
|     return data->is_present ?
 | |
|                flipper_format_write_uint32(ff, MF_DESFIRE_FFF_FREE_MEM_KEY, &data->bytes_free, 1) :
 | |
|                true;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_key_settings_save(
 | |
|     const MfDesfireKeySettings* data,
 | |
|     const char* prefix,
 | |
|     FlipperFormat* ff) {
 | |
|     bool success = false;
 | |
| 
 | |
|     FuriString* key = furi_string_alloc();
 | |
| 
 | |
|     do {
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY);
 | |
|         if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->change_key_id, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY);
 | |
|         if(!flipper_format_write_bool(
 | |
|                ff, furi_string_get_cstr(key), &data->is_config_changeable, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY);
 | |
|         if(!flipper_format_write_bool(
 | |
|                ff, furi_string_get_cstr(key), &data->is_free_create_delete, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_DIR_LIST_KEY);
 | |
|         if(!flipper_format_write_bool(
 | |
|                ff, furi_string_get_cstr(key), &data->is_free_directory_list, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY);
 | |
|         if(!flipper_format_write_bool(
 | |
|                ff, furi_string_get_cstr(key), &data->is_master_key_changeable, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FLAGS_KEY);
 | |
|         if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->flags, 1)) break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_MAX_KEYS_KEY);
 | |
|         if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->max_keys, 1)) break;
 | |
| 
 | |
|         success = true;
 | |
|     } while(false);
 | |
| 
 | |
|     furi_string_free(key);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_key_version_save(
 | |
|     const MfDesfireKeyVersion* data,
 | |
|     const char* prefix,
 | |
|     uint32_t index,
 | |
|     FlipperFormat* ff) {
 | |
|     FuriString* key = furi_string_alloc_printf(
 | |
|         "%s %s %lu %s",
 | |
|         prefix,
 | |
|         MF_DESFIRE_FFF_KEY_SUB_PREFIX,
 | |
|         index,
 | |
|         MF_DESFIRE_FFF_KEY_VERSION_KEY);
 | |
|     const bool success = flipper_format_write_hex(ff, furi_string_get_cstr(key), data, 1);
 | |
|     furi_string_free(key);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_ids_save(
 | |
|     const MfDesfireFileId* data,
 | |
|     uint32_t count,
 | |
|     const char* prefix,
 | |
|     FlipperFormat* ff) {
 | |
|     FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY);
 | |
|     const bool success = flipper_format_write_hex(ff, furi_string_get_cstr(key), data, count);
 | |
|     furi_string_free(key);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_settings_save(
 | |
|     const MfDesfireFileSettings* data,
 | |
|     const char* prefix,
 | |
|     FlipperFormat* ff) {
 | |
|     bool success = false;
 | |
| 
 | |
|     FuriString* key = furi_string_alloc();
 | |
| 
 | |
|     do {
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_TYPE_KEY);
 | |
|         if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), (const uint8_t*)&data->type, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY);
 | |
|         if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), (const uint8_t*)&data->comm, 1))
 | |
|             break;
 | |
| 
 | |
|         furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY);
 | |
|         if(!flipper_format_write_hex(
 | |
|                ff,
 | |
|                furi_string_get_cstr(key),
 | |
|                (const uint8_t*)&data->access_rights,
 | |
|                sizeof(MfDesfireFileAccessRights)))
 | |
|             break;
 | |
| 
 | |
|         if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY);
 | |
|             if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1))
 | |
|                 break;
 | |
| 
 | |
|         } else if(data->type == MfDesfireFileTypeValue) {
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY);
 | |
|             if(!flipper_format_write_uint32(
 | |
|                    ff, furi_string_get_cstr(key), &data->value.hi_limit, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY);
 | |
|             if(!flipper_format_write_uint32(
 | |
|                    ff, furi_string_get_cstr(key), &data->value.lo_limit, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY);
 | |
|             if(!flipper_format_write_uint32(
 | |
|                    ff, furi_string_get_cstr(key), &data->value.limited_credit_value, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY);
 | |
|             if(!flipper_format_write_bool(
 | |
|                    ff, furi_string_get_cstr(key), &data->value.limited_credit_enabled, 1))
 | |
|                 break;
 | |
|         } else if(
 | |
|             data->type == MfDesfireFileTypeLinearRecord ||
 | |
|             data->type == MfDesfireFileTypeCyclicRecord) {
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY);
 | |
|             if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.size, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_MAX_KEY);
 | |
|             if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.max, 1))
 | |
|                 break;
 | |
| 
 | |
|             furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY);
 | |
|             if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1))
 | |
|                 break;
 | |
|         }
 | |
| 
 | |
|         success = true;
 | |
|     } while(false);
 | |
| 
 | |
|     furi_string_free(key);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_file_data_save(
 | |
|     const MfDesfireFileData* data,
 | |
|     const char* prefix,
 | |
|     FlipperFormat* ff) {
 | |
|     const uint32_t data_size = simple_array_get_count(data->data);
 | |
|     return data_size > 0 ? flipper_format_write_hex(
 | |
|                                ff, prefix, simple_array_cget_data(data->data), data_size) :
 | |
|                            true;
 | |
| }
 | |
| 
 | |
| bool mf_desfire_application_count_save(const uint32_t* data, FlipperFormat* ff) {
 | |
|     return flipper_format_write_uint32(ff, MF_DESFIRE_FFF_APPLICATION_COUNT_KEY, data, 1);
 | |
| }
 | |
| 
 | |
| bool mf_desfire_application_ids_save(
 | |
|     const MfDesfireApplicationId* data,
 | |
|     uint32_t count,
 | |
|     FlipperFormat* ff) {
 | |
|     return flipper_format_write_hex(
 | |
|         ff, MF_DESFIRE_FFF_APPLICATION_IDS_KEY, data->data, count * sizeof(MfDesfireApplicationId));
 | |
| }
 | |
| 
 | |
| bool mf_desfire_application_save(
 | |
|     const MfDesfireApplication* data,
 | |
|     const char* prefix,
 | |
|     FlipperFormat* ff) {
 | |
|     FuriString* sub_prefix = furi_string_alloc();
 | |
|     bool success = false;
 | |
| 
 | |
|     do {
 | |
|         if(!mf_desfire_key_settings_save(&data->key_settings, prefix, ff)) break;
 | |
| 
 | |
|         const uint32_t key_version_count = data->key_settings.max_keys;
 | |
| 
 | |
|         uint32_t i;
 | |
|         for(i = 0; i < key_version_count; ++i) {
 | |
|             if(!mf_desfire_key_version_save(
 | |
|                    simple_array_cget(data->key_versions, i), prefix, i, ff))
 | |
|                 break;
 | |
|         }
 | |
| 
 | |
|         if(i != key_version_count) break;
 | |
| 
 | |
|         const uint32_t file_count = simple_array_get_count(data->file_ids);
 | |
|         if(!mf_desfire_file_ids_save(simple_array_get_data(data->file_ids), file_count, prefix, ff))
 | |
|             break;
 | |
| 
 | |
|         for(i = 0; i < file_count; ++i) {
 | |
|             const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i);
 | |
|             furi_string_printf(
 | |
|                 sub_prefix, "%s %s %u", prefix, MF_DESFIRE_FFF_FILE_SUB_PREFIX, *file_id);
 | |
| 
 | |
|             const MfDesfireFileSettings* file_settings = simple_array_cget(data->file_settings, i);
 | |
|             if(!mf_desfire_file_settings_save(file_settings, furi_string_get_cstr(sub_prefix), ff))
 | |
|                 break;
 | |
| 
 | |
|             const MfDesfireFileData* file_data = simple_array_cget(data->file_data, i);
 | |
|             if(!mf_desfire_file_data_save(file_data, furi_string_get_cstr(sub_prefix), ff)) break;
 | |
|         }
 | |
| 
 | |
|         if(i != file_count) break;
 | |
| 
 | |
|         success = true;
 | |
|     } while(false);
 | |
| 
 | |
|     furi_string_free(sub_prefix);
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| const SimpleArrayConfig mf_desfire_key_version_array_config = {
 | |
|     .init = NULL,
 | |
|     .copy = NULL,
 | |
|     .reset = NULL,
 | |
|     .type_size = sizeof(MfDesfireKeyVersion),
 | |
| };
 | |
| 
 | |
| const SimpleArrayConfig mf_desfire_app_id_array_config = {
 | |
|     .init = NULL,
 | |
|     .copy = NULL,
 | |
|     .reset = NULL,
 | |
|     .type_size = sizeof(MfDesfireApplicationId),
 | |
| };
 | |
| 
 | |
| const SimpleArrayConfig mf_desfire_file_id_array_config = {
 | |
|     .init = NULL,
 | |
|     .copy = NULL,
 | |
|     .reset = NULL,
 | |
|     .type_size = sizeof(MfDesfireFileId),
 | |
| };
 | |
| 
 | |
| const SimpleArrayConfig mf_desfire_file_settings_array_config = {
 | |
|     .init = NULL,
 | |
|     .copy = NULL,
 | |
|     .reset = NULL,
 | |
|     .type_size = sizeof(MfDesfireFileSettings),
 | |
| };
 | |
| 
 | |
| const SimpleArrayConfig mf_desfire_file_data_array_config = {
 | |
|     .init = (SimpleArrayInit)mf_desfire_file_data_init,
 | |
|     .copy = (SimpleArrayCopy)mf_desfire_file_data_copy,
 | |
|     .reset = (SimpleArrayReset)mf_desfire_file_data_reset,
 | |
|     .type_size = sizeof(MfDesfireData),
 | |
| };
 | |
| 
 | |
| const SimpleArrayConfig mf_desfire_application_array_config = {
 | |
|     .init = (SimpleArrayInit)mf_desfire_application_init,
 | |
|     .copy = (SimpleArrayCopy)mf_desfire_application_copy,
 | |
|     .reset = (SimpleArrayReset)mf_desfire_application_reset,
 | |
|     .type_size = sizeof(MfDesfireApplication),
 | |
| };
 |