diff --git a/Cargo.lock b/Cargo.lock index 075589ee87..4515713e5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1983,7 +1983,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] diff --git a/deny.toml b/deny.toml index 3eb86f2216..cc23a1d144 100644 --- a/deny.toml +++ b/deny.toml @@ -25,3 +25,6 @@ ignore = [ "RUSTSEC-2024-0436", # paste "RUSTSEC-2023-0089", # unmainatined: postcard -> heapless -> atomic-polyfill ] + +[sources] +allow-git = [] diff --git a/iroh-base/Cargo.toml b/iroh-base/Cargo.toml index e3e0141657..9dafab2a51 100644 --- a/iroh-base/Cargo.toml +++ b/iroh-base/Cargo.toml @@ -20,7 +20,6 @@ data-encoding = { version = "2.3.3", optional = true } ed25519-dalek = { version = "=3.0.0-pre.1", features = ["serde", "rand_core", "zeroize"], optional = true } derive_more = { version = "2.0.1", features = ["display"], optional = true } url = { version = "2.5.3", features = ["serde"], optional = true } -postcard = { version = "1", default-features = false, features = ["alloc", "use-std", "experimental-derive"], optional = true } rand_core = { version = "0.9.3", optional = true } serde = { version = "1", features = ["derive", "rc"] } snafu = { version = "0.8.5", features = ["rust_1_81"], optional = true } @@ -39,8 +38,7 @@ serde_test = "1" [features] -default = ["ticket", "relay"] -ticket = ["key", "dep:postcard", "dep:data-encoding"] +default = ["relay"] key = [ "dep:curve25519-dalek", "dep:ed25519-dalek", diff --git a/iroh-base/src/lib.rs b/iroh-base/src/lib.rs index 82320ca15f..4b51346962 100644 --- a/iroh-base/src/lib.rs +++ b/iroh-base/src/lib.rs @@ -3,9 +3,6 @@ #![deny(missing_docs, rustdoc::broken_intra_doc_links)] #![cfg_attr(not(test), deny(clippy::unwrap_used))] -#[cfg(feature = "ticket")] -pub mod ticket; - #[cfg(feature = "key")] mod endpoint_addr; #[cfg(feature = "key")] diff --git a/iroh-base/src/ticket.rs b/iroh-base/src/ticket.rs deleted file mode 100644 index dcf5dd1bc8..0000000000 --- a/iroh-base/src/ticket.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Tickets is a serializable object combining information required for an operation. -//! Typically tickets contain all information required for an operation, e.g. an iroh blob -//! ticket would contain the hash of the data as well as information about how to reach the -//! provider. - -use std::collections::BTreeSet; - -use nested_enum_utils::common_fields; -use serde::{Deserialize, Serialize}; -use snafu::{Backtrace, Snafu}; - -use crate::{TransportAddr, key::EndpointId}; - -mod endpoint; - -pub use self::endpoint::EndpointTicket; - -/// A ticket is a serializable object combining information required for an operation. -/// -/// Tickets support serialization to a string using base32 encoding. The kind of -/// ticket will be prepended to the string to make it somewhat self describing. -/// -/// Versioning is left to the implementer. Some kinds of tickets might need -/// versioning, others might not. -/// -/// The serialization format for converting the ticket from and to bytes is left -/// to the implementer. We recommend using [postcard] for serialization. -/// -/// [postcard]: https://docs.rs/postcard/latest/postcard/ -pub trait Ticket: Sized { - /// String prefix describing the kind of iroh ticket. - /// - /// This should be lower case ascii characters. - const KIND: &'static str; - - /// Serialize to bytes used in the base32 string representation. - fn to_bytes(&self) -> Vec; - - /// Deserialize from the base32 string representation bytes. - fn from_bytes(bytes: &[u8]) -> Result; - - /// Serialize to string. - fn serialize(&self) -> String { - let mut out = Self::KIND.to_string(); - data_encoding::BASE32_NOPAD.encode_append(&self.to_bytes(), &mut out); - out.to_ascii_lowercase() - } - - /// Deserialize from a string. - fn deserialize(str: &str) -> Result { - let expected = Self::KIND; - let Some(rest) = str.strip_prefix(expected) else { - return Err(KindSnafu { expected }.build()); - }; - let bytes = data_encoding::BASE32_NOPAD.decode(rest.to_ascii_uppercase().as_bytes())?; - let ticket = Self::from_bytes(&bytes)?; - Ok(ticket) - } -} - -/// An error deserializing an iroh ticket. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] -#[derive(Debug, Snafu)] -#[allow(missing_docs)] -#[snafu(visibility(pub(crate)))] -#[non_exhaustive] -pub enum ParseError { - /// Found a ticket with the wrong prefix, indicating the wrong kind. - #[snafu(display("wrong prefix, expected {expected}"))] - Kind { - /// The expected prefix. - expected: &'static str, - }, - /// This looks like a ticket, but postcard deserialization failed. - #[snafu(transparent)] - Postcard { source: postcard::Error }, - /// This looks like a ticket, but base32 decoding failed. - #[snafu(transparent)] - Encoding { source: data_encoding::DecodeError }, - /// Verification of the deserialized bytes failed. - #[snafu(display("verification failed: {message}"))] - Verify { message: &'static str }, -} - -impl ParseError { - /// Returns a [`ParseError`] that indicates the given ticket has the wrong - /// prefix. - /// - /// Indicate the expected prefix. - pub fn wrong_prefix(expected: &'static str) -> Self { - KindSnafu { expected }.build() - } - - /// Return a `ParseError` variant that indicates verification of the - /// deserialized bytes failed. - pub fn verification_failed(message: &'static str) -> Self { - VerifySnafu { message }.build() - } -} - -#[derive(Serialize, Deserialize)] -struct Variant1EndpointAddr { - id: EndpointId, - info: Variant1AddrInfo, -} - -#[derive(Serialize, Deserialize)] -struct Variant1AddrInfo { - addrs: BTreeSet, -} diff --git a/iroh-base/src/ticket/endpoint.rs b/iroh-base/src/ticket/endpoint.rs deleted file mode 100644 index 68da6ef6d7..0000000000 --- a/iroh-base/src/ticket/endpoint.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! Tickets for endpoints. - -use std::str::FromStr; - -use serde::{Deserialize, Serialize}; - -use super::{Variant1AddrInfo, Variant1EndpointAddr}; -use crate::{ - endpoint_addr::EndpointAddr, - ticket::{self, ParseError, Ticket}, -}; - -/// A token containing information for establishing a connection to an endpoint. -/// -/// Contains -/// - The [`EndpointId`] of the endpoint to connect to (a 32-byte ed25519 public key). -/// - Any known [`TransportAddr`]s on which the endpoint can be reached. -/// -/// This allows establishing a connection to the endpoint in most circumstances where it is -/// possible to do so. -/// -/// This [`EndpointTicket`] is a single item which can be easily serialized and deserialized and -/// implements the [`Ticket`] trait. The [`Display`] and [`FromStr`] traits can also be -/// used to round-trip the ticket to string. -/// -/// [`EndpointId`]: crate::key::EndpointId -/// [`Display`]: std::fmt::Display -/// [`FromStr`]: std::str::FromStr -/// [`TransportAddr`]: crate::TransportAddr -#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)] -#[display("{}", Ticket::serialize(self))] -pub struct EndpointTicket { - addr: EndpointAddr, -} - -/// Wire format for [`EndpointTicket`]. -#[derive(Serialize, Deserialize)] -enum TicketWireFormat { - Variant1(Variant1EndpointTicket), -} - -// Legacy -#[derive(Serialize, Deserialize)] -struct Variant1EndpointTicket { - addr: Variant1EndpointAddr, -} - -impl Ticket for EndpointTicket { - const KIND: &'static str = "endpoint"; - - fn to_bytes(&self) -> Vec { - let data = TicketWireFormat::Variant1(Variant1EndpointTicket { - addr: Variant1EndpointAddr { - id: self.addr.id, - info: Variant1AddrInfo { - addrs: self.addr.addrs.clone(), - }, - }, - }); - postcard::to_stdvec(&data).expect("postcard serialization failed") - } - - fn from_bytes(bytes: &[u8]) -> Result { - let res: TicketWireFormat = postcard::from_bytes(bytes)?; - let TicketWireFormat::Variant1(Variant1EndpointTicket { addr }) = res; - Ok(Self { - addr: EndpointAddr { - id: addr.id, - addrs: addr.info.addrs, - }, - }) - } -} - -impl FromStr for EndpointTicket { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - ticket::Ticket::deserialize(s) - } -} - -impl EndpointTicket { - /// Creates a new ticket. - pub fn new(addr: EndpointAddr) -> Self { - Self { addr } - } - - /// The [`EndpointAddr`] of the provider for this ticket. - pub fn endpoint_addr(&self) -> &EndpointAddr { - &self.addr - } -} - -impl From for EndpointTicket { - /// Creates a ticket from given addressing info. - fn from(addr: EndpointAddr) -> Self { - Self { addr } - } -} - -impl From for EndpointAddr { - /// Returns the addressing info from given ticket. - fn from(ticket: EndpointTicket) -> Self { - ticket.addr - } -} - -impl Serialize for EndpointTicket { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) - } else { - let EndpointTicket { addr } = self; - (addr).serialize(serializer) - } - } -} - -impl<'de> Deserialize<'de> for EndpointTicket { - fn deserialize>(deserializer: D) -> Result { - if deserializer.is_human_readable() { - let s = String::deserialize(deserializer)?; - Self::from_str(&s).map_err(serde::de::Error::custom) - } else { - let peer = Deserialize::deserialize(deserializer)?; - Ok(Self::new(peer)) - } - } -} - -#[cfg(test)] -mod tests { - use std::net::{Ipv4Addr, SocketAddr}; - - use data_encoding::HEXLOWER; - use rand::SeedableRng; - - use super::*; - use crate::{ - TransportAddr, - key::{PublicKey, SecretKey}, - }; - - fn make_ticket() -> EndpointTicket { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); - let peer = SecretKey::generate(&mut rng).public(); - let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, 1234)); - EndpointTicket { - addr: EndpointAddr::from_parts(peer, [TransportAddr::Ip(addr)]), - } - } - - #[test] - fn test_ticket_postcard() { - let ticket = make_ticket(); - let bytes = postcard::to_stdvec(&ticket).unwrap(); - let ticket2: EndpointTicket = postcard::from_bytes(&bytes).unwrap(); - assert_eq!(ticket2, ticket); - } - - #[test] - fn test_ticket_json() { - let ticket = make_ticket(); - let json = serde_json::to_string(&ticket).unwrap(); - let ticket2: EndpointTicket = serde_json::from_str(&json).unwrap(); - assert_eq!(ticket2, ticket); - } - - #[test] - fn test_ticket_base32() { - let endpoint_id = - PublicKey::from_str("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6") - .unwrap(); - - let ticket = EndpointTicket { - addr: EndpointAddr::from_parts( - endpoint_id, - [ - TransportAddr::Relay("http://derp.me./".parse().unwrap()), - TransportAddr::Ip("127.0.0.1:1024".parse().unwrap()), - ], - ), - }; - let base32 = data_encoding::BASE32_NOPAD - .decode( - ticket - .to_string() - .strip_prefix("endpoint") - .unwrap() - .to_ascii_uppercase() - .as_bytes(), - ) - .unwrap(); - let expected = [ - // variant - "00", - // endpoint id, 32 bytes, see above - "ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6", - // two addrs - "02", - // TransportAddr: Relay - "00", - // 16 bytes - "10", - // RelayUrl - "687474703a2f2f646572702e6d652e2f", - // TransportAddr: IP - "01", - // IPv4 - "00", - // address, see above - "7f0000018008", - ]; - - // 00ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6 - // 02 - // 00 - // 10 - // 687474703a2f2f646572702e6d652e2f - // 01 - // 00 - // 7f0000018008 - dbg!(&expected); - dbg!(HEXLOWER.encode(&base32)); - let expected = HEXLOWER.decode(expected.concat().as_bytes()).unwrap(); - assert_eq!(base32, expected); - } -} diff --git a/iroh/Cargo.toml b/iroh/Cargo.toml index 2bea278f51..328f80c251 100644 --- a/iroh/Cargo.toml +++ b/iroh/Cargo.toml @@ -150,7 +150,7 @@ tracing-subscriber = { version = "0.3", features = [ ] } indicatif = { version = "0.18", features = ["tokio"] } parse-size = { version = "1.1.0", features = ['std'] } -iroh-base = { version = "0.93.2", default-features = false, features = ["key", "relay", "ticket"], path = "../iroh-base" } +iroh-base = { version = "0.93.2", default-features = false, features = ["key", "relay"], path = "../iroh-base" } # wasm-in-browser test/dev dependencies [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies] diff --git a/iroh/examples/0rtt.rs b/iroh/examples/0rtt.rs index e5b556d538..99069eb28b 100644 --- a/iroh/examples/0rtt.rs +++ b/iroh/examples/0rtt.rs @@ -3,10 +3,9 @@ use std::{env, future::Future, str::FromStr, time::Instant}; use clap::Parser; use data_encoding::HEXLOWER; use iroh::{ - SecretKey, + EndpointId, SecretKey, endpoint::{Connecting, Connection}, }; -use iroh_base::ticket::EndpointTicket; use n0_future::{StreamExt, future}; use n0_snafu::ResultExt; use n0_watcher::Watcher; @@ -17,7 +16,7 @@ const PINGPONG_ALPN: &[u8] = b"0rtt-pingpong"; #[derive(Parser)] struct Args { /// The endpoint id to connect to. If not set, the program will start a server. - endpoint: Option, + endpoint_id: Option, /// Number of rounds to run. #[clap(long, default_value = "100")] rounds: u64, @@ -92,7 +91,7 @@ async fn pingpong_0rtt(connecting: Connecting, i: u64) -> n0_snafu::Result n0_snafu::Result<()> { - let endpoint_addr = args.endpoint.unwrap().endpoint_addr().clone(); + let remote_id = args.endpoint_id.unwrap(); let endpoint = iroh::Endpoint::builder() .relay_mode(iroh::RelayMode::Disabled) .keylog(true) @@ -102,7 +101,7 @@ async fn connect(args: Args) -> n0_snafu::Result<()> { for i in 0..args.rounds { let t0 = Instant::now(); let connecting = endpoint - .connect_with_opts(endpoint_addr.clone(), PINGPONG_ALPN, Default::default()) + .connect_with_opts(remote_id, PINGPONG_ALPN, Default::default()) .await?; let connection = if args.disable_0rtt { let connection = connecting.await.e()?; @@ -149,8 +148,6 @@ async fn accept(_args: Args) -> n0_snafu::Result<()> { } }; println!("Listening on: {addr:?}"); - println!("Endpoint ID: {:?}", addr.id); - println!("Ticket: {}", EndpointTicket::from(addr)); let accept = async move { while let Some(incoming) = endpoint.accept().await { @@ -185,7 +182,7 @@ async fn accept(_args: Args) -> n0_snafu::Result<()> { async fn main() -> n0_snafu::Result<()> { tracing_subscriber::fmt::init(); let args = Args::parse(); - if args.endpoint.is_some() { + if args.endpoint_id.is_some() { connect(args).await?; } else { accept(args).await?; diff --git a/iroh/examples/transfer.rs b/iroh/examples/transfer.rs index 587055885a..54097fd90e 100644 --- a/iroh/examples/transfer.rs +++ b/iroh/examples/transfer.rs @@ -1,4 +1,5 @@ use std::{ + net::SocketAddr, str::FromStr, time::{Duration, Instant}, }; @@ -16,7 +17,6 @@ use iroh::{ dns::{DnsResolver, N0_DNS_ENDPOINT_ORIGIN_PROD, N0_DNS_ENDPOINT_ORIGIN_STAGING}, endpoint::ConnectionError, }; -use iroh_base::ticket::EndpointTicket; use n0_future::task::AbortOnDropHandle; use n0_snafu::{Result, ResultExt}; use n0_watcher::Watcher as _; @@ -146,7 +146,11 @@ enum Commands { }, /// Fetch data. Fetch { - ticket: String, + remote_id: EndpointId, + #[clap(long)] + remote_relay_url: Option, + #[clap(long)] + remote_direct_address: Vec, #[clap(flatten)] endpoint_args: EndpointArgs, }, @@ -167,11 +171,18 @@ async fn main() -> Result<()> { provide(endpoint, size).await? } Commands::Fetch { - ticket, + remote_id, + remote_relay_url, + remote_direct_address, endpoint_args, } => { let endpoint = endpoint_args.bind_endpoint().await?; - fetch(endpoint, &ticket).await? + let addrs = remote_relay_url + .into_iter() + .map(TransportAddr::Relay) + .chain(remote_direct_address.into_iter().map(TransportAddr::Ip)); + let remote_addr = EndpointAddr::from_parts(remote_id, addrs); + fetch(endpoint, remote_addr).await? } } @@ -305,20 +316,14 @@ impl EndpointArgs { async fn provide(endpoint: Endpoint, size: u64) -> Result<()> { let endpoint_id = endpoint.id(); - let endpoint_addr = endpoint.addr(); - let ticket = EndpointTicket::new(endpoint_addr); - println!("Ticket with our home relay and direct addresses:\n{ticket}\n",); - - let mut endpoint_addr = endpoint.addr(); - endpoint_addr - .addrs - .retain(|addr| !matches!(addr, TransportAddr::Ip(_))); - let ticket = EndpointTicket::new(endpoint_addr); - println!("Ticket with our home relay but no direct addresses:\n{ticket}\n",); - let ticket = EndpointTicket::new(EndpointAddr::new(endpoint_id)); - println!("Ticket with only our endpoint id:\n{ticket}\n"); + println!("Endpoint id:\n{endpoint_id}"); + println!("Direct addresses:"); + for addr in endpoint_addr.ip_addrs() { + println!("\t{addr}"); + } + println!(); // accept incoming connections, returns a normal QUIC connection while let Some(incoming) = endpoint.accept().await { @@ -389,20 +394,17 @@ async fn provide(endpoint: Endpoint, size: u64) -> Result<()> { Ok(()) } -async fn fetch(endpoint: Endpoint, ticket: &str) -> Result<()> { +async fn fetch(endpoint: Endpoint, remote_addr: EndpointAddr) -> Result<()> { let me = endpoint.id().fmt_short(); - let ticket: EndpointTicket = ticket.parse()?; - let remote_endpoint_id = ticket.endpoint_addr().id; let start = Instant::now(); + let remote_id = remote_addr.id; // Attempt to connect, over the given ALPN. // Returns a Quinn connection. - let conn = endpoint - .connect(EndpointAddr::from(ticket), TRANSFER_ALPN) - .await?; - println!("Connected to {remote_endpoint_id}"); + let conn = endpoint.connect(remote_addr, TRANSFER_ALPN).await?; + println!("Connected to {}", remote_id); // Spawn a background task that prints connection type changes. Will be aborted on drop. - let _guard = watch_conn_type(&endpoint, remote_endpoint_id); + let _guard = watch_conn_type(&endpoint, remote_id); // Use the Quinn API to send and recv content. let (mut send, mut recv) = conn.open_bi().await.e()?; diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 05ba80ea3d..60150c1275 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -619,7 +619,7 @@ impl Endpoint { /// Connects to a remote [`Endpoint`]. /// /// A value that can be converted into a [`EndpointAddr`] is required. This can be either a - /// [`EndpointAddr`], a [`EndpointId`] or a [`iroh_base::ticket::EndpointTicket`]. + /// [`EndpointAddr`] or a [`EndpointId`]. /// /// The [`EndpointAddr`] must contain the [`EndpointId`] to dial and may also contain a [`RelayUrl`] /// and direct addresses. If direct addresses are provided, they will be used to try and