1use bytes::{Buf, BufMut};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::varint;
8
9const MAX_STRING_LENGTH: usize = 32767;
11
12const MAX_PROPERTIES: usize = 16;
19
20#[derive(Debug, thiserror::Error)]
22pub enum ProtocolError {
23 #[error("VarInt is too long (exceeded 5 bytes)")]
25 VarIntTooLong,
26
27 #[error("unexpected end of data")]
29 UnexpectedEof,
30
31 #[error("string too long: {length} > {max}")]
33 StringTooLong {
34 length: usize,
36 max: usize,
38 },
39
40 #[error("invalid UTF-8 in string")]
42 InvalidUtf8,
43
44 #[error("unknown packet ID {id:#04x} in state {state}")]
46 UnknownPacket {
47 id: i32,
49 state: String,
51 },
52
53 #[error("invalid packet data: {0}")]
55 InvalidData(String),
56
57 #[error("zlib decompression failed: {0}")]
59 DecompressionFailed(#[from] libdeflater::DecompressionError),
60
61 #[error("zlib compression failed: {0}")]
63 CompressionFailed(#[from] libdeflater::CompressionError),
64
65 #[error("uncompressed size {size} exceeds maximum {max}")]
67 UncompressedSizeTooLarge {
68 size: usize,
70 max: usize,
72 },
73
74 #[error("frame too large: {size} bytes (max {max})")]
76 FrameTooLarge {
77 size: usize,
79 max: usize,
81 },
82
83 #[error("read buffer overflow: {size} bytes (max {max})")]
85 ReadBufferOverflow {
86 size: usize,
88 max: usize,
90 },
91
92 #[error("property count too large: {count} (max {max})")]
94 PropertyCountExceeded {
95 count: i32,
97 max: usize,
99 },
100
101 #[error("byte array too long: {length} > {max}")]
103 ByteArrayTooLong {
104 length: usize,
106 max: usize,
108 },
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113pub struct GameProfile {
114 pub id: Uuid,
116 pub name: String,
118 pub properties: Vec<ProfileProperty>,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct ProfileProperty {
125 pub name: String,
127 pub value: String,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub signature: Option<String>,
132}
133
134pub fn read_string(buf: &mut impl Buf) -> Result<String, ProtocolError> {
140 read_string_max(buf, MAX_STRING_LENGTH)
141}
142
143#[allow(clippy::cast_sign_loss)]
149pub fn read_string_max(buf: &mut impl Buf, max_len: usize) -> Result<String, ProtocolError> {
150 let length = varint::read_var_int(buf)? as usize;
151 if length > max_len * 4 {
152 return Err(ProtocolError::StringTooLong {
153 length,
154 max: max_len * 4,
155 });
156 }
157 if buf.remaining() < length {
158 return Err(ProtocolError::UnexpectedEof);
159 }
160
161 if buf.chunk().len() >= length {
163 let s = std::str::from_utf8(&buf.chunk()[..length])
164 .map_err(|_| ProtocolError::InvalidUtf8)?
165 .to_owned();
166 buf.advance(length);
167 return Ok(s);
168 }
169
170 let mut data = vec![0u8; length];
172 buf.copy_to_slice(&mut data);
173 String::from_utf8(data).map_err(|_| ProtocolError::InvalidUtf8)
174}
175
176#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
178pub fn write_string(buf: &mut impl BufMut, value: &str) {
179 let bytes = value.as_bytes();
180 varint::write_var_int(buf, bytes.len() as i32);
181 buf.put_slice(bytes);
182}
183
184pub fn read_uuid(buf: &mut impl Buf) -> Result<Uuid, ProtocolError> {
190 if buf.remaining() < 16 {
191 return Err(ProtocolError::UnexpectedEof);
192 }
193 let most = buf.get_u64();
194 let least = buf.get_u64();
195 Ok(Uuid::from_u64_pair(most, least))
196}
197
198pub fn write_uuid(buf: &mut impl BufMut, uuid: Uuid) {
200 let (most, least) = uuid.as_u64_pair();
201 buf.put_u64(most);
202 buf.put_u64(least);
203}
204
205#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
207pub fn write_properties(buf: &mut impl BufMut, properties: &[ProfileProperty]) {
208 varint::write_var_int(buf, properties.len() as i32);
209 for prop in properties {
210 write_string(buf, &prop.name);
211 write_string(buf, &prop.value);
212 if let Some(sig) = &prop.signature {
213 buf.put_u8(1); write_string(buf, sig);
215 } else {
216 buf.put_u8(0); }
218 }
219}
220
221pub fn read_properties(buf: &mut impl Buf) -> Result<Vec<ProfileProperty>, ProtocolError> {
229 let raw_count = varint::read_var_int(buf)?;
230 let count = usize::try_from(raw_count)
231 .ok()
232 .filter(|&n| n <= MAX_PROPERTIES)
233 .ok_or(ProtocolError::PropertyCountExceeded {
234 count: raw_count,
235 max: MAX_PROPERTIES,
236 })?;
237 let mut properties = Vec::with_capacity(count);
238 for _ in 0..count {
239 let name = read_string(buf)?;
240 let value = read_string(buf)?;
241 let has_signature = if buf.remaining() < 1 {
242 return Err(ProtocolError::UnexpectedEof);
243 } else {
244 buf.get_u8() != 0
245 };
246 let signature = if has_signature {
247 Some(read_string(buf)?)
248 } else {
249 None
250 };
251 properties.push(ProfileProperty {
252 name,
253 value,
254 signature,
255 });
256 }
257 Ok(properties)
258}
259
260#[allow(clippy::cast_possible_truncation)]
264fn write_nbt_string_tag(buf: &mut impl BufMut, name: &str, value: &str) {
265 buf.put_u8(0x08); buf.put_u16(name.len() as u16);
267 buf.put_slice(name.as_bytes());
268 buf.put_u16(value.len() as u16);
269 buf.put_slice(value.as_bytes());
270}
271
272pub fn write_nbt_text_component(buf: &mut impl BufMut, text: &str, color: Option<&str>) {
281 buf.put_u8(0x0A); write_nbt_string_tag(buf, "text", text);
283 if let Some(color) = color {
284 write_nbt_string_tag(buf, "color", color);
285 }
286 buf.put_u8(0x00); }
288
289#[must_use]
291pub fn undashed_uuid(uuid: Uuid) -> String {
292 uuid.as_simple().to_string()
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use proptest::prelude::*;
299
300 proptest! {
301 #[test]
302 fn roundtrip_any_string(s in ".{0,1024}") {
303 let mut buf = Vec::new();
304 write_string(&mut buf, &s);
305 let result = read_string(&mut &buf[..]).unwrap();
306 prop_assert_eq!(result, s);
307 }
308
309 #[test]
310 fn roundtrip_any_uuid(u in any::<u128>()) {
311 let uuid = Uuid::from_u128(u);
312 let mut buf = Vec::new();
313 write_uuid(&mut buf, uuid);
314 let result = read_uuid(&mut &buf[..]).unwrap();
315 prop_assert_eq!(result, uuid);
316 }
317
318 #[test]
319 fn roundtrip_any_properties(
320 props in prop::collection::vec(
321 (
322 ".{0,32}",
323 ".{0,1024}",
324 prop::option::weighted(0.5, ".{0,1024}")
325 ).prop_map(|(name, value, signature)| ProfileProperty { name, value, signature }),
326 0..4
327 )
328 ) {
329 let mut buf = Vec::new();
330 write_properties(&mut buf, &props);
331 let result = read_properties(&mut &buf[..]).unwrap();
332 prop_assert_eq!(result, props);
333 }
334 }
335
336 #[test]
337 fn test_string_roundtrip() {
338 let mut buf = Vec::new();
339 write_string(&mut buf, "Hello, Minecraft!");
340 let result = read_string(&mut &buf[..]).unwrap();
341 assert_eq!(result, "Hello, Minecraft!");
342 }
343
344 #[test]
345 fn test_string_empty() {
346 let mut buf = Vec::new();
347 write_string(&mut buf, "");
348 let result = read_string(&mut &buf[..]).unwrap();
349 assert_eq!(result, "");
350 }
351
352 #[test]
353 fn test_uuid_roundtrip() {
354 let uuid = Uuid::parse_str("069a79f4-44e9-4726-a5be-fca90e38aaf5").unwrap();
355 let mut buf = Vec::new();
356 write_uuid(&mut buf, uuid);
357 assert_eq!(buf.len(), 16);
358 let result = read_uuid(&mut &buf[..]).unwrap();
359 assert_eq!(result, uuid);
360 }
361
362 #[test]
363 fn test_properties_roundtrip() {
364 let props = vec![
365 ProfileProperty {
366 name: "textures".to_string(),
367 value: "base64data".to_string(),
368 signature: Some("sig".to_string()),
369 },
370 ProfileProperty {
371 name: "other".to_string(),
372 value: "val".to_string(),
373 signature: None,
374 },
375 ];
376 let mut buf = Vec::new();
377 write_properties(&mut buf, &props);
378 let result = read_properties(&mut &buf[..]).unwrap();
379 assert_eq!(result.len(), 2);
380 assert_eq!(result[0].name, "textures");
381 assert_eq!(result[0].signature.as_deref(), Some("sig"));
382 assert_eq!(result[1].signature, None);
383 }
384
385 #[test]
386 fn test_properties_rejects_huge_count() {
387 let mut buf = Vec::new();
388 varint::write_var_int(&mut buf, i32::MAX);
389 let err = read_properties(&mut &buf[..]).unwrap_err();
390 assert!(
391 matches!(err, ProtocolError::PropertyCountExceeded { count, max } if count == i32::MAX && max == MAX_PROPERTIES),
392 "expected PropertyCountExceeded, got {err:?}"
393 );
394 }
395
396 #[test]
397 fn test_properties_rejects_negative_count() {
398 let mut buf = Vec::new();
399 varint::write_var_int(&mut buf, -1);
400 let err = read_properties(&mut &buf[..]).unwrap_err();
401 assert!(
402 matches!(err, ProtocolError::PropertyCountExceeded { count, max } if count == -1 && max == MAX_PROPERTIES),
403 "expected PropertyCountExceeded, got {err:?}"
404 );
405 }
406
407 #[test]
408 fn test_properties_rejects_count_above_max() {
409 let mut buf = Vec::new();
410 varint::write_var_int(&mut buf, i32::try_from(MAX_PROPERTIES + 1).unwrap());
412 let err = read_properties(&mut &buf[..]).unwrap_err();
413 assert!(
414 matches!(err, ProtocolError::PropertyCountExceeded { .. }),
415 "expected PropertyCountExceeded, got {err:?}"
416 );
417 }
418
419 #[test]
420 fn test_properties_accepts_empty() {
421 let mut buf = Vec::new();
422 varint::write_var_int(&mut buf, 0);
423 let result = read_properties(&mut &buf[..]).unwrap();
424 assert!(result.is_empty());
425 }
426
427 #[test]
428 fn test_properties_accepts_count_at_max() {
429 let props: Vec<ProfileProperty> = (0..MAX_PROPERTIES)
431 .map(|i| ProfileProperty {
432 name: format!("prop{i}"),
433 value: format!("val{i}"),
434 signature: None,
435 })
436 .collect();
437 let mut buf = Vec::new();
438 write_properties(&mut buf, &props);
439 let result = read_properties(&mut &buf[..]).unwrap();
440 assert_eq!(result.len(), MAX_PROPERTIES);
441 }
442
443 #[test]
444 fn test_nbt_text_component_simple() {
445 let mut buf = Vec::new();
446 write_nbt_text_component(&mut buf, "hello", None);
447 assert_eq!(buf[0], 0x0A); assert_eq!(buf[1], 0x08); assert_eq!(&buf[2..4], &[0x00, 0x04]); assert_eq!(&buf[4..8], b"text");
452 assert_eq!(&buf[8..10], &[0x00, 0x05]); assert_eq!(&buf[10..15], b"hello");
454 assert_eq!(buf[15], 0x00); assert_eq!(buf.len(), 16);
456 }
457
458 #[test]
459 fn test_nbt_text_component_with_color() {
460 let mut buf = Vec::new();
461 write_nbt_text_component(&mut buf, "hi", Some("yellow"));
462 assert_eq!(buf[0], 0x0A); assert_eq!(buf[1], 0x08);
465 assert_eq!(&buf[2..4], &[0x00, 0x04]);
466 assert_eq!(&buf[4..8], b"text");
467 assert_eq!(&buf[8..10], &[0x00, 0x02]);
468 assert_eq!(&buf[10..12], b"hi");
469 assert_eq!(buf[12], 0x08);
471 assert_eq!(&buf[13..15], &[0x00, 0x05]);
472 assert_eq!(&buf[15..20], b"color");
473 assert_eq!(&buf[20..22], &[0x00, 0x06]);
474 assert_eq!(&buf[22..28], b"yellow");
475 assert_eq!(buf[28], 0x00);
477 assert_eq!(buf.len(), 29);
478 }
479}