mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-02-26 00:23:16 +00:00
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
This commit is contained in:
parent
1cb1ffc996
commit
1f0354f8e2
@ -12,7 +12,7 @@ pub enum ErrorCode {
|
|||||||
UnknownError = -6,
|
UnknownError = -6,
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::context::{Context, Introduction};
|
use crate::context::{Context, ConvoHandle, Introduction};
|
||||||
|
|
||||||
/// Opaque wrapper for Context
|
/// Opaque wrapper for Context
|
||||||
#[derive_ReprC]
|
#[derive_ReprC]
|
||||||
@ -101,6 +101,69 @@ pub fn create_new_private_convo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends content to an existing conversation
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Returns a PayloadResult with payloads that must be delivered to participants.
|
||||||
|
/// Check error_code field: 0 means success, negative values indicate errors (see ErrorCode).
|
||||||
|
#[ffi_export]
|
||||||
|
pub fn send_content(
|
||||||
|
ctx: &mut ContextHandle,
|
||||||
|
convo_handle: ConvoHandle,
|
||||||
|
content: c_slice::Ref<'_, u8>,
|
||||||
|
) -> PayloadResult {
|
||||||
|
let payloads = ctx.0.send_content(convo_handle, &content);
|
||||||
|
|
||||||
|
let ffi_payloads: Vec<Payload> = payloads
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| Payload {
|
||||||
|
address: p.delivery_address.into(),
|
||||||
|
data: p.data.into(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
PayloadResult {
|
||||||
|
error_code: 0,
|
||||||
|
payloads: ffi_payloads.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles an incoming payload and writes content to caller-provided buffers
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Returns the number of bytes written to data_out on success (>= 0).
|
||||||
|
/// Returns negative error code on failure (see ErrorCode).
|
||||||
|
/// conversation_id_out_len is set to the number of bytes written to conversation_id_out.
|
||||||
|
#[ffi_export]
|
||||||
|
pub fn handle_payload(
|
||||||
|
ctx: &mut ContextHandle,
|
||||||
|
payload: c_slice::Ref<'_, u8>,
|
||||||
|
mut conversation_id_out: c_slice::Mut<'_, u8>,
|
||||||
|
conversation_id_out_len: Out<'_, u32>,
|
||||||
|
mut content_out: c_slice::Mut<'_, u8>,
|
||||||
|
) -> i32 {
|
||||||
|
match ctx.0.handle_payload(&payload) {
|
||||||
|
Some(content) => {
|
||||||
|
let convo_id_bytes = content.conversation_id.as_bytes();
|
||||||
|
|
||||||
|
if conversation_id_out.len() < convo_id_bytes.len() {
|
||||||
|
return ErrorCode::BufferExceeded as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if content_out.len() < content.data.len() {
|
||||||
|
return ErrorCode::BufferExceeded as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation_id_out[..convo_id_bytes.len()].copy_from_slice(convo_id_bytes);
|
||||||
|
conversation_id_out_len.write(convo_id_bytes.len() as u32);
|
||||||
|
content_out[..content.data.len()].copy_from_slice(&content.data);
|
||||||
|
|
||||||
|
content.data.len() as i32
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// safer_ffi implementation
|
// safer_ffi implementation
|
||||||
// ===============================================================================================================================
|
// ===============================================================================================================================
|
||||||
|
|||||||
@ -14,7 +14,7 @@ pub use crate::inbox::Introduction;
|
|||||||
const INITIAL_CONVO_HANDLE: u32 = 0xF5000001;
|
const INITIAL_CONVO_HANDLE: u32 = 0xF5000001;
|
||||||
|
|
||||||
/// Used to identify a conversation on the othersize of the FFI.
|
/// Used to identify a conversation on the othersize of the FFI.
|
||||||
type ConvoHandle = u32;
|
pub type ConvoHandle = u32;
|
||||||
|
|
||||||
// This is the main entry point to the conversations api.
|
// This is the main entry point to the conversations api.
|
||||||
// Ctx manages lifetimes of objects to process and generate payloads.
|
// Ctx manages lifetimes of objects to process and generate payloads.
|
||||||
@ -63,10 +63,10 @@ impl Context {
|
|||||||
(convo_handle, payloads)
|
(convo_handle, payloads)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_content(&mut self, _convo_id: ConversationId, _content: &[u8]) -> Vec<PayloadData> {
|
pub fn send_content(&mut self, convo_id: ConvoHandle, _content: &[u8]) -> Vec<PayloadData> {
|
||||||
// !TODO Replace Mock
|
// !TODO Replace Mock
|
||||||
vec![PayloadData {
|
vec![PayloadData {
|
||||||
delivery_address: _convo_id.into(),
|
delivery_address: format!("addr-for-{convo_id}"),
|
||||||
data: vec![40, 30, 20, 10],
|
data: vec![40, 30, 20, 10],
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,18 +30,64 @@ mod tests {
|
|||||||
bundle.set_len(bundle_len as usize);
|
bundle.set_len(bundle_len as usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert!(bundle_len > 0, "bundle failed: {}", bundle_len);
|
||||||
|
let content = b"Hello";
|
||||||
|
let result = create_new_private_convo(&mut ctx, bundle[..].into(), content[..].into());
|
||||||
|
|
||||||
|
assert!(result.error_code == 0, "Error: {}", result.error_code);
|
||||||
|
|
||||||
|
destroy_context(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_message_roundtrip() {
|
||||||
|
let mut saro = create_context();
|
||||||
|
let mut raya = create_context();
|
||||||
|
let mut raya_bundle = vec![0u8; 200];
|
||||||
|
|
||||||
|
let bundle_len = create_intro_bundle(&mut raya, (&mut raya_bundle[..]).into());
|
||||||
|
unsafe {
|
||||||
|
raya_bundle.set_len(bundle_len as usize);
|
||||||
|
}
|
||||||
|
|
||||||
assert!(bundle_len > 0, "bundle failed: {}", bundle_len);
|
assert!(bundle_len > 0, "bundle failed: {}", bundle_len);
|
||||||
let content = String::from_str("Hello").unwrap();
|
let content = String::from_str("Hello").unwrap();
|
||||||
let result = create_new_private_convo(
|
let result = create_new_private_convo(
|
||||||
&mut ctx,
|
&mut saro,
|
||||||
bundle.as_slice().into(),
|
raya_bundle.as_slice().into(),
|
||||||
content.as_bytes().into(),
|
content.as_bytes().into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(result.error_code == 0, "Error: {}", result.error_code);
|
assert!(result.error_code == 0, "Error: {}", result.error_code);
|
||||||
|
|
||||||
println!(" ID:{:?} Payloads:{:?}", result.convo_id, result.payloads);
|
// Handle payloads on raya's side
|
||||||
|
let mut conversation_id_out = vec![0u8; 256];
|
||||||
|
let mut conversation_id_out_len: u32 = 0;
|
||||||
|
let mut content_out = vec![0u8; 256];
|
||||||
|
|
||||||
destroy_context(ctx);
|
for p in result.payloads.iter() {
|
||||||
|
let bytes_written = handle_payload(
|
||||||
|
&mut raya,
|
||||||
|
p.data[..].into(),
|
||||||
|
(&mut conversation_id_out[..]).into(),
|
||||||
|
(&mut conversation_id_out_len).into(),
|
||||||
|
(&mut content_out[..]).into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
content_out.set_len(bytes_written as usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
bytes_written >= 0,
|
||||||
|
"handle_payload failed: {}",
|
||||||
|
bytes_written
|
||||||
|
);
|
||||||
|
|
||||||
|
//TODO: Verify output match
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_context(saro);
|
||||||
|
destroy_context(raya);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import options
|
||||||
import results
|
import results
|
||||||
|
|
||||||
import ../src/libchat
|
import ../src/libchat
|
||||||
@ -12,11 +13,23 @@ proc pingpong() =
|
|||||||
let intro = raya.createIntroductionBundle().expect("[Raya] Couldn't create intro bundle")
|
let intro = raya.createIntroductionBundle().expect("[Raya] Couldn't create intro bundle")
|
||||||
echo "Raya's Intro Bundle: ",intro
|
echo "Raya's Intro Bundle: ",intro
|
||||||
|
|
||||||
var (convo_sr, payload) = saro.createNewPrivateConvo(intro,"Hey Raya").expect("[Saro] Couldn't create convo")
|
var (convo_sr, payloads) = saro.createNewPrivateConvo(intro, "Hey Raya").expect("[Saro] Couldn't create convo")
|
||||||
echo "ConvoHandle:: ", convo_sr
|
echo "ConvoHandle:: ", convo_sr
|
||||||
echo "Payload:: ", payload
|
echo "Payload:: ", payloads
|
||||||
|
|
||||||
|
|
||||||
|
## Send Payloads to Raya
|
||||||
|
for p in payloads:
|
||||||
|
let res = raya.handlePayload(p.data)
|
||||||
|
if res.isOk:
|
||||||
|
let opt = res.get()
|
||||||
|
if opt.isSome:
|
||||||
|
let content_result = opt.get()
|
||||||
|
echo "RecvContent: ", content_result.conversationId, " ", content_result.data
|
||||||
|
else:
|
||||||
|
echo "Failed to handle payload: ", res.error
|
||||||
|
|
||||||
|
echo "Done"
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
pingpong()
|
pingpong()
|
||||||
|
|
||||||
|
|||||||
@ -107,6 +107,26 @@ proc create_new_private_convo*(
|
|||||||
content: SliceUint8,
|
content: SliceUint8,
|
||||||
): NewConvoResult {.importc, dynlib: CONVERSATIONS_LIB.}
|
): 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
|
## Free the result from create_new_private_convo
|
||||||
proc destroy_convo_result*(result: NewConvoResult) {.importc, dynlib: CONVERSATIONS_LIB.}
|
proc destroy_convo_result*(result: NewConvoResult) {.importc, dynlib: CONVERSATIONS_LIB.}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import std/options
|
||||||
import results
|
import results
|
||||||
import bindings
|
import bindings
|
||||||
|
|
||||||
@ -81,6 +82,76 @@ proc createNewPrivateConvo*(ctx: LibChat, bundle: string, content: string): Resu
|
|||||||
|
|
||||||
return ok((convoId, payloads))
|
return ok((convoId, payloads))
|
||||||
|
|
||||||
|
## Send content to an existing conversation
|
||||||
|
proc sendContent*(ctx: LibChat, convoHandle: ConvoHandle, content: string): Result[seq[PayloadResult], string] =
|
||||||
|
if ctx.handle == nil:
|
||||||
|
return err("Context handle is nil")
|
||||||
|
|
||||||
|
if content.len == 0:
|
||||||
|
return err("content is zero length")
|
||||||
|
|
||||||
|
let res = bindings.send_content(
|
||||||
|
ctx.handle,
|
||||||
|
convoHandle,
|
||||||
|
content.toSlice()
|
||||||
|
)
|
||||||
|
|
||||||
|
if res.error_code != 0:
|
||||||
|
result = err("Failed to send content: " & $res.error_code)
|
||||||
|
destroy_payload_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)
|
||||||
|
return ok(payloads)
|
||||||
|
|
||||||
|
type
|
||||||
|
ContentResult* = object
|
||||||
|
conversationId*: string
|
||||||
|
data*: seq[uint8]
|
||||||
|
|
||||||
|
## Handle an incoming payload and decrypt content
|
||||||
|
proc handlePayload*(ctx: LibChat, payload: seq[byte]): Result[Option[ContentResult], string] =
|
||||||
|
if ctx.handle == nil:
|
||||||
|
return err("Context handle is nil")
|
||||||
|
|
||||||
|
if payload.len == 0:
|
||||||
|
return err("payload is zero length")
|
||||||
|
|
||||||
|
var conversationIdBuf = newSeq[byte](ctx.buffer_size)
|
||||||
|
var contentBuf = newSeq[byte](ctx.buffer_size)
|
||||||
|
var conversationIdLen: uint32 = 0
|
||||||
|
|
||||||
|
let bytesWritten = bindings.handle_payload(
|
||||||
|
ctx.handle,
|
||||||
|
payload.toSlice(),
|
||||||
|
conversationIdBuf.toSlice(),
|
||||||
|
addr conversationIdLen,
|
||||||
|
contentBuf.toSlice()
|
||||||
|
)
|
||||||
|
|
||||||
|
if bytesWritten < 0:
|
||||||
|
return err("Failed to handle payload: " & $bytesWritten)
|
||||||
|
|
||||||
|
if bytesWritten == 0:
|
||||||
|
return ok(none(ContentResult))
|
||||||
|
|
||||||
|
conversationIdBuf.setLen(conversationIdLen)
|
||||||
|
contentBuf.setLen(bytesWritten)
|
||||||
|
|
||||||
|
return ok(some(ContentResult(
|
||||||
|
conversationId: cast[string](conversationIdBuf),
|
||||||
|
data: contentBuf
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
proc `=destroy`(x: var LibChat) =
|
proc `=destroy`(x: var LibChat) =
|
||||||
# Automatically free handle when the destructor is called
|
# Automatically free handle when the destructor is called
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user