From 252a0d54cda43d5ee37532ca1039be7a5d37fb41 Mon Sep 17 00:00:00 2001 From: Ivan FB Date: Wed, 3 Jun 2026 12:14:29 +0200 Subject: [PATCH 1/2] refactor(ffi): use nim-ffi {.ffi.} macros for libsds Switch the C wrapper from the lower-level registerReqFFI + hand-written exports to nim-ffi's declareLibrary/{.ffiCtor.}/{.ffi.}/{.ffiDtor.} macros. This removes the per-op boilerplate (request structs, shared-mem copy helpers, dispatch template) in favour of the framework's JSON marshalling, and lets participantId be threaded in via config. Consequences (intentional): C exports are now snake_case and parameters travel as JSON, so the ABI changes and sds-go-bindings must be updated to match. Two cases can't ride the JSON path: the retrieval-hint provider is a C function pointer (kept hand-written, dispatched to set a worker-thread threadvar) and the unwrap response has nested objects the framework's serializer can't emit (returned as a hand-built JSON string). Co-Authored-By: Claude Opus 4.8 --- library/libsds.h | 63 +++--- library/libsds.nim | 549 +++++++++++++-------------------------------- 2 files changed, 187 insertions(+), 425 deletions(-) diff --git a/library/libsds.h b/library/libsds.h index 0d9840e..01ce4fa 100644 --- a/library/libsds.h +++ b/library/libsds.h @@ -1,8 +1,10 @@ -// 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 framework. +// +// Parameters and results are marshalled as JSON: each request/response struct +// in library/libsds.nim is a JSON object, passed in via the `*Json` cstring +// argument and returned to the callback as a JSON string. Binary fields +// (message bytes) are JSON arrays of byte values. #ifndef __libsds__ #define __libsds__ @@ -18,51 +20,52 @@ extern "C" { #endif +// Result/event callback. `msg` is the (JSON) payload of length `len`. +// callerRet is one of the RET_* codes above. 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`); the +// library takes ownership and frees it with deallocShared. 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. configJson: {"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 char* configJson, SdsCallBack callback, void* userData); -void SdsSetEventCallback(void* ctx, SdsCallBack callback, void* userData); +// Register the event callback (message_ready, message_sent, +// missing_dependencies, periodic_sync, repair_ready). Payloads are JSON. +void sds_set_event_callback(void* ctx, SdsCallBack callback, void* userData); -void SdsSetRetrievalHintProvider(void* ctx, SdsRetrievalHintProvider 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 SdsCleanupReliabilityManager(void* ctx, SdsCallBack callback, void* userData); +// reqJson: {"message":[..bytes..],"messageId":"..","channelId":".."} +// Result JSON: {"message":[..bytes..]} +int sds_wrap_outgoing_message(void* ctx, SdsCallBack callback, void* userData, const char* reqJson); -int SdsResetReliabilityManager(void* ctx, SdsCallBack callback, void* userData); +// reqJson: {"message":[..bytes..]} +// Result JSON: {"message":[..],"channelId":"..","missingDeps":[{"messageId":"..","retrievalHint":""}]} +int sds_unwrap_received_message(void* ctx, SdsCallBack callback, void* userData, const char* reqJson); -int SdsWrapOutgoingMessage(void* ctx, - void* message, - size_t messageLen, - const char* messageId, - const char* channelId, - SdsCallBack callback, - void* userData); +// reqJson: {"messageIds":["..",".."],"channelId":".."} +int sds_mark_dependencies_met(void* ctx, SdsCallBack callback, void* userData, const char* reqJson); -int SdsUnwrapReceivedMessage(void* ctx, - void* message, - size_t messageLen, - SdsCallBack callback, - void* userData); +int sds_reset(void* ctx, SdsCallBack callback, void* userData); -int SdsMarkDependenciesMet(void* ctx, - char** messageIDs, - size_t count, - const char* channelId, - SdsCallBack callback, - void* userData); - -int SdsStartPeriodicTasks(void* ctx, SdsCallBack callback, void* userData); +int sds_start_periodic_tasks(void* ctx, SdsCallBack callback, void* userData); +// Tear down the context created by sds_create. +int sds_destroy(void* ctx, SdsCallBack callback, void* userData); #ifdef __cplusplus } #endif -#endif /* __libsds__ */ \ No newline at end of file +#endif /* __libsds__ */ diff --git a/library/libsds.nim b/library/libsds.nim index c7f41e9..a0bead0 100644 --- a/library/libsds.nim +++ b/library/libsds.nim @@ -1,4 +1,16 @@ -import std/[strutils, sequtils, json, base64, locks] +## C-compatible FFI wrapper around the SDS ReliabilityManager. +## +## Built on the `nim-ffi` package's high-level macros: `declareLibrary` emits the +## bootstrap + `sds_set_event_callback`; `{.ffiCtor.}`/`{.ffi.}`/`{.ffiDtor.}` +## generate the C entry points, marshalling parameters and return values as JSON. +## 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 JSON representation, so it is hand-written and +## dispatched to the worker thread to store the provider in a thread-local. + +import std/[base64, json, sequtils] import ffi import sds import ./events/[ @@ -6,464 +18,211 @@ import ./events/[ 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_set_event_callback(ctx, callback, userData)` C export. +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. +### JSON-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 +type SdsMarkDependenciesRequest* {.ffi.} = object + messageIds: seq[string] + channelId: string ################################################################################ -### Shared-memory copy helpers +### Constructor — creates the FFI context and the ReliabilityManager. ### -### 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. +### The AppCallbacks closures run on the worker thread and forward events to the +### C callback registered via sds_set_event_callback (dispatchFfiEvent reads the +### per-thread callback state, so no context handle is needed here). -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) +proc sdsCreate*( + config: SdsConfig +): Future[Result[ReliabilityManager, string]] {.ffiCtor.} = + let rm = newReliabilityManager(participantId = config.participantId.SdsParticipantID).valueOr: + error "Failed creating reliability manager", error = error + return err("Failed creating reliability manager: " & $error) -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) - -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 - -################################################################################ -### Event callbacks -### -### 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). - -proc onMessageReady(ctx: ptr FFIContext[ReliabilityManager]): MessageReadyCallback = - return proc(messageId: SdsMessageID, channelId: SdsChannelID) {.gcsafe.} = - callEventCallback(ctx, "onMessageReady"): + let messageReadyCb = proc( + messageId: SdsMessageID, channelId: SdsChannelID + ) {.gcsafe.} = + dispatchFfiEvent("message_ready"): $JsonMessageReadyEvent.new(messageId, channelId) -proc onMessageSent(ctx: ptr FFIContext[ReliabilityManager]): MessageSentCallback = - return proc(messageId: SdsMessageID, channelId: SdsChannelID) {.gcsafe.} = - callEventCallback(ctx, "onMessageSent"): + let messageSentCb = proc( + messageId: SdsMessageID, channelId: SdsChannelID + ) {.gcsafe.} = + dispatchFfiEvent("message_sent"): $JsonMessageSentEvent.new(messageId, channelId) -proc onMissingDependencies( - ctx: ptr FFIContext[ReliabilityManager] -): MissingDependenciesCallback = - return proc( + let missingDependenciesCb = proc( messageId: SdsMessageID, missingDeps: seq[HistoryEntry], channelId: SdsChannelID ) {.gcsafe.} = - callEventCallback(ctx, "onMissingDependencies"): + dispatchFfiEvent("missing_dependencies"): $JsonMissingDependenciesEvent.new(messageId, missingDeps, channelId) -proc onPeriodicSync(ctx: ptr FFIContext[ReliabilityManager]): PeriodicSyncCallback = - return proc() {.gcsafe.} = - callEventCallback(ctx, "onPeriodicSync"): + let periodicSyncCb = proc() {.gcsafe.} = + dispatchFfiEvent("periodic_sync"): $JsonPeriodicSyncEvent.new() -proc onRepairReady(ctx: ptr FFIContext[ReliabilityManager]): RepairReadyCallback = - return proc(message: seq[byte], channelId: SdsChannelID) {.gcsafe.} = - callEventCallback(ctx, "onRepairReady"): + let repairReadyCb = proc(message: seq[byte], channelId: SdsChannelID) {.gcsafe.} = + dispatchFfiEvent("repair_ready"): $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(): + let retrievalHintProvider = proc(messageId: SdsMessageID): seq[byte] {.gcsafe.} = + if sdsRetrievalHintCb.isNil(): return @[] - var hint: cstring var hintLen: csize_t - cast[SdsRetrievalHintProvider](cb)( - messageId.cstring, addr hint, addr hintLen, userData + 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) 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) - - await rm.setCallbacks( - onMessageReady(ctx), onMessageSent(ctx), onMissingDependencies(ctx), - onPeriodicSync(ctx), onRetrievalHint(ctx), onRepairReady(ctx), +proc sdsWrapOutgoingMessage*( + rm: ReliabilityManager, req: SdsWrapRequest +): Future[Result[SdsWrapResponse, string]] {.ffi.} = + let wrapped = ( + await wrapOutgoingMessage( + rm, req.message, req.messageId.SdsMessageID, req.channelId.SdsChannelID ) + ).valueOr: + error "WRAP_MESSAGE failed", error = error + return err("error processing wrap request: " & $error) + return ok(SdsWrapResponse(message: wrapped)) - ctx.myLib[] = rm - return ok("") +proc sdsUnwrapReceivedMessage*( + rm: ReliabilityManager, req: SdsUnwrapRequest +): Future[Result[string, string]] {.ffi.} = + # The response carries nested objects (missingDeps) which the framework's + # object serializer cannot emit, so the JSON is built by hand and returned as + # a string. Shape matches the legacy unwrap response. + let (unwrapped, missingDeps, channelId) = ( + await unwrapReceivedMessage(rm, req.message) + ).valueOr: + return err("error processing unwrap request: " & $error) -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("") + var node = newJObject() + node["message"] = %*unwrapped + node["channelId"] = %*channelId + 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(SdsStartPeriodicTasksReq, ctx: ptr FFIContext[ReliabilityManager]): - proc(): Future[Result[string, string]] {.async.} = - ctx.myLib[].startPeriodicTasks() - return ok("") +proc sdsMarkDependenciesMet*( + rm: ReliabilityManager, req: SdsMarkDependenciesRequest +): Future[Result[string, string]] {.ffi.} = + let messageIds = req.messageIds.mapIt(it.SdsMessageID) + (await markDependenciesMet(rm, messageIds, req.channelId.SdsChannelID)).isOkOr: + error "MARK_DEPENDENCIES_MET failed", error = error + return err("error processing mark-dependencies request: " & $error) + 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) +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("") - 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 JSON). The setter dispatches a request 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: pointer, udPtr: pointer): Future[Result[string, string]] {.async.} = + sdsRetrievalHintCb = cbPtr + sdsRetrievalHintUserData = 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)) - - let res = ReliabilityManagerFFIPool.destroyFFIContext(ctx) - 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[pointer](callback), userData + ), + ) + except Exception as exc: + Result[void, string].err("sendRequestToFFIThread exception: " & exc.msg) + if sendRes.isErr(): return RET_ERR - - callback(RET_OK, nil, 0, userData) 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() From 05595e2d92c6618d5d75ed8dc0fa5ef3ebda9376 Mon Sep 17 00:00:00 2001 From: Ivan FB Date: Wed, 3 Jun 2026 13:22:11 +0200 Subject: [PATCH 2/2] =?UTF-8?q?feat(ffi):=20target=20nim-ffi=20master=20(v?= =?UTF-8?q?0.2.0)=20=E2=80=94=20CBOR=20+=20event=20registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the {.ffi.} wrapper to nim-ffi 0.2.0 (master). 0.2.0 is a breaking redesign over 0.1.4: events move to a per-context multi-listener registry (sds_add_event_listener / sds_remove_event_listener) fired via {.ffiEvent.} emitters, and request/response/event marshalling switches from JSON to CBOR. - libsds.nim: typed {.ffiEvent.} payloads replace the JSON event modules; CBOR handles nesting, so unwrap returns a typed response again. The retrieval-hint provider (a C function pointer, not CBOR-encodable) passes its address as a uint64 via a {.ffi.} method that stores it in a worker-thread threadvar. - pin nim-ffi to master HEAD by commit. The lock version is kept a clean semver ("0.2.0") on purpose: nimble's `#`-prefixed special version in the lock breaks `nimble setup -l` (an unquoted `#` truncates the git path), so only vcsRevision carries the commit. - add the new transitive dep cbor_serialization to the lock and nix/deps.nix. - regenerate libsds.h for the CBOR/registry ABI. Co-Authored-By: Claude Opus 4.8 --- library/libsds.h | 78 +++++++++++------- library/libsds.nim | 192 +++++++++++++++++++++++++-------------------- nimble.lock | 23 +++++- nix/deps.nix | 11 ++- sds.nimble | 2 +- 5 files changed, 186 insertions(+), 120 deletions(-) diff --git a/library/libsds.h b/library/libsds.h index 01ce4fa..3c5d982 100644 --- a/library/libsds.h +++ b/library/libsds.h @@ -1,10 +1,11 @@ -// C API for libsds, built on the nim-ffi framework. +// C API for libsds, built on the nim-ffi framework (v0.2.0+). // -// Parameters and results are marshalled as JSON: each request/response struct -// in library/libsds.nim is a JSON object, passed in via the `*Json` cstring -// argument and returned to the callback as a JSON string. Binary fields -// (message bytes) are JSON arrays of byte values. +// Requests, responses and events are marshalled as CBOR. Request payloads are +// passed as a (reqCbor, reqCborLen) byte buffer; results and events are +// delivered to the callback as a CBOR buffer (msg, len). Each request/response +// struct and event payload is defined in library/libsds.nim. Events are +// wrapped in a CBOR envelope { eventType: , payload: }. #ifndef __libsds__ #define __libsds__ @@ -20,48 +21,67 @@ extern "C" { #endif -// Result/event callback. `msg` is the (JSON) payload of length `len`. +// Result/event callback. `msg` is the CBOR payload of length `len`. // callerRet is one of the RET_* codes above. 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`); the -// library takes ownership and frees it with deallocShared. +// library takes ownership and frees it with deallocShared. Registered via +// sds_set_retrieval_hint_provider (see below). typedef void (*SdsRetrievalHintProvider) (const char* messageId, char** hint, size_t* hintLen, void* userData); -// --- Core API Functions --- +// --- Lifecycle ------------------------------------------------------------- + +// Create a context + ReliabilityManager. reqCbor encodes SdsConfig +// { participantId: tstr } (empty participantId disables SDS-R). Returns the +// context handle, or NULL on failure; the callback also fires on completion. +void* sds_create(const uint8_t* reqCbor, size_t reqCborLen, SdsCallBack callback, void* userData); + +// Tear down the context created by sds_create. Blocks until the worker and +// watchdog threads have joined. +int sds_destroy(void* ctx); -// Create a context + ReliabilityManager. configJson: {"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 char* configJson, SdsCallBack callback, void* userData); +// --- Events ---------------------------------------------------------------- +// Subscribe `callback` to an event by wire name and receive a stable listener +// id (non-zero). Event wire names: "message_ready", "message_sent", +// "missing_dependencies", "periodic_sync", "repair_ready". Subscribe to each +// event separately. Payloads arrive as CBOR { eventType, payload }. +uint64_t sds_add_event_listener(void* ctx, const char* eventName, SdsCallBack callback, void* userData); -// Register the event callback (message_ready, message_sent, -// missing_dependencies, periodic_sync, repair_ready). Payloads are JSON. -void sds_set_event_callback(void* ctx, SdsCallBack callback, void* userData); +// Remove a listener by id. Returns 0 on success, non-zero if not found. +int sds_remove_event_listener(void* ctx, uint64_t listenerId); -// Register the retrieval-hint provider used by SDS-R. -int sds_set_retrieval_hint_provider(void* ctx, SdsRetrievalHintProvider callback, void* userData); +// Register the SDS-R retrieval-hint provider. reqCbor encodes +// SdsHintProviderRequest { callbackAddr: uint, userDataAddr: uint } — the +// SdsRetrievalHintProvider function pointer and its user-data as integer +// addresses. +int sds_set_retrieval_hint_provider(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen); -// reqJson: {"message":[..bytes..],"messageId":"..","channelId":".."} -// Result JSON: {"message":[..bytes..]} -int sds_wrap_outgoing_message(void* ctx, SdsCallBack callback, void* userData, const char* reqJson); -// reqJson: {"message":[..bytes..]} -// Result JSON: {"message":[..],"channelId":"..","missingDeps":[{"messageId":"..","retrievalHint":""}]} -int sds_unwrap_received_message(void* ctx, SdsCallBack callback, void* userData, const char* reqJson); +// --- Core API Functions ---------------------------------------------------- +// Each takes a CBOR-encoded request buffer; the result is delivered to +// `callback` as CBOR. -// reqJson: {"messageIds":["..",".."],"channelId":".."} -int sds_mark_dependencies_met(void* ctx, SdsCallBack callback, void* userData, const char* reqJson); +// reqCbor: SdsWrapRequest { message: bytes, messageId: tstr, channelId: tstr } +// result: SdsWrapResponse { message: bytes } +int sds_wrap_outgoing_message(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen); -int sds_reset(void* ctx, SdsCallBack callback, void* userData); +// reqCbor: SdsUnwrapRequest { message: bytes } +// result: SdsUnwrapResponse { message: bytes, channelId: tstr, +// missingDeps: [{ messageId: tstr, retrievalHint: bytes }] } +int sds_unwrap_received_message(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen); -int sds_start_periodic_tasks(void* ctx, SdsCallBack callback, void* userData); +// reqCbor: SdsMarkDependenciesRequest { messageIds: [tstr], channelId: tstr } +int sds_mark_dependencies_met(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, SdsCallBack callback, void* userData); +// reqCbor: empty/unit payload (no fields). +int sds_reset(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen); + +// reqCbor: empty/unit payload (no fields). +int sds_start_periodic_tasks(void* ctx, SdsCallBack callback, void* userData, const uint8_t* reqCbor, size_t reqCborLen); #ifdef __cplusplus diff --git a/library/libsds.nim b/library/libsds.nim index a0bead0..5e614e9 100644 --- a/library/libsds.nim +++ b/library/libsds.nim @@ -1,40 +1,35 @@ ## C-compatible FFI wrapper around the SDS ReliabilityManager. ## -## Built on the `nim-ffi` package's high-level macros: `declareLibrary` emits the -## bootstrap + `sds_set_event_callback`; `{.ffiCtor.}`/`{.ffi.}`/`{.ffiDtor.}` -## generate the C entry points, marshalling parameters and return values as JSON. -## Exported C names are snake_case (`sds_wrap_outgoing_message`, …); see -## `library/libsds.h`. The Go bindings (sds-go-bindings) must match this API. +## Built on nim-ffi (v0.2.0+): `declareLibrary` emits the bootstrap plus the +## event-listener ABI (`sds_add_event_listener` / `sds_remove_event_listener`); +## `{.ffiCtor.}`/`{.ffi.}`/`{.ffiDtor.}` generate the C entry points; and +## `{.ffiEvent.}` declares library-initiated events. Requests, responses and +## events are marshalled as CBOR (see library/libsds.h). Exported C names are +## snake_case. 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 JSON representation, so it is hand-written and -## dispatched to the worker thread to store the provider in a thread-local. +## The one hand-written export is `sds_set_retrieval_hint_provider`: it takes a +## C function pointer (no CBOR representation), so it dispatches a request that +## stores the provider in a worker-thread thread-local. -import std/[base64, json, sequtils] +import std/[sequtils] 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, -] -# Bootstrap (pragmas, linker flags, libsdsNimMain, initializeLibrary) plus the -# `sds_set_event_callback(ctx, callback, userData)` C export. +# Bootstrap + sds_add_event_listener / sds_remove_event_listener. declareLibrary("sds", ReliabilityManager) type SdsRetrievalHintProvider* = proc( messageId: cstring, hint: ptr cstring, hintLen: ptr csize_t, userData: pointer ) {.cdecl, gcsafe, raises: [].} -# 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. +# Active retrieval-hint provider, per worker thread (one thread per context). +# Set by sds_set_retrieval_hint_provider through a dispatched request so the +# write lands on the worker thread, where the manager's hint closure reads it. var sdsRetrievalHintCb {.threadvar.}: pointer var sdsRetrievalHintUserData {.threadvar.}: pointer ################################################################################ -### JSON-marshalled request/response types +### CBOR-marshalled request/response types type SdsConfig* {.ffi.} = object participantId: string ## empty disables SDS-R (see newReliabilityManager) @@ -50,16 +45,59 @@ type SdsWrapResponse* {.ffi.} = object type SdsUnwrapRequest* {.ffi.} = object message: seq[byte] +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 +################################################################################ +### Library-initiated events +### +### Each {.ffiEvent.} proc is an emitter: calling it from a worker-thread +### handler dispatches a CBOR EventEnvelope to every listener subscribed (via +### sds_add_event_listener) to the matching wire name. + +type SdsMessageReadyPayload* {.ffi.} = object + messageId: string + channelId: string + +type SdsMessageSentPayload* {.ffi.} = object + messageId: string + channelId: string + +type SdsMissingDependenciesPayload* {.ffi.} = object + messageId: string + channelId: string + missingDeps: seq[SdsMissingDep] + +type SdsPeriodicSyncPayload* {.ffi.} = object + placeholder: bool ## events need a payload type; periodic sync carries no data + +type SdsRepairReadyPayload* {.ffi.} = object + message: seq[byte] + channelId: string + +proc emitMessageReady*(p: SdsMessageReadyPayload) {.ffiEvent: "message_ready".} +proc emitMessageSent*(p: SdsMessageSentPayload) {.ffiEvent: "message_sent".} +proc emitMissingDependencies*( + p: SdsMissingDependenciesPayload +) {.ffiEvent: "missing_dependencies".} +proc emitPeriodicSync*(p: SdsPeriodicSyncPayload) {.ffiEvent: "periodic_sync".} +proc emitRepairReady*(p: SdsRepairReadyPayload) {.ffiEvent: "repair_ready".} + ################################################################################ ### Constructor — creates the FFI context and the ReliabilityManager. ### -### The AppCallbacks closures run on the worker thread and forward events to the -### C callback registered via sds_set_event_callback (dispatchFfiEvent reads the -### per-thread callback state, so no context handle is needed here). +### The AppCallbacks closures run on the worker thread; they build typed +### payloads and fire the {.ffiEvent.} emitters, which reach the C listeners. proc sdsCreate*( config: SdsConfig @@ -71,28 +109,39 @@ proc sdsCreate*( let messageReadyCb = proc( messageId: SdsMessageID, channelId: SdsChannelID ) {.gcsafe.} = - dispatchFfiEvent("message_ready"): - $JsonMessageReadyEvent.new(messageId, channelId) + {.cast(gcsafe).}: + emitMessageReady( + SdsMessageReadyPayload(messageId: $messageId, channelId: $channelId) + ) let messageSentCb = proc( messageId: SdsMessageID, channelId: SdsChannelID ) {.gcsafe.} = - dispatchFfiEvent("message_sent"): - $JsonMessageSentEvent.new(messageId, channelId) + {.cast(gcsafe).}: + emitMessageSent( + SdsMessageSentPayload(messageId: $messageId, channelId: $channelId) + ) let missingDependenciesCb = proc( messageId: SdsMessageID, missingDeps: seq[HistoryEntry], channelId: SdsChannelID ) {.gcsafe.} = - dispatchFfiEvent("missing_dependencies"): - $JsonMissingDependenciesEvent.new(messageId, missingDeps, channelId) + {.cast(gcsafe).}: + let deps = missingDeps.mapIt( + SdsMissingDep(messageId: $it.messageId, retrievalHint: it.retrievalHint) + ) + emitMissingDependencies( + SdsMissingDependenciesPayload( + messageId: $messageId, channelId: $channelId, missingDeps: deps + ) + ) let periodicSyncCb = proc() {.gcsafe.} = - dispatchFfiEvent("periodic_sync"): - $JsonPeriodicSyncEvent.new() + {.cast(gcsafe).}: + emitPeriodicSync(SdsPeriodicSyncPayload(placeholder: false)) let repairReadyCb = proc(message: seq[byte], channelId: SdsChannelID) {.gcsafe.} = - dispatchFfiEvent("repair_ready"): - $JsonRepairReadyEvent.new(message, channelId) + {.cast(gcsafe).}: + emitRepairReady(SdsRepairReadyPayload(message: message, channelId: $channelId)) let retrievalHintProvider = proc(messageId: SdsMessageID): seq[byte] {.gcsafe.} = if sdsRetrievalHintCb.isNil(): @@ -133,26 +182,18 @@ proc sdsWrapOutgoingMessage*( proc sdsUnwrapReceivedMessage*( rm: ReliabilityManager, req: SdsUnwrapRequest -): Future[Result[string, string]] {.ffi.} = - # The response carries nested objects (missingDeps) which the framework's - # object serializer cannot emit, so the JSON is built by hand and returned as - # a string. Shape matches the legacy unwrap response. +): Future[Result[SdsUnwrapResponse, string]] {.ffi.} = let (unwrapped, missingDeps, channelId) = ( await unwrapReceivedMessage(rm, req.message) ).valueOr: return err("error processing unwrap request: " & $error) - var node = newJObject() - node["message"] = %*unwrapped - node["channelId"] = %*channelId - 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) + let deps = missingDeps.mapIt( + SdsMissingDep(messageId: $it.messageId, retrievalHint: it.retrievalHint) + ) + return ok( + SdsUnwrapResponse(message: unwrapped, channelId: $channelId, missingDeps: deps) + ) proc sdsMarkDependenciesMet*( rm: ReliabilityManager, req: SdsMarkDependenciesRequest @@ -185,44 +226,27 @@ proc sdsDestroy*(rm: ReliabilityManager) {.ffiDtor.} = discard ################################################################################ -### Retrieval-hint provider (hand-written: a C function pointer cannot be passed -### as JSON). The setter dispatches a request so the provider is stored in the -### worker thread's thread-local, where sdsCreate's hint closure reads it. +### Retrieval-hint provider. +### +### The provider is a C function pointer, which has no CBOR representation, so +### it is passed as integer addresses. The body runs on the worker thread (the +### empty await forces the async path) and stores the pointers in the +### thread-local that sdsCreate's hint closure reads. The caller passes the +### function pointer and user-data as uint64 addresses. -proc sdsNoopCallback( - callerRet: cint, msg: ptr cchar, len: csize_t, userData: pointer -) {.cdecl, gcsafe, raises: [].} = - discard +type SdsHintProviderRequest* {.ffi.} = object + callbackAddr: uint64 + userDataAddr: uint64 -registerReqFFI(SdsSetHintReq, ctx: ptr FFIContext[ReliabilityManager]): - proc(cbPtr: pointer, udPtr: pointer): Future[Result[string, string]] {.async.} = - sdsRetrievalHintCb = cbPtr - sdsRetrievalHintUserData = udPtr - return ok("") - -proc sds_set_retrieval_hint_provider( - ctx: ptr FFIContext[ReliabilityManager], - callback: SdsRetrievalHintProvider, - userData: pointer, -): cint {.dynlib, exportc, cdecl, raises: [].} = - initializeLibrary() - if not ReliabilityManagerFFIPool.isValidCtx(cast[pointer](ctx)): - return RET_ERR - - let sendRes = - try: - ffi_context.sendRequestToFFIThread( - ctx, - SdsSetHintReq.ffiNewReq( - sdsNoopCallback, nil, cast[pointer](callback), userData - ), - ) - except Exception as exc: - Result[void, string].err("sendRequestToFFIThread exception: " & exc.msg) - if sendRes.isErr(): - return RET_ERR - return RET_OK +proc sdsSetRetrievalHintProvider*( + rm: ReliabilityManager, req: SdsHintProviderRequest +): Future[Result[string, string]] {.ffi.} = + discard rm + await sleepAsync(chronos.milliseconds(0)) + sdsRetrievalHintCb = cast[pointer](req.callbackAddr) + sdsRetrievalHintUserData = cast[pointer](req.userDataAddr) + return ok("") # Emit binding metadata (no-op unless -d:ffiGenBindings). Must follow every -# {.ffi.}/{.ffiCtor.}/{.ffiDtor.} annotation. +# {.ffi.}/{.ffiCtor.}/{.ffiDtor.}/{.ffiEvent.} annotation. genBindings() diff --git a/nimble.lock b/nimble.lock index ad7d9e3..a014fc3 100644 --- a/nimble.lock +++ b/nimble.lock @@ -315,17 +315,32 @@ } }, "ffi": { - "version": "0.1.4", - "vcsRevision": "fb25f069d2dfae2b543d79d2c1a81f197de22a2b", + "version": "0.2.0", + "vcsRevision": "f96a5b158add9c321e33ff804f2275a5314a501b", "url": "https://github.com/logos-messaging/nim-ffi", "downloadMethod": "git", "dependencies": [ "chronos", "chronicles", - "taskpools" + "taskpools", + "cbor_serialization" ], "checksums": { - "sha1": "4a5d4020a40106fa2a698d5fe975b9a8ba961f91" + "sha1": "46fad75cc79b7ae6eabfadb8fc3e68764dde89a5" + } + }, + "cbor_serialization": { + "version": "0.3.0", + "vcsRevision": "1664160e04d153573373afddc552b9cbf6fbe4dc", + "url": "https://github.com/vacp2p/nim-cbor-serialization", + "downloadMethod": "git", + "dependencies": [ + "serialization", + "stew", + "results" + ], + "checksums": { + "sha1": "ab126eae09a6e39c72972a6a0b83cb06a2ffe8f0" } } }, diff --git a/nix/deps.nix b/nix/deps.nix index a73c829..f312dd6 100644 --- a/nix/deps.nix +++ b/nix/deps.nix @@ -166,8 +166,15 @@ ffi = pkgs.fetchgit { url = "https://github.com/logos-messaging/nim-ffi"; - rev = "fb25f069d2dfae2b543d79d2c1a81f197de22a2b"; - sha256 = "0zkjnrm2yjlw27q99kv2x8ll61mbz4nr0cvmyq0csydh43c08k0p"; + rev = "f96a5b158add9c321e33ff804f2275a5314a501b"; + sha256 = "1hcv1k3c18rhg2nrndld2b6xx23nfnlcfkm0bidqha4by4hzn9lr"; + fetchSubmodules = true; + }; + + cbor_serialization = pkgs.fetchgit { + url = "https://github.com/vacp2p/nim-cbor-serialization"; + rev = "1664160e04d153573373afddc552b9cbf6fbe4dc"; + sha256 = "0c1rj4fk0fcqvsf0yqhxvm8h10aww75gi4yfsjhlczh88ypywii2"; fetchSubmodules = true; }; diff --git a/sds.nimble b/sds.nimble index 25f65b4..d0a7b9a 100644 --- a/sds.nimble +++ b/sds.nimble @@ -16,7 +16,7 @@ requires "stew" requires "stint" requires "metrics" requires "results" -requires "https://github.com/logos-messaging/nim-ffi >= 0.1.4" +requires "https://github.com/logos-messaging/nim-ffi#f96a5b158add9c321e33ff804f2275a5314a501b" proc buildLibrary( outLibNameAndExt: string,