use crate::error::I2pError;
use crate::router::I2pRouter;
use crate::stream::I2pStream;
use std::collections::HashMap;
use std::ffi::c_void;
use std::os::raw::c_int;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, LazyLock, Mutex};
const ACCEPT_QUEUE_CAPACITY: usize = 128;
const IDENT_HASH_LEN: usize = 32;
struct RawStream(*mut i2pd_sys::I2pdStream);
unsafe impl Send for RawStream {}
impl RawStream {
fn into_raw(self) -> *mut i2pd_sys::I2pdStream {
let ptr = self.0;
std::mem::forget(self);
ptr
}
}
impl Drop for RawStream {
fn drop(&mut self) {
unsafe {
i2pd_sys::i2pd_destroy_stream(self.0);
}
}
}
static ACCEPT_REGISTRY: LazyLock<Mutex<HashMap<usize, tokio::sync::mpsc::Sender<RawStream>>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static NEXT_ACCEPT_TOKEN: AtomicUsize = AtomicUsize::new(1);
fn accept_registry()
-> std::sync::MutexGuard<'static, HashMap<usize, tokio::sync::mpsc::Sender<RawStream>>> {
ACCEPT_REGISTRY.lock().unwrap_or_else(|e| e.into_inner())
}
extern "C" fn on_accept(ctx: *mut c_void, stream: *mut i2pd_sys::I2pdStream) {
if stream.is_null() {
return;
}
let stream = RawStream(stream);
let token = ctx.addr();
let rejected = {
let registry = accept_registry();
match registry.get(&token) {
Some(tx) => tx.try_send(stream).err().map(|e| e.into_inner()),
None => Some(stream),
}
};
drop(rejected);
}
pub(crate) struct DestinationHandle {
pub(crate) ptr: *mut i2pd_sys::I2pdDestination,
accept_token: usize,
}
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);
}
let mut registry = accept_registry();
drop(registry.remove(&self.accept_token));
}
}
#[derive(Debug)]
pub struct Destination {
handle: Arc<DestinationHandle>,
b32_address: Box<str>,
ident_hash: [u8; IDENT_HASH_LEN],
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 std::fmt::Debug for RawStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RawStream").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; IDENT_HASH_LEN];
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_token = NEXT_ACCEPT_TOKEN.fetch_add(1, Ordering::Relaxed);
drop(accept_registry().insert(accept_token, tx));
unsafe {
i2pd_sys::i2pd_accept_stream(
ptr,
Some(on_accept),
std::ptr::without_provenance_mut::<c_void>(accept_token),
);
}
Ok(Self {
handle: Arc::new(DestinationHandle { ptr, accept_token }),
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 raw = self
.accept_rx
.recv()
.await
.ok_or(I2pError::DestinationClosed)?;
Ok(unsafe { I2pStream::from_raw(self.router.clone(), raw.into_raw()) })
}
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 raw = 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 raw.0.is_null() {
return Err(I2pError::ConnectFailed);
}
Ok(unsafe { I2pStream::from_raw(router, raw.into_raw()) })
}
}