mirror of
https://github.com/logos-co/logos-protocol.git
synced 2026-08-30 05:21:07 +00:00
* Extract the Logos protocol layer from logos-cpp-sdk
Transports (plain TCP/TLS, qt_local, qt_remote/QRO, mock), token manager,
consumer core (LogosAPIClient/LogosAPIConsumer incl. the capability
auto-requestModule flow), ModuleProxy, the abstract LogosProviderObject
interface, and the canonical QVariant<->JSON conversion — now behind the
language-neutral lp_* C ABI (logos_protocol.h) carrying the protocol
semver (LOGOS_PROTOCOL_VERSION_*, lp_protocol_version()).
Bytes crossing the ABI use the lossless {"_bytes": base64url} tagging
(NUL-safe), matching the plain wire encoding.
Provider lp_* surface is compiled groundwork; serving lands with module
authoring.
* consumer: typed requestModule for the capability flow
Port of logos-cpp-sdk master f5a127dd ('use updated capability module',
cpp-sdk#85, Iuri Matias) — the touched files (logos_api_client.cpp,
logos_api_consumer.{h,cpp}) moved into this repo in the P1 extraction.
The capability auto-requestModule path now calls a typed std::string
helper on the consumer (which acquires the capability object directly)
instead of a stringly invokeRemoteMethod round-trip. 111/111 tests.
130 lines
3.6 KiB
C++
130 lines
3.6 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include "rpc_framing.h"
|
|
#include "json_codec.h"
|
|
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
using namespace logos::plain;
|
|
|
|
// Encode one frame, decode it back, compare.
|
|
TEST(RpcFramingTest, RoundTripCallMessage)
|
|
{
|
|
JsonCodec codec;
|
|
CallMessage call;
|
|
call.id = 42;
|
|
call.authToken = "tok";
|
|
call.object = "core_service";
|
|
call.method = "loadModule";
|
|
call.args.push_back(RpcValue{std::string{"chat"}});
|
|
|
|
auto frame = encodeFrame(codec, AnyMessage{call});
|
|
|
|
FrameReader reader;
|
|
reader.append(frame);
|
|
|
|
MessageType tag;
|
|
std::vector<uint8_t> payload;
|
|
ASSERT_TRUE(reader.next(tag, payload));
|
|
EXPECT_EQ(tag, MessageType::Call);
|
|
|
|
AnyMessage decoded = codec.decode(tag, payload.data(), payload.size());
|
|
auto* c = std::get_if<CallMessage>(&decoded);
|
|
ASSERT_NE(c, nullptr);
|
|
EXPECT_EQ(c->id, 42u);
|
|
EXPECT_EQ(c->authToken, "tok");
|
|
EXPECT_EQ(c->object, "core_service");
|
|
EXPECT_EQ(c->method, "loadModule");
|
|
ASSERT_EQ(c->args.size(), 1u);
|
|
EXPECT_TRUE(c->args[0].isString());
|
|
EXPECT_EQ(c->args[0].asString(), "chat");
|
|
}
|
|
|
|
// Feed the frame byte-by-byte; reader must wait for the whole frame.
|
|
TEST(RpcFramingTest, PartialReadsCoalesce)
|
|
{
|
|
JsonCodec codec;
|
|
EventMessage evt;
|
|
evt.object = "core_service";
|
|
evt.eventName = "module_event";
|
|
evt.data.push_back(RpcValue{std::string{"test_basic_module"}});
|
|
evt.data.push_back(RpcValue{std::string{"testEvent"}});
|
|
|
|
auto frame = encodeFrame(codec, AnyMessage{evt});
|
|
|
|
FrameReader reader;
|
|
MessageType tag;
|
|
std::vector<uint8_t> payload;
|
|
|
|
// Feed one byte at a time; only the last byte should yield a complete frame.
|
|
for (std::size_t i = 0; i < frame.size() - 1; ++i) {
|
|
reader.append(&frame[i], 1);
|
|
EXPECT_FALSE(reader.next(tag, payload));
|
|
}
|
|
reader.append(&frame.back(), 1);
|
|
ASSERT_TRUE(reader.next(tag, payload));
|
|
|
|
auto decoded = codec.decode(tag, payload.data(), payload.size());
|
|
auto* e = std::get_if<EventMessage>(&decoded);
|
|
ASSERT_NE(e, nullptr);
|
|
EXPECT_EQ(e->object, "core_service");
|
|
EXPECT_EQ(e->eventName, "module_event");
|
|
ASSERT_EQ(e->data.size(), 2u);
|
|
EXPECT_EQ(e->data[0].asString(), "test_basic_module");
|
|
EXPECT_EQ(e->data[1].asString(), "testEvent");
|
|
}
|
|
|
|
// Two frames concatenated; reader yields them in order.
|
|
TEST(RpcFramingTest, ConcatenatedFrames)
|
|
{
|
|
JsonCodec codec;
|
|
SubscribeMessage sub;
|
|
sub.object = "obj1";
|
|
sub.eventName = "evt1";
|
|
|
|
TokenMessage tok;
|
|
tok.authToken = "auth";
|
|
tok.moduleName = "mod";
|
|
tok.token = "tok";
|
|
|
|
auto f1 = encodeFrame(codec, AnyMessage{sub});
|
|
auto f2 = encodeFrame(codec, AnyMessage{tok});
|
|
std::vector<uint8_t> both(f1.begin(), f1.end());
|
|
both.insert(both.end(), f2.begin(), f2.end());
|
|
|
|
FrameReader reader;
|
|
reader.append(both);
|
|
|
|
MessageType t1, t2;
|
|
std::vector<uint8_t> p1, p2;
|
|
ASSERT_TRUE(reader.next(t1, p1));
|
|
ASSERT_TRUE(reader.next(t2, p2));
|
|
EXPECT_EQ(t1, MessageType::Subscribe);
|
|
EXPECT_EQ(t2, MessageType::Token);
|
|
}
|
|
|
|
// Oversized length prefix rejected.
|
|
TEST(RpcFramingTest, RejectsOversizedFrame)
|
|
{
|
|
FrameReader reader;
|
|
std::vector<uint8_t> badPrefix = { 0xff, 0xff, 0xff, 0xff, 0x00 }; // 4 GiB
|
|
reader.append(badPrefix);
|
|
|
|
MessageType tag;
|
|
std::vector<uint8_t> payload;
|
|
EXPECT_THROW({ reader.next(tag, payload); }, FramingError);
|
|
}
|
|
|
|
// Zero length rejected.
|
|
TEST(RpcFramingTest, RejectsZeroLength)
|
|
{
|
|
FrameReader reader;
|
|
std::vector<uint8_t> zero = { 0x00, 0x00, 0x00, 0x00 };
|
|
reader.append(zero);
|
|
|
|
MessageType tag;
|
|
std::vector<uint8_t> payload;
|
|
EXPECT_THROW({ reader.next(tag, payload); }, FramingError);
|
|
}
|