diff --git a/applications/debug/unit_tests/bt/bt_test.c b/applications/debug/unit_tests/bt/bt_test.c new file mode 100644 index 000000000..2cbfd684a --- /dev/null +++ b/applications/debug/unit_tests/bt/bt_test.c @@ -0,0 +1,110 @@ +#include +#include +#include "../minunit.h" + +#include +#include + +#define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys") +#define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage + +typedef struct { + Storage* storage; + BtKeysStorage* bt_keys_storage; + uint8_t* nvm_ram_buff_dut; + uint8_t* nvm_ram_buff_ref; +} BtTest; + +BtTest* bt_test = NULL; + +void bt_test_alloc() { + bt_test = malloc(sizeof(BtTest)); + bt_test->storage = furi_record_open(RECORD_STORAGE); + bt_test->nvm_ram_buff_dut = malloc(BT_TEST_NVM_RAM_BUFF_SIZE); + bt_test->nvm_ram_buff_ref = malloc(BT_TEST_NVM_RAM_BUFF_SIZE); + bt_test->bt_keys_storage = bt_keys_storage_alloc(BT_TEST_KEY_STORAGE_FILE_PATH); + bt_keys_storage_set_ram_params( + bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, BT_TEST_NVM_RAM_BUFF_SIZE); +} + +void bt_test_free() { + furi_assert(bt_test); + free(bt_test->nvm_ram_buff_ref); + free(bt_test->nvm_ram_buff_dut); + bt_keys_storage_free(bt_test->bt_keys_storage); + furi_record_close(RECORD_STORAGE); + free(bt_test); + bt_test = NULL; +} + +static void bt_test_keys_storage_profile() { + // Emulate nvm change on initial connection + const int nvm_change_size_on_connection = 88; + for(size_t i = 0; i < nvm_change_size_on_connection; i++) { + bt_test->nvm_ram_buff_dut[i] = rand(); + bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i]; + } + // Emulate update storage on initial connect + mu_assert( + bt_keys_storage_update( + bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection), + "Failed to update key storage on initial connect"); + memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE); + mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM"); + mu_assert( + memcmp( + bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection) == + 0, + "Wrong buffer loaded"); + + const int nvm_disconnect_update_offset = 84; + const int nvm_disconnect_update_size = 324; + const int nvm_total_size = nvm_change_size_on_connection - + (nvm_change_size_on_connection - nvm_disconnect_update_offset) + + nvm_disconnect_update_size; + // Emulate update storage on initial disconnect + for(size_t i = nvm_disconnect_update_offset; + i < nvm_disconnect_update_offset + nvm_disconnect_update_size; + i++) { + bt_test->nvm_ram_buff_dut[i] = rand(); + bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i]; + } + mu_assert( + bt_keys_storage_update( + bt_test->bt_keys_storage, + &bt_test->nvm_ram_buff_dut[nvm_disconnect_update_offset], + nvm_disconnect_update_size), + "Failed to update key storage on initial disconnect"); + memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE); + mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM"); + mu_assert( + memcmp(bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_total_size) == 0, + "Wrong buffer loaded"); +} + +static void bt_test_keys_remove_test_file() { + mu_assert( + storage_simply_remove(bt_test->storage, BT_TEST_KEY_STORAGE_FILE_PATH), + "Can't remove test file"); +} + +MU_TEST(bt_test_keys_storage_serial_profile) { + furi_assert(bt_test); + + bt_test_keys_remove_test_file(); + bt_test_keys_storage_profile(); + bt_test_keys_remove_test_file(); +} + +MU_TEST_SUITE(test_bt) { + bt_test_alloc(); + + MU_RUN_TEST(bt_test_keys_storage_serial_profile); + + bt_test_free(); +} + +int run_minunit_test_bt() { + MU_RUN_SUITE(test_bt); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 65fa23c01..36c2b4aef 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -24,6 +24,7 @@ int run_minunit_test_protocol_dict(); int run_minunit_test_lfrfid_protocols(); int run_minunit_test_nfc(); int run_minunit_test_bit_lib(); +int run_minunit_test_bt(); typedef int (*UnitTestEntry)(); @@ -49,6 +50,7 @@ const UnitTest unit_tests[] = { {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, + {.name = "bt", .entry = run_minunit_test_bt}, }; void minunit_print_progress() { diff --git a/applications/plugins/hid_app/hid.c b/applications/plugins/hid_app/hid.c index d205d96eb..1d2235e08 100644 --- a/applications/plugins/hid_app/hid.c +++ b/applications/plugins/hid_app/hid.c @@ -367,9 +367,17 @@ int32_t hid_ble_app(void* p) { Hid* app = hid_alloc(HidTransportBle); app = hid_app_alloc_view(app); + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(app->bt, HID_BT_KEYS_STORAGE_PATH); + if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { - FURI_LOG_E(TAG, "Failed to switch profile"); + FURI_LOG_E(TAG, "Failed to switch to HID profile"); } + furi_hal_bt_start_advertising(); bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); @@ -378,7 +386,17 @@ int32_t hid_ble_app(void* p) { view_dispatcher_run(app->view_dispatcher); bt_set_status_changed_callback(app->bt, NULL, NULL); - bt_set_profile(app->bt, BtProfileSerial); + + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_default_path(app->bt); + + if(!bt_set_profile(app->bt, BtProfileSerial)) { + FURI_LOG_E(TAG, "Failed to switch to Serial profile"); + } hid_free(app); diff --git a/applications/plugins/hid_app/hid.h b/applications/plugins/hid_app/hid.h index 375a89706..fe32a199b 100644 --- a/applications/plugins/hid_app/hid.h +++ b/applications/plugins/hid_app/hid.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,8 @@ #include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" +#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys") + typedef enum { HidTransportUsb, HidTransportBle, diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 62b5ab109..024cb6e50 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -117,6 +117,8 @@ Bt* bt_alloc() { if(!bt_settings_load(&bt->bt_settings)) { bt_settings_save(&bt->bt_settings); } + // Keys storage + bt->keys_storage = bt_keys_storage_alloc(BT_KEYS_STORAGE_PATH); // Alloc queue bt->message_queue = furi_message_queue_alloc(8, sizeof(BtMessage)); @@ -285,8 +287,10 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) { furi_assert(context); Bt* bt = context; - FURI_LOG_I(TAG, "Changed addr start: %p, size changed: %d", addr, size); - BtMessage message = {.type = BtMessageTypeKeysStorageUpdated}; + BtMessage message = { + .type = BtMessageTypeKeysStorageUpdated, + .data.key_storage_data.start_address = addr, + .data.key_storage_data.size = size}; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } @@ -331,6 +335,8 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { furi_profile = FuriHalBtProfileSerial; } + bt_keys_storage_load(bt->keys_storage); + if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) { FURI_LOG_I(TAG, "Bt App started"); if(bt->bt_settings.enabled) { @@ -358,6 +364,7 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { static void bt_close_connection(Bt* bt) { bt_close_rpc_connection(bt); + furi_hal_bt_stop_advertising(); furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); } @@ -372,8 +379,8 @@ int32_t bt_srv(void* p) { return 0; } - // Read keys - if(!bt_keys_storage_load(bt)) { + // Load keys + if(!bt_keys_storage_load(bt->keys_storage)) { FURI_LOG_W(TAG, "Failed to load bonding keys"); } @@ -418,13 +425,16 @@ int32_t bt_srv(void* p) { // Display PIN code bt_pin_code_show(bt, message.data.pin_code); } else if(message.type == BtMessageTypeKeysStorageUpdated) { - bt_keys_storage_save(bt); + bt_keys_storage_update( + bt->keys_storage, + message.data.key_storage_data.start_address, + message.data.key_storage_data.size); } else if(message.type == BtMessageTypeSetProfile) { bt_change_profile(bt, &message); } else if(message.type == BtMessageTypeDisconnect) { bt_close_connection(bt); } else if(message.type == BtMessageTypeForgetBondedDevices) { - bt_keys_storage_delete(bt); + bt_keys_storage_delete(bt->keys_storage); } } return 0; diff --git a/applications/services/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h index 6e4e1b824..ca47936db 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -56,6 +56,19 @@ void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, vo */ void bt_forget_bonded_devices(Bt* bt); +/** Set keys storage file path + * + * @param bt Bt instance + * @param keys_storage_path Path to file with saved keys + */ +void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path); + +/** Set default keys storage file path + * + * @param bt Bt instance + */ +void bt_keys_storage_set_default_path(Bt* bt); + #ifdef __cplusplus } #endif diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index 3de896d5a..e3cf78cc7 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -39,3 +39,18 @@ void bt_forget_bonded_devices(Bt* bt) { furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } + +void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path) { + furi_assert(bt); + furi_assert(bt->keys_storage); + furi_assert(keys_storage_path); + + bt_keys_storage_set_file_path(bt->keys_storage, keys_storage_path); +} + +void bt_keys_storage_set_default_path(Bt* bt) { + furi_assert(bt); + furi_assert(bt->keys_storage); + + bt_keys_storage_set_file_path(bt->keys_storage, BT_KEYS_STORAGE_PATH); +} diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h index 5769243ec..c8a0e9965 100644 --- a/applications/services/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -13,8 +13,14 @@ #include #include #include +#include #include +#include + +#include "bt_keys_filename.h" + +#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME) #define BT_API_UNLOCK_EVENT (1UL << 0) @@ -29,10 +35,16 @@ typedef enum { BtMessageTypeForgetBondedDevices, } BtMessageType; +typedef struct { + uint8_t* start_address; + uint16_t size; +} BtKeyStorageUpdateData; + typedef union { uint32_t pin_code; uint8_t battery_level; BtProfile profile; + BtKeyStorageUpdateData key_storage_data; } BtMessageData; typedef struct { @@ -46,6 +58,7 @@ struct Bt { uint16_t bt_keys_size; uint16_t max_packet_size; BtSettings bt_settings; + BtKeysStorage* keys_storage; BtStatus status; BtProfile profile; FuriMessageQueue* message_queue; diff --git a/applications/services/bt/bt_service/bt_keys_storage.c b/applications/services/bt/bt_service/bt_keys_storage.c index 91d97d67e..7cff99944 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.c +++ b/applications/services/bt/bt_service/bt_keys_storage.c @@ -1,49 +1,24 @@ #include "bt_keys_storage.h" #include +#include #include #include -#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME) #define BT_KEYS_STORAGE_VERSION (0) #define BT_KEYS_STORAGE_MAGIC (0x18) -bool bt_keys_storage_load(Bt* bt) { - furi_assert(bt); - bool file_loaded = false; +#define TAG "BtKeyStorage" - furi_hal_bt_get_key_storage_buff(&bt->bt_keys_addr_start, &bt->bt_keys_size); - furi_hal_bt_nvm_sram_sem_acquire(); - file_loaded = saved_struct_load( - BT_KEYS_STORAGE_PATH, - bt->bt_keys_addr_start, - bt->bt_keys_size, - BT_KEYS_STORAGE_MAGIC, - BT_KEYS_STORAGE_VERSION); - furi_hal_bt_nvm_sram_sem_release(); +struct BtKeysStorage { + uint8_t* nvm_sram_buff; + uint16_t nvm_sram_buff_size; + FuriString* file_path; +}; - return file_loaded; -} +bool bt_keys_storage_delete(BtKeysStorage* instance) { + furi_assert(instance); -bool bt_keys_storage_save(Bt* bt) { - furi_assert(bt); - furi_assert(bt->bt_keys_addr_start); - bool file_saved = false; - - furi_hal_bt_nvm_sram_sem_acquire(); - file_saved = saved_struct_save( - BT_KEYS_STORAGE_PATH, - bt->bt_keys_addr_start, - bt->bt_keys_size, - BT_KEYS_STORAGE_MAGIC, - BT_KEYS_STORAGE_VERSION); - furi_hal_bt_nvm_sram_sem_release(); - - return file_saved; -} - -bool bt_keys_storage_delete(Bt* bt) { - furi_assert(bt); bool delete_succeed = false; bool bt_is_active = furi_hal_bt_is_active(); @@ -55,3 +30,117 @@ bool bt_keys_storage_delete(Bt* bt) { return delete_succeed; } + +BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path) { + furi_assert(keys_storage_path); + + BtKeysStorage* instance = malloc(sizeof(BtKeysStorage)); + // Set default nvm ram parameters + furi_hal_bt_get_key_storage_buff(&instance->nvm_sram_buff, &instance->nvm_sram_buff_size); + // Set key storage file + instance->file_path = furi_string_alloc(); + furi_string_set_str(instance->file_path, keys_storage_path); + + return instance; +} + +void bt_keys_storage_free(BtKeysStorage* instance) { + furi_assert(instance); + + furi_string_free(instance->file_path); + free(instance); +} + +void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path) { + furi_assert(instance); + furi_assert(path); + + furi_string_set_str(instance->file_path, path); +} + +void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size) { + furi_assert(instance); + furi_assert(buff); + + instance->nvm_sram_buff = buff; + instance->nvm_sram_buff_size = size; +} + +bool bt_keys_storage_load(BtKeysStorage* instance) { + furi_assert(instance); + + bool loaded = false; + do { + // Get payload size + size_t payload_size = 0; + if(!saved_struct_get_payload_size( + furi_string_get_cstr(instance->file_path), + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION, + &payload_size)) { + FURI_LOG_E(TAG, "Failed to read payload size"); + break; + } + + if(payload_size > instance->nvm_sram_buff_size) { + FURI_LOG_E(TAG, "Saved data doesn't fit ram buffer"); + break; + } + + // Load saved data to ram + furi_hal_bt_nvm_sram_sem_acquire(); + bool data_loaded = saved_struct_load( + furi_string_get_cstr(instance->file_path), + instance->nvm_sram_buff, + payload_size, + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION); + furi_hal_bt_nvm_sram_sem_release(); + if(!data_loaded) { + FURI_LOG_E(TAG, "Failed to load struct"); + break; + } + + loaded = true; + } while(false); + + return loaded; +} + +bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size) { + furi_assert(instance); + furi_assert(start_addr); + + bool updated = false; + + FURI_LOG_I( + TAG, + "Base address: %p. Start update address: %p. Size changed: %ld", + (void*)instance->nvm_sram_buff, + start_addr, + size); + + do { + size_t new_size = start_addr - instance->nvm_sram_buff + size; + if(new_size > instance->nvm_sram_buff_size) { + FURI_LOG_E(TAG, "NVM RAM buffer overflow"); + break; + } + + furi_hal_bt_nvm_sram_sem_acquire(); + bool data_updated = saved_struct_save( + furi_string_get_cstr(instance->file_path), + instance->nvm_sram_buff, + new_size, + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION); + furi_hal_bt_nvm_sram_sem_release(); + if(!data_updated) { + FURI_LOG_E(TAG, "Failed to update key storage"); + break; + } + updated = true; + } while(false); + + return updated; +} diff --git a/applications/services/bt/bt_service/bt_keys_storage.h b/applications/services/bt/bt_service/bt_keys_storage.h index b82b1035f..cb808ca30 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.h +++ b/applications/services/bt/bt_service/bt_keys_storage.h @@ -1,10 +1,20 @@ #pragma once -#include "bt_i.h" -#include "bt_keys_filename.h" +#include +#include -bool bt_keys_storage_load(Bt* bt); +typedef struct BtKeysStorage BtKeysStorage; -bool bt_keys_storage_save(Bt* bt); +BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path); -bool bt_keys_storage_delete(Bt* bt); +void bt_keys_storage_free(BtKeysStorage* instance); + +void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path); + +void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size); + +bool bt_keys_storage_load(BtKeysStorage* instance); + +bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size); + +bool bt_keys_storage_delete(BtKeysStorage* instance); diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index ada82685c..8503a838a 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.1,, +Version,+,11.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -562,6 +562,8 @@ Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* @@ -2321,6 +2323,7 @@ Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcApp Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t" Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*" Function,-,rpmatch,int,const char* +Function,+,saved_struct_get_payload_size,_Bool,"const char*, uint8_t, uint8_t, size_t*" Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" Function,-,scalbln,double,"double, long int" diff --git a/lib/toolbox/saved_struct.c b/lib/toolbox/saved_struct.c index 65b761f80..02b73f210 100644 --- a/lib/toolbox/saved_struct.c +++ b/lib/toolbox/saved_struct.c @@ -125,3 +125,54 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, return result; } + +bool saved_struct_get_payload_size( + const char* path, + uint8_t magic, + uint8_t version, + size_t* payload_size) { + furi_assert(path); + furi_assert(payload_size); + + SavedStructHeader header; + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + bool result = false; + do { + if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E( + TAG, "Failed to read \"%s\". Error: %s", path, storage_file_get_error_desc(file)); + break; + } + + uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); + if(bytes_count != sizeof(SavedStructHeader)) { + FURI_LOG_E(TAG, "Failed to read header"); + break; + } + + if((header.magic != magic) || (header.version != version)) { + FURI_LOG_E( + TAG, + "Magic(%d != %d) or Version(%d != %d) mismatch of file \"%s\"", + header.magic, + magic, + header.version, + version, + path); + break; + } + + uint64_t file_size = storage_file_size(file); + *payload_size = file_size - sizeof(SavedStructHeader); + + result = true; + } while(false); + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + return result; +} diff --git a/lib/toolbox/saved_struct.h b/lib/toolbox/saved_struct.h index aaa04eef5..9ce836564 100644 --- a/lib/toolbox/saved_struct.h +++ b/lib/toolbox/saved_struct.h @@ -12,6 +12,12 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, bool saved_struct_save(const char* path, void* data, size_t size, uint8_t magic, uint8_t version); +bool saved_struct_get_payload_size( + const char* path, + uint8_t magic, + uint8_t version, + size_t* payload_size); + #ifdef __cplusplus } #endif