From e5cefdc02dfbf2b1d36b9a2d52cbe01d1bb91c37 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:15:20 -0700 Subject: [PATCH] Remove libchat legacy FFI --- core/conversations/src/api.rs | 379 ---------------------------------- core/conversations/src/lib.rs | 56 ----- 2 files changed, 435 deletions(-) delete mode 100644 core/conversations/src/api.rs diff --git a/core/conversations/src/api.rs b/core/conversations/src/api.rs deleted file mode 100644 index bd1e300..0000000 --- a/core/conversations/src/api.rs +++ /dev/null @@ -1,379 +0,0 @@ -// This is the FFI Interface to enable libchat to be used from other languages such as Nim and C. -// This interface makes heavy use of safer_ffi in order to safely move bytes across the FFI. -// -// The following table explains the safer_ffi types in use, and under what circumstances. -// -// - c_slice::Ref<'_, u8> : Borrowed, read-only byte slice for input parameters -// - c_slice::Mut<'_, u8> : Borrowed, mutable byte slice for in/out parameters -// - repr_c::Vec : Owned vector, used for return values (transfers ownership to caller) -// - repr_c::String : Owned string, used for return values (transfers ownership to caller) - -use safer_ffi::{ - String, derive_ReprC, ffi_export, - prelude::{c_slice, repr_c}, -}; - -use crate::{ - context::{Context, Introduction}, - errors::ChatError, - types::ContentData, -}; - -// Must only contain negative values or 0, values cannot be changed once set. -#[repr(i32)] -pub enum ErrorCode { - None = 0, - BadPtr = -1, - BadConvoId = -2, - BadIntro = -3, - NotImplemented = -4, - BufferExceeded = -5, - UnknownError = -6, -} - -pub fn is_ok(error: i32) -> bool { - error == ErrorCode::None as i32 -} - -// ------------------------------------------ -// Exported Functions -// ------------------------------------------ - -/// Opaque wrapper for Context -#[derive_ReprC] -#[repr(opaque)] -pub struct ContextHandle(pub(crate) Context); - -/// Creates a new libchat Ctx -/// -/// # Returns -/// Opaque handle to the store. Must be freed with destroy_context() -#[ffi_export] -pub fn create_context(name: repr_c::String) -> repr_c::Box { - // Deference name to to `str` and then borrow to &str - Box::new(ContextHandle(Context::new_with_name(&*name))).into() -} - -/// Returns the friendly name of the contexts installation. -/// -#[ffi_export] -pub fn installation_name(ctx: &ContextHandle) -> repr_c::String { - ctx.0.installation_name().to_string().into() -} - -/// Destroys a conversation store and frees its memory -/// -/// # Safety -/// - handle must be a valid pointer from conversation_store_create() -/// - handle must not be used after this call -/// - handle must not be freed twice -#[ffi_export] -pub fn destroy_context(ctx: repr_c::Box) { - drop(ctx); -} - -/// Destroys a repr_c::String and frees its memory -/// -/// # Safety -/// - s must be an owned repr_c::String value returned from a libchat FFI function -/// - s must not be used after this call -/// - s must not be freed twice -#[ffi_export] -pub fn destroy_string(s: repr_c::String) { - drop(s); -} - -/// Creates an intro bundle for sharing with other users -/// -/// # Returns -/// 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, out: &mut CreateIntroResult) { - *out = match ctx.0.create_intro_bundle() { - Ok(v) => CreateIntroResult { - error_code: ErrorCode::None as i32, - intro_bytes: v.into(), - }, - Err(_e) => 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>, - out: &mut NewConvoResult, -) { - // Convert input bundle to Introduction - let Ok(intro) = Introduction::try_from(bundle.as_slice()) else { - *out = NewConvoResult { - error_code: ErrorCode::BadIntro as i32, - convo_id: "".into(), - payloads: Vec::new().into(), - }; - return; - }; - - // Create conversation - let (convo_id, payloads) = ctx.0.create_private_convo(&intro, &content); - - // Convert payloads to FFI-compatible vector - let ffi_payloads: Vec = payloads - .into_iter() - .map(|p| Payload { - address: p.delivery_address.into(), - data: p.data.into(), - }) - .collect(); - - *out = NewConvoResult { - error_code: 0, - convo_id: convo_id.to_string().into(), - payloads: ffi_payloads.into(), - }; -} - -/// List existing conversations -/// -/// # Returns -/// Returns a struct with conversation ids of available conversations -/// The ListConvoResult must be freed. -#[ffi_export] -pub fn list_conversations(ctx: &mut ContextHandle) -> ListConvoResult { - match ctx.0.list_conversations() { - Ok(ids) => { - let ffi_ids: Vec = - ids.into_iter().map(|id| id.to_string().into()).collect(); - ListConvoResult { - error_code: ErrorCode::None as i32, - convo_ids: ffi_ids.into(), - } - } - Err(_) => ListConvoResult { - error_code: ErrorCode::UnknownError as i32, - convo_ids: repr_c::Vec::EMPTY, - }, - } -} - -/// 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>, - out: &mut SendContentResult, -) { - let payloads = match ctx.0.send_content(&convo_id, &content) { - Ok(p) => p, - Err(_) => { - *out = SendContentResult { - error_code: ErrorCode::UnknownError as i32, - payloads: safer_ffi::Vec::EMPTY, - }; - return; - } - }; - - let ffi_payloads: Vec = payloads - .into_iter() - .map(|p| Payload { - address: p.delivery_address.into(), - data: p.data.into(), - }) - .collect(); - - *out = SendContentResult { - error_code: 0, - payloads: ffi_payloads.into(), - }; -} - -/// Handles an incoming payload -/// -/// # Returns -/// 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>, - out: &mut HandlePayloadResult, -) { - *out = match ctx.0.handle_payload(&payload) { - Ok(o) => o.into(), - Err(e) => e.into(), - }; -} - -// ------------------------------------------ -// Return Type Definitions -// ------------------------------------------ - -/// Result structure for create_intro_bundle -/// error_code is 0 on success, negative on error (see ErrorCode) -#[derive_ReprC] -#[repr(C)] -pub struct CreateIntroResult { - pub error_code: i32, - pub intro_bytes: repr_c::Vec, -} - -/// 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: &mut CreateIntroResult) { - unsafe { std::ptr::drop_in_place(result) } -} - -/// Payload structure for FFI -#[derive(Debug)] -#[derive_ReprC] -#[repr(C)] -pub struct Payload { - pub address: repr_c::String, - pub data: repr_c::Vec, -} - -/// Result structure for send_content -/// error_code is 0 on success, negative on error (see ErrorCode) -#[derive_ReprC] -#[repr(C)] -pub struct SendContentResult { - pub error_code: i32, - pub payloads: repr_c::Vec, -} - -/// 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: &mut SendContentResult) { - unsafe { std::ptr::drop_in_place(result) } -} - -/// Result structure for handle_payload -/// error_code is 0 on success, negative on error (see ErrorCode) -#[derive(Debug)] -#[derive_ReprC] -#[repr(C)] -pub struct HandlePayloadResult { - pub error_code: i32, - pub convo_id: repr_c::String, - pub content: repr_c::Vec, - pub is_new_convo: bool, -} - -/// 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: &mut HandlePayloadResult) { - unsafe { std::ptr::drop_in_place(result) } -} - -impl From for HandlePayloadResult { - fn from(value: ContentData) -> Self { - HandlePayloadResult { - error_code: ErrorCode::None as i32, - convo_id: value.conversation_id.into(), - content: value.data.into(), - is_new_convo: value.is_new_convo, - } - } -} - -impl From> for HandlePayloadResult { - fn from(value: Option) -> Self { - if let Some(content) = value { - content.into() - } else { - HandlePayloadResult { - error_code: ErrorCode::None as i32, - convo_id: repr_c::String::EMPTY, - content: repr_c::Vec::EMPTY, - is_new_convo: false, - } - } - } -} - -impl From for HandlePayloadResult { - fn from(_value: ChatError) -> Self { - HandlePayloadResult { - // TODO: (P2) Translate ChatError into ErrorCode - error_code: ErrorCode::UnknownError as i32, - convo_id: String::EMPTY, - content: repr_c::Vec::EMPTY, - is_new_convo: false, - } - } -} - -/// Result structure for create_new_private_convo -/// error_code is 0 on success, negative on error (see ErrorCode) -#[derive_ReprC] -#[repr(C)] -pub struct NewConvoResult { - pub error_code: i32, - pub convo_id: repr_c::String, - pub payloads: repr_c::Vec, -} - -/// 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: &mut NewConvoResult) { - unsafe { std::ptr::drop_in_place(result) } -} - -/// Result structure for create_new_private_convo -/// error_code is 0 on success, negative on error (see ErrorCode) -#[derive_ReprC] -#[repr(C)] -pub struct ListConvoResult { - pub error_code: i32, - pub convo_ids: repr_c::Vec, -} - -/// Free the result from create_new_private_convo -#[ffi_export] -pub fn destroy_list_result(result: ListConvoResult) { - drop(result); -} diff --git a/core/conversations/src/lib.rs b/core/conversations/src/lib.rs index de0c023..018701c 100644 --- a/core/conversations/src/lib.rs +++ b/core/conversations/src/lib.rs @@ -1,4 +1,3 @@ -mod api; mod context; mod conversation; mod crypto; @@ -10,60 +9,5 @@ mod storage; mod types; mod utils; -pub use api::*; pub use context::{Context, Introduction}; pub use errors::ChatError; - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test_ffi() {} - - #[test] - fn test_message_roundtrip() { - let mut saro = create_context("saro".into()); - let mut raya = create_context("raya".into()); - - // Raya Creates Bundle and Sends to Saro - 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 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 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 - assert!(handle_result.content.as_ref().as_slice() == content); - - destroy_context(saro); - destroy_context(raya); - } -}