keep details provided by topology instead of just a url

This commit is contained in:
jmwample
2025-12-05 13:39:34 -07:00
parent 17b9fa4dd5
commit 26b537e256
15 changed files with 179 additions and 142 deletions
Generated
+3
View File
@@ -5254,7 +5254,9 @@ dependencies = [
"cosmrs",
"nym-crypto",
"nym-gateway-requests",
"nym-topology",
"serde",
"serde_json",
"sqlx",
"thiserror 2.0.12",
"time",
@@ -5872,6 +5874,7 @@ dependencies = [
"nym-sphinx",
"nym-statistics-common",
"nym-task",
"nym-topology",
"nym-validator-client",
"rand 0.8.5",
"serde",
@@ -11,6 +11,7 @@ rust-version.workspace = true
async-trait.workspace = true
cosmrs.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
thiserror.workspace = true
time.workspace = true
tokio = { workspace = true, features = ["sync"] }
@@ -20,6 +21,7 @@ zeroize = { workspace = true, features = ["zeroize_derive"] }
nym-crypto = { path = "../../crypto", features = ["asymmetric"] }
nym-gateway-requests = { path = "../../gateway-requests" }
nym-topology = { path = "../../topology", features = ["persistence"] }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
workspace = true
@@ -48,6 +48,6 @@ pub enum BadGateway {
raw_listener: String,
#[source]
source: url::ParseError,
source: serde_json::Error,
},
}
@@ -5,13 +5,13 @@ use crate::BadGateway;
use cosmrs::AccountId;
use nym_crypto::asymmetric::ed25519;
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
use nym_topology::EntryDetails;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::str::FromStr;
use std::sync::Arc;
use time::OffsetDateTime;
use url::Url;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub const REMOTE_GATEWAY_TYPE: &str = "remote";
@@ -67,7 +67,7 @@ impl GatewayDetails {
gateway_id: ed25519::PublicKey,
shared_key: Arc<SharedGatewayKey>,
gateway_owner_address: Option<AccountId>,
gateway_listener: Url,
gateway_listener: EntryDetails,
) -> Self {
GatewayDetails::Remote(RemoteGatewayDetails {
gateway_id,
@@ -164,8 +164,7 @@ pub struct RegisteredGateway {
pub gateway_type: GatewayType,
}
#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
#[derive(Debug, Serialize, Deserialize)]
pub struct RawRemoteGatewayDetails {
pub gateway_id_bs58: String,
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
@@ -174,6 +173,16 @@ pub struct RawRemoteGatewayDetails {
pub gateway_listener: String,
}
impl Zeroize for RawRemoteGatewayDetails {
fn zeroize(&mut self) {
self.gateway_id_bs58.zeroize();
self.derived_aes128_ctr_blake3_hmac_keys_bs58.zeroize();
self.derived_aes256_gcm_siv_key.zeroize();
self.gateway_owner_address.zeroize();
}
}
impl ZeroizeOnDrop for RawRemoteGatewayDetails {}
impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
type Error = BadGateway;
@@ -230,7 +239,7 @@ impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
})
.transpose()?;
let gateway_listener = Url::parse(&value.gateway_listener).map_err(|source| {
let gateway_listener = serde_json::from_str(&value.gateway_listener).map_err(|source| {
BadGateway::MalformedListener {
gateway_id: value.gateway_id_bs58.clone(),
raw_listener: value.gateway_listener.clone(),
@@ -255,12 +264,16 @@ impl<'a> From<&'a RemoteGatewayDetails> for RawRemoteGatewayDetails {
SharedGatewayKey::Legacy(key) => (Some(key.to_base58_string()), None),
};
#[allow(clippy::expect_used)]
let gateway_listener = serde_json::to_string(&value.gateway_listener)
.expect("serialize for gateway details should never fail");
RawRemoteGatewayDetails {
gateway_id_bs58: value.gateway_id.to_base58_string(),
derived_aes128_ctr_blake3_hmac_keys_bs58,
derived_aes256_gcm_siv_key,
gateway_owner_address: value.gateway_owner_address.as_ref().map(|o| o.to_string()),
gateway_listener: value.gateway_listener.to_string(),
gateway_listener,
}
}
}
@@ -273,7 +286,7 @@ pub struct RemoteGatewayDetails {
pub gateway_owner_address: Option<AccountId>,
pub gateway_listener: Url,
pub gateway_listener: EntryDetails,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -561,7 +561,7 @@ where
.gateway_owner_address
.as_ref()
.map(|o| o.to_string()),
details.gateway_listener.to_string(),
details.gateway_listener,
);
GatewayClient::new(
GatewayClientConfig::new_default()
@@ -436,7 +436,7 @@ where
}
}
if let Some(ref mut next_delay) = &mut self.next_delay {
if let Some(next_delay) = &mut self.next_delay {
// it is not yet time to return a message
if next_delay.as_mut().poll(cx).is_pending() {
return Poll::Pending;
+2 -2
View File
@@ -40,8 +40,8 @@ pub enum ClientCoreError {
#[error("no gateway with id: {0}")]
NoGatewayWithId(String),
#[error("Invalid URL: {0}")]
InvalidUrl(String),
#[error("Invalid Endpoint: {0}")]
InvalidEndpoint(String),
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
+9 -9
View File
@@ -21,7 +21,7 @@ use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use crate::init::websockets::connect_async;
use nym_topology::NodeId;
use nym_topology::{EntryDetails, NodeId};
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
#[cfg(not(target_arch = "wasm32"))]
@@ -55,7 +55,7 @@ const PING_TIMEOUT: Duration = Duration::from_millis(1000);
pub trait ConnectableGateway {
fn node_id(&self) -> NodeId;
fn identity(&self) -> ed25519::PublicKey;
fn clients_address(&self, prefer_ipv6: bool) -> Option<String>;
fn endpoint_details(&self) -> Option<EntryDetails>;
fn is_wss(&self) -> bool;
}
@@ -68,8 +68,8 @@ impl ConnectableGateway for RoutingNode {
self.identity_key
}
fn clients_address(&self, prefer_ipv6: bool) -> Option<String> {
self.ws_entry_address(prefer_ipv6)
fn endpoint_details(&self) -> Option<EntryDetails> {
self.entry.clone()
}
fn is_wss(&self) -> bool {
@@ -191,7 +191,7 @@ pub async fn gateways_for_init(
}
#[cfg(not(target_arch = "wasm32"))]
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
async fn connect(endpoint: &EntryDetails) -> Result<WsConn, ClientCoreError> {
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
Ok(Err(conn_failure)) => Err(conn_failure),
@@ -208,17 +208,17 @@ async fn measure_latency<G>(gateway: &G) -> Result<GatewayWithLatency<'_, G>, Cl
where
G: ConnectableGateway,
{
let Some(addr) = gateway.clients_address(false) else {
let Some(endpoint) = gateway.endpoint_details() else {
return Err(ClientCoreError::UnsupportedEntry {
id: gateway.node_id(),
identity: gateway.identity().to_string(),
});
};
trace!(
"establishing connection to {} ({addr})...",
"establishing connection to {} ({endpoint})...",
gateway.identity(),
);
let mut stream = connect(&addr).await?;
let mut stream = connect(&endpoint).await?;
let mut results = Vec::new();
for _ in 0..MEASUREMENTS {
@@ -379,7 +379,7 @@ pub(super) fn get_specified_gateway(
pub(super) async fn register_with_gateway(
gateway_id: ed25519::PublicKey,
gateway_listener: Url,
gateway_listener: EntryDetails,
our_identity: Arc<ed25519::KeyPair>,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Result<RegistrationResult, ClientCoreError> {
+20 -20
View File
@@ -14,6 +14,7 @@ use nym_gateway_client::client::InitGatewayClient;
use nym_gateway_requests::shared_key::SharedGatewayKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::node::RoutingNode;
use nym_topology::EntryDetails;
use nym_validator_client::client::IdentityKey;
use nym_validator_client::nyxd::AccountId;
use serde::Serialize;
@@ -22,7 +23,6 @@ use std::fmt::{Debug, Display};
use std::os::fd::RawFd;
use std::sync::Arc;
use time::OffsetDateTime;
use url::Url;
pub enum SelectedGateway {
Remote {
@@ -30,7 +30,7 @@ pub enum SelectedGateway {
gateway_owner_address: Option<AccountId>,
gateway_listener: Url,
gateway_listener: EntryDetails,
},
Custom {
gateway_id: ed25519::PublicKey,
@@ -46,25 +46,25 @@ impl SelectedGateway {
// for now, let's use 'old' behaviour, if you want to change it, you can pass it up the enum stack yourself : )
let prefer_ipv6 = false;
let gateway_listener = if must_use_tls {
node.ws_entry_address_tls()
.ok_or(ClientCoreError::UnsupportedWssProtocol {
gateway: node.identity_key.to_base58_string(),
})?
} else {
node.ws_entry_address(prefer_ipv6)
.ok_or(ClientCoreError::UnsupportedEntry {
id: node.node_id,
identity: node.identity_key.to_base58_string(),
})?
};
let gateway_listener = node.entry.ok_or(ClientCoreError::UnsupportedEntry {
id: node.node_id,
identity: node.identity_key.to_base58_string(),
})?;
let gateway_listener =
Url::parse(&gateway_listener).map_err(|source| ClientCoreError::MalformedListener {
gateway_id: node.identity_key.to_base58_string(),
raw_listener: gateway_listener,
source,
})?;
if must_use_tls
&& (gateway_listener.hostname.is_none() || gateway_listener.clients_wss_port.is_none())
{
return Err(ClientCoreError::UnsupportedWssProtocol {
gateway: node.identity_key.to_base58_string(),
});
}
if prefer_ipv6 && gateway_listener.ip_addresses.iter().all(|i| i.is_ipv4()) {
return Err(ClientCoreError::UnsupportedEntry {
id: node.node_id,
identity: node.identity_key.to_base58_string(),
});
}
Ok(SelectedGateway::Remote {
gateway_id: node.identity_key,
+41 -24
View File
@@ -1,43 +1,60 @@
use crate::error::ClientCoreError;
use nym_http_api_client::HickoryDnsResolver;
#[cfg(not(target_arch = "wasm32"))]
use nym_topology::EntryDetails;
use tokio::net::TcpStream;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
use tungstenite::handshake::client::Response;
use url::{Host, Url};
use url::Url;
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn connect_async(
endpoint: &str,
endpoint: &EntryDetails,
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), ClientCoreError> {
let resolver = HickoryDnsResolver::default();
let uri = Url::parse(endpoint).map_err(|_| ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
let uri = ws_entry_address(endpoint, false)
.ok_or(ClientCoreError::InvalidEndpoint(endpoint.to_string()))?;
let port: u16 = uri.port_or_known_default().unwrap_or(443);
let host = uri
.host()
.ok_or(ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
// the default std resolve
let sock_addrs: Vec<SocketAddr> = match host {
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Domain(domain) => {
// Do a DNS lookup for the domain using our custom DNS resolver
resolver
.resolve_str(domain)
.await?
.map(|a| SocketAddr::new(a, port))
.collect()
}
};
let sock_addrs: Vec<SocketAddr> = endpoint
.ip_addresses
.iter()
.map(|addr| SocketAddr::new(addr.clone(), port))
.collect();
let stream = TcpStream::connect(&sock_addrs[..]).await?;
tokio_tungstenite::client_async_tls(endpoint, stream)
tokio_tungstenite::client_async_tls(uri.to_string(), stream)
.await
.map_err(Into::into)
}
pub fn ws_entry_address_tls(entry: &EntryDetails) -> Option<Url> {
let hostname = entry.hostname.as_ref()?;
let wss_port = entry.clients_wss_port?;
Url::parse(&format!("wss://{hostname}:{wss_port}")).ok()
}
pub fn ws_entry_address_no_tls(entry: &EntryDetails, prefer_ipv6: bool) -> Option<Url> {
if let Some(hostname) = entry.hostname.as_ref() {
return Url::parse(&format!("ws://{hostname}:{}", entry.clients_ws_port)).ok();
}
if prefer_ipv6 {
if let Some(ipv6) = entry.ip_addresses.iter().find(|ip| ip.is_ipv6()) {
return Url::parse(&format!("ws://{ipv6}:{}", entry.clients_ws_port)).ok();
}
}
let any_ip = entry.ip_addresses.first()?;
Url::parse(&format!("ws://{any_ip}:{}", entry.clients_ws_port)).ok()
}
pub fn ws_entry_address(entry: &EntryDetails, prefer_ipv6: bool) -> Option<Url> {
if let Some(tls) = ws_entry_address_tls(entry) {
return Some(tls);
}
ws_entry_address_no_tls(entry, prefer_ipv6)
}
@@ -32,6 +32,7 @@ nym-network-defaults = { path = "../../network-defaults" }
nym-sphinx = { path = "../../nymsphinx" }
nym-statistics-common = { path = "../../statistics" }
nym-pemstore = { path = "../../pemstore" }
nym-topology = { path = "../../topology", features = ["persistence"] }
nym-validator-client = { path = "../validator-client", default-features = false }
nym-task = { path = "../../task" }
serde = { workspace = true, features = ["derive"] }
@@ -28,13 +28,13 @@ use nym_sphinx::forwarding::packet::MixPacket;
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
use nym_statistics_common::clients::ClientStatsSender;
use nym_task::ShutdownToken;
use nym_topology::EntryDetails;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use rand::rngs::OsRng;
use std::sync::Arc;
use tracing::instrument;
use tracing::*;
use tungstenite::protocol::Message;
use url::Url;
#[cfg(unix)]
use std::os::fd::RawFd;
@@ -62,14 +62,14 @@ pub struct GatewayConfig {
// currently a dead field
pub gateway_owner: Option<String>,
pub gateway_listener: String,
pub gateway_listener: EntryDetails,
}
impl GatewayConfig {
pub fn new(
gateway_identity: ed25519::PublicKey,
gateway_owner: Option<String>,
gateway_listener: String,
gateway_listener: EntryDetails,
) -> Self {
GatewayConfig {
gateway_identity,
@@ -92,7 +92,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
authenticated: bool,
bandwidth: ClientBandwidth,
gateway_address: String,
gateway_address: EntryDetails,
gateway_identity: ed25519::PublicKey,
local_identity: Arc<ed25519::KeyPair>,
shared_key: Option<Arc<SharedGatewayKey>>,
@@ -201,7 +201,7 @@ impl<C, St> GatewayClient<C, St> {
#[cfg(not(target_arch = "wasm32"))]
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
debug!(
"Attempting to establish connection to gateway at: {}",
"Attempting to establish connection to gateway at: {:?}",
self.gateway_address
);
let (ws_stream, _) = connect_async(
@@ -467,7 +467,9 @@ impl<C, St> GatewayClient<C, St> {
// right now there are no failure cases here, but this might change in the future
match gateway_protocol {
None => {
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
warn!(
"the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0"
);
// note: in +1.2.0 we will have to return a hard error here
Ok(())
}
@@ -481,7 +483,9 @@ impl<C, St> GatewayClient<C, St> {
}
Some(_) => {
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
debug!(
"the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!"
);
Ok(())
}
}
@@ -527,7 +531,7 @@ impl<C, St> GatewayClient<C, St> {
status,
} => (status, protocol_version),
ServerResponse::Error { message } => {
return Err(GatewayClientError::GatewayError(message))
return Err(GatewayClientError::GatewayError(message));
}
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
};
@@ -589,7 +593,7 @@ impl<C, St> GatewayClient<C, St> {
{
ServerResponse::EncryptedResponse { ciphertext, nonce } => (ciphertext, nonce),
ServerResponse::Error { message } => {
return Err(GatewayClientError::GatewayError(message))
return Err(GatewayClientError::GatewayError(message));
}
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
};
@@ -858,7 +862,9 @@ impl<C, St> GatewayClient<C, St> {
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
if !self.cfg.bandwidth.require_tickets {
info!("The client is running in disabled credentials mode - attempting to claim bandwidth without a credential");
info!(
"The client is running in disabled credentials mode - attempting to claim bandwidth without a credential"
);
return self.try_claim_testnet_bandwidth().await;
}
@@ -896,7 +902,9 @@ impl<C, St> GatewayClient<C, St> {
Err(err) => {
error!("failed to claim ecash bandwidth with the gateway...: {err}");
if err.is_ticket_replay() {
warn!("this was due to our ticket being replayed! have you messed with the database file?")
warn!(
"this was due to our ticket being replayed! have you messed with the database file?"
)
} else {
// TODO: tracing span
info!("attempting to revert ticket withdrawal...");
@@ -1112,7 +1120,9 @@ impl<C, St> GatewayClient<C, St> {
self.cfg
.bandwidth
.ensure_above_cutoff(bandwidth_remaining)?;
info!("Claiming more bandwidth with existing credentials. Stop the process now if you don't want that to happen.");
info!(
"Claiming more bandwidth with existing credentials. Stop the process now if you don't want that to happen."
);
self.claim_bandwidth().await?;
}
Ok(())
@@ -1128,7 +1138,7 @@ pub struct InitOnly;
impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
// for initialisation we do not need credential storage. Though it's still a bit weird we have to set the generic...
pub fn new_init(
gateway_listener: Url,
gateway_listener: EntryDetails,
gateway_identity: ed25519::PublicKey,
local_identity: Arc<ed25519::KeyPair>,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
@@ -1147,7 +1157,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
cfg: GatewayClientConfig::default().with_disabled_credentials_mode(true),
authenticated: false,
bandwidth: ClientBandwidth::new_empty(),
gateway_address: gateway_listener.to_string(),
gateway_address: gateway_listener.clone(),
gateway_identity,
local_identity,
shared_key: None,
@@ -1,51 +1,37 @@
use crate::error::GatewayClientError;
use nym_http_api_client::HickoryDnsResolver;
#[cfg(not(target_arch = "wasm32"))]
use nym_topology::EntryDetails;
#[cfg(unix)]
use std::{
os::fd::{AsRawFd, RawFd},
sync::Arc,
};
use tokio::net::TcpSocket;
use tokio::net::TcpStream;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
use tungstenite::handshake::client::Response;
use url::{Host, Url};
use url::Url;
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn connect_async(
endpoint: &str,
endpoint: &EntryDetails,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
use tokio::net::TcpSocket;
let resolver = HickoryDnsResolver::default();
let uri =
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
let uri = ws_entry_address(endpoint, false)
.ok_or(GatewayClientError::InvalidEndpoint(endpoint.to_string()))?;
let port: u16 = uri.port_or_known_default().unwrap_or(443);
let host = uri
.host()
.ok_or(GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
// the default std resolve
let sock_addrs: Vec<SocketAddr> = match host {
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Domain(domain) => {
// Do a DNS lookup for the domain using our custom DNS resolver
resolver
.resolve_str(domain)
.await?
.map(|a| SocketAddr::new(a, port))
.collect()
}
};
let sock_addrs = endpoint
.ip_addresses
.iter()
.map(|addr| SocketAddr::new(*addr, port));
let uri_str = uri.to_string();
let mut stream = Err(GatewayClientError::NoEndpointForConnection {
address: endpoint.to_owned(),
address: uri_str.clone(),
});
for sock_addr in sock_addrs {
let socket = if sock_addr.is_ipv4() {
@@ -54,7 +40,7 @@ pub(crate) async fn connect_async(
TcpSocket::new_v6()
}
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
address: uri_str.clone(),
source: Box::new(tungstenite::Error::from(err)),
})?;
@@ -70,7 +56,7 @@ pub(crate) async fn connect_async(
}
Err(err) => {
stream = Err(GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
address: uri_str.clone(),
source: Box::new(tungstenite::Error::from(err)),
});
continue;
@@ -78,10 +64,39 @@ pub(crate) async fn connect_async(
}
}
tokio_tungstenite::client_async_tls(endpoint, stream?)
tokio_tungstenite::client_async_tls(uri.clone(), stream?)
.await
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
address: uri_str.clone(),
source: Box::new(error),
})
}
pub fn ws_entry_address_tls(entry: &EntryDetails) -> Option<Url> {
let hostname = entry.hostname.as_ref()?;
let wss_port = entry.clients_wss_port?;
Url::parse(&format!("wss://{hostname}:{wss_port}")).ok()
}
pub fn ws_entry_address_no_tls(entry: &EntryDetails, prefer_ipv6: bool) -> Option<Url> {
if let Some(hostname) = entry.hostname.as_ref() {
return Url::parse(&format!("ws://{hostname}:{}", entry.clients_ws_port)).ok();
}
if prefer_ipv6 {
if let Some(ipv6) = entry.ip_addresses.iter().find(|ip| ip.is_ipv6()) {
return Url::parse(&format!("ws://{ipv6}:{}", entry.clients_ws_port)).ok();
}
}
let any_ip = entry.ip_addresses.first()?;
Url::parse(&format!("ws://{any_ip}:{}", entry.clients_ws_port)).ok()
}
pub fn ws_entry_address(entry: &EntryDetails, prefer_ipv6: bool) -> Option<Url> {
if let Some(tls) = ws_entry_address_tls(entry) {
return Some(tls);
}
ws_entry_address_no_tls(entry, prefer_ipv6)
}
@@ -49,8 +49,8 @@ pub enum GatewayClientError {
#[error("no socket address for endpoint: {address}")]
NoEndpointForConnection { address: String },
#[error("Invalid URL: {0}")]
InvalidUrl(String),
#[error("Invalid Endpoint: {0}")]
InvalidEndpoint(String),
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
+6 -30
View File
@@ -28,6 +28,12 @@ pub struct EntryDetails {
pub clients_wss_port: Option<u16>,
}
impl std::fmt::Display for EntryDetails {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct SupportedRoles {
pub mixnode: bool,
@@ -59,36 +65,6 @@ pub struct RoutingNode {
}
impl RoutingNode {
pub fn ws_entry_address_tls(&self) -> Option<String> {
let entry = self.entry.as_ref()?;
let hostname = entry.hostname.as_ref()?;
let wss_port = entry.clients_wss_port?;
Some(format!("wss://{hostname}:{wss_port}"))
}
pub fn ws_entry_address_no_tls(&self, prefer_ipv6: bool) -> Option<String> {
let entry = self.entry.as_ref()?;
if let Some(hostname) = entry.hostname.as_ref() {
return Some(format!("ws://{hostname}:{}", entry.clients_ws_port));
}
if prefer_ipv6 && let Some(ipv6) = entry.ip_addresses.iter().find(|ip| ip.is_ipv6()) {
return Some(format!("ws://{ipv6}:{}", entry.clients_ws_port));
}
let any_ip = entry.ip_addresses.first()?;
Some(format!("ws://{any_ip}:{}", entry.clients_ws_port))
}
pub fn ws_entry_address(&self, prefer_ipv6: bool) -> Option<String> {
if let Some(tls) = self.ws_entry_address_tls() {
return Some(tls);
}
self.ws_entry_address_no_tls(prefer_ipv6)
}
pub fn identity(&self) -> ed25519::PublicKey {
self.identity_key
}