use std::str::FromStr;
use iroh::endpoint::presets;
use iroh::{Endpoint, EndpointAddr, RelayMode, RelayUrl, SecretKey};
use tokio::time::timeout;
use crate::auth::{
AuthConfig, AuthPolicy, AuthRole, DEFAULT_PRE_AUTH_TIMEOUT, NodeAuth, run_auth_phase,
};
use crate::session::{DEFAULT_IDLE_TIMEOUT, SessionError, SessionReport, SyncStore, run_session};
use crate::wire::SYNC_ALPN;
#[derive(Debug)]
pub enum EndpointError {
RelayUrl(String),
Bind(String),
Connect(String),
}
impl std::fmt::Display for EndpointError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EndpointError::RelayUrl(m) => write!(f, "invalid relay url: {m}"),
EndpointError::Bind(m) => write!(f, "binding the sync endpoint failed: {m}"),
EndpointError::Connect(m) => write!(f, "sync connection setup failed: {m}"),
}
}
}
impl std::error::Error for EndpointError {}
pub async fn build_endpoint(
secret_key: [u8; 32],
relay_url: &str,
) -> Result<Endpoint, EndpointError> {
let relay_url =
RelayUrl::from_str(relay_url.trim()).map_err(|e| EndpointError::RelayUrl(e.to_string()))?;
Endpoint::builder(presets::Minimal)
.alpns(vec![SYNC_ALPN.to_vec()])
.relay_mode(RelayMode::custom([relay_url]))
.secret_key(SecretKey::from_bytes(&secret_key))
.bind()
.await
.map_err(|e| EndpointError::Bind(e.to_string()))
}
pub fn endpoint_addr(endpoint: &Endpoint) -> EndpointAddr {
endpoint.addr()
}
pub async fn connect_and_sync<S: SyncStore + NodeAuth>(
endpoint: &Endpoint,
peer: impl Into<EndpointAddr>,
store: &mut S,
policy: AuthPolicy,
now_ms: i64,
) -> Result<SessionReport, SyncFailure> {
let local_node = *endpoint.id().as_bytes();
let conn = endpoint
.connect(peer, SYNC_ALPN)
.await
.map_err(|e| SyncFailure::Endpoint(EndpointError::Connect(e.to_string())))?;
let remote_node = *conn.remote_id().as_bytes();
let (mut send, mut recv) = conn
.open_bi()
.await
.map_err(|e| SyncFailure::Endpoint(EndpointError::Connect(e.to_string())))?;
run_auth_phase(&mut send, &mut recv, &*store, AuthConfig {
role: AuthRole::Dialer,
account_id: store.account_id(),
local_node,
remote_node,
policy,
now_ms,
pre_auth_timeout: DEFAULT_PRE_AUTH_TIMEOUT,
})
.await
.map_err(SyncFailure::Auth)?;
let report = run_session(store, send, recv).await.map_err(SyncFailure::Session)?;
conn.close(0u32.into(), b"done");
Ok(report)
}
pub async fn accept_and_sync<S: SyncStore + NodeAuth>(
endpoint: &Endpoint,
store: &mut S,
policy: AuthPolicy,
now_ms: i64,
) -> Result<SessionReport, SyncFailure> {
let local_node = *endpoint.id().as_bytes();
let incoming = endpoint
.accept()
.await
.ok_or_else(|| SyncFailure::Endpoint(EndpointError::Connect("endpoint closed".into())))?;
let conn = timeout(DEFAULT_IDLE_TIMEOUT, incoming)
.await
.map_err(|_| SyncFailure::Endpoint(EndpointError::Connect("handshake timed out".into())))?
.map_err(|e| SyncFailure::Endpoint(EndpointError::Connect(e.to_string())))?;
let remote_node = *conn.remote_id().as_bytes();
let (mut send, mut recv) = timeout(DEFAULT_IDLE_TIMEOUT, conn.accept_bi())
.await
.map_err(|_| SyncFailure::Endpoint(EndpointError::Connect("peer opened no stream".into())))?
.map_err(|e| SyncFailure::Endpoint(EndpointError::Connect(e.to_string())))?;
run_auth_phase(&mut send, &mut recv, &*store, AuthConfig {
role: AuthRole::Acceptor,
account_id: store.account_id(),
local_node,
remote_node,
policy,
now_ms,
pre_auth_timeout: DEFAULT_PRE_AUTH_TIMEOUT,
})
.await
.map_err(SyncFailure::Auth)?;
let report = run_session(store, send, recv).await.map_err(SyncFailure::Session)?;
conn.close(0u32.into(), b"done");
Ok(report)
}
#[derive(Debug)]
pub enum SyncFailure {
Endpoint(EndpointError),
Auth(crate::auth::AuthError),
Session(SessionError),
}
impl std::fmt::Display for SyncFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SyncFailure::Endpoint(e) => write!(f, "{e}"),
SyncFailure::Auth(e) => write!(f, "{e}"),
SyncFailure::Session(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for SyncFailure {}