feat: implement WASM phase 1 DValue ABI

This commit is contained in:
udo
2026-06-12 12:42:56 +00:00
parent 7e2faf1472
commit 80285b7fb4
8 changed files with 657 additions and 725 deletions
+28
View File
@@ -0,0 +1,28 @@
:title
ucb_decode
:sig
DValue ucb_decode(String encoded)
bool ucb_decode(String encoded, DValue& out, String* error_out = 0)
:see
ucb_encode
0_DValue
json_decode
:content
Decodes UCEB1 bytes produced by `ucb_encode()` back into a `DValue`.
The one-argument form returns an empty `DValue` on invalid input. The three-argument form reports whether decoding succeeded and can return a human-readable error string.
```cpp
DValue decoded;
String error;
if(!ucb_decode(bytes, decoded, &error))
{
print("decode failed: " + error);
return;
}
print(decoded["name"].to_string());
```
+23
View File
@@ -0,0 +1,23 @@
:title
ucb_encode
:sig
String ucb_encode(DValue value)
:see
ucb_decode
0_DValue
json_encode
:content
Serializes a `DValue` to UCEB1, UCE's binary DValue wire format for the WASM membrane and future cross-instance calls.
UCEB1 is length-prefixed and binary-safe. It preserves nested maps, list-shaped maps, empty lists, and scalar bytes, including embedded NUL bytes. Use JSON/YAML/XML serializers for human-facing formats; use UCEB1 when UCE code needs the native DValue protocol.
```cpp
DValue payload;
payload["name"] = "uce";
payload["items"].push("first");
String bytes = ucb_encode(payload);
```
+73
View File
@@ -109,6 +109,79 @@ RENDER(Request& context)
ordered.each([&](const DValue& item, String key) { ordered_keys += key + ","; });
check("DValue list iteration is numeric", ordered_keys == "0,1,2,3,4,5,6,7,8,9,10,11," && dv_values(ordered)["10"].to_string() == "v10" && dv_map(ordered, [](const DValue& item, String key) { return(item); })["11"].to_string() == "v11", ordered_keys);
DValue uceb_source;
uceb_source["name"] = "phase1";
uceb_source["nested"]["answer"] = "42";
uceb_source["float"] = (f64)0.0000001;
uceb_source["bool"].set_bool(true);
uceb_source["empty_list"].set_array();
DValue uceb_item;
uceb_item = "first";
uceb_source["items"].push(uceb_item);
uceb_item = "second";
uceb_source["items"].push(uceb_item);
String uceb_binary = "bin";
uceb_binary.push_back((char)0x00);
uceb_binary += "ary";
uceb_source["binary"] = uceb_binary;
uceb_source["pointer"] = (void*)0x1234;
String uceb_encoded = ucb_encode(uceb_source);
String uceb_error = "";
DValue uceb_decoded;
bool uceb_ok = ucb_decode(uceb_encoded, uceb_decoded, &uceb_error);
check("UCEB1 DValue codec round-trip", uceb_ok && uceb_decoded["name"].to_string() == "phase1" && uceb_decoded["nested"]["answer"].to_string() == "42" && uceb_decoded["float"].to_f64() > 0.00000009 && uceb_decoded["float"].to_f64() < 0.00000011 && uceb_decoded["bool"].to_bool() && uceb_decoded["items"].is_list() && uceb_decoded["items"]["1"].to_string() == "second" && uceb_decoded["empty_list"].is_list() && uceb_decoded["binary"].to_string().size() == uceb_binary.size() && uceb_decoded["binary"].to_string() == uceb_binary && uceb_decoded["pointer"].to_string() == "", "bytes=" + std::to_string((u64)uceb_encoded.size()) + " error=" + uceb_error + " float=" + uceb_decoded["float"].to_string());
DValue uceb_invalid;
String uceb_bad_magic = "NOPE";
uceb_bad_magic.push_back((char)1);
String uceb_wrong_version = "UCEB";
uceb_wrong_version.push_back((char)2);
String uceb_truncated = "UCEB";
uceb_truncated.push_back((char)1);
uceb_truncated.push_back((char)0);
String uceb_depth_bomb = "UCEB";
uceb_depth_bomb.push_back((char)1);
for(u32 i = 0; i < 1030; i++)
{
uceb_depth_bomb.push_back((char)0); // flags
uceb_depth_bomb.push_back((char)0); // scalar length
uceb_depth_bomb.push_back((char)1); // child count
uceb_depth_bomb.push_back((char)1); // key length
uceb_depth_bomb += "x";
}
uceb_depth_bomb.push_back((char)0);
uceb_depth_bomb.push_back((char)0);
uceb_depth_bomb.push_back((char)0);
String uceb_negative_error = "";
bool uceb_bad_magic_ok = ucb_decode(uceb_bad_magic, uceb_invalid, &uceb_negative_error);
String uceb_magic_error = uceb_negative_error;
bool uceb_wrong_version_ok = ucb_decode(uceb_wrong_version, uceb_invalid, &uceb_negative_error);
String uceb_version_error = uceb_negative_error;
bool uceb_truncated_ok = ucb_decode(uceb_truncated, uceb_invalid, &uceb_negative_error);
String uceb_truncated_error = uceb_negative_error;
String uceb_trailing = uceb_encoded + "x";
bool uceb_trailing_ok = ucb_decode(uceb_trailing, uceb_invalid, &uceb_negative_error);
String uceb_trailing_error = uceb_negative_error;
bool uceb_depth_ok = ucb_decode(uceb_depth_bomb, uceb_invalid, &uceb_negative_error);
check("UCEB1 rejects invalid input", !uceb_bad_magic_ok && contains(uceb_magic_error, "magic") && !uceb_wrong_version_ok && contains(uceb_version_error, "version") && !uceb_truncated_ok && contains(uceb_truncated_error, "length") && !uceb_trailing_ok && contains(uceb_trailing_error, "trailing") && !uceb_depth_ok && contains(uceb_negative_error, "nesting"), uceb_magic_error + " / " + uceb_version_error + " / " + uceb_truncated_error + " / " + uceb_trailing_error + " / " + uceb_negative_error);
size_t abi_len = 0;
uce_dvalue* abi_root = reinterpret_cast<uce_dvalue*>(&uceb_source);
uce_dvalue* abi_nested = uce_dv_find(abi_root, "nested", 6);
uce_dvalue* abi_answer = uce_dv_get(abi_nested, "answer", 6);
const char* abi_answer_value = uce_dv_value(abi_answer, &abi_len);
uce_dv_iter abi_iter = uce_dv_iter_begin(uce_dv_find(abi_root, "items", 5));
const char* abi_key = 0;
size_t abi_key_len = 0;
uce_dvalue* abi_child = 0;
bool abi_iter_first = uce_dv_iter_next(uce_dv_find(abi_root, "items", 5), &abi_iter, &abi_key, &abi_key_len, &abi_child) == 1;
size_t abi_encoded_len = uce_dv_encode(abi_root, 0, 0);
String abi_encoded;
abi_encoded.resize(abi_encoded_len);
uce_dv_encode(abi_root, &abi_encoded[0], abi_encoded.size());
uce_dvalue* abi_decoded = uce_dv_decode(abi_encoded.data(), abi_encoded.size());
check("DValue C ABI accessors", abi_answer_value != 0 && String(abi_answer_value, abi_len) == "42" && uce_dv_count(abi_nested) == 1 && uce_dv_is_list(uce_dv_find(abi_root, "items", 5)) == 1 && abi_iter_first && String(abi_key, abi_key_len) == "0" && uce_dv_value(abi_child, &abi_len) != 0 && uce_dv_count(abi_root) == 8 && abi_decoded != 0 && uce_dv_find(abi_decoded, "nested", 6) != 0, "encoded=" + std::to_string((u64)abi_encoded_len) + " last_error=" + String(uce_dv_last_error()));
String binary_payload = "core";
binary_payload.push_back((char)0x00);
binary_payload.push_back((char)0xff);