diff --git a/Cargo.toml b/Cargo.toml index e1a1d7e..ea464bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,12 @@ rust-version = "1.89" anyhow = "1.0.95" derive_more = { version = "2.0.1", features = ["display", "from"] } ed25519-dalek = { version = "=3.0.0-pre.6" } -irpc = { version = "0.14.0", default-features = false, features = ["derive", "stream", "spans"] } -irpc-iroh = "0.14" -iroh = { version = "0.98", default-features = false } +# irpc = { version = "0.14.0", default-features = false, features = ["derive", "stream", "spans"] } +irpc = { path = "../irpc" } +# irpc-iroh = "0.14" +irpc-iroh = { path = "../irpc/irpc-iroh" } +# iroh = { version = "0.98", default-features = false } +iroh = { git = "https://github.com/n0-computer/iroh", branch = "Frando/relay-client-auth", default-features = false } iroh-metrics = { version = "0.38", default-features = false } iroh-tickets = "0.5" n0-error = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 517b1ac..710feae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ mod client_host; pub mod api_secret; pub mod caps; pub mod net_diagnostics; +pub mod preset; pub mod protocol; mod built_info { @@ -60,5 +61,6 @@ pub use self::{ api_secret::ApiSecret, client::{API_SECRET_ENV_VAR_NAME, Client, ClientBuilder}, net_diagnostics::{DiagnosticsReport, checks::run_diagnostics}, + preset::{IrohServicesPreset, PresetBuilder, preset}, protocol::ALPN, }; diff --git a/src/preset.rs b/src/preset.rs new file mode 100644 index 0000000..6743862 --- /dev/null +++ b/src/preset.rs @@ -0,0 +1,137 @@ +//! An [`iroh::endpoint`] preset tailored for use with iroh-services. +//! +//! [`IrohServicesPreset`] starts from the n0 stock preset (production crypto +//! provider + n0 DNS-based address lookup) and overlays the bits that +//! iroh-services callers usually want to configure together: the relay map +//! the endpoint should use, an optional explicit [`SecretKey`], and an +//! optional [`ApiSecret`] that downstream code can retrieve to wire up a +//! [`crate::Client`]. +//! +//! # Example +//! ```no_run +//! use iroh::Endpoint; +//! use iroh_services::preset::preset; +//! +//! # async fn run() -> anyhow::Result<()> { +//! let endpoint = Endpoint::builder(preset().build()).bind().await?; +//! # Ok(()) } +//! ``` +use anyhow::Context; +use iroh::{RelayMap, RelayMode, RelayUrl, SecretKey, endpoint::presets::Preset}; + +use crate::ApiSecret; + +/// An iroh endpoint preset configured for iroh-services. Build one with +/// [`preset`] or [`IrohServicesPreset::builder`], then pass it to +/// [`iroh::Endpoint::builder`]. +#[derive(Debug, Clone)] +pub struct IrohServicesPreset { + secret_key: Option, + relays: RelayMap, + api_secret: Option, +} + +impl IrohServicesPreset { + /// Start a new builder seeded with iroh-services defaults. Equivalent to + /// the free-standing [`preset`] function. + pub fn builder() -> PresetBuilder { + preset() + } + + /// Returns the [`ApiSecret`] stashed on this preset, if one was set. + /// Useful for handing the same secret to a [`crate::Client`] without + /// plumbing it through twice. + pub fn api_secret(&self) -> Option<&ApiSecret> { + self.api_secret.as_ref() + } +} + +impl Preset for IrohServicesPreset { + fn apply(self, builder: iroh::endpoint::Builder) -> iroh::endpoint::Builder { + // Inherit n0 defaults (crypto provider + DNS address lookup), then + // overlay our relay map and (optionally) an explicit secret key. + let mut builder = iroh::endpoint::presets::N0.apply(builder); + builder = builder.relay_mode(RelayMode::Custom(self.relays)); + if let Some(sk) = self.secret_key { + builder = builder.secret_key(sk); + } + builder + } +} + +/// Fluent builder for [`IrohServicesPreset`]. Construct one through +/// [`preset`] or [`IrohServicesPreset::builder`]. +#[derive(Debug, Clone)] +pub struct PresetBuilder { + secret_key: Option, + relays: RelayMap, + api_secret: Option, +} + +/// Start a new [`IrohServicesPreset`] builder seeded with iroh-services +/// defaults: the n0 production relay map and no explicit secret key (the +/// endpoint will generate one at bind time). +pub fn preset() -> PresetBuilder { + PresetBuilder { + secret_key: None, + relays: RelayMode::Default.relay_map(), + api_secret: None, + } +} + +impl PresetBuilder { + /// Set the endpoint's long-lived [`SecretKey`]. If left unset the + /// endpoint will generate a fresh random key at bind time. + pub fn secret_key(mut self, secret_key: SecretKey) -> Self { + self.secret_key = Some(secret_key); + self + } + + /// Replace the default relay map with a custom set of relay URLs. + /// Each entry is parsed into a [`RelayUrl`]; any failure aborts the call. + pub fn relays(mut self, relays: I) -> anyhow::Result + where + I: IntoIterator, + S: AsRef, + { + let parsed = relays + .into_iter() + .map(|s| { + let s = s.as_ref(); + s.parse::() + .with_context(|| format!("invalid relay url {s:?}")) + }) + .collect::>>()?; + self.relays = RelayMap::from_iter(parsed); + Ok(self) + } + + /// Pick relays via a [`RelayMode`] (e.g. `RelayMode::Staging` or a + /// pre-built `RelayMode::Custom(RelayMap)`). + pub fn relay_mode(mut self, mode: RelayMode) -> Self { + self.relays = mode.relay_map(); + self + } + + /// Pass in a [`RelayMap`] directly, bypassing URL parsing. + pub fn relay_map(mut self, map: RelayMap) -> Self { + self.relays = map; + self + } + + /// Stash an [`ApiSecret`] on the preset so callers can retrieve it later + /// via [`IrohServicesPreset::api_secret`] when constructing a client. + pub fn api_secret(mut self, api_secret: ApiSecret) -> Self { + self.api_secret = Some(api_secret); + self + } + + /// Finalize the configuration into an [`IrohServicesPreset`]. + pub fn build(self) -> IrohServicesPreset { + IrohServicesPreset { + secret_key: self.secret_key, + relays: self.relays, + api_secret: self.api_secret, + } + } +}