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 {}