11# # C-compatible FFI wrapper around the SDS ReliabilityManager.
22# #
3- # # Built on the `nim-ffi` package's high-level macros: `declareLibrary` emits the
4- # # bootstrap + `sds_set_event_callback`; `{.ffiCtor.}`/`{.ffi.}`/`{.ffiDtor.}`
5- # # generate the C entry points, marshalling parameters and return values as JSON.
6- # # Exported C names are snake_case (`sds_wrap_outgoing_message`, …); see
7- # # `library/libsds.h`. The Go bindings (sds-go-bindings) must match this API.
3+ # # Built on nim-ffi (v0.2.0+): `declareLibrary` emits the bootstrap plus the
4+ # # event-listener ABI (`sds_add_event_listener` / `sds_remove_event_listener`);
5+ # # `{.ffiCtor.}`/`{.ffi.}`/`{.ffiDtor.}` generate the C entry points; and
6+ # # `{.ffiEvent.}` declares library-initiated events. Requests, responses and
7+ # # events are marshalled as CBOR (see library/libsds.h). Exported C names are
8+ # # snake_case. The Go bindings (sds-go-bindings) must match this API.
89# #
9- # # The one exception is `sds_set_retrieval_hint_provider`: it takes a C function
10- # # pointer, which has no sensible JSON representation, so it is hand-written and
11- # # dispatched to the worker thread to store the provider in a thread-local.
10+ # # The one hand-written export is `sds_set_retrieval_hint_provider`: it takes a
11+ # # C function pointer ( no CBOR representation) , so it dispatches a request that
12+ # # stores the provider in a worker-thread thread-local.
1213
13- import std/ [base64, json, sequtils]
14+ import std/ [sequtils]
1415import ffi
1516import sds
16- import ./ events/ [
17- json_message_ready_event, json_message_sent_event, json_missing_dependencies_event,
18- json_periodic_sync_event, json_repair_ready_event,
19- ]
2017
21- # Bootstrap (pragmas, linker flags, libsdsNimMain, initializeLibrary) plus the
22- # `sds_set_event_callback(ctx, callback, userData)` C export.
18+ # Bootstrap + sds_add_event_listener / sds_remove_event_listener.
2319declareLibrary (" sds" , ReliabilityManager )
2420
2521type SdsRetrievalHintProvider * = proc (
2622 messageId: cstring , hint: ptr cstring , hintLen: ptr csize_t , userData: pointer
2723) {.cdecl , gcsafe , raises : [].}
2824
29- # The active retrieval-hint provider, stored per worker thread (one thread per
30- # context). Set by sds_set_retrieval_hint_provider via a dispatched request so
31- # the write lands on the worker thread, where the manager's hint closure reads
32- # it during message processing.
25+ # Active retrieval-hint provider, per worker thread (one thread per context).
26+ # Set by sds_set_retrieval_hint_provider through a dispatched request so the
27+ # write lands on the worker thread, where the manager's hint closure reads it.
3328var sdsRetrievalHintCb {.threadvar .}: pointer
3429var sdsRetrievalHintUserData {.threadvar .}: pointer
3530
3631# ###############################################################################
37- # ## JSON -marshalled request/response types
32+ # ## CBOR -marshalled request/response types
3833
3934type SdsConfig * {.ffi .} = object
4035 participantId: string # # empty disables SDS-R (see newReliabilityManager)
@@ -50,16 +45,59 @@ type SdsWrapResponse* {.ffi.} = object
5045type SdsUnwrapRequest * {.ffi .} = object
5146 message: seq [byte ]
5247
48+ type SdsMissingDep * {.ffi .} = object
49+ messageId: string
50+ retrievalHint: seq [byte ]
51+
52+ type SdsUnwrapResponse * {.ffi .} = object
53+ message: seq [byte ]
54+ channelId: string
55+ missingDeps: seq [SdsMissingDep ]
56+
5357type SdsMarkDependenciesRequest * {.ffi .} = object
5458 messageIds: seq [string ]
5559 channelId: string
5660
61+ # ###############################################################################
62+ # ## Library-initiated events
63+ # ##
64+ # ## Each {.ffiEvent.} proc is an emitter: calling it from a worker-thread
65+ # ## handler dispatches a CBOR EventEnvelope to every listener subscribed (via
66+ # ## sds_add_event_listener) to the matching wire name.
67+
68+ type SdsMessageReadyPayload * {.ffi .} = object
69+ messageId: string
70+ channelId: string
71+
72+ type SdsMessageSentPayload * {.ffi .} = object
73+ messageId: string
74+ channelId: string
75+
76+ type SdsMissingDependenciesPayload * {.ffi .} = object
77+ messageId: string
78+ channelId: string
79+ missingDeps: seq [SdsMissingDep ]
80+
81+ type SdsPeriodicSyncPayload * {.ffi .} = object
82+ placeholder: bool # # events need a payload type; periodic sync carries no data
83+
84+ type SdsRepairReadyPayload * {.ffi .} = object
85+ message: seq [byte ]
86+ channelId: string
87+
88+ proc emitMessageReady * (p: SdsMessageReadyPayload ) {.ffiEvent : " message_ready" .}
89+ proc emitMessageSent * (p: SdsMessageSentPayload ) {.ffiEvent : " message_sent" .}
90+ proc emitMissingDependencies * (
91+ p: SdsMissingDependenciesPayload
92+ ) {.ffiEvent : " missing_dependencies" .}
93+ proc emitPeriodicSync * (p: SdsPeriodicSyncPayload ) {.ffiEvent : " periodic_sync" .}
94+ proc emitRepairReady * (p: SdsRepairReadyPayload ) {.ffiEvent : " repair_ready" .}
95+
5796# ###############################################################################
5897# ## Constructor — creates the FFI context and the ReliabilityManager.
5998# ##
60- # ## The AppCallbacks closures run on the worker thread and forward events to the
61- # ## C callback registered via sds_set_event_callback (dispatchFfiEvent reads the
62- # ## per-thread callback state, so no context handle is needed here).
99+ # ## The AppCallbacks closures run on the worker thread; they build typed
100+ # ## payloads and fire the {.ffiEvent.} emitters, which reach the C listeners.
63101
64102proc sdsCreate * (
65103 config: SdsConfig
@@ -71,28 +109,39 @@ proc sdsCreate*(
71109 let messageReadyCb = proc (
72110 messageId: SdsMessageID , channelId: SdsChannelID
73111 ) {.gcsafe .} =
74- dispatchFfiEvent (" message_ready" ):
75- $ JsonMessageReadyEvent .new (messageId, channelId)
112+ {.cast (gcsafe).}:
113+ emitMessageReady (
114+ SdsMessageReadyPayload (messageId: $ messageId, channelId: $ channelId)
115+ )
76116
77117 let messageSentCb = proc (
78118 messageId: SdsMessageID , channelId: SdsChannelID
79119 ) {.gcsafe .} =
80- dispatchFfiEvent (" message_sent" ):
81- $ JsonMessageSentEvent .new (messageId, channelId)
120+ {.cast (gcsafe).}:
121+ emitMessageSent (
122+ SdsMessageSentPayload (messageId: $ messageId, channelId: $ channelId)
123+ )
82124
83125 let missingDependenciesCb = proc (
84126 messageId: SdsMessageID , missingDeps: seq [HistoryEntry ], channelId: SdsChannelID
85127 ) {.gcsafe .} =
86- dispatchFfiEvent (" missing_dependencies" ):
87- $ JsonMissingDependenciesEvent .new (messageId, missingDeps, channelId)
128+ {.cast (gcsafe).}:
129+ let deps = missingDeps.mapIt (
130+ SdsMissingDep (messageId: $ it.messageId, retrievalHint: it.retrievalHint)
131+ )
132+ emitMissingDependencies (
133+ SdsMissingDependenciesPayload (
134+ messageId: $ messageId, channelId: $ channelId, missingDeps: deps
135+ )
136+ )
88137
89138 let periodicSyncCb = proc () {.gcsafe .} =
90- dispatchFfiEvent ( " periodic_sync " ) :
91- $ JsonPeriodicSyncEvent . new ( )
139+ {. cast (gcsafe).} :
140+ emitPeriodicSync ( SdsPeriodicSyncPayload (placeholder: false ) )
92141
93142 let repairReadyCb = proc (message: seq [byte ], channelId: SdsChannelID ) {.gcsafe .} =
94- dispatchFfiEvent ( " repair_ready " ) :
95- $ JsonRepairReadyEvent . new ( message, channelId)
143+ {. cast (gcsafe).} :
144+ emitRepairReady ( SdsRepairReadyPayload ( message: message , channelId: $ channelId) )
96145
97146 let retrievalHintProvider = proc (messageId: SdsMessageID ): seq [byte ] {.gcsafe .} =
98147 if sdsRetrievalHintCb.isNil ():
@@ -133,26 +182,18 @@ proc sdsWrapOutgoingMessage*(
133182
134183proc sdsUnwrapReceivedMessage * (
135184 rm: ReliabilityManager , req: SdsUnwrapRequest
136- ): Future [Result [string , string ]] {.ffi .} =
137- # The response carries nested objects (missingDeps) which the framework's
138- # object serializer cannot emit, so the JSON is built by hand and returned as
139- # a string. Shape matches the legacy unwrap response.
185+ ): Future [Result [SdsUnwrapResponse , string ]] {.ffi .} =
140186 let (unwrapped, missingDeps, channelId) = (
141187 await unwrapReceivedMessage (rm, req.message)
142188 ).valueOr:
143189 return err (" error processing unwrap request: " & $ error)
144190
145- var node = newJObject ()
146- node[" message" ] = %* unwrapped
147- node[" channelId" ] = %* channelId
148- var missingDepsNode = newJArray ()
149- for dep in missingDeps:
150- var depNode = newJObject ()
151- depNode[" messageId" ] = %* dep.messageId
152- depNode[" retrievalHint" ] = %* encode (dep.retrievalHint)
153- missingDepsNode.add (depNode)
154- node[" missingDeps" ] = missingDepsNode
155- return ok ($ node)
191+ let deps = missingDeps.mapIt (
192+ SdsMissingDep (messageId: $ it.messageId, retrievalHint: it.retrievalHint)
193+ )
194+ return ok (
195+ SdsUnwrapResponse (message: unwrapped, channelId: $ channelId, missingDeps: deps)
196+ )
156197
157198proc sdsMarkDependenciesMet * (
158199 rm: ReliabilityManager , req: SdsMarkDependenciesRequest
@@ -185,44 +226,27 @@ proc sdsDestroy*(rm: ReliabilityManager) {.ffiDtor.} =
185226 discard
186227
187228# ###############################################################################
188- # ## Retrieval-hint provider (hand-written: a C function pointer cannot be passed
189- # ## as JSON). The setter dispatches a request so the provider is stored in the
190- # ## worker thread's thread-local, where sdsCreate's hint closure reads it.
191-
192- proc sdsNoopCallback (
193- callerRet: cint , msg: ptr cchar , len: csize_t , userData: pointer
194- ) {.cdecl , gcsafe , raises : [].} =
195- discard
196-
197- registerReqFFI (SdsSetHintReq , ctx: ptr FFIContext [ReliabilityManager ]):
198- proc (cbPtr: pointer , udPtr: pointer ): Future [Result [string , string ]] {.async .} =
199- sdsRetrievalHintCb = cbPtr
200- sdsRetrievalHintUserData = udPtr
201- return ok (" " )
202-
203- proc sds_set_retrieval_hint_provider (
204- ctx: ptr FFIContext [ReliabilityManager ],
205- callback: SdsRetrievalHintProvider ,
206- userData: pointer ,
207- ): cint {.dynlib , exportc , cdecl , raises : [].} =
208- initializeLibrary ()
209- if not ReliabilityManagerFFIPool .isValidCtx (cast [pointer ](ctx)):
210- return RET_ERR
211-
212- let sendRes =
213- try :
214- ffi_context.sendRequestToFFIThread (
215- ctx,
216- SdsSetHintReq .ffiNewReq (
217- sdsNoopCallback, nil , cast [pointer ](callback), userData
218- ),
219- )
220- except Exception as exc:
221- Result [void , string ].err (" sendRequestToFFIThread exception: " & exc.msg)
222- if sendRes.isErr ():
223- return RET_ERR
224- return RET_OK
229+ # ## Retrieval-hint provider.
230+ # ##
231+ # ## The provider is a C function pointer, which has no CBOR representation, so
232+ # ## it is passed as integer addresses. The body runs on the worker thread (the
233+ # ## empty await forces the async path) and stores the pointers in the
234+ # ## thread-local that sdsCreate's hint closure reads. The caller passes the
235+ # ## function pointer and user-data as uint64 addresses.
236+
237+ type SdsHintProviderRequest * {.ffi .} = object
238+ callbackAddr: uint64
239+ userDataAddr: uint64
240+
241+ proc sdsSetRetrievalHintProvider * (
242+ rm: ReliabilityManager , req: SdsHintProviderRequest
243+ ): Future [Result [string , string ]] {.ffi .} =
244+ discard rm
245+ await sleepAsync (chronos.milliseconds (0 ))
246+ sdsRetrievalHintCb = cast [pointer ](req.callbackAddr)
247+ sdsRetrievalHintUserData = cast [pointer ](req.userDataAddr)
248+ return ok (" " )
225249
226250# Emit binding metadata (no-op unless -d:ffiGenBindings). Must follow every
227- # {.ffi.}/{.ffiCtor.}/{.ffiDtor.} annotation.
251+ # {.ffi.}/{.ffiCtor.}/{.ffiDtor.}/{.ffiEvent.} annotation.
228252genBindings ()
0 commit comments