2026-01-29 00:59:07 +07:00
|
|
|
use safer_ffi::prelude::*;
|
2025-12-22 09:40:46 -08:00
|
|
|
|
|
|
|
|
// Must only contain negative values, values cannot be changed once set.
|
|
|
|
|
#[repr(i32)]
|
|
|
|
|
pub enum ErrorCode {
|
2026-01-29 00:59:07 +07:00
|
|
|
None = 0,
|
2025-12-22 09:40:46 -08:00
|
|
|
BadPtr = -1,
|
|
|
|
|
BadConvoId = -2,
|
2026-01-29 00:59:07 +07:00
|
|
|
BadIntro = -3,
|
|
|
|
|
NotImplemented = -4,
|
|
|
|
|
BufferExceeded = -5,
|
|
|
|
|
UnknownError = -6,
|
2025-12-22 09:40:46 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-04 10:18:32 -08:00
|
|
|
use crate::{
|
|
|
|
|
context::{Context, Introduction},
|
|
|
|
|
types::ContentData,
|
|
|
|
|
};
|
2025-12-22 09:40:46 -08:00
|
|
|
|
2026-01-29 00:59:07 +07:00
|
|
|
/// Opaque wrapper for Context
|
|
|
|
|
#[derive_ReprC]
|
|
|
|
|
#[repr(opaque)]
|
|
|
|
|
pub struct ContextHandle(pub(crate) Context);
|
2025-12-22 09:40:46 -08:00
|
|
|
|
|
|
|
|
/// Creates a new libchat Ctx
|
|
|
|
|
///
|
|
|
|
|
/// # Returns
|
2026-01-29 00:59:07 +07:00
|
|
|
/// Opaque handle to the store. Must be freed with destroy_context()
|
|
|
|
|
#[ffi_export]
|
|
|
|
|
pub fn create_context() -> repr_c::Box<ContextHandle> {
|
|
|
|
|
Box::new(ContextHandle(Context::new())).into()
|
2025-12-22 09:40:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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
|
2026-01-29 00:59:07 +07:00
|
|
|
#[ffi_export]
|
|
|
|
|
pub fn destroy_context(ctx: repr_c::Box<ContextHandle>) {
|
|
|
|
|
drop(ctx);
|
2025-12-22 09:40:46 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 00:59:07 +07:00
|
|
|
/// Creates an intro bundle for sharing with other users
|
2025-12-22 09:40:46 -08:00
|
|
|
///
|
|
|
|
|
/// # Returns
|
2026-01-29 00:59:07 +07:00
|
|
|
/// Returns the number of bytes written to bundle_out
|
|
|
|
|
/// Check error_code field: 0 means success, negative values indicate errors (see ErrorCode).
|
|
|
|
|
#[ffi_export]
|
|
|
|
|
pub fn create_intro_bundle(ctx: &mut ContextHandle, mut bundle_out: c_slice::Mut<'_, u8>) -> i32 {
|
|
|
|
|
let Ok(bundle) = ctx.0.create_intro_bundle() else {
|
|
|
|
|
return ErrorCode::UnknownError as i32;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Check buffer is large enough
|
|
|
|
|
if bundle_out.len() < bundle.len() {
|
|
|
|
|
return ErrorCode::BufferExceeded as i32;
|
2025-12-22 09:40:46 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 00:59:07 +07:00
|
|
|
bundle_out[..bundle.len()].copy_from_slice(&bundle);
|
|
|
|
|
bundle.len() as i32
|
2025-12-22 09:40:46 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 00:59:07 +07:00
|
|
|
/// Creates a new private conversation
|
2025-12-22 09:40:46 -08:00
|
|
|
///
|
|
|
|
|
/// # Returns
|
2026-01-29 00:59:07 +07:00
|
|
|
/// Returns a struct with payloads that must be sent, the conversation_id that was created.
|
|
|
|
|
/// The NewConvoResult must be freed.
|
|
|
|
|
#[ffi_export]
|
|
|
|
|
pub fn create_new_private_convo(
|
|
|
|
|
ctx: &mut ContextHandle,
|
|
|
|
|
bundle: c_slice::Ref<'_, u8>,
|
|
|
|
|
content: c_slice::Ref<'_, u8>,
|
|
|
|
|
) -> NewConvoResult {
|
|
|
|
|
// Convert input bundle to Introduction
|
2026-01-29 01:38:08 +07:00
|
|
|
let Ok(intro) = Introduction::try_from(bundle.as_slice()) else {
|
2026-01-29 00:59:07 +07:00
|
|
|
return NewConvoResult {
|
|
|
|
|
error_code: ErrorCode::BadIntro as i32,
|
2026-02-04 07:33:27 -08:00
|
|
|
convo_id: "".into(),
|
2026-01-29 00:59:07 +07:00
|
|
|
payloads: Vec::new().into(),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Convert input content to String
|
|
|
|
|
let msg = String::from_utf8_lossy(&content).into_owned();
|
|
|
|
|
|
|
|
|
|
// Create conversation
|
2026-02-04 07:33:27 -08:00
|
|
|
let (convo_id, payloads) = ctx.0.create_private_convo(&intro, msg);
|
2026-01-29 00:59:07 +07:00
|
|
|
|
|
|
|
|
// Convert payloads to FFI-compatible vector
|
|
|
|
|
let ffi_payloads: Vec<Payload> = payloads
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|p| Payload {
|
|
|
|
|
address: p.delivery_address.into(),
|
|
|
|
|
data: p.data.into(),
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
NewConvoResult {
|
|
|
|
|
error_code: 0,
|
2026-02-04 07:33:27 -08:00
|
|
|
convo_id: convo_id.to_string().into(),
|
2026-01-29 00:59:07 +07:00
|
|
|
payloads: ffi_payloads.into(),
|
2025-12-22 09:40:46 -08:00
|
|
|
}
|
2026-01-29 00:59:07 +07:00
|
|
|
}
|
2025-12-22 09:40:46 -08:00
|
|
|
|
2026-01-29 01:50:41 +07:00
|
|
|
/// 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,
|
2026-02-04 07:33:27 -08:00
|
|
|
convo_id: repr_c::String,
|
2026-01-29 01:50:41 +07:00
|
|
|
content: c_slice::Ref<'_, u8>,
|
2026-02-04 08:21:39 -08:00
|
|
|
) -> SendContentResult {
|
2026-02-04 07:33:27 -08:00
|
|
|
let payloads = match ctx.0.send_content(&convo_id, &content) {
|
2026-01-29 23:36:18 +07:00
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => {
|
2026-02-04 08:21:39 -08:00
|
|
|
return SendContentResult {
|
2026-01-29 23:36:18 +07:00
|
|
|
error_code: ErrorCode::UnknownError as i32,
|
|
|
|
|
payloads: safer_ffi::Vec::EMPTY,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-01-29 01:50:41 +07:00
|
|
|
|
|
|
|
|
let ffi_payloads: Vec<Payload> = payloads
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|p| Payload {
|
|
|
|
|
address: p.delivery_address.into(),
|
|
|
|
|
data: p.data.into(),
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
2026-02-04 08:21:39 -08:00
|
|
|
SendContentResult {
|
2026-01-29 01:50:41 +07:00
|
|
|
error_code: 0,
|
|
|
|
|
payloads: ffi_payloads.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 08:21:39 -08:00
|
|
|
/// Handles an incoming payload
|
2026-01-29 01:50:41 +07:00
|
|
|
///
|
|
|
|
|
/// # Returns
|
2026-02-04 08:21:39 -08:00
|
|
|
/// Returns HandlePayloadResult
|
2026-01-29 01:50:41 +07:00
|
|
|
/// 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>,
|
2026-02-04 08:21:39 -08:00
|
|
|
) -> HandlePayloadResult {
|
2026-02-04 13:30:02 -08:00
|
|
|
let content = match ctx.0.handle_payload(&payload) {
|
|
|
|
|
Some(v) => v,
|
|
|
|
|
None => {
|
|
|
|
|
return HandlePayloadResult {
|
|
|
|
|
error_code: ErrorCode::UnknownError as i32,
|
|
|
|
|
convo_id: "".into(),
|
|
|
|
|
content: safer_ffi::Vec::EMPTY,
|
|
|
|
|
events: Events::None,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-01-29 01:50:41 +07:00
|
|
|
|
2026-02-04 08:21:39 -08:00
|
|
|
HandlePayloadResult {
|
2026-02-04 13:30:02 -08:00
|
|
|
error_code: ErrorCode::None as i32,
|
|
|
|
|
convo_id: repr_c::String::from(content.conversation_id),
|
|
|
|
|
content: safer_ffi::Vec::from(content.data),
|
|
|
|
|
events: if content.isNewConvo {
|
|
|
|
|
Events::NewConvo
|
|
|
|
|
} else {
|
|
|
|
|
Events::None
|
|
|
|
|
},
|
2026-01-29 01:50:41 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 00:59:07 +07:00
|
|
|
// ============================================================================
|
|
|
|
|
// safer_ffi implementation
|
|
|
|
|
// ===============================================================================================================================
|
|
|
|
|
|
|
|
|
|
/// Payload structure for FFI
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
#[derive_ReprC]
|
|
|
|
|
#[repr(C)]
|
|
|
|
|
pub struct Payload {
|
|
|
|
|
pub address: repr_c::String,
|
|
|
|
|
pub data: repr_c::Vec<u8>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Result structure for create_intro_bundle_safe
|
|
|
|
|
/// error_code is 0 on success, negative on error (see ErrorCode)
|
|
|
|
|
#[derive_ReprC]
|
|
|
|
|
#[repr(C)]
|
2026-02-04 08:21:39 -08:00
|
|
|
pub struct SendContentResult {
|
2026-01-29 00:59:07 +07:00
|
|
|
pub error_code: i32,
|
|
|
|
|
pub payloads: repr_c::Vec<Payload>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Free the result from create_intro_bundle_safe
|
|
|
|
|
#[ffi_export]
|
2026-02-04 08:21:39 -08:00
|
|
|
pub fn destroy_send_content_result(result: SendContentResult) {
|
|
|
|
|
drop(result);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 10:18:32 -08:00
|
|
|
#[derive_ReprC]
|
|
|
|
|
#[repr(i32)]
|
|
|
|
|
pub enum Events {
|
|
|
|
|
None = 0,
|
|
|
|
|
NewConvo = 1,
|
|
|
|
|
}
|
2026-02-04 08:21:39 -08:00
|
|
|
/// Result structure for create_new_private_convo_safe
|
|
|
|
|
/// error_code is 0 on success, negative on error (see ErrorCode)
|
|
|
|
|
#[derive_ReprC]
|
|
|
|
|
#[repr(C)]
|
|
|
|
|
pub struct HandlePayloadResult {
|
|
|
|
|
pub error_code: i32,
|
|
|
|
|
pub convo_id: repr_c::String,
|
2026-02-04 10:18:32 -08:00
|
|
|
pub content: repr_c::Vec<u8>,
|
|
|
|
|
pub events: Events,
|
2026-02-04 08:21:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Free the result from create_new_private_convo_safe
|
|
|
|
|
#[ffi_export]
|
|
|
|
|
pub fn destroy_handle_payload_result(result: HandlePayloadResult) {
|
2026-01-29 00:59:07 +07:00
|
|
|
drop(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Result structure for create_new_private_convo_safe
|
|
|
|
|
/// error_code is 0 on success, negative on error (see ErrorCode)
|
|
|
|
|
#[derive_ReprC]
|
|
|
|
|
#[repr(C)]
|
|
|
|
|
pub struct NewConvoResult {
|
|
|
|
|
pub error_code: i32,
|
2026-02-04 07:33:27 -08:00
|
|
|
pub convo_id: repr_c::String,
|
2026-01-29 00:59:07 +07:00
|
|
|
pub payloads: repr_c::Vec<Payload>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Free the result from create_new_private_convo_safe
|
|
|
|
|
#[ffi_export]
|
|
|
|
|
pub fn destroy_convo_result(result: NewConvoResult) {
|
|
|
|
|
drop(result);
|
2025-12-22 09:40:46 -08:00
|
|
|
}
|