mirror of
https://github.com/logos-messaging/nim-sds.git
synced 2026-01-05 07:33:07 +00:00
308 lines
10 KiB
Go
308 lines
10 KiB
Go
package main
|
|
|
|
/*
|
|
#cgo CFLAGS: -I${SRCDIR}/bindings
|
|
#cgo LDFLAGS: -L${SRCDIR}/bindings/generated -lbindings
|
|
#cgo LDFLAGS: -Wl,-rpath,${SRCDIR}/bindings/generated
|
|
|
|
#include <stdlib.h> // For C.free
|
|
#include "bindings/bindings.h" // Update include path
|
|
|
|
// Forward declarations for Go callback functions exported to C
|
|
// These are the functions Nim will eventually call via the pointers we give it.
|
|
extern void goMessageReadyCallback(char* messageID);
|
|
extern void goMessageSentCallback(char* messageID);
|
|
extern void goMissingDependenciesCallback(char* messageID, char** missingDeps, size_t missingDepsCount);
|
|
extern void goPeriodicSyncCallback();
|
|
|
|
// Helper function to call the C memory freeing functions
|
|
static void callFreeCResultError(CResult res) { FreeCResultError(res); }
|
|
static void callFreeCWrapResult(CWrapResult res) { FreeCWrapResult(res); }
|
|
static void callFreeCUnwrapResult(CUnwrapResult res) { FreeCUnwrapResult(res); }
|
|
|
|
*/
|
|
import "C"
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"unsafe"
|
|
)
|
|
|
|
// --- Go Types ---
|
|
|
|
// ReliabilityManagerHandle represents the opaque handle to the Nim object
|
|
type ReliabilityManagerHandle unsafe.Pointer
|
|
|
|
// MessageID is a type alias for string for clarity
|
|
type MessageID string
|
|
|
|
// Callbacks holds the Go functions to be called by the Nim library
|
|
type Callbacks struct {
|
|
OnMessageReady func(messageId MessageID)
|
|
OnMessageSent func(messageId MessageID)
|
|
OnMissingDependencies func(messageId MessageID, missingDeps []MessageID)
|
|
OnPeriodicSync func()
|
|
}
|
|
|
|
// Global map to store callbacks associated with handles (necessary due to cgo limitations)
|
|
var (
|
|
callbackRegistry = make(map[ReliabilityManagerHandle]*Callbacks)
|
|
registryMutex sync.RWMutex
|
|
)
|
|
|
|
// --- Go Wrapper Functions ---
|
|
|
|
// NewReliabilityManager creates a new instance of the Nim ReliabilityManager
|
|
func NewReliabilityManager(channelId string) (ReliabilityManagerHandle, error) {
|
|
cChannelId := C.CString(channelId)
|
|
defer C.free(unsafe.Pointer(cChannelId))
|
|
|
|
handle := C.NewReliabilityManager(cChannelId)
|
|
if handle == nil {
|
|
// Note: Nim side currently just prints to stdout on creation failure
|
|
return nil, errors.New("failed to create ReliabilityManager (check Nim logs/stdout)")
|
|
}
|
|
return ReliabilityManagerHandle(handle), nil
|
|
}
|
|
|
|
// CleanupReliabilityManager frees the resources associated with the handle
|
|
func CleanupReliabilityManager(handle ReliabilityManagerHandle) {
|
|
if handle == nil {
|
|
return
|
|
}
|
|
registryMutex.Lock()
|
|
delete(callbackRegistry, handle)
|
|
registryMutex.Unlock()
|
|
C.CleanupReliabilityManager(unsafe.Pointer(handle))
|
|
}
|
|
|
|
// ResetReliabilityManager resets the state of the manager
|
|
func ResetReliabilityManager(handle ReliabilityManagerHandle) error {
|
|
if handle == nil {
|
|
return errors.New("handle is nil")
|
|
}
|
|
cResult := C.ResetReliabilityManager(unsafe.Pointer(handle))
|
|
if !cResult.is_ok {
|
|
errMsg := C.GoString(cResult.error_message)
|
|
C.callFreeCResultError(cResult) // Free the error message
|
|
return errors.New(errMsg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WrapOutgoingMessage wraps a message with reliability metadata
|
|
func WrapOutgoingMessage(handle ReliabilityManagerHandle, message []byte, messageId MessageID) ([]byte, error) {
|
|
if handle == nil {
|
|
return nil, errors.New("handle is nil")
|
|
}
|
|
cMessageId := C.CString(string(messageId))
|
|
defer C.free(unsafe.Pointer(cMessageId))
|
|
|
|
var cMessagePtr unsafe.Pointer
|
|
if len(message) > 0 {
|
|
cMessagePtr = C.CBytes(message) // C.CBytes allocates memory that needs to be freed
|
|
defer C.free(cMessagePtr)
|
|
} else {
|
|
cMessagePtr = nil
|
|
}
|
|
cMessageLen := C.size_t(len(message))
|
|
|
|
cWrapResult := C.WrapOutgoingMessage(unsafe.Pointer(handle), cMessagePtr, cMessageLen, cMessageId)
|
|
|
|
if !cWrapResult.base_result.is_ok {
|
|
errMsg := C.GoString(cWrapResult.base_result.error_message)
|
|
C.callFreeCWrapResult(cWrapResult) // Free error and potentially allocated message
|
|
return nil, errors.New(errMsg)
|
|
}
|
|
|
|
// Copy the wrapped message from C memory to Go slice
|
|
// Explicitly cast the message pointer to unsafe.Pointer
|
|
wrappedMessage := C.GoBytes(unsafe.Pointer(cWrapResult.message), C.int(cWrapResult.message_len))
|
|
C.callFreeCWrapResult(cWrapResult) // Free the C-allocated message buffer
|
|
|
|
return wrappedMessage, nil
|
|
}
|
|
|
|
// UnwrapReceivedMessage unwraps a received message
|
|
func UnwrapReceivedMessage(handle ReliabilityManagerHandle, message []byte) ([]byte, []MessageID, error) {
|
|
if handle == nil {
|
|
return nil, nil, errors.New("handle is nil")
|
|
}
|
|
|
|
var cMessagePtr unsafe.Pointer
|
|
if len(message) > 0 {
|
|
cMessagePtr = C.CBytes(message)
|
|
defer C.free(cMessagePtr)
|
|
} else {
|
|
cMessagePtr = nil
|
|
}
|
|
cMessageLen := C.size_t(len(message))
|
|
|
|
cUnwrapResult := C.UnwrapReceivedMessage(unsafe.Pointer(handle), cMessagePtr, cMessageLen)
|
|
|
|
if !cUnwrapResult.base_result.is_ok {
|
|
errMsg := C.GoString(cUnwrapResult.base_result.error_message)
|
|
C.callFreeCUnwrapResult(cUnwrapResult) // Free error and potentially allocated fields
|
|
return nil, nil, errors.New(errMsg)
|
|
}
|
|
|
|
// Copy unwrapped message content
|
|
// Explicitly cast the message pointer to unsafe.Pointer
|
|
unwrappedContent := C.GoBytes(unsafe.Pointer(cUnwrapResult.message), C.int(cUnwrapResult.message_len))
|
|
|
|
// Copy missing dependencies
|
|
missingDeps := make([]MessageID, cUnwrapResult.missing_deps_count)
|
|
if cUnwrapResult.missing_deps_count > 0 {
|
|
// Convert C array of C strings to Go slice of strings
|
|
cDepsArray := (*[1 << 30]*C.char)(unsafe.Pointer(cUnwrapResult.missing_deps))[:cUnwrapResult.missing_deps_count:cUnwrapResult.missing_deps_count]
|
|
for i, s := range cDepsArray {
|
|
missingDeps[i] = MessageID(C.GoString(s))
|
|
}
|
|
}
|
|
|
|
C.callFreeCUnwrapResult(cUnwrapResult) // Free C-allocated message, deps array, and strings
|
|
|
|
return unwrappedContent, missingDeps, nil
|
|
}
|
|
|
|
// MarkDependenciesMet informs the library that dependencies are met
|
|
func MarkDependenciesMet(handle ReliabilityManagerHandle, messageIDs []MessageID) error {
|
|
if handle == nil {
|
|
return errors.New("handle is nil")
|
|
}
|
|
if len(messageIDs) == 0 {
|
|
return nil // Nothing to do
|
|
}
|
|
|
|
// Convert Go string slice to C array of C strings (char**)
|
|
cMessageIDs := make([]*C.char, len(messageIDs))
|
|
for i, id := range messageIDs {
|
|
cMessageIDs[i] = C.CString(string(id))
|
|
defer C.free(unsafe.Pointer(cMessageIDs[i])) // Ensure each CString is freed
|
|
}
|
|
|
|
// Create a pointer (**C.char) to the first element of the slice
|
|
var cMessageIDsPtr **C.char
|
|
if len(cMessageIDs) > 0 {
|
|
cMessageIDsPtr = &cMessageIDs[0]
|
|
} else {
|
|
cMessageIDsPtr = nil // Handle empty slice case
|
|
}
|
|
|
|
// Pass the address of the pointer variable (&cMessageIDsPtr), which is of type ***C.char
|
|
cResult := C.MarkDependenciesMet(unsafe.Pointer(handle), &cMessageIDsPtr, C.size_t(len(messageIDs)))
|
|
|
|
if !cResult.is_ok {
|
|
errMsg := C.GoString(cResult.error_message)
|
|
C.callFreeCResultError(cResult)
|
|
return errors.New(errMsg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RegisterCallbacks sets the Go callback functions
|
|
func RegisterCallbacks(handle ReliabilityManagerHandle, callbacks Callbacks) error {
|
|
if handle == nil {
|
|
return errors.New("handle is nil")
|
|
}
|
|
|
|
registryMutex.Lock()
|
|
callbackRegistry[handle] = &callbacks
|
|
registryMutex.Unlock()
|
|
|
|
// Pass the C relay functions to Nim
|
|
// Nim will store these function pointers. When Nim calls them, they execute the C relay,
|
|
// Pass pointers to the exported Go functions directly.
|
|
// Nim expects function pointers matching the C callback typedefs.
|
|
// Cgo makes the exported Go functions available as C function pointers.
|
|
// Cast these function pointers to unsafe.Pointer to match the void* expected by the C function.
|
|
C.RegisterCallbacks(
|
|
unsafe.Pointer(handle),
|
|
unsafe.Pointer(C.goMessageReadyCallback),
|
|
unsafe.Pointer(C.goMessageSentCallback),
|
|
unsafe.Pointer(C.goMissingDependenciesCallback),
|
|
unsafe.Pointer(C.goPeriodicSyncCallback),
|
|
unsafe.Pointer(handle), // Pass handle as user_data
|
|
)
|
|
return nil
|
|
}
|
|
|
|
// StartPeriodicTasks starts the background tasks in the Nim library
|
|
func StartPeriodicTasks(handle ReliabilityManagerHandle) error {
|
|
if handle == nil {
|
|
return errors.New("handle is nil")
|
|
}
|
|
C.StartPeriodicTasks(unsafe.Pointer(handle))
|
|
// Assuming StartPeriodicTasks doesn't return an error status in C API
|
|
return nil
|
|
}
|
|
|
|
// --- Go Callback Implementations (Exported to C) ---
|
|
|
|
func goMessageReadyCallback(messageID *C.char) {
|
|
msgIdStr := C.GoString(messageID)
|
|
registryMutex.RLock()
|
|
defer registryMutex.RUnlock()
|
|
|
|
// Find the correct Go callback based on handle (this is tricky without handle passed)
|
|
// For now, iterate through all registered callbacks. This is NOT ideal for multiple managers.
|
|
// A better approach would involve passing the handle back through user_data if possible,
|
|
// or maintaining a single global callback handler if only one manager instance is expected.
|
|
// Let's assume a single instance for simplicity for now.
|
|
for _, callbacks := range callbackRegistry {
|
|
if callbacks != nil && callbacks.OnMessageReady != nil {
|
|
// Run in a goroutine to avoid blocking the C thread
|
|
go callbacks.OnMessageReady(MessageID(msgIdStr))
|
|
}
|
|
}
|
|
fmt.Printf("Go: Message Ready: %s\n", msgIdStr) // Debug print
|
|
}
|
|
|
|
func goMessageSentCallback(messageID *C.char) {
|
|
msgIdStr := C.GoString(messageID)
|
|
registryMutex.RLock()
|
|
defer registryMutex.RUnlock()
|
|
|
|
for _, callbacks := range callbackRegistry {
|
|
if callbacks != nil && callbacks.OnMessageSent != nil {
|
|
go callbacks.OnMessageSent(MessageID(msgIdStr))
|
|
}
|
|
}
|
|
fmt.Printf("Go: Message Sent: %s\n", msgIdStr) // Debug print
|
|
}
|
|
|
|
func goMissingDependenciesCallback(messageID *C.char, missingDeps **C.char, missingDepsCount C.size_t) {
|
|
msgIdStr := C.GoString(messageID)
|
|
deps := make([]MessageID, missingDepsCount)
|
|
if missingDepsCount > 0 {
|
|
// Convert C array of C strings to Go slice
|
|
cDepsArray := (*[1 << 30]*C.char)(unsafe.Pointer(missingDeps))[:missingDepsCount:missingDepsCount]
|
|
for i, s := range cDepsArray {
|
|
deps[i] = MessageID(C.GoString(s))
|
|
}
|
|
}
|
|
|
|
registryMutex.RLock()
|
|
defer registryMutex.RUnlock()
|
|
|
|
for _, callbacks := range callbackRegistry {
|
|
if callbacks != nil && callbacks.OnMissingDependencies != nil {
|
|
go callbacks.OnMissingDependencies(MessageID(msgIdStr), deps)
|
|
}
|
|
}
|
|
fmt.Printf("Go: Missing Deps for %s: %v\n", msgIdStr, deps) // Debug print
|
|
}
|
|
|
|
func goPeriodicSyncCallback() {
|
|
registryMutex.RLock()
|
|
defer registryMutex.RUnlock()
|
|
|
|
for _, callbacks := range callbackRegistry {
|
|
if callbacks != nil && callbacks.OnPeriodicSync != nil {
|
|
go callbacks.OnPeriodicSync()
|
|
}
|
|
}
|
|
fmt.Println("Go: Periodic Sync Requested") // Debug print
|
|
}
|