1use bytes::{Buf, BufMut};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::varint;
8
9const MAX_STRING_LENGTH: usize = 32767;
11
12#[derive(Debug, thiserror::Error)]
14pub enum ProtocolError {
15 #[error("VarInt is too long (exceeded 5 bytes)")]
17 VarIntTooLong,
18
19 #[error("unexpected end of data")]
21 UnexpectedEof,
22
23 #[error("string too long: {length} > {max}")]
25 StringTooLong {
26 length: usize,
28 max: usize,
30 },
31
32 #[error("invalid UTF-8 in string")]
34 InvalidUtf8,
35
36 #[error("unknown packet ID {id:#04x} in state {state}")]
38 UnknownPacket {
39 id: i32,
41 state: String,
43 },
44
45 #[error("invalid packet data: {0}")]
47 InvalidData(String),
48
49 #[error("compression error: {0}")]
51 CompressionError(String),
52
53 #[error("frame too large: {size} bytes (max {max})")]
55 FrameTooLarge {
56 size: usize,
58 max: usize,
60 },
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct GameProfile {
66 pub id: Uuid,
68 pub name: String,
70 pub properties: Vec<ProfileProperty>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct ProfileProperty {
77 pub name: String,
79 pub value: String,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub signature: Option<String>,
84}
85
86pub fn read_string(buf: &mut impl Buf) -> Result<String, ProtocolError> {
92 read_string_max(buf, MAX_STRING_LENGTH)
93}
94
95#[allow(clippy::cast_sign_loss)]
101pub fn read_string_max(buf: &mut impl Buf, max_len: usize) -> Result<String, ProtocolError> {
102 let length = varint::read_var_int(buf)? as usize;
103 if length > max_len * 4 {
104 return Err(ProtocolError::StringTooLong {
105 length,
106 max: max_len * 4,
107 });
108 }
109 if buf.remaining() < length {
110 return Err(ProtocolError::UnexpectedEof);
111 }
112 let mut data = vec![0u8; length];
113 buf.copy_to_slice(&mut data);
114 String::from_utf8(data).map_err(|_| ProtocolError::InvalidUtf8)
115}
116
117#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
119pub fn write_string(buf: &mut impl BufMut, value: &str) {
120 let bytes = value.as_bytes();
121 varint::write_var_int(buf, bytes.len() as i32);
122 buf.put_slice(bytes);
123}
124
125pub fn read_uuid(buf: &mut impl Buf) -> Result<Uuid, ProtocolError> {
131 if buf.remaining() < 16 {
132 return Err(ProtocolError::UnexpectedEof);
133 }
134 let most = buf.get_u64();
135 let least = buf.get_u64();
136 Ok(Uuid::from_u64_pair(most, least))
137}
138
139pub fn write_uuid(buf: &mut impl BufMut, uuid: Uuid) {
141 let (most, least) = uuid.as_u64_pair();
142 buf.put_u64(most);
143 buf.put_u64(least);
144}
145
146#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
148pub fn write_properties(buf: &mut impl BufMut, properties: &[ProfileProperty]) {
149 varint::write_var_int(buf, properties.len() as i32);
150 for prop in properties {
151 write_string(buf, &prop.name);
152 write_string(buf, &prop.value);
153 if let Some(sig) = &prop.signature {
154 buf.put_u8(1); write_string(buf, sig);
156 } else {
157 buf.put_u8(0); }
159 }
160}
161
162#[allow(clippy::cast_sign_loss)]
168pub fn read_properties(buf: &mut impl Buf) -> Result<Vec<ProfileProperty>, ProtocolError> {
169 let count = varint::read_var_int(buf)? as usize;
170 let mut properties = Vec::with_capacity(count);
171 for _ in 0..count {
172 let name = read_string(buf)?;
173 let value = read_string(buf)?;
174 let has_signature = if buf.remaining() < 1 {
175 return Err(ProtocolError::UnexpectedEof);
176 } else {
177 buf.get_u8() != 0
178 };
179 let signature = if has_signature {
180 Some(read_string(buf)?)
181 } else {
182 None
183 };
184 properties.push(ProfileProperty {
185 name,
186 value,
187 signature,
188 });
189 }
190 Ok(properties)
191}
192
193#[allow(clippy::cast_possible_truncation)]
197fn write_nbt_string_tag(buf: &mut impl BufMut, name: &str, value: &str) {
198 buf.put_u8(0x08); buf.put_u16(name.len() as u16);
200 buf.put_slice(name.as_bytes());
201 buf.put_u16(value.len() as u16);
202 buf.put_slice(value.as_bytes());
203}
204
205pub fn write_nbt_text_component(buf: &mut impl BufMut, text: &str, color: Option<&str>) {
214 buf.put_u8(0x0A); write_nbt_string_tag(buf, "text", text);
216 if let Some(color) = color {
217 write_nbt_string_tag(buf, "color", color);
218 }
219 buf.put_u8(0x00); }
221
222#[must_use]
224pub fn undashed_uuid(uuid: Uuid) -> String {
225 uuid.as_simple().to_string()
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_string_roundtrip() {
234 let mut buf = Vec::new();
235 write_string(&mut buf, "Hello, Minecraft!");
236 let result = read_string(&mut &buf[..]).unwrap();
237 assert_eq!(result, "Hello, Minecraft!");
238 }
239
240 #[test]
241 fn test_string_empty() {
242 let mut buf = Vec::new();
243 write_string(&mut buf, "");
244 let result = read_string(&mut &buf[..]).unwrap();
245 assert_eq!(result, "");
246 }
247
248 #[test]
249 fn test_uuid_roundtrip() {
250 let uuid = Uuid::parse_str("069a79f4-44e9-4726-a5be-fca90e38aaf5").unwrap();
251 let mut buf = Vec::new();
252 write_uuid(&mut buf, uuid);
253 assert_eq!(buf.len(), 16);
254 let result = read_uuid(&mut &buf[..]).unwrap();
255 assert_eq!(result, uuid);
256 }
257
258 #[test]
259 fn test_properties_roundtrip() {
260 let props = vec![
261 ProfileProperty {
262 name: "textures".to_string(),
263 value: "base64data".to_string(),
264 signature: Some("sig".to_string()),
265 },
266 ProfileProperty {
267 name: "other".to_string(),
268 value: "val".to_string(),
269 signature: None,
270 },
271 ];
272 let mut buf = Vec::new();
273 write_properties(&mut buf, &props);
274 let result = read_properties(&mut &buf[..]).unwrap();
275 assert_eq!(result.len(), 2);
276 assert_eq!(result[0].name, "textures");
277 assert_eq!(result[0].signature.as_deref(), Some("sig"));
278 assert_eq!(result[1].signature, None);
279 }
280
281 #[test]
282 fn test_nbt_text_component_simple() {
283 let mut buf = Vec::new();
284 write_nbt_text_component(&mut buf, "hello", None);
285 assert_eq!(buf[0], 0x0A); assert_eq!(buf[1], 0x08); assert_eq!(&buf[2..4], &[0x00, 0x04]); assert_eq!(&buf[4..8], b"text");
290 assert_eq!(&buf[8..10], &[0x00, 0x05]); assert_eq!(&buf[10..15], b"hello");
292 assert_eq!(buf[15], 0x00); assert_eq!(buf.len(), 16);
294 }
295
296 #[test]
297 fn test_nbt_text_component_with_color() {
298 let mut buf = Vec::new();
299 write_nbt_text_component(&mut buf, "hi", Some("yellow"));
300 assert_eq!(buf[0], 0x0A); assert_eq!(buf[1], 0x08);
303 assert_eq!(&buf[2..4], &[0x00, 0x04]);
304 assert_eq!(&buf[4..8], b"text");
305 assert_eq!(&buf[8..10], &[0x00, 0x02]);
306 assert_eq!(&buf[10..12], b"hi");
307 assert_eq!(buf[12], 0x08);
309 assert_eq!(&buf[13..15], &[0x00, 0x05]);
310 assert_eq!(&buf[15..20], b"color");
311 assert_eq!(&buf[20..22], &[0x00, 0x06]);
312 assert_eq!(&buf[22..28], b"yellow");
313 assert_eq!(buf[28], 0x00);
315 assert_eq!(buf.len(), 29);
316 }
317}