feat: adapt libsds to nim-ffi 0.2 CBOR ABI

Rebuild the C wrapper on nim-ffi 0.2's high-level macros (declareLibrary +
{.ffiCtor.}/{.ffi.}/{.ffiEvent.}), which marshal parameters and results as
CBOR and expose snake_case sds_* entry points. Replaces the previous
hand-written positional/JSON ABI.

- request/response objects are {.ffi.} types (CBOR); the unwrap response is
  a proper nested object (message, channelId, missingDeps) instead of
  hand-built JSON, and retrievalHint travels as raw bytes (no base64).
- events become {.ffiEvent.} procs (message_ready, message_sent,
  missing_dependencies, periodic_sync, repair_ready), delivered to the host
  via sds_add_event_listener.
- the retrieval-hint provider stays hand-written (a C function pointer has
  no CBOR form); its pointers travel as uint64 through the request channel,
  and the provided buffer is freed with libc free to match the host's
  malloc (Go's C.CBytes).
- pin nim-ffi at the fix/foreign-host-concurrency-v0.2 branch (recycle pool
  + foreign-thread GC fixes) and add the cbor_serialization dependency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ivan FB 2026-06-19 23:36:22 +02:00
parent c88205dc82
commit e3b49d18a2
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
10 changed files with 423 additions and 654 deletions

View File

@ -1,6 +0,0 @@
type JsonEvent* = ref object of RootObj # https://rfc.vac.dev/spec/36/#jsonsignal-type
eventType* {.requiresInit.}: string
method `$`*(jsonEvent: JsonEvent): string {.base.} =
discard
# All events should implement this

View File

@ -1,16 +0,0 @@
import std/json
import ./json_base_event, sds/[message]
type JsonMessageReadyEvent* = ref object of JsonEvent
messageId*: SdsMessageID
channelId*: SdsChannelID
proc new*(
T: type JsonMessageReadyEvent, messageId: SdsMessageID, channelId: SdsChannelID
): T =
return JsonMessageReadyEvent(
eventType: "message_ready", messageId: messageId, channelId: channelId
)
method `$`*(jsonMessageReady: JsonMessageReadyEvent): string =
$(%*jsonMessageReady)

View File

@ -1,16 +0,0 @@
import std/json
import ./json_base_event, sds/[message]
type JsonMessageSentEvent* = ref object of JsonEvent
messageId*: SdsMessageID
channelId*: SdsChannelID
proc new*(
T: type JsonMessageSentEvent, messageId: SdsMessageID, channelId: SdsChannelID
): T =
return JsonMessageSentEvent(
eventType: "message_sent", messageId: messageId, channelId: channelId
)
method `$`*(jsonMessageSent: JsonMessageSentEvent): string =
$(%*jsonMessageSent)

View File

@ -1,34 +0,0 @@
import std/json
import ./json_base_event, sds/[message], std/base64
type JsonMissingDependenciesEvent* = ref object of JsonEvent
messageId*: SdsMessageID
missingDeps*: seq[HistoryEntry]
channelId*: SdsChannelID
proc new*(
T: type JsonMissingDependenciesEvent,
messageId: SdsMessageID,
missingDeps: seq[HistoryEntry],
channelId: SdsChannelID,
): T =
return JsonMissingDependenciesEvent(
eventType: "missing_dependencies",
messageId: messageId,
missingDeps: missingDeps,
channelId: channelId,
)
method `$`*(jsonMissingDependencies: JsonMissingDependenciesEvent): string =
var node = newJObject()
node["eventType"] = %*jsonMissingDependencies.eventType
node["messageId"] = %*jsonMissingDependencies.messageId
node["channelId"] = %*jsonMissingDependencies.channelId
var missingDepsNode = newJArray()
for dep in jsonMissingDependencies.missingDeps:
var depNode = newJObject()
depNode["messageId"] = %*dep.messageId
depNode["retrievalHint"] = %*encode(dep.retrievalHint)
missingDepsNode.add(depNode)
node["missingDeps"] = missingDepsNode
$node

View File

@ -1,10 +0,0 @@
import std/json
import ./json_base_event
type JsonPeriodicSyncEvent* = ref object of JsonEvent
proc new*(T: type JsonPeriodicSyncEvent): T =
return JsonPeriodicSyncEvent(eventType: "periodic_sync")
method `$`*(jsonPeriodicSync: JsonPeriodicSyncEvent): string =
$(%*jsonPeriodicSync)

View File

@ -1,20 +0,0 @@
import std/[json, base64]
import ./json_base_event, sds/[message]
type JsonRepairReadyEvent* = ref object of JsonEvent
channelId*: SdsChannelID
message*: seq[byte]
proc new*(
T: type JsonRepairReadyEvent, message: seq[byte], channelId: SdsChannelID
): T =
return JsonRepairReadyEvent(
eventType: "repair_ready", message: message, channelId: channelId
)
method `$`*(jsonRepairReady: JsonRepairReadyEvent): string =
var node = newJObject()
node["eventType"] = %*jsonRepairReady.eventType
node["channelId"] = %*jsonRepairReady.channelId
node["message"] = %*encode(jsonRepairReady.message)
$node

View File

@ -1,8 +1,11 @@
// Generated manually and inspired by the one generated by the Nim Compiler.
// In order to see the header file generated by Nim just run `make libsds`
// from the root repo folder and the header should be created in
// nimcache/release/libsds/libsds.h
// C API for libsds, built on the nim-ffi 0.2 framework.
//
// Parameters and results are marshalled as CBOR (RFC 8949): each request and
// response struct in library/libsds.nim is a CBOR map keyed by the exact field
// names; binary fields (message bytes, retrieval hints) are CBOR byte strings.
// Requests are passed in as a length-delimited CBOR buffer (reqCbor/reqCborLen)
// and results are delivered to the callback as a CBOR buffer (msg/len).
#ifndef __libsds__
#define __libsds__
@ -18,51 +21,61 @@
extern "C" {
#endif
// Result/event callback. On RET_OK, `msg` points to the CBOR-encoded payload of
// length `len` (an empty/void result is the single CBOR-null byte 0xf6). On
// RET_ERR, `msg` is the raw UTF-8 error string (NOT CBOR).
typedef void (*SdsCallBack) (int callerRet, const char* msg, size_t len, void* userData);
// Synchronous provider invoked by SDS-R to fetch a retrieval hint for a
// message id. The implementation allocates `*hint` (and sets `*hintLen`) with
// libc malloc; the library takes ownership and frees it with libc free.
typedef void (*SdsRetrievalHintProvider) (const char* messageId, char** hint, size_t* hintLen, void* userData);
// --- Core API Functions ---
void* SdsNewReliabilityManager(SdsCallBack callback, void* userData);
// Create a context + ReliabilityManager. reqCbor: CBOR of
// {"config":{"participantId":"..."}} (empty participantId disables SDS-R).
// Returns the context handle, or NULL on failure. The callback also fires on
// async completion.
void* sds_create(const uint8_t* reqCbor, size_t reqCborLen, SdsCallBack callback, void* userData);
void SdsSetEventCallback(void* ctx, SdsCallBack callback, void* userData);
// Register an event listener for `eventName` (message_ready, message_sent,
// missing_dependencies, periodic_sync, repair_ready). The callback receives a
// CBOR EventEnvelope {"eventType":"<name>","payload":{...}}. Returns a listener
// id (> 0) usable with sds_remove_event_listener, or 0 if the callback is NULL.
uint64_t sds_add_event_listener(void* ctx, const char* eventName, SdsCallBack callback, void* userData);
void SdsSetRetrievalHintProvider(void* ctx, SdsRetrievalHintProvider callback, void* userData);
// Remove a previously registered event listener. Returns RET_OK on success.
int sds_remove_event_listener(void* ctx, uint64_t listenerId);
int SdsCleanupReliabilityManager(void* ctx, SdsCallBack callback, void* userData);
// Register the retrieval-hint provider used by SDS-R.
int sds_set_retrieval_hint_provider(void* ctx, SdsRetrievalHintProvider callback, void* userData);
int SdsResetReliabilityManager(void* ctx, SdsCallBack callback, void* userData);
// reqCbor: CBOR of {"req":{"message":<bytes>,"messageId":"..","channelId":".."}}
// Result CBOR: {"message":<bytes>}
int sds_wrap_outgoing_message(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen);
int SdsWrapOutgoingMessage(void* ctx,
void* message,
size_t messageLen,
const char* messageId,
const char* channelId,
SdsCallBack callback,
void* userData);
// reqCbor: CBOR of {"req":{"message":<bytes>}}
// Result CBOR: {"message":<bytes>,"channelId":"..","missingDeps":[{"messageId":"..","retrievalHint":<bytes>}]}
int sds_unwrap_received_message(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen);
int SdsUnwrapReceivedMessage(void* ctx,
void* message,
size_t messageLen,
SdsCallBack callback,
void* userData);
// reqCbor: CBOR of {"req":{"messageIds":["..",".."],"channelId":".."}}
int sds_mark_dependencies_met(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen);
int SdsMarkDependenciesMet(void* ctx,
char** messageIDs,
size_t count,
const char* channelId,
SdsCallBack callback,
void* userData);
// No request payload — pass reqCbor=NULL, reqCborLen=0.
int sds_reset(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen);
int SdsStartPeriodicTasks(void* ctx, SdsCallBack callback, void* userData);
// No request payload — pass reqCbor=NULL, reqCborLen=0.
int sds_start_periodic_tasks(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen);
// Tear down the context created by sds_create.
int sds_destroy(void* ctx);
#ifdef __cplusplus
}
#endif
#endif /* __libsds__ */
#endif /* __libsds__ */

View File

@ -1,472 +1,282 @@
import std/[strutils, sequtils, json, base64, locks]
## C-compatible FFI wrapper around the SDS ReliabilityManager.
##
## Built on the `nim-ffi` 0.2 package's high-level macros: `declareLibrary`
## emits the bootstrap plus the per-event listener registry
## (`sds_add_event_listener` / `sds_remove_event_listener`);
## `{.ffiCtor.}`/`{.ffi.}`/`{.ffiDtor.}` generate the C entry points,
## marshalling parameters and return values as CBOR; `{.ffiEvent.}` declares
## the library-initiated events (also CBOR). Exported C names are snake_case
## (`sds_wrap_outgoing_message`, …); see `library/libsds.h`. The Go bindings
## (sds-go-bindings) must match this API.
##
## The one exception is `sds_set_retrieval_hint_provider`: it takes a C
## function pointer, which has no sensible CBOR representation, so it is
## hand-written and dispatched to the worker thread (the pointers travel as
## uint64 through the request channel) to store the provider in a thread-local.
import system/ansi_c
import ffi
import sds
import ./events/[
json_message_ready_event, json_message_sent_event, json_missing_dependencies_event,
json_periodic_sync_event, json_repair_ready_event,
]
# Emit the library bootstrap: the {.exported.}/{.callback.} pragmas, the
# `-fPIC`/soname linker flags, the `libsdsNimMain` import and the
# `initializeLibrary()` proc the exported entry points call on every hop.
declareLibraryBase("sds")
# C callback typedefs (mirrors libsds.h). `SdsCallBack` is structurally the
# nim-ffi `FFICallBack`; the alias keeps the exported signatures readable.
type SdsCallBack* = FFICallBack
# Bootstrap (pragmas, linker flags, libsdsNimMain, initializeLibrary) plus the
# `sds_add_event_listener` / `sds_remove_event_listener` C exports and the
# per-type `ReliabilityManagerFFIPool` used by the hand-written entry point
# below (the ffiCtor/ffiDtor macros declare it too, guarded by `when not
# declared`).
declareLibrary("sds", ReliabilityManager)
type SdsRetrievalHintProvider* = proc(
messageId: cstring, hint: ptr cstring, hintLen: ptr csize_t, userData: pointer
) {.cdecl, gcsafe, raises: [].}
# One pool per library type; the macros that would normally declare it
# (ffiCtor/ffiDtor) are not used here because we hand-write the entry points
# to preserve the exact C ABI, so we declare it explicitly.
var ReliabilityManagerFFIPool: FFIContextPool[ReliabilityManager]
# registerReqFFI inspects each request field's type via `$node`, which only
# handles plain identifiers — a bracketed `SharedSeq[byte]` makes it choke. The
# aliases give the generated request structs non-bracketed field types.
type
SdsSharedBytes = SharedSeq[byte]
SdsSharedCstrs = SharedSeq[cstring]
# The active retrieval-hint provider, stored per worker thread (one thread per
# context). Set by sds_set_retrieval_hint_provider via a dispatched request so
# the write lands on the worker thread, where the manager's hint closure reads
# it during message processing.
var sdsRetrievalHintCb {.threadvar.}: pointer
var sdsRetrievalHintUserData {.threadvar.}: pointer
################################################################################
### Retrieval-hint provider registry
###
### The retrieval-hint provider is a synchronous request/response callback
### (the C side returns bytes inline), so it does not fit the fire-and-forget
### event model. nim-ffi's FFIContext has no slot for it, so we keep a small
### per-context registry here. A fixed array of plain (non-GC) records keeps
### the lookup callable from the {.gcsafe.} hint closure running on the FFI
### thread.
### CBOR-marshalled request/response types
type RetrievalHintSlot = object
ctx: pointer
cb: pointer
userData: pointer
type SdsConfig* {.ffi.} = object
participantId: string ## empty disables SDS-R (see newReliabilityManager)
var retrievalHintSlots: array[MaxFFIContexts, RetrievalHintSlot]
var retrievalHintsLock: Lock
retrievalHintsLock.initLock()
type SdsWrapRequest* {.ffi.} = object
message: seq[byte]
messageId: string
channelId: string
proc setRetrievalHint(ctx: pointer, cb: pointer, userData: pointer) =
withLock retrievalHintsLock:
var free = -1
for i in 0 ..< MaxFFIContexts:
if retrievalHintSlots[i].ctx == ctx:
retrievalHintSlots[i] = RetrievalHintSlot(ctx: ctx, cb: cb, userData: userData)
return
if free < 0 and retrievalHintSlots[i].ctx.isNil:
free = i
if free >= 0:
retrievalHintSlots[free] = RetrievalHintSlot(ctx: ctx, cb: cb, userData: userData)
type SdsWrapResponse* {.ffi.} = object
message: seq[byte]
proc getRetrievalHint(ctx: pointer): tuple[cb: pointer, userData: pointer] {.gcsafe.} =
withLock retrievalHintsLock:
for i in 0 ..< MaxFFIContexts:
if retrievalHintSlots[i].ctx == ctx:
return (retrievalHintSlots[i].cb, retrievalHintSlots[i].userData)
return (nil, nil)
type SdsUnwrapRequest* {.ffi.} = object
message: seq[byte]
proc clearRetrievalHint(ctx: pointer) =
withLock retrievalHintsLock:
for i in 0 ..< MaxFFIContexts:
if retrievalHintSlots[i].ctx == ctx:
retrievalHintSlots[i] = RetrievalHintSlot()
return
# One missing dependency: the message id plus an optional retrieval hint. The
# hint is a raw byte string on the CBOR wire (no base64, unlike the old JSON).
type SdsMissingDep* {.ffi.} = object
messageId: string
retrievalHint: seq[byte]
type SdsUnwrapResponse* {.ffi.} = object
message: seq[byte]
channelId: string
missingDeps: seq[SdsMissingDep]
type SdsMarkDependenciesRequest* {.ffi.} = object
messageIds: seq[string]
channelId: string
################################################################################
### Shared-memory copy helpers
###
### Request payloads carrying binary/pointer data must be deep-copied into
### shared memory on the caller thread, because the FFI thread acks receipt
### before it reads the payload — the caller may free its buffer in between.
### cstring fields are deep-copied by the generated `ffiNewReq`; raw byte and
### `char**` arrays are not, so we copy them here.
### Library-initiated events (CBOR EventEnvelope via {.ffiEvent.})
proc copyToSharedSeqByte(p: pointer, len: int): SharedSeq[byte] =
if p.isNil or len <= 0:
return (cast[ptr UncheckedArray[byte]](nil), 0)
let data = allocShared(len)
copyMem(data, p, len)
return (cast[ptr UncheckedArray[byte]](data), len)
type SdsMessageReadyEvent* {.ffi.} = object
messageId: string
channelId: string
proc copyToSharedSeqCstr(p: pointer, count: int): SharedSeq[cstring] =
if p.isNil or count <= 0:
return (cast[ptr UncheckedArray[cstring]](nil), 0)
let data = cast[ptr UncheckedArray[cstring]](allocShared(sizeof(cstring) * count))
let src = cast[ptr UncheckedArray[cstring]](p)
for i in 0 ..< count:
data[i] = src[i].alloc()
return (data, count)
type SdsMessageSentEvent* {.ffi.} = object
messageId: string
channelId: string
proc freeSharedSeqCstr(s: var SharedSeq[cstring]) =
if not s.data.isNil():
for i in 0 ..< s.len:
if not s.data[i].isNil:
deallocShared(s.data[i])
deallocShared(s.data)
s.len = 0
type SdsMissingDependenciesEvent* {.ffi.} = object
messageId: string
missingDeps: seq[SdsMissingDep]
channelId: string
type SdsPeriodicSyncEvent* {.ffi.} = object
ok: bool ## carries no data; a field keeps the CBOR map non-degenerate
type SdsRepairReadyEvent* {.ffi.} = object
message: seq[byte]
channelId: string
proc onMessageReady*(evt: SdsMessageReadyEvent) {.ffiEvent: "message_ready".}
proc onMessageSent*(evt: SdsMessageSentEvent) {.ffiEvent: "message_sent".}
proc onMissingDependencies*(
evt: SdsMissingDependenciesEvent
) {.ffiEvent: "missing_dependencies".}
proc onPeriodicSync*(evt: SdsPeriodicSyncEvent) {.ffiEvent: "periodic_sync".}
proc onRepairReady*(evt: SdsRepairReadyEvent) {.ffiEvent: "repair_ready".}
proc toMissingDeps(entries: seq[HistoryEntry]): seq[SdsMissingDep] =
var deps = newSeq[SdsMissingDep](entries.len)
for i, entry in entries:
deps[i] =
SdsMissingDep(messageId: entry.messageId, retrievalHint: entry.retrievalHint)
return deps
################################################################################
### Event callbacks
### Constructor — creates the FFI context and the ReliabilityManager.
###
### These build the AppCallbacks closures handed to the ReliabilityManager.
### They run on the FFI worker thread and forward JSON event payloads to the
### C callback registered via SdsSetEventCallback (stored on the context).
### The event closures run on the worker thread and forward CBOR payloads to
### the listeners registered via sds_add_event_listener (the {.ffiEvent.} procs
### read the per-thread event queue, so no context handle is needed here).
proc onMessageReady(ctx: ptr FFIContext[ReliabilityManager]): MessageReadyCallback =
return proc(messageId: SdsMessageID, channelId: SdsChannelID) {.gcsafe.} =
callEventCallback(ctx, "onMessageReady"):
$JsonMessageReadyEvent.new(messageId, channelId)
proc sdsCreate*(
config: SdsConfig
): Future[Result[ReliabilityManager, string]] {.ffiCtor.} =
# The ctor body runs on the (possibly recycled) worker thread. Drop any
# retrieval-hint provider left over from a previous owner of this thread so a
# stale C function pointer is never invoked.
sdsRetrievalHintCb = nil
sdsRetrievalHintUserData = nil
proc onMessageSent(ctx: ptr FFIContext[ReliabilityManager]): MessageSentCallback =
return proc(messageId: SdsMessageID, channelId: SdsChannelID) {.gcsafe.} =
callEventCallback(ctx, "onMessageSent"):
$JsonMessageSentEvent.new(messageId, channelId)
let rm = newReliabilityManager(
participantId = config.participantId.SdsParticipantID
).valueOr:
error "Failed creating reliability manager", error = error
return err("Failed creating reliability manager: " & $error)
proc onMissingDependencies(
ctx: ptr FFIContext[ReliabilityManager]
): MissingDependenciesCallback =
return proc(
let messageReadyCb = proc(
messageId: SdsMessageID, channelId: SdsChannelID
) {.gcsafe.} =
onMessageReady(SdsMessageReadyEvent(messageId: messageId, channelId: channelId))
let messageSentCb = proc(
messageId: SdsMessageID, channelId: SdsChannelID
) {.gcsafe.} =
onMessageSent(SdsMessageSentEvent(messageId: messageId, channelId: channelId))
let missingDependenciesCb = proc(
messageId: SdsMessageID, missingDeps: seq[HistoryEntry], channelId: SdsChannelID
) {.gcsafe.} =
callEventCallback(ctx, "onMissingDependencies"):
$JsonMissingDependenciesEvent.new(messageId, missingDeps, channelId)
proc onPeriodicSync(ctx: ptr FFIContext[ReliabilityManager]): PeriodicSyncCallback =
return proc() {.gcsafe.} =
callEventCallback(ctx, "onPeriodicSync"):
$JsonPeriodicSyncEvent.new()
proc onRepairReady(ctx: ptr FFIContext[ReliabilityManager]): RepairReadyCallback =
return proc(message: seq[byte], channelId: SdsChannelID) {.gcsafe.} =
callEventCallback(ctx, "onRepairReady"):
$JsonRepairReadyEvent.new(message, channelId)
proc onRetrievalHint(ctx: ptr FFIContext[ReliabilityManager]): RetrievalHintProvider =
return proc(messageId: SdsMessageID): seq[byte] {.gcsafe.} =
let (cb, userData) = getRetrievalHint(cast[pointer](ctx))
if cb.isNil():
return @[]
var hint: cstring
var hintLen: csize_t
cast[SdsRetrievalHintProvider](cb)(
messageId.cstring, addr hint, addr hintLen, userData
onMissingDependencies(
SdsMissingDependenciesEvent(
messageId: messageId,
missingDeps: toMissingDeps(missingDeps),
channelId: channelId,
)
)
let periodicSyncCb = proc() {.gcsafe.} =
onPeriodicSync(SdsPeriodicSyncEvent(ok: true))
let repairReadyCb = proc(message: seq[byte], channelId: SdsChannelID) {.gcsafe.} =
onRepairReady(SdsRepairReadyEvent(message: message, channelId: channelId))
let retrievalHintProvider = proc(messageId: SdsMessageID): seq[byte] {.gcsafe.} =
if sdsRetrievalHintCb.isNil():
return @[]
var hint: cstring
var hintLen: csize_t
cast[SdsRetrievalHintProvider](sdsRetrievalHintCb)(
messageId.cstring, addr hint, addr hintLen, sdsRetrievalHintUserData
)
if not hint.isNil() and hintLen > 0:
var hintBytes = newSeq[byte](hintLen)
copyMem(addr hintBytes[0], hint, hintLen)
deallocShared(hint)
# The provider allocates *hint with libc malloc (Go's C.CBytes); free it
# with libc free, not Nim's deallocShared, to keep the allocator paired.
c_free(cast[pointer](hint))
return hintBytes
return @[]
await rm.setCallbacks(
messageReadyCb, messageSentCb, missingDependenciesCb, periodicSyncCb,
retrievalHintProvider, repairReadyCb,
)
return ok(rm)
################################################################################
### Request handlers (executed on the FFI worker thread)
### Async methods — each runs its body on the worker thread.
registerReqFFI(SdsCreateRmReq, ctx: ptr FFIContext[ReliabilityManager]):
proc(): Future[Result[string, string]] {.async.} =
# TODO: thread `participantId` through SdsNewReliabilityManager FFI input
# and remove this hardcoded "". Empty id silently disables SDS-R; this is
# acceptable as a temporary FFI-only fallback until sds-go-bindings and
# logos-delivery's C-side caller are updated to supply the identity.
let rm = newReliabilityManager(participantId = "".SdsParticipantID).valueOr:
error "Failed creating reliability manager", error = error
return err("Failed creating reliability manager: " & $error)
proc sdsWrapOutgoingMessage*(
rm: ReliabilityManager, req: SdsWrapRequest
): Future[Result[SdsWrapResponse, string]] {.ffi.} =
let wrapped = (
await wrapOutgoingMessage(rm, req.message, req.messageId, req.channelId)
).valueOr:
error "WRAP_MESSAGE failed", error = error
return err("error processing wrap request: " & $error)
return ok(SdsWrapResponse(message: wrapped))
await rm.setCallbacks(
onMessageReady(ctx), onMessageSent(ctx), onMissingDependencies(ctx),
onPeriodicSync(ctx), onRetrievalHint(ctx), onRepairReady(ctx),
proc sdsUnwrapReceivedMessage*(
rm: ReliabilityManager, req: SdsUnwrapRequest
): Future[Result[SdsUnwrapResponse, string]] {.ffi.} =
let (unwrapped, missingDeps, channelId) = (
await unwrapReceivedMessage(rm, req.message)
).valueOr:
return err("error processing unwrap request: " & $error)
return ok(
SdsUnwrapResponse(
message: unwrapped, channelId: channelId, missingDeps: toMissingDeps(missingDeps)
)
)
# nim-ffi frees myLib on recycle, so (re)allocate it here.
if ctx.myLib.isNil():
ctx.myLib = createShared(ReliabilityManager)
ctx.myLib[] = rm
return ok("")
proc sdsMarkDependenciesMet*(
rm: ReliabilityManager, req: SdsMarkDependenciesRequest
): Future[Result[string, string]] {.ffi.} =
(await markDependenciesMet(rm, req.messageIds, req.channelId)).isOkOr:
error "MARK_DEPENDENCIES_MET failed", error = error
return err("error processing mark-dependencies request: " & $error)
return ok("")
registerReqFFI(SdsResetRmReq, ctx: ptr FFIContext[ReliabilityManager]):
proc(): Future[Result[string, string]] {.async.} =
(await resetReliabilityManager(ctx.myLib[])).isOkOr:
error "RESET_RELIABILITY_MANAGER failed", error = error
return err("error processing RESET_RELIABILITY_MANAGER request: " & $error)
return ok("")
proc sdsReset*(rm: ReliabilityManager): Future[Result[string, string]] {.ffi.} =
(await resetReliabilityManager(rm)).isOkOr:
error "RESET failed", error = error
return err("error processing reset request: " & $error)
return ok("")
registerReqFFI(SdsStartPeriodicTasksReq, ctx: ptr FFIContext[ReliabilityManager]):
proc(): Future[Result[string, string]] {.async.} =
ctx.myLib[].startPeriodicTasks()
return ok("")
registerReqFFI(SdsWrapMessageReq, ctx: ptr FFIContext[ReliabilityManager]):
proc(
message: SdsSharedBytes, messageId: cstring, channelId: cstring
): Future[Result[string, string]] {.async.} =
var msg = message
defer:
deallocSharedSeq(msg)
let wrappedMessage = (
await wrapOutgoingMessage(ctx.myLib[], message.toSeq(), $messageId, $channelId)
).valueOr:
error "WRAP_MESSAGE failed", error = error
return err("error processing WRAP_MESSAGE request: " & $error)
# returns a comma-separated string of bytes
return ok(wrappedMessage.mapIt($it).join(","))
registerReqFFI(SdsUnwrapMessageReq, ctx: ptr FFIContext[ReliabilityManager]):
proc(message: SdsSharedBytes): Future[Result[string, string]] {.async.} =
var msg = message
defer:
deallocSharedSeq(msg)
let (unwrappedMessage, missingDeps, extractedChannelId) = (
await unwrapReceivedMessage(ctx.myLib[], message.toSeq())
).valueOr:
return err("error processing UNWRAP_MESSAGE request: " & $error)
# return the result as a json string
var node = newJObject()
node["message"] = %*unwrappedMessage
node["channelId"] = %*extractedChannelId
var missingDepsNode = newJArray()
for dep in missingDeps:
var depNode = newJObject()
depNode["messageId"] = %*dep.messageId
depNode["retrievalHint"] = %*encode(dep.retrievalHint)
missingDepsNode.add(depNode)
node["missingDeps"] = missingDepsNode
return ok($node)
registerReqFFI(SdsMarkDepsReq, ctx: ptr FFIContext[ReliabilityManager]):
proc(
messageIds: SdsSharedCstrs, channelId: cstring
): Future[Result[string, string]] {.async.} =
var ids = messageIds
defer:
freeSharedSeqCstr(ids)
let messageIdSeq = ids.toSeq().mapIt($it)
(await markDependenciesMet(ctx.myLib[], messageIdSeq, $channelId)).isOkOr:
error "MARK_DEPENDENCIES_MET failed", error = error
return err("error processing MARK_DEPENDENCIES_MET request: " & $error)
return ok("")
proc sdsStartPeriodicTasks*(
rm: ReliabilityManager
): Future[Result[string, string]] {.ffi.} =
# The empty await forces the macro down its async path so the body runs on the
# worker thread — startPeriodicTasks schedules futures on that thread's loop.
await sleepAsync(chronos.milliseconds(0))
rm.startPeriodicTasks()
return ok("")
################################################################################
### Dispatch helper
###
### Sends a request to the FFI worker thread and returns RET_OK/RET_ERR,
### reporting any failure through the callback. The try/except keeps the
### exported entry points `raises: []` (sendRequestToFFIThread can raise),
### which `processReq` alone would not guarantee.
### Destructor — runs library cleanup then tears down the FFI context.
template dispatchReq(
ctx: untyped, callback: FFICallBack, userData: pointer, reqExpr: untyped
) =
let sendRes =
try:
ffi_context.sendRequestToFFIThread(ctx, reqExpr)
except Exception as exc:
Result[void, string].err("sendRequestToFFIThread exception: " & exc.msg)
if sendRes.isErr():
let m = "libsds error: " & sendRes.error
callback(RET_ERR, unsafeAddr m[0], cast[csize_t](m.len), userData)
return RET_ERR
return RET_OK
proc sdsDestroy*(rm: ReliabilityManager) {.ffiDtor.} =
discard
################################################################################
### Exported C entry points (called from the application thread)
###
### Signatures must match library/libsds.h exactly. Each one validates the
### context against the pool (rejecting nil/dangling pointers at the boundary),
### checks the callback, deep-copies any pointer payloads into shared memory,
### then dispatches a request to the FFI worker thread.
### Retrieval-hint provider (hand-written: a C function pointer cannot be passed
### as CBOR). The setter dispatches a request — the provider/userData pointers
### travel as uint64 — so the provider is stored in the worker thread's
### thread-local, where sdsCreate's hint closure reads it.
proc SdsNewReliabilityManager(
callback: FFICallBack, userData: pointer
): pointer {.dynlib, exportc, cdecl, raises: [].} =
initializeLibrary()
proc sdsNoopCallback(
callerRet: cint, msg: ptr cchar, len: csize_t, userData: pointer
) {.cdecl, gcsafe, raises: [].} =
discard
if isNil(callback):
echo "error: missing callback in SdsNewReliabilityManager"
return nil
registerReqFFI(SdsSetHintReq, ctx: ptr FFIContext[ReliabilityManager]):
proc(cbPtr: uint64, udPtr: uint64): Future[Result[string, string]] {.async.} =
sdsRetrievalHintCb = cast[pointer](cbPtr)
sdsRetrievalHintUserData = cast[pointer](udPtr)
return ok("")
let ctx = ReliabilityManagerFFIPool.createFFIContext().valueOr:
let msg = "Error creating SDS FFI context: " & $error
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
return nil
let sendRes =
try:
ffi_context.sendRequestToFFIThread(ctx, SdsCreateRmReq.ffiNewReq(callback, userData))
except Exception as exc:
Result[void, string].err("sendRequestToFFIThread exception: " & exc.msg)
if sendRes.isErr():
let msg = "error creating reliability manager: " & sendRes.error
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
discard ReliabilityManagerFFIPool.destroyFFIContext(ctx)
return nil
return cast[pointer](ctx)
proc SdsSetEventCallback(
ctx: ptr FFIContext[ReliabilityManager], callback: FFICallBack, userData: pointer
) {.dynlib, exportc, cdecl, raises: [].} =
initializeLibrary()
if not ReliabilityManagerFFIPool.isValidCtx(cast[pointer](ctx)):
echo "error: invalid context in SdsSetEventCallback"
return
ctx[].callbackState.callback = cast[pointer](callback)
ctx[].callbackState.userData = userData
proc SdsSetRetrievalHintProvider(
proc sds_set_retrieval_hint_provider(
ctx: ptr FFIContext[ReliabilityManager],
callback: SdsRetrievalHintProvider,
userData: pointer,
) {.dynlib, exportc, cdecl, raises: [].} =
initializeLibrary()
if not ReliabilityManagerFFIPool.isValidCtx(cast[pointer](ctx)):
echo "error: invalid context in SdsSetRetrievalHintProvider"
return
setRetrievalHint(cast[pointer](ctx), cast[pointer](callback), userData)
proc SdsCleanupReliabilityManager(
ctx: ptr FFIContext[ReliabilityManager], callback: FFICallBack, userData: pointer
): cint {.dynlib, exportc, cdecl, raises: [].} =
initializeLibrary()
if not ReliabilityManagerFFIPool.isValidCtx(cast[pointer](ctx)):
return RET_ERR
if isNil(callback):
return RET_MISSING_CALLBACK
clearRetrievalHint(cast[pointer](ctx))
# Recycle (not destroy) to reuse the worker + its fds. NON-BLOCKING: the FFI
# thread fires `callback` once drained; the caller blocks on it, not the return.
let res = releaseFFIContext(ctx, callback, userData)
if res.isErr():
let msg = "error cleaning up reliability manager: " & res.error
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
let sendRes =
try:
ffi_context.sendRequestToFFIThread(
ctx,
SdsSetHintReq.ffiNewReq(
sdsNoopCallback, nil, cast[uint64](cast[pointer](callback)),
cast[uint64](userData),
),
)
except Exception as exc:
Result[void, string].err("sendRequestToFFIThread exception: " & exc.msg)
if sendRes.isErr():
return RET_ERR
return RET_OK
proc SdsResetReliabilityManager(
ctx: ptr FFIContext[ReliabilityManager], callback: FFICallBack, userData: pointer
): cint {.dynlib, exportc, cdecl, raises: [].} =
initializeLibrary()
if not ReliabilityManagerFFIPool.isValidCtx(cast[pointer](ctx)):
return RET_ERR
if isNil(callback):
return RET_MISSING_CALLBACK
dispatchReq(ctx, callback, userData, SdsResetRmReq.ffiNewReq(callback, userData))
proc SdsWrapOutgoingMessage(
ctx: ptr FFIContext[ReliabilityManager],
message: pointer,
messageLen: csize_t,
messageId: cstring,
channelId: cstring,
callback: FFICallBack,
userData: pointer,
): cint {.dynlib, exportc, cdecl, raises: [].} =
initializeLibrary()
if not ReliabilityManagerFFIPool.isValidCtx(cast[pointer](ctx)):
return RET_ERR
if isNil(callback):
return RET_MISSING_CALLBACK
if message == nil and messageLen > 0:
let msg = "libsds error: message pointer is NULL but length > 0"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
return RET_ERR
if messageId == nil:
let msg = "libsds error: message ID pointer is NULL"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
return RET_ERR
if channelId == nil:
let msg = "libsds error: channel ID pointer is NULL"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
return RET_ERR
if $channelId == "":
let msg = "libsds error: channel ID is empty string"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
return RET_ERR
let sharedMsg = copyToSharedSeqByte(message, messageLen.int)
dispatchReq(
ctx, callback, userData,
SdsWrapMessageReq.ffiNewReq(callback, userData, sharedMsg, messageId, channelId),
)
proc SdsUnwrapReceivedMessage(
ctx: ptr FFIContext[ReliabilityManager],
message: pointer,
messageLen: csize_t,
callback: FFICallBack,
userData: pointer,
): cint {.dynlib, exportc, cdecl, raises: [].} =
initializeLibrary()
if not ReliabilityManagerFFIPool.isValidCtx(cast[pointer](ctx)):
return RET_ERR
if isNil(callback):
return RET_MISSING_CALLBACK
if message == nil and messageLen > 0:
let msg = "libsds error: message pointer is NULL but length > 0"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
return RET_ERR
let sharedMsg = copyToSharedSeqByte(message, messageLen.int)
dispatchReq(ctx, callback, userData, SdsUnwrapMessageReq.ffiNewReq(callback, userData, sharedMsg))
proc SdsMarkDependenciesMet(
ctx: ptr FFIContext[ReliabilityManager],
messageIds: pointer,
count: csize_t,
channelId: cstring,
callback: FFICallBack,
userData: pointer,
): cint {.dynlib, exportc, cdecl, raises: [].} =
initializeLibrary()
if not ReliabilityManagerFFIPool.isValidCtx(cast[pointer](ctx)):
return RET_ERR
if isNil(callback):
return RET_MISSING_CALLBACK
if messageIds == nil and count > 0:
let msg = "libsds error: MessageIDs pointer is NULL but count > 0"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
return RET_ERR
if channelId == nil:
let msg = "libsds error: channel ID pointer is NULL"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
return RET_ERR
if $channelId == "":
let msg = "libsds error: channel ID is empty string"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
return RET_ERR
let sharedIds = copyToSharedSeqCstr(messageIds, count.int)
dispatchReq(
ctx, callback, userData,
SdsMarkDepsReq.ffiNewReq(callback, userData, sharedIds, channelId),
)
proc SdsStartPeriodicTasks(
ctx: ptr FFIContext[ReliabilityManager], callback: FFICallBack, userData: pointer
): cint {.dynlib, exportc, cdecl, raises: [].} =
initializeLibrary()
if not ReliabilityManagerFFIPool.isValidCtx(cast[pointer](ctx)):
return RET_ERR
if isNil(callback):
return RET_MISSING_CALLBACK
dispatchReq(ctx, callback, userData, SdsStartPeriodicTasksReq.ffiNewReq(callback, userData))
# Emit binding metadata (no-op unless -d:ffiGenBindings). Must follow every
# {.ffi.}/{.ffiCtor.}/{.ffiDtor.} annotation.
genBindings()

View File

@ -1,76 +1,52 @@
{
"version": 2,
"packages": {
"nim": {
"version": "2.2.10",
"vcsRevision": "9fe2137fa2f3f66cf5a44f357d461829ac9e20c4",
"url": "https://github.com/nim-lang/Nim.git",
"downloadMethod": "git",
"dependencies": [],
"checksums": {
"sha1": "17ec440fdb89f8903db29a17898af590087d2b64"
}
},
"unittest2": {
"version": "0.2.5",
"vcsRevision": "26f2ef3ae0ec72a2a75bfe557e02e88f6a31c189",
"url": "https://github.com/status-im/nim-unittest2",
"downloadMethod": "git",
"dependencies": [],
"dependencies": [
"nim"
],
"checksums": {
"sha1": "02bb3751ba9ddc3c17bfd89f2e41cb6bfb8fc0c9"
}
},
"bearssl": {
"version": "0.2.6",
"vcsRevision": "11e798b62b8e6beabe958e048e9e24c7e0f9ee63",
"version": "0.2.8",
"vcsRevision": "22c6a76ce015bc07e011562bdcfc51d9446c1e82",
"url": "https://github.com/status-im/nim-bearssl",
"downloadMethod": "git",
"dependencies": [
"nim",
"unittest2"
],
"checksums": {
"sha1": "7e068f119664cf47ad0cfb74ef4c56fb6b616523"
"sha1": "da4dd7ae96d536bdaf42dca9c85d7aed024b6a86"
}
},
"results": {
"version": "0.5.1",
"vcsRevision": "df8113dda4c2d74d460a8fa98252b0b771bf1f27",
"url": "https://github.com/arnetheduck/nim-results",
"downloadMethod": "git",
"dependencies": [],
"checksums": {
"sha1": "a9c011f74bc9ed5c91103917b9f382b12e82a9e7"
}
},
"stew": {
"version": "0.4.2",
"vcsRevision": "b66168735d6f3841c5239c3169d3fe5fe98b1257",
"url": "https://github.com/status-im/nim-stew",
"testutils": {
"version": "0.8.1",
"vcsRevision": "6ce5e5e2301ccbc04b09d27ff78741ff4d352b4d",
"url": "https://github.com/status-im/nim-testutils",
"downloadMethod": "git",
"dependencies": [
"results",
"nim",
"unittest2"
],
"checksums": {
"sha1": "928e82cb8d2f554e8f10feb2349ee9c32fee3a8c"
}
},
"faststreams": {
"version": "0.5.0",
"vcsRevision": "ce27581a3e881f782f482cb66dc5b07a02bd615e",
"url": "https://github.com/status-im/nim-faststreams",
"downloadMethod": "git",
"dependencies": [
"stew",
"unittest2"
],
"checksums": {
"sha1": "ee61e507b805ae1df7ec936f03f2d101b0d72383"
}
},
"serialization": {
"version": "0.5.2",
"vcsRevision": "b0f2fa32960ea532a184394b0f27be37bd80248b",
"url": "https://github.com/status-im/nim-serialization",
"downloadMethod": "git",
"dependencies": [
"faststreams",
"unittest2",
"stew"
],
"checksums": {
"sha1": "fa35c1bb76a0a02a2379fe86eaae0957c7527cb8"
"sha1": "96a11cf8b84fa9bd12d4a553afa1cc4b7f9df4e3"
}
},
"npeg": {
@ -78,17 +54,122 @@
"vcsRevision": "409f6796d0e880b3f0222c964d1da7de6e450811",
"url": "https://github.com/zevv/npeg",
"downloadMethod": "git",
"dependencies": [],
"dependencies": [
"nim"
],
"checksums": {
"sha1": "64f15c85a059c889cb11c5fe72372677c50da621"
}
},
"protobuf_serialization": {
"results": {
"version": "0.5.1",
"vcsRevision": "d9aa950b9d9e8bfc8a201740042b5e8ea5880875",
"vcsRevision": "df8113dda4c2d74d460a8fa98252b0b771bf1f27",
"url": "https://github.com/arnetheduck/nim-results",
"downloadMethod": "git",
"dependencies": [
"nim"
],
"checksums": {
"sha1": "a9c011f74bc9ed5c91103917b9f382b12e82a9e7"
}
},
"stew": {
"version": "0.5.0",
"vcsRevision": "4382b18f04b3c43c8409bfcd6b62063773b2bbaa",
"url": "https://github.com/status-im/nim-stew",
"downloadMethod": "git",
"dependencies": [
"nim",
"results",
"unittest2"
],
"checksums": {
"sha1": "db22942939773ab7d5a0f2b2668c237240c67dd6"
}
},
"httputils": {
"version": "0.4.1",
"vcsRevision": "f142cb2e8bd812dd002a6493b6082827bb248592",
"url": "https://github.com/status-im/nim-http-utils",
"downloadMethod": "git",
"dependencies": [
"nim",
"stew",
"results",
"unittest2"
],
"checksums": {
"sha1": "016774ab31c3afff9a423f7d80584905ee59c570"
}
},
"chronos": {
"version": "4.2.2",
"vcsRevision": "45f43a9ad8bd8bcf5903b42f365c1c879bd54240",
"url": "https://github.com/status-im/nim-chronos",
"downloadMethod": "git",
"dependencies": [
"nim",
"results",
"stew",
"bearssl",
"httputils",
"unittest2"
],
"checksums": {
"sha1": "3a4c9477df8cef20a04e4f1b54a2d74fdfc2a3d0"
}
},
"metrics": {
"version": "0.2.1",
"vcsRevision": "a1296caf3ebb5f30f51a5feae7749a30df2824c2",
"url": "https://github.com/status-im/nim-metrics",
"downloadMethod": "git",
"dependencies": [
"nim",
"chronos",
"results",
"stew"
],
"checksums": {
"sha1": "84bb09873d7677c06046f391c7b473cd2fcff8a2"
}
},
"faststreams": {
"version": "0.5.1",
"vcsRevision": "50889cd16ec8771106cdd0eeea460039e8571e06",
"url": "https://github.com/status-im/nim-faststreams",
"downloadMethod": "git",
"dependencies": [
"nim",
"stew",
"unittest2"
],
"checksums": {
"sha1": "969ceb3666e807db8fe5c8df63466749822367a9"
}
},
"serialization": {
"version": "0.5.3",
"vcsRevision": "4092500cea76154576539371709ae801afbd2a9d",
"url": "https://github.com/status-im/nim-serialization",
"downloadMethod": "git",
"dependencies": [
"nim",
"faststreams",
"unittest2",
"stew"
],
"checksums": {
"sha1": "c087d26c50da40436599163888532660d6f9e631"
}
},
"protobuf_serialization": {
"version": "0.5.2",
"vcsRevision": "cec5f1da897c0b3e6d3a1f2da6a36b4bbdc3a1a8",
"url": "https://github.com/status-im/nim-protobuf-serialization",
"downloadMethod": "git",
"dependencies": [
"nim",
"stew",
"faststreams",
"serialization",
@ -96,7 +177,22 @@
"unittest2"
],
"checksums": {
"sha1": "c02d1124931612041a25c6c8ed59a6120849c85d"
"sha1": "48db6535e6c85825c7761820388a0e50c8f3eab3"
}
},
"cbor_serialization": {
"version": "#v0.3.0",
"vcsRevision": "1664160e04d153573373afddc552b9cbf6fbe4dc",
"url": "https://github.com/vacp2p/nim-cbor-serialization",
"downloadMethod": "git",
"dependencies": [
"nim",
"serialization",
"stew",
"results"
],
"checksums": {
"sha1": "ab126eae09a6e39c72972a6a0b83cb06a2ffe8f0"
}
},
"json_serialization": {
@ -105,6 +201,7 @@
"url": "https://github.com/status-im/nim-json-serialization",
"downloadMethod": "git",
"dependencies": [
"nim",
"faststreams",
"serialization",
"stew",
@ -114,24 +211,13 @@
"sha1": "8b3115354104858a0ac9019356fb29720529c2bd"
}
},
"testutils": {
"version": "0.8.0",
"vcsRevision": "e4d37dc1652d5c63afb89907efb5a5e812261797",
"url": "https://github.com/status-im/nim-testutils",
"downloadMethod": "git",
"dependencies": [
"unittest2"
],
"checksums": {
"sha1": "d1678f50aa47d113b4e77d41eec2190830b523fa"
}
},
"chronicles": {
"version": "0.12.2",
"vcsRevision": "27ec507429a4eb81edc20f28292ee8ec420be05b",
"url": "https://github.com/status-im/nim-chronicles",
"downloadMethod": "git",
"dependencies": [
"nim",
"faststreams",
"serialization",
"json_serialization",
@ -141,56 +227,13 @@
"sha1": "02febb20d088120b2836d3306cfa21f434f88f65"
}
},
"httputils": {
"version": "0.4.0",
"vcsRevision": "c53852d9e24205b6363bba517fa8ee7bde823691",
"url": "https://github.com/status-im/nim-http-utils",
"downloadMethod": "git",
"dependencies": [
"stew",
"results",
"unittest2"
],
"checksums": {
"sha1": "298bc5b6fe4e5aa9c3b7e2ebfa17191675020f10"
}
},
"chronos": {
"version": "4.0.4",
"vcsRevision": "0646c444fce7c7ed08ef6f2c9a7abfd172ffe655",
"url": "https://github.com/status-im/nim-chronos",
"downloadMethod": "git",
"dependencies": [
"results",
"stew",
"bearssl",
"httputils",
"unittest2"
],
"checksums": {
"sha1": "455802a90204d8ad6b31d53f2efff8ebfe4c834a"
}
},
"metrics": {
"version": "0.1.2",
"vcsRevision": "11d0cddfb0e711aa2a8c75d1892ae24a64c299fc",
"url": "https://github.com/status-im/nim-metrics",
"downloadMethod": "git",
"dependencies": [
"chronos",
"results",
"stew"
],
"checksums": {
"sha1": "5cdac99d85d3c146d170e85064c88fb28f377842"
}
},
"stint": {
"version": "0.8.2",
"vcsRevision": "470b7892561b5179ab20bd389a69217d6213fe58",
"url": "https://github.com/status-im/nim-stint",
"downloadMethod": "git",
"dependencies": [
"nim",
"stew",
"unittest2"
],
@ -203,23 +246,27 @@
"vcsRevision": "9e8ccc754631ac55ac2fd495e167e74e86293edb",
"url": "https://github.com/status-im/nim-taskpools",
"downloadMethod": "git",
"dependencies": [],
"dependencies": [
"nim"
],
"checksums": {
"sha1": "09e1b2fdad55b973724d61227971afc0df0b7a81"
}
},
"ffi": {
"version": "#v0.1.5-rc.1",
"vcsRevision": "e86b136e793adc9617e3c85ff4e8abe256a80100",
"version": "#fix/foreign-host-concurrency-v0.2",
"vcsRevision": "c3d135f46f809e09602d44f48949c2a4b2688e37",
"url": "https://github.com/logos-messaging/nim-ffi",
"downloadMethod": "git",
"dependencies": [
"nim",
"chronos",
"chronicles",
"taskpools"
"taskpools",
"cbor_serialization"
],
"checksums": {
"sha1": "301ae3a3a6889a1641a102a2c74798d1d82e6efb"
"sha1": "05f23ea84128482302e0ac43d6d55eab607f494c"
}
}
},

View File

@ -16,7 +16,8 @@ requires "stew"
requires "stint"
requires "metrics"
requires "results"
requires "https://github.com/logos-messaging/nim-ffi#v0.1.5-rc.1"
requires "https://github.com/logos-messaging/nim-ffi#fix/foreign-host-concurrency-v0.2"
requires "https://github.com/vacp2p/nim-cbor-serialization#v0.3.0"
proc buildLibrary(
outLibNameAndExt: string,