Adjust liblmapi example

This commit is contained in:
NagyZoltanPeter 2026-02-05 16:23:34 +01:00
parent 10367d9c81
commit 936e36ada1
No known key found for this signature in database
GPG Key ID: 3E1F97CF4A7B6F42
7 changed files with 862 additions and 17 deletions

View File

@ -433,7 +433,7 @@ docker-liteprotocoltester-push:
################
## C Bindings ##
################
.PHONY: cbindings cwaku_example libwaku liblmapi
.PHONY: cbindings cwaku_example libwaku liblmapi liblmapi_example
STATIC ?= 0
LIBWAKU_BUILD_COMMAND ?= libwakuDynamic
@ -463,6 +463,31 @@ libwaku: | build deps librln
liblmapi: | build deps librln
echo -e $(BUILD_MSG) "build/$@.$(LIB_EXT)" && $(ENV_SCRIPT) nim $(LMAPI_BUILD_COMMAND) $(NIM_PARAMS) waku.nims $@.$(LIB_EXT)
liblmapi_example: | build liblmapi
@echo -e $(BUILD_MSG) "build/$@"
ifeq ($(detected_OS),Darwin)
gcc -o build/$@ \
liblmapi/examples/liblmapi_example.c \
-I./liblmapi \
-L./build \
-llmapi \
-Wl,-rpath,./build
else ifeq ($(detected_OS),Linux)
gcc -o build/$@ \
liblmapi/examples/liblmapi_example.c \
-I./liblmapi \
-L./build \
-llmapi \
-Wl,-rpath,'$$ORIGIN'
else ifeq ($(detected_OS),Windows)
gcc -o build/$@.exe \
liblmapi/examples/liblmapi_example.c \
-I./liblmapi \
-L./build \
-llmapi \
-lws2_32
endif
#####################
## Mobile Bindings ##
#####################

165
liblmapi/BUILD.md Normal file
View File

@ -0,0 +1,165 @@
# Building liblmapi and Examples
## Prerequisites
- Nim 2.x compiler
- Rust toolchain (for RLN dependencies)
- GCC or Clang compiler
- Make
## Building the Library
### Dynamic Library
```bash
make liblmapi
```
This creates `build/liblmapi.dylib` (macOS) or `build/liblmapi.so` (Linux).
### Static Library
```bash
nim liblmapiStatic
```
This creates `build/liblmapi.a`.
## Building Examples
### liblmapi Example
Compile the C example that demonstrates all library features:
```bash
# Using Make (recommended)
make liblmapi_example
# Or manually on macOS:
gcc -o build/liblmapi_example \
liblmapi/examples/liblmapi_example.c \
-I./liblmapi \
-L./build \
-llmapi \
-Wl,-rpath,./build
# Or manually on Linux:
gcc -o build/liblmapi_example \
liblmapi/examples/liblmapi_example.c \
-I./liblmapi \
-L./build \
-llmapi \
-Wl,-rpath='$ORIGIN'
```
## Running Examples
```bash
./build/liblmapi_example
```
The example will:
1. Create a Logos Messaging node
2. Register event callbacks for message events
3. Start the node
4. Subscribe to a content topic
5. Send a message
6. Show message delivery events (sent, propagated, or error)
7. Unsubscribe and cleanup
## Build Artifacts
After building, you'll have:
```
build/
├── liblmapi.dylib # Dynamic library (34MB)
├── liblmapi.dylib.dSYM/ # Debug symbols
└── liblmapi_example # Compiled example (34KB)
```
## Library Headers
The main header file is:
- `liblmapi/liblmapi.h` - C API declarations
## Troubleshooting
### Library not found at runtime
If you get "library not found" errors when running the example:
**macOS:**
```bash
export DYLD_LIBRARY_PATH=/path/to/build:$DYLD_LIBRARY_PATH
./build/liblmapi_example
```
**Linux:**
```bash
export LD_LIBRARY_PATH=/path/to/build:$LD_LIBRARY_PATH
./build/liblmapi_example
```
### Compilation fails
Make sure you've run:
```bash
make update
```
This updates all git submodules which are required for building.
## Static Linking
To link statically instead of dynamically:
```bash
gcc -o build/simple_example \
liblmapi/examples/simple_example.c \
-I./liblmapi \
build/liblmapi.a \
-lm -lpthread
```
Note: Static library is much larger (~129MB) but creates a standalone executable.
## Cross-Compilation
For cross-compilation, you need to:
1. Build the Nim library for the target platform
2. Use the appropriate cross-compiler
3. Link against the target platform's liblmapi
Example for Linux from macOS:
```bash
# Build library for Linux (requires Docker or cross-compilation setup)
# Then compile with cross-compiler
```
## Integration with Your Project
### CMake
```cmake
find_library(LMAPI_LIBRARY NAMES lmapi PATHS ${PROJECT_SOURCE_DIR}/build)
include_directories(${PROJECT_SOURCE_DIR}/liblmapi)
target_link_libraries(your_target ${LMAPI_LIBRARY})
```
### Makefile
```makefile
CFLAGS += -I/path/to/liblmapi
LDFLAGS += -L/path/to/build -llmapi -Wl,-rpath,/path/to/build
your_program: your_program.c
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
```
## API Documentation
See:
- [liblmapi.h](liblmapi/liblmapi.h) - API function declarations
- [MESSAGE_EVENTS.md](liblmapi/MESSAGE_EVENTS.md) - Message event handling guide
- [IMPLEMENTATION_SUMMARY.md](liblmapi/IMPLEMENTATION_SUMMARY.md) - Implementation details

