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}