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("zlib decompression failed: {0}")]
51 DecompressionFailed(#[from] libdeflater::DecompressionError),
52
53 #[error("zlib compression failed: {0}")]
55 CompressionFailed(#[from] libdeflater::CompressionError),
56
57 #[error("uncompressed size {size} exceeds maximum {max}")]
59 UncompressedSizeTooLarge {
60 size: usize,
62 max: usize,
64 },
65
66 #[error("frame too large: {size} bytes (max {max})")]
68 FrameTooLarge {
69 size: usize,
71 max: usize,
73 },
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78pub struct GameProfile {
79 pub id: Uuid,
81 pub name: String,
83 pub properties: Vec<ProfileProperty>,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct ProfileProperty {
90 pub name: String,
92 pub value: String,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub signature: Option<String>,
97}
98
99pub fn read_string(buf: &mut impl Buf) -> Result<String, ProtocolError> {
105 read_string_max(buf, MAX_STRING_LENGTH)
106}
107
108#[allow(clippy::cast_sign_loss)]
114pub fn read_string_max(buf: &mut impl Buf, max_len: usize) -> Result<String, ProtocolError> {
115 let length = varint::read_var_int(buf)? as usize;
116 if length > max_len * 4 {
117 return Err(ProtocolError::StringTooLong {
118 length,
119 max: max_len * 4,
120 });
121 }
122 if buf.remaining() < length {
123 return Err(ProtocolError::UnexpectedEof);
124 }
125
126 if buf.chunk().len() >= length {
128 let s = std::str::from_utf8(&buf.chunk()[..length])
129 .map_err(|_| ProtocolError::InvalidUtf8)?
130 .to_owned();
131 buf.advance(length);
132 return Ok(s);
133 }
134
135 let mut data = vec![0u8; length];
137 buf.copy_to_slice(&mut data);
138 String::from_utf8(data).map_err(|_| ProtocolError::InvalidUtf8)
139}
140
141#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
143pub fn write_string(buf: &mut impl BufMut, value: &str) {
144 let bytes = value.as_bytes();
145 varint::write_var_int(buf, bytes.len() as i32);
146 buf.put_slice(bytes);
147}
148
149pub fn read_uuid(buf: &mut impl Buf) -> Result<Uuid, ProtocolError> {
155 if buf.remaining() < 16 {
156 return Err(ProtocolError::UnexpectedEof);
157 }
158 let most = buf.get_u64();
159 let least = buf.get_u64();
160 Ok(Uuid::from_u64_pair(most, least))
161}
162
163pub fn write_uuid(buf: &mut impl BufMut, uuid: Uuid) {
165 let (most, least) = uuid.as_u64_pair();
166 buf.put_u64(most);
167 buf.put_u64(least);
168}
169
170#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
172pub fn write_properties(buf: &mut impl BufMut, properties: &[ProfileProperty]) {
173 varint::write_var_int(buf, properties.len() as i32);
174 for prop in properties {
175 write_string(buf, &prop.name);
176 write_string(buf, &prop.value);
177 if let Some(sig) = &prop.signature {
178 buf.put_u8(1); write_string(buf, sig);
180 } else {
181 buf.put_u8(0); }
183 }
184}
185
186#[allow(clippy::cast_sign_loss)]
192pub fn read_properties(buf: &mut impl Buf) -> Result<Vec<ProfileProperty>, ProtocolError> {
193 let count = varint::read_var_int(buf)? as usize;
194 let mut properties = Vec::with_capacity(count);
195 for _ in 0..count {
196 let name = read_string(buf)?;
197 let value = read_string(buf)?;
198 let has_signature = if buf.remaining() < 1 {
199 return Err(ProtocolError::UnexpectedEof);
200 } else {
201 buf.get_u8() != 0
202 };
203 let signature = if has_signature {
204 Some(read_string(buf)?)
205 } else {
206 None
207 };
208 properties.push(ProfileProperty {
209 name,
210 value,
211 signature,
212 });
213 }
214 Ok(properties)
215}
216
217#[allow(clippy::cast_possible_truncation)]
221fn write_nbt_string_tag(buf: &mut impl BufMut, name: &str, value: &str) {
222 buf.put_u8(0x08); buf.put_u16(name.len() as u16);
224 buf.put_slice(name.as_bytes());
225 buf.put_u16(value.len() as u16);
226 buf.put_slice(value.as_bytes());
227}
228
229pub fn write_nbt_text_component(buf: &mut impl BufMut, text: &str, color: Option<&str>) {
238 buf.put_u8(0x0A); write_nbt_string_tag(buf, "text", text);
240 if let Some(color) = color {
241 write_nbt_string_tag(buf, "color", color);
242 }
243 buf.put_u8(0x00); }
245
246#[must_use]
248pub fn undashed_uuid(uuid: Uuid) -> String {
249 uuid.as_simple().to_string()
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use proptest::prelude::*;
256
257 proptest! {
258 #[test]
259 fn roundtrip_any_string(s in ".{0,1024}") {
260 let mut buf = Vec::new();
261 write_string(&mut buf, &s);
262 let result = read_string(&mut &buf[..]).unwrap();
263 prop_assert_eq!(result, s);
264 }
265
266 #[test]
267 fn roundtrip_any_uuid(u in any::<u128>()) {
268 let uuid = Uuid::from_u128(u);
269 let mut buf = Vec::new();
270 write_uuid(&mut buf, uuid);
271 let result = read_uuid(&mut &buf[..]).unwrap();
272 prop_assert_eq!(result, uuid);
273 }
274
275 #[test]
276 fn roundtrip_any_properties(
277 props in prop::collection::vec(
278 (
279 ".{0,32}",
280 ".{0,1024}",
281 prop::option::weighted(0.5, ".{0,1024}")
282 ).prop_map(|(name, value, signature)| ProfileProperty { name, value, signature }),
283 0..4
284 )
285 ) {
286 let mut buf = Vec::new();
287 write_properties(&mut buf, &props);
288 let result = read_properties(&mut &buf[..]).unwrap();
289 prop_assert_eq!(result, props);
290 }
291 }
292
293 #[test]
294 fn test_string_roundtrip() {
295 let mut buf = Vec::new();
296 write_string(&mut buf, "Hello, Minecraft!");
297 let result = read_string(&mut &buf[..]).unwrap();
298 assert_eq!(result, "Hello, Minecraft!");
299 }
300
301 #[test]
302 fn test_string_empty() {
303 let mut buf = Vec::new();
304 write_string(&mut buf, "");
305 let result = read_string(&mut &buf[..]).unwrap();
306 assert_eq!(result, "");
307 }
308
309 #[test]
310 fn test_uuid_roundtrip() {
311 let uuid = Uuid::parse_str("069a79f4-44e9-4726-a5be-fca90e38aaf5").unwrap();
312 let mut buf = Vec::new();
313 write_uuid(&mut buf, uuid);
314 assert_eq!(buf.len(), 16);
315 let result = read_uuid(&mut &buf[..]).unwrap();
316 assert_eq!(result, uuid);
317 }
318
319 #[test]
320 fn test_properties_roundtrip() {
321 let props = vec![
322 ProfileProperty {
323 name: "textures".to_string(),
324 value: "base64data".to_string(),
325 signature: Some("sig".to_string()),
326 },
327 ProfileProperty {
328 name: "other".to_string(),
329 value: "val".to_string(),
330 signature: None,
331 },
332 ];
333 let mut buf = Vec::new();
334 write_properties(&mut buf, &props);
335 let result = read_properties(&mut &buf[..]).unwrap();
336 assert_eq!(result.len(), 2);
337 assert_eq!(result[0].name, "textures");
338 assert_eq!(result[0].signature.as_deref(), Some("sig"));
339 assert_eq!(result[1].signature, None);
340 }
341
342 #[test]
343 fn test_nbt_text_component_simple() {
344 let mut buf = Vec::new();
345 write_nbt_text_component(&mut buf, "hello", None);
346 assert_eq!(buf[0], 0x0A); assert_eq!(buf[1], 0x08); assert_eq!(&buf[2..4], &[0x00, 0x04]); assert_eq!(&buf[4..8], b"text");
351 assert_eq!(&buf[8..10], &[0x00, 0x05]); assert_eq!(&buf[10..15], b"hello");
353 assert_eq!(buf[15], 0x00); assert_eq!(buf.len(), 16);
355 }
356
357 #[test]
358 fn test_nbt_text_component_with_color() {
359 let mut buf = Vec::new();
360 write_nbt_text_component(&mut buf, "hi", Some("yellow"));
361 assert_eq!(buf[0], 0x0A); assert_eq!(buf[1], 0x08);
364 assert_eq!(&buf[2..4], &[0x00, 0x04]);
365 assert_eq!(&buf[4..8], b"text");
366 assert_eq!(&buf[8..10], &[0x00, 0x02]);
367 assert_eq!(&buf[10..12], b"hi");
368 assert_eq!(buf[12], 0x08);
370 assert_eq!(&buf[13..15], &[0x00, 0x05]);
371 assert_eq!(&buf[15..20], b"color");
372 assert_eq!(&buf[20..22], &[0x00, 0x06]);
373 assert_eq!(&buf[22..28], b"yellow");
374 assert_eq!(buf[28], 0x00);
376 assert_eq!(buf.len(), 29);
377 }
378}