mirror of
https://github.com/logos-co/logos-protocol.git
synced 2026-08-27 20:11: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.
149 lines
4.9 KiB
C++
149 lines
4.9 KiB
C++
#include "plain_transport_connection.h"
|
|
|
|
#include "cbor_codec.h"
|
|
#include "io_context_pool.h"
|
|
#include "json_codec.h"
|
|
#include "plain_logos_object.h"
|
|
#include "rpc_server.h"
|
|
|
|
#include <QDebug>
|
|
|
|
#include <boost/asio/connect.hpp>
|
|
#include <boost/asio/ip/tcp.hpp>
|
|
#include <boost/asio/ssl/context.hpp>
|
|
#include <boost/asio/ssl/host_name_verification.hpp>
|
|
#include <boost/asio/ssl/stream.hpp>
|
|
|
|
#include <openssl/ssl.h>
|
|
|
|
namespace logos::plain {
|
|
|
|
namespace {
|
|
|
|
std::shared_ptr<IWireCodec> makeCodec(LogosWireCodec kind)
|
|
{
|
|
switch (kind) {
|
|
case LogosWireCodec::Cbor: return std::make_shared<CborCodec>();
|
|
case LogosWireCodec::Json:
|
|
default: return std::make_shared<JsonCodec>();
|
|
}
|
|
}
|
|
|
|
boost::asio::ssl::context buildClientSslCtx(const LogosTransportConfig& cfg)
|
|
{
|
|
boost::asio::ssl::context ctx(boost::asio::ssl::context::tls_client);
|
|
ctx.set_options(boost::asio::ssl::context::default_workarounds
|
|
| boost::asio::ssl::context::no_sslv2
|
|
| boost::asio::ssl::context::no_sslv3);
|
|
if (!cfg.caFile.empty())
|
|
ctx.load_verify_file(cfg.caFile);
|
|
ctx.set_verify_mode(cfg.verifyPeer
|
|
? boost::asio::ssl::verify_peer
|
|
: boost::asio::ssl::verify_none);
|
|
return ctx;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
PlainTransportConnection::PlainTransportConnection(LogosTransportConfig cfg)
|
|
: m_cfg(std::move(cfg))
|
|
{
|
|
}
|
|
|
|
PlainTransportConnection::~PlainTransportConnection()
|
|
{
|
|
if (m_conn) m_conn->stop("connection destroyed");
|
|
}
|
|
|
|
bool PlainTransportConnection::connectToHost()
|
|
{
|
|
if (m_connected) return true;
|
|
|
|
auto& ioc = IoContextPool::shared().ioContext();
|
|
auto codec = makeCodec(m_cfg.codec);
|
|
|
|
try {
|
|
boost::asio::ip::tcp::resolver resolver(ioc);
|
|
auto endpoints = resolver.resolve(m_cfg.host, std::to_string(m_cfg.port));
|
|
|
|
if (m_cfg.protocol == LogosProtocol::Tcp) {
|
|
boost::asio::ip::tcp::socket socket(ioc);
|
|
boost::asio::connect(socket, endpoints);
|
|
auto conn = std::make_shared<TcpConnection>(
|
|
std::move(socket), codec, nullptr);
|
|
conn->start();
|
|
m_conn = conn;
|
|
m_connected = true;
|
|
return true;
|
|
}
|
|
|
|
if (m_cfg.protocol == LogosProtocol::TcpSsl) {
|
|
auto ctx = buildClientSslCtx(m_cfg);
|
|
SslStream stream(ioc, ctx);
|
|
// Set SNI: TLS clients must advertise the target host name
|
|
// in the ClientHello so the server picks the right cert
|
|
// (and so any intermediate proxy can route correctly). Without
|
|
// this, vhost-style deployments would terminate the handshake.
|
|
// Cast through the OpenSSL macro because Asio doesn't expose
|
|
// SNI configuration at the wrapper level.
|
|
if (!SSL_set_tlsext_host_name(stream.native_handle(),
|
|
m_cfg.host.c_str())) {
|
|
qWarning() << "PlainTransportConnection: SSL_set_tlsext_host_name failed";
|
|
}
|
|
// Verify the peer's certificate name matches the host we
|
|
// dialed when verifyPeer is on. verify_peer alone only
|
|
// validates the chain — without host-name verification a
|
|
// valid cert for a *different* name would still pass, which
|
|
// is exactly the MITM hole verify_peer is meant to close.
|
|
if (m_cfg.verifyPeer) {
|
|
stream.set_verify_callback(
|
|
boost::asio::ssl::host_name_verification(m_cfg.host));
|
|
}
|
|
boost::asio::connect(stream.lowest_layer(), endpoints);
|
|
stream.handshake(boost::asio::ssl::stream_base::client);
|
|
auto conn = std::make_shared<SslConnection>(
|
|
std::move(stream), codec, nullptr);
|
|
conn->start();
|
|
m_conn = conn;
|
|
m_connected = true;
|
|
return true;
|
|
}
|
|
|
|
qCritical() << "PlainTransportConnection: unsupported protocol";
|
|
return false;
|
|
} catch (const std::exception& e) {
|
|
qWarning() << "PlainTransportConnection::connectToHost failed:" << e.what();
|
|
m_connected = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool PlainTransportConnection::isConnected() const
|
|
{
|
|
return m_connected && m_conn && m_conn->isOpen();
|
|
}
|
|
|
|
bool PlainTransportConnection::reconnect()
|
|
{
|
|
if (m_conn) m_conn->stop("reconnecting");
|
|
m_conn.reset();
|
|
m_connected = false;
|
|
return connectToHost();
|
|
}
|
|
|
|
LogosObject* PlainTransportConnection::requestObject(const QString& objectName, int /*timeoutMs*/)
|
|
{
|
|
if (!isConnected()) return nullptr;
|
|
return new PlainLogosObject(objectName.toStdString(), m_conn);
|
|
}
|
|
|
|
QString PlainTransportConnection::endpointUrl(const QString& /*instanceId*/,
|
|
const QString& /*moduleName*/)
|
|
{
|
|
return QString("tcp://%1:%2")
|
|
.arg(QString::fromStdString(m_cfg.host))
|
|
.arg(m_cfg.port);
|
|
}
|
|
|
|
} // namespace logos::plain
|