diff --git a/CMakeLists.txt b/CMakeLists.txt index 350c0ec..47fdc70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,11 @@ if(BUILD_TESTING) target_include_directories(test_codec_abi PRIVATE src include) target_link_libraries(test_codec_abi PRIVATE kvspace) add_test(NAME codec_abi COMMAND test_codec_abi) + # wire 金标准:打印同一批操作的字节,供 kvspace-c / kvspace-durable 逐字节比对 + # (tests/compare_wire_golden.sh)。它不做断言,只产出可比对的文本。 + add_executable(xvalue_wire_golden tests/xvalue_wire_golden.c) + target_include_directories(xvalue_wire_golden PRIVATE src include) + target_link_libraries(xvalue_wire_golden PRIVATE kvspace) endif() # 前端 SONAME:libkvspace.so.1 diff --git a/cli/kvspace.rs b/cli/kvspace.rs index a939abb..ee3e8ab 100644 --- a/cli/kvspace.rs +++ b/cli/kvspace.rs @@ -5,10 +5,15 @@ use std::env; use std::os::raw::{c_char, c_int, c_void}; use std::process::exit; -/* 固定 head 8 字节(见 kvspace/xvalue_head.h):body 偏移恒 8。 */ -const KVSPACE_XVALUE_HEAD_LEN: usize = 8; -const KVSPACE_XVALUE_ST_SHORT: u8 = 0; -/* head.ptr: 0=inline, 1=ptr, 2=@ext (KVSPACE_XVALUE_PTR_*). */ +/* 固定 head 16 字节(见 kvspace/xvalue_head.h):body 偏移恒 16。 */ +const KVSPACE_XVALUE_HEAD_LEN: usize = 16; +/* head 只有 layout@12:0=bodylen 是有效长度,1=容量(有效长度在 attr tag 2)。 + 指针/扩展性**不在 head**,看 langtype 首字符 `*`/`@`。 */ +const KVSPACE_XVALUE_LAYOUT_CAPACITY: u8 = 1; +/* 后端写侧 storage class(由 layout/dims 推出,传给 kvspaceWriteNewPlace)。 */ +const KVSPACE_STORETYPE_FIXED_SMALL: u8 = 0; +const KVSPACE_STORETYPE_SLACK: u8 = 1; +const KVSPACE_STORETYPE_FIXED_LARGE: u8 = 2; const KVSPACE_XVALUE_PTR_PTR: u8 = 1; const KVSPACE_XVALUE_PTR_EXT: u8 = 2; @@ -131,7 +136,6 @@ extern "C" { out_len: *mut u32, ) -> c_int; fn kvspaceXvalueHeadNewScalar( - storetype: u8, raw: *const u8, raw_len: u32, langtype: *const c_char, @@ -166,15 +170,16 @@ fn fail_op(err: &[u8; 256], op: &str) -> ! { struct Value { kind: String, // Base kind (langtype without leading [dims]) - r#ref: u8, // head.ptr: 0=inline, 1=ptr, 2=@ext - storetype: u8, // head.storetype + r#ref: u8, // langtype 前缀:0=普通, 1=`*`, 2=`@` + storetype: u8, // 后端 storage class(由 layout/dims 推出,不在 head) dims: Vec, // Leading [dims] parsed from langtype body: Vec, } -/* head 固定 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8; - langtype 作为 attr 记录 tag=1 置于 body 之尾(值内,非独立 key,见 #571)。 - 值自描述:直接解 wire,不经 kvspaceGetHead(它对 Ptr 穿透,读不到本体)。 */ +/* head 固定 16 字节 [bodylen:u64@0][attrslen:u32@8][layout@12][padding@13-15], + body 偏移恒 16,attr 区紧随 body 且无自带长度前缀(长度只在 head 里); + langtype 作为 attr 记录 tag=1 置于 body 之后(值内,非独立 key)。 + 值自描述:直接解 wire(取头函数已删除;它对 Ptr 穿透,读不到本体)。 */ fn from_wire(data: &[u8]) -> Value { if data.len() < KVSPACE_XVALUE_HEAD_LEN { return Value { @@ -186,23 +191,39 @@ fn from_wire(data: &[u8]) -> Value { }; } let body_len = wire_len(data) as usize; - let (dims, kind) = parse_langtype(&wire_langtype(data, body_len)); - /* body 只到 head.len 为止;其后是 attr 区,不属 body。 */ + /* 指针/扩展性由 langtype 前缀表达,不再是 head 的第 13 字节。 */ + let raw_langtype = wire_langtype(data, body_len); + let (r#ref, langtype_body) = match raw_langtype.as_bytes().first() { + Some(b'*') => (KVSPACE_XVALUE_PTR_PTR, &raw_langtype[1..]), + Some(b'@') => (KVSPACE_XVALUE_PTR_EXT, &raw_langtype[1..]), + _ => (0u8, &raw_langtype[..]), + }; + let (dims, kind) = parse_langtype(langtype_body); + /* 后端 storage class 不在 head 里:layout=1 即按容量存(slack), + 有 dims 的定长张量是 fixed-large,其余 fixed-small。 */ + let storetype = if data[12] == KVSPACE_XVALUE_LAYOUT_CAPACITY { + KVSPACE_STORETYPE_SLACK + } else if !dims.is_empty() { + KVSPACE_STORETYPE_FIXED_LARGE + } else { + KVSPACE_STORETYPE_FIXED_SMALL + }; + /* body 只到 head.bodylen 为止;其后是 attr 区,不属 body。 */ let start = KVSPACE_XVALUE_HEAD_LEN; let end = start.saturating_add(body_len).min(data.len()); Value { kind, - r#ref: data[7], - storetype: data[6], + r#ref, + storetype, dims, body: data[start..end].to_vec(), } } -/// u48 小端 @0-5(唯一读点,与 C 侧 kvspaceXvalueHeadLen 一致)。 +/// bodylen:u64 小端 @0-7(唯一读点,与 C 侧 kvspaceXvalueHeadLen 一致)。 fn wire_len(data: &[u8]) -> u64 { let mut v = 0u64; - for i in (0..6).rev() { + for i in (0..8).rev() { v = (v << 8) | data[i] as u64; } v @@ -211,15 +232,23 @@ fn wire_len(data: &[u8]) -> u64 { /// 扫 attr 区取 tag=1(langtype);无记录/坏 attr → 空串。 fn wire_langtype(data: &[u8], body_len: usize) -> String { let base = KVSPACE_XVALUE_HEAD_LEN.saturating_add(body_len); - if base.saturating_add(2) > data.len() { + /* attrslen:u32 @8-11;attr 区无自带前缀。padding 非零视为非法。 */ + if data.len() < KVSPACE_XVALUE_HEAD_LEN + || data[13] != 0 + || data[14] != 0 + || data[15] != 0 + { return String::new(); } - let alen = (data[base] as usize) | ((data[base + 1] as usize) << 8); - if alen < 2 || base + alen > data.len() { + let alen = (data[8] as usize) + | ((data[9] as usize) << 8) + | ((data[10] as usize) << 16) + | ((data[11] as usize) << 24); + if base + alen > data.len() { return String::new(); } let end = base + alen; - let mut p = base + 2; + let mut p = base; while p + 3 <= end { let tag = data[p]; let rlen = (data[p + 1] as usize) | ((data[p + 2] as usize) << 8); @@ -386,7 +415,6 @@ fn parse_value(raw: &str) -> Vec { let mut len64: u64 = 0; if unsafe { kvspaceXvalueHeadNewScalar( - KVSPACE_XVALUE_ST_SHORT, std::ptr::null(), 0, cs(langtype), @@ -440,7 +468,6 @@ fn parse_value(raw: &str) -> Vec { let mut len64: u64 = 0; if unsafe { kvspaceXvalueHeadNewScalar( - KVSPACE_XVALUE_ST_SHORT, le.as_ptr(), 4, cs(langtype), diff --git a/include/kvspace/kvspace.h b/include/kvspace/kvspace.h index 19f9e8b..4f39488 100644 --- a/include/kvspace/kvspace.h +++ b/include/kvspace/kvspace.h @@ -6,11 +6,20 @@ * shm://... → libkvspace-c.so.1 * 其余(redis/fs/s3)→ libkvspace_durable.so.1 * - * Wire: [len:u48 LE@0-5][storetype:u8@6][ptr:u8@7][body@8][attr@8+len]. - * Head is fixed 8 bytes; body offset is always 8. attr = [attr_len:u16 LE] - * + records [tag:u8][len:u16 LE][data]; tag 1 = langtype (plaintext UTF-8, - * incl. leading [dims]). Empty attr = 2 bytes. Single-key write — no attr key. - * See kvspace/xvalue_head.h. + * Wire: [bodylen:u64 LE@0][attrslen:u32 LE@8][layout:u8@12][padding:3@13-15] + * [body@16][attr records]. + * Head is fixed 16 bytes, so the body offset equals the head length — never a + * literal; read it from kvspaceXvalueHeadLen / the `headlen` field below. + * `layout` 0 = bodylen is the effective length; 1 = bodylen is capacity and the + * effective length lives in attr tag 2. There is no storetype/ptr field: the + * storage form and reference-ness are carried by the langtype text (`*`/`@`). + * The attr region carries NO length prefix: its size is attrslen in the head, + * so a second prefix would be a second source of truth. Records are + * [tag:u8][len:u16 LE][data]; tag 1 = langtype (plaintext UTF-8, incl. leading + * [dims] and `*`/`@` prefix), tag 2 = reallen (layout=1 only). + * Empty attr = attrslen 0. Single-key write — no attr key. + * Total length = 16 + bodylen + attrslen. + * See kvspace/xvalue_head.h for the authoritative layout and helpers. */ #ifndef KVSPACE_H @@ -29,25 +38,21 @@ extern "C" { #define KVSPACE_REF_PTR 1 #define KVSPACE_REF_EXT 2 -/* Storage classes (kvspaceXvalueHead.storetype wire byte). */ +/* 后端写侧 storage class(wire head 已无此字段;由 langtype 推出后传给 + * kvspaceWriteNewPlace)。 */ #define KVSPACE_STORETYPE_FIXED_SMALL 0 #define KVSPACE_STORETYPE_SLACK 1 #define KVSPACE_STORETYPE_FIXED_LARGE 2 #define KVSPACE_STORETYPE_EXT 3 -/* XValue metadata. langtype is read from attr tag=1; ndim/dims are parsed from leading [dims]. */ -typedef struct { - uint16_t headlen; /* head 总字节数;body 起于偏移 headlen */ - uint8_t ref; /* 存储位置:见 KVSPACE_REF_* */ - uint8_t storetype; /* Storage class. */ - int32_t body_len; /* body 字节数 */ - int32_t ndim; /* Tensor dimensions; zero for other values. */ - int32_t dims[8]; /* Tensor shape. */ - char langtype[256]; /* 语义类型 kindexpr 串,NUL 终止(含 [dims]、无 ptr/ext 前缀) */ - int32_t langtype_len; /* langtype 内容长度(去 padding) */ - int32_t body_offset; /* body 在 data 内的起始偏移(= headlen) */ - uint64_t body_cap; -} kvspaceHead_t; +/* + * **读头一律用 `kvspace/xvalue_head.h` 的 inline 方法;这里不另设解码结构体。** + * + * 一个「手抄的 head 结构体」就是第二份字段真相:字段一改,副本不跟着变, + * 而它仍然能编译、仍然读得出**看起来合理**的值——kvspace-c 曾因此留下一份 + * 8 字节 head 的过时副本,读新值全是垃圾却不报错。所以「解码结构体 + 取头函数」这一对已删除:需要 langtype/维度就调 `kvspaceXvalueHeadScan`, + * 需要 body 长度就调 `kvspaceXvalueHeadEffectiveLen`。 + */ /* ── 生命周期 ─────────────────────────────────────────────────── */ void *kvspaceConnect(const char *dsn); @@ -151,9 +156,6 @@ int kvspaceGetPart(void *h, const char *key, uint32_t offset, uint32_t len, int kvspaceSetPart(void *h, const char *key, uint32_t offset, const uint8_t *buf, uint32_t buf_len, char *err, uint32_t err_cap); -/* Read the wire head + langtype tail. Missing keys return nonzero. */ -int kvspaceGetHead(void *h, const char *key, kvspaceHead_t *out); - /* In-place write: key must exist with unchanged kind and body_len; returns existing body * pointer for direct mutation, or nonzero + err on precondition failure (never reallocates). * resolve != 0 follows leaf Ptr chains (up to 16 hops) and writes the terminal target in place; @@ -163,7 +165,16 @@ int kvspaceWriteInPlace(void *h, const char *key, int resolve, uint32_t body_len uint8_t **body, char *err, uint32_t err_cap); /* Reserve a new XValue and return its body pointer; fill it before another KVSpace call. - * langtype (empty/NULL = untyped) is written with the header/attr in a single-key write. */ + * langtype (empty/NULL = untyped) is written with the header/attr in a single-key write. + * + * `storetype` carries no information in the current XValue model: the storage form is + * derived from langtype and `layout` is derived from `body_cap > body_len`. The + * parameter stays only because the frontend dispatches through a function-pointer + * table, so every backend export must keep the exact argument list. Implementations + * must not branch on it and must not validate its value; `ref` is still meaningful + * (0 inline / 1 pointer / 2 extension) and becomes the `*` / `@` langtype prefix. + * Removing the parameter and the KVSPACE_STORETYPE_* constants is a separate ABI + * change that must sweep every caller at once. */ int kvspaceWriteNewPlace(void *h, const char *key, uint8_t ref, uint8_t storetype, uint32_t body_len, uint64_t body_cap, const char *langtype, uint8_t **body, char *err, uint32_t err_cap); @@ -192,11 +203,12 @@ int kvspaceWatch(void *h, const char *key, const uint8_t *target, uint32_t targe uint64_t tick_ns, uint8_t **out, uint32_t *out_len); /* XValue constructors (stateless, implemented in frontend, byte-identical across backends). - * Fixed 8-byte header + body + attr region (see kvspace/xvalue_head.h). - * Empty/NULL langtype = untyped (omits langtype record; attr region is just 2-byte attr_len). */ + * Fixed 16-byte header + body + attr region (see kvspace/xvalue_head.h). + * Empty/NULL langtype = untyped (omits the langtype record; then attrslen is 0). */ -/* Construct a leaf Ptr XValue (storetype=SLACK, ptr=PTR, body=target path bytes, - * target_kindexpr written to attr tag=1; "" means unconstrained; rejects "any" and empty target). +/* Construct a leaf Ptr XValue (body=target path bytes, langtype = `*` + + * target_kindexpr written to attr tag=1; "" means unconstrained -> bare "*"; + * rejects "any" and empty target). The head carries no ptr bit. * Pure wire constructor: does not connect to a backend or validate target existence/langtype. */ int kvspaceNewPtr(const char *target_kindexpr, const char *target, uint8_t **out, uint32_t *out_len); diff --git a/include/kvspace/xvalue_head.h b/include/kvspace/xvalue_head.h index 3b12a12..2fa0c4f 100644 --- a/include/kvspace/xvalue_head.h +++ b/include/kvspace/xvalue_head.h @@ -1,119 +1,176 @@ -/* Wire: [len:u48][storetype:u8][ptr:u8][body][attr]. Head is fixed 8 bytes, - body offset is always 8; attr region starts at 8+len and is length-prefixed - (total = 8 + len + attr_len). Type rides in the same single-key value — no - attr key. attr = [attr_len:u16][records]; record = [tag:u8][len:u16][data], - tag 1 = langtype, tags 2+ reserved (ro/vid...). */ +/* Wire: [bodylen:u64 LE@0][attrslen:u32 LE@8][layout:u8@12][padding:3@13-15] + [body @16..16+bodylen][attr @16+bodylen..16+bodylen+attrslen]. + Head is fixed 16 bytes; body offset is always 16. + head 只描述「字节怎么读」:bodylen 是 body 区字节数,layout 说它是不是容量。 + 「这个值是什么」全在 langtype(attr tag 1,含 [dims] 与 * / @ 前缀),head 不承载。 + The attr region carries NO length prefix — attrslen is in the head, so a second + prefix would be a second source of truth. Total = 16 + bodylen + attrslen, + computed from the head alone. + Type rides in the same single-key value — no attr key. attr record = + [tag:u8][len:u16 LE][data]; tag 1 = langtype, tag 2 = reallen, tags 3+ reserved. + Wire is little-endian. Current platforms are LE, so a straight memcpy of the + struct equals the wire; a big-endian target must convert at the boundary. */ #ifndef XVALUE_HEAD_H #define XVALUE_HEAD_H #include #include +#include + + +/* C++ 不认 `_Static_assert`(C11 关键字);同一份断言给两种语言用。 */ +#if defined(__cplusplus) +#define KVSPACE_STATIC_ASSERT(condition, message) static_assert(condition, message) +#else +#define KVSPACE_STATIC_ASSERT(condition, message) _Static_assert(condition, message) +#endif #ifdef __cplusplus extern "C" { #endif -#define KVSPACE_XVALUE_HEAD_LEN 8u - -/* storetype:存储形态(= 旧 class) */ -#define KVSPACE_XVALUE_ST_SHORT 0u /* 短定长(标量/None/map 头等,body 宽度由类型定) */ -#define KVSPACE_XVALUE_ST_SLACK 1u /* 变长(字节/字符数组/代码槽/指针路径,len=有效字节) */ -#define KVSPACE_XVALUE_ST_LARGE 2u /* 大定长(张量,body=元素连续) */ -#define KVSPACE_XVALUE_ST_EXT 3u /* 扩展定位(@ext,body=定位串) */ +#define KVSPACE_XVALUE_HEAD_LEN 16u -/* ptr:指针/扩展形态 */ -#define KVSPACE_XVALUE_PTR_INLINE 0u -#define KVSPACE_XVALUE_PTR_PTR 1u -#define KVSPACE_XVALUE_PTR_EXT 2u +/* layout:bodylen 的语义。没有 storetype/ptr 字段——存储形态与引用性都从 + langtype 推出;唯一推不出的是「body 区是容量而非有效长度」,故只保留这一标志。 */ +#define KVSPACE_XVALUE_LAYOUT_LENGTH 0u /* bodylen = 有效长度 */ +#define KVSPACE_XVALUE_LAYOUT_CAPACITY 1u /* bodylen = 容量;有效长度在 attr tag 2 */ -/* Fixed 8-byte layout (direct memcpy struct + body). - wire: len:u48@0-5 (little-endian), storetype@6, ptr@7, body@8. */ +/* Fixed 16-byte layout (direct memcpy struct + body + attrs). + wire: bodylen:u64@0, attrslen:u32@8, layout@12, padding@13-15. */ typedef struct kvspaceXvalueHead { - uint8_t len[6]; /* u48 小端(offset 0-5) */ - uint8_t storetype; - uint8_t ptr; + uint64_t bodylen; /* body 区字节数:有效长度,或容量(见 layout) */ + uint32_t attrslen; /* attr 区字节数(不含任何前缀) */ + uint8_t layout; /* KVSPACE_XVALUE_LAYOUT_*;其它值非法 */ + uint8_t padding[3]; /* 必须为零;读取方遇到非零按非法处理 */ } kvspaceXvalueHead; -/* u48 len 的唯一定义点:读/写一律走这两个 inline 方法,禁止各处手写拼接。 */ +KVSPACE_STATIC_ASSERT(sizeof(kvspaceXvalueHead) == KVSPACE_XVALUE_HEAD_LEN, + "XValue head must be exactly 16 bytes"); +KVSPACE_STATIC_ASSERT(offsetof(kvspaceXvalueHead, bodylen) == 0U, "bodylen@0"); +KVSPACE_STATIC_ASSERT(offsetof(kvspaceXvalueHead, attrslen) == 8U, "attrslen@8"); +KVSPACE_STATIC_ASSERT(offsetof(kvspaceXvalueHead, layout) == 12U, "layout@12"); +KVSPACE_STATIC_ASSERT(offsetof(kvspaceXvalueHead, padding) == 13U, "padding@13"); + +/* 字段的唯一定义点:读/写一律走这些 inline 方法,禁止各处直接取字段或手写偏移。 */ static inline uint64_t kvspaceXvalueHeadLen(const kvspaceXvalueHead *h) { - uint64_t v = 0; - for (int i = 5; i >= 0; i--) - v = (v << 8) | h->len[i]; - return v; + return h->bodylen; } static inline void kvspaceXvalueHeadSetLen(kvspaceXvalueHead *h, uint64_t v) { - for (int i = 0; i < 6; i++) { - h->len[i] = (uint8_t)(v & 0xff); - v >>= 8; - } + h->bodylen = v; +} +static inline uint32_t kvspaceXvalueHeadAttrsLen(const kvspaceXvalueHead *h) { + return h->attrslen; +} +static inline void kvspaceXvalueHeadSetAttrsLen(kvspaceXvalueHead *h, uint32_t v) { + h->attrslen = v; +} +static inline uint8_t kvspaceXvalueHeadLayout(const kvspaceXvalueHead *h) { + return h->layout; +} +static inline void kvspaceXvalueHeadSetLayout(kvspaceXvalueHead *h, uint8_t v) { + h->layout = v; +} +static inline int kvspaceXvalueHeadLayoutIsValid(const kvspaceXvalueHead *h) { + return h->layout == KVSPACE_XVALUE_LAYOUT_LENGTH || + h->layout == KVSPACE_XVALUE_LAYOUT_CAPACITY; +} +/* padding 是 ABI 的一部分:非零即非法,读取路径必须先过这一关。 */ +static inline int kvspaceXvalueHeadPaddingIsZero(const kvspaceXvalueHead *h) { + return h->padding[0] == 0u && h->padding[1] == 0u && h->padding[2] == 0u; +} +/* 总长 = 16 + bodylen + attrslen;非法 layout、非零 padding、溢出都返回 0 + (0 不是合法总长)。 */ +static inline uint64_t kvspaceXvalueHeadTotalLen(const kvspaceXvalueHead *h) { + if (!kvspaceXvalueHeadPaddingIsZero(h) || !kvspaceXvalueHeadLayoutIsValid(h)) + return 0; + uint64_t body = kvspaceXvalueHeadLen(h); + uint64_t attrs = kvspaceXvalueHeadAttrsLen(h); + if (body > UINT64_MAX - KVSPACE_XVALUE_HEAD_LEN || + attrs > UINT64_MAX - KVSPACE_XVALUE_HEAD_LEN - body) + return 0; + return (uint64_t)KVSPACE_XVALUE_HEAD_LEN + body + attrs; +} +/* attr 区起点:总由 head 推出,不传 body_len。 */ +static inline const uint8_t *kvspaceXvalueHeadAttrs(const uint8_t *wire) { + const kvspaceXvalueHead *h = (const kvspaceXvalueHead *)(const void *)wire; + return wire + KVSPACE_XVALUE_HEAD_LEN + (uint64_t)h->bodylen; } -/* attr 区常量:record tag 1 = langtype;2+ 预留扩展(ro/vid 等,未实现)。 */ +/* attr 区常量:record tag 1 = langtype;tag 2 = reallen;其余预留。 */ #define KVSPACE_XVALUE_ATTR_TAG_LANGTYPE 1u +/* tag 2 的归属是**排他**的:layout=1 必须恰有一条、layout=0 必须不带。 + 未知 tag 一般忽略以保持前向兼容;tag 2 例外——它已分配给 reallen,而 reallen + 只在 layout=1 下有定义。「有定义但在此 layout 下无意义」说明写读双方对格式 + 理解不一致,必须拒绝;「我不认识这个 tag」才该忽略。 + data 恒 8 字节 u64 LE。 */ +#define KVSPACE_XVALUE_ATTR_TAG_REALLEN 2u -/* Read total attr length (including its own 2-byte length prefix; empty attr = 2). - Unbounded: caller must ensure wire has at least 8+body_len+2 bytes; use - kvspaceXvalueHeadLangtypeBounded for untrusted or backend-borrowed inputs. */ -static inline uint16_t kvspaceXvalueHeadAttrLen(const uint8_t *wire, - uint64_t body_len) { - const uint8_t *p = wire + KVSPACE_XVALUE_HEAD_LEN + body_len; - return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); -} +/* attr 扫描的唯一入口:校验 padding、layout、总长与 tag 2 归属,取出 tag 1 + 与 tag 2。输出参数都可为 NULL。返回 0 成功,-1 失败。 */ +int kvspaceXvalueHeadScan(const uint8_t *wire, uint64_t wire_len, + const uint8_t **out_langtype, + uint16_t *out_langtype_len, + uint64_t *out_reallen); -/* Scan attr records for tag=1 (langtype) data; returns 0 if missing or malformed. - Unbounded: caller must ensure wire has at least 8+body_len+attr_len bytes; use - kvspaceXvalueHeadLangtypeBounded when bounds are not yet verified. */ +int kvspaceXvalueHeadEffectiveLen(const uint8_t *wire, uint64_t wire_len, + uint64_t *out); + +/* Scan the attr region for tag=1 (langtype) data; returns 0 if missing or + malformed. Unbounded: caller must ensure the wire holds 16+bodylen+attrslen + bytes; use kvspaceXvalueHeadLangtypeBounded when bounds are not yet verified. + The attr region has no length prefix — its size comes from the head. */ static inline uint16_t kvspaceXvalueHeadLangtype(const uint8_t *wire, - uint64_t body_len, const uint8_t **out) { - *out = NULL; - uint16_t alen = kvspaceXvalueHeadAttrLen(wire, body_len); - if (alen < 2) + if (out) + *out = NULL; + const kvspaceXvalueHead *h = (const kvspaceXvalueHead *)(const void *)wire; + const uint64_t total = kvspaceXvalueHeadTotalLen(h); + if (total == 0) + return 0; + const uint8_t *langtype = NULL; + uint16_t langtype_len = 0; + if (kvspaceXvalueHeadScan(wire, total, &langtype, &langtype_len, NULL) != 0) return 0; - const uint8_t *p = wire + KVSPACE_XVALUE_HEAD_LEN + body_len + 2; - const uint8_t *end = wire + KVSPACE_XVALUE_HEAD_LEN + body_len + alen; - while (p + 3 <= end) { - uint8_t tag = p[0]; - uint16_t rlen = (uint16_t)((uint16_t)p[1] | ((uint16_t)p[2] << 8)); - if (p + 3 + rlen > end) - return 0; - if (tag == KVSPACE_XVALUE_ATTR_TAG_LANGTYPE) { - *out = p + 3; - return rlen; - } - p += 3 + rlen; - } - return 0; + if (out) + *out = langtype; + return langtype_len; } -/* Bounded variant: verifies 8+len+2 <= wire_len and 8+len+attr_len <= wire_len - before scanning records. Returns 0 and sets *out=NULL on missing/malformed/truncated values. */ +/* Bounded variant: verifies the head fits, padding is zero, layout is valid and + 16+bodylen+attrslen <= wire_len before scanning records. + Returns 0 and sets *out=NULL on missing/malformed/truncated values. */ static inline uint16_t kvspaceXvalueHeadLangtypeBounded(const uint8_t *wire, uint64_t wire_len, const uint8_t **out) { - *out = NULL; - if (!wire || wire_len < KVSPACE_XVALUE_HEAD_LEN + 2u) - return 0; - uint64_t body_len = 0; - for (int i = 5; i >= 0; i--) - body_len = (body_len << 8) | wire[i]; - const uint64_t room = wire_len - KVSPACE_XVALUE_HEAD_LEN; - if (body_len > room - 2u) + if (out) + *out = NULL; + const uint8_t *langtype = NULL; + uint16_t langtype_len = 0; + if (kvspaceXvalueHeadScan(wire, wire_len, &langtype, &langtype_len, NULL) != 0) return 0; - if (kvspaceXvalueHeadAttrLen(wire, body_len) > room - body_len) - return 0; - return kvspaceXvalueHeadLangtype(wire, body_len, out); + if (out) + *out = langtype; + return langtype_len; } -/* Constructors (fixed 8-byte header + body + attr region in a single value). - Empty/NULL langtype omits the langtype record (empty attr = [attr_len:2]). +/* Constructors (fixed 16-byte header + body + attr region in a single value). + Empty/NULL langtype omits the langtype record (attrslen = 0). Returns 0 on success, -1 and clears *out on failure (when out is non-NULL). - Fixed header has no capacity/shape fields: cap only checks cap >= len, and - dims/ndim only check non-empty (shape lives in leading [dims] of langtype). - NewCode is an alias of NewSlack (SLACK/INLINE) kept for ABI compatibility. */ + + layout 判据:只有「容量 > 有效长度」才是 LAYOUT_CAPACITY(此时 body 区按容量 + 分配、有效长度写进 attr tag 2);其余一律 LAYOUT_LENGTH。NewScalar/NewTensor + 没有容量参数,恒为 LAYOUT_LENGTH。NewSlack/NewCode/NewReserve 以 cap > len 判定。 + + NewPtr/NewExt 是唯一知道「这个值是引用」的构造点:调用方传 pointed-to 的 + 基 kindexpr(不带 * / @),构造器补前缀(空 langtype → 裸 "*"/"@")。 + 带 * / @ 的输入直接拒绝——双重前缀说明两处对格式理解不一致。 + + **`@` 仅保留线格式构造**:NewExt 能产出带 `@` 前缀的合法 wire,但运行时 + 语义(读、写、比较、解引用)本期不实现、不消费——线格式要先立住,语义后补。 */ int kvspaceXvalueHeadNewNone(uint8_t **out, uint64_t *out_len); -int kvspaceXvalueHeadNewScalar(uint8_t storetype, const uint8_t *raw, - uint32_t raw_len, const char *langtype, +int kvspaceXvalueHeadNewScalar(const uint8_t *raw, uint32_t raw_len, + const char *langtype, uint8_t **out, uint64_t *out_len); int kvspaceXvalueHeadNewSlack(const uint8_t *data, uint64_t data_len, uint64_t cap, const char *langtype, @@ -131,8 +188,9 @@ int kvspaceXvalueHeadNewExt(const uint8_t *locator, uint32_t locator_len, int kvspaceXvalueHeadNewCode(const uint8_t *body, uint64_t body_len, uint64_t cap, const char *langtype, uint8_t **out, uint64_t *out_len); -int kvspaceXvalueHeadReserve(uint8_t storetype, uint64_t body_len, - uint64_t cap, const char *langtype, +/* 预留 = body 区 cap 字节零填;有效长度 body_len(cap > body_len 时 layout=1)。 */ +int kvspaceXvalueHeadReserve(uint64_t body_len, uint64_t cap, + const char *langtype, uint8_t **out, uint64_t *out_len); #ifdef __cplusplus diff --git a/src/codec_abi.c b/src/codec_abi.c index fc0c214..d367a34 100644 --- a/src/codec_abi.c +++ b/src/codec_abi.c @@ -22,7 +22,10 @@ int kvspaceNewPtr(const char *target_kindexpr, const char *target, /* `any` 不是类型名(空 kindexpr = 不约束,见 spec [[文法与合法性]])。 */ if (strcmp(target_kindexpr, "any") == 0) return 1; - /* target_kindexpr 拼入 value 尾部(单 key 整体,langtype 尾部)。 */ + /* 空 target 不是可用的指针目标(None 指针走 xvalue_head 构造器,不经本 codec)。 */ + if (*target == '\0') + return 1; + /* target_kindexpr 是被指向类型的基 kindexpr;`*` 前缀由 NewPtr 唯一补上。 */ uint8_t *value = NULL; uint64_t total = 0; if (kvspaceXvalueHeadNewPtr((const uint8_t *)target, @@ -50,8 +53,7 @@ int kvspaceNewBool(uint8_t v, const char *langtype, uint8_t b = v ? 1 : 0; uint8_t *value = NULL; uint64_t total = 0; - if (kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, &b, 1, langtype, - &value, &total) != 0) + if (kvspaceXvalueHeadNewScalar(&b, 1, langtype, &value, &total) != 0) return 1; return finish(value, total, out, out_len); } @@ -65,8 +67,7 @@ int kvspaceNewInt64(int64_t v, const char *langtype, raw[i] = (uint8_t)((uint64_t)v >> (8 * i)); uint8_t *value = NULL; uint64_t total = 0; - if (kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, langtype, - &value, &total) != 0) + if (kvspaceXvalueHeadNewScalar(raw, 8, langtype, &value, &total) != 0) return 1; return finish(value, total, out, out_len); } @@ -82,8 +83,7 @@ int kvspaceNewFloat64(double v, const char *langtype, raw[i] = (uint8_t)(bits >> (8 * i)); uint8_t *value = NULL; uint64_t total = 0; - if (kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, langtype, - &value, &total) != 0) + if (kvspaceXvalueHeadNewScalar(raw, 8, langtype, &value, &total) != 0) return 1; return finish(value, total, out, out_len); } diff --git a/src/frontend.c b/src/frontend.c index 8dcc14f..21272df 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -2,7 +2,7 @@ * * 导出 kvspace* C ABI(与两个后端同名同签名)。运行期按 DSN scheme 用 dlopen * (RTLD_NOW | RTLD_LOCAL)装载后端,把 handle 包一层 {vtable, dl, backend}。 - * XValue 构造(kvspaceNew*,head 固定 8 字节 memcpy + body)与 kvspaceConst + * XValue 构造(kvspaceNew*,head 固定 16 字节 memcpy + body)与 kvspaceConst * 无 handle,由前端静态实现,byte-identical。 * * 后端装载名(后缀随平台): @@ -37,7 +37,6 @@ typedef struct { uint8_t **out, uint32_t *out_len); int (*setpart)(void *h, const char *key, uint32_t offset, const uint8_t *buf, uint32_t buf_len, char *err, uint32_t err_cap); - int (*gethead)(void *h, const char *key, kvspaceHead_t *out); int (*writeinplace)(void *h, const char *key, int resolve, uint32_t body_len, uint8_t **body, char *err, uint32_t err_cap); int (*writenewplace)(void *h, const char *key, uint8_t ref, uint8_t storetype, @@ -226,7 +225,6 @@ void *kvspaceConnect(const char *dsn) { LOAD(setvalue, "kvspaceSetValue"); LOAD(getpart, "kvspaceGetPart"); LOAD(setpart, "kvspaceSetPart"); - LOAD(gethead, "kvspaceGetHead"); LOAD(writeinplace, "kvspaceWriteInPlace"); LOAD(writenewplace, "kvspaceWriteNewPlace"); LOAD(listlen, "kvspaceListLen"); @@ -359,11 +357,6 @@ int kvspaceSetPart(void *h, const char *key, uint32_t offset, const uint8_t *buf return x->vt->setpart(x->backend, key, offset, buf, buf_len, err, err_cap); } -int kvspaceGetHead(void *h, const char *key, kvspaceHead_t *out) { - kvspace_handle *x = H(h); - return x->vt->gethead(x->backend, key, out); -} - int kvspaceWriteInPlace(void *h, const char *key, int resolve, uint32_t body_len, uint8_t **body, char *err, uint32_t err_cap) { kvspace_handle *x = H(h); diff --git a/src/xvalue_head.c b/src/xvalue_head.c index d7dd7cc..5dae8be 100644 --- a/src/xvalue_head.c +++ b/src/xvalue_head.c @@ -3,11 +3,13 @@ #include #include -/* V2 final layout (#571): fixed 8-byte header [len:u48 LE@0-5][storetype:u8@6][ptr:u8@7], - body always at offset 8, self-delimited attr region at 8+len (carrying langtype). - Single-key atomic write — no separate attr key. Empty langtype = empty attr. */ +/* Layout: fixed 16-byte header [bodylen:u64@0][attrslen:u32@8][layout@12][padding@13] + [padding@14,16), body at offset 16, attr region right after the body with NO length + prefix (attrslen is in the head — a second prefix would be a second truth). + Single-key atomic write — no separate attr key. Empty langtype = empty attr. + layout=1 时 bodylen 是容量:body 区按容量分配、有效长度写进 attr tag 2。 */ -#define UINT48_MAX UINT64_C(0xFFFFFFFFFFFF) +#define REALLEN_RECORD_LEN 11u /* [tag:u8][len:u16][reallen:u64] */ /* Failure helper: clear *out per header contract. */ static int fail(uint8_t **out) { @@ -16,41 +18,67 @@ static int fail(uint8_t **out) { return -1; } -/* Single wire builder. Zero-fills body when body is NULL (Reserve placeholder); - public entry points validate whether non-zero len requires non-NULL body. */ -static int build(uint8_t storetype, uint8_t ptr, const uint8_t *body, - uint64_t len, const char *langtype, uint8_t **out, - uint64_t *out_len) { +/* Single wire builder. Zero-fills the body when body is NULL (Reserve + placeholder); public entry points validate whether non-zero effective_len + requires non-NULL body. + langtype_prefix 只由 NewPtr/NewExt 传入 '*' 或 '@',构造成 langtype 记录的前缀。 */ +static int build(uint8_t layout, const uint8_t *body, uint64_t region_len, + uint64_t effective_len, char langtype_prefix, + const char *langtype, uint8_t **out, uint64_t *out_len) { if (!out || !out_len) return fail(out); *out = NULL; - if (storetype > KVSPACE_XVALUE_ST_EXT || ptr > KVSPACE_XVALUE_PTR_EXT) + if (layout == KVSPACE_XVALUE_LAYOUT_LENGTH) { + if (effective_len != region_len) + return -1; + } else if (layout == KVSPACE_XVALUE_LAYOUT_CAPACITY) { + if (effective_len > region_len) + return -1; + } else { return -1; - if (len > UINT48_MAX) + } + const uint64_t ltl = langtype ? (uint64_t)strlen(langtype) : 0; + const uint64_t record_len = ltl + (langtype_prefix ? 1u : 0u); + if (record_len > UINT16_MAX) + return -1; + uint64_t alen = record_len ? 3 + record_len : 0; + if (layout == KVSPACE_XVALUE_LAYOUT_CAPACITY) + alen += REALLEN_RECORD_LEN; + if (alen > UINT32_MAX) return -1; - uint64_t ltl = langtype ? strlen(langtype) : 0; - uint64_t alen = 2 + (ltl ? 3 + ltl : 0); /* attr 总长(含自身 2 字节) */ - if (alen > UINT16_MAX) + if (region_len > UINT64_MAX - KVSPACE_XVALUE_HEAD_LEN || + alen > UINT64_MAX - KVSPACE_XVALUE_HEAD_LEN - region_len) return -1; - uint64_t n = (uint64_t)KVSPACE_XVALUE_HEAD_LEN + len + alen; + const uint64_t n = (uint64_t)KVSPACE_XVALUE_HEAD_LEN + region_len + alen; if (n > SIZE_MAX) return -1; uint8_t *p = (uint8_t *)calloc(1, (size_t)n); if (!p) return -1; - kvspaceXvalueHead h = {.storetype = storetype, .ptr = ptr}; - kvspaceXvalueHeadSetLen(&h, len); + kvspaceXvalueHead h = {.layout = layout, .padding = {0u, 0u, 0u}}; + kvspaceXvalueHeadSetLen(&h, region_len); + kvspaceXvalueHeadSetAttrsLen(&h, (uint32_t)alen); memcpy(p, &h, sizeof h); - if (len && body) - memcpy(p + KVSPACE_XVALUE_HEAD_LEN, body, len); - uint8_t *a = p + KVSPACE_XVALUE_HEAD_LEN + len; - a[0] = (uint8_t)alen; - a[1] = (uint8_t)(alen >> 8); - if (ltl) { - a[2] = KVSPACE_XVALUE_ATTR_TAG_LANGTYPE; - a[3] = (uint8_t)ltl; - a[4] = (uint8_t)(ltl >> 8); - memcpy(a + 5, langtype, ltl); + if (body && effective_len) + memcpy(p + KVSPACE_XVALUE_HEAD_LEN, body, effective_len); + uint8_t *a = p + KVSPACE_XVALUE_HEAD_LEN + region_len; + if (record_len) { + a[0] = KVSPACE_XVALUE_ATTR_TAG_LANGTYPE; + a[1] = (uint8_t)record_len; + a[2] = (uint8_t)(record_len >> 8); + uint64_t at = 3; + if (langtype_prefix) + a[at++] = (uint8_t)langtype_prefix; + if (ltl) + memcpy(a + at, langtype, ltl); + a += 3 + record_len; + } + if (layout == KVSPACE_XVALUE_LAYOUT_CAPACITY) { + a[0] = KVSPACE_XVALUE_ATTR_TAG_REALLEN; + a[1] = 8; + a[2] = 0; + for (int i = 0; i < 8; i++) + a[3 + i] = (uint8_t)(effective_len >> (8 * i)); } *out = p; *out_len = n; @@ -58,17 +86,17 @@ static int build(uint8_t storetype, uint8_t ptr, const uint8_t *body, } int kvspaceXvalueHeadNewNone(uint8_t **out, uint64_t *out_len) { - return build(KVSPACE_XVALUE_ST_SHORT, KVSPACE_XVALUE_PTR_INLINE, NULL, 0, - NULL, out, out_len); + return build(KVSPACE_XVALUE_LAYOUT_LENGTH, NULL, 0, 0, 0, NULL, out, + out_len); } -int kvspaceXvalueHeadNewScalar(uint8_t storetype, const uint8_t *raw, - uint32_t raw_len, const char *langtype, +int kvspaceXvalueHeadNewScalar(const uint8_t *raw, uint32_t raw_len, + const char *langtype, uint8_t **out, uint64_t *out_len) { if (raw_len && !raw) return fail(out); - return build(storetype, KVSPACE_XVALUE_PTR_INLINE, raw, raw_len, langtype, - out, out_len); + return build(KVSPACE_XVALUE_LAYOUT_LENGTH, raw, raw_len, raw_len, 0, + langtype, out, out_len); } int kvspaceXvalueHeadNewSlack(const uint8_t *data, uint64_t data_len, @@ -76,8 +104,9 @@ int kvspaceXvalueHeadNewSlack(const uint8_t *data, uint64_t data_len, uint8_t **out, uint64_t *out_len) { if (cap < data_len || (data_len && !data)) return fail(out); - return build(KVSPACE_XVALUE_ST_SLACK, KVSPACE_XVALUE_PTR_INLINE, data, - data_len, langtype, out, out_len); + const uint8_t layout = cap > data_len ? KVSPACE_XVALUE_LAYOUT_CAPACITY + : KVSPACE_XVALUE_LAYOUT_LENGTH; + return build(layout, data, cap, data_len, 0, langtype, out, out_len); } int kvspaceXvalueHeadNewTensor(const uint64_t *dims, uint32_t ndim, @@ -87,16 +116,19 @@ int kvspaceXvalueHeadNewTensor(const uint64_t *dims, uint32_t ndim, /* Shape lives in leading [dims] of langtype; body byte length is raw_len. */ if (!dims || ndim == 0 || ndim > 64 || (raw_len && !raw)) return fail(out); - return build(KVSPACE_XVALUE_ST_LARGE, KVSPACE_XVALUE_PTR_INLINE, raw, - raw_len, langtype, out, out_len); + return build(KVSPACE_XVALUE_LAYOUT_LENGTH, raw, raw_len, raw_len, 0, + langtype, out, out_len); } +/* path_len == 0 是合法的 None 指针:`*T` + 空 body。 */ int kvspaceXvalueHeadNewPtr(const uint8_t *path, uint32_t path_len, const char *langtype, uint8_t **out, uint64_t *out_len) { - if (!path_len || !path) + if (path_len && !path) return fail(out); - return build(KVSPACE_XVALUE_ST_SLACK, KVSPACE_XVALUE_PTR_PTR, path, path_len, + if (langtype && (langtype[0] == '*' || langtype[0] == '@')) + return fail(out); + return build(KVSPACE_XVALUE_LAYOUT_LENGTH, path, path_len, path_len, '*', langtype, out, out_len); } @@ -105,8 +137,10 @@ int kvspaceXvalueHeadNewExt(const uint8_t *locator, uint32_t locator_len, uint8_t **out, uint64_t *out_len) { if (!locator_len || !locator) return fail(out); - return build(KVSPACE_XVALUE_ST_EXT, KVSPACE_XVALUE_PTR_EXT, locator, - locator_len, langtype, out, out_len); + if (langtype && (langtype[0] == '*' || langtype[0] == '@')) + return fail(out); + return build(KVSPACE_XVALUE_LAYOUT_LENGTH, locator, locator_len, locator_len, + '@', langtype, out, out_len); } int kvspaceXvalueHeadNewCode(const uint8_t *body, uint64_t body_len, @@ -116,13 +150,86 @@ int kvspaceXvalueHeadNewCode(const uint8_t *body, uint64_t body_len, out_len); } -int kvspaceXvalueHeadReserve(uint8_t storetype, uint64_t body_len, - uint64_t cap, const char *langtype, +int kvspaceXvalueHeadReserve(uint64_t body_len, uint64_t cap, + const char *langtype, uint8_t **out, uint64_t *out_len) { - /* 固定头无容量字段:预留 = 产 body_len 字节零 body 的占位(后由调用方填充), - attr 区(含 langtype 记录)一并写入。 */ + /* 预留 = 产 cap 字节零 body 的占位(后由调用方填充前 body_len 字节), + attr 区(含 langtype 与 reallen)一并写入。 */ if (cap < body_len) return fail(out); - return build(storetype, KVSPACE_XVALUE_PTR_INLINE, NULL, body_len, - langtype, out, out_len); + const uint8_t layout = cap > body_len ? KVSPACE_XVALUE_LAYOUT_CAPACITY + : KVSPACE_XVALUE_LAYOUT_LENGTH; + return build(layout, NULL, cap, body_len, 0, langtype, out, out_len); +} + +int kvspaceXvalueHeadScan(const uint8_t *wire, uint64_t wire_len, + const uint8_t **out_langtype, + uint16_t *out_langtype_len, + uint64_t *out_reallen) { + if (out_langtype) + *out_langtype = NULL; + if (out_langtype_len) + *out_langtype_len = 0; + if (out_reallen) + *out_reallen = 0; + if (!wire || wire_len < KVSPACE_XVALUE_HEAD_LEN) + return -1; + const kvspaceXvalueHead *h = (const kvspaceXvalueHead *)(const void *)wire; + const uint64_t total = kvspaceXvalueHeadTotalLen(h); + if (total == 0 || total > wire_len) + return -1; + const uint8_t *p = wire + KVSPACE_XVALUE_HEAD_LEN + h->bodylen; + const uint8_t *end = p + h->attrslen; + const uint8_t *langtype = NULL; + uint16_t langtype_len = 0; + uint64_t reallen = 0; + int seen_reallen = 0; + while (p + 3 <= end) { + uint8_t tag = p[0]; + uint16_t rlen = (uint16_t)((uint16_t)p[1] | ((uint16_t)p[2] << 8)); + if (p + 3 + rlen > end) + return -1; + if (tag == KVSPACE_XVALUE_ATTR_TAG_LANGTYPE) { + if (!langtype) { /* 重复 langtype 取第一条:单 key 只有一个写方。 */ + langtype = p + 3; + langtype_len = rlen; + } + } else if (tag == KVSPACE_XVALUE_ATTR_TAG_REALLEN) { + if (h->layout != KVSPACE_XVALUE_LAYOUT_CAPACITY || seen_reallen || + rlen != 8) + return -1; + for (int i = 0; i < 8; i++) + reallen |= (uint64_t)p[3 + i] << (8 * i); + seen_reallen = 1; + } + p += 3 + rlen; + } + if (h->layout == KVSPACE_XVALUE_LAYOUT_CAPACITY && + (!seen_reallen || reallen > h->bodylen)) + return -1; + if (out_langtype) + *out_langtype = langtype; + if (out_langtype_len) + *out_langtype_len = langtype_len; + if (out_reallen) + *out_reallen = reallen; + return 0; +} + +/* 有效长度:layout=0 时是 bodylen;layout=1 时是 attr tag 2 的 reallen。 + 返回 0 成功,-1 失败(*out 置 0;0 本身是合法长度,故错误不走长度值)。 */ +int kvspaceXvalueHeadEffectiveLen(const uint8_t *wire, + uint64_t wire_len, + uint64_t *out) { + if (out) + *out = 0; + if (!out) + return -1; + uint64_t reallen = 0; + if (kvspaceXvalueHeadScan(wire, wire_len, NULL, NULL, &reallen) != 0) + return -1; + const kvspaceXvalueHead *h = (const kvspaceXvalueHead *)(const void *)wire; + *out = h->layout == KVSPACE_XVALUE_LAYOUT_CAPACITY ? reallen : h->bodylen; + return 0; } + diff --git a/tests/compare_wire_golden.sh b/tests/compare_wire_golden.sh new file mode 100755 index 0000000..3d79964 --- /dev/null +++ b/tests/compare_wire_golden.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# 比对两个 wire 金标准输出,逐字节。 +# +# tests/compare_wire_golden.sh +# +# 两者必须是**同一批操作**的产出(同一组用例、同一顺序),只有实现不同: +# kvspace 前端、kvspace-c、kvspace-durable 各跑一遍,输出必须完全一致。 +# +# 只比字节,不解释字节——解释会引入第二套「什么算合法」的判断,而这里要验的 +# 恰恰是两边对同一份 head 定义的理解一致。 +set -eu + +left_bin=$1 +right_bin=$2 + +left_out=$("$left_bin") +right_out=$("$right_bin") + +if [ "$left_out" = "$right_out" ]; then + echo "wire golden: identical ($(printf '%s\n' "$left_out" | wc -l) cases)" + exit 0 +fi + +echo "wire golden: MISMATCH" >&2 +printf '%s\n' "$left_out" > /tmp/wire-golden-left.txt +printf '%s\n' "$right_out" > /tmp/wire-golden-right.txt +diff -u /tmp/wire-golden-left.txt /tmp/wire-golden-right.txt >&2 || true +exit 1 diff --git a/tests/test_codec_abi.c b/tests/test_codec_abi.c index f36b411..43ea281 100644 --- a/tests/test_codec_abi.c +++ b/tests/test_codec_abi.c @@ -8,23 +8,29 @@ #define CHECK(x) do { if (!(x)) { fprintf(stderr, "failed: %s\n", #x); return 1; } } while (0) -/* 固定 head 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8, - attr 区 @8+len(attr_len:u16 自描述,含 langtype 记录)。单 key 整体。 */ +/* 固定 head 16 字节 [bodylen:u64@0][attrslen:u32@8][layout@12][padding@13-15], + body 偏移恒 16,attr 区紧随 body 且无自带长度前缀。单 key 整体。 */ -/* 校验 attr 区:total = 8+body_len+attr_len;langtype 记录存在且与 lt 一致。 */ +/* 校验 head 与 attr 区:head 的 bodylen/attrslen/padding 正确, + total = 16+bodylen+attrslen;langtype 记录存在且与 lt 一致。 */ static int attr_ok(const uint8_t *value, uint32_t len, uint64_t body_len, const char *langtype) { size_t lt = strlen(langtype); - const uint8_t *a = value + KVSPACE_XVALUE_HEAD_LEN + body_len; - uint64_t alen = (uint64_t)a[0] | ((uint64_t)a[1] << 8); + kvspaceXvalueHead h; + memcpy(&h, value, sizeof h); + if (kvspaceXvalueHeadLen(&h) != body_len || + !kvspaceXvalueHeadPaddingIsZero(&h)) + return 0; + const uint64_t alen = kvspaceXvalueHeadAttrsLen(&h); if ((uint64_t)len != KVSPACE_XVALUE_HEAD_LEN + body_len + alen) return 0; + const uint8_t *a = value + KVSPACE_XVALUE_HEAD_LEN + body_len; if (lt == 0) - return alen == 2; - if (alen != 2 + 3 + lt) + return alen == 0; + if (alen != 3 + lt) return 0; - return a[2] == KVSPACE_XVALUE_ATTR_TAG_LANGTYPE && a[3] == lt && a[4] == 0 && - memcmp(a + 5, langtype, lt) == 0; + return a[0] == KVSPACE_XVALUE_ATTR_TAG_LANGTYPE && a[1] == lt && a[2] == 0 && + memcmp(a + 3, langtype, lt) == 0; } int main(void) { @@ -33,9 +39,9 @@ int main(void) { kvspaceXvalueHead h; CHECK(kvspaceNewInt64(42, "int64", &value, &len) == 0); - CHECK(len == 26); + CHECK(len == 32); memcpy(&h, value, sizeof h); - CHECK(h.storetype == KVSPACE_XVALUE_ST_SHORT && h.ptr == KVSPACE_XVALUE_PTR_INLINE && + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_LENGTH && kvspaceXvalueHeadLen(&h) == 8); CHECK(attr_ok(value, len, 8, "int64")); uint64_t v = 0; @@ -45,9 +51,9 @@ int main(void) { free(value); CHECK(kvspaceNewBool(1, "bool", &value, &len) == 0); - CHECK(len == 18); + CHECK(len == 24); memcpy(&h, value, sizeof h); - CHECK(h.storetype == KVSPACE_XVALUE_ST_SHORT && + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_LENGTH && kvspaceXvalueHeadLen(&h) == 1 && value[KVSPACE_XVALUE_HEAD_LEN] == 1); CHECK(attr_ok(value, len, 1, "bool")); free(value); @@ -56,9 +62,9 @@ int main(void) { free(value); CHECK(kvspaceNewFloat64(2.5, "float64", &value, &len) == 0); - CHECK(len == 28); + CHECK(len == 34); memcpy(&h, value, sizeof h); - CHECK(h.storetype == KVSPACE_XVALUE_ST_SHORT && + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_LENGTH && kvspaceXvalueHeadLen(&h) == 8); CHECK(attr_ok(value, len, 8, "float64")); uint64_t bits = 0; @@ -71,31 +77,36 @@ int main(void) { const uint8_t utf8[] = {0xe4, 0xbd, 0xa0, 0xf0, 0x9f, 0x98, 0x80}; CHECK(kvspaceNewChar(utf8, sizeof utf8, "char/utf8", &value, &len) == 0); - CHECK(len == 8 + sizeof utf8 + 14); + CHECK(len == 16 + sizeof utf8 + 12); memcpy(&h, value, sizeof h); - CHECK(h.storetype == KVSPACE_XVALUE_ST_SLACK && - h.ptr == KVSPACE_XVALUE_PTR_INLINE && + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_LENGTH && kvspaceXvalueHeadLen(&h) == sizeof utf8); CHECK(memcmp(value + KVSPACE_XVALUE_HEAD_LEN, utf8, sizeof utf8) == 0); CHECK(attr_ok(value, len, sizeof utf8, "char/utf8")); free(value); CHECK(kvspaceNewChar(NULL, 0, "", &value, &len) == 0); - CHECK(len == 10); + CHECK(len == 16); free(value); CHECK(kvspaceNewPtr("int64", "/x", &value, &len) == 0); - CHECK(len == 20); + /* langtype 记录带 `*` 前缀:record = "*int64"(6 字节)。 */ + CHECK(len == KVSPACE_XVALUE_HEAD_LEN + 2 + 3 + 6); memcpy(&h, value, sizeof h); - CHECK(h.ptr == KVSPACE_XVALUE_PTR_PTR && h.storetype == KVSPACE_XVALUE_ST_SLACK && + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_LENGTH && kvspaceXvalueHeadLen(&h) == 2); CHECK(memcmp(value + KVSPACE_XVALUE_HEAD_LEN, "/x", 2) == 0); - CHECK(attr_ok(value, len, 2, "int64")); + CHECK(attr_ok(value, len, 2, "*int64")); free(value); /* 空 kindexpr = 不约束:Ptr 目标允许为空;`any` 不是类型名,创建即拒。 */ CHECK(kvspaceNewPtr("", "/x", &value, &len) == 0); - CHECK(len == 12); + /* 空 kindexpr ⇒ 裸 `*` 一条记录。 */ + CHECK(len == KVSPACE_XVALUE_HEAD_LEN + 2 + 3 + 1); memcpy(&h, value, sizeof h); - CHECK(h.ptr == KVSPACE_XVALUE_PTR_PTR && kvspaceXvalueHeadLen(&h) == 2); + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_LENGTH && + kvspaceXvalueHeadLen(&h) == 2); + /* **空 kindexpr 时仍写裸 `*`**:指针形态只有一个落点(langtype 前缀), + 构造器自己补——否则 runtimec 的 form 判据会在运行期判不出 POINTER。 */ + CHECK(attr_ok(value, len, 2, "*")); free(value); CHECK(kvspaceNewPtr("any", "/x", &value, &len) != 0); diff --git a/tests/test_xvalue_head.c b/tests/test_xvalue_head.c index 31a1656..f2b4b8e 100644 --- a/tests/test_xvalue_head.c +++ b/tests/test_xvalue_head.c @@ -4,8 +4,9 @@ #include #include -/* 固定 head 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8, - attr 区 @8+len(attr_len:u16 自描述,含 langtype 记录)。单 key 整体——无 attr key。 */ +/* 固定 head 16 字节 [bodylen:u64@0][attrslen:u32@8][layout@12][padding:3@13-15], + body 偏移恒 16,attr 区紧随 body 且**无**自带长度前缀(长度只在 head 里)。 + 单 key 整体——无 attr key。head 不含 storetype/ptr:引用性在 langtype 前缀。 */ static int failures = 0; #define CHECK(cond, ...) \ @@ -18,36 +19,67 @@ static int failures = 0; } \ } while (0) -/* 校验 attr 区:total = 8+body_len+attr_len;langtype 记录存在且与 lt 一致。 */ -static int attr_ok(const uint8_t *v, uint64_t total, uint64_t body_len, - const char *lt) { - size_t l = strlen(lt); - const uint8_t *a = v + KVSPACE_XVALUE_HEAD_LEN + body_len; - uint64_t alen = (uint64_t)a[0] | ((uint64_t)a[1] << 8); - if (total != KVSPACE_XVALUE_HEAD_LEN + body_len + alen) +/* 校验 head 与 attr 区:region_len 是 body 区字节数(layout=1 时为容量), + effective_len 是有效长度;total = 16+region_len+attrslen; + langtype 记录存在且与 lt 一致(lt 含 * / @ 前缀)。 */ +static int value_ok(const uint8_t *v, uint64_t total, uint8_t layout, + uint64_t region_len, uint64_t effective_len, + const char *lt) { + size_t want = strlen(lt); + kvspaceXvalueHead h; + memcpy(&h, v, sizeof h); + if (kvspaceXvalueHeadLen(&h) != region_len || + kvspaceXvalueHeadLayout(&h) != layout || + !kvspaceXvalueHeadPaddingIsZero(&h)) + return 0; + if (kvspaceXvalueHeadTotalLen(&h) != total) + return 0; + const uint64_t alen = kvspaceXvalueHeadAttrsLen(&h); + if (total != KVSPACE_XVALUE_HEAD_LEN + region_len + alen) + return 0; + uint64_t eff = UINT64_MAX; + if (kvspaceXvalueHeadEffectiveLen(v, total, &eff) != 0 || eff != effective_len) return 0; - if (l == 0) - return alen == 2; - if (alen != 2 + 3 + l) + const uint8_t *ltp = NULL; + uint16_t n = kvspaceXvalueHeadLangtypeBounded(v, total, <p); + if (n != want || (want && (!ltp || memcmp(ltp, lt, want) != 0))) return 0; - return a[2] == KVSPACE_XVALUE_ATTR_TAG_LANGTYPE && a[3] == l && a[4] == 0 && - memcmp(a + 5, lt, l) == 0; + /* 构造器把 langtype 记录放在最前;layout=1 时 tag 2 紧随其后。 */ + const uint8_t *a = v + KVSPACE_XVALUE_HEAD_LEN + region_len; + if (want) { + if (a[0] != KVSPACE_XVALUE_ATTR_TAG_LANGTYPE || a[1] != want || + a[2] != 0) + return 0; + a += 3 + want; + } + if (layout == KVSPACE_XVALUE_LAYOUT_CAPACITY) { + if (a[0] != KVSPACE_XVALUE_ATTR_TAG_REALLEN || a[1] != 8 || a[2] != 0) + return 0; + uint64_t reallen = 0; + for (int i = 0; i < 8; i++) + reallen |= (uint64_t)a[3 + i] << (8 * i); + if (reallen != effective_len) + return 0; + } + return 1; } static void test_none(void) { uint8_t *buf = NULL; uint64_t n = 0; CHECK(kvspaceXvalueHeadNewNone(&buf, &n) == 0, "none new"); - CHECK(n == 10, "none len %llu", (unsigned long long)n); + CHECK(n == 16, "none len %llu", (unsigned long long)n); kvspaceXvalueHead h; memcpy(&h, buf, sizeof h); - CHECK(h.storetype == KVSPACE_XVALUE_ST_SHORT && - h.ptr == KVSPACE_XVALUE_PTR_INLINE && kvspaceXvalueHeadLen(&h) == 0, "none head"); - /* wire 布局:len:u48@0-5、storetype@6、ptr@7、attr_len@8 */ - CHECK(buf[0] == 0 && buf[5] == 0 && - buf[6] == KVSPACE_XVALUE_ST_SHORT && buf[7] == KVSPACE_XVALUE_PTR_INLINE, + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_LENGTH && + kvspaceXvalueHeadLen(&h) == 0, "none head"); + /* wire 布局:bodylen@0、attrslen@8、layout@12、padding@13-15 全零 */ + CHECK(buf[0] == 0 && buf[7] == 0 && buf[8] == 0 && buf[11] == 0 && + buf[12] == KVSPACE_XVALUE_LAYOUT_LENGTH && buf[13] == 0 && + buf[14] == 0 && buf[15] == 0, "none wire layout"); - CHECK(attr_ok(buf, n, 0, ""), "none empty attr"); + CHECK(value_ok(buf, n, KVSPACE_XVALUE_LAYOUT_LENGTH, 0, 0, ""), + "none empty attr"); free(buf); } @@ -55,20 +87,23 @@ static void test_scalar(void) { uint8_t raw[8] = {0x0a, 0, 0, 0, 0, 0, 0, 0}; uint8_t *buf = NULL; uint64_t n = 0; - CHECK(kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, "int64", - &buf, &n) == 0, "i64 new"); - CHECK(n == 26, "i64 len %llu", (unsigned long long)n); + CHECK(kvspaceXvalueHeadNewScalar(raw, 8, "int64", &buf, &n) == 0, "i64 new"); + CHECK(n == 32, "i64 len %llu", (unsigned long long)n); kvspaceXvalueHead h; memcpy(&h, buf, sizeof h); - CHECK(kvspaceXvalueHeadLen(&h) == 8 && h.storetype == KVSPACE_XVALUE_ST_SHORT, "i64 head"); - CHECK(memcmp(buf + KVSPACE_XVALUE_HEAD_LEN, raw, 8) == 0, "i64 body@8"); - CHECK(attr_ok(buf, n, 8, "int64"), "i64 langtype attr"); + CHECK(kvspaceXvalueHeadLen(&h) == 8 && + kvspaceXvalueHeadLayout(&h) == KVSPACE_XVALUE_LAYOUT_LENGTH, + "i64 head"); + CHECK(memcmp(buf + KVSPACE_XVALUE_HEAD_LEN, raw, 8) == 0, "i64 body@16"); + CHECK(value_ok(buf, n, KVSPACE_XVALUE_LAYOUT_LENGTH, 8, 8, "int64"), + "i64 langtype attr"); free(buf); - /* 无类型(空 langtype):空 attr [attr_len:2]。 */ - CHECK(kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, "", - &buf, &n) == 0, "scalar no-langtype"); - CHECK(n == 18, "no-langtype len %llu", (unsigned long long)n); - CHECK(attr_ok(buf, n, 8, ""), "no-langtype empty attr"); + /* 无类型(空 langtype):attr 区为空,attrslen = 0。 */ + CHECK(kvspaceXvalueHeadNewScalar(raw, 8, "", &buf, &n) == 0, + "scalar no-langtype"); + CHECK(n == 24, "no-langtype len %llu", (unsigned long long)n); + CHECK(value_ok(buf, n, KVSPACE_XVALUE_LAYOUT_LENGTH, 8, 8, ""), + "no-langtype empty attr"); free(buf); } @@ -76,19 +111,40 @@ static void test_slack(void) { const uint8_t hi[] = {'h', 'i'}; uint8_t *buf = NULL; uint64_t n = 0; - CHECK(kvspaceXvalueHeadNewSlack(hi, 2, 2, "[]char/utf8", &buf, &n) == 0, "slack new"); - CHECK(n == 26, "slack len %llu", (unsigned long long)n); + CHECK(kvspaceXvalueHeadNewSlack(hi, 2, 2, "[]char/utf8", &buf, &n) == 0, + "slack new"); + CHECK(n == 32, "slack len %llu", (unsigned long long)n); kvspaceXvalueHead h; memcpy(&h, buf, sizeof h); - CHECK(h.storetype == KVSPACE_XVALUE_ST_SLACK && + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_LENGTH && kvspaceXvalueHeadLen(&h) == 2, "slack head"); - /* wire:len u48 LE@0-5=2,storetype@6,ptr@7 */ - CHECK(buf[0] == 2 && buf[1] == 0 && buf[5] == 0 && - buf[6] == KVSPACE_XVALUE_ST_SLACK && buf[7] == KVSPACE_XVALUE_PTR_INLINE, + CHECK(buf[0] == 2 && buf[1] == 0 && buf[7] == 0 && + buf[12] == KVSPACE_XVALUE_LAYOUT_LENGTH && buf[13] == 0 && + buf[14] == 0 && buf[15] == 0, "slack wire layout"); CHECK(memcmp(buf + KVSPACE_XVALUE_HEAD_LEN, hi, 2) == 0, "slack body"); - CHECK(attr_ok(buf, n, 2, "[]char/utf8"), "slack langtype attr"); + CHECK(value_ok(buf, n, KVSPACE_XVALUE_LAYOUT_LENGTH, 2, 2, "[]char/utf8"), + "slack langtype attr"); + free(buf); + + /* 容量 > 有效长度:layout=1,bodylen=容量 8,reallen=2,余量零填。 */ + CHECK(kvspaceXvalueHeadNewSlack(hi, 2, 8, "[]char/utf8", &buf, &n) == 0, + "slack capacity new"); + CHECK(n == 49, "slack capacity len %llu", (unsigned long long)n); + memcpy(&h, buf, sizeof h); + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_CAPACITY && + kvspaceXvalueHeadLen(&h) == 8, "slack capacity head"); + CHECK(buf[0] == 8 && buf[12] == KVSPACE_XVALUE_LAYOUT_CAPACITY && + buf[13] == 0 && buf[14] == 0 && buf[15] == 0, + "slack capacity wire layout"); + CHECK(memcmp(buf + KVSPACE_XVALUE_HEAD_LEN, hi, 2) == 0 && + buf[KVSPACE_XVALUE_HEAD_LEN + 2] == 0 && + buf[KVSPACE_XVALUE_HEAD_LEN + 7] == 0, + "slack capacity body + zero fill"); + CHECK(value_ok(buf, n, KVSPACE_XVALUE_LAYOUT_CAPACITY, 8, 2, "[]char/utf8"), + "slack capacity attrs"); free(buf); + CHECK(kvspaceXvalueHeadNewSlack(hi, 2, 1, "", &buf, &n) != 0, "slack cap 有效 7:layout=1,body 区 10 字节零填,reallen=7。 */ + CHECK(kvspaceXvalueHeadReserve(7, 10, "[]u8", &value, &len) == 0, + "reserve capacity"); + CHECK(len == 44, "reserve len %llu", (unsigned long long)len); kvspaceXvalueHead h; memcpy(&h, value, sizeof h); - CHECK(kvspaceXvalueHeadLen(&h) == 7, "reserve head len"); - CHECK(attr_ok(value, len, 7, "[]u8"), "reserve langtype attr"); - const uint8_t utf8[] = {0xe4, 0xbd, 0xa0, 0xf0, 0x9f, 0x98, 0x80}; - memcpy(value + KVSPACE_XVALUE_HEAD_LEN, utf8, sizeof utf8); + CHECK(h.layout == KVSPACE_XVALUE_LAYOUT_CAPACITY && + kvspaceXvalueHeadLen(&h) == 10, "reserve head"); + CHECK(value_ok(value, len, KVSPACE_XVALUE_LAYOUT_CAPACITY, 10, 7, "[]u8"), + "reserve langtype + reallen"); + for (uint64_t i = 0; i < 10; i++) + CHECK(value[KVSPACE_XVALUE_HEAD_LEN + i] == 0, "reserve body zero"); + free(value); + + /* cap == 有效:layout=0,body 区即有效长度。 */ + CHECK(kvspaceXvalueHeadReserve(7, 7, "[]u8", &value, &len) == 0, + "reserve exact"); + CHECK(value_ok(value, len, KVSPACE_XVALUE_LAYOUT_LENGTH, 7, 7, "[]u8"), + "reserve exact layout"); free(value); - CHECK(kvspaceXvalueHeadReserve(KVSPACE_XVALUE_ST_LARGE, 10, 5, "", - &value, &len) != 0, "reserve body>cap"); + + CHECK(kvspaceXvalueHeadReserve(10, 5, "", &value, &len) != 0, + "reserve body>cap"); const uint8_t call_body[] = {0, 0, 0, 0, 0, 'f'}; CHECK(kvspaceXvalueHeadNewCode(call_body, sizeof call_body, sizeof call_body, "rwir", &value, &len) == 0, "code new"); - memcpy(&h, value, sizeof h); - CHECK(h.storetype == KVSPACE_XVALUE_ST_SLACK && - kvspaceXvalueHeadLen(&h) == sizeof call_body, - "code head"); + CHECK(value_ok(value, len, KVSPACE_XVALUE_LAYOUT_LENGTH, sizeof call_body, + sizeof call_body, "rwir"), + "code langtype attr"); CHECK(memcmp(value + KVSPACE_XVALUE_HEAD_LEN, call_body, sizeof call_body) == 0, "code body"); - CHECK(attr_ok(value, len, sizeof call_body, "rwir"), "code langtype attr"); + free(value); + CHECK(kvspaceXvalueHeadNewCode(call_body, sizeof call_body, + sizeof call_body + 4, "rwir", &value, + &len) == 0, "code capacity new"); + CHECK(value_ok(value, len, KVSPACE_XVALUE_LAYOUT_CAPACITY, + sizeof call_body + 4, sizeof call_body, "rwir"), + "code capacity layout"); free(value); CHECK(kvspaceXvalueHeadNewCode(call_body, 5, 3, "", &value, &len) != 0, "code cap 容量 → 拒。 */ + uint8_t over[16 + 8 + 11] = {0}; + over[0] = 8; + over[8] = 11; + over[12] = KVSPACE_XVALUE_LAYOUT_CAPACITY; + over[16 + 8] = KVSPACE_XVALUE_ATTR_TAG_REALLEN; + over[16 + 8 + 1] = 8; + over[16 + 8 + 3] = 9; + CHECK(kvspaceXvalueHeadEffectiveLen(over, sizeof over, &eff) != 0, + "reallen > capacity rejected"); + + /* layout=1 + reallen == 0 合法(容量已分配、当前无内容)。 */ + uint8_t empty[16 + 8 + 11] = {0}; + empty[0] = 8; + empty[8] = 11; + empty[12] = KVSPACE_XVALUE_LAYOUT_CAPACITY; + empty[16 + 8] = KVSPACE_XVALUE_ATTR_TAG_REALLEN; + empty[16 + 8 + 1] = 8; + CHECK(kvspaceXvalueHeadEffectiveLen(empty, sizeof empty, &eff) == 0 && + eff == 0, + "reallen 0 legal"); +} + +/* head 单独就能算出总长:不需要先扫 body 之后的前缀。 */ +static void test_total_len_from_head_only(void) { + uint8_t *buf = NULL; + uint64_t n = 0; + CHECK(kvspaceXvalueHeadNewSlack((const uint8_t *)"xyz", 3, 3, "[]char/utf8", + &buf, &n) == 0, "total build"); + kvspaceXvalueHead h; + memcpy(&h, buf, sizeof h); + CHECK(kvspaceXvalueHeadTotalLen(&h) == n, "total len equals built length"); + CHECK(kvspaceXvalueHeadAttrsLen(&h) == 3 + 11, "attrslen excludes any prefix"); + CHECK(kvspaceXvalueHeadAttrs(buf) == buf + KVSPACE_XVALUE_HEAD_LEN + 3, + "attrs start right after body"); + free(buf); + + CHECK(kvspaceXvalueHeadNewSlack((const uint8_t *)"xyz", 3, 9, "", + &buf, &n) == 0, "total capacity build"); + memcpy(&h, buf, sizeof h); + CHECK(kvspaceXvalueHeadTotalLen(&h) == n, + "total len uses bodylen=capacity for layout=1"); + CHECK(kvspaceXvalueHeadAttrs(buf) == buf + KVSPACE_XVALUE_HEAD_LEN + 9, + "attrs start after the capacity-sized body"); + free(buf); +} + /* Failure contract and bounded langtype parsing: - All constructors return -1 and clear *out = NULL on failure; - - Reserve validates storetype via build (rejecting > KVSPACE_XVALUE_ST_EXT); - LangtypeBounded returns the exact non-NUL-terminated slice for valid values and 0 with *out = NULL on truncated/malformed inputs without reading out of bounds. */ static void test_bounded_and_reserve_fail(void) { @@ -179,50 +462,36 @@ static void test_bounded_and_reserve_fail(void) { uint64_t n = 0; const uint8_t *lt = &sentinel; - /* Reserve with invalid storetype (4, 9, 255) or cap < body_len: reject and clear *out */ - CHECK(kvspaceXvalueHeadReserve(4, 4, 4, "int32", &buf, &n) == -1 && buf == NULL, - "Reserve(storetype=4) rejected and clears *out"); - buf = &sentinel; - CHECK(kvspaceXvalueHeadReserve(9, 4, 4, "int32", &buf, &n) == -1 && buf == NULL, - "Reserve(storetype=9) rejected and clears *out"); buf = &sentinel; - CHECK(kvspaceXvalueHeadReserve(255, 0, 0, "", &buf, &n) == -1 && buf == NULL, - "Reserve(storetype=255) rejected and clears *out"); - buf = &sentinel; - CHECK(kvspaceXvalueHeadReserve(KVSPACE_XVALUE_ST_SLACK, 8, 4, "", &buf, &n) == -1 && - buf == NULL, + CHECK(kvspaceXvalueHeadReserve(8, 4, "", &buf, &n) == -1 && buf == NULL, "Reserve(cap < body_len) rejected and clears *out"); - CHECK(kvspaceXvalueHeadReserve(KVSPACE_XVALUE_ST_SLACK, 4, 4, "", NULL, &n) == -1, + CHECK(kvspaceXvalueHeadReserve(4, 4, "", NULL, &n) == -1, "Reserve(out=NULL) rejected"); buf = &sentinel; - CHECK(kvspaceXvalueHeadReserve(KVSPACE_XVALUE_ST_SLACK, 4, 4, "", &buf, NULL) == -1 && - buf == NULL, + CHECK(kvspaceXvalueHeadReserve(4, 4, "", &buf, NULL) == -1 && buf == NULL, "Reserve(out_len=NULL) rejected and clears *out"); /* Other constructors also clear *out on failure */ buf = &sentinel; - CHECK(kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, NULL, 3, "", &buf, &n) == -1 && - buf == NULL, + CHECK(kvspaceXvalueHeadNewScalar(NULL, 3, "", &buf, &n) == -1 && buf == NULL, "NewScalar(NULL, 3) clears *out"); buf = &sentinel; - CHECK(kvspaceXvalueHeadNewScalar(4, &sentinel, 1, "", &buf, &n) == -1 && buf == NULL, - "NewScalar(storetype=4) clears *out"); - buf = &sentinel; CHECK(kvspaceXvalueHeadNewSlack(NULL, 4, 4, "", &buf, &n) == -1 && buf == NULL, "NewSlack(NULL, 4) clears *out"); buf = &sentinel; - CHECK(kvspaceXvalueHeadNewTensor(NULL, 0, &sentinel, 1, "", &buf, &n) == -1 && buf == NULL, + CHECK(kvspaceXvalueHeadNewTensor(NULL, 0, &sentinel, 1, "", &buf, &n) == -1 && + buf == NULL, "NewTensor(NULL dims) clears *out"); buf = &sentinel; - CHECK(kvspaceXvalueHeadNewPtr(NULL, 0, "", &buf, &n) == -1 && buf == NULL, - "NewPtr(empty) clears *out"); + CHECK(kvspaceXvalueHeadNewPtr(NULL, 0, "", NULL, &n) == -1, + "NewPtr(out=NULL) rejected"); buf = &sentinel; CHECK(kvspaceXvalueHeadNewExt(NULL, 0, "", &buf, &n) == -1 && buf == NULL, "NewExt(empty) clears *out"); /* LangtypeBounded valid inputs: scalar / tensor / ptr / empty langtype */ uint8_t raw8[8] = {1, 0, 0, 0, 0, 0, 0, 0}; - CHECK(kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw8, 8, "int64", &buf, &n) == 0, + CHECK(kvspaceXvalueHeadNewScalar(raw8, 8, "int64", &buf, &n) == 0, "scalar build"); lt = &sentinel; CHECK(kvspaceXvalueHeadLangtypeBounded(buf, n, <) == 5 && lt != NULL && @@ -232,20 +501,20 @@ static void test_bounded_and_reserve_fail(void) { uint64_t dims[2] = {2, 3}; uint8_t raw24[24] = {0}; - CHECK(kvspaceXvalueHeadNewTensor(dims, 2, raw24, sizeof raw24, "[2,3]float32", &buf, &n) == 0, - "tensor build"); + CHECK(kvspaceXvalueHeadNewTensor(dims, 2, raw24, sizeof raw24, "[2,3]float32", + &buf, &n) == 0, "tensor build"); lt = &sentinel; CHECK(kvspaceXvalueHeadLangtypeBounded(buf, n, <) == 12 && lt != NULL && memcmp(lt, "[2,3]float32", 12) == 0, "bounded tensor langtype"); free(buf); - CHECK(kvspaceXvalueHeadNewPtr((const uint8_t *)"/dst", 4, "[]char/utf8", &buf, &n) == 0, - "ptr build"); + CHECK(kvspaceXvalueHeadNewPtr((const uint8_t *)"/dst", 4, "[]char/utf8", + &buf, &n) == 0, "ptr build"); lt = &sentinel; - CHECK(kvspaceXvalueHeadLangtypeBounded(buf, n, <) == 11 && lt != NULL && - memcmp(lt, "[]char/utf8", 11) == 0, - "bounded ptr langtype"); + CHECK(kvspaceXvalueHeadLangtypeBounded(buf, n, <) == 12 && lt != NULL && + memcmp(lt, "*[]char/utf8", 12) == 0, + "bounded ptr langtype keeps the star"); free(buf); CHECK(kvspaceXvalueHeadNewNone(&buf, &n) == 0, "none build"); @@ -265,47 +534,52 @@ static void test_bounded_and_reserve_fail(void) { "bounded wire_len=0"); lt = &sentinel; CHECK(kvspaceXvalueHeadLangtypeBounded(short_wire, 9, <) == 0 && lt == NULL, - "bounded wire_len=9 (< 10)"); + "bounded wire_len=9 (< 16)"); - /* body_len > wire_len - 10 (legacy wire without attr tail or truncated body) */ - CHECK(kvspaceXvalueHeadReserve(KVSPACE_XVALUE_ST_SLACK, 4, 4, "int32", &buf, &n) == 0, + /* 截断的 wire:head 说 body+attrs 比实际字节多(head 自己就必须能挡住)。 */ + CHECK(kvspaceXvalueHeadReserve(4, 4, "int32", &buf, &n) == 0, "reserve for truncation checks"); lt = &sentinel; CHECK(kvspaceXvalueHeadLangtypeBounded(buf, KVSPACE_XVALUE_HEAD_LEN + 4, <) == 0 && lt == NULL, - "bounded rejects wire without 2-byte attr_len (body_len > wire_len - 10)"); + "bounded rejects wire cut before the attr region"); lt = &sentinel; CHECK(kvspaceXvalueHeadLangtypeBounded(buf, KVSPACE_XVALUE_HEAD_LEN + 4 + 1, <) == 0 && lt == NULL, - "bounded rejects 1-byte truncated attr_len"); - /* attr_len exceeds remaining room (truncate last byte) */ + "bounded rejects wire cut one byte into the attr region"); + /* attrslen 超出剩余字节(掐掉最后一个字节) */ lt = &sentinel; CHECK(kvspaceXvalueHeadLangtypeBounded(buf, n - 1, <) == 0 && lt == NULL, - "bounded rejects attr_len exceeding remaining room"); + "bounded rejects attrslen exceeding the remaining bytes"); free(buf); - /* attr_len < 2 */ - uint8_t bad_alen[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0}; + /* attrslen 放不下一个 record 头(< 3):无可解析记录 → 无 langtype。 */ + uint8_t bad_alen[16 + 1] = {0}; + bad_alen[8] = 1; lt = &sentinel; - CHECK(kvspaceXvalueHeadLangtypeBounded(bad_alen, sizeof bad_alen, <) == 0 && lt == NULL, - "bounded rejects attr_len < 2"); + CHECK(kvspaceXvalueHeadLangtypeBounded(bad_alen, sizeof bad_alen, <) == 0 && + lt == NULL, + "bounded tolerates trailing bytes shorter than a record header"); - /* Truncated record: attr_len fits in wire_len, but p + 3 + rlen > end */ - uint8_t trunc_rec[15] = { - 0, 0, 0, 0, 0, 0, 0, 0, /* head: body_len=0 */ - 7, 0, /* attr_len = 7 (end = 15) */ + /* Truncated record: attrslen fits in wire_len, but p + 3 + rlen > end */ + uint8_t trunc_rec[16 + 7] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* head: bodylen=0 */ + 7, 0, 0, 0, /* attrslen=7 (end = 16+7) */ + 0, 0, 0, 0, /* layout/padding */ KVSPACE_XVALUE_ATTR_TAG_LANGTYPE, /* tag = 1 */ - 10, 0, /* rlen = 10 -> p+3+10 = 23 > 15 */ + 10, 0, /* rlen = 10 -> p+3+10 = 29 > 23 */ 'i', '6' }; lt = &sentinel; - CHECK(kvspaceXvalueHeadLangtypeBounded(trunc_rec, sizeof trunc_rec, <) == 0 && lt == NULL, + CHECK(kvspaceXvalueHeadLangtypeBounded(trunc_rec, sizeof trunc_rec, <) == 0 && + lt == NULL, "bounded rejects truncated attr record (p + 3 + rlen > end)"); - /* Trailing record header shorter than 3 bytes (alen = 4 leaves 2 bytes) */ - uint8_t short_rec_hdr[12] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 4, 0, + /* Trailing record header shorter than 3 bytes (attrslen = 2 leaves 2 bytes) */ + uint8_t short_rec_hdr[16 + 2] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* bodylen=0 */ + 2, 0, 0, 0, /* attrslen=2 -> p+3 > end */ + 0, 0, 0, 0, KVSPACE_XVALUE_ATTR_TAG_LANGTYPE, 0 }; lt = &sentinel; @@ -314,13 +588,15 @@ static void test_bounded_and_reserve_fail(void) { "bounded rejects <3B trailing record header"); /* Non-NUL-terminated bounded slice after skipping an unknown tag: - record 1 is tag=2 (reserved), record 2 is tag=1 (langtype="int64") immediately - followed by non-NUL byte 9 (next record tag); verify exact rlen=5 slice. */ - uint8_t multi_non_nul[8 + 0 + 2 + (3 + 2) + (3 + 5) + (3 + 1)] = { - 0, 0, 0, 0, 0, 0, 0, 0, /* head: body_len=0 */ - 19, 0, /* attr_len = 2 + 5 + 8 + 4 = 19 */ - 2, 2, 0, 0xaa, 0xbb, /* tag=2, rlen=2 */ - KVSPACE_XVALUE_ATTR_TAG_LANGTYPE, 5, 0, 'i', 'n', 't', '6', '4', /* tag=1, rlen=5 (followed by non-zero) */ + record 1 is tag=9 (unknown), record 2 is tag=1 (langtype="int64") + immediately followed by non-NUL byte 9 (next record tag); verify exact + rlen=5 slice. */ + uint8_t multi_non_nul[16 + (3 + 2) + (3 + 5) + (3 + 1)] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* bodylen=0 */ + 17, 0, 0, 0, /* attrslen = 5 + 8 + 4 = 17 */ + 0, 0, 0, 0, + 9, 2, 0, 0xaa, 0xbb, /* tag=9 (unknown), rlen=2 */ + KVSPACE_XVALUE_ATTR_TAG_LANGTYPE, 5, 0, 'i', 'n', 't', '6', '4', /* tag=1, rlen=5 */ 9, 1, 0, 'Z' /* tag=9, rlen=1 -> lt[5] == 9 != '\0' */ }; lt = &sentinel; @@ -330,6 +606,10 @@ static void test_bounded_and_reserve_fail(void) { } int main(void) { + test_nonzero_padding_and_layout(); + test_old_format_rejected(); + test_reallen_rules(); + test_total_len_from_head_only(); test_none(); test_scalar(); test_slack(); diff --git a/tests/xvalue_wire_golden.c b/tests/xvalue_wire_golden.c new file mode 100644 index 0000000..cac9df6 --- /dev/null +++ b/tests/xvalue_wire_golden.c @@ -0,0 +1,174 @@ +/* + * XValue wire 金标准:把「同一批操作」产出的字节逐字节打印出来,供跨仓库比对。 + * + * 用它验证的是一条 ABI 事实,不是本仓库的实现自证:kvspace(前端)、kvspace-c、 + * kvspace-durable 各自按同一份 head 定义构造同一组值,输出的十六进制行必须完全 + * 相同。比对由 tests/compare_wire_golden.sh 完成——它只比字节,不解释字节。 + * + * 输出格式:每行 ` `,顺序固定,便于 diff。 + */ + +#include + +#include +#include +#include + +static int failures; + +#define REQUIRE(condition, name) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "golden: %s failed\n", (name)); \ + ++failures; \ + } \ + } while (0) + +static void emit(const char *name, const uint8_t *wire, uint64_t length) { + printf("%s %llu ", name, (unsigned long long)length); + for (uint64_t index = 0; index < length; ++index) { + printf("%02x", wire[index]); + } + printf("\n"); +} + +/* 每个用例:构造 → 打印 → 释放。空值也要打印,空也是有形的字节。 */ +static void emit_none(void) { + uint8_t *wire = NULL; + uint64_t length = 0; + + REQUIRE(kvspaceXvalueHeadNewNone(&wire, &length) == 0, "none"); + if (wire != NULL) { + emit("none", wire, length); + free(wire); + } +} + +static void emit_scalar_int64(void) { + const uint8_t body[8] = {0x2a, 0, 0, 0, 0, 0, 0, 0}; + uint8_t *wire = NULL; + uint64_t length = 0; + + REQUIRE(kvspaceXvalueHeadNewScalar(body, 8, + "int64", &wire, &length) == 0, + "int64"); + if (wire != NULL) { + emit("scalar-int64", wire, length); + free(wire); + } +} + +static void emit_scalar_bool(void) { + const uint8_t body[1] = {1}; + uint8_t *wire = NULL; + uint64_t length = 0; + + REQUIRE(kvspaceXvalueHeadNewScalar(body, 1, "bool", + &wire, &length) == 0, + "bool"); + if (wire != NULL) { + emit("scalar-bool", wire, length); + free(wire); + } +} + +static void emit_slack_utf8(void) { + const uint8_t body[7] = {0xe4, 0xbd, 0xa0, 0xf0, 0x9f, 0x98, 0x80}; + uint8_t *wire = NULL; + uint64_t length = 0; + + REQUIRE(kvspaceXvalueHeadNewSlack(body, sizeof body, sizeof body, + "[]char/utf8", &wire, &length) == 0, + "utf8"); + if (wire != NULL) { + emit("slack-utf8", wire, length); + free(wire); + } +} + +static void emit_slack_no_langtype(void) { + const uint8_t body[3] = {'a', 'b', 'c'}; + uint8_t *wire = NULL; + uint64_t length = 0; + + REQUIRE(kvspaceXvalueHeadNewSlack(body, sizeof body, sizeof body, "", &wire, + &length) == 0, + "no-langtype"); + if (wire != NULL) { + emit("slack-no-langtype", wire, length); + free(wire); + } +} + +static void emit_tensor(void) { + const uint64_t dims[2] = {2, 3}; + uint8_t body[24]; + uint8_t *wire = NULL; + uint64_t length = 0; + + memset(body, 0xab, sizeof body); + REQUIRE(kvspaceXvalueHeadNewTensor(dims, 2, body, sizeof body, + "[2,3]float32", &wire, &length) == 0, + "tensor"); + if (wire != NULL) { + emit("tensor", wire, length); + free(wire); + } +} + +static void emit_ptr(void) { + uint8_t *wire = NULL; + uint64_t length = 0; + + REQUIRE(kvspaceXvalueHeadNewPtr((const uint8_t *)"/lib/f", 6, "[]char/utf8", + &wire, &length) == 0, + "ptr"); + if (wire != NULL) { + emit("ptr", wire, length); + free(wire); + } +} + +static void emit_ext(void) { + uint8_t *wire = NULL; + uint64_t length = 0; + + REQUIRE(kvspaceXvalueHeadNewExt((const uint8_t *)"s3://b/k", 8, "", &wire, + &length) == 0, + "ext"); + if (wire != NULL) { + emit("ext", wire, length); + free(wire); + } +} + +static void emit_reserve(void) { + uint8_t *wire = NULL; + uint64_t length = 0; + + REQUIRE(kvspaceXvalueHeadReserve(4, 8, "[]u8", + &wire, &length) == 0, + "reserve"); + if (wire != NULL) { + emit("reserve", wire, length); + free(wire); + } +} + +int main(void) { + emit_none(); + emit_scalar_int64(); + emit_scalar_bool(); + emit_slack_utf8(); + emit_slack_no_langtype(); + emit_tensor(); + emit_ptr(); + emit_ext(); + emit_reserve(); + if (failures != 0) { + fprintf(stderr, "xvalue_wire_golden: %d construction failure(s)\n", + failures); + return 1; + } + return 0; +} diff --git a/tutorial-c/02_cpp.cpp b/tutorial-c/02_cpp.cpp index 31dd3db..8806a5a 100644 --- a/tutorial-c/02_cpp.cpp +++ b/tutorial-c/02_cpp.cpp @@ -32,31 +32,52 @@ struct KV { ~KV() { kvspaceClose(kv); ::unlink(path.c_str()); } // Write into returned body pointer; WriteNewPlace writes langtype with the header/attr region. + // body 长度用 `kvspaceXvalueHeadEffectiveLen` 取(layout=1 时有效长度在 attr tag 2, + // 不是 bodylen);`ref` 由 langtype 的 `*`/`@` 前缀隐含,调用方不再单独给 head 位。 void set(const char *key, const char *langtype, const uint8_t *val, uint32_t len) { if (!val || len < KVSPACE_XVALUE_HEAD_LEN) return; - kvspaceXvalueHead hd; - memcpy(&hd, val, sizeof hd); - uint64_t bl = kvspaceXvalueHeadLen(&hd); + uint64_t effective = 0; + if (kvspaceXvalueHeadEffectiveLen(val, len, &effective) != 0) return; + if (effective > UINT32_MAX) return; + const uint32_t bl = (uint32_t)effective; + const uint8_t ref = (langtype[0] == '*') ? 1U + : (langtype[0] == '@') ? 2U + : 0U; uint8_t *dst = nullptr; char err[256]; - if (kvspaceWriteInPlace(kv, key, 1, (uint32_t)bl, &dst, err, sizeof(err)) != 0) - kvspaceWriteNewPlace(kv, key, hd.ptr, hd.storetype, (uint32_t)bl, bl, - langtype, &dst, err, sizeof(err)); + if (kvspaceWriteInPlace(kv, key, 1, bl, &dst, err, sizeof(err)) != 0) + kvspaceWriteNewPlace(kv, key, ref, 0U, bl, bl, langtype, &dst, err, + sizeof(err)); if (bl && dst) memcpy(dst, val + KVSPACE_XVALUE_HEAD_LEN, (size_t)bl); } - // Borrowed read: kind is decoded by kvspaceGetHead from inline attr; body length is head.body_len. + // Borrowed read: kind 与 body 长度都从**同一条 wire**解出,不经第二套头结构。 + // `kvspaceGet` 拿整条 wire;`xvalue_head.h` 的 inline 访问器读它。 bool get(const char *key, std::string &kind, std::string &raw) { uint8_t *out = nullptr; uint32_t len = 0; kvspaceGet(kv, key, 0, &out, &len); if (!out || len < KVSPACE_XVALUE_HEAD_LEN) return false; - kvspaceHead_t h; - if (kvspaceGetHead(kv, key, &h) != 0) return false; - const char *k = (const char *)h.langtype; - if (k[0] == '*' || k[0] == '@') k++; - if (k[0] == '[') { const char *e = strchr(k, ']'); if (e) k = e + 1; } - kind = k; - uint32_t bl = h.body_len > 0 ? (uint32_t)h.body_len : 0; - if (bl > len - KVSPACE_XVALUE_HEAD_LEN) return false; - raw.assign((const char *)(out + KVSPACE_XVALUE_HEAD_LEN), bl); + const uint8_t *lt = nullptr; + uint16_t lt_len = 0; + uint64_t effective = 0; + if (kvspaceXvalueHeadScan(out, len, <, <_len, nullptr) != 0) return false; + if (kvspaceXvalueHeadEffectiveLen(out, len, &effective) != 0) return false; + const char *k = (const char *)(const void *)lt; + uint16_t k_len = lt_len; + if (k != nullptr && k_len != 0) { + // 去掉 `*`/`@` 前缀与 `[dims]` 形状段,只留基种类。 + if (k[0] == '*' || k[0] == '@') { k++; k_len--; } + if (k_len != 0 && k[0] == '[') { + const char *close = (const char *)memchr(k, ']', k_len); + if (close != nullptr) { + const size_t skip = (size_t)(close - k) + 1U; + k += skip; + k_len = (uint16_t)(k_len - skip); + } + } + } + kind.assign(k != nullptr ? k : "", k_len); + if (effective > len - KVSPACE_XVALUE_HEAD_LEN) return false; + raw.assign((const char *)(out + KVSPACE_XVALUE_HEAD_LEN), + (size_t)effective); return true; } void del(const char *key) { diff --git a/tutorial-c/03_python.py b/tutorial-c/03_python.py index e3cf788..53d354c 100644 --- a/tutorial-c/03_python.py +++ b/tutorial-c/03_python.py @@ -14,39 +14,52 @@ U64P = ctypes.POINTER(ctypes.c_uint64) CCHARPP = ctypes.POINTER(ctypes.c_char_p) -KVSPACE_XVALUE_HEAD_LEN = 8 - - -# Matches kvspaceHead_t (langtype populated by kvspaceGetHead from inline attr). -class Head(ctypes.Structure): - _fields_ = [ - ("headlen", ctypes.c_uint16), - ("ref", ctypes.c_uint8), - ("storetype", ctypes.c_uint8), - ("body_len", ctypes.c_int32), - ("ndim", ctypes.c_int32), - ("dims", ctypes.c_int32 * 8), - ("langtype", ctypes.c_char * 256), - ("langtype_len", ctypes.c_int32), - ("body_offset", ctypes.c_int32), - ("body_cap", ctypes.c_uint64), - ] - - -# 固定 8 字节 head:[len:u48 LE@0-5][storetype@6][ptr@7]。 -class XvalueHead(ctypes.Structure): - _fields_ = [ - ("len", ctypes.c_uint8 * 6), - ("storetype", ctypes.c_uint8), - ("ptr", ctypes.c_uint8), - ] - - -def xvalue_head_len(h): - v = 0 - for i in reversed(range(6)): - v = (v << 8) | h.len[i] - return v +KVSPACE_XVALUE_HEAD_LEN = 16 +KVSPACE_XVALUE_LAYOUT_CAPACITY = 1 +KVSPACE_XVALUE_ATTR_TAG_LANGTYPE = 1 +KVSPACE_XVALUE_ATTR_TAG_REALLEN = 2 + + +def xvalue_head_scan(wire): + """扫描 16 字节 head + attr 区,返回 (langtype_bytes, reallen)。 + + 与 C 的 `kvspaceXvalueHeadScan` 同语义;Python 侧没有 inline 头可用, + 这里按线格式直接解,**不复制 C 的结构体字段**(那是第二份字段真相)。 + wire: [bodylen:u64][attrslen:u32][layout:u8][padding:3][body][attrs] + """ + if len(wire) < KVSPACE_XVALUE_HEAD_LEN: + return b"", None + bodylen = int.from_bytes(wire[0:8], "little") + attrslen = int.from_bytes(wire[8:12], "little") + if wire[13:16] != b"\0\0\0": + return b"", None + if KVSPACE_XVALUE_HEAD_LEN + bodylen + attrslen > len(wire): + return b"", None + base = KVSPACE_XVALUE_HEAD_LEN + bodylen + langtype, reallen, p, left = b"", None, base, attrslen + while left >= 3: + tag = wire[p] + rlen = int.from_bytes(wire[p + 1:p + 3], "little") + if left < 3 + rlen: + return b"", None + if tag == KVSPACE_XVALUE_ATTR_TAG_LANGTYPE: + langtype = bytes(wire[p + 3:p + 3 + rlen]) + elif tag == KVSPACE_XVALUE_ATTR_TAG_REALLEN: + reallen = int.from_bytes(wire[p + 3:p + 3 + rlen], "little") + p += 3 + rlen + left -= 3 + rlen + return langtype, reallen + + +def xvalue_head_effective_len(wire): + """有效长度:layout=0 是 bodylen;layout=1 是 attr tag 2 的 reallen。""" + if len(wire) < KVSPACE_XVALUE_HEAD_LEN: + return None + bodylen = int.from_bytes(wire[0:8], "little") + if wire[12] != KVSPACE_XVALUE_LAYOUT_CAPACITY: + return bodylen + _, reallen = xvalue_head_scan(wire) + return reallen def bind(fn, args, restype): @@ -58,7 +71,6 @@ def bind(fn, args, restype): bind(lib.kvspaceClose, [ctypes.c_void_p], None) # 借用读:out 指向 kvspace 常驻空间,string_at 拷出后不 free。resolve 穿透 link。 bind(lib.kvspaceGet, [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(U8P), U32P], ctypes.c_int) -bind(lib.kvspaceGetHead, [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(Head)], ctypes.c_int) # 写即构造:就地/新位置二选一原语,往返回的 body 偏移指针写字节。 bind(lib.kvspaceWriteInPlace, [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_uint32, ctypes.POINTER(U8P), ctypes.c_char_p, ctypes.c_uint32], ctypes.c_int) # Signature matches kvspace.h: (h, key, ref, storetype, body_len, body_cap, langtype, body, err, err_cap). @@ -98,17 +110,18 @@ def new_str(s): return data -# Read kind via kvspaceGetHead (from inline attr); body starts at offset 8 with length h.body_len. +# kind 与 body 长度都从**同一条 wire**解出(`kvspaceGet` 的返回值), +# 不再调已删除的 kvspaceGetHead,也不维护第二份 head 结构体。 def read_kind(kv, key, data): - h = Head() - if lib.kvspaceGetHead(kv, key.encode(), ctypes.byref(h)) != 0: + if not data: return "", b"" - kx = bytes(h.langtype).split(b"\0", 1)[0].decode() + langtype, _ = xvalue_head_scan(data) + kx = langtype.split(b"\0", 1)[0].decode() if kx.startswith(("*", "@")): kx = kx[1:] if kx.startswith("["): kx = kx[kx.index("]") + 1:] - bl = h.body_len if h.body_len > 0 else 0 + bl = xvalue_head_effective_len(data) or 0 return kx, data[KVSPACE_XVALUE_HEAD_LEN:KVSPACE_XVALUE_HEAD_LEN + bl] @@ -140,14 +153,16 @@ def __exit__(self, *a): def set(self, key, langtype, val): if len(val) < KVSPACE_XVALUE_HEAD_LEN: return + bl = xvalue_head_effective_len(val) + if bl is None: + raise RuntimeError(f"write {key}: value head is not well-formed") + # ref 由 langtype 的 * / @ 前缀隐含,不再从 head 位取。 + ref = 1 if langtype.startswith("*") else (2 if langtype.startswith("@") else 0) buf = (ctypes.c_uint8 * len(val)).from_buffer_copy(val) - hd = XvalueHead() - ctypes.memmove(ctypes.byref(hd), buf, ctypes.sizeof(hd)) - bl = xvalue_head_len(hd) dst = U8P() err = ctypes.create_string_buffer(256) if lib.kvspaceWriteInPlace(self.kv, key.encode(), 1, bl, ctypes.byref(dst), err, 256) != 0: - if lib.kvspaceWriteNewPlace(self.kv, key.encode(), hd.ptr, hd.storetype, + if lib.kvspaceWriteNewPlace(self.kv, key.encode(), ref, 0, bl, bl, langtype.encode(), ctypes.byref(dst), err, 256) != 0: raise RuntimeError(f"write {key}: {err.value.decode(errors='replace')}") if bl > 0 and dst: diff --git a/tutorial-c/04_rust.rs b/tutorial-c/04_rust.rs index 6248d7e..cd1cc47 100644 --- a/tutorial-c/04_rust.rs +++ b/tutorial-c/04_rust.rs @@ -2,43 +2,66 @@ // #571: Fixed 8-byte header (body offset is always 8); langtype in inline attr region. // 编译: rustc 04_rust.rs -l kvspace +use std::convert::TryInto; use std::ffi::{c_char, c_int, c_void, CString}; use std::ptr; -// Matches kvspaceHead_t (langtype populated by kvspaceGetHead from inline attr). -#[repr(C)] -struct Head { - headlen: u16, - r#ref: u8, - storetype: u8, - body_len: i32, - ndim: i32, - dims: [i32; 8], - langtype: [u8; 256], - langtype_len: i32, - body_offset: i32, - body_cap: u64, -} - -impl Default for Head { - fn default() -> Self { - Head { headlen: 0, r#ref: 0, storetype: 0, body_len: 0, ndim: 0, dims: [0; 8], - langtype: [0; 256], langtype_len: 0, body_offset: 0, body_cap: 0 } +const KVSPACE_XVALUE_HEAD_LEN: usize = 16; +const KVSPACE_XVALUE_LAYOUT_CAPACITY: u8 = 1; +const KVSPACE_XVALUE_ATTR_TAG_LANGTYPE: u8 = 1; +const KVSPACE_XVALUE_ATTR_TAG_REALLEN: u8 = 2; + +/// 扫描 16 字节 head + attr 区,返回 (langtype, reallen)。 +/// 与 C 的 `kvspaceXvalueHeadScan` 同语义;Rust 侧没有 inline 头可用, +/// 这里按线格式直接解,**不复制 C 的结构体字段**(那是第二份字段真相)。 +/// wire: [bodylen:u64][attrslen:u32][layout:u8][padding:3][body][attrs] +fn xvalue_head_scan(wire: &[u8]) -> (Vec, Option) { + if wire.len() < KVSPACE_XVALUE_HEAD_LEN { + return (vec![], None); } + let bodylen = u64::from_le_bytes(wire[0..8].try_into().unwrap()) as usize; + let attrslen = u32::from_le_bytes(wire[8..12].try_into().unwrap()) as usize; + if wire[13..16] != [0u8, 0, 0] { + return (vec![], None); + } + if KVSPACE_XVALUE_HEAD_LEN + bodylen + attrslen > wire.len() { + return (vec![], None); + } + let mut p = KVSPACE_XVALUE_HEAD_LEN + bodylen; + let mut left = attrslen; + let (mut langtype, mut reallen) = (vec![], None); + while left >= 3 { + let tag = wire[p]; + let rlen = u16::from_le_bytes([wire[p + 1], wire[p + 2]]) as usize; + if left < 3 + rlen { + return (vec![], None); + } + match tag { + KVSPACE_XVALUE_ATTR_TAG_LANGTYPE => langtype = wire[p + 3..p + 3 + rlen].to_vec(), + KVSPACE_XVALUE_ATTR_TAG_REALLEN => { + if rlen == 8 { + reallen = Some(u64::from_le_bytes( + wire[p + 3..p + 11].try_into().unwrap())); + } + } + _ => {} + } + p += 3 + rlen; + left -= 3 + rlen; + } + (langtype, reallen) } -// 固定 8 字节 head:[len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8。 -#[repr(C)] -struct XvalueHead { - len: [u8; 6], - storetype: u8, - ptr: u8, -} - -fn xvalue_head_len(h: &XvalueHead) -> u64 { - let mut v: u64 = 0; - for i in (0..6).rev() { v = (v << 8) | h.len[i] as u64; } - v +/// 有效长度:layout=0 是 bodylen;layout=1 是 attr tag 2 的 reallen。 +fn xvalue_head_effective_len(wire: &[u8]) -> Option { + if wire.len() < KVSPACE_XVALUE_HEAD_LEN { + return None; + } + let bodylen = u64::from_le_bytes(wire[0..8].try_into().unwrap()); + if wire[12] != KVSPACE_XVALUE_LAYOUT_CAPACITY { + return Some(bodylen); + } + xvalue_head_scan(wire).1 } extern "C" { @@ -49,7 +72,6 @@ extern "C" { // 借用读:out 指向 kvspace 常驻空间,调用方不得 free。resolve=1 穿透 link。 fn kvspaceGet(h: *mut c_void, key: *const c_char, resolve: c_int, out: *mut *mut u8, out_len: *mut u32) -> c_int; - fn kvspaceGetHead(h: *mut c_void, key: *const c_char, out: *mut Head) -> c_int; // 就地写:key 已存在且 body_len 不变 → 返回原 box body 偏移指针;否则非 0。 fn kvspaceWriteInPlace(h: *mut c_void, key: *const c_char, resolve: c_int, body_len: u32, body: *mut *mut u8, err: *mut c_char, err_cap: u32) -> c_int; @@ -94,19 +116,17 @@ fn new_str(s: &str) -> Vec { r } -// Read kind via kvspaceGetHead (from inline attr); body starts at offset 8 with length h.body_len. -fn read_kind(kv: *mut c_void, key: &str, data: &[u8]) -> (String, Vec) { - let mut h = Head::default(); - if unsafe { kvspaceGetHead(kv, cs(key).as_ptr(), &mut h) } != 0 { - return (String::new(), vec![]); - } - let klen = h.langtype.iter().position(|&b| b == 0).unwrap_or(256); - let mut kx = String::from_utf8_lossy(&h.langtype[..klen]).into_owned(); +// kind 与 body 长度都从**同一条 wire**解出(kvspaceGet 的返回值), +// 不再调已删除的 kvspaceGetHead,也不维护第二份 head 结构体。 +fn read_kind(_kv: *mut c_void, _key: &str, data: &[u8]) -> (String, Vec) { + let (langtype, _) = xvalue_head_scan(data); + let klen = langtype.iter().position(|&b| b == 0).unwrap_or(langtype.len()); + let mut kx = String::from_utf8_lossy(&langtype[..klen]).into_owned(); if kx.starts_with('*') || kx.starts_with('@') { kx.drain(..1); } if let Some(i) = kx.find(']') { kx.drain(..=i); } - let bl = h.body_len.max(0) as usize; - let end = (8 + bl).min(data.len()); - (kx, data[8..end].to_vec()) + let bl = xvalue_head_effective_len(data).unwrap_or(0) as usize; + let end = (KVSPACE_XVALUE_HEAD_LEN + bl).min(data.len()); + (kx, data[KVSPACE_XVALUE_HEAD_LEN..end].to_vec()) } struct KV { ptr: *mut c_void, path: String } @@ -121,22 +141,25 @@ impl KV { } // Write into returned body pointer; WriteNewPlace writes langtype with the header/attr region. fn set(&self, key: &str, langtype: &str, val: &[u8]) { - if val.len() < 8 { return; } - let hd: XvalueHead = unsafe { std::ptr::read(val.as_ptr() as *const XvalueHead) }; - let bl = xvalue_head_len(&hd) as u32; + if val.len() < KVSPACE_XVALUE_HEAD_LEN { return; } + let bl = xvalue_head_effective_len(val).expect("value head is not well-formed") as u32; + // ref 由 langtype 的 * / @ 前缀隐含,不再从 head 位取。 + let r#ref: u8 = if langtype.starts_with('*') { 1 } + else if langtype.starts_with('@') { 2 } else { 0 }; let ck = cs(key); let lt = cs(langtype); let mut dst: *mut u8 = ptr::null_mut(); let mut err = [0i8; 256]; unsafe { if kvspaceWriteInPlace(self.ptr, ck.as_ptr(), 1, bl, &mut dst, err.as_mut_ptr(), 256) != 0 { - let rc = kvspaceWriteNewPlace(self.ptr, ck.as_ptr(), hd.ptr, hd.storetype, + let rc = kvspaceWriteNewPlace(self.ptr, ck.as_ptr(), r#ref, 0, bl, bl as u64, lt.as_ptr(), &mut dst, err.as_mut_ptr(), 256); assert_eq!(rc, 0, "write {key} failed"); } if bl > 0 && !dst.is_null() { - ptr::copy_nonoverlapping(val[8..].as_ptr(), dst, bl as usize); + ptr::copy_nonoverlapping( + val[KVSPACE_XVALUE_HEAD_LEN..].as_ptr(), dst, bl as usize); } } } diff --git a/tutorial-c/common.h b/tutorial-c/common.h index c5d529e..fd9d3c4 100644 --- a/tutorial-c/common.h +++ b/tutorial-c/common.h @@ -42,9 +42,9 @@ static inline const char *read_kind(const uint8_t *data, uint32_t len, if (cap) kind[0] = 0; *body = NULL; *body_len = 0; if (!data || len < KVSPACE_XVALUE_HEAD_LEN || cap == 0) return NULL; - kvspaceXvalueHead hd; - memcpy(&hd, data, sizeof hd); - uint64_t bl = kvspaceXvalueHeadLen(&hd); + /* 有效长度走 EffectiveLen:layout=1 时 bodylen 是容量,不是有效长度。 */ + uint64_t bl = 0; + if (kvspaceXvalueHeadEffectiveLen(data, len, &bl) != 0) return NULL; if (bl > len - KVSPACE_XVALUE_HEAD_LEN) return NULL; const uint8_t *lt = NULL; size_t n = kvspaceXvalueHeadLangtypeBounded(data, len, <); @@ -63,12 +63,14 @@ static inline const char *read_kind(const uint8_t *data, uint32_t len, static inline int kv_set(void *h, const char *key, const char *langtype, const uint8_t *val, uint32_t len) { if (!val || len < KVSPACE_XVALUE_HEAD_LEN) return -1; - kvspaceXvalueHead hd; - memcpy(&hd, val, sizeof hd); - uint64_t bl = kvspaceXvalueHeadLen(&hd); + uint64_t bl = 0; + if (kvspaceXvalueHeadEffectiveLen(val, len, &bl) != 0) return -1; + if (bl > UINT32_MAX) return -1; + /* ref 由 langtype 的 * / @ 前缀隐含;storetype 在当前模型不承载信息。 */ + uint8_t ref = langtype[0] == '*' ? 1U : (langtype[0] == '@' ? 2U : 0U); uint8_t *dst = NULL; char err[256]; if (kvspaceWriteInPlace(h, key, 1, (uint32_t)bl, &dst, err, sizeof err) != 0 && - kvspaceWriteNewPlace(h, key, hd.ptr, hd.storetype, (uint32_t)bl, bl, + kvspaceWriteNewPlace(h, key, ref, 0U, (uint32_t)bl, bl, langtype, &dst, err, sizeof err) != 0) return -1; if (bl && dst) memcpy(dst, val + KVSPACE_XVALUE_HEAD_LEN, (size_t)bl); diff --git a/tutorial-durable/14-head-perm.sh b/tutorial-durable/14-head-perm.sh deleted file mode 100644 index 3230613..0000000 --- a/tutorial-durable/14-head-perm.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# expected: -# === scalar (ref=0, storetype=0) === -# /t14/a int64 ref=0 storetype=0 ndim=0 dims=[] -# === slack string (ref=0, storetype=1) === -# /t14/c char/utf8 ref=0 storetype=1 ndim=0 dims=[] -# === typed ptr (ref=1, storetype=1) === -# /t14/d int64 ref=1 storetype=1 ndim=0 dims=[] -# === untyped ptr (ref=1, storetype=1) === -# /t14/e ref=1 storetype=1 ndim=0 dims=[] -# /end - -set -e -KV="${KVSPACE_BIN:-kvspace}" -$KV deltree /t14/ - -echo "=== scalar (ref=0, storetype=0) ===" -$KV set /t14/a int:42 -$KV head /t14/a - -echo "=== slack string (ref=0, storetype=1) ===" -$KV set /t14/c string:hello -$KV head /t14/c - -echo "=== typed ptr (ref=1, storetype=1) ===" -$KV set /t14/d '*:int64:/t14/a' -$KV head /t14/d - -echo "=== untyped ptr (ref=1, storetype=1) ===" -$KV set /t14/e '*:/t14/a' -$KV head /t14/e diff --git a/tutorial-durable/test.py b/tutorial-durable/test.py index cb8875f..57ee2bf 100644 --- a/tutorial-durable/test.py +++ b/tutorial-durable/test.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """Run tutorial-durable/*.sh and compare against expected header comments; -cross-check XValue constructors (New*) and header decoding (GetHead) between -kvspace-c and kvspace-durable; and verify write flush on next op and exit (issue #23).""" +and verify write flush on next op and exit (issue #23).""" import ctypes import os import shutil -import struct import subprocess import sys import tempfile @@ -28,15 +26,6 @@ def find_first(env_key, candidates): return Path(candidates[0]) -DURABLE_SO = find_first("KVSPACE_DURABLE_SO", [ - WS / "kvspace-durable" / "target" / "release" / "libkvspace_durable.so", - "/usr/lib/kvspace/libkvspace_durable.so.1", -]) -KVSPACE_C_DIR = WS / "kvspace-c" -KVSPACE_C_SO = find_first("KVSPACE_C_SO", [ - KVSPACE_C_DIR / "build" / "libkvspace-c.so", - "/usr/lib/kvspace/libkvspace-c.so.1", -]) FRONTEND_SO = str(find_first("KVSPACE_SO", [ ROOT / "build" / "libkvspace.so.1", "/usr/lib/kvspace/libkvspace.so.1", @@ -97,183 +86,6 @@ def test_script(script): return False -# -- kvspace-c <-> kvspace-durable cross-check (ctypes, #571 v0.3.23+ ABI byte alignment) -- - -class HeadV(ctypes.Structure): - # Matches kvspaceHead_t field layout in kvspace/include/kvspace/kvspace.h. - _fields_ = [ - ("headlen", ctypes.c_uint16), - ("ref", ctypes.c_uint8), - ("storetype", ctypes.c_uint8), - ("body_len", ctypes.c_int32), - ("ndim", ctypes.c_int32), - ("dims", ctypes.c_int32 * 8), - ("langtype", ctypes.c_char * 256), - ("langtype_len", ctypes.c_int32), - ("body_offset", ctypes.c_int32), - ("body_cap", ctypes.c_uint64), - ] - - -U8P = ctypes.POINTER(ctypes.c_uint8) -U32P = ctypes.POINTER(ctypes.c_uint32) -U8PP = ctypes.POINTER(U8P) - -_libc = ctypes.CDLL(None) -_libc.free.argtypes = [ctypes.c_void_p] - - -def setup_lib(lib): - lib.kvspaceConnect.argtypes = [ctypes.c_char_p] - lib.kvspaceConnect.restype = ctypes.c_void_p - lib.kvspaceClose.argtypes = [ctypes.c_void_p] - lib.kvspaceClose.restype = None - lib.kvspaceSetValue.argtypes = [ctypes.c_void_p, ctypes.c_char_p, U8P, ctypes.c_uint32, - ctypes.c_char_p, ctypes.c_uint32] - lib.kvspaceSetValue.restype = ctypes.c_int - lib.kvspaceGet.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, U8PP, U32P] - lib.kvspaceGet.restype = ctypes.c_int - lib.kvspaceGetHead.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(HeadV)] - lib.kvspaceGetHead.restype = ctypes.c_int - lib.kvspaceWriteNewPlace.argtypes = [ - ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint8, ctypes.c_uint8, - ctypes.c_uint32, ctypes.c_uint64, ctypes.c_char_p, - U8PP, ctypes.c_char_p, ctypes.c_uint32, - ] - lib.kvspaceWriteNewPlace.restype = ctypes.c_int - lib.kvspaceNewInt64.argtypes = [ctypes.c_int64, ctypes.c_char_p, U8PP, U32P] - lib.kvspaceNewInt64.restype = ctypes.c_int - lib.kvspaceNewFloat64.argtypes = [ctypes.c_double, ctypes.c_char_p, U8PP, U32P] - lib.kvspaceNewFloat64.restype = ctypes.c_int - lib.kvspaceNewBool.argtypes = [ctypes.c_uint8, ctypes.c_char_p, U8PP, U32P] - lib.kvspaceNewBool.restype = ctypes.c_int - lib.kvspaceNewChar.argtypes = [U8P, ctypes.c_uint32, ctypes.c_char_p, U8PP, U32P] - lib.kvspaceNewChar.restype = ctypes.c_int - lib.kvspaceNewPtr.argtypes = [ctypes.c_char_p, ctypes.c_char_p, U8PP, U32P] - lib.kvspaceNewPtr.restype = ctypes.c_int - - -def call_new(lib, kind, val, langtype): - out = U8P() - out_len = ctypes.c_uint32() - lt = None if langtype is None else langtype.encode() - if kind == "int64": - rc = lib.kvspaceNewInt64(val, lt, ctypes.byref(out), ctypes.byref(out_len)) - elif kind == "float64": - rc = lib.kvspaceNewFloat64(val, lt, ctypes.byref(out), ctypes.byref(out_len)) - elif kind == "bool": - rc = lib.kvspaceNewBool(val, lt, ctypes.byref(out), ctypes.byref(out_len)) - elif kind == "char": - arr = (ctypes.c_uint8 * len(val)).from_buffer_copy(val) if val else None - rc = lib.kvspaceNewChar(arr, len(val), lt, ctypes.byref(out), ctypes.byref(out_len)) - elif kind == "ptr": - rc = lib.kvspaceNewPtr(lt, val.encode(), ctypes.byref(out), ctypes.byref(out_len)) - else: - raise ValueError(kind) - assert rc == 0, f"New({kind}, {langtype}) rc={rc}" - data = ctypes.string_at(out, out_len.value) - # Only frontend and C outputs use libc malloc; avoid calling libc free on Rust cdylib Box<[u8]>. - return data - - -def put_and_head(lib, h, key, ref, storetype, raw, langtype): - err = ctypes.create_string_buffer(256) - body = U8P() - lt = langtype.encode() if langtype else b"" - rc = lib.kvspaceWriteNewPlace(h, key.encode(), ref, storetype, len(raw), len(raw), - lt, ctypes.byref(body), err, 256) - assert rc == 0, f"WriteNewPlace({key}) rc={rc}: {err.value}" - if raw: - ctypes.memmove(body, raw, len(raw)) - out, out_len = U8P(), ctypes.c_uint32() - assert lib.kvspaceGet(h, key.encode(), 0, ctypes.byref(out), ctypes.byref(out_len)) == 0 - wire = ctypes.string_at(out, out_len.value) - hd = HeadV() - assert lib.kvspaceGetHead(h, key.encode(), ctypes.byref(hd)) == 0 - meta = { - "headlen": hd.headlen, - "ref": hd.ref, - "storetype": hd.storetype, - "body_len": hd.body_len, - "langtype": bytes(hd.langtype).split(b"\0", 1)[0].decode(), - "langtype_len": hd.langtype_len, - "body_offset": hd.body_offset, - } - return wire, meta - - -def test_kvspace_c_alignment(): - """Verify kvspace-c and kvspace-durable produce byte-identical wire values and HeadV metadata.""" - if not DURABLE_SO.exists(): - subprocess.run(["cargo", "build", "--release"], cwd=WS / "kvspace-durable", - check=True, capture_output=True) - if not KVSPACE_C_SO.exists(): - subprocess.run(["make", "kvspace-c"], cwd=KVSPACE_C_DIR / "build", - check=True, capture_output=True) - - lib_d = ctypes.CDLL(str(DURABLE_SO)) - try: - lib_c = ctypes.CDLL(str(KVSPACE_C_SO)) - except OSError: - # Preload local shared blockmalloc/slotsboxmalloc only when dynamically linked. - for dep in ["blockmalloc/build/libblockmalloc.so.1", "slotsboxmalloc/build/libslotsboxmalloc.so"]: - dep_path = KVSPACE_C_DIR.parent / dep - if dep_path.exists(): - ctypes.CDLL(str(dep_path), mode=ctypes.RTLD_GLOBAL) - lib_c = ctypes.CDLL(str(KVSPACE_C_SO)) - setup_lib(lib_d) - setup_lib(lib_c) - - new_cases = [ - ("int64", 42, "int64"), - ("int64", -7, ""), - ("float64", 3.14, "float64"), - ("bool", 1, "bool"), - ("char", b"hello", "[]char/utf8"), - ("ptr", "/target", "int64"), - ("ptr", "/untyped", ""), - ] - ok = True - for kind, val, lt in new_cases: - label = f"align New({kind}, langtype={lt!r})" - bd = call_new(lib_d, kind, val, lt) - bc = call_new(lib_c, kind, val, lt) - if bd != bc: - print(f"FAIL {label} (bytes differ: {bd.hex()} vs {bc.hex()})") - ok = False - else: - print(f"PASS {label}") - - with tempfile.TemporaryDirectory(prefix="kvspace-align-") as tmp: - hd = lib_d.kvspaceConnect(f"fs://{tmp}/fs".encode()) - hc = lib_c.kvspaceConnect(f"shm://{tmp}/c.shm".encode()) - assert hd and hc, "connect backend handles failed" - try: - store_cases = [ - ("/s_i64", 0, 0, struct.pack("