Skip to main content

tf_demo_parser/demo/message/
stringtable.rs

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        // threshold and both widths are the ones used by the read side above
172        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            // no compression for now
184            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        // set value
573        if stream.read()? {
574            // reuse from history
575            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, // best guess, happens in some pov demos but only for unimportant tables it seems
591                },
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)?; // writing the string as bytes doesn't add the null terminator
639        } 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// encode the int in such a way that it has a fixed size, but still decodes the same
751// result is the first 40 bits of the return value
752#[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    // log(0) = inf, but that's a useless result
814    // since this would only happen in malformed demos, we just return 0
815    (std::mem::size_of::<T>() as u32 * 8 - 1).saturating_sub(num.leading_zeros())
816}