cheetah_game_realtime_protocol/codec/
variable_int.rs1use std::io::{Cursor, ErrorKind};
2
3use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
4
5pub trait VariableIntWriter {
9 fn write_variable_u64(&mut self, value: u64) -> std::io::Result<()>;
10 fn write_variable_i64(&mut self, value: i64) -> std::io::Result<()>;
11}
12
13pub trait VariableIntReader {
14 fn read_variable_u64(&mut self) -> std::io::Result<u64>;
15 fn read_variable_i64(&mut self) -> std::io::Result<i64>;
16}
17const U8_MAX: u64 = 249;
18const U9_MARKER: u8 = 250;
19const U16_MARKER: u8 = 251;
20const U24_MARKER: u8 = 252;
21const U32_MARKER: u8 = 253;
22const U48_MARKER: u8 = 254;
23const U64_MARKER: u8 = 255;
24
25impl VariableIntWriter for Cursor<&mut [u8]> {
26 #[allow(clippy::cast_possible_truncation)]
27 fn write_variable_u64(&mut self, value: u64) -> std::io::Result<()> {
28 if value < U8_MAX {
29 return self.write_u8(value as u8);
30 };
31
32 if value < U8_MAX + 255 {
33 self.write_u8(U9_MARKER)?;
34 return self.write_u8((value - U8_MAX) as u8);
35 };
36
37 if value < u64::from(u16::MAX) {
38 self.write_u8(U16_MARKER)?;
39 return self.write_u16::<BigEndian>(value as u16);
40 };
41
42 if value < u64::from(u16::MAX) * u64::from(u8::MAX) {
43 self.write_u8(U24_MARKER)?;
44 return self.write_u24::<BigEndian>(value as u32);
45 }
46
47 if value < u64::from(u32::MAX) {
48 self.write_u8(U32_MARKER)?;
49 return self.write_u32::<BigEndian>(value as u32);
50 };
51
52 if value < u64::from(u32::MAX) * u64::from(u8::MAX) * u64::from(u8::MAX) {
53 self.write_u8(U48_MARKER)?;
54 return self.write_u48::<BigEndian>(value);
55 };
56
57 self.write_u8(U64_MARKER)?;
58 self.write_u64::<BigEndian>(value)
59 }
60
61 #[allow(clippy::cast_possible_wrap)]
62 #[allow(clippy::cast_sign_loss)]
63 fn write_variable_i64(&mut self, value: i64) -> std::io::Result<()> {
64 let zigzag = if value < 0 { !(value as u64) * 2 + 1 } else { (value as u64) * 2 };
65 self.write_variable_u64(zigzag)
66 }
67}
68impl VariableIntReader for Cursor<&[u8]> {
69 #[allow(clippy::cast_possible_truncation)]
70 fn read_variable_u64(&mut self) -> std::io::Result<u64> {
71 let first = self.read_u8()?;
72 if first < U8_MAX as u8 {
73 return Ok(u64::from(first));
74 };
75 Ok(match first {
76 U9_MARKER => U8_MAX + u64::from(self.read_u8()?),
77 U16_MARKER => u64::from(self.read_u16::<BigEndian>()?),
78 U24_MARKER => u64::from(self.read_u24::<BigEndian>()?),
79 U32_MARKER => u64::from(self.read_u32::<BigEndian>()?),
80 U48_MARKER => self.read_u48::<BigEndian>()?,
81 U64_MARKER => self.read_u64::<BigEndian>()?,
82 _ => {
83 return Err(std::io::Error::new(ErrorKind::InvalidData, format!("Variable int marker not valid {first}")));
84 }
85 })
86 }
87
88 #[allow(clippy::cast_possible_wrap)]
89 fn read_variable_i64(&mut self) -> std::io::Result<i64> {
90 let unsigned = self.read_variable_u64()?;
91 Ok(if unsigned % 2 == 0 { unsigned / 2 } else { !(unsigned / 2) } as i64)
92 }
93}
94
95#[cfg(test)]
96mod test {
97 use std::io::Cursor;
98
99 use crate::codec::variable_int::{VariableIntReader, VariableIntWriter, U8_MAX, U9_MARKER};
100
101 #[test]
102 fn test_u64() {
103 check_u64(U8_MAX - 1, 1);
104 check_u64(U8_MAX, 2);
105 check_u64(U8_MAX + 255 - 1, 2);
106 check_u64(u64::from(u16::MAX - 1), 3);
107 check_u64(u64::from(u16::MAX) * u64::from(u8::MAX) - 1, 4);
108 check_u64(u64::from(u32::MAX - 1), 5);
109 check_u64(u64::from(u32::MAX) * u64::from(u8::MAX) - 1, 7);
110 check_u64(u64::MAX - 1, 9);
111 }
112
113 #[test]
114 fn test_i64() {
115 check_i64(-1, 1);
116 check_i64(1, 1);
117 check_i64((i64::from(U9_MARKER) + 255 - 2) / 2, 2);
118 check_i64(-(i64::from(U9_MARKER) + 255 - 2) / 2, 2);
119 }
120
121 fn check_u64(value: u64, size: u64) {
122 let mut buffer = [0_u8; 100];
123 let mut cursor = Cursor::new(buffer.as_mut());
124 cursor.write_variable_u64(value).unwrap();
125 assert_eq!(cursor.position(), size);
126 let write_position = cursor.position();
127 let mut read_cursor = Cursor::<&[u8]>::new(&buffer);
128 assert_eq!(read_cursor.read_variable_u64().unwrap(), value);
129 assert_eq!(write_position, read_cursor.position());
130 }
131
132 fn check_i64(value: i64, size: u64) {
133 let mut buffer = [0_u8; 100];
134 let mut cursor = Cursor::new(buffer.as_mut());
135 cursor.write_variable_i64(value).unwrap();
136 assert_eq!(cursor.position(), size);
137 let write_position = cursor.position();
138
139 let mut read_cursor = Cursor::<&[u8]>::new(&buffer);
140 assert_eq!(read_cursor.read_variable_i64().unwrap(), value);
141 assert_eq!(write_position, read_cursor.position());
142 }
143}