From 010a984c3e7853c22207b34f8bc66cbfea90756c Mon Sep 17 00:00:00 2001 From: pablo Date: Sun, 8 Jun 2025 11:19:45 +0300 Subject: [PATCH] feat(skelleton): skelleton with bindings --- Makefile | 54 ++++++++++ README.md | 19 ++++ bindings/c-bindings/Makefile | 35 +++++++ bindings/c-bindings/chatsdk.h | 64 +++++++++++ bindings/c-bindings/libchatsdk.so | Bin 0 -> 168832 bytes bindings/go-bindings/callbacks.go | 56 ++++++++++ bindings/go-bindings/chatsdk.go | 169 ++++++++++++++++++++++++++++++ bindings/go-bindings/go.mod | 3 + chat_sdk.nimble | 6 ++ examples/go-app/go.mod | 7 ++ examples/go-app/main.go | 137 ++++++++++++++++++++++++ src/chat_sdk.nim | 108 +++++++++++++++++++ 12 files changed, 658 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 bindings/c-bindings/Makefile create mode 100644 bindings/c-bindings/chatsdk.h create mode 100755 bindings/c-bindings/libchatsdk.so create mode 100644 bindings/go-bindings/callbacks.go create mode 100644 bindings/go-bindings/chatsdk.go create mode 100644 bindings/go-bindings/go.mod create mode 100644 examples/go-app/go.mod create mode 100644 examples/go-app/main.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..55c7570 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +# ChatSDK Makefile +# This builds the complete chain: Nim -> C bindings -> Go bindings -> Go example + +.PHONY: all clean build-nim build-c build-go run-go-example help + +# Default target +all: build-nim build-c build-go + +# Help target +help: + @echo "ChatSDK Build System" + @echo "====================" + @echo "Available targets:" + @echo " all - Build everything (Nim + C + Go)" + @echo " build-nim - Build Nim library" + @echo " build-c - Build C bindings (shared library)" + @echo " build-go - Build Go bindings" + @echo " run-go-example - Run the Go example application" + @echo " clean - Clean all build artifacts" + @echo " help - Show this help message" + +# Build Nim library +build-nim: + @echo "Building Nim library..." + cd src && nim c --app:lib --opt:speed --mm:arc --out:../bindings/c-bindings/libchatsdk.so chat_sdk.nim + +# Build C bindings +build-c: build-nim + @echo "C bindings ready (built with Nim)" + +# Build Go bindings (just verify they compile) +build-go: build-c + @echo "Building Go bindings..." + cd bindings/go-bindings && go build . + +# Run Go example +run-go-example: build-go + @echo "Running Go example..." + cd examples/go-app && \ + LD_LIBRARY_PATH=../../bindings/c-bindings:$$LD_LIBRARY_PATH \ + go run main.go + +# Clean all build artifacts +clean: + @echo "Cleaning build artifacts..." + rm -f bindings/c-bindings/*.so bindings/c-bindings/*.a + rm -rf src/nimcache bindings/c-bindings/nimcache + cd bindings/go-bindings && go clean + cd examples/go-app && go clean + +# Test the Nim library directly +test-nim: + @echo "Testing Nim library directly..." + cd src && nim r chat_sdk.nim \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ad3f26 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Chat SDK + +## Quick Start + +### Build Everything +```bash +# Build the complete chain: Nim → C → Go +make all + +# Or step by step: +make build-nim # Build Nim library to shared library +make build-c # Prepare C bindings +make build-go # Verify Go bindings compile +``` + +### Run the Go Example +```bash +make run-go-example +``` \ No newline at end of file diff --git a/bindings/c-bindings/Makefile b/bindings/c-bindings/Makefile new file mode 100644 index 0000000..d8de2fd --- /dev/null +++ b/bindings/c-bindings/Makefile @@ -0,0 +1,35 @@ +CC = gcc +CFLAGS = -Wall -Wextra -fPIC +NIMFLAGS = --app:lib --threads:on --gc:orc + +# Directories +SRC_DIR = ../../src +BUILD_DIR = . + +# Targets +SHARED_LIB = libchatsdk.so +STATIC_LIB = libchatsdk.a +HEADER = chatsdk.h + +.PHONY: all clean shared static + +all: shared static + +shared: $(SHARED_LIB) + +static: $(STATIC_LIB) + +$(SHARED_LIB): $(SRC_DIR)/chat_sdk.nim $(HEADER) + cd $(SRC_DIR) && nim c $(NIMFLAGS) --out:../bindings/c-bindings/$(SHARED_LIB) chat_sdk.nim + +$(STATIC_LIB): $(SRC_DIR)/chat_sdk.nim $(HEADER) + cd $(SRC_DIR) && nim c $(NIMFLAGS) --app:staticLib --out:../bindings/c-bindings/$(STATIC_LIB) chat_sdk.nim + +clean: + rm -f $(SHARED_LIB) $(STATIC_LIB) *.o *.so *.a + rm -rf nimcache + +install: $(SHARED_LIB) $(HEADER) + sudo cp $(SHARED_LIB) /usr/local/lib/ + sudo cp $(HEADER) /usr/local/include/ + sudo ldconfig \ No newline at end of file diff --git a/bindings/c-bindings/chatsdk.h b/bindings/c-bindings/chatsdk.h new file mode 100644 index 0000000..a10187f --- /dev/null +++ b/bindings/c-bindings/chatsdk.h @@ -0,0 +1,64 @@ +#ifndef CHATSDK_H +#define CHATSDK_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Forward declaration for ChatSDK +typedef struct ChatSDK ChatSDK; + +// Storage interface function pointer types +typedef int (*StoreMessageProc)(const char* id, const char* message, void* userData); +typedef const char* (*GetMessageProc)(const char* id, void* userData); + +/** + * Send a message through the ChatSDK (standalone version) + * @param message The message to send as a null-terminated string + * @return 0 on success, non-zero on error + */ +int sendMessageCString(const char* message); + +/** + * Create a new ChatSDK instance with storage callbacks + * @param storeProc Function pointer for storing messages + * @param getProc Function pointer for retrieving messages + * @param userData Optional user data pointer + * @return Pointer to ChatSDK instance or NULL on error + */ +ChatSDK* newChatSDKC(StoreMessageProc storeProc, GetMessageProc getProc, void* userData); + +/** + * Free a ChatSDK instance + * @param sdk Pointer to ChatSDK instance to free + */ +void freeChatSDKC(ChatSDK* sdk); + +/** + * Send a message through a ChatSDK instance + * @param sdk Pointer to ChatSDK instance + * @param id Message ID + * @param message The message to send + * @return 0 on success, non-zero on error + */ +int sendMessageSDKC(ChatSDK* sdk, const char* id, const char* message); + +/** + * Get a message from a ChatSDK instance + * @param sdk Pointer to ChatSDK instance + * @param id Message ID to retrieve + * @return Message string (caller must call freeCString) or NULL if not found + */ +const char* getMessageSDKC(ChatSDK* sdk, const char* id); + +/** + * Free a C string allocated by the library + * @param str String to free + */ +void freeCString(const char* str); + +#ifdef __cplusplus +} +#endif + +#endif // CHATSDK_H \ No newline at end of file diff --git a/bindings/c-bindings/libchatsdk.so b/bindings/c-bindings/libchatsdk.so new file mode 100755 index 0000000000000000000000000000000000000000..13f3f8ee4d92a1cdad36b06fc0c0b8b7a3bd4815 GIT binary patch literal 168832 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%0D==?s4ie@@%a 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 V({0|SJ%>t0rZy<{> +*/ +import "C" +import ( + "unsafe" +) + +// goStoreMessage is called from C to store a message using the Go Store interface +// +//export goStoreMessage +func goStoreMessage(cID *C.char, cMessage *C.char, userData unsafe.Pointer) C.int { + if cID == nil || cMessage == nil { + return 1 // Error + } + + store := getStoreFromUserData(userData) + if store == nil { + return 1 // Error + } + + id := C.GoString(cID) + message := C.GoString(cMessage) + + success := store.StoreMessage(id, message) + if success { + return 0 // Success + } + return 1 // Error +} + +// goGetMessage is called from C to retrieve a message using the Go Store interface +// +//export goGetMessage +func goGetMessage(cID *C.char, userData unsafe.Pointer) *C.char { + if cID == nil { + return nil + } + + store := getStoreFromUserData(userData) + if store == nil { + return nil + } + + id := C.GoString(cID) + message := store.GetMessage(id) + + if message == "" { + return nil + } + + // Allocate C string - Nim side will free this + return C.CString(message) +} diff --git a/bindings/go-bindings/chatsdk.go b/bindings/go-bindings/chatsdk.go new file mode 100644 index 0000000..33f163c --- /dev/null +++ b/bindings/go-bindings/chatsdk.go @@ -0,0 +1,169 @@ +package chatsdk + +/* +#cgo CFLAGS: -I../c-bindings +#cgo LDFLAGS: -L../c-bindings -lchatsdk +#include "chatsdk.h" +#include + +// Forward declarations for the Go callback functions +int goStoreMessage(const char* id, const char* message, void* userData); +const char* goGetMessage(const char* id, void* userData); +*/ +import "C" +import ( + "errors" + "runtime" + "sync" + "unsafe" +) + +// Store interface that Go implementations must satisfy +type Store interface { + StoreMessage(id, message string) bool + GetMessage(id string) string +} + +// ChatSDK represents a chat SDK instance with storage capabilities +type ChatSDK struct { + cSDK *C.ChatSDK + store Store + closed bool + mu sync.RWMutex +} + +// Global registry to map C callback calls back to Go Store implementations +var ( + storeRegistry = make(map[uintptr]Store) + registryMu sync.RWMutex + nextID uintptr = 1 +) + +// SendMessage sends a message through the ChatSDK (standalone version) +func SendMessage(message string) error { + cMessage := C.CString(message) + defer C.free(unsafe.Pointer(cMessage)) + + result := C.sendMessageCString(cMessage) + if result != 0 { + return errors.New("failed to send message") + } + + return nil +} + +// NewChatSDK creates a new ChatSDK instance with the provided store implementation +func NewChatSDK(store Store) (*ChatSDK, error) { + if store == nil { + return nil, errors.New("store cannot be nil") + } + + // Register the store implementation + registryMu.Lock() + storeID := nextID + nextID++ + storeRegistry[storeID] = store + registryMu.Unlock() + + // Create the C SDK instance with callback function pointers + userData := unsafe.Pointer(uintptr(storeID)) + cSDK := C.newChatSDKC( + C.StoreMessageProc(C.goStoreMessage), + C.GetMessageProc(C.goGetMessage), + userData, + ) + + if cSDK == nil { + // Clean up registry on failure + registryMu.Lock() + delete(storeRegistry, storeID) + registryMu.Unlock() + return nil, errors.New("failed to create ChatSDK instance") + } + + sdk := &ChatSDK{ + cSDK: cSDK, + store: store, + } + + // Set finalizer to ensure cleanup + runtime.SetFinalizer(sdk, (*ChatSDK).Close) + + return sdk, nil +} + +// SendMessage sends a message through this ChatSDK instance +func (sdk *ChatSDK) SendMessage(id, message string) error { + sdk.mu.RLock() + defer sdk.mu.RUnlock() + + if sdk.closed { + return errors.New("ChatSDK instance is closed") + } + + cID := C.CString(id) + cMessage := C.CString(message) + defer C.free(unsafe.Pointer(cID)) + defer C.free(unsafe.Pointer(cMessage)) + + result := C.sendMessageSDKC(sdk.cSDK, cID, cMessage) + if result != 0 { + return errors.New("failed to send message") + } + + return nil +} + +// GetMessage retrieves a message by ID through this ChatSDK instance +func (sdk *ChatSDK) GetMessage(id string) (string, error) { + sdk.mu.RLock() + defer sdk.mu.RUnlock() + + if sdk.closed { + return "", errors.New("ChatSDK instance is closed") + } + + cID := C.CString(id) + defer C.free(unsafe.Pointer(cID)) + + cResult := C.getMessageSDKC(sdk.cSDK, cID) + if cResult == nil { + return "", nil // Message not found + } + + result := C.GoString(cResult) + C.freeCString(cResult) // Free the string allocated by Nim + return result, nil +} + +// Close frees the ChatSDK instance and cleans up resources +func (sdk *ChatSDK) Close() error { + sdk.mu.Lock() + defer sdk.mu.Unlock() + + if sdk.closed { + return nil + } + + if sdk.cSDK != nil { + C.freeChatSDKC(sdk.cSDK) + sdk.cSDK = nil + } + + sdk.closed = true + runtime.SetFinalizer(sdk, nil) + return nil +} + +// getStoreFromUserData retrieves a Store implementation from userData pointer +func getStoreFromUserData(userData unsafe.Pointer) Store { + if userData == nil { + return nil + } + + storeID := uintptr(userData) + registryMu.RLock() + store := storeRegistry[storeID] + registryMu.RUnlock() + return store +} diff --git a/bindings/go-bindings/go.mod b/bindings/go-bindings/go.mod new file mode 100644 index 0000000..9c924cf --- /dev/null +++ b/bindings/go-bindings/go.mod @@ -0,0 +1,3 @@ +module github.com/waku-org/nim-chat-sdk/bindings/go-bindings + +go 1.24 diff --git a/chat_sdk.nimble b/chat_sdk.nimble index d9f6b71..2eca593 100644 --- a/chat_sdk.nimble +++ b/chat_sdk.nimble @@ -10,3 +10,9 @@ srcDir = "src" # Dependencies requires "nim >= 2.0.0" + +task buildSharedLib, "Build shared library for C bindings": + exec "nim c --app:lib --out:../bindings/c-bindings/libchatsdk.so src/chat_sdk.nim" + +task buildStaticLib, "Build static library for C bindings": + exec "nim c --app:staticLib --out:../bindings/c-bindings/libchatsdk.a src/chat_sdk.nim" diff --git a/examples/go-app/go.mod b/examples/go-app/go.mod new file mode 100644 index 0000000..ed2205e --- /dev/null +++ b/examples/go-app/go.mod @@ -0,0 +1,7 @@ +module go-app + +replace github.com/waku-org/nim-chat-sdk/bindings/go-bindings => ../../bindings/go-bindings + +require github.com/waku-org/nim-chat-sdk/bindings/go-bindings v0.0.0-00010101000000-000000000000 + +go 1.24 diff --git a/examples/go-app/main.go b/examples/go-app/main.go new file mode 100644 index 0000000..7517c46 --- /dev/null +++ b/examples/go-app/main.go @@ -0,0 +1,137 @@ +package main + +import ( + "fmt" + "log" + "sync" + + chatsdk "github.com/waku-org/nim-chat-sdk/bindings/go-bindings" +) + +// SimpleStore implements the chatsdk.Store interface with in-memory storage +type SimpleStore struct { + messages map[string]string + mu sync.RWMutex +} + +// NewSimpleStore creates a new SimpleStore instance +func NewSimpleStore() *SimpleStore { + return &SimpleStore{ + messages: make(map[string]string), + } +} + +// StoreMessage stores a message with the given ID +func (s *SimpleStore) StoreMessage(id, message string) bool { + s.mu.Lock() + defer s.mu.Unlock() + + fmt.Printf("šŸ“¦ Storing message [%s]: %s\n", id, message) + s.messages[id] = message + return true +} + +// GetMessage retrieves a message by ID +func (s *SimpleStore) GetMessage(id string) string { + s.mu.RLock() + defer s.mu.RUnlock() + + message, exists := s.messages[id] + if exists { + fmt.Printf("šŸ“¤ Retrieved message [%s]: %s\n", id, message) + return message + } + fmt.Printf("āŒ Message not found [%s]\n", id) + return "" +} + +// ListAllMessages shows all stored messages +func (s *SimpleStore) ListAllMessages() { + s.mu.RLock() + defer s.mu.RUnlock() + + fmt.Println("\nšŸ“‹ All stored messages:") + if len(s.messages) == 0 { + fmt.Println(" (no messages stored)") + return + } + + for id, message := range s.messages { + fmt.Printf(" [%s]: %s\n", id, message) + } +} + +func main() { + fmt.Println("ChatSDK Go Example - Enhanced with Storage") + fmt.Println("==========================================") + + // Test 1: Original standalone API (backward compatibility) + fmt.Println("\nšŸ”ø Testing standalone API (backward compatibility):") + messages := []string{ + "Hello from standalone API!", + "This message won't be stored", + } + + for i, msg := range messages { + fmt.Printf("Sending standalone message #%d: %s\n", i+1, msg) + if err := chatsdk.SendMessage(msg); err != nil { + log.Printf("Error: %v", err) + } else { + fmt.Println("āœ“ Message sent successfully") + } + } + + // Test 2: ChatSDK object with Store interface + fmt.Println("\nšŸ”ø Testing ChatSDK object with Store interface:") + + // Create a store implementation + store := NewSimpleStore() + + // Create ChatSDK instance with the store + sdk, err := chatsdk.NewChatSDK(store) + if err != nil { + log.Fatalf("Failed to create ChatSDK: %v", err) + } + defer sdk.Close() + + // Send messages with IDs (they will be stored) + testMessages := map[string]string{ + "msg1": "Hello from ChatSDK object!", + "msg2": "This message will be stored and can be retrieved", + "msg3": "Nim ā¤ļø Go with storage interface working!", + "msg4": "Another stored message with a longer ID", + } + + fmt.Println("\nšŸ“¤ Sending messages with storage:") + for id, message := range testMessages { + fmt.Printf("Sending message [%s]: %s\n", id, message) + if err := sdk.SendMessage(id, message); err != nil { + log.Printf("Error sending message: %v", err) + } else { + fmt.Println("āœ“ Message sent and stored successfully") + } + fmt.Println() + } + + // Test message retrieval + fmt.Println("\nšŸ“„ Testing message retrieval:") + testIDs := []string{"msg1", "msg2", "msg3", "msg4", "nonexistent"} + + for _, id := range testIDs { + fmt.Printf("Retrieving message [%s]...\n", id) + message, err := sdk.GetMessage(id) + if err != nil { + log.Printf("Error retrieving message: %v", err) + } else if message != "" { + fmt.Printf("āœ“ Found: %s\n", message) + } else { + fmt.Printf("āŒ Message not found\n") + } + fmt.Println() + } + + // Show all stored messages + store.ListAllMessages() + + fmt.Println("\nāœ… Example completed successfully!") +} diff --git a/src/chat_sdk.nim b/src/chat_sdk.nim index b7a2480..931bea8 100644 --- a/src/chat_sdk.nim +++ b/src/chat_sdk.nim @@ -1,3 +1,111 @@ +import std/[times] + +# Storage interface function pointer types +type + StoreMessageProc* = proc(id: cstring, message: cstring, userData: pointer): cint {.cdecl.} + GetMessageProc* = proc(id: cstring, userData: pointer): cstring {.cdecl.} + +# ChatSDK object +type + ChatSDK* = object + storeCallback: StoreMessageProc + getCallback: GetMessageProc + userData: pointer # For Go-side data if needed + +# Create a new ChatSDK instance +proc newChatSDK*(storeProc: StoreMessageProc, getProc: GetMessageProc, userData: pointer = nil): ChatSDK = + ChatSDK( + storeCallback: storeProc, + getCallback: getProc, + userData: userData + ) + +# Send message method for ChatSDK +proc sendMessage*(sdk: ChatSDK, id: string, message: string): bool = + ## Sends a message by printing it to stdout with timestamp and storing it + let timestamp = now() + echo "[", timestamp.format("yyyy-MM-dd HH:mm:ss"), "] ChatSDK: ", message + + # Store the message using the provided storage interface + if sdk.storeCallback != nil: + let storeResult = sdk.storeCallback(cstring(id), cstring(message), sdk.userData) + return storeResult == 0 + return false + +# Get message method for ChatSDK +proc getMessage*(sdk: ChatSDK, id: string): string = + ## Gets a message using the provided storage interface + if sdk.getCallback != nil: + let messageResult = sdk.getCallback(cstring(id), sdk.userData) + if messageResult != nil: + return $messageResult + return "" + +# Original standalone sendMessage for backward compatibility +proc sendMessage*(message: string) = + ## Sends a message by printing it to stdout with timestamp + let timestamp = now() + echo "[", timestamp.format("yyyy-MM-dd HH:mm:ss"), "] ChatSDK: ", message + +# C-compatible wrappers +proc sendMessageCString*(message: cstring): cint {.exportc, dynlib.} = + ## C-compatible wrapper for standalone sendMessage + try: + sendMessage($message) + return 0 # Success + except: + return 1 # Error + +proc newChatSDKC*(storeProc: StoreMessageProc, getProc: GetMessageProc, userData: pointer = nil): ptr ChatSDK {.exportc, dynlib.} = + ## C-compatible wrapper to create a new ChatSDK instance + try: + let sdk = newChatSDK(storeProc, getProc, userData) + let sdkPtr = cast[ptr ChatSDK](alloc(sizeof(ChatSDK))) + sdkPtr[] = sdk + return sdkPtr + except: + return nil + +proc freeChatSDKC*(sdk: ptr ChatSDK) {.exportc, dynlib.} = + ## C-compatible wrapper to free ChatSDK instance + if sdk != nil: + dealloc(sdk) + +proc sendMessageSDKC*(sdk: ptr ChatSDK, id: cstring, message: cstring): cint {.exportc, dynlib.} = + ## C-compatible wrapper for ChatSDK sendMessage + try: + if sdk == nil: + return 1 + let success = sdk[].sendMessage($id, $message) + return if success: 0 else: 1 + except: + return 1 + +proc getMessageSDKC*(sdk: ptr ChatSDK, id: cstring): cstring {.exportc, dynlib.} = + ## C-compatible wrapper for ChatSDK getMessage + try: + if sdk == nil: + return nil + let message = sdk[].getMessage($id) # Convert cstring to string for internal method + if message.len > 0: + # Allocate C string - caller must free + let cStr = cast[cstring](alloc(message.len + 1)) + copyMem(cStr, cstring(message), message.len + 1) + return cStr + return nil + except: + return nil + +proc freeCString*(str: cstring) {.exportc, dynlib.} = + ## Free a C string allocated by the library + if str != nil: + dealloc(str) + +# Export the module for C bindings +when isMainModule: + sendMessage("Test message from Nim!") + + # This is just an example to get you started. A typical library package # exports the main API in this file. Note that you cannot rename this file # but you can remove it if you wish.