Skip to main content

blvm_protocol/
varint.rs

1//! Variable-length integer encoding (Bitcoin protocol)
2//!
3//! Bitcoin uses a variable-length integer encoding where:
4//! - Values < 0xfd: encoded as single byte
5//! - Values < 0xffff: encoded as 0xfd + 2 bytes (little-endian)
6//! - Values < 0xffffffff: encoded as 0xfe + 4 bytes (little-endian)
7//! - Values >= 0xffffffff: encoded as 0xff + 8 bytes (little-endian)
8
9use crate::ConsensusError;
10use crate::Result;
11use std::io::{Read, Write};
12
13/// Maximum value for a varint (2^64 - 1)
14pub const MAX_VARINT: u64 = u64::MAX;
15
16/// Read a variable-length integer from bytes
17pub fn read_varint<R: Read>(reader: &mut R) -> Result<u64> {
18    let mut buf = [0u8; 1];
19    reader
20        .read_exact(&mut buf)
21        .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
22    let first_byte = buf[0];
23
24    match first_byte {
25        0xfd => {
26            let mut buf = [0u8; 2];
27            reader
28                .read_exact(&mut buf)
29                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
30            Ok(u16::from_le_bytes(buf) as u64)
31        }
32        0xfe => {
33            let mut buf = [0u8; 4];
34            reader
35                .read_exact(&mut buf)
36                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
37            Ok(u32::from_le_bytes(buf) as u64)
38        }
39        0xff => {
40            let mut buf = [0u8; 8];
41            reader
42                .read_exact(&mut buf)
43                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
44            Ok(u64::from_le_bytes(buf))
45        }
46        _ => Ok(first_byte as u64),
47    }
48}
49
50/// Write a variable-length integer to bytes
51pub fn write_varint<W: Write>(writer: &mut W, value: u64) -> Result<usize> {
52    match value {
53        0..=0xfc => {
54            writer
55                .write_all(&[value as u8])
56                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
57            Ok(1)
58        }
59        0xfd..=0xffff => {
60            writer
61                .write_all(&[0xfd])
62                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
63            writer
64                .write_all(&(value as u16).to_le_bytes())
65                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
66            Ok(3)
67        }
68        0x10000..=0xffff_ffff => {
69            writer
70                .write_all(&[0xfe])
71                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
72            writer
73                .write_all(&(value as u32).to_le_bytes())
74                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
75            Ok(5)
76        }
77        _ => {
78            writer
79                .write_all(&[0xff])
80                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
81            writer
82                .write_all(&value.to_le_bytes())
83                .map_err(|e| ConsensusError::Serialization(format!("IO error: {e}").into()))?;
84            Ok(9)
85        }
86    }
87}
88
89/// Get the encoded size of a varint without writing it
90pub fn varint_size(value: u64) -> usize {
91    match value {
92        0..=0xfc => 1,
93        0xfd..=0xffff => 3,
94        0x10000..=0xffff_ffff => 5,
95        _ => 9,
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use std::io::Cursor;
103
104    #[test]
105    fn test_varint_roundtrip() {
106        let test_values = vec![
107            0,
108            1,
109            0xfc,
110            0xfd,
111            0xffff,
112            0x10000,
113            0xffff_ffff,
114            0x1_0000_0000,
115            u64::MAX,
116        ];
117
118        for value in test_values {
119            let mut buf = Vec::new();
120            write_varint(&mut buf, value).unwrap();
121            let mut cursor = Cursor::new(&buf);
122            let decoded = read_varint(&mut cursor).unwrap();
123            assert_eq!(value, decoded);
124        }
125    }
126
127    #[test]
128    fn test_varint_size() {
129        assert_eq!(varint_size(0), 1);
130        assert_eq!(varint_size(0xfc), 1);
131        assert_eq!(varint_size(0xfd), 3);
132        assert_eq!(varint_size(0xffff), 3);
133        assert_eq!(varint_size(0x10000), 5);
134        assert_eq!(varint_size(0xffff_ffff), 5);
135        assert_eq!(varint_size(0x1_0000_0000), 9);
136        assert_eq!(varint_size(u64::MAX), 9);
137    }
138}