diff --git a/Cargo.lock b/Cargo.lock index 025dc2e016b..d705895f099 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1026,6 +1026,41 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.106", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -2160,7 +2195,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -2276,6 +2311,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -2444,6 +2485,7 @@ dependencies = [ "iroh-quinn-proto", "iroh-quinn-udp", "iroh-relay", + "n0-error", "n0-future", "n0-snafu", "n0-watcher", @@ -2495,7 +2537,7 @@ dependencies = [ "data-encoding", "derive_more 2.0.1", "ed25519-dalek", - "n0-snafu", + "n0-error", "nested_enum_utils", "postcard", "proptest", @@ -2505,7 +2547,6 @@ dependencies = [ "serde", "serde_json", "serde_test", - "snafu", "url", ] @@ -2695,8 +2736,8 @@ dependencies = [ "iroh-quinn", "iroh-quinn-proto", "lru 0.16.1", + "n0-error", "n0-future", - "n0-snafu", "nested_enum_utils", "num_enum", "pin-project", @@ -2720,7 +2761,6 @@ dependencies = [ "serde_json", "sha1", "simdutf8", - "snafu", "strum", "time", "tokio", @@ -2995,6 +3035,27 @@ dependencies = [ "uuid", ] +[[package]] +name = "n0-error" +version = "0.1.0" +dependencies = [ + "anyhow", + "derive_more 2.0.1", + "n0-error-macros", + "spez", +] + +[[package]] +name = "n0-error-macros" +version = "0.1.0" +dependencies = [ + "darling", + "heck", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "n0-future" version = "0.1.3" @@ -3866,7 +3927,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.0", "thiserror 2.0.17", "tokio", "tracing", @@ -3903,7 +3964,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", "windows-sys 0.60.2", ] @@ -4753,6 +4814,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "spez" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "spin" version = "0.9.8" diff --git a/iroh-base/Cargo.toml b/iroh-base/Cargo.toml index 208d90e00af..79ef7f7369c 100644 --- a/iroh-base/Cargo.toml +++ b/iroh-base/Cargo.toml @@ -23,8 +23,7 @@ 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 } -n0-snafu = "0.2.2" +n0-error = { path = "../../n0-error", optional = true } nested_enum_utils = "0.2.0" [dev-dependencies] @@ -44,7 +43,7 @@ key = [ "dep:ed25519-dalek", "dep:url", "dep:derive_more", - "dep:snafu", + "dep:n0-error", "dep:data-encoding", "dep:rand_core", "relay", @@ -52,7 +51,7 @@ key = [ relay = [ "dep:url", "dep:derive_more", - "dep:snafu", + "dep:n0-error", ] [package.metadata.docs.rs] diff --git a/iroh-base/src/key.rs b/iroh-base/src/key.rs index e408402fdd5..5cd951efb4f 100644 --- a/iroh-base/src/key.rs +++ b/iroh-base/src/key.rs @@ -12,10 +12,8 @@ use std::{ use curve25519_dalek::edwards::CompressedEdwardsY; pub use ed25519_dalek::{Signature, SignatureError}; use ed25519_dalek::{SigningKey, VerifyingKey}; -use nested_enum_utils::common_fields; use rand_core::CryptoRng; use serde::{Deserialize, Serialize}; -use snafu::{Backtrace, Snafu}; /// A public key. /// @@ -203,25 +201,22 @@ impl Display for PublicKey { } /// Error when deserialising a [`PublicKey`] or a [`SecretKey`]. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] -#[derive(Snafu, Debug)] +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[error(from_sources, std_sources)] #[allow(missing_docs)] -#[snafu(visibility(pub(crate)))] +#[non_exhaustive] pub enum KeyParsingError { /// Error when decoding. - #[snafu(transparent)] + #[error(transparent)] Decode { source: data_encoding::DecodeError }, /// Error when decoding the public key. - #[snafu(transparent)] + #[error(transparent)] Key { source: ed25519_dalek::SignatureError, }, /// The encoded information had the wrong length. - #[snafu(display("invalid length"))] + #[display("invalid length")] DecodeInvalidLength {}, } @@ -353,14 +348,14 @@ fn decode_base32_hex(s: &str) -> Result<[u8; 32], KeyParsingError> { let input = s.to_ascii_uppercase(); let input = input.as_bytes(); if data_encoding::BASE32_NOPAD.decode_len(input.len())? != bytes.len() { - return Err(DecodeInvalidLengthSnafu.build()); + return Err(KeyParsingError::decode_invalid_length()); } data_encoding::BASE32_NOPAD.decode_mut(input, &mut bytes) }; match res { Ok(len) => { if len != PublicKey::LENGTH { - return Err(DecodeInvalidLengthSnafu.build()); + return Err(KeyParsingError::decode_invalid_length()); } } Err(partial) => return Err(partial.error.into()), diff --git a/iroh-base/src/relay_url.rs b/iroh-base/src/relay_url.rs index b1aef365282..fe794721c0a 100644 --- a/iroh-base/src/relay_url.rs +++ b/iroh-base/src/relay_url.rs @@ -1,7 +1,7 @@ use std::{fmt, ops::Deref, str::FromStr, sync::Arc}; +use n0_error::{ResultExt, StackErrorExt}; use serde::{Deserialize, Serialize}; -use snafu::{Backtrace, ResultExt, Snafu}; use url::Url; /// A URL identifying a relay server. @@ -39,11 +39,12 @@ impl From for RelayUrl { } /// Can occur when parsing a string into a [`RelayUrl`]. -#[derive(Debug, Snafu)] -#[snafu(display("Failed to parse"))] +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[display("Failed to parse")] pub struct RelayUrlParseError { - source: url::ParseError, - backtrace: Option, + #[error(from, std_err)] + parse_error: url::ParseError, } /// Support for parsing strings directly. @@ -54,7 +55,7 @@ impl FromStr for RelayUrl { type Err = RelayUrlParseError; fn from_str(s: &str) -> Result { - let inner = Url::from_str(s).context(RelayUrlParseSnafu)?; + let inner = Url::from_str(s).context(RelayUrlParseError::from)?; Ok(RelayUrl::from(inner)) } } diff --git a/iroh-base/src/ticket.rs b/iroh-base/src/ticket.rs index 1c8f647093b..d9e0c1932e0 100644 --- a/iroh-base/src/ticket.rs +++ b/iroh-base/src/ticket.rs @@ -7,7 +7,6 @@ use std::{collections::BTreeSet, net::SocketAddr}; use nested_enum_utils::common_fields; use serde::{Deserialize, Serialize}; -use snafu::{Backtrace, Snafu}; use crate::{key::NodeId, relay_url::RelayUrl}; @@ -50,7 +49,7 @@ pub trait Ticket: Sized { fn deserialize(str: &str) -> Result { let expected = Self::KIND; let Some(rest) = str.strip_prefix(expected) else { - return Err(KindSnafu { expected }.build()); + return Err(ParseError::kind(expected)); }; let bytes = data_encoding::BASE32_NOPAD.decode(rest.to_ascii_uppercase().as_bytes())?; let ticket = Self::from_bytes(&bytes)?; @@ -59,30 +58,23 @@ pub trait Ticket: Sized { } /// An error deserializing an iroh ticket. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] -#[derive(Debug, Snafu)] +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[error(from_sources, std_sources)] #[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, - }, + #[display("wrong prefix, expected {expected}")] + Kind { expected: &'static str }, /// This looks like a ticket, but postcard deserialization failed. - #[snafu(transparent)] + #[error(transparent)] Postcard { source: postcard::Error }, /// This looks like a ticket, but base32 decoding failed. - #[snafu(transparent)] + #[error(transparent)] Encoding { source: data_encoding::DecodeError }, /// Verification of the deserialized bytes failed. - #[snafu(display("verification failed: {message}"))] + #[display("verification failed: {message}")] Verify { message: &'static str }, } @@ -92,13 +84,13 @@ impl ParseError { /// /// Indicate the expected prefix. pub fn wrong_prefix(expected: &'static str) -> Self { - KindSnafu { expected }.build() + ParseError::kind(expected) } /// Return a `ParseError` variant that indicates verification of the /// deserialized bytes failed. pub fn verification_failed(message: &'static str) -> Self { - VerifySnafu { message }.build() + ParseError::verify(message) } } diff --git a/iroh-relay/Cargo.toml b/iroh-relay/Cargo.toml index 8db3536ea73..cc761afd786 100644 --- a/iroh-relay/Cargo.toml +++ b/iroh-relay/Cargo.toml @@ -71,8 +71,7 @@ webpki_types = { package = "rustls-pki-types", version = "1.12" } data-encoding = "2.6.0" lru = "0.16" z32 = "1.0.3" -snafu = { version = "0.8.5", features = ["rust_1_81"] } -n0-snafu = "0.2.2" +n0-error = { path = "../../n0-error" } nested_enum_utils = "0.2.0" # server feature diff --git a/iroh-relay/src/client.rs b/iroh-relay/src/client.rs index 9fbad400b2d..2172b751a2a 100644 --- a/iroh-relay/src/client.rs +++ b/iroh-relay/src/client.rs @@ -11,13 +11,12 @@ use std::{ use conn::Conn; use iroh_base::{RelayUrl, SecretKey}; +use n0_error as _; use n0_future::{ Sink, Stream, split::{SplitSink, SplitStream, split}, time, }; -use nested_enum_utils::common_fields; -use snafu::{Backtrace, Snafu}; #[cfg(any(test, feature = "test-utils"))] use tracing::warn; use tracing::{Level, debug, event, trace}; @@ -47,75 +46,69 @@ mod util; /// /// `ConnectError` contains `DialError`, errors that can occur while dialing the /// relay, as well as errors that occur while creating or maintaining a connection. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] -#[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[error(std_sources, from_sources)] #[non_exhaustive] +#[allow(missing_docs)] pub enum ConnectError { - #[snafu(display("Invalid URL for websocket: {url}"))] + #[display("Invalid URL for websocket: {url}")] InvalidWebsocketUrl { url: Url }, - #[snafu(display("Invalid relay URL: {url}"))] + #[display("Invalid relay URL: {url}")] InvalidRelayUrl { url: Url }, - #[snafu(transparent)] + #[error(transparent)] Websocket { #[cfg(not(wasm_browser))] source: tokio_websockets::Error, #[cfg(wasm_browser)] source: ws_stream_wasm::WsErr, }, - #[snafu(transparent)] + #[error(transparent)] Handshake { source: handshake::Error }, - #[snafu(transparent)] + #[error(transparent)] Dial { source: DialError }, - #[snafu(display("Unexpected status during upgrade: {code}"))] + #[display("Unexpected status during upgrade: {code}")] UnexpectedUpgradeStatus { code: hyper::StatusCode }, - #[snafu(display("Failed to upgrade response"))] + #[display("Failed to upgrade response")] Upgrade { source: hyper::Error }, - #[snafu(display("Invalid TLS servername"))] + #[display("Invalid TLS servername")] InvalidTlsServername {}, - #[snafu(display("No local address available"))] + #[display("No local address available")] NoLocalAddr {}, - #[snafu(display("tls connection failed"))] + #[display("tls connection failed")] Tls { source: std::io::Error }, #[cfg(wasm_browser)] - #[snafu(display("The relay protocol is not available in browsers"))] + #[display("The relay protocol is not available in browsers")] RelayProtoNotAvailable {}, } /// Errors that can occur while dialing the relay server. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] -#[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[error(std_sources, from_sources)] #[non_exhaustive] +#[allow(missing_docs)] pub enum DialError { - #[snafu(display("Invliad target port"))] + #[display("Invliad target port")] InvalidTargetPort {}, - #[snafu(transparent)] + #[error(transparent)] #[cfg(not(wasm_browser))] Dns { source: DnsError }, - #[snafu(transparent)] + #[error(transparent)] Timeout { source: time::Elapsed }, - #[snafu(transparent)] + #[error(transparent)] Io { source: std::io::Error }, - #[snafu(display("Invalid URL: {url}"))] + #[display("Invalid URL: {url}")] InvalidUrl { url: Url }, - #[snafu(display("Failed proxy connection: {status}"))] + #[display("Failed proxy connection: {status}")] ProxyConnectInvalidStatus { status: hyper::StatusCode }, - #[snafu(display("Invalid Proxy URL {proxy_url}"))] + #[display("Invalid Proxy URL {proxy_url}")] ProxyInvalidUrl { proxy_url: Url }, - #[snafu(display("failed to establish proxy connection"))] + #[display("failed to establish proxy connection")] ProxyConnect { source: hyper::Error }, - #[snafu(display("Invalid proxy TLS servername: {proxy_hostname}"))] + #[display("Invalid proxy TLS servername: {proxy_hostname}")] ProxyInvalidTlsServername { proxy_hostname: String }, - #[snafu(display("Invalid proxy target port"))] + #[display("Invalid proxy target port")] ProxyInvalidTargetPort {}, } @@ -219,12 +212,7 @@ impl ClientBuilder { "ws" => "ws", _ => "wss", }) - .map_err(|_| { - InvalidWebsocketUrlSnafu { - url: dial_url.clone(), - } - .build() - })?; + .map_err(|_| ConnectError::invalid_websocket_url(dial_url.clone()))?; debug!(%dial_url, "Dialing relay by websocket"); @@ -242,15 +230,10 @@ impl ClientBuilder { let local_addr = stream .as_ref() .local_addr() - .map_err(|_| NoLocalAddrSnafu.build())?; + .map_err(|_| ConnectError::no_local_addr())?; let mut builder = tokio_websockets::ClientBuilder::new() .uri(dial_url.as_str()) - .map_err(|_| { - InvalidRelayUrlSnafu { - url: dial_url.clone(), - } - .build() - })? + .map_err(|_| ConnectError::invalid_relay_url(dial_url.clone()))? .add_header( SEC_WEBSOCKET_PROTOCOL, http::HeaderValue::from_static(RELAY_PROTOCOL_VERSION), @@ -272,10 +255,7 @@ impl ClientBuilder { let (conn, response) = builder.connect_on(stream).await?; if response.status() != hyper::StatusCode::SWITCHING_PROTOCOLS { - UnexpectedUpgradeStatusSnafu { - code: response.status(), - } - .fail()?; + return Err(ConnectError::unexpected_upgrade_status(response.status())); } let conn = Conn::new(conn, self.key_cache.clone(), &self.secret_key).await?; diff --git a/iroh-relay/src/client/conn.rs b/iroh-relay/src/client/conn.rs index 7a76bcff21a..d27619914d4 100644 --- a/iroh-relay/src/client/conn.rs +++ b/iroh-relay/src/client/conn.rs @@ -8,9 +8,8 @@ use std::{ }; use iroh_base::SecretKey; +use n0_error::ensure; use n0_future::{Sink, Stream}; -use nested_enum_utils::common_fields; -use snafu::{Backtrace, Snafu}; use tracing::debug; use super::KeyCache; @@ -26,42 +25,39 @@ use crate::{ }; /// Error for sending messages to the relay server. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum SendError { - #[snafu(transparent)] + #[error(transparent)] StreamError { + #[error(from, std_err)] #[cfg(not(wasm_browser))] source: tokio_websockets::Error, #[cfg(wasm_browser)] source: ws_stream_wasm::WsErr, }, - #[snafu(display("Exceeds max packet size ({MAX_PACKET_SIZE}): {size}"))] + #[display("Exceeds max packet size ({MAX_PACKET_SIZE}): {size}")] ExceedsMaxPacketSize { size: usize }, - #[snafu(display("Attempted to send empty packet"))] + #[display("Attempted to send empty packet")] EmptyPacket {}, } /// Errors when receiving messages from the relay server. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum RecvError { - #[snafu(transparent)] - Protocol { source: ProtoError }, - #[snafu(transparent)] + #[error(transparent)] + Protocol { + #[error(from)] + source: ProtoError, + }, + #[error(transparent)] StreamError { + #[error(from, std_err)] #[cfg(not(wasm_browser))] source: tokio_websockets::Error, #[cfg(wasm_browser)] @@ -146,9 +142,12 @@ impl Sink for Conn { fn start_send(mut self: Pin<&mut Self>, frame: ClientToRelayMsg) -> Result<(), Self::Error> { let size = frame.encoded_len(); - snafu::ensure!(size <= MAX_PACKET_SIZE, ExceedsMaxPacketSizeSnafu { size }); + ensure!( + size <= MAX_PACKET_SIZE, + SendError::exceeds_max_packet_size(size) + ); if let ClientToRelayMsg::Datagrams { datagrams, .. } = &frame { - snafu::ensure!(!datagrams.contents.is_empty(), EmptyPacketSnafu); + ensure!(!datagrams.contents.is_empty(), SendError::empty_packet()); } Pin::new(&mut self.conn) diff --git a/iroh-relay/src/client/tls.rs b/iroh-relay/src/client/tls.rs index add947bfda9..187cb0ce441 100644 --- a/iroh-relay/src/client/tls.rs +++ b/iroh-relay/src/client/tls.rs @@ -10,9 +10,9 @@ use bytes::Bytes; use data_encoding::BASE64URL; use http_body_util::Empty; use hyper::{Request, upgrade::Parts}; +use n0_error::ResultExt; use n0_future::{task, time}; use rustls::client::Resumption; -use snafu::{OptionExt, ResultExt}; use tracing::error; use super::{ @@ -84,7 +84,7 @@ impl MaybeTlsStreamBuilder { let local_addr = tcp_stream .local_addr() - .map_err(|_| NoLocalAddrSnafu.build())?; + .map_err(|_| ConnectError::no_local_addr())?; debug!(server_addr = ?tcp_stream.peer_addr(), %local_addr, "TCP stream connected"); @@ -92,13 +92,13 @@ impl MaybeTlsStreamBuilder { debug!("Starting TLS handshake"); let hostname = self .tls_servername() - .ok_or_else(|| InvalidTlsServernameSnafu.build())?; + .ok_or_else(|| ConnectError::invalid_tls_servername())?; let hostname = hostname.to_owned(); let tls_stream = tls_connector .connect(hostname, tcp_stream) .await - .context(TlsSnafu)?; + .context(ConnectError::tls)?; debug!("tls_connector connect success"); Ok(MaybeTlsStream::Tls(tls_stream)) } else { @@ -144,7 +144,7 @@ impl MaybeTlsStreamBuilder { .resolve_host(&self.url, self.prefer_ipv6, DNS_TIMEOUT) .await?; - let port = url_port(&self.url).context(InvalidTargetPortSnafu)?; + let port = url_port(&self.url).ok_or_else(|| DialError::invalid_target_port())?; let addr = SocketAddr::new(dst_ip, port); debug!("connecting to {}", addr); @@ -175,7 +175,8 @@ impl MaybeTlsStreamBuilder { .resolve_host(&proxy_url, self.prefer_ipv6, DNS_TIMEOUT) .await?; - let proxy_port = url_port(&proxy_url).context(ProxyInvalidTargetPortSnafu)?; + let proxy_port = + url_port(&proxy_url).ok_or_else(|| DialError::proxy_invalid_target_port())?; let proxy_addr = SocketAddr::new(proxy_ip, proxy_port); debug!(%proxy_addr, "connecting to proxy"); @@ -191,26 +192,22 @@ impl MaybeTlsStreamBuilder { let io = if proxy_url.scheme() == "http" { MaybeTlsStream::Raw(tcp_stream) } else { - let hostname = proxy_url.host_str().context(ProxyInvalidUrlSnafu { - proxy_url: proxy_url.clone(), - })?; - let hostname = - rustls::pki_types::ServerName::try_from(hostname.to_string()).map_err(|_| { - ProxyInvalidTlsServernameSnafu { - proxy_hostname: hostname.to_string(), - } - .build() - })?; + let hostname = proxy_url + .host_str() + .ok_or_else(|| DialError::proxy_invalid_url(proxy_url.clone()))?; + let hostname = rustls::pki_types::ServerName::try_from(hostname.to_string()) + .map_err(|_| DialError::proxy_invalid_tls_servername(hostname.to_string()))?; let tls_stream = tls_connector.connect(hostname, tcp_stream).await?; MaybeTlsStream::Tls(tls_stream) }; let io = TokioIo::new(io); - let target_host = self.url.host_str().context(InvalidUrlSnafu { - url: self.url.clone(), - })?; + let target_host = self + .url + .host_str() + .ok_or_else(|| DialError::invalid_url(self.url.clone()))?; - let port = url_port(&self.url).context(InvalidTargetPortSnafu)?; + let port = url_port(&self.url).ok_or_else(|| DialError::invalid_target_port())?; // Establish Proxy Tunnel let mut req_builder = Request::builder() @@ -241,22 +238,24 @@ impl MaybeTlsStreamBuilder { let (mut sender, conn) = hyper::client::conn::http1::handshake(io) .await - .context(ProxyConnectSnafu)?; + .context(DialError::proxy_connect)?; task::spawn(async move { if let Err(err) = conn.with_upgrades().await { error!("Proxy connection failed: {:?}", err); } }); - let res = sender.send_request(req).await.context(ProxyConnectSnafu)?; + let res = sender + .send_request(req) + .await + .context(DialError::proxy_connect)?; if !res.status().is_success() { - return Err(ProxyConnectInvalidStatusSnafu { - status: res.status(), - } - .build()); + return Err(DialError::proxy_connect_invalid_status(res.status())); } - let upgraded = hyper::upgrade::on(res).await.context(ProxyConnectSnafu)?; + let upgraded = hyper::upgrade::on(res) + .await + .context(DialError::proxy_connect)?; let Parts { io, read_buf, .. } = upgraded .downcast::>>() .expect("only this upgrade used"); diff --git a/iroh-relay/src/dns.rs b/iroh-relay/src/dns.rs index 9f20884c541..c80a98fea69 100644 --- a/iroh-relay/src/dns.rs +++ b/iroh-relay/src/dns.rs @@ -13,13 +13,12 @@ use hickory_resolver::{ name_server::TokioConnectionProvider, }; use iroh_base::NodeId; +use n0_error::ResultExt; use n0_future::{ StreamExt, boxed::BoxFuture, time::{self, Duration}, }; -use nested_enum_utils::common_fields; -use snafu::{Backtrace, GenerateImplicitData, OptionExt, ResultExt, Snafu}; use tokio::sync::RwLock; use tracing::debug; use url::Url; @@ -62,80 +61,84 @@ pub trait Resolver: fmt::Debug + Send + Sync + 'static { pub type BoxIter = Box + Send + 'static>; /// Potential errors related to dns. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] -#[snafu(visibility(pub(crate)))] pub enum DnsError { - #[snafu(transparent)] - Timeout { source: tokio::time::error::Elapsed }, - #[snafu(display("No response"))] + #[error(transparent)] + Timeout { + #[error(from, std_err)] + source: tokio::time::error::Elapsed, + }, + #[display("No response")] NoResponse {}, - #[snafu(display("Resolve failed ipv4: {ipv4}, ipv6 {ipv6}"))] + #[display("Resolve failed ipv4: {ipv4}, ipv6 {ipv6}")] ResolveBoth { ipv4: Box, ipv6: Box, }, - #[snafu(display("missing host"))] + #[display("missing host")] MissingHost {}, - #[snafu(transparent)] + #[error(transparent)] Resolve { + #[error(from, std_err)] source: hickory_resolver::ResolveError, }, - #[snafu(display("invalid DNS response: not a query for _iroh.z32encodedpubkey"))] + #[display("invalid DNS response: not a query for _iroh.z32encodedpubkey")] InvalidResponse {}, } #[cfg(not(wasm_browser))] -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] -#[snafu(visibility(pub(crate)))] pub enum LookupError { - #[snafu(display("Malformed txt from lookup"))] + #[display("Malformed txt from lookup")] ParseError { - #[snafu(source(from(ParseError, Box::new)))] - source: Box, + #[error(from)] + source: ParseError, }, - #[snafu(display("Failed to resolve TXT record"))] + #[display("Failed to resolve TXT record")] LookupFailed { - #[snafu(source(from(DnsError, Box::new)))] - source: Box, + #[error(from)] + source: DnsError, }, } /// Error returned when an input value is too long for [`crate::node_info::UserData`]. #[allow(missing_docs)] -#[derive(Debug, Snafu)] -#[snafu(module)] -#[snafu(display("no calls succeeded: [{}]", errors.iter().map(|e| e.to_string()).collect::>().join("")))] -pub struct StaggeredError { - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, +#[derive(Debug)] +pub struct StaggeredError +where + E: std::fmt::Debug + std::fmt::Display, +{ errors: Vec, } impl StaggeredError { pub(crate) fn new(errors: Vec) -> Self { - Self { - errors, - backtrace: GenerateImplicitData::generate(), - span_trace: n0_snafu::SpanTrace::generate(), - } + Self { errors } } } +impl std::fmt::Display for StaggeredError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "no calls succeeded: [{}]", + self.errors + .iter() + .map(|e| e.to_string()) + .collect::>() + .join("") + ) + } +} + +impl std::error::Error for StaggeredError {} + /// Builder for [`DnsResolver`]. #[derive(Debug, Clone, Default)] pub struct Builder { @@ -313,11 +316,10 @@ impl DnsResolver { (Ok(ipv4), Ok(ipv6)) => Ok(LookupIter::Both(ipv4.chain(ipv6))), (Ok(ipv4), Err(_)) => Ok(LookupIter::Ipv4(ipv4)), (Err(_), Ok(ipv6)) => Ok(LookupIter::Ipv6(ipv6)), - (Err(ipv4_err), Err(ipv6_err)) => Err(ResolveBothSnafu { - ipv4: Box::new(ipv4_err), - ipv6: Box::new(ipv6_err), - } - .build()), + (Err(ipv4_err), Err(ipv6_err)) => Err(DnsError::resolve_both( + Box::new(ipv4_err), + Box::new(ipv6_err), + )), } } @@ -328,7 +330,7 @@ impl DnsResolver { prefer_ipv6: bool, timeout: Duration, ) -> Result { - let host = url.host().context(MissingHostSnafu)?; + let host = url.host().ok_or_else(|| DnsError::missing_host())?; match host { url::Host::Domain(domain) => { // Need to do a DNS lookup @@ -338,20 +340,19 @@ impl DnsResolver { ); let (v4, v6) = match lookup { (Err(ipv4_err), Err(ipv6_err)) => { - return Err(ResolveBothSnafu { - ipv4: Box::new(ipv4_err), - ipv6: Box::new(ipv6_err), - } - .build()); + return Err(DnsError::resolve_both( + Box::new(ipv4_err), + Box::new(ipv6_err), + )); } (Err(_), Ok(mut v6)) => (None, v6.next()), (Ok(mut v4), Err(_)) => (v4.next(), None), (Ok(mut v4), Ok(mut v6)) => (v4.next(), v6.next()), }; if prefer_ipv6 { - v6.or(v4).context(NoResponseSnafu) + v6.or(v4).ok_or_else(|| DnsError::no_response()) } else { - v4.or(v6).context(NoResponseSnafu) + v4.or(v6).ok_or_else(|| DnsError::no_response()) } } url::Host::Ipv4(ip) => Ok(IpAddr::V4(ip)), @@ -425,8 +426,8 @@ impl DnsResolver { let lookup = self .lookup_txt(name.clone(), DNS_TIMEOUT) .await - .context(LookupFailedSnafu)?; - let info = NodeInfo::from_txt_lookup(name, lookup).context(ParseSnafu)?; + .context(LookupError::lookup_failed)?; + let info = NodeInfo::from_txt_lookup(name, lookup).context(LookupError::parse_error)?; Ok(info) } @@ -436,8 +437,8 @@ impl DnsResolver { let lookup = self .lookup_txt(name.clone(), DNS_TIMEOUT) .await - .context(LookupFailedSnafu)?; - let info = NodeInfo::from_txt_lookup(name, lookup).context(ParseSnafu)?; + .context(LookupError::lookup_failed)?; + let info = NodeInfo::from_txt_lookup(name, lookup).context(LookupError::parse_error)?; Ok(info) } @@ -869,7 +870,7 @@ pub(crate) mod tests { let addr = if host == "foo.example" { Ipv4Addr::new(1, 1, 1, 1) } else { - return Err(NoResponseSnafu.build()); + return Err(DnsError::no_response()); }; let iter: BoxIter = Box::new(vec![addr].into_iter()); Ok(iter) diff --git a/iroh-relay/src/main.rs b/iroh-relay/src/main.rs index dfac90ae1f5..488a2dcec26 100644 --- a/iroh-relay/src/main.rs +++ b/iroh-relay/src/main.rs @@ -19,10 +19,9 @@ use iroh_relay::{ }, server::{self as relay, ClientRateLimit, QuicConfig}, }; +use n0_error::{Result, ResultExt, StackErrorExt, whatever}; use n0_future::FutureExt; -use n0_snafu::{Error, Result, ResultExt}; use serde::{Deserialize, Serialize}; -use snafu::whatever; use tokio_rustls_acme::{AcmeConfig, caches::DirCache}; use tracing::{debug, warn}; use tracing_subscriber::{EnvFilter, prelude::*}; @@ -540,7 +539,9 @@ async fn main() -> Result<()> { let relay_config = build_relay_config(cfg).await?; debug!("{relay_config:#?}"); - let mut relay = relay::Server::spawn(relay_config).await?; + let mut relay = relay::Server::spawn(relay_config) + .await + .context("relay spawn")?; tokio::select! { biased; @@ -548,7 +549,7 @@ async fn main() -> Result<()> { _ = relay.task_handle() => (), } - relay.shutdown().await?; + relay.shutdown().await.context("shutdown")?; Ok(()) } @@ -572,7 +573,7 @@ async fn maybe_load_tls( let (private_key, certs) = tokio::task::spawn_blocking(move || { let key = load_secret_key(key_path)?; let certs = load_certs(cert_path)?; - Ok::<_, Error>((key, certs)) + Ok::<_, n0_error::AnyError>((key, certs)) }) .await .context("join")??; @@ -585,11 +586,11 @@ async fn maybe_load_tls( let hostname = tls .hostname .clone() - .context("LetsEncrypt needs a hostname")?; + .ok_or_else(|| n0_error::format_err!("LetsEncrypt needs a hostname"))?; let contact = tls .contact .clone() - .context("LetsEncrypt needs a contact email")?; + .ok_or_else(|| n0_error::format_err!("LetsEncrypt needs a contact email"))?; let config = AcmeConfig::new(vec![hostname.clone()]) .contact([format!("mailto:{contact}")]) .cache_option(Some(DirCache::new(tls.cert_dir()))) @@ -725,7 +726,7 @@ mod tests { use std::num::NonZeroU32; use iroh_base::SecretKey; - use n0_snafu::Result; + use n0_error::Result; use rand::SeedableRng; use rand_chacha::ChaCha8Rng; diff --git a/iroh-relay/src/node_info.rs b/iroh-relay/src/node_info.rs index 72b5a172f6e..c0733864428 100644 --- a/iroh-relay/src/node_info.rs +++ b/iroh-relay/src/node_info.rs @@ -41,46 +41,40 @@ use std::{ }; use iroh_base::{NodeAddr, NodeId, RelayUrl, SecretKey, SignatureError}; -use nested_enum_utils::common_fields; -use snafu::{Backtrace, ResultExt, Snafu}; +use n0_error::{ResultExt, StackErrorExt, ensure}; use url::Url; /// The DNS name for the iroh TXT record. pub const IROH_TXT_NAME: &str = "_iroh"; -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] -#[snafu(visibility(pub(crate)))] pub enum EncodingError { - #[snafu(transparent)] + #[error(transparent)] FailedBuildingPacket { + #[error(from, std_err)] source: pkarr::errors::SignedPacketBuildError, }, - #[snafu(display("invalid TXT entry"))] - InvalidTxtEntry { source: pkarr::dns::SimpleDnsError }, + #[display("invalid TXT entry")] + InvalidTxtEntry { + #[error(std_err)] + source: pkarr::dns::SimpleDnsError, + }, } -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] +#[error(from_sources, std_sources)] #[non_exhaustive] -#[snafu(visibility(pub(crate)))] pub enum DecodingError { - #[snafu(display("node id was not encoded in valid z32"))] + #[display("node id was not encoded in valid z32")] InvalidEncodingZ32 { source: z32::Z32Error }, - #[snafu(display("length must be 32 bytes, but got {len} byte(s)"))] + #[display("length must be 32 bytes, but got {len} byte(s)")] InvalidLength { len: usize }, - #[snafu(display("node id is not a valid public key"))] + #[display("node id is not a valid public key")] InvalidSignature { source: SignatureError }, } @@ -104,11 +98,11 @@ impl NodeIdExt for NodeId { } fn from_z32(s: &str) -> Result { - let bytes = z32::decode(s.as_bytes()).context(InvalidEncodingZ32Snafu)?; + let bytes = z32::decode(s.as_bytes()).context(DecodingError::invalid_encoding_z32)?; let bytes: &[u8; 32] = &bytes .try_into() - .map_err(|_| InvalidLengthSnafu { len: s.len() }.build())?; - let node_id = NodeId::from_bytes(bytes).context(InvalidSignatureSnafu)?; + .map_err(|_| DecodingError::invalid_length(s.len()))?; + let node_id = NodeId::from_bytes(bytes).context(DecodingError::invalid_signature)?; Ok(node_id) } } @@ -227,18 +221,19 @@ impl UserData { /// Error returned when an input value is too long for [`UserData`]. #[allow(missing_docs)] -#[derive(Debug, Snafu)] -pub struct MaxLengthExceededError { - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -} +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[display("user data exceeds max length")] +pub struct MaxLengthExceededError {} impl TryFrom for UserData { type Error = MaxLengthExceededError; fn try_from(value: String) -> Result { - snafu::ensure!(value.len() <= Self::MAX_LENGTH, MaxLengthExceededSnafu); + ensure!( + value.len() <= Self::MAX_LENGTH, + MaxLengthExceededError::new() + ); Ok(Self(value)) } } @@ -247,7 +242,7 @@ impl FromStr for UserData { type Err = MaxLengthExceededError; fn from_str(s: &str) -> std::result::Result { - snafu::ensure!(s.len() <= Self::MAX_LENGTH, MaxLengthExceededSnafu); + ensure!(s.len() <= Self::MAX_LENGTH, MaxLengthExceededError::new()); Ok(Self(s.to_string())) } } @@ -410,30 +405,28 @@ impl NodeInfo { } } -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] -#[snafu(visibility(pub(crate)))] pub enum ParseError { - #[snafu(display("Expected format `key=value`, received `{s}`"))] + #[display("Expected format `key=value`, received `{s}`")] UnexpectedFormat { s: String }, - #[snafu(display("Could not convert key to Attr"))] + #[display("Could not convert key to Attr")] AttrFromString { key: String }, - #[snafu(display("Expected 2 labels, received {num_labels}"))] + #[display("Expected 2 labels, received {num_labels}")] NumLabels { num_labels: usize }, - #[snafu(display("Could not parse labels"))] - Utf8 { source: Utf8Error }, - #[snafu(display("Record is not an `iroh` record, expected `_iroh`, got `{label}`"))] + #[display("Could not parse labels")] + Utf8 { + #[error(from, std_err)] + source: Utf8Error, + }, + #[display("Record is not an `iroh` record, expected `_iroh`, got `{label}`")] NotAnIrohRecord { label: String }, - #[snafu(transparent)] - DecodingError { - #[snafu(source(from(DecodingError, Box::new)))] - source: Box, + #[error(transparent)] + Decoding { + #[error(from)] + source: DecodingError, }, } @@ -459,12 +452,12 @@ impl std::ops::DerefMut for NodeInfo { fn node_id_from_txt_name(name: &str) -> Result { let num_labels = name.split(".").count(); if num_labels < 2 { - return Err(NumLabelsSnafu { num_labels }.build()); + return Err(ParseError::num_labels(num_labels)); } let mut labels = name.split("."); let label = labels.next().expect("checked above"); if label != IROH_TXT_NAME { - return Err(NotAnIrohRecordSnafu { label }.build()); + return Err(ParseError::not_an_iroh_record(label.to_string())); } let label = labels.next().expect("checked above"); let node_id = NodeId::from_z32(label)?; @@ -533,9 +526,10 @@ impl TxtAttrs { for s in strings { let mut parts = s.split('='); let (Some(key), Some(value)) = (parts.next(), parts.next()) else { - return Err(UnexpectedFormatSnafu { s }.build()); + return Err(ParseError::unexpected_format(s)); }; - let attr = T::from_str(key).map_err(|_| AttrFromStringSnafu { key }.build())?; + let attr = + T::from_str(key).map_err(|_| ParseError::attr_from_string(key.to_string()))?; attrs.entry(attr).or_default().push(value.to_string()); } Ok(Self { attrs, node_id }) @@ -610,7 +604,8 @@ impl TxtAttrs { let mut builder = pkarr::SignedPacket::builder(); for s in self.to_txt_strings() { let mut txt = rdata::TXT::new(); - txt.add_string(&s).context(InvalidTxtEntrySnafu)?; + txt.add_string(&s) + .context(EncodingError::invalid_txt_entry)?; builder = builder.txt(name.clone(), txt.into_owned(), ttl); } let signed_packet = builder.build(&keypair)?; @@ -649,7 +644,7 @@ mod tests { }, }; use iroh_base::{NodeId, SecretKey}; - use n0_snafu::{Result, ResultExt}; + use n0_error::{Result, ResultExt}; use super::{NodeData, NodeIdExt, NodeInfo}; use crate::dns::TxtRecordData; diff --git a/iroh-relay/src/protos/common.rs b/iroh-relay/src/protos/common.rs index 1c6dd894d95..8a857a814da 100644 --- a/iroh-relay/src/protos/common.rs +++ b/iroh-relay/src/protos/common.rs @@ -4,9 +4,7 @@ //! integers for different frames. use bytes::{Buf, BufMut}; -use nested_enum_utils::common_fields; use quinn_proto::{VarInt, coding::Codec}; -use snafu::{Backtrace, OptionExt, Snafu}; /// Possible frame types during handshaking #[repr(u32)] @@ -53,18 +51,14 @@ pub enum FrameType { Restarting = 12, } -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum FrameTypeError { - #[snafu(display("not enough bytes to parse frame type"))] + #[display("not enough bytes to parse frame type")] UnexpectedEnd {}, - #[snafu(display("frame type unknown"))] + #[display("frame type unknown")] UnknownFrameType { tag: VarInt }, } @@ -93,13 +87,15 @@ impl FrameType { /// Parses the frame type (as a QUIC-encoded varint) from the first couple of bytes given /// and returns the frame type and the rest. pub(crate) fn from_bytes(buf: &mut impl Buf) -> Result { - let tag = VarInt::decode(buf).ok().context(UnexpectedEndSnafu)?; + let tag = VarInt::decode(buf) + .ok() + .ok_or_else(|| FrameTypeError::unexpected_end())?; let tag_u32 = u32::try_from(u64::from(tag)) .ok() - .context(UnknownFrameTypeSnafu { tag })?; + .ok_or_else(|| FrameTypeError::unknown_frame_type(tag))?; let frame_type = FrameType::try_from(tag_u32) .ok() - .context(UnknownFrameTypeSnafu { tag })?; + .ok_or_else(|| FrameTypeError::unknown_frame_type(tag))?; Ok(frame_type) } } diff --git a/iroh-relay/src/protos/handshake.rs b/iroh-relay/src/protos/handshake.rs index 82f1112ff57..ba3280e8178 100644 --- a/iroh-relay/src/protos/handshake.rs +++ b/iroh-relay/src/protos/handshake.rs @@ -31,11 +31,10 @@ use http::HeaderValue; #[cfg(feature = "server")] use iroh_base::Signature; use iroh_base::{PublicKey, SecretKey}; +use n0_error::{ResultExt, ensure}; use n0_future::{SinkExt, TryStreamExt}; -use nested_enum_utils::common_fields; #[cfg(feature = "server")] use rand::CryptoRng; -use snafu::{Backtrace, ResultExt, Snafu}; use tracing::trace; use super::{ @@ -133,36 +132,38 @@ impl Frame for ServerDeniesAuth { const TAG: FrameType = FrameType::ServerDeniesAuth; } -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum Error { - #[snafu(transparent)] + #[error(transparent)] Websocket { #[cfg(not(wasm_browser))] + #[error(from, std_err)] source: tokio_websockets::Error, #[cfg(wasm_browser)] + #[error(from, std_err)] source: ws_stream_wasm::WsErr, }, - #[snafu(display("Handshake stream ended prematurely"))] + #[display("Handshake stream ended prematurely")] UnexpectedEnd {}, - #[snafu(transparent)] - FrameTypeError { source: FrameTypeError }, - #[snafu(display("The relay denied our authentication ({reason})"))] + #[error(transparent)] + FrameTypeError { + #[error(from)] + source: FrameTypeError, + }, + #[display("The relay denied our authentication ({reason})")] ServerDeniedAuth { reason: String }, - #[snafu(display("Unexpected tag, got {frame_type:?}, but expected one of {expected_types:?}"))] + #[display("Unexpected tag, got {frame_type:?}, but expected one of {expected_types:?}")] UnexpectedFrameType { frame_type: FrameType, expected_types: Vec, }, - #[snafu(display("Handshake failed while deserializing {frame_type:?} frame"))] + #[display("Handshake failed while deserializing {frame_type:?} frame")] DeserializationError { frame_type: FrameType, + #[error(std_err)] source: postcard::Error, }, #[cfg(feature = "server")] @@ -171,21 +172,24 @@ pub enum Error { } #[cfg(feature = "server")] -#[derive(Debug, Snafu)] +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[error(std_sources)] pub(crate) enum VerificationError { - #[snafu(display("Couldn't export TLS keying material on our end"))] + #[display("Couldn't export TLS keying material on our end")] NoKeyingMaterial, - #[snafu(display( + #[display( "Client didn't extract the same keying material, the suffix mismatched: expected {expected:X?} but got {actual:X?}" - ))] + )] MismatchedSuffix { expected: [u8; 16], actual: [u8; 16], }, - #[snafu(display( + #[display( "Client signature {signature:X?} for message {message:X?} invalid for public key {public_key}" - ))] + )] SignatureInvalid { + #[error(std_err)] source: iroh_base::SignatureError, message: Vec, signature: [u8; 64], @@ -231,10 +235,13 @@ impl ClientAuth { let message = challenge.message_to_sign(); self.public_key .verify(&message, &Signature::from_bytes(&self.signature)) - .with_context(|_| SignatureInvalidSnafu { - message: message.to_vec(), - signature: self.signature, - public_key: self.public_key, + .context(|source| { + VerificationError::signature_invalid( + source, + message.to_vec(), + self.signature, + self.public_key, + ) }) .map_err(Box::new) } @@ -282,7 +289,7 @@ impl KeyMaterialClientAuth { &self, io: &impl ExportKeyingMaterial, ) -> Result<(), Box> { - use snafu::OptionExt; + // let key_material = io .export_keying_material( @@ -290,7 +297,7 @@ impl KeyMaterialClientAuth { DOMAIN_SEP_TLS_EXPORT_LABEL, Some(self.public_key.as_bytes()), ) - .context(NoKeyingMaterialSnafu)?; + .ok_or_else(|| VerificationError::no_keying_material())?; // We split the export and only sign the first 16 bytes, and // pass through the last 16 bytes. // Passing on the suffix helps the verifying end figure out what @@ -301,22 +308,24 @@ impl KeyMaterialClientAuth { // there must be something wrong with the client's secret key or signature. let (message, suffix) = key_material.split_at(16); let suffix: [u8; 16] = suffix.try_into().expect("hardcoded length"); - snafu::ensure!( - suffix == self.key_material_suffix, - MismatchedSuffixSnafu { - expected: self.key_material_suffix, - actual: suffix - } - ); + if suffix != self.key_material_suffix { + return Err(Box::new(VerificationError::mismatched_suffix( + self.key_material_suffix, + suffix, + ))); + } // NOTE: We don't blake3-hash here as we do it in [`ServerChallenge::message_to_sign`], // because we already have a domain separation string and keyed hashing step in // the TLS export keying material above. self.public_key .verify(message, &Signature::from_bytes(&self.signature)) - .with_context(|_| SignatureInvalidSnafu { - message: message.to_vec(), - public_key: self.public_key, - signature: self.signature, + .context(|source| { + VerificationError::signature_invalid( + source, + message.to_vec(), + self.signature, + self.public_key, + ) }) .map_err(Box::new) } @@ -353,10 +362,7 @@ pub(crate) async fn clientside( } FrameType::ServerDeniesAuth => { let denial: ServerDeniesAuth = deserialize_frame(frame)?; - Err(ServerDeniedAuthSnafu { - reason: denial.reason, - } - .build()) + Err(Error::server_denied_auth(denial.reason)) } _ => unreachable!(), } @@ -406,20 +412,10 @@ pub(crate) async fn serverside( if let Some(client_auth_header) = client_auth_header { let client_auth_bytes = data_encoding::BASE64URL_NOPAD .decode(client_auth_header.as_ref()) - .map_err(|_| { - ClientAuthHeaderInvalidSnafu { - value: client_auth_header.clone(), - } - .build() - })?; - - let client_auth: KeyMaterialClientAuth = - postcard::from_bytes(&client_auth_bytes).map_err(|_| { - ClientAuthHeaderInvalidSnafu { - value: client_auth_header.clone(), - } - .build() - })?; + .map_err(|_| Error::client_auth_header_invalid(client_auth_header.clone()))?; + + let client_auth: KeyMaterialClientAuth = postcard::from_bytes(&client_auth_bytes) + .map_err(|_| Error::client_auth_header_invalid(client_auth_header.clone()))?; if client_auth.verify(io).is_ok() { trace!(?client_auth.public_key, "authentication succeeded via keying material"); @@ -444,10 +440,7 @@ pub(crate) async fn serverside( reason: "signature invalid".into(), }; write_frame(io, denial.clone()).await?; - ServerDeniedAuthSnafu { - reason: denial.reason, - } - .fail() + return Err(Error::server_denied_auth(denial.reason)); } else { trace!(?client_auth.public_key, "authentication succeeded via challenge"); Ok(SuccessfulAuthentication { @@ -474,10 +467,7 @@ impl SuccessfulAuthentication { reason: "not authorized".into(), }; write_frame(io, denial.clone()).await?; - ServerDeniedAuthSnafu { - reason: denial.reason, - } - .fail() + return Err(Error::server_denied_auth(denial.reason)); } } } @@ -505,31 +495,29 @@ async fn read_frame( let mut payload = io .try_next() .await? - .ok_or_else(|| UnexpectedEndSnafu.build())?; + .ok_or_else(|| Error::unexpected_end())?; let frame_type = FrameType::from_bytes(&mut payload)?; trace!(?frame_type, "Reading frame"); - snafu::ensure!( + ensure!( expected_types.contains(&frame_type), - UnexpectedFrameTypeSnafu { - frame_type, - expected_types: expected_types.to_vec() - } + Error::unexpected_frame_type(frame_type, expected_types.to_vec()) ); Ok((frame_type, payload)) } fn deserialize_frame(frame: Bytes) -> Result { - postcard::from_bytes(&frame).context(DeserializationSnafu { frame_type: F::TAG }) + postcard::from_bytes(&frame) + .with_context(|| |source| Error::deserialization_error(F::TAG, source)) } #[cfg(all(test, feature = "server"))] mod tests { use bytes::BytesMut; use iroh_base::{PublicKey, SecretKey}; + use n0_error::{Result, ResultExt}; use n0_future::{Sink, SinkExt, Stream, TryStreamExt}; - use n0_snafu::{Result, ResultExt}; use rand::SeedableRng; use tokio_util::codec::{Framed, LengthDelimitedCodec}; use tracing::{Instrument, info_span}; diff --git a/iroh-relay/src/protos/relay.rs b/iroh-relay/src/protos/relay.rs index 6439c3409c0..ec90a520d3b 100644 --- a/iroh-relay/src/protos/relay.rs +++ b/iroh-relay/src/protos/relay.rs @@ -11,9 +11,8 @@ use std::num::NonZeroU16; use bytes::{Buf, BufMut, Bytes, BytesMut}; use iroh_base::{NodeId, SignatureError}; +use n0_error::{ResultExt, ensure}; use n0_future::time::Duration; -use nested_enum_utils::common_fields; -use snafu::{Backtrace, ResultExt, Snafu}; use super::common::{FrameType, FrameTypeError}; use crate::KeyCache; @@ -41,32 +40,40 @@ pub(crate) const PING_INTERVAL: Duration = Duration::from_secs(15); pub(crate) const PER_CLIENT_SEND_QUEUE_DEPTH: usize = 512; /// Protocol send errors. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum Error { - #[snafu(display("unexpected frame: got {got:?}, expected {expected:?}"))] + #[display("unexpected frame: got {got:?}, expected {expected:?}")] UnexpectedFrame { got: FrameType, expected: FrameType }, - #[snafu(display("Frame is too large, has {frame_len} bytes"))] + #[display("Frame is too large, has {frame_len} bytes")] FrameTooLarge { frame_len: usize }, - #[snafu(transparent)] - SerDe { source: postcard::Error }, - #[snafu(transparent)] - FrameTypeError { source: FrameTypeError }, - #[snafu(display("Invalid public key"))] - InvalidPublicKey { source: SignatureError }, - #[snafu(display("Invalid frame encoding"))] + #[error(transparent)] + SerDe { + #[error(from, std_err)] + source: postcard::Error, + }, + #[error(transparent)] + FrameTypeError { + #[error(from)] + source: FrameTypeError, + }, + #[display("Invalid public key")] + InvalidPublicKey { + #[error(from, std_err)] + source: SignatureError, + }, + #[display("Invalid frame encoding")] InvalidFrame {}, - #[snafu(display("Invalid frame type: {frame_type:?}"))] + #[display("Invalid frame type: {frame_type:?}")] InvalidFrameType { frame_type: FrameType }, - #[snafu(display("Invalid protocol message encoding"))] - InvalidProtocolMessageEncoding { source: std::str::Utf8Error }, - #[snafu(display("Too few bytes"))] + #[display("Invalid protocol message encoding")] + InvalidProtocolMessageEncoding { + #[error(from, std_err)] + source: std::str::Utf8Error, + }, + #[display("Too few bytes")] TooSmall {}, } @@ -220,9 +227,9 @@ impl Datagrams { fn from_bytes(mut bytes: Bytes, is_batch: bool) -> Result { if is_batch { // 1 bytes ECN, 2 bytes segment size - snafu::ensure!(bytes.len() >= 3, InvalidFrameSnafu); + ensure!(bytes.len() >= 3, Error::invalid_frame()); } else { - snafu::ensure!(bytes.len() >= 1, InvalidFrameSnafu); + ensure!(bytes.len() >= 1, Error::invalid_frame()); } let ecn_byte = bytes.get_u8(); @@ -329,18 +336,18 @@ impl RelayToClientMsg { pub(crate) fn from_bytes(mut content: Bytes, cache: &KeyCache) -> Result { let frame_type = FrameType::from_bytes(&mut content)?; let frame_len = content.len(); - snafu::ensure!( + ensure!( frame_len <= MAX_PACKET_SIZE, - FrameTooLargeSnafu { frame_len } + Error::frame_too_large(frame_len) ); let res = match frame_type { FrameType::RelayToClientDatagram | FrameType::RelayToClientDatagramBatch => { - snafu::ensure!(content.len() >= NodeId::LENGTH, InvalidFrameSnafu); + ensure!(content.len() >= NodeId::LENGTH, Error::invalid_frame()); let remote_node_id = cache .key_from_slice(&content[..NodeId::LENGTH]) - .context(InvalidPublicKeySnafu)?; + .context(Error::invalid_public_key)?; let datagrams = Datagrams::from_bytes( content.slice(NodeId::LENGTH..), frame_type == FrameType::RelayToClientDatagramBatch, @@ -351,41 +358,41 @@ impl RelayToClientMsg { } } FrameType::NodeGone => { - snafu::ensure!(content.len() == NodeId::LENGTH, InvalidFrameSnafu); + ensure!(content.len() == NodeId::LENGTH, Error::invalid_frame()); let node_id = cache .key_from_slice(content.as_ref()) - .context(InvalidPublicKeySnafu)?; + .context(Error::invalid_public_key)?; Self::NodeGone(node_id) } FrameType::Ping => { - snafu::ensure!(content.len() == 8, InvalidFrameSnafu); + ensure!(content.len() == 8, Error::invalid_frame()); let mut data = [0u8; 8]; data.copy_from_slice(&content[..8]); Self::Ping(data) } FrameType::Pong => { - snafu::ensure!(content.len() == 8, InvalidFrameSnafu); + ensure!(content.len() == 8, Error::invalid_frame()); let mut data = [0u8; 8]; data.copy_from_slice(&content[..8]); Self::Pong(data) } FrameType::Health => { let problem = std::str::from_utf8(&content) - .context(InvalidProtocolMessageEncodingSnafu)? + .context(Error::invalid_protocol_message_encoding)? .to_owned(); Self::Health { problem } } FrameType::Restarting => { - snafu::ensure!(content.len() == 4 + 4, InvalidFrameSnafu); + ensure!(content.len() == 4 + 4, Error::invalid_frame()); let reconnect_in = u32::from_be_bytes( content[..4] .try_into() - .map_err(|_| InvalidFrameSnafu.build())?, + .map_err(|_| Error::invalid_frame())?, ); let try_for = u32::from_be_bytes( content[4..] .try_into() - .map_err(|_| InvalidFrameSnafu.build())?, + .map_err(|_| Error::invalid_frame())?, ); let reconnect_in = Duration::from_millis(reconnect_in as u64); let try_for = Duration::from_millis(try_for as u64); @@ -395,7 +402,7 @@ impl RelayToClientMsg { } } _ => { - return Err(InvalidFrameTypeSnafu { frame_type }.build()); + return Err(Error::invalid_frame_type(frame_type)); } }; Ok(res) @@ -463,16 +470,16 @@ impl ClientToRelayMsg { pub(crate) fn from_bytes(mut content: Bytes, cache: &KeyCache) -> Result { let frame_type = FrameType::from_bytes(&mut content)?; let frame_len = content.len(); - snafu::ensure!( + ensure!( frame_len <= MAX_PACKET_SIZE, - FrameTooLargeSnafu { frame_len } + Error::frame_too_large(frame_len) ); let res = match frame_type { FrameType::ClientToRelayDatagram | FrameType::ClientToRelayDatagramBatch => { let dst_node_id = cache .key_from_slice(&content[..NodeId::LENGTH]) - .context(InvalidPublicKeySnafu)?; + .context(Error::invalid_public_key)?; let datagrams = Datagrams::from_bytes( content.slice(NodeId::LENGTH..), frame_type == FrameType::ClientToRelayDatagramBatch, @@ -483,19 +490,19 @@ impl ClientToRelayMsg { } } FrameType::Ping => { - snafu::ensure!(content.len() == 8, InvalidFrameSnafu); + ensure!(content.len() == 8, Error::invalid_frame()); let mut data = [0u8; 8]; data.copy_from_slice(&content[..8]); Self::Ping(data) } FrameType::Pong => { - snafu::ensure!(content.len() == 8, InvalidFrameSnafu); + ensure!(content.len() == 8, Error::invalid_frame()); let mut data = [0u8; 8]; data.copy_from_slice(&content[..8]); Self::Pong(data) } _ => { - return Err(InvalidFrameTypeSnafu { frame_type }.build()); + return Err(Error::invalid_frame_type(frame_type)); } }; Ok(res) @@ -507,7 +514,7 @@ impl ClientToRelayMsg { mod tests { use data_encoding::HEXLOWER; use iroh_base::SecretKey; - use n0_snafu::Result; + use n0_error::Result; use super::*; diff --git a/iroh-relay/src/quic.rs b/iroh-relay/src/quic.rs index b634802dda0..8d54af71297 100644 --- a/iroh-relay/src/quic.rs +++ b/iroh-relay/src/quic.rs @@ -3,9 +3,7 @@ use std::{net::SocketAddr, sync::Arc}; use n0_future::time::Duration; -use nested_enum_utils::common_fields; use quinn::{VarInt, crypto::rustls::QuicClientConfig}; -use snafu::{Backtrace, Snafu}; use tokio::sync::watch; /// ALPN for our quic addr discovery @@ -17,11 +15,11 @@ pub const QUIC_ADDR_DISC_CLOSE_REASON: &[u8] = b"finished"; #[cfg(feature = "server")] pub(crate) mod server { + use n0_error::ResultExt; use quinn::{ ApplicationClose, ConnectionError, crypto::rustls::{NoInitialCipherSuite, QuicServerConfig}, }; - use snafu::ResultExt; use tokio::task::JoinSet; use tokio_util::{sync::CancellationToken, task::AbortOnDropHandle}; use tracing::{Instrument, debug, info, info_span}; @@ -37,30 +35,20 @@ pub(crate) mod server { /// Server spawn errors #[allow(missing_docs)] - #[derive(Debug, Snafu)] + #[n0_error::add_location] + #[derive(n0_error::Error)] + #[error(std_sources)] #[non_exhaustive] pub enum QuicSpawnError { - #[snafu(transparent)] + #[error(transparent)] NoInitialCipherSuite { + #[error(from)] source: NoInitialCipherSuite, - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("Unable to spawn a QUIC endpoint server"))] - EndpointServer { - source: std::io::Error, - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("Unable to get the local address from the endpoint"))] - LocalAddr { - source: std::io::Error, - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, }, + #[display("Unable to spawn a QUIC endpoint server")] + EndpointServer { source: std::io::Error }, + #[display("Unable to get the local address from the endpoint")] + LocalAddr { source: std::io::Error }, } impl QuicServer { @@ -114,8 +102,8 @@ pub(crate) mod server { .send_observed_address_reports(true); let endpoint = quinn::Endpoint::server(server_config, quic_config.bind_addr) - .context(EndpointServerSnafu)?; - let bind_addr = endpoint.local_addr().context(LocalAddrSnafu)?; + .context(QuicSpawnError::endpoint_server)?; + let bind_addr = endpoint.local_addr().context(QuicSpawnError::local_addr)?; info!(?bind_addr, "QUIC server listening on"); @@ -228,20 +216,17 @@ pub(crate) mod server { } /// Quic client related errors. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] +#[error(std_sources, from_sources)] #[non_exhaustive] pub enum Error { - #[snafu(transparent)] + #[error(transparent)] Connect { source: quinn::ConnectError }, - #[snafu(transparent)] + #[error(transparent)] Connection { source: quinn::ConnectionError }, - #[snafu(transparent)] + #[error(transparent)] WatchRecv { source: watch::error::RecvError }, } @@ -358,11 +343,11 @@ impl QuicClient { mod tests { use std::net::Ipv4Addr; + use n0_error::{AnyError as Error, Result, ResultExt}; use n0_future::{ task::AbortOnDropHandle, time::{self, Instant}, }; - use n0_snafu::{Error, Result, ResultExt}; use quinn::crypto::rustls::QuicServerConfig; use tracing::{Instrument, debug, info, info_span}; use tracing_test::traced_test; diff --git a/iroh-relay/src/server.rs b/iroh-relay/src/server.rs index f3dd2b5c752..ad1740d1eb9 100644 --- a/iroh-relay/src/server.rs +++ b/iroh-relay/src/server.rs @@ -26,9 +26,8 @@ use hyper::body::Incoming; use iroh_base::NodeId; #[cfg(feature = "test-utils")] use iroh_base::RelayUrl; +use n0_error::ResultExt; use n0_future::{StreamExt, future::Boxed}; -use nested_enum_utils::common_fields; -use snafu::{Backtrace, ResultExt, Snafu}; use tokio::{ net::TcpListener, task::{JoinError, JoinSet}, @@ -268,48 +267,48 @@ pub struct Server { } /// Server spawn errors -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] +#[error(std_sources)] #[non_exhaustive] pub enum SpawnError { - #[snafu(display("Unable to get local address"))] + #[display("Unable to get local address")] LocalAddr { source: std::io::Error }, - #[snafu(display("Failed to bind QAD listener"))] + #[display("Failed to bind QAD listener")] QuicSpawn { source: QuicSpawnError }, - #[snafu(display("Failed to parse TLS header"))] + #[display("Failed to parse TLS header")] TlsHeaderParse { source: InvalidHeaderValue }, - #[snafu(display("Failed to bind TcpListener"))] + #[display("Failed to bind TcpListener")] BindTlsListener { source: std::io::Error }, - #[snafu(display("No local address"))] + #[display("No local address")] NoLocalAddr { source: std::io::Error }, - #[snafu(display("Failed to bind server socket to {addr}"))] + #[display("Failed to bind server socket to {addr}")] BindTcpListener { addr: SocketAddr }, } /// Server task errors -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] +#[error(std_sources)] #[non_exhaustive] pub enum SupervisorError { - #[snafu(display("Error starting metrics server"))] - Metrics { source: std::io::Error }, - #[snafu(display("Acme event stream finished"))] + #[display("Error starting metrics server")] + Metrics { + #[error(from, std_err)] + source: std::io::Error, + }, + #[display("Acme event stream finished")] AcmeEventStreamFinished {}, - #[snafu(transparent)] - JoinError { source: JoinError }, - #[snafu(display("No relay services are enabled"))] + #[error(transparent)] + JoinError { + #[error(from)] + source: JoinError, + }, + #[display("No relay services are enabled")] NoRelayServicesEnabled {}, - #[snafu(display("Task cancelled"))] + #[display("Task cancelled")] TaskCancelled {}, } @@ -320,7 +319,7 @@ impl Server { EC: fmt::Debug + 'static, EA: fmt::Debug + 'static, { - let mut tasks = JoinSet::new(); + let mut tasks: JoinSet> = JoinSet::new(); let metrics = RelayMetrics::default(); @@ -333,7 +332,7 @@ impl Server { async move { iroh_metrics::service::start_metrics_server(addr, Arc::new(registry)) .await - .context(MetricsSnafu) + .context(SupervisorError::metrics) } .instrument(info_span!("metrics-server")), ); @@ -351,7 +350,7 @@ impl Server { let quic_server = match config.quic { Some(quic_config) => { debug!("Starting QUIC server {}", quic_config.bind_addr); - Some(QuicServer::spawn(quic_config).context(QuicSpawnSnafu)?) + Some(QuicServer::spawn(quic_config).context(SpawnError::quic_spawn)?) } None => None, }; @@ -363,7 +362,7 @@ impl Server { debug!("Starting Relay server"); let mut headers = HeaderMap::new(); for (name, value) in TLS_HEADERS.iter() { - headers.insert(*name, value.parse().context(TlsHeaderParseSnafu)?); + headers.insert(*name, value.parse().context(SpawnError::tls_header_parse)?); } let relay_bind_addr = match relay_config.tls { Some(ref tls) => tls.https_bind_addr, @@ -398,7 +397,7 @@ impl Server { Err(err) => error!("error: {err:?}"), } } - Err(AcmeEventStreamFinishedSnafu.build()) + Err(SupervisorError::acme_event_stream_finished()) } .instrument(info_span!("acme")), ); @@ -424,8 +423,10 @@ impl Server { // these standalone. let http_listener = TcpListener::bind(&relay_config.http_bind_addr) .await - .context(BindTlsListenerSnafu)?; - let http_addr = http_listener.local_addr().context(NoLocalAddrSnafu)?; + .context(SpawnError::bind_tls_listener)?; + let http_addr = http_listener + .local_addr() + .context(SpawnError::no_local_addr)?; tasks.spawn( async move { run_captive_portal_service(http_listener).await; @@ -567,7 +568,7 @@ async fn relay_supervisor( Some(ret) = tasks.join_next() => ret, ret = &mut quic_fut, if quic_enabled => ret.map(Ok), ret = &mut relay_fut, if relay_enabled => ret.map(Ok), - else => Ok(Err(NoRelayServicesEnabledSnafu.build())), + else => Ok(Err(SupervisorError::no_relay_services_enabled())), }; let ret = match res { Ok(Ok(())) => { @@ -584,7 +585,7 @@ async fn relay_supervisor( std::panic::resume_unwind(panic); } debug!("Task cancelled"); - Err(TaskCancelledSnafu.build()) + Err(SupervisorError::task_cancelled()) } }; @@ -762,8 +763,8 @@ mod tests { use http::StatusCode; use iroh_base::{NodeId, RelayUrl, SecretKey}; + use n0_error::Result; use n0_future::{FutureExt, SinkExt, StreamExt}; - use n0_snafu::Result; use rand::SeedableRng; use tracing::{info, instrument}; use tracing_test::traced_test; diff --git a/iroh-relay/src/server/client.rs b/iroh-relay/src/server/client.rs index afdfaeb7de5..9abf706c7b3 100644 --- a/iroh-relay/src/server/client.rs +++ b/iroh-relay/src/server/client.rs @@ -3,10 +3,9 @@ use std::{collections::HashSet, sync::Arc, time::Duration}; use iroh_base::NodeId; +use n0_error::StackErrorExt; use n0_future::{SinkExt, StreamExt}; -use nested_enum_utils::common_fields; use rand::Rng; -use snafu::{Backtrace, GenerateImplicitData, Snafu}; use time::{Date, OffsetDateTime}; use tokio::{ sync::mpsc::{self, error::TrySendError}, @@ -168,98 +167,82 @@ impl Client { } /// Error for [`Actor::handle_frame`] -#[common_fields({ - backtrace: Option, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum HandleFrameError { - #[snafu(transparent)] - ForwardPacket { source: ForwardPacketError }, - #[snafu(display("Stream terminated"))] + #[error(transparent)] + ForwardPacket { + #[error(from)] + source: ForwardPacketError, + }, + #[display("Stream terminated")] StreamTerminated {}, - #[snafu(transparent)] - Recv { source: RelayRecvError }, - #[snafu(transparent)] - Send { source: WriteFrameError }, + #[error(transparent)] + Recv { + #[error(from)] + source: RelayRecvError, + }, + #[error(transparent)] + Send { + #[error(from)] + source: WriteFrameError, + }, } /// Error for [`Actor::write_frame`] -#[common_fields({ - backtrace: Option, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum WriteFrameError { - #[snafu(transparent)] - Stream { source: RelaySendError }, - #[snafu(transparent)] - Timeout { source: tokio::time::error::Elapsed }, + #[error(transparent)] + Stream { + #[error(from)] + source: RelaySendError, + }, + #[error(transparent)] + Timeout { + #[error(from, std_err)] + source: tokio::time::error::Elapsed, + }, } /// Run error -#[common_fields({ - backtrace: Option, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum RunError { - #[snafu(transparent)] - ForwardPacket { source: ForwardPacketError }, - #[snafu(display("Flush"))] - Flush { - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, + #[error(transparent)] + ForwardPacket { + #[error(from)] + source: ForwardPacketError, }, - #[snafu(transparent)] - HandleFrame { source: HandleFrameError }, - #[snafu(display("Server.disco_send_queue dropped"))] - DiscoSendQueuePacketDrop { - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("Failed to send disco packet"))] - DiscoPacketSend { - source: WriteFrameError, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("Server.send_queue dropped"))] - SendQueuePacketDrop { - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("Failed to send packet"))] - PacketSend { - source: WriteFrameError, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("Server.node_gone dropped"))] - NodeGoneDrop { - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("NodeGone write frame failed"))] - NodeGoneWriteFrame { - source: WriteFrameError, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("Keep alive write frame failed"))] - KeepAliveWriteFrame { - source: WriteFrameError, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("Tick flush"))] - TickFlush { - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, + #[display("Flush")] + Flush {}, + #[error(transparent)] + HandleFrame { + #[error(from)] + source: HandleFrameError, }, + #[display("Server.disco_send_queue dropped")] + DiscoSendQueuePacketDrop {}, + #[display("Failed to send disco packet")] + DiscoPacketSend { source: WriteFrameError }, + #[display("Server.send_queue dropped")] + SendQueuePacketDrop {}, + #[display("Failed to send packet")] + PacketSend { source: WriteFrameError }, + #[display("Server.node_gone dropped")] + NodeGoneDrop {}, + #[display("NodeGone write frame failed")] + NodeGoneWriteFrame { source: WriteFrameError }, + #[display("Keep alive write frame failed")] + KeepAliveWriteFrame { source: WriteFrameError }, + #[display("Tick flush")] + TickFlush {}, } /// Manages all the reads and writes to this client. It periodically sends a `KEEP_ALIVE` @@ -326,7 +309,7 @@ impl Actor { } async fn run_inner(&mut self, done: CancellationToken) -> Result<(), RunError> { - use snafu::ResultExt; + use n0_error::ResultExt; // Add some jitter to ping pong interactions, to avoid all pings being sent at the same time let next_interval = || { @@ -346,7 +329,7 @@ impl Actor { _ = done.cancelled() => { trace!("actor loop cancelled, exiting"); // final flush - self.stream.flush().await.map_err(|_| FlushSnafu.build())?; + self.stream.flush().await.map_err(|_| RunError::flush())?; break; } maybe_frame = self.stream.next() => { @@ -356,19 +339,19 @@ impl Actor { } // First priority, disco packets packet = self.disco_send_queue.recv() => { - let packet = packet.ok_or(DiscoSendQueuePacketDropSnafu.build())?; - self.send_disco_packet(packet).await.context(DiscoPacketSendSnafu)?; + let packet = packet.ok_or(RunError::disco_send_queue_packet_drop())?; + self.send_disco_packet(packet).await.context(RunError::disco_packet_send)?; } // Second priority, sending regular packets packet = self.send_queue.recv() => { - let packet = packet.ok_or(SendQueuePacketDropSnafu.build())?; - self.send_packet(packet).await.context(PacketSendSnafu)?; + let packet = packet.ok_or(RunError::send_queue_packet_drop())?; + self.send_packet(packet).await.context(RunError::packet_send)?; } // Last priority, sending left nodes node_id = self.node_gone.recv() => { - let node_id = node_id.ok_or(NodeGoneDropSnafu.build())?; + let node_id = node_id.ok_or(RunError::node_gone_drop())?; trace!("node_id gone: {:?}", node_id); - self.write_frame(RelayToClientMsg::NodeGone(node_id)).await.context(NodeGoneWriteFrameSnafu)?; + self.write_frame(RelayToClientMsg::NodeGone(node_id)).await.context(RunError::node_gone_write_frame)?; } _ = self.ping_tracker.timeout() => { trace!("pong timed out"); @@ -379,14 +362,14 @@ impl Actor { // new interval ping_interval.reset_after(next_interval()); let data = self.ping_tracker.new_ping(); - self.write_frame(RelayToClientMsg::Ping(data)).await.context(KeepAliveWriteFrameSnafu)?; + self.write_frame(RelayToClientMsg::Ping(data)).await.context(RunError::keep_alive_write_frame)?; } } self.stream .flush() .await - .map_err(|_| TickFlushSnafu.build())?; + .map_err(|_| RunError::tick_flush())?; } Ok(()) } @@ -453,7 +436,7 @@ impl Actor { trace!(?maybe_frame, "handle incoming frame"); let frame = match maybe_frame { Some(frame) => frame?, - None => return Err(StreamTerminatedSnafu.build()), + None => return Err(HandleFrameError::stream_terminated()), }; match frame { @@ -512,23 +495,15 @@ pub(crate) enum SendError { Closed, } -#[derive(Debug, Snafu)] -#[snafu(display("failed to forward {scope:?} packet: {reason:?}"))] +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[display("failed to forward {scope:?} packet: {reason:?}")] pub(crate) struct ForwardPacketError { scope: PacketScope, reason: SendError, - backtrace: Option, } -impl ForwardPacketError { - pub(crate) fn new(scope: PacketScope, reason: SendError) -> Self { - Self { - scope, - reason, - backtrace: GenerateImplicitData::generate(), - } - } -} +impl ForwardPacketError {} /// Tracks how many unique nodes have been seen during the last day. #[derive(Debug)] @@ -565,8 +540,8 @@ impl ClientCounter { #[cfg(test)] mod tests { use iroh_base::SecretKey; + use n0_error::{Result, ResultExt, whatever}; use n0_future::Stream; - use n0_snafu::{Result, ResultExt}; use rand::SeedableRng; use tracing::info; use tracing_test::traced_test; @@ -575,7 +550,7 @@ mod tests { use crate::{client::conn::Conn, protos::common::FrameType}; async fn recv_frame< - E: snafu::Error + Sync + Send + 'static, + E: std::error::Error + Sync + Send + 'static, S: Stream> + Unpin, >( frame_type: FrameType, @@ -584,7 +559,7 @@ mod tests { match stream.next().await { Some(Ok(frame)) => { if frame_type != frame.typ() { - snafu::whatever!( + whatever!( "Unexpected frame, got {:?}, but expected {:?}", frame.typ(), frame_type @@ -593,7 +568,7 @@ mod tests { Ok(frame) } Some(Err(err)) => Err(err).e(), - None => snafu::whatever!("Unexpected EOF, expected frame {frame_type:?}"), + None => whatever!("Unexpected EOF, expected frame {frame_type:?}"), } } diff --git a/iroh-relay/src/server/clients.rs b/iroh-relay/src/server/clients.rs index fc4927b78a6..e328920f119 100644 --- a/iroh-relay/src/server/clients.rs +++ b/iroh-relay/src/server/clients.rs @@ -194,8 +194,8 @@ mod tests { use std::time::Duration; use iroh_base::SecretKey; + use n0_error::{Result, ResultExt, whatever}; use n0_future::{Stream, StreamExt}; - use n0_snafu::{Result, ResultExt}; use rand::SeedableRng; use super::*; @@ -206,7 +206,7 @@ mod tests { }; async fn recv_frame< - E: snafu::Error + Sync + Send + 'static, + E: std::error::Error + Sync + Send + 'static, S: Stream> + Unpin, >( frame_type: FrameType, @@ -215,7 +215,7 @@ mod tests { match stream.next().await { Some(Ok(frame)) => { if frame_type != frame.typ() { - snafu::whatever!( + whatever!( "Unexpected frame, got {:?}, but expected {:?}", frame.typ(), frame_type @@ -224,7 +224,7 @@ mod tests { Ok(frame) } Some(Err(err)) => Err(err).e(), - None => snafu::whatever!("Unexpected EOF, expected frame {frame_type:?}"), + None => whatever!("Unexpected EOF, expected frame {frame_type:?}"), } } diff --git a/iroh-relay/src/server/http_server.rs b/iroh-relay/src/server/http_server.rs index 2ed7b5d558c..9dc64fa2c4a 100644 --- a/iroh-relay/src/server/http_server.rs +++ b/iroh-relay/src/server/http_server.rs @@ -15,9 +15,9 @@ use hyper::{ service::Service, upgrade::Upgraded, }; +// use nested_enum_utils::common_fields; +use n0_error::{ResultExt, ensure}; use n0_future::time::Elapsed; -use nested_enum_utils::common_fields; -use snafu::{Backtrace, OptionExt, ResultExt, Snafu}; use tokio::net::{TcpListener, TcpStream}; use tokio_rustls_acme::AcmeAcceptor; use tokio_util::{sync::CancellationToken, task::AbortOnDropHandle}; @@ -37,12 +37,13 @@ use crate::{ streams::WsBytesFramed, }, server::{ - BindTcpListenerSnafu, ClientRateLimit, NoLocalAddrSnafu, + ClientRateLimit, client::Config, metrics::Metrics, streams::{MaybeTlsStream, RateLimited, RelayedStream}, }, }; +// keep ResultExt imported above type BytesBody = http_body_util::Full; type HyperError = Box; @@ -77,7 +78,7 @@ fn body_full(content: impl Into) -> BytesBody { fn downcast_upgrade(upgraded: Upgraded) -> Result<(MaybeTlsStream, Bytes), ConnectionHandlerError> { match upgraded.downcast::>() { Ok(parts) => Ok((parts.io.into_inner(), parts.read_buf)), - Err(_) => Err(DowncastUpgradeSnafu.build()), + Err(_) => Err(ConnectionHandlerError::downcast_upgrade()), } } @@ -151,92 +152,54 @@ pub(super) struct TlsConfig { } /// Errors when attempting to upgrade and -#[common_fields({ - backtrace: Option, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] +#[error(std_sources)] #[non_exhaustive] pub enum ServeConnectionError { - #[snafu(display("TLS[acme] handshake"))] - TlsHandshake { - source: std::io::Error, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("TLS[acme] serve connection"))] - ServeConnection { - source: hyper::Error, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("TLS[manual] timeout"))] - Timeout { - source: Elapsed, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("TLS[manual] accept"))] - ManualAccept { - source: std::io::Error, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("TLS[acme] accept"))] - LetsEncryptAccept { - source: std::io::Error, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("HTTPS connection"))] - Https { - source: hyper::Error, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("HTTP connection"))] - Http { - source: hyper::Error, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, + #[display("TLS[acme] handshake")] + TlsHandshake { source: std::io::Error }, + #[display("TLS[acme] serve connection")] + ServeConnection { source: hyper::Error }, + #[display("TLS[manual] timeout")] + Timeout { source: Elapsed }, + #[display("TLS[manual] accept")] + ManualAccept { source: std::io::Error }, + #[display("TLS[acme] accept")] + LetsEncryptAccept { source: std::io::Error }, + #[display("HTTPS connection")] + Https { source: hyper::Error }, + #[display("HTTP connection")] + Http { source: hyper::Error }, } /// Server accept errors. -#[common_fields({ - backtrace: Option, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] +#[error(std_sources, from_sources)] #[non_exhaustive] pub enum AcceptError { - #[snafu(transparent)] + #[error(transparent)] Handshake { source: handshake::Error }, - #[snafu(display("rate limiting misconfigured"))] + #[display("rate limiting misconfigured")] RateLimitingMisconfigured { source: InvalidBucketConfig }, } /// Server connection errors, includes errors that can happen on `accept`. -#[common_fields({ - backtrace: Option, -})] +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] +#[error(from_sources)] #[non_exhaustive] pub enum ConnectionHandlerError { - #[snafu(transparent)] + #[error(transparent)] Accept { source: AcceptError }, - #[snafu(display("Could not downcast the upgraded connection to MaybeTlsStream"))] - DowncastUpgrade { - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, - #[snafu(display("Cannot deal with buffered data yet: {buf:?}"))] - BufferNotEmpty { - buf: Bytes, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, - }, + #[display("Could not downcast the upgraded connection to MaybeTlsStream")] + DowncastUpgrade {}, + #[display("Cannot deal with buffered data yet: {buf:?}")] + BufferNotEmpty { buf: Bytes }, } /// Builder for the Relay HTTP Server. @@ -339,7 +302,7 @@ impl ServerBuilder { /// Builds and spawns an HTTP(S) Relay Server. pub(super) async fn spawn(self) -> Result { - use snafu::ResultExt; + use n0_error::ResultExt; let cancel_token = CancellationToken::new(); @@ -359,9 +322,9 @@ impl ServerBuilder { let listener = TcpListener::bind(&addr) .await - .map_err(|_| BindTcpListenerSnafu { addr }.build())?; + .context(|_| SpawnError::bind_tcp_listener(addr))?; - let addr = listener.local_addr().context(NoLocalAddrSnafu)?; + let addr = listener.local_addr().context(SpawnError::no_local_addr)?; let http_str = tls_config.as_ref().map_or("HTTP/WS", |_| "HTTPS/WSS"); info!("[{http_str}] relay: serving on {addr}"); @@ -432,22 +395,23 @@ struct Inner { metrics: Arc, } -#[derive(Debug, Snafu)] +#[n0_error::add_location] +#[derive(n0_error::Error)] enum RelayUpgradeReqError { - #[snafu(display("missing header: {header}"))] + #[display("missing header: {header}")] MissingHeader { header: http::HeaderName }, - #[snafu(display("invalid header value for {header}: {details}"))] + #[display("invalid header value for {header}: {details}")] InvalidHeader { header: http::HeaderName, details: String, }, - #[snafu(display( + #[display( "invalid header value for {SEC_WEBSOCKET_VERSION}: unsupported websocket version, only supporting {SUPPORTED_WEBSOCKET_VERSION}" - ))] - UnsupportedWebsocketVersion, - #[snafu(display( + )] + UnsupportedWebsocketVersion {}, + #[display( "invalid header value for {SEC_WEBSOCKET_PROTOCOL}: unsupported relay version: we support {we_support} but you only provide {you_support}" - ))] + )] UnsupportedRelayVersion { we_support: &'static str, you_support: String, @@ -474,42 +438,44 @@ impl RelayService { ) -> Result<&HeaderValue, RelayUpgradeReqError> { req.headers() .get(&header) - .context(MissingHeaderSnafu { header }) + .ok_or_else(|| RelayUpgradeReqError::missing_header(header.clone())) } let upgrade_header = expect_header(&req, UPGRADE)?; - snafu::ensure!( + ensure!( upgrade_header == HeaderValue::from_static(WEBSOCKET_UPGRADE_PROTOCOL), - InvalidHeaderSnafu { - header: UPGRADE, - details: format!("value must be {WEBSOCKET_UPGRADE_PROTOCOL}"), - } + RelayUpgradeReqError::invalid_header( + UPGRADE, + format!("value must be {WEBSOCKET_UPGRADE_PROTOCOL}") + ) ); let key = expect_header(&req, SEC_WEBSOCKET_KEY)?.clone(); let version = expect_header(&req, SEC_WEBSOCKET_VERSION)?.clone(); - snafu::ensure!( + ensure!( version.as_bytes() == SUPPORTED_WEBSOCKET_VERSION.as_bytes(), - UnsupportedWebsocketVersionSnafu + RelayUpgradeReqError::unsupported_websocket_version() ); let subprotocols = expect_header(&req, SEC_WEBSOCKET_PROTOCOL)? .to_str() .ok() - .context(InvalidHeaderSnafu { - header: SEC_WEBSOCKET_PROTOCOL, - details: "header value is not ascii".to_string(), + .ok_or_else(|| { + RelayUpgradeReqError::invalid_header( + SEC_WEBSOCKET_PROTOCOL, + "header value is not ascii".to_string(), + ) })?; let supports_our_version = subprotocols .split_whitespace() .any(|p| p == RELAY_PROTOCOL_VERSION); - snafu::ensure!( + ensure!( supports_our_version, - UnsupportedRelayVersionSnafu { - we_support: RELAY_PROTOCOL_VERSION, - you_support: subprotocols.to_string(), - } + RelayUpgradeReqError::unsupported_relay_version( + RELAY_PROTOCOL_VERSION, + subprotocols.to_string() + ) ); let client_auth_header = req.headers().get(CLIENT_AUTH_HEADER).cloned(); @@ -576,7 +542,7 @@ impl Service> for RelayService { let res = match self.handle_relay_ws_upgrade(req) { Ok(response) => Ok(response), // It's convention to send back the version(s) we *do* support - Err(e @ RelayUpgradeReqError::UnsupportedWebsocketVersion) => self + Err(e @ RelayUpgradeReqError::UnsupportedWebsocketVersion { .. }) => self .build_response() .status(StatusCode::BAD_REQUEST) .header(SEC_WEBSOCKET_VERSION, SUPPORTED_WEBSOCKET_VERSION) @@ -638,7 +604,7 @@ impl Inner { debug!("relay_connection upgraded"); let (io, read_buf) = downcast_upgrade(upgraded)?; if !read_buf.is_empty() { - return Err(BufferNotEmptySnafu { buf: read_buf }.build()); + return Err(ConnectionHandlerError::buffer_not_empty(read_buf)); } self.accept(io, client_auth_header).await?; @@ -663,7 +629,7 @@ impl Inner { trace!("accept: start"); let io = RateLimited::from_cfg(self.rate_limit, io, self.metrics.clone()) - .context(RateLimitingMisconfiguredSnafu)?; + .context(AcceptError::rate_limiting_misconfigured)?; // Create a server builder with default config let websocket = tokio_websockets::ServerBuilder::new() @@ -755,7 +721,7 @@ impl RelayService { debug!("HTTP: serve connection"); self.serve_connection(MaybeTlsStream::Plain(stream)) .await - .context(HttpSnafu) + .context(ServeConnectionError::http) } }; match res { @@ -791,7 +757,11 @@ impl RelayService { let TlsConfig { acceptor, config } = tls_config; match acceptor { TlsAcceptor::LetsEncrypt(a) => { - match a.accept(stream).await.context(LetsEncryptAcceptSnafu)? { + match a + .accept(stream) + .await + .context(ServeConnectionError::lets_encrypt_accept)? + { None => { info!("TLS[acme]: received TLS-ALPN-01 validation request"); } @@ -800,10 +770,10 @@ impl RelayService { let tls_stream = start_handshake .into_stream(config) .await - .context(TlsHandshakeSnafu)?; + .context(ServeConnectionError::tls_handshake)?; self.serve_connection(MaybeTlsStream::Tls(tls_stream)) .await - .context(HttpsSnafu)?; + .context(ServeConnectionError::https)?; } } } @@ -811,12 +781,12 @@ impl RelayService { debug!("TLS[manual]: accept"); let tls_stream = tokio::time::timeout(Duration::from_secs(30), a.accept(stream)) .await - .context(TimeoutSnafu)? - .context(ManualAcceptSnafu)?; + .context(ServeConnectionError::timeout)? + .context(ServeConnectionError::manual_accept)?; self.serve_connection(MaybeTlsStream::Tls(tls_stream)) .await - .context(ServeConnectionSnafu)?; + .context(ServeConnectionError::serve_connection)?; } } Ok(()) @@ -866,11 +836,10 @@ mod tests { use std::sync::Arc; use iroh_base::{PublicKey, SecretKey}; + use n0_error::{Result, ResultExt, whatever}; use n0_future::{SinkExt, StreamExt}; - use n0_snafu::{Result, ResultExt}; use rand::SeedableRng; use reqwest::Url; - use snafu::whatever; use tracing::info; use tracing_test::traced_test; diff --git a/iroh-relay/src/server/streams.rs b/iroh-relay/src/server/streams.rs index 545e61bd494..70905c0d25f 100644 --- a/iroh-relay/src/server/streams.rs +++ b/iroh-relay/src/server/streams.rs @@ -6,8 +6,8 @@ use std::{ task::{Context, Poll}, }; +use n0_error::{StackErrorExt, ensure}; use n0_future::{FutureExt, Sink, Stream, ready, time}; -use snafu::{Backtrace, Snafu}; use tokio::io::{AsyncRead, AsyncWrite}; use tracing::instrument; @@ -75,14 +75,18 @@ impl RelayedStream { } /// Relay send errors -#[derive(Debug, Snafu)] +#[n0_error::add_location] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum SendError { - #[snafu(transparent)] - StreamError { source: StreamError }, - #[snafu(display("Packet exceeds max packet size"))] + #[error(transparent)] + StreamError { + #[error(from, std_err)] + source: StreamError, + }, + #[display("Packet exceeds max packet size")] ExceedsMaxPacketSize { size: usize }, - #[snafu(display("Attempted to send empty packet"))] + #[display("Attempted to send empty packet")] EmptyPacket {}, } @@ -95,9 +99,12 @@ impl Sink for RelayedStream { fn start_send(mut self: Pin<&mut Self>, item: RelayToClientMsg) -> Result<(), Self::Error> { let size = item.encoded_len(); - snafu::ensure!(size <= MAX_PACKET_SIZE, ExceedsMaxPacketSizeSnafu { size }); + ensure!( + size <= MAX_PACKET_SIZE, + SendError::exceeds_max_packet_size(size) + ); if let RelayToClientMsg::Datagrams { datagrams, .. } = &item { - snafu::ensure!(!datagrams.contents.is_empty(), EmptyPacketSnafu); + ensure!(!datagrams.contents.is_empty(), SendError::empty_packet()); } Pin::new(&mut self.inner) @@ -115,13 +122,20 @@ impl Sink for RelayedStream { } /// Relay receive errors -#[derive(Debug, Snafu)] +#[n0_error::add_location] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum RecvError { - #[snafu(transparent)] - Proto { source: ProtoError }, - #[snafu(transparent)] - StreamError { source: StreamError }, + #[error(transparent)] + Proto { + #[error(from)] + source: ProtoError, + }, + #[error(transparent)] + StreamError { + #[error(from, std_err)] + source: StreamError, + }, } impl Stream for RelayedStream { @@ -277,12 +291,11 @@ struct Bucket { refill: i64, } +#[n0_error::add_location] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[derive(n0_error::Error)] +#[display("Invalid bucket config")] pub struct InvalidBucketConfig { - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, max: i64, bytes_per_second: i64, refill_period: time::Duration, @@ -296,13 +309,9 @@ impl Bucket { ) -> Result { // milliseconds is the tokio timer resolution let refill = bytes_per_second.saturating_mul(refill_period.as_millis() as i64) / 1000; - snafu::ensure!( + ensure!( max > 0 && bytes_per_second > 0 && refill_period.as_millis() as u32 > 0 && refill > 0, - InvalidBucketConfigSnafu { - max, - bytes_per_second, - refill_period, - }, + InvalidBucketConfig::new(max, bytes_per_second, refill_period), ); Ok(Self { fill: max, @@ -480,8 +489,8 @@ impl AsyncWrite for RateLimited { mod tests { use std::sync::Arc; + use n0_error::{Result, ResultExt}; use n0_future::time; - use n0_snafu::{Result, ResultExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tracing_test::traced_test; diff --git a/iroh/Cargo.toml b/iroh/Cargo.toml index cc109069bfd..ebafa727578 100644 --- a/iroh/Cargo.toml +++ b/iroh/Cargo.toml @@ -40,6 +40,7 @@ http = "1" iroh-base = { version = "0.92.0", default-features = false, features = ["key", "relay"], path = "../iroh-base" } iroh-relay = { version = "0.92", path = "../iroh-relay", default-features = false } n0-future = "0.1.2" +n0-error = { path = "../../n0-error" } n0-snafu = "0.2.2" n0-watcher = "0.3" nested_enum_utils = "0.2.1" @@ -117,6 +118,7 @@ tokio = { version = "1", features = [ "signal", "process", ] } +n0-error = { path = "../../n0-error" } surge-ping = "0.8.0" # wasm-in-browser dependencies diff --git a/iroh/src/discovery.rs b/iroh/src/discovery.rs index db902b912ff..f6fd76ef74e 100644 --- a/iroh/src/discovery.rs +++ b/iroh/src/discovery.rs @@ -111,6 +111,7 @@ use std::sync::{Arc, RwLock}; use iroh_base::{NodeAddr, NodeId}; +use n0_error::ensure; use n0_future::{ boxed::BoxStream, stream::StreamExt, @@ -118,7 +119,6 @@ use n0_future::{ time::{self, Duration}, }; use nested_enum_utils::common_fields; -use snafu::{IntoError, Snafu, ensure}; use tokio::sync::oneshot; use tracing::{Instrument, debug, error_span, warn}; @@ -217,20 +217,18 @@ impl DiscoveryContext<'_> { } /// IntoDiscovery errors -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[error(std_sources)] #[allow(missing_docs)] -#[derive(Debug, Snafu)] #[non_exhaustive] -#[snafu(module)] pub enum IntoDiscoveryError { - #[snafu(display("Service '{provenance}' error"))] + /// Service '{provenance}' error + #[display("Service '{provenance}' error")] User { provenance: &'static str, - source: Box, + #[error(from)] + source: n0_error::AnyError, }, } @@ -240,7 +238,7 @@ impl IntoDiscoveryError { provenance: &'static str, source: T, ) -> Self { - into_discovery_error::UserSnafu { provenance }.into_error(Box::new(source)) + Self::user(provenance, n0_error::AnyError::from_std(source)) } /// Creates a new user error from an arbitrary boxed error type. @@ -248,28 +246,29 @@ impl IntoDiscoveryError { provenance: &'static str, source: Box, ) -> Self { - into_discovery_error::UserSnafu { provenance }.into_error(source) + Self::user(provenance, n0_error::AnyError::from_std(source)) } } /// Discovery errors -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] +#[n0_error::add_location] +#[derive(n0_error::Error)] +#[error(std_sources)] #[allow(missing_docs)] -#[derive(Debug, Snafu)] #[non_exhaustive] pub enum DiscoveryError { - #[snafu(display("No discovery service configured"))] + /// No discovery service configured + #[display("No discovery service configured")] NoServiceConfigured {}, - #[snafu(display("Discovery produced no results for {}", node_id.fmt_short()))] + /// Discovery produced no results for node + #[display("Discovery produced no results for {}", node_id.fmt_short())] NoResults { node_id: NodeId }, - #[snafu(display("Service '{provenance}' error"))] + /// Service '{provenance}' error + #[display("Service '{provenance}' error")] User { provenance: &'static str, - source: Box, + #[error(from)] + source: n0_error::AnyError, }, } @@ -279,7 +278,7 @@ impl DiscoveryError { provenance: &'static str, source: T, ) -> Self { - UserSnafu { provenance }.into_error(Box::new(source)) + Self::user(provenance, n0_error::AnyError::from_std(source)) } /// Creates a new user error from an arbitrary boxed error type. @@ -287,7 +286,7 @@ impl DiscoveryError { provenance: &'static str, source: Box, ) -> Self { - UserSnafu { provenance }.into_error(source) + Self::user(provenance, n0_error::AnyError::from_std(source)) } } @@ -510,7 +509,10 @@ pub(super) struct DiscoveryTask { impl DiscoveryTask { /// Starts a discovery task. pub(super) fn start(ep: Endpoint, node_id: NodeId) -> Result { - ensure!(!ep.discovery().is_empty(), NoServiceConfiguredSnafu); + ensure!( + !ep.discovery().is_empty(), + DiscoveryError::no_service_configured() + ); let (on_first_tx, on_first_rx) = oneshot::channel(); let me = ep.node_id(); let task = task::spawn( @@ -541,7 +543,10 @@ impl DiscoveryTask { if !ep.needs_discovery(node_id, MAX_AGE) { return Ok(None); } - ensure!(!ep.discovery().is_empty(), NoServiceConfiguredSnafu); + ensure!( + !ep.discovery().is_empty(), + DiscoveryError::no_service_configured() + ); let (on_first_tx, on_first_rx) = oneshot::channel(); let ep = ep.clone(); let me = ep.node_id(); @@ -579,11 +584,14 @@ impl DiscoveryTask { ep: &Endpoint, node_id: NodeId, ) -> Result>, DiscoveryError> { - ensure!(!ep.discovery().is_empty(), NoServiceConfiguredSnafu); + ensure!( + !ep.discovery().is_empty(), + DiscoveryError::no_service_configured() + ); let stream = ep .discovery() .resolve(node_id) - .ok_or(NoResultsSnafu { node_id }.build())?; + .ok_or_else(|| DiscoveryError::no_results(node_id))?; Ok(stream) } @@ -625,7 +633,7 @@ impl DiscoveryTask { } } if let Some(tx) = on_first_tx.take() { - tx.send(Err(NoResultsSnafu { node_id }.build())).ok(); + tx.send(Err(DiscoveryError::no_results(node_id))).ok(); } } } diff --git a/iroh/src/discovery/pkarr.rs b/iroh/src/discovery/pkarr.rs index b3bb67ebcc8..c07d37a95a3 100644 --- a/iroh/src/discovery/pkarr.rs +++ b/iroh/src/discovery/pkarr.rs @@ -48,6 +48,7 @@ use std::sync::Arc; use iroh_base::{NodeId, RelayUrl, SecretKey}; use iroh_relay::node_info::{EncodingError, NodeInfo}; +use n0_error::ResultExt; use n0_future::{ boxed::BoxStream, task::{self, AbortOnDropHandle}, @@ -58,7 +59,6 @@ use pkarr::{ SignedPacket, errors::{PublicKeyError, SignedPacketVerifyError}, }; -use snafu::{ResultExt, Snafu}; use tracing::{Instrument, debug, error_span, warn}; use url::Url; @@ -74,24 +74,29 @@ use crate::{ #[cfg(feature = "discovery-pkarr-dht")] pub mod dht; +#[n0_error::add_location] +#[derive(n0_error::Error)] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[error(std_sources)] #[non_exhaustive] pub enum PkarrError { - #[snafu(display("Invalid public key"))] + #[display("Invalid public key")] PublicKey { source: PublicKeyError }, - #[snafu(display("Packet failed to verify"))] + #[display("Packet failed to verify")] Verify { source: SignedPacketVerifyError }, - #[snafu(display("Invalid relay URL"))] + #[display("Invalid relay URL")] InvalidRelayUrl { url: RelayUrl }, - #[snafu(display("Error sending http request"))] + #[display("Error sending http request")] HttpSend { source: reqwest::Error }, - #[snafu(display("Error resolving http request"))] + #[display("Error resolving http request")] HttpRequest { status: reqwest::StatusCode }, - #[snafu(display("Http payload error"))] + #[display("Http payload error")] HttpPayload { source: reqwest::Error }, - #[snafu(display("EncodingError"))] - Encoding { source: EncodingError }, + #[display("EncodingError")] + Encoding { + #[error(stack_err)] + source: EncodingError, + }, } impl From for DiscoveryError { @@ -396,7 +401,7 @@ impl PublisherService { ); let signed_packet = info .to_pkarr_signed_packet(&self.secret_key, self.ttl) - .context(EncodingSnafu)?; + .context(PkarrError::encoding)?; self.pkarr_client.publish(&signed_packet).await?; Ok(()) } @@ -551,16 +556,12 @@ impl PkarrRelayClient { /// Resolves a [`SignedPacket`] for the given [`NodeId`]. pub async fn resolve(&self, node_id: NodeId) -> Result { // We map the error to string, as in browsers the error is !Send - let public_key = pkarr::PublicKey::try_from(node_id.as_bytes()).context(PublicKeySnafu)?; + let public_key = + pkarr::PublicKey::try_from(node_id.as_bytes()).context(PkarrError::public_key)?; let mut url = self.pkarr_relay_url.clone(); url.path_segments_mut() - .map_err(|_| { - InvalidRelayUrlSnafu { - url: self.pkarr_relay_url.clone(), - } - .build() - })? + .map_err(|_| PkarrError::invalid_relay_url(self.pkarr_relay_url.clone().into()))? .push(&public_key.to_z32()); let response = self @@ -568,20 +569,16 @@ impl PkarrRelayClient { .get(url) .send() .await - .context(HttpSendSnafu)?; + .context(PkarrError::http_send)?; if !response.status().is_success() { - return Err(HttpRequestSnafu { - status: response.status(), - } - .build() - .into()); + return Err(PkarrError::http_request(response.status()).into()); } - let payload = response.bytes().await.context(HttpPayloadSnafu)?; + let payload = response.bytes().await.context(PkarrError::http_payload)?; // We map the error to string, as in browsers the error is !Send let packet = - SignedPacket::from_relay_payload(&public_key, &payload).context(VerifySnafu)?; + SignedPacket::from_relay_payload(&public_key, &payload).context(PkarrError::verify)?; Ok(packet) } @@ -589,12 +586,7 @@ impl PkarrRelayClient { pub async fn publish(&self, signed_packet: &SignedPacket) -> Result<(), PkarrError> { let mut url = self.pkarr_relay_url.clone(); url.path_segments_mut() - .map_err(|_| { - InvalidRelayUrlSnafu { - url: self.pkarr_relay_url.clone(), - } - .build() - })? + .map_err(|_| PkarrError::invalid_relay_url(self.pkarr_relay_url.clone().into()))? .push(&signed_packet.public_key().to_z32()); let response = self @@ -603,13 +595,10 @@ impl PkarrRelayClient { .body(signed_packet.to_relay_payload()) .send() .await - .context(HttpSendSnafu)?; + .context(PkarrError::http_send)?; if !response.status().is_success() { - return Err(HttpRequestSnafu { - status: response.status(), - } - .build()); + return Err(PkarrError::http_request(response.status())); } Ok(()) diff --git a/iroh/src/key.rs b/iroh/src/key.rs index f596000e798..3257290f49d 100644 --- a/iroh/src/key.rs +++ b/iroh/src/key.rs @@ -3,8 +3,8 @@ use std::fmt::Debug; use aead::{AeadCore, AeadInOut, Buffer}; +use n0_error::ensure; use nested_enum_utils::common_fields; -use snafu::{ResultExt, Snafu, ensure}; pub(crate) const NONCE_LEN: usize = 24; @@ -22,20 +22,19 @@ pub(super) fn secret_ed_box(key: &ed25519_dalek::SigningKey) -> crypto_box::Secr pub struct SharedSecret(crypto_box::ChaChaBox); /// Errors that can occur during [`SharedSecret::open`]. -#[common_fields({ - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, -})] -#[derive(Debug, Snafu)] +#[n0_error::add_location] +#[derive(n0_error::Error)] #[non_exhaustive] pub enum DecryptionError { /// The nonce had the wrong size. - #[snafu(display("Invalid nonce"))] + #[display("Invalid nonce")] InvalidNonce {}, /// AEAD decryption failed. - #[snafu(display("Aead error"))] - Aead { source: aead::Error }, + #[display("Aead error")] + Aead { + #[error(std_err)] + source: aead::Error, + }, } impl Debug for SharedSecret { @@ -63,17 +62,17 @@ impl SharedSecret { /// Opens the ciphertext, which must have been created using `Self::seal`, and places the clear text into the provided buffer. pub fn open(&self, buffer: &mut dyn Buffer) -> Result<(), DecryptionError> { - ensure!(buffer.len() >= NONCE_LEN, InvalidNonceSnafu); + ensure!(buffer.len() >= NONCE_LEN, DecryptionError::invalid_nonce()); let offset = buffer.len() - NONCE_LEN; let nonce: [u8; NONCE_LEN] = buffer.as_ref()[offset..] .try_into() - .map_err(|_| InvalidNonceSnafu.build())?; + .map_err(|_| DecryptionError::invalid_nonce())?; buffer.truncate(offset); self.0 .decrypt_in_place(&nonce.into(), AEAD_DATA, buffer) - .context(AeadSnafu)?; + .map_err(DecryptionError::aead)?; Ok(()) } diff --git a/iroh/src/protocol.rs b/iroh/src/protocol.rs index b16346c1c7f..2f21f9c8ae3 100644 --- a/iroh/src/protocol.rs +++ b/iroh/src/protocol.rs @@ -40,11 +40,11 @@ use std::{ }; use iroh_base::NodeId; +use n0_error as _; use n0_future::{ join_all, task::{self, AbortOnDropHandle, JoinSet}, }; -use snafu::{Backtrace, Snafu}; use tokio_util::sync::CancellationToken; use tracing::{Instrument, error, field::Empty, info_span, trace, warn}; @@ -98,34 +98,43 @@ pub struct RouterBuilder { protocols: ProtocolMap, } +/// Errors returned by protocol handlers during accept or setup. +#[n0_error::add_location] +#[derive(n0_error::Error)] #[allow(missing_docs)] -#[derive(Debug, Snafu)] +#[error(from_sources)] #[non_exhaustive] pub enum AcceptError { - #[snafu(transparent)] + /// Underlying connection error while accepting/establishing. + #[error(transparent)] Connection { + /// Source connection error. + #[error(std_err)] source: crate::endpoint::ConnectionError, - backtrace: Option, - #[snafu(implicit)] - span_trace: n0_snafu::SpanTrace, }, - #[snafu(transparent)] - MissingRemoteNodeId { source: RemoteNodeIdError }, - #[snafu(display("Not allowed."))] + /// Missing remote node id on an incoming connection. + #[error(transparent)] + MissingRemoteNodeId { + /// Source error indicating the missing id. + #[error(std_err)] + source: RemoteNodeIdError, + }, + /// Operation not allowed by policy. + #[display("Not allowed.")] NotAllowed {}, - - #[snafu(transparent)] + /// User-defined error bubbled up from handler code. + #[error(transparent)] User { - source: Box, + /// Arbitrary error promoted to `AnyError`. + #[error(from)] + source: n0_error::AnyError, }, } impl AcceptError { /// Creates a new user error from an arbitrary error type. pub fn from_err(value: T) -> Self { - Self::User { - source: Box::new(value), - } + Self::user(n0_error::AnyError::from_std(value)) } } @@ -559,7 +568,7 @@ impl ProtocolHandler for AccessLimit

{ let is_allowed = (self.limiter)(remote); if !is_allowed { conn.close(0u32.into(), b"not allowed"); - return Err(NotAllowedSnafu.build()); + return Err(AcceptError::not_allowed()); } self.proto.accept(conn).await?; Ok(())