1#[cfg(feature = "write")]
2use bitbuffer::{BitError, BitWrite, BitWriteSized, BitWriteStream};
3use bitbuffer::{BitReadBuffer, BitReadStream, LittleEndian};
4use num_traits::{PrimInt, Unsigned};
5use serde::{Deserialize, Serialize};
6use snap::raw::{decompress_len, Decoder};
7
8use crate::demo::lzss::decompress;
9use crate::demo::packet::stringtable::{
10 ExtraData, FixedUserDataSize, StringTable, StringTableEntry,
11};
12#[cfg(feature = "write")]
13use crate::demo::parser::Encode;
14use crate::demo::parser::ParseBitSkip;
15use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
16use std::borrow::Cow;
17use std::cmp::min;
18
19#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
20#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
21#[serde(bound(deserialize = "'a: 'static"))]
22pub struct CreateStringTableMessage<'a> {
23 pub table: StringTable<'a>,
24}
25
26#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct StringTableMeta {
29 pub max_entries: u16,
30 pub fixed_userdata_size: Option<FixedUserDataSize>,
31}
32
33impl From<&StringTable<'_>> for StringTableMeta {
34 fn from(table: &StringTable) -> Self {
35 StringTableMeta {
36 max_entries: table.max_entries,
37 fixed_userdata_size: table.fixed_user_data_size,
38 }
39 }
40}
41
42impl<'a> Parse<'a> for CreateStringTableMessage<'a> {
43 fn parse(stream: &mut Stream<'a>, state: &ParserState) -> Result<Self> {
44 let name = stream.read()?;
45 let max_entries: u16 = stream.read()?;
46 let encode_bits = log_base2(max_entries);
47 let entity_count: u16 = stream.read_sized(encode_bits as usize + 1)?;
48 let length = if state.protocol_version > 23 {
49 read_var_int(stream)?
50 } else {
51 stream.read_sized(20)?
52 };
53
54 let fixed_userdata_size = stream.read()?;
55
56 let compressed = stream.read()?;
57
58 let mut table_data = stream.read_bits(length as usize)?;
59
60 if compressed {
61 let decompressed_size: u32 = table_data.read()?;
62 let compressed_size: u32 = table_data.read()?;
63
64 if !(4..=10 * 1024 * 1024).contains(&compressed_size) {
65 return Err(ParseError::InvalidDemo(
66 "Invalid compressed string table size",
67 ));
68 }
69
70 if decompressed_size > 100 * 1024 * 1024 {
71 return Err(ParseError::InvalidDemo(
72 "Invalid decompressed string table size",
73 ));
74 }
75
76 let magic: [u8; 4] = table_data.read()?;
77
78 match magic.as_ref() {
79 b"SNAP" => {
80 let compressed_data = table_data.read_bytes(compressed_size as usize - 4)?;
81
82 let mut decoder = Decoder::new();
83
84 let decompressed_size_from_header = decompress_len(&compressed_data)?;
85
86 if decompressed_size_from_header != decompressed_size as usize {
87 return Err(ParseError::UnexpectedDecompressedSize {
88 expected: decompressed_size,
89 size: decompressed_size_from_header as u32,
90 });
91 }
92
93 let mut decompressed_data = vec![0; decompressed_size_from_header];
94 decoder
95 .decompress(&compressed_data, &mut decompressed_data)
96 .map_err(ParseError::from)?;
97
98 let buffer = BitReadBuffer::new_owned(decompressed_data, LittleEndian);
99 table_data = BitReadStream::new(buffer);
100 }
101 b"LZSS" => {
102 let compressed_data = table_data.read_bytes(compressed_size as usize - 4)?;
103 let mut decompressed_data = Vec::with_capacity(decompressed_size as usize);
104 decompress(&compressed_data, &mut decompressed_data);
105
106 if decompressed_data.len() != decompressed_size as usize {
107 return Err(ParseError::UnexpectedDecompressedSize {
108 expected: decompressed_size,
109 size: decompressed_data.len() as u32,
110 });
111 }
112
113 let buffer = BitReadBuffer::new_owned(decompressed_data, LittleEndian);
114 table_data = BitReadStream::new(buffer);
115 }
116 _ => {
117 let magic_str = String::from_utf8(magic.to_ascii_uppercase())?;
118 return Err(ParseError::UnexpectedCompressionType(magic_str));
119 }
120 }
121 }
122
123 let table_meta = StringTableMeta {
124 max_entries,
125 fixed_userdata_size,
126 };
127
128 let entries = parse_string_table_update(&mut table_data, &table_meta, entity_count)?;
129
130 let table = StringTable {
131 entries,
132 max_entries,
133 fixed_user_data_size: fixed_userdata_size,
134 client_entries: None,
135 compressed,
136 name,
137 };
138 Ok(CreateStringTableMessage { table })
139 }
140}
141
142impl<'a> ParseBitSkip<'a> for CreateStringTableMessage<'a> {
143 fn parse_skip(stream: &mut Stream<'a>, state: &ParserState) -> Result<()> {
144 let _: String = stream.read()?;
145 let max_entries: u16 = stream.read()?;
146 let encode_bits = log_base2(max_entries);
147 let _: u16 = stream.read_sized(encode_bits as usize + 1)?;
148 let length = if state.protocol_version > 23 {
149 read_var_int(stream)?
150 } else {
151 stream.read_sized(20)?
152 };
153
154 let _: Option<FixedUserDataSize> = stream.read()?;
155
156 let _: bool = stream.read()?;
157
158 stream.skip_bits(length as usize).map_err(ParseError::from)
159 }
160}
161
162#[cfg(feature = "write")]
163impl Encode for CreateStringTableMessage<'_> {
164 fn encode(&self, stream: &mut BitWriteStream<LittleEndian>, state: &ParserState) -> Result<()> {
165 let table = &self.table;
166 table.name.write(stream)?;
167 table.max_entries.write(stream)?;
168 let encode_bits = log_base2(table.max_entries) as usize;
169 (table.entries.len() as u16).write_sized(stream, encode_bits + 1)?;
170
171 let (length_bits, encode_length): (usize, fn(u32) -> u64) = if state.protocol_version > 23 {
173 (40, encode_var_int_fixed)
174 } else {
175 (20, |length| length as u64)
176 };
177 stream.reserve_int::<ParseError, _>(length_bits, |stream| {
178 table.fixed_user_data_size.is_some().write(stream)?;
179 if let Some(fixed_size) = table.fixed_user_data_size {
180 fixed_size.write(stream)?;
181 }
182
183 false.write(stream)?;
185
186 let start = stream.bit_len();
187
188 let table_meta = table.get_table_meta();
189
190 write_string_table_update(&table.entries, stream, &table_meta)?;
191
192 let end = stream.bit_len();
193 Ok(encode_length((end - start) as u32))
194 })?;
195
196 Ok(())
197 }
198}
199
200#[test]
201#[cfg(feature = "write")]
202fn test_create_string_table_roundtrip() {
203 let state = ParserState::new(24, |_| false, false);
204 crate::test_roundtrip_encode(
205 CreateStringTableMessage {
206 table: StringTable {
207 name: "table1".into(),
208 entries: vec![],
209 max_entries: 16,
210 fixed_user_data_size: None,
211 client_entries: None,
212 compressed: false,
213 },
214 },
215 &state,
216 );
217 crate::test_roundtrip_encode(
218 CreateStringTableMessage {
219 table: StringTable {
220 name: "table1".into(),
221 entries: vec![
222 (
223 0,
224 StringTableEntry {
225 text: Some("foo".into()),
226 extra_data: None,
227 },
228 ),
229 (
230 1,
231 StringTableEntry {
232 text: Some("bar".into()),
233 extra_data: None,
234 },
235 ),
236 ],
237 max_entries: 16,
238 fixed_user_data_size: Some(FixedUserDataSize { size: 12, bits: 4 }),
239 client_entries: None,
240 compressed: false,
241 },
242 },
243 &state,
244 );
245}
246
247#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
248#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
249#[serde(bound(deserialize = "'a: 'static"))]
250pub struct UpdateStringTableMessage<'a> {
251 pub entries: Vec<(u16, StringTableEntry<'a>)>,
252 pub table_id: u8,
253}
254
255impl<'a> Parse<'a> for UpdateStringTableMessage<'a> {
256 fn parse(stream: &mut Stream<'a>, state: &ParserState) -> Result<Self> {
257 let table_id = stream.read_sized(5)?;
258
259 let changed: u16 = if stream.read()? { stream.read()? } else { 1 };
260 let length: u32 = stream.read_int(20)?;
261
262 let mut data = stream.read_bits(length as usize)?;
263
264 let entries = match state.string_tables.get(table_id as usize) {
265 Some(table) => parse_string_table_update(&mut data, table, changed),
266 None => return Err(ParseError::StringTableNotFound(table_id)),
267 }?;
268
269 Ok(UpdateStringTableMessage { entries, table_id })
270 }
271}
272
273impl<'a> ParseBitSkip<'a> for UpdateStringTableMessage<'a> {
274 fn parse_skip(stream: &mut Stream<'a>, _state: &ParserState) -> Result<()> {
275 let _: u8 = stream.read_sized(5)?;
276
277 let _: u16 = if stream.read()? { stream.read()? } else { 1 };
278 let length: u32 = stream.read_int(20)?;
279 stream.skip_bits(length as usize).map_err(ParseError::from)
280 }
281}
282
283#[cfg(feature = "write")]
284impl Encode for UpdateStringTableMessage<'_> {
285 fn encode(&self, stream: &mut BitWriteStream<LittleEndian>, state: &ParserState) -> Result<()> {
286 self.table_id.write_sized(stream, 5)?;
287 if self.entries.len() == 1 {
288 false.write(stream)?;
289 } else {
290 true.write(stream)?;
291 (self.entries.len() as u16).write(stream)?;
292 }
293
294 match state.string_tables.get(self.table_id as usize) {
295 Some(table) => Ok(stream.reserve_length(20, |stream| {
296 write_string_table_update(&self.entries, stream, table)
297 })?),
298 None => Err(ParseError::StringTableNotFound(self.table_id)),
299 }
300 }
301}
302
303#[test]
304#[cfg(feature = "write")]
305fn test_update_string_table_roundtrip() {
306 let mut state = ParserState::new(24, |_| false, false);
307 state.string_tables = vec![StringTableMeta {
308 max_entries: 16,
309 fixed_userdata_size: None,
310 }];
311 crate::test_roundtrip_encode(
312 UpdateStringTableMessage {
313 entries: vec![],
314 table_id: 0,
315 },
316 &state,
317 );
318 crate::test_roundtrip_encode(
319 UpdateStringTableMessage {
320 entries: vec![(
321 2,
322 StringTableEntry {
323 text: Some("foo".into()),
324 extra_data: None,
325 },
326 )],
327 table_id: 0,
328 },
329 &state,
330 );
331 crate::test_roundtrip_encode(
332 UpdateStringTableMessage {
333 entries: vec![
334 (
335 2,
336 StringTableEntry {
337 text: Some("foo".into()),
338 extra_data: None,
339 },
340 ),
341 (
342 3,
343 StringTableEntry {
344 text: Some("bar".into()),
345 extra_data: None,
346 },
347 ),
348 ],
349 table_id: 0,
350 },
351 &state,
352 );
353}
354
355struct TableEntries<'a> {
356 entries: Vec<(u16, StringTableEntry<'a>)>,
357 history: Vec<u16>,
358}
359
360impl<'a> TableEntries<'a> {
361 pub fn new(count: usize) -> Self {
362 TableEntries {
363 entries: Vec::with_capacity(min(count, 128)),
364 history: Vec::with_capacity(32),
365 }
366 }
367
368 pub fn push(&mut self, entry: (u16, StringTableEntry<'a>)) {
369 if self.history.len() > 31 {
370 self.history.remove(0);
371 }
372 let entry_index = self.entries.len();
373 self.entries.push(entry);
374 self.history.push(entry_index as u16);
375 }
376
377 pub fn get_history(&self, index: usize) -> Option<&StringTableEntry<'a>> {
378 self.history
379 .get(index)
380 .and_then(|entry_index| self.entries.get(*entry_index as usize))
381 .map(|entry| &entry.1)
382 }
383
384 pub fn into_entries(self) -> Vec<(u16, StringTableEntry<'a>)> {
385 self.entries
386 }
387
388 #[cfg(feature = "write")]
389 pub fn find_best_history(&self, text: &str) -> Option<(usize, usize)> {
390 let mut best_index = None;
391 let mut best_count = 0;
392 for (history_index, entry_index) in self.history.iter().enumerate() {
393 if let Some((_, entry)) = self.entries.get(*entry_index as usize) {
394 let similar = min(31, count_similar_characters(entry.text(), text));
395 if similar >= 3 && similar > best_count {
396 best_index = Some(history_index);
397 best_count = similar;
398 }
399 }
400 }
401
402 best_index.map(|index| (index, best_count))
403 }
404}
405
406#[cfg(feature = "write")]
407fn count_similar_characters(a: &str, b: &str) -> usize {
408 for (i, (a, b)) in a.bytes().zip(b.bytes()).enumerate() {
409 if a != b {
410 return i;
411 }
412 }
413 min(a.len(), b.len())
414}
415
416pub fn parse_string_table_update<'a>(
417 stream: &mut Stream<'a>,
418 table_meta: &StringTableMeta,
419 entry_count: u16,
420) -> ReadResult<Vec<(u16, StringTableEntry<'a>)>> {
421 let entry_bits = log_base2(table_meta.max_entries);
422 let mut entries = TableEntries::new(entry_count as usize);
423
424 let mut last_entry: i16 = -1;
425
426 for _ in 0..entry_count {
427 let index = if stream.read()? {
428 last_entry.saturating_add(1) as u16
429 } else {
430 stream.read_sized(entry_bits as usize)?
431 };
432
433 last_entry = index as i16;
434
435 let entry = read_table_entry(stream, table_meta, &entries)?;
436 entries.push((index, entry));
437 }
438
439 Ok(entries.into_entries())
440}
441
442#[cfg(feature = "write")]
443pub fn write_string_table_update(
444 entries: &[(u16, StringTableEntry)],
445 stream: &mut BitWriteStream<LittleEndian>,
446 table_meta: &StringTableMeta,
447) -> ReadResult<()> {
448 let entry_bits = log_base2(table_meta.max_entries);
449
450 let mut last_entry: i16 = -1;
451 let mut history = TableEntries::new(entries.len());
452
453 for (index, entry) in entries.iter() {
454 let index = *index as i16;
455 if index == (last_entry + 1) {
456 true.write(stream)?;
457 } else {
458 false.write(stream)?;
459 index.write_sized(stream, entry_bits as usize)?;
460 }
461 last_entry = index;
462
463 write_table_entry(entry, stream, table_meta, &history)?;
464 history.push((index as u16, entry.clone()));
465 }
466
467 Ok(())
468}
469
470#[test]
471#[cfg(feature = "write")]
472fn test_table_update_roundtrip() {
473 fn entry_roundtrip(
474 entries: Vec<(u16, StringTableEntry)>,
475 max_entries: u16,
476 fixed_bits: Option<u8>,
477 ) {
478 let table_meta = StringTableMeta {
479 max_entries,
480 fixed_userdata_size: fixed_bits.map(|bits| FixedUserDataSize { size: 0, bits }),
481 };
482 let mut data = Vec::new();
483 let pos = {
484 let mut write = BitWriteStream::new(&mut data, LittleEndian);
485 write_string_table_update(&entries, &mut write, &table_meta).unwrap();
486 write.bit_len()
487 };
488 let mut read = BitReadStream::new(BitReadBuffer::new(&data, LittleEndian));
489 assert_eq!(
490 entries,
491 parse_string_table_update(&mut read, &table_meta, entries.len() as u16).unwrap()
492 );
493 assert_eq!(pos, read.pos());
494 }
495 entry_roundtrip(
496 vec![(
497 3,
498 StringTableEntry {
499 text: None,
500 extra_data: None,
501 },
502 )],
503 8,
504 None,
505 );
506 entry_roundtrip(
507 vec![
508 (
509 0,
510 StringTableEntry {
511 text: Some("bar".into()),
512 extra_data: None,
513 },
514 ),
515 (
516 1,
517 StringTableEntry {
518 text: Some("foo".into()),
519 extra_data: None,
520 },
521 ),
522 (
523 5,
524 StringTableEntry {
525 text: Some("asd".into()),
526 extra_data: None,
527 },
528 ),
529 ],
530 16,
531 None,
532 );
533 entry_roundtrip(
534 vec![
535 (
536 1,
537 StringTableEntry {
538 text: Some("foo".into()),
539 extra_data: None,
540 },
541 ),
542 (
543 2,
544 StringTableEntry {
545 text: Some("asd".into()),
546 extra_data: None,
547 },
548 ),
549 ],
550 16,
551 None,
552 );
553 entry_roundtrip(
554 vec![(
555 1,
556 StringTableEntry {
557 text: Some("foo".into()),
558 extra_data: None,
559 },
560 )],
561 16,
562 None,
563 );
564}
565
566fn read_table_entry<'a>(
567 stream: &mut Stream<'a>,
568 table_meta: &StringTableMeta,
569 history: &TableEntries,
570) -> ReadResult<StringTableEntry<'a>> {
571 let text = if stream.read()? {
572 if stream.read()? {
574 let index: u16 = stream.read_sized(5)?;
576 let bytes_to_copy: u32 = stream.read_sized(5)?;
577 let rest_of_string: Cow<str> = stream.read()?;
578
579 Some(
580 match history
581 .get_history(index as usize)
582 .and_then(|entry| entry.text.as_ref())
583 {
584 Some(text) => Cow::Owned(String::from_utf8({
585 text.bytes()
586 .take(bytes_to_copy as usize)
587 .chain(rest_of_string.bytes())
588 .collect()
589 })?),
590 None => rest_of_string, },
592 )
593 } else {
594 Some(stream.read()?)
595 }
596 } else {
597 None
598 };
599
600 let extra_data = if stream.read()? {
601 Some(match table_meta.fixed_userdata_size {
602 Some(size) => stream.read_bits(size.bits as usize)?,
603 None => {
604 let bytes: u16 = stream.read_sized(14)?;
605 stream.read_bits(bytes as usize * 8)?
606 }
607 })
608 } else {
609 None
610 }
611 .map(ExtraData::new);
612
613 Ok(StringTableEntry { text, extra_data })
614}
615
616#[cfg(feature = "write")]
617fn write_table_entry(
618 entry: &StringTableEntry,
619 stream: &mut BitWriteStream<LittleEndian>,
620 table_meta: &StringTableMeta,
621 history: &TableEntries,
622) -> ReadResult<()> {
623 entry.text.is_some().write(stream)?;
624 if let Some(text) = entry.text.as_deref() {
625 let history_item = history.find_best_history(text);
626 history_item.is_some().write(stream)?;
627 if let Some((history_index, history_count)) = history_item {
628 history_index.write_sized(stream, 5)?;
629 history_count.write_sized(stream, 5)?;
630 let diff_bytes =
631 text.as_bytes()
632 .get(history_count..)
633 .ok_or(BitError::IndexOutOfBounds {
634 pos: history_count,
635 size: text.len(),
636 })?;
637 stream.write_bytes(diff_bytes)?;
638 0u8.write(stream)?; } else {
640 text.write(stream)?;
641 }
642 }
643
644 entry.extra_data.is_some().write(stream)?;
645 if let Some(extra_data) = entry.extra_data.as_ref() {
646 match table_meta.fixed_userdata_size {
647 Some(size) => {
648 extra_data.data.write_sized(stream, size.bits as usize)?;
649 }
650 None => {
651 extra_data.byte_len.write_sized(stream, 14)?;
652 extra_data
653 .data
654 .write_sized(stream, extra_data.byte_len as usize * 8)?;
655 }
656 }
657 }
658
659 Ok(())
660}
661
662#[test]
663#[cfg(feature = "write")]
664fn test_table_entry_roundtrip() {
665 fn entry_roundtrip(entry: StringTableEntry, fixed_bits: Option<u8>) {
666 let table_meta = StringTableMeta {
667 max_entries: 0,
668 fixed_userdata_size: fixed_bits.map(|bits| FixedUserDataSize { size: 0, bits }),
669 };
670 let mut data = Vec::new();
671 let pos = {
672 let history = TableEntries::new(1);
673 let mut write = BitWriteStream::new(&mut data, LittleEndian);
674 write_table_entry(&entry, &mut write, &table_meta, &history).unwrap();
675 write.bit_len()
676 };
677 let mut read = BitReadStream::new(BitReadBuffer::new(&data, LittleEndian));
678 assert_eq!(
679 entry,
680 read_table_entry(&mut read, &table_meta, &TableEntries::new(0)).unwrap()
681 );
682 assert_eq!(pos, read.pos());
683 }
684 entry_roundtrip(
685 StringTableEntry {
686 text: None,
687 extra_data: None,
688 },
689 None,
690 );
691 entry_roundtrip(
692 StringTableEntry {
693 text: Some("foo".into()),
694 extra_data: None,
695 },
696 None,
697 );
698 entry_roundtrip(
699 StringTableEntry {
700 text: None,
701 extra_data: Some(ExtraData::new(BitReadStream::new(
702 BitReadBuffer::new_owned(vec![0x55], LittleEndian),
703 ))),
704 },
705 None,
706 );
707 entry_roundtrip(
708 StringTableEntry {
709 text: None,
710 extra_data: Some(ExtraData::new(BitReadStream::new(
711 BitReadBuffer::new_owned(vec![0x55; 128], LittleEndian),
712 ))),
713 },
714 None,
715 );
716 entry_roundtrip(
717 StringTableEntry {
718 text: None,
719 extra_data: Some(ExtraData::new(BitReadStream::new(
720 BitReadBuffer::new_owned(vec![0x55; 4], LittleEndian),
721 ))),
722 },
723 Some(4 * 8),
724 );
725}
726
727pub fn read_var_int(stream: &mut Stream) -> ReadResult<u32> {
728 let mut result: u32 = 0;
729 for i in (0..35u32).step_by(7) {
730 let byte: u8 = stream.read()?;
731 result |= (byte as u32 & 0x7F) << i;
732
733 if (byte >> 7) == 0 {
734 break;
735 }
736 }
737 Ok(result)
738}
739
740#[cfg(feature = "write")]
741pub fn write_var_int(mut int: u32, stream: &mut BitWriteStream<LittleEndian>) -> ReadResult<()> {
742 while int > 0x7F {
743 let byte: u8 = int as u8 & 0x7F;
744 (byte | 0x80).write(stream)?;
745 int >>= 7;
746 }
747 (int as u8).write(stream)
748}
749
750#[cfg(feature = "write")]
753pub fn encode_var_int_fixed(mut int: u32) -> u64 {
754 let mut out = 0;
755 for i in 0..4 {
756 let byte: u8 = int as u8 & 0x7F;
757 out |= ((byte | 0x80) as u64) << (i * 8);
758 int >>= 7;
759 }
760 out |= (int as u64) << 32;
761 out
762}
763
764#[test]
765#[cfg(feature = "write")]
766fn test_var_int_roundtrip() {
767 fn var_int_roundtrip(int: u32) {
768 let mut data = Vec::new();
769 let pos = {
770 let mut write = BitWriteStream::new(&mut data, LittleEndian);
771 write_var_int(int, &mut write).unwrap();
772 write.bit_len()
773 };
774 let mut read = BitReadStream::new(BitReadBuffer::new(&data, LittleEndian));
775 assert_eq!(int, read_var_int(&mut read).unwrap());
776 assert_eq!(pos, read.pos());
777 }
778 var_int_roundtrip(0);
779 var_int_roundtrip(1);
780 var_int_roundtrip(10);
781 var_int_roundtrip(55);
782 var_int_roundtrip(355);
783 var_int_roundtrip(12354);
784 var_int_roundtrip(123125412);
785}
786
787#[test]
788#[cfg(feature = "write")]
789fn test_var_int_fixed_roundtrip() {
790 fn var_int_roundtrip(int: u32) {
791 let mut data = Vec::new();
792 let pos = {
793 let mut write = BitWriteStream::new(&mut data, LittleEndian);
794 let encoded = encode_var_int_fixed(int);
795 encoded.write_sized(&mut write, 40).unwrap();
796 write.bit_len()
797 };
798 assert_eq!(40, pos);
799 let mut read = BitReadStream::new(BitReadBuffer::new(&data, LittleEndian));
800 assert_eq!(int, read_var_int(&mut read).unwrap());
801 assert_eq!(pos, read.pos());
802 }
803 var_int_roundtrip(0);
804 var_int_roundtrip(1);
805 var_int_roundtrip(10);
806 var_int_roundtrip(55);
807 var_int_roundtrip(355);
808 var_int_roundtrip(12354);
809 var_int_roundtrip(123125412);
810}
811
812pub fn log_base2<T: PrimInt + Unsigned>(num: T) -> u32 {
813 (std::mem::size_of::<T>() as u32 * 8 - 1).saturating_sub(num.leading_zeros())
816}