Merge back from master

This commit is contained in:
NagyZoltanPeter
2026-04-13 10:31:36 +02:00
16 changed files with 2311 additions and 813 deletions
+5 -38
View File
@@ -1,39 +1,6 @@
# Build directories
build/
result
result-*
.direnv/
# CMake
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
*.cmake
# Compiled objects
*.o
*.a
*.so
*.dylib
*.dll
# Qt
*.moc
moc_*.cpp
qrc_*.cpp
*.qm
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# macOS
.DS_Store
._*
# Nix
.pre-commit-config.yaml
build
.deps/
vendor/*
lib/*
result
-11
View File
@@ -1,11 +0,0 @@
[submodule "vendor/logos-delivery"]
path = vendor/logos-delivery
url = https://github.com/logos-messaging/logos-delivery.git
branch = master
fetchRecurseSubmodules = true
[submodule "vendor/logos-liblogos"]
path = vendor/logos-liblogos
url = https://github.com/logos-co/logos-liblogos
[submodule "vendor/logos-cpp-sdk"]
path = vendor/logos-cpp-sdk
url = https://github.com/logos-co/logos-cpp-sdk
+18 -291
View File
@@ -1,304 +1,31 @@
cmake_minimum_required(VERSION 3.14)
project(DeliveryModulePlugin LANGUAGES CXX)
project(LogosDeliveryModulePlugin LANGUAGES CXX)
# Require C++20
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
include(GNUInstallDirs)
set(CMAKE_AUTOMOC ON)
option(LOGOS_MESSAGING_MODULE_USE_VENDOR "Force use of vendored Logos dependencies" OFF)
# Allow override from environment or command line
if(NOT DEFINED LOGOS_LIBLOGOS_ROOT)
set(_parent_liblogos "${CMAKE_CURRENT_SOURCE_DIR}/../logos-liblogos")
set(_use_vendor ${LOGOS_MESSAGING_MODULE_USE_VENDOR})
if(NOT _use_vendor)
if(NOT EXISTS "${_parent_liblogos}/interface.h")
set(_use_vendor ON)
endif()
endif()
if(_use_vendor)
set(LOGOS_LIBLOGOS_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/vendor/logos-liblogos")
else()
set(LOGOS_LIBLOGOS_ROOT "${_parent_liblogos}")
endif()
endif()
if(NOT DEFINED LOGOS_CPP_SDK_ROOT)
set(_parent_cpp_sdk "${CMAKE_CURRENT_SOURCE_DIR}/../logos-cpp-sdk")
set(_use_vendor ${LOGOS_MESSAGING_MODULE_USE_VENDOR})
if(NOT _use_vendor)
if(NOT EXISTS "${_parent_cpp_sdk}/cpp/logos_api.h")
set(_use_vendor ON)
endif()
endif()
if(_use_vendor)
set(LOGOS_CPP_SDK_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/vendor/logos-cpp-sdk")
else()
set(LOGOS_CPP_SDK_ROOT "${_parent_cpp_sdk}")
endif()
endif()
if(NOT DEFINED LOGOS_DELIVERY_ROOT)
set(LOGOS_DELIVERY_ROOT "$ENV{LOGOS_DELIVERY_ROOT}")
endif()
# Check if dependencies are available (support both source and installed layouts)
set(_liblogos_found FALSE)
if(EXISTS "${LOGOS_LIBLOGOS_ROOT}/interface.h")
set(_liblogos_found TRUE)
set(_liblogos_is_source TRUE)
elseif(EXISTS "${LOGOS_LIBLOGOS_ROOT}/include/interface.h")
set(_liblogos_found TRUE)
set(_liblogos_is_source FALSE)
endif()
set(_cpp_sdk_found FALSE)
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.h")
set(_cpp_sdk_found TRUE)
set(_cpp_sdk_is_source TRUE)
elseif(EXISTS "${LOGOS_CPP_SDK_ROOT}/include/cpp/logos_api.h")
set(_cpp_sdk_found TRUE)
set(_cpp_sdk_is_source FALSE)
endif()
if(NOT _liblogos_found)
message(FATAL_ERROR "logos-liblogos not found at ${LOGOS_LIBLOGOS_ROOT}. "
"Set LOGOS_LIBLOGOS_ROOT or run git submodule update --init --recursive.")
endif()
if(NOT _cpp_sdk_found)
message(FATAL_ERROR "logos-cpp-sdk not found at ${LOGOS_CPP_SDK_ROOT}. "
"Set LOGOS_CPP_SDK_ROOT or run git submodule update --init --recursive.")
endif()
# Root that contains the vendored dependencies (logos-core checkout or script vendor directory)
get_filename_component(LOGOS_DEPS_ROOT "${LOGOS_CPP_SDK_ROOT}" DIRECTORY)
# Find Qt RemoteObjects (needed for LogosAPI)
if(NOT DEFINED QT_VERSION_MAJOR)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core RemoteObjects)
if(Qt6_FOUND)
set(QT_VERSION_MAJOR 6)
else()
set(QT_VERSION_MAJOR 5)
endif()
endif()
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core RemoteObjects)
# Run Logos C++ generator on metadata before compilation
set(METADATA_JSON "${CMAKE_CURRENT_SOURCE_DIR}/metadata.json")
set(PLUGINS_OUTPUT_DIR "${CMAKE_BINARY_DIR}/modules")
# Try to find the generator binary
if(_cpp_sdk_is_source)
# Source layout: build the generator
set(CPP_GENERATOR_BUILD_DIR "${LOGOS_DEPS_ROOT}/build/cpp-generator")
set(CPP_GENERATOR "${CPP_GENERATOR_BUILD_DIR}/bin/logos-cpp-generator")
if(NOT TARGET cpp_generator_build)
add_custom_target(cpp_generator_build
COMMAND bash "${LOGOS_CPP_SDK_ROOT}/cpp-generator/compile.sh"
WORKING_DIRECTORY "${LOGOS_DEPS_ROOT}"
COMMENT "Building logos-cpp-generator via ${LOGOS_CPP_SDK_ROOT}/cpp-generator/compile.sh"
VERBATIM
)
endif()
add_custom_target(run_cpp_generator_messaging
COMMAND "${CPP_GENERATOR}" --metadata "${METADATA_JSON}" --module-dir "${PLUGINS_OUTPUT_DIR}"
WORKING_DIRECTORY "${LOGOS_DEPS_ROOT}"
COMMENT "Running logos-cpp-generator on ${METADATA_JSON} with module-dir ${PLUGINS_OUTPUT_DIR}"
VERBATIM
)
add_dependencies(run_cpp_generator_messaging cpp_generator_build)
# Include the Logos Module CMake helper
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
else()
# Installed layout: use the generator from the install path
find_program(CPP_GENERATOR logos-cpp-generator PATHS ${LOGOS_CPP_SDK_ROOT}/bin NO_DEFAULT_PATH REQUIRED)
add_custom_target(run_cpp_generator_messaging
COMMAND "${CPP_GENERATOR}" --metadata "${METADATA_JSON}" --module-dir "${PLUGINS_OUTPUT_DIR}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
COMMENT "Running logos-cpp-generator on ${METADATA_JSON} with module-dir ${PLUGINS_OUTPUT_DIR}"
VERBATIM
)
message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.")
endif()
# Find liblogosdelivery library
set(LIBLOGOSDELIVERY_DIR "${LOGOS_DELIVERY_ROOT}/bin")
# Prioritize platform-specific library names
if(APPLE)
set(LIBLOGOSDELIVERY_NAMES liblogosdelivery.dylib liblogosdelivery.so)
elseif(WIN32)
set(LIBLOGOSDELIVERY_NAMES liblogosdelivery.dll lmapi.dll)
else()
set(LIBLOGOSDELIVERY_NAMES liblogosdelivery.so)
endif()
find_library(LIBLOGOSDELIVERY_PATH NAMES ${LIBLOGOSDELIVERY_NAMES} PATHS ${LIBLOGOSDELIVERY_DIR} NO_DEFAULT_PATH)
# Plugin sources
set(PLUGIN_SOURCES
src/delivery_module_plugin.cpp
src/delivery_module_plugin.h
src/delivery_module_interface.h
)
# Add liblogos interface header
if(_liblogos_is_source)
list(APPEND PLUGIN_SOURCES ${LOGOS_LIBLOGOS_ROOT}/interface.h)
else()
list(APPEND PLUGIN_SOURCES ${LOGOS_LIBLOGOS_ROOT}/include/interface.h)
endif()
# Add SDK sources (only if source layout, installed layout uses the library)
if(_cpp_sdk_is_source)
list(APPEND PLUGIN_SOURCES
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.h
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_client.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_client.h
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_consumer.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_consumer.h
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.h
${LOGOS_CPP_SDK_ROOT}/cpp/token_manager.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/token_manager.h
${LOGOS_CPP_SDK_ROOT}/cpp/module_proxy.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/module_proxy.h
)
endif()
# Create the plugin library
add_library(delivery_module_plugin SHARED ${PLUGIN_SOURCES})
# Set output name without lib prefix
set_target_properties(delivery_module_plugin PROPERTIES
PREFIX "")
# Ensure generator runs before building the plugin
add_dependencies(delivery_module_plugin run_cpp_generator_messaging)
# Link Qt libraries
target_link_libraries(delivery_module_plugin PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::RemoteObjects
)
# Link SDK library if using installed layout
if(NOT _cpp_sdk_is_source)
find_library(LOGOS_SDK_LIB logos_sdk PATHS ${LOGOS_CPP_SDK_ROOT}/lib NO_DEFAULT_PATH REQUIRED)
target_link_libraries(delivery_module_plugin PRIVATE ${LOGOS_SDK_LIB})
endif()
# Link to liblogosdelivery on all platforms
if(LIBLOGOSDELIVERY_PATH)
target_link_libraries(delivery_module_plugin PRIVATE ${LIBLOGOSDELIVERY_PATH})
else()
message(WARNING "liblogosdelivery not found in ${LIBLOGOSDELIVERY_DIR}. Build or provide it before linking.")
endif()
# Include directories
target_include_directories(delivery_module_plugin PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${LIBLOGOSDELIVERY_DIR}
)
# Look for liblogosdelivery.h in logos-delivery
if(EXISTS "${LOGOS_DELIVERY_ROOT}/include/liblogosdelivery.h")
target_include_directories(delivery_module_plugin PRIVATE ${LOGOS_DELIVERY_ROOT}/include)
elseif(EXISTS "${LOGOS_DELIVERY_ROOT}/liblogosdelivery/liblogosdelivery.h")
target_include_directories(delivery_module_plugin PRIVATE ${LOGOS_DELIVERY_ROOT}/liblogosdelivery)
endif()
# Add include directories based on layout type
if(_liblogos_is_source)
target_include_directories(delivery_module_plugin PRIVATE ${LOGOS_LIBLOGOS_ROOT})
else()
target_include_directories(delivery_module_plugin PRIVATE ${LOGOS_LIBLOGOS_ROOT}/include)
endif()
if(_cpp_sdk_is_source)
target_include_directories(delivery_module_plugin PRIVATE
${LOGOS_CPP_SDK_ROOT}/cpp
${LOGOS_CPP_SDK_ROOT}/cpp/generated
)
else()
target_include_directories(delivery_module_plugin PRIVATE
${LOGOS_CPP_SDK_ROOT}/include
${LOGOS_CPP_SDK_ROOT}/include/cpp
${LOGOS_CPP_SDK_ROOT}/include/core
)
endif()
set_target_properties(delivery_module_plugin PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules" # For Windows .dll
BUILD_WITH_INSTALL_RPATH TRUE
SKIP_BUILD_RPATH FALSE)
if(APPLE)
# Allow unresolved symbols at link time; liblogosdelivery will be provided at runtime
target_link_options(delivery_module_plugin PRIVATE -undefined dynamic_lookup)
set_target_properties(delivery_module_plugin PROPERTIES
INSTALL_RPATH "@loader_path"
INSTALL_NAME_DIR "@rpath"
BUILD_WITH_INSTALL_NAME_DIR TRUE)
if(LIBLOGOSDELIVERY_PATH)
get_filename_component(LIBLOGOSDELIVERY_FILENAME "${LIBLOGOSDELIVERY_PATH}" NAME)
add_custom_command(TARGET delivery_module_plugin PRE_LINK
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${LIBLOGOSDELIVERY_PATH}
${CMAKE_BINARY_DIR}/modules/${LIBLOGOSDELIVERY_FILENAME}
COMMENT "Copying ${LIBLOGOSDELIVERY_FILENAME} to modules directory"
)
add_custom_command(TARGET delivery_module_plugin POST_BUILD
COMMAND install_name_tool -id "@rpath/delivery_module_plugin.dylib" $<TARGET_FILE:delivery_module_plugin>
COMMAND install_name_tool -id "@rpath/${LIBLOGOSDELIVERY_FILENAME}" "${CMAKE_BINARY_DIR}/modules/${LIBLOGOSDELIVERY_FILENAME}"
COMMAND install_name_tool -change "${LIBLOGOSDELIVERY_PATH}" "@rpath/${LIBLOGOSDELIVERY_FILENAME}" $<TARGET_FILE:delivery_module_plugin>
COMMENT "Updating library paths for macOS"
)
else()
add_custom_command(TARGET delivery_module_plugin POST_BUILD
COMMAND install_name_tool -id "@rpath/delivery_module_plugin.dylib" $<TARGET_FILE:delivery_module_plugin>
COMMENT "Updating library paths for macOS (liblogosdelivery not found)"
)
endif()
else()
set_target_properties(delivery_module_plugin PROPERTIES
INSTALL_RPATH "$ORIGIN"
INSTALL_RPATH_USE_LINK_PATH FALSE)
if(LIBLOGOSDELIVERY_PATH)
get_filename_component(LIBLOGOSDELIVERY_FILENAME "${LIBLOGOSDELIVERY_PATH}" NAME)
add_custom_command(TARGET delivery_module_plugin PRE_LINK
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${LIBLOGOSDELIVERY_PATH}
${CMAKE_BINARY_DIR}/modules/${LIBLOGOSDELIVERY_FILENAME}
COMMENT "Copying ${LIBLOGOSDELIVERY_FILENAME} to modules directory"
)
endif()
endif()
install(TARGETS delivery_module_plugin
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
)
install(FILES ${METADATA_JSON}
DESTINATION ${CMAKE_INSTALL_DATADIR}/logos-delivery-module
)
install(DIRECTORY "${PLUGINS_OUTPUT_DIR}/"
DESTINATION ${CMAKE_INSTALL_DATADIR}/logos-delivery-module/generated
OPTIONAL
# Define the module
logos_module(
NAME delivery_module
SOURCES
src/delivery_module_interface.h
src/delivery_module_plugin.h
src/delivery_module_plugin.cpp
src/api_call_handler.h
src/QExpected.h
EXTERNAL_LIBS
logosdelivery
INCLUDE_DIRS
lib
)
include(CTest)
+100 -9
View File
@@ -88,24 +88,115 @@ All dependencies are automatically handled by the Nix flake configuration.
## Module Interface
The delivery module provides the following API methods:
The delivery module provides the following API methods (all synchronous):
- `createNode(cfg: QString)` - Initialize the delivery node with JSON configuration
- `createNode(cfg: QString)` - Initialize the delivery node with a JSON configuration (call once)
- `start()` - Start the delivery node
- `stop()` - Stop the delivery node
- `send(contentTopic: QString, payload: QString)` - Send a message
- `send(contentTopic: QString, payload: QString)` - Send a message (returns a request id)
- `subscribe(contentTopic: QString)` - Subscribe to receive messages on a topic
- `unsubscribe(contentTopic: QString)` - Unsubscribe from a topic
- `getAvailableNodeInfoIDs()` - List queryable node info identifiers
- `getNodeInfo(nodeInfoId: QString)` - Retrieve node info by identifier
- `getAvailableConfigs()` - Retrieve available configuration parameter descriptions
### Node Configuration (`createNode`)
`createNode` accepts a **flat** JSON object whose keys correspond to `WakuNodeConf`
field names (camelCase) from
[logos-delivery](https://github.com/logos-messaging/logos-delivery).
Unknown keys are silently ignored. Every field has a built-in default, so only
values that differ from defaults need to be supplied.
#### Commonly used keys
| Key | Type | Default | Description |
|----------------------|------------------|------------|------------------------------------------|
| `mode` | string | `"noMode"` | `"Core"`, `"Edge"`, or `"noMode"` |
| `preset` | string | `""` | Network preset (`"twn"`, `"logos.dev"`) |
| `clusterId` | number (uint16) | `0` | Cluster identifier |
| `entryNodes` | array of string | `[]` | Bootstrap peers (enrtree / multiaddress) |
| `relay` | boolean | `false` | Enable relay protocol |
| `rlnRelay` | boolean | `false` | Enable RLN rate-limit nullifier |
| `tcpPort` | number (uint16) | `60000` | P2P TCP listen port |
| `numShardsInNetwork` | number (uint16) | `1` | Auto-sharding shard count |
| `logLevel` | string | `"INFO"` | `"TRACE"`, `"DEBUG"`, `"INFO"`, `"WARN"` |
| `logFormat` | string | `"TEXT"` | `"TEXT"` or `"JSON"` |
| `maxMessageSize` | string | `"150KiB"` | Maximum message payload size |
#### Presets
Using a `preset` populates cluster ID, entry nodes, sharding, RLN, and other
network-specific defaults automatically. Individual keys supplied alongside a
preset override the preset values.
- `"twn"` The RLN-protected Waku Network (cluster 1).
- `"logos.dev"` Logos Dev Network (cluster 2, mix enabled, p2pReliability on,
8 auto-shards, built-in bootstrap nodes).
Minimal example using the `logos.dev` preset:
```json
{
"logLevel": "INFO",
"mode": "Core",
"preset": "logos.dev"
}
```
### Content Topics
Content topics identify message channels for publishing and subscribing. Use a
properly structured content topic for your application following the format
specified in
[LIP-23: Topics](https://lip.logos.co/messaging/informational/23/topics.html#content-topics).
Example: `"/myapp/1/chat/proto"`
### Sending Messages (`send`)
`send(contentTopic, payload)` accepts a content topic and a raw payload string.
The plugin converts the payload to UTF-8 bytes, base64-encodes it, and wraps it
in a JSON envelope before crossing the FFI boundary:
```json
{ "contentTopic": "<topic>", "payload": "<base64>", "ephemeral": false }
```
The call is synchronous and returns a **request id** on success. The actual
network delivery is asynchronous — track results via the emitted events:
- **`messageError`** the module could not send the message.
- **`messagePropagated`** the message reached the network but is not yet
validated.
- **`messageSent`** the message has been confirmed by the network.
### Events
The module emits the following events:
Asynchronous events are emitted off-thread as Logos Plugin events. Each event
carries a `QVariantList data` with positional values:
- `deliveryInitialized` - When the node is initialized
- `deliveryStarted` - When the node is started
- `messageSent` - When a message is successfully sent
- `messageReceived` - When a message is received
- `messageError` - When an error occurs during message sending
- **`messageSent`** message confirmed by the network
- `data[0]` (`QString`): request id
- `data[1]` (`QString`): message hash
- `data[2]` (`QString`): local timestamp (ISO-8601)
- **`messageError`** send failure
- `data[0]` (`QString`): request id
- `data[1]` (`QString`): message hash
- `data[2]` (`QString`): error message
- `data[3]` (`QString`): local timestamp (ISO-8601)
- **`messagePropagated`** message reached the network but not yet validated
- `data[0]` (`QString`): request id
- `data[1]` (`QString`): message hash
- `data[2]` (`QString`): local timestamp (ISO-8601)
- **`messageReceived`** a message arrived on a subscribed topic
- `data[0]` (`QString`): message hash
- `data[1]` (`QString`): content topic
- `data[2]` (`QString`): payload (base64-encoded)
- `data[3]` (`QString`): timestamp (nanoseconds since epoch)
- **`connectionStateChanged`** node connectivity change
- `data[0]` (`QString`): connection status
- `data[1]` (`QString`): local timestamp (ISO-8601)
## Architecture
Generated
+1788 -71
View File
File diff suppressed because it is too large Load Diff
+29 -68
View File
@@ -2,76 +2,37 @@
description = "Logos Delivery Module";
inputs = {
# Follow the same nixpkgs as logos-liblogos to ensure compatibility
nixpkgs.follows = "logos-liblogos/nixpkgs";
logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk?ref=feat/logos-result";
logos-liblogos.url = "github:logos-co/logos-liblogos";
logos-module-builder.url = "github:logos-co/logos-module-builder";
nix-bundle-lgx.url = "github:logos-co/nix-bundle-lgx";
logos-delivery.url = "git+https://github.com/logos-messaging/logos-delivery?submodules=1";
};
outputs = { self, nixpkgs, logos-cpp-sdk, logos-liblogos, logos-delivery }:
let
systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f {
pkgs = import nixpkgs { inherit system; };
logosSdk = logos-cpp-sdk.packages.${system}.default;
logosLiblogos = logos-liblogos.packages.${system}.default;
logosDelivery = (logos-delivery.packages.${system}.liblogosdelivery).overrideAttrs (old: {
NIMFLAGS = (old.NIMFLAGS or "") + " -d:postgres -d:nimDebugDlOpen -d:chronicles_colors:none ";
});
});
in
{
packages = forAllSystems ({ pkgs, logosSdk, logosLiblogos, logosDelivery }:
let
# Common configuration
common = import ./nix/default.nix { inherit pkgs logosSdk logosLiblogos logosDelivery; };
src = ./.;
# Library package (plugin + libcodex)
lib = import ./nix/lib.nix { inherit pkgs common src logosDelivery; };
# Include package (generated headers from plugin)
include = import ./nix/header.nix { inherit pkgs common src lib logosSdk logosDelivery; };
# Combined package
combined = pkgs.symlinkJoin {
name = "logos-delivery-module";
paths = [ lib include ];
};
in
{
# Individual outputs
lib = lib;
include = include;
# Default package (combined)
default = combined;
}
);
devShells = forAllSystems ({ pkgs, logosSdk, logosLiblogos, logosDelivery }: {
default = pkgs.mkShell {
nativeBuildInputs = [
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
];
buildInputs = [
pkgs.qt6.qtbase
pkgs.qt6.qtremoteobjects
];
shellHook = ''
export LOGOS_CPP_SDK_ROOT="${logosSdk}"
export LOGOS_LIBLOGOS_ROOT="${logosLiblogos}"
export LOGOS_DELIVERY_ROOT="${logosDelivery}"
echo "Logos Delivery Module development environment"
echo "LOGOS_CPP_SDK_ROOT: $LOGOS_CPP_SDK_ROOT"
echo "LOGOS_LIBLOGOS_ROOT: $LOGOS_LIBLOGOS_ROOT"
echo "LOGOS_DELIVERY_ROOT: $LOGOS_DELIVERY_ROOT"
'';
};
});
outputs = inputs@{ logos-module-builder, ... }:
logos-module-builder.lib.mkLogosModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
externalLibInputs = {
delivery = inputs.logos-delivery;
};
# TODO: The module builder copies libwaku.h from the flake output instead of
# liblogosdelivery.h from the source. This workaround copies the correct header.
# Should be fixed in logos-module-builder (e.g. header_path in metadata.json).
preConfigure = ''
mkdir -p lib
for f in $(find /nix/store -maxdepth 5 -name "liblogosdelivery.h" 2>/dev/null); do
cp "$f" lib/ 2>/dev/null || true
done
'';
# Bundle runtime libraries alongside the plugin.
postInstall = ''
# Use pkg-config to locate the exact libpq from the build environment
LIBPQ_LIBDIR=$(pkg-config --variable=libdir libpq 2>/dev/null || true)
if [ -n "$LIBPQ_LIBDIR" ] && [ -d "$LIBPQ_LIBDIR" ]; then
for f in "$LIBPQ_LIBDIR"/libpq.*; do
[ -f "$f" ] && cp -L "$f" $out/lib/ 2>/dev/null || true
done
fi
'';
};
}
+34 -19
View File
@@ -1,20 +1,35 @@
{
"name": "delivery_module",
"version": "1.0.0",
"description": "Logos Delivery Module - High-level message-delivery API",
"author": "Logos Core Team",
"type": "core",
"category": "protocol",
"main": "delivery_module_plugin",
"dependencies": [],
"include": [
"liblogosdelivery.so",
"liblogosdelivery.dylib",
"liblogosdelivery.dll",
"libpq.so",
"libpq.so.5",
"libpq.dylib",
"libpq.5.dylib",
"libpq.dll"],
"capabilities": []
}
"name": "delivery_module",
"version": "1.0.0",
"description": "Logos Delivery Module - High-level message-delivery API",
"author": "Logos Core Team",
"type": "core",
"category": "protocol",
"main": "delivery_module_plugin",
"dependencies": [],
"include": [
"liblogosdelivery.so",
"liblogosdelivery.dylib",
"liblogosdelivery.dll",
"libpq.so",
"libpq.so.5",
"libpq.dylib",
"libpq.5.dylib",
"libpq.dll"
],
"capabilities": [],
"nix": {
"packages": {
"runtime": ["postgresql"]
},
"external_libraries": [
{
"name": "delivery",
"build_command": "true"
}
],
"cmake": {
"extra_include_dirs": ["lib"]
}
}
}
-45
View File
@@ -1,45 +0,0 @@
# Common build configuration shared across all packages
{ pkgs, logosSdk, logosLiblogos, logosDelivery }:
{
pname = "logos-delivery-module";
version = "1.0.0";
# Common native build inputs
nativeBuildInputs = [
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
pkgs.qt6.wrapQtAppsNoGuiHook
];
# Common runtime dependencies
buildInputs = [
pkgs.qt6.qtbase
pkgs.qt6.qtremoteobjects
pkgs.postgresql
];
# Common CMake flags
cmakeFlags = [
"-GNinja"
"-DBUILD_TESTING=ON"
"-DLOGOS_CPP_SDK_ROOT=${logosSdk}"
"-DLOGOS_LIBLOGOS_ROOT=${logosLiblogos}"
"-DLOGOS_DELIVERY_ROOT=${logosDelivery}"
"-DLOGOS_MESSAGING_MODULE_USE_VENDOR=OFF"
];
# Environment variables
env = {
LOGOS_CPP_SDK_ROOT = "${logosSdk}";
LOGOS_LIBLOGOS_ROOT = "${logosLiblogos}";
LOGOS_DELIVERY_ROOT = "${logosDelivery}";
};
# Metadata
meta = with pkgs.lib; {
description = "Logos Delivery Module - Provides Logos Delivery communication capabilities";
platforms = platforms.unix;
};
}
-86
View File
@@ -1,86 +0,0 @@
# Generates headers from the messaging module plugin using logos-cpp-generator
{ pkgs, common, src, lib, logosSdk, logosDelivery }:
pkgs.stdenv.mkDerivation {
pname = "${common.pname}-headers";
version = common.version;
inherit src;
inherit (common) meta;
# We need the generator and the built plugin
nativeBuildInputs = [ logosSdk ];
buildInputs = [ pkgs.qt6.qtbase pkgs.qt6.qtremoteobjects ];
# No configure phase needed
dontConfigure = true;
dontWrapQtApps = true;
buildPhase = ''
runHook preBuild
# Create output directory for generated headers
mkdir -p ./generated_headers
# Determine platform-specific library extension
if [ -f "${lib}/lib/delivery_module_plugin.dylib" ]; then
PLUGIN_FILE="${lib}/lib/delivery_module_plugin.dylib"
elif [ -f "${lib}/lib/delivery_module_plugin.so" ]; then
PLUGIN_FILE="${lib}/lib/delivery_module_plugin.so"
else
echo "Error: No delivery_module_plugin library file found"
exit 1
fi
# Set library path so the plugin can find liblogosdelivery when loaded
if [ "$(uname -s)" = "Darwin" ]; then
export DYLD_LIBRARY_PATH="${lib}/lib:''${DYLD_LIBRARY_PATH:-}"
else
export LD_LIBRARY_PATH="${lib}/lib:${pkgs.qt6.qtbase}/lib:${pkgs.qt6.qtremoteobjects}/lib:''${LD_LIBRARY_PATH:-}"
fi
# Run logos-cpp-generator on the built plugin with --module-only flag
echo "Running logos-cpp-generator on $PLUGIN_FILE"
echo "Library path: ${lib}/lib"
ls -la "${lib}/lib"
logos-cpp-generator "$PLUGIN_FILE" --output-dir ./generated_headers --module-only || {
echo "Warning: logos-cpp-generator failed, this may be expected if the module has no public API"
# Create a marker file to indicate attempt was made
touch ./generated_headers/.no-api
}
runHook postBuild
'';
installPhase = ''
runHook preInstall
# Install generated headers
mkdir -p $out/include
# Copy all generated files to include/ if they exist
if [ -d ./generated_headers ] && [ "$(ls -A ./generated_headers 2>/dev/null)" ]; then
echo "Copying generated headers..."
ls -la ./generated_headers
cp -r ./generated_headers/. $out/include/
else
echo "Warning: No generated headers found, creating empty include directory"
# Create a placeholder file to indicate headers should be generated from metadata
echo "# Generated headers from metadata.json" > $out/include/.generated
fi
# Copy header from logos-delivery
echo "Copying header from logos-delivery..."
if [ -d "${logosDelivery}/include" ]; then
echo "Found include directory in logos-delivery"
cp -r "${logosDelivery}/include"/. $out/include/
else
echo "Warning: No include directory found in logos-delivery"
fi
echo "Copied include files:"
ls -la $out/include/
runHook postInstall
'';
}
-148
View File
@@ -1,148 +0,0 @@
# Builds the logos-delivery-module library
{ pkgs, common, src, logosDelivery }:
pkgs.stdenv.mkDerivation {
pname = "${common.pname}-lib";
version = common.version;
inherit src;
inherit (common) nativeBuildInputs buildInputs cmakeFlags meta env;
doCheck = true;
checkPhase = ''
runHook preCheck
ctest --output-on-failure
runHook postCheck
'';
# Determine platform-specific library extension
libdeliveryLib = if pkgs.stdenv.hostPlatform.isDarwin then "liblogosdelivery.dylib" else "liblogosdelivery.so";
libpqPattern = if pkgs.stdenv.hostPlatform.isDarwin then "libpq*.dylib" else "libpq.so*";
postInstall = ''
mkdir -p $out/lib
# Copy libpq from PostgreSQL so it ships with the module runtime libs
for pq in ${pkgs.lib.getLib pkgs.postgresql}/lib/''${libpqPattern}; do
if [ -e "$pq" ]; then
cp -L "$pq" "$out/lib/$(basename "$pq")"
fi
done
# Normalize libpq naming for runtime loaders
if [ -f "$out/lib/libpq.5.dylib" ] && [ ! -e "$out/lib/libpq.dylib" ]; then
ln -s libpq.5.dylib "$out/lib/libpq.dylib"
fi
if [ -f "$out/lib/libpq.so.5" ] && [ ! -e "$out/lib/libpq.so" ]; then
ln -s libpq.so.5 "$out/lib/libpq.so"
fi
# Copy liblogosdelivery directly from the delivery package
if [ -f "${logosDelivery}/bin/''${libdeliveryLib}" ]; then
cp "${logosDelivery}/bin/''${libdeliveryLib}" "$out/lib/''${libdeliveryLib}"
elif [ -f "$out/share/logos-delivery-module/generated/''${libdeliveryLib}" ]; then
cp "$out/share/logos-delivery-module/generated/''${libdeliveryLib}" "$out/lib/''${libdeliveryLib}"
fi
# Fix the install name of liblogosdelivery on macOS
${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isDarwin ''
if [ -f "$out/lib/''${libdeliveryLib}" ]; then
${pkgs.darwin.cctools}/bin/install_name_tool -id "@rpath/''${libdeliveryLib}" "$out/lib/''${libdeliveryLib}"
# Ensure @rpath lookups resolve against the library's own directory
${pkgs.darwin.cctools}/bin/install_name_tool -add_rpath "@loader_path" "$out/lib/''${libdeliveryLib}" 2>/dev/null || true
fi
if [ -f "$out/lib/libpq.dylib" ]; then
${pkgs.darwin.cctools}/bin/install_name_tool -id "@loader_path/libpq.dylib" "$out/lib/libpq.dylib" 2>/dev/null || true
fi
if [ -f "$out/lib/libpq.5.dylib" ]; then
${pkgs.darwin.cctools}/bin/install_name_tool -id "@loader_path/libpq.5.dylib" "$out/lib/libpq.5.dylib" 2>/dev/null || true
fi
''}
# Ensure Linux runtime lookup can find adjacent shared libraries
${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isLinux ''
if [ -f "$out/lib/''${libdeliveryLib}" ]; then
chmod 755 -R $out/lib
${pkgs.patchelf}/bin/patchelf --set-rpath '$ORIGIN' "$out/lib/''${libdeliveryLib}"
${pkgs.patchelf}/bin/patchelf --add-needed libpq.so.5 "$out/lib/''${libdeliveryLib}" 2>/dev/null || true
fi
''}
# Copy the storage module plugin from the installed location
if [ -f "$out/lib/logos/modules/delivery_module_plugin.dylib" ]; then
cp "$out/lib/logos/modules/delivery_module_plugin.dylib" "$out/lib/"
# Fix the plugin's reference to liblogosdelivery on macOS
${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isDarwin ''
# Find what liblogosdelivery path the plugin is referencing and change it to @rpath
for dep in $(${pkgs.darwin.cctools}/bin/otool -L "$out/lib/delivery_module_plugin.dylib" | grep liblogosdelivery | awk '{print $1}'); do
${pkgs.darwin.cctools}/bin/install_name_tool -change "$dep" "@rpath/''${libdeliveryLib}" "$out/lib/delivery_module_plugin.dylib"
done
# Ensure plugin resolves @rpath entries from its own location
${pkgs.darwin.cctools}/bin/install_name_tool -add_rpath "@loader_path" "$out/lib/delivery_module_plugin.dylib" 2>/dev/null || true
# If plugin references libpq directly, make it resolve next to the plugin
for dep in $(${pkgs.darwin.cctools}/bin/otool -L "$out/lib/delivery_module_plugin.dylib" | grep libpq | awk '{print $1}'); do
${pkgs.darwin.cctools}/bin/install_name_tool -change "$dep" "@loader_path/libpq.dylib" "$out/lib/delivery_module_plugin.dylib" 2>/dev/null || true
done
''}
elif [ -f "$out/lib/logos/modules/delivery_module_plugin.so" ]; then
cp "$out/lib/logos/modules/delivery_module_plugin.so" "$out/lib/"
${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isLinux ''
${pkgs.patchelf}/bin/patchelf --set-rpath '$ORIGIN' "$out/lib/delivery_module_plugin.so"
${pkgs.patchelf}/bin/patchelf --add-needed libpq.so.5 "$out/lib/delivery_module_plugin.so" 2>/dev/null || true
''}
else
echo "Error: No delivery_module_plugin library file found"
exit 1
fi
# Remove the nested structure we don't want
rm -rf "$out/lib/logos" 2>/dev/null || true
rm -rf "$out/share" 2>/dev/null || true
# Assert runtime packaging/fixups are in place
if [ ! -e "$out/lib/libpq.dylib" ] && [ ! -e "$out/lib/libpq.so" ] && [ ! -e "$out/lib/libpq.so.5" ]; then
echo "Error: libpq was not packaged into $out/lib"
exit 1
fi
${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isDarwin ''
if [ ! -f "$out/lib/liblogosdelivery.dylib" ]; then
echo "Error: liblogosdelivery.dylib missing"
exit 1
fi
if ! ${pkgs.darwin.cctools}/bin/otool -l "$out/lib/liblogosdelivery.dylib" | grep -A2 LC_RPATH | grep -q '@loader_path'; then
echo "Error: liblogosdelivery.dylib is missing LC_RPATH @loader_path"
exit 1
fi
if [ -f "$out/lib/libpq.dylib" ] && ! ${pkgs.darwin.cctools}/bin/otool -D "$out/lib/libpq.dylib" | grep -q '@loader_path/libpq.dylib'; then
echo "Error: libpq.dylib install id is not @loader_path/libpq.dylib"
exit 1
fi
''}
${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isLinux ''
if [ ! -f "$out/lib/liblogosdelivery.so" ]; then
echo "Error: liblogosdelivery.so missing"
exit 1
fi
if [ "$(${pkgs.patchelf}/bin/patchelf --print-rpath "$out/lib/liblogosdelivery.so")" != '$ORIGIN' ]; then
echo "Error: liblogosdelivery.so rpath is not $ORIGIN"
exit 1
fi
if ! ${pkgs.patchelf}/bin/patchelf --print-needed "$out/lib/liblogosdelivery.so" | grep -q '^libpq\.so\.5$'; then
echo "Error: liblogosdelivery.so is missing NEEDED libpq.so.5"
exit 1
fi
''}
'';
}
+5 -3
View File
@@ -2,7 +2,7 @@
#include <QtCore/QObject>
#include "interface.h"
#include "QExpected.h"
#include "logos_types.h"
class DeliveryModuleInterface : public PluginInterface
{
@@ -11,12 +11,14 @@ public:
Q_INVOKABLE virtual bool createNode(const QString &cfg) = 0;
Q_INVOKABLE virtual bool start() = 0;
Q_INVOKABLE virtual bool stop() = 0;
Q_INVOKABLE virtual QExpected<QString> send(const QString &contentTopic, const QString &payload) = 0;
Q_INVOKABLE virtual LogosResult send(const QString &contentTopic, const QString &payload) = 0;
Q_INVOKABLE virtual bool subscribe(const QString &contentTopic) = 0;
Q_INVOKABLE virtual bool unsubscribe(const QString &contentTopic) = 0;
Q_INVOKABLE virtual QString getAvailableNodeInfoIDs() = 0;
Q_INVOKABLE virtual QString getNodeInfo(const QString &nodeInfoId) = 0;
Q_INVOKABLE virtual QString getAvailableConfigs() = 0;
signals:
// for now this is required for events, later it might not be necessary if using a proxy
void eventResponse(const QString& eventName, const QVariantList& data);
};
+109 -8
View File
@@ -6,6 +6,7 @@
#include <QJsonObject>
#include <memory>
#include <mutex>
#include <QJsonArray>
#include <semaphore>
#include <unordered_map>
@@ -104,6 +105,37 @@ void DeliveryModulePlugin::event_callback(int callerRet, const char* msg, size_t
eventData << timestamp;
plugin->emitEvent("messagePropagated", eventData);
} else if (eventType == "message_received") {
// MessageReceivedEvent: messageHash, message (WakuMessage)
QJsonObject msgObj = jsonObj["message"].toObject();
QVariantList eventData;
eventData << jsonObj["messageHash"].toString();
eventData << msgObj["contentTopic"].toString();
// The waku API returns payload as a JSON byte array (e.g. [106,106,106,106]).
// Convert it to a base64 string to match the documented event contract.
QJsonValue payloadValue = msgObj["payload"];
if (payloadValue.isArray()) {
QJsonArray payloadArray = payloadValue.toArray();
QByteArray payloadBytes;
payloadBytes.reserve(payloadArray.size());
for (const QJsonValue& val : payloadArray) {
payloadBytes.append(static_cast<char>(val.toInt()));
}
eventData << QString::fromLatin1(payloadBytes.toBase64());
} else {
eventData << payloadValue.toString();
}
eventData << QString::number(msgObj["timestamp"].toDouble(), 'f', 0);
plugin->emitEvent("messageReceived", eventData);
} else if (eventType == "connection_status_change") {
QVariantList eventData;
eventData << jsonObj["connectionStatus"].toString();
eventData << timestamp;
plugin->emitEvent("connectionStateChanged", eventData);
} else {
qWarning() << "DeliveryModulePlugin::event_callback: Unknown event type:" << eventType;
}
@@ -263,26 +295,26 @@ bool DeliveryModulePlugin::stop()
qDebug() << "DeliveryModulePlugin: Messaging stop completed with success: true";
return true;
}
QExpected<QString> DeliveryModulePlugin::send(const QString &contentTopic, const QString &payload)
LogosResult DeliveryModulePlugin::send(const QString &contentTopic, const QString &payload)
{
qDebug() << "DeliveryModulePlugin::send called with contentTopic:" << contentTopic;
qDebug() << "DeliveryModulePlugin::send payload:" << payload;
if (!deliveryCtx) {
qWarning() << "DeliveryModulePlugin: Cannot send message - context not initialized. Call createNode first.";
return QExpected<QString>::err("Context not initialized");
return {false, QVariant(), QStringLiteral("Context not initialized")};
}
// Construct JSON message according to logosdelivery_send API
// The payload should be base64-encoded as per the API spec
QJsonObject messageObj;
messageObj["contentTopic"] = contentTopic;
messageObj["payload"] = QString::fromUtf8(payload.toUtf8().toBase64());
messageObj["ephemeral"] = false;
QJsonDocument doc(messageObj);
QByteArray messageJson = doc.toJson(QJsonDocument::Compact);
auto outcome = callApiRetValue<QString>(
"send",
CALLBACK_TIMEOUT,
@@ -290,12 +322,12 @@ QExpected<QString> DeliveryModulePlugin::send(const QString &contentTopic, const
if (outcome.isErr()) {
qWarning() << "DeliveryModulePlugin: Send failed for topic:" << contentTopic << ", reason:" << outcome.error();
return QExpected<QString>::err(outcome.error());
return {false, QVariant(), outcome.error()};
}
const QString responseMessage = outcome.value();
qDebug() << "DeliveryModulePlugin: Send initiated for topic:" << contentTopic << ", with success: true";
return QExpected<QString>::ok(responseMessage);
return {true, responseMessage};
}
bool DeliveryModulePlugin::subscribe(const QString &contentTopic)
@@ -349,3 +381,72 @@ bool DeliveryModulePlugin::unsubscribe(const QString &contentTopic)
qDebug() << "DeliveryModulePlugin: Unsubscribe completed for topic:" << contentTopic << " with success: true";
return true;
}
QString DeliveryModulePlugin::version() const {
QString moduleVersion = "1.0.0";
if (!deliveryCtx) {
qWarning() << "DeliveryModulePlugin: Cannot subscribe - context not initialized. Call createNode first.";
return moduleVersion + " (liblogosdelivery version unknown, context not initialized)";
}
auto attributeName = "Version";
auto liblogosDeliveryVersion = callApiRetValue<QString>(
"get_node_info",
CALLBACK_TIMEOUT,
bindApiCall(logosdelivery_get_node_info, deliveryCtx, attributeName));
if (liblogosDeliveryVersion.isErr()) {
qWarning() << "DeliveryModulePlugin: Get node info failed getting version, reason:" <<
liblogosDeliveryVersion.error();
return moduleVersion + " (liblogosdelivery version unknown)";
}
const QString version = liblogosDeliveryVersion.value();
qDebug() << "DeliveryModulePlugin: Get node info completed for attribute:" <<
attributeName << ", with success: " << version;
return moduleVersion + " (liblogosdelivery version: " + version + ")";
}
QString DeliveryModulePlugin::getAvailableNodeInfoIDs() {
auto outcome = callApiRetValue<QString>(
"get_available_node_info_ids",
CALLBACK_TIMEOUT,
bindApiCall(logosdelivery_get_available_node_info_ids, deliveryCtx));
if (outcome.isErr()) {
qWarning() << "DeliveryModulePlugin: Get available node info IDs failed, reason:" << outcome.error();
return QString();
}
return outcome.value();
}
QString DeliveryModulePlugin::getNodeInfo(const QString &nodeInfoId) {
auto outcome = callApiRetValue<QString>(
"get_node_info",
CALLBACK_TIMEOUT,
bindApiCall(logosdelivery_get_node_info, deliveryCtx, nodeInfoId.toUtf8().constData()));
if (outcome.isErr()) {
qWarning() << "DeliveryModulePlugin: Get node info failed for ID:" << nodeInfoId <<
", reason:" << outcome.error();
return QString();
}
return outcome.value();
}
QString DeliveryModulePlugin::getAvailableConfigs() {
auto outcome = callApiRetValue<QString>(
"get_available_configs",
CALLBACK_TIMEOUT,
bindApiCall(logosdelivery_get_available_configs, deliveryCtx));
if (outcome.isErr()) {
qWarning() << "DeliveryModulePlugin: Get available configs failed, reason:" << outcome.error();
return QString();
}
return outcome.value();
}
+223 -13
View File
@@ -7,42 +7,252 @@
#include "logos_api.h"
#include "logos_api_client.h"
/**
* @brief Concrete Qt plugin implementing the delivery messaging module.
*
* This class adapts the host plugin API to liblogosdelivery C-FFI calls and
* forwards asynchronous events back to the host through Logos API clients.
*
* Lifecycle contract:
* - call @ref createNode exactly once per context
* - call @ref start before message operations
* - use @ref subscribe / @ref send / @ref unsubscribe as needed
* - call @ref stop before shutdown
* Notice all of these calls are synchronous.
*
* Asynchronous events are emitted off thread as Logos Plugin events.
* Emitted plugin event contracts (name + `QVariantList data` indices):
* - `messageSent` (see `send` method)
* - `data[0]` (`QString`): request id
* - `data[1]` (`QString`): message hash
* - `data[2]` (`QString`): local timestamp (ISO-8601)
* - `messageError` (see `send` method)
* - `data[0]` (`QString`): request id
* - `data[1]` (`QString`): message hash
* - `data[2]` (`QString`): error message
* - `data[3]` (`QString`): local timestamp (ISO-8601)
* - `messagePropagated` (see `send` method)
* - `data[0]` (`QString`): request id
* - `data[1]` (`QString`): message hash
* - `data[2]` (`QString`): local timestamp (ISO-8601)
* - `messageReceived` (emitted when a message arrives on a subscribed topic)
* - `data[0]` (`QString`): message hash
* - `data[1]` (`QString`): content topic
* - `data[2]` (`QString`): payload (base64-encoded)
* - `data[3]` (`QString`): timestamp (nanoseconds since epoch)
* - `connectionStateChanged`
* - `data[0]` (`QString`): connection status
* - `data[1]` (`QString`): local timestamp (ISO-8601)
*
* The raw FFI `eventType` values mapped into these plugin events are:
* - `message_sent` -> `messageSent`
* - `message_error` -> `messageError`
* - `message_propagated` -> `messagePropagated`
* - `message_received` -> `messageReceived`
* - `connection_status_change` -> `connectionStateChanged`
*
* As a general concept consider using proper content_topic format for your purpose.
* --> https://lip.logos.co/messaging/informational/23/topics.html#content-topics
*/
class DeliveryModulePlugin : public QObject, public DeliveryModuleInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID DeliveryModuleInterface_iid FILE "../metadata.json")
Q_PLUGIN_METADATA(IID DeliveryModuleInterface_iid FILE "metadata.json")
Q_INTERFACES(DeliveryModuleInterface PluginInterface)
public:
/**
* @brief Constructs the plugin with no active delivery context.
*/
DeliveryModulePlugin();
/**
* @brief Destroys the plugin and releases owned resources.
*
* If present, the owned `LogosAPI` instance is deleted and the underlying
* liblogosdelivery context is destroyed.
*/
virtual ~DeliveryModulePlugin();
/**
* @brief Creates a liblogosdelivery node from a WakuNodeConf JSON document.
*
* The JSON is parsed by logos-delivery (liblogosdelivery folder) side and maps to
* `WakuNodeConf` from `tools/confutils/cli_args.nim`
* (https://github.com/logos-messaging/logos-delivery).
*
* The configuration is a **flat** JSON object whose keys correspond to
* `WakuNodeConf` Nim field names (camelCase). Unknown keys are silently
* ignored. Every field has a built-in default, so only the values that
* differ from defaults need to be supplied.
*
* ## Commonly used keys
* | Key | Type | Default | Description |
* |----------------------|------------------|------------|---------------------------------------------|
* | `mode` | string | `"noMode"` | `"Core"`, `"Edge"`, or `"noMode"` |
* | `preset` | string | `""` | Network preset (`"twn"`, `"logos.dev"`, …) |
* | `clusterId` | number (uint16) | `0` | Cluster identifier |
* | `entryNodes` | array of string | `[]` | Bootstrap peers (enrtree / multiaddress) |
* | `relay` | boolean | `false` | Enable relay protocol |
* | `rlnRelay` | boolean | `false` | Enable RLN rate-limit nullifier |
* | `tcpPort` | number (uint16) | `60000` | P2P TCP listen port |
* | `numShardsInNetwork` | number (uint16) | `1` | Auto-sharding shard count |
* | `logLevel` | string | `"INFO"` | `"TRACE"`, `"DEBUG"`, `"INFO"`, `"WARN"`, … |
* | `logFormat` | string | `"TEXT"` | `"TEXT"` or `"JSON"` |
* | `maxMessageSize` | string | `"150KiB"` | Maximum message payload size |
*
* ## Presets
* Using a `preset` populates cluster ID, entry nodes, sharding, RLN, and
* other network-specific defaults automatically. Individual keys supplied
* alongside a preset override the preset values.
* - `"twn"` The RLN-protected Waku Network (cluster 1).
* - `"logos.dev"` Logos Dev Network (cluster 2, mix enabled,
* p2pReliability on, 8 auto-shards, built-in bootstrap nodes).
*
* Minimal `logos.dev` example:
* @code{.json}
* {
* "logLevel": "INFO",
* "mode": "Core",
* "preset": "logos.dev"
* }
* @endcode
*
* Full override example:
* @code{.json}
* {
* "mode": "Core",
* "clusterId": 42,
* "entryNodes": ["enrtree://TREE@nodes.example.com"],
* "relay": true,
* "tcpPort": 60000,
* "numShardsInNetwork": 8,
* "maxMessageSize": "150KiB",
* "logLevel": "INFO",
* "logFormat": "TEXT"
* }
* @endcode
*
* @param cfg UTF-16 Qt string containing a UTF-8 serializable JSON payload.
* @return `true` if context creation succeeds and callback returns `RET_OK`,
* otherwise `false`.
*/
Q_INVOKABLE bool createNode(const QString &cfg) override;
Q_INVOKABLE bool start() override;
Q_INVOKABLE bool stop() override;
Q_INVOKABLE QExpected<QString> send(const QString &contentTopic, const QString &payload) override;
Q_INVOKABLE bool subscribe(const QString &contentTopic) override;
Q_INVOKABLE bool unsubscribe(const QString &contentTopic) override;
QString name() const override { return "delivery_module"; }
QString version() const override { return "1.0.0"; }
// LogosAPI initialization
/**
* @brief Starts the delivery node.
* @return `true` on success; `false` when no context exists or start fails.
*/
Q_INVOKABLE bool start() override;
/**
* @brief Stops the delivery node.
* @return `true` on success; `false` when no context exists or stop fails.
*/
Q_INVOKABLE bool stop() override;
/**
* @brief Sends a message over the active node.
*
* This method builds a JSON envelope expected by `logosdelivery_send`:
* `{ "contentTopic": string, "payload": base64, "ephemeral": (bool, default: false) }`.
*
* `send` call validates the input and returns with an associated requestId.
* After all the exact send operation is done async and user can expect Message events in response
* The requestId helps keep track of send operation results.
* - `messageError` emitted in case module can't sent the message
* - `messagePropagated` emitted if message has hit the network, you can expect delivery but
* module could not validate it yet.
* - `messageSent` emitted after the sent message is validated by the network.
*
* @param contentTopic Destination content topic.
* @param payload Raw message bytes represented as QString; converted to UTF-8
* bytes and base64-encoded before crossing the FFI boundary.
* @return Success with request id, or error details.
*/
Q_INVOKABLE LogosResult send(const QString &contentTopic, const QString &payload) override;
/**
* @brief Subscribes to the supplied content topic.
* @param contentTopic Topic identifier.
* @return `true` when subscribed successfully, otherwise `false`.
*/
Q_INVOKABLE bool subscribe(const QString &contentTopic) override;
/**
* @brief Unsubscribes from the supplied content topic.
* @param contentTopic Topic identifier.
* @return `true` when unsubscribed successfully, otherwise `false`.
*/
Q_INVOKABLE bool unsubscribe(const QString &contentTopic) override;
Q_INVOKABLE QString getAvailableNodeInfoIDs() override;
/**
* @brief Semantic version of this plugin implementation.
* @param nodeInfoId Identifier for the requested node info item.
* @return UTF-16 string containing UTF-8 serializable JSON data, or an empty string on error.
*/
Q_INVOKABLE QString getNodeInfo(const QString &nodeInfoId) override;
/**
* @brief Information about the available configuration parameters to be used in `createNode`.
*/
Q_INVOKABLE QString getAvailableConfigs() override;
QString name() const override { return "delivery_module"; }
QString version() const;
/**
* @brief Injects/replaces the Logos API bridge used for event forwarding.
*
* Ownership is transferred to this plugin instance.
*
* @param logosAPIInstance Heap-allocated API object or `nullptr`.
*/
Q_INVOKABLE void initLogos(LogosAPI* logosAPIInstance);
signals:
// for now this is required for events, later it might not be necessary if using a proxy
/**
* @brief Module event signal (currently retained for compatibility).
* @param eventName Event identifier.
* @param data Event payload as positional values.
*/
void eventResponse(const QString& eventName, const QVariantList& data);
private:
/**
* @brief Opaque liblogosdelivery context pointer.
*/
void* deliveryCtx;
/**
* @brief Serializes node creation to a single in-flight operation.
*/
std::mutex createNodeMutex;
// Timeout for callback operations
/**
* @brief Common timeout for FFI operations that complete via callback.
*/
static constexpr std::chrono::seconds CALLBACK_TIMEOUT{30};
// Helper method for emitting events
/**
* @brief Forwards normalized events to the registered Logos API client.
* @param eventName Canonical event name.
* @param data Event payload list.
*/
void emitEvent(const QString& eventName, const QVariantList& data);
// Static callback functions for liblogosdelivery
/**
* @brief Global C callback used by liblogosdelivery to report async events.
*
* Expected event payload format is a JSON document containing an `eventType`
* discriminator and event-specific fields.
*
* @param callerRet FFI return code associated with callback dispatch.
* @param msg UTF-8 JSON event payload buffer.
* @param len Message length in bytes.
* @param userData Opaque pointer expected to be `DeliveryModulePlugin*`.
*/
static void event_callback(int callerRet, const char* msg, size_t len, void* userData);
};
Submodule vendor/logos-cpp-sdk deleted from 30ef7986f4
Submodule vendor/logos-delivery deleted from 8e41a27ad2
Submodule vendor/logos-liblogos deleted from e3741c01fd