diff --git a/CMakeLists.txt b/CMakeLists.txt index 91b59bd..7b362e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,9 @@ set(CMAKE_C_EXTENSIONS OFF) include(GNUInstallDirs) add_library(kvspace-c SHARED - src/xvalue.c + src/coord.c + src/xvalue_head.c + src/xvalue_meta.c src/kvspace.c src/durable_abi.c ) @@ -19,7 +21,7 @@ set_target_properties(kvspace-c PROPERTIES SOVERSION 1 ) -# 无公共头:内部头在 src/(kvspace_shm.h、xvalue.h),权威 ABI 头在 kvspace 仓库。 +# Internal headers are in src/; the public ABI header is in kvspace. # 两个依赖库走 header-only(src/kvspace.c 里 #define *_IMPLEMENTATION),故不链接 .so; # 头的搜索路径由系统 include(/usr/include/blockmalloc、/usr/include/slotsboxmalloc)提供。 @@ -54,4 +56,12 @@ if(KVSPACE_BUILD_TESTS) target_include_directories(test_handle_cache PRIVATE src) target_link_libraries(test_handle_cache PRIVATE kvspace-c) add_test(NAME handle_cache COMMAND test_handle_cache) + + add_executable(test_xvalue_head tests/test_xvalue_head.c src/xvalue_head.c src/xvalue_meta.c) + target_include_directories(test_xvalue_head PRIVATE src) + add_test(NAME xvalue_head COMMAND test_xvalue_head) + add_executable(test_new_wire_shm tests/test_new_wire_shm.c) + target_include_directories(test_new_wire_shm PRIVATE src) + target_link_libraries(test_new_wire_shm PRIVATE kvspace-c) + add_test(NAME new_wire_shm COMMAND test_new_wire_shm) endif() diff --git a/README.md b/README.md index e8077ef..d806098 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,17 @@ Two surfaces: ## XValue -kindexpr TLV head, byte-identical to `kvspace-durable` (`include/kvspace/xvalue.h`): +kindexpr XValue uses the headlenpow wire format: ``` -[1B kindexprlen][kindexpr + 0x00 pad][1B ro][4B vid LE][4B raw_len LE][raw] +[pow:u8][flags:u8][a:u64le][b:u64le][langtype, padded to 2^pow][body] ``` -- kindexpr first byte: `*` = soft link (raw = target path), `@` = ext handle, otherwise inline. -- `[d0,d1]kind` carries ndim+dims; bare `kind` is a scalar; `char/*` is always a 1-D sequence (`[n]`). -- `None` is encoded as NULL / length 0. +- The low two flag bits select fixed, sized, tensor, or extension storage; bit 2 marks a pointer. +- Directory members come from physical key prefixes. `@ext` stores its locator in class 3. +- `ro` and `vid` live at `/.kvspace-meta/`. -Kinds: `bool`, `int8..int64`, `uint8..uint64`, `float32/64`, `char/utf32|utf8|ascii`, `stringkeymap`, `index`, `extindex`, `rwir`, `rwfunc`, `defrwir`, `scope`, `time`, `duration`. +The codec accepts `None`, scalars, sized byte and character arrays, tensors, maps, and code values. ## Tutorial diff --git a/src/coord.c b/src/coord.c new file mode 100644 index 0000000..225c8bb --- /dev/null +++ b/src/coord.c @@ -0,0 +1,66 @@ +#include "coord.h" +#include +#include + +int kvspaceCoordIsCoord(const char *name) { + if (!name || name[0] != '[') + return 0; + size_t n = strlen(name); + if (n < 3 || name[n - 1] != ']') + return 0; + for (size_t i = 1; i + 1 < n; i++) + if (name[i] == '[' || name[i] == ']') + return 0; + return 1; +} + +/* Parse integer coordinates; return -1 on mismatch. */ +int kvspaceParseCoord(const char *name, int64_t *coords, int maxn) { + if (!name || name[0] != '[') + return -1; + int n = 0; + int64_t cur = 0; + bool has = false; + for (const char *p = name + 1; *p; p++) { + if (*p >= '0' && *p <= '9') { + cur = cur * 10 + (*p - '0'); + has = true; + } else if (*p == ',') { + if (!has || n >= maxn) + return -1; + coords[n++] = cur; + cur = 0; + has = false; + } else if (*p == ']') { + if (!has || n >= maxn) + return -1; + coords[n++] = cur; + return (p[1] == '\0') ? n : -1; + } else { + return -1; + } + } + return -1; +} + +int kvspaceCoordCmp(const char *a, const char *b) { + int ia = kvspaceCoordIsCoord(a), ib = kvspaceCoordIsCoord(b); + if (!ia && !ib) + return strcmp(a, b); + if (!ia) + return 1; /* 非坐标段排后 */ + if (!ib) + return -1; + int64_t ca[8], cb[8]; + int na = kvspaceParseCoord(a, ca, 8); + int nb = kvspaceParseCoord(b, cb, 8); + if (na < 0 || nb < 0) + return strcmp(a, b); /* 含小数/字符串坐标 → 字典序 */ + for (int i = 0; i < na && i < nb; i++) { + if (ca[i] != cb[i]) + return ca[i] < cb[i] ? -1 : 1; + } + if (na != nb) + return na < nb ? -1 : 1; + return 0; +} diff --git a/src/coord.h b/src/coord.h new file mode 100644 index 0000000..3df8dbe --- /dev/null +++ b/src/coord.h @@ -0,0 +1,7 @@ +#ifndef KVSPACE_COORD_H +#define KVSPACE_COORD_H +#include +int kvspaceCoordIsCoord(const char *name); +int kvspaceParseCoord(const char *name, int64_t *coords, int maxn); +int kvspaceCoordCmp(const char *a, const char *b); +#endif diff --git a/src/durable_abi.c b/src/durable_abi.c index de51f20..96314d2 100644 --- a/src/durable_abi.c +++ b/src/durable_abi.c @@ -1,6 +1,4 @@ -/* durable_abi.c — 对齐 kvspace-durable 的 C ABI(kvspaceConnect 等 22 符号), - * 让 kvlang 的 Rust layout 零改动对接 kvspace-c 的 SHM。内部复用 kvspaceShm* 与 - * kvspaceXvalue*。 */ +/* KVSpace SHM ABI. */ /* macOS 需 _DARWIN_C_SOURCE 才暴露 usleep 等完整 Darwin API;_POSIX_C_SOURCE ≥200112 * 才在 macOS 暴露 C99 的 snprintf(Linux 走 glibc 的 _DEFAULT_SOURCE)。 */ @@ -11,17 +9,34 @@ #define _DEFAULT_SOURCE #include "kvspace_shm.h" -#include "xvalue.h" +#include "xvalue_head.h" #include #include #include #include #include +#include + +#define KVSPACE_REF_INLINE 0 +#define KVSPACE_REF_PTR 1 +#define KVSPACE_REF_EXT 2 +#define KVSPACE_KIND_CHAR_UTF8 "char/utf8" +#define KVSPACE_KIND_CHAR_ASCII "char/ascii" +#define KVSPACE_KIND_CHAR "char/utf32" +#define KVSPACE_KIND_BYTE "byte" +#define KVSPACE_KIND_BOOL "bool" +#define KVSPACE_KIND_INT64 "int64" +#define KVSPACE_KIND_FLOAT64 "float64" + +int kvspaceTlvEncodeMode(const char *kind, const uint8_t *raw, uint32_t raw_len, + const int32_t *dims, int32_t ndim, int32_t ref, + uint8_t ro, uint32_t vid, uint8_t **out, + uint32_t *out_len); + #define SHM_DEFAULT_SIZE (8ULL * 64 * 64 * 64 * 64) -/* 对齐权威 kvspace/include/kvspace/kvspace.h 的三正交轴 - * kvspaceHead_t(repr(C))。 */ +/* Mirrors the public ABI. */ typedef struct { uint16_t headlen; uint8_t ref; @@ -34,6 +49,7 @@ typedef struct { char langtype[256]; int32_t langtype_len; int32_t body_offset; + uint64_t body_cap; } kvspaceHead_t; int kvspaceDecodeHead(const uint8_t *data, uint32_t data_len, kvspaceHead_t *out); @@ -73,6 +89,25 @@ int kvspaceGet(void *h, const char *key, int resolve, uint8_t **out, return 0; } +int kvspaceSetValue(void *h, const char *key, const uint8_t *value, + uint32_t value_len, uint8_t ro, uint32_t vid, + char *err, uint32_t err_cap) { + int rc = value_len > INT32_MAX ? -2 : + kvspaceShmSetValue((kvspace_t *)h, key, value, + (int32_t)value_len, ro, vid); + if (rc == -2) { + if (err && err_cap) + snprintf(err, err_cap, "invalid XValue"); + return 1; + } + if (rc != 0) { + if (err && err_cap) + snprintf(err, err_cap, "kvspace: set-value failed at %s", key ? key : ""); + return 1; + } + return 0; +} + int kvspaceResolveRef(void *h, const char *key, kvspaceRef_t *ref) { return kvspaceShmResolveRef((kvspace_t *)h, key, ref); } @@ -144,7 +179,9 @@ int kvspaceGetHead(void *h, const char *key, kvspaceHead_t *out) { uint8_t *d = kvspaceShmGet((kvspace_t *)h, key, 1, &total); if (!d || total <= 0) return 1; - return kvspaceDecodeHead(d, (uint32_t)total, out); + if (kvspaceDecodeHead(d, (uint32_t)total, out) != 0) + return 1; + return kvspaceShmMetaGet((kvspace_t *)h, key, &out->ro, &out->vid) == 0 ? 0 : 1; } /* 就地写:返回原 box body 偏移指针;前置条件不满足 → 非 0 + err。 */ @@ -164,10 +201,10 @@ int kvspaceWriteInPlace(void *h, const char *key, int resolve, * 返回 body 偏移指针。 */ int kvspaceWriteNewPlace(void *h, const char *key, uint8_t ref, uint8_t storetype, uint8_t ro, uint32_t vid, - const char *langtype, uint32_t body_len, + const char *langtype, uint32_t body_len, uint64_t body_cap, uint8_t **body, char *err, uint32_t err_cap) { if (kvspaceShmWriteNewPlace((kvspace_t *)h, key, ref, storetype, ro, vid, - langtype, (int32_t)body_len, body) != 0) { + langtype, (int32_t)body_len, body_cap, body) != 0) { if (err && err_cap) snprintf(err, err_cap, "kvspace: write-new-place failed at %s", key); return 1; @@ -272,96 +309,167 @@ int kvspaceClear(void *h, char *err, uint32_t err_cap) { return kvspaceShmDeltree((kvspace_t *)h, "/"); } -int kvspaceTlvEncode(const char *kind, const uint8_t *raw, uint32_t raw_len, - const int32_t *dims, int32_t ndim, uint8_t **out, - uint32_t *out_len) { - int32_t n = kvspaceXvalueEncode(kind, raw, (int32_t)raw_len, dims, ndim, out); - if (n < 0) +static int finish(uint8_t *value, uint64_t len, uint8_t **out, uint32_t *out_len) { + if (!value || len > UINT32_MAX) { + free(value); return 1; - *out_len = (uint32_t)n; + } + *out = value; + *out_len = (uint32_t)len; return 0; } +static int locator(int ext, const char *type, const uint8_t *raw, uint32_t len, + uint8_t **out, uint32_t *out_len) { + if (!raw || memchr(raw, 0, len)) + return 1; + char *path = malloc((size_t)len + 1); + if (!path) + return 1; + memcpy(path, raw, len); + path[len] = 0; + uint8_t *value = NULL; + uint64_t total = 0; + int rc = ext ? kvspaceXhNewExt(type, path, len, &value, &total) : + kvspaceXhNewPtr(type, path, len, &value, &total); + free(path); + return rc == 0 ? finish(value, total, out, out_len) : 1; +} + +int kvspaceTlvEncode(const char *kind, const uint8_t *raw, uint32_t raw_len, + const int32_t *dims, int32_t ndim, uint8_t **out, uint32_t *out_len) { + return kvspaceTlvEncodeMode(kind, raw, raw_len, dims, ndim, 0, 0, 0, out, out_len); +} + int kvspaceTlvEncodeMode(const char *kind, const uint8_t *raw, uint32_t raw_len, - const int32_t *dims, int32_t ndim, int32_t ref, - uint8_t ro, uint32_t vid, uint8_t **out, - uint32_t *out_len) { - int32_t n = kvspaceXvalueEncodeMode(kind, raw, (int32_t)raw_len, dims, ndim, - ref, (int32_t)ro, vid, out); - if (n < 0) + const int32_t *dims, int32_t ndim, int32_t ref, uint8_t ro, uint32_t vid, + uint8_t **out, uint32_t *out_len) { + if (!out || !out_len || !kind || ndim < 0 || ndim > 8 || + (ndim && !dims) || (raw_len && !raw) || ro || vid) return 1; - *out_len = (uint32_t)n; - return 0; + *out = NULL; + *out_len = 0; + if (ref == KVSPACE_REF_PTR || ref == KVSPACE_REF_EXT) + return locator(ref == KVSPACE_REF_EXT, kind, raw, raw_len, out, out_len); + if (ref != KVSPACE_REF_INLINE) + return 1; + + uint8_t *value = NULL; + uint64_t total = 0; + int rc = -1; + if ((kind[0] == 0 || strcmp(kind, "None") == 0) && raw_len == 0 && ndim == 0) { + rc = kvspaceXhNewNone(&value, &total); + } else if (strcmp(kind, KVSPACE_KIND_CHAR_UTF8) == 0 || + strcmp(kind, KVSPACE_KIND_CHAR_ASCII) == 0 || + strcmp(kind, KVSPACE_KIND_CHAR) == 0 || + strcmp(kind, KVSPACE_KIND_BYTE) == 0) { + int elem = strcmp(kind, KVSPACE_KIND_CHAR_UTF8) == 0 ? KVSPACE_XH_UTF8 : + strcmp(kind, KVSPACE_KIND_CHAR_ASCII) == 0 ? KVSPACE_XH_ASCII : + strcmp(kind, KVSPACE_KIND_CHAR) == 0 ? KVSPACE_XH_UTF32 : + KVSPACE_XH_BYTE; + rc = kvspaceXhNewSlack(elem, raw, raw_len, raw_len, &value, &total); + } else if (ndim > 0) { + uint64_t shape[8]; + for (int32_t i = 0; i < ndim; i++) { + if (dims[i] < 0) + return 1; + shape[i] = (uint64_t)dims[i]; + } + rc = kvspaceXhNewTensor(shape, (uint32_t)ndim, kind, raw, raw_len, + &value, &total); + } else if (strcmp(kind, "rwir") == 0 || strcmp(kind, "def langtype") == 0 || + (strcmp(kind, "rwfunc") == 0 && raw_len != 0)) { + rc = kvspaceXhNewCode(kind, raw, raw_len, raw_len, &value, &total); + } else if (kvspaceXhNewScalar(kind, raw, raw_len, &value, &total) == 0) { + rc = 0; + } else { + rc = kvspaceXhNewShort(kind, raw, raw_len, &value, &total); + } + return rc == 0 ? finish(value, total, out, out_len) : 1; } -int kvspaceDecodeHead(const uint8_t *data, uint32_t data_len, - kvspaceHead_t *out) { +static int parse_dims(const char *type, int32_t *dims, int32_t *ndim) { + *ndim = 0; + if (type[0] != '[') + return 0; + const char *p = type + 1; + for (;;) { + if (*ndim == 8 || *p < '0' || *p > '9') + return -1; + uint64_t n = 0; + do { + n = n * 10 + (uint64_t)(*p++ - '0'); + if (n > INT32_MAX) + return -1; + } while (*p >= '0' && *p <= '9'); + dims[(*ndim)++] = (int32_t)n; + if (*p == ']') + return 0; + if (*p++ != ',') + return -1; + } +} + +int kvspaceDecodeHead(const uint8_t *data, uint32_t data_len, kvspaceHead_t *out) { if (!out) return 1; - xvalue_head_t h = kvspaceXvalueDecodeHead(data, (int32_t)data_len); - memset(out, 0, sizeof(*out)); - out->headlen = h.headlen; - out->ref = (uint8_t)h.ref; - out->storetype = h.storetype; - int32_t lt = h.langtype_len; - if (lt > 255) - lt = 255; - memcpy(out->langtype, h.langtype, (size_t)lt); - out->langtype[lt] = 0; - out->langtype_len = lt; - out->ndim = h.ndim; - for (int i = 0; i < h.ndim && i < 8; i++) - out->dims[i] = h.dims[i]; - out->ro = (uint8_t)h.ro; - out->vid = h.vid; - out->body_len = h.raw_len; - out->body_offset = kvspaceXvalueHeadLen(&h); + memset(out, 0, sizeof *out); + kvspaceXh h; + if (kvspaceXhDecode(data, data_len, &h) != 0 || h.total != data_len || + h.headlen > UINT16_MAX || h.content_len > INT32_MAX || + h.langtype_len > sizeof out->langtype - 1) + return 1; + out->headlen = (uint16_t)h.headlen; + out->ref = h.kind & KVSPACE_XH_PTR_FLAG ? KVSPACE_REF_PTR : + h.kind == KVSPACE_XH_EXT ? KVSPACE_REF_EXT : KVSPACE_REF_INLINE; + out->storetype = h.kind & 3; + out->body_len = (int32_t)h.content_len; + out->body_offset = (int32_t)h.headlen; + out->body_cap = h.body_cap; + out->langtype_len = (int32_t)h.langtype_len; + memcpy(out->langtype, h.langtype, h.langtype_len); + if (h.kind == KVSPACE_XH_FIXED_LARGE || + (h.kind == KVSPACE_XH_SLACK && h.langtype_len && h.langtype[0] == '[')) { + if (parse_dims(out->langtype, out->dims, &out->ndim) != 0) + return 1; + } return 0; } int kvspaceNewPtr(const char *target_kindexpr, const char *target, uint8_t **out, uint32_t *out_len) { - int32_t n = kvspaceXvalueNewPtr(target_kindexpr, target, out); - if (n < 0) + if (!target_kindexpr || !target || !out || !out_len) return 1; - *out_len = (uint32_t)n; - return 0; + uint8_t *value = NULL; + uint64_t total = 0; + if (kvspaceXhNewPtr(target_kindexpr, target, strlen(target), &value, &total) != 0) + return 1; + return finish(value, total, out, out_len); } -int kvspaceNewChar(const uint8_t *bytes, uint32_t len, uint8_t **out, - uint32_t *out_len) { - int32_t d = (int32_t)len; - int32_t n = kvspaceXvalueEncode(KVSPACE_KIND_CHAR_UTF8, bytes, (int32_t)len, - &d, 1, out); - if (n < 0) - return 1; - *out_len = (uint32_t)n; - return 0; +int kvspaceNewChar(const uint8_t *bytes, uint32_t len, uint8_t **out, uint32_t *out_len) { + return kvspaceTlvEncode(KVSPACE_KIND_CHAR_UTF8, bytes, len, NULL, 0, out, out_len); } int kvspaceNewBool(uint8_t v, uint8_t **out, uint32_t *out_len) { - bool b = v != 0; - int32_t n = kvspaceXvalueNewBool(&b, 1, out); - if (n < 0) - return 1; - *out_len = (uint32_t)n; - return 0; + uint8_t b = v ? 1 : 0; + return kvspaceTlvEncode(KVSPACE_KIND_BOOL, &b, 1, NULL, 0, out, out_len); } int kvspaceNewInt64(int64_t v, uint8_t **out, uint32_t *out_len) { - int32_t n = kvspaceXvalueNewInt64(&v, 1, out); - if (n < 0) - return 1; - *out_len = (uint32_t)n; - return 0; + uint8_t raw[8]; + for (int i = 0; i < 8; i++) + raw[i] = (uint8_t)((uint64_t)v >> (8 * i)); + return kvspaceTlvEncode(KVSPACE_KIND_INT64, raw, 8, NULL, 0, out, out_len); } int kvspaceNewFloat64(double v, uint8_t **out, uint32_t *out_len) { - int32_t n = kvspaceXvalueNewFloat64(&v, 1, out); - if (n < 0) - return 1; - *out_len = (uint32_t)n; - return 0; + uint64_t bits; + memcpy(&bits, &v, sizeof bits); + uint8_t raw[8]; + for (int i = 0; i < 8; i++) + raw[i] = (uint8_t)(bits >> (8 * i)); + return kvspaceTlvEncode(KVSPACE_KIND_FLOAT64, raw, 8, NULL, 0, out, out_len); } int kvspaceWatch(void *h, const char *key, const uint8_t *target, diff --git a/src/kvspace.c b/src/kvspace.c index 5bff2af..279f0fc 100644 --- a/src/kvspace.c +++ b/src/kvspace.c @@ -10,6 +10,8 @@ #define SLOTSBOXMALLOC_IMPLEMENTATION #include "slotsboxmalloc/slotsboxobj.h" #include "kvspace_shm.h" +#include "xvalue_head.h" +#include "xvalue_meta.h" #include #include #include @@ -36,6 +38,12 @@ #define SBO_PUNCH_MIN (64UL * 1024) #define WATCH_TABLE_SZ 256 +static int reserved_meta_path(const char *key) { + static const char root[] = "/.kvspace-meta"; + return strncmp(key, root, sizeof root - 1) == 0 && + (key[sizeof root - 1] == 0 || key[sizeof root - 1] == '/'); +} + enum { ART_N4 = 0, ART_N16 = 1, ART_N48 = 2, @@ -984,82 +992,121 @@ static int32_t art_cover(kvspace_t *kv, int32_t nid, const uint8_t *pfx, return -1; } -/* ---- prefix scan: collect all keys under prefix into out[0..*n-1] ---- */ -static void art_scan(kvspace_t *kv, int32_t nid, char *buf, int bpos, int bcap, - const char *pfx, int plen, char ***out, int32_t *n) { - if (nid < 0 || *n >= 4096) - return; +typedef struct { + char **keys; + int32_t count; + int32_t cap; +} keyscan_t; + +static void keyscan_free(keyscan_t *scan) { + for (int32_t i = 0; i < scan->count; i++) + free(scan->keys[i]); + free(scan->keys); + scan->keys = NULL; + scan->count = scan->cap = 0; +} + +static int keyscan_add_n(keyscan_t *scan, const char *key, size_t len) { + if (scan->count == INT32_MAX) + return -1; + if (scan->count == scan->cap) { + int32_t cap = scan->cap > INT32_MAX / 2 ? INT32_MAX : + scan->cap ? scan->cap * 2 : 64; + char **keys = realloc(scan->keys, (size_t)cap * sizeof *keys); + if (!keys) + return -1; + scan->keys = keys; + scan->cap = cap; + } + char *copy = strndup(key, len); + if (!copy) + return -1; + scan->keys[scan->count++] = copy; + return 0; +} + +static int art_scan(kvspace_t *kv, int32_t nid, char *buf, int bpos, int bcap, + const char *pfx, int plen, keyscan_t *scan) { + if (nid < 0) + return 0; art_hdr_t *h = art_hdr(kv, nid); - if (!h) - return; - // write node's prefix into buf - for (int i = 0; i < h->prefix_len && bpos < bcap; i++) + if (!h || h->prefix_len > bcap - bpos - 1) + return -1; + for (int i = 0; i < h->prefix_len; i++) buf[bpos++] = h->prefix[i]; - if (bpos >= bcap) - return; - // if this node has value, emit key if (h->has_value) { - buf[bpos] = '\0'; - if (bpos >= plen && memcmp(buf, pfx, plen) == 0) { - (*out)[*n] = strdup(buf); - (*n)++; - } + buf[bpos] = 0; + if (bpos >= plen && memcmp(buf, pfx, (size_t)plen) == 0 && + keyscan_add_n(scan, buf, (size_t)bpos) != 0) + return -1; } - // recurse into children switch (h->type) { case ART_N4: { art_n4_t *x = (art_n4_t *)h; for (int i = 0; i < (int)h->count; i++) { - if (bpos < bcap) - buf[bpos] = x->keys[i]; - art_scan(kv, x->children[i], buf, bpos + (bpos < bcap ? 1 : 0), bcap, pfx, - plen, out, n); + if (bpos + 1 >= bcap) + return -1; + buf[bpos] = x->keys[i]; + if (art_scan(kv, x->children[i], buf, bpos + 1, bcap, + pfx, plen, scan) != 0) + return -1; } break; } case ART_N16: { art_n16_t *x = (art_n16_t *)h; for (int i = 0; i < (int)h->count; i++) { - if (bpos < bcap) - buf[bpos] = x->keys[i]; - art_scan(kv, x->children[i], buf, bpos + (bpos < bcap ? 1 : 0), bcap, pfx, - plen, out, n); + if (bpos + 1 >= bcap) + return -1; + buf[bpos] = x->keys[i]; + if (art_scan(kv, x->children[i], buf, bpos + 1, bcap, + pfx, plen, scan) != 0) + return -1; } break; } case ART_N48: { art_n48_t *x = (art_n48_t *)h; - for (int i = 0; i < 256; i++) - if (x->index[i] != 255) { - if (bpos < bcap) - buf[bpos] = (uint8_t)i; - art_scan(kv, x->children[x->index[i]], buf, - bpos + (bpos < bcap ? 1 : 0), bcap, pfx, plen, out, n); - } + for (int i = 0; i < 256; i++) { + if (x->index[i] == 255) + continue; + if (bpos + 1 >= bcap) + return -1; + buf[bpos] = (uint8_t)i; + if (art_scan(kv, x->children[x->index[i]], buf, bpos + 1, + bcap, pfx, plen, scan) != 0) + return -1; + } break; } case ART_N256: { art_n256_t *x = (art_n256_t *)h; - for (int i = 0; i < 256; i++) - if (x->children[i] >= 0) { - if (bpos < bcap) - buf[bpos] = (uint8_t)i; - art_scan(kv, x->children[i], buf, bpos + (bpos < bcap ? 1 : 0), bcap, - pfx, plen, out, n); - } + for (int i = 0; i < 256; i++) { + if (x->children[i] < 0) + continue; + if (bpos + 1 >= bcap) + return -1; + buf[bpos] = (uint8_t)i; + if (art_scan(kv, x->children[i], buf, bpos + 1, bcap, + pfx, plen, scan) != 0) + return -1; + } break; } } + return 0; } -static void art_scan_pfx(kvspace_t *kv, const char *pfx, int plen, char *buf, - int bcap, char ***out, int32_t *n) { +static int art_scan_pfx(kvspace_t *kv, const char *pfx, int plen, char *buf, + int bcap, keyscan_t *scan) { + if (plen >= bcap) + return -1; int bpos = 0; - int32_t nid = art_cover(kv, kv->hdr->art_root, (const uint8_t *)pfx, plen, - buf, bcap, &bpos); + int32_t nid = art_cover(kv, kv->hdr->art_root, (const uint8_t *)pfx, + plen, buf, bcap, &bpos); if (nid < 0) - return; - art_scan(kv, nid, buf, bpos, bcap, pfx, plen, out, n); + return 0; + return art_scan(kv, nid, buf, bpos, bcap, pfx, plen, scan); } /* ============ lifecycle ============ */ @@ -1183,15 +1230,15 @@ static int read_tlv(kvspace_t *kv, uint64_t off, uint8_t **out, int32_t *ol) { uint8_t *s = kv->sbo_data + off; /* box 内必含完整 TLV,用 xvalue 解码器算长度 */ size_t sz = sbo_allocated_size(kv->sbo_meta, off); - xvalue_head_t h = - kvspaceXvalueDecodeHead(s, sz > INT32_MAX ? INT32_MAX : (int32_t)sz); - *out = s; /* SHM pointer */ - if (h.langtype_len == 0) { - *ol = 0; + kvspaceXh wire; + if (kvspaceXhDecode(s, sz, &wire) == 0) { + if (wire.total > INT32_MAX) + return -1; + *out = s; + *ol = wire.langtype_len ? (int32_t)wire.total : 0; return 0; - } /* None(空 langtype)→ len 0 */ - *ol = kvspaceXvalueHeadLen(&h) + h.raw_len; - return 0; + } + return -1; } /* link 解析:ptr(ref=1)与 @ext(ref=2)的 XValue 恒为叶子(spec 硬规则——指针/扩展键 * 之后不可能再有成员,成员只挂在 target 上),故路径中任何 `/` 分隔的容器前缀都绝不会是 @@ -1210,14 +1257,16 @@ static void resolve_path(kvspace_t *kv, const char *path, char *out, int osz) { int32_t rl; if (read_tlv(kv, h->box_offset, &raw, &rl) < 0) return; - xvalue_head_t hh = kvspaceXvalueDecodeHead(raw, rl); - if (hh.ref != 1) - return; - int tl = hh.raw_len; - if (tl <= 0 || tl >= osz) - return; - memcpy(out, hh.raw, tl); - out[tl] = '\0'; + kvspaceXh wire; + if (rl > 0 && kvspaceXhDecode(raw, (uint64_t)rl, &wire) == 0) { + if (wire.kind != (KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG) || + wire.content_len >= (uint64_t)osz) + return; + memcpy(out, wire.body, (size_t)wire.content_len); + out[wire.content_len] = 0; + continue; + } + return; } } @@ -1238,94 +1287,27 @@ static art_hdr_t *resolve_fetch(kvspace_t *kv, const char *path, char *out, int return h; uint8_t *s = kv->sbo_data + h->box_offset; size_t sz = sbo_allocated_size(kv->sbo_meta, h->box_offset); - xvalue_head_t hh = - kvspaceXvalueDecodeHead(s, sz > INT32_MAX ? INT32_MAX : (int32_t)sz); - int32_t tlvlen = - hh.langtype_len == 0 ? 0 : kvspaceXvalueHeadLen(&hh) + hh.raw_len; - int tl = hh.raw_len; - if (hh.ref != 1 || tl <= 0 || tl >= osz) { /* 非 ptr 或无法续链:终端 */ - *raw = s; - *rl = tlvlen; - *fetched = 1; - return h; + kvspaceXh wire; + if (kvspaceXhDecode(s, sz, &wire) == 0) { + if (wire.total > INT32_MAX) + return NULL; + if (wire.kind != (KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG) || + wire.content_len >= (uint64_t)osz) { + *raw = s; + *rl = wire.langtype_len ? (int32_t)wire.total : 0; + *fetched = 1; + return h; + } + memcpy(out, wire.body, (size_t)wire.content_len); + out[wire.content_len] = 0; + continue; } - memcpy(out, hh.raw, tl); - out[tl] = '\0'; - } - return NULL; -} - -/* memindex 定宽矩阵几何:n=dims[0]、m=dims[1];返回矩阵起点(extindex 矩阵在 - * body 尾部, off=body_len−N*M;普通 index off=0)。head dims 由 DecodeHead 从 - * kindexpr 解出。 */ -/* cap 增长:容量足够不变;空取 need;否则从旧容量翻倍覆盖 need(对齐 durable - * grow_cap)。 */ -static int32_t grow_cap(int32_t old_cap, int32_t need) { - if (need <= old_cap) - return old_cap; - if (old_cap == 0) - return need; - int32_t c = old_cap; - while (c < need) - c *= 2; - return c; -} - -/* memindex 矩阵(dims=[len,cap,M]):返回矩阵起点(跳过 extindex 的 ext_path - 头部 off=raw_len−cap*M), 出参 *len=有效成员数=dims[0]、*m=行宽=dims[2]。 */ -static const uint8_t *index_matrix(const xvalue_head_t *hh, int32_t *len, - int32_t *m) { - int32_t l = hh->ndim >= 1 && hh->dims[0] > 0 ? hh->dims[0] : 0; - int32_t cap = hh->ndim >= 2 && hh->dims[1] > 0 ? hh->dims[1] : 0; - int32_t mm = hh->ndim >= 3 && hh->dims[2] > 0 ? hh->dims[2] : 0; - int32_t off = hh->raw_len - cap * mm; - if (off < 0) - off = 0; - *len = l; - *m = mm; - return hh->raw + off; -} - -/* 定宽矩阵 → malloc 成员名数组(每行去尾 NUL)。 */ -static char **index_names(const xvalue_head_t *hh, int32_t *oc) { - *oc = 0; - int32_t n, m; - const uint8_t *mat = index_matrix(hh, &n, &m); - if (n <= 0) return NULL; - char **names = malloc(sizeof(char *) * (size_t)n); - if (!names) - return NULL; - for (int32_t i = 0; i < n; i++) { - const char *row = (const char *)mat + (size_t)i * m; - int32_t len = 0; - while (len < m && row[len] != 0) - len++; - names[i] = strndup(row, (size_t)len); - } - *oc = n; - return names; -} - -/* 成员是否已在矩阵中:直扫定宽行 memcmp,零分配(成员密集写的幂等快路径)。 */ -static int index_has_member(const xvalue_head_t *hh, const char *name) { - int32_t n, m; - const uint8_t *mat = index_matrix(hh, &n, &m); - if (n <= 0 || m <= 0) - return 0; - size_t nl = strlen(name); - if ((int32_t)nl > m) - return 0; - for (int32_t i = 0; i < n; i++) { - const char *row = (const char *)mat + (size_t)i * m; - if (memcmp(row, name, nl) == 0 && ((int32_t)nl == m || row[nl] == 0)) - return 1; } - return 0; + return NULL; } -/* 读 dir(尾斜杠目录键)的 extindex,返回 extpath(body 头部 - * [0..body_len−N*M]);非 extindex 返回 0。 */ +/* Read the extension locator stored at a directory key. */ static int dir_ext_path(kvspace_t *kv, const char *dir, char *out, int osz) { out[0] = 0; art_hdr_t *h = @@ -1336,20 +1318,13 @@ static int dir_ext_path(kvspace_t *kv, const char *dir, char *out, int osz) { int32_t rl; if (read_tlv(kv, h->box_offset, &raw, &rl) < 0) return 0; - xvalue_head_t hh = kvspaceXvalueDecodeHead(raw, rl); - if (hh.kind_len != (int32_t)strlen(KVSPACE_KIND_EXT_INDEX) || - memcmp(hh.kind, KVSPACE_KIND_EXT_INDEX, hh.kind_len) != 0) + kvspaceXh wire; + if (rl <= 0 || kvspaceXhDecode(raw, (uint64_t)rl, &wire) != 0 || + wire.kind != KVSPACE_XH_EXT || wire.a == 0 || wire.a >= (uint64_t)osz) return 0; - int32_t len, m; - const uint8_t *mat = index_matrix(&hh, &len, &m); - int32_t el = (int32_t)(mat - hh.raw); - if (el < 0) - el = 0; - if (el >= osz) - el = osz - 1; - memcpy(out, hh.raw, (size_t)el); - out[el] = 0; - return out[0] ? 1 : 0; + memcpy(out, wire.body, (size_t)wire.a); + out[wire.a] = 0; + return 1; } /* ============ CRUD ============ */ @@ -1358,6 +1333,8 @@ uint8_t *kvspaceShmGet(kvspace_t *kv, const char *key, int resolve, if (!kv || !key || !ol) return NULL; *ol = 0; + if (reserved_meta_path(key)) + return NULL; if (kv_sync(kv) != 0) return NULL; char kbuf[1024]; @@ -1428,7 +1405,7 @@ static int32_t ref_leaf(kvspace_t *kv, kvspaceRef_t *ref, const char *key) { } int kvspaceShmResolveRef(kvspace_t *kv, const char *key, kvspaceRef_t *ref) { - if (!kv || !key || !ref) + if (!kv || !key || !ref || reserved_meta_path(key)) return -1; memset(ref, 0, sizeof(*ref)); if (kv_sync(kv) != 0) @@ -1513,16 +1490,6 @@ int kvspaceShmSetPartByRef(kvspace_t *kv, kvspaceRef_t *ref, return 0; } -/* ── 值/索引分离(方案2,对齐 kvspace-durable backend.rs) ────────── */ -/* 坐标段工具见 xvalue.c:kvspaceCoordIsCoord / kvspaceParseCoord / - * kvspaceCoordCmp。 */ - -/* kind 判定(kindexpr 非 NUL 终止)。 */ -static int is_kind(const xvalue_head_t *h, const char *k) { - int32_t kl = (int32_t)strlen(k); - return h->kind_len == kl && memcmp(h->kind, k, (size_t)kl) == 0; -} - /* 原始落盘(不处理容器/member 语义,供内部调用,避免递归)。 */ static int shm_set_raw(kvspace_t *kv, const char *key, const uint8_t *val, int32_t val_len) { @@ -1547,365 +1514,85 @@ static int shm_set_raw(kvspace_t *kv, const char *key, const uint8_t *val, return 0; } -/* 去掉尾部分隔符(/ 或 ·),返回 malloc;根 "/" 保持 "/"。 */ -static char *strip_dir_suf_alloc(const char *p) { - size_t l = strlen(p); - if (l == 0) - return strdup(p); - if (l >= 2 && (unsigned char)p[l - 2] == 0xC2 && - (unsigned char)p[l - 1] == 0xB7) - return strndup(p, l - 2); - if (p[l - 1] == '/') { - if (l == 1) - return strdup("/"); - return strndup(p, l - 1); - } - return strdup(p); -} - -/* base + "·"(memindex 键)。 */ -static char *memjoin(const char *base) { - size_t l = strlen(base); - char *r = malloc(l + 3); - memcpy(r, base, l); - r[l] = (char)0xC2; - r[l + 1] = (char)0xB7; - r[l + 2] = 0; - return r; -} - -/* 找字符串内最后一个 ·(U+00B7,2 字节),无则返回 NULL。 */ -static const char *strrstr_mid(const char *s) { - const char *last = NULL; - for (const char *p = s; *p; p++) - if ((unsigned char)p[0] == 0xC2 && (unsigned char)p[1] == 0xB7) - last = p; - return last; -} - -/* 解析 key 的父目录/成员名(对齐 durable split_index,取末段最后一个 - * ·)。父目录含尾分隔符。 */ -static void shm_split_index(const char *key, char **parent, char **name, - bool *is_member) { - *parent = NULL; - *name = NULL; - *is_member = false; - const char *s = strrchr(key, '/'); - const char *last; - size_t plen; - if (!s || s == key) { - plen = 1; /* 父前缀 "/" */ - last = s ? s + 1 : key; - } else { - plen = (size_t)(s - key) + 1; /* 含尾 '/' */ - last = s + 1; - } - const char *dot = strrstr_mid(last); - if (dot && dot != last && dot[2]) { - size_t prelen = (size_t)(dot - last); - char *p = malloc(plen + prelen + 2 + 1); - if (plen == 1 && (s == key)) { - p[0] = '/'; - memcpy(p + 1, last, prelen + 2); - p[plen + prelen + 2] = 0; - } else { - memcpy(p, key, plen); - memcpy(p + plen, last, prelen + 2); - p[plen + prelen + 2] = 0; - } - *parent = p; - *name = strdup(dot + 2); - *is_member = true; +static int sync_metadata(kvspace_t *kv, const char *key, uint8_t ro, uint32_t vid) { + char *meta = NULL; + if (kvspaceMetaKey(key, &meta) != 0) + return -1; + int rc = 0; + if (ro || vid) { + uint8_t *value = NULL; + uint64_t len = 0; + if (kvspaceMetaEncode(ro, vid, &value, &len) != 0) + rc = -1; + else + rc = shm_set_raw(kv, meta, value, (int32_t)len); + free(value); } else { - char *p = malloc(plen + 1); - if (plen == 1 && (s == key)) { - p[0] = '/'; - p[1] = 0; - } else { - memcpy(p, key, plen); - p[plen] = 0; - } - *parent = p; - *name = strdup(last); - } -} - -/* 坐标段 → stringkeymap dims(对齐 durable grow_coord_dims,单成员)。 */ -static void grow_coord_dims_one(const char *name, int32_t *dims, - int32_t *ndim) { - int64_t coords[8]; - int n = kvspaceParseCoord(name, coords, 8); - if (n < 0) { - dims[0] = 1; - *ndim = 1; - return; + bool removed = false; + kv->hdr->art_root = art_del(kv, kv->hdr->art_root, + (const uint8_t *)meta, (int)strlen(meta), + 0, &removed); } - *ndim = n; - for (int i = 0; i < n; i++) - dims[i] = (int32_t)(coords[i] + 1); -} - -/* 确保 memindex 存在(不存在 → 建空 index)。 */ -static void ensure_memindex(kvspace_t *kv, const char *mem) { - art_hdr_t *h = - art_search(kv, kv->hdr->art_root, (const uint8_t *)mem, (int)strlen(mem)); - if (h && h->has_value) - return; - uint8_t *iv; - int32_t ivl = kvspaceXvalueNewIndex(NULL, 0, &iv); - shm_set_raw(kv, mem, iv, ivl); - free(iv); -} - -/* 向 memindex 追加成员名(幂等)。 */ -static int add_child_index(kvspace_t *kv, const char *mem, const char *name) { - char **names = NULL; - int32_t nnames = 0; - int32_t old_cap = 0, old_m = 0; - art_hdr_t *h = - art_search(kv, kv->hdr->art_root, (const uint8_t *)mem, (int)strlen(mem)); - if (h && h->has_value) { - uint8_t *raw; - int32_t rl; - if (read_tlv(kv, h->box_offset, &raw, &rl) == 0) { - xvalue_head_t hh = kvspaceXvalueDecodeHead(raw, rl); - if (hh.ref == 0 && is_kind(&hh, KVSPACE_KIND_EXT_INDEX)) - return 0; /* extindex:成员由 extpath 展开,不维护本地 childs */ - if (hh.ref == 0 && is_kind(&hh, KVSPACE_KIND_INDEX)) { - if (index_has_member(&hh, name)) - return 1; /* 已存在:零分配快路径,不物化 names、不重建矩阵 */ - names = index_names(&hh, &nnames); - old_cap = hh.ndim >= 2 && hh.dims[1] > 0 ? hh.dims[1] : 0; - old_m = hh.ndim >= 3 && hh.dims[2] > 0 ? hh.dims[2] : 0; - } - } - } - char **nn = realloc(names, sizeof(char *) * (size_t)(nnames + 1)); - if (!nn) { - for (int32_t j = 0; j < nnames; j++) - free(names[j]); - free(names); - return -1; - } - nn[nnames] = strdup(name); - uint8_t *iv; - int32_t ivl = kvspaceXvalueNewIndexGrow( - (const char **)nn, nnames + 1, grow_cap(old_cap, nnames + 1), old_m, &iv); - int rc = shm_set_raw(kv, mem, iv, ivl); - free(iv); - for (int32_t j = 0; j <= nnames; j++) - free(nn[j]); - free(nn); + free(meta); return rc; } -/* 从 memindex 移除成员名(幂等)。 */ -static int remove_child_index(kvspace_t *kv, const char *mem, - const char *name) { - art_hdr_t *h = - art_search(kv, kv->hdr->art_root, (const uint8_t *)mem, (int)strlen(mem)); +int kvspaceShmMetaGetAt(kvspace_t *kv, const char *key, uint8_t *ro, + uint32_t *vid) { + if (!kv || !key || !ro || !vid || kv_sync(kv) != 0) + return -1; + *ro = 0; + *vid = 0; + char *meta = NULL; + if (kvspaceMetaKey(key, &meta) != 0) + return -1; + art_hdr_t *h = art_search(kv, kv->hdr->art_root, + (const uint8_t *)meta, (int)strlen(meta)); + free(meta); if (!h || !h->has_value) return 0; - uint8_t *raw; - int32_t rl; - if (read_tlv(kv, h->box_offset, &raw, &rl) < 0) - return 0; - xvalue_head_t hh = kvspaceXvalueDecodeHead(raw, rl); - if (hh.ref != 0 || !is_kind(&hh, KVSPACE_KIND_INDEX)) - return 0; - int32_t old_cap = hh.ndim >= 2 && hh.dims[1] > 0 ? hh.dims[1] : 0; - int32_t old_m = hh.ndim >= 3 && hh.dims[2] > 0 ? hh.dims[2] : 0; - int32_t nnames; - char **names = index_names(&hh, &nnames); - if (!names) - return 0; - int32_t j = 0; - for (int32_t i = 0; i < nnames; i++) { - if (strcmp(names[i], name) == 0) { - free(names[i]); - continue; - } - names[j++] = names[i]; - } - if (j == nnames) { - for (int32_t i = 0; i < j; i++) - free(names[i]); - free(names); - return 0; - } - uint8_t *iv; - int32_t ivl = - kvspaceXvalueNewIndexGrow((const char **)names, j, old_cap, old_m, &iv); - int rc = shm_set_raw(kv, mem, iv, ivl); - free(iv); - for (int32_t i = 0; i < j; i++) - free(names[i]); - free(names); - return rc; + const uint8_t *data = kv->sbo_data + h->box_offset; + uint64_t allocated = sbo_allocated_size(kv->sbo_meta, h->box_offset); + kvspaceXh head; + if (kvspaceXhDecode(data, allocated, &head) != 0) + return -1; + return kvspaceMetaDecode(data, head.total, ro, vid); } -/* 写成员时沿父链逐层兜底容器值(leaf base + 中间层 - * stringkeymap)并注册成员(对齐 durable)。 parent 是尾 · - * 的成员父目录,name 是该成员名;逐层向上建容器值并注册成员到各自 memindex。 */ -static void ensure_member_chain(kvspace_t *kv, char *parent, char *name) { - char *dir = strdup(parent); - char *child = strdup(name); - for (;;) { - char *base = strip_dir_suf_alloc(dir); - art_hdr_t *ch = art_search(kv, kv->hdr->art_root, (const uint8_t *)base, - (int)strlen(base)); - if (!ch || !ch->has_value) { - if (kvspaceCoordIsCoord(child)) { - int32_t dims[8]; - int32_t ndim; - grow_coord_dims_one(child, dims, &ndim); - uint8_t *mv; - int32_t mvl = - kvspaceXvalueEncode(KVSPACE_KIND_MAP, NULL, 0, dims, ndim, &mv); - shm_set_raw(kv, base, mv, mvl); - free(mv); - } else { - int32_t odims[1] = { 0 }; - uint8_t *ov; - int32_t ovl = - kvspaceXvalueEncode(KVSPACE_KIND_MAP, NULL, 0, odims, 1, &ov); - shm_set_raw(kv, base, ov, ovl); - free(ov); - } - } - ensure_memindex(kv, dir); - if (add_child_index(kv, dir, child) == 1) { - /* 叶子成员已存在 → 祖先链早已建立,steady-state 写无需上溯 */ - free(base); - free(dir); - free(child); - break; - } - char *pp = NULL, *pn = NULL; - bool pm = false; - shm_split_index(base, &pp, &pn, &pm); - free(base); - if (!pm) { - free(pp); - free(pn); - free(dir); - free(child); - break; /* 父是层级目录(如 /),shm 不维护根 index */ - } - free(dir); - dir = pp; - free(child); - child = pn; - } -} - -/* 非容器写的父索引维护:member → ensure_member_chain;dir index/extindex → - 注册父 index。 kvspaceShmSet 与零拷贝 kvspaceShmWriteNewPlace - 共用,杜绝逻辑分叉。 */ -static void shm_ensure_indexes(kvspace_t *kv, const char *kbuf, - const xvalue_head_t *hh) { - char *parent = NULL, *name = NULL; - bool is_member = false; - shm_split_index(kbuf, &parent, &name, &is_member); - if (is_member) { - ensure_member_chain(kv, parent, name); - free(parent); - free(name); - return; - } - free(parent); - free(name); - size_t l = strlen(kbuf); - bool is_dir = (l > 0 && kbuf[l - 1] == '/') || - (l >= 2 && (unsigned char)kbuf[l - 2] == 0xC2 && - (unsigned char)kbuf[l - 1] == 0xB7); - if (is_dir && hh->ref == 0 && - (is_kind(hh, KVSPACE_KIND_INDEX) || - is_kind(hh, KVSPACE_KIND_EXT_INDEX))) { - char *strip = strip_dir_suf_alloc(kbuf); - char *pp = NULL, *pn = NULL; - bool pm = false; - shm_split_index(strip, &pp, &pn, &pm); - if (pn && pn[0]) { - char *dn = pn; - if (kbuf[l - 1] == '/') { - size_t nl = strlen(pn); - dn = malloc(nl + 2); - memcpy(dn, pn, nl); - dn[nl] = '/'; - dn[nl + 1] = 0; - } - add_child_index(kv, pp, dn); - if (dn != pn) - free(dn); - } - free(pp); - free(pn); - free(strip); - } -} - -/* langtype 前导 [dims] → 物理 dims(仅 ARRAYND 有意义);无则 ndim=0。 */ -static int32_t parse_langtype_dims(const char *lt, int32_t *dims) { - int32_t nd = 0; - if (lt && lt[0] == '[') { - const char *p = lt + 1; - while (*p && *p != ']' && nd < X_MAX_NDIM) { - int32_t d = 0; - while (*p >= '0' && *p <= '9') - d = d * 10 + (*p++ - '0'); - dims[nd++] = d; - if (*p == ',') - p++; - } - } - return nd; +int kvspaceShmMetaGet(kvspace_t *kv, const char *key, uint8_t *ro, + uint32_t *vid) { + if (!kv || !key || kv_sync(kv) != 0) + return -1; + char resolved[1024]; + resolve_path(kv, key, resolved, sizeof resolved); + return kvspaceShmMetaGetAt(kv, resolved, ro, vid); } -/* 分配 box、就地写三轴 head(ref/storetype/langtype, body_len),art_ins - 挂树,返回 body 偏移指针。 已存在 key 先释放旧 box(新位置写=换 - box)。零拷贝写路径唯一分配点。 */ -static int shm_alloc_head(kvspace_t *kv, const char *key, uint8_t ref, - uint8_t storetype, uint8_t ro, uint32_t vid, - const char *langtype, int32_t headlen, - int32_t body_len, uint8_t **body) { - int32_t total = headlen + body_len; - art_hdr_t *old = - art_search(kv, kv->hdr->art_root, (const uint8_t *)key, (int)strlen(key)); - if (old && old->has_value) - kv_sbo_free(kv, old->box_offset); - uint64_t off = kv_sbo_alloc(kv, (size_t)total); - if (off == (uint64_t)-1) - return -1; - int32_t dims[X_MAX_NDIM]; - int32_t ndim = parse_langtype_dims(langtype, dims); - kvspaceXvalueWriteHead(kv->sbo_data + off, ref, storetype, ro, vid, langtype, - dims, ndim, body_len); - kv->hdr->art_root = art_ins(kv, kv->hdr->art_root, (const uint8_t *)key, - (int)strlen(key), 0, off); - *body = kv->sbo_data + off + headlen; - return 0; +static int frame_operand_ptr(const char *key, const kvspaceXh *wire) { + if (wire->kind != (KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG) || + strncmp(key, "/vthread/", 9) != 0) + return 0; + const char *slot = strrchr(key, '/'); + if (!slot || slot[1] != '[') + return 0; + char *end; + long row = strtol(slot + 2, &end, 10); + if (row <= 0 || *end++ != ',') + return 0; + long col = strtol(end, &end, 10); + return col != 0 && *end++ == ']' && *end == '\0'; } -int kvspaceShmSet(kvspace_t *kv, const char *key, const uint8_t *val, - int32_t val_len) { - if (!kv || !key) - return -1; - if (!val && val_len > 0) - return -1; +static int shm_set_validated(kvspace_t *kv, const char *key, + const uint8_t *val, int32_t val_len, + const kvspaceXh *wire, uint8_t ro, uint32_t vid) { if (kv_sync(kv) != 0) return -1; - if (val_len <= 0) { - /* None → 写 1 字节空 kind TLV(sbo 不支持 0 字节),读时 read_tlv 判 None - * 返 len 0。 */ - static const uint8_t none_tlv[1] = {0}; - val = none_tlv; - val_len = 1; - } char kbuf[1024]; - resolve_path(kv, key, kbuf, sizeof(kbuf)); // always resolve through link - if (strstr(kbuf, "//")) + if (strlen(key) >= sizeof kbuf || strstr(key, "//") || + reserved_meta_path(key)) return -1; + strcpy(kbuf, key); /* extindex 写保护:父是只读扩展层、本地无同名节点但扩展层有 → 禁止写(对齐 * durable backend.rs / fs)。以「父是否 extindex」为唯一首闸——非 extindex(如 @@ -1922,7 +1609,7 @@ int kvspaceShmSet(kvspace_t *kv, const char *key, const uint8_t *val, art_hdr_t *eh = art_search(kv, kv->hdr->art_root, (const uint8_t *)tgt, (int)strlen(tgt)); free(tgt); - if (eh && eh->has_value) { + if (eh && eh->has_value && !frame_operand_ptr(kbuf, wire)) { free(pp); free(nn); return -1; @@ -1933,66 +1620,35 @@ int kvspaceShmSet(kvspace_t *kv, const char *key, const uint8_t *val, free(nn); } - xvalue_head_t hh = kvspaceXvalueDecodeHead(val, val_len); - - /* 目录 kind(index/extindex)必须落在目录键(尾 / 或 ·)。 */ - if (hh.ref == 0 && (is_kind(&hh, KVSPACE_KIND_INDEX) || - is_kind(&hh, KVSPACE_KIND_EXT_INDEX))) { - size_t l = strlen(kbuf); - bool is_dir = (l > 0 && kbuf[l - 1] == '/') || - (l >= 2 && (unsigned char)kbuf[l - 2] == 0xC2 && - (unsigned char)kbuf[l - 1] == 0xB7); - if (!is_dir) - return -1; - } + if (shm_set_raw(kv, kbuf, val, val_len) != 0) + return -1; + return sync_metadata(kv, kbuf, ro, vid); +} - /* 容器值(stringkeymap):值写 p(无后缀、body 空、dims/ro/vid - * 保留), memindex p· 写空 index(成员由后续 add_child 维护);对齐 durable - * set() 的 Obj/Map 分支。 */ - if (hh.ref == 0 && - is_kind(&hh, KVSPACE_KIND_MAP)) { - char *base = strip_dir_suf_alloc(kbuf); - if (!base || !base[0]) { - free(base); - return -1; - } - char *kind = strndup(hh.kind, hh.kind_len); - uint8_t *cv; - int32_t cvl = kvspaceXvalueEncodeMode(kind, NULL, 0, hh.dims, hh.ndim, 0, - hh.ro, hh.vid, &cv); - int rc = shm_set_raw(kv, base, cv, cvl); - free(cv); - free(kind); - if (rc < 0) { - free(base); - return -1; - } - char *mem = memjoin(base); - uint8_t *iv; - int32_t ivl = kvspaceXvalueNewIndex(NULL, 0, &iv); - rc = shm_set_raw(kv, mem, iv, ivl); - free(iv); - free(mem); - /* 注册 base 为其父 memindex 成员(嵌套容器)。 */ - char *pp = NULL, *pn = NULL; - bool pm = false; - shm_split_index(base, &pp, &pn, &pm); - if (pm) - add_child_index(kv, pp, pn); - free(pp); - free(pn); - free(base); - return rc; - } +int kvspaceShmSet(kvspace_t *kv, const char *key, const uint8_t *val, + int32_t val_len) { + if (!kv || !key || !val || val_len <= 0) + return -1; + kvspaceXh wire; + if (kvspaceXhDecode(val, (uint64_t)val_len, &wire) != 0 || + wire.total != (uint64_t)val_len) + return -1; + return shm_set_validated(kv, key, val, val_len, &wire, 0, 0); +} - /* 成员/目录索引维护(与 WriteNewPlace 共用),随后落盘。 */ - shm_ensure_indexes(kv, kbuf, &hh); - return shm_set_raw(kv, kbuf, val, val_len); +int kvspaceShmSetValue(kvspace_t *kv, const char *key, const uint8_t *val, + int32_t val_len, uint8_t ro, uint32_t vid) { + kvspaceXh head; + if (!kv || !key || !val || val_len <= 0 || + kvspaceXhDecode(val, (uint64_t)val_len, &head) != 0 || + head.total != (uint64_t)val_len) + return -2; + return shm_set_validated(kv, key, val, val_len, &head, ro, vid); } int kvspaceShmWriteInPlace(kvspace_t *kv, const char *key, int resolve, int32_t body_len, uint8_t **body) { - if (!kv || !key || !body || body_len < 0) + if (!kv || !key || !body || body_len < 0 || reserved_meta_path(key)) return -1; if (kv_sync(kv) != 0) return -1; @@ -2013,81 +1669,62 @@ int kvspaceShmWriteInPlace(kvspace_t *kv, const char *key, int resolve, return -1; if ((!fetched && read_tlv(kv, h->box_offset, &raw, &rl) < 0) || rl <= 0) return -1; /* None 或读失败 → 强制走 NewPlace */ - xvalue_head_t hh = kvspaceXvalueDecodeHead(raw, rl); - if (hh.raw_len != body_len) - return -1; /* 前置条件:同 body_len(同 kind 覆写) */ - *body = raw + kvspaceXvalueHeadLen(&hh); - return 0; + kvspaceXh wire; + if (kvspaceXhDecode(raw, (uint64_t)rl, &wire) == 0) { + if (wire.content_len != (uint64_t)body_len) + return -1; + *body = raw + wire.headlen; + return 0; + } + return -1; } int kvspaceShmWriteNewPlace(kvspace_t *kv, const char *key, uint8_t ref, uint8_t storetype, uint8_t ro, uint32_t vid, const char *langtype, int32_t body_len, + uint64_t body_cap, uint8_t **body) { if (!kv || !key || !langtype || !body || body_len < 0) return -1; + if (body_cap > INT32_MAX - 32) + return -1; + *body = NULL; if (kv_sync(kv) != 0) return -1; - char kbuf[1024]; - strncpy(kbuf, key, sizeof(kbuf) - 1); - kbuf[sizeof(kbuf) - 1] = '\0'; /* 写键本身,不穿透 link——显式解引用由 runtime 掌控 */ - if (strstr(kbuf, "//")) - return -1; - - int32_t hdims[X_MAX_NDIM]; - int32_t hndim = parse_langtype_dims(langtype, hdims); - int32_t headlen = kvspaceXvalueHeadLenForLangtype(storetype, langtype, hndim); - uint8_t hbuf[512]; - if (headlen > (int32_t)sizeof(hbuf)) - return -1; - kvspaceXvalueWriteHead(hbuf, ref, storetype, ro, vid, langtype, hdims, hndim, 0); - xvalue_head_t hh = kvspaceXvalueDecodeHead(hbuf, headlen); - - size_t l = strlen(kbuf); - bool is_dir = (l > 0 && kbuf[l - 1] == '/') || - (l >= 2 && (unsigned char)kbuf[l - 2] == 0xC2 && - (unsigned char)kbuf[l - 1] == 0xB7); - if (hh.ref == 0 && - (is_kind(&hh, KVSPACE_KIND_INDEX) || - is_kind(&hh, KVSPACE_KIND_EXT_INDEX)) && - !is_dir) - return -1; - - /* 容器值(stringkeymap,body 恒空):base 空 box + 空 memindex + - * 注册父。 */ - if (hh.ref == 0 && - is_kind(&hh, KVSPACE_KIND_MAP)) { - if (body_len != 0) - return -1; - char *base = strip_dir_suf_alloc(kbuf); - if (!base || !base[0]) { - free(base); - return -1; - } - char *mem = memjoin(base); - uint8_t *iv; - int32_t ivl = kvspaceXvalueNewIndex(NULL, 0, &iv); - if (ivl > 0) { - shm_set_raw(kv, mem, iv, ivl); - free(iv); - } - free(mem); - char *pp = NULL, *pn = NULL; - bool pm = false; - shm_split_index(base, &pp, &pn, &pm); - if (pm) - add_child_index(kv, pp, pn); - free(pp); - free(pn); - int rc = - shm_alloc_head(kv, base, ref, storetype, ro, vid, langtype, headlen, 0, body); - free(base); - return rc; + size_t key_len = strlen(key); + if (key_len >= 1024 || strstr(key, "//") || reserved_meta_path(key)) + return -1; + uint8_t kind; + if (ref == 0 && storetype <= KVSPACE_XH_FIXED_LARGE) + kind = storetype; + else if (ref == 1 && storetype == KVSPACE_XH_SLACK) + kind = KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG; + else if (ref == 2 && storetype == KVSPACE_XH_EXT) + kind = KVSPACE_XH_EXT; + else + return -1; + uint8_t *value = NULL; + uint64_t total = 0; + if (kvspaceXhReserve(kind, langtype, (uint64_t)body_len, + body_cap, &value, &total) != 0) + return -1; + if (total > INT32_MAX) { + free(value); + return -1; } - - shm_ensure_indexes(kv, kbuf, &hh); - return shm_alloc_head(kv, kbuf, ref, storetype, ro, vid, langtype, headlen, - body_len, body); + uint32_t headlen = 1u << value[0]; + int rc = sync_metadata(kv, key, ro, vid); + if (rc == 0) + rc = shm_set_raw(kv, key, value, (int32_t)total); + free(value); + if (rc != 0) + return -1; + art_hdr_t *node = art_search(kv, kv->hdr->art_root, + (const uint8_t *)key, (int)key_len); + if (!node || !node->has_value) + return -1; + *body = kv->sbo_data + node->box_offset + headlen; + return 0; } int kvspaceShmListLen(kvspace_t *kv, const char *prefix, bool ex, int resolve, @@ -2113,14 +1750,8 @@ int kvspaceShmDel(kvspace_t *kv, const char *key) { char kbuf[1024]; strncpy(kbuf, key, sizeof(kbuf) - 1); kbuf[sizeof(kbuf) - 1] = '\0'; /* 删键本身,不穿透 link——显式解引用由 runtime 掌控 */ - /* memindex 成员删除 → 同步从 p· 的 index 移除。 */ - char *parent = NULL, *name = NULL; - bool is_member = false; - shm_split_index(kbuf, &parent, &name, &is_member); - if (is_member) - remove_child_index(kv, parent, name); - free(parent); - free(name); + if (sync_metadata(kv, kbuf, 0, 0) != 0) + return -1; bool d = false; kv->hdr->art_root = art_del(kv, kv->hdr->art_root, (const uint8_t *)kbuf, (int)strlen(kbuf), 0, &d); @@ -2128,7 +1759,7 @@ int kvspaceShmDel(kvspace_t *kv, const char *key) { } int kvspaceShmDeltree(kvspace_t *kv, const char *prefix) { - if (!kv || !prefix) + if (!kv || !prefix || reserved_meta_path(prefix)) return -1; if (kv_sync(kv) != 0) return -1; @@ -2139,8 +1770,10 @@ int kvspaceShmDeltree(kvspace_t *kv, const char *prefix) { uint8_t *raw; int32_t rl; read_tlv(kv, h->box_offset, &raw, &rl); - xvalue_head_t hh = kvspaceXvalueDecodeHead(raw, rl); - if (hh.ref == 1) { + kvspaceXh wire; + int is_ptr = rl > 0 && kvspaceXhDecode(raw, (uint64_t)rl, &wire) == 0 && + (wire.kind & KVSPACE_XH_PTR_FLAG) != 0; + if (is_ptr) { return kvspaceShmDel(kv, prefix); } } @@ -2201,7 +1834,7 @@ static char *memdir(const char *p) { return r; } -/* 单 key 原样拷贝(Set 可能移动 slab,先拷出)。 */ +/* Copy one value before Set can move its slab. */ int kvspaceShmCp(kvspace_t *kv, const char *src, const char *dst) { if (!kv || !src || !dst) return -1; @@ -2210,146 +1843,181 @@ int kvspaceShmCp(kvspace_t *kv, const char *src, const char *dst) { if (!raw || rl <= 0) return -1; uint8_t *tmp = malloc((size_t)rl); + if (!tmp) + return -1; memcpy(tmp, raw, (size_t)rl); + uint8_t ro = 0; + uint32_t vid = 0; + if (kvspaceShmMetaGetAt(kv, src, &ro, &vid) != 0) { + free(tmp); + return -1; + } int rc = kvspaceShmSet(kv, dst, tmp, rl); free(tmp); - return rc; + return rc == 0 ? sync_metadata(kv, dst, ro, vid) : rc; } -/* 递归拷贝:镜像 kvspaceShmDeltree 的遍历(/ 子节点 + · 成员),逐 key - 原样复制。 index 由 ART 前缀扫描派生,故写入 dst 各 key - 即自动重建目录;extindex marker 一并复制。 */ +/* Copy values and physical children recursively. */ static int cptree_rec(kvspace_t *kv, const char *src, const char *dst) { { int32_t rl; uint8_t *raw = kvspaceShmGet(kv, src, 0, &rl); if (raw && rl > 0) { - uint8_t *tmp = malloc((size_t)rl); - memcpy(tmp, raw, (size_t)rl); - kvspaceShmSet(kv, dst, tmp, rl); - free(tmp); + if (kvspaceShmCp(kv, src, dst) != 0) + return -1; } } char *es = edir(src), *ed = edir(dst); char **ns; int32_t nc; - kvspaceShmList(kv, es, false, 1, &ns, &nc); + if (kvspaceShmList(kv, es, false, 1, &ns, &nc) != 0) { + free(es); + free(ed); + return -1; + } + int rc = 0; for (int i = 0; i < nc; i++) { char *cs = pjoin(es, ns[i]), *cd = pjoin(ed, ns[i]); - cptree_rec(kv, cs, cd); + if (!cs || !cd || cptree_rec(kv, cs, cd) != 0) + rc = -1; free(cs); free(cd); + if (rc != 0) + break; } for (int i = 0; i < nc; i++) free(ns[i]); free(ns); free(es); free(ed); + if (rc != 0) + return -1; char *ms = memdir(src), *md = memdir(dst); - kvspaceShmCp(kv, ms, - md); /* memindex marker 值(extindex marker / map dims)本身。 */ char **mms; int32_t mc; - kvspaceShmList(kv, ms, false, 1, &mms, &mc); + if (kvspaceShmList(kv, ms, false, 1, &mms, &mc) != 0) { + free(ms); + free(md); + return -1; + } for (int i = 0; i < mc; i++) { size_t msl = strlen(ms), mdl = strlen(md), nl = strlen(mms[i]); char *cs = malloc(msl + nl + 1); - memcpy(cs, ms, msl); - memcpy(cs + msl, mms[i], nl + 1); char *cd = malloc(mdl + nl + 1); - memcpy(cd, md, mdl); - memcpy(cd + mdl, mms[i], nl + 1); - cptree_rec(kv, cs, cd); + if (!cs || !cd) { + rc = -1; + } else { + memcpy(cs, ms, msl); + memcpy(cs + msl, mms[i], nl + 1); + memcpy(cd, md, mdl); + memcpy(cd + mdl, mms[i], nl + 1); + if (cptree_rec(kv, cs, cd) != 0) + rc = -1; + } free(cs); free(cd); + if (rc != 0) + break; } for (int i = 0; i < mc; i++) free(mms[i]); free(mms); free(ms); free(md); - return 0; + return rc; } int kvspaceShmCptree(kvspace_t *kv, const char *src, const char *dst) { - if (!kv || !src || !dst) - return -1; - kvspaceShmDeltree(kv, dst); /* 覆盖语义:先清 dst 子树。 */ + if (!kv || !src || !dst || reserved_meta_path(src) || + reserved_meta_path(dst)) + return -1; + size_t sl = strlen(src); + while (sl > 1 && src[sl - 1] == '/') + sl--; + if (sl == 1 && src[0] == '/' && dst[0] == '/') + return dst[1] ? -1 : 0; + if (strncmp(src, dst, sl) == 0) { + if (dst[sl] == 0 || (dst[sl] == '/' && dst[sl + 1] == 0)) + return 0; + if (dst[sl] == '/' || + ((unsigned char)dst[sl] == 0xc2 && + (unsigned char)dst[sl + 1] == 0xb7)) + return -1; + } + kvspaceShmDeltree(kv, dst); return cptree_rec(kv, src, dst); } -/* 浅拷贝:base 值 + 一层 · 成员(不遍历 / 子节点、不递归成员子树)。用于单 - * struct/扁平容器。 */ +/* Copy the base value and direct member values. */ int kvspaceShmCplist(kvspace_t *kv, const char *src, const char *dst) { - if (!kv || !src || !dst) + if (!kv || !src || !dst || reserved_meta_path(src) || + reserved_meta_path(dst)) + return -1; + if (strcmp(src, dst) == 0) + return 0; + kvspaceShmDeltree(kv, dst); + int32_t rl; + uint8_t *raw = kvspaceShmGet(kv, src, 0, &rl); + if (raw && rl > 0 && kvspaceShmCp(kv, src, dst) != 0) return -1; - kvspaceShmDeltree(kv, dst); /* 覆盖语义:先清 dst 子树。 */ - kvspaceShmCp(kv, src, dst); /* base 值。 */ char *ms = memdir(src), *md = memdir(dst); - kvspaceShmCp(kv, ms, md); /* memindex marker(成员名单 / map dims)。 */ char **mms; int32_t mc; - kvspaceShmList(kv, ms, false, 1, &mms, &mc); + if (kvspaceShmList(kv, ms, false, 1, &mms, &mc) != 0) { + free(ms); + free(md); + return -1; + } size_t msl = strlen(ms), mdl = strlen(md); + int rc = 0; for (int i = 0; i < mc; i++) { size_t nl = strlen(mms[i]); char *cs = malloc(msl + nl + 1); - memcpy(cs, ms, msl); - memcpy(cs + msl, mms[i], nl + 1); char *cd = malloc(mdl + nl + 1); - memcpy(cd, md, mdl); - memcpy(cd + mdl, mms[i], nl + 1); - kvspaceShmCp(kv, cs, cd); /* 成员值,单 key,不递归。 */ + if (!cs || !cd) { + rc = -1; + } else { + memcpy(cs, ms, msl); + memcpy(cs + msl, mms[i], nl + 1); + memcpy(cd, md, mdl); + memcpy(cd + mdl, mms[i], nl + 1); + if (kvspaceShmCp(kv, cs, cd) != 0) + rc = -1; + } free(cs); free(cd); - free(mms[i]); + if (rc != 0) + break; } + for (int i = 0; i < mc; i++) + free(mms[i]); free(mms); free(ms); free(md); - return 0; + return rc; } int kvspaceShmMkindex(kvspace_t *kv, const char *path, uint32_t capacity) { if (!kv || !path) return -1; + (void)capacity; char *d = edir(path); - uint8_t *v; - int32_t vl = kvspaceXvalueNewIndexGrow(NULL, 0, (int32_t)capacity, 0, &v); - int r = kvspaceShmSet(kv, d, v, vl); + int32_t existing_len = 0; + if (kvspaceShmGet(kv, d, 0, &existing_len)) { + free(d); + return 0; + } + uint8_t *v = NULL; + uint64_t vl = 0; + int r = kvspaceXhNewShort("lib", NULL, 0, &v, &vl); + if (r == 0) + r = kvspaceShmSetValue(kv, d, v, (int32_t)vl, 0, 0); free(v); free(d); return r; } -/* 读 memindex(p·)的成员名:index 定宽矩阵 body 是成员名唯一权威,encode - * 侧已按 cmp_coord 规范排序(坐标 row-major - * 数值序、非坐标字典序),读侧原样返回矩阵行序即有序。 仅 p·(尾 - * ·)目录命中;slash 目录(p/)不自动维护 index,调用方回退 ART scan。 */ -static int read_index_names(kvspace_t *kv, const char *dir, char ***on, - int32_t *oc) { - *on = NULL; - *oc = 0; - size_t dl = strlen(dir); - if (dl < 2 || !((unsigned char)dir[dl - 2] == 0xC2 && - (unsigned char)dir[dl - 1] == 0xB7)) - return 0; /* 仅 memindex(p·) */ - art_hdr_t *h = - art_search(kv, kv->hdr->art_root, (const uint8_t *)dir, (int)dl); - if (!h || !h->has_value) - return 0; - uint8_t *raw; - int32_t rl; - if (read_tlv(kv, h->box_offset, &raw, &rl) < 0) - return 0; - xvalue_head_t hh = kvspaceXvalueDecodeHead(raw, rl); - if (hh.ref != 0 || !is_kind(&hh, KVSPACE_KIND_INDEX)) - return 0; - *on = index_names(&hh, oc); - return 1; -} - /* 提取直接成员名长度:到第一个 / 或 ·(U+00B7,2 字节)为止。 */ static int child_name_len(const char *rest, int restlen) { for (int i = 0; i < restlen; i++) { @@ -2362,110 +2030,136 @@ static int child_name_len(const char *rest, int restlen) { return restlen; } +static int child_cmp(const void *a, const void *b) { + const char *x = *(const char *const *)a; + const char *y = *(const char *const *)b; + size_t nx = strlen(x), ny = strlen(y); + char *tx = nx && x[nx - 1] == '/' ? strndup(x, nx - 1) : NULL; + char *ty = ny && y[ny - 1] == '/' ? strndup(y, ny - 1) : NULL; + int c = kvspaceCoordCmp(tx ? tx : x, ty ? ty : y); + free(tx); + free(ty); + return c ? c : strcmp(x, y); +} + +static int same_child(const char *a, const char *b) { + size_t na = strlen(a), nb = strlen(b); + if (na && a[na - 1] == '/') na--; + if (nb && b[nb - 1] == '/') nb--; + return na == nb && memcmp(a, b, na) == 0; +} + +static int append_direct_children(keyscan_t *children, const keyscan_t *keys, + size_t prefix_len) { + for (int32_t i = 0; i < keys->count; i++) { + const char *key = keys->keys[i]; + size_t key_len = strlen(key); + if (key_len <= prefix_len) + continue; + const char *rest = key + prefix_len; + int len = child_name_len(rest, (int)(key_len - prefix_len)); + if (len <= 0) + continue; + if (rest[len] == '/' && (size_t)len + 1 == key_len - prefix_len) + len++; + if (keyscan_add_n(children, rest, (size_t)len) != 0) + return -1; + } + return 0; +} + int kvspaceShmList(kvspace_t *kv, const char *prefix, bool ex, int resolve, char ***on, int32_t *oc) { if (!kv || !prefix || !on || !oc) return -1; *on = NULL; *oc = 0; + if (reserved_meta_path(prefix)) + return 0; if (kv_sync(kv) != 0 || bad_dir_prefix(prefix)) return -1; const char *pfx = prefix; char tbuf[1024]; if (resolve) { - resolve_path(kv, prefix, tbuf, sizeof(tbuf)); + resolve_path(kv, prefix, tbuf, sizeof tbuf); pfx = tbuf; } - /* memindex(p·):读 index body 成员名(唯一权威);stringkeymap 按坐标 - * row-major 升序。读到 0 个名字时回落到 ART scan(空 memindex 不是「没有子项」)。 */ - if (read_index_names(kv, pfx, on, oc) && *oc > 0) - return 0; - int plen = (int)strlen(pfx); - char **out = malloc(sizeof(char *) * 4096); - int32_t n = 0; - char buf[2048]; - memset(buf, 0, sizeof(buf)); - if (kv->hdr->art_root < 0) + if (reserved_meta_path(pfx)) return 0; - art_scan_pfx(kv, pfx, plen, buf, (int)sizeof(buf), &out, &n); - // filter: only direct children (one level below prefix) - char **filt = malloc(sizeof(char *) * n); - int32_t fn = 0; - for (int i = 0; i < n; i++) { - const char *k = out[i]; - int kl = (int)strlen(k); - if (kl <= plen) - continue; - // extract the name segment immediately after prefix - const char *rest = k + plen; - int nlen = child_name_len(rest, kl - plen); - if (nlen == 0) - continue; - // dedup - bool dup = false; - for (int j = 0; j < fn; j++) - if (strncmp(filt[j], rest, nlen) == 0 && filt[j][nlen] == '\0') { - dup = true; - break; - } - if (!dup) { - filt[fn] = strndup(rest, nlen); - fn++; - } - } - for (int i = 0; i < n; i++) - free(out[i]); - free(out); + keyscan_t keys = {0}, children = {0}; + char buf[4096]; + if (kv->hdr->art_root >= 0 && + art_scan_pfx(kv, pfx, (int)strlen(pfx), buf, (int)sizeof buf, &keys) != 0) + goto fail; + if (append_direct_children(&children, &keys, strlen(pfx)) != 0) + goto fail; + keyscan_free(&keys); - /* extindex 展开:ex 且 prefix 是 extindex → 追加 extpath 的直接子项。 */ if (ex) { char extpath[1024]; - char *d = edir(pfx); - if (dir_ext_path(kv, d, extpath, sizeof extpath)) { - char **eo = malloc(sizeof(char *) * 4096); - int32_t en = 0; - char ebuf[2048]; - memset(ebuf, 0, sizeof ebuf); - int el = (int)strlen(extpath); - art_scan_pfx(kv, extpath, el, ebuf, (int)sizeof ebuf, &eo, &en); - filt = realloc(filt, sizeof(char *) * (size_t)(fn + en)); - for (int i = 0; i < en; i++) { - const char *k = eo[i]; - int kl = (int)strlen(k); - if (kl <= el) - continue; - const char *rest = k + el; - int nlen = child_name_len(rest, kl - el); - if (nlen == 0) - continue; - bool dup = false; - for (int j = 0; j < fn; j++) - if (strncmp(filt[j], rest, (size_t)nlen) == 0 && - filt[j][nlen] == '\0') { - dup = true; - break; - } - if (!dup) { - filt[fn] = strndup(rest, (size_t)nlen); - fn++; - } + char *dir = edir(pfx); + if (!dir) + goto fail; + int has_ext = dir_ext_path(kv, dir, extpath, sizeof extpath); + free(dir); + if (has_ext) { + if (art_scan_pfx(kv, extpath, (int)strlen(extpath), buf, + (int)sizeof buf, &keys) != 0 || + append_direct_children(&children, &keys, strlen(extpath)) != 0) + goto fail; + keyscan_free(&keys); + } + } + if (children.count > 1) + qsort(children.keys, (size_t)children.count, sizeof *children.keys, + child_cmp); + int32_t unique = 0; + for (int32_t i = 0; i < children.count; i++) { + if (strcmp(pfx, "/") == 0 && + strcmp(children.keys[i], ".kvspace-meta") == 0) { + free(children.keys[i]); + continue; + } + if (unique && same_child(children.keys[unique - 1], children.keys[i])) { + if (children.keys[i][strlen(children.keys[i]) - 1] == '/') { + free(children.keys[unique - 1]); + children.keys[unique - 1] = children.keys[i]; + continue; } - for (int i = 0; i < en; i++) - free(eo[i]); - free(eo); + free(children.keys[i]); + continue; } - free(d); + children.keys[unique++] = children.keys[i]; } - - *on = filt; - *oc = fn; + children.count = unique; + *on = children.keys; + *oc = children.count; return 0; + +fail: + keyscan_free(&keys); + keyscan_free(&children); + return -1; } int kvspaceShmExtindex(kvspace_t *kv, const char *p, const char *ep) { - uint8_t *v; - int32_t vl = kvspaceXvalueNewExtindex(ep, NULL, 0, &v); - int r = kvspaceShmSet(kv, p, v, vl); + if (!kv || !p || !ep) + return -1; + int32_t source_len = 0; + uint8_t *source = kvspaceShmGet(kv, ep, 0, &source_len); + kvspaceXh source_head; + if (!source || source_len <= 0 || + kvspaceXhDecode(source, (uint64_t)source_len, &source_head) != 0) + return -1; + char *type = strndup((const char *)source_head.langtype, source_head.langtype_len); + if (!type) + return -1; + uint8_t *v = NULL; + uint64_t vl = 0; + int r = kvspaceXhNewExt(type, ep, strlen(ep), &v, &vl); + if (r == 0) + r = kvspaceShmSetValue(kv, p, v, (int32_t)vl, 0, 0); + free(type); free(v); return r; } diff --git a/src/kvspace_shm.h b/src/kvspace_shm.h index 987262b..150df0a 100644 --- a/src/kvspace_shm.h +++ b/src/kvspace_shm.h @@ -8,7 +8,7 @@ #ifndef KVSPACE_H #define KVSPACE_H -#include "xvalue.h" +#include "coord.h" #include #include #include @@ -39,6 +39,8 @@ void kvspaceShmClose(kvspace_t *kv); // Get: resolve=1 穿透 link,resolve=0 返回 link 本身。 uint8_t *kvspaceShmGet(kvspace_t *kv, const char *key, int resolve, int32_t *out_len); +int kvspaceShmMetaGet(kvspace_t *kv, const char *key, uint8_t *ro, uint32_t *vid); +int kvspaceShmMetaGetAt(kvspace_t *kv, const char *key, uint8_t *ro, uint32_t *vid); int kvspaceShmResolveRef(kvspace_t *kv, const char *key, kvspaceRef_t *ref); uint8_t *kvspaceShmGetByRef(kvspace_t *kv, kvspaceRef_t *ref, @@ -50,6 +52,8 @@ int kvspaceShmSetPartByRef(kvspace_t *kv, kvspaceRef_t *ref, // Set: 写入 value(TLV 编码的字节)。总是穿透 link 写入 target。 int kvspaceShmSet(kvspace_t *kv, const char *key, const uint8_t *val, int32_t val_len); +int kvspaceShmSetValue(kvspace_t *kv, const char *key, const uint8_t *val, + int32_t val_len, uint8_t ro, uint32_t vid); // 零拷贝写原语——返回 SHM 常驻 body 偏移指针供调用方直接写;写即持久,无收尾。 // WriteInPlace: key 必须已存在、body_len 必须等于原 body_len(同 kind @@ -61,6 +65,7 @@ int kvspaceShmWriteInPlace(kvspace_t *kv, const char *key, int resolve, int kvspaceShmWriteNewPlace(kvspace_t *kv, const char *key, uint8_t ref, uint8_t storetype, uint8_t ro, uint32_t vid, const char *langtype, int32_t body_len, + uint64_t body_cap, uint8_t **body); // ListLen: 只返回前缀下子项计数,无缓冲。 int kvspaceShmListLen(kvspace_t *kv, const char *prefix, bool expand_ext, diff --git a/src/xvalue.c b/src/xvalue.c deleted file mode 100644 index 667b712..0000000 --- a/src/xvalue.c +++ /dev/null @@ -1,523 +0,0 @@ -#include "xvalue.h" -#include -#include -#include -#include - -/* 对齐 kvspace-durable 的 kindexp TLV 编解码。 */ - -static void wr_u8(uint8_t *dst, uint8_t v) { dst[0] = v; } -static void wr_u16(uint8_t *dst, uint16_t v) { - dst[0] = (uint8_t)v; - dst[1] = (uint8_t)(v >> 8); -} -static void wr_u32(uint8_t *dst, uint32_t v) { - for (int i = 0; i < 4; i++) - dst[i] = (uint8_t)(v >> (i * 8)); -} -static void wr_u64(uint8_t *dst, uint64_t v) { - for (int i = 0; i < 8; i++) - dst[i] = (uint8_t)(v >> (i * 8)); -} -static void wr_i8(uint8_t *dst, int8_t v) { wr_u8(dst, (uint8_t)v); } -static void wr_i16(uint8_t *dst, int16_t v) { wr_u16(dst, (uint16_t)v); } -static void wr_i32(uint8_t *dst, int32_t v) { wr_u32(dst, (uint32_t)v); } -static void wr_i64(uint8_t *dst, int64_t v) { wr_u64(dst, (uint64_t)v); } - -static uint16_t rd_u16(const uint8_t *r) { - return (uint16_t)((uint16_t)r[0] | ((uint16_t)r[1] << 8)); -} -static uint32_t rd_u32(const uint8_t *r) { - return (uint32_t)r[0] | ((uint32_t)r[1] << 8) | ((uint32_t)r[2] << 16) | - ((uint32_t)r[3] << 24); -} - -static int32_t header_array_len(int32_t ndim, const int32_t *dims) { - if (ndim <= 0) - return 1; - int32_t n = 1; - for (int i = 0; i < ndim; i++) - n *= dims[i]; - return n; -} - -#define X_HEAD_PREFIX \ - 13 /* headlen(2)+ref(1)+storetype(1)+ro(1)+vid(4)+body_len(4) */ - -/* ARRAYND / index / extindex 携带 ndim+dims 物理字段。 */ -static int store_has_dims(uint8_t st) { - return st == KVSPACE_STORETYPE_ARRAYND || st == KVSPACE_STORETYPE_INDEX || - st == KVSPACE_STORETYPE_EXTINDEX; -} - -int32_t kvspaceXvalueHeadLen(const xvalue_head_t *h) { return h->headlen; } - -int32_t kvspaceXvalueHeadLenForLangtype(uint8_t storetype, const char *langtype, - int32_t ndim) { - if (!store_has_dims(storetype)) - ndim = 0; - int32_t lt = langtype ? (int32_t)strlen(langtype) : 0; - int32_t phys = store_has_dims(storetype) ? (1 + 4 * ndim) : 0; - return X_HEAD_PREFIX + phys + lt; -} - -void kvspaceXvalueWriteHead(uint8_t *dst, uint8_t ref, uint8_t storetype, - uint8_t ro, uint32_t vid, const char *langtype, - const int32_t *dims, int32_t ndim, - int32_t body_len) { - if (!store_has_dims(storetype)) - ndim = 0; - int32_t lt = langtype ? (int32_t)strlen(langtype) : 0; - int32_t phys = store_has_dims(storetype) ? (1 + 4 * ndim) : 0; - int32_t headlen = X_HEAD_PREFIX + phys + lt; - wr_u16(dst, (uint16_t)headlen); - dst[2] = ref; - dst[3] = storetype; - dst[4] = ro; /* ro */ - wr_u32(dst + 5, vid); /* vid */ - wr_u32(dst + 9, (uint32_t)body_len); /* body_len */ - int32_t o = X_HEAD_PREFIX; - if (store_has_dims(storetype)) { - dst[o++] = (uint8_t)ndim; - for (int i = 0; i < ndim; i++) { - wr_u32(dst + o, (uint32_t)dims[i]); - o += 4; - } - } - if (lt > 0) - memcpy(dst + o, langtype, (size_t)lt); -} - -/* 由 base 种类名(+ndim)推 storetype——便利编码函数用;WriteNewPlace 直接收 - * storetype。 */ -static int is_index_kind(const char *kind) { - return strcmp(kind, KVSPACE_KIND_INDEX) == 0 || - strcmp(kind, KVSPACE_KIND_EXT_INDEX) == 0 || - strcmp(kind, KVSPACE_KIND_RWFUNC) == 0 || - strcmp(kind, KVSPACE_KIND_DEF_RWIR) == 0; -} -static uint8_t storetype_of(const char *kind, int32_t ndim) { - if (!kind || !kind[0]) - return KVSPACE_STORETYPE_NONE; - if (strcmp(kind, KVSPACE_KIND_EXT_INDEX) == 0) - return KVSPACE_STORETYPE_EXTINDEX; - if (is_index_kind(kind)) - return KVSPACE_STORETYPE_INDEX; - if (ndim > 0) - return KVSPACE_STORETYPE_ARRAYND; - return KVSPACE_STORETYPE_ATOM; -} - -/* 指针 head 的 storetype = 目标语义 storetype(据目标完整 kindexpr - * 推;指针自身物理字段恒空)。 */ -static uint8_t storetype_from_kindexpr(const char *kx) { - if (!kx || !kx[0]) - return KVSPACE_STORETYPE_NONE; - const char *base = kx; - int has_dims = 0; - if (*base == '[') { - const char *p = strchr(base, ']'); - if (p) { - base = p + 1; - has_dims = 1; - } - } - if (strcmp(base, KVSPACE_KIND_EXT_INDEX) == 0) - return KVSPACE_STORETYPE_EXTINDEX; - if (is_index_kind(base) || base[0] == '/' || - strstr(base, "\xC2\xB7")) /* · = U+00B7 */ - return KVSPACE_STORETYPE_INDEX; - if (has_dims) - return KVSPACE_STORETYPE_ARRAYND; - return KVSPACE_STORETYPE_ATOM; -} - -/* langtype 串(ARRAYND 含 [dims],其余为裸种类名/路径)。 */ -static int32_t build_langtype(char *buf, int32_t cap, const char *kind, - uint8_t storetype, const int32_t *dims, - int32_t ndim) { - int32_t o = 0; - if (storetype == KVSPACE_STORETYPE_ARRAYND && ndim > 0) { - buf[o++] = '['; - for (int i = 0; i < ndim; i++) { - if (i > 0) - buf[o++] = ','; - o += snprintf(buf + o, (size_t)(cap - o), "%d", dims[i]); - } - buf[o++] = ']'; - } - int32_t kl = (int32_t)strlen(kind); - memcpy(buf + o, kind, (size_t)kl); - return o + kl; -} - -/* 核心编码:写三轴 head + body。dims 仅在 store_has_dims(storetype) - * 时落物理字段。 */ -static int32_t encode_head(uint8_t ref, uint8_t storetype, const char *langtype, - int32_t ro, uint32_t vid, const int32_t *dims, - int32_t ndim, const uint8_t *raw, int32_t raw_len, - uint8_t **out) { - if (!store_has_dims(storetype)) - ndim = 0; - int32_t lt = langtype ? (int32_t)strlen(langtype) : 0; - int32_t phys = store_has_dims(storetype) ? (1 + 4 * ndim) : 0; - int32_t headlen = X_HEAD_PREFIX + phys + lt; - int32_t total = headlen + raw_len; - uint8_t *buf = (uint8_t *)malloc((size_t)total); - if (!buf) - return -1; - wr_u16(buf, (uint16_t)headlen); - buf[2] = ref; - buf[3] = storetype; - buf[4] = (uint8_t)(ro ? 1 : 0); - wr_u32(buf + 5, vid); - wr_u32(buf + 9, (uint32_t)raw_len); - int32_t o = X_HEAD_PREFIX; - if (store_has_dims(storetype)) { - buf[o++] = (uint8_t)ndim; - for (int i = 0; i < ndim; i++) { - wr_u32(buf + o, (uint32_t)dims[i]); - o += 4; - } - } - if (lt > 0) - memcpy(buf + o, langtype, (size_t)lt); - if (raw_len > 0 && raw) - memcpy(buf + headlen, raw, (size_t)raw_len); - *out = buf; - return total; -} - -int32_t kvspaceXvalueEncode(const char *kind, const uint8_t *raw, - int32_t raw_len, const int32_t *dims, int32_t ndim, - uint8_t **out) { - return kvspaceXvalueEncodeMode(kind, raw, raw_len, dims, ndim, 0, 0, 0, out); -} - -int32_t kvspaceXvalueEncodeMode(const char *kind, const uint8_t *raw, - int32_t raw_len, const int32_t *dims, - int32_t ndim, int32_t ref, int32_t ro, - uint32_t vid, uint8_t **out) { - if (!out || !kind) - return -1; - if (ndim < 0) - ndim = 0; - if (ndim > X_MAX_NDIM) - return -1; - if (raw_len < 0) - raw_len = 0; - uint8_t storetype = storetype_of(kind, ndim); - char lt[256]; - int32_t ltl = - build_langtype(lt, (int32_t)sizeof lt, kind, storetype, dims, ndim); - lt[ltl] = 0; - return encode_head((uint8_t)ref, storetype, lt, ro, vid, dims, ndim, raw, - raw_len, out); -} - -/* 标量/一维便捷编码(内部):array_len → dims。char/* 恒一维(含空串/单字符); - * 其余标量(≤1)=0 维、多元素=1 维。公开的 kvspaceXvalueEncode 只认 dims/ndim。 - */ -static int32_t al_to_dims(const char *kind, int32_t array_len, int32_t *dims) { - if (strncmp(kind, "char/", 5) == 0) { - dims[0] = array_len < 0 ? 0 : array_len; - return 1; - } - if (array_len > 1) { - dims[0] = array_len; - return 1; - } - return 0; -} - -static int32_t encode_al(const char *kind, const uint8_t *raw, int32_t raw_len, - int32_t array_len, uint8_t **out) { - int32_t dims[1]; - int32_t nd = al_to_dims(kind, array_len, dims); - return kvspaceXvalueEncode(kind, raw, raw_len, dims, nd, out); -} - -xvalue_head_t kvspaceXvalueDecodeHead(const uint8_t *data, int32_t data_len) { - xvalue_head_t h = {0}; - if (!data || data_len < X_HEAD_PREFIX) - return h; - h.headlen = rd_u16(data); - h.ref = data[2]; - h.storetype = data[3]; - h.ro = data[4] & 0x01; - h.vid = rd_u32(data + 5); - h.raw_len = (int32_t)rd_u32(data + 9); - if ((int32_t)h.headlen < X_HEAD_PREFIX || data_len < (int32_t)h.headlen) - return h; - int32_t o = X_HEAD_PREFIX; - if (store_has_dims(h.storetype)) { - h.ndim = data[o++]; - if (h.ndim > X_MAX_NDIM) - h.ndim = X_MAX_NDIM; - for (int i = 0; i < h.ndim; i++) { - h.dims[i] = (int32_t)rd_u32(data + o); - o += 4; - } - } - h.langtype = (const char *)(data + o); - h.langtype_len = (int32_t)h.headlen - o; - if (h.langtype_len < 0) - h.langtype_len = 0; - /* kind = langtype 越过前导 [dims](若有)。 */ - int32_t i = 0; - if (i < h.langtype_len && h.langtype[i] == '[') { - while (i < h.langtype_len && h.langtype[i] != ']') - i++; - if (i < h.langtype_len) - i++; - } - h.kind = h.langtype + i; - h.kind_len = h.langtype_len - i; - if (data_len < (int32_t)h.headlen + h.raw_len) - return h; - h.raw = data + h.headlen; - h.array_len = - store_has_dims(h.storetype) ? header_array_len(h.ndim, h.dims) : 1; - return h; -} - -#define DEF_NEW_ARRAY(name, kind, T, elem_sz, wr_fn) \ - int32_t kvspaceXvalueNew##name(const T *vals, int32_t count, \ - uint8_t **out) { \ - if (!vals || count <= 0) \ - return -1; \ - int32_t raw_len = count * elem_sz; \ - uint8_t *raw = (uint8_t *)malloc((size_t)raw_len); \ - if (!raw) \ - return -1; \ - for (int32_t i = 0; i < count; i++) \ - wr_fn(raw + i * elem_sz, vals[i]); \ - int32_t r = encode_al(kind, raw, raw_len, count, out); \ - free(raw); \ - return r; \ - } - -DEF_NEW_ARRAY(Bool, KVSPACE_KIND_BOOL, bool, 1, wr_u8) -DEF_NEW_ARRAY(Int8, KVSPACE_KIND_INT8, int8_t, 1, wr_i8) -DEF_NEW_ARRAY(Int16, KVSPACE_KIND_INT16, int16_t, 2, wr_i16) -DEF_NEW_ARRAY(Int32, KVSPACE_KIND_INT32, int32_t, 4, wr_i32) -DEF_NEW_ARRAY(Int64, KVSPACE_KIND_INT64, int64_t, 8, wr_i64) -DEF_NEW_ARRAY(Uint8, KVSPACE_KIND_UINT8, uint8_t, 1, wr_u8) -DEF_NEW_ARRAY(Uint16, KVSPACE_KIND_UINT16, uint16_t, 2, wr_u16) -DEF_NEW_ARRAY(Uint32, KVSPACE_KIND_UINT32, uint32_t, 4, wr_u32) -DEF_NEW_ARRAY(Uint64, KVSPACE_KIND_UINT64, uint64_t, 8, wr_u64) - -int32_t kvspaceXvalueNewFloat32(const float *vals, int32_t count, - uint8_t **out) { - if (!vals || count <= 0) - return -1; - int32_t raw_len = count * 4; - uint8_t *raw = (uint8_t *)malloc((size_t)raw_len); - if (!raw) - return -1; - for (int32_t i = 0; i < count; i++) { - union { - float f; - uint32_t u; - } c = {vals[i]}; - wr_u32(raw + i * 4, c.u); - } - int32_t r = encode_al(KVSPACE_KIND_FLOAT32, raw, raw_len, count, out); - free(raw); - return r; -} - -int32_t kvspaceXvalueNewFloat64(const double *vals, int32_t count, - uint8_t **out) { - if (!vals || count <= 0) - return -1; - int32_t raw_len = count * 8; - uint8_t *raw = (uint8_t *)malloc((size_t)raw_len); - if (!raw) - return -1; - for (int32_t i = 0; i < count; i++) { - union { - double f; - uint64_t u; - } c = {vals[i]}; - wr_u64(raw + i * 8, c.u); - } - int32_t r = encode_al(KVSPACE_KIND_FLOAT64, raw, raw_len, count, out); - free(raw); - return r; -} - -/* ── 坐标段比较(对齐 durable coord.rs;memindex 矩阵规范排序与容器 dims - * 派生共用)── */ - -int kvspaceCoordIsCoord(const char *name) { - if (!name || name[0] != '[') - return 0; - size_t n = strlen(name); - if (n < 3 || name[n - 1] != ']') - return 0; - for (size_t i = 1; i + 1 < n; i++) - if (name[i] == '[' || name[i] == ']') - return 0; - return 1; -} - -/* 解析整数坐标段,成功填充 coords 并返回维数;非整数返回 -1。 */ -int kvspaceParseCoord(const char *name, int64_t *coords, int maxn) { - if (!name || name[0] != '[') - return -1; - int n = 0; - int64_t cur = 0; - bool has = false; - for (const char *p = name + 1; *p; p++) { - if (*p >= '0' && *p <= '9') { - cur = cur * 10 + (*p - '0'); - has = true; - } else if (*p == ',') { - if (!has || n >= maxn) - return -1; - coords[n++] = cur; - cur = 0; - has = false; - } else if (*p == ']') { - if (!has || n >= maxn) - return -1; - coords[n++] = cur; - return (p[1] == '\0') ? n : -1; - } else { - return -1; - } - } - return -1; -} - -int kvspaceCoordCmp(const char *a, const char *b) { - int ia = kvspaceCoordIsCoord(a), ib = kvspaceCoordIsCoord(b); - if (!ia && !ib) - return strcmp(a, b); - if (!ia) - return 1; /* 非坐标段排后 */ - if (!ib) - return -1; - int64_t ca[8], cb[8]; - int na = kvspaceParseCoord(a, ca, 8); - int nb = kvspaceParseCoord(b, cb, 8); - if (na < 0 || nb < 0) - return strcmp(a, b); /* 含小数/字符串坐标 → 字典序 */ - for (int i = 0; i < na && i < nb; i++) { - if (ca[i] != cb[i]) - return ca[i] < cb[i] ? -1 : 1; - } - if (na != nb) - return na < nb ? -1 : 1; - return 0; -} - -/* ── index / ptr / extindex:定宽排序矩阵 memindex(Go-slice cap/len,对齐 - * durable xvalue_index.rs)── - * dims=[len,cap,M]:len=有效成员数、cap≥len=预留行数、M=成员 UTF-8 - * 字节最大长向上 8 对齐(行宽); body=cap×M,前 len 行成员名 + NUL - * 补齐(cmp_coord 有序),后 cap−len 行全 NUL。容量内增删 body 长度 恒 cap×M → - * 就地覆写不重分配;满则 cap 翻倍。与 durable blob 逐字节一致,listat/listlen - * O(1)。 */ - -static int32_t align8(int32_t n) { return (n + 7) & ~7; } - -static int matrix_qsort_cmp(const void *a, const void *b) { - return kvspaceCoordCmp(*(const char *const *)a, *(const char *const *)b); -} - -/* children → (dims=[len,cap,M], body=malloc(cap×M)),encode - * 侧规范排序;cap_hint/m_hint 为下限(只增)。 */ -static uint8_t *build_matrix(const char **children, int32_t count, - int32_t cap_hint, int32_t m_hint, int32_t *len_out, - int32_t *cap_out, int32_t *m_out, int32_t *bl) { - const char **c = - (const char **)malloc(sizeof(char *) * (size_t)(count > 0 ? count : 1)); - int32_t len = 0; - for (int32_t i = 0; i < count; i++) - if (children[i]) - c[len++] = children[i]; - if (len > 1) - qsort((void *)c, (size_t)len, sizeof(char *), matrix_qsort_cmp); - int32_t maxl = 0; - for (int32_t i = 0; i < len; i++) { - int32_t l = (int32_t)strlen(c[i]); - if (l > maxl) - maxl = l; - } - int32_t m = align8(maxl); - if (m_hint > m) - m = m_hint; - int32_t cap = len; - if (cap_hint > cap) - cap = cap_hint; - int32_t total = cap * m; - uint8_t *body = (uint8_t *)calloc((size_t)(total > 0 ? total : 1), 1); - for (int32_t i = 0; i < len; i++) - memcpy(body + (size_t)i * m, c[i], strlen(c[i])); - free((void *)c); - *len_out = len; - *cap_out = cap; - *m_out = m; - *bl = total; - return body; -} - -int32_t kvspaceXvalueNewIndexGrow(const char **children, int32_t count, - int32_t cap_hint, int32_t m_hint, - uint8_t **out) { - int32_t len, cap, m, bl; - uint8_t *body = - build_matrix(children, count, cap_hint, m_hint, &len, &cap, &m, &bl); - int32_t dims[3] = {len, cap, m}; - int32_t r = kvspaceXvalueEncode(KVSPACE_KIND_INDEX, body, bl, dims, 3, out); - free(body); - return r; -} - -int32_t kvspaceXvalueNewIndex(const char **children, int32_t count, - uint8_t **out) { - return kvspaceXvalueNewIndexGrow(children, count, 0, 0, out); -} - -/* 指针(ref=PTR):langtype = target_kindexpr(目标完整 - * kindexpr、无前缀),storetype = 目标语义 storetype,物理字段恒空;body = 目标 - * key 路径。 */ -int32_t kvspaceXvalueNewPtr(const char *target_kindexpr, const char *target, - uint8_t **out) { - if (!target || !target_kindexpr || !out) - return -1; - uint8_t st = storetype_from_kindexpr(target_kindexpr); - return encode_head(KVSPACE_REF_PTR, st, target_kindexpr, 0, 0, NULL, 0, - (const uint8_t *)target, (int32_t)strlen(target), out); -} - -/* extindex:body = 头部变长 ext_path + 尾部 cap×M 矩阵(childs,cmp_coord - * 有序);dims=[len,cap,M]。 ext_path 置头部:帧生命周期内一次写定、childs 才 - * churn,矩阵起点 off=body_len−cap*M 稳定。 */ -int32_t kvspaceXvalueNewExtindexGrow(const char *extpath, const char **children, - int32_t count, int32_t cap_hint, - int32_t m_hint, uint8_t **out) { - if (!extpath) - return -1; - int32_t len, cap, m, mbl; - uint8_t *mat = - build_matrix(children, count, cap_hint, m_hint, &len, &cap, &m, &mbl); - size_t el = strlen(extpath); - int32_t bl = (int32_t)el + mbl; - uint8_t *body = (uint8_t *)malloc((size_t)(bl > 0 ? bl : 1)); - memcpy(body, extpath, el); - memcpy(body + el, mat, (size_t)mbl); - int32_t dims[3] = {len, cap, m}; - int32_t r = - kvspaceXvalueEncode(KVSPACE_KIND_EXT_INDEX, body, bl, dims, 3, out); - free(mat); - free(body); - return r; -} - -int32_t kvspaceXvalueNewExtindex(const char *extpath, const char **children, - int32_t count, uint8_t **out) { - return kvspaceXvalueNewExtindexGrow(extpath, children, count, 0, 0, out); -} diff --git a/src/xvalue.h b/src/xvalue.h deleted file mode 100644 index 2825bfa..0000000 --- a/src/xvalue.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * xvalue.h — XValue 类型系统与三轴 head 编解码(对齐 kvspace/frontend.c 黄金基准 + kvspace-durable)。 - * - * head = [headlen u16 LE][ref u8][storetype u8][ro u8][vid u32 LE][body_len u32 LE] - * [storetype 物理字段][langtype kindexpr 串(占至 headlen)] - * body = [body_len B raw] - * ref 0=inline(body=值本体)/1=ptr(body=目标 key)/2=@ext(body=扩展定位符) - * storetype NONE/ATOM/ARRAYND/index/extindex;codec 唯一分派。 - * 物理字段:ARRAYND / index / extindex 为 ndim u8 + dims[ndim] u32 LE - * (index/extindex 的 dims=[len,cap,M]);NONE / ATOM 无物理字段。 - * langtype 完整 kindexpr 串,恒为 head 最后一段(长度 = headlen − 当前偏移,无独立长度字段)。 - * ARRAYND 含 [dims];ATOM/index/extindex 为裸种类名/路径;None 为空串。 - * Ptr: langtype=目标完整 kindexpr、body=目标 key、物理字段恒空。char/* 一维序列 → ARRAYND。 - */ - -#ifndef XVALUE_H -#define XVALUE_H - -#include -#include -#include - -#define KVSPACE_KIND_NONE "" -#define KVSPACE_KIND_BOOL "bool" -#define KVSPACE_KIND_INT8 "int8" -#define KVSPACE_KIND_INT16 "int16" -#define KVSPACE_KIND_INT32 "int32" -#define KVSPACE_KIND_INT64 "int64" -#define KVSPACE_KIND_UINT8 "uint8" -#define KVSPACE_KIND_UINT16 "uint16" -#define KVSPACE_KIND_UINT32 "uint32" -#define KVSPACE_KIND_UINT64 "uint64" -#define KVSPACE_KIND_FLOAT32 "float32" -#define KVSPACE_KIND_FLOAT64 "float64" -#define KVSPACE_KIND_CHAR "char/utf32" -#define KVSPACE_KIND_CHAR_UTF8 "char/utf8" -#define KVSPACE_KIND_CHAR_ASCII "char/ascii" -#define KVSPACE_KIND_MAP "stringkeymap" -#define KVSPACE_KIND_INDEX "index" -#define KVSPACE_KIND_EXT_INDEX "extindex" -#define KVSPACE_KIND_RWIR "rwir" -#define KVSPACE_KIND_RWFUNC "rwfunc" -#define KVSPACE_KIND_DEF_RWIR "def rwir" -#define KVSPACE_KIND_SCOPE "scope" -#define KVSPACE_KIND_TIME "time" -#define KVSPACE_KIND_DURATION "duration" - -#define X_MAX_NDIM 8 - -/* ref:存储位置维(head 第 2 字节)。 */ -#define KVSPACE_REF_INLINE 0 /* body = 值本体 raw */ -#define KVSPACE_REF_PTR 1 /* body = 目标 key 路径(软链接) */ -#define KVSPACE_REF_EXT 2 /* body = 扩展世界定位符 */ - -/* storetype:物理布局维(head 第 3 字节,codec 唯一分派)。 */ -#define KVSPACE_STORETYPE_NONE 0 /* 无物理字段,body 空 */ -#define KVSPACE_STORETYPE_ATOM 1 /* 无物理字段,body 定宽 raw */ -#define KVSPACE_STORETYPE_ARRAYND 2 /* ndim u8 + dims[ndim] u32 LE,body 稠密数组 */ -#define KVSPACE_STORETYPE_INDEX 3 /* 成员名矩阵 dims=[len,cap,M] */ -#define KVSPACE_STORETYPE_EXTINDEX 4 /* 同 index,cap 可增长 */ - -typedef struct { - uint16_t headlen; /* head 总字节数;body 起于偏移 headlen */ - int32_t ref; /* 存储位置:见 KVSPACE_REF_* */ - uint8_t storetype; /* 物理布局:见 KVSPACE_STORETYPE_* */ - const char *langtype; /* langtype kindexpr 串(data 内,含 [dims]、无前缀,非 NUL 终止) */ - int32_t langtype_len; /* langtype 内容长度 */ - const char *kind; /* 派生:base 种类名(越过 [dims],langtype 子串),非 NUL 终止 */ - int32_t kind_len; - int32_t ro; /* 1=只读,0=可写 */ - uint32_t vid; /* vthread id(默认 0) */ - int32_t ndim; /* ARRAYND:维数;index/extindex:3;NONE/ATOM:0 */ - int32_t dims[X_MAX_NDIM]; /* ARRAYND:各维长;index/extindex:[len,cap,M] */ - int32_t array_len; /* 派生:标量=1,定长=∏dims */ - int32_t raw_len; /* body 字节数 */ - const uint8_t *raw; /* body 指针(data + headlen) */ -} xvalue_head_t; - -/* head 字节数(不含 body) */ -int32_t kvspaceXvalueHeadLen(const xvalue_head_t *h); - -/* 由 (storetype, langtype, ndim) 直接算 head 字节数。零拷贝写路径用:ro/vid 恒 0。 */ -int32_t kvspaceXvalueHeadLenForLangtype(uint8_t storetype, const char *langtype, int32_t ndim); -/* 把 head(ref + storetype + ro + vid + langtype + 物理字段 + body_len)就地写入 dst 前 headlen - * 字节;body 随后由调用方填。dims 仅在 store_has_dims(storetype) 时落物理字段。 */ -void kvspaceXvalueWriteHead(uint8_t *dst, uint8_t ref, uint8_t storetype, uint8_t ro, uint32_t vid, - const char *langtype, const int32_t *dims, int32_t ndim, - int32_t body_len); - -/* 内联编码(ref=0)。dims/ndim 直接落盘:ndim=0 标量,dims 可为 NULL。 */ -int32_t kvspaceXvalueEncode(const char *kind, const uint8_t *raw, int32_t raw_len, - const int32_t *dims, int32_t ndim, uint8_t **out); -/* 带权限编码(ref + ro + vid)。 */ -int32_t kvspaceXvalueEncodeMode(const char *kind, const uint8_t *raw, int32_t raw_len, - const int32_t *dims, int32_t ndim, int32_t ref, int32_t ro, uint32_t vid, - uint8_t **out); -xvalue_head_t kvspaceXvalueDecodeHead(const uint8_t *data, int32_t data_len); - -/* raw 读取 helpers(小端) */ -static inline int8_t kvspaceXvalueRawInt8(const uint8_t *r) { return (int8_t)r[0]; } -static inline int16_t kvspaceXvalueRawInt16(const uint8_t *r) { return (int16_t)(r[0]|(r[1]<<8)); } -static inline int32_t kvspaceXvalueRawInt32(const uint8_t *r) { return (int32_t)(r[0]|(r[1]<<8)|(r[2]<<16)|(r[3]<<24)); } -static inline int64_t kvspaceXvalueRawInt64(const uint8_t *r) { return (int64_t)r[0]|((int64_t)r[1]<<8)|((int64_t)r[2]<<16)|((int64_t)r[3]<<24)|((int64_t)r[4]<<32)|((int64_t)r[5]<<40)|((int64_t)r[6]<<48)|((int64_t)r[7]<<56); } -static inline uint8_t kvspaceXvalueRawUint8(const uint8_t *r) { return r[0]; } -static inline uint16_t kvspaceXvalueRawUint16(const uint8_t *r) { return (uint16_t)(r[0]|(r[1]<<8)); } -static inline uint32_t kvspaceXvalueRawUint32(const uint8_t *r) { return (uint32_t)(r[0]|(r[1]<<8)|(r[2]<<16)|(r[3]<<24)); } -static inline uint64_t kvspaceXvalueRawUint64(const uint8_t *r) { return (uint64_t)r[0]|((uint64_t)r[1]<<8)|((uint64_t)r[2]<<16)|((uint64_t)r[3]<<24)|((uint64_t)r[4]<<32)|((uint64_t)r[5]<<40)|((uint64_t)r[6]<<48)|((uint64_t)r[7]<<56); } -static inline float kvspaceXvalueRawFloat32(const uint8_t *r) { union{uint32_t u;float f;}v;v.u=kvspaceXvalueRawUint32(r);return v.f;} -static inline double kvspaceXvalueRawFloat64(const uint8_t *r) { union{uint64_t u;double f;}v;v.u=kvspaceXvalueRawUint64(r);return v.f;} - -static inline int8_t kvspaceXvalueAtInt8(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawInt8(h->raw+idx); } -static inline int16_t kvspaceXvalueAtInt16(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawInt16(h->raw+idx*2); } -static inline int32_t kvspaceXvalueAtInt32(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawInt32(h->raw+idx*4); } -static inline int64_t kvspaceXvalueAtInt64(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawInt64(h->raw+idx*8); } -static inline uint8_t kvspaceXvalueAtUint8(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawUint8(h->raw+idx); } -static inline uint16_t kvspaceXvalueAtUint16(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawUint16(h->raw+idx*2); } -static inline uint32_t kvspaceXvalueAtUint32(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawUint32(h->raw+idx*4); } -static inline uint64_t kvspaceXvalueAtUint64(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawUint64(h->raw+idx*8); } -static inline float kvspaceXvalueAtFloat32(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawFloat32(h->raw+idx*4); } -static inline double kvspaceXvalueAtFloat64(const xvalue_head_t *h, int32_t idx) { return kvspaceXvalueRawFloat64(h->raw+idx*8); } -static inline bool kvspaceXvalueAtBool(const xvalue_head_t *h, int32_t idx) { return h->raw[idx] != 0; } - -int32_t kvspaceXvalueNewNone(uint8_t **out); - -int32_t kvspaceXvalueNewBool(const bool *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewInt8(const int8_t *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewInt16(const int16_t *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewInt32(const int32_t *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewInt64(const int64_t *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewUint8(const uint8_t *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewUint16(const uint16_t *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewUint32(const uint32_t *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewUint64(const uint64_t *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewFloat32(const float *vals, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewFloat64(const double *vals, int32_t count, uint8_t **out); - - -int32_t kvspaceXvalueNewIndex(const char **children, int32_t count, uint8_t **out); -/* memindex 定宽矩阵(dims=[len,cap,M],M 向上 8 对齐):cap_hint/m_hint 为容量与行宽下限(只增), - * 供增删时保留预留容量与既有行宽 → body 长度恒 cap×M、就地覆写不重分配。 */ -int32_t kvspaceXvalueNewIndexGrow(const char **children, int32_t count, int32_t cap_hint, - int32_t m_hint, uint8_t **out); -int32_t kvspaceXvalueNewPtr(const char *target_kindexpr, const char *target, uint8_t **out); -int32_t kvspaceXvalueNewExtindex(const char *extpath, const char **children, int32_t count, uint8_t **out); -int32_t kvspaceXvalueNewExtindexGrow(const char *extpath, const char **children, int32_t count, - int32_t cap_hint, int32_t m_hint, uint8_t **out); - -/* 坐标段比较(对齐 durable coord::cmp_coord):坐标段([i]/[i,j])数值 row-major 在前、 - * 非坐标段字典序在后。供 memindex 矩阵规范排序与容器 dims 派生共用。 */ -int kvspaceCoordIsCoord(const char *name); -int kvspaceParseCoord(const char *name, int64_t *coords, int maxn); -int kvspaceCoordCmp(const char *a, const char *b); - -#define kvspaceXvalueNewBool1(v, out) kvspaceXvalueNewBool(&(bool){v}, 1, out) -#define kvspaceXvalueNewInt81(v, out) kvspaceXvalueNewInt8(&(int8_t){v}, 1, out) -#define kvspaceXvalueNewInt161(v, out) kvspaceXvalueNewInt16(&(int16_t){v}, 1, out) -#define kvspaceXvalueNewInt321(v, out) kvspaceXvalueNewInt32(&(int32_t){v}, 1, out) -#define kvspaceXvalueNewInt641(v, out) kvspaceXvalueNewInt64(&(int64_t){v}, 1, out) -#define kvspaceXvalueNewUint81(v, out) kvspaceXvalueNewUint8(&(uint8_t){v}, 1, out) -#define kvspaceXvalueNewUint161(v, out) kvspaceXvalueNewUint16(&(uint16_t){v}, 1, out) -#define kvspaceXvalueNewUint321(v, out) kvspaceXvalueNewUint32(&(uint32_t){v}, 1, out) -#define kvspaceXvalueNewUint641(v, out) kvspaceXvalueNewUint64(&(uint64_t){v}, 1, out) -#define kvspaceXvalueNewFloat321(v, out) kvspaceXvalueNewFloat32(&(float){v}, 1, out) -#define kvspaceXvalueNewFloat641(v, out) kvspaceXvalueNewFloat64(&(double){v}, 1, out) - -#endif diff --git a/src/xvalue_head.c b/src/xvalue_head.c new file mode 100644 index 0000000..c8a4534 --- /dev/null +++ b/src/xvalue_head.c @@ -0,0 +1,576 @@ +#include "xvalue_head.h" + +#include +#include +#include +#include + +static void put_u64(uint8_t *d, uint64_t v) { + for (int i = 0; i < 8; i++) + d[i] = (uint8_t)(v >> (8 * i)); +} + +static uint64_t get_u64(const uint8_t *d) { + uint64_t v = 0; + for (int i = 0; i < 8; i++) + v |= (uint64_t)d[i] << (8 * i); + return v; +} + +static int headlen_of(uint8_t pow, uint32_t *headlen) { + if (pow < KVSPACE_XH_POW_SCALAR || pow > 31) + return -1; + *headlen = 1u << pow; + return 0; +} + +static int scalar_width(const uint8_t *s, uint32_t n) { + static const struct { + const char *name; + uint8_t w; + } tab[] = { + {"bool", 1}, {"int8", 1}, {"int16", 2}, {"int32", 4}, + {"int64", 8}, {"uint8", 1}, {"uint16", 2}, {"uint32", 4}, + {"uint64", 8}, {"float8/e4m3", 1}, {"float8/e5m2", 1}, + {"float16", 2}, {"bfloat16", 2}, {"float32", 4}, {"float64", 8}, + }; + for (size_t i = 0; i < sizeof tab / sizeof tab[0]; i++) { + size_t L = strlen(tab[i].name); + if (L == n && memcmp(s, tab[i].name, n) == 0) + return tab[i].w; + } + return -1; +} + +static int type_eq(const uint8_t *s, uint32_t n, const char *want) { + size_t m = strlen(want); + return n == m && memcmp(s, want, m) == 0; +} + +static int short_width(const uint8_t *s, uint32_t n) { + int width = scalar_width(s, n); + if (width >= 0 || n == 0) + return width >= 0 ? width : 0; + if (type_eq(s, n, "def rwir")) + return 5; + if (type_eq(s, n, "time") || type_eq(s, n, "duration")) + return 8; + if (type_eq(s, n, "def struct") || type_eq(s, n, "lib") || + type_eq(s, n, "rwfunc") || s[0] == '/') + return 0; + for (uint32_t i = 0; i + 1 < n; i++) + if (s[i] == 0xc2 && s[i + 1] == 0xb7) + return 0; + return -1; +} + +static int min_pow(uint32_t type_len) { + for (int pow = KVSPACE_XH_POW_SCALAR; pow <= 31; pow++) + if ((uint64_t)type_len + KVSPACE_XH_PREFIX <= (1ULL << pow)) + return pow; + return -1; +} + +static const struct { + const char *suf; + uint8_t width; +} k_slack[] = { + {"char/utf8", 0}, + {"byte", 1}, + {"char/utf32", 4}, + {"char/ascii", 1}, +}; + +static int slack_count(size_t k, const uint8_t *data, uint64_t len, uint64_t *count) { + uint8_t width = k_slack[k].width; + if (width) { + if (len % width) + return -1; + if (k == KVSPACE_XH_ASCII - KVSPACE_XH_UTF8) { + for (uint64_t i = 0; i < len; i++) + if (data[i] > 0x7f) + return -1; + } else if (k == KVSPACE_XH_UTF32 - KVSPACE_XH_UTF8) { + for (uint64_t i = 0; i < len; i += 4) { + uint32_t cp = (uint32_t)data[i] | (uint32_t)data[i + 1] << 8 | + (uint32_t)data[i + 2] << 16 | (uint32_t)data[i + 3] << 24; + if (cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) + return -1; + } + } + *count = len / width; + return 0; + } + *count = 0; + for (uint64_t i = 0; i < len; (*count)++) { + uint8_t c = data[i++]; + if (c < 0x80) + continue; + unsigned tail = c < 0xe0 ? 1 : c < 0xf0 ? 2 : 3; + if (c < 0xc2 || c > 0xf4 || len - i < tail) + return -1; + uint8_t next = data[i]; + if ((c == 0xe0 && next < 0xa0) || (c == 0xed && next > 0x9f) || + (c == 0xf0 && next < 0x90) || (c == 0xf4 && next > 0x8f)) + return -1; + for (unsigned j = 0; j < tail; j++) + if ((data[i++] & 0xc0) != 0x80) + return -1; + } + return 0; +} + +static int parse_u64(const uint8_t *s, uint32_t n, uint32_t *i, uint64_t *v) { + if (*i >= n || s[*i] < '0' || s[*i] > '9') + return -1; + if (s[*i] == '0') { + *v = 0; + (*i)++; + if (*i < n && s[*i] >= '0' && s[*i] <= '9') + return -1; + return 0; + } + uint64_t x = 0; + while (*i < n && s[*i] >= '0' && s[*i] <= '9') { + uint64_t d = (uint64_t)(s[*i] - '0'); + if (x > (UINT64_MAX - d) / 10) + return -1; + x = x * 10 + d; + (*i)++; + } + *v = x; + return 0; +} + +static int alloc_value(uint32_t headlen, uint64_t body_len, uint8_t **out, uint64_t *out_len) { + if (body_len > UINT64_MAX - headlen) + return -1; + uint64_t n = (uint64_t)headlen + body_len; + if (n > SIZE_MAX) + return -1; + uint8_t *p = (uint8_t *)calloc(1, (size_t)n); + if (!p) + return -1; + *out = p; + *out_len = n; + return 0; +} + +static int emit(uint8_t pow, uint8_t kind, uint64_t a, uint64_t b, + const char *lang, uint32_t lang_len, + const uint8_t *body, uint64_t copy_len, uint64_t store_len, + uint8_t **out, uint64_t *out_len) { + uint32_t headlen = 0; + if (headlen_of(pow, &headlen) != 0 || lang_len > headlen - KVSPACE_XH_PREFIX || + copy_len > store_len) + return -1; + if (alloc_value(headlen, store_len, out, out_len) != 0) + return -1; + uint8_t *p = *out; + p[0] = pow; + p[1] = kind; + put_u64(p + 2, a); + put_u64(p + 10, b); + if (lang_len) + memcpy(p + KVSPACE_XH_PREFIX, lang, lang_len); + if (copy_len) + memcpy(p + headlen, body, (size_t)copy_len); + return 0; +} + +int kvspaceXhNewNone(uint8_t **out, uint64_t *out_len) { + if (!out || !out_len) + return -1; + *out = NULL; + return emit(KVSPACE_XH_POW_SCALAR, KVSPACE_XH_FIXED_SMALL, 0, 0, NULL, 0, NULL, 0, 0, + out, out_len); +} + +int kvspaceXhNewScalar(const char *langtype, const uint8_t *raw, uint32_t raw_len, + uint8_t **out, uint64_t *out_len) { + if (!out || !out_len) + return -1; + *out = NULL; + if (!langtype || !raw) + return -1; + uint32_t lt = (uint32_t)strlen(langtype); + int width = scalar_width((const uint8_t *)langtype, lt); + if (width < 0 || raw_len != (uint32_t)width || + (strcmp(langtype, "bool") == 0 && raw[0] > 1)) + return -1; + return emit(KVSPACE_XH_POW_SCALAR, KVSPACE_XH_FIXED_SMALL, 0, 0, langtype, lt, raw, + width, width, out, out_len); +} + +static int decode_emitted(uint8_t **out, uint64_t *out_len) { + kvspaceXh h; + if (kvspaceXhDecode(*out, *out_len, &h) == 0) + return 0; + free(*out); + *out = NULL; + *out_len = 0; + return -1; +} + +int kvspaceXhNewShort(const char *langtype, const uint8_t *body, uint32_t body_len, + uint8_t **out, uint64_t *out_len) { + if (!out || !out_len) + return -1; + *out = NULL; + if (!langtype || (body_len && !body)) + return -1; + size_t lt = strlen(langtype); + if (lt > UINT32_MAX || short_width((const uint8_t *)langtype, (uint32_t)lt) != + (int)body_len) + return -1; + int pow = min_pow((uint32_t)lt); + if (pow < 0 || emit((uint8_t)pow, KVSPACE_XH_FIXED_SMALL, 0, 0, + langtype, (uint32_t)lt, body, body_len, body_len, + out, out_len) != 0) + return -1; + return decode_emitted(out, out_len); +} + +int kvspaceXhNewSlack(int elem, const uint8_t *data, uint64_t data_len, uint64_t cap, + uint8_t **out, uint64_t *out_len) { + if (!out || !out_len) + return -1; + *out = NULL; + if (elem < KVSPACE_XH_UTF8 || elem > KVSPACE_XH_ASCII || cap < data_len || + cap > SIZE_MAX - (1u << KVSPACE_XH_POW_SLACK) || (data_len && !data)) + return -1; + size_t k = (size_t)(elem - KVSPACE_XH_UTF8); + uint64_t count = 0; + if (slack_count(k, data, data_len, &count) != 0) + return -1; + char text[48]; + int kn = snprintf(text, sizeof text, "[%" PRIu64 "]%s", count, k_slack[k].suf); + if (kn < 0 || (size_t)kn >= sizeof text) + return -1; + return emit(KVSPACE_XH_POW_SLACK, KVSPACE_XH_SLACK, data_len, cap, text, (uint32_t)kn, + data, data_len, cap, out, out_len); +} + +int kvspaceXhNewTensor(const uint64_t *dims, uint32_t ndim, const char *elem, + const uint8_t *raw, uint64_t raw_len, + uint8_t **out, uint64_t *out_len) { + if (!out || !out_len) + return -1; + *out = NULL; + if (!dims || ndim == 0 || ndim > 64 || !elem) + return -1; + uint32_t el = (uint32_t)strlen(elem); + int width = scalar_width((const uint8_t *)elem, el); + if (width < 0) + return -1; + uint64_t numel = 1; + for (uint32_t i = 0; i < ndim; i++) { + if (dims[i] != 0 && numel > UINT64_MAX / dims[i]) + return -1; + numel *= dims[i]; + } + if (numel > UINT64_MAX / (uint64_t)width) + return -1; + uint64_t bytes = numel * (uint64_t)width; + if (raw_len != bytes || (bytes > 0 && !raw)) + return -1; + char text[111]; + size_t o = 0; + text[o++] = '['; + for (uint32_t i = 0; i < ndim; i++) { + if (i && o < sizeof text) + text[o++] = ','; + int kn = snprintf(text + o, sizeof text - o, "%" PRIu64, dims[i]); + if (kn < 0 || (size_t)kn >= sizeof text - o) + return -1; + o += (size_t)kn; + } + if (o + 1 + el >= sizeof text) + return -1; + text[o++] = ']'; + memcpy(text + o, elem, el); + o += el; + return emit(KVSPACE_XH_POW_TENSOR, KVSPACE_XH_FIXED_LARGE, numel, (uint64_t)width, text, + (uint32_t)o, raw, bytes, bytes, out, out_len); +} + +static int new_locator(uint8_t kind, const char *langtype, const char *locator, + uint64_t cap, uint8_t **out, uint64_t *out_len) { + if (!out || !out_len) + return -1; + *out = NULL; + if (!langtype || !locator) + return -1; + size_t lt = strlen(langtype); + size_t locator_len = strlen(locator); + if (lt > UINT32_MAX || cap < locator_len) + return -1; + int pow = min_pow((uint32_t)lt); + if (pow < 0 || emit((uint8_t)pow, kind, + locator_len, cap, langtype, (uint32_t)lt, + (const uint8_t *)locator, locator_len, cap, + out, out_len) != 0) + return -1; + return decode_emitted(out, out_len); +} + +int kvspaceXhNewPtr(const char *langtype, const char *path, uint64_t cap, + uint8_t **out, uint64_t *out_len) { + return new_locator(KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG, + langtype, path, cap, out, out_len); +} + +int kvspaceXhNewExt(const char *langtype, const char *locator, uint64_t cap, + uint8_t **out, uint64_t *out_len) { + return new_locator(KVSPACE_XH_EXT, langtype, locator, cap, out, out_len); +} + +int kvspaceXhNewCode(const char *langtype, const uint8_t *body, uint64_t body_len, + uint64_t cap, uint8_t **out, uint64_t *out_len) { + if (!out || !out_len) + return -1; + *out = NULL; + if (!langtype || (body_len && !body) || cap < body_len) + return -1; + size_t lt = strlen(langtype); + if (lt > UINT32_MAX || + (!type_eq((const uint8_t *)langtype, (uint32_t)lt, "rwir") && + !type_eq((const uint8_t *)langtype, (uint32_t)lt, "def langtype") && + !type_eq((const uint8_t *)langtype, (uint32_t)lt, "rwfunc"))) + return -1; + if (emit(KVSPACE_XH_POW_SCALAR, KVSPACE_XH_SLACK, body_len, cap, + langtype, (uint32_t)lt, body, body_len, cap, + out, out_len) != 0) + return -1; + return decode_emitted(out, out_len); +} + +static int parse_slack(const uint8_t *s, uint32_t n, const uint8_t *body, uint64_t len) { + if (n < 3 || s[0] != '[') + return -1; + uint32_t i = 1; + uint64_t logical = 0; + if (parse_u64(s, n, &i, &logical) != 0 || i >= n || s[i] != ']') + return -1; + i++; + uint32_t sl = n - i; + for (size_t k = 0; k < sizeof k_slack / sizeof k_slack[0]; k++) { + size_t L = strlen(k_slack[k].suf); + if (L != sl || memcmp(s + i, k_slack[k].suf, L) != 0) + continue; + uint64_t count = 0; + return slack_count(k, body, len, &count) == 0 && count == logical ? 0 : -1; + } + return -1; +} + +static int parse_tensor(const uint8_t *s, uint32_t n, uint64_t *numel, int *width) { + if (n < 4 || s[0] != '[') + return -1; + uint32_t i = 1; + uint64_t prod = 1; + int any = 0; + while (i < n && s[i] != ']') { + uint64_t d = 0; + if (parse_u64(s, n, &i, &d) != 0) + return -1; + if (d != 0 && prod > UINT64_MAX / d) + return -1; + prod *= d; + any = 1; + if (i >= n) + return -1; + if (s[i] == ',') { + i++; + if (i >= n || s[i] == ']') + return -1; + continue; + } + if (s[i] != ']') + return -1; + } + if (!any || i >= n || s[i] != ']') + return -1; + i++; + int w = scalar_width(s + i, n - i); + if (w < 0) + return -1; + *numel = prod; + *width = w; + return 0; +} + +static int slack_langtype(const uint8_t *s, uint32_t n) { + if (n < 3 || s[0] != '[') + return -1; + uint32_t i = 1; + uint64_t count = 0; + if (parse_u64(s, n, &i, &count) != 0 || i >= n || s[i++] != ']') + return -1; + for (size_t k = 0; k < sizeof k_slack / sizeof k_slack[0]; k++) { + size_t len = strlen(k_slack[k].suf); + if (n - i == len && memcmp(s + i, k_slack[k].suf, len) == 0) + return 0; + } + return -1; +} + +int kvspaceXhReserve(uint8_t kind, const char *langtype, uint64_t body_len, + uint64_t cap, uint8_t **out, uint64_t *out_len) { + if (!out || !out_len) + return -1; + *out = NULL; + *out_len = 0; + if (!langtype) + return -1; + size_t lt = strlen(langtype); + if (lt > UINT32_MAX) + return -1; + const uint8_t *type = (const uint8_t *)langtype; + uint64_t type_chars = 0; + if (slack_count(0, type, (uint64_t)lt, &type_chars) != 0) + return -1; + uint64_t a = 0, b = 0, reserve = cap; + int pow = -1; + if (kind == KVSPACE_XH_FIXED_SMALL) { + int width = short_width(type, (uint32_t)lt); + if (width < 0 || body_len != (uint64_t)width || cap != body_len) + return -1; + pow = min_pow((uint32_t)lt); + } else if (kind == KVSPACE_XH_SLACK) { + int code = type_eq(type, (uint32_t)lt, "rwir") || + type_eq(type, (uint32_t)lt, "def langtype") || + type_eq(type, (uint32_t)lt, "rwfunc"); + if ((!code && slack_langtype(type, (uint32_t)lt) != 0) || + (code && ((type_eq(type, (uint32_t)lt, "rwir") && body_len < 5) || + (type_eq(type, (uint32_t)lt, "rwfunc") && body_len < 5)))) + return -1; + pow = code ? KVSPACE_XH_POW_SCALAR : KVSPACE_XH_POW_SLACK; + a = body_len; + b = cap; + } else if (kind == KVSPACE_XH_FIXED_LARGE) { + int width = 0; + if (parse_tensor(type, (uint32_t)lt, &a, &width) != 0 || + a > UINT64_MAX / (uint64_t)width || + body_len != a * (uint64_t)width || cap != body_len) + return -1; + b = (uint64_t)width; + pow = KVSPACE_XH_POW_TENSOR; + reserve = body_len; + } else if (kind == KVSPACE_XH_EXT || + kind == (KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG)) { + if (!lt || !body_len) + return -1; + pow = min_pow((uint32_t)lt); + a = body_len; + b = cap; + } else { + return -1; + } + if (pow < 0 || body_len > cap) + return -1; + return emit((uint8_t)pow, kind, a, b, langtype, (uint32_t)lt, + NULL, 0, reserve, out, out_len); +} + +int kvspaceXhDecode(const uint8_t *data, uint64_t len, kvspaceXh *out) { + if (!out) + return -1; + memset(out, 0, sizeof *out); + if (!data || len < KVSPACE_XH_PREFIX) + return -1; + uint8_t pow = data[0]; + uint32_t headlen = 0; + if (headlen_of(pow, &headlen) != 0 || len < headlen) + return -1; + uint8_t kind = data[1]; + if (kind & ~(KVSPACE_XH_PTR_FLAG | 3u)) + return -1; + uint64_t a = get_u64(data + 2); + uint64_t b = get_u64(data + 10); + const uint8_t *lt = data + KVSPACE_XH_PREFIX; + uint32_t region = headlen - KVSPACE_XH_PREFIX; + uint32_t ltlen = 0; + while (ltlen < region && lt[ltlen] != 0) + ltlen++; + uint64_t type_chars = 0; + if (slack_count(0, lt, ltlen, &type_chars) != 0) + return -1; + + uint64_t content = 0; + uint64_t cap = 0; + if (kind == KVSPACE_XH_FIXED_SMALL) { + if (pow != min_pow(ltlen) || a != 0 || b != 0) + return -1; + int width = short_width(lt, ltlen); + if (width < 0) + return -1; + content = (uint64_t)width; + cap = content; + } else if (kind == KVSPACE_XH_SLACK) { + int code = type_eq(lt, ltlen, "rwir") || + type_eq(lt, ltlen, "def langtype") || + type_eq(lt, ltlen, "rwfunc"); + if (pow != (code ? KVSPACE_XH_POW_SCALAR : KVSPACE_XH_POW_SLACK) || + a > b) + return -1; + content = a; + cap = b; + } else if (kind == KVSPACE_XH_EXT || + kind == (KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG)) { + if (pow != min_pow(ltlen) || a == 0 || a > b || ltlen == 0) + return -1; + content = a; + cap = b; + } else if (kind == KVSPACE_XH_FIXED_LARGE) { + if (pow != KVSPACE_XH_POW_TENSOR) + return -1; + uint64_t numel = 0; + int width = 0; + if (parse_tensor(lt, ltlen, &numel, &width) != 0 || numel != a || + (uint64_t)width != b) + return -1; + if (b != 0 && a > UINT64_MAX / b) + return -1; + content = a * b; + cap = content; + } else { + return -1; + } + if (cap > len - headlen) + return -1; + if (kind == KVSPACE_XH_FIXED_SMALL && type_eq(lt, ltlen, "bool") && + data[headlen] > 1) + return -1; + if (kind == KVSPACE_XH_SLACK) { + if (type_eq(lt, ltlen, "rwir") || type_eq(lt, ltlen, "def langtype") || + type_eq(lt, ltlen, "rwfunc")) { + if (pow != KVSPACE_XH_POW_SCALAR || + (type_eq(lt, ltlen, "rwir") && content < 5) || + (type_eq(lt, ltlen, "rwfunc") && content < 5)) + return -1; + } else if (parse_slack(lt, ltlen, data + headlen, content) != 0) { + return -1; + } + } else if (kind == KVSPACE_XH_EXT || + kind == (KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG)) { + if (memchr(data + headlen, 0, (size_t)content)) + return -1; + uint64_t path_chars = 0; + if (slack_count(0, data + headlen, content, &path_chars) != 0) + return -1; + } + + out->pow = pow; + out->kind = kind; + out->a = a; + out->b = b; + out->langtype = lt; + out->langtype_len = ltlen; + out->body = data + headlen; + out->content_len = content; + out->body_cap = cap; + out->headlen = headlen; + out->total = (uint64_t)headlen + cap; + return 0; +} diff --git a/src/xvalue_head.h b/src/xvalue_head.h new file mode 100644 index 0000000..c38a9ce --- /dev/null +++ b/src/xvalue_head.h @@ -0,0 +1,70 @@ +/* Wire: [pow][flags][a:u64le][b:u64le][langtype][body]. Head length is 1 << pow. + flags: low two bits are storage class; bit 2 marks a pointer. */ +#ifndef XVALUE_HEAD_H +#define XVALUE_HEAD_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define KVSPACE_XH_PREFIX 18u + +#define KVSPACE_XH_FIXED_SMALL 0u +#define KVSPACE_XH_SLACK 1u +#define KVSPACE_XH_FIXED_LARGE 2u +#define KVSPACE_XH_EXT 3u +#define KVSPACE_XH_PTR_FLAG 4u + +#define KVSPACE_XH_POW_SCALAR 5u +#define KVSPACE_XH_POW_SLACK 6u +#define KVSPACE_XH_POW_TENSOR 7u + +#define KVSPACE_XH_UTF8 1 +#define KVSPACE_XH_BYTE 2 +#define KVSPACE_XH_UTF32 3 +#define KVSPACE_XH_ASCII 4 + +typedef struct kvspaceXh { + uint8_t pow; + uint8_t kind; + uint64_t a; + uint64_t b; + const uint8_t *langtype; + uint32_t langtype_len; + const uint8_t *body; + uint64_t content_len; + uint64_t body_cap; + uint32_t headlen; + uint64_t total; +} kvspaceXh; + +/* 0 on success. -1 and *out cleared on failure. Bytes past total are ignored. */ +int kvspaceXhDecode(const uint8_t *data, uint64_t len, kvspaceXh *out); + +int kvspaceXhNewNone(uint8_t **out, uint64_t *out_len); +int kvspaceXhNewScalar(const char *langtype, const uint8_t *raw, uint32_t raw_len, + uint8_t **out, uint64_t *out_len); +int kvspaceXhNewShort(const char *langtype, const uint8_t *body, uint32_t body_len, + uint8_t **out, uint64_t *out_len); +/* data_len and cap are bytes; character counts are derived. */ +int kvspaceXhNewSlack(int elem, const uint8_t *data, uint64_t data_len, uint64_t cap, + uint8_t **out, uint64_t *out_len); +int kvspaceXhNewTensor(const uint64_t *dims, uint32_t ndim, const char *elem, + const uint8_t *raw, uint64_t raw_len, + uint8_t **out, uint64_t *out_len); +int kvspaceXhNewPtr(const char *langtype, const char *path, uint64_t cap, + uint8_t **out, uint64_t *out_len); +int kvspaceXhNewExt(const char *langtype, const char *locator, uint64_t cap, + uint8_t **out, uint64_t *out_len); +int kvspaceXhNewCode(const char *langtype, const uint8_t *body, uint64_t body_len, + uint64_t cap, uint8_t **out, uint64_t *out_len); +int kvspaceXhReserve(uint8_t kind, const char *langtype, uint64_t body_len, + uint64_t cap, uint8_t **out, uint64_t *out_len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/xvalue_meta.c b/src/xvalue_meta.c new file mode 100644 index 0000000..d954973 --- /dev/null +++ b/src/xvalue_meta.c @@ -0,0 +1,60 @@ +#include "xvalue_meta.h" +#include "xvalue_head.h" + +#include +#include + +int kvspaceMetaKey(const char *key, char **out) { + static const char prefix[] = "/.kvspace-meta/"; + static const char hex[] = "0123456789abcdef"; + if (!out) + return -1; + *out = NULL; + if (!key || key[0] != '/' || + (strncmp(key, prefix, sizeof prefix - 2) == 0 && + (key[sizeof prefix - 2] == '/' || key[sizeof prefix - 2] == 0))) + return -1; + size_t len = strlen(key); + if (len > (SIZE_MAX - sizeof prefix) / 2) + return -1; + char *p = malloc(sizeof prefix + len * 2); + if (!p) + return -1; + memcpy(p, prefix, sizeof prefix - 1); + for (size_t i = 0; i < len; i++) { + unsigned char b = (unsigned char)key[i]; + p[sizeof prefix - 1 + 2 * i] = hex[b >> 4]; + p[sizeof prefix + 2 * i] = hex[b & 15]; + } + p[sizeof prefix - 1 + 2 * len] = 0; + *out = p; + return 0; +} + +int kvspaceMetaEncode(uint8_t ro, uint32_t vid, uint8_t **out, uint64_t *out_len) { + if (!out || !out_len) + return -1; + *out = NULL; + *out_len = 0; + if (ro > 1) + return -1; + uint8_t body[5] = {ro, (uint8_t)vid, (uint8_t)(vid >> 8), + (uint8_t)(vid >> 16), (uint8_t)(vid >> 24)}; + return kvspaceXhNewSlack(KVSPACE_XH_BYTE, body, sizeof body, sizeof body, + out, out_len); +} + +int kvspaceMetaDecode(const uint8_t *data, uint64_t len, uint8_t *ro, uint32_t *vid) { + if (!ro || !vid) + return -1; + kvspaceXh h; + if (kvspaceXhDecode(data, len, &h) != 0 || h.total != len || + h.kind != KVSPACE_XH_SLACK || h.a != 5 || h.b != 5 || + h.langtype_len != 7 || memcmp(h.langtype, "[5]byte", 7) != 0 || + h.body[0] > 1) + return -1; + *ro = h.body[0]; + *vid = (uint32_t)h.body[1] | (uint32_t)h.body[2] << 8 | + (uint32_t)h.body[3] << 16 | (uint32_t)h.body[4] << 24; + return 0; +} diff --git a/src/xvalue_meta.h b/src/xvalue_meta.h new file mode 100644 index 0000000..ccb23b7 --- /dev/null +++ b/src/xvalue_meta.h @@ -0,0 +1,18 @@ +#ifndef XVALUE_META_H +#define XVALUE_META_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int kvspaceMetaKey(const char *key, char **out); +int kvspaceMetaEncode(uint8_t ro, uint32_t vid, uint8_t **out, uint64_t *out_len); +int kvspaceMetaDecode(const uint8_t *data, uint64_t len, uint8_t *ro, uint32_t *vid); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tests/test_art_scan.c b/tests/test_art_scan.c index ed61c66..72c4eae 100644 --- a/tests/test_art_scan.c +++ b/tests/test_art_scan.c @@ -2,6 +2,7 @@ #define _GNU_SOURCE #include "kvspace_shm.h" +#include "xvalue_head.h" #include #include #include @@ -37,8 +38,10 @@ static int set_int32(kvspace_t *kv, const char *key, int32_t v) { uint8_t raw[4] = {(uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), (uint8_t)(v >> 24)}; uint8_t *tlv; - int32_t n = kvspaceXvalueEncode(KVSPACE_KIND_INT32, raw, 4, NULL, 0, &tlv); - int rc = kvspaceShmSet(kv, key, tlv, n); + uint64_t n = 0; + if (kvspaceXhNewScalar("int32", raw, 4, &tlv, &n) != 0) + return -1; + int rc = kvspaceShmSet(kv, key, tlv, (int32_t)n); free(tlv); return rc; } @@ -48,8 +51,11 @@ static int32_t get_int32(kvspace_t *kv, const char *key) { uint8_t *d = kvspaceShmGet(kv, key, 1, &len); if (!d || len <= 0) return -1; - xvalue_head_t h = kvspaceXvalueDecodeHead(d, len); - return h.raw_len == 4 ? kvspaceXvalueRawInt32(h.raw) : -1; + kvspaceXh h; + if (kvspaceXhDecode(d, (uint64_t)len, &h) != 0 || h.content_len != 4) + return -1; + return (int32_t)((uint32_t)h.body[0] | ((uint32_t)h.body[1] << 8) | + ((uint32_t)h.body[2] << 16) | ((uint32_t)h.body[3] << 24)); } static void list_free(char **ns, int32_t n) { @@ -87,6 +93,37 @@ static void t_correct(const char *db) { CHECK(set_int32(kv, "/foo/bar1", 7) == 0, "set /foo/bar1"); CHECK(set_int32(kv, "/foo/bar2", 8) == 0, "set /foo/bar2"); CHECK(set_int32(kv, "/foo/zzz", 9) == 0, "set /foo/zzz"); + uint8_t raw[4] = {7}; + uint8_t *permitted = NULL; + uint64_t permitted_len = 0; + CHECK(kvspaceXhNewScalar("int32", raw, 4, &permitted, &permitted_len) == 0 && + kvspaceShmSetValue(kv, "/private", permitted, (int32_t)permitted_len, 1, 42) == 0, + "set permitted value"); + free(permitted); + uint8_t ro = 0; + uint32_t vid = 0; + CHECK(kvspaceShmMetaGet(kv, "/private", &ro, &vid) == 0 && + ro == 1 && vid == 42, "read sidecar"); + CHECK(kvspaceShmCp(kv, "/private", "/private_copy") == 0, + "copy permitted value"); + CHECK(kvspaceShmMetaGet(kv, "/private_copy", &ro, &vid) == 0 && + ro == 1 && vid == 42, "copy sidecar"); + CHECK(kvspaceShmDel(kv, "/private_copy") == 0, "delete copied value"); + CHECK(kvspaceShmMetaGet(kv, "/private_copy", &ro, &vid) == 0 && + ro == 0 && vid == 0, "delete copied sidecar"); + + uint8_t *ptr = NULL; + uint64_t ptr_len = 0; + CHECK(kvspaceXhNewPtr("int32", "/private", 8, &ptr, &ptr_len) == 0 && + kvspaceShmSetValue(kv, "/link", ptr, (int32_t)ptr_len, 1, 17) == 0, + "set pointer metadata"); + free(ptr); + CHECK(kvspaceShmCp(kv, "/link", "/link_copy") == 0, + "copy pointer metadata"); + CHECK(kvspaceShmMetaGetAt(kv, "/link_copy", &ro, &vid) == 0 && + ro == 1 && vid == 17, "copy pointer slot metadata"); + CHECK(kvspaceShmMetaGet(kv, "/link_copy", &ro, &vid) == 0 && + ro == 1 && vid == 42, "resolve pointer target metadata"); char **ns; int32_t n; @@ -125,8 +162,46 @@ static void t_correct(const char *db) { REQUIRE(do_list(kv, "/", &ns, &n) == 0, "list /"); CHECK(list_has(ns, n, "lib") && list_has(ns, n, "d") && list_has(ns, n, "foo"), "list / names"); + CHECK(!list_has(ns, n, ".kvspace-meta"), "hide metadata root"); list_free(ns, n); + CHECK(kvspaceShmMkindex(kv, "/frames/[1]/", 0) == 0, "frame directory"); + CHECK(set_int32(kv, "/frames/[1]/value", 7) == 0, "frame child"); + REQUIRE(do_list(kv, "/frames/", &ns, &n) == 0, "list frames"); + CHECK(n == 1 && list_has(ns, n, "[1]/"), "deduplicate directory child"); + list_free(ns, n); + + CHECK(set_int32(kv, "/private", 8) == 0, "overwrite permitted value"); + CHECK(kvspaceShmMetaGet(kv, "/private", &ro, &vid) == 0 && + ro == 0 && vid == 0, "clear sidecar on overwrite"); + uint8_t *body = NULL; + CHECK(kvspaceShmWriteNewPlace(kv, "/private", 0, KVSPACE_XH_FIXED_SMALL, + 1, 7, "int32", 4, 4, &body) == 0, + "write new permitted place"); + if (body) + memcpy(body, raw, 4); + CHECK(kvspaceShmMetaGet(kv, "/private", &ro, &vid) == 0 && + ro == 1 && vid == 7, "write-new sidecar"); + CHECK(kvspaceShmDel(kv, "/private") == 0, "delete private value"); + CHECK(kvspaceShmMetaGet(kv, "/private", &ro, &vid) == 0 && + ro == 0 && vid == 0, "delete sidecar"); + + permitted = NULL; + permitted_len = 0; + CHECK(kvspaceXhNewScalar("int32", raw, 4, &permitted, &permitted_len) == 0 && + kvspaceShmSetValue(kv, "/tree/a", permitted, (int32_t)permitted_len, 1, 11) == 0, + "set tree metadata"); + free(permitted); + CHECK(kvspaceShmCptree(kv, "/tree", "/tree_copy") == 0, + "copy tree metadata"); + CHECK(kvspaceShmMetaGet(kv, "/tree_copy/a", &ro, &vid) == 0 && + ro == 1 && vid == 11, "copied tree sidecar"); + CHECK(kvspaceShmDeltree(kv, "/tree_copy") == 0, + "delete copied tree"); + CHECK(kvspaceShmMetaGet(kv, "/tree_copy/a", &ro, &vid) == 0 && + ro == 0 && vid == 0, "deleted tree sidecar"); + + CHECK(kvspaceShmMkindex(kv, "/lib/", 0) == 0, "new directory value"); CHECK(kvspaceShmExtindex(kv, "/e/", "/lib/") == 0, "extindex"); REQUIRE(kvspaceShmList(kv, "/e/", true, 0, &ns, &n) == 0, "list /e/ ex"); CHECK(list_has(ns, n, "a") && list_has(ns, n, "b") && list_has(ns, n, "mod"), @@ -215,6 +290,25 @@ static void t_scale(const char *db) { kvspaceShmClose(kv); } +static void t_wide(const char *db) { + kvspace_t *kv = kvspaceShmOpen(db, SBO_DATA_SIZE); + REQUIRE(kv != NULL, "open wide failed"); + for (int i = 0; i < 4097; i++) { + char key[32]; + snprintf(key, sizeof key, "/wide/%04d", i); + REQUIRE(set_int32(kv, key, i) == 0, "set wide %d", i); + } + char **names; + int32_t count; + REQUIRE(do_list(kv, "/wide/", &names, &count) == 0, "list wide"); + CHECK(count == 4097, "wide count %d", count); + if (count == 4097) + CHECK(strcmp(names[0], "0000") == 0 && + strcmp(names[4096], "4096") == 0, "wide order"); + list_free(names, count); + kvspaceShmClose(kv); +} + int main(int argc, char **argv) { char tmpl[] = "/tmp/kvspace-artscan-XXXXXX"; const char *dir = argc > 1 ? argv[1] : mkdtemp(tmpl); @@ -222,12 +316,14 @@ int main(int argc, char **argv) { perror("mkdtemp"); return 2; } - char db[512], db2[512]; + char db[512], db2[512], db3[512]; snprintf(db, sizeof db, "%s/db", dir); snprintf(db2, sizeof db2, "%s/db2", dir); + snprintf(db3, sizeof db3, "%s/db3", dir); t_correct(db); t_scale(db2); + t_wide(db3); if (argc <= 1) { char cmd[700]; diff --git a/tests/test_handle_cache.c b/tests/test_handle_cache.c index 310d614..eed816f 100644 --- a/tests/test_handle_cache.c +++ b/tests/test_handle_cache.c @@ -1,6 +1,6 @@ #define _GNU_SOURCE #include "kvspace_shm.h" -#include "xvalue.h" +#include "xvalue_head.h" #include #include #include @@ -20,10 +20,10 @@ static int failures; static int set_i64(kvspace_t *kv, const char *key, int64_t v) { uint8_t *tlv; - int32_t n = kvspaceXvalueNewInt64(&v, 1, &tlv); - if (n < 0) + uint64_t n = 0; + if (kvspaceXhNewScalar("int64", (const uint8_t *)&v, 8, &tlv, &n) != 0) return -1; - int rc = kvspaceShmSet(kv, key, tlv, n); + int rc = kvspaceShmSet(kv, key, tlv, (int32_t)n); free(tlv); return rc; } @@ -31,8 +31,12 @@ static int set_i64(kvspace_t *kv, const char *key, int64_t v) { static int64_t get_i64(const uint8_t *d, int32_t len) { if (!d || len <= 0) return -999; - xvalue_head_t h = kvspaceXvalueDecodeHead(d, len); - return h.raw_len == 8 ? kvspaceXvalueRawInt64(h.raw) : -999; + kvspaceXh h; + if (kvspaceXhDecode(d, (uint64_t)len, &h) != 0 || h.content_len != 8) + return -999; + uint64_t v = 0; + for (int i = 0; i < 8; i++) v |= (uint64_t)h.body[i] << (i * 8); + return (int64_t)v; } static uint64_t nsec(void) { @@ -90,13 +94,14 @@ int main(void) { CHECK(kvspaceShmResolveRef(kv, "/p", &pr) == 0); d = kvspaceShmGet(kv, "/p", 0, &len); CHECK(d && len > 8); - xvalue_head_t hh = kvspaceXvalueDecodeHead(d, len); + kvspaceXh hh; + CHECK(kvspaceXhDecode(d, (uint64_t)len, &hh) == 0); int64_t nv = 200; uint8_t raw[8]; for (int i = 0; i < 8; i++) raw[i] = (uint8_t)((uint64_t)nv >> (8 * i)); CHECK(kvspaceShmSetPartByRef(kv, &pr, "/p", - (uint32_t)kvspaceXvalueHeadLen(&hh), raw, + hh.headlen, raw, 8) == 0); d = kvspaceShmGetByRef(kv, &pr, "/p", &len); CHECK(get_i64(d, len) == 200); diff --git a/tests/test_new_wire_shm.c b/tests/test_new_wire_shm.c new file mode 100644 index 0000000..62b2d2f --- /dev/null +++ b/tests/test_new_wire_shm.c @@ -0,0 +1,127 @@ +#define _GNU_SOURCE +#include "kvspace_shm.h" +#include "xvalue_head.h" + +#include +#include +#include +#include + +#define CHECK(x) do { if (!(x)) { fprintf(stderr, "failed: %s\n", #x); return 1; } } while (0) + +int main(void) { + char dir[] = "/tmp/kvspace-new-wire-XXXXXX"; + CHECK(mkdtemp(dir) != NULL); + char path[128]; + snprintf(path, sizeof path, "%s/db", dir); + kvspace_t *kv = kvspaceShmOpen(path, 8UL * 64 * 64 * 64 * 64); + CHECK(kv != NULL); + uint8_t *value = NULL; + uint64_t n = 0; + uint8_t raw[8] = {42}; + CHECK(kvspaceXhNewScalar("int64", raw, 8, &value, &n) == 0); + CHECK(kvspaceShmSet(kv, "/x", value, (int32_t)n) == 0); + free(value); + int32_t got = 0; + uint8_t *stored = kvspaceShmGet(kv, "/x", 0, &got); + kvspaceXh head; + CHECK(stored && got == 40 && kvspaceXhDecode(stored, got, &head) == 0 && + head.body[0] == 42); + + CHECK(kvspaceXhNewPtr("int64", "/x", 2, &value, &n) == 0); + CHECK(kvspaceShmSet(kv, "/p", value, (int32_t)n) == 0); + free(value); + stored = kvspaceShmGet(kv, "/p", 1, &got); + CHECK(stored && got == 40 && kvspaceXhDecode(stored, got, &head) == 0 && + head.body[0] == 42); + + CHECK(kvspaceXhNewShort("[int64]·int64", NULL, 0, &value, &n) == 0); + CHECK(kvspaceShmSet(kv, "/m", value, (int32_t)n) == 0); + free(value); + CHECK(kvspaceXhNewScalar("int64", raw, 8, &value, &n) == 0); + CHECK(kvspaceShmSet(kv, "/m·[0]", value, (int32_t)n) == 0); + free(value); + char **names = NULL; + int32_t count = 0; + CHECK(kvspaceShmList(kv, "/m·", false, 0, &names, &count) == 0 && + count == 1 && strcmp(names[0], "[0]") == 0); + free(names[0]); + free(names); + CHECK(kvspaceShmCptree(kv, "/m", "/m-tree") == 0); + CHECK(kvspaceShmCplist(kv, "/m", "/m-list") == 0); + CHECK(kvspaceShmCptree(kv, "/m", "/m") == 0); + CHECK(kvspaceShmCplist(kv, "/m", "/m") == 0); + CHECK(kvspaceShmCptree(kv, "/m", "/m/child") != 0); + CHECK(kvspaceShmCptree(kv, "/", "/under-root") != 0); + for (int i = 0; i < 2; i++) { + const char *base = i ? "/m-list" : "/m-tree"; + char member[32]; + snprintf(member, sizeof member, "%s·[0]", base); + stored = kvspaceShmGet(kv, member, 0, &got); + CHECK(stored && kvspaceXhDecode(stored, got, &head) == 0 && + head.body[0] == 42); + } + CHECK(kvspaceShmGet(kv, "/m-tree·", 0, &got) == NULL); + CHECK(kvspaceShmGet(kv, "/m-list·", 0, &got) == NULL); + + CHECK(kvspaceXhNewShort("rwfunc", NULL, 0, &value, &n) == 0); + CHECK(kvspaceShmSetValue(kv, "/fn/", value, (int32_t)n, 1, 7) == 0); + free(value); + CHECK(kvspaceShmMkindex(kv, "/fn/", 0) == 0); + stored = kvspaceShmGet(kv, "/fn/", 0, &got); + CHECK(stored && kvspaceXhDecode(stored, got, &head) == 0 && + head.langtype_len == 6 && memcmp(head.langtype, "rwfunc", 6) == 0); + uint8_t ro = 0; + uint32_t vid = 0; + CHECK(kvspaceShmMetaGet(kv, "/fn/", &ro, &vid) == 0 && ro == 1 && vid == 7); + + CHECK(kvspaceShmMkindex(kv, "/lib/f/", 0) == 0); + const uint8_t code[] = {0, 0, 0, 0, 0, 'x'}; + CHECK(kvspaceXhNewCode("rwir", code, sizeof code, sizeof code, &value, &n) == 0); + CHECK(kvspaceShmSet(kv, "/lib/f/[1,-1]", value, (int32_t)n) == 0); + CHECK(kvspaceShmSet(kv, "/lib/f/[1,0]", value, (int32_t)n) == 0); + free(value); + CHECK(kvspaceShmExtindex(kv, "/vthread/1/[1]/", "/lib/f/") == 0); + CHECK(kvspaceXhNewPtr("int64", "/x", 2, &value, &n) == 0); + CHECK(kvspaceShmSet(kv, "/vthread/1/[1]/[1,0]", value, (int32_t)n) != 0); + CHECK(kvspaceShmSetValue(kv, "/vthread/1/[1]/[1,-1]", value, + (int32_t)n, 0, 0) == 0); + free(value); + stored = kvspaceShmGet(kv, "/vthread/1/[1]/[1,-1]", 0, &got); + CHECK(stored && kvspaceXhDecode(stored, got, &head) == 0 && + head.kind == (KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG)); + + uint8_t *body = NULL; + CHECK(kvspaceShmWriteInPlace(kv, "/x", 0, 8, &body) == 0); + body[0] = 43; + stored = kvspaceShmGet(kv, "/x", 0, &got); + CHECK(stored && kvspaceXhDecode(stored, got, &head) == 0 && head.body[0] == 43); + + CHECK(kvspaceShmWriteNewPlace(kv, "/fresh", 0, KVSPACE_XH_FIXED_SMALL, + 1, 19, "int64", 8, 8, &body) == 0); + memcpy(body, raw, 8); + stored = kvspaceShmGet(kv, "/fresh", 0, &got); + CHECK(stored && kvspaceXhDecode(stored, got, &head) == 0 && head.body[0] == 42); + CHECK(kvspaceShmMetaGet(kv, "/fresh", &ro, &vid) == 0 && ro == 1 && vid == 19); + CHECK(kvspaceShmCp(kv, "/fresh", "/fresh-copy") == 0); + CHECK(kvspaceShmMetaGet(kv, "/fresh-copy", &ro, &vid) == 0 && ro == 1 && vid == 19); + CHECK(kvspaceShmCptree(kv, "/fresh", "/fresh-tree") == 0); + CHECK(kvspaceShmMetaGet(kv, "/fresh-tree", &ro, &vid) == 0 && ro == 1 && vid == 19); + + static const uint8_t utf8[] = {0xc3, 0xa9, 'a'}; + CHECK(kvspaceShmWriteNewPlace(kv, "/text", 0, KVSPACE_XH_SLACK, + 0, 0, "[2]char/utf8", 3, 9, &body) == 0); + memcpy(body, utf8, sizeof utf8); + stored = kvspaceShmGet(kv, "/text", 0, &got); + CHECK(stored && kvspaceXhDecode(stored, got, &head) == 0 && head.a == 3 && + head.b == 9 && got == 73 && + memcmp(head.body, utf8, sizeof utf8) == 0); + kvspaceShmClose(kv); + unlink(path); + char extra[144]; + snprintf(extra, sizeof extra, "%s.sbo.head", path); unlink(extra); + snprintf(extra, sizeof extra, "%s.sbo.data", path); unlink(extra); + rmdir(dir); + puts("ok"); + return 0; +} diff --git a/tests/test_shm_resize.c b/tests/test_shm_resize.c index b566ec2..fc38d1c 100644 --- a/tests/test_shm_resize.c +++ b/tests/test_shm_resize.c @@ -3,6 +3,7 @@ #define _GNU_SOURCE #include "kvspace_shm.h" +#include "xvalue_head.h" #include "test_util.h" #include #include @@ -15,11 +16,9 @@ #define DATA_2MB (8UL * 64 * 64 * 64) #define DATA_128MB (8UL * 64 * 64 * 64 * 64) #define HEAD_POOL_INIT (4UL * 1024 * 1024) -/* 4MB pool / 347B stride ~= 12085 blocks. A TLV of 257..504B occupies one L1 - box alone (> 32 of 64 slots, still level 1), so 14000 values overflow the - pool. Head is 28B today. */ +/* A value near 500 bytes consumes one L1 box; 14000 boxes grow the pool. */ #define HEAD_KEYS 14000 -#define HEAD_VAL_RAW 440 +#define HEAD_VAL_RAW 360 /* 900KB = 29 slots of 32KB; the 2MB root has 64, so the third one re-roots. */ #define BIG_RAW (900 * 1024) @@ -35,9 +34,12 @@ static int set_bytes(kvspace_t *kv, const char *key, int32_t n, int seed) { for (int32_t j = 0; j < n; j++) raw[j] = (uint8_t)(seed + j); uint8_t *tlv; - int32_t dims[1] = {n}; - int32_t len = kvspaceXvalueEncode(KVSPACE_KIND_UINT8, raw, n, dims, 1, &tlv); - int rc = kvspaceShmSet(kv, key, tlv, len); + uint64_t dims[1] = {(uint64_t)n}, len = 0; + if (kvspaceXhNewTensor(dims, 1, "uint8", raw, (uint64_t)n, &tlv, &len) != 0) { + free(raw); + return -1; + } + int rc = kvspaceShmSet(kv, key, tlv, (int32_t)len); free(tlv); free(raw); return rc; @@ -49,11 +51,11 @@ static int check_bytes(kvspace_t *kv, const char *key, int32_t n, int seed) { uint8_t *d = kvspaceShmGet(kv, key, 1, &len); if (!d || len <= 0) return 1; - xvalue_head_t h = kvspaceXvalueDecodeHead(d, len); - if (h.raw_len != n) + kvspaceXh h; + if (kvspaceXhDecode(d, (uint64_t)len, &h) != 0 || h.content_len != (uint64_t)n) return 2; for (int32_t j = 0; j < n; j++) - if (h.raw[j] != (uint8_t)(seed + j)) + if (h.body[j] != (uint8_t)(seed + j)) return 3; return 0; } diff --git a/tests/test_shm_split.c b/tests/test_shm_split.c index 7a166b5..a651fd5 100644 --- a/tests/test_shm_split.c +++ b/tests/test_shm_split.c @@ -2,6 +2,7 @@ #define _GNU_SOURCE #include "kvspace_shm.h" +#include "xvalue_head.h" #include "test_util.h" #include #include @@ -31,8 +32,10 @@ static int set_int32(kvspace_t *kv, const char *key, int32_t v) { uint8_t raw[4] = {(uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), (uint8_t)(v >> 24)}; uint8_t *tlv; - int32_t n = kvspaceXvalueEncode(KVSPACE_KIND_INT32, raw, 4, NULL, 0, &tlv); - int rc = kvspaceShmSet(kv, key, tlv, n); + uint64_t n = 0; + if (kvspaceXhNewScalar("int32", raw, 4, &tlv, &n) != 0) + return -1; + int rc = kvspaceShmSet(kv, key, tlv, (int32_t)n); free(tlv); return rc; } @@ -42,8 +45,11 @@ static int32_t get_int32(kvspace_t *kv, const char *key) { uint8_t *d = kvspaceShmGet(kv, key, 1, &len); if (!d || len <= 0) return -1; - xvalue_head_t h = kvspaceXvalueDecodeHead(d, len); - return h.raw_len == 4 ? kvspaceXvalueRawInt32(h.raw) : -1; + kvspaceXh h; + if (kvspaceXhDecode(d, (uint64_t)len, &h) != 0 || h.content_len != 4) + return -1; + return (int32_t)((uint32_t)h.body[0] | ((uint32_t)h.body[1] << 8) | + ((uint32_t)h.body[2] << 16) | ((uint32_t)h.body[3] << 24)); } /* returns failure count */ diff --git a/tests/test_xvalue_head.c b/tests/test_xvalue_head.c new file mode 100644 index 0000000..b20b86b --- /dev/null +++ b/tests/test_xvalue_head.c @@ -0,0 +1,412 @@ +#include "xvalue_head.h" +#include "xvalue_meta.h" + +#include +#include +#include + +static int failures = 0; +#define CHECK(cond, ...) \ + do { \ + if (!(cond)) { \ + failures++; \ + fprintf(stderr, " FAIL %s:%d: ", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } \ + } while (0) + +static void test_none(void) { + uint8_t *buf = NULL; + uint64_t n = 0; + CHECK(kvspaceXhNewNone(&buf, &n) == 0, "none encode"); + CHECK(n == 32, "none len %llu", (unsigned long long)n); + CHECK(buf[0] == 5, "pow"); + int rest = 0; + for (int i = 1; i < 32; i++) + rest |= buf[i]; + CHECK(rest == 0, "none head tail"); + kvspaceXh h; + CHECK(kvspaceXhDecode(buf, n, &h) == 0, "none decode"); + CHECK(h.kind == KVSPACE_XH_FIXED_SMALL && h.langtype_len == 0, "none kind"); + CHECK(h.a == 0 && h.b == 0 && h.content_len == 0 && h.total == 32, "none fields"); + uint8_t zeros[32]; + memset(zeros, 0, sizeof zeros); + CHECK(kvspaceXhDecode(zeros, 32, &h) != 0, "pow 0"); + CHECK(h.langtype_len == 0 && h.total == 0, "fail clears"); + free(buf); +} + +static void test_int64(void) { + uint8_t raw[8] = {0x0a, 0, 0, 0, 0, 0, 0, 0}; + uint8_t *buf = NULL; + uint64_t n = 0; + CHECK(kvspaceXhNewScalar("int64", raw, 8, &buf, &n) == 0, "i64"); + CHECK(n == 40, "i64 len %llu", (unsigned long long)n); + CHECK(buf[0] == 5 && buf[1] == 0, "i64 prefix"); + CHECK(memcmp(buf + 18, "int64", 5) == 0, "i64 lang"); + CHECK(buf[23] == 0, "i64 nul"); + CHECK(memcmp(buf + 32, raw, 8) == 0, "i64 body"); + kvspaceXh h; + uint8_t padded[64] = {0}; + memcpy(padded, buf, (size_t)n); + CHECK(kvspaceXhDecode(padded, sizeof padded, &h) == 0, "read 64"); + CHECK(h.total == 40 && h.content_len == 8 && h.body[0] == 0x0a, "ignore overrun"); + CHECK(kvspaceXhDecode(buf, 39, &h) != 0, "short body"); + free(buf); + + uint8_t b = 1; + CHECK(kvspaceXhNewScalar("bool", &b, 1, &buf, &n) == 0, "bool"); + CHECK(n == 33 && buf[32] == 1, "bool body"); + buf[32] = 2; + CHECK(kvspaceXhDecode(buf, n, &h) != 0, "invalid bool decode"); + free(buf); + b = 2; + CHECK(kvspaceXhNewScalar("bool", &b, 1, &buf, &n) != 0, "invalid bool encode"); + CHECK(kvspaceXhNewScalar("int64", raw, 4, &buf, &n) != 0, "bad width"); + CHECK(buf == NULL, "no buf"); +} + +static void test_slack(void) { + const uint8_t hi[] = {'h', 'i'}; + uint8_t *buf = NULL; + uint64_t n = 0; + kvspaceXh h; + uint8_t cps[8] = {0x41, 0, 0, 0, 0x16, 0x4e, 0, 0}; + CHECK(kvspaceXhNewSlack(KVSPACE_XH_UTF32, cps, sizeof cps, 8, &buf, &n) == 0, "utf32"); + CHECK(kvspaceXhDecode(buf, n, &h) == 0, "utf32 dec"); + CHECK(h.a == 8 && h.langtype_len == 13 && memcmp(h.langtype, "[2]char/utf32", 13) == 0, + "utf32 lt"); + free(buf); + + CHECK(kvspaceXhNewSlack(KVSPACE_XH_UTF32, cps, 3, 8, &buf, &n) != 0, + "partial utf32"); + CHECK(buf == NULL, "partial utf32 output"); + uint8_t surrogate[4] = {0, 0xd8, 0, 0}; + CHECK(kvspaceXhNewSlack(KVSPACE_XH_UTF32, surrogate, 4, 4, &buf, &n) != 0, + "utf32 surrogate"); + uint8_t too_high[4] = {0, 0, 0x11, 0}; + CHECK(kvspaceXhNewSlack(KVSPACE_XH_UTF32, too_high, 4, 4, &buf, &n) != 0, + "utf32 range"); + + CHECK(kvspaceXhNewSlack(KVSPACE_XH_ASCII, hi, 2, 2, &buf, &n) == 0, "ascii"); + CHECK(kvspaceXhDecode(buf, n, &h) == 0 && h.a == 2, "ascii dec"); + CHECK(memcmp(h.langtype, "[2]char/ascii", h.langtype_len) == 0, "ascii lt"); + free(buf); + uint8_t non_ascii = 0x80; + CHECK(kvspaceXhNewSlack(KVSPACE_XH_ASCII, &non_ascii, 1, 1, &buf, &n) != 0, + "ascii range"); + + CHECK(kvspaceXhNewSlack(KVSPACE_XH_BYTE, hi, 2, 2, &buf, &n) == 0, "byte"); + CHECK(kvspaceXhDecode(buf, n, &h) == 0, "byte dec"); + CHECK(memcmp(h.langtype, "[2]byte", h.langtype_len) == 0, "byte lt"); + buf[2] = 9; + CHECK(kvspaceXhDecode(buf, n, &h) != 0, "len > cap"); + free(buf); +} + +static void test_utf8(void) { + static const struct { + const char *data; + uint8_t len, count; + } cases[] = { + {"", 0, 0}, + {"hi", 2, 2}, + {"\xe4\xbd\xa0\xe5\xa5\xbd", 6, 2}, + {"\xf0\x9f\x98\x80", 4, 1}, + {"A\xc3\xa9\xe4\xb8\xad\xf0\x9f\x98\x80", 10, 4}, + {"A\0B", 3, 3}, + {"\xc2\x80\xdf\xbf", 4, 2}, + {"\xe0\xa0\x80\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf", 12, 4}, + {"\xf0\x90\x80\x80\xf4\x8f\xbf\xbf", 8, 2}, + }; + for (size_t i = 0; i < sizeof cases / sizeof cases[0]; i++) { + uint8_t wire[80] = {6, 1}; + wire[2] = cases[i].len; + wire[10] = 16; + memcpy(wire + 18, "[0]char/utf8", 12); + wire[19] += cases[i].count; + memcpy(wire + 64, cases[i].data, cases[i].len); + uint8_t *buf = NULL; + uint64_t n = 0; + CHECK(kvspaceXhNewSlack(KVSPACE_XH_UTF8, (const uint8_t *)cases[i].data, + cases[i].len, 16, &buf, &n) == 0, "utf8 encode %zu", i); + CHECK(buf && n == sizeof wire && memcmp(buf, wire, sizeof wire) == 0, + "utf8 wire %zu", i); + free(buf); + kvspaceXh h; + CHECK(kvspaceXhDecode(wire, sizeof wire, &h) == 0, "utf8 fixture %zu", i); + CHECK(h.content_len == cases[i].len && h.body_cap == 16 && h.total == 80, + "utf8 lengths %zu", i); + wire[19]++; + CHECK(kvspaceXhDecode(wire, sizeof wire, &h) != 0, "utf8 count %zu", i); + CHECK(h.body == NULL && h.total == 0, "utf8 failure clears %zu", i); + wire[19]--; + memset(wire + 64 + cases[i].len, 0xff, 16 - cases[i].len); + CHECK(kvspaceXhDecode(wire, sizeof wire, &h) == 0, "utf8 slack %zu", i); + for (uint64_t cut = 64; cut < sizeof wire; cut++) + CHECK(kvspaceXhDecode(wire, cut, &h) != 0, "utf8 truncation %zu", i); + if (cases[i].len) { + CHECK(kvspaceXhNewSlack(KVSPACE_XH_UTF8, (const uint8_t *)cases[i].data, + cases[i].len, cases[i].len - 1, &buf, &n) != 0, + "utf8 capacity %zu", i); + CHECK(buf == NULL, "utf8 capacity output %zu", i); + } + wire[2] = wire[10] = 255; + CHECK(kvspaceXhDecode(wire, sizeof wire, &h) != 0, "utf8 body bounds %zu", i); + } + uint8_t *buf = NULL; + uint64_t n = 0; + CHECK(kvspaceXhNewSlack(KVSPACE_XH_UTF8, NULL, 0, 0, &buf, &n) == 0, "empty utf8"); + kvspaceXh h; + CHECK(kvspaceXhDecode(buf, n, &h) == 0 && h.content_len == 0, "empty utf8 decode"); + free(buf); + CHECK(kvspaceXhNewSlack(KVSPACE_XH_UTF8, NULL, 1, 1, &buf, &n) != 0, "null utf8"); +} + +static void test_invalid_utf8(void) { + static const char *cases[] = { + "\x80", "\xbf", "\xff", "\xc0\xaf", "\xc1\xbf", "\xe0\x80\x80", + "\xed\xa0\x80", "\xf0\x80\x80\x80", "\xf4\x90\x80\x80", "\xf5\x80\x80\x80", + "\xc2", "\xe2\x82", "\xf0\x9f\x98", "\xc2\x41", "\xe2\x28\xa1", "\xf0\x90\x41\x80", + }; + for (size_t i = 0; i < sizeof cases / sizeof cases[0]; i++) { + size_t len = strlen(cases[i]); + uint8_t *buf = NULL; + uint64_t n = 0; + CHECK(kvspaceXhNewSlack(KVSPACE_XH_UTF8, (const uint8_t *)cases[i], len, 16, + &buf, &n) != 0, "invalid utf8 encode %zu", i); + CHECK(buf == NULL, "invalid utf8 output %zu", i); + free(buf); + uint8_t wire[80] = {6, 1}; + wire[2] = (uint8_t)len; + wire[10] = 16; + memcpy(wire + 18, "[1]char/utf8", 12); + memcpy(wire + 64, cases[i], len); + kvspaceXh h; + CHECK(kvspaceXhDecode(wire, sizeof wire, &h) != 0, "invalid utf8 decode %zu", i); + } +} + +static void test_tensor(void) { + uint64_t dims[2] = {2, 3}; + uint8_t raw[24]; + memset(raw, 0xab, sizeof raw); + uint8_t *buf = NULL; + uint64_t n = 0; + CHECK(kvspaceXhNewTensor(dims, 2, "float32", raw, sizeof raw, + &buf, &n) == 0, "td"); + uint8_t *bad = NULL; + uint64_t bad_len = 0; + CHECK(kvspaceXhNewTensor(dims, 2, "float32", raw, sizeof raw - 1, + &bad, &bad_len) != 0, "tensor body length"); + CHECK(n == 128 + 24, "td len %llu", (unsigned long long)n); + CHECK(buf[0] == 7 && buf[1] == 2, "td kind"); + kvspaceXh h; + CHECK(kvspaceXhDecode(buf, n, &h) == 0, "td dec"); + CHECK(h.a == 6 && h.b == 4 && h.content_len == 24, "td fields"); + CHECK(h.langtype_len == 12 && memcmp(h.langtype, "[2,3]float32", 12) == 0, "td lt"); + CHECK(memcmp(h.body, raw, 24) == 0, "td body"); + buf[2] = 5; + CHECK(kvspaceXhDecode(buf, n, &h) != 0, "numel mismatch"); + free(buf); + + uint8_t ext[32]; + memset(ext, 0, sizeof ext); + ext[0] = 5; + ext[1] = KVSPACE_XH_EXT; + CHECK(kvspaceXhDecode(ext, sizeof ext, &h) != 0, "ext"); +} + +static void test_metadata(void) { + char *key = NULL; + CHECK(kvspaceMetaKey("/a", &key) == 0 && + strcmp(key, "/.kvspace-meta/2f61") == 0, "metadata key"); + free(key); + uint8_t *encoded = NULL; + uint64_t len = 0; + CHECK(kvspaceMetaEncode(1, 0x12345678, &encoded, &len) == 0, + "metadata encode"); + uint8_t ro = 0; + uint32_t vid = 0; + CHECK(kvspaceMetaDecode(encoded, len, &ro, &vid) == 0 && + ro == 1 && vid == 0x12345678, "metadata decode"); + encoded[64] = 2; + CHECK(kvspaceMetaDecode(encoded, len, &ro, &vid) != 0, + "metadata bool"); + free(encoded); +} + +static void test_reserve(void) { + uint8_t *value = NULL; + uint64_t len = 0; + kvspaceXh h; + const uint8_t utf8[] = {0xe4, 0xbd, 0xa0, 0xf0, 0x9f, 0x98, 0x80}; + CHECK(kvspaceXhReserve(KVSPACE_XH_SLACK, "[2]char/utf8", 7, 10, + &value, &len) == 0, "reserve utf8"); + memcpy(value + 64, utf8, sizeof utf8); + CHECK(kvspaceXhDecode(value, len, &h) == 0 && h.a == 7 && h.b == 10, + "reserved utf8 decodes after fill"); + free(value); + CHECK(kvspaceXhReserve(KVSPACE_XH_FIXED_LARGE, "[2,3]float32", 23, 23, + &value, &len) != 0, "reserve wrong tensor width"); + CHECK(kvspaceXhReserve(KVSPACE_XH_FIXED_LARGE, "[2,3]float32", 24, 24, + &value, &len) == 0, "reserve tensor"); + free(value); + CHECK(kvspaceXhReserve(KVSPACE_XH_SLACK, "[1]char/utf8\xff", 1, 1, + &value, &len) != 0, "reserve invalid langtype"); +} + +static void test_container_ptr_code(void) { + kvspaceXh h; + uint8_t *encoded = NULL; + uint64_t encoded_len = 0; + uint8_t map[64] = {6, KVSPACE_XH_FIXED_SMALL}; + const char *map_type = "[int64]\xc2\xb7[]char/utf32"; + memcpy(map + 18, map_type, strlen(map_type)); + CHECK(kvspaceXhDecode(map, sizeof map, &h) == 0, "map head"); + CHECK(h.langtype_len == strlen(map_type) && h.content_len == 0, + "map fields"); + CHECK(kvspaceXhNewShort(map_type, NULL, 0, &encoded, &encoded_len) == 0, + "map encode"); + CHECK(encoded_len == sizeof map && memcmp(encoded, map, sizeof map) == 0, + "map wire"); + free(encoded); + + uint8_t def[37] = {5, KVSPACE_XH_FIXED_SMALL}; + memcpy(def + 18, "def rwir", 8); + CHECK(kvspaceXhDecode(def, sizeof def, &h) == 0, "def rwir head"); + CHECK(h.content_len == 5 && h.total == sizeof def, "def rwir body"); + CHECK(kvspaceXhNewShort("def rwir", def + 32, 5, &encoded, &encoded_len) == 0, + "def rwir encode"); + CHECK(encoded_len == sizeof def && memcmp(encoded, def, sizeof def) == 0, + "def rwir wire"); + free(encoded); + + uint8_t time_raw[8] = {1}; + CHECK(kvspaceXhNewShort("time", time_raw, 8, &encoded, &encoded_len) == 0, + "time encode"); + CHECK(kvspaceXhDecode(encoded, encoded_len, &h) == 0 && + h.content_len == 8 && h.headlen == 32, "time head"); + free(encoded); + + uint8_t func[32] = {5, KVSPACE_XH_FIXED_SMALL}; + memcpy(func + 18, "rwfunc", 6); + CHECK(kvspaceXhDecode(func, sizeof func, &h) == 0 && h.content_len == 0, + "rwfunc directory"); + CHECK(kvspaceXhNewShort("rwfunc", NULL, 0, &encoded, &encoded_len) == 0, + "rwfunc directory encode"); + CHECK(encoded_len == sizeof func && memcmp(encoded, func, sizeof func) == 0, + "rwfunc directory wire"); + free(encoded); + + uint8_t anchor[40] = {5, KVSPACE_XH_SLACK}; + anchor[2] = 5; + anchor[10] = 8; + memcpy(anchor + 18, "rwfunc", 6); + CHECK(kvspaceXhDecode(anchor, sizeof anchor, &h) == 0 && + h.content_len == 5 && h.body_cap == 8, "rwfunc anchor"); + CHECK(kvspaceXhNewCode("rwfunc", anchor + 32, 5, 8, + &encoded, &encoded_len) == 0, "rwfunc anchor encode"); + CHECK(encoded_len == sizeof anchor && memcmp(encoded, anchor, sizeof anchor) == 0, + "rwfunc anchor wire"); + free(encoded); + const uint8_t call_body[] = {0, 0, 0, 0, 0, 'f'}; + CHECK(kvspaceXhNewCode("rwfunc", call_body, sizeof call_body, sizeof call_body, + &encoded, &encoded_len) == 0, "rwfunc call encode"); + CHECK(kvspaceXhDecode(encoded, encoded_len, &h) == 0 && + h.content_len == sizeof call_body && + memcmp(h.body, call_body, sizeof call_body) == 0, "rwfunc call wire"); + free(encoded); + anchor[2] = 4; + CHECK(kvspaceXhDecode(anchor, sizeof anchor, &h) != 0, "short rwfunc anchor"); + + uint8_t ptr[40] = {5, KVSPACE_XH_SLACK | KVSPACE_XH_PTR_FLAG}; + ptr[2] = 5; + ptr[10] = 8; + memcpy(ptr + 18, "int64", 5); + memcpy(ptr + 32, "/varx", 5); + CHECK(kvspaceXhDecode(ptr, sizeof ptr, &h) == 0, "pointer head"); + CHECK(h.content_len == 5 && h.body_cap == 8 && h.kind == 5, + "pointer fields"); + CHECK(kvspaceXhNewPtr("int64", "/varx", 8, &encoded, &encoded_len) == 0, + "pointer encode"); + CHECK(encoded_len == sizeof ptr && memcmp(encoded, ptr, sizeof ptr) == 0, + "pointer wire"); + free(encoded); + ptr[1] |= 0x80; + CHECK(kvspaceXhDecode(ptr, sizeof ptr, &h) != 0, "reserved pointer bit"); + ptr[1] &= 0x7f; + ptr[2] = 9; + CHECK(kvspaceXhDecode(ptr, sizeof ptr, &h) != 0, "pointer length"); + ptr[2] = 5; + ptr[32] = 0; + CHECK(kvspaceXhDecode(ptr, sizeof ptr, &h) != 0, "pointer NUL"); + + uint8_t ext[40] = {5, KVSPACE_XH_EXT}; + ext[2] = 5; + ext[10] = 8; + memcpy(ext + 18, "int64", 5); + memcpy(ext + 32, "s3://", 5); + CHECK(kvspaceXhDecode(ext, sizeof ext, &h) == 0 && + h.kind == KVSPACE_XH_EXT && h.content_len == 5 && h.body_cap == 8, + "external locator"); + CHECK(kvspaceXhNewExt("int64", "s3://", 8, &encoded, &encoded_len) == 0, + "external encode"); + CHECK(encoded_len == sizeof ext && memcmp(encoded, ext, sizeof ext) == 0, + "external wire"); + free(encoded); + ext[32] = 0; + CHECK(kvspaceXhDecode(ext, sizeof ext, &h) != 0, "external NUL"); + ext[32] = 0xff; + CHECK(kvspaceXhDecode(ext, sizeof ext, &h) != 0, "external UTF-8"); + ext[32] = 's'; + ext[2] = 0; + CHECK(kvspaceXhDecode(ext, sizeof ext, &h) != 0, "empty external locator"); + + uint8_t rwir[40] = {5, KVSPACE_XH_SLACK}; + rwir[2] = 8; + rwir[10] = 8; + memcpy(rwir + 18, "rwir", 4); + CHECK(kvspaceXhDecode(rwir, sizeof rwir, &h) == 0, "rwir head"); + CHECK(h.content_len == 8 && h.total == sizeof rwir, "rwir body"); + CHECK(kvspaceXhNewCode("rwir", rwir + 32, 8, 8, &encoded, &encoded_len) == 0, + "rwir encode"); + CHECK(encoded_len == sizeof rwir && memcmp(encoded, rwir, sizeof rwir) == 0, + "rwir wire"); + free(encoded); + rwir[2] = 4; + CHECK(kvspaceXhDecode(rwir, sizeof rwir, &h) != 0, "short rwir body"); + + uint8_t def_type[35] = {5, KVSPACE_XH_SLACK}; + def_type[2] = def_type[10] = 3; + memcpy(def_type + 18, "def langtype", 12); + memcpy(def_type + 32, "int", 3); + CHECK(kvspaceXhDecode(def_type, sizeof def_type, &h) == 0, + "def langtype head"); + CHECK(kvspaceXhNewCode("def langtype", (const uint8_t *)"int", 3, 3, + &encoded, &encoded_len) == 0, + "def langtype encode"); + CHECK(encoded_len == sizeof def_type && + memcmp(encoded, def_type, sizeof def_type) == 0, + "def langtype wire"); + free(encoded); +} + +int main(void) { + test_none(); + test_int64(); + test_slack(); + test_utf8(); + test_invalid_utf8(); + test_tensor(); + test_metadata(); + test_reserve(); + test_container_ptr_code(); + if (failures) { + fprintf(stderr, "%d failed\n", failures); + return 1; + } + printf("ok\n"); + return 0; +}