cheetah_game_realtime_protocol/codec/
variable_int.rs

1use std::io::{Cursor, ErrorKind};
2
3use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
4
5///
6/// Запись/чтения целого числа с переменным количеством бит для хранения
7///
8pub 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}