#![allow(clippy::missing_errors_doc)]
mod content_context;
use core::ffi::{c_char, c_int, c_void};
use core::ptr;
use std::ffi::{CStr, CString};
use std::sync::{Arc, Mutex};
use doom_fish_utils::panic_safe::catch_user_panic;
pub use content_context::{ContentContext, ReceivedContent};
use crate::error::{from_status, NetworkError};
use crate::ffi;
use crate::parameters::{ConnectionParameters, KeepAlives};
use crate::path::Path;
use crate::protocol::{ProtocolDefinition, ProtocolMetadata};
type BooleanCallback = Mutex<Box<dyn FnMut(bool) + Send + 'static>>;
type PathChangedCallback = Mutex<Box<dyn FnMut(Option<Path>) + Send + 'static>>;
pub struct TcpClient {
handle: *mut c_void,
_keepalives: KeepAlives,
viability_raw: *const BooleanCallback,
better_path_raw: *const BooleanCallback,
path_raw: *const PathChangedCallback,
}
unsafe impl Send for TcpClient {}
unsafe impl Sync for TcpClient {}
impl std::fmt::Debug for TcpClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TcpClient")
.field("handle", &self.handle)
.field("viability_raw", &self.viability_raw)
.field("better_path_raw", &self.better_path_raw)
.field("path_raw", &self.path_raw)
.finish_non_exhaustive()
}
}
fn reclaim_arc_raw<T>(raw: &mut *const T) {
if !raw.is_null() {
unsafe {
drop(Arc::from_raw(*raw));
}
*raw = ptr::null();
}
}
fn copied_string(ptr: *mut c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
let value = unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned();
unsafe {
ffi::nw_shim_free_buffer(ptr.cast());
}
Some(value)
}
impl TcpClient {
pub fn connect(host: &str, port: u16) -> Result<Self, NetworkError> {
Self::connect_inner(host, port, false)
}
pub fn connect_tls(host: &str, port: u16) -> Result<Self, NetworkError> {
Self::connect_inner(host, port, true)
}
pub fn connect_with_parameters(
host: &str,
port: u16,
parameters: &ConnectionParameters,
) -> Result<Self, NetworkError> {
let host = CString::new(host)
.map_err(|e| NetworkError::InvalidArgument(format!("host NUL byte: {e}")))?;
let mut status: c_int = 0;
let handle = unsafe {
ffi::nw_shim_connection_create_with_parameters(
host.as_ptr(),
port,
parameters.as_ptr(),
&mut status,
)
};
if status != ffi::NW_OK || handle.is_null() {
return Err(from_status(status));
}
Ok(Self {
handle,
_keepalives: parameters.keepalives(),
viability_raw: ptr::null(),
better_path_raw: ptr::null(),
path_raw: ptr::null(),
})
}
fn connect_inner(host: &str, port: u16, use_tls: bool) -> Result<Self, NetworkError> {
let host_c = CString::new(host)
.map_err(|e| NetworkError::InvalidArgument(format!("host NUL byte: {e}")))?;
let mut status: c_int = 0;
let handle = unsafe {
ffi::nw_shim_tcp_connect(host_c.as_ptr(), port, c_int::from(use_tls), &mut status)
};
if status != ffi::NW_OK || handle.is_null() {
return Err(from_status(status));
}
Ok(Self {
handle,
_keepalives: KeepAlives::empty(),
viability_raw: ptr::null(),
better_path_raw: ptr::null(),
path_raw: ptr::null(),
})
}
#[must_use]
pub(crate) const unsafe fn from_raw_with_keepalives(
handle: *mut c_void,
keepalives: KeepAlives,
) -> Self {
Self {
handle,
_keepalives: keepalives,
viability_raw: ptr::null(),
better_path_raw: ptr::null(),
path_raw: ptr::null(),
}
}
#[must_use]
pub fn endpoint(&self) -> Option<crate::endpoint::Endpoint> {
let handle = unsafe { ffi::nw_shim_connection_copy_endpoint(self.handle) };
if handle.is_null() {
None
} else {
Some(unsafe { crate::endpoint::Endpoint::from_raw(handle) })
}
}
#[must_use]
pub fn parameters(&self) -> Option<ConnectionParameters> {
let handle = unsafe { ffi::nw_shim_connection_copy_parameters(self.handle) };
if handle.is_null() {
None
} else {
Some(unsafe { ConnectionParameters::from_raw(handle) })
}
}
#[must_use]
pub fn current_path(&self) -> Option<crate::path::Path> {
let handle = unsafe { ffi::nw_shim_connection_copy_current_path(self.handle) };
if handle.is_null() {
None
} else {
Some(unsafe { crate::path::Path::from_raw(handle) })
}
}
#[must_use]
pub(crate) const fn as_ptr(&self) -> *mut c_void {
self.handle
}
fn clear_viability_changed_handler(&mut self) {
if !self.viability_raw.is_null() {
if !self.handle.is_null() {
unsafe {
ffi::nw_shim_connection_set_viability_changed_handler(
self.handle,
None,
ptr::null_mut(),
);
ffi::nw_shim_connection_drain_queue(self.handle);
}
}
reclaim_arc_raw(&mut self.viability_raw);
}
}
fn clear_better_path_available_handler(&mut self) {
if !self.better_path_raw.is_null() {
if !self.handle.is_null() {
unsafe {
ffi::nw_shim_connection_set_better_path_available_handler(
self.handle,
None,
ptr::null_mut(),
);
ffi::nw_shim_connection_drain_queue(self.handle);
}
}
reclaim_arc_raw(&mut self.better_path_raw);
}
}
fn clear_path_changed_handler(&mut self) {
if !self.path_raw.is_null() {
if !self.handle.is_null() {
unsafe {
ffi::nw_shim_connection_set_path_changed_handler(
self.handle,
None,
ptr::null_mut(),
);
ffi::nw_shim_connection_drain_queue(self.handle);
}
}
reclaim_arc_raw(&mut self.path_raw);
}
}
pub fn restart(&self) {
unsafe {
ffi::nw_shim_connection_restart(self.handle);
}
}
pub fn force_cancel(&self) {
unsafe {
ffi::nw_shim_connection_force_cancel(self.handle);
}
}
pub fn cancel_current_endpoint(&self) {
unsafe {
ffi::nw_shim_connection_cancel_current_endpoint(self.handle);
}
}
pub fn batch<F>(&self, mut callback: F)
where
F: FnMut(),
{
unsafe extern "C" fn invoke(user_info: *mut c_void) {
if user_info.is_null() {
return;
}
let callback = unsafe { &mut *user_info.cast::<&mut dyn FnMut()>() };
catch_user_panic("tcp_client_batch_invoke", callback);
}
let mut callback_ref: &mut dyn FnMut() = &mut callback;
unsafe {
ffi::nw_shim_connection_batch(
self.handle,
Some(invoke),
ptr::addr_of_mut!(callback_ref).cast(),
);
}
}
#[must_use]
pub fn description(&self) -> Option<String> {
let description = unsafe { ffi::nw_shim_connection_copy_description(self.handle) };
copied_string(description)
}
#[must_use]
pub fn maximum_datagram_size(&self) -> u32 {
unsafe { ffi::nw_shim_connection_get_maximum_datagram_size(self.handle) }
}
#[must_use]
pub fn protocol_metadata(&self, definition: &ProtocolDefinition) -> Option<ProtocolMetadata> {
let handle = unsafe {
ffi::nw_shim_connection_copy_protocol_metadata(self.handle, definition.as_ptr())
};
if handle.is_null() {
None
} else {
Some(unsafe { ProtocolMetadata::from_raw(handle) })
}
}
pub fn set_viability_changed_handler<F>(&mut self, callback: F)
where
F: FnMut(bool) + Send + 'static,
{
self.clear_viability_changed_handler();
let callback: Box<dyn FnMut(bool) + Send + 'static> = Box::new(callback);
let viability_raw = Arc::into_raw(Arc::new(Mutex::new(callback)));
if self.handle.is_null() {
self.viability_raw = viability_raw;
return;
}
unsafe {
ffi::nw_shim_connection_set_viability_changed_handler(
self.handle,
Some(boolean_trampoline),
viability_raw.cast::<c_void>().cast_mut(),
);
}
self.viability_raw = viability_raw;
}
pub fn set_better_path_available_handler<F>(&mut self, callback: F)
where
F: FnMut(bool) + Send + 'static,
{
self.clear_better_path_available_handler();
let callback: Box<dyn FnMut(bool) + Send + 'static> = Box::new(callback);
let better_path_raw = Arc::into_raw(Arc::new(Mutex::new(callback)));
if self.handle.is_null() {
self.better_path_raw = better_path_raw;
return;
}
unsafe {
ffi::nw_shim_connection_set_better_path_available_handler(
self.handle,
Some(boolean_trampoline),
better_path_raw.cast::<c_void>().cast_mut(),
);
}
self.better_path_raw = better_path_raw;
}
pub fn set_path_changed_handler<F>(&mut self, callback: F)
where
F: FnMut(Option<Path>) + Send + 'static,
{
self.clear_path_changed_handler();
let callback: Box<dyn FnMut(Option<Path>) + Send + 'static> = Box::new(callback);
let path_raw = Arc::into_raw(Arc::new(Mutex::new(callback)));
if self.handle.is_null() {
self.path_raw = path_raw;
return;
}
unsafe {
ffi::nw_shim_connection_set_path_changed_handler(
self.handle,
Some(path_trampoline),
path_raw.cast::<c_void>().cast_mut(),
);
}
self.path_raw = path_raw;
}
pub fn send(&self, data: &[u8]) -> Result<(), NetworkError> {
let status = unsafe { ffi::nw_shim_tcp_send(self.handle, data.as_ptr(), data.len()) };
if status != ffi::NW_OK {
return Err(from_status(status));
}
Ok(())
}
pub fn send_with_context(
&self,
data: &[u8],
context: &ContentContext,
) -> Result<(), NetworkError> {
let status = unsafe {
ffi::nw_shim_connection_send_with_context(
self.handle,
data.as_ptr(),
data.len(),
context.as_ptr(),
)
};
if status != ffi::NW_OK {
return Err(from_status(status));
}
Ok(())
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn receive(&self, max_len: usize) -> Result<Vec<u8>, NetworkError> {
let mut buf = vec![0u8; max_len];
let n = unsafe { ffi::nw_shim_tcp_receive(self.handle, buf.as_mut_ptr(), max_len) };
if n < 0 {
return Err(from_status(n as i32));
}
buf.truncate(n as usize);
Ok(buf)
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn receive_with_context(&self, max_len: usize) -> Result<ReceivedContent, NetworkError> {
let mut buf = vec![0_u8; max_len];
let mut context = ptr::null_mut();
let mut is_complete = 0;
let n = unsafe {
ffi::nw_shim_connection_receive_with_context(
self.handle,
buf.as_mut_ptr(),
max_len,
&mut context,
&mut is_complete,
)
};
if n < 0 {
return Err(from_status(n as i32));
}
buf.truncate(n as usize);
let context = if context.is_null() {
None
} else {
Some(unsafe { ContentContext::from_raw(context) })
};
Ok(ReceivedContent {
data: buf,
context,
is_complete: is_complete != 0,
})
}
}
unsafe extern "C" fn boolean_trampoline(value: c_int, user_info: *mut c_void) {
if user_info.is_null() {
return;
}
let callback = unsafe { &*user_info.cast::<BooleanCallback>() };
let Ok(mut guard) = callback.lock() else {
return;
};
catch_user_panic("tcp_client_boolean_trampoline", || guard(value != 0));
}
unsafe extern "C" fn path_trampoline(path: *mut c_void, user_info: *mut c_void) {
if user_info.is_null() {
return;
}
let callback = unsafe { &*user_info.cast::<PathChangedCallback>() };
let Ok(mut guard) = callback.lock() else {
return;
};
let path = if path.is_null() {
None
} else {
Some(unsafe { Path::from_raw(path) })
};
catch_user_panic("tcp_client_path_trampoline", || guard(path));
}
impl Drop for TcpClient {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe {
ffi::nw_shim_tcp_close(self.handle);
}
self.handle = ptr::null_mut();
}
reclaim_arc_raw(&mut self.viability_raw);
reclaim_arc_raw(&mut self.better_path_raw);
reclaim_arc_raw(&mut self.path_raw);
}
}