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
88 changes: 4 additions & 84 deletions whatsrust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
use std::{
ffi::{CStr, CString, c_char, c_void},
path::Path,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
sync::Arc,
};

#[macro_use]
mod callbacks;
mod registrations;
mod lifecycle;
mod presence;
mod abi;
mod caches;
mod events;
Expand All @@ -25,6 +23,7 @@ pub use registrations::{
set_optimistic_text_sent_handler, set_presence_handler,
};
pub use lifecycle::{connect, disconnect, logout, new_client, pair_phone};
pub use presence::{SubscribePresenceResult, drain_raw_presence_diagnostics, subscribe_presence};
pub(crate) use models::file_kind_discriminant;
pub use models::{
ChatSettings, CommunitiesError, CommunityInfo, Contact, DownloadFailed, Event, FileContent,
Expand All @@ -35,7 +34,6 @@ pub use models::{
};
use strum::FromRepr;

static PRESENCE_CALLBACK_INGRESS: AtomicUsize = AtomicUsize::new(0);
#[cfg(test)]
mod file_kind_tests {
use super::{FileKind, file_kind_discriminant};
Expand Down Expand Up @@ -648,84 +646,6 @@ impl CallbackTranslator<i64> for i64 {
}
}

unsafe fn take_owned_c_string(
value: *mut c_char,
free: unsafe extern "C" fn(*mut c_char),
) -> Option<String> {
if value.is_null() {
return None;
}
let result = unsafe { CStr::from_ptr(value) }
.to_string_lossy()
.into_owned();
unsafe { free(value) };
Some(result)
}

pub fn drain_raw_presence_diagnostics() -> Option<String> {
unsafe {
let mut report = take_owned_c_string(
C_DrainRawPresenceDiagnostics(),
C_FreeRawPresenceDiagnostics,
);
if std::env::var("WPTUI_PRESENCE_DEBUG").as_deref() == Ok("1") {
let ingress = PRESENCE_CALLBACK_INGRESS.swap(0, Ordering::Relaxed);
report.get_or_insert_default().push_str(&format!(
"Rust callback ingress Presence events: {ingress}\n"
));
}
report
}
}

#[cfg(test)]
mod raw_presence_diagnostic_tests {
use std::{
ffi::{CString, c_char},
sync::atomic::{AtomicBool, Ordering},
};

use super::take_owned_c_string;

static FREED: AtomicBool = AtomicBool::new(false);

unsafe extern "C" fn free_test_string(value: *mut c_char) {
drop(unsafe { CString::from_raw(value) });
FREED.store(true, Ordering::SeqCst);
}

#[test]
fn owned_diagnostic_string_is_copied_and_freed_once() {
FREED.store(false, Ordering::SeqCst);
let value = CString::new("raw presence events received: 0\n")
.unwrap()
.into_raw();

let report = unsafe { take_owned_c_string(value, free_test_string) };

assert_eq!(report.as_deref(), Some("raw presence events received: 0\n"));
assert!(FREED.load(Ordering::SeqCst));
assert!(unsafe { take_owned_c_string(std::ptr::null_mut(), free_test_string) }.is_none());
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum SubscribePresenceResult {
Accepted = 0,
NoPrivacyToken = 1,
Rejected = 2,
}

pub fn subscribe_presence(jid: &JID) -> SubscribePresenceResult {
let jid = CString::new(jid.0.as_ref()).unwrap();
match unsafe { C_SubscribePresence(jid.as_ptr()) } {
0 => SubscribePresenceResult::Accepted,
1 => SubscribePresenceResult::NoPrivacyToken,
_ => SubscribePresenceResult::Rejected,
}
}

/// Keeps CStrings and C structs alive for the duration of an FFI call.
/// The inner C structs are boxed so their heap addresses remain stable.
#[allow(dead_code)]
Expand Down Expand Up @@ -1253,7 +1173,7 @@ mod group_info_tests {
/// JID is unusable.
pub fn resolve_dm_chat(jid: &JID) -> Option<JID> {
unsafe {
take_owned_c_string(C_ResolveDmChatId(CJID::from(jid)), C_FreeResolveDmChatId)
presence::take_owned_c_string(C_ResolveDmChatId(CJID::from(jid)), C_FreeResolveDmChatId)
.map(JID::from)
}
}
85 changes: 85 additions & 0 deletions whatsrust/src/presence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use std::sync::atomic::{AtomicUsize, Ordering};

use super::*;

static PRESENCE_CALLBACK_INGRESS: AtomicUsize = AtomicUsize::new(0);

pub(crate) fn record_callback_ingress() {
PRESENCE_CALLBACK_INGRESS.fetch_add(1, Ordering::Relaxed);
}

pub(crate) unsafe fn take_owned_c_string(
value: *mut c_char,
free: unsafe extern "C" fn(*mut c_char),
) -> Option<String> {
if value.is_null() {
return None;
}
let result = unsafe { CStr::from_ptr(value) }
.to_string_lossy()
.into_owned();
unsafe { free(value) };
Some(result)
}

pub fn drain_raw_presence_diagnostics() -> Option<String> {
unsafe {
let mut report = take_owned_c_string(
C_DrainRawPresenceDiagnostics(),
C_FreeRawPresenceDiagnostics,
);
if std::env::var("WPTUI_PRESENCE_DEBUG").as_deref() == Ok("1") {
let ingress = PRESENCE_CALLBACK_INGRESS.swap(0, Ordering::Relaxed);
report.get_or_insert_default().push_str(&format!(
"Rust callback ingress Presence events: {ingress}\n"
));
}
report
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum SubscribePresenceResult {
Accepted = 0,
NoPrivacyToken = 1,
Rejected = 2,
}

pub fn subscribe_presence(jid: &JID) -> SubscribePresenceResult {
let jid = CString::new(jid.0.as_ref()).unwrap();
match unsafe { C_SubscribePresence(jid.as_ptr()) } {
0 => SubscribePresenceResult::Accepted,
1 => SubscribePresenceResult::NoPrivacyToken,
_ => SubscribePresenceResult::Rejected,
}
}

#[cfg(test)]
mod tests {
use std::{
ffi::{CString, c_char},
sync::atomic::{AtomicBool, Ordering},
};

use super::take_owned_c_string;

static FREED: AtomicBool = AtomicBool::new(false);

unsafe extern "C" fn free_test_string(value: *mut c_char) {
drop(unsafe { CString::from_raw(value) });
FREED.store(true, Ordering::SeqCst);
}

#[test]
fn owned_diagnostic_string_is_copied_and_freed_once() {
FREED.store(false, Ordering::SeqCst);
let value = CString::new("raw presence events received: 0\n")
.unwrap()
.into_raw();
let report = unsafe { take_owned_c_string(value, free_test_string) };
assert_eq!(report.as_deref(), Some("raw presence events received: 0\n"));
assert!(FREED.load(Ordering::SeqCst));
assert!(unsafe { take_owned_c_string(std::ptr::null_mut(), free_test_string) }.is_none());
}
}
2 changes: 1 addition & 1 deletion whatsrust/src/registrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ where
{
setup_presence_handler(move |from, unavailable, last_seen| {
if std::env::var("WPTUI_PRESENCE_DEBUG").as_deref() == Ok("1") {
PRESENCE_CALLBACK_INGRESS.fetch_add(1, Ordering::Relaxed);
super::presence::record_callback_ingress();
}
callback(PresenceUpdate {
from,
Expand Down
Loading