//! Ping Minecraft servers. use std::io; use azalea_protocol::{ address::{ResolvableAddr, ServerAddr}, connect::{Connection, ConnectionError, Proxy}, packets::{ ClientIntention, PROTOCOL_VERSION, handshake::{ ClientboundHandshakePacket, ServerboundHandshakePacket, s_intention::ServerboundIntention, }, status::{ ClientboundStatusPacket, c_status_response::ClientboundStatusResponse, s_status_request::ServerboundStatusRequest, }, }, resolve, }; use thiserror::Error; #[derive(Debug, Error)] pub enum PingError { #[error("{0}")] Resolve(#[from] resolve::ResolveError), #[error("{0}")] Connection(#[from] ConnectionError), #[error("{0}")] ReadPacket(#[from] Box), #[error("{0}")] WritePacket(#[from] io::Error), #[error("The given address could not be parsed into a ServerAddress")] InvalidAddress, } /// Ping a Minecraft server. /// /// # Examples /// /// ```rust,no_run /// use azalea_client::ping; /// /// #[tokio::main] /// async fn main() { /// let response = ping::ping_server("play.hypixel.net").await.unwrap(); /// println!("{}", response.description.to_ansi()); /// } /// ``` pub async fn ping_server( address: impl ResolvableAddr, ) -> Result { let address = address.resolve().await?; let conn = Connection::new(&address.socket).await?; ping_server_with_connection(address.server, conn).await } /// Ping a Minecraft server through a SOCKS5 proxy. pub async fn ping_server_with_proxy( address: impl ResolvableAddr, proxy: Proxy, ) -> Result { let address = address.resolve().await?; let conn = Connection::new_with_proxy(&address.socket, proxy).await?; ping_server_with_connection(address.server, conn).await } /// Ping a Minecraft server after we've already created a [`Connection`]. /// /// The `Connection` must still be in the handshake state (which is the state /// it's in immediately after it's created). pub async fn ping_server_with_connection( address: ServerAddr, mut conn: Connection, ) -> Result { // send the client intention packet and switch to the status state conn.write(ServerboundIntention { protocol_version: PROTOCOL_VERSION, hostname: address.host.clone(), port: address.port, intention: ClientIntention::Status, }) .await?; let mut conn = conn.status(); // send the empty status request packet conn.write(ServerboundStatusRequest {}).await?; let packet = conn.read().await?; loop { match packet { ClientboundStatusPacket::StatusResponse(p) => return Ok(p), ClientboundStatusPacket::PongResponse(_) => { // we should never get this packet since we didn't send a ping } } } }