From fe5fe64e035afa09d51568f55de72534a28c4a87 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:41:20 -0800 Subject: [PATCH] Update nim bindings --- nim-bindings/src/bindings.nim | 67 ++++++++++++++++++++++++++--------- nim-bindings/src/libchat.nim | 56 +++++++++++++---------------- 2 files changed, 74 insertions(+), 49 deletions(-) diff --git a/nim-bindings/src/bindings.nim b/nim-bindings/src/bindings.nim index c453e0a..9b3d878 100644 --- a/nim-bindings/src/bindings.nim +++ b/nim-bindings/src/bindings.nim @@ -68,10 +68,23 @@ type ## Result structure for create_intro_bundle ## error_code is 0 on success, negative on error (see ErrorCode) - PayloadResult* = object + CreateIntroResult* = object + error_code*: int32 + intro_bytes*: VecUint8 + + ## Result structure for send_content + ## error_code is 0 on success, negative on error (see ErrorCode) + SendContentResult* = object error_code*: int32 payloads*: VecPayload + ## Result structure for handle_payload + ## error_code is 0 on success, negative on error (see ErrorCode) + HandlePayloadResult* = object + error_code*: int32 + convo_id*: ReprCString + content*: VecUint8 + ## Result from create_new_private_convo ## error_code is 0 on success, negative on error (see ErrorCode) NewConvoResult* = object @@ -91,11 +104,11 @@ proc create_context*(): ContextHandle {.importc, dynlib: CONVERSATIONS_LIB.} proc destroy_context*(ctx: ContextHandle) {.importc, dynlib: CONVERSATIONS_LIB.} ## Creates an intro bundle for sharing with other users -## Returns: Number of bytes written to bundle_out, or negative error code +## Returns: CreateIntroResult struct - check error_code field (0 = success, negative = error) +## The result must be freed with destroy_intro_result() proc create_intro_bundle*( ctx: ContextHandle, - bundle_out: SliceUint8, -): int32 {.importc, dynlib: CONVERSATIONS_LIB.} +): CreateIntroResult {.importc, dynlib: CONVERSATIONS_LIB.} ## Creates a new private conversation ## Returns: NewConvoResult struct - check error_code field (0 = success, negative = error) @@ -107,30 +120,35 @@ proc create_new_private_convo*( ): NewConvoResult {.importc, dynlib: CONVERSATIONS_LIB.} ## Sends content to an existing conversation -## Returns: PayloadResult struct - check error_code field (0 = success, negative = error) -## The result must be freed with destroy_payload_result() +## Returns: SendContentResult struct - check error_code field (0 = success, negative = error) +## The result must be freed with destroy_send_content_result() proc send_content*( ctx: ContextHandle, - convo_id: SliceUint8, + convo_id: ReprCString, content: SliceUint8, -): PayloadResult {.importc, dynlib: CONVERSATIONS_LIB.} +): SendContentResult {.importc, dynlib: CONVERSATIONS_LIB.} -## Handles an incoming payload and writes content to caller-provided buffers -## Returns: Number of bytes written to content_out on success (>= 0), negative error code on failure -## conversation_id_out_len is set to the number of bytes written to conversation_id_out +## Handles an incoming payload +## Returns: HandlePayloadResult struct - check error_code field (0 = success, negative = error) +## This call does not always generate content. If content is zero bytes long then there +## is no data, and the convo_id should be ignored. +## The result must be freed with destroy_handle_payload_result() proc handle_payload*( ctx: ContextHandle, payload: SliceUint8, - conversation_id_out: SliceUint8, - conversation_id_out_len: ptr uint32, - content_out: SliceUint8, -): int32 {.importc, dynlib: CONVERSATIONS_LIB.} +): HandlePayloadResult {.importc, dynlib: CONVERSATIONS_LIB.} + +## Free the result from create_intro_bundle +proc destroy_intro_result*(result: CreateIntroResult) {.importc, dynlib: CONVERSATIONS_LIB.} ## Free the result from create_new_private_convo proc destroy_convo_result*(result: NewConvoResult) {.importc, dynlib: CONVERSATIONS_LIB.} -## Free the PayloadResult -proc destroy_payload_result*(result: PayloadResult) {.importc, dynlib: CONVERSATIONS_LIB.} +## Free the result from send_content +proc destroy_send_content_result*(result: SendContentResult) {.importc, dynlib: CONVERSATIONS_LIB.} + +## Free the result from handle_payload +proc destroy_handle_payload_result*(result: HandlePayloadResult) {.importc, dynlib: CONVERSATIONS_LIB.} # ============================================================================ # Helper functions @@ -157,6 +175,16 @@ proc `$`*(s: ReprCString): string = result = newString(s.len) copyMem(addr result[0], s.ptr, s.len) +## Create a ReprCString from a Nim string for passing to FFI functions. +## WARNING: The returned ReprCString borrows from the input string. +## The input string must remain valid for the duration of the FFI call. +## cap is set to 0 to prevent Rust from attempting to deallocate Nim memory. +proc toReprCString*(s: string): ReprCString = + if s.len == 0: + ReprCString(`ptr`: nil, len: 0, cap: 0) + else: + ReprCString(`ptr`: cast[ptr char](unsafeAddr s[0]), len: csize_t(s.len), cap: 0) + ## Convert a VecUint8 to a seq[byte] proc toSeq*(v: VecUint8): seq[byte] = if v.ptr == nil or v.len == 0: @@ -173,6 +201,11 @@ proc `[]`*(v: VecPayload, i: int): Payload = proc len*(v: VecPayload): int = int(v.len) +## Iterator for VecPayload +iterator items*(v: VecPayload): Payload = + for i in 0 ..< v.len: + yield v[int(i)] + ## Convert a string to seq[byte] proc toBytes*(s: string): seq[byte] = if s.len == 0: diff --git a/nim-bindings/src/libchat.nim b/nim-bindings/src/libchat.nim index ae9748c..dddd43d 100644 --- a/nim-bindings/src/libchat.nim +++ b/nim-bindings/src/libchat.nim @@ -1,4 +1,5 @@ import std/options +import std/sequtils import results import bindings @@ -31,22 +32,21 @@ proc getBuffer*(ctx: LibChat): seq[byte] = newSeq[byte](ctx.buffer_size) ## Generate a Introduction Bundle -proc createIntroductionBundle*(ctx: LibChat): Result[string, string] = +proc createIntroductionBundle*(ctx: LibChat): Result[seq[byte], string] = if ctx.handle == nil: return err("Context handle is nil") - var buffer = ctx.getBuffer() - var slice = buffer.toSlice() - let len = create_intro_bundle(ctx.handle, slice) + let res = create_intro_bundle(ctx.handle) - if len < 0: - return err("Failed to create intro bundle: " & $len) + if res.error_code != ErrNone: + result = err("Failed to create private convo: " & $res.error_code) + destroy_intro_result(res) + return - buffer.setLen(len) - return ok(cast[string](buffer)) + return ok(res.intro_bytes.toSeq()) ## Create a Private Convo -proc createNewPrivateConvo*(ctx: LibChat, bundle: string, content: seq[byte]): Result[(string, seq[PayloadResult]), string] = +proc createNewPrivateConvo*(ctx: LibChat, bundle: seq[byte], content: seq[byte]): Result[(string, seq[PayloadResult]), string] = if ctx.handle == nil: return err("Context handle is nil") @@ -92,25 +92,22 @@ proc sendContent*(ctx: LibChat, convoId: string, content: seq[byte]): Result[seq let res = bindings.send_content( ctx.handle, - convoId.toSlice(), + convoId.toReprCString, content.toSlice() ) if res.error_code != 0: result = err("Failed to send content: " & $res.error_code) - destroy_payload_result(res) + destroy_send_content_result(res) return - # Convert payloads to Nim types - var payloads = newSeq[PayloadResult](res.payloads.len) - for i in 0 ..< res.payloads.len: - let p = res.payloads[int(i)] - payloads[int(i)] = PayloadResult( - address: $p.address, - data: p.data.toSeq() - ) - destroy_payload_result(res) + let payloads = res.payloads.toSeq().mapIt(PayloadResult( + address: $it.address, + data: it.data.toSeq() + )) + + destroy_send_content_result(res) return ok(payloads) type @@ -130,26 +127,21 @@ proc handlePayload*(ctx: LibChat, payload: seq[byte]): Result[Option[ContentResu var contentBuf = newSeq[byte](ctx.buffer_size) var conversationIdLen: uint32 = 0 - let bytesWritten = bindings.handle_payload( + let res = bindings.handle_payload( ctx.handle, payload.toSlice(), - conversationIdBuf.toSlice(), - addr conversationIdLen, - contentBuf.toSlice() ) - if bytesWritten < 0: - return err("Failed to handle payload: " & $bytesWritten) + if res.error_code != ErrNone: + return err("Failed to handle payload: " & $res.error_code) - if bytesWritten == 0: + let content = res.content.toSeq() + if content.len == 0: return ok(none(ContentResult)) - conversationIdBuf.setLen(conversationIdLen) - contentBuf.setLen(bytesWritten) - return ok(some(ContentResult( - conversationId: cast[string](conversationIdBuf), - data: contentBuf + conversationId: $res.convo_id, + data: content )))