diff options
Diffstat (limited to 'azalea-protocol')
| -rw-r--r-- | azalea-protocol/src/address.rs | 157 | ||||
| -rw-r--r-- | azalea-protocol/src/lib.rs | 99 | ||||
| -rw-r--r-- | azalea-protocol/src/resolve.rs | 10 |
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(), }) |
