Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion conversations/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub enum ErrorCode {
UnknownError = -6,
}

use crate::context::{Context, Introduction};
use crate::context::{Context, ConvoHandle, Introduction};

/// Opaque wrapper for Context
#[derive_ReprC]
Expand Down Expand Up @@ -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
// ===============================================================================================================================
Expand Down
6 changes: 3 additions & 3 deletions conversations/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub use crate::inbox::Introduction;
const INITIAL_CONVO_HANDLE: u32 = 0xF5000001;

/// 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.
// Ctx manages lifetimes of objects to process and generate payloads.
Expand Down Expand Up @@ -63,10 +63,10 @@ impl Context {
(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
vec![PayloadData {
delivery_address: _convo_id.into(),
delivery_address: format!("addr-for-{convo_id}"),
data: vec![40, 30, 20, 10],
}]
}
Expand Down
54 changes: 50 additions & 4 deletions conversations/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,64 @@ mod tests {
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);
let content = String::from_str("Hello").unwrap();
let result = create_new_private_convo(
&mut ctx,
bundle.as_slice().into(),
&mut saro,
raya_bundle.as_slice().into(),
content.as_bytes().into(),
);

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);
}
}
19 changes: 16 additions & 3 deletions nim-bindings/examples/pingpong.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import options
import results

import ../src/libchat
Expand All @@ -12,11 +13,23 @@ proc pingpong() =
let intro = raya.createIntroductionBundle().expect("[Raya] Couldn't create intro bundle")
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 "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:
pingpong()

20 changes: 20 additions & 0 deletions nim-bindings/src/bindings.nim
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,26 @@ proc create_new_private_convo*(
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.}

Expand Down
71 changes: 71 additions & 0 deletions nim-bindings/src/libchat.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import std/options
import results
import bindings

Expand Down Expand Up @@ -81,6 +82,76 @@ proc createNewPrivateConvo*(ctx: LibChat, bundle: string, content: string): Resu

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) =
# Automatically free handle when the destructor is called
Expand Down