Skip to main content

mcproto_codec/
varlong.rs

1use std::io::{Read, Write};
2
3use crate::error::{CodecError, CodecKind, InvalidEncodingReason};
4use crate::io::{read_exact_counted, write_all_counted};
5
6pub trait VarLongWrite: Write {
7    #[inline]
8    fn write_varlong(&mut self, value: i64) -> Result<(), CodecError> {
9        let mut value = value as u64;
10        let mut bytes_processed = 0;
11
12        loop {
13            let byte = (value & 0x7F) as u8;
14            value >>= 7;
15            let has_next = value != 0;
16            let byte = if has_next { byte | 0x80 } else { byte };
17
18            write_all_counted(self, &[byte], CodecKind::VarLong, bytes_processed)?;
19            bytes_processed += 1;
20
21            if !has_next {
22                return Ok(());
23            }
24        }
25    }
26}
27
28pub trait VarLongRead: Read {
29    #[inline]
30    fn read_varlong(&mut self) -> Result<i64, CodecError> {
31        let mut result = 0u64;
32        let mut shift = 0;
33
34        for i in 0..10 {
35            let mut buf = [0u8; 1];
36            read_exact_counted(self, &mut buf, CodecKind::VarLong, i)?;
37            let byte = buf[0];
38
39            if i == 9 {
40                if (byte & 0x80) != 0 {
41                    return Err(CodecError::invalid_encoding(
42                        CodecKind::VarLong,
43                        i + 1,
44                        InvalidEncodingReason::TooLong { max_bytes: 10 },
45                    ));
46                }
47                if (byte & !0x01) != 0 {
48                    return Err(CodecError::invalid_encoding(
49                        CodecKind::VarLong,
50                        i + 1,
51                        InvalidEncodingReason::ValueOutOfRange {
52                            terminal_byte: byte,
53                            allowed_mask: 0x01,
54                        },
55                    ));
56                }
57            }
58
59            let value = (byte & 0x7F) as u64;
60            result |= value << shift;
61
62            if (byte & 0x80) == 0 {
63                return Ok(result as i64);
64            }
65
66            shift += 7;
67        }
68
69        unreachable!("the tenth VarLong byte always terminates or returns an error")
70    }
71}
72
73impl<R: Read> VarLongRead for R {}
74impl<W: Write> VarLongWrite for W {}