Skip to main content

basalt_types/
bit_set.rs

1use crate::{Decode, Encode, EncodedSize, Error, Result, VarInt};
2
3/// A variable-length bit array encoded as a VarInt-prefixed array of i64 values.
4///
5/// BitSet is used in the Minecraft protocol for chunk light masks, section
6/// bitmasks, and other bitfield data where an arbitrary number of boolean
7/// flags need to be packed efficiently. Each i64 holds 64 bits, and the
8/// array grows as needed to accommodate the highest set bit.
9///
10/// Wire format: VarInt(number of longs) followed by that many big-endian
11/// i64 values. An empty BitSet has zero longs.
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct BitSet {
14    data: Vec<i64>,
15}
16
17impl BitSet {
18    /// Creates a new empty BitSet with no bits set.
19    pub fn new() -> Self {
20        Self { data: Vec::new() }
21    }
22
23    /// Creates a BitSet from a pre-existing vector of i64 words.
24    ///
25    /// Each i64 holds 64 bits. The first element contains bits 0-63,
26    /// the second 64-127, and so on. This is useful when constructing
27    /// a BitSet from data received outside the protocol decoding path.
28    pub fn from_longs(data: Vec<i64>) -> Self {
29        Self { data }
30    }
31
32    /// Returns the number of bits this BitSet can currently hold without
33    /// growing (i.e., the number of longs × 64).
34    pub fn len(&self) -> usize {
35        self.data.len() * 64
36    }
37
38    /// Returns true if the BitSet contains no longs (zero capacity).
39    pub fn is_empty(&self) -> bool {
40        self.data.is_empty()
41    }
42
43    /// Returns the value of the bit at the given index.
44    ///
45    /// Returns `false` for indices beyond the current capacity — bits
46    /// outside the allocated range are implicitly zero.
47    pub fn get(&self, index: usize) -> bool {
48        let word = index / 64;
49        let bit = index % 64;
50        if word >= self.data.len() {
51            return false;
52        }
53        (self.data[word] >> bit) & 1 != 0
54    }
55
56    /// Sets or clears the bit at the given index.
57    ///
58    /// If the index is beyond the current capacity and `value` is `true`,
59    /// the internal storage is automatically extended with zero-filled
60    /// longs. Setting a bit to `false` beyond the current capacity is
61    /// a no-op (the bit is already implicitly zero).
62    pub fn set(&mut self, index: usize, value: bool) {
63        let word = index / 64;
64        let bit = index % 64;
65
66        if value {
67            if word >= self.data.len() {
68                self.data.resize(word + 1, 0);
69            }
70            self.data[word] |= 1i64 << bit;
71        } else if word < self.data.len() {
72            self.data[word] &= !(1i64 << bit);
73        }
74    }
75}
76
77impl Default for BitSet {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83/// Encodes the BitSet as a VarInt-prefixed array of big-endian i64 values.
84///
85/// The VarInt indicates the number of longs in the array, followed by
86/// each long encoded as 8 big-endian bytes. An empty BitSet encodes as
87/// a single VarInt(0) byte.
88impl Encode for BitSet {
89    /// Writes VarInt(number of longs) followed by each long as 8 big-endian bytes.
90    fn encode(&self, buf: &mut Vec<u8>) -> Result<()> {
91        VarInt(self.data.len() as i32).encode(buf)?;
92        for &word in &self.data {
93            word.encode(buf)?;
94        }
95        Ok(())
96    }
97}
98
99/// Decodes a BitSet from a VarInt-prefixed array of big-endian i64 values.
100///
101/// Reads the VarInt count, then that many 8-byte big-endian i64 values.
102/// Fails if the buffer doesn't contain enough bytes for the declared
103/// number of longs.
104impl Decode for BitSet {
105    /// Reads the VarInt length, then decodes that many i64 words.
106    ///
107    /// Fails with `Error::BufferUnderflow` if the buffer is too short,
108    /// or with `Error::InvalidData` if the declared length is negative.
109    fn decode(buf: &mut &[u8]) -> Result<Self> {
110        let len = VarInt::decode(buf)?.0;
111        if len < 0 {
112            return Err(Error::InvalidData(format!("negative BitSet length: {len}")));
113        }
114        let len = len as usize;
115        let mut data = Vec::with_capacity(len);
116        for _ in 0..len {
117            data.push(i64::decode(buf)?);
118        }
119        Ok(Self { data })
120    }
121}
122
123/// Computes the wire size of the BitSet.
124///
125/// The total size is the VarInt-encoded length prefix plus 8 bytes per
126/// long. This enables exact buffer pre-allocation before encoding.
127impl EncodedSize for BitSet {
128    /// Returns VarInt prefix size + (number of longs × 8).
129    fn encoded_size(&self) -> usize {
130        VarInt(self.data.len() as i32).encoded_size() + self.data.len() * 8
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    fn roundtrip(bs: &BitSet) {
139        let mut buf = Vec::with_capacity(bs.encoded_size());
140        bs.encode(&mut buf).unwrap();
141        assert_eq!(buf.len(), bs.encoded_size());
142
143        let mut cursor = buf.as_slice();
144        let decoded = BitSet::decode(&mut cursor).unwrap();
145        assert!(cursor.is_empty());
146        assert_eq!(decoded, *bs);
147    }
148
149    // -- Construction --
150
151    #[test]
152    fn new_is_empty() {
153        let bs = BitSet::new();
154        assert!(bs.is_empty());
155        assert_eq!(bs.len(), 0);
156    }
157
158    #[test]
159    fn default_is_empty() {
160        let bs = BitSet::default();
161        assert!(bs.is_empty());
162    }
163
164    #[test]
165    fn from_longs() {
166        let bs = BitSet::from_longs(vec![0xFF, 0x00]);
167        assert_eq!(bs.len(), 128);
168        assert!(!bs.is_empty());
169    }
170
171    // -- Get/Set --
172
173    #[test]
174    fn get_out_of_range() {
175        let bs = BitSet::new();
176        assert!(!bs.get(0));
177        assert!(!bs.get(1000));
178    }
179
180    #[test]
181    fn set_and_get() {
182        let mut bs = BitSet::new();
183        bs.set(0, true);
184        assert!(bs.get(0));
185        assert!(!bs.get(1));
186    }
187
188    #[test]
189    fn set_high_bit() {
190        let mut bs = BitSet::new();
191        bs.set(200, true);
192        assert!(bs.get(200));
193        assert!(!bs.get(199));
194        assert!(!bs.get(201));
195        // Should have allocated 4 longs (200 / 64 = 3, so index 3 needs 4 longs)
196        assert_eq!(bs.len(), 256);
197    }
198
199    #[test]
200    fn clear_bit() {
201        let mut bs = BitSet::new();
202        bs.set(5, true);
203        assert!(bs.get(5));
204        bs.set(5, false);
205        assert!(!bs.get(5));
206    }
207
208    #[test]
209    fn clear_out_of_range_is_noop() {
210        let mut bs = BitSet::new();
211        bs.set(1000, false);
212        assert!(bs.is_empty());
213    }
214
215    #[test]
216    fn word_boundary() {
217        let mut bs = BitSet::new();
218        bs.set(63, true);
219        bs.set(64, true);
220        assert!(bs.get(63));
221        assert!(bs.get(64));
222        assert!(!bs.get(62));
223        assert!(!bs.get(65));
224    }
225
226    // -- Encode/Decode --
227
228    #[test]
229    fn roundtrip_empty() {
230        roundtrip(&BitSet::new());
231    }
232
233    #[test]
234    fn roundtrip_single_word() {
235        let mut bs = BitSet::new();
236        bs.set(0, true);
237        bs.set(7, true);
238        bs.set(63, true);
239        roundtrip(&bs);
240    }
241
242    #[test]
243    fn roundtrip_multiple_words() {
244        let mut bs = BitSet::new();
245        bs.set(0, true);
246        bs.set(64, true);
247        bs.set(128, true);
248        roundtrip(&bs);
249    }
250
251    #[test]
252    fn roundtrip_from_longs() {
253        let bs = BitSet::from_longs(vec![0x0102030405060708, -1]);
254        roundtrip(&bs);
255    }
256
257    #[test]
258    fn empty_encodes_as_varint_zero() {
259        let bs = BitSet::new();
260        let mut buf = Vec::new();
261        bs.encode(&mut buf).unwrap();
262        assert_eq!(buf, [0x00]);
263    }
264
265    #[test]
266    fn encoded_size_empty() {
267        assert_eq!(BitSet::new().encoded_size(), 1);
268    }
269
270    #[test]
271    fn encoded_size_one_word() {
272        let bs = BitSet::from_longs(vec![1]);
273        // VarInt(1) = 1 byte + 1 long = 8 bytes
274        assert_eq!(bs.encoded_size(), 9);
275    }
276
277    #[test]
278    fn negative_length_decode() {
279        let mut buf = Vec::new();
280        VarInt(-1).encode(&mut buf).unwrap();
281        let mut cursor = buf.as_slice();
282        assert!(matches!(
283            BitSet::decode(&mut cursor),
284            Err(Error::InvalidData(_))
285        ));
286    }
287
288    #[test]
289    fn truncated_buffer() {
290        let mut buf = Vec::new();
291        VarInt(2).encode(&mut buf).unwrap();
292        // Only provide one long instead of two
293        buf.extend_from_slice(&[0u8; 8]);
294        let mut cursor = buf.as_slice();
295        assert!(matches!(
296            BitSet::decode(&mut cursor),
297            Err(Error::BufferUnderflow { .. })
298        ));
299    }
300
301    mod proptests {
302        use super::*;
303        use proptest::prelude::*;
304
305        proptest! {
306            #[test]
307            fn bitset_roundtrip(data in proptest::collection::vec(any::<i64>(), 0..10)) {
308                let bs = BitSet::from_longs(data);
309                roundtrip(&bs);
310            }
311        }
312    }
313}