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}