mc_ping/connection.rs
1use tokio::io::{AsyncWriteExt, AsyncReadExt};
2use std::time::Duration;
3use tokio::net::TcpStream;
4use tokio::time::timeout;
5use crate::mc_text::ServerStatus;
6use crate::packets::{ClientHandshake, ServerQueryResponse, StatusQuery};
7use anyhow::{anyhow, Result};
8use tokio::net::lookup_host;
9
10
11fn is_domain(addr: &str) -> bool {
12 addr.parse::<std::net::IpAddr>().is_err()
13}
14
15/// Represents a TCP connection to a Minecraft server.
16///
17/// # Examples
18///
19/// ```no_run
20/// use std::time::Duration;
21/// # use anyhow::Result;
22/// # #[tokio::main]
23/// # async fn main() -> Result<()> {
24/// use mc_ping::connection::Connection;
25/// let addr = ("play.example.com".to_string(), 25565);
26/// let mut conn = Connection::connect(addr).await?;
27/// let status = conn.ping().await?;
28/// println!("Server status: {:?}", status);
29/// # Ok(())
30/// # }
31/// ```
32pub struct Connection {
33 /// Underlying TCP stream.
34 pub stream: TcpStream,
35
36 /// Server address as (IP/domain, port).
37 pub addr: (String, u16),
38}
39
40impl Connection {
41 /// Connects to a Minecraft server at the specified address.
42 ///
43 /// If the `resolve` feature is enabled, attempts to resolve domain names to IP addresses before connecting.
44 ///
45 /// # Errors
46 ///
47 /// Returns an error if the connection or DNS resolution fails.
48 ///
49 /// # Examples
50 ///
51 /// ```no_run
52 /// # use anyhow::Result;
53 /// # #[tokio::main]
54 /// # async fn main() -> Result<()> {
55 /// use mc_ping::connection::Connection;
56 /// let addr = ("localhost".to_string(), 25565);
57 /// let conn = Connection::connect(addr).await?;
58 /// # Ok(())
59 /// # }
60 /// ```
61 pub async fn connect(addr: (String, u16)) -> Result<Self> {
62 #[cfg(not(feature = "resolve"))]
63 {
64 if is_domain(&addr.0) {
65 return Err(anyhow!(r#"Enable feature "resolve" to enable domain resolving"#))
66 }
67 let stream = TcpStream::connect(addr.clone()).await?;
68 Ok(Self {
69 stream,
70 addr,
71 })
72 }
73 #[cfg(feature = "resolve")]
74 {
75 let host_port = format!("{}:{}", addr.0.clone(), addr.1);
76 let mut addrs = lookup_host(host_port.clone()).await?;
77 if let Some(sock_addr) = addrs.next() {
78 let stream = TcpStream::connect(sock_addr).await?;
79 Ok(Self {
80 stream,
81 addr: (addr.0, sock_addr.port()),
82 })
83 } else {
84 Err(anyhow::anyhow!("Could not resolve address: {}", host_port))
85 }
86 }
87 }
88
89 /// Connects to a Minecraft server with a timeout.
90 ///
91 /// Attempts to connect and returns an error if the timeout elapses.
92 ///
93 /// # Errors
94 ///
95 /// Returns an error if the connection fails or times out.
96 ///
97 /// # Examples
98 ///
99 /// ```no_run
100 /// # use std::time::Duration;
101 /// # use anyhow::Result;
102 /// # #[tokio::main]
103 /// # async fn main() -> Result<()> {
104 /// use mc_ping::connection::Connection;
105 /// let addr = ("example.com".to_string(), 25565);
106 /// match Connection::connect_timeout(addr, Duration::from_secs(5)).await {
107 /// Ok(conn) => println!("Connected!"),
108 /// Err(e) => println!("Failed to connect: {}", e),
109 /// }
110 /// # Ok(())
111 /// # }
112 /// ```
113 pub async fn connect_timeout(addr: (String, u16), _timeout: Duration) -> Result<Self> {
114 let _conn = timeout(_timeout, Self::connect(addr)).await;
115 match _conn {
116 Ok(Ok(conn)) => Ok(conn),
117 Err(err) => Err(anyhow::anyhow!("Could not connect: {} (timeout)", err))?,
118 Ok(Err(err)) => Err(anyhow::anyhow!("Could not connect: {}", err))?,
119 }
120 }
121
122 /// Sends the Minecraft handshake packet.
123 ///
124 /// This prepares the connection for further communication such as status query or login.
125 ///
126 /// # Errors
127 ///
128 /// Returns an error if writing to the TCP stream fails.
129 ///
130 /// # Examples
131 ///
132 /// ```no_run
133 /// # use anyhow::Result;
134 /// # #[tokio::main]
135 /// # async fn main() -> Result<()> {
136 /// use mc_ping::connection::Connection;
137 /// let addr = ("127.0.0.1".to_string(), 25565);
138 /// let mut conn = Connection::connect(addr).await?;
139 /// conn.send_handshake().await?;
140 /// # Ok(())
141 /// # }
142 /// ```
143 pub async fn send_handshake(&mut self) -> Result<()> {
144 let _ip = self.addr.0.clone();
145 let _port = self.addr.1;
146 let handshake = ClientHandshake::new(_ip, _port);
147 let bytes = handshake.to_bytes();
148 self.stream.write_all(bytes.as_slice()).await?;
149 Ok(())
150 }
151
152 /// Sends the status query packet.
153 ///
154 /// Internal helper function, generally not called directly.
155 async fn __send_query_packet(&mut self) -> Result<()> {
156 let query = StatusQuery::new();
157 let bytes = query.to_bytes();
158 self.stream.write_all(bytes.as_slice()).await?;
159 Ok(())
160 }
161
162 /// Reads and parses the server's status response packet.
163 ///
164 /// Internal helper function, generally not called directly.
165 async fn __read_status_packet(&mut self) -> Result<ServerQueryResponse> {
166 let mut buf = [0u8; 4096];
167 self.stream.read(&mut buf).await?;
168 let status_packet = ServerQueryResponse::from(&buf[..]);
169 Ok(status_packet)
170 }
171
172 /// Queries the server status.
173 ///
174 /// Sends a status query and reads the response, returning a parsed ServerStatus.
175 /// Assumes handshake has been sent beforehand.
176 ///
177 /// # Errors
178 ///
179 /// Returns an error if sending or receiving fails, or parsing fails.
180 ///
181 /// # Examples
182 ///
183 /// ```no_run
184 /// # use anyhow::Result;
185 /// # #[tokio::main]
186 /// # async fn main() -> Result<()> {
187 /// use mc_ping::connection::Connection;
188 /// let addr = ("localhost".to_string(), 25565);
189 /// let mut conn = Connection::connect(addr).await?;
190 /// conn.send_handshake().await?;
191 /// let status = conn.get_status().await?;
192 /// println!("Status: {:?}", status);
193 /// # Ok(())
194 /// # }
195 /// ```
196 pub async fn get_status(&mut self) -> Result<ServerStatus> {
197 self.__send_query_packet().await?;
198 let _status = self.__read_status_packet().await?;
199 Ok(_status.parse_status()?)
200 }
201
202 /// Performs a full ping: sends handshake + status query and returns server status.
203 ///
204 /// Convenient for a single-step status check.
205 ///
206 /// # Errors
207 ///
208 /// Returns an error if any network or parsing step fails.
209 ///
210 /// # Examples
211 ///
212 /// ```no_run
213 /// # use anyhow::Result;
214 /// # #[tokio::main]
215 /// # async fn main() -> Result<()> {
216 /// use mc_ping::connection::Connection;
217 /// let addr = ("play.example.com".to_string(), 25565);
218 /// let mut conn = Connection::connect(addr).await?;
219 /// let status = conn.ping().await?;
220 /// println!("Server status: {:?}", status);
221 /// # Ok(())
222 /// # }
223 /// ```
224 pub async fn ping(&mut self) -> Result<ServerStatus> {
225 self.send_handshake().await?;
226 self.__send_query_packet().await?;
227 let status = self.__read_status_packet().await?;
228 status.parse_status()
229 }
230}