mirror of
https://github.com/logos-messaging/logos-delivery.git
synced 2026-07-28 07:23:16 +00:00
feat: generate the C header instead of hand-writing it
The hand-written liblogosdelivery.h / _kernel.h declared the pre-CBOR string ABI, so they lied about the real one -- a C consumer would link against signatures that no longer exist. genBindings already produces the Rust bindings from the Nim source; this adds the C target on the same footing. liblogosdeliveryGenBindingsC emits library/c_bindings/: logosdelivery.h covering the whole surface (messaging, channels and the waku_* kernel procs in one file), plus the nim_ffi_cbor.h / _prelude.h helpers that marshal the CBOR payloads. Params ride as CBOR blobs, matching the library's default ABI and what the Rust crate does internally; the typed C ABI (abi=c) stays available per-proc for later. The two hand-written headers are deleted and nix packaging ships the generated ones. The C examples target the old string ABI and removed headers, so they no longer compile; each is annotated as awaiting a port rather than left with a dangling include that pretends to work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1ef53c9506
commit
047b709c55
@ -63,12 +63,19 @@ build/
|
||||
|
||||
## Library Headers
|
||||
|
||||
The C API is tiered across two headers (one library, two stability promises):
|
||||
- `library/liblogosdelivery.h` - stable, supported Messaging / Reliable
|
||||
Channels API. This is the front door for consumers.
|
||||
- `library/liblogosdelivery_kernel.h` - advanced, low-level kernel API
|
||||
(`waku_*`). "Use at your own risk", may change at any time. Including it is
|
||||
a deliberate opt-in; it pulls in `liblogosdelivery.h` for shared types.
|
||||
The C header is generated from the Nim source by nim-ffi, not hand-written:
|
||||
|
||||
nimble liblogosdeliveryGenBindingsC
|
||||
|
||||
This emits `library/c_bindings/`:
|
||||
- `logosdelivery.h` - the full C API (Messaging, Reliable Channels and the
|
||||
low-level `waku_*` kernel procs, in one file).
|
||||
- `nim_ffi_cbor.h` / `nim_ffi_prelude.h` - request/response payloads cross the
|
||||
boundary as CBOR blobs (the library's default ABI), and these helpers encode
|
||||
and decode them. `logosdelivery.h` includes them.
|
||||
|
||||
Regenerate whenever the `{.ffi.}` surface changes; the checked-in copy must
|
||||
match the Nim source.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
47
library/c_bindings/CMakeLists.txt
Normal file
47
library/c_bindings/CMakeLists.txt
Normal file
@ -0,0 +1,47 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(logosdelivery_c_bindings C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# ── Locate the repository root (contains ffi.nimble) ─────────────────────────
|
||||
set(_search_dir "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
set(REPO_ROOT "")
|
||||
foreach(_i RANGE 10)
|
||||
if(EXISTS "${_search_dir}/ffi.nimble")
|
||||
set(REPO_ROOT "${_search_dir}")
|
||||
break()
|
||||
endif()
|
||||
get_filename_component(_search_dir "${_search_dir}" DIRECTORY)
|
||||
endforeach()
|
||||
if("${REPO_ROOT}" STREQUAL "")
|
||||
message(FATAL_ERROR "Cannot find repo root (no ffi.nimble in any ancestor)")
|
||||
endif()
|
||||
|
||||
# Build the Nim dylib + vendored TinyCBOR (shared with the C++ backend).
|
||||
set(NIM_FFI_LIB logosdelivery)
|
||||
set(NIM_FFI_SRC library/liblogosdelivery.nim)
|
||||
include("${REPO_ROOT}/ffi/codegen/templates/nim_ffi_lib.cmake")
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_library(logosdelivery_headers INTERFACE)
|
||||
target_include_directories(logosdelivery_headers INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
target_link_libraries(logosdelivery_headers INTERFACE logosdelivery tinycbor Threads::Threads)
|
||||
# The generated header is async (no blocking helper), but consumer code that
|
||||
# waits on a result callback typically uses nanosleep / pthreads, which need a
|
||||
# POSIX feature level that strict `-std=c11` hides. Define it for consumers.
|
||||
target_compile_definitions(logosdelivery_headers INTERFACE _POSIX_C_SOURCE=200809L)
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/main.c")
|
||||
add_executable(logosdelivery_example main.c)
|
||||
target_link_libraries(logosdelivery_example PRIVATE logosdelivery_headers)
|
||||
add_dependencies(logosdelivery_example logosdelivery_nim_lib)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
add_custom_command(TARGET logosdelivery_example POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${logosdelivery_RUNTIME_LIB}"
|
||||
"$<TARGET_FILE_DIR:logosdelivery_example>"
|
||||
COMMENT "Staging logosdelivery.dll next to logosdelivery_example.exe")
|
||||
endif()
|
||||
endif()
|
||||
5710
library/c_bindings/logosdelivery.h
Normal file
5710
library/c_bindings/logosdelivery.h
Normal file
File diff suppressed because it is too large
Load Diff
349
library/c_bindings/nim_ffi_cbor.h
Normal file
349
library/c_bindings/nim_ffi_cbor.h
Normal file
@ -0,0 +1,349 @@
|
||||
#ifndef NIM_FFI_CBOR_HELPERS_H_INCLUDED
|
||||
#define NIM_FFI_CBOR_HELPERS_H_INCLUDED
|
||||
/* Leaf CBOR codecs (scalars, text strings, byte strings) plus the buffer
|
||||
* drivers. The per-struct / per-container codecs in the library header call
|
||||
* into these by name (C has no overloading, so each leaf gets a distinct
|
||||
* nimffi_enc_* / nimffi_dec_* symbol). Guarded so two nim-ffi headers can
|
||||
* share a translation unit. */
|
||||
#include "nim_ffi_prelude.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Result delivery callback exported by the Nim dylib: `ret` is 0 on success
|
||||
* (then `msg`/`len` carry the CBOR response) or non-zero on failure (then
|
||||
* `msg`/`len` carry the error text, which is NOT NUL-terminated). */
|
||||
typedef void (*FFICallback)(int ret, const char* msg, size_t len, void* user_data);
|
||||
|
||||
/* Return / callback status codes. NIMFFI_RET_OK (0) is success; any non-zero
|
||||
* value handed to a result callback's `err_code` (or returned by a submit call)
|
||||
* is a failure. NIMFFI_RET_MISSING_CALLBACK is a special case from the Nim
|
||||
* dispatcher: the callback will never fire, so the request path must report the
|
||||
* failure itself.
|
||||
*
|
||||
* NIMFFI_RET_STALE_WARN is the one NON-terminal code: nim-ffi delivers it every
|
||||
* ~5s while a handler is still running (with `msg`/`len` carrying the elapsed
|
||||
* milliseconds as decimal text), then still ends with a terminal RET_OK/RET_ERR.
|
||||
* A caller that only wants the final answer must ignore it, not treat it as an
|
||||
* error. */
|
||||
#define NIMFFI_RET_OK 0
|
||||
#define NIMFFI_RET_ERROR 1
|
||||
#define NIMFFI_RET_MISSING_CALLBACK 2
|
||||
#define NIMFFI_RET_STALE_WARN 3
|
||||
|
||||
/* ── leaf encoders ─────────────────────────────────────────────────────── */
|
||||
static inline CborError nimffi_enc_bool(CborEncoder* e, const bool* v) {
|
||||
return cbor_encode_boolean(e, *v);
|
||||
}
|
||||
static inline CborError nimffi_enc_i64(CborEncoder* e, const int64_t* v) {
|
||||
return cbor_encode_int(e, *v);
|
||||
}
|
||||
static inline CborError nimffi_enc_i32(CborEncoder* e, const int32_t* v) {
|
||||
return cbor_encode_int(e, (int64_t)*v);
|
||||
}
|
||||
static inline CborError nimffi_enc_i16(CborEncoder* e, const int16_t* v) {
|
||||
return cbor_encode_int(e, (int64_t)*v);
|
||||
}
|
||||
static inline CborError nimffi_enc_i8(CborEncoder* e, const int8_t* v) {
|
||||
return cbor_encode_int(e, (int64_t)*v);
|
||||
}
|
||||
static inline CborError nimffi_enc_u64(CborEncoder* e, const uint64_t* v) {
|
||||
return cbor_encode_uint(e, *v);
|
||||
}
|
||||
static inline CborError nimffi_enc_u32(CborEncoder* e, const uint32_t* v) {
|
||||
return cbor_encode_uint(e, (uint64_t)*v);
|
||||
}
|
||||
static inline CborError nimffi_enc_u16(CborEncoder* e, const uint16_t* v) {
|
||||
return cbor_encode_uint(e, (uint64_t)*v);
|
||||
}
|
||||
static inline CborError nimffi_enc_u8(CborEncoder* e, const uint8_t* v) {
|
||||
return cbor_encode_uint(e, (uint64_t)*v);
|
||||
}
|
||||
static inline CborError nimffi_enc_f64(CborEncoder* e, const double* v) {
|
||||
return cbor_encode_double(e, *v);
|
||||
}
|
||||
static inline CborError nimffi_enc_f32(CborEncoder* e, const float* v) {
|
||||
return cbor_encode_float(e, *v);
|
||||
}
|
||||
static inline CborError nimffi_enc_str(CborEncoder* e, const NimFfiStr* v) {
|
||||
return cbor_encode_text_string(e, v->data ? v->data : "", v->len);
|
||||
}
|
||||
static inline CborError nimffi_enc_bytes(CborEncoder* e, const NimFfiBytes* v) {
|
||||
return cbor_encode_byte_string(e, v->data, v->len);
|
||||
}
|
||||
|
||||
/* ── leaf decoders ─────────────────────────────────────────────────────── */
|
||||
/* After reading a leaf, the parser must advance past it; both steps
|
||||
* short-circuit on the same CborError, so they travel together. */
|
||||
static inline CborError nimffi_advance_if_ok(CborValue* it, CborError err) {
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
return cbor_value_advance(it);
|
||||
}
|
||||
|
||||
static inline CborError nimffi_dec_bool(CborValue* it, bool* out) {
|
||||
if (!cbor_value_is_boolean(it)) {
|
||||
return CborErrorImproperValue;
|
||||
}
|
||||
return nimffi_advance_if_ok(it, cbor_value_get_boolean(it, out));
|
||||
}
|
||||
static inline CborError nimffi_dec_i64(CborValue* it, int64_t* out) {
|
||||
if (!cbor_value_is_integer(it)) {
|
||||
return CborErrorImproperValue;
|
||||
}
|
||||
return nimffi_advance_if_ok(it, cbor_value_get_int64_checked(it, out));
|
||||
}
|
||||
static inline CborError nimffi_dec_i32(CborValue* it, int32_t* out) {
|
||||
int64_t tmp = 0;
|
||||
CborError err = nimffi_dec_i64(it, &tmp);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (tmp < INT32_MIN || tmp > INT32_MAX) {
|
||||
return CborErrorDataTooLarge;
|
||||
}
|
||||
*out = (int32_t)tmp;
|
||||
return CborNoError;
|
||||
}
|
||||
static inline CborError nimffi_dec_i16(CborValue* it, int16_t* out) {
|
||||
int64_t tmp = 0;
|
||||
CborError err = nimffi_dec_i64(it, &tmp);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (tmp < INT16_MIN || tmp > INT16_MAX) {
|
||||
return CborErrorDataTooLarge;
|
||||
}
|
||||
*out = (int16_t)tmp;
|
||||
return CborNoError;
|
||||
}
|
||||
static inline CborError nimffi_dec_i8(CborValue* it, int8_t* out) {
|
||||
int64_t tmp = 0;
|
||||
CborError err = nimffi_dec_i64(it, &tmp);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (tmp < INT8_MIN || tmp > INT8_MAX) {
|
||||
return CborErrorDataTooLarge;
|
||||
}
|
||||
*out = (int8_t)tmp;
|
||||
return CborNoError;
|
||||
}
|
||||
static inline CborError nimffi_dec_u64(CborValue* it, uint64_t* out) {
|
||||
if (!cbor_value_is_unsigned_integer(it)) {
|
||||
return CborErrorImproperValue;
|
||||
}
|
||||
return nimffi_advance_if_ok(it, cbor_value_get_uint64(it, out));
|
||||
}
|
||||
static inline CborError nimffi_dec_u32(CborValue* it, uint32_t* out) {
|
||||
uint64_t tmp = 0;
|
||||
CborError err = nimffi_dec_u64(it, &tmp);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (tmp > UINT32_MAX) {
|
||||
return CborErrorDataTooLarge;
|
||||
}
|
||||
*out = (uint32_t)tmp;
|
||||
return CborNoError;
|
||||
}
|
||||
static inline CborError nimffi_dec_u16(CborValue* it, uint16_t* out) {
|
||||
uint64_t tmp = 0;
|
||||
CborError err = nimffi_dec_u64(it, &tmp);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (tmp > UINT16_MAX) {
|
||||
return CborErrorDataTooLarge;
|
||||
}
|
||||
*out = (uint16_t)tmp;
|
||||
return CborNoError;
|
||||
}
|
||||
static inline CborError nimffi_dec_u8(CborValue* it, uint8_t* out) {
|
||||
uint64_t tmp = 0;
|
||||
CborError err = nimffi_dec_u64(it, &tmp);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (tmp > UINT8_MAX) {
|
||||
return CborErrorDataTooLarge;
|
||||
}
|
||||
*out = (uint8_t)tmp;
|
||||
return CborNoError;
|
||||
}
|
||||
static inline CborError nimffi_dec_f64(CborValue* it, double* out) {
|
||||
if (cbor_value_is_double(it)) {
|
||||
return nimffi_advance_if_ok(it, cbor_value_get_double(it, out));
|
||||
}
|
||||
if (cbor_value_is_float(it)) {
|
||||
float f = 0.0f;
|
||||
CborError err = cbor_value_get_float(it, &f);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
*out = (double)f;
|
||||
return cbor_value_advance(it);
|
||||
}
|
||||
return CborErrorImproperValue;
|
||||
}
|
||||
static inline CborError nimffi_dec_f32(CborValue* it, float* out) {
|
||||
if (cbor_value_is_float(it)) {
|
||||
return nimffi_advance_if_ok(it, cbor_value_get_float(it, out));
|
||||
}
|
||||
if (cbor_value_is_double(it)) {
|
||||
double d = 0.0;
|
||||
CborError err = cbor_value_get_double(it, &d);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
*out = (float)d;
|
||||
return cbor_value_advance(it);
|
||||
}
|
||||
return CborErrorImproperValue;
|
||||
}
|
||||
static inline CborError nimffi_dec_str(CborValue* it, NimFfiStr* out) {
|
||||
if (!cbor_value_is_text_string(it)) {
|
||||
return CborErrorImproperValue;
|
||||
}
|
||||
size_t len = 0;
|
||||
CborError err = cbor_value_get_string_length(it, &len);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (len == SIZE_MAX) { /* len + 1 would wrap to a 0-byte allocation */
|
||||
return CborErrorDataTooLarge;
|
||||
}
|
||||
/* one extra byte so a NUL-free payload is a valid C string */
|
||||
out->data = (char*)malloc(len + 1);
|
||||
if (!out->data) {
|
||||
return CborErrorOutOfMemory;
|
||||
}
|
||||
out->len = len;
|
||||
size_t copied = len;
|
||||
err = cbor_value_copy_text_string(it, out->data, &copied, NULL);
|
||||
if (err) {
|
||||
free(out->data);
|
||||
out->data = NULL;
|
||||
out->len = 0;
|
||||
return err;
|
||||
}
|
||||
out->data[len] = '\0';
|
||||
return cbor_value_advance(it);
|
||||
}
|
||||
static inline CborError nimffi_dec_bytes(CborValue* it, NimFfiBytes* out) {
|
||||
if (!cbor_value_is_byte_string(it)) {
|
||||
return CborErrorImproperValue;
|
||||
}
|
||||
size_t len = 0;
|
||||
CborError err = cbor_value_get_string_length(it, &len);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
out->data = (uint8_t*)malloc(len ? len : 1);
|
||||
if (!out->data) {
|
||||
return CborErrorOutOfMemory;
|
||||
}
|
||||
out->len = len;
|
||||
size_t copied = len;
|
||||
err = cbor_value_copy_byte_string(it, out->data, &copied, NULL);
|
||||
if (err) {
|
||||
free(out->data);
|
||||
out->data = NULL;
|
||||
out->len = 0;
|
||||
return err;
|
||||
}
|
||||
return cbor_value_advance(it);
|
||||
}
|
||||
|
||||
/* ── buffer drivers ────────────────────────────────────────────────────── */
|
||||
typedef CborError (*nimffi_enc_fn)(CborEncoder*, const void*);
|
||||
typedef CborError (*nimffi_dec_fn)(CborValue*, void*);
|
||||
|
||||
static inline char* nimffi_dup_cstr(const char* s) {
|
||||
size_t n = strlen(s) + 1;
|
||||
char* p = (char*)malloc(n);
|
||||
if (p) {
|
||||
memcpy(p, s, n);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/* NUL-terminated copy of a length-delimited (not NUL-terminated) byte run,
|
||||
* for turning the FFICallback's raw error `msg`/`len` into a C string. */
|
||||
static inline char* nimffi_dup_cstr_n(const char* s, size_t n) {
|
||||
char* p = (char*)malloc(n + 1);
|
||||
if (p) {
|
||||
if (n > 0) {
|
||||
memcpy(p, s, n);
|
||||
}
|
||||
p[n] = '\0';
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Encode `val` with `fn` into a freshly malloc'd buffer, doubling on overflow.
|
||||
* Returns 0 and sets out/outlen on success; -1 and *err (heap) on failure. */
|
||||
static inline int nimffi_encode_to_buf(
|
||||
nimffi_enc_fn fn, const void* val,
|
||||
uint8_t** out, size_t* outlen, char** err) {
|
||||
size_t cap = 4096;
|
||||
uint8_t* buf = (uint8_t*)malloc(cap);
|
||||
if (!buf) {
|
||||
if (err) *err = nimffi_dup_cstr("out of memory");
|
||||
return -1;
|
||||
}
|
||||
for (;;) {
|
||||
CborEncoder enc;
|
||||
cbor_encoder_init(&enc, buf, cap, 0);
|
||||
CborError e = fn(&enc, val);
|
||||
if (e == CborNoError) {
|
||||
*outlen = cbor_encoder_get_buffer_size(&enc, buf);
|
||||
*out = buf;
|
||||
return 0;
|
||||
}
|
||||
if (e == CborErrorOutOfMemory) {
|
||||
size_t extra = cbor_encoder_get_extra_bytes_needed(&enc);
|
||||
cap += extra > 0 ? extra : cap;
|
||||
uint8_t* grown = (uint8_t*)realloc(buf, cap);
|
||||
if (!grown) {
|
||||
free(buf);
|
||||
if (err) *err = nimffi_dup_cstr("out of memory");
|
||||
return -1;
|
||||
}
|
||||
buf = grown;
|
||||
continue;
|
||||
}
|
||||
free(buf);
|
||||
if (err) *err = nimffi_dup_cstr(cbor_error_string(e));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Decode a CBOR buffer into `out` with `fn`. Returns 0 on success; -1 and
|
||||
* *err (heap) on failure. */
|
||||
static inline int nimffi_decode_from_buf(
|
||||
nimffi_dec_fn fn, const uint8_t* buf, size_t len,
|
||||
void* out, char** err) {
|
||||
CborParser parser;
|
||||
CborValue it;
|
||||
CborError e = cbor_parser_init(buf, len, 0, &parser, &it);
|
||||
if (e != CborNoError) {
|
||||
if (err) *err = nimffi_dup_cstr(cbor_error_string(e));
|
||||
return -1;
|
||||
}
|
||||
e = fn(&it, out);
|
||||
if (e != CborNoError) {
|
||||
if (err) *err = nimffi_dup_cstr(cbor_error_string(e));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NIM_FFI_CBOR_HELPERS_H_INCLUDED */
|
||||
|
||||
89
library/c_bindings/nim_ffi_prelude.h
Normal file
89
library/c_bindings/nim_ffi_prelude.h
Normal file
@ -0,0 +1,89 @@
|
||||
#ifndef NIM_FFI_PRELUDE_H_INCLUDED
|
||||
#define NIM_FFI_PRELUDE_H_INCLUDED
|
||||
/* Generated C binding for a nim-ffi library. Requests/responses travel as
|
||||
* CBOR (encoded with vendored TinyCBOR on this side, matching the Nim-side
|
||||
* cbor_serial codec on the wire — both ends speak RFC 8949).
|
||||
*
|
||||
* The API is asynchronous: every method/constructor takes a result callback
|
||||
* and returns immediately. The callback fires exactly once — synchronously on
|
||||
* a submit-time failure, otherwise from the Nim dispatch thread when the reply
|
||||
* arrives.
|
||||
*
|
||||
* Memory ownership contract:
|
||||
* - Request-side strings/sequences are *borrowed*: the binding only reads
|
||||
* them while encoding, so a string literal wrapped with nimffi_str() is
|
||||
* fine and is never freed by the binding.
|
||||
* - Response values and error strings passed into a result callback are
|
||||
* *owned by the binding* and valid only for the duration of that callback;
|
||||
* the binding reclaims them once the callback returns. The caller never
|
||||
* frees them. (The generated <lib>_free_<Type>() helpers are internal — the
|
||||
* trampolines use them to reclaim decoded payloads.)
|
||||
* - A context handle delivered to a constructor callback is the exception:
|
||||
* ownership transfers to the caller, who releases it with
|
||||
* <lib>_ctx_destroy(). It is a lifecycle handle, not returned data.
|
||||
*
|
||||
* Trust boundary: the decoders assume the CBOR they parse was produced by the
|
||||
* paired Nim library. They reject malformed input rather than trusting it, but
|
||||
* they are not hardened against a hostile peer feeding crafted payloads through
|
||||
* the raw nimffi_decode_from_buf entry point.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <tinycbor/cbor.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Owned, length-delimited UTF-8 text (Nim `string`/`cstring`). On the request
|
||||
* side `data` may point at borrowed storage (see nimffi_str); on the response
|
||||
* side it is heap-allocated and freed by nimffi_free_str. Always NUL-padded by
|
||||
* one byte after decode so `data` is usable as a C string when it has no
|
||||
* embedded NULs. */
|
||||
typedef struct {
|
||||
char* data;
|
||||
size_t len;
|
||||
} NimFfiStr;
|
||||
|
||||
/* Owned, length-delimited byte buffer (Nim `seq[byte]`). */
|
||||
typedef struct {
|
||||
uint8_t* data;
|
||||
size_t len;
|
||||
} NimFfiBytes;
|
||||
|
||||
/* Wrap a borrowed C string for use as a request field. The returned view is
|
||||
* not owned by the binding and must outlive the call that encodes it. */
|
||||
static inline NimFfiStr nimffi_str(const char* s) {
|
||||
NimFfiStr v;
|
||||
v.data = (char*)s;
|
||||
v.len = s ? strlen(s) : 0;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void nimffi_free_str(NimFfiStr* v) {
|
||||
if (!v || !v->data) {
|
||||
return;
|
||||
}
|
||||
free(v->data);
|
||||
v->data = NULL;
|
||||
v->len = 0;
|
||||
}
|
||||
|
||||
static inline void nimffi_free_bytes(NimFfiBytes* v) {
|
||||
if (!v || !v->data) {
|
||||
return;
|
||||
}
|
||||
free(v->data);
|
||||
v->data = NULL;
|
||||
v->len = 0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NIM_FFI_PRELUDE_H_INCLUDED */
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
/* NOTE: This example targets the pre-CBOR string ABI and the removed
|
||||
* hand-written headers. The FFI now generates its C header
|
||||
* (library/c_bindings/logosdelivery.h) and passes requests as CBOR blobs, so
|
||||
* this example no longer compiles as-is; it awaits a port to the generated
|
||||
* CBOR ABI. See library/BUILD.md. */
|
||||
#include "../liblogosdelivery.h"
|
||||
#include "json_utils.h"
|
||||
#include <stdio.h>
|
||||
|
||||
@ -1,148 +0,0 @@
|
||||
|
||||
// Generated manually and inspired by libwaku.h
|
||||
// Header file for Logos Messaging API (LMAPI) library
|
||||
#pragma once
|
||||
#ifndef __liblogosdelivery__
|
||||
#define __liblogosdelivery__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// The possible returned values for the functions that return int
|
||||
#define RET_OK 0
|
||||
#define RET_ERR 1
|
||||
#define RET_MISSING_CALLBACK 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef void (*FFICallBack)(int callerRet, const char *msg, size_t len, void *userData);
|
||||
|
||||
// Creates a new instance of the node from the given configuration JSON.
|
||||
// Returns a pointer to the Context needed by the rest of the API functions.
|
||||
// The configuration is a JSON object with these optional keys:
|
||||
// "mode": "Core" | "Edge" (messaging role; defaults to "Core")
|
||||
// "preset": "<network preset>" (e.g. "twn")
|
||||
// "messagingOverrides": { ... } (per-field messaging config overrides)
|
||||
// "channelsOverrides": { ... } (per-field 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.
|
||||
// Example: {"mode":"Core","messagingOverrides":{"cluster-id":42,"log-level":"INFO"}}
|
||||
void *logosdelivery_create_node(
|
||||
const char *configJson,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// Starts the node.
|
||||
int logosdelivery_start_node(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// Stops the node.
|
||||
int logosdelivery_stop_node(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// Destroys an instance of a node created with logosdelivery_create_node
|
||||
int logosdelivery_destroy(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// Subscribe to a content topic.
|
||||
// contentTopic: string representing the content topic (e.g., "/myapp/1/chat/proto")
|
||||
int logosdelivery_subscribe(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *contentTopic);
|
||||
|
||||
// Unsubscribe from a content topic.
|
||||
int logosdelivery_unsubscribe(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *contentTopic);
|
||||
|
||||
// Send a message.
|
||||
// messageJson: JSON string with the following structure:
|
||||
// {
|
||||
// "contentTopic": "/myapp/1/chat/proto",
|
||||
// "payload": "base64-encoded-payload",
|
||||
// "ephemeral": false
|
||||
// }
|
||||
// Returns a request ID that can be used to track the message delivery.
|
||||
int logosdelivery_send(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *messageJson);
|
||||
|
||||
// --- Reliable Channels API (stable surface) ---
|
||||
|
||||
// Create a reliable channel. Returns the channel id.
|
||||
int logosdelivery_channel_create(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *channelId,
|
||||
const char *contentTopic,
|
||||
const char *senderId);
|
||||
|
||||
// Send a message on a reliable channel.
|
||||
// messageJson: { "payload": "base64-encoded-payload", "ephemeral": false }
|
||||
// Returns a request ID that can be used to track delivery.
|
||||
int logosdelivery_channel_send(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *channelId,
|
||||
const char *messageJson);
|
||||
|
||||
// Close a reliable channel: stops its SDS loops; persisted state survives, so
|
||||
// re-creating the channel restores it.
|
||||
int logosdelivery_channel_close(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *channelId);
|
||||
|
||||
// Channel lifecycle events are delivered to listeners registered via
|
||||
// logosdelivery_add_event_listener: "onChannelMessageReceived" (payload
|
||||
// base64-encoded), "onChannelMessageSent", "onChannelMessageError".
|
||||
|
||||
// Registers a callback invoked whenever the named event fires. Listeners are
|
||||
// per event name; register once per event you care about. Returns the listener
|
||||
// id (> 0) to pass to logosdelivery_remove_event_listener, or 0 if callback is
|
||||
// NULL.
|
||||
// It is crucial that the passed callback is fast, non-blocking and potentially thread-safe.
|
||||
uint64_t logosdelivery_add_event_listener(void *ctx,
|
||||
const char *eventName,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// Unregisters the listener with the given id. Returns RET_OK when a listener
|
||||
// was removed, RET_ERR otherwise.
|
||||
int logosdelivery_remove_event_listener(void *ctx,
|
||||
uint64_t listenerId);
|
||||
|
||||
// Retrieves the list of available node info IDs.
|
||||
int logosdelivery_get_available_node_info_ids(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// Given a node info ID, retrieves the corresponding info.
|
||||
int logosdelivery_get_node_info(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *nodeInfoId);
|
||||
|
||||
// Retrieves the list of available configurations.
|
||||
int logosdelivery_get_available_configs(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// NOTE: the low-level kernel API (waku_*) lives in the separate, advanced
|
||||
// header liblogosdelivery_kernel.h. It is intentionally not declared here so
|
||||
// this header only promises the stable Messaging / Reliable Channels surface.
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __liblogosdelivery__ */
|
||||
@ -1,241 +0,0 @@
|
||||
|
||||
// liblogosdelivery_kernel.h — Kernel / advanced API (low-level, per-protocol).
|
||||
//
|
||||
// ⚠️ USE AT YOUR OWN RISK — UNSUPPORTED, UNSTABLE SURFACE.
|
||||
//
|
||||
// These `waku_*` functions are the low-level kernel API. They are NOT part of
|
||||
// the stable, supported Messaging / Reliable Channels surface declared in
|
||||
// liblogosdelivery.h. They expose per-protocol internals (relay, filter,
|
||||
// lightpush, store, discovery, peer management) and may change or be removed
|
||||
// at ANY time, without notice or a deprecation cycle.
|
||||
//
|
||||
// Including this header is a deliberate opt-in into the advanced tier. If you
|
||||
// only need messaging, include liblogosdelivery.h and nothing here.
|
||||
//
|
||||
// See https://github.com/logos-messaging/logos-delivery/issues/3851 for the
|
||||
// tiering rationale.
|
||||
#pragma once
|
||||
#ifndef __liblogosdelivery_kernel__
|
||||
#define __liblogosdelivery_kernel__
|
||||
|
||||
// Shared FFICallBack typedef and RET_* return codes live in the stable header.
|
||||
#include "liblogosdelivery.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// NOTE: node lifecycle (create / start / stop / destroy) is unified and lives
|
||||
// only in the stable header. Use logosdelivery_create_node,
|
||||
// logosdelivery_start_node, logosdelivery_stop_node and logosdelivery_destroy
|
||||
// (declared in liblogosdelivery.h, included above) regardless of whether you
|
||||
// drive the node through the messaging surface or this kernel API.
|
||||
|
||||
int waku_version(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// NOTE: event listeners are registered via logosdelivery_add_event_listener
|
||||
// (declared above) which the waku_* API shares.
|
||||
|
||||
int waku_content_topic(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *appName,
|
||||
unsigned int appVersion,
|
||||
const char *contentTopicName,
|
||||
const char *encoding);
|
||||
|
||||
int waku_pubsub_topic(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *topicName);
|
||||
|
||||
int waku_default_pubsub_topic(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_relay_publish(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic,
|
||||
const char *jsonWakuMessage,
|
||||
unsigned int timeoutMs);
|
||||
|
||||
int waku_lightpush_publish(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic,
|
||||
const char *jsonWakuMessage);
|
||||
|
||||
int waku_relay_subscribe(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic);
|
||||
|
||||
int waku_relay_add_protected_shard(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
int clusterId,
|
||||
int shardId,
|
||||
char *publicKey);
|
||||
|
||||
int waku_relay_unsubscribe(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic);
|
||||
|
||||
int waku_filter_subscribe(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic,
|
||||
const char *contentTopics);
|
||||
|
||||
int waku_filter_unsubscribe(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic,
|
||||
const char *contentTopics);
|
||||
|
||||
int waku_filter_unsubscribe_all(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_relay_get_num_connected_peers(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic);
|
||||
|
||||
int waku_relay_get_connected_peers(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic);
|
||||
|
||||
int waku_relay_get_num_peers_in_mesh(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic);
|
||||
|
||||
int waku_relay_get_peers_in_mesh(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *pubSubTopic);
|
||||
|
||||
int waku_store_query(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *jsonQuery,
|
||||
const char *peerAddr,
|
||||
int timeoutMs);
|
||||
|
||||
int waku_connect(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *peerMultiAddr,
|
||||
unsigned int timeoutMs);
|
||||
|
||||
int waku_disconnect_peer_by_id(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *peerId);
|
||||
|
||||
int waku_disconnect_all_peers(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_dial_peer(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *peerMultiAddr,
|
||||
const char *protocol,
|
||||
int timeoutMs);
|
||||
|
||||
int waku_dial_peer_by_id(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *peerId,
|
||||
const char *protocol,
|
||||
int timeoutMs);
|
||||
|
||||
int waku_get_peerids_from_peerstore(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_get_connected_peers_info(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_get_peerids_by_protocol(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *protocol);
|
||||
|
||||
int waku_listen_addresses(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_get_connected_peers(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// Returns a list of multiaddress given a url to a DNS discoverable ENR tree
|
||||
// Parameters
|
||||
// char* entTreeUrl: URL containing a discoverable ENR tree
|
||||
// char* nameDnsServer: The nameserver to resolve the ENR tree url.
|
||||
// int timeoutMs: Timeout value in milliseconds to execute the call.
|
||||
int waku_dns_discovery(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *entTreeUrl,
|
||||
const char *nameDnsServer,
|
||||
int timeoutMs);
|
||||
|
||||
// Updates the bootnode list used for discovering new peers via DiscoveryV5
|
||||
// bootnodes - JSON array containing the bootnode ENRs i.e. `["enr:...", "enr:..."]`
|
||||
int waku_discv5_update_bootnodes(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
char *bootnodes);
|
||||
|
||||
int waku_start_discv5(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_stop_discv5(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
// Retrieves the ENR information
|
||||
int waku_get_my_enr(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_get_my_peerid(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_get_metrics(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
int waku_peer_exchange_request(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
int numPeers);
|
||||
|
||||
int waku_ping_peer(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *peerAddr,
|
||||
int timeoutMs);
|
||||
|
||||
int waku_is_online(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __liblogosdelivery_kernel__ */
|
||||
@ -512,6 +512,15 @@ task liblogosdeliveryGenBindingsRust, "Emit the Rust bindings for liblogosdelive
|
||||
"-d:ffiSrcPath=library/liblogosdelivery.nim " & getMyCPU() & getNimParams() &
|
||||
" --compileOnly library/liblogosdelivery.nim"
|
||||
|
||||
task liblogosdeliveryGenBindingsC, "Emit the C header for liblogosdelivery":
|
||||
# Replaces the hand-written liblogosdelivery.h; params ride as CBOR blobs
|
||||
# (the library's default ABI), so the emitted nim_ffi_cbor.h helpers marshal
|
||||
# them. --compileOnly for the same reason as the Rust task.
|
||||
exec "nim c --threads:on --mm:refc --skipParentCfg:off -d:discv5_protocol_id=d5waku " &
|
||||
"-d:ffiGenBindings -d:targetLang=c -d:ffiOutputDir=library/c_bindings " &
|
||||
"-d:ffiSrcPath=library/liblogosdelivery.nim " & getMyCPU() & getNimParams() &
|
||||
" --compileOnly library/liblogosdelivery.nim"
|
||||
|
||||
### Formatting tasks
|
||||
|
||||
task nphchanges, "Run nph on .nim/.nims/.nimble files changed on this branch/PR":
|
||||
|
||||
@ -139,8 +139,9 @@ pkgs.stdenv.mkDerivation {
|
||||
mkdir -p $out/lib $out/include
|
||||
cp build/liblogosdelivery.${libExt} $out/lib/ 2>/dev/null || true
|
||||
cp build/liblogosdelivery.a $out/lib/ 2>/dev/null || true
|
||||
cp library/liblogosdelivery.h $out/include/ 2>/dev/null || true
|
||||
cp library/liblogosdelivery_kernel.h $out/include/ 2>/dev/null || true
|
||||
cp library/c_bindings/logosdelivery.h $out/include/ 2>/dev/null || true
|
||||
cp library/c_bindings/nim_ffi_cbor.h $out/include/ 2>/dev/null || true
|
||||
cp library/c_bindings/nim_ffi_prelude.h $out/include/ 2>/dev/null || true
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user