* 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),
 | 
						|
};
 |