Skip to main content

mcproto_codec/
varlong.rs

1//! Reading and writing [Minecraft protocol VarLong] values.
2//!
3//! [Minecraft protocol VarLong]: https://minecraft.wiki/w/Java_Edition_protocol/Packets#VarInt_and_VarLong
4
5use std::io::{Read, Write};
6
7use crate::error::{CodecError, CodecKind, InvalidEncodingReason};
8use crate::io::{read_exact_counted, write_all_counted};
9
10/// Extension methods for writing [Minecraft protocol VarLong] values.
11///
12/// This trait is implemented for every [`Write`] type.
13///
14/// # Example
15///
16/// ```
17/// use mcproto_codec::varlong::VarLongWrite;
18///
19/// let mut output = Vec::new();
20/// output.write_varlong(9_223_372_036_854_775_000)?;
21///
22/// # Ok::<(), mcproto_codec::error::CodecError>(())
23/// ```
24///
25/// [Minecraft protocol VarLong]: https://minecraft.wiki/w/Java_Edition_protocol/Packets#VarInt_and_VarLong
26pub trait VarLongWrite: Write {
27    /// Writes `value` to this writer as a VarLong.
28    ///
29    /// # Errors
30    ///
31    /// Returns a [`CodecError`] if the underlying writer fails. The error's
32    /// byte count reports how much of this value was written successfully.
33    ///
34    /// [`CodecError`]: crate::error::CodecError
35    #[inline]
36    fn write_varlong(&mut self, value: i64) -> Result<(), CodecError> {
37        let mut value = value as u64;
38        let mut bytes_processed = 0;
39
40        loop {
41            let byte = (value & 0x7F) as u8;
42            value >>= 7;
43            let has_next = value != 0;
44            let byte = if has_next { byte | 0x80 } else { byte };
45
46            write_all_counted(self, &[byte], CodecKind::VarLong, bytes_processed)?;
47            bytes_processed += 1;
48
49            if !has_next {
50                return Ok(());
51            }
52        }
53    }
54}
55
56/// Extension methods for reading [Minecraft protocol VarLong] values.
57///
58/// This trait is implemented for every [`Read`] type.
59///
60/// # Example
61///
62/// ```
63/// use mcproto_codec::varlong::{VarLongRead, VarLongWrite};
64///
65/// let mut encoded = Vec::new();
66/// encoded.write_varlong(9_223_372_036_854_775_000)?;
67///
68/// let value = encoded.as_slice().read_varlong()?;
69/// assert_eq!(value, 9_223_372_036_854_775_000);
70///
71/// # Ok::<(), mcproto_codec::error::CodecError>(())
72/// ```
73///
74/// [Minecraft protocol VarLong]: https://minecraft.wiki/w/Java_Edition_protocol/Packets#VarInt_and_VarLong
75pub trait VarLongRead: Read {
76    /// Reads and returns one VarLong from this reader.
77    ///
78    /// # Errors
79    ///
80    /// Returns a [`CodecError`] if the input ends early, the underlying reader
81    /// fails, or the input is not a valid VarLong.
82    ///
83    /// [`CodecError`]: crate::error::CodecError
84    #[inline]
85    fn read_varlong(&mut self) -> Result<i64, CodecError> {
86        let mut result = 0u64;
87        let mut shift = 0;
88
89        for i in 0..10 {
90            let mut buf = [0u8; 1];
91            read_exact_counted(self, &mut buf, CodecKind::VarLong, i)?;
92            let byte = buf[0];
93
94            if i == 9 {
95                if (byte & 0x80) != 0 {
96                    return Err(CodecError::invalid_encoding(
97                        CodecKind::VarLong,
98                        i + 1,
99                        InvalidEncodingReason::TooLong { max_bytes: 10 },
100                    ));
101                }
102                if (byte & !0x01) != 0 {
103                    return Err(CodecError::invalid_encoding(
104                        CodecKind::VarLong,
105                        i + 1,
106                        InvalidEncodingReason::ValueOutOfRange {
107                            terminal_byte: byte,
108                            allowed_mask: 0x01,
109                        },
110                    ));
111                }
112            }
113
114            let value = (byte & 0x7F) as u64;
115            result |= value << shift;
116
117            if (byte & 0x80) == 0 {
118                return Ok(result as i64);
119            }
120
121            shift += 7;
122        }
123
124        unreachable!("the tenth VarLong byte always terminates or returns an error")
125    }
126}
127
128impl<R: Read> VarLongRead for R {}
129impl<W: Write> VarLongWrite for W {}