Skip to main content

blvm_primitives/serialization/
varint.rs

1//! Bitcoin VarInt encoding/decoding
2//!
3//! VarInt (Variable Integer) is a compact encoding for integers used throughout
4//! Bitcoin's wire format. It uses 1-9 bytes depending on the value.
5//!
6//! Encoding rules:
7//! - If value < 0xfd: single byte
8//! - If value <= 0xffff: 0xfd prefix + 2 bytes (little-endian)
9//! - If value <= 0xffffffff: 0xfe prefix + 4 bytes (little-endian)
10//! - Otherwise: 0xff prefix + 8 bytes (little-endian)
11//!
12//! This must match consensus's CVarInt implementation exactly.
13
14use crate::error::{ConsensusError, Result};
15use std::borrow::Cow;
16
17/// Error type for VarInt encoding/decoding failures
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum VarIntError {
20    /// Insufficient bytes to decode VarInt
21    InsufficientBytes,
22    /// Invalid VarInt encoding format
23    InvalidEncoding,
24    /// VarInt value exceeds maximum (u64::MAX)
25    ValueTooLarge,
26}
27
28impl std::fmt::Display for VarIntError {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            VarIntError::InsufficientBytes => write!(f, "Insufficient bytes to decode VarInt"),
32            VarIntError::InvalidEncoding => write!(f, "Invalid VarInt encoding"),
33            VarIntError::ValueTooLarge => write!(f, "VarInt value too large"),
34        }
35    }
36}
37
38impl std::error::Error for VarIntError {}
39
40/// Encode a u64 value as a Bitcoin VarInt
41pub fn encode_varint(value: u64) -> Vec<u8> {
42    if value < 0xfd {
43        vec![value as u8]
44    } else if value <= 0xffff {
45        debug_assert!(value >= 0xfd);
46        let mut result = vec![0xfd];
47        result.extend_from_slice(&(value as u16).to_le_bytes());
48        debug_assert!(result.len() == 3);
49        result
50    } else if value <= 0xffffffff {
51        debug_assert!(value > 0xffff);
52        let mut result = vec![0xfe];
53        result.extend_from_slice(&(value as u32).to_le_bytes());
54        debug_assert!(result.len() == 5);
55        result
56    } else {
57        debug_assert!(value > 0xffffffff);
58        let mut result = vec![0xff];
59        result.extend_from_slice(&value.to_le_bytes());
60        debug_assert!(result.len() == 9);
61        result
62    }
63}
64
65/// Decode a Bitcoin VarInt from bytes
66///
67/// Returns the decoded value and the number of bytes consumed.
68pub fn decode_varint(data: &[u8]) -> Result<(u64, usize)> {
69    if data.is_empty() {
70        return Err(ConsensusError::Serialization(Cow::Owned(
71            VarIntError::InsufficientBytes.to_string(),
72        )));
73    }
74
75    let first_byte = data[0];
76
77    match first_byte {
78        b if b < 0xfd => Ok((b as u64, 1)),
79
80        0xfd => {
81            if data.len() < 3 {
82                return Err(ConsensusError::Serialization(Cow::Owned(
83                    VarIntError::InsufficientBytes.to_string(),
84                )));
85            }
86            let value = u16::from_le_bytes([data[1], data[2]]) as u64;
87            if value < 0xfd {
88                return Err(ConsensusError::Serialization(Cow::Owned(
89                    VarIntError::InvalidEncoding.to_string(),
90                )));
91            }
92            Ok((value, 3))
93        }
94
95        0xfe => {
96            if data.len() < 5 {
97                return Err(ConsensusError::Serialization(Cow::Owned(
98                    VarIntError::InsufficientBytes.to_string(),
99                )));
100            }
101            let value = u32::from_le_bytes([data[1], data[2], data[3], data[4]]) as u64;
102            if value <= 0xffff {
103                return Err(ConsensusError::Serialization(Cow::Owned(
104                    VarIntError::InvalidEncoding.to_string(),
105                )));
106            }
107            Ok((value, 5))
108        }
109
110        0xff => {
111            if data.len() < 9 {
112                return Err(ConsensusError::Serialization(Cow::Owned(
113                    VarIntError::InsufficientBytes.to_string(),
114                )));
115            }
116            let value = u64::from_le_bytes([
117                data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
118            ]);
119            if value <= 0xffffffff {
120                return Err(ConsensusError::Serialization(Cow::Owned(
121                    VarIntError::InvalidEncoding.to_string(),
122                )));
123            }
124            Ok((value, 9))
125        }
126
127        _ => Err(ConsensusError::Serialization(Cow::Owned(
128            VarIntError::InvalidEncoding.to_string(),
129        ))),
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_encode_decode_round_trip() {
139        let values = [
140            0u64,
141            252,
142            253,
143            65535,
144            65536,
145            0xffffffff,
146            0x100000000,
147            u64::MAX,
148        ];
149        for value in values {
150            let encoded = encode_varint(value);
151            let (decoded, len) = decode_varint(&encoded).unwrap();
152            assert_eq!(decoded, value);
153            assert_eq!(len, encoded.len());
154        }
155    }
156}