11 KiB

Logos Messaging API (LMAPI) Library

A C FFI library providing a simplified interface to Logos Messaging functionality.

Overview

This library wraps the high-level API functions from waku/api/api.nim and exposes them via a C FFI interface, making them accessible from C, C++, and other languages that support C FFI.

The call surface is generated by nim-ffi from the {.ffi.} annotations in library/*.nim. make liblogosdelivery writes it to library/generated/logosdelivery.h on every build, so it can never drift from the Nim signatures. It is a build artifact and is not checked in: build the library before you compile anything against it.

Include library/liblogosdelivery.h, which pulls in the generated header and adds the event-listener ABI.

Every entry point takes the context handle (void *ctx) first, except the constructor. The rest of the signature depends on the call:

  • No-argument calls (start_node, stop_node, get_available_configs, get_available_node_info_ids) take a raw LogosDeliveryScalarRawFn: (void *ctx, LogosDeliveryScalarRawFn cb, void *userData).
  • Argument-taking calls (subscribe, unsubscribe, send, get_node_info) take a per-call LogosDelivery<Name>ReplyFn and pass their arguments last, in a request struct: (void *ctx, LogosDelivery<Name>ReplyFn onReply, void *userData, const <Name>Req *req).

The generator emits one reply typedef per call (e.g. LogosDeliverySubscribeReplyFn), all with the same shape:

typedef void (*LogosDeliveryScalarRawFn)(int callerRet, char *msg, size_t len, void *userData);
typedef void (*LogosDeliverySubscribeReplyFn)(int errCode, const char *reply, const char *errMsg, void *userData);

reply, errMsg and msg are borrowed: copy them if you need them after the callback returns.

API Functions

Node Lifecycle

logosdelivery_create_node

Creates a node from the given configuration JSON.

typedef struct { const char *configJson; } CreateNodeCtorReq;

typedef void (*LogosDeliveryCreateRawFn)(
    int errCode,
    const char *ctxAddr,   // context address as decimal text, on success
    const char *errMsg,
    void *userData
);

void *logosdelivery_create_node(
    const CreateNodeCtorReq *req,
    LogosDeliveryCreateRawFn onCreated,
    void *userData
);

Parameters:

  • req->configJson: JSON string containing node configuration
  • onCreated: Callback that receives the terminal result
  • userData: User data passed to the callback

Returns: the context handle, or NULL on failure. Creation is asynchronous: wait for onCreated before you make any other call.

Example configuration JSON:

{
  "mode": "Core",
  "preset": "logos.dev",
  "messagingOverrides": {
    "listen-address": "0.0.0.0",
    "tcp-port": 60000,
    "discv5-udp-port": 9000
  }
}

The configuration object has four optional top-level keys: mode ("Core" or "Edge", defaults to "Core"), preset, messagingOverrides (per-field node config overrides), and channelsOverrides (reliable-channel overrides). Override keys accept the config field name or its CLI switch name (e.g. "clusterId" or "cluster-id"); unknown keys are rejected. Use "preset" to select a network preset (e.g., "twn", "logos.dev", "status.prod") which auto-configures entry nodes, cluster ID, sharding, and other network-specific settings.

Available presets:

Preset Cluster ID RLN Sharding Network
twn 1 on auto (8 shards) The Waku Network
logos.dev 2 off auto (8 shards) Logos Dev Network
logos.test 2 off auto (8 shards) Logos Test Network
status.prod 16 off auto (1 shard) Status Production Network

logosdelivery_start_node

Starts the node.

int logosdelivery_start_node(void *ctx, LogosDeliveryScalarRawFn callback, void *userData);

logosdelivery_stop_node

Stops the node.

int logosdelivery_stop_node(void *ctx, LogosDeliveryScalarRawFn callback, void *userData);

logosdelivery_destroy

Destroys a node instance and frees resources. This call is synchronous; do not use ctx afterwards.

int logosdelivery_destroy(void *ctx);

Messaging

logosdelivery_subscribe

Subscribe to a content topic to receive messages.

typedef struct { const char *contentTopicStr; } SubscribeReq;

int logosdelivery_subscribe(
    void *ctx,
    LogosDeliverySubscribeReplyFn onReply,
    void *userData,
    const SubscribeReq *req
);

Parameters:

  • ctx: Context handle returned by logosdelivery_create_node
  • req->contentTopicStr: Content topic string (e.g., "/myapp/1/chat/proto")
  • onReply: Callback function to receive the result
  • userData: User data passed to the callback

logosdelivery_unsubscribe

Unsubscribe from a content topic.

typedef struct { const char *contentTopicStr; } UnsubscribeReq;

int logosdelivery_unsubscribe(
    void *ctx,
    LogosDeliveryUnsubscribeReplyFn onReply,
    void *userData,
    const UnsubscribeReq *req
);

logosdelivery_send

Send a message.

typedef struct { const char *messageJson; } SendReq;

int logosdelivery_send(
    void *ctx,
    LogosDeliverySendReplyFn onReply,
    void *userData,
    const SendReq *req
);

Parameters:

  • req->messageJson: JSON string containing the message

Example message JSON:

{
  "contentTopic": "/myapp/1/chat/proto",
  "payload": "SGVsbG8gV29ybGQ=",
  "ephemeral": false
}

Note: The payload field should be base64-encoded.

Returns: Request ID in the callback message that can be used to track message delivery.

Events

Events are delivered through a per-event listener registry: register one callback per event name you care about. A registration returns a listener id you can later pass to remove it.

logosdelivery_add_event_listener

Registers callback for the named event and returns a non-zero listener id (0 on an invalid context).

uint64_t logosdelivery_add_event_listener(
    void *ctx,
    const char *eventName,
    FFICallBack callback,
    void *userData
);

Event names: onMessageSent, onMessageError, onMessagePropagated, onMessageReceived, onConnectionStatusChange, onTopicHealthChange, onConnectionChange, onReceivedMessage, onChannelMessageReceived, onChannelMessageSent, onChannelMessageError.

logosdelivery_remove_event_listener

Removes a previously registered listener. Returns 0 on success, 1 if the listener id was not found or the context is invalid.

int logosdelivery_remove_event_listener(
    void *ctx,
    uint64_t listenerId
);

Important: Callbacks run on a dedicated event thread and should be fast, non-blocking, and thread-safe.

Building

The library follows the same build system as the main Logos Messaging project.

Build the library

make liblogosdeliveryStatic    # Build static library
# or
make liblogosdeliveryDynamic   # Build dynamic library

Return Codes

All functions that return int use the following return codes:

  • NIMFFI_RET_OK / RET_OK (0): Success
  • NIMFFI_RET_ERR / RET_ERR (1): Error
  • NIMFFI_RET_MISSING_CALLBACK / RET_MISSING_CALLBACK (2): Missing callback function
  • NIMFFI_RET_STALE_WARN (3): Non-terminal progress tick, always followed by a terminal code. Ignore it unless you want progress.

Callback Functions

Results come back through one of four callback shapes. The generated names carry the library prefix (LogosDelivery); the reply typedef is emitted once per call.

// Argument-taking calls: one typedef per call, all this shape.
typedef void (*LogosDeliverySubscribeReplyFn)(
    int errCode,
    const char *reply,
    const char *errMsg,
    void *userData
);

// No-argument calls (start/stop/get_available_*).
typedef void (*LogosDeliveryScalarRawFn)(
    int callerRet,
    char *msg,
    size_t len,
    void *userData
);

// Constructor.
typedef void (*LogosDeliveryCreateRawFn)(
    int errCode,
    const char *ctxAddr,
    const char *errMsg,
    void *userData
);

// Event listeners (declared by liblogosdelivery.h, not the generated header).
typedef void (*FFICallBack)(
    int callerRet,
    const char *msg,
    size_t len,
    void *userData
);
  • Reply typedefs (LogosDelivery<Name>ReplyFn): reply is the result on success (NUL-terminated, may be empty); errMsg is the message on failure.
  • LogosDeliveryScalarRawFn and FFICallBack: msg holds len bytes and is not NUL-terminated.
  • LogosDeliveryCreateRawFn: ctxAddr is the context address as decimal text on success.

All of these strings are borrowed and valid only for the duration of the call. Copy them if you need them afterwards.

Example Usage

#include "liblogosdelivery.h"
#include <stdio.h>
#include <unistd.h>

static volatile int created = -1;
static void *node = NULL;

// The argument-taking calls share this reply shape.
void on_reply(int ret, const char *reply, const char *errMsg, void *userData) {
    if (ret == RET_OK) {
        printf("Success: %s\n", reply ? reply : "");
    } else {
        printf("Error: %s\n", errMsg ? errMsg : "unknown error");
    }
}

// The no-argument calls (start/stop) take the raw callback.
void on_scalar(int ret, char *msg, size_t len, void *userData) {
    if (ret == RET_STALE_WARN) return;  // progress tick, ignore
    printf("%.*s\n", (int)len, msg ? msg : "");
}

void on_created(int ret, const char *ctxAddr, const char *errMsg, void *userData) {
    created = (ret == RET_OK);
}

int main() {
    const char *config = "{"
        "\"mode\": \"Core\","
        "\"preset\": \"logos.dev\""
        "}";

    // Create the node. The return value is the context handle; wait for
    // on_created before making any other call.
    CreateNodeCtorReq createReq = { .configJson = config };
    node = logosdelivery_create_node(&createReq, on_created, NULL);
    for (int i = 0; i < 100 && created == -1; i++) {
        usleep(100000);
    }
    if (created != 1 || node == NULL) {
        return 1;
    }

    // Start node
    logosdelivery_start_node(node, on_scalar, NULL);

    // Subscribe to a topic
    SubscribeReq subReq = { .contentTopicStr = "/myapp/1/chat/proto" };
    logosdelivery_subscribe(node, on_reply, NULL, &subReq);

    // Send a message
    const char *msg = "{"
        "\"contentTopic\": \"/myapp/1/chat/proto\","
        "\"payload\": \"SGVsbG8gV29ybGQ=\","
        "\"ephemeral\": false"
        "}";
    SendReq sendReq = { .messageJson = msg };
    logosdelivery_send(node, on_reply, NULL, &sendReq);

    // Clean up. logosdelivery_destroy is synchronous.
    logosdelivery_stop_node(node, on_scalar, NULL);
    logosdelivery_destroy(node);

    return 0;
}

Architecture

The library is structured as follows:

  • liblogosdelivery.h: Public C header; includes the generated header and adds the event ABI
  • generated/logosdelivery.h: Generated call surface, emitted by make liblogosdelivery (not checked in)
  • liblogosdelivery.nim: Main library entry point
  • declare_lib.nim: Library declaration and initialization
  • logos_delivery_api/node_api.nim: Node lifecycle API implementation
  • logos_delivery_api/messaging_api.nim: Subscribe/send API implementation

The library uses the nim-ffi framework for FFI infrastructure, which handles:

  • Thread-safe request processing
  • Async operation management
  • Memory management between C and Nim
  • Callback marshaling

See Also

  • Main API documentation: waku/api/api.nim
  • Original libwaku library: library/libwaku.nim
  • nim-ffi framework: vendor/nim-ffi/