Skip to main content

mcproto_types/
basic.rs

1//! Basic Minecraft protocol types and their wire encodings.
2//!
3//! This module includes primitive numeric values, booleans, variable-length
4//! integers, length-prefixed strings, and resource identifiers.
5
6use crate::TypeCodec;
7use mcproto_codec::{
8    error::{CodecError, CodecKind, CodecOperation, InvalidEncodingReason},
9    io::{read_exact_counted, write_all_counted},
10    varint::{VarIntRead, VarIntWrite},
11    varlong::{VarLongRead, VarLongWrite},
12};
13use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
14use std::{
15    fmt,
16    io::{Read, Write},
17};
18
19/// A boolean encoded as `0x00` for false or `0x01` for true.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
21pub struct Boolean(
22    /// The boolean value.
23    pub bool,
24);
25
26impl TypeCodec for Boolean {
27    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
28        let byte = if self.0 { 1u8 } else { 0u8 };
29        write_all_counted(writer, &[byte], CodecKind::Boolean, 0)
30    }
31
32    fn decode(reader: &mut impl Read) -> Result<Self, CodecError>
33    where
34        Self: Sized,
35    {
36        let mut buf = [0u8; 1];
37        read_exact_counted(reader, &mut buf, CodecKind::Boolean, 0)?;
38        match buf[0] {
39            0 => Ok(Boolean(false)),
40            1 => Ok(Boolean(true)),
41            _ => Err(CodecError::invalid_encoding(
42                CodecKind::Boolean,
43                1,
44                InvalidEncodingReason::InvalidBooleanValue { value: buf[0] },
45            )),
46        }
47    }
48}
49
50/// A two's-complement signed 8-bit integer from -128 through 127.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
52pub struct Byte(
53    /// The integer value.
54    pub i8,
55);
56
57impl TypeCodec for Byte {
58    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
59        write_all_counted(writer, &self.0.to_be_bytes(), CodecKind::Byte, 0)
60    }
61
62    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
63        let mut bytes = [0; 1];
64        read_exact_counted(reader, &mut bytes, CodecKind::Byte, 0)?;
65        Ok(Self(i8::from_be_bytes(bytes)))
66    }
67}
68
69/// An unsigned 8-bit integer from 0 through 255.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
71pub struct UnsignedByte(
72    /// The integer value.
73    pub u8,
74);
75
76impl TypeCodec for UnsignedByte {
77    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
78        write_all_counted(writer, &self.0.to_be_bytes(), CodecKind::UnsignedByte, 0)
79    }
80
81    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
82        let mut bytes = [0; 1];
83        read_exact_counted(reader, &mut bytes, CodecKind::UnsignedByte, 0)?;
84        Ok(Self(u8::from_be_bytes(bytes)))
85    }
86}
87
88/// A two's-complement signed 16-bit integer from -32,768 through 32,767.
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
90pub struct Short(
91    /// The integer value.
92    pub i16,
93);
94
95impl TypeCodec for Short {
96    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
97        write_all_counted(writer, &self.0.to_be_bytes(), CodecKind::Short, 0)
98    }
99
100    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
101        let mut bytes = [0; 2];
102        read_exact_counted(reader, &mut bytes, CodecKind::Short, 0)?;
103        Ok(Self(i16::from_be_bytes(bytes)))
104    }
105}
106
107/// An unsigned 16-bit integer from 0 through 65,535.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
109pub struct UnsignedShort(
110    /// The integer value.
111    pub u16,
112);
113
114impl TypeCodec for UnsignedShort {
115    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
116        write_all_counted(writer, &self.0.to_be_bytes(), CodecKind::UnsignedShort, 0)
117    }
118
119    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
120        let mut bytes = [0; 2];
121        read_exact_counted(reader, &mut bytes, CodecKind::UnsignedShort, 0)?;
122        Ok(Self(u16::from_be_bytes(bytes)))
123    }
124}
125
126/// A two's-complement signed 32-bit integer from -2,147,483,648 through
127/// 2,147,483,647.
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
129pub struct Int(
130    /// The integer value.
131    pub i32,
132);
133
134impl TypeCodec for Int {
135    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
136        write_all_counted(writer, &self.0.to_be_bytes(), CodecKind::Int, 0)
137    }
138
139    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
140        let mut bytes = [0; 4];
141        read_exact_counted(reader, &mut bytes, CodecKind::Int, 0)?;
142        Ok(Self(i32::from_be_bytes(bytes)))
143    }
144}
145
146/// A two's-complement signed 64-bit integer from -9,223,372,036,854,775,808
147/// through 9,223,372,036,854,775,807.
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
149pub struct Long(
150    /// The integer value.
151    pub i64,
152);
153
154impl TypeCodec for Long {
155    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
156        write_all_counted(writer, &self.0.to_be_bytes(), CodecKind::Long, 0)
157    }
158
159    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
160        let mut bytes = [0; 8];
161        read_exact_counted(reader, &mut bytes, CodecKind::Long, 0)?;
162        Ok(Self(i64::from_be_bytes(bytes)))
163    }
164}
165
166/// A UTF-8 string prefixed by its byte length as a VarInt.
167///
168/// The protocol limits both the UTF-8 payload size and the number of UTF-16
169/// code units. Supplementary [Unicode scalar values] count as two UTF-16
170/// code units. The general protocol limit is 32,767 UTF-16 code units and
171/// three UTF-8 bytes per permitted code unit; a particular field may impose
172/// a lower limit.
173///
174/// [Unicode scalar values]: https://www.unicode.org/glossary/#unicode_scalar_value
175#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
176pub struct PrefixedString(
177    /// The string value.
178    pub String,
179);
180
181impl PrefixedString {
182    /// The maximum number of UTF-16 code units permitted in the string.
183    pub const MAX_UTF16_CODE_UNITS: usize = 0x7fff;
184    /// The maximum size of the UTF-8 payload, excluding its VarInt length prefix.
185    pub const MAX_BYTES: usize = Self::MAX_UTF16_CODE_UNITS * 3;
186
187    fn encode_value(value: &str, writer: &mut impl Write) -> Result<(), CodecError> {
188        encode_prefixed_string(
189            value,
190            writer,
191            CodecKind::String,
192            Self::MAX_BYTES,
193            Self::MAX_UTF16_CODE_UNITS,
194        )
195    }
196
197    fn decode_value(reader: &mut impl Read) -> Result<(String, usize), CodecError> {
198        decode_prefixed_string(
199            reader,
200            CodecKind::String,
201            Self::MAX_BYTES,
202            Self::MAX_UTF16_CODE_UNITS,
203        )
204    }
205}
206
207/// Encodes `value` as a VarInt-length-prefixed string.
208///
209/// `codec` identifies the codec in errors, `max_bytes` limits the UTF-8
210/// payload, and `max_code_units` limits the number of UTF-16 code units.
211/// Supplementary Unicode scalar values count as two UTF-16 code units.
212///
213/// # Errors
214///
215/// Returns a [`CodecError`] if the string exceeds `max_bytes` or
216/// `max_code_units`, or if the underlying writer fails while writing the length
217/// prefix or payload.
218pub(crate) fn encode_prefixed_string(
219    value: &str,
220    writer: &mut impl Write,
221    codec: CodecKind,
222    max_bytes: usize,
223    max_code_units: usize,
224) -> Result<(), CodecError> {
225    if value.len() > max_bytes {
226        return Err(CodecError::invalid_encoding_for_operation(
227            codec,
228            CodecOperation::Write,
229            0,
230            InvalidEncodingReason::StringTooLong { max_bytes },
231        ));
232    }
233    if value.encode_utf16().count() > max_code_units {
234        return Err(CodecError::invalid_encoding_for_operation(
235            codec,
236            CodecOperation::Write,
237            0,
238            InvalidEncodingReason::TooManyUtf16CodeUnits { max_code_units },
239        ));
240    }
241
242    let bytes = value.as_bytes();
243    let prefix_size = writer
244        .write_varint_with_size(bytes.len() as i32)
245        .map_err(|error| error.with_context(codec))?;
246    write_all_counted(writer, bytes, codec, prefix_size)
247}
248
249/// Decodes a VarInt-length-prefixed string and returns its value and the total
250/// number of bytes processed, including the length prefix.
251///
252/// `codec` is the codec in errors, `max_bytes` limits the UTF-8 payload, and
253/// `max_code_units` limits the number of UTF-16 code units.
254///
255/// # Errors
256///
257/// Returns a [`CodecError`] if the length prefix is negative, the payload
258/// exceeds `max_bytes`, the payload contains invalid UTF-8 or more than
259/// `max_code_units` UTF-16 code units, or the reader reaches an unexpected end
260/// of input.
261pub(crate) fn decode_prefixed_string(
262    reader: &mut impl Read,
263    codec: CodecKind,
264    max_bytes: usize,
265    max_code_units: usize,
266) -> Result<(String, usize), CodecError> {
267    let (byte_length, prefix_size) = reader
268        .read_varint_with_size()
269        .map_err(|error| error.with_context(codec))?;
270    let byte_length = usize::try_from(byte_length).map_err(|_| {
271        CodecError::invalid_encoding(
272            codec,
273            prefix_size,
274            InvalidEncodingReason::NegativeLength { value: byte_length },
275        )
276    })?;
277
278    if byte_length > max_bytes {
279        return Err(CodecError::invalid_encoding(
280            codec,
281            prefix_size,
282            InvalidEncodingReason::StringTooLong { max_bytes },
283        ));
284    }
285
286    let mut bytes = vec![0; byte_length];
287    read_exact_counted(reader, &mut bytes, codec, prefix_size)?;
288    let bytes_processed = prefix_size + byte_length;
289    let value = String::from_utf8(bytes).map_err(|error| {
290        let utf8_error = error.utf8_error();
291        CodecError::invalid_encoding(
292            codec,
293            bytes_processed,
294            InvalidEncodingReason::InvalidUtf8 {
295                valid_up_to: utf8_error.valid_up_to(),
296                error_len: utf8_error.error_len(),
297            },
298        )
299    })?;
300    if value.len() > max_bytes {
301        return Err(CodecError::invalid_encoding(
302            codec,
303            bytes_processed,
304            InvalidEncodingReason::StringTooLong { max_bytes },
305        ));
306    }
307    if value.encode_utf16().count() > max_code_units {
308        return Err(CodecError::invalid_encoding(
309            codec,
310            bytes_processed,
311            InvalidEncodingReason::TooManyUtf16CodeUnits { max_code_units },
312        ));
313    }
314    Ok((value, bytes_processed))
315}
316
317impl TypeCodec for PrefixedString {
318    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
319        Self::encode_value(&self.0, writer)
320    }
321
322    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
323        Self::decode_value(reader).map(|(value, _)| Self(value))
324    }
325}
326
327/// A resource identifier encoded as a [`PrefixedString`].
328///
329/// The namespace permits `[a-z0-9._-]`; the value permits
330/// `[a-z0-9._/-]`. See the protocol's [identifier format] for details.
331///
332/// [identifier format]: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Identifier
333#[derive(Debug, Clone, PartialEq, Eq, Hash)]
334pub struct Identifier(String);
335
336impl Identifier {
337    /// The maximum number of UTF-16 code units permitted in the identifier.
338    pub const MAX_UTF16_CODE_UNITS: usize = PrefixedString::MAX_UTF16_CODE_UNITS;
339    /// The maximum size of the UTF-8 payload, excluding its VarInt length prefix.
340    pub const MAX_BYTES: usize = PrefixedString::MAX_BYTES;
341    /// The maximum encoded size, including the VarInt length prefix.
342    pub const MAX_ENCODED_BYTES: usize = Self::MAX_BYTES + 3;
343
344    /// Creates an identifier after validating its namespace and path.
345    ///
346    /// An identifier without an explicit namespace is validated as belonging
347    /// to the `minecraft` namespace, but its original spelling is preserved.
348    ///
349    /// # Errors
350    ///
351    /// Returns [`InvalidIdentifier`] if the namespace or path is empty or
352    /// contains a character not permitted by the identifier format.
353    pub fn new(value: impl Into<String>) -> Result<Self, InvalidIdentifier> {
354        let value = value.into();
355        validate_identifier(&value)?;
356        Ok(Self(value))
357    }
358
359    /// Returns the identifier as a string slice.
360    pub fn as_str(&self) -> &str {
361        &self.0
362    }
363
364    /// Returns the owned identifier string.
365    pub fn into_inner(self) -> String {
366        self.0
367    }
368}
369
370impl fmt::Display for Identifier {
371    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
372        formatter.write_str(&self.0)
373    }
374}
375
376impl Serialize for Identifier {
377    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
378    where
379        S: Serializer,
380    {
381        serializer.serialize_str(&self.0)
382    }
383}
384
385impl<'de> Deserialize<'de> for Identifier {
386    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
387    where
388        D: Deserializer<'de>,
389    {
390        Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
391    }
392}
393
394impl TypeCodec for Identifier {
395    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
396        PrefixedString::encode_value(&self.0, writer)
397            .map_err(|error| error.with_context(CodecKind::Identifier))
398    }
399
400    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
401        let (value, bytes_processed) = PrefixedString::decode_value(reader)
402            .map_err(|error| error.with_context(CodecKind::Identifier))?;
403        Self::new(value).map_err(|_| {
404            CodecError::invalid_encoding(
405                CodecKind::Identifier,
406                bytes_processed,
407                InvalidEncodingReason::InvalidIdentifier,
408            )
409        })
410    }
411}
412
413/// Returns whether a string is a valid Minecraft resource identifier.
414///
415/// An identifier without an explicit namespace is validated as belonging to
416/// the `minecraft` namespace. The namespace permits `[a-z0-9._-]`; the path
417/// permits `[a-z0-9._/-]`. See the protocol's [identifier format].
418///
419/// [identifier format]: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Identifier
420pub(crate) fn is_valid_identifier(value: &str) -> bool {
421    let (namespace, path) = match value.split_once(':') {
422        Some((namespace, path)) => (namespace, path),
423        None => ("minecraft", value),
424    };
425    let namespace_is_valid = !namespace.is_empty()
426        && namespace.bytes().all(|byte| {
427            byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"_.-".contains(&byte)
428        });
429    let path_is_valid = !path.is_empty()
430        && path.bytes().all(|byte| {
431            byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"/._-".contains(&byte)
432        });
433    namespace_is_valid && path_is_valid && !path.contains(':')
434}
435
436fn validate_identifier(value: &str) -> Result<(), InvalidIdentifier> {
437    if is_valid_identifier(value) {
438        Ok(())
439    } else {
440        Err(InvalidIdentifier)
441    }
442}
443
444/// An error returned when a string is not a valid Minecraft resource identifier.
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
446pub struct InvalidIdentifier;
447
448impl fmt::Display for InvalidIdentifier {
449    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
450        formatter.write_str("invalid Minecraft identifier")
451    }
452}
453
454impl std::error::Error for InvalidIdentifier {}
455
456/// A variable-length two's-complement signed 32-bit integer.
457///
458/// VarInts use one to five bytes on the wire. Each byte carries seven payload
459/// bits; the most-significant bit is set on every byte except the last.
460///
461/// # Examples
462///
463/// ```
464/// use mcproto_types::{TypeCodec, basic::VarInt};
465///
466/// let mut encoded = Vec::new();
467/// VarInt(25565).encode(&mut encoded)?;
468/// assert_eq!(encoded, [0xdd, 0xc7, 0x01]);
469///
470/// let mut input = encoded.as_slice();
471/// assert_eq!(VarInt::decode(&mut input)?, VarInt(25565));
472/// assert!(input.is_empty());
473/// # Ok::<(), mcproto_codec::error::CodecError>(())
474/// ```
475#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
476pub struct VarInt(
477    /// The integer value.
478    pub i32,
479);
480
481impl TypeCodec for VarInt {
482    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
483        writer.write_varint(self.0)
484    }
485
486    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
487        reader.read_varint().map(Self)
488    }
489}
490
491/// A variable-length two's-complement signed 64-bit integer.
492///
493/// VarLongs use one to ten bytes on the wire. Each byte carries seven payload
494/// bits; the most-significant bit is set on every byte except the last.
495///
496/// # Examples
497///
498/// ```
499/// use mcproto_types::{TypeCodec, basic::VarLong};
500///
501/// let mut encoded = Vec::new();
502/// VarLong(9_223_372_036_854_775_000).encode(&mut encoded)?;
503///
504/// let mut input = encoded.as_slice();
505/// assert_eq!(
506///     VarLong::decode(&mut input)?,
507///     VarLong(9_223_372_036_854_775_000),
508/// );
509/// assert!(input.is_empty());
510/// # Ok::<(), mcproto_codec::error::CodecError>(())
511/// ```
512#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
513pub struct VarLong(
514    /// The integer value.
515    pub i64,
516);
517
518impl TypeCodec for VarLong {
519    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
520        writer.write_varlong(self.0)
521    }
522
523    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
524        reader.read_varlong().map(Self)
525    }
526}