View File

@ -0,0 +1,155 @@
# liblmapi - Summary
## Overview
Created a new C FFI library `liblmapi` (Logos Messaging API) that provides a simplified interface to the high-level API functions from `waku/api/api.nim`.
## Files Created
### Core Library Files
1. **liblmapi/liblmapi.h** - C header file
- Defines C FFI interface
- Includes function declarations for node lifecycle and messaging
- Return codes and callback typedef
2. **liblmapi/liblmapi.nim** - Main library entry point
- Imports and includes API modules
- Exports `lmapi_destroy` function
- Follows same pattern as `library/libwaku.nim`
3. **liblmapi/declare_lib.nim** - Library declaration
- Uses `declareLibrary("lmapi")` macro
- Exports `lmapi_set_event_callback` function
4. **liblmapi/nim.cfg** - Nim configuration
- Sets up compiler flags (gc:refc, threads:on)
- Configures paths for nim-ffi and project root
- Library-specific build settings
### API Implementation Files
5. **liblmapi/lmapi/node_api.nim** - Node lifecycle API
- `lmapi_create_node` - Creates node from JSON config
- `lmapi_start_node` - Starts the node
- `lmapi_stop_node` - Stops the node
- Uses `registerReqFFI` macro for CreateNodeRequest
6. **liblmapi/lmapi/messaging_api.nim** - Messaging API
- `lmapi_subscribe` - Subscribe to content topic
- `lmapi_unsubscribe` - Unsubscribe from content topic
- `lmapi_send` - Send message with base64-encoded payload
- All use `{.ffi.}` pragma for automatic FFI wrapping
### Documentation and Examples
7. **liblmapi/README.md** - Main documentation
- API function reference
- Configuration examples
- Build instructions
- Usage patterns
- Architecture overview
8. **liblmapi/examples/liblmapi_example.c** - C example program
- Demonstrates all API functions
- Shows proper callback handling
- Complete lifecycle example
9. **liblmapi/examples/README.md** - Example documentation
- Build instructions per platform
- Expected output
- Usage notes
### Build System Integration
10. **Modified waku.nims**
- Added `liblmapiStatic` task
- Added `liblmapiDynamic` task
- Both use `buildLibrary` helper with chronicle params
11. **Modified Makefile**
- Added `liblmapi` to PHONY targets
- Added `LMAPI_BUILD_COMMAND` variable
- Added `liblmapi` target that calls nim tasks
- Respects STATIC flag for static/dynamic build
## API Functions
### Node Lifecycle
- `lmapi_create_node` - Create and configure node
- `lmapi_start_node` - Start node operations
- `lmapi_stop_node` - Stop node operations
- `lmapi_destroy` - Clean up and free resources
### Messaging
- `lmapi_subscribe` - Subscribe to content topic
- `lmapi_unsubscribe` - Unsubscribe from content topic
- `lmapi_send` - Send message envelope
### Events
- `lmapi_set_event_callback` - Register event callback
## Build Commands
```bash
# Build dynamic library (default)
make liblmapi
# Build static library
make liblmapi STATIC=1
# Or directly via nim
nim liblmapiDynamic waku.nims liblmapi.so
nim liblmapiStatic waku.nims liblmapi.a
```
## Key Design Decisions
1. **Follows libwaku pattern**: Same structure and conventions as existing `library/libwaku.nim`
2. **Uses nim-ffi framework**: Leverages vendor/nim-ffi for:
- Thread-safe request processing
- Async operation management
- Callback marshaling
- Memory management between C and Nim
3. **Wraps new high-level API**: Directly wraps `waku/api/api.nim` functions:
- `createNode(config: NodeConfig)`
- `subscribe(w: Waku, contentTopic: ContentTopic)`
- `send(w: Waku, envelope: MessageEnvelope)`
4. **JSON-based configuration**: Uses JSON for:
- Node configuration (mode, networking, protocols)
- Message envelopes (contentTopic, payload, ephemeral)
- Simplifies C interface while maintaining flexibility
5. **Base64 payload encoding**: Message payloads must be base64-encoded in JSON
- Avoids binary data issues in JSON
- Standard encoding for C interop
## Integration Points
The library integrates with:
- `waku/api/api.nim` - Main API functions
- `waku/api/api_conf.nim` - Configuration types (NodeConfig, NetworkingConfig, etc.)
- `waku/api/types.nim` - Core types (MessageEnvelope, RequestId, etc.)
- `waku/factory/waku.nim` - Waku instance type
- `vendor/nim-ffi/` - FFI infrastructure
## Testing
To test the library:
1. Build it: `make liblmapi`
2. Build the example: `make liblmapi_example` or `cd liblmapi/examples && gcc -o liblmapi_example liblmapi_example.c -I.. -L../../build -llmapi`
3. Run: `./build/liblmapi_example`
## Next Steps
Potential enhancements:
- Add more examples (async, multi-threaded, etc.)
- Add proper test suite
- Add CI/CD integration
- Add mobile platform support (Android/iOS)
- Add language bindings (Python, Go, etc.)

