libchat/nim-bindings/src/bindings.nim
Jazz Turner-Baggs 1f0354f8e2
Add FFI functions for send_content and handle_payload (#29)
* Add api calls for handle_payload and send_content

* Add handle_payload and send_content to FFI
2026-01-28 10:50:41 -08:00

176 lines
5.6 KiB
Nim

# Nim FFI bindings for libchat conversations library
import std/[os]
# Dynamic library path resolution
# Can be overridden at compile time with -d:CONVERSATIONS_LIB:"path/to/lib"
# Or at runtime via LIBCHAT_LIB environment variable
when defined(macosx):
const DEFAULT_LIB_NAME = "liblogos_chat.dylib"
elif defined(linux):
const DEFAULT_LIB_NAME = "liblogos_chat.so"
elif defined(windows):
const DEFAULT_LIB_NAME = "logos_chat.dll"
else:
const DEFAULT_LIB_NAME = "logos_chat"
# Try to find the library relative to the source file location at compile time
const
thisDir = currentSourcePath().parentDir()
projectRoot = thisDir.parentDir().parentDir()
releaseLibPath = projectRoot / "target" / "release" / DEFAULT_LIB_NAME
debugLibPath = projectRoot / "target" / "debug" / DEFAULT_LIB_NAME
# Default to release path, can be overridden with -d:CONVERSATIONS_LIB:"..."
const CONVERSATIONS_LIB* {.strdefine.} = releaseLibPath
# Error codes (must match Rust ErrorCode enum)
const
ErrNone* = 0'i32
ErrBadPtr* = -1'i32
ErrBadConvoId* = -2'i32
ErrBadIntro* = -3'i32
ErrNotImplemented* = -4'i32
ErrBufferExceeded* = -5'i32
ErrUnknownError* = -6'i32
# Opaque handle type for Context
type ContextHandle* = pointer
type ConvoHandle* = uint32
type
## Slice for passing byte arrays to safer_ffi functions
SliceUint8* = object
`ptr`*: ptr uint8
len*: csize_t
## Vector type returned by safer_ffi functions (must be freed)
VecUint8* = object
`ptr`*: ptr uint8
len*: csize_t
cap*: csize_t
## repr_c::String type from safer_ffi
ReprCString* = object
`ptr`*: ptr char
len*: csize_t
cap*: csize_t
## Payload structure for FFI (matches Rust Payload struct)
Payload* = object
address*: ReprCString
data*: VecUint8
## Vector of Payloads returned by safer_ffi functions
VecPayload* = object
`ptr`*: ptr Payload
len*: csize_t
cap*: csize_t
## Result structure for create_intro_bundle
## error_code is 0 on success, negative on error (see ErrorCode)
PayloadResult* = object
error_code*: int32
payloads*: VecPayload
## Result from create_new_private_convo
## error_code is 0 on success, negative on error (see ErrorCode)
NewConvoResult* = object
error_code*: int32
convo_id*: uint32
payloads*: VecPayload
# FFI function imports
## Creates a new libchat Context
## Returns: Opaque handle to the context. Must be freed with destroy_context()
proc create_context*(): ContextHandle {.importc, dynlib: CONVERSATIONS_LIB.}
## Destroys a context and frees its memory
## - handle must be a valid pointer from create_context()
## - handle must not be used after this call
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
proc create_intro_bundle*(
ctx: ContextHandle,
bundle_out: SliceUint8,
): int32 {.importc, dynlib: CONVERSATIONS_LIB.}
## Creates a new private conversation
## Returns: NewConvoResult struct - check error_code field (0 = success, negative = error)
## The result must be freed with destroy_convo_result()
proc create_new_private_convo*(
ctx: ContextHandle,
bundle: SliceUint8,
content: SliceUint8,
): 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()
proc send_content*(
ctx: ContextHandle,
convo_handle: ConvoHandle,
content: SliceUint8,
): PayloadResult {.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
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.}
## 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.}
# ============================================================================
# Helper functions
# ============================================================================
## Create a SliceRefUint8 from a string
proc toSlice*(s: string): SliceUint8 =
if s.len == 0:
SliceUint8(`ptr`: nil, len: 0)
else:
SliceUint8(`ptr`: cast[ptr uint8](unsafeAddr s[0]), len: csize_t(s.len))
## Create a SliceRefUint8 from a seq[byte]
proc toSlice*(s: seq[byte]): SliceUint8 =
if s.len == 0:
SliceUint8(`ptr`: nil, len: 0)
else:
SliceUint8(`ptr`: cast[ptr uint8](unsafeAddr s[0]), len: csize_t(s.len))
## Convert a ReprCString to a Nim string
proc `$`*(s: ReprCString): string =
if s.ptr == nil or s.len == 0:
return ""
result = newString(s.len)
copyMem(addr result[0], s.ptr, s.len)
## Convert a VecUint8 to a seq[byte]
proc toSeq*(v: VecUint8): seq[byte] =
if v.ptr == nil or v.len == 0:
return @[]
result = newSeq[byte](v.len)
copyMem(addr result[0], v.ptr, v.len)
## Access payloads from VecPayload
proc `[]`*(v: VecPayload, i: int): Payload =
assert i >= 0 and csize_t(i) < v.len
cast[ptr UncheckedArray[Payload]](v.ptr)[i]
## Get length of VecPayload
proc len*(v: VecPayload): int =
int(v.len)