{.pragma: exported, exportc, cdecl, raises: [].} {.pragma: callback, cdecl, raises: [], gcsafe.} {.passc: "-fPIC".} when defined(linux): {.passl: "-Wl,-soname,libsds.so".} import std/[typetraits, tables, atomics], chronos, chronicles, os, times, strutils import ./sds_thread/sds_thread, ./alloc, ./ffi_types, ./sds_thread/inter_thread_communication/sds_thread_request, ./sds_thread/inter_thread_communication/requests/ [sds_lifecycle_request, sds_message_request, sds_dependencies_request], ../src/[reliability_utils, message], ./events/[ json_message_ready_event, json_message_sent_event, json_missing_dependencies_event, json_periodic_sync_event, ] ################################################################################ ### Wrapper around the reliability manager ################################################################################ ################################################################################ ### Not-exported components template checkLibsdsParams*( ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer ) = ctx[].userData = userData if isNil(callback): return RET_MISSING_CALLBACK template callEventCallback(ctx: ptr SdsContext, eventName: string, body: untyped) = if isNil(ctx[].eventCallback): error eventName & " - eventCallback is nil" return if isNil(ctx[].eventUserData): error eventName & " - eventUserData is nil" return foreignThreadGc: try: let event = body cast[SdsCallBack](ctx[].eventCallback)( RET_OK, unsafeAddr event[0], cast[csize_t](len(event)), ctx[].eventUserData ) except Exception, CatchableError: let msg = "Exception " & eventName & " when calling 'eventCallBack': " & getCurrentExceptionMsg() cast[SdsCallBack](ctx[].eventCallback)( RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), ctx[].eventUserData ) proc handleRequest( ctx: ptr SdsContext, requestType: RequestType, content: pointer, callback: SdsCallBack, userData: pointer, ): cint = sds_thread.sendRequestToSdsThread(ctx, requestType, content, callback, userData).isOkOr: let msg = "libsds error: " & $error callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_ERR return RET_OK proc onMessageReady(ctx: ptr SdsContext): MessageReadyCallback = return proc(messageId: SdsMessageID, channelId: SdsChannelID) {.gcsafe.} = callEventCallback(ctx, "onMessageReady"): $JsonMessageReadyEvent.new(messageId, channelId) proc onMessageSent(ctx: ptr SdsContext): MessageSentCallback = return proc(messageId: SdsMessageID, channelId: SdsChannelID) {.gcsafe.} = callEventCallback(ctx, "onMessageSent"): $JsonMessageSentEvent.new(messageId, channelId) proc onMissingDependencies(ctx: ptr SdsContext): MissingDependenciesCallback = return proc(messageId: SdsMessageID, missingDeps: seq[SdsMessageID], channelId: SdsChannelID) {.gcsafe.} = callEventCallback(ctx, "onMissingDependencies"): $JsonMissingDependenciesEvent.new(messageId, missingDeps, channelId) proc onPeriodicSync(ctx: ptr SdsContext): PeriodicSyncCallback = return proc() {.gcsafe.} = callEventCallback(ctx, "onPeriodicSync"): $JsonPeriodicSyncEvent.new() ### End of not-exported components ################################################################################ ################################################################################ ### Library setup # Every Nim library must have this function called - the name is derived from # the `--nimMainPrefix` command line option proc libsdsNimMain() {.importc.} # To control when the library has been initialized var initialized: Atomic[bool] proc getLogFilePath(): string = # Check for environment variable first, fallback to default location let envPath = getEnv("SDS_LOG_FILE") if envPath.len > 0: return envPath # Default to a location that status-go can manage let defaultDir = getEnv("SDS_LOG_DIR", "/tmp") return joinPath(defaultDir, "nim-sds.log") proc writeToLogFile(logLevel: LogLevel, msg: LogOutputStr) {.raises: [].} = try: let logFile = getLogFilePath() let timestamp = now().format("yyyy-MM-dd HH:mm:ss.fff") let logLine = "[$1] [$2] $3\n".format(timestamp, $logLevel, $msg) # Create directory if it doesn't exist let logDir = parentDir(logFile) if not dirExists(logDir): createDir(logDir) # Append to log file let file = open(logFile, fmAppend) defer: file.close() file.write(logLine) except: # Fallback to console if file writing fails echo "[nim-sds-fallback] ", logLevel, ": ", msg when defined(android): when compiles(defaultChroniclesStream.outputs[0].writer): defaultChroniclesStream.outputs[0].writer = proc( logLevel: LogLevel, msg: LogOutputStr ) {.raises: [].} = echo logLevel, msg else: when compiles(defaultChroniclesStream.outputs[0].writer): defaultChroniclesStream.outputs[0].writer = proc( logLevel: LogLevel, msg: LogOutputStr ) {.raises: [].} = # Critical logs (ERROR, FATAL) are written to console if logLevel >= LogLevel.ERROR: echo "[nim-sds] ", logLevel, ": ", msg else: # All other logs are written to a file writeToLogFile(logLevel, msg) proc initializeLibrary() {.exported.} = if not initialized.exchange(true): ## Every Nim library needs to call `NimMain` once exactly, to initialize the Nim runtime. ## Being `` the value given in the optional compilation flag --nimMainPrefix:yourprefix libsdsNimMain() when declared(setupForeignThreadGc): setupForeignThreadGc() when declared(nimGC_setStackBottom): var locals {.volatile, noinit.}: pointer locals = addr(locals) nimGC_setStackBottom(locals) ### End of library setup ################################################################################ ################################################################################ ### Exported procs proc SdsNewReliabilityManager( callback: SdsCallBack, userData: pointer ): pointer {.dynlib, exportc, cdecl.} = initializeLibrary() ## Creates a new instance of the Reliability Manager. if isNil(callback): echo "error: missing callback in NewReliabilityManager" return nil ## Create the SDS thread that will keep waiting for req from the main thread. var ctx = sds_thread.createSdsThread().valueOr: let msg = "Error in createSdsThread: " & $error callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return nil ctx.userData = userData let appCallbacks = AppCallbacks( messageReadyCb: onMessageReady(ctx), messageSentCb: onMessageSent(ctx), missingDependenciesCb: onMissingDependencies(ctx), periodicSyncCb: onPeriodicSync(ctx), ) let retCode = handleRequest( ctx, RequestType.LIFECYCLE, SdsLifecycleRequest.createShared( SdsLifecycleMsgType.CREATE_RELIABILITY_MANAGER, nil, appCallbacks ), callback, userData, ) if retCode == RET_ERR: return nil return ctx proc SdsSetEventCallback( ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer ) {.dynlib, exportc.} = initializeLibrary() ctx[].eventCallback = cast[pointer](callback) ctx[].eventUserData = userData proc SdsCleanupReliabilityManager( ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer ): cint {.dynlib, exportc.} = initializeLibrary() checkLibsdsParams(ctx, callback, userData) sds_thread.destroySdsThread(ctx).isOkOr: let msg = "libsds error: " & $error callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_ERR ## always need to invoke the callback although we don't retrieve value to the caller callback(RET_OK, nil, 0, userData) return RET_OK proc SdsResetReliabilityManager( ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer ): cint {.dynlib, exportc.} = initializeLibrary() checkLibsdsParams(ctx, callback, userData) handleRequest( ctx, RequestType.LIFECYCLE, SdsLifecycleRequest.createShared(SdsLifecycleMsgType.RESET_RELIABILITY_MANAGER), callback, userData, ) proc SdsWrapOutgoingMessage( ctx: ptr SdsContext, message: pointer, messageLen: csize_t, messageId: cstring, channelId: cstring, callback: SdsCallBack, userData: pointer, ): cint {.dynlib, exportc.} = initializeLibrary() checkLibsdsParams(ctx, callback, userData) 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](len(msg)), 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](len(msg)), 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](len(msg)), userData) return RET_ERR if channelId != nil and $channelId == "": let msg = "libsds error: " & "channel ID is empty string" callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_ERR handleRequest( ctx, RequestType.MESSAGE, SdsMessageRequest.createShared( SdsMessageMsgType.WRAP_MESSAGE, message, messageLen, messageId, channelId ), callback, userData, ) proc SdsUnwrapReceivedMessage( ctx: ptr SdsContext, message: pointer, messageLen: csize_t, callback: SdsCallBack, userData: pointer, ): cint {.dynlib, exportc.} = initializeLibrary() checkLibsdsParams(ctx, callback, userData) 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](len(msg)), userData) return RET_ERR handleRequest( ctx, RequestType.MESSAGE, SdsMessageRequest.createShared( SdsMessageMsgType.UNWRAP_MESSAGE, message, messageLen ), callback, userData, ) proc SdsMarkDependenciesMet( ctx: ptr SdsContext, messageIds: pointer, count: csize_t, channelId: cstring, callback: SdsCallBack, userData: pointer, ): cint {.dynlib, exportc.} = initializeLibrary() checkLibsdsParams(ctx, callback, userData) 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](len(msg)), 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](len(msg)), userData) return RET_ERR if channelId != nil and $channelId == "": let msg = "libsds error: " & "channel ID is empty string" callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return RET_ERR handleRequest( ctx, RequestType.DEPENDENCIES, SdsDependenciesRequest.createShared( SdsDependenciesMsgType.MARK_DEPENDENCIES_MET, messageIds, count, channelId ), callback, userData, ) proc SdsStartPeriodicTasks( ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer ): cint {.dynlib, exportc.} = initializeLibrary() checkLibsdsParams(ctx, callback, userData) handleRequest( ctx, RequestType.LIFECYCLE, SdsLifecycleRequest.createShared(SdsLifecycleMsgType.START_PERIODIC_TASKS), callback, userData, ) proc SdsSetLogFile( logFilePath: cstring ): cint {.dynlib, exportc.} = ## Sets the log file path for nim-sds logging ## This allows applications to configure where nim-sds logs should be written initializeLibrary() if logFilePath == nil: return RET_ERR try: putEnv("SDS_LOG_FILE", $logFilePath) return RET_OK except: return RET_ERR ### End of exported procs ################################################################################