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        self.write_varlong_with_size(value).map(|_| ())
38    }
39
40    /// Writes `value` as a VarLong and returns the number of bytes written.
41    ///
42    /// # Errors
43    ///
44    /// Returns a [`CodecError`] if the underlying writer fails. The error's
45    /// byte count reports how much of this value was written successfully.
46    ///
47    /// [`CodecError`]: crate::error::CodecError
48    #[inline]
49    fn write_varlong_with_size(&mut self, value: i64) -> Result<usize, CodecError> {
50        let mut value = value as u64;
51        let mut bytes_processed = 0;
52
53        loop {
54            let byte = (value & 0x7F) as u8;
55            value >>= 7;
56            let has_next = value != 0;
57            let byte = if has_next { byte | 0x80 } else { byte };
58
59            write_all_counted(self, &[byte], CodecKind::VarLong, bytes_processed)?;
60            bytes_processed += 1;
61
62            if !has_next {
63                return Ok(bytes_processed);
64            }
65        }
66    }
67}
68
69/// Extension methods for reading [Minecraft protocol VarLong] values.
70///
71/// This trait is implemented for every [`Read`] type.
72///
73/// # Example
74///
75/// ```
76/// use mcproto_codec::varlong::{VarLongRead, VarLongWrite};
77///
78/// let mut encoded = Vec::new();
79/// encoded.write_varlong(9_223_372_036_854_775_000)?;
80///
81/// let value = encoded.as_slice().read_varlong()?;
82/// assert_eq!(value, 9_223_372_036_854_775_000);
83///
84/// # Ok::<(), mcproto_codec::error::CodecError>(())
85/// ```
86///
87/// [Minecraft protocol VarLong]: https://minecraft.wiki/w/Java_Edition_protocol/Packets#VarInt_and_VarLong
88pub trait VarLongRead: Read {
89    /// Reads and returns one VarLong from this reader.
90    ///
91    /// # Errors
92    ///
93    /// Returns a [`CodecError`] if the input ends early, the underlying reader
94    /// fails, or the input is not a valid VarLong.
95    ///
96    /// [`CodecError`]: crate::error::CodecError
97    #[inline]
98    fn read_varlong(&mut self) -> Result<i64, CodecError> {
99        self.read_varlong_with_size().map(|(value, _)| value)
100    }
101
102    /// Reads one VarLong and returns its value and encoded size in bytes.
103    ///
104    /// # Errors
105    ///
106    /// Returns a [`CodecError`] if the input ends early, the underlying reader
107    /// fails, or the input is not a valid VarLong.
108    ///
109    /// [`CodecError`]: crate::error::CodecError
110    #[inline]
111    fn read_varlong_with_size(&mut self) -> Result<(i64, usize), CodecError> {
112        let mut result = 0u64;
113        let mut shift = 0;
114
115        for i in 0..10 {
116            let mut buf = [0u8; 1];
117            read_exact_counted(self, &mut buf, CodecKind::VarLong, i)?;
118            let byte = buf[0];
119
120            if i == 9 {
121                if (byte & 0x80) != 0 {
122                    return Err(CodecError::invalid_encoding(
123                        CodecKind::VarLong,
124                        i + 1,
125                        InvalidEncodingReason::TooLong { max_bytes: 10 },
126                    ));
127                }
128            }
129
130            let value = (byte & 0x7F) as u64;
131            result |= value << shift;
132
133            if (byte & 0x80) == 0 {
134                return Ok((result as i64, i + 1));
135            }
136
137            shift += 7;
138        }
139
140        unreachable!("the tenth VarLong byte always terminates or returns an error")
141    }
142}
143
144impl<R: Read> VarLongRead for R {}
145impl<W: Write> VarLongWrite for W {}