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
7 changes: 7 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[env]
# Pin the macOS deployment target so vendored C/C++ builds (SQLCipher, OpenSSL)
# compile with the same minimum version as the Nim linker expects.
# Without this, the host SDK version is used (currently 15.5), causing
# "was built for newer macOS version" linker warnings.
# This is caused by nimble and cargo defaulting to different targets.
MACOSX_DEPLOYMENT_TARGET = { value = "15.0", force = false }
24 changes: 21 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ name: CI

on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]

env:
env:
CARGO_TERM_COLOR: always

jobs:
Expand Down Expand Up @@ -36,3 +36,21 @@ jobs:
- run: rustup update stable && rustup default stable
- run: rustup component add rustfmt
- run: cargo fmt --all -- --check

nim-bindings-test:
name: Nim Bindings Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- run: rustup update stable && rustup default stable
- name: Install Nim via choosenim
run: |
curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y
echo "$HOME/.nimble/bin" >> $GITHUB_PATH
- run: nimble install -dy
working-directory: nim-bindings
- run: nimble pingpong
working-directory: nim-bindings
85 changes: 60 additions & 25 deletions conversations/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ pub fn create_context(name: repr_c::String) -> repr_c::Box<ContextHandle> {
}

/// Returns the friendly name of the contexts installation.
///
/// # ABI note
/// The result is written through `out` (Nim's calling convention for large struct returns).
#[ffi_export]
pub fn installation_name(ctx: &ContextHandle) -> repr_c::String {
ctx.0.installation_name().to_string().into()
pub fn installation_name(ctx: &ContextHandle, out: &mut repr_c::String) {
*out = ctx.0.installation_name().to_string().into();
}

/// Destroys a conversation store and frees its memory
Expand All @@ -74,11 +77,13 @@ pub fn destroy_context(ctx: repr_c::Box<ContextHandle>) {
/// Creates an intro bundle for sharing with other users
///
/// # Returns
/// Returns the number of bytes written to bundle_out
/// Check error_code field: 0 means success, negative values indicate errors (see ErrorCode).
///
/// # ABI note
/// The result is written through `out` (Nim's calling convention for large struct returns).
#[ffi_export]
pub fn create_intro_bundle(ctx: &mut ContextHandle) -> CreateIntroResult {
match ctx.0.create_intro_bundle() {
pub fn create_intro_bundle(ctx: &mut ContextHandle, out: &mut CreateIntroResult) {
*out = match ctx.0.create_intro_bundle() {
Ok(v) => CreateIntroResult {
error_code: ErrorCode::None as i32,
intro_bytes: v.into(),
Expand All @@ -87,27 +92,32 @@ pub fn create_intro_bundle(ctx: &mut ContextHandle) -> CreateIntroResult {
error_code: ErrorCode::UnknownError as i32,
intro_bytes: repr_c::Vec::EMPTY,
},
}
};
}

/// Creates a new private conversation
///
/// # Returns
/// Returns a struct with payloads that must be sent, the conversation_id that was created.
/// The NewConvoResult must be freed.
///
/// # ABI note
/// The result is written through `out` (Nim's calling convention for large struct returns).
#[ffi_export]
pub fn create_new_private_convo(
ctx: &mut ContextHandle,
bundle: c_slice::Ref<'_, u8>,
content: c_slice::Ref<'_, u8>,
) -> NewConvoResult {
out: &mut NewConvoResult,
) {
// Convert input bundle to Introduction
let Ok(intro) = Introduction::try_from(bundle.as_slice()) else {
return NewConvoResult {
*out = NewConvoResult {
error_code: ErrorCode::BadIntro as i32,
convo_id: "".into(),
payloads: Vec::new().into(),
};
return;
};

// Create conversation
Expand All @@ -122,31 +132,36 @@ pub fn create_new_private_convo(
})
.collect();

NewConvoResult {
*out = NewConvoResult {
error_code: 0,
convo_id: convo_id.to_string().into(),
payloads: ffi_payloads.into(),
}
};
}

/// 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).
///
/// # ABI note
/// The result is written through `out` (Nim's calling convention for large struct returns).
#[ffi_export]
pub fn send_content(
ctx: &mut ContextHandle,
convo_id: repr_c::String,
content: c_slice::Ref<'_, u8>,
) -> SendContentResult {
out: &mut SendContentResult,
) {
let payloads = match ctx.0.send_content(&convo_id, &content) {
Ok(p) => p,
Err(_) => {
return SendContentResult {
*out = SendContentResult {
error_code: ErrorCode::UnknownError as i32,
payloads: safer_ffi::Vec::EMPTY,
};
return;
}
};

Expand All @@ -158,10 +173,10 @@ pub fn send_content(
})
.collect();

SendContentResult {
*out = SendContentResult {
error_code: 0,
payloads: ffi_payloads.into(),
}
};
}

/// Handles an incoming payload
Expand All @@ -170,15 +185,19 @@ pub fn send_content(
/// Returns HandlePayloadResult
/// This call does not always generate content. If data is zero bytes long then there
/// is no data, and the converation_id should be ignored.
///
/// # ABI note
/// The result is written through `out` (Nim's calling convention for large struct returns).
#[ffi_export]
pub fn handle_payload(
ctx: &mut ContextHandle,
payload: c_slice::Ref<'_, u8>,
) -> HandlePayloadResult {
match ctx.0.handle_payload(&payload) {
out: &mut HandlePayloadResult,
) {
*out = match ctx.0.handle_payload(&payload) {
Ok(o) => o.into(),
Err(e) => e.into(),
}
};
}

// ------------------------------------------
Expand All @@ -195,9 +214,13 @@ pub struct CreateIntroResult {
}

/// Free the result from create_intro_bundle
///
/// # ABI note
/// Takes `&mut` instead of ownership because Nim always passes large structs as a pointer;
/// accepting the struct by value would be an ABI mismatch on the caller side.
#[ffi_export]
pub fn destroy_intro_result(result: CreateIntroResult) {
drop(result);
pub fn destroy_intro_result(result: &mut CreateIntroResult) {
unsafe { std::ptr::drop_in_place(result) }
}

/// Payload structure for FFI
Expand All @@ -219,9 +242,13 @@ pub struct SendContentResult {
}

/// Free the result from send_content
///
/// # ABI note
/// Takes `&mut` instead of ownership because Nim always passes large structs as a pointer;
/// accepting the struct by value would be an ABI mismatch on the caller side.
#[ffi_export]
pub fn destroy_send_content_result(result: SendContentResult) {
drop(result);
pub fn destroy_send_content_result(result: &mut SendContentResult) {
unsafe { std::ptr::drop_in_place(result) }
}

/// Result structure for handle_payload
Expand All @@ -237,9 +264,13 @@ pub struct HandlePayloadResult {
}

/// Free the result from handle_payload
///
/// # ABI note
/// Takes `&mut` instead of ownership because Nim always passes large structs as a pointer;
/// accepting the struct by value would be an ABI mismatch on the caller side.
#[ffi_export]
pub fn destroy_handle_payload_result(result: HandlePayloadResult) {
drop(result);
pub fn destroy_handle_payload_result(result: &mut HandlePayloadResult) {
unsafe { std::ptr::drop_in_place(result) }
}

impl From<ContentData> for HandlePayloadResult {
Expand Down Expand Up @@ -291,7 +322,11 @@ pub struct NewConvoResult {
}

/// Free the result from create_new_private_convo
///
/// # ABI note
/// Takes `&mut` instead of ownership because Nim always passes large structs as a pointer;
/// accepting the struct by value would be an ABI mismatch on the caller side.
#[ffi_export]
pub fn destroy_convo_result(result: NewConvoResult) {
drop(result);
pub fn destroy_convo_result(result: &mut NewConvoResult) {
unsafe { std::ptr::drop_in_place(result) }
}
21 changes: 18 additions & 3 deletions conversations/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,36 @@ mod tests {
let mut raya = create_context("raya".into());

// Raya Creates Bundle and Sends to Saro
let intro_result = create_intro_bundle(&mut raya);
let mut intro_result = CreateIntroResult {
error_code: -99,
intro_bytes: safer_ffi::Vec::EMPTY,
};
create_intro_bundle(&mut raya, &mut intro_result);
assert!(is_ok(intro_result.error_code));

let raya_bundle = intro_result.intro_bytes.as_ref();

// Saro creates a new conversation with Raya
let content: &[u8] = "hello".as_bytes();

let convo_result = create_new_private_convo(&mut saro, raya_bundle, content.into());
let mut convo_result = NewConvoResult {
error_code: -99,
convo_id: "".into(),
payloads: safer_ffi::Vec::EMPTY,
};
create_new_private_convo(&mut saro, raya_bundle, content.into(), &mut convo_result);
assert!(is_ok(convo_result.error_code));

// Raya recieves initial message
let payload = convo_result.payloads.first().unwrap();

let handle_result = handle_payload(&mut raya, payload.data.as_ref());
let mut handle_result: HandlePayloadResult = HandlePayloadResult {
error_code: -99,
convo_id: "".into(),
content: safer_ffi::Vec::EMPTY,
is_new_convo: false,
};
handle_payload(&mut raya, payload.data.as_ref(), &mut handle_result);
assert!(is_ok(handle_result.error_code));

// Check that the Content sent was the content received
Expand Down
9 changes: 7 additions & 2 deletions nim-bindings/conversations_example.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ bin = @["libchat"]
requires "nim >= 2.2.4"
requires "results"

proc buildRust() =
exec "cargo build --release --manifest-path ../Cargo.toml"


# Build Rust library before compiling Nim
before build:
exec "cargo build --release --manifest-path ../Cargo.toml"
buildRust()

task pingpong, "Run pingpong example":
exec "nim c -r --path:src examples/pingpong.nim"
buildRust()
exec "nim c -r --path:src --passL:../target/release/liblibchat.a --passL:-lm examples/pingpong.nim"
Loading