From 8455ae063ea694a5a70193c379649ce929e753f2 Mon Sep 17 00:00:00 2001 From: eshork <1829176+eshork@users.noreply.github.com> Date: Tue, 28 Apr 2026 01:00:58 -0400 Subject: [PATCH 1/2] Add #[non_exhaustive] to public structs and enums Mark public configuration structs and enums as non_exhaustive to allow future field additions without breaking downstream consumers. Update call sites to use Default + field assignment or constructors instead of struct literal initialization. --- .gitignore | 1 + libudx/src/error.rs | 1 + libudx/src/native/header.rs | 1 + peeroxide-dht/examples/relay_soak.rs | 23 ++++---- peeroxide-dht/src/blind_relay.rs | 1 + peeroxide-dht/src/compact_encoding.rs | 1 + peeroxide-dht/src/holepuncher.rs | 1 + peeroxide-dht/src/hyperdht.rs | 18 ++++++ peeroxide-dht/src/io.rs | 1 + peeroxide-dht/src/noise.rs | 1 + peeroxide-dht/src/noise_wrap.rs | 2 + peeroxide-dht/src/persistent.rs | 2 + peeroxide-dht/src/protomux.rs | 2 + peeroxide-dht/src/query.rs | 2 + peeroxide-dht/src/router.rs | 6 ++ peeroxide-dht/src/rpc.rs | 4 ++ peeroxide-dht/src/secret_stream.rs | 1 + peeroxide-dht/src/secure_payload.rs | 1 + peeroxide-dht/src/socket_pool.rs | 1 + peeroxide-dht/tests/dht_interop.rs | 12 ++-- .../tests/hyperdht_connect_interop.rs | 59 ++++++++----------- peeroxide-dht/tests/hyperdht_interop.rs | 18 +++--- peeroxide/examples/peer_test.rs | 8 +-- peeroxide/examples/relay_test.rs | 30 +++++----- peeroxide/examples/swarm_announce.rs | 7 ++- peeroxide/examples/swarm_join.rs | 7 ++- peeroxide/src/error.rs | 1 + peeroxide/src/peer_info.rs | 2 + peeroxide/src/swarm.rs | 4 ++ peeroxide/tests/hyperswarm_interop.rs | 31 +++++----- 30 files changed, 143 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index 325f8a8..14521ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target/ tests/node/node_modules/ .sisyphus +.vogon_poetry/ diff --git a/libudx/src/error.rs b/libudx/src/error.rs index b8c2818..eed1ec0 100644 --- a/libudx/src/error.rs +++ b/libudx/src/error.rs @@ -1,5 +1,6 @@ /// Errors returned by libudx operations. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum UdxError { /// A libuv/udx numeric error code. Retained for API compatibility with /// the former FFI backend; the native implementation returns [`Io`](Self::Io) diff --git a/libudx/src/native/header.rs b/libudx/src/native/header.rs index 17cd78b..f4e7bec 100644 --- a/libudx/src/native/header.rs +++ b/libudx/src/native/header.rs @@ -10,6 +10,7 @@ pub const FLAG_DESTROY: u8 = 0x10; pub const FLAG_HEARTBEAT: u8 = 0x20; #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum HeaderError { #[error("packet too short: {0} bytes (minimum {HEADER_SIZE})")] TooShort(usize), diff --git a/peeroxide-dht/examples/relay_soak.rs b/peeroxide-dht/examples/relay_soak.rs index 53e1d60..259341e 100644 --- a/peeroxide-dht/examples/relay_soak.rs +++ b/peeroxide-dht/examples/relay_soak.rs @@ -34,18 +34,16 @@ async fn main() { ) .init(); - let config = HyperDhtConfig { - dht: DhtConfig { - bootstrap: DEFAULT_BOOTSTRAP - .iter() - .map(|s| (*s).to_string()) - .collect(), - ephemeral: Some(false), - firewalled: false, - ..DhtConfig::default() - }, - ..HyperDhtConfig::default() - }; + let mut dht_config = DhtConfig::default(); + dht_config.bootstrap = DEFAULT_BOOTSTRAP + .iter() + .map(|s| (*s).to_string()) + .collect(); + dht_config.ephemeral = Some(false); + dht_config.firewalled = false; + + let mut config = HyperDhtConfig::default(); + config.dht = dht_config; let runtime = UdxRuntime::new().expect("failed to create UDX runtime"); let (join_handle, handle, mut server_rx) = @@ -85,6 +83,7 @@ async fn main() { ); let _ = reply_tx.send(None); } + _ => {} } } }); diff --git a/peeroxide-dht/src/blind_relay.rs b/peeroxide-dht/src/blind_relay.rs index b93b641..54dbc1a 100644 --- a/peeroxide-dht/src/blind_relay.rs +++ b/peeroxide-dht/src/blind_relay.rs @@ -140,6 +140,7 @@ pub fn decode_unpair_from_slice(data: &[u8]) -> c::Result { /// Errors that can occur while using the blind relay client. #[derive(Debug, Error)] +#[non_exhaustive] pub enum RelayError { /// A Protomux operation failed while opening, sending, or receiving. #[error("protomux error: {0}")] diff --git a/peeroxide-dht/src/compact_encoding.rs b/peeroxide-dht/src/compact_encoding.rs index 4fe78a2..0765742 100644 --- a/peeroxide-dht/src/compact_encoding.rs +++ b/peeroxide-dht/src/compact_encoding.rs @@ -2,6 +2,7 @@ use thiserror::Error; /// Errors returned by compact encoding operations #[derive(Error, Debug)] +#[non_exhaustive] pub enum EncodingError { #[error("out of bounds: need {need} bytes, have {have}")] /// The buffer did not contain enough bytes diff --git a/peeroxide-dht/src/holepuncher.rs b/peeroxide-dht/src/holepuncher.rs index 37492ee..19e1abb 100644 --- a/peeroxide-dht/src/holepuncher.rs +++ b/peeroxide-dht/src/holepuncher.rs @@ -10,6 +10,7 @@ use crate::socket_pool::{coerce_firewall, random_port, SocketPool, SocketRef}; const BIRTHDAY_SOCKETS: usize = 256; +#[non_exhaustive] pub enum HolepunchEvent { Connected { addr: SocketAddr, diff --git a/peeroxide-dht/src/hyperdht.rs b/peeroxide-dht/src/hyperdht.rs index 398bffd..922992d 100644 --- a/peeroxide-dht/src/hyperdht.rs +++ b/peeroxide-dht/src/hyperdht.rs @@ -68,6 +68,7 @@ fn is_addr_private(host: &str) -> bool { #[derive(Debug, Error)] /// Errors returned by HyperDHT operations. +#[non_exhaustive] pub enum HyperDhtError { /// Error propagated from the underlying DHT client. #[error("DHT error: {0}")] @@ -135,6 +136,7 @@ pub enum HyperDhtError { #[derive(Debug)] /// Events forwarded to server-side listeners. +#[non_exhaustive] pub enum ServerEvent { /// A peer handshake request that may need local server handling. PeerHandshake { @@ -215,6 +217,7 @@ impl KeyPair { #[derive(Debug, Clone)] /// Result from a LOOKUP query. +#[non_exhaustive] pub struct LookupResult { /// Node that returned the lookup result. pub from: Ipv4Peer, @@ -226,6 +229,7 @@ pub struct LookupResult { #[derive(Debug, Clone)] /// Result from an ANNOUNCE operation. +#[non_exhaustive] pub struct AnnounceResult { /// Closest nodes contacted during the announce. pub closest_nodes: Vec, @@ -233,6 +237,7 @@ pub struct AnnounceResult { #[derive(Debug, Clone)] /// Result from an immutable put operation. +#[non_exhaustive] pub struct ImmutablePutResult { /// Content hash used as the target key. pub hash: [u8; 32], @@ -242,6 +247,7 @@ pub struct ImmutablePutResult { #[derive(Debug, Clone)] /// Result from a mutable put operation. +#[non_exhaustive] pub struct MutablePutResult { /// Public key used as the mutable record key. pub public_key: [u8; 32], @@ -255,6 +261,7 @@ pub struct MutablePutResult { #[derive(Debug, Clone)] /// Result from a mutable get operation. +#[non_exhaustive] pub struct MutableGetResult { /// Retrieved value bytes. pub value: Vec, @@ -268,6 +275,7 @@ pub struct MutableGetResult { #[derive(Debug, Clone)] /// Metadata needed to establish a peer connection. +#[non_exhaustive] pub struct ConnectResult { /// Remote peer's public key. pub remote_public_key: [u8; 32], @@ -289,6 +297,7 @@ pub struct ConnectResult { /// /// Wraps a [`SecretStream`] over a UDX transport, keeping the underlying /// socket alive for the connection's lifetime. +#[non_exhaustive] pub struct PeerConnection { /// Encrypted bidirectional stream to the peer. pub stream: SecretStream, @@ -349,6 +358,7 @@ impl fmt::Debug for PeerConnection { } /// Configuration used by the server-side handshake and holepunch handler. +#[non_exhaustive] pub struct ServerConfig { /// Server identity key pair. pub key_pair: KeyPair, @@ -356,6 +366,13 @@ pub struct ServerConfig { pub firewall: u64, } +impl ServerConfig { + /// Create a new server configuration. + pub fn new(key_pair: KeyPair, firewall: u64) -> Self { + Self { key_pair, firewall } + } +} + // ── Bootstrap defaults ──────────────────────────────────────────────────────── /// The three public HyperDHT bootstrap nodes (from `hyperdht/lib/constants.js`). @@ -372,6 +389,7 @@ pub const DEFAULT_BOOTSTRAP: [&str; 3] = [ #[derive(Debug, Clone, Default)] /// Configuration for a HyperDHT instance. +#[non_exhaustive] pub struct HyperDhtConfig { /// DHT transport and bootstrap settings. pub dht: DhtConfig, diff --git a/peeroxide-dht/src/io.rs b/peeroxide-dht/src/io.rs index a240307..454387d 100644 --- a/peeroxide-dht/src/io.rs +++ b/peeroxide-dht/src/io.rs @@ -28,6 +28,7 @@ const DEFAULT_RETRIES: u32 = 3; // ── Errors ──────────────────────────────────────────────────────────────────── #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum IoError { #[error("UDP error: {0}")] Udx(#[from] libudx::UdxError), diff --git a/peeroxide-dht/src/noise.rs b/peeroxide-dht/src/noise.rs index 3e20669..6519f6a 100644 --- a/peeroxide-dht/src/noise.rs +++ b/peeroxide-dht/src/noise.rs @@ -33,6 +33,7 @@ const PROTOCOL_NAME_IK: &[u8] = b"Noise_IK_Ed25519_ChaChaPoly_BLAKE2b"; /// Errors that can occur during a Noise protocol handshake. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum NoiseError { /// The provided public key could not be decompressed to a valid curve point. #[error("invalid public key")] diff --git a/peeroxide-dht/src/noise_wrap.rs b/peeroxide-dht/src/noise_wrap.rs index b6c95a7..e953001 100644 --- a/peeroxide-dht/src/noise_wrap.rs +++ b/peeroxide-dht/src/noise_wrap.rs @@ -22,6 +22,7 @@ type Blake2bMac256 = Blake2bMac; /// Errors from the [`NoiseWrap`] handshake layer. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum NoiseWrapError { /// The underlying Noise IK handshake failed. #[error("noise handshake failed: {0}")] @@ -40,6 +41,7 @@ pub enum NoiseWrapError { /// Final output after a completed NoiseWrap handshake. #[derive(Clone, Debug)] +#[non_exhaustive] pub struct NoiseWrapResult { /// Whether this side initiated the handshake. pub is_initiator: bool, diff --git a/peeroxide-dht/src/persistent.rs b/peeroxide-dht/src/persistent.rs index 9b426cf..c73e71b 100644 --- a/peeroxide-dht/src/persistent.rs +++ b/peeroxide-dht/src/persistent.rs @@ -37,6 +37,7 @@ const MAX_RELAY_ADDRESSES: usize = 3; /// Configuration for the `Persistent` storage. #[derive(Debug, Clone)] +#[non_exhaustive] pub struct PersistentConfig { pub max_records: usize, pub max_record_age: Duration, @@ -70,6 +71,7 @@ pub struct IncomingHyperRequest { } /// What the handler wants to send back. +#[non_exhaustive] pub enum HandlerReply { /// Reply with an optional value and token / closer nodes. Value(Option>), diff --git a/peeroxide-dht/src/protomux.rs b/peeroxide-dht/src/protomux.rs index 8ee57c7..dc77ad5 100644 --- a/peeroxide-dht/src/protomux.rs +++ b/peeroxide-dht/src/protomux.rs @@ -69,6 +69,7 @@ const MAX_BATCH: usize = 8 * 1024 * 1024; #[derive(Debug, Error)] /// Errors returned by the Protomux encoder, decoder, and channel runtime. +#[non_exhaustive] pub enum ProtomuxError { /// Encoding failed while serializing a frame. #[error("encoding error: {0}")] @@ -380,6 +381,7 @@ pub trait FramedStream: Send + 'static { /// Events dispatched to individual channel handles. #[derive(Debug)] +#[non_exhaustive] pub enum ChannelEvent { /// Remote opened the channel (with optional handshake data). Opened { diff --git a/peeroxide-dht/src/query.rs b/peeroxide-dht/src/query.rs index d0267cd..759ba99 100644 --- a/peeroxide-dht/src/query.rs +++ b/peeroxide-dht/src/query.rs @@ -34,6 +34,7 @@ pub(crate) struct QueryNode { /// A reply from a node that made it into the `closest_replies` set. #[derive(Debug, Clone)] +#[non_exhaustive] pub struct QueryReply { /// The peer that replied. pub from: Ipv4Peer, @@ -53,6 +54,7 @@ pub struct QueryReply { /// The final result of a completed query. #[derive(Debug)] +#[non_exhaustive] pub struct QueryResult { /// Up to K closest nodes found, sorted by XOR distance to target. pub closest_replies: Vec, diff --git a/peeroxide-dht/src/router.rs b/peeroxide-dht/src/router.rs index 72c19e2..42183f4 100644 --- a/peeroxide-dht/src/router.rs +++ b/peeroxide-dht/src/router.rs @@ -10,6 +10,7 @@ use crate::messages::Ipv4Peer; const DEFAULT_FORWARD_TTL: Duration = Duration::from_secs(30); #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum RouterError { #[error("bad handshake reply")] BadHandshakeReply, @@ -22,6 +23,7 @@ pub enum RouterError { pub type Result = std::result::Result; #[derive(Debug, Clone)] +#[non_exhaustive] pub struct HandshakeResult { pub noise: Vec, pub relayed: bool, @@ -30,6 +32,7 @@ pub struct HandshakeResult { } #[derive(Debug, Clone)] +#[non_exhaustive] pub struct HolepunchResult { pub from: Ipv4Peer, pub to: Ipv4Peer, @@ -38,6 +41,7 @@ pub struct HolepunchResult { } #[derive(Debug, Clone)] +#[non_exhaustive] pub enum HandshakeAction { Reply(Vec), Relay { value: Vec, to: Ipv4Peer }, @@ -47,6 +51,7 @@ pub enum HandshakeAction { } #[derive(Debug, Clone)] +#[non_exhaustive] pub enum HolepunchAction { Reply { value: Vec, to: Ipv4Peer }, Relay { value: Vec, to: Ipv4Peer }, @@ -57,6 +62,7 @@ pub enum HolepunchAction { Drop, } +#[non_exhaustive] pub struct ForwardEntry { pub relay: Option, pub has_server: bool, diff --git a/peeroxide-dht/src/rpc.rs b/peeroxide-dht/src/rpc.rs index d77dc34..9c123ac 100644 --- a/peeroxide-dht/src/rpc.rs +++ b/peeroxide-dht/src/rpc.rs @@ -41,6 +41,7 @@ const ERR_UNKNOWN_COMMAND: u64 = 1; #[derive(Debug, Error)] /// Errors returned by [`DhtHandle`] and [`spawn`]. +#[non_exhaustive] pub enum DhtError { /// Underlying I/O failed. #[error("IO error: {0}")] @@ -63,6 +64,7 @@ pub enum DhtError { /// Configuration for creating a DHT node. #[derive(Debug, Clone)] +#[non_exhaustive] pub struct DhtConfig { /// Bootstrap node addresses. pub bootstrap: Vec, @@ -96,6 +98,7 @@ impl Default for DhtConfig { #[derive(Debug, Clone)] /// Response to a ping request. +#[non_exhaustive] pub struct PingResponse { /// Remote peer that replied. pub from: Ipv4Peer, @@ -107,6 +110,7 @@ pub struct PingResponse { #[derive(Debug, Clone)] /// Data returned from a DHT request. +#[non_exhaustive] pub struct ResponseData { /// Remote peer that replied. pub from: Ipv4Peer, diff --git a/peeroxide-dht/src/secret_stream.rs b/peeroxide-dht/src/secret_stream.rs index 941451f..f7422da 100644 --- a/peeroxide-dht/src/secret_stream.rs +++ b/peeroxide-dht/src/secret_stream.rs @@ -58,6 +58,7 @@ fn ns_responder() -> &'static [u8; 32] { #[derive(Debug, Error)] /// Errors returned by `SecretStream` and its framing helpers. +#[non_exhaustive] pub enum SecretStreamError { /// An underlying I/O operation failed. #[error("I/O error: {0}")] diff --git a/peeroxide-dht/src/secure_payload.rs b/peeroxide-dht/src/secure_payload.rs index fee3fe4..d4d31e0 100644 --- a/peeroxide-dht/src/secure_payload.rs +++ b/peeroxide-dht/src/secure_payload.rs @@ -14,6 +14,7 @@ const NONCE_SIZE: usize = 24; const TAG_SIZE: usize = 16; #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum SecurePayloadError { #[error("encoding error: {0}")] Encoding(#[from] EncodingError), diff --git a/peeroxide-dht/src/socket_pool.rs b/peeroxide-dht/src/socket_pool.rs index 32f416f..1d03328 100644 --- a/peeroxide-dht/src/socket_pool.rs +++ b/peeroxide-dht/src/socket_pool.rs @@ -8,6 +8,7 @@ const HOLEPUNCH_TTL: u32 = 5; const DEFAULT_TTL: u32 = 64; #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum SocketPoolError { #[error("udx error: {0}")] Udx(#[from] libudx::UdxError), diff --git a/peeroxide-dht/tests/dht_interop.rs b/peeroxide-dht/tests/dht_interop.rs index 3ac9704..0cc5d7f 100644 --- a/peeroxide-dht/tests/dht_interop.rs +++ b/peeroxide-dht/tests/dht_interop.rs @@ -52,13 +52,11 @@ async fn run_interop() -> Result<(), Box> { // ── Create Rust DHT node that bootstraps from the JS node ──────────── let runtime = UdxRuntime::new()?; - let config = DhtConfig { - bootstrap: vec![format!("127.0.0.1:{js_port}")], - port: 0, - host: "127.0.0.1".to_string(), - firewalled: true, - ..DhtConfig::default() - }; + let mut config = DhtConfig::default(); + config.bootstrap = vec![format!("127.0.0.1:{js_port}")]; + config.port = 0; + config.host = "127.0.0.1".to_string(); + config.firewalled = true; let (task, handle) = peeroxide_dht::rpc::spawn(&runtime, config).await?; diff --git a/peeroxide-dht/tests/hyperdht_connect_interop.rs b/peeroxide-dht/tests/hyperdht_connect_interop.rs index 0a76ff8..752d78c 100644 --- a/peeroxide-dht/tests/hyperdht_connect_interop.rs +++ b/peeroxide-dht/tests/hyperdht_connect_interop.rs @@ -40,16 +40,14 @@ async fn run_handshake_test() -> Result<(), Box> { // ── 1. Bootstrap node ───────────────────────────────────────────────── let rt = UdxRuntime::new()?; - let bs_config = HyperDhtConfig { - dht: DhtConfig { - bootstrap: vec![], - port: 0, - host: "127.0.0.1".to_string(), - firewalled: true, - ..DhtConfig::default() - }, - ..HyperDhtConfig::default() - }; + let mut bs_dht = DhtConfig::default(); + bs_dht.bootstrap = vec![]; + bs_dht.port = 0; + bs_dht.host = "127.0.0.1".to_string(); + bs_dht.firewalled = true; + + let mut bs_config = HyperDhtConfig::default(); + bs_config.dht = bs_dht; let (_bs_join, bs_handle, _bs_rx) = spawn(&rt, bs_config).await?; let bs_port = bs_handle.dht().local_port().await?; tracing::info!(bs_port, "bootstrap node ready"); @@ -57,16 +55,14 @@ async fn run_handshake_test() -> Result<(), Box> { let bootstrap = vec![format!("127.0.0.1:{bs_port}")]; // ── 2. Server node ──────────────────────────────────────────────────── - let srv_config = HyperDhtConfig { - dht: DhtConfig { - bootstrap: bootstrap.clone(), - port: 0, - host: "127.0.0.1".to_string(), - firewalled: true, - ..DhtConfig::default() - }, - ..HyperDhtConfig::default() - }; + let mut srv_dht = DhtConfig::default(); + srv_dht.bootstrap = bootstrap.clone(); + srv_dht.port = 0; + srv_dht.host = "127.0.0.1".to_string(); + srv_dht.firewalled = true; + + let mut srv_config = HyperDhtConfig::default(); + srv_config.dht = srv_dht; let (_srv_join, srv_handle, srv_rx) = spawn(&rt, srv_config).await?; let srv_port = srv_handle.dht().local_port().await?; tracing::info!(srv_port, "server node ready"); @@ -76,24 +72,19 @@ async fn run_handshake_test() -> Result<(), Box> { srv_handle.register_server(&target); - let server_config = ServerConfig { - key_pair: server_kp.clone(), - firewall: 0, - }; + let server_config = ServerConfig::new(server_kp.clone(), 0); let server_rt = UdxRuntime::new()?; let server_task = tokio::spawn(run_server(srv_rx, server_config, server_rt)); // ── 3. Client node ──────────────────────────────────────────────────── - let cli_config = HyperDhtConfig { - dht: DhtConfig { - bootstrap: bootstrap.clone(), - port: 0, - host: "127.0.0.1".to_string(), - firewalled: true, - ..DhtConfig::default() - }, - ..HyperDhtConfig::default() - }; + let mut cli_dht = DhtConfig::default(); + cli_dht.bootstrap = bootstrap.clone(); + cli_dht.port = 0; + cli_dht.host = "127.0.0.1".to_string(); + cli_dht.firewalled = true; + + let mut cli_config = HyperDhtConfig::default(); + cli_config.dht = cli_dht; let (_cli_join, cli_handle, _cli_rx) = spawn(&rt, cli_config).await?; srv_handle.bootstrapped().await?; diff --git a/peeroxide-dht/tests/hyperdht_interop.rs b/peeroxide-dht/tests/hyperdht_interop.rs index f5ec0c8..7b95d1d 100644 --- a/peeroxide-dht/tests/hyperdht_interop.rs +++ b/peeroxide-dht/tests/hyperdht_interop.rs @@ -157,16 +157,14 @@ async fn run_interop() -> Result<(), Box> { let runtime = UdxRuntime::new()?; - let config = HyperDhtConfig { - dht: DhtConfig { - bootstrap: vec![format!("127.0.0.1:{bs_port}")], - port: 0, - host: "127.0.0.1".to_string(), - firewalled: true, - ..DhtConfig::default() - }, - ..HyperDhtConfig::default() - }; + let mut dht_config = DhtConfig::default(); + dht_config.bootstrap = vec![format!("127.0.0.1:{bs_port}")]; + dht_config.port = 0; + dht_config.host = "127.0.0.1".to_string(); + dht_config.firewalled = true; + + let mut config = HyperDhtConfig::default(); + config.dht = dht_config; let (task, handle, _server_rx) = spawn(&runtime, config).await?; diff --git a/peeroxide/examples/peer_test.rs b/peeroxide/examples/peer_test.rs index 1f73fc4..0af9d53 100644 --- a/peeroxide/examples/peer_test.rs +++ b/peeroxide/examples/peer_test.rs @@ -38,14 +38,12 @@ async fn main() { println!(); println!("[*] bootstrapping..."); - let config = SwarmConfig { - key_pair: Some(key_pair), - ..SwarmConfig::with_public_bootstrap() - }; + let mut config = SwarmConfig::with_public_bootstrap(); + config.key_pair = Some(key_pair); let (_join, handle, mut conn_rx) = spawn(config).await.expect("swarm spawn failed"); handle - .join(topic, JoinOpts { server: true, client: true }) + .join(topic, JoinOpts::default()) .await .expect("join failed"); handle.flush().await.expect("flush failed"); diff --git a/peeroxide/examples/relay_test.rs b/peeroxide/examples/relay_test.rs index 0ca9f61..334717f 100644 --- a/peeroxide/examples/relay_test.rs +++ b/peeroxide/examples/relay_test.rs @@ -106,12 +106,10 @@ async fn main() { hex::encode(&server_pk[..8]) ); - let server_config = SwarmConfig { - key_pair: Some(server_kp), - relay_through: Some(relay.public_key), - relay_address: Some(relay.addr), - ..SwarmConfig::with_public_bootstrap() - }; + let mut server_config = SwarmConfig::with_public_bootstrap(); + server_config.key_pair = Some(server_kp); + server_config.relay_through = Some(relay.public_key); + server_config.relay_address = Some(relay.addr); let (server_join, server_handle, mut server_rx) = spawn(server_config).await.expect("server spawn"); @@ -119,9 +117,10 @@ async fn main() { server_handle .join( topic, - JoinOpts { - server: true, - client: false, + { + let mut opts = JoinOpts::default(); + opts.client = false; + opts }, ) .await @@ -136,10 +135,8 @@ async fn main() { hex::encode(&client_kp.public_key[..8]) ); - let client_config = SwarmConfig { - key_pair: Some(client_kp), - ..SwarmConfig::with_public_bootstrap() - }; + let mut client_config = SwarmConfig::with_public_bootstrap(); + client_config.key_pair = Some(client_kp); let (client_join, client_handle, mut client_rx) = spawn(client_config).await.expect("client spawn"); @@ -147,9 +144,10 @@ async fn main() { client_handle .join( topic, - JoinOpts { - server: false, - client: true, + { + let mut opts = JoinOpts::default(); + opts.server = false; + opts }, ) .await diff --git a/peeroxide/examples/swarm_announce.rs b/peeroxide/examples/swarm_announce.rs index 44d6683..0c57712 100644 --- a/peeroxide/examples/swarm_announce.rs +++ b/peeroxide/examples/swarm_announce.rs @@ -43,9 +43,10 @@ async fn main() { handle .join( topic, - JoinOpts { - server: true, - client: false, + { + let mut opts = JoinOpts::default(); + opts.client = false; + opts }, ) .await diff --git a/peeroxide/examples/swarm_join.rs b/peeroxide/examples/swarm_join.rs index a899ca7..db42da4 100644 --- a/peeroxide/examples/swarm_join.rs +++ b/peeroxide/examples/swarm_join.rs @@ -39,9 +39,10 @@ async fn main() { handle .join( topic, - JoinOpts { - server: false, - client: true, + { + let mut opts = JoinOpts::default(); + opts.server = false; + opts }, ) .await diff --git a/peeroxide/src/error.rs b/peeroxide/src/error.rs index 3ae50ee..48455b8 100644 --- a/peeroxide/src/error.rs +++ b/peeroxide/src/error.rs @@ -2,6 +2,7 @@ use thiserror::Error; /// Errors from the Hyperswarm layer. #[derive(Debug, Error)] +#[non_exhaustive] pub enum SwarmError { /// Error from the HyperDHT layer. #[error("DHT error: {0}")] diff --git a/peeroxide/src/peer_info.rs b/peeroxide/src/peer_info.rs index 2262118..2074066 100644 --- a/peeroxide/src/peer_info.rs +++ b/peeroxide/src/peer_info.rs @@ -14,6 +14,7 @@ const PROVEN_THRESHOLD_SECS: u64 = 15; /// - 4+ attempts → VeryLow #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] +#[non_exhaustive] pub enum Priority { /// Lowest priority (4+ failed attempts). VeryLow = 0, @@ -30,6 +31,7 @@ pub enum Priority { /// Per-peer metadata tracking connection attempts, priority, and topic associations. /// /// Modelled after `lib/peer-info.js` in the Node.js Hyperswarm. +#[non_exhaustive] pub struct PeerInfo { /// The peer's Ed25519 public key. pub public_key: [u8; 32], diff --git a/peeroxide/src/swarm.rs b/peeroxide/src/swarm.rs index 57889d6..7b41c13 100644 --- a/peeroxide/src/swarm.rs +++ b/peeroxide/src/swarm.rs @@ -75,6 +75,7 @@ fn short_hex(bytes: &[u8]) -> String { // ── Public types ───────────────────────────────────────────────────────────── /// Configuration for a [`Hyperswarm`](SwarmHandle) instance. +#[non_exhaustive] pub struct SwarmConfig { /// Ed25519 key pair. Auto-generated if `None`. pub key_pair: Option, @@ -120,6 +121,7 @@ impl SwarmConfig { } /// Options for joining a topic. +#[non_exhaustive] pub struct JoinOpts { /// Announce on this topic (server mode). pub server: bool, @@ -137,6 +139,7 @@ impl Default for JoinOpts { } /// An established swarm connection. +#[non_exhaustive] pub struct SwarmConnection { /// The underlying encrypted peer connection. pub peer: PeerConnection, @@ -685,6 +688,7 @@ impl SwarmActor { // Acknowledge without creating a connection. let _ = reply_tx.send(None); } + _ => {} } } diff --git a/peeroxide/tests/hyperswarm_interop.rs b/peeroxide/tests/hyperswarm_interop.rs index b7e8ace..ecd4c10 100644 --- a/peeroxide/tests/hyperswarm_interop.rs +++ b/peeroxide/tests/hyperswarm_interop.rs @@ -181,19 +181,17 @@ async fn run_interop() -> Result<(), Box> { tokio::time::sleep(Duration::from_millis(500)).await; - let config = SwarmConfig { - dht: HyperDhtConfig { - dht: DhtConfig { - bootstrap: vec![format!("127.0.0.1:{bs_port}")], - port: 0, - host: "127.0.0.1".to_string(), - firewalled: true, - ..DhtConfig::default() - }, - ..HyperDhtConfig::default() - }, - ..SwarmConfig::default() - }; + let mut dht_config = DhtConfig::default(); + dht_config.bootstrap = vec![format!("127.0.0.1:{bs_port}")]; + dht_config.port = 0; + dht_config.host = "127.0.0.1".to_string(); + dht_config.firewalled = true; + + let mut hyper_config = HyperDhtConfig::default(); + hyper_config.dht = dht_config; + + let mut config = SwarmConfig::default(); + config.dht = hyper_config; let (_task, handle, mut conn_rx) = spawn(config).await?; tracing::info!("Rust swarm started"); @@ -201,9 +199,10 @@ async fn run_interop() -> Result<(), Box> { handle .join( topic, - JoinOpts { - server: false, - client: true, + { + let mut opts = JoinOpts::default(); + opts.server = false; + opts }, ) .await?; From 1f3f9e6ddbc78454e0de34895d42b4cbec48cadc Mon Sep 17 00:00:00 2001 From: eshork <1829176+eshork@users.noreply.github.com> Date: Tue, 28 Apr 2026 01:12:10 -0400 Subject: [PATCH 2/2] Bump workspace crate versions to 1.1.0 --- Cargo.lock | 6 +++--- libudx/Cargo.toml | 2 +- peeroxide-dht/Cargo.toml | 4 ++-- peeroxide/Cargo.toml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d803108..75299ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,7 +281,7 @@ checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libudx" -version = "1.0.1" +version = "1.1.0" dependencies = [ "rand", "serde_json", @@ -379,7 +379,7 @@ dependencies = [ [[package]] name = "peeroxide" -version = "1.0.1" +version = "1.1.0" dependencies = [ "hex", "libudx", @@ -394,7 +394,7 @@ dependencies = [ [[package]] name = "peeroxide-dht" -version = "1.0.1" +version = "1.1.0" dependencies = [ "blake2", "chacha20", diff --git a/libudx/Cargo.toml b/libudx/Cargo.toml index 2768dbc..7225e72 100644 --- a/libudx/Cargo.toml +++ b/libudx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libudx" -version = "1.0.1" +version = "1.1.0" edition.workspace = true license.workspace = true rust-version.workspace = true diff --git a/peeroxide-dht/Cargo.toml b/peeroxide-dht/Cargo.toml index 5b4de86..ab084b8 100644 --- a/peeroxide-dht/Cargo.toml +++ b/peeroxide-dht/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "peeroxide-dht" -version = "1.0.1" +version = "1.1.0" edition.workspace = true license.workspace = true rust-version.workspace = true @@ -15,7 +15,7 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -libudx = { path = "../libudx", version = "1.0.1" } +libudx = { path = "../libudx", version = "1.1.0" } tokio = { workspace = true } tracing = { workspace = true } thiserror = { workspace = true } diff --git a/peeroxide/Cargo.toml b/peeroxide/Cargo.toml index 477434d..94a11d4 100644 --- a/peeroxide/Cargo.toml +++ b/peeroxide/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "peeroxide" -version = "1.0.1" +version = "1.1.0" edition.workspace = true license.workspace = true rust-version.workspace = true @@ -15,8 +15,8 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -peeroxide-dht = { path = "../peeroxide-dht", version = "1.0.1" } -libudx = { path = "../libudx", version = "1.0.1" } +peeroxide-dht = { path = "../peeroxide-dht", version = "1.1.0" } +libudx = { path = "../libudx", version = "1.1.0" } tokio = { workspace = true } tracing = { workspace = true } thiserror = { workspace = true }