aboutsummaryrefslogtreecommitdiff
path: root/azalea-protocol
diff options
context:
space:
mode:
Diffstat (limited to 'azalea-protocol')
-rw-r--r--azalea-protocol/src/address.rs157
-rw-r--r--azalea-protocol/src/lib.rs99
-rw-r--r--azalea-protocol/src/resolve.rs10
3 files changed, 167 insertions, 99 deletions
diff --git a/azalea-protocol/src/address.rs b/azalea-protocol/src/address.rs
new file mode 100644
index 00000000..a3e487a1
--- /dev/null
+++ b/azalea-protocol/src/address.rs
@@ -0,0 +1,157 @@
+use std::{
+ fmt::{self, Debug, Display},
+ net::SocketAddr,
+ str::FromStr,
+};
+
+use hickory_resolver::ResolveError;
+
+use crate::resolve::resolve_address;
+
+/// Something that might be able to be parsed and looked up as a server address.
+///
+/// This is typically used by Azalea as a generic argument, so the user can
+/// choose to pass either a string or an already-resolved address.
+pub trait ResolvableAddr: Debug + Clone {
+ fn server_addr(self) -> Result<ServerAddr, ResolveError>;
+ fn resolve(self) -> impl Future<Output = Result<ResolvedAddr, ResolveError>> + Send;
+}
+impl<T: TryInto<ServerAddr, Error = ServerAddrParseError> + Debug + Send + Clone> ResolvableAddr
+ for T
+{
+ fn server_addr(self) -> Result<ServerAddr, ResolveError> {
+ self.try_into()
+ .map_err(|_| "failed to parse address".into())
+ }
+
+ async fn resolve(self) -> Result<ResolvedAddr, ResolveError> {
+ ResolvedAddr::new(self.server_addr()?).await
+ }
+}
+
+impl ResolvableAddr for &ResolvedAddr {
+ fn server_addr(self) -> Result<ServerAddr, ResolveError> {
+ Ok(self.server.clone())
+ }
+
+ async fn resolve(self) -> Result<ResolvedAddr, ResolveError> {
+ Ok(self.clone())
+ }
+}
+
+/// A host and port. It's possible that the port doesn't resolve to anything.
+///
+/// # Examples
+///
+/// `ServerAddr` implements TryFrom<&str>, so you can use it like this:
+/// ```
+/// use azalea_protocol::address::ServerAddr;
+///
+/// let addr = ServerAddr::try_from("localhost:25565").unwrap();
+/// assert_eq!(addr.host, "localhost");
+/// assert_eq!(addr.port, 25565);
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ServerAddr {
+ pub host: String,
+ pub port: u16,
+}
+
+/// An empty error type that's used when we fail to convert a type to a
+/// `ServerAddr`.
+///
+/// You usually want to use the [`ResolvableAddr`] type instead, which works
+/// with [`ResolveError`]s.
+#[derive(Debug)]
+pub struct ServerAddrParseError;
+
+impl TryFrom<&str> for ServerAddr {
+ type Error = ServerAddrParseError;
+
+ /// Convert a Minecraft server address (`host:port`, the port is optional)
+ /// to a `ServerAddress`
+ fn try_from(string: &str) -> Result<Self, Self::Error> {
+ if string.is_empty() {
+ return Err(ServerAddrParseError);
+ }
+ let mut parts = string.split(':');
+ let host = parts.next().ok_or(ServerAddrParseError)?.to_string();
+ // default the port to 25565
+ let port = parts.next().unwrap_or("25565");
+ let port = u16::from_str(port).ok().ok_or(ServerAddrParseError)?;
+ Ok(ServerAddr { host, port })
+ }
+}
+impl TryFrom<String> for ServerAddr {
+ type Error = ServerAddrParseError;
+
+ fn try_from(string: String) -> Result<Self, Self::Error> {
+ ServerAddr::try_from(string.as_str())
+ }
+}
+
+impl From<SocketAddr> for ServerAddr {
+ /// Convert an existing `SocketAddr` into a `ServerAddress`.
+ ///
+ /// This just converts the IP to a string and passes along the port. The
+ /// resolver will realize it's already an IP address and not do any DNS
+ /// requests.
+ fn from(addr: SocketAddr) -> Self {
+ ServerAddr {
+ host: addr.ip().to_string(),
+ port: addr.port(),
+ }
+ }
+}
+
+impl Display for ServerAddr {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}:{}", self.host, self.port)
+ }
+}
+
+/// Serde deserialization for ServerAddress.
+///
+/// This is useful if you're storing the server address in a config file.
+impl<'de> serde::Deserialize<'de> for ServerAddr {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let string = String::deserialize(deserializer)?;
+ ServerAddr::try_from(string.as_str())
+ .map_err(|_| serde::de::Error::custom("failed to parse address"))
+ }
+}
+
+/// Serde serialization for ServerAddress.
+///
+/// This uses the Display impl, so it will serialize to a string.
+impl serde::Serialize for ServerAddr {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ serializer.serialize_str(&self.to_string())
+ }
+}
+
+/// An address that may be used to connect to a Minecraft server.
+#[derive(Debug, Clone)]
+pub struct ResolvedAddr {
+ /// The initial address that we passed when trying to connect.
+ ///
+ /// This is necessary because clients send this to the server when they
+ /// connect.
+ pub server: ServerAddr,
+ /// The IP and port that we will actually connect to.
+ pub socket: SocketAddr,
+}
+
+impl ResolvedAddr {
+ pub async fn new(server: impl Into<ServerAddr>) -> Result<Self, ResolveError> {
+ let server = server.into();
+ let socket = resolve_address(&server).await?;
+ Ok(Self { server, socket })
+ }
+}
diff --git a/azalea-protocol/src/lib.rs b/azalea-protocol/src/lib.rs
index b481afb8..803f8630 100644
--- a/azalea-protocol/src/lib.rs
+++ b/azalea-protocol/src/lib.rs
@@ -12,12 +12,7 @@
// this is necessary for thiserror backtraces
#![feature(error_generic_member_access)]
-use std::{
- fmt::{self, Display},
- net::SocketAddr,
- str::FromStr,
-};
-
+pub mod address;
pub mod common;
#[cfg(feature = "connecting")]
pub mod connect;
@@ -27,98 +22,14 @@ pub mod resolve;
pub mod write;
#[doc(hidden)]
-#[deprecated(note = "Renamed to resolve")]
+#[deprecated(note = "renamed to `resolve`.")]
pub mod resolver {
pub use super::resolve::*;
}
-/// A host and port. It's possible that the port doesn't resolve to anything.
-///
-/// # Examples
-///
-/// `ServerAddress` implements TryFrom<&str>, so you can use it like this:
-/// ```
-/// use azalea_protocol::ServerAddress;
-///
-/// let addr = ServerAddress::try_from("localhost:25565").unwrap();
-/// assert_eq!(addr.host, "localhost");
-/// assert_eq!(addr.port, 25565);
-/// ```
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ServerAddress {
- pub host: String,
- pub port: u16,
-}
-
-impl TryFrom<&str> for ServerAddress {
- type Error = String;
-
- /// Convert a Minecraft server address (host:port, the port is optional) to
- /// a `ServerAddress`
- fn try_from(string: &str) -> Result<Self, Self::Error> {
- if string.is_empty() {
- return Err("Empty string".to_string());
- }
- let mut parts = string.split(':');
- let host = parts.next().ok_or("No host specified")?.to_string();
- // default the port to 25565
- let port = parts.next().unwrap_or("25565");
- let port = u16::from_str(port).map_err(|_| "Invalid port specified")?;
- Ok(ServerAddress { host, port })
- }
-}
-impl TryFrom<String> for ServerAddress {
- type Error = String;
-
- fn try_from(string: String) -> Result<Self, Self::Error> {
- ServerAddress::try_from(string.as_str())
- }
-}
-
-impl From<SocketAddr> for ServerAddress {
- /// Convert an existing `SocketAddr` into a `ServerAddress`.
- ///
- /// This just converts the IP to a string and passes along the port. The
- /// resolver will realize it's already an IP address and not do any DNS
- /// requests.
- fn from(addr: SocketAddr) -> Self {
- ServerAddress {
- host: addr.ip().to_string(),
- port: addr.port(),
- }
- }
-}
-
-impl Display for ServerAddress {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}:{}", self.host, self.port)
- }
-}
-
-/// Serde deserialization for ServerAddress.
-///
-/// This is useful if you're storing the server address in a config file.
-impl<'de> serde::Deserialize<'de> for ServerAddress {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- let string = String::deserialize(deserializer)?;
- ServerAddress::try_from(string.as_str()).map_err(serde::de::Error::custom)
- }
-}
-
-/// Serde serialization for ServerAddress.
-///
-/// This uses the Display impl, so it will serialize to a string.
-impl serde::Serialize for ServerAddress {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- serializer.serialize_str(&self.to_string())
- }
-}
+#[doc(hidden)]
+#[deprecated(note = "moved to `address::ServerAddr`.")]
+pub type ServerAddress = address::ServerAddr;
#[cfg(test)]
mod tests {
diff --git a/azalea-protocol/src/resolve.rs b/azalea-protocol/src/resolve.rs
index 468371aa..92939c8c 100644
--- a/azalea-protocol/src/resolve.rs
+++ b/azalea-protocol/src/resolve.rs
@@ -8,7 +8,7 @@ use std::{
pub use hickory_resolver::ResolveError;
use hickory_resolver::{Name, TokioResolver, name_server::TokioConnectionProvider};
-use crate::ServerAddress;
+use crate::address::ServerAddr;
#[doc(hidden)]
#[deprecated(note = "Renamed to ResolveError")]
@@ -23,7 +23,7 @@ static RESOLVER: LazyLock<TokioResolver> = LazyLock::new(|| {
/// Resolve a Minecraft server address into an IP address and port.
///
/// If it's already an IP address, it's returned as-is.
-pub async fn resolve_address(mut address: &ServerAddress) -> Result<SocketAddr, ResolveError> {
+pub async fn resolve_address(mut address: &ServerAddr) -> Result<SocketAddr, ResolveError> {
let redirect = resolve_srv_redirect(address).await;
if let Ok(redirect_target) = &redirect {
address = redirect_target;
@@ -32,7 +32,7 @@ pub async fn resolve_address(mut address: &ServerAddress) -> Result<SocketAddr,
resolve_ip_without_redirects(address).await
}
-async fn resolve_ip_without_redirects(address: &ServerAddress) -> Result<SocketAddr, ResolveError> {
+async fn resolve_ip_without_redirects(address: &ServerAddr) -> Result<SocketAddr, ResolveError> {
if let Ok(ip) = address.host.parse::<IpAddr>() {
// no need to do a lookup
return Ok(SocketAddr::new(ip, address.port));
@@ -51,7 +51,7 @@ async fn resolve_ip_without_redirects(address: &ServerAddress) -> Result<SocketA
Ok(SocketAddr::new(ip, address.port))
}
-async fn resolve_srv_redirect(address: &ServerAddress) -> Result<ServerAddress, ResolveError> {
+async fn resolve_srv_redirect(address: &ServerAddr) -> Result<ServerAddr, ResolveError> {
if address.port != 25565 {
return Err(ResolveError::from("Port must be 25565 to do a SRV lookup"));
}
@@ -63,7 +63,7 @@ async fn resolve_srv_redirect(address: &ServerAddress) -> Result<ServerAddress,
.iter()
.next()
.ok_or(ResolveError::from("No SRV record found"))?;
- Ok(ServerAddress {
+ Ok(ServerAddr {
host: srv.target().to_ascii(),
port: srv.port(),
})