use crate::destination::Destination;
use crate::error::I2pError;
use std::ffi::CString;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
static ROUTER_STARTED: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum SigType {
DsaSha1,
EcdsaP256,
EcdsaP384,
EcdsaP521,
#[default]
Eddsa25519,
}
impl SigType {
pub(crate) const fn as_raw(self) -> std::os::raw::c_int {
match self {
Self::DsaSha1 => 0,
Self::EcdsaP256 => 1,
Self::EcdsaP384 => 2,
Self::EcdsaP521 => 3,
Self::Eddsa25519 => 7,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum CryptoType {
ElGamal,
EciesP256,
#[default]
EciesX25519,
EciesMlkem512X25519,
EciesMlkem768X25519,
EciesMlkem1024X25519,
}
impl CryptoType {
pub(crate) const fn as_raw(self) -> std::os::raw::c_int {
match self {
Self::ElGamal => 0,
Self::EciesP256 => 1,
Self::EciesX25519 => 4,
Self::EciesMlkem512X25519 => 5,
Self::EciesMlkem768X25519 => 6,
Self::EciesMlkem1024X25519 => 7,
}
}
}
#[derive(Debug)]
struct RouterInner {
keys_file_lock: tokio::sync::Mutex<()>,
}
impl Drop for RouterInner {
fn drop(&mut self) {
unsafe {
i2pd_sys::i2pd_stop();
i2pd_sys::i2pd_terminate();
}
ROUTER_STARTED.store(false, Ordering::Release);
}
}
#[derive(Clone, Debug)]
pub struct I2pRouter {
_inner: Arc<RouterInner>,
}
impl I2pRouter {
pub async fn start(app_name: impl Into<String>) -> Result<Self, I2pError> {
if ROUTER_STARTED.swap(true, Ordering::AcqRel) {
return Err(I2pError::AlreadyRunning);
}
let app_name = app_name.into();
let result = tokio::task::spawn_blocking(move || {
let c_name = CString::new(app_name).map_err(|_| I2pError::InvalidAppName)?;
unsafe {
i2pd_sys::i2pd_init(c_name.as_ptr());
i2pd_sys::i2pd_start();
}
Ok(())
})
.await;
match result {
Ok(Ok(())) => Ok(Self {
_inner: Arc::new(RouterInner {
keys_file_lock: tokio::sync::Mutex::new(()),
}),
}),
Ok(Err(e)) => {
ROUTER_STARTED.store(false, Ordering::Release);
Err(e)
}
Err(_) => {
ROUTER_STARTED.store(false, Ordering::Release);
Err(I2pError::WorkerPanicked)
}
}
}
pub async fn create_transient_destination(&self) -> Result<Destination, I2pError> {
let router = self.clone();
tokio::task::spawn_blocking(move || {
let ptr = unsafe { i2pd_sys::i2pd_create_transient_destination() };
Destination::from_raw(router, ptr)
})
.await
.map_err(|_| I2pError::WorkerPanicked)?
}
pub async fn generate_keys(&self, sig: SigType) -> Result<Vec<u8>, I2pError> {
tokio::task::spawn_blocking(move || {
let mut buf: *mut u8 = std::ptr::null_mut();
let mut len: usize = 0;
let ok = unsafe {
i2pd_sys::i2pd_generate_keys(
sig.as_raw(),
CryptoType::ElGamal.as_raw(),
&raw mut buf,
&raw mut len,
)
};
if ok == 0 || buf.is_null() {
return Err(I2pError::KeyGenerationFailed);
}
let bytes = unsafe { std::slice::from_raw_parts(buf, len) }.to_vec();
unsafe { i2pd_sys::i2pd_free_buffer(buf) };
Ok(bytes)
})
.await
.map_err(|_| I2pError::WorkerPanicked)?
}
pub async fn create_persistent_destination(
&self,
keys: Vec<u8>,
is_public: bool,
encryption_types: &[CryptoType],
) -> Result<Destination, I2pError> {
let router = self.clone();
let csv = if encryption_types.is_empty() {
None
} else {
Some(
encryption_types
.iter()
.map(|c| c.as_raw().to_string())
.collect::<Vec<_>>()
.join(","),
)
};
let csv = csv.map(|s| std::ffi::CString::new(s).unwrap_or_default());
tokio::task::spawn_blocking(move || {
let csv_ptr = csv.as_deref().map_or(std::ptr::null(), |c| c.as_ptr());
let ptr = unsafe {
i2pd_sys::i2pd_create_persistent_destination(
keys.as_ptr(),
keys.len(),
std::os::raw::c_int::from(is_public),
csv_ptr,
)
};
Destination::from_raw(router, ptr)
})
.await
.map_err(|_| I2pError::WorkerPanicked)?
}
pub async fn destination_from_keys_file(
&self,
path: impl Into<PathBuf>,
is_public: bool,
sig: SigType,
encryption_types: &[CryptoType],
) -> Result<Destination, I2pError> {
let path = path.into();
let keys = {
let _guard = self._inner.keys_file_lock.lock().await;
match tokio::fs::read(&path).await {
Ok(bytes) => bytes,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
let generated = self.generate_keys(sig).await?;
if let Some(parent) = path.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(I2pError::Io)?;
}
tokio::fs::write(&path, &generated)
.await
.map_err(I2pError::Io)?;
generated
}
Err(e) => return Err(I2pError::Io(e)),
}
};
self.create_persistent_destination(keys, is_public, encryption_types)
.await
}
}