#include "keys_dict.h" #include #include #include #include #include #define TAG "KeysDict" struct KeysDict { Stream* stream; size_t key_size; size_t key_size_symbols; size_t total_keys; }; static inline void keys_dict_add_ending_new_line(KeysDict* instance) { if(stream_seek(instance->stream, -1, StreamOffsetFromEnd)) { uint8_t last_char = 0; // Check if the last char is new line or add a new line if(stream_read(instance->stream, &last_char, 1) == 1 && last_char != '\n') { FURI_LOG_D(TAG, "Adding new line ending"); stream_write_char(instance->stream, '\n'); } stream_rewind(instance->stream); } } static bool keys_dict_read_key_line(KeysDict* instance, FuriString* line, bool* is_endfile) { if(stream_read_line(instance->stream, line) == false) { *is_endfile = true; } else { FURI_LOG_T( TAG, "Read line: %s, len: %zu", furi_string_get_cstr(line), furi_string_size(line)); bool is_comment = furi_string_get_char(line, 0) == '#'; if(!is_comment) { furi_string_left(line, instance->key_size_symbols - 1); } bool is_correct_size = furi_string_size(line) == instance->key_size_symbols - 1; return !is_comment && is_correct_size; } return false; } bool keys_dict_check_presence(const char* path) { furi_assert(path); Storage* storage = furi_record_open(RECORD_STORAGE); bool dict_present = storage_common_stat(storage, path, NULL) == FSE_OK; furi_record_close(RECORD_STORAGE); return dict_present; } KeysDict* keys_dict_alloc(const char* path, KeysDictMode mode, size_t key_size) { furi_assert(path); furi_assert(key_size > 0); KeysDict* instance = malloc(sizeof(KeysDict)); Storage* storage = furi_record_open(RECORD_STORAGE); furi_assert(storage); instance->stream = buffered_file_stream_alloc(storage); furi_assert(instance->stream); FS_OpenMode open_mode = (mode == KeysDictModeOpenAlways) ? FSOM_OPEN_ALWAYS : FSOM_OPEN_EXISTING; // Byte = 2 symbols + 1 end of line instance->key_size = key_size; instance->key_size_symbols = key_size * 2 + 1; instance->total_keys = 0; bool file_exists = buffered_file_stream_open(instance->stream, path, FSAM_READ_WRITE, open_mode); if(!file_exists) { buffered_file_stream_close(instance->stream); } else { // Eventually add new line character in the last line to avoid skipping keys keys_dict_add_ending_new_line(instance); } FuriString* line = furi_string_alloc(); bool is_endfile = false; // In this loop we only count the entries in the file // We prefer not to load the whole file in memory for space reasons while(file_exists && !is_endfile) { bool read_key = keys_dict_read_key_line(instance, line, &is_endfile); if(read_key) { instance->total_keys++; } } stream_rewind(instance->stream); FURI_LOG_I(TAG, "Loaded dictionary with %zu keys", instance->total_keys); furi_string_free(line); return instance; } void keys_dict_free(KeysDict* instance) { furi_assert(instance); furi_assert(instance->stream); buffered_file_stream_close(instance->stream); stream_free(instance->stream); free(instance); furi_record_close(RECORD_STORAGE); } static void keys_dict_int_to_str(KeysDict* instance, const uint8_t* key_int, FuriString* key_str) { furi_assert(instance); furi_assert(key_str); furi_assert(key_int); furi_string_reset(key_str); for(size_t i = 0; i < instance->key_size; i++) furi_string_cat_printf(key_str, "%02X", key_int[i]); } static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint64_t* key_int) { furi_assert(instance); furi_assert(key_str); furi_assert(key_int); uint8_t key_byte_tmp; char h, l; *key_int = 0ULL; for(size_t i = 0; i < instance->key_size_symbols - 1; i += 2) { h = furi_string_get_char(key_str, i); l = furi_string_get_char(key_str, i + 1); args_char_to_hex(h, l, &key_byte_tmp); *key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2)); } } size_t keys_dict_get_total_keys(KeysDict* instance) { furi_assert(instance); return instance->total_keys; } bool keys_dict_rewind(KeysDict* instance) { furi_assert(instance); furi_assert(instance->stream); return stream_rewind(instance->stream); } static bool keys_dict_get_next_key_str(KeysDict* instance, FuriString* key) { furi_assert(instance); furi_assert(instance->stream); furi_assert(key); bool key_read = false; bool is_endfile = false; furi_string_reset(key); while(!key_read && !is_endfile) key_read = keys_dict_read_key_line(instance, key, &is_endfile); return key_read; } bool keys_dict_get_next_key(KeysDict* instance, uint8_t* key, size_t key_size) { furi_assert(instance); furi_assert(instance->stream); furi_assert(instance->key_size == key_size); furi_assert(key); FuriString* temp_key = furi_string_alloc(); bool key_read = keys_dict_get_next_key_str(instance, temp_key); if(key_read) { size_t tmp_len = key_size; uint64_t key_int = 0; keys_dict_str_to_int(instance, temp_key, &key_int); while(tmp_len--) { key[tmp_len] = (uint8_t)key_int; key_int >>= 8; } } furi_string_free(temp_key); return key_read; } static bool keys_dict_is_key_present_str(KeysDict* instance, FuriString* key) { furi_assert(instance); furi_assert(instance->stream); furi_assert(key); FuriString* line = furi_string_alloc(); bool is_endfile = false; bool line_found = false; uint32_t actual_pos = stream_tell(instance->stream); stream_rewind(instance->stream); while(!line_found && !is_endfile) line_found = // The line is found if the line was read and the key is equal to the line (keys_dict_read_key_line(instance, line, &is_endfile)) && (furi_string_equal(key, line)); furi_string_free(line); // Restore the position of the stream stream_seek(instance->stream, actual_pos, StreamOffsetFromStart); return line_found; } bool keys_dict_is_key_present(KeysDict* instance, const uint8_t* key, size_t key_size) { furi_assert(instance); furi_assert(instance->stream); furi_assert(instance->key_size == key_size); furi_assert(key); FuriString* temp_key = furi_string_alloc(); keys_dict_int_to_str(instance, key, temp_key); bool key_found = keys_dict_is_key_present_str(instance, temp_key); furi_string_free(temp_key); return key_found; } static bool keys_dict_add_key_str(KeysDict* instance, FuriString* key) { furi_assert(instance); furi_assert(instance->stream); furi_assert(key); furi_string_cat_str(key, "\n"); bool key_added = false; uint32_t actual_pos = stream_tell(instance->stream); if(stream_seek(instance->stream, 0, StreamOffsetFromEnd) && stream_insert_string(instance->stream, key)) { instance->total_keys++; key_added = true; } stream_seek(instance->stream, actual_pos, StreamOffsetFromStart); return key_added; } bool keys_dict_add_key(KeysDict* instance, const uint8_t* key, size_t key_size) { furi_assert(instance); furi_assert(instance->stream); furi_assert(instance->key_size == key_size); furi_assert(key); FuriString* temp_key = furi_string_alloc(); furi_assert(temp_key); keys_dict_int_to_str(instance, key, temp_key); bool key_added = keys_dict_add_key_str(instance, temp_key); FURI_LOG_I(TAG, "Added key %s", furi_string_get_cstr(temp_key)); furi_string_free(temp_key); return key_added; } bool keys_dict_delete_key(KeysDict* instance, const uint8_t* key, size_t key_size) { furi_assert(instance); furi_assert(instance->stream); furi_assert(instance->key_size == key_size); furi_assert(key); bool key_removed = false; uint8_t* temp_key = malloc(key_size); stream_rewind(instance->stream); while(!key_removed) { if(!keys_dict_get_next_key(instance, temp_key, key_size)) { break; } if(memcmp(temp_key, key, key_size) == 0) { stream_seek(instance->stream, -instance->key_size_symbols, StreamOffsetFromCurrent); if(stream_delete(instance->stream, instance->key_size_symbols) == false) { break; } instance->total_keys--; key_removed = true; } } FuriString* tmp = furi_string_alloc(); keys_dict_int_to_str(instance, key, tmp); FURI_LOG_I(TAG, "Removed key %s", furi_string_get_cstr(tmp)); furi_string_free(tmp); stream_rewind(instance->stream); free(temp_key); return key_removed; }