mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-02-10 00:43:09 +00:00
Add Project scaffolding (#1)
This commit is contained in:
parent
afefa3c5a2
commit
04d6f8a84b
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug
|
||||||
|
target
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Generated by cargo mutants
|
||||||
|
# Contains mutation testing data
|
||||||
|
**/mutants.out*/
|
||||||
|
|
||||||
|
# RustRover
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
[workspace]
|
||||||
|
resolver = "3"
|
||||||
|
members = [
|
||||||
|
"conversations"
|
||||||
|
, "crypto"]
|
||||||
|
default-members = [
|
||||||
|
"conversations"
|
||||||
|
]
|
||||||
11
conversations/Cargo.toml
Normal file
11
conversations/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "logos-chat"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = "2.0.17"
|
||||||
|
|
||||||
174
conversations/src/api.rs
Normal file
174
conversations/src/api.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
use core::ffi::c_char;
|
||||||
|
use std::{ffi::CStr, slice};
|
||||||
|
|
||||||
|
// Must only contain negative values, values cannot be changed once set.
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum ErrorCode {
|
||||||
|
BadPtr = -1,
|
||||||
|
BadConvoId = -2,
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::context::Ctx;
|
||||||
|
|
||||||
|
pub type ContextHandle = *mut Ctx;
|
||||||
|
|
||||||
|
/// Creates a new libchat Ctx
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Opaque handle to the store. Must be freed with conversation_store_destroy()
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn create_context() -> ContextHandle {
|
||||||
|
let store = Box::new(Ctx::new());
|
||||||
|
Box::into_raw(store) // Leak the box, return raw pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn destroy_context(handle: ContextHandle) {
|
||||||
|
if !handle.is_null() {
|
||||||
|
unsafe {
|
||||||
|
let _ = Box::from_raw(handle); // Reconstruct box and drop it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypts/encodes content into payloads.
|
||||||
|
/// There may be multiple payloads generated from a single content.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Returns the number of payloads created.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Negative numbers symbolize an error has occured. See `ErrorCode`
|
||||||
|
///
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn generate_payload(
|
||||||
|
// Input: Context Handle
|
||||||
|
handle: ContextHandle,
|
||||||
|
// Input: Conversation_id
|
||||||
|
conversation_id: *const c_char,
|
||||||
|
// Input: Content array
|
||||||
|
content: *const u8,
|
||||||
|
content_len: usize,
|
||||||
|
|
||||||
|
max_payload_count: usize,
|
||||||
|
// Output: Addresses
|
||||||
|
addrs: *const *mut c_char,
|
||||||
|
addr_max_len: usize,
|
||||||
|
|
||||||
|
// Output: Frame data
|
||||||
|
payload_buffer_ptrs: *const *mut u8,
|
||||||
|
payload_buffer_max_len: *const usize, //Single Value
|
||||||
|
|
||||||
|
// Output: Array - Number of bytes written to each payload
|
||||||
|
output_actual_lengths: *mut usize,
|
||||||
|
) -> i32 {
|
||||||
|
if handle.is_null() || content.is_null() || payload_buffer_ptrs.is_null() || addrs.is_null() {
|
||||||
|
return ErrorCode::BadPtr as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ctx = &mut *handle;
|
||||||
|
let content_slice = slice::from_raw_parts(content, content_len);
|
||||||
|
let payload_ptrs_slice = slice::from_raw_parts(payload_buffer_ptrs, max_payload_count);
|
||||||
|
let payload_max_len = if !payload_buffer_max_len.is_null() {
|
||||||
|
*payload_buffer_max_len
|
||||||
|
} else {
|
||||||
|
return ErrorCode::BadPtr as i32;
|
||||||
|
};
|
||||||
|
let addrs_slice = slice::from_raw_parts(addrs, max_payload_count);
|
||||||
|
let actual_lengths_slice =
|
||||||
|
slice::from_raw_parts_mut(output_actual_lengths, max_payload_count);
|
||||||
|
|
||||||
|
let c_str = CStr::from_ptr(conversation_id);
|
||||||
|
let id_str = match c_str.to_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return ErrorCode::BadConvoId as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call ctx.send_content to get payloads
|
||||||
|
let payloads = ctx.send_content(id_str, content_slice);
|
||||||
|
|
||||||
|
// Check if we have enough output buffers
|
||||||
|
if payloads.len() > max_payload_count {
|
||||||
|
return ErrorCode::BadPtr as i32; // Not enough output buffers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write each payload to the output buffers
|
||||||
|
for (i, payload) in payloads.iter().enumerate() {
|
||||||
|
let payload_ptr = payload_ptrs_slice[i];
|
||||||
|
let addr_ptr = addrs_slice[i];
|
||||||
|
|
||||||
|
// Write payload data
|
||||||
|
if !payload_ptr.is_null() {
|
||||||
|
let payload_buf = slice::from_raw_parts_mut(payload_ptr, payload_max_len);
|
||||||
|
let copy_len = payload.data.len().min(payload_max_len);
|
||||||
|
payload_buf[..copy_len].copy_from_slice(&payload.data[..copy_len]);
|
||||||
|
actual_lengths_slice[i] = copy_len;
|
||||||
|
} else {
|
||||||
|
return ErrorCode::BadPtr as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write delivery address
|
||||||
|
if !addr_ptr.is_null() {
|
||||||
|
let addr_bytes = payload.delivery_address.as_bytes();
|
||||||
|
let addr_buf = slice::from_raw_parts_mut(addr_ptr as *mut u8, addr_max_len);
|
||||||
|
let copy_len = addr_bytes.len().min(addr_max_len - 1);
|
||||||
|
addr_buf[..copy_len].copy_from_slice(&addr_bytes[..copy_len]);
|
||||||
|
addr_buf[copy_len] = 0; // Null-terminate
|
||||||
|
} else {
|
||||||
|
return ErrorCode::BadPtr as i32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payloads.len() as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts/decodes payloads into content.
|
||||||
|
/// A payload may return 1 or 0 contents.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Returns the number of bytes written to content
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Negative numbers symbolize an error has occured. See `ErrorCode`
|
||||||
|
///
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn handle_payload(
|
||||||
|
// Input: Context handle
|
||||||
|
handle: ContextHandle,
|
||||||
|
// Input: Payload data
|
||||||
|
payload_data: *const u8,
|
||||||
|
payload_len: usize,
|
||||||
|
|
||||||
|
// Output: Content
|
||||||
|
content: *mut u8,
|
||||||
|
content_max_len: usize,
|
||||||
|
) -> i32 {
|
||||||
|
if handle.is_null() || payload_data.is_null() || content.is_null() {
|
||||||
|
return ErrorCode::BadPtr as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ctx = &mut *handle;
|
||||||
|
let payload_slice = slice::from_raw_parts(payload_data, payload_len);
|
||||||
|
let content_buf = slice::from_raw_parts_mut(content, content_max_len);
|
||||||
|
|
||||||
|
// Call ctx.handle_payload to decode the payload
|
||||||
|
let contents = ctx.handle_payload(payload_slice);
|
||||||
|
|
||||||
|
if let Some(content_data) = contents {
|
||||||
|
let copy_len = content_data.data.len().min(content_max_len);
|
||||||
|
content_buf[..copy_len].copy_from_slice(&content_data.data[..copy_len]);
|
||||||
|
copy_len as i32
|
||||||
|
} else {
|
||||||
|
0 // No content produced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
conversations/src/context.rs
Normal file
44
conversations/src/context.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use crate::conversation::{ConversationId, ConversationIdOwned, ConversationStore, PrivateV1Convo};
|
||||||
|
|
||||||
|
pub struct PayloadData {
|
||||||
|
pub delivery_address: String,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ContentData {
|
||||||
|
pub conversation_id: String,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Ctx {
|
||||||
|
store: ConversationStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ctx {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
store: ConversationStore::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_private_convo(&mut self, _content: &[u8]) -> ConversationIdOwned {
|
||||||
|
let new_convo = PrivateV1Convo::new();
|
||||||
|
self.store.insert(new_convo)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_content(&mut self, _convo_id: ConversationId, _content: &[u8]) -> Vec<PayloadData> {
|
||||||
|
// !TODO Replace Mock
|
||||||
|
vec![PayloadData {
|
||||||
|
delivery_address: _convo_id.into(),
|
||||||
|
data: vec![40, 30, 20, 10],
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_payload(&mut self, _payload: &[u8]) -> Option<ContentData> {
|
||||||
|
// !TODO Replace Mock
|
||||||
|
Some(ContentData {
|
||||||
|
conversation_id: "convo_id".into(),
|
||||||
|
data: vec![1, 2, 3, 4, 5, 6],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
67
conversations/src/conversation.rs
Normal file
67
conversations/src/conversation.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub use crate::errors::ChatError;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// Type Definitions
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
pub type ConversationId<'a> = &'a str;
|
||||||
|
pub type ConversationIdOwned = Arc<str>;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// Trait Definitions
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
pub trait Convo: Debug {
|
||||||
|
fn id(&self) -> ConversationId;
|
||||||
|
fn send_frame(&mut self, message: &[u8]) -> Result<(), ChatError>;
|
||||||
|
fn handle_frame(&mut self, message: &[u8]) -> Result<(), ChatError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// Structs
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
pub struct ConversationStore {
|
||||||
|
conversations: HashMap<Arc<str>, Box<dyn Convo>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConversationStore {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
conversations: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, conversation: impl Convo + 'static) -> ConversationIdOwned {
|
||||||
|
let key: ConversationIdOwned = Arc::from(conversation.id());
|
||||||
|
self.conversations
|
||||||
|
.insert(key.clone(), Box::new(conversation));
|
||||||
|
key
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: ConversationId) -> Option<&(dyn Convo + '_)> {
|
||||||
|
self.conversations.get(id).map(|c| c.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, id: &str) -> Option<&mut (dyn Convo + '_)> {
|
||||||
|
Some(self.conversations.get_mut(id)?.as_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn conversation_ids(&self) -> impl Iterator<Item = ConversationIdOwned> + '_ {
|
||||||
|
self.conversations.keys().cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// Modules
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
mod group_test;
|
||||||
|
mod privatev1;
|
||||||
|
|
||||||
|
pub use group_test::GroupTestConvo;
|
||||||
|
pub use privatev1::PrivateV1Convo;
|
||||||
27
conversations/src/conversation/group_test.rs
Normal file
27
conversations/src/conversation/group_test.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::conversation::{ChatError, ConversationId, Convo};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GroupTestConvo {}
|
||||||
|
|
||||||
|
impl GroupTestConvo {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Convo for GroupTestConvo {
|
||||||
|
fn id(&self) -> ConversationId {
|
||||||
|
// implementation
|
||||||
|
"grouptest"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_frame(&mut self, _message: &[u8]) -> Result<(), ChatError> {
|
||||||
|
// todo!("Not Implemented")
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_frame(&mut self, _message: &[u8]) -> Result<(), ChatError> {
|
||||||
|
// todo!("Not Implemented")
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
25
conversations/src/conversation/privatev1.rs
Normal file
25
conversations/src/conversation/privatev1.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use crate::conversation::{ChatError, ConversationId, Convo};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PrivateV1Convo {}
|
||||||
|
|
||||||
|
impl PrivateV1Convo {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Convo for PrivateV1Convo {
|
||||||
|
fn id(&self) -> ConversationId {
|
||||||
|
// implementation
|
||||||
|
"private_v1_convo_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_frame(&mut self, _message: &[u8]) -> Result<(), ChatError> {
|
||||||
|
todo!("Not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_frame(&mut self, _message: &[u8]) -> Result<(), ChatError> {
|
||||||
|
todo!("Not Implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
0
conversations/src/crypto.rs
Normal file
0
conversations/src/crypto.rs
Normal file
7
conversations/src/errors.rs
Normal file
7
conversations/src/errors.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
pub use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ChatError {
|
||||||
|
#[error("protocol error: {0:?}")]
|
||||||
|
Protocol(String),
|
||||||
|
}
|
||||||
31
conversations/src/inbox.rs
Normal file
31
conversations/src/inbox.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use crate::conversation::{ChatError, ConversationId, Convo};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Inbox {
|
||||||
|
address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inbox {
|
||||||
|
pub fn new(address: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
address: address.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Convo for Inbox {
|
||||||
|
fn id(&self) -> ConversationId {
|
||||||
|
self.address.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_frame(&mut self, _message: &[u8]) -> Result<(), ChatError> {
|
||||||
|
todo!("Not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_frame(&mut self, message: &[u8]) -> Result<(), ChatError> {
|
||||||
|
if message.len() == 0 {
|
||||||
|
return Err(ChatError::Protocol("Example error".into()));
|
||||||
|
}
|
||||||
|
todo!("Not Implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
153
conversations/src/lib.rs
Normal file
153
conversations/src/lib.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
mod api;
|
||||||
|
mod context;
|
||||||
|
mod conversation;
|
||||||
|
mod errors;
|
||||||
|
mod inbox;
|
||||||
|
|
||||||
|
pub use api::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use conversation::{ConversationStore, GroupTestConvo, PrivateV1Convo};
|
||||||
|
use inbox::Inbox;
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ffi() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_and_write() {
|
||||||
|
// Create Context
|
||||||
|
let ctx = create_context();
|
||||||
|
|
||||||
|
// Setup conversation_id
|
||||||
|
let conv_id = CString::new("test_conversation_123").unwrap();
|
||||||
|
|
||||||
|
// Setup content
|
||||||
|
let content = b"Hello, World!";
|
||||||
|
|
||||||
|
// Setup output buffers for addresses (labels)
|
||||||
|
let addr_max_len = 256;
|
||||||
|
let mut addr_buffer1: Vec<u8> = vec![0; addr_max_len];
|
||||||
|
let mut addr_buffer2: Vec<u8> = vec![0; addr_max_len];
|
||||||
|
|
||||||
|
let addr_ptrs: Vec<*mut i8> = vec![
|
||||||
|
addr_buffer1.as_mut_ptr() as *mut i8,
|
||||||
|
addr_buffer2.as_mut_ptr() as *mut i8,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Setup payload buffers
|
||||||
|
let max_payload_count = 2;
|
||||||
|
let payload_max_len = 1024;
|
||||||
|
let mut payload1: Vec<u8> = vec![0; payload_max_len];
|
||||||
|
let mut payload2: Vec<u8> = vec![0; payload_max_len];
|
||||||
|
|
||||||
|
let payload_ptrs: Vec<*mut u8> = vec![payload1.as_mut_ptr(), payload2.as_mut_ptr()];
|
||||||
|
|
||||||
|
let payload_max_lens: Vec<usize> = vec![payload_max_len, payload_max_len];
|
||||||
|
let mut actual_lengths: Vec<usize> = vec![0; max_payload_count];
|
||||||
|
|
||||||
|
// Call the FFI function
|
||||||
|
let result = unsafe {
|
||||||
|
generate_payload(
|
||||||
|
ctx,
|
||||||
|
conv_id.as_ptr(),
|
||||||
|
content.as_ptr(),
|
||||||
|
content.len(),
|
||||||
|
max_payload_count,
|
||||||
|
addr_ptrs.as_ptr(),
|
||||||
|
addr_max_len,
|
||||||
|
payload_ptrs.as_ptr(),
|
||||||
|
payload_max_lens.as_ptr(),
|
||||||
|
actual_lengths.as_mut_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify results
|
||||||
|
assert_eq!(result, 1, "Function should return 1 on success");
|
||||||
|
|
||||||
|
// Check that the conversation ID was written to the first label buffer
|
||||||
|
let written_addr = std::ffi::CStr::from_bytes_until_nul(&addr_buffer1)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(written_addr, "test_conversation_123");
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
destroy_context(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_and_write_null_ptr() {
|
||||||
|
use std::ptr;
|
||||||
|
// Create Context
|
||||||
|
let ctx = create_context();
|
||||||
|
|
||||||
|
let conv_id = CString::new("test").unwrap();
|
||||||
|
let content = b"test";
|
||||||
|
|
||||||
|
// Test with null content pointer
|
||||||
|
let result = unsafe {
|
||||||
|
generate_payload(
|
||||||
|
ctx,
|
||||||
|
conv_id.as_ptr(),
|
||||||
|
ptr::null(),
|
||||||
|
content.len(),
|
||||||
|
1,
|
||||||
|
ptr::null(),
|
||||||
|
256,
|
||||||
|
ptr::null(),
|
||||||
|
ptr::null(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
destroy_context(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(result, -1, "Should return ERR_BAD_PTR for null pointer");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convo_store_get() {
|
||||||
|
let mut store: ConversationStore = ConversationStore::new();
|
||||||
|
|
||||||
|
let new_convo = GroupTestConvo::new();
|
||||||
|
let convo_id = store.insert(new_convo);
|
||||||
|
|
||||||
|
let convo = store.get_mut(&convo_id).ok_or_else(|| 0);
|
||||||
|
convo.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_convo_example() {
|
||||||
|
let mut store: ConversationStore = ConversationStore::new();
|
||||||
|
|
||||||
|
let raya = Inbox::new("Raya");
|
||||||
|
let saro = PrivateV1Convo::new();
|
||||||
|
let pax = GroupTestConvo::new();
|
||||||
|
|
||||||
|
store.insert(raya);
|
||||||
|
store.insert(saro);
|
||||||
|
let convo_id = store.insert(pax);
|
||||||
|
|
||||||
|
for id in store.conversation_ids().collect::<Vec<_>>() {
|
||||||
|
let a = store.get_mut(&id).unwrap();
|
||||||
|
a.send_frame(b"test message").unwrap();
|
||||||
|
println!("Conversation ID: {} :: {:?}", id, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in store.conversation_ids().collect::<Vec<_>>() {
|
||||||
|
let a = store.get_mut(&id).unwrap();
|
||||||
|
let _ = a.handle_frame(&[0x1, 0x2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("ID -> {}", store.get(&convo_id).unwrap().id());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user