148
liblmapi/MESSAGE_EVENTS.md Normal file
View File

@ -0,0 +1,148 @@
# Message Event Handling in LMAPI
## Overview
The LMAPI library emits three types of message delivery events that clients can listen to by registering an event callback using `lmapi_set_event_callback()`.
## Event Types
### 1. message_sent
Emitted when a message is successfully accepted by the send service and queued for delivery.
**JSON Structure:**
```json
{
"eventType": "message_sent",
"requestId": "unique-request-id",
"messageHash": "0x..."
}
```
**Fields:**
- `eventType`: Always "message_sent"
- `requestId`: Request ID returned from the send operation
- `messageHash`: Hash of the message that was sent
### 2. message_propagated
Emitted when a message has been successfully propagated to neighboring nodes on the network.
**JSON Structure:**
```json
{
"eventType": "message_propagated",
"requestId": "unique-request-id",
"messageHash": "0x..."
}
```
**Fields:**
- `eventType`: Always "message_propagated"
- `requestId`: Request ID from the send operation
- `messageHash`: Hash of the message that was propagated
### 3. message_error
Emitted when an error occurs during message sending or propagation.
**JSON Structure:**
```json
{
"eventType": "message_error",
"requestId": "unique-request-id",
"messageHash": "0x...",
"error": "error description"
}
```
**Fields:**
- `eventType`: Always "message_error"
- `requestId`: Request ID from the send operation
- `messageHash`: Hash of the message that failed
- `error`: Description of what went wrong
## Usage
### 1. Define an Event Callback
```c
void event_callback(int ret, const char *msg, size_t len, void *userData) {
if (ret != RET_OK || msg == NULL || len == 0) {
return;
}
// Parse the JSON message
// Extract eventType field
// Handle based on event type
if (eventType == "message_sent") {
// Handle message sent
} else if (eventType == "message_propagated") {
// Handle message propagated
} else if (eventType == "message_error") {
// Handle message error
}
}
```
### 2. Register the Callback
```c
void *ctx = lmapi_create_node(config, callback, userData);
lmapi_set_event_callback(ctx, event_callback, NULL);
```
### 3. Start the Node
Once the node is started, events will be delivered to your callback:
```c
lmapi_start_node(ctx, callback, userData);
```
## Event Flow
For a typical successful message send:
1. **send** → Returns request ID
2. **message_sent** → Message accepted and queued
3. **message_propagated** → Message delivered to peers
For a failed message send:
1. **send** → Returns request ID
2. **message_sent** → Message accepted and queued
3. **message_error** → Delivery failed with error description
## Important Notes
1. **Thread Safety**: The event callback is invoked from the FFI worker thread. Ensure your callback is thread-safe if it accesses shared state.
2. **Non-Blocking**: Keep the callback fast and non-blocking. Do not perform long-running operations in the callback.
3. **JSON Parsing**: The example uses a simple string-based parser. For production, use a proper JSON library like:
- [cJSON](https://github.com/DaveGamble/cJSON)
- [json-c](https://github.com/json-c/json-c)
- [Jansson](https://github.com/akheron/jansson)
4. **Memory Management**: The message buffer is owned by the library. Copy any data you need to retain.
5. **Event Order**: Events are delivered in the order they occur, but timing depends on network conditions.
## Example Implementation
See `examples/liblmapi_example.c` for a complete working example that:
- Registers an event callback
- Sends a message
- Receives and prints all three event types
- Properly parses the JSON event structure
## Debugging Events
To see all events during development:
```c
void debug_event_callback(int ret, const char *msg, size_t len, void *userData) {
printf("Event received: %.*s\n", (int)len, msg);
}
```
This will print the raw JSON for all events, helping you understand the event structure.

262
liblmapi/README.md Normal file
View File

@ -0,0 +1,262 @@
# 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.
## API Functions
### Node Lifecycle
#### `lmapi_create_node`
Creates a new instance of the node from the given configuration JSON.
```c
void *lmapi_create_node(
const char *configJson,
FFICallBack callback,
void *userData
);
```
**Parameters:**
- `configJson`: JSON string containing node configuration
- `callback`: Callback function to receive the result
- `userData`: User data passed to the callback
**Returns:** Pointer to the context needed by other API functions, or NULL on error.
**Example configuration JSON:**
```json
{
"mode": "Core",
"clusterId": 1,
"entryNodes": [
"enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im"
],
"networkingConfig": {
"listenIpv4": "0.0.0.0",
"p2pTcpPort": 60000,
"discv5UdpPort": 9000
}
}
```
#### `lmapi_start_node`
Starts the node.
```c
int lmapi_start_node(
void *ctx,
FFICallBack callback,
void *userData
);
```
#### `lmapi_stop_node`
Stops the node.
```c
int lmapi_stop_node(
void *ctx,
FFICallBack callback,
void *userData
);
```
#### `lmapi_destroy`
Destroys a node instance and frees resources.
```c
int lmapi_destroy(
void *ctx,
FFICallBack callback,
void *userData
);
```
### Messaging
#### `lmapi_subscribe`
Subscribe to a content topic to receive messages.
```c
int lmapi_subscribe(
void *ctx,
FFICallBack callback,
void *userData,
const char *contentTopic
);
```
**Parameters:**
- `ctx`: Context pointer from `lmapi_create_node`
- `callback`: Callback function to receive the result
- `userData`: User data passed to the callback
- `contentTopic`: Content topic string (e.g., "/myapp/1/chat/proto")
#### `lmapi_unsubscribe`
Unsubscribe from a content topic.
```c
int lmapi_unsubscribe(
void *ctx,
FFICallBack callback,
void *userData,
const char *contentTopic
);
```
#### `lmapi_send`
Send a message.
```c
int lmapi_send(
void *ctx,
FFICallBack callback,
void *userData,
const char *messageJson
);
```
**Parameters:**
- `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
#### `lmapi_set_event_callback`
Sets a callback that will be invoked whenever an event occurs (e.g., message received).
```c
void lmapi_set_event_callback(
void *ctx,
FFICallBack callback,
void *userData
);
```
**Important:** The callback 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 liblmapiStatic # Build static library
# or
make liblmapiDynamic # Build dynamic library
```
## Return Codes
All functions that return `int` use the following return codes:
- `RET_OK` (0): Success
- `RET_ERR` (1): Error
- `RET_MISSING_CALLBACK` (2): Missing callback function
## Callback Function
All API functions use the following callback signature:
```c
typedef void (*FFICallBack)(
int callerRet,
const char *msg,
size_t len,
void *userData
);
```
**Parameters:**
- `callerRet`: Return code (RET_OK, RET_ERR, etc.)
- `msg`: Response message (may be empty for success)
- `len`: Length of the message
- `userData`: User data passed in the original call
## Example Usage
```c
#include "liblmapi.h"
#include <stdio.h>
void callback(int ret, const char *msg, size_t len, void *userData) {
if (ret == RET_OK) {
printf("Success: %.*s\n", (int)len, msg);
} else {
printf("Error: %.*s\n", (int)len, msg);
}
}
int main() {
const char *config = "{"
"\"mode\": \"Core\","
"\"clusterId\": 1"
"}";
// Create node
void *ctx = lmapi_create_node(config, callback, NULL);
if (ctx == NULL) {
return 1;
}
// Start node
lmapi_start_node(ctx, callback, NULL);
// Subscribe to a topic
lmapi_subscribe(ctx, callback, NULL, "/myapp/1/chat/proto");
// Send a message
const char *msg = "{"
"\"contentTopic\": \"/myapp/1/chat/proto\","
"\"payload\": \"SGVsbG8gV29ybGQ=\","
"\"ephemeral\": false"
"}";
lmapi_send(ctx, callback, NULL, msg);
// Clean up
lmapi_stop_node(ctx, callback, NULL);
lmapi_destroy(ctx, callback, NULL);
return 0;
}
```
## Architecture
The library is structured as follows:
- `liblmapi.h`: C header file with function declarations
- `liblmapi.nim`: Main library entry point
- `declare_lib.nim`: Library declaration and initialization
- `lmapi/node_api.nim`: Node lifecycle API implementation
- `lmapi/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/`

View File

@ -0,0 +1,88 @@
# Examples for liblmapi
This directory contains example programs demonstrating the usage of the Logos Messaging API (LMAPI) library.
## Building the Examples
### Prerequisites
1. Build the liblmapi library first:
```bash
cd /path/to/logos-messaging-nim
make liblmapi
```
2. The library will be available in `build/liblmapi.so` (or `.dylib` on macOS, `.dll` on Windows)
### Compile the liblmapi Example
#### On Linux/macOS:
```bash
# Shared library
gcc -o liblmapi_example liblmapi_example.c -I.. -L../../build -llmapi -Wl,-rpath,../../build
# Static library (if built with STATIC=1)
gcc -o liblmapi_example liblmapi_example.c -I.. ../../build/liblmapi.a -lpthread -lm -ldl
```
#### On macOS:
```bash
gcc -o liblmapi_example liblmapi_example.c -I.. -L../../build -llmapi
```
#### On Windows (MinGW):
```bash
gcc -o liblmapi_example.exe liblmapi_example.c -I.. -L../../build -llmapi -lws2_32
```
## Running the Examples
### liblmapi Example
```bash
./liblmapi_example
```
This example demonstrates:
1. Creating a node with configuration
2. Starting the node
3. Subscribing to a content topic
4. Sending a message
5. Unsubscribing from the topic
6. Stopping and destroying the node
### Expected Output
```
=== Logos Messaging API (LMAPI) Example ===
1. Creating node...
[create_node] Success
2. Starting node...
[start_node] Success
3. Subscribing to content topic...
[subscribe] Success
4. Sending a message...
[send] Success: <request-id>
5. Unsubscribing from content topic...
[unsubscribe] Success
6. Stopping node...
[stop_node] Success
7. Destroying context...
[destroy] Success
=== Example completed ===
```
## Notes
- The examples use simple synchronous callbacks with sleep() for demonstration
- In production code, you should use proper async patterns
- Error handling in these examples is basic - production code should be more robust
- The payload in messages must be base64-encoded

View File

@ -9,26 +9,26 @@
const char* extract_json_field(const char *json, const char *field, char *buffer, size_t bufSize) {
char searchStr[256];
snprintf(searchStr, sizeof(searchStr), "\"%s\":\"", field);
const char *start = strstr(json, searchStr);
if (!start) {
return NULL;
}
start += strlen(searchStr);
const char *end = strchr(start, '"');
if (!end) {
return NULL;
}
size_t len = end - start;
if (len >= bufSize) {
len = bufSize - 1;
}
memcpy(buffer, start, len);
buffer[len] = '\0';
return buffer;
}
@ -37,7 +37,7 @@ void event_callback(int ret, const char *msg, size_t len, void *userData) {
if (ret != RET_OK || msg == NULL || len == 0) {
return;
}
// Create null-terminated string for easier parsing
char *eventJson = malloc(len + 1);
if (!eventJson) {
@ -45,14 +45,14 @@ void event_callback(int ret, const char *msg, size_t len, void *userData) {
}
memcpy(eventJson, msg, len);
eventJson[len] = '\0';
// Extract eventType
char eventType[64];
if (!extract_json_field(eventJson, "eventType", eventType, sizeof(eventType))) {
free(eventJson);
return;
}
// Handle different event types
if (strcmp(eventType, "message_sent") == 0) {
char requestId[128];
@ -60,7 +60,7 @@ void event_callback(int ret, const char *msg, size_t len, void *userData) {
extract_json_field(eventJson, "requestId", requestId, sizeof(requestId));
extract_json_field(eventJson, "messageHash", messageHash, sizeof(messageHash));
printf("📤 [EVENT] Message sent - RequestID: %s, Hash: %s\n", requestId, messageHash);
} else if (strcmp(eventType, "message_error") == 0) {
char requestId[128];
char messageHash[128];
@ -68,20 +68,20 @@ void event_callback(int ret, const char *msg, size_t len, void *userData) {
extract_json_field(eventJson, "requestId", requestId, sizeof(requestId));
extract_json_field(eventJson, "messageHash", messageHash, sizeof(messageHash));
extract_json_field(eventJson, "error", error, sizeof(error));
printf("❌ [EVENT] Message error - RequestID: %s, Hash: %s, Error: %s\n",
printf("❌ [EVENT] Message error - RequestID: %s, Hash: %s, Error: %s\n",
requestId, messageHash, error);
} else if (strcmp(eventType, "message_propagated") == 0) {
char requestId[128];
char messageHash[128];
extract_json_field(eventJson, "requestId", requestId, sizeof(requestId));
extract_json_field(eventJson, "messageHash", messageHash, sizeof(messageHash));
printf("✅ [EVENT] Message propagated - RequestID: %s, Hash: %s\n", requestId, messageHash);
} else {
printf(" [EVENT] Unknown event type: %s\n", eventType);
}
free(eventJson);
}
@ -105,8 +105,10 @@ int main() {
// Configuration JSON for creating a node
const char *config = "{"
"\"mode\": \"Core\","
"\"clusterId\": 1,"
"\"entryNodes\": [],"
"\"clusterId\": 16,"
"\"numShards\": 5,"
"\"shards\": [64, 128, 1, 32, 256],"
"\"entryNodes\": [\"/dns4/boot-01.gc-us-central1-a.status.prod.status.im/tcp/30303/p2p/16Uiu2HAm8mUZ18tBWPXDQsaF7PbCKYA35z7WB2xNZH2EVq1qS8LJ\"],"
"\"networkingConfig\": {"
"\"listenIpv4\": \"0.0.0.0\","
"\"p2pTcpPort\": 60000,"
@ -153,7 +155,7 @@ int main() {
// Wait for message events to arrive
printf("Waiting for message delivery events...\n");
sleep(3);
sleep(60);
printf("\n6. Unsubscribing from content topic...\n");
lmapi_unsubscribe(ctx, simple_callback, (void *)"unsubscribe", contentTopic);