1pub mod data;
5mod packet;
6
7use crate::{
8 errors::MinecraftProtocolError,
9 socket::{ReadWriteMinecraftString, ReadWriteVarInt},
10 varint::VarInt,
11};
12use tokio::{
13 io::{self, AsyncWriteExt, Interest},
14 net::TcpStream,
15};
16
17use self::{
18 data::StatusResponse,
19 packet::{Packet, PacketId},
20};
21
22pub async fn status(host: &str, port: u16) -> io::Result<StatusResponse> {
45 let mut socket = TcpStream::connect(format!("{host}:{port}")).await?;
46
47 socket
48 .ready(Interest::READABLE | Interest::WRITABLE)
49 .await?;
50
51 let handshake = Packet::builder(PacketId::Handshake)
54 .add_varint(&VarInt::from(-1))
55 .add_string(host)
56 .add_u16(port)
57 .add_varint(&VarInt::from(PacketId::Status))
58 .build();
59
60 socket.write_all(&handshake.bytes()).await?;
61
62 let status_request = Packet::builder(PacketId::Handshake).build();
65 socket.write_all(&status_request.bytes()).await?;
66
67 let _len = socket.read_varint().await?;
70 let id = socket.read_varint().await?;
71
72 if id != 0 {
73 return Err(MinecraftProtocolError::InvalidStatusResponse.into());
74 }
75
76 let data = socket.read_mc_string().await?;
77 socket.shutdown().await?;
78
79 serde_json::from_str::<StatusResponse>(&data)
80 .map_err(|_| MinecraftProtocolError::InvalidStatusResponse.into())
81}
82
83create_timeout!(status, StatusResponse);
84
85#[cfg(test)]
86mod tests {
87 use super::status;
88 use tokio::io::Result;
89
90 #[tokio::test]
91 async fn test_hypixel_status() -> Result<()> {
92 let data = status("mc.hypixel.net", 25565).await?;
93 println!("{data:#?}");
94
95 Ok(())
96 }
97
98 #[tokio::test]
99 async fn test_local_status() -> Result<()> {
100 let data = status("localhost", 25565).await?;
101 println!("{data:#?}");
102
103 Ok(())
104 }
105}