From b60b230e66ea985869f50f236606cb260ac69356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20Galv=C3=A1n?= Date: Thu, 16 Jul 2026 16:32:03 -0300 Subject: [PATCH] Fix binary payloads in cdylib events (#100) --- .../experimental/lidl_gen_cdylib.cpp | 52 +++++++++++-------- tests/experimental/CMakeLists.txt | 2 + tests/experimental/test_lidl_gen_cdylib.cpp | 28 ++++++++++ 3 files changed, 61 insertions(+), 21 deletions(-) create mode 100644 tests/experimental/test_lidl_gen_cdylib.cpp diff --git a/cpp-generator/experimental/lidl_gen_cdylib.cpp b/cpp-generator/experimental/lidl_gen_cdylib.cpp index b0e9d14..f59d069 100644 --- a/cpp-generator/experimental/lidl_gen_cdylib.cpp +++ b/cpp-generator/experimental/lidl_gen_cdylib.cpp @@ -76,6 +76,29 @@ QString stdReturnToJson(const MethodDecl& md, const QString& var) return "nlohmann::json(" + var + ")"; } +void emitBytesEncodeHelpers(QTextStream& s) +{ + s << "// Canonical tagged bytes form {\"_bytes\": base64url} (see logos_protocol.h)\n"; + s << "std::string lidlB64UrlEncode(const std::vector& bytes)\n{\n"; + s << " static const char* alpha = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n"; + s << " std::string out;\n"; + s << " size_t i = 0;\n"; + s << " while (i + 3 <= bytes.size()) {\n"; + s << " uint32_t n = (uint32_t(bytes[i]) << 16) | (uint32_t(bytes[i+1]) << 8) | uint32_t(bytes[i+2]);\n"; + s << " out += alpha[(n >> 18) & 0x3f]; out += alpha[(n >> 12) & 0x3f];\n"; + s << " out += alpha[(n >> 6) & 0x3f]; out += alpha[n & 0x3f];\n"; + s << " i += 3;\n }\n"; + s << " if (i < bytes.size()) {\n"; + s << " uint32_t n = uint32_t(bytes[i]) << 16;\n"; + s << " if (i + 1 < bytes.size()) n |= uint32_t(bytes[i+1]) << 8;\n"; + s << " out += alpha[(n >> 18) & 0x3f]; out += alpha[(n >> 12) & 0x3f];\n"; + s << " if (i + 1 < bytes.size()) out += alpha[(n >> 6) & 0x3f];\n"; + s << " }\n return out;\n}\n\n"; + + s << "nlohmann::json lidlBytesToJson(const std::vector& bytes)\n{\n"; + s << " return nlohmann::json{{\"_bytes\", lidlB64UrlEncode(bytes)}};\n}\n\n"; +} + void emitInterfaceJson(QTextStream& s, const ModuleDecl& module) { s << "static nlohmann::json lidlInterfaceJson()\n{\n"; @@ -228,22 +251,7 @@ QString lidlMakeModuleImplExports(const ModuleDecl& module, s << " if (out) std::memcpy(out, str.data(), str.size() + 1);\n"; s << " return out;\n}\n\n"; - s << "// Canonical tagged bytes form {\"_bytes\": base64url} (see logos_protocol.h)\n"; - s << "std::string lidlB64UrlEncode(const std::vector& bytes)\n{\n"; - s << " static const char* alpha = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n"; - s << " std::string out;\n"; - s << " size_t i = 0;\n"; - s << " while (i + 3 <= bytes.size()) {\n"; - s << " uint32_t n = (uint32_t(bytes[i]) << 16) | (uint32_t(bytes[i+1]) << 8) | uint32_t(bytes[i+2]);\n"; - s << " out += alpha[(n >> 18) & 0x3f]; out += alpha[(n >> 12) & 0x3f];\n"; - s << " out += alpha[(n >> 6) & 0x3f]; out += alpha[n & 0x3f];\n"; - s << " i += 3;\n }\n"; - s << " if (i < bytes.size()) {\n"; - s << " uint32_t n = uint32_t(bytes[i]) << 16;\n"; - s << " if (i + 1 < bytes.size()) n |= uint32_t(bytes[i+1]) << 8;\n"; - s << " out += alpha[(n >> 18) & 0x3f]; out += alpha[(n >> 12) & 0x3f];\n"; - s << " if (i + 1 < bytes.size()) out += alpha[(n >> 6) & 0x3f];\n"; - s << " }\n return out;\n}\n\n"; + emitBytesEncodeHelpers(s); s << "int lidlB64Idx(char ch)\n{\n"; s << " if (ch >= 'A' && ch <= 'Z') return ch - 'A';\n"; @@ -300,9 +308,6 @@ QString lidlMakeModuleImplExports(const ModuleDecl& module, s << " out.push_back((n >> 8) & 0xff);\n"; s << " }\n }\n return out;\n}\n\n"; - s << "nlohmann::json lidlBytesToJson(const std::vector& bytes)\n{\n"; - s << " return nlohmann::json{{\"_bytes\", lidlB64UrlEncode(bytes)}};\n}\n\n"; - s << "nlohmann::json lidlResultToJson(const StdLogosResult& r)\n{\n"; s << " nlohmann::json obj;\n"; s << " obj[\"success\"] = r.success;\n"; @@ -476,6 +481,12 @@ QString lidlMakeEventsSourceCdylib(const ModuleDecl& module, s << "// (the export wrapper forwards to the host's emit callback).\n"; s << "#include \"" << implHeader << "\"\n"; s << "#include \n\n"; + s << "#include \n"; + s << "#include \n"; + s << "#include \n\n"; + s << "namespace {\n\n"; + emitBytesEncodeHelpers(s); + s << "} // namespace\n\n"; for (const EventDecl& ed : module.events) { s << "void " << implClass << "::" << ed.name << "("; @@ -491,7 +502,7 @@ QString lidlMakeEventsSourceCdylib(const ModuleDecl& module, s << " nlohmann::json args = nlohmann::json::array();\n"; for (const ParamDecl& pd : ed.params) { if (pd.type.kind == TypeExpr::Primitive && pd.type.name == "bstr") - s << " args.push_back(nlohmann::json{{\"_bytes\", \"\"}}); // bstr events: encode upstream\n"; + s << " args.push_back(lidlBytesToJson(" << pd.name << "));\n"; else s << " args.push_back(" << pd.name << ");\n"; } @@ -500,4 +511,3 @@ QString lidlMakeEventsSourceCdylib(const ModuleDecl& module, } return c; } - diff --git a/tests/experimental/CMakeLists.txt b/tests/experimental/CMakeLists.txt index 1c7ac98..d67fb9e 100644 --- a/tests/experimental/CMakeLists.txt +++ b/tests/experimental/CMakeLists.txt @@ -11,6 +11,7 @@ add_executable(experimental_tests # Code generation tests test_lidl_gen_client.cpp + test_lidl_gen_cdylib.cpp # Impl header parser tests test_impl_header_parser.cpp @@ -18,6 +19,7 @@ add_executable(experimental_tests # Sources under test ${EXPERIMENTAL_SRC_DIR}/lidl_emit_common.cpp ${EXPERIMENTAL_SRC_DIR}/lidl_gen_client.cpp + ${EXPERIMENTAL_SRC_DIR}/lidl_gen_cdylib.cpp ${EXPERIMENTAL_SRC_DIR}/impl_header_parser.cpp ) diff --git a/tests/experimental/test_lidl_gen_cdylib.cpp b/tests/experimental/test_lidl_gen_cdylib.cpp new file mode 100644 index 0000000..766e3f8 --- /dev/null +++ b/tests/experimental/test_lidl_gen_cdylib.cpp @@ -0,0 +1,28 @@ +#include + +#include "lidl_gen_cdylib.h" + +TEST(LidlGenCdylib, BinaryEventPayloadUsesCanonicalBytesEncoding) +{ + ModuleDecl module; + module.name = "delivery_module"; + + EventDecl event; + event.name = "messageReceived"; + + ParamDecl payload; + payload.name = "payload"; + payload.type = {TypeExpr::Primitive, "bstr", {}}; + event.params.push_back(payload); + module.events.push_back(event); + + const QString source = lidlMakeEventsSourceCdylib( + module, + "DeliveryModuleImpl", + "delivery_module_plugin.h"); + + EXPECT_TRUE(source.contains("args.push_back(lidlBytesToJson(payload));")); + EXPECT_TRUE(source.contains("std::string lidlB64UrlEncode")); + EXPECT_TRUE(source.contains("nlohmann::json lidlBytesToJson")); + EXPECT_FALSE(source.contains("nlohmann::json{{\"_bytes\", \"\"}}")); +}