392 lines
11 KiB
Markdown

# 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:
```c
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.
```c
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:**
```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.
```c
int logosdelivery_start_node(void *ctx, LogosDeliveryScalarRawFn callback, void *userData);
```
#### `logosdelivery_stop_node`
Stops the node.
```c
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.
```c
int logosdelivery_destroy(void *ctx);
```
### Messaging
#### `logosdelivery_subscribe`
Subscribe to a content topic to receive messages.
```c
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.
```c
typedef struct { const char *contentTopicStr; } UnsubscribeReq;
int logosdelivery_unsubscribe(
void *ctx,
LogosDeliveryUnsubscribeReplyFn onReply,
void *userData,
const UnsubscribeReq *req
);
```
#### `logosdelivery_send`
Send a message.
```c
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:**
```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).
```c
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.
```c
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
```bash
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.
```c
// 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
```c
#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/`