use crate::error::I2pError;
use crate::router::I2pRouter;
use crate::stream::I2pStream;
use std::ffi::c_void;
use std::os::raw::c_int;
use std::sync::Arc;
const ACCEPT_QUEUE_CAPACITY: usize = 128;
struct RawStream(*mut i2pd_sys::I2pdStream);
unsafe impl Send for RawStream {}
struct AcceptCtx {
tx: tokio::sync::mpsc::Sender<RawStream>,
}
extern "C" fn on_accept(ctx: *mut c_void, stream: *mut i2pd_sys::I2pdStream) {
let ctx = unsafe { &*ctx.cast::<AcceptCtx>() };
if ctx.tx.try_send(RawStream(stream)).is_err() {
unsafe {
i2pd_sys::i2pd_stream_close(stream);
i2pd_sys::i2pd_destroy_stream(stream);
}
}
}
pub(crate) struct DestinationHandle {
pub(crate) ptr: *mut i2pd_sys::I2pdDestination,
accept_ctx: *mut AcceptCtx,
}
unsafe impl Send for DestinationHandle {}
unsafe impl Sync for DestinationHandle {}
impl Drop for DestinationHandle {
fn drop(&mut self) {
unsafe {
i2pd_sys::i2pd_destroy_destination(self.ptr);
drop(Box::from_raw(self.accept_ctx));
}
}
}
#[derive(Debug)]
pub struct Destination {
handle: Arc<DestinationHandle>,
b32_address: Box<str>,
ident_hash: [u8; 32],
accept_rx: tokio::sync::mpsc::Receiver<RawStream>,
router: I2pRouter,
}
impl std::fmt::Debug for DestinationHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DestinationHandle").finish_non_exhaustive()
}
}
impl Destination {
pub(crate) fn from_raw(
router: I2pRouter,
ptr: *mut i2pd_sys::I2pdDestination,
) -> Result<Self, I2pError> {
if ptr.is_null() {
return Err(I2pError::DestinationCreationFailed);
}
let b32_address = unsafe {
let raw = i2pd_sys::i2pd_destination_b32_address(ptr);
if raw.is_null() {
i2pd_sys::i2pd_destroy_destination(ptr);
return Err(I2pError::DestinationCreationFailed);
}
let s = std::ffi::CStr::from_ptr(raw.cast())
.to_string_lossy()
.into_owned();
i2pd_sys::i2pd_free_string(raw);
s
};
let mut ident_hash = [0u8; 32];
let ok = unsafe { i2pd_sys::i2pd_destination_ident_hash(ptr, ident_hash.as_mut_ptr()) };
if ok == 0 {
unsafe { i2pd_sys::i2pd_destroy_destination(ptr) };
return Err(I2pError::DestinationCreationFailed);
}
let (tx, accept_rx) = tokio::sync::mpsc::channel(ACCEPT_QUEUE_CAPACITY);
let accept_ctx = Box::into_raw(Box::new(AcceptCtx { tx }));
unsafe {
i2pd_sys::i2pd_accept_stream(ptr, Some(on_accept), accept_ctx.cast());
}
Ok(Self {
handle: Arc::new(DestinationHandle { ptr, accept_ctx }),
b32_address: b32_address.into_boxed_str(),
ident_hash,
accept_rx,
router,
})
}
#[must_use]
pub fn b32_address(&self) -> &str {
&self.b32_address
}
#[must_use]
pub const fn ident_hash(&self) -> [u8; 32] {
self.ident_hash
}
pub async fn accept(&mut self) -> Result<I2pStream, I2pError> {
let RawStream(ptr) = self
.accept_rx
.recv()
.await
.ok_or(I2pError::DestinationClosed)?;
Ok(unsafe { I2pStream::from_raw(self.router.clone(), ptr) })
}
pub async fn connect(
&self,
remote_ident_hash: [u8; 32],
timeout: std::time::Duration,
) -> Result<I2pStream, I2pError> {
let handle = self.handle.clone();
let router = self.router.clone();
let timeout_secs = c_int::try_from(timeout.as_secs()).unwrap_or(c_int::MAX);
let RawStream(ptr) = tokio::task::spawn_blocking(move || {
RawStream(unsafe {
i2pd_sys::i2pd_create_stream(handle.ptr, remote_ident_hash.as_ptr(), timeout_secs)
})
})
.await
.map_err(|_| I2pError::WorkerPanicked)?;
if ptr.is_null() {
return Err(I2pError::ConnectFailed);
}
Ok(unsafe { I2pStream::from_raw(router, ptr) })
}
}