feat(core): split library layout and add tests
All checks were successful
Build / linux-build-and-test (push) Successful in 46s
All checks were successful
Build / linux-build-and-test (push) Successful in 46s
This commit is contained in:
43
.gitea/workflows/build.yml
Normal file
43
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
linux-build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install build tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- name: Build demo
|
||||
run: |
|
||||
mkdir -p demo/build
|
||||
gcc -std=c11 -Wall -Wextra -pedantic \
|
||||
-Iinclude \
|
||||
demo/main.c \
|
||||
src/ikv.c \
|
||||
src/loaders/ikv1.c \
|
||||
src/loaders/ikv2.c \
|
||||
-o demo/build/ikv_demo
|
||||
|
||||
- name: Build unit tests
|
||||
run: |
|
||||
mkdir -p demo/build
|
||||
gcc -std=c11 -Wall -Wextra -pedantic \
|
||||
-Iinclude \
|
||||
demo/unit_test.c \
|
||||
src/ikv.c \
|
||||
src/loaders/ikv1.c \
|
||||
src/loaders/ikv2.c \
|
||||
-o demo/build/ikv_tests
|
||||
|
||||
- name: Run unit tests
|
||||
run: ./demo/build/ikv_tests
|
||||
23
README.md
23
README.md
@@ -1,3 +1,24 @@
|
||||
# iKv
|
||||
|
||||
Out internal iKv file format and C library
|
||||
Standalone iKv C library.
|
||||
|
||||
Layout:
|
||||
- `include/ikv.h`: public API only.
|
||||
- `src/ikv.c`: shared implementation and loader dispatch.
|
||||
- `src/loaders/ikv1.c`: iKv1 loader.
|
||||
- `src/loaders/ikv2.c`: iKv2 loader.
|
||||
- `src/internal/ikv_internal.h`: private node layout and loader interface.
|
||||
|
||||
Behavior:
|
||||
- The generic parse APIs auto-detect iKv1 vs iKv2 and choose the correct loader.
|
||||
- The generic write APIs emit the current format version, which is `iKv2`.
|
||||
- Public callers do not depend on loader-specific headers or internal structs.
|
||||
- `iKv2` binary files now use an indexed root layout with a key table and payload offsets.
|
||||
- Parsing an `iKv2` binary file reads the root index first and loads individual top-level values on demand.
|
||||
- `iKv1` binary files still use the legacy full-tree format for compatibility.
|
||||
|
||||
Adding a new version:
|
||||
1. Add `src/loaders/ikv3.h` and `src/loaders/ikv3.c` exporting an `ikv_loader_t`.
|
||||
2. Implement that loader using the shared internal helpers or custom logic.
|
||||
3. Register the loader in `src/ikv.c`.
|
||||
4. Add the new version constant to `include/ikv.h` if it is part of the public API.
|
||||
|
||||
39
demo/build.bat
Normal file
39
demo/build.bat
Normal file
@@ -0,0 +1,39 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set ROOT_DIR=%~dp0..
|
||||
set OUT_DIR=%~dp0build
|
||||
set DEMO_EXE=%OUT_DIR%\ikv_demo.exe
|
||||
set TEST_EXE=%OUT_DIR%\ikv_tests.exe
|
||||
|
||||
if not exist "%OUT_DIR%" mkdir "%OUT_DIR%"
|
||||
|
||||
gcc -std=c11 -Wall -Wextra -pedantic ^
|
||||
-I"%ROOT_DIR%\include" ^
|
||||
"%~dp0main.c" ^
|
||||
"%ROOT_DIR%\src\ikv.c" ^
|
||||
"%ROOT_DIR%\src\loaders\ikv1.c" ^
|
||||
"%ROOT_DIR%\src\loaders\ikv2.c" ^
|
||||
-o "%DEMO_EXE%"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo demo build failed
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
gcc -std=c11 -Wall -Wextra -pedantic ^
|
||||
-I"%ROOT_DIR%\include" ^
|
||||
"%~dp0unit_test.c" ^
|
||||
"%ROOT_DIR%\src\ikv.c" ^
|
||||
"%ROOT_DIR%\src\loaders\ikv1.c" ^
|
||||
"%ROOT_DIR%\src\loaders\ikv2.c" ^
|
||||
-o "%TEST_EXE%"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo unit test build failed
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo built "%DEMO_EXE%"
|
||||
echo built "%TEST_EXE%"
|
||||
endlocal
|
||||
72
demo/main.c
Normal file
72
demo/main.c
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "ikv.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
ikv_node_t *root = ikv_create_object("demo");
|
||||
ikv_node_t *player = NULL;
|
||||
ikv_node_t *inventory = NULL;
|
||||
ikv_node_t *loaded = NULL;
|
||||
uint8_t *binary = NULL;
|
||||
uint32_t binary_size = 0u;
|
||||
|
||||
if (!root)
|
||||
{
|
||||
fputs("failed to create root\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ikv_object_set_string(root, "title", "iKv demo");
|
||||
ikv_object_set_int(root, "version", 2);
|
||||
|
||||
player = ikv_object_add_object(root, "player");
|
||||
inventory = ikv_object_add_array(root, "inventory", IKV_STRING);
|
||||
if (!player || !inventory)
|
||||
{
|
||||
ikv_free(root);
|
||||
fputs("failed to build demo tree\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ikv_object_set_string(player, "name", "jondoe");
|
||||
ikv_object_set_bool(player, "alive", true);
|
||||
ikv_object_set_float(player, "speed", 12.5);
|
||||
|
||||
ikv_array_add_string(inventory, "wrench");
|
||||
ikv_array_add_string(inventory, "battery");
|
||||
ikv_array_add_string(inventory, "map");
|
||||
|
||||
if (!ikv_write_file("demo_output.ikv", root))
|
||||
{
|
||||
ikv_free(root);
|
||||
fputs("failed to write demo_output.ikv\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!ikvb_write_memory(root, &binary, &binary_size))
|
||||
{
|
||||
ikv_free(root);
|
||||
fputs("failed to write binary output\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
loaded = ikvb_parse_memory(binary, binary_size);
|
||||
if (!loaded)
|
||||
{
|
||||
free(binary);
|
||||
ikv_free(root);
|
||||
fputs("failed to parse binary output\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("title: %s\n", ikv_as_string(ikv_object_get(loaded, "title")));
|
||||
printf("version: %lld\n", (long long)ikv_as_int(ikv_object_get(loaded, "version")));
|
||||
printf("inventory items: %u\n", ikv_array_size(ikv_object_get(loaded, "inventory")));
|
||||
|
||||
ikv_free(loaded);
|
||||
free(binary);
|
||||
ikv_free(root);
|
||||
return 0;
|
||||
}
|
||||
128
demo/unit_test.c
Normal file
128
demo/unit_test.c
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "ikv.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int fail(const char *message)
|
||||
{
|
||||
fputs(message, stderr);
|
||||
fputc('\n', stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int test_text_roundtrip(void)
|
||||
{
|
||||
const char *src =
|
||||
"ikv1 \"root\"\n"
|
||||
"{\n"
|
||||
" \"name\" \"legacy\"\n"
|
||||
" \"count\" 7\n"
|
||||
"}\n";
|
||||
ikv_node_t *root = ikv_parse_string(src);
|
||||
|
||||
if (!root)
|
||||
return fail("text parse failed");
|
||||
if (ikv_node_type(root) != IKV_OBJECT)
|
||||
return fail("text root type mismatch");
|
||||
if (strcmp(ikv_as_string(ikv_object_get(root, "name")), "legacy") != 0)
|
||||
return fail("text string value mismatch");
|
||||
if (ikv_as_int(ikv_object_get(root, "count")) != 7)
|
||||
return fail("text int value mismatch");
|
||||
|
||||
ikv_free(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_binary_v1_roundtrip(void)
|
||||
{
|
||||
ikv_node_t *root = ikv_create_object("root");
|
||||
ikv_node_t *loaded = NULL;
|
||||
uint8_t *data = NULL;
|
||||
uint32_t size = 0u;
|
||||
int status = 0;
|
||||
|
||||
if (!root)
|
||||
return fail("v1 root allocation failed");
|
||||
|
||||
ikv_object_set_int(root, "value", 11);
|
||||
if (!ikvb_write_memory_version(root, &data, &size, IKV_VERSION_1))
|
||||
status = fail("v1 binary write failed");
|
||||
else
|
||||
{
|
||||
loaded = ikvb_parse_memory(data, size);
|
||||
if (!loaded)
|
||||
status = fail("v1 binary parse failed");
|
||||
else if (ikv_as_int(ikv_object_get(loaded, "value")) != 11)
|
||||
status = fail("v1 binary value mismatch");
|
||||
}
|
||||
|
||||
ikv_free(loaded);
|
||||
free(data);
|
||||
ikv_free(root);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int test_binary_v2_lazy_root(void)
|
||||
{
|
||||
ikv_node_t *root = ikv_create_object("root");
|
||||
ikv_node_t *nested = NULL;
|
||||
ikv_node_t *loaded = NULL;
|
||||
ikv_node_t *loaded_nested = NULL;
|
||||
uint8_t *data = NULL;
|
||||
uint32_t size = 0u;
|
||||
int status = 0;
|
||||
|
||||
if (!root)
|
||||
return fail("v2 root allocation failed");
|
||||
|
||||
ikv_object_set_string(root, "title", "fast");
|
||||
nested = ikv_object_add_object(root, "nested");
|
||||
if (!nested)
|
||||
{
|
||||
ikv_free(root);
|
||||
return fail("v2 nested allocation failed");
|
||||
}
|
||||
|
||||
ikv_object_set_bool(nested, "flag", true);
|
||||
ikv_object_set_float(nested, "speed", 9.25);
|
||||
|
||||
if (!ikvb_write_memory_version(root, &data, &size, IKV_VERSION_2))
|
||||
status = fail("v2 binary write failed");
|
||||
else
|
||||
{
|
||||
loaded = ikvb_parse_memory(data, size);
|
||||
if (!loaded)
|
||||
status = fail("v2 binary parse failed");
|
||||
else if (ikv_object_size(loaded) != 2u)
|
||||
status = fail("v2 root key count mismatch");
|
||||
else if (strcmp(ikv_as_string(ikv_object_get(loaded, "title")), "fast") != 0)
|
||||
status = fail("v2 root lazy lookup failed");
|
||||
else
|
||||
{
|
||||
loaded_nested = ikv_object_get(loaded, "nested");
|
||||
if (!loaded_nested)
|
||||
status = fail("v2 nested lazy lookup failed");
|
||||
else if (!ikv_as_bool(ikv_object_get(loaded_nested, "flag")))
|
||||
status = fail("v2 nested bool mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
ikv_free(loaded);
|
||||
free(data);
|
||||
ikv_free(root);
|
||||
return status;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
if (test_text_roundtrip() != 0)
|
||||
return 1;
|
||||
if (test_binary_v1_roundtrip() != 0)
|
||||
return 1;
|
||||
if (test_binary_v2_lazy_root() != 0)
|
||||
return 1;
|
||||
|
||||
puts("all tests passed");
|
||||
return 0;
|
||||
}
|
||||
91
include/ikv.h
Normal file
91
include/ikv.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define IKV_V1 1u
|
||||
#define IKV_V2 2u
|
||||
#define IKV_CURRENT_VERSION IKV_V2
|
||||
|
||||
typedef struct ikv_node_t ikv_node_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
IKV_VERSION_UNKNOWN = 0,
|
||||
IKV_VERSION_1 = IKV_V1,
|
||||
IKV_VERSION_2 = IKV_V2
|
||||
} ikv_version_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
IKV_NULL = 0,
|
||||
IKV_STRING,
|
||||
IKV_INT,
|
||||
IKV_FLOAT,
|
||||
IKV_BOOL,
|
||||
IKV_OBJECT,
|
||||
IKV_ARRAY
|
||||
} ikv_type_t;
|
||||
|
||||
/* Node lifetime */
|
||||
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);
|
||||
|
||||
/* Node metadata */
|
||||
const char *ikv_node_key(const ikv_node_t *node);
|
||||
ikv_type_t ikv_node_type(const ikv_node_t *node);
|
||||
|
||||
/* Object access */
|
||||
ikv_node_t *ikv_object_get(const ikv_node_t *object_node, const char *key);
|
||||
uint32_t ikv_object_size(const ikv_node_t *object_node);
|
||||
|
||||
/* Object mutation */
|
||||
ikv_node_t *ikv_object_add_object(ikv_node_t *object_node, const char *key);
|
||||
ikv_node_t *ikv_object_add_array(ikv_node_t *object_node, const char *key, ikv_type_t element_type);
|
||||
void ikv_object_set_int(ikv_node_t *object_node, const char *key, int64_t value);
|
||||
void ikv_object_set_float(ikv_node_t *object_node, const char *key, double value);
|
||||
void ikv_object_set_bool(ikv_node_t *object_node, const char *key, bool value);
|
||||
void ikv_object_set_string(ikv_node_t *object_node, const char *key, const char *value);
|
||||
|
||||
/* Array access */
|
||||
ikv_node_t *ikv_array_get(const ikv_node_t *array_node, uint32_t index);
|
||||
uint32_t ikv_array_size(const ikv_node_t *array_node);
|
||||
ikv_type_t ikv_array_element_type(const ikv_node_t *array_node);
|
||||
|
||||
/* Array mutation */
|
||||
ikv_node_t *ikv_array_add_object(ikv_node_t *array_node);
|
||||
void ikv_array_add_int(ikv_node_t *array_node, int64_t value);
|
||||
void ikv_array_add_float(ikv_node_t *array_node, double value);
|
||||
void ikv_array_add_bool(ikv_node_t *array_node, bool value);
|
||||
void ikv_array_add_string(ikv_node_t *array_node, const char *value);
|
||||
|
||||
/* Scalar reads */
|
||||
const char *ikv_as_string(const ikv_node_t *node);
|
||||
int64_t ikv_as_int(const ikv_node_t *node);
|
||||
double ikv_as_float(const ikv_node_t *node);
|
||||
bool ikv_as_bool(const ikv_node_t *node);
|
||||
|
||||
/* Version detection */
|
||||
ikv_version_t ikv_detect_text_version(const char *src);
|
||||
ikv_version_t ikv_detect_binary_version(const void *data, size_t size);
|
||||
ikv_version_t ikv_detect_file_version(const char *path, bool binary);
|
||||
|
||||
/* Text I/O */
|
||||
ikv_node_t *ikv_parse_string(const char *src);
|
||||
ikv_node_t *ikv_parse_string_version(const char *src, ikv_version_t version);
|
||||
ikv_node_t *ikv_parse_file(const char *path);
|
||||
ikv_node_t *ikv_parse_file_version(const char *path, ikv_version_t version);
|
||||
bool ikv_write_file(const char *path, const ikv_node_t *root);
|
||||
bool ikv_write_file_version(const char *path, const ikv_node_t *root, ikv_version_t version);
|
||||
|
||||
/* Binary I/O */
|
||||
ikv_node_t *ikvb_parse_memory(const void *data, size_t size);
|
||||
ikv_node_t *ikvb_parse_memory_version(const void *data, size_t size, ikv_version_t version);
|
||||
ikv_node_t *ikvb_parse_file(const char *path);
|
||||
ikv_node_t *ikvb_parse_file_version(const char *path, ikv_version_t version);
|
||||
bool ikvb_write_memory(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size);
|
||||
bool ikvb_write_memory_version(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size, ikv_version_t version);
|
||||
bool ikvb_write_file(const char *path, const ikv_node_t *root);
|
||||
bool ikvb_write_file_version(const char *path, const ikv_node_t *root, ikv_version_t version);
|
||||
66
src/internal/ikv_internal.h
Normal file
66
src/internal/ikv_internal.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../include/ikv.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ikv_type_t element_type;
|
||||
uint32_t count;
|
||||
ikv_node_t **items;
|
||||
} ikv_array_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t bucket_count;
|
||||
uint32_t size;
|
||||
ikv_node_t **buckets;
|
||||
} ikv_object_t;
|
||||
|
||||
typedef struct ikv_lazy_state_t
|
||||
{
|
||||
void (*destroy)(struct ikv_lazy_state_t *state);
|
||||
ikv_node_t *(*load_object_key)(struct ikv_lazy_state_t *state, ikv_node_t *object_node, const char *key);
|
||||
} ikv_lazy_state_t;
|
||||
|
||||
struct ikv_node_t
|
||||
{
|
||||
char *key;
|
||||
ikv_type_t type;
|
||||
ikv_lazy_state_t *lazy_state;
|
||||
union
|
||||
{
|
||||
char *string;
|
||||
int64_t i;
|
||||
double f;
|
||||
bool b;
|
||||
ikv_object_t object;
|
||||
ikv_array_t array;
|
||||
} value;
|
||||
ikv_node_t *next;
|
||||
};
|
||||
|
||||
typedef struct ikv_loader_t
|
||||
{
|
||||
ikv_version_t version;
|
||||
bool (*write_text_file)(const char *path, const ikv_node_t *root);
|
||||
ikv_node_t *(*parse_text_file)(const char *path);
|
||||
ikv_node_t *(*parse_text_string)(const char *src);
|
||||
bool (*write_binary_file)(const char *path, const ikv_node_t *root);
|
||||
bool (*write_binary_memory)(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size);
|
||||
ikv_node_t *(*parse_binary_file)(const char *path);
|
||||
ikv_node_t *(*parse_binary_memory)(const void *data, size_t size);
|
||||
} ikv_loader_t;
|
||||
|
||||
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__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);
|
||||
ikv_node_t *ikv__parse_binary_file_version(const char *path, uint32_t version);
|
||||
ikv_node_t *ikv__parse_binary_memory_version(const void *data, size_t size, uint32_t version);
|
||||
|
||||
bool ikv__write_binary_node_memory(const ikv_node_t *node, uint8_t **out_data, uint32_t *out_size);
|
||||
ikv_node_t *ikv__parse_binary_node_memory(const void *data, size_t size, size_t *out_consumed);
|
||||
void ikv__object_attach_loaded_node(ikv_node_t *object_node, ikv_node_t *child_node);
|
||||
bool ikv__object_reserve_for_count(ikv_node_t *object_node, uint32_t entry_count);
|
||||
47
src/loaders/ikv1.c
Normal file
47
src/loaders/ikv1.c
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "ikv1.h"
|
||||
|
||||
static bool ikv1_write_text_file(const char *path, const ikv_node_t *root)
|
||||
{
|
||||
return ikv__write_text_file_version(path, root, IKV_V1);
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv1_parse_text_file(const char *path)
|
||||
{
|
||||
return ikv__parse_text_file_version(path, IKV_V1);
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv1_parse_text_string(const char *src)
|
||||
{
|
||||
return ikv__parse_text_string_version(src, IKV_V1);
|
||||
}
|
||||
|
||||
static bool ikv1_write_binary_file(const char *path, const ikv_node_t *root)
|
||||
{
|
||||
return ikv__write_binary_file_version(path, root, IKV_V1);
|
||||
}
|
||||
|
||||
static bool ikv1_write_binary_memory(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size)
|
||||
{
|
||||
return ikv__write_binary_memory_version(root, out_data, out_size, IKV_V1);
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv1_parse_binary_file(const char *path)
|
||||
{
|
||||
return ikv__parse_binary_file_version(path, IKV_V1);
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv1_parse_binary_memory(const void *data, size_t size)
|
||||
{
|
||||
return ikv__parse_binary_memory_version(data, size, IKV_V1);
|
||||
}
|
||||
|
||||
const ikv_loader_t ikv_loader_v1 = {
|
||||
IKV_VERSION_1,
|
||||
ikv1_write_text_file,
|
||||
ikv1_parse_text_file,
|
||||
ikv1_parse_text_string,
|
||||
ikv1_write_binary_file,
|
||||
ikv1_write_binary_memory,
|
||||
ikv1_parse_binary_file,
|
||||
ikv1_parse_binary_memory,
|
||||
};
|
||||
5
src/loaders/ikv1.h
Normal file
5
src/loaders/ikv1.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../internal/ikv_internal.h"
|
||||
|
||||
extern const ikv_loader_t ikv_loader_v1;
|
||||
810
src/loaders/ikv2.c
Normal file
810
src/loaders/ikv2.c
Normal file
@@ -0,0 +1,810 @@
|
||||
#include "ikv2.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define IKV2_BINARY_FLAGS_INDEXED_ROOT 1u
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *key;
|
||||
uint8_t type;
|
||||
uint32_t payload_offset;
|
||||
uint32_t payload_size;
|
||||
uint8_t *payload_data;
|
||||
bool loaded;
|
||||
} 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;
|
||||
char *file_path;
|
||||
uint8_t *memory_data;
|
||||
size_t memory_size;
|
||||
uint32_t entry_count;
|
||||
ikv2_index_entry_t *entries;
|
||||
} ikv2_lazy_root_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t *data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
bool ok;
|
||||
} ikv2_buffer_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const uint8_t *data;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
} ikv2_cursor_t;
|
||||
|
||||
static bool ikv2_buffer_reserve(ikv2_buffer_t *buffer, size_t additional)
|
||||
{
|
||||
uint8_t *next = NULL;
|
||||
size_t required = 0;
|
||||
size_t capacity = 0;
|
||||
|
||||
if (!buffer || !buffer->ok)
|
||||
return false;
|
||||
|
||||
required = buffer->size + additional;
|
||||
if (required <= buffer->capacity)
|
||||
return true;
|
||||
|
||||
capacity = buffer->capacity ? buffer->capacity : 256u;
|
||||
while (capacity < required)
|
||||
capacity *= 2u;
|
||||
|
||||
next = (uint8_t *)realloc(buffer->data, capacity);
|
||||
if (!next)
|
||||
{
|
||||
buffer->ok = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer->data = next;
|
||||
buffer->capacity = capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *ikv2_strdup(const char *value)
|
||||
{
|
||||
size_t length = 0u;
|
||||
char *copy = NULL;
|
||||
|
||||
if (!value)
|
||||
value = "";
|
||||
|
||||
length = strlen(value);
|
||||
copy = (char *)malloc(length + 1u);
|
||||
if (!copy)
|
||||
return NULL;
|
||||
|
||||
memcpy(copy, value, length + 1u);
|
||||
return copy;
|
||||
}
|
||||
|
||||
static void ikv2_buffer_write_bytes(ikv2_buffer_t *buffer, const void *data, size_t size)
|
||||
{
|
||||
if (!ikv2_buffer_reserve(buffer, size))
|
||||
return;
|
||||
|
||||
if (size > 0u)
|
||||
memcpy(buffer->data + buffer->size, data, size);
|
||||
buffer->size += size;
|
||||
}
|
||||
|
||||
static void ikv2_buffer_write_u8(ikv2_buffer_t *buffer, uint8_t value)
|
||||
{
|
||||
ikv2_buffer_write_bytes(buffer, &value, 1u);
|
||||
}
|
||||
|
||||
static void ikv2_buffer_write_u32le(ikv2_buffer_t *buffer, uint32_t value)
|
||||
{
|
||||
uint8_t bytes[4];
|
||||
bytes[0] = (uint8_t)(value & 0xFFu);
|
||||
bytes[1] = (uint8_t)((value >> 8) & 0xFFu);
|
||||
bytes[2] = (uint8_t)((value >> 16) & 0xFFu);
|
||||
bytes[3] = (uint8_t)((value >> 24) & 0xFFu);
|
||||
ikv2_buffer_write_bytes(buffer, bytes, sizeof(bytes));
|
||||
}
|
||||
|
||||
static void ikv2_buffer_write_varu32(ikv2_buffer_t *buffer, uint32_t value)
|
||||
{
|
||||
while (value >= 0x80u)
|
||||
{
|
||||
ikv2_buffer_write_u8(buffer, (uint8_t)((value & 0x7Fu) | 0x80u));
|
||||
value >>= 7;
|
||||
}
|
||||
|
||||
ikv2_buffer_write_u8(buffer, (uint8_t)value);
|
||||
}
|
||||
|
||||
static void ikv2_buffer_write_string(ikv2_buffer_t *buffer, const char *value)
|
||||
{
|
||||
size_t length = value ? strlen(value) : 0u;
|
||||
ikv2_buffer_write_varu32(buffer, (uint32_t)length);
|
||||
if (length > 0u)
|
||||
ikv2_buffer_write_bytes(buffer, value, length);
|
||||
}
|
||||
|
||||
static bool ikv2_cursor_need(const ikv2_cursor_t *cursor, size_t size)
|
||||
{
|
||||
return cursor && cursor->offset + size <= cursor->size;
|
||||
}
|
||||
|
||||
static bool ikv2_cursor_read_u8(ikv2_cursor_t *cursor, uint8_t *out_value)
|
||||
{
|
||||
if (!ikv2_cursor_need(cursor, 1u))
|
||||
return false;
|
||||
*out_value = cursor->data[cursor->offset++];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ikv2_cursor_read_u32le(ikv2_cursor_t *cursor, uint32_t *out_value)
|
||||
{
|
||||
const uint8_t *p = NULL;
|
||||
|
||||
if (!ikv2_cursor_need(cursor, 4u))
|
||||
return false;
|
||||
|
||||
p = cursor->data + cursor->offset;
|
||||
*out_value = (uint32_t)p[0] |
|
||||
((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) |
|
||||
((uint32_t)p[3] << 24);
|
||||
cursor->offset += 4u;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ikv2_cursor_read_varu32(ikv2_cursor_t *cursor, uint32_t *out_value)
|
||||
{
|
||||
uint32_t shift = 0u;
|
||||
uint32_t value = 0u;
|
||||
|
||||
while (shift < 32u)
|
||||
{
|
||||
uint8_t byte = 0u;
|
||||
if (!ikv2_cursor_read_u8(cursor, &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_cursor_read_string(ikv2_cursor_t *cursor)
|
||||
{
|
||||
uint32_t length = 0u;
|
||||
char *value = NULL;
|
||||
|
||||
if (!ikv2_cursor_read_varu32(cursor, &length))
|
||||
return NULL;
|
||||
if (!ikv2_cursor_need(cursor, (size_t)length))
|
||||
return NULL;
|
||||
|
||||
value = (char *)malloc((size_t)length + 1u);
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
if (length > 0u)
|
||||
memcpy(value, cursor->data + cursor->offset, length);
|
||||
value[length] = 0;
|
||||
cursor->offset += (size_t)length;
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool ikv2_file_read_u8(FILE *file, uint8_t *out_value)
|
||||
{
|
||||
return file && out_value && 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 || 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 *)malloc((size_t)length + 1u);
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
if (length > 0u && fread(value, 1u, length, file) != length)
|
||||
{
|
||||
free(value);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
value[length] = 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
static int ikv2_compare_entries(const void *lhs, const void *rhs)
|
||||
{
|
||||
const ikv2_index_entry_t *left = (const ikv2_index_entry_t *)lhs;
|
||||
const ikv2_index_entry_t *right = (const ikv2_index_entry_t *)rhs;
|
||||
return strcmp(left->key ? left->key : "", right->key ? right->key : "");
|
||||
}
|
||||
|
||||
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;
|
||||
uint32_t count = 0u;
|
||||
uint32_t index = 0u;
|
||||
|
||||
if (!root || root->type != IKV_OBJECT || !out_entries || !out_count)
|
||||
return false;
|
||||
|
||||
count = root->value.object.size;
|
||||
entries = count ? (ikv2_index_entry_t *)calloc(count, sizeof(*entries)) : NULL;
|
||||
if (count > 0u && !entries)
|
||||
return false;
|
||||
|
||||
for (uint32_t bucket = 0; bucket < root->value.object.bucket_count; ++bucket)
|
||||
{
|
||||
for (ikv_node_t *node = root->value.object.buckets[bucket]; node; node = node->next)
|
||||
{
|
||||
uint8_t *payload = NULL;
|
||||
uint32_t payload_size = 0u;
|
||||
|
||||
if (index >= count || !ikv__write_binary_node_memory(node, &payload, &payload_size))
|
||||
{
|
||||
if (payload)
|
||||
free(payload);
|
||||
for (uint32_t i = 0; i < index; ++i)
|
||||
{
|
||||
free(entries[i].key);
|
||||
free(entries[i].payload_data);
|
||||
}
|
||||
free(entries);
|
||||
return false;
|
||||
}
|
||||
|
||||
entries[index].key = ikv2_strdup(node->key ? node->key : "");
|
||||
entries[index].type = (uint8_t)node->type;
|
||||
entries[index].payload_data = payload;
|
||||
entries[index].payload_size = payload_size;
|
||||
entries[index].loaded = false;
|
||||
if (!entries[index].key)
|
||||
{
|
||||
free(payload);
|
||||
for (uint32_t i = 0; i < index; ++i)
|
||||
{
|
||||
free(entries[i].key);
|
||||
free(entries[i].payload_data);
|
||||
}
|
||||
free(entries);
|
||||
return false;
|
||||
}
|
||||
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
qsort(entries, count, sizeof(*entries), ikv2_compare_entries);
|
||||
*out_entries = entries;
|
||||
*out_count = count;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ikv2_build_indexed_binary(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size)
|
||||
{
|
||||
ikv2_index_entry_t *entries = NULL;
|
||||
uint32_t entry_count = 0u;
|
||||
uint32_t header_size = 0u;
|
||||
uint32_t payload_base = 0u;
|
||||
ikv2_buffer_t buffer = {0};
|
||||
|
||||
if (!root || !out_data || !out_size || root->type != IKV_OBJECT)
|
||||
return false;
|
||||
|
||||
buffer.ok = true;
|
||||
*out_data = NULL;
|
||||
*out_size = 0u;
|
||||
|
||||
if (!ikv2_collect_root_entries(root, &entries, &entry_count))
|
||||
return false;
|
||||
|
||||
header_size = 4u + 1u + 4u + 4u;
|
||||
header_size += 1u + (uint32_t)strlen(root->key && root->key[0] ? root->key : "root");
|
||||
header_size += 1u;
|
||||
for (uint32_t i = 0; i < entry_count; ++i)
|
||||
header_size += 1u + (uint32_t)strlen(entries[i].key ? entries[i].key : "");
|
||||
header_size += entry_count * (1u + 4u + 4u);
|
||||
payload_base = header_size;
|
||||
|
||||
ikv2_buffer_write_bytes(&buffer, "iKv2", 4u);
|
||||
ikv2_buffer_write_u8(&buffer, (uint8_t)'b');
|
||||
ikv2_buffer_write_u32le(&buffer, IKV_V2);
|
||||
ikv2_buffer_write_u32le(&buffer, IKV2_BINARY_FLAGS_INDEXED_ROOT);
|
||||
ikv2_buffer_write_string(&buffer, (root->key && root->key[0]) ? root->key : "root");
|
||||
ikv2_buffer_write_varu32(&buffer, entry_count);
|
||||
|
||||
for (uint32_t i = 0; i < entry_count; ++i)
|
||||
ikv2_buffer_write_string(&buffer, entries[i].key ? entries[i].key : "");
|
||||
|
||||
for (uint32_t i = 0; i < entry_count; ++i)
|
||||
{
|
||||
ikv2_buffer_write_u8(&buffer, entries[i].type);
|
||||
ikv2_buffer_write_u32le(&buffer, payload_base);
|
||||
ikv2_buffer_write_u32le(&buffer, entries[i].payload_size);
|
||||
payload_base += entries[i].payload_size;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < entry_count; ++i)
|
||||
{
|
||||
const uint8_t *payload = entries[i].payload_data;
|
||||
ikv2_buffer_write_bytes(&buffer, payload, entries[i].payload_size);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < entry_count; ++i)
|
||||
{
|
||||
free(entries[i].key);
|
||||
free(entries[i].payload_data);
|
||||
}
|
||||
free(entries);
|
||||
|
||||
if (!buffer.ok || buffer.size == 0u || buffer.size > 0xFFFFFFFFu)
|
||||
{
|
||||
free(buffer.data);
|
||||
return false;
|
||||
}
|
||||
|
||||
*out_data = buffer.data;
|
||||
*out_size = (uint32_t)buffer.size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ikv2_lazy_root_destroy(ikv_lazy_state_t *state)
|
||||
{
|
||||
ikv2_lazy_root_t *lazy_root = (ikv2_lazy_root_t *)state;
|
||||
|
||||
if (!lazy_root)
|
||||
return;
|
||||
|
||||
for (uint32_t i = 0; i < lazy_root->entry_count; ++i)
|
||||
free(lazy_root->entries[i].key);
|
||||
free(lazy_root->entries);
|
||||
free(lazy_root->file_path);
|
||||
free(lazy_root->memory_data);
|
||||
free(lazy_root);
|
||||
}
|
||||
|
||||
static ikv2_index_entry_t *ikv2_find_entry(ikv2_lazy_root_t *lazy_root, const char *key)
|
||||
{
|
||||
uint32_t left = 0u;
|
||||
uint32_t right = 0u;
|
||||
|
||||
if (!lazy_root || !key || lazy_root->entry_count == 0u)
|
||||
return NULL;
|
||||
|
||||
right = lazy_root->entry_count;
|
||||
while (left < right)
|
||||
{
|
||||
uint32_t mid = left + (right - left) / 2u;
|
||||
int compare = strcmp(key, lazy_root->entries[mid].key ? lazy_root->entries[mid].key : "");
|
||||
if (compare == 0)
|
||||
return &lazy_root->entries[mid];
|
||||
if (compare < 0)
|
||||
right = mid;
|
||||
else
|
||||
left = mid + 1u;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool ikv2_read_payload_from_file(const char *path, uint32_t offset, uint32_t size, uint8_t **out_data)
|
||||
{
|
||||
FILE *file = NULL;
|
||||
uint8_t *data = NULL;
|
||||
|
||||
if (!path || !out_data)
|
||||
return false;
|
||||
|
||||
*out_data = NULL;
|
||||
file = fopen(path, "rb");
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
if (fseek(file, (long)offset, SEEK_SET) != 0)
|
||||
{
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
data = (uint8_t *)malloc(size);
|
||||
if (!data)
|
||||
{
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (size > 0u && fread(data, 1u, size, file) != size)
|
||||
{
|
||||
free(data);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
*out_data = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv2_lazy_root_load_object_key(ikv_lazy_state_t *state, ikv_node_t *object_node, const char *key)
|
||||
{
|
||||
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;
|
||||
|
||||
(void)object_node;
|
||||
|
||||
if (!lazy_root || !key)
|
||||
return NULL;
|
||||
|
||||
entry = ikv2_find_entry(lazy_root, key);
|
||||
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->file_path, entry->payload_offset, entry->payload_size, &owned_payload))
|
||||
return NULL;
|
||||
payload_data = owned_payload;
|
||||
}
|
||||
|
||||
node = ikv__parse_binary_node_memory(payload_data, entry->payload_size, &consumed);
|
||||
free(owned_payload);
|
||||
if (!node || consumed != entry->payload_size)
|
||||
{
|
||||
if (node)
|
||||
ikv_free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry->loaded = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv2_parse_indexed_binary_buffer(uint8_t *buffer, size_t size)
|
||||
{
|
||||
ikv2_cursor_t cursor;
|
||||
uint32_t version = 0u;
|
||||
uint32_t flags = 0u;
|
||||
uint32_t entry_count = 0u;
|
||||
uint8_t kind = 0u;
|
||||
char *root_name = NULL;
|
||||
ikv_node_t *root = NULL;
|
||||
ikv2_lazy_root_t *lazy_root = NULL;
|
||||
|
||||
cursor.data = buffer;
|
||||
cursor.size = size;
|
||||
cursor.offset = 0u;
|
||||
|
||||
if (!ikv2_cursor_need(&cursor, 9u))
|
||||
return NULL;
|
||||
if (memcmp(cursor.data, "iKv2", 4u) != 0)
|
||||
return NULL;
|
||||
cursor.offset += 4u;
|
||||
if (!ikv2_cursor_read_u8(&cursor, &kind))
|
||||
return NULL;
|
||||
if (kind != (uint8_t)'b')
|
||||
return NULL;
|
||||
if (!ikv2_cursor_read_u32le(&cursor, &version) || version != IKV_V2)
|
||||
return NULL;
|
||||
if (!ikv2_cursor_read_u32le(&cursor, &flags) || (flags & IKV2_BINARY_FLAGS_INDEXED_ROOT) == 0u)
|
||||
return NULL;
|
||||
|
||||
root_name = ikv2_cursor_read_string(&cursor);
|
||||
if (!root_name)
|
||||
return NULL;
|
||||
if (!ikv2_cursor_read_varu32(&cursor, &entry_count))
|
||||
{
|
||||
free(root_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
root = ikv_create_object(root_name);
|
||||
free(root_name);
|
||||
if (!root)
|
||||
return NULL;
|
||||
if (!ikv__object_reserve_for_count(root, entry_count))
|
||||
{
|
||||
ikv_free(root);
|
||||
return NULL;
|
||||
}
|
||||
root->value.object.size = entry_count;
|
||||
|
||||
lazy_root = (ikv2_lazy_root_t *)calloc(1u, sizeof(*lazy_root));
|
||||
if (!lazy_root)
|
||||
{
|
||||
ikv_free(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lazy_root->base.destroy = ikv2_lazy_root_destroy;
|
||||
lazy_root->base.load_object_key = ikv2_lazy_root_load_object_key;
|
||||
lazy_root->entry_count = entry_count;
|
||||
lazy_root->entries = entry_count ? (ikv2_index_entry_t *)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);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < entry_count; ++i)
|
||||
{
|
||||
lazy_root->entries[i].key = ikv2_cursor_read_string(&cursor);
|
||||
if (!lazy_root->entries[i].key)
|
||||
{
|
||||
ikv2_lazy_root_destroy(&lazy_root->base);
|
||||
ikv_free(root);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < entry_count; ++i)
|
||||
{
|
||||
if (!ikv2_cursor_read_u8(&cursor, &lazy_root->entries[i].type) ||
|
||||
!ikv2_cursor_read_u32le(&cursor, &lazy_root->entries[i].payload_offset) ||
|
||||
!ikv2_cursor_read_u32le(&cursor, &lazy_root->entries[i].payload_size))
|
||||
{
|
||||
ikv2_lazy_root_destroy(&lazy_root->base);
|
||||
ikv_free(root);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
lazy_root->source_kind = IKV2_SOURCE_MEMORY;
|
||||
lazy_root->memory_data = buffer;
|
||||
lazy_root->memory_size = size;
|
||||
|
||||
root->lazy_state = &lazy_root->base;
|
||||
return root;
|
||||
}
|
||||
|
||||
static bool ikv2_write_text_file(const char *path, const ikv_node_t *root)
|
||||
{
|
||||
return ikv__write_text_file_version(path, root, IKV_V2);
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv2_parse_text_file(const char *path)
|
||||
{
|
||||
return ikv__parse_text_file_version(path, IKV_V2);
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv2_parse_text_string(const char *src)
|
||||
{
|
||||
return ikv__parse_text_string_version(src, IKV_V2);
|
||||
}
|
||||
|
||||
static bool ikv2_write_binary_memory(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size)
|
||||
{
|
||||
return ikv2_build_indexed_binary(root, out_data, out_size);
|
||||
}
|
||||
|
||||
static bool ikv2_write_binary_file(const char *path, const ikv_node_t *root)
|
||||
{
|
||||
uint8_t *data = NULL;
|
||||
uint32_t size = 0u;
|
||||
FILE *file = NULL;
|
||||
bool ok = false;
|
||||
|
||||
if (!path || !ikv2_write_binary_memory(root, &data, &size))
|
||||
return false;
|
||||
|
||||
file = fopen(path, "wb");
|
||||
if (!file)
|
||||
{
|
||||
free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = (fwrite(data, 1u, size, file) == size);
|
||||
if (fclose(file) != 0)
|
||||
ok = false;
|
||||
free(data);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv2_parse_binary_file(const char *path)
|
||||
{
|
||||
FILE *file = NULL;
|
||||
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;
|
||||
|
||||
if (!path)
|
||||
return NULL;
|
||||
|
||||
file = fopen(path, "rb");
|
||||
if (!file)
|
||||
return NULL;
|
||||
|
||||
if (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)
|
||||
{
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
root_name = ikv2_file_read_string(file);
|
||||
if (!root_name || !ikv2_file_read_varu32(file, &entry_count))
|
||||
{
|
||||
free(root_name);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
root = ikv_create_object(root_name);
|
||||
free(root_name);
|
||||
if (!root || !ikv__object_reserve_for_count(root, entry_count))
|
||||
{
|
||||
if (root)
|
||||
ikv_free(root);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
root->value.object.size = entry_count;
|
||||
|
||||
lazy_root = (ikv2_lazy_root_t *)calloc(1u, sizeof(*lazy_root));
|
||||
if (!lazy_root)
|
||||
{
|
||||
ikv_free(root);
|
||||
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->file_path = ikv2_strdup(path);
|
||||
lazy_root->entry_count = entry_count;
|
||||
lazy_root->entries = entry_count ? (ikv2_index_entry_t *)calloc(entry_count, sizeof(*lazy_root->entries)) : NULL;
|
||||
if (!lazy_root->file_path || (entry_count > 0u && !lazy_root->entries))
|
||||
{
|
||||
ikv2_lazy_root_destroy(&lazy_root->base);
|
||||
ikv_free(root);
|
||||
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);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
root->lazy_state = &lazy_root->base;
|
||||
return root;
|
||||
}
|
||||
|
||||
static ikv_node_t *ikv2_parse_binary_memory(const void *data, size_t size)
|
||||
{
|
||||
uint8_t *buffer = NULL;
|
||||
|
||||
if (!data || size == 0u)
|
||||
return NULL;
|
||||
|
||||
buffer = (uint8_t *)malloc(size);
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
memcpy(buffer, data, size);
|
||||
|
||||
{
|
||||
ikv_node_t *root = ikv2_parse_indexed_binary_buffer(buffer, size);
|
||||
if (!root)
|
||||
free(buffer);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
const ikv_loader_t ikv_loader_v2 = {
|
||||
IKV_VERSION_2,
|
||||
ikv2_write_text_file,
|
||||
ikv2_parse_text_file,
|
||||
ikv2_parse_text_string,
|
||||
ikv2_write_binary_file,
|
||||
ikv2_write_binary_memory,
|
||||
ikv2_parse_binary_file,
|
||||
ikv2_parse_binary_memory,
|
||||
};
|
||||
5
src/loaders/ikv2.h
Normal file
5
src/loaders/ikv2.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../internal/ikv_internal.h"
|
||||
|
||||
extern const ikv_loader_t ikv_loader_v2;
|
||||
Reference in New Issue
Block a user