steel_utils/codec/
variable_integer.rs1use std::io::{Cursor, Error, Write};
2
3#[cfg(all(target_arch = "x86_64", target_feature = "bmi2"))]
4use std::arch::x86_64::_pdep_u64;
5
6use tokio::io::{AsyncRead, AsyncReadExt};
7
8use crate::{
9 FrontVec,
10 serial::{ReadFrom, WriteTo},
11};
12
13#[cfg(all(target_arch = "x86_64", target_feature = "bmi2"))]
15#[inline]
16fn pack_payload(value: u64) -> u64 {
17 unsafe { _pdep_u64(value, 0x7f7f_7f7f_7f7f_7f7f) }
19}
20
21#[cfg(not(all(target_arch = "x86_64", target_feature = "bmi2")))]
22const fn pack_payload(value: u64) -> u64 {
23 (value & 0x7f)
24 | ((value & 0x3f80) << 1)
25 | ((value & 0x001f_c000) << 2)
26 | ((value & 0x0fe0_0000) << 3)
27 | ((value & 0x0000_0007_f000_0000) << 4)
28 | ((value & 0x0000_03f8_0000_0000) << 5)
29 | ((value & 0x0001_fc00_0000_0000) << 6)
30 | ((value & 0x00fe_0000_0000_0000) << 7)
31}
32
33#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct VarInt(pub i32);
36
37impl VarInt {
38 pub const MAX_SIZE: usize = 5;
40
41 #[must_use]
44 pub const fn written_size(val: i32) -> usize {
45 match val {
46 0 => 1,
47 n => (31 - n.leading_zeros() as usize) / 7 + 1,
48 }
49 }
50
51 pub async fn read_async(read: &mut (impl AsyncRead + Unpin)) -> Result<i32, Error> {
56 let mut val = 0;
57 for i in 0..Self::MAX_SIZE {
58 let byte = read
59 .read_u8()
60 .await
61 .map_err(|err| Error::new(err.kind(), "VarInt"))?;
62 val |= (i32::from(byte) & 0x7F) << (i * 7);
63 if byte & 0x80 == 0 {
64 return Ok(val);
65 }
66 }
67 Err(Error::other("VarInt"))
68 }
69
70 pub fn set_in_front(self, vec: &mut FrontVec, varint_size: usize) {
77 debug_assert_eq!(varint_size, Self::written_size(self.0));
78 let continuation = 0x0080_8080_8080_8080_u64 >> ((8 - varint_size) * 8);
79 let bytes = (pack_payload(u64::from(self.0 as u32)) | continuation).to_le_bytes();
80
81 vec.set_in_front(&bytes[..Self::MAX_SIZE][..varint_size]);
82 }
83}
84
85impl ReadFrom for VarInt {
86 fn read(read: &mut Cursor<&[u8]>) -> Result<Self, Error> {
87 let mut val = 0;
88 for i in 0..Self::MAX_SIZE {
89 let byte = u8::read(read)?;
90 val |= (i32::from(byte) & 0x7F) << (i * 7);
91
92 if byte & 0x80 == 0 {
93 return Ok(Self(val));
94 }
95 }
96
97 Err(Error::other("VarInt to long"))
98 }
99}
100
101impl WriteTo for VarInt {
102 #[expect(
103 clippy::inline_always,
104 reason = "codec benchmarks show these fixed-size writes need to inline into packet serializers"
105 )]
106 #[inline(always)]
107 fn write(&self, writer: &mut impl Write) -> Result<(), Error> {
108 let value = self.0 as u32;
109 if value < 0x80 {
110 return writer.write_all(&[value as u8]);
111 }
112
113 if value < 0x4000 {
114 return writer.write_all(&[value as u8 | 0x80, (value >> 7) as u8]);
115 }
116
117 if value < 0x20_0000 {
118 return writer.write_all(&[
119 value as u8 | 0x80,
120 (value >> 7) as u8 | 0x80,
121 (value >> 14) as u8,
122 ]);
123 }
124
125 let packed = pack_payload(u64::from(value));
126 if value < (1 << 28) {
127 writer.write_all(&(packed | 0x0080_8080).to_le_bytes()[..4])
128 } else {
129 writer.write_all(&(packed | 0x8080_8080).to_le_bytes()[..5])
130 }
131 }
132}
133
134impl From<usize> for VarInt {
135 fn from(value: usize) -> Self {
136 Self(value as _)
137 }
138}
139
140#[expect(
141 clippy::cast_sign_loss,
142 reason = "VarInt values used as lengths are always non-negative"
143)]
144impl From<VarInt> for usize {
145 fn from(value: VarInt) -> usize {
146 value.0 as _
147 }
148}
149
150impl From<i32> for VarInt {
151 fn from(value: i32) -> Self {
152 Self(value as _)
153 }
154}
155
156impl From<VarInt> for i32 {
157 fn from(value: VarInt) -> i32 {
158 value.0
159 }
160}
161
162#[cfg(test)]
163mod var_int_tests {
164 use super::*;
165 use std::io::Cursor;
166
167 #[test]
168 fn test_varint_read_write_negative() {
169 let val = VarInt(-1);
170 let mut buf = Vec::new();
171 val.write(&mut buf).expect("write failed");
172
173 assert_eq!(buf, vec![0xff, 0xff, 0xff, 0xff, 0x0f]);
175
176 let mut cursor = Cursor::new(buf.as_slice());
177 let read_val = VarInt::read(&mut cursor).expect("read failed");
178 assert_eq!(read_val, val);
179 }
180}
181
182#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
184pub struct VarLong(pub i64);
185
186impl VarLong {
187 pub const MAX_SIZE: usize = 10;
189}
190
191impl ReadFrom for VarLong {
192 fn read(read: &mut Cursor<&[u8]>) -> Result<Self, Error> {
193 let mut val = 0i64;
194 for i in 0..Self::MAX_SIZE {
195 let byte = u8::read(read)?;
196 val |= (i64::from(byte) & 0x7F) << (i * 7);
197 if byte & 0x80 == 0 {
198 return Ok(Self(val));
199 }
200 }
201 Err(Error::other("VarLong too long"))
202 }
203}
204
205impl WriteTo for VarLong {
206 #[expect(
207 clippy::inline_always,
208 reason = "codec benchmarks show these fixed-size writes need to inline into packet serializers"
209 )]
210 #[inline(always)]
211 fn write(&self, writer: &mut impl Write) -> Result<(), Error> {
212 let value = self.0 as u64;
213 if value < 0x80 {
214 return writer.write_all(&[value as u8]);
215 }
216
217 if let Ok(value) = u32::try_from(value) {
218 return VarInt(value as i32).write(writer);
219 }
220
221 let packed = pack_payload(value);
222 if value >= (1 << 56) {
223 let low = (packed | 0x8080_8080_8080_8080).to_le_bytes();
224 let bytes = [
225 low[0],
226 low[1],
227 low[2],
228 low[3],
229 low[4],
230 low[5],
231 low[6],
232 low[7],
233 (value >> 56) as u8,
234 1,
235 ];
236
237 return if self.0 >= 0 {
238 writer.write_all(&bytes[..9])
239 } else {
240 writer.write_all(&bytes)
241 };
242 }
243
244 let size = (64 - value.leading_zeros() as usize).div_ceil(7);
245 let continuation = 0x0080_8080_8080_8080_u64 >> ((8 - size) * 8);
246 let bytes = (packed | continuation).to_le_bytes();
247
248 match size {
249 5 => writer.write_all(&bytes[..5]),
250 6 => writer.write_all(&bytes[..6]),
251 7 => writer.write_all(&bytes[..7]),
252 _ => writer.write_all(&bytes),
253 }
254 }
255}
256
257impl From<i64> for VarLong {
258 fn from(value: i64) -> Self {
259 Self(value)
260 }
261}
262
263impl From<VarLong> for i64 {
264 fn from(value: VarLong) -> i64 {
265 value.0
266 }
267}
268
269#[cfg(test)]
270mod var_long_tests {
271 use super::*;
272 use std::io::Cursor;
273
274 #[test]
275 fn test_varlong_read_write() {
276 let test_values = vec![
277 0i64,
278 1i64,
279 127i64,
280 128i64,
281 255i64,
282 2_147_483_647_i64,
283 9_223_372_036_854_775_807_i64,
284 -1i64,
285 -2_147_483_648_i64,
286 ];
287
288 for val in test_values {
289 let var_long = VarLong(val);
290 let mut buf = Vec::new();
291 var_long.write(&mut buf).expect("write failed");
292
293 let mut cursor = Cursor::new(buf.as_slice());
294 let read_val = VarLong::read(&mut cursor).expect("read failed");
295 assert_eq!(read_val, var_long, "Failed for value {val}");
296 }
297 }
298}
299
300#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
302pub struct VarUint(pub u32);
303
304impl VarUint {
305 const MAX_SIZE: usize = 5;
306
307 #[must_use]
310 pub const fn written_size(self) -> usize {
311 (32 - self.0.leading_zeros() as usize).max(1).div_ceil(7)
312 }
313
314 pub fn write(self, writer: &mut impl Write) -> Result<(), Error> {
319 let mut val = self.0;
320 loop {
321 let mut byte = (val & 0x7F) as u8;
322 val >>= 7;
323 if val != 0 {
324 byte |= 0x80;
325 }
326 byte.write(writer)?;
327 if val == 0 {
328 break;
329 }
330 }
331 Ok(())
332 }
333
334 pub fn read(read: &mut Cursor<&[u8]>) -> Result<u32, Error> {
339 let mut val = 0;
340 for i in 0..Self::MAX_SIZE {
341 let byte = u8::read(read)?;
342 val |= (u32::from(byte) & 0x7F) << (i * 7);
343 if byte & 0x80 == 0 {
344 return Ok(val);
345 }
346 }
347 Err(Error::other("Malformed VarUint"))
348 }
349}