From 7f94ef31796712885368ccff9e73619b3de69093 Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 4 Nov 2021 20:06:13 +1000 Subject: [PATCH] [FL-1926] Flipper File Format addons (#753) * Flipper file format: remove C wrapper * Flipper file format: open append, float, uint32_t as array, delete key, value count * Flipper file format: fix scratchpad location * Flipper file format: add EOL on append * SubGHZ keystore: update encryption type read and write * Flipper File Format: enhanced version * Flipper File Format: fix naming * Flipper File Format: fix "open" subset naming * Flipper File Format: tests * Flipper File Format: file helper naming * SubGHZ keystore: merge with current state of flipper file format * Flipper File Format: update make recipe * Flipper File Format: open new file method --- applications/ibutton/ibutton-app.cpp | 37 +- applications/lfrfid/lfrfid-app.cpp | 39 +- .../tests/flipper_file/flipper_file_test.c | 500 ++++++++++++++++++ applications/tests/test_index.c | 15 +- lib/flipper_file/file_helper.c | 204 +++++++ lib/flipper_file/file_helper.h | 81 +++ lib/flipper_file/flipper_file.c | 395 ++++++++++++++ .../flipper_file.h} | 246 +++++++-- lib/flipper_file/flipper_file_float.c | 42 ++ lib/flipper_file/flipper_file_helper.c | 118 +++++ lib/flipper_file/flipper_file_helper.h | 51 ++ lib/flipper_file/flipper_file_hex.c | 42 ++ lib/flipper_file/flipper_file_i.h | 72 +++ lib/flipper_file/flipper_file_int32.c | 42 ++ lib/flipper_file/flipper_file_string.c | 67 +++ lib/flipper_file/flipper_file_uint32.c | 42 ++ lib/lib.mk | 4 + lib/subghz/subghz_keystore.c | 34 +- lib/toolbox/flipper-file-cpp.cpp | 72 --- lib/toolbox/flipper-file-cpp.h | 41 -- lib/toolbox/flipper-file.c | 471 ----------------- lib/toolbox/hex.c | 12 + lib/toolbox/hex.h | 12 +- 23 files changed, 1969 insertions(+), 670 deletions(-) create mode 100644 applications/tests/flipper_file/flipper_file_test.c create mode 100644 lib/flipper_file/file_helper.c create mode 100644 lib/flipper_file/file_helper.h create mode 100644 lib/flipper_file/flipper_file.c rename lib/{toolbox/flipper-file.h => flipper_file/flipper_file.h} (50%) create mode 100644 lib/flipper_file/flipper_file_float.c create mode 100644 lib/flipper_file/flipper_file_helper.c create mode 100644 lib/flipper_file/flipper_file_helper.h create mode 100644 lib/flipper_file/flipper_file_hex.c create mode 100644 lib/flipper_file/flipper_file_i.h create mode 100644 lib/flipper_file/flipper_file_int32.c create mode 100644 lib/flipper_file/flipper_file_string.c create mode 100644 lib/flipper_file/flipper_file_uint32.c delete mode 100644 lib/toolbox/flipper-file-cpp.cpp delete mode 100644 lib/toolbox/flipper-file-cpp.h delete mode 100644 lib/toolbox/flipper-file.c diff --git a/applications/ibutton/ibutton-app.cpp b/applications/ibutton/ibutton-app.cpp index a8d0081f3..99454cf07 100644 --- a/applications/ibutton/ibutton-app.cpp +++ b/applications/ibutton/ibutton-app.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include const char* iButtonApp::app_folder = "/any/ibutton"; const char* iButtonApp::app_extension = ".ibtn"; @@ -191,7 +191,7 @@ bool iButtonApp::save_key(const char* key_name) { // Create ibutton directory if necessary make_app_folder(); - FlipperFileCpp file(storage); + FlipperFile* file = flipper_file_alloc(storage); string_t key_file_name; bool result = false; string_init(key_file_name); @@ -207,27 +207,30 @@ bool iButtonApp::save_key(const char* key_name) { string_printf(key_file_name, "%s/%s%s", app_folder, key.get_name(), app_extension); // Open file for write - if(!file.new_write(string_get_cstr(key_file_name))) break; + if(!flipper_file_open_always(file, string_get_cstr(key_file_name))) break; // Write header - if(!file.write_header_cstr(iButtonApp::app_filetype, 1)) break; + if(!flipper_file_write_header_cstr(file, iButtonApp::app_filetype, 1)) break; // Write key type - if(!file.write_comment_cstr("Key type can be Cyfral, Dallas or Metakom")) break; + if(!flipper_file_write_comment_cstr(file, "Key type can be Cyfral, Dallas or Metakom")) + break; const char* key_type = key.get_key_type_string_by_type(key.get_key_type()); - if(!file.write_string_cstr("Key type", key_type)) break; + if(!flipper_file_write_string_cstr(file, "Key type", key_type)) break; // Write data - if(!file.write_comment_cstr( - "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8")) + if(!flipper_file_write_comment_cstr( + file, "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8")) break; - if(!file.write_hex_array("Data", key.get_data(), key.get_type_data_size())) break; + if(!flipper_file_write_hex(file, "Data", key.get_data(), key.get_type_data_size())) break; result = true; } while(false); - file.close(); + flipper_file_close(file); + flipper_file_free(file); + string_clear(key_file_name); if(!result) { @@ -238,28 +241,29 @@ bool iButtonApp::save_key(const char* key_name) { } bool iButtonApp::load_key_data(string_t key_path) { - FlipperFileCpp file(storage); + FlipperFile* file = flipper_file_alloc(storage); bool result = false; string_t data; string_init(data); do { - if(!file.open_read(string_get_cstr(key_path))) break; + if(!flipper_file_open_existing(file, string_get_cstr(key_path))) break; // header uint32_t version; - if(!file.read_header(data, &version)) break; + if(!flipper_file_read_header(file, data, &version)) break; if(string_cmp_str(data, iButtonApp::app_filetype) != 0) break; if(version != 1) break; // key type iButtonKeyType type; - if(!file.read_string("Key type", data)) break; + if(!flipper_file_read_string(file, "Key type", data)) break; if(!key.get_key_type_by_type_string(string_get_cstr(data), &type)) break; // key data uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0}; - if(!file.read_hex_array("Data", key_data, key.get_type_data_size_by_type(type))) break; + if(!flipper_file_read_hex(file, "Data", key_data, key.get_type_data_size_by_type(type))) + break; key.set_type(type); key.set_data(key_data, IBUTTON_KEY_DATA_SIZE); @@ -267,7 +271,8 @@ bool iButtonApp::load_key_data(string_t key_path) { result = true; } while(false); - file.close(); + flipper_file_close(file); + flipper_file_free(file); string_clear(data); if(!result) { diff --git a/applications/lfrfid/lfrfid-app.cpp b/applications/lfrfid/lfrfid-app.cpp index 92ddaa63a..a64b13804 100644 --- a/applications/lfrfid/lfrfid-app.cpp +++ b/applications/lfrfid/lfrfid-app.cpp @@ -16,8 +16,8 @@ #include "scene/lfrfid-app-scene-delete-confirm.h" #include "scene/lfrfid-app-scene-delete-success.h" -#include -#include +#include +#include const char* LfRfidApp::app_folder = "/any/lfrfid"; const char* LfRfidApp::app_extension = ".rfid"; @@ -119,17 +119,17 @@ bool LfRfidApp::delete_key(RfidKey* key) { } bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { - FlipperFileCpp file(storage); + FlipperFile* file = flipper_file_alloc(storage); bool result = false; string_t str_result; string_init(str_result); do { - if(!file.open_read(path)) break; + if(!flipper_file_open_existing(file, path)) break; // header uint32_t version; - if(!file.read_header(str_result, &version)) break; + if(!flipper_file_read_header(file, str_result, &version)) break; if(string_cmp_str(str_result, app_filetype) != 0) break; if(version != 1) break; @@ -137,13 +137,13 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { LfrfidKeyType type; RfidKey loaded_key; - if(!file.read_string("Key type", str_result)) break; + if(!flipper_file_read_string(file, "Key type", str_result)) break; if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &type)) break; loaded_key.set_type(type); // key data uint8_t key_data[loaded_key.get_type_data_count()] = {}; - if(!file.read_hex_array("Data", key_data, loaded_key.get_type_data_count())) break; + if(!flipper_file_read_hex(file, "Data", key_data, loaded_key.get_type_data_count())) break; loaded_key.set_data(key_data, loaded_key.get_type_data_count()); path_extract_filename_no_ext(path, str_result); @@ -153,7 +153,8 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { result = true; } while(0); - file.close(); + flipper_file_close(file); + flipper_file_free(file); string_clear(str_result); if(!result) { @@ -164,21 +165,27 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { } bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { - FlipperFileCpp file(storage); + FlipperFile* file = flipper_file_alloc(storage); bool result = false; do { - if(!file.new_write(path)) break; - if(!file.write_header_cstr(app_filetype, 1)) break; - if(!file.write_comment_cstr("Key type can be EM4100, H10301 or I40134")) break; - if(!file.write_string_cstr("Key type", lfrfid_key_get_type_string(key->get_type()))) break; - if(!file.write_comment_cstr("Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3")) + if(!flipper_file_open_always(file, path)) break; + if(!flipper_file_write_header_cstr(file, app_filetype, 1)) break; + if(!flipper_file_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) + break; + if(!flipper_file_write_string_cstr( + file, "Key type", lfrfid_key_get_type_string(key->get_type()))) + break; + if(!flipper_file_write_comment_cstr( + file, "Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3")) + break; + if(!flipper_file_write_hex(file, "Data", key->get_data(), key->get_type_data_count())) break; - if(!file.write_hex_array("Data", key->get_data(), key->get_type_data_count())) break; result = true; } while(0); - file.close(); + flipper_file_close(file); + flipper_file_free(file); if(!result) { dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); diff --git a/applications/tests/flipper_file/flipper_file_test.c b/applications/tests/flipper_file/flipper_file_test.c new file mode 100644 index 000000000..8d21bb424 --- /dev/null +++ b/applications/tests/flipper_file/flipper_file_test.c @@ -0,0 +1,500 @@ +#include +#include +#include "../minunit.h" + +#define TEST_DIR TEST_DIR_NAME "/" +#define TEST_DIR_NAME "/ext/unit_tests_tmp" + +static const char* test_filetype = "Flipper File test"; +static const uint32_t test_version = 666; + +static const char* test_string_key = "String data"; +static const char* test_string_data = "String"; +static const char* test_string_updated_data = "New string"; + +static const char* test_int_key = "Int32 data"; +static const int32_t test_int_data[] = {1234, -6345, 7813, 0}; +static const int32_t test_int_updated_data[] = {-1337, 69}; + +static const char* test_uint_key = "Uint32 data"; +static const uint32_t test_uint_data[] = {1234, 0, 5678, 9098, 7654321}; +static const uint32_t test_uint_updated_data[] = {8, 800, 555, 35, 35}; + +static const char* test_float_key = "Float data"; +static const float test_float_data[] = {1.5f, 1000.0f}; +static const float test_float_updated_data[] = {1.2f}; + +static const char* test_hex_key = "Hex data"; +static const uint8_t test_hex_data[] = {0xDE, 0xAD, 0xBE}; +static const uint8_t test_hex_updated_data[] = {0xFE, 0xCA}; + +#define READ_TEST_WIN "ff_win.test" +static const char* test_data_win = "Filetype: Flipper File test\n" + "Version: 666\n" + "# This is comment\n" + "String data: String\n" + "Int32 data: 1234 -6345 7813 0\n" + "Uint32 data: 1234 0 5678 9098 7654321\n" + "Float data: 1.5 1000.0\n" + "Hex data: DE AD BE"; + +#define READ_TEST_NIX "ff_nix.test" +static const char* test_data_nix = "Filetype: Flipper File test\r\n" + "Version: 666\r\n" + "# This is comment\r\n" + "String data: String\r\n" + "Int32 data: 1234 -6345 7813 0\r\n" + "Uint32 data: 1234 0 5678 9098 7654321\r\n" + "Float data: 1.5 1000.0\r\n" + "Hex data: DE AD BE"; + +#define READ_TEST_FLP "ff_flp.test" + +// data created by user on linux machine +const char* test_file_linux = TEST_DIR READ_TEST_WIN; +// data created by user on windows machine +const char* test_file_windows = TEST_DIR READ_TEST_NIX; +// data created by flipper itself +const char* test_file_flipper = TEST_DIR READ_TEST_FLP; + +static bool storage_write_string(const char* path, const char* data) { + Storage* storage = furi_record_open("storage"); + File* file = storage_file_alloc(storage); + bool result = false; + + do { + if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) break; + if(storage_file_write(file, data, strlen(data)) != strlen(data)) break; + + result = true; + } while(false); + + storage_file_close(file); + storage_file_free(file); + furi_record_close("storage"); + + return result; +} + +static void tests_setup() { + Storage* storage = furi_record_open("storage"); + mu_assert(storage_simply_remove_recursive(storage, TEST_DIR_NAME), "Cannot clean data"); + mu_assert(storage_simply_mkdir(storage, TEST_DIR_NAME), "Cannot create dir"); + furi_record_close("storage"); +} + +static void tests_teardown() { + Storage* storage = furi_record_open("storage"); + mu_assert(storage_simply_remove_recursive(storage, TEST_DIR_NAME), "Cannot clean data"); + furi_record_close("storage"); +} + +static bool test_read(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + + FlipperFile* file = flipper_file_alloc(storage); + string_t string_value; + string_init(string_value); + uint32_t uint32_value; + void* scratchpad = malloc(512); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + + if(!flipper_file_read_header(file, string_value, &uint32_value)) break; + if(string_cmp_str(string_value, test_filetype) != 0) break; + if(uint32_value != test_version) break; + + if(!flipper_file_read_string(file, test_string_key, string_value)) break; + if(string_cmp_str(string_value, test_string_data) != 0) break; + + if(!flipper_file_get_value_count(file, test_int_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_int_data)) break; + if(!flipper_file_read_int32(file, test_int_key, scratchpad, uint32_value)) break; + if(memcmp(scratchpad, test_int_data, sizeof(int32_t) * COUNT_OF(test_int_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_uint_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_uint_data)) break; + if(!flipper_file_read_uint32(file, test_uint_key, scratchpad, uint32_value)) break; + if(memcmp(scratchpad, test_uint_data, sizeof(uint32_t) * COUNT_OF(test_uint_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_float_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_float_data)) break; + if(!flipper_file_read_float(file, test_float_key, scratchpad, uint32_value)) break; + if(memcmp(scratchpad, test_float_data, sizeof(float) * COUNT_OF(test_float_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_hex_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_hex_data)) break; + if(!flipper_file_read_hex(file, test_hex_key, scratchpad, uint32_value)) break; + if(memcmp(scratchpad, test_hex_data, sizeof(uint8_t) * COUNT_OF(test_hex_data)) != 0) + break; + + result = true; + } while(false); + + free(scratchpad); + string_clear(string_value); + flipper_file_close(file); + flipper_file_free(file); + + furi_record_close("storage"); + + return result; +} + +static bool test_read_updated(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + + FlipperFile* file = flipper_file_alloc(storage); + string_t string_value; + string_init(string_value); + uint32_t uint32_value; + void* scratchpad = malloc(512); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + + if(!flipper_file_read_header(file, string_value, &uint32_value)) break; + if(string_cmp_str(string_value, test_filetype) != 0) break; + if(uint32_value != test_version) break; + + if(!flipper_file_read_string(file, test_string_key, string_value)) break; + if(string_cmp_str(string_value, test_string_updated_data) != 0) break; + + if(!flipper_file_get_value_count(file, test_int_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_int_updated_data)) break; + if(!flipper_file_read_int32(file, test_int_key, scratchpad, uint32_value)) break; + if(memcmp( + scratchpad, + test_int_updated_data, + sizeof(int32_t) * COUNT_OF(test_int_updated_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_uint_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_uint_updated_data)) break; + if(!flipper_file_read_uint32(file, test_uint_key, scratchpad, uint32_value)) break; + if(memcmp( + scratchpad, + test_uint_updated_data, + sizeof(uint32_t) * COUNT_OF(test_uint_updated_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_float_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_float_updated_data)) break; + if(!flipper_file_read_float(file, test_float_key, scratchpad, uint32_value)) break; + if(memcmp( + scratchpad, + test_float_updated_data, + sizeof(float) * COUNT_OF(test_float_updated_data)) != 0) + break; + + if(!flipper_file_get_value_count(file, test_hex_key, &uint32_value)) break; + if(uint32_value != COUNT_OF(test_hex_updated_data)) break; + if(!flipper_file_read_hex(file, test_hex_key, scratchpad, uint32_value)) break; + if(memcmp( + scratchpad, + test_hex_updated_data, + sizeof(uint8_t) * COUNT_OF(test_hex_updated_data)) != 0) + break; + + result = true; + } while(false); + + free(scratchpad); + string_clear(string_value); + flipper_file_close(file); + flipper_file_free(file); + + furi_record_close("storage"); + + return result; +} + +static bool test_write(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_always(file, file_name)) break; + if(!flipper_file_write_header_cstr(file, test_filetype, test_version)) break; + if(!flipper_file_write_comment_cstr(file, "This is comment")) break; + if(!flipper_file_write_string_cstr(file, test_string_key, test_string_data)) break; + if(!flipper_file_write_int32(file, test_int_key, test_int_data, COUNT_OF(test_int_data))) + break; + if(!flipper_file_write_uint32( + file, test_uint_key, test_uint_data, COUNT_OF(test_uint_data))) + break; + if(!flipper_file_write_float( + file, test_float_key, test_float_data, COUNT_OF(test_float_data))) + break; + if(!flipper_file_write_hex(file, test_hex_key, test_hex_data, COUNT_OF(test_hex_data))) + break; + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_delete_last_key(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + if(!flipper_file_delete_key(file, test_hex_key)) break; + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_append_key(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_append(file, file_name)) break; + if(!flipper_file_write_hex(file, test_hex_key, test_hex_data, COUNT_OF(test_hex_data))) + break; + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_update(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + if(!flipper_file_update_string_cstr(file, test_string_key, test_string_updated_data)) + break; + if(!flipper_file_update_int32( + file, test_int_key, test_int_updated_data, COUNT_OF(test_int_updated_data))) + break; + if(!flipper_file_update_uint32( + file, test_uint_key, test_uint_updated_data, COUNT_OF(test_uint_updated_data))) + break; + if(!flipper_file_update_float( + file, test_float_key, test_float_updated_data, COUNT_OF(test_float_updated_data))) + break; + if(!flipper_file_update_hex( + file, test_hex_key, test_hex_updated_data, COUNT_OF(test_hex_updated_data))) + break; + + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_update_backward(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_existing(file, file_name)) break; + if(!flipper_file_update_string_cstr(file, test_string_key, test_string_data)) break; + if(!flipper_file_update_int32(file, test_int_key, test_int_data, COUNT_OF(test_int_data))) + break; + if(!flipper_file_update_uint32( + file, test_uint_key, test_uint_data, COUNT_OF(test_uint_data))) + break; + if(!flipper_file_update_float( + file, test_float_key, test_float_data, COUNT_OF(test_float_data))) + break; + if(!flipper_file_update_hex(file, test_hex_key, test_hex_data, COUNT_OF(test_hex_data))) + break; + + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_write_multikey(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + do { + if(!flipper_file_open_always(file, file_name)) break; + if(!flipper_file_write_header_cstr(file, test_filetype, test_version)) break; + + bool error = false; + for(uint8_t index = 0; index < 100; index++) { + if(!flipper_file_write_hex(file, test_hex_key, &index, 1)) { + error = true; + break; + } + } + if(error) break; + + result = true; + } while(false); + + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +static bool test_read_multikey(const char* file_name) { + Storage* storage = furi_record_open("storage"); + bool result = false; + FlipperFile* file = flipper_file_alloc(storage); + + string_t string_value; + string_init(string_value); + uint32_t uint32_value; + + do { + if(!flipper_file_open_existing(file, file_name)) break; + if(!flipper_file_read_header(file, string_value, &uint32_value)) break; + if(string_cmp_str(string_value, test_filetype) != 0) break; + if(uint32_value != test_version) break; + + bool error = false; + uint8_t uint8_value; + for(uint8_t index = 0; index < 100; index++) { + if(!flipper_file_read_hex(file, test_hex_key, &uint8_value, 1)) { + error = true; + break; + } + + if(uint8_value != index) { + error = true; + break; + } + } + if(error) break; + + result = true; + } while(false); + + string_clear(string_value); + flipper_file_close(file); + flipper_file_free(file); + furi_record_close("storage"); + + return result; +} + +MU_TEST(flipper_file_write_test) { + mu_assert(storage_write_string(test_file_linux, test_data_nix), "Write test error [Linux]"); + mu_assert( + storage_write_string(test_file_windows, test_data_win), "Write test error [Windows]"); + mu_assert(test_write(test_file_flipper), "Write test error [Flipper]"); +} + +MU_TEST(flipper_file_read_test) { + mu_assert(test_read(test_file_linux), "Read test error [Linux]"); + mu_assert(test_read(test_file_windows), "Read test error [Windows]"); + mu_assert(test_read(test_file_flipper), "Read test error [Flipper]"); +} + +MU_TEST(flipper_file_delete_test) { + mu_assert(test_delete_last_key(test_file_linux), "Cannot delete key [Linux]"); + mu_assert(test_delete_last_key(test_file_windows), "Cannot delete key [Windows]"); + mu_assert(test_delete_last_key(test_file_flipper), "Cannot delete key [Flipper]"); +} + +MU_TEST(flipper_file_delete_result_test) { + mu_assert(!test_read(test_file_linux), "Key deleted incorrectly [Linux]"); + mu_assert(!test_read(test_file_windows), "Key deleted incorrectly [Windows]"); + mu_assert(!test_read(test_file_flipper), "Key deleted incorrectly [Flipper]"); +} + +MU_TEST(flipper_file_append_test) { + mu_assert(test_append_key(test_file_linux), "Cannot append data [Linux]"); + mu_assert(test_append_key(test_file_windows), "Cannot append data [Windows]"); + mu_assert(test_append_key(test_file_flipper), "Cannot append data [Flipper]"); +} + +MU_TEST(flipper_file_append_result_test) { + mu_assert(test_read(test_file_linux), "Data appended incorrectly [Linux]"); + mu_assert(test_read(test_file_windows), "Data appended incorrectly [Windows]"); + mu_assert(test_read(test_file_flipper), "Data appended incorrectly [Flipper]"); +} + +MU_TEST(flipper_file_update_1_test) { + mu_assert(test_update(test_file_linux), "Cannot update data #1 [Linux]"); + mu_assert(test_update(test_file_windows), "Cannot update data #1 [Windows]"); + mu_assert(test_update(test_file_flipper), "Cannot update data #1 [Flipper]"); +} + +MU_TEST(flipper_file_update_1_result_test) { + mu_assert(test_read_updated(test_file_linux), "Data #1 updated incorrectly [Linux]"); + mu_assert(test_read_updated(test_file_windows), "Data #1 updated incorrectly [Windows]"); + mu_assert(test_read_updated(test_file_flipper), "Data #1 updated incorrectly [Flipper]"); +} + +MU_TEST(flipper_file_update_2_test) { + mu_assert(test_update_backward(test_file_linux), "Cannot update data #2 [Linux]"); + mu_assert(test_update_backward(test_file_windows), "Cannot update data #2 [Windows]"); + mu_assert(test_update_backward(test_file_flipper), "Cannot update data #2 [Flipper]"); +} + +MU_TEST(flipper_file_update_2_result_test) { + mu_assert(test_read(test_file_linux), "Data #2 updated incorrectly [Linux]"); + mu_assert(test_read(test_file_windows), "Data #2 updated incorrectly [Windows]"); + mu_assert(test_read(test_file_flipper), "Data #2 updated incorrectly [Flipper]"); +} + +MU_TEST(flipper_file_multikey_test) { + mu_assert(test_write_multikey(TEST_DIR "ff_multiline.test"), "Multikey write test error"); + mu_assert(test_read_multikey(TEST_DIR "ff_multiline.test"), "Multikey read test error"); +} + +MU_TEST_SUITE(flipper_file) { + tests_setup(); + MU_RUN_TEST(flipper_file_write_test); + MU_RUN_TEST(flipper_file_read_test); + MU_RUN_TEST(flipper_file_delete_test); + MU_RUN_TEST(flipper_file_delete_result_test); + MU_RUN_TEST(flipper_file_append_test); + MU_RUN_TEST(flipper_file_append_result_test); + MU_RUN_TEST(flipper_file_update_1_test); + MU_RUN_TEST(flipper_file_update_1_result_test); + MU_RUN_TEST(flipper_file_update_2_test); + MU_RUN_TEST(flipper_file_update_2_result_test); + MU_RUN_TEST(flipper_file_multikey_test); + tests_teardown(); +} + +int run_minunit_test_flipper_file() { + MU_RUN_SUITE(flipper_file); + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/tests/test_index.c b/applications/tests/test_index.c index d86e587a1..f436c0efc 100644 --- a/applications/tests/test_index.c +++ b/applications/tests/test_index.c @@ -13,6 +13,7 @@ int run_minunit(); int run_minunit_test_irda_decoder_encoder(); int run_minunit_test_rpc(); +int run_minunit_test_flipper_file(); void minunit_print_progress(void) { static char progress[] = {'\\', '|', '/', '-'}; @@ -37,11 +38,9 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { minunit_status = 0; Loader* loader = furi_record_open("loader"); - furi_record_close("loader"); - NotificationApp* notification = furi_record_open("notification"); - furi_record_close("notification"); + // TODO: lock device while test running if(loader_is_locked(loader)) { FURI_LOG_E(TESTS_TAG, "RPC: stop all applications to run tests"); notification_message(notification, &sequence_blink_magenta_100); @@ -49,10 +48,15 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { notification_message_block(notification, &sequence_set_only_blue_255); uint32_t heap_before = memmgr_get_free_heap(); + uint32_t cycle_counter = DWT->CYCCNT; test_result |= run_minunit(); test_result |= run_minunit_test_irda_decoder_encoder(); test_result |= run_minunit_test_rpc(); + test_result |= run_minunit_test_flipper_file(); + cycle_counter = (DWT->CYCCNT - cycle_counter); + + FURI_LOG_I(TESTS_TAG, "Consumed: %0.2fs", (float)cycle_counter / (SystemCoreClock)); if(test_result == 0) { delay(200); /* wait for tested services and apps to deallocate */ @@ -69,10 +73,15 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { FURI_LOG_E(TESTS_TAG, "FAILED"); } } + + furi_record_close("notification"); + furi_record_close("loader"); } void unit_tests_cli_init() { Cli* cli = furi_record_open("cli"); + + // We need to launch apps from tests, so we cannot lock loader cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); furi_record_close("cli"); } diff --git a/lib/flipper_file/file_helper.c b/lib/flipper_file/file_helper.c new file mode 100644 index 000000000..c0feb993d --- /dev/null +++ b/lib/flipper_file/file_helper.c @@ -0,0 +1,204 @@ +#include "file_helper.h" + +const char flipper_file_eoln = '\n'; +const char flipper_file_eolr = '\r'; + +bool file_helper_seek(File* file, int32_t offset) { + uint64_t position = storage_file_tell(file); + return storage_file_seek(file, position + offset, true); +} + +bool file_helper_write_hex(File* file, const uint8_t* data, const uint16_t data_size) { + const uint8_t byte_text_size = 3; + char byte_text[byte_text_size]; + + bool result = true; + uint16_t bytes_written; + for(uint8_t i = 0; i < data_size; i++) { + snprintf(byte_text, byte_text_size, "%02X", data[i]); + + if(i != 0) { + // space + const char space = ' '; + bytes_written = storage_file_write(file, &space, sizeof(char)); + if(bytes_written != sizeof(char)) { + result = false; + break; + } + } + + bytes_written = storage_file_write(file, &byte_text, strlen(byte_text)); + if(bytes_written != strlen(byte_text)) { + result = false; + break; + } + } + + return result; +} + +bool file_helper_read_line(File* file, string_t str_result) { + string_clean(str_result); + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + + do { + uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); + // TODO process EOF + if(bytes_were_read == 0) break; + + bool result = false; + bool error = false; + for(uint16_t i = 0; i < bytes_were_read; i++) { + if(buffer[i] == flipper_file_eoln) { + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + result = true; + break; + } else if(buffer[i] == flipper_file_eolr) { + // Ignore + } else { + string_push_back(str_result, buffer[i]); + } + } + + if(result || error) { + break; + } + } while(true); + + return string_size(str_result) != 0; +} + +bool file_helper_seek_to_next_line(File* file) { + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + bool result = false; + bool error = false; + + do { + uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); + if(bytes_were_read == 0) { + if(storage_file_eof(file)) { + result = true; + break; + } + } + + for(uint16_t i = 0; i < bytes_were_read; i++) { + if(buffer[i] == flipper_file_eoln) { + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + result = true; + break; + } + } + + if(result || error) { + break; + } + } while(true); + + return result; +} + +bool file_helper_read_value(File* file, string_t value, bool* last) { + string_clean(value); + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + bool result = false; + bool error = false; + + while(true) { + uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); + + if(bytes_were_read == 0) { + // check EOF + if(storage_file_eof(file) && string_size(value) > 0) { + result = true; + *last = true; + break; + } + } + + for(uint16_t i = 0; i < bytes_were_read; i++) { + if(buffer[i] == flipper_file_eoln) { + if(string_size(value) > 0) { + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + result = true; + *last = true; + break; + } else { + error = true; + } + } else if(buffer[i] == ' ') { + if(string_size(value) > 0) { + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + result = true; + *last = false; + break; + } + + } else if(buffer[i] == flipper_file_eolr) { + // Ignore + } else { + string_push_back(value, buffer[i]); + } + } + + if(error || result) break; + } + + return result; +} + +bool file_helper_write(File* file, const void* data, uint16_t data_size) { + uint16_t bytes_written = storage_file_write(file, data, data_size); + return bytes_written == data_size; +} + +bool file_helper_write_eol(File* file) { + return file_helper_write(file, &flipper_file_eoln, sizeof(char)); +} + +bool file_helper_copy(File* file_from, File* file_to, uint64_t start_offset, uint64_t stop_offset) { + bool result = false; + + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + uint64_t current_offset = start_offset; + + if(storage_file_seek(file_from, start_offset, true)) { + do { + int32_t bytes_count = MIN(buffer_size, stop_offset - current_offset); + if(bytes_count <= 0) { + result = true; + break; + } + + uint16_t bytes_were_read = storage_file_read(file_from, buffer, bytes_count); + if(bytes_were_read != bytes_count) break; + + uint16_t bytes_were_written = storage_file_write(file_to, buffer, bytes_count); + if(bytes_were_written != bytes_count) break; + + current_offset += bytes_count; + } while(true); + } + + return result; +} \ No newline at end of file diff --git a/lib/flipper_file/file_helper.h b/lib/flipper_file/file_helper.h new file mode 100644 index 000000000..663067970 --- /dev/null +++ b/lib/flipper_file/file_helper.h @@ -0,0 +1,81 @@ +#pragma once +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char flipper_file_eoln; +extern const char flipper_file_eolr; + +/** + * Negative seek helper + * @param file + * @param offset + * @return bool + */ +bool file_helper_seek(File* file, int32_t offset); + +/** + * Writes data to a file as a hexadecimal array. + * @param file + * @param data + * @param data_size + * @return true on success write + */ +bool file_helper_write_hex(File* file, const uint8_t* data, const uint16_t data_size); + +/** + * Reads data as a string from the stored rw pointer to the \\n symbol position. Ignores \r. + * @param file + * @param str_result + * @return true on success read + */ +bool file_helper_read_line(File* file, string_t str_result); + +/** + * Moves the RW pointer to the beginning of the next line + * @param file + * @return bool + */ +bool file_helper_seek_to_next_line(File* file); + +/** + * Read one value from array-like string (separated by ' ') + * @param file + * @param value + * @return bool + */ +bool file_helper_read_value(File* file, string_t value, bool* last); + +/** + * Write helper + * @param file + * @param data + * @param data_size + * @return bool + */ +bool file_helper_write(File* file, const void* data, uint16_t data_size); + +/** + * Write EOL + * @param file + * @return bool + */ +bool file_helper_write_eol(File* file); + +/** + * Appends part of one file to the end of another file + * @param file_from + * @param file_to + * @param start_offset + * @param stop_offset + * @return bool + */ +bool file_helper_copy(File* file_from, File* file_to, uint64_t start_offset, uint64_t stop_offset); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_file/flipper_file.c b/lib/flipper_file/flipper_file.c new file mode 100644 index 000000000..21cd5f265 --- /dev/null +++ b/lib/flipper_file/flipper_file.c @@ -0,0 +1,395 @@ +#include +#include "file_helper.h" +#include "flipper_file_helper.h" +#include "flipper_file.h" +#include "flipper_file_i.h" +#include +#include + +FlipperFile* flipper_file_alloc(Storage* storage) { + // furi_assert(storage); + + FlipperFile* flipper_file = malloc(sizeof(FlipperFile)); + flipper_file->storage = storage; + flipper_file->file = storage_file_alloc(flipper_file->storage); + + return flipper_file; +} + +void flipper_file_free(FlipperFile* flipper_file) { + furi_assert(flipper_file); + if(storage_file_is_open(flipper_file->file)) { + storage_file_close(flipper_file->file); + } + storage_file_free(flipper_file->file); + free(flipper_file); +} + +bool flipper_file_open_existing(FlipperFile* flipper_file, const char* filename) { + furi_assert(flipper_file); + bool result = storage_file_open( + flipper_file->file, filename, FSAM_READ | FSAM_WRITE, FSOM_OPEN_EXISTING); + return result; +} + +bool flipper_file_open_append(FlipperFile* flipper_file, const char* filename) { + furi_assert(flipper_file); + + bool result = + storage_file_open(flipper_file->file, filename, FSAM_READ | FSAM_WRITE, FSOM_OPEN_APPEND); + + // Add EOL if it is not there + if(storage_file_size(flipper_file->file) >= 1) { + do { + char last_char; + result = false; + + if(!file_helper_seek(flipper_file->file, -1)) break; + + uint16_t bytes_were_read = storage_file_read(flipper_file->file, &last_char, 1); + if(bytes_were_read != 1) break; + + if(last_char != flipper_file_eoln) { + if(!file_helper_write_eol(flipper_file->file)) break; + } + + result = true; + } while(false); + } + + return result; +} + +bool flipper_file_open_always(FlipperFile* flipper_file, const char* filename) { + furi_assert(flipper_file); + bool result = storage_file_open( + flipper_file->file, filename, FSAM_READ | FSAM_WRITE, FSOM_CREATE_ALWAYS); + return result; +} + +bool flipper_file_open_new(FlipperFile* flipper_file, const char* filename) { + furi_assert(flipper_file); + bool result = storage_file_open( + flipper_file->file, filename, FSAM_READ | FSAM_WRITE, FSOM_CREATE_NEW); + return result; +} + +bool flipper_file_close(FlipperFile* flipper_file) { + furi_assert(flipper_file); + if(storage_file_is_open(flipper_file->file)) { + return storage_file_close(flipper_file->file); + } + return true; +} + +bool flipper_file_rewind(FlipperFile* flipper_file) { + furi_assert(flipper_file); + return storage_file_seek(flipper_file->file, 0, true); +} + +bool flipper_file_read_header(FlipperFile* flipper_file, string_t filetype, uint32_t* version) { + bool result = false; + do { + result = flipper_file_read_string(flipper_file, flipper_file_filetype_key, filetype); + if(!result) break; + result = flipper_file_read_uint32(flipper_file, flipper_file_version_key, version, 1); + if(!result) break; + } while(false); + + return result; +} + +bool flipper_file_write_header( + FlipperFile* flipper_file, + string_t filetype, + const uint32_t version) { + bool result = false; + do { + result = flipper_file_write_string(flipper_file, flipper_file_filetype_key, filetype); + if(!result) break; + result = flipper_file_write_uint32(flipper_file, flipper_file_version_key, &version, 1); + if(!result) break; + } while(false); + + return result; +} + +bool flipper_file_write_header_cstr( + FlipperFile* flipper_file, + const char* filetype, + const uint32_t version) { + bool result = false; + string_t value; + string_init_set(value, filetype); + result = flipper_file_write_header(flipper_file, value, version); + string_clear(value); + return result; +} + +bool flipper_file_get_value_count(FlipperFile* flipper_file, const char* key, uint32_t* count) { + furi_assert(flipper_file); + bool result = false; + bool last = false; + + string_t value; + string_init(value); + + uint32_t position = storage_file_tell(flipper_file->file); + do { + if(!flipper_file_seek_to_key(flipper_file->file, key)) break; + + // Balance between speed and memory consumption + // I prefer lower speed but less memory consumption + *count = 0; + + result = true; + while(true) { + if(!file_helper_read_value(flipper_file->file, value, &last)) { + result = false; + break; + } + + *count = *count + 1; + if(last) break; + } + + } while(true); + + if(!storage_file_seek(flipper_file->file, position, true)) { + result = false; + } + + string_clear(value); + return result; +} + +bool flipper_file_write_comment(FlipperFile* flipper_file, string_t data) { + furi_assert(flipper_file); + + bool result = false; + do { + const char comment_buffer[2] = {flipper_file_comment, ' '}; + result = file_helper_write(flipper_file->file, comment_buffer, sizeof(comment_buffer)); + if(!result) break; + + result = file_helper_write(flipper_file->file, string_get_cstr(data), string_size(data)); + if(!result) break; + + result = file_helper_write_eol(flipper_file->file); + } while(false); + + return result; +} + +bool flipper_file_write_comment_cstr(FlipperFile* flipper_file, const char* data) { + bool result = false; + string_t value; + string_init_set(value, data); + result = flipper_file_write_comment(flipper_file, value); + string_clear(value); + return result; +} + +bool flipper_file_delete_key_and_call( + FlipperFile* flipper_file, + const char* key, + flipper_file_cb call, + const char* cb_key, + const void* cb_data, + const uint16_t cb_data_size) { + bool result = false; + File* scratch_file = storage_file_alloc(flipper_file->storage); + + do { + // get size + uint64_t file_size = storage_file_size(flipper_file->file); + if(file_size == 0) break; + + if(!storage_file_seek(flipper_file->file, 0, true)) break; + + // find key + if(!flipper_file_seek_to_key(flipper_file->file, key)) break; + // get key start position + uint64_t start_position = storage_file_tell(flipper_file->file) - strlen(key); + if(start_position >= 2) { + start_position -= 2; + } else { + // something wrong + break; + } + + // get value end position + if(!file_helper_seek_to_next_line(flipper_file->file)) break; + uint64_t end_position = storage_file_tell(flipper_file->file); + // newline symbol + if(end_position < file_size) { + end_position += 1; + } + + // open scratchpad + const char* scratch_name = ""; + if(!flipper_file_get_scratchpad_name(&scratch_name)) break; + + if(!storage_file_open( + scratch_file, scratch_name, FSAM_READ | FSAM_WRITE, FSOM_CREATE_ALWAYS)) + break; + + // copy key file before key to scratchpad + if(!file_helper_copy(flipper_file->file, scratch_file, 0, start_position)) break; + + // do something in between if needed + if(call != NULL) { + if(!call(scratch_file, cb_key, cb_data, cb_data_size)) break; + }; + + // copy key file after key value to scratchpad + if(!file_helper_copy(flipper_file->file, scratch_file, end_position, file_size)) break; + + file_size = storage_file_tell(scratch_file); + if(file_size == 0) break; + + if(!storage_file_seek(flipper_file->file, 0, true)) break; + + // copy whole scratchpad file to the original file + if(!file_helper_copy(scratch_file, flipper_file->file, 0, file_size)) break; + + // and truncate original file + if(!storage_file_truncate(flipper_file->file)) break; + + // close and remove scratchpad file + if(!storage_file_close(scratch_file)) break; + if(storage_common_remove(flipper_file->storage, scratch_name) != FSE_OK) break; + result = true; + } while(false); + + storage_file_free(scratch_file); + + return result; +} + +bool flipper_file_delete_key(FlipperFile* flipper_file, const char* key) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call(flipper_file, key, NULL, NULL, NULL, 0); +} + +bool flipper_file_write_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size, + FlipperFileValueType type) { + bool result = false; + string_t value; + string_init(value); + + do { + result = flipper_file_write_key(file, key); + if(!result) break; + + for(uint16_t i = 0; i < data_size; i++) { + switch(type) { + case FlipperFileValueHex: { + const uint8_t* data = _data; + string_printf(value, "%02X", data[i]); + }; break; + case FlipperFileValueFloat: { + const float* data = _data; + string_printf(value, "%f", data[i]); + }; break; + case FlipperFileValueInt32: { + const int32_t* data = _data; + string_printf(value, "%" PRIi32, data[i]); + }; break; + case FlipperFileValueUint32: { + const uint32_t* data = _data; + string_printf(value, "%" PRId32, data[i]); + }; break; + } + + if((i + 1) < data_size) { + string_cat(value, " "); + } + + result = file_helper_write(file, string_get_cstr(value), string_size(value)); + if(!result) break; + } + + result = file_helper_write_eol(file); + } while(false); + + string_clear(value); + return result; +} + +bool flipper_file_read_internal( + File* file, + const char* key, + void* _data, + const uint16_t data_size, + FlipperFileValueType type) { + bool result = false; + string_t value; + string_init(value); + + if(flipper_file_seek_to_key(file, key)) { + result = true; + for(uint16_t i = 0; i < data_size; i++) { + bool last = false; + result = file_helper_read_value(file, value, &last); + if(result) { + int scan_values = 0; + switch(type) { + case FlipperFileValueHex: { + uint8_t* data = _data; + // sscanf "%02X" does not work here + if(hex_chars_to_uint8( + string_get_char(value, 0), string_get_char(value, 1), &data[i])) { + scan_values = 1; + } + }; break; + case FlipperFileValueFloat: { + float* data = _data; + // newlib-nano does not have sscanf for floats + // scan_values = sscanf(string_get_cstr(value), "%f", &data[i]); + char* end_char; + data[i] = strtof(string_get_cstr(value), &end_char); + if(*end_char == 0) { + // very probably ok + scan_values = 1; + } + }; break; + case FlipperFileValueInt32: { + int32_t* data = _data; + scan_values = sscanf(string_get_cstr(value), "%" PRIi32, &data[i]); + }; break; + case FlipperFileValueUint32: { + uint32_t* data = _data; + scan_values = sscanf(string_get_cstr(value), "%" PRId32, &data[i]); + }; break; + } + + if(scan_values != 1) { + result = false; + break; + } + } else { + break; + } + + if(last && ((i + 1) != data_size)) { + result = false; + break; + } + } + } + + string_clear(value); + return result; +} + +File* flipper_file_get_file(FlipperFile* flipper_file) { + furi_assert(flipper_file); + furi_assert(flipper_file->file); + + return flipper_file->file; +} \ No newline at end of file diff --git a/lib/toolbox/flipper-file.h b/lib/flipper_file/flipper_file.h similarity index 50% rename from lib/toolbox/flipper-file.h rename to lib/flipper_file/flipper_file.h index 942ea74a8..4c0f6cf1c 100644 --- a/lib/toolbox/flipper-file.h +++ b/lib/flipper_file/flipper_file.h @@ -17,8 +17,10 @@ * * ~~~~~~~~~~~~~~~~~~~~~ * String: text - * Uint32: 1 - * Hex Array: A4 B3 C2 D1 12 FF + * Int32: 1 2 -3 4 + * Uint32: 1 2 3 4 + * Float: 1.0 1234.654 + * Hex: A4 B3 C2 D1 12 FF * ~~~~~~~~~~~~~~~~~~~~~ * * End of line is LF when writing, but CR is supported when reading. @@ -33,13 +35,13 @@ * # Just test file * String: String value * UINT: 1234 - * Hex Array: 00 01 FF A3 + * Hex: 00 01 FF A3 * ~~~~~~~~~~~~~~~~~~~~~ * * Writing: * * ~~~~~~~~~~~~~~~~~~~~~ - * FlipperFile* file = flipper_file_alloc(storage); + * FlipperFile file = flipper_file_alloc(storage); * * do { * const uint32_t version = 1; @@ -48,12 +50,12 @@ * const uint16_t array_size = 4; * const uint8_t* array[array_size] = {0x00, 0x01, 0xFF, 0xA3}; * - * if(!flipper_file_new_write(file, "/ext/flipper_file_test")) break; + * if(!flipper_file_open_new(file, "/ext/flipper_file_test")) break; * if(!flipper_file_write_header_cstr(file, "Flipper Test File", version)) break; * if(!flipper_file_write_comment_cstr(file, "Just test file")) break; * if(!flipper_file_write_string_cstr(file, "String", string_value)) break; - * if(!flipper_file_flipper_file_write_uint32(file, "UINT", uint32_value)) break; - * if(!flipper_file_write_hex_array(file, "Hex Array", array, array_size)) break; + * if(!flipper_file_flipper_file_write_uint32(file, "UINT", &uint32_value, 1)) break; + * if(!flipper_file_write_hex(file, "Hex Array", array, array_size)) break; * * // signal that the file was written successfully * } while(0); @@ -65,7 +67,7 @@ * Reading: * * ~~~~~~~~~~~~~~~~~~~~~ - * FlipperFile* file = flipper_file_alloc(storage); + * FlipperFile file = flipper_file_alloc(storage); * * do { * uint32_t version = 1; @@ -77,11 +79,11 @@ * string_init(file_type); * string_init(string_value); * - * if(!flipper_file_open_read(file, "/ext/flipper_file_test")) break; + * if(!flipper_file_open_existing(file, "/ext/flipper_file_test")) break; * if(!flipper_file_read_header(file, file_type, &version)) break; * if(!flipper_file_read_string(file, "String", string_value)) break; - * if(!flipper_file_read_uint32(file, "UINT", &uint32_value)) break; - * if(!flipper_file_read_hex_array(file, "Hex Array", array, array_size)) break; + * if(!flipper_file_read_uint32(file, "UINT", &uint32_value, 1)) break; + * if(!flipper_file_read_hex(file, "Hex Array", array, array_size)) break; * * // signal that the file was read successfully * } while(0); @@ -118,20 +120,36 @@ FlipperFile* flipper_file_alloc(Storage* storage); void flipper_file_free(FlipperFile* flipper_file); /** - * Open file for reading. + * Open existing file. * @param flipper_file Pointer to a FlipperFile instance * @param filename File name and path * @return True on success */ -bool flipper_file_open_read(FlipperFile* flipper_file, const char* filename); +bool flipper_file_open_existing(FlipperFile* flipper_file, const char* filename); /** - * Open file for writing. Creates a new file, or deletes the contents of the file if it already exists. + * Open existing file for writing and add values to the end of file. * @param flipper_file Pointer to a FlipperFile instance * @param filename File name and path * @return True on success */ -bool flipper_file_new_write(FlipperFile* flipper_file, const char* filename); +bool flipper_file_open_append(FlipperFile* flipper_file, const char* filename); + +/** + * Open file. Creates a new file, or deletes the contents of the file if it already exists. + * @param flipper_file Pointer to a FlipperFile instance + * @param filename File name and path + * @return True on success + */ +bool flipper_file_open_always(FlipperFile* flipper_file, const char* filename); + +/** + * Open file. Creates a new file, fails if file already exists. + * @param flipper_file Pointer to a FlipperFile instance + * @param filename File name and path + * @return True on success + */ +bool flipper_file_open_new(FlipperFile* flipper_file, const char* filename); /** * Close the file. @@ -140,6 +158,13 @@ bool flipper_file_new_write(FlipperFile* flipper_file, const char* filename); */ bool flipper_file_close(FlipperFile* flipper_file); +/** + * Rewind the file RW pointer. + * @param flipper_file Pointer to a FlipperFile instance + * @return True on success + */ +bool flipper_file_rewind(FlipperFile* flipper_file); + /** * Read the header (file type and version) from the file. * @param flipper_file Pointer to a FlipperFile instance @@ -173,6 +198,15 @@ bool flipper_file_write_header_cstr( const char* filetype, const uint32_t version); +/** + * Get the count of values by key + * @param flipper_file + * @param key + * @param count + * @return bool + */ +bool flipper_file_get_value_count(FlipperFile* flipper_file, const char* key, uint32_t* count); + /** * Read a string from a file by Key * @param flipper_file Pointer to a FlipperFile instance @@ -201,22 +235,116 @@ bool flipper_file_write_string(FlipperFile* flipper_file, const char* key, strin bool flipper_file_write_string_cstr(FlipperFile* flipper_file, const char* key, const char* data); /** - * Read uint32 from a file by Key + * Read array of uint32 from a file by Key * @param flipper_file Pointer to a FlipperFile instance * @param key Key * @param data Value + * @param data_size Values count * @return True on success */ -bool flipper_file_read_uint32(FlipperFile* flipper_file, const char* key, uint32_t* data); +bool flipper_file_read_uint32( + FlipperFile* flipper_file, + const char* key, + uint32_t* data, + const uint16_t data_size); /** - * Write key and uint32 to file. + * Write key and array of uint32 to file. * @param flipper_file Pointer to a FlipperFile instance * @param key Key * @param data Value + * @param data_size Values count * @return True on success */ -bool flipper_file_write_uint32(FlipperFile* flipper_file, const char* key, const uint32_t data); +bool flipper_file_write_uint32( + FlipperFile* flipper_file, + const char* key, + const uint32_t* data, + const uint16_t data_size); + +/** + * Read array of int32 from a file by Key + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_read_int32( + FlipperFile* flipper_file, + const char* key, + int32_t* data, + const uint16_t data_size); + +/** + * Write key and array of int32 to file. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_write_int32( + FlipperFile* flipper_file, + const char* key, + const int32_t* data, + const uint16_t data_size); + +/** + * Read array of float from a file by Key + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_read_float( + FlipperFile* flipper_file, + const char* key, + float* data, + const uint16_t data_size); + +/** + * Write key and array of float to file. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_write_float( + FlipperFile* flipper_file, + const char* key, + const float* data, + const uint16_t data_size); + +/** + * Read hex array from a file by Key + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Value size + * @return True on success + */ +bool flipper_file_read_hex( + FlipperFile* flipper_file, + const char* key, + uint8_t* data, + const uint16_t data_size); + +/** + * Write key and hex array to file. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_write_hex( + FlipperFile* flipper_file, + const char* key, + const uint8_t* data, + const uint16_t data_size); /** * Write comment to file. @@ -235,41 +363,93 @@ bool flipper_file_write_comment(FlipperFile* flipper_file, string_t data); bool flipper_file_write_comment_cstr(FlipperFile* flipper_file, const char* data); /** - * Read hex array from a file by Key + * Removes the first matching key and its value from the file. Changes the RW pointer to an undefined position. * @param flipper_file Pointer to a FlipperFile instance * @param key Key - * @param data Value - * @param data_size Value size * @return True on success */ -bool flipper_file_read_hex_array( +bool flipper_file_delete_key(FlipperFile* flipper_file, const char* key); + +/** + * Updates the value of the first matching key to a string value. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_file_update_string(FlipperFile* flipper_file, const char* key, string_t data); + +/** + * Updates the value of the first matching key to a string value. Plain C version. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @return True on success + */ +bool flipper_file_update_string_cstr(FlipperFile* flipper_file, const char* key, const char* data); + +/** + * Updates the value of the first matching key to a uint32 array value. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_update_uint32( FlipperFile* flipper_file, const char* key, - uint8_t* data, + const uint32_t* data, const uint16_t data_size); /** - * Write key and hex array to file. - * @param flipper_file Pointer to a FlipperFile instance + * Updates the value of the first matching key to a int32 array value. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance * @param key Key * @param data Value - * @param data_size Value size + * @param data_size Values count * @return True on success */ -bool flipper_file_write_hex_array( +bool flipper_file_update_int32( + FlipperFile* flipper_file, + const char* key, + const int32_t* data, + const uint16_t data_size); + +/** + * Updates the value of the first matching key to a float array value. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_update_float( + FlipperFile* flipper_file, + const char* key, + const float* data, + const uint16_t data_size); + +/** + * Updates the value of the first matching key to a hex array value. Changes the RW pointer to an undefined position. + * @param flipper_file Pointer to a FlipperFile instance + * @param key Key + * @param data Value + * @param data_size Values count + * @return True on success + */ +bool flipper_file_update_hex( FlipperFile* flipper_file, const char* key, const uint8_t* data, const uint16_t data_size); /** Get file descriptor. - * + * * We higly don't recommend to use it. * This instance is owned by FlipperFile. - * - * @param flipper_file pointer to FlipperFile instance - * - * @return pointer to File instance + * @param flipper_file + * @return File* */ File* flipper_file_get_file(FlipperFile* flipper_file); diff --git a/lib/flipper_file/flipper_file_float.c b/lib/flipper_file/flipper_file_float.c new file mode 100644 index 000000000..288e545d0 --- /dev/null +++ b/lib/flipper_file/flipper_file_float.c @@ -0,0 +1,42 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_float_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size) { + return flipper_file_write_internal(file, key, _data, data_size, FlipperFileValueFloat); +}; + +bool flipper_file_read_float( + FlipperFile* flipper_file, + const char* key, + float* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_read_internal( + flipper_file->file, key, data, data_size, FlipperFileValueFloat); +} + +bool flipper_file_write_float( + FlipperFile* flipper_file, + const char* key, + const float* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_write_float_internal(flipper_file->file, key, data, data_size); +} + +bool flipper_file_update_float( + FlipperFile* flipper_file, + const char* key, + const float* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_float_internal, key, data, data_size); +} \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_helper.c b/lib/flipper_file/flipper_file_helper.c new file mode 100644 index 000000000..5b8bfb2d6 --- /dev/null +++ b/lib/flipper_file/flipper_file_helper.c @@ -0,0 +1,118 @@ +#include "flipper_file_helper.h" + +const char* flipper_file_filetype_key = "Filetype"; +const char* flipper_file_version_key = "Version"; +const char flipper_file_delimiter = ':'; +const char flipper_file_comment = '#'; + +#ifdef __linux__ +const char* flipper_file_scratchpad = ".scratch.pad"; +#else +const char* flipper_file_scratchpad = "/any/.scratch.pad"; +#endif + +bool flipper_file_read_valid_key(File* file, string_t key) { + string_clean(key); + bool found = false; + bool error = false; + const uint8_t buffer_size = 32; + uint8_t buffer[buffer_size]; + bool accumulate = true; + bool new_line = true; + + while(true) { + uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); + if(bytes_were_read == 0) break; + + for(uint16_t i = 0; i < bytes_were_read; i++) { + if(buffer[i] == flipper_file_eoln) { + // EOL found, clean data, start accumulating data and set the new_line flag + string_clean(key); + accumulate = true; + new_line = true; + } else if(buffer[i] == flipper_file_eolr) { + // Ignore + } else if(buffer[i] == flipper_file_comment && new_line) { + // if there is a comment character and we are at the beginning of a new line + // do not accumulate comment data and reset the new_line flag + accumulate = false; + new_line = false; + } else if(buffer[i] == flipper_file_delimiter) { + if(new_line) { + // we are on a "new line" and found the delimiter + // this can only be if we have previously found some kind of key, so + // clear the data, set the flag that we no longer want to accumulate data + // and reset the new_line flag + string_clean(key); + accumulate = false; + new_line = false; + } else { + // parse the delimiter only if we are accumulating data + if(accumulate) { + // we found the delimiter, move the rw pointer to the correct location + // and signal that we have found something + if(!file_helper_seek(file, i - bytes_were_read)) { + error = true; + break; + } + + found = true; + break; + } + } + } else { + // just new symbol, reset the new_line flag + new_line = false; + if(accumulate) { + // and accumulate data if we want + string_push_back(key, buffer[i]); + } + } + } + + if(found || error) break; + } + + return found; +} + +bool flipper_file_seek_to_key(File* file, const char* key) { + bool found = false; + string_t readed_key; + + string_init(readed_key); + + while(!storage_file_eof(file)) { + if(flipper_file_read_valid_key(file, readed_key)) { + if(string_cmp_str(readed_key, key) == 0) { + if(!file_helper_seek(file, 2)) break; + + found = true; + break; + } + } + } + string_clear(readed_key); + + return found; +} + +bool flipper_file_write_key(File* file, const char* key) { + bool result = false; + + do { + result = file_helper_write(file, key, strlen(key)); + if(!result) break; + + const char delimiter_buffer[2] = {flipper_file_delimiter, ' '}; + result = file_helper_write(file, delimiter_buffer, sizeof(delimiter_buffer)); + } while(false); + + return result; +} + +bool flipper_file_get_scratchpad_name(const char** name) { + // TODO do not rewrite existing file + *name = flipper_file_scratchpad; + return true; +} \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_helper.h b/lib/flipper_file/flipper_file_helper.h new file mode 100644 index 000000000..770be74e6 --- /dev/null +++ b/lib/flipper_file/flipper_file_helper.h @@ -0,0 +1,51 @@ +#pragma once +#include +#include +#include +#include "file_helper.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char* flipper_file_filetype_key; +extern const char* flipper_file_version_key; +extern const char flipper_file_delimiter; +extern const char flipper_file_comment; + +/** + * Reads a valid key from a file as a string. + * After reading, the rw pointer will be on the flipper_file_delimiter symbol. + * Optimized not to read comments and values into RAM. + * @param file + * @param key + * @return true on success read + */ +bool flipper_file_read_valid_key(File* file, string_t key); + +/** + * Sets rw pointer to the data after the key + * @param file + * @param key + * @return true if key was found + */ +bool flipper_file_seek_to_key(File* file, const char* key); + +/** + * Write key and key delimiter + * @param file + * @param key + * @return bool + */ +bool flipper_file_write_key(File* file, const char* key); + +/** + * Get scratchpad name and path + * @param name + * @return bool + */ +bool flipper_file_get_scratchpad_name(const char** name); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_hex.c b/lib/flipper_file/flipper_file_hex.c new file mode 100644 index 000000000..602652eb4 --- /dev/null +++ b/lib/flipper_file/flipper_file_hex.c @@ -0,0 +1,42 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_hex_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size) { + return flipper_file_write_internal(file, key, _data, data_size, FlipperFileValueHex); +}; + +bool flipper_file_write_hex( + FlipperFile* flipper_file, + const char* key, + const uint8_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_write_hex_internal(flipper_file->file, key, data, data_size); +} + +bool flipper_file_read_hex( + FlipperFile* flipper_file, + const char* key, + uint8_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_read_internal( + flipper_file->file, key, data, data_size, FlipperFileValueHex); +} + +bool flipper_file_update_hex( + FlipperFile* flipper_file, + const char* key, + const uint8_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_hex_internal, key, data, data_size); +} \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_i.h b/lib/flipper_file/flipper_file_i.h new file mode 100644 index 000000000..68f7fe6ac --- /dev/null +++ b/lib/flipper_file/flipper_file_i.h @@ -0,0 +1,72 @@ +#include +#include + +struct FlipperFile { + File* file; + Storage* storage; +}; + +/** + * Value write type callback + */ +typedef bool (*flipper_file_cb)(File* file, const char* key, const void* data, uint16_t data_size); + +/** + * + * @param flipper_file + * @param key + * @param cb + * @param cb_key + * @param cb_data + * @param cb_data_size + * @return bool + */ +bool flipper_file_delete_key_and_call( + FlipperFile* flipper_file, + const char* key, + flipper_file_cb cb, + const char* cb_key, + const void* cb_data, + const uint16_t cb_data_size); + +/** + * Value types + */ +typedef enum { + FlipperFileValueHex, + FlipperFileValueFloat, + FlipperFileValueInt32, + FlipperFileValueUint32, +} FlipperFileValueType; + +/** + * Internal write values function + * @param file + * @param key + * @param _data + * @param data_size + * @param type + * @return bool + */ +bool flipper_file_write_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size, + FlipperFileValueType type); + +/** + * Internal read values function + * @param file + * @param key + * @param _data + * @param data_size + * @param type + * @return bool + */ +bool flipper_file_read_internal( + File* file, + const char* key, + void* _data, + const uint16_t data_size, + FlipperFileValueType type); \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_int32.c b/lib/flipper_file/flipper_file_int32.c new file mode 100644 index 000000000..6b8ad3b87 --- /dev/null +++ b/lib/flipper_file/flipper_file_int32.c @@ -0,0 +1,42 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_int32_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size) { + return flipper_file_write_internal(file, key, _data, data_size, FlipperFileValueInt32); +}; + +bool flipper_file_read_int32( + FlipperFile* flipper_file, + const char* key, + int32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_read_internal( + flipper_file->file, key, data, data_size, FlipperFileValueInt32); +} + +bool flipper_file_write_int32( + FlipperFile* flipper_file, + const char* key, + const int32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_write_int32_internal(flipper_file->file, key, data, data_size); +} + +bool flipper_file_update_int32( + FlipperFile* flipper_file, + const char* key, + const int32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_int32_internal, key, data, data_size); +} \ No newline at end of file diff --git a/lib/flipper_file/flipper_file_string.c b/lib/flipper_file/flipper_file_string.c new file mode 100644 index 000000000..6b75fcc55 --- /dev/null +++ b/lib/flipper_file/flipper_file_string.c @@ -0,0 +1,67 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_string_internal( + File* file, + const char* key, + const void* data, + const uint16_t data_size) { + bool result = false; + (void)data_size; + + do { + result = flipper_file_write_key(file, key); + if(!result) break; + + result = file_helper_write(file, string_get_cstr(data), string_size(data)); + if(!result) break; + + result = file_helper_write_eol(file); + } while(false); + + return result; +}; + +bool flipper_file_read_string(FlipperFile* flipper_file, const char* key, string_t data) { + furi_assert(flipper_file); + + bool result = false; + if(flipper_file_seek_to_key(flipper_file->file, key)) { + if(file_helper_read_line(flipper_file->file, data)) { + result = true; + } + } + return result; +} + +bool flipper_file_write_string(FlipperFile* flipper_file, const char* key, string_t data) { + furi_assert(flipper_file); + return flipper_file_write_string_internal(flipper_file->file, key, data, 0); +} + +bool flipper_file_write_string_cstr(FlipperFile* flipper_file, const char* key, const char* data) { + bool result = false; + string_t value; + string_init_set(value, data); + result = flipper_file_write_string(flipper_file, key, value); + string_clear(value); + return result; +} + +bool flipper_file_update_string(FlipperFile* flipper_file, const char* key, string_t data) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_string_internal, key, data, 0); +} + +bool flipper_file_update_string_cstr(FlipperFile* flipper_file, const char* key, const char* data) { + bool result = false; + string_t value; + string_init_set(value, data); + result = flipper_file_update_string(flipper_file, key, value); + string_clear(value); + return result; +} diff --git a/lib/flipper_file/flipper_file_uint32.c b/lib/flipper_file/flipper_file_uint32.c new file mode 100644 index 000000000..6a3b58e4b --- /dev/null +++ b/lib/flipper_file/flipper_file_uint32.c @@ -0,0 +1,42 @@ +#include + +#include "flipper_file.h" +#include "flipper_file_i.h" +#include "flipper_file_helper.h" + +static bool flipper_file_write_uint32_internal( + File* file, + const char* key, + const void* _data, + const uint16_t data_size) { + return flipper_file_write_internal(file, key, _data, data_size, FlipperFileValueUint32); +}; + +bool flipper_file_read_uint32( + FlipperFile* flipper_file, + const char* key, + uint32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_read_internal( + flipper_file->file, key, data, data_size, FlipperFileValueUint32); +} + +bool flipper_file_write_uint32( + FlipperFile* flipper_file, + const char* key, + const uint32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_write_uint32_internal(flipper_file->file, key, data, data_size); +} + +bool flipper_file_update_uint32( + FlipperFile* flipper_file, + const char* key, + const uint32_t* data, + const uint16_t data_size) { + furi_assert(flipper_file); + return flipper_file_delete_key_and_call( + flipper_file, key, flipper_file_write_uint32_internal, key, data, data_size); +} \ No newline at end of file diff --git a/lib/lib.mk b/lib/lib.mk index f1b3ddd94..681b5b59b 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -120,3 +120,7 @@ C_SOURCES += $(wildcard $(LIB_DIR)/nanopb/*.c) # heatshrink CFLAGS += -I$(LIB_DIR)/heatshrink C_SOURCES += $(wildcard $(LIB_DIR)/heatshrink/*.c) + +# Toolbox +CFLAGS += -I$(LIB_DIR)/flipper_file +C_SOURCES += $(wildcard $(LIB_DIR)/flipper_file/*.c) \ No newline at end of file diff --git a/lib/subghz/subghz_keystore.c b/lib/subghz/subghz_keystore.c index cd9015d18..3b55b7002 100644 --- a/lib/subghz/subghz_keystore.c +++ b/lib/subghz/subghz_keystore.c @@ -4,8 +4,8 @@ #include #include -#include -#include +#include +#include #define SUBGHZ_KEYSTORE_TAG "SubGhzParser" @@ -185,7 +185,7 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { FlipperFile* flipper_file = flipper_file_alloc(storage); do { - if(!flipper_file_open_read(flipper_file, file_name)) { + if(!flipper_file_open_existing(flipper_file, file_name)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to open file for read: %s", file_name); break; } @@ -193,7 +193,7 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Missing or incorrect header"); break; } - if(!flipper_file_read_uint32(flipper_file, "Encryption", (uint32_t*)&encryption)) { + if(!flipper_file_read_uint32(flipper_file, "Encryption", (uint32_t*)&encryption, 1)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Missing encryption type"); break; } @@ -208,7 +208,7 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { if(encryption == SubGhzKeystoreEncryptionNone) { result = subghz_keystore_read_file(instance, file, NULL); } else if(encryption == SubGhzKeystoreEncryptionAES256) { - if(!flipper_file_read_hex_array(flipper_file, "IV", iv, 16)) { + if(!flipper_file_read_hex(flipper_file, "IV", iv, 16)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Missing IV"); break; } @@ -239,7 +239,7 @@ bool subghz_keystore_save(SubGhzKeystore* instance, const char* file_name, uint8 FlipperFile* flipper_file = flipper_file_alloc(storage); do { - if(!flipper_file_new_write(flipper_file, file_name)) { + if(!flipper_file_open_always(flipper_file, file_name)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to open file for write: %s", file_name); break; } @@ -248,11 +248,12 @@ bool subghz_keystore_save(SubGhzKeystore* instance, const char* file_name, uint8 FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to add header"); break; } - if(!flipper_file_write_uint32(flipper_file, "Encryption", SubGhzKeystoreEncryptionAES256)) { + SubGhzKeystoreEncryption encryption = SubGhzKeystoreEncryptionAES256; + if(!flipper_file_write_uint32(flipper_file, "Encryption", (uint32_t*)&encryption, 1)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to add Encryption"); break; } - if(!flipper_file_write_hex_array(flipper_file, "IV", iv, 16)) { + if(!flipper_file_write_hex(flipper_file, "IV", iv, 16)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to add IV"); break; } @@ -342,7 +343,7 @@ bool subghz_keystore_raw_encrypted_save( FlipperFile* input_flipper_file = flipper_file_alloc(storage); do { - if(!flipper_file_open_read(input_flipper_file, input_file_name)) { + if(!flipper_file_open_existing(input_flipper_file, input_file_name)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to open file for read: %s", input_file_name); break; } @@ -350,7 +351,7 @@ bool subghz_keystore_raw_encrypted_save( FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Missing or incorrect header"); break; } - if(!flipper_file_read_uint32(input_flipper_file, "Encryption", (uint32_t*)&encryption)) { + if(!flipper_file_read_uint32(input_flipper_file, "Encryption", (uint32_t*)&encryption, 1)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Missing encryption type"); break; } @@ -369,7 +370,7 @@ bool subghz_keystore_raw_encrypted_save( FlipperFile* output_flipper_file = flipper_file_alloc(storage); - if(!flipper_file_new_write(output_flipper_file, output_file_name)) { + if(!flipper_file_open_always(output_flipper_file, output_file_name)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to open file for write: %s", output_file_name); break; } @@ -378,12 +379,13 @@ bool subghz_keystore_raw_encrypted_save( FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to add header"); break; } + SubGhzKeystoreEncryption tmp_encryption = SubGhzKeystoreEncryptionAES256; if(!flipper_file_write_uint32( - output_flipper_file, "Encryption", SubGhzKeystoreEncryptionAES256)) { + output_flipper_file, "Encryption", (uint32_t*)&tmp_encryption, 1)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to add Encryption"); break; } - if(!flipper_file_write_hex_array(output_flipper_file, "IV", iv, 16)) { + if(!flipper_file_write_hex(output_flipper_file, "IV", iv, 16)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to add IV"); break; } @@ -480,7 +482,7 @@ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* FlipperFile* flipper_file = flipper_file_alloc(storage); do { - if(!flipper_file_open_read(flipper_file, file_name)) { + if(!flipper_file_open_existing(flipper_file, file_name)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Unable to open file for read: %s", file_name); break; } @@ -488,7 +490,7 @@ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Missing or incorrect header"); break; } - if(!flipper_file_read_uint32(flipper_file, "Encryption", (uint32_t*)&encryption)) { + if(!flipper_file_read_uint32(flipper_file, "Encryption", (uint32_t*)&encryption, 1)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Missing encryption type"); break; } @@ -506,7 +508,7 @@ bool subghz_keystore_raw_get_data(const char* file_name, size_t offset, uint8_t* } if(offset < 16) { - if(!flipper_file_read_hex_array(flipper_file, "IV", iv, 16)) { + if(!flipper_file_read_hex(flipper_file, "IV", iv, 16)) { FURI_LOG_E(SUBGHZ_KEYSTORE_TAG, "Missing IV"); break; } diff --git a/lib/toolbox/flipper-file-cpp.cpp b/lib/toolbox/flipper-file-cpp.cpp deleted file mode 100644 index 84e6d1811..000000000 --- a/lib/toolbox/flipper-file-cpp.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "flipper-file-cpp.h" - -FlipperFileCpp::FlipperFileCpp(Storage* storage) { - file = flipper_file_alloc(storage); -} - -FlipperFileCpp::~FlipperFileCpp() { - flipper_file_free(file); -} - -bool FlipperFileCpp::open_read(const char* filename) { - return flipper_file_open_read(file, filename); -} - -bool FlipperFileCpp::new_write(const char* filename) { - return flipper_file_new_write(file, filename); -} - -bool FlipperFileCpp::close() { - return flipper_file_close(file); -} - -bool FlipperFileCpp::read_header(string_t filetype, uint32_t* version) { - return flipper_file_read_header(file, filetype, version); -} - -bool FlipperFileCpp::write_header(string_t filetype, const uint32_t version) { - return flipper_file_write_header(file, filetype, version); -} - -bool FlipperFileCpp::write_header_cstr(const char* filetype, const uint32_t version) { - return flipper_file_write_header_cstr(file, filetype, version); -} - -bool FlipperFileCpp::read_string(const char* key, string_t data) { - return flipper_file_read_string(file, key, data); -} - -bool FlipperFileCpp::write_string(const char* key, string_t data) { - return flipper_file_write_string(file, key, data); -} - -bool FlipperFileCpp::write_string_cstr(const char* key, const char* data) { - return flipper_file_write_string_cstr(file, key, data); -} - -bool FlipperFileCpp::read_uint32(const char* key, uint32_t* data) { - return flipper_file_read_uint32(file, key, data); -} - -bool FlipperFileCpp::write_uint32(const char* key, const uint32_t data) { - return flipper_file_write_uint32(file, key, data); -} - -bool FlipperFileCpp::write_comment(string_t data) { - return flipper_file_write_comment(file, data); -} - -bool FlipperFileCpp::write_comment_cstr(const char* data) { - return flipper_file_write_comment_cstr(file, data); -} - -bool FlipperFileCpp::write_hex_array( - const char* key, - const uint8_t* data, - const uint16_t data_size) { - return flipper_file_write_hex_array(file, key, data, data_size); -} - -bool FlipperFileCpp::read_hex_array(const char* key, uint8_t* data, const uint16_t data_size) { - return flipper_file_read_hex_array(file, key, data, data_size); -} diff --git a/lib/toolbox/flipper-file-cpp.h b/lib/toolbox/flipper-file-cpp.h deleted file mode 100644 index bac19cbae..000000000 --- a/lib/toolbox/flipper-file-cpp.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -#include "flipper-file.h" - -class FlipperFileCpp { -private: - FlipperFile* file; - -public: - FlipperFileCpp(Storage* storage); - ~FlipperFileCpp(); - - bool open_read(const char* filename); - - bool new_write(const char* filename); - - bool close(); - - bool read_header(string_t filetype, uint32_t* version); - - bool write_header(string_t filetype, const uint32_t version); - - bool write_header_cstr(const char* filetype, const uint32_t version); - - bool read_string(const char* key, string_t data); - - bool write_string(const char* key, string_t data); - - bool write_string_cstr(const char* key, const char* data); - - bool read_uint32(const char* key, uint32_t* data); - - bool write_uint32(const char* key, const uint32_t data); - - bool write_comment(string_t data); - - bool write_comment_cstr(const char* data); - - bool write_hex_array(const char* key, const uint8_t* data, const uint16_t data_size); - - bool read_hex_array(const char* key, uint8_t* data, const uint16_t data_size); -}; diff --git a/lib/toolbox/flipper-file.c b/lib/toolbox/flipper-file.c deleted file mode 100644 index 45bcc0ed4..000000000 --- a/lib/toolbox/flipper-file.c +++ /dev/null @@ -1,471 +0,0 @@ -#include -#include "flipper-file.h" -#include -#include - -struct FlipperFile { - File* file; -}; - -const char* flipper_file_filetype_key = "Filetype"; -const char* flipper_file_version_key = "Version"; -const char flipper_file_eoln = '\n'; -const char flipper_file_eolr = '\r'; -const char flipper_file_delimiter = ':'; -const char flipper_file_comment = '#'; - -/** - * Writes data to a file as a hexadecimal array. - * @param file - * @param data - * @param data_size - * @return true on success write - */ -bool flipper_file_write_hex_internal(File* file, const uint8_t* data, const uint16_t data_size) { - const uint8_t byte_text_size = 3; - char byte_text[byte_text_size]; - - bool result = true; - uint16_t bytes_written; - for(uint8_t i = 0; i < data_size; i++) { - snprintf(byte_text, byte_text_size, "%02X", data[i]); - - if(i != 0) { - // space - const char space = ' '; - bytes_written = storage_file_write(file, &space, sizeof(space)); - if(bytes_written != sizeof(space)) { - result = false; - break; - } - } - - bytes_written = storage_file_write(file, &byte_text, strlen(byte_text)); - if(bytes_written != strlen(byte_text)) { - result = false; - break; - } - } - - return result; -} - -/** - * Reads a valid key from a file as a string. - * After reading, the rw pointer will be on the flipper_file_delimiter symbol. - * Optimized not to read comments and values into RAM. - * @param file - * @param key - * @return true on success read - */ -bool flipper_file_read_valid_key(File* file, string_t key) { - string_clean(key); - bool found = false; - bool error = false; - const uint8_t buffer_size = 32; - uint8_t buffer[buffer_size]; - bool accumulate = true; - bool new_line = true; - - while(true) { - uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); - if(bytes_were_read == 0) break; - - for(uint16_t i = 0; i < bytes_were_read; i++) { - if(buffer[i] == flipper_file_eoln) { - // EOL found, clean data, start accumulating data and set the new_line flag - string_clean(key); - accumulate = true; - new_line = true; - } else if(buffer[i] == flipper_file_eolr) { - // Ignore - } else if(buffer[i] == flipper_file_comment && new_line) { - // if there is a comment character and we are at the beginning of a new line - // do not accumulate comment data and reset the new_line flag - accumulate = false; - new_line = false; - } else if(buffer[i] == flipper_file_delimiter) { - if(new_line) { - // we are on a "new line" and found the delimiter - // this can only be if we have previously found some kind of key, so - // clear the data, set the flag that we no longer want to accumulate data - // and reset the new_line flag - string_clean(key); - accumulate = false; - new_line = false; - } else { - // parse the delimiter only if we are accumulating data - if(accumulate) { - // we found the delimiter, move the rw pointer to the correct location - // and signal that we have found something - // TODO negative seek - uint64_t position = storage_file_tell(file); - position = position - bytes_were_read + i; - if(!storage_file_seek(file, position, true)) { - error = true; - break; - } - - found = true; - break; - } - } - } else { - // just new symbol, reset the new_line flag - new_line = false; - if(accumulate) { - // and accumulate data if we want - string_push_back(key, buffer[i]); - } - } - } - - if(found || error) break; - } - - return found; -} - -/** - * Sets rw pointer to the data after the key - * @param file - * @param key - * @return true if key was found - */ -bool flipper_file_seek_to_key(File* file, const char* key) { - bool found = false; - string_t readed_key; - - string_init(readed_key); - - // TODO optimize this to search from a stored rw pointer - if(storage_file_seek(file, 0, true)) { - while(!storage_file_eof(file)) { - if(flipper_file_read_valid_key(file, readed_key)) { - if(string_cmp_str(readed_key, key) == 0) { - uint64_t position = storage_file_tell(file); - if(!storage_file_seek(file, position + 2, true)) break; - - found = true; - break; - } - } - } - } - string_clear(readed_key); - - return found; -} - -/** - * Reads data as a string from the stored rw pointer to the \r or \n symbol position - * @param file - * @param str_result - * @return true on success read - */ -bool flipper_file_read_until(File* file, string_t str_result) { - string_clean(str_result); - const uint8_t buffer_size = 32; - uint8_t buffer[buffer_size]; - - do { - uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); - if(bytes_were_read == 0) break; - - bool result = false; - bool error = false; - for(uint16_t i = 0; i < bytes_were_read; i++) { - if(buffer[i] == flipper_file_eoln) { - // TODO negative seek - uint64_t position = storage_file_tell(file); - position = position - bytes_were_read + i; - if(!storage_file_seek(file, position, true)) { - error = true; - break; - } - - result = true; - break; - } else if(buffer[i] == flipper_file_eolr) { - // Ignore - } else { - string_push_back(str_result, buffer[i]); - } - } - - if(result || error) { - break; - } - } while(true); - - return string_size(str_result) != 0; -} - -/** - * Reads single hexadecimal data from a file to byte - * @param file - * @param byte - * @return bool - */ -bool flipper_file_read_hex_byte(File* file, uint8_t* byte) { - uint8_t hi_nibble_value, low_nibble_value; - uint8_t text[3]; - bool result = false; - - uint16_t bytes_were_read = storage_file_read(file, text, 3); - if(bytes_were_read >= 2) { - if(text[0] != ' ') { - if(hex_char_to_hex_nibble(text[0], &hi_nibble_value) && - hex_char_to_hex_nibble(text[1], &low_nibble_value)) { - *byte = (hi_nibble_value << 4) | low_nibble_value; - result = true; - } - } else { - if(hex_char_to_hex_nibble(text[1], &hi_nibble_value) && - hex_char_to_hex_nibble(text[2], &low_nibble_value)) { - *byte = (hi_nibble_value << 4) | low_nibble_value; - result = true; - } - } - } - - return result; -} - -FlipperFile* flipper_file_alloc(Storage* storage) { - FlipperFile* flipper_file = malloc(sizeof(FlipperFile)); - flipper_file->file = storage_file_alloc(storage); - return flipper_file; -} - -void flipper_file_free(FlipperFile* flipper_file) { - furi_assert(flipper_file); - if(storage_file_is_open(flipper_file->file)) { - storage_file_close(flipper_file->file); - } - storage_file_free(flipper_file->file); - free(flipper_file); -} - -bool flipper_file_open_read(FlipperFile* flipper_file, const char* filename) { - furi_assert(flipper_file); - bool result = storage_file_open(flipper_file->file, filename, FSAM_READ, FSOM_OPEN_EXISTING); - return result; -} - -bool flipper_file_new_write(FlipperFile* flipper_file, const char* filename) { - furi_assert(flipper_file); - bool result = storage_file_open(flipper_file->file, filename, FSAM_WRITE, FSOM_CREATE_ALWAYS); - return result; -} - -bool flipper_file_close(FlipperFile* flipper_file) { - furi_assert(flipper_file); - if(storage_file_is_open(flipper_file->file)) { - return storage_file_close(flipper_file->file); - } - return true; -} - -bool flipper_file_read_header(FlipperFile* flipper_file, string_t filetype, uint32_t* version) { - bool result = false; - do { - result = flipper_file_read_string(flipper_file, flipper_file_filetype_key, filetype); - if(!result) break; - result = flipper_file_read_uint32(flipper_file, flipper_file_version_key, version); - if(!result) break; - } while(false); - - return result; -} - -bool flipper_file_write_header( - FlipperFile* flipper_file, - string_t filetype, - const uint32_t version) { - bool result = false; - do { - result = flipper_file_write_string(flipper_file, flipper_file_filetype_key, filetype); - if(!result) break; - result = flipper_file_write_uint32(flipper_file, flipper_file_version_key, version); - if(!result) break; - } while(false); - - return result; -} - -bool flipper_file_write_header_cstr( - FlipperFile* flipper_file, - const char* filetype, - const uint32_t version) { - bool result = false; - string_t value; - string_init_set(value, filetype); - result = flipper_file_write_header(flipper_file, value, version); - string_clear(value); - return result; -} - -bool flipper_file_read_string(FlipperFile* flipper_file, const char* key, string_t data) { - furi_assert(flipper_file); - - bool result = false; - if(flipper_file_seek_to_key(flipper_file->file, key)) { - if(flipper_file_read_until(flipper_file->file, data)) { - result = true; - } - } - return result; -} - -bool flipper_file_write_string(FlipperFile* flipper_file, const char* key, string_t data) { - furi_assert(flipper_file); - - bool result = false; - do { - uint16_t bytes_written; - bytes_written = storage_file_write(flipper_file->file, key, strlen(key)); - if(bytes_written != strlen(key)) break; - - const char delimiter_buffer[2] = {flipper_file_delimiter, ' '}; - bytes_written = - storage_file_write(flipper_file->file, delimiter_buffer, sizeof(delimiter_buffer)); - if(bytes_written != sizeof(delimiter_buffer)) break; - - bytes_written = - storage_file_write(flipper_file->file, string_get_cstr(data), string_size(data)); - if(bytes_written != string_size(data)) break; - - bytes_written = - storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln)); - if(bytes_written != sizeof(flipper_file_eoln)) break; - - result = true; - } while(false); - - return result; -} - -bool flipper_file_write_string_cstr(FlipperFile* flipper_file, const char* key, const char* data) { - bool result = false; - string_t value; - string_init_set(value, data); - result = flipper_file_write_string(flipper_file, key, value); - string_clear(value); - return result; -} - -bool flipper_file_read_uint32(FlipperFile* flipper_file, const char* key, uint32_t* data) { - bool result = false; - string_t value; - string_init(value); - - result = flipper_file_read_string(flipper_file, key, value); - if(result) { - int ret = sscanf(string_get_cstr(value), "%" PRIu32, data); - if(ret != 1) result = false; - } - - string_clear(value); - return result; -} - -bool flipper_file_write_uint32(FlipperFile* flipper_file, const char* key, const uint32_t data) { - bool result = false; - string_t value; - string_init_printf(value, "%" PRIu32, data); - result = flipper_file_write_string(flipper_file, key, value); - string_clear(value); - return result; -} - -bool flipper_file_write_comment(FlipperFile* flipper_file, string_t data) { - furi_assert(flipper_file); - - bool result = false; - do { - uint16_t bytes_written; - const char comment_buffer[2] = {flipper_file_comment, ' '}; - bytes_written = - storage_file_write(flipper_file->file, comment_buffer, sizeof(comment_buffer)); - if(bytes_written != sizeof(comment_buffer)) break; - - bytes_written = - storage_file_write(flipper_file->file, string_get_cstr(data), string_size(data)); - if(bytes_written != string_size(data)) break; - - bytes_written = - storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln)); - if(bytes_written != sizeof(flipper_file_eoln)) break; - - result = true; - } while(false); - - return result; -} - -bool flipper_file_write_comment_cstr(FlipperFile* flipper_file, const char* data) { - bool result = false; - string_t value; - string_init_set(value, data); - result = flipper_file_write_comment(flipper_file, value); - string_clear(value); - return result; -} - -bool flipper_file_write_hex_array( - FlipperFile* flipper_file, - const char* key, - const uint8_t* data, - const uint16_t data_size) { - furi_assert(flipper_file); - - bool result = false; - do { - uint16_t bytes_written; - bytes_written = storage_file_write(flipper_file->file, key, strlen(key)); - if(bytes_written != strlen(key)) break; - - const char delimiter_buffer[2] = {flipper_file_delimiter, ' '}; - bytes_written = - storage_file_write(flipper_file->file, delimiter_buffer, sizeof(delimiter_buffer)); - if(bytes_written != sizeof(delimiter_buffer)) break; - - if(!flipper_file_write_hex_internal(flipper_file->file, data, data_size)) break; - - bytes_written = - storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln)); - if(bytes_written != sizeof(flipper_file_eoln)) break; - - result = true; - } while(false); - - return result; -} - -bool flipper_file_read_hex_array( - FlipperFile* flipper_file, - const char* key, - uint8_t* data, - const uint16_t data_size) { - furi_assert(flipper_file); - - bool result = false; - if(flipper_file_seek_to_key(flipper_file->file, key)) { - result = true; - for(uint16_t i = 0; i < data_size; i++) { - if(!flipper_file_read_hex_byte(flipper_file->file, &data[i])) { - result = false; - break; - } - } - } - return result; -} - -File* flipper_file_get_file(FlipperFile* flipper_file) { - furi_assert(flipper_file); - furi_assert(flipper_file->file); - - return flipper_file->file; -} diff --git a/lib/toolbox/hex.c b/lib/toolbox/hex.c index cc869beb8..8b443e124 100644 --- a/lib/toolbox/hex.c +++ b/lib/toolbox/hex.c @@ -13,4 +13,16 @@ bool hex_char_to_hex_nibble(char c, uint8_t* nibble) { } else { return false; } +} + +bool hex_chars_to_uint8(char hi, char low, uint8_t* value) { + uint8_t hi_nibble_value, low_nibble_value; + + if(hex_char_to_hex_nibble(hi, &hi_nibble_value) && + hex_char_to_hex_nibble(low, &low_nibble_value)) { + *value = (hi_nibble_value << 4) | low_nibble_value; + return true; + } else { + return false; + } } \ No newline at end of file diff --git a/lib/toolbox/hex.h b/lib/toolbox/hex.h index 2c5d84041..ac67549af 100644 --- a/lib/toolbox/hex.h +++ b/lib/toolbox/hex.h @@ -7,14 +7,22 @@ extern "C" { #endif /** - * @brief Convert ASCII hex value to nibble - * + * Convert ASCII hex value to nibble * @param c ASCII character * @param nibble nibble pointer, output * @return bool conversion status */ bool hex_char_to_hex_nibble(char c, uint8_t* nibble); +/** + * Convert ASCII hex values to byte + * @param hi hi nibble text + * @param low low nibble text + * @param value output value + * @return bool conversion status + */ +bool hex_chars_to_uint8(char hi, char low, uint8_t* value); + #ifdef __cplusplus } #endif \ No newline at end of file