Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
71 changes: 49 additions & 22 deletions cli/kvspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<i32>, // Leading [dims] parsed from langtype
body: Vec<u8>,
}

/* 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 {
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -386,7 +415,6 @@ fn parse_value(raw: &str) -> Vec<u8> {
let mut len64: u64 = 0;
if unsafe {
kvspaceXvalueHeadNewScalar(
KVSPACE_XVALUE_ST_SHORT,
std::ptr::null(),
0,
cs(langtype),
Expand Down Expand Up @@ -440,7 +468,6 @@ fn parse_value(raw: &str) -> Vec<u8> {
let mut len64: u64 = 0;
if unsafe {
kvspaceXvalueHeadNewScalar(
KVSPACE_XVALUE_ST_SHORT,
le.as_ptr(),
4,
cs(langtype),
Expand Down
66 changes: 39 additions & 27 deletions include/kvspace/kvspace.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading
Loading