async_minecraft_ping/
server.rs1use std::time::Duration;
5
6use serde::Deserialize;
7use thiserror::Error;
8use tokio::net::TcpStream;
9
10use crate::protocol::{self, AsyncReadRawPacket, AsyncWriteRawPacket};
11
12#[derive(Error, Debug)]
13pub enum ServerError {
14 #[error("error reading or writing data")]
15 ProtocolError,
16
17 #[error("failed to connect to server")]
18 FailedToConnect,
19
20 #[error("invalid JSON response: \"{0}\"")]
21 InvalidJson(String),
22
23 #[error("mismatched pong payload (expected \"{expected}\", got \"{actual}\")")]
24 MismatchedPayload { expected: u64, actual: u64 },
25}
26
27impl From<protocol::ProtocolError> for ServerError {
28 fn from(_err: protocol::ProtocolError) -> Self {
29 ServerError::ProtocolError
30 }
31}
32
33#[derive(Debug, Deserialize)]
35pub struct ServerVersion {
36 pub name: String,
38
39 pub protocol: u32,
41}
42
43#[derive(Debug, Deserialize)]
45pub struct ServerPlayer {
46 pub name: String,
48
49 pub id: String,
51}
52
53#[derive(Debug, Deserialize)]
56pub struct ServerPlayers {
57 pub max: u32,
60
61 pub online: u32,
63
64 pub sample: Option<Vec<ServerPlayer>>,
67}
68
69#[derive(Debug, Deserialize)]
71#[serde(untagged)]
72pub enum ServerDescription {
73 Plain(String),
74 Object { text: String },
75}
76
77#[derive(Debug, Deserialize)]
80pub struct StatusResponse {
81 pub version: ServerVersion,
83
84 pub players: ServerPlayers,
86
87 pub description: ServerDescription,
89
90 pub favicon: Option<String>,
93}
94
95const LATEST_PROTOCOL_VERSION: usize = 578;
96const DEFAULT_PORT: u16 = 25565;
97const DEFAULT_TIMEOUT: Duration = Duration::from_secs(2);
98
99pub struct ConnectionConfig {
102 protocol_version: usize,
103 address: String,
104 port: u16,
105 timeout: Duration,
106}
107
108impl ConnectionConfig {
109 pub fn build<T: Into<String>>(address: T) -> Self {
112 ConnectionConfig {
113 protocol_version: LATEST_PROTOCOL_VERSION,
114 address: address.into(),
115 port: DEFAULT_PORT,
116 timeout: DEFAULT_TIMEOUT,
117 }
118 }
119
120 pub fn with_protocol_version(mut self, protocol_version: usize) -> Self {
125 self.protocol_version = protocol_version;
126 self
127 }
128
129 pub fn with_port(mut self, port: u16) -> Self {
133 self.port = port;
134 self
135 }
136
137 pub fn with_timeout(mut self, timeout: Duration) -> Self {
141 self.timeout = timeout;
142 self
143 }
144
145 pub async fn connect(self) -> Result<StatusConnection, ServerError> {
147 let stream = TcpStream::connect(format!("{}:{}", self.address, self.port))
148 .await
149 .map_err(|_| ServerError::FailedToConnect)?;
150
151 Ok(StatusConnection {
152 stream,
153 protocol_version: self.protocol_version,
154 address: self.address,
155 port: self.port,
156 timeout: self.timeout,
157 })
158 }
159}
160
161pub async fn connect(address: String) -> Result<StatusConnection, ServerError> {
165 ConnectionConfig::build(address).connect().await
166}
167
168pub struct StatusConnection {
170 stream: TcpStream,
171 protocol_version: usize,
172 address: String,
173 port: u16,
174 timeout: Duration,
175}
176
177impl StatusConnection {
178 pub async fn status(mut self) -> Result<PingConnection, ServerError> {
186 let handshake = protocol::HandshakePacket::new(
187 self.protocol_version,
188 self.address.to_string(),
189 self.port,
190 );
191
192 self.stream
193 .write_packet_with_timeout(handshake, self.timeout.clone())
194 .await?;
195
196 self.stream
197 .write_packet_with_timeout(protocol::RequestPacket::new(), self.timeout.clone())
198 .await?;
199
200 let response: protocol::ResponsePacket = self
201 .stream
202 .read_packet_with_timeout(self.timeout.clone())
203 .await?;
204
205 let status: StatusResponse = serde_json::from_str(&response.body)
206 .map_err(|_| ServerError::InvalidJson(response.body))?;
207
208 Ok(PingConnection {
209 stream: self.stream,
210 protocol_version: self.protocol_version,
211 address: self.address,
212 port: self.port,
213 status,
214 timeout: self.timeout,
215 })
216 }
217}
218
219pub struct PingConnection {
224 stream: TcpStream,
225 protocol_version: usize,
226 address: String,
227 port: u16,
228 timeout: Duration,
229 pub status: StatusResponse,
230}
231
232impl PingConnection {
233 pub async fn ping(mut self, payload: u64) -> Result<(), ServerError> {
240 let ping = protocol::PingPacket::new(payload);
241
242 self.stream
243 .write_packet_with_timeout(ping, self.timeout.clone())
244 .await?;
245
246 let pong: protocol::PongPacket = self
247 .stream
248 .read_packet_with_timeout(self.timeout.clone())
249 .await?;
250
251 if pong.payload != payload {
252 return Err(ServerError::MismatchedPayload {
253 expected: payload,
254 actual: pong.payload,
255 }
256 .into());
257 }
258
259 Ok(())
260 }
261}