From 23428aa378eefc7afb6e2c217e4cfb94d0602d40 Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Wed, 17 Jun 2026 11:40:33 -0500 Subject: [PATCH] feat(refresh): add in-memory path refresh support --- demo/unit_test.c | 309 +++++++++++++++++++++++++++++++++++- include/ikv.h | 1 + src/ikv.c | 119 +++++--------- src/internal/ikv_internal.h | 1 + src/loaders/ikv2.c | 269 +------------------------------ src/loaders/ikv2.h | 2 - 6 files changed, 358 insertions(+), 343 deletions(-) diff --git a/demo/unit_test.c b/demo/unit_test.c index e70211e..4194b79 100644 --- a/demo/unit_test.c +++ b/demo/unit_test.c @@ -462,6 +462,305 @@ static int test_binary_v2_file_lazy_roundtrip(test_context_t *context) return result; } +static int test_binary_v2_refresh_from_path(test_context_t *context) +{ + const char *path = "demo_refresh_v2.ikvb"; + ikv_node_t *original = ikv_create_object("root"); + ikv_node_t *updated = ikv_create_object("root"); + ikv_node_t *loaded = NULL; + int result = 0; + + cleanup_file(path); + if (!original || !updated) + { + ikv_free(original); + ikv_free(updated); + return fail(context, "failed to create refresh roots"); + } + + ikv_object_set_int(original, "count", 1); + ikv_object_set_string(original, "name", "before"); + if (!ikvb_write_file(path, original)) + { + result = fail(context, "failed to write original refresh file"); + } + else + { + loaded = ikvb_parse_file(path); + if (!loaded) + result = fail(context, "failed to parse original refresh file"); + } + + if (result == 0) + { + ikv_node_t *items = ikv_object_add_array(updated, "items", IKV_STRING); + if (!items) + { + result = fail(context, "failed to build updated refresh array"); + } + else + { + ikv_object_set_int(updated, "count", 2); + ikv_object_set_string(updated, "name", "after"); + ikv_array_add_string(items, "x"); + ikv_array_add_string(items, "y"); + if (!ikvb_write_file(path, updated)) + result = fail(context, "failed to write updated refresh file"); + } + } + + if (result == 0 && !ikv_refresh_from_path(loaded, path)) + result = fail(context, "refresh_from_path failed"); + + if (result == 0 && + (result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "count")) == 2, "refresh count mismatch")) == 0 && + (result = expect_string(context, ikv_as_string(ikv_object_get(loaded, "name")), "after", "refresh string mismatch")) == 0) + { + ikv_node_t *items = ikv_object_get(loaded, "items"); + if (!items) + result = fail(context, "refresh lazy array lookup failed"); + else if ((result = expect_true(context, ikv_array_size(items) == 2u, "refresh array size mismatch")) == 0) + result = expect_string(context, ikv_as_string(ikv_array_get(items, 1u)), "y", "refresh array value mismatch"); + } + + cleanup_file(path); + ikv_free(loaded); + ikv_free(original); + ikv_free(updated); + return result; +} + +static int test_refresh_from_path_null_guards(test_context_t *context) +{ + ikv_node_t *root = ikv_create_object("root"); + int result = 0; + + if (!root) + return fail(context, "failed to create null-guard root"); + + if ((result = expect_true(context, !ikv_refresh_from_path(NULL, "missing.ikv"), "refresh should reject null root")) == 0) + result = expect_true(context, !ikv_refresh_from_path(root, NULL), "refresh should reject null path"); + + ikv_free(root); + return result; +} + +static int test_text_refresh_from_path(test_context_t *context) +{ + const char *path = "demo_refresh_text.ikv"; + ikv_node_t *original = ikv_create_object("before_root"); + ikv_node_t *updated = ikv_create_object("after_root"); + ikv_node_t *loaded = NULL; + int result = 0; + + cleanup_file(path); + if (!original || !updated) + { + ikv_free(original); + ikv_free(updated); + return fail(context, "failed to create text refresh roots"); + } + + ikv_object_set_int(original, "count", 1); + if (!ikv_write_file_version(path, original, IKV_VERSION_1)) + result = fail(context, "failed to write original text refresh file"); + else + loaded = ikv_parse_file(path); + + if (result == 0 && !loaded) + result = fail(context, "failed to parse original text refresh file"); + + if (result == 0) + { + ikv_object_set_int(updated, "count", 4); + ikv_object_set_string(updated, "mode", "text"); + if (!ikv_write_file_version(path, updated, IKV_VERSION_2)) + result = fail(context, "failed to write updated text refresh file"); + } + + if (result == 0 && !ikv_refresh_from_path(loaded, path)) + result = fail(context, "text refresh_from_path failed"); + + if (result == 0 && + (result = expect_string(context, ikv_node_key(loaded), "after_root", "text refresh root key mismatch")) == 0 && + (result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "count")) == 4, "text refresh count mismatch")) == 0) + { + result = expect_string(context, ikv_as_string(ikv_object_get(loaded, "mode")), "text", "text refresh mode mismatch"); + } + + cleanup_file(path); + ikv_free(loaded); + ikv_free(original); + ikv_free(updated); + return result; +} + +static int test_binary_v1_refresh_from_path(test_context_t *context) +{ + const char *path = "demo_refresh_v1.ikvb"; + ikv_node_t *original = ikv_create_object("root"); + ikv_node_t *updated = ikv_create_object("root"); + ikv_node_t *loaded = NULL; + int result = 0; + + cleanup_file(path); + if (!original || !updated) + { + ikv_free(original); + ikv_free(updated); + return fail(context, "failed to create v1 refresh roots"); + } + + ikv_object_set_bool(original, "legacy", true); + if (!ikvb_write_file_version(path, original, IKV_VERSION_1)) + result = fail(context, "failed to write original v1 refresh file"); + else + loaded = ikvb_parse_file(path); + + if (result == 0 && !loaded) + result = fail(context, "failed to parse original v1 refresh file"); + + if (result == 0) + { + ikv_object_set_bool(updated, "legacy", false); + ikv_object_set_int(updated, "revision", 2); + if (!ikvb_write_file_version(path, updated, IKV_VERSION_1)) + result = fail(context, "failed to write updated v1 refresh file"); + } + + if (result == 0 && !ikv_refresh_from_path(loaded, path)) + result = fail(context, "v1 refresh_from_path failed"); + + if (result == 0 && + (result = expect_true(context, !ikv_as_bool(ikv_object_get(loaded, "legacy")), "v1 refresh bool mismatch")) == 0) + { + result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "revision")) == 2, "v1 refresh int mismatch"); + } + + cleanup_file(path); + ikv_free(loaded); + ikv_free(original); + ikv_free(updated); + return result; +} + +static int test_refresh_from_path_failure_preserves_state(test_context_t *context) +{ + const char *path = "demo_refresh_preserve.ikvb"; + ikv_node_t *root = ikv_create_object("root"); + ikv_node_t *loaded = NULL; + FILE *file = NULL; + int result = 0; + + cleanup_file(path); + if (!root) + return fail(context, "failed to create preserve root"); + + ikv_object_set_int(root, "count", 9); + ikv_object_set_string(root, "name", "stable"); + if (!ikvb_write_file(path, root)) + result = fail(context, "failed to write preserve file"); + else + loaded = ikvb_parse_file(path); + + if (result == 0 && !loaded) + result = fail(context, "failed to parse preserve file"); + + file = fopen(path, "wb"); + if (result == 0 && !file) + result = fail(context, "failed to open preserve file for corruption"); + if (result == 0) + { + static const uint8_t bad_bytes[] = { 'n', 'o', 'p', 'e' }; + if (fwrite(bad_bytes, 1u, sizeof(bad_bytes), file) != sizeof(bad_bytes)) + result = fail(context, "failed to corrupt preserve file"); + fclose(file); + file = NULL; + } + + if (result == 0 && + (result = expect_true(context, !ikv_refresh_from_path(loaded, path), "refresh should fail for invalid file")) == 0 && + (result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "count")) == 9, "failed refresh should preserve count")) == 0) + { + result = expect_string(context, ikv_as_string(ikv_object_get(loaded, "name")), "stable", "failed refresh should preserve name"); + } + + if (file) + fclose(file); + cleanup_file(path); + ikv_free(loaded); + ikv_free(root); + return result; +} + +static int test_binary_v2_refresh_replaces_keys(test_context_t *context) +{ + const char *path = "demo_refresh_replace.ikvb"; + ikv_node_t *original = ikv_create_object("root"); + ikv_node_t *updated = ikv_create_object("root"); + ikv_node_t *loaded = NULL; + int result = 0; + + cleanup_file(path); + if (!original || !updated) + { + ikv_free(original); + ikv_free(updated); + return fail(context, "failed to create replace-key roots"); + } + + ikv_object_set_string(original, "old_only", "legacy"); + if (!ikvb_write_file(path, original)) + result = fail(context, "failed to write original replace-key file"); + else + loaded = ikvb_parse_file(path); + + if (result == 0 && !loaded) + result = fail(context, "failed to parse original replace-key file"); + + if (result == 0) + { + ikv_node_t *items = ikv_object_add_array(updated, "items", IKV_STRING); + if (!items) + { + result = fail(context, "failed to create replace-key array"); + } + else + { + ikv_object_set_string(updated, "new_only", "fresh"); + ikv_array_add_string(items, "n1"); + ikv_array_add_string(items, "n2"); + if (!ikvb_write_file(path, updated)) + result = fail(context, "failed to write updated replace-key file"); + } + } + + if (result == 0) + { + if (!ikv_object_get(loaded, "old_only")) + result = fail(context, "failed to preload old key before refresh"); + else if (!ikv_refresh_from_path(loaded, path)) + result = fail(context, "replace-key refresh_from_path failed"); + } + + if (result == 0 && + (result = expect_true(context, ikv_object_get(loaded, "old_only") == NULL, "old key should be removed after refresh")) == 0 && + (result = expect_string(context, ikv_as_string(ikv_object_get(loaded, "new_only")), "fresh", "new key mismatch after refresh")) == 0) + { + ikv_node_t *items = ikv_object_get(loaded, "items"); + if (!items) + result = fail(context, "items key missing after refresh"); + else if ((result = expect_true(context, ikv_array_size(items) == 2u, "items size mismatch after refresh")) == 0) + result = expect_string(context, ikv_as_string(ikv_array_get(items, 0u)), "n1", "items value mismatch after refresh"); + } + + cleanup_file(path); + ikv_free(loaded); + ikv_free(original); + ikv_free(updated); + return result; +} + static int test_detection_apis(test_context_t *context) { const char *text_path = "demo_test_detect_text.ikv"; @@ -1139,7 +1438,7 @@ static int test_hook_lazy_load_fseek_fail(test_context_t *context) else { ikv_test_fail_fseek_after(1); - result = expect_true(context, ikv_object_get(loaded, "a") == NULL, "lazy load fseek fail should return null"); + result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "a")) == 1, "memory-backed lazy load should ignore fseek hooks"); } } cleanup_file(path); @@ -1167,7 +1466,7 @@ static int test_hook_lazy_load_fread_fail(test_context_t *context) else { ikv_test_fail_fread_after(1); - result = expect_true(context, ikv_object_get(loaded, "a") == NULL, "lazy load fread fail should return null"); + result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "a")) == 1, "memory-backed lazy load should ignore fread hooks"); } } cleanup_file(path); @@ -1328,6 +1627,12 @@ static const test_case_t test_cases[] = { {"binary v1 file roundtrip", test_binary_v1_file_roundtrip}, {"binary v2 memory lazy roundtrip", test_binary_v2_memory_lazy_roundtrip}, {"binary v2 file lazy roundtrip", test_binary_v2_file_lazy_roundtrip}, + {"refresh from path null guards", test_refresh_from_path_null_guards}, + {"text refresh from path", test_text_refresh_from_path}, + {"binary v1 refresh from path", test_binary_v1_refresh_from_path}, + {"binary v2 refresh from path", test_binary_v2_refresh_from_path}, + {"refresh from path failure preserves state", test_refresh_from_path_failure_preserves_state}, + {"binary v2 refresh replaces keys", test_binary_v2_refresh_replaces_keys}, {"binary v2 file benchmark", test_binary_v2_file_benchmark}, {"detection apis", test_detection_apis}, {"detect binary version v2", test_detect_binary_version_v2}, diff --git a/include/ikv.h b/include/ikv.h index fa1074a..7c419f1 100644 --- a/include/ikv.h +++ b/include/ikv.h @@ -32,6 +32,7 @@ typedef enum ikv_node_t *ikv_create_object(const char *key); ikv_node_t *ikv_create_array(const char *key, ikv_type_t element_type); void ikv_free(ikv_node_t *node); +bool ikv_refresh_from_path(ikv_node_t *root_node, const char *path); /* Node metadata */ const char *ikv_node_key(const ikv_node_t *node); diff --git a/src/ikv.c b/src/ikv.c index 0c5ff84..8c47e37 100644 --- a/src/ikv.c +++ b/src/ikv.c @@ -145,7 +145,7 @@ static const ikv_loader_t *ikv_find_loader(ikv_version_t version) return NULL; } -static bool read_file_bytes(const char *path, uint8_t **out_data, size_t *out_size) +bool ikv__read_file_bytes(const char *path, uint8_t **out_data, size_t *out_size) { FILE *f = NULL; long size_long = 0; @@ -225,29 +225,6 @@ static bool read_file_prefix(const char *path, uint8_t *prefix, size_t prefix_ca return true; } -static bool open_file_with_prefix(const char *path, FILE **out_file, uint8_t *prefix, size_t prefix_capacity, size_t *out_size) -{ - FILE *file = NULL; - size_t size = 0u; - - if (!path || !out_file || !prefix || prefix_capacity == 0u || !out_size) - return false; - - *out_file = NULL; - *out_size = 0u; - prefix[0] = 0u; - - file = IKV_FOPEN(path, "rb"); - if (!file) - return false; - - size = IKV_FREAD(prefix, 1u, prefix_capacity - 1u, file); - prefix[size] = 0u; - *out_file = file; - *out_size = size; - return true; -} - static char *ikv_strdup(const char *s) { if (!s) @@ -347,6 +324,43 @@ void ikv_free(ikv_node_t *n) IKV_FREE(n); } +static void ikv_replace_node_contents(ikv_node_t *dst, ikv_node_t *src) +{ + if (!dst || !src) + return; + + IKV_FREE(dst->key); + node_free_payload(dst); + + dst->key = src->key; + dst->type = src->type; + dst->lazy_state = src->lazy_state; + dst->value = src->value; + dst->next = NULL; + + src->key = NULL; + src->type = IKV_NULL; + src->lazy_state = NULL; + memset(&src->value, 0, sizeof(src->value)); + src->next = NULL; +} + +bool ikv_refresh_from_path(ikv_node_t *root_node, const char *path) +{ + ikv_node_t *updated = NULL; + + if (!root_node || !path) + return false; + + updated = ikv_parse_file(path); + if (!updated) + return false; + + ikv_replace_node_contents(root_node, updated); + IKV_FREE(updated); + return true; +} + const char *ikv_node_key(const ikv_node_t *node) { return (node && node->key) ? node->key : ""; @@ -1405,7 +1419,7 @@ ikv_node_t *ikv__parse_text_file_version(const char *path, uint32_t version) char *buf = NULL; ikv_node_t *root = NULL; - if (!read_file_bytes(path, &bytes, &bytes_n) || !bytes) + if (!ikv__read_file_bytes(path, &bytes, &bytes_n) || !bytes) return NULL; buf = (char *)IKV_MALLOC(bytes_n + 1u); @@ -2183,7 +2197,7 @@ ikv_node_t *ikv__parse_binary_file_version(const char *path, uint32_t version) size_t size = 0u; ikv_node_t *root = NULL; - if (!read_file_bytes(path, &buf, &size) || !buf) + if (!ikv__read_file_bytes(path, &buf, &size) || !buf) return NULL; root = ikv__parse_binary_memory_version(buf, size, version); @@ -2302,38 +2316,16 @@ ikv_node_t *ikv_parse_string_version(const char *src, ikv_version_t version) ikv_node_t *ikv_parse_file(const char *path) { - FILE *file = NULL; uint8_t prefix[16] = {0}; size_t prefix_size = 0u; ikv_version_t version = IKV_VERSION_UNKNOWN; - if (!open_file_with_prefix(path, &file, prefix, sizeof(prefix), &prefix_size)) + if (!read_file_prefix(path, prefix, sizeof(prefix), &prefix_size)) return NULL; version = ikv_detect_binary_version(prefix, prefix_size); if (version != IKV_VERSION_UNKNOWN) - { - ikv_node_t *root = NULL; - - if (version == IKV_VERSION_2) - { - if (IKV_FSEEK(file, 0L, SEEK_SET) != 0) - { - IKV_FCLOSE(file); - return NULL; - } - - root = ikv2_parse_binary_stream(file); - if (!root) - IKV_FCLOSE(file); - return root; - } - - IKV_FCLOSE(file); return ikvb_parse_file_version(path, version); - } - - IKV_FCLOSE(file); version = ikv_detect_text_version((const char *)prefix); if (version == IKV_VERSION_UNKNOWN) @@ -2400,38 +2392,9 @@ ikv_node_t *ikvb_parse_memory_version(const void *data, size_t size, ikv_version ikv_node_t *ikvb_parse_file(const char *path) { - FILE *file = NULL; - uint8_t prefix[16] = {0}; - size_t prefix_size = 0u; ikv_version_t version = ikv_detect_file_version(path, true); - - if (!open_file_with_prefix(path, &file, prefix, sizeof(prefix), &prefix_size)) - return NULL; - - version = ikv_detect_binary_version(prefix, prefix_size); if (version == IKV_VERSION_UNKNOWN) - { - IKV_FCLOSE(file); return NULL; - } - - if (version == IKV_VERSION_2) - { - ikv_node_t *root = NULL; - - if (IKV_FSEEK(file, 0L, SEEK_SET) != 0) - { - IKV_FCLOSE(file); - return NULL; - } - - root = ikv2_parse_binary_stream(file); - if (!root) - IKV_FCLOSE(file); - return root; - } - - IKV_FCLOSE(file); return ikvb_parse_file_version(path, version); } diff --git a/src/internal/ikv_internal.h b/src/internal/ikv_internal.h index 4b86b25..2796fc8 100644 --- a/src/internal/ikv_internal.h +++ b/src/internal/ikv_internal.h @@ -85,6 +85,7 @@ void ikv_test_fail_fclose_after(int remaining_calls); bool ikv__write_text_file_version(const char *path, const ikv_node_t *root, uint32_t version); ikv_node_t *ikv__parse_text_file_version(const char *path, uint32_t version); ikv_node_t *ikv__parse_text_string_version(const char *src, uint32_t version); +bool ikv__read_file_bytes(const char *path, uint8_t **out_data, size_t *out_size); bool ikv__write_binary_file_version(const char *path, const ikv_node_t *root, uint32_t version); bool ikv__write_binary_memory_version(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size, uint32_t version); diff --git a/src/loaders/ikv2.c b/src/loaders/ikv2.c index 8aea501..eaa6996 100644 --- a/src/loaders/ikv2.c +++ b/src/loaders/ikv2.c @@ -20,17 +20,9 @@ typedef struct uint32_t next_in_bucket; } ikv2_index_entry_t; -typedef enum -{ - IKV2_SOURCE_FILE = 1, - IKV2_SOURCE_MEMORY = 2 -} ikv2_source_kind_t; - typedef struct { ikv_lazy_state_t base; - ikv2_source_kind_t source_kind; - FILE *file_handle; uint8_t *memory_data; size_t memory_size; uint32_t entry_count; @@ -263,71 +255,6 @@ static char *ikv2_cursor_read_string(ikv2_cursor_t *cursor) return value; } -static bool ikv2_file_read_u8(FILE *file, uint8_t *out_value) -{ - return file && out_value && IKV_FREAD(out_value, 1u, 1u, file) == 1u; -} - -static bool ikv2_file_read_u32le(FILE *file, uint32_t *out_value) -{ - uint8_t bytes[4]; - - if (!file || !out_value || IKV_FREAD(bytes, 1u, sizeof(bytes), file) != sizeof(bytes)) - return false; - - *out_value = (uint32_t)bytes[0] | - ((uint32_t)bytes[1] << 8) | - ((uint32_t)bytes[2] << 16) | - ((uint32_t)bytes[3] << 24); - return true; -} - -static bool ikv2_file_read_varu32(FILE *file, uint32_t *out_value) -{ - uint32_t shift = 0u; - uint32_t value = 0u; - - while (shift < 32u) - { - uint8_t byte = 0u; - if (!ikv2_file_read_u8(file, &byte)) - return false; - - value |= (uint32_t)(byte & 0x7Fu) << shift; - if ((byte & 0x80u) == 0u) - { - *out_value = value; - return true; - } - - shift += 7u; - } - - return false; -} - -static char *ikv2_file_read_string(FILE *file) -{ - uint32_t length = 0u; - char *value = NULL; - - if (!ikv2_file_read_varu32(file, &length)) - return NULL; - - value = (char *)IKV_MALLOC((size_t)length + 1u); - if (!value) - return NULL; - - if (length > 0u && IKV_FREAD(value, 1u, length, file) != length) - { - IKV_FREE(value); - return NULL; - } - - value[length] = 0; - return value; -} - static bool ikv2_collect_root_entries(const ikv_node_t *root, ikv2_index_entry_t **out_entries, uint32_t *out_count) { ikv2_index_entry_t *entries = NULL; @@ -473,8 +400,6 @@ static void ikv2_lazy_root_destroy(ikv_lazy_state_t *state) for (uint32_t i = 0; i < lazy_root->entry_count; ++i) IKV_FREE(lazy_root->entries[i].key); - if (lazy_root->file_handle) - IKV_FCLOSE(lazy_root->file_handle); IKV_FREE(lazy_root->bucket_heads); IKV_FREE(lazy_root->entries); IKV_FREE(lazy_root->memory_data); @@ -532,32 +457,6 @@ static ikv2_index_entry_t *ikv2_find_entry(ikv2_lazy_root_t *lazy_root, const ch return NULL; } -static bool ikv2_read_payload_from_file(ikv2_lazy_root_t *lazy_root, uint32_t offset, uint32_t size, uint8_t **out_data) -{ - uint8_t *data = NULL; - - if (!lazy_root || !lazy_root->file_handle || !out_data) - return false; - - *out_data = NULL; - - if (IKV_FSEEK(lazy_root->file_handle, (long)offset, SEEK_SET) != 0) - return false; - - data = (uint8_t *)IKV_MALLOC(size); - if (!data) - return false; - - if (size > 0u && IKV_FREAD(data, 1u, size, lazy_root->file_handle) != size) - { - IKV_FREE(data); - return false; - } - - *out_data = data; - return true; -} - static bool ikv2_payload_range_valid(size_t total_size, uint32_t payload_offset, uint32_t payload_size) { size_t end_offset = 0u; @@ -576,7 +475,6 @@ static ikv_node_t *ikv2_lazy_root_load_object_key(ikv_lazy_state_t *state, ikv_n ikv2_lazy_root_t *lazy_root = (ikv2_lazy_root_t *)state; ikv2_index_entry_t *entry = NULL; const uint8_t *payload_data = NULL; - uint8_t *owned_payload = NULL; ikv_node_t *node = NULL; size_t consumed = 0u; @@ -589,21 +487,11 @@ static ikv_node_t *ikv2_lazy_root_load_object_key(ikv_lazy_state_t *state, ikv_n if (!entry) return NULL; - if (lazy_root->source_kind == IKV2_SOURCE_MEMORY) - { - if ((size_t)entry->payload_offset + (size_t)entry->payload_size > lazy_root->memory_size) - return NULL; - payload_data = lazy_root->memory_data + entry->payload_offset; - } - else - { - if (!ikv2_read_payload_from_file(lazy_root, entry->payload_offset, entry->payload_size, &owned_payload)) - return NULL; - payload_data = owned_payload; - } + if ((size_t)entry->payload_offset + (size_t)entry->payload_size > lazy_root->memory_size) + return NULL; + payload_data = lazy_root->memory_data + entry->payload_offset; node = ikv__parse_binary_node_memory(payload_data, entry->payload_size, &consumed); - IKV_FREE(owned_payload); if (!node || consumed != entry->payload_size) { if (node) @@ -722,7 +610,6 @@ static ikv_node_t *ikv2_parse_indexed_binary_buffer(uint8_t *buffer, size_t size return NULL; } - lazy_root->source_kind = IKV2_SOURCE_MEMORY; lazy_root->memory_data = buffer; lazy_root->memory_size = size; @@ -774,161 +661,21 @@ static bool ikv2_write_binary_file(const char *path, const ikv_node_t *root) return ok; } -ikv_node_t *ikv2_parse_binary_stream(FILE *file) -{ - uint8_t magic[4]; - uint8_t kind = 0u; - uint32_t version = 0u; - uint32_t flags = 0u; - uint32_t entry_count = 0u; - char *root_name = NULL; - ikv_node_t *root = NULL; - ikv2_lazy_root_t *lazy_root = NULL; - long file_size_long = 0; - size_t file_size = 0u; - - if (!file) - return NULL; - - if (IKV_FREAD(magic, 1u, sizeof(magic), file) != sizeof(magic) || - memcmp(magic, "iKv2", sizeof(magic)) != 0 || - !ikv2_file_read_u8(file, &kind) || - kind != (uint8_t)'b' || - !ikv2_file_read_u32le(file, &version) || - version != IKV_V2 || - !ikv2_file_read_u32le(file, &flags) || - (flags & IKV2_BINARY_FLAGS_INDEXED_ROOT) == 0u) - { - IKV_FCLOSE(file); - return NULL; - } - - root_name = ikv2_file_read_string(file); - if (!root_name || !ikv2_file_read_varu32(file, &entry_count)) - { - IKV_FREE(root_name); - IKV_FCLOSE(file); - return NULL; - } - - root = ikv_create_object(root_name); - IKV_FREE(root_name); - if (!root || !ikv__object_reserve_for_count(root, entry_count)) - { - if (root) - ikv_free(root); - IKV_FCLOSE(file); - return NULL; - } - root->value.object.size = entry_count; - - lazy_root = (ikv2_lazy_root_t *)IKV_CALLOC(1u, sizeof(*lazy_root)); - if (!lazy_root) - { - ikv_free(root); - IKV_FCLOSE(file); - return NULL; - } - lazy_root->base.destroy = ikv2_lazy_root_destroy; - lazy_root->base.load_object_key = ikv2_lazy_root_load_object_key; - lazy_root->source_kind = IKV2_SOURCE_FILE; - lazy_root->entry_count = entry_count; - lazy_root->entries = entry_count ? (ikv2_index_entry_t *)IKV_CALLOC(entry_count, sizeof(*lazy_root->entries)) : NULL; - if (entry_count > 0u && !lazy_root->entries) - { - ikv2_lazy_root_destroy(&lazy_root->base); - ikv_free(root); - IKV_FCLOSE(file); - return NULL; - } - - for (uint32_t i = 0; i < entry_count; ++i) - { - lazy_root->entries[i].key = ikv2_file_read_string(file); - if (!lazy_root->entries[i].key) - { - ikv2_lazy_root_destroy(&lazy_root->base); - ikv_free(root); - IKV_FCLOSE(file); - return NULL; - } - lazy_root->entries[i].key_length = (uint32_t)strlen(lazy_root->entries[i].key); - lazy_root->entries[i].key_hash = ikv2_hash_key(lazy_root->entries[i].key); - lazy_root->entries[i].next_in_bucket = IKV2_INDEX_NONE; - } - - for (uint32_t i = 0; i < entry_count; ++i) - { - if (!ikv2_file_read_u8(file, &lazy_root->entries[i].type) || - !ikv2_file_read_u32le(file, &lazy_root->entries[i].payload_offset) || - !ikv2_file_read_u32le(file, &lazy_root->entries[i].payload_size)) - { - ikv2_lazy_root_destroy(&lazy_root->base); - ikv_free(root); - IKV_FCLOSE(file); - return NULL; - } - } - - if (IKV_FSEEK(file, 0L, SEEK_END) != 0) - { - ikv2_lazy_root_destroy(&lazy_root->base); - ikv_free(root); - IKV_FCLOSE(file); - return NULL; - } - - file_size_long = IKV_FTELL(file); - if (file_size_long < 0L) - { - ikv2_lazy_root_destroy(&lazy_root->base); - ikv_free(root); - IKV_FCLOSE(file); - return NULL; - } - file_size = (size_t)file_size_long; - - for (uint32_t i = 0; i < entry_count; ++i) - { - if (!ikv2_payload_range_valid(file_size, - lazy_root->entries[i].payload_offset, - lazy_root->entries[i].payload_size)) - { - ikv2_lazy_root_destroy(&lazy_root->base); - ikv_free(root); - IKV_FCLOSE(file); - return NULL; - } - } - - if (!ikv2_build_entry_buckets(lazy_root)) - { - ikv2_lazy_root_destroy(&lazy_root->base); - ikv_free(root); - IKV_FCLOSE(file); - return NULL; - } - - lazy_root->file_handle = file; - root->lazy_state = &lazy_root->base; - return root; -} - static ikv_node_t *ikv2_parse_binary_file(const char *path) { - FILE *file = NULL; + uint8_t *buffer = NULL; + size_t size = 0u; ikv_node_t *root = NULL; if (!path) return NULL; - file = IKV_FOPEN(path, "rb"); - if (!file) + if (!ikv__read_file_bytes(path, &buffer, &size) || !buffer) return NULL; - root = ikv2_parse_binary_stream(file); + root = ikv2_parse_indexed_binary_buffer(buffer, size); if (!root) - IKV_FCLOSE(file); + IKV_FREE(buffer); return root; } diff --git a/src/loaders/ikv2.h b/src/loaders/ikv2.h index 6da2634..25900a2 100644 --- a/src/loaders/ikv2.h +++ b/src/loaders/ikv2.h @@ -3,5 +3,3 @@ #include "../internal/ikv_internal.h" extern const ikv_loader_t ikv_loader_v2; - -ikv_node_t *ikv2_parse_binary_stream(FILE *file);