1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
//! Get the status of a server using the [Server List Ping](https://wiki.vg/Server_List_Ping) protocol.
//! See documentation for [`status`] for more information.
pub mod data;
mod packet;
use crate::{
errors::MinecraftProtocolError,
socket::{ReadWriteMinecraftString, ReadWriteVarInt},
varint::VarInt,
};
use tokio::{
io::{self, AsyncWriteExt, Interest},
net::TcpStream,
};
use self::{
data::StatusResponse,
packet::{Packet, PacketId},
};
/// Ping the server for information following the [Server List Ping](https://wiki.vg/Server_List_Ping) protocol.
///
/// # Arguments
/// * `host` - A string slice that holds the hostname of the server to connect to.
/// * `port` - The port to connect to on that server.
///
/// # Errors
/// Returns `Err` if there was a network issue or the server sent invalid data.
///
/// # Examples
/// ```
/// use mc_query::status;
/// use tokio::io::Result;
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let data = status("mc.hypixel.net", 25565).await?;
/// println!("{data:#?}");
///
/// Ok(())
/// }
/// ```
pub async fn status(host: &str, port: u16) -> io::Result<StatusResponse> {
let mut socket = TcpStream::connect(format!("{host}:{port}")).await?;
socket
.ready(Interest::READABLE | Interest::WRITABLE)
.await?;
// handshake packet
// https://wiki.vg/Server_List_Ping#Handshake
let handshake = Packet::builder(PacketId::Handshake)
.add_varint(&VarInt::from(-1))
.add_string(host)
.add_u16(port)
.add_varint(&VarInt::from(PacketId::Status))
.build();
socket.write_all(&handshake.bytes()).await?;
// status request packet
// https://wiki.vg/Server_List_Ping#Status_Request
let status_request = Packet::builder(PacketId::Handshake).build();
socket.write_all(&status_request.bytes()).await?;
// listen to status response
// https://wiki.vg/Server_List_Ping#Status_Response
let _len = socket.read_varint().await?;
let id = socket.read_varint().await?;
if id != 0 {
return Err(MinecraftProtocolError::InvalidStatusResponse.into());
}
let data = socket.read_mc_string().await?;
socket.shutdown().await?;
serde_json::from_str::<StatusResponse>(&data)
.map_err(|_| MinecraftProtocolError::InvalidStatusResponse.into())
}
#[cfg(test)]
mod tests {
use super::status;
use tokio::io::Result;
#[tokio::test]
async fn test_hypixel_status() -> Result<()> {
let data = status("mc.hypixel.net", 25565).await?;
println!("{data:#?}");
Ok(())
}
#[tokio::test]
async fn test_local_status() -> Result<()> {
let data = status("localhost", 25565).await?;
println!("{data:#?}");
Ok(())
}
}