mc_ping/packets.rs
1use anyhow::Context;
2use crate::mc_text::ServerStatus;
3use crate::varint::VarInt;
4
5/// Represents the Minecraft client handshake packet.
6///
7/// This packet initiates the handshake with the server before status or login requests.
8///
9/// # Example
10/// ```
11/// let handshake = ClientHandshake::new("127.0.0.1".to_string(), 25565);
12/// let bytes = handshake.to_bytes();
13/// ```
14#[derive(Debug)]
15pub struct ClientHandshake {
16 /// Length of the entire packet, encoded as a VarInt.
17 pub len: VarInt,
18 /// Packet ID (0x00 for handshake).
19 pub packet_id: VarInt,
20 /// Protocol version number, e.g., 768 for Minecraft 1.21.
21 pub protocol_version: VarInt,
22 /// Server address as a string (domain or IP).
23 pub server_addr: String,
24 /// Server port number.
25 pub server_port: u16,
26 /// Next state after handshake: 1 = status, 2 = login.
27 pub next_state: VarInt,
28}
29
30impl ClientHandshake {
31 /// Creates a new ClientHandshake packet for the given server address and port.
32 ///
33 /// Automatically calculates packet length and uses default protocol version 768.
34 pub fn new(server_addr: String, server_port: u16) -> ClientHandshake {
35 let packet_id = VarInt::from(0x00);
36 let protocol_version = VarInt::from(768);
37 let next_state = VarInt::from(1);
38
39 // Calculate length of the packet payload:
40 // packet_id + protocol_version + length of server_addr string + server_addr bytes + port(2 bytes) + next_state
41 let len_val =
42 packet_id.size() +
43 protocol_version.size() +
44 VarInt::from(server_addr.len() as i32).size() +
45 server_addr.len() +
46 2 + // server_port is 2 bytes
47 next_state.size();
48
49 let len = VarInt::from(len_val as i32);
50
51 let handshake = ClientHandshake {
52 len,
53 packet_id,
54 protocol_version,
55 server_addr,
56 server_port,
57 next_state,
58 };
59
60
61 handshake
62 }
63
64 /// Serializes the handshake packet into a byte vector ready for sending over the network.
65 ///
66 /// The format follows Minecraft's VarInt and packet structure conventions.
67 pub fn to_bytes(&self) -> Vec<u8> {
68 let mut buf = Vec::new();
69
70 // Helper function to write VarInt bytes until continuation bit is zero.
71 fn write_varint_bytes(buf: &mut Vec<u8>, varint_inner: &[u8]) {
72 for &byte in varint_inner {
73 buf.push(byte);
74 if byte & 0b1000_0000 == 0 {
75 break;
76 }
77 }
78 }
79
80 // Write packet length
81 write_varint_bytes(&mut buf, &self.len.inner);
82 // Write packet ID
83 write_varint_bytes(&mut buf, &self.packet_id.inner);
84 // Write protocol version
85 write_varint_bytes(&mut buf, &self.protocol_version.inner);
86
87 // Write server address as Minecraft String: VarInt length + UTF-8 bytes
88 let addr_len = VarInt::from(self.server_addr.len() as i32);
89 write_varint_bytes(&mut buf, &addr_len.inner);
90 buf.extend(self.server_addr.as_bytes());
91
92 // Write server port as 2 bytes big-endian
93 buf.push((self.server_port >> 8) as u8);
94 buf.push(self.server_port as u8);
95
96 // Write next state VarInt
97 write_varint_bytes(&mut buf, &self.next_state.inner);
98
99 buf
100 }
101}
102
103/// Represents the status query packet.
104///
105/// This packet is sent after handshake to request the server status.
106pub struct StatusQuery {
107 len: VarInt,
108 packet_id: VarInt,
109}
110
111impl StatusQuery {
112 /// Creates a new status query packet.
113 ///
114 /// # Example
115 /// ```
116 /// let query = StatusQuery::new();
117 /// let bytes = query.to_bytes();
118 /// ```
119 pub fn new() -> StatusQuery {
120 let packet_id = VarInt::from(0x00);
121 let len = VarInt::from(1); // Packet length: 1 byte for packet_id
122 StatusQuery {
123 len,
124 packet_id,
125 }
126 }
127
128 /// Returns the serialized bytes of the status query packet.
129 ///
130 /// This packet is always 2 bytes: [0x01, 0x00]
131 pub fn to_bytes(&self) -> Vec<u8> {
132 vec![0x01, 0x00]
133 }
134}
135
136/// Represents the server's response to a status query.
137///
138/// Contains the raw JSON string with server information.
139#[derive(Debug)]
140pub struct ServerQueryResponse {
141 /// Length of the entire response packet.
142 pub len: VarInt,
143 /// Packet ID (should be 0x00).
144 pub packet_id: VarInt,
145 /// Length of the JSON string.
146 pub json_len: VarInt,
147 /// JSON string with server status information.
148 pub json: String,
149}
150
151impl ServerQueryResponse {
152 /// Parses a ServerQueryResponse from raw bytes.
153 ///
154 /// Reads VarInts for lengths and packet IDs, then extracts the JSON string.
155 ///
156 /// # Panics
157 /// If the byte slice is too short or malformed, this may panic.
158 pub fn from(bytes: &[u8]) -> ServerQueryResponse {
159 // Helper to read a VarInt from a byte slice,
160 // returning the VarInt and number of bytes read.
161 fn read_varint(data: &[u8]) -> (VarInt, usize) {
162 let mut val = VarInt::default();
163 let mut i = 0;
164 loop {
165 let byte = data[i];
166 val.inner[i] = byte;
167 i += 1;
168 if byte & 0x80 == 0 {
169 break;
170 }
171 }
172 (val, i)
173 }
174
175 let mut cursor = 0;
176
177 // 1. Read length VarInt
178 let (len, len_size) = read_varint(&bytes[cursor..]);
179 cursor += len_size;
180
181 // 2. Read packet_id VarInt
182 let (packet_id, packet_id_size) = read_varint(&bytes[cursor..]);
183 cursor += packet_id_size;
184
185 // 3. Read json_len VarInt
186 let (json_len, json_len_size) = read_varint(&bytes[cursor..]);
187 cursor += json_len_size;
188
189 // 4. Read JSON bytes using length from json_len
190 let json_bytes = &bytes[cursor..cursor + i32::from(json_len.clone()) as usize];
191 cursor += i32::from(json_len.clone()) as usize;
192
193 let json = String::from_utf8_lossy(json_bytes).to_string();
194
195 ServerQueryResponse {
196 len,
197 packet_id,
198 json_len,
199 json,
200 }
201 }
202
203 /// Parses the JSON string into a strongly-typed ServerStatus struct.
204 ///
205 /// Returns an error if JSON deserialization fails.
206 ///
207 /// # Example
208 /// ```
209 /// let response = ServerQueryResponse::from(&bytes);
210 /// let status = response.parse_status()?;
211 /// ```
212 pub fn parse_status(&self) -> anyhow::Result<ServerStatus> {
213 let status: ServerStatus = serde_json::from_str(&self.json)
214 .context("Failed to deserialize ServerQueryResponse.json into ServerStatus")?;
215 Ok(status)
216 }
217}