Skip to main content

vexil_runtime/
bit_writer.rs

1/// A byte-buffer builder that packs fields LSB-first at the bit level.
2///
3/// Created with [`BitWriter::new`], written to with `write_*` methods, and
4/// finalized with [`BitWriter::finish`] which flushes any partial byte and
5/// returns the completed buffer.
6///
7/// Sub-byte fields are accumulated in a single byte; once 8 bits are filled
8/// the byte is flushed. Multi-byte writes (e.g. [`write_u16`](Self::write_u16))
9/// first align to a byte boundary, then append little-endian bytes directly.
10pub struct BitWriter {
11    buf: Vec<u8>,
12    current_byte: u8,
13    bit_offset: u8,
14}
15
16impl BitWriter {
17    /// Create a new, empty `BitWriter`.
18    pub fn new() -> Self {
19        Self {
20            buf: Vec::new(),
21            current_byte: 0,
22            bit_offset: 0,
23        }
24    }
25
26    /// Internal: align to a byte boundary without the "empty = zero byte" rule.
27    /// Used before multi-byte writes to ensure alignment.
28    fn align(&mut self) {
29        if self.bit_offset > 0 {
30            self.buf.push(self.current_byte);
31            self.current_byte = 0;
32            self.bit_offset = 0;
33        }
34    }
35
36    /// Write `count` bits from `value`, LSB first.
37    pub fn write_bits(&mut self, value: u64, count: u8) {
38        let mut v = value;
39        for _ in 0..count {
40            let bit = (v & 1) as u8;
41            self.current_byte |= bit << self.bit_offset;
42            self.bit_offset += 1;
43            if self.bit_offset == 8 {
44                self.buf.push(self.current_byte);
45                self.current_byte = 0;
46                self.bit_offset = 0;
47            }
48            v >>= 1;
49        }
50    }
51
52    /// Write a single boolean as 1 bit.
53    pub fn write_bool(&mut self, v: bool) {
54        self.write_bits(v as u64, 1);
55    }
56
57    /// Flush any partial byte to the buffer.
58    ///
59    /// Special case per spec §4.1: if nothing has been written at all
60    /// (bit_offset == 0 AND buf is empty), push a zero byte anyway.
61    /// If bit_offset == 0 and buf is non-empty, this is a no-op.
62    pub fn flush_to_byte_boundary(&mut self) {
63        if self.bit_offset == 0 {
64            if self.buf.is_empty() {
65                self.buf.push(0x00);
66            }
67            // else: already aligned and something was written — no-op
68        } else {
69            self.buf.push(self.current_byte);
70            self.current_byte = 0;
71            self.bit_offset = 0;
72        }
73    }
74
75    /// Write a `u8`, aligning to a byte boundary first.
76    pub fn write_u8(&mut self, v: u8) {
77        self.align();
78        self.buf.push(v);
79    }
80
81    /// Write a `u16` in little-endian byte order, aligning first.
82    pub fn write_u16(&mut self, v: u16) {
83        self.align();
84        self.buf.extend_from_slice(&v.to_le_bytes());
85    }
86
87    /// Write a `u32` in little-endian byte order, aligning first.
88    pub fn write_u32(&mut self, v: u32) {
89        self.align();
90        self.buf.extend_from_slice(&v.to_le_bytes());
91    }
92
93    /// Write a `u64` in little-endian byte order, aligning first.
94    pub fn write_u64(&mut self, v: u64) {
95        self.align();
96        self.buf.extend_from_slice(&v.to_le_bytes());
97    }
98
99    /// Write an `i8`, aligning to a byte boundary first.
100    pub fn write_i8(&mut self, v: i8) {
101        self.align();
102        self.buf.extend_from_slice(&v.to_le_bytes());
103    }
104
105    /// Write an `i16` in little-endian byte order, aligning first.
106    pub fn write_i16(&mut self, v: i16) {
107        self.align();
108        self.buf.extend_from_slice(&v.to_le_bytes());
109    }
110
111    /// Write an `i32` in little-endian byte order, aligning first.
112    pub fn write_i32(&mut self, v: i32) {
113        self.align();
114        self.buf.extend_from_slice(&v.to_le_bytes());
115    }
116
117    /// Write an `i64` in little-endian byte order, aligning first.
118    pub fn write_i64(&mut self, v: i64) {
119        self.align();
120        self.buf.extend_from_slice(&v.to_le_bytes());
121    }
122
123    /// Write an f32, canonicalizing NaN to 0x7FC00000.
124    pub fn write_f32(&mut self, v: f32) {
125        self.align();
126        let bits: u32 = if v.is_nan() {
127            0x7FC00000u32
128        } else {
129            v.to_bits()
130        };
131        self.buf.extend_from_slice(&bits.to_le_bytes());
132    }
133
134    /// Write an f64, canonicalizing NaN to 0x7FF8000000000000.
135    pub fn write_f64(&mut self, v: f64) {
136        self.align();
137        let bits: u64 = if v.is_nan() {
138            0x7FF8000000000000u64
139        } else {
140            v.to_bits()
141        };
142        self.buf.extend_from_slice(&bits.to_le_bytes());
143    }
144
145    /// Write a LEB128-encoded unsigned integer.
146    pub fn write_leb128(&mut self, v: u64) {
147        self.align();
148        crate::leb128::encode(&mut self.buf, v);
149    }
150
151    /// Write a ZigZag + LEB128 encoded signed integer.
152    pub fn write_zigzag(&mut self, v: i64, type_bits: u8) {
153        let encoded = crate::zigzag::zigzag_encode(v, type_bits);
154        self.write_leb128(encoded);
155    }
156
157    /// Write a UTF-8 string with a LEB128 length prefix.
158    pub fn write_string(&mut self, s: &str) {
159        self.align();
160        crate::leb128::encode(&mut self.buf, s.len() as u64);
161        self.buf.extend_from_slice(s.as_bytes());
162    }
163
164    /// Write a byte slice with a LEB128 length prefix.
165    pub fn write_bytes(&mut self, data: &[u8]) {
166        self.align();
167        crate::leb128::encode(&mut self.buf, data.len() as u64);
168        self.buf.extend_from_slice(data);
169    }
170
171    /// Write raw bytes with no length prefix.
172    pub fn write_raw_bytes(&mut self, data: &[u8]) {
173        self.align();
174        self.buf.extend_from_slice(data);
175    }
176
177    /// Flush any partial byte and return the finished buffer.
178    pub fn finish(mut self) -> Vec<u8> {
179        self.flush_to_byte_boundary();
180        self.buf
181    }
182}
183
184impl Default for BitWriter {
185    fn default() -> Self {
186        Self::new()
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn write_single_bit_true() {
196        let mut w = BitWriter::new();
197        w.write_bool(true);
198        assert_eq!(w.finish(), [0x01]);
199    }
200
201    #[test]
202    fn write_single_bit_false() {
203        let mut w = BitWriter::new();
204        w.write_bool(false);
205        assert_eq!(w.finish(), [0x00]);
206    }
207
208    #[test]
209    fn write_bits_lsb_first() {
210        let mut w = BitWriter::new();
211        w.write_bits(5, 3); // 101
212        w.write_bits(19, 5); // 10011
213                             // LSB-first: byte = 10011_101 = 0x9D
214        assert_eq!(w.finish(), [0x9D]);
215    }
216
217    #[test]
218    fn write_bits_cross_byte_boundary() {
219        let mut w = BitWriter::new();
220        w.write_bits(5, 3);
221        w.write_bits(19, 5);
222        w.write_bits(42, 6); // 101010
223                             // Byte 0: 0x9D, Byte 1: 00_101010 = 0x2A
224        assert_eq!(w.finish(), [0x9D, 0x2A]);
225    }
226
227    #[test]
228    fn flush_to_byte_boundary_pads_zeros() {
229        let mut w = BitWriter::new();
230        w.write_bits(0b101, 3);
231        w.flush_to_byte_boundary();
232        w.write_bits(0xFF, 8);
233        assert_eq!(w.finish(), [0x05, 0xFF]);
234    }
235
236    #[test]
237    fn write_u8_flushes_first() {
238        let mut w = BitWriter::new();
239        w.write_bool(true);
240        w.write_u8(0xAB);
241        assert_eq!(w.finish(), [0x01, 0xAB]);
242    }
243
244    #[test]
245    fn write_u16_le() {
246        let mut w = BitWriter::new();
247        w.write_u16(0x0102);
248        assert_eq!(w.finish(), [0x02, 0x01]);
249    }
250
251    #[test]
252    fn write_u32_le() {
253        let mut w = BitWriter::new();
254        w.write_u32(0x01020304);
255        assert_eq!(w.finish(), [0x04, 0x03, 0x02, 0x01]);
256    }
257
258    #[test]
259    fn write_i16_negative() {
260        let mut w = BitWriter::new();
261        w.write_i16(-1);
262        assert_eq!(w.finish(), [0xFF, 0xFF]);
263    }
264
265    #[test]
266    fn write_f32_nan_canonicalized() {
267        let mut w = BitWriter::new();
268        w.write_f32(f32::NAN);
269        assert_eq!(w.finish(), [0x00, 0x00, 0xC0, 0x7F]);
270    }
271
272    #[test]
273    fn write_f64_nan_canonicalized() {
274        let mut w = BitWriter::new();
275        w.write_f64(f64::NAN);
276        assert_eq!(w.finish(), 0x7FF8000000000000u64.to_le_bytes());
277    }
278
279    #[test]
280    fn write_f32_negative_zero_preserved() {
281        let mut w = BitWriter::new();
282        w.write_f32(-0.0f32);
283        let buf = w.finish();
284        assert_eq!(buf, (-0.0f32).to_le_bytes());
285        assert_ne!(buf, 0.0f32.to_le_bytes());
286    }
287
288    #[test]
289    fn write_leb128_test() {
290        let mut w = BitWriter::new();
291        w.write_leb128(300);
292        assert_eq!(w.finish(), [0xAC, 0x02]);
293    }
294
295    #[test]
296    fn write_zigzag_neg1() {
297        let mut w = BitWriter::new();
298        w.write_zigzag(-1, 64);
299        assert_eq!(w.finish(), [0x01]);
300    }
301
302    #[test]
303    fn write_string_test() {
304        let mut w = BitWriter::new();
305        w.write_string("hi");
306        assert_eq!(w.finish(), [0x02, 0x68, 0x69]);
307    }
308
309    #[test]
310    fn write_bytes_test() {
311        let mut w = BitWriter::new();
312        w.write_bytes(&[0xDE, 0xAD]);
313        assert_eq!(w.finish(), [0x02, 0xDE, 0xAD]);
314    }
315
316    #[test]
317    fn write_raw_bytes_test() {
318        let mut w = BitWriter::new();
319        w.write_raw_bytes(&[0xCA, 0xFE]);
320        assert_eq!(w.finish(), [0xCA, 0xFE]);
321    }
322
323    #[test]
324    fn empty_flush_produces_zero_byte() {
325        let mut w = BitWriter::new();
326        w.flush_to_byte_boundary();
327        assert_eq!(w.finish(), [0x00]);
328    }
329}