feat: implement WASM phase 1 DValue ABI
This commit is contained in:
@@ -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());
|
||||
```
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user