Skip to main content

kacrab_protocol/
primitives.rs

1//! Fixed-width and varint primitive read/write helpers.
2//!
3//! Every Kafka schema field eventually decomposes to one of these primitives.
4//! Helpers operate on `bytes::Bytes` / `bytes::BytesMut` and return
5//! [`PrimitiveError`] on insufficient data or malformed varints.
6
7pub mod error;
8
9use bytes::{Buf, BufMut, Bytes, BytesMut};
10
11pub use self::error::{PrimitiveError, PrimitiveErrorKind};
12
13/// Result alias for primitive read operations.
14pub type Result<T> = core::result::Result<T, PrimitiveError>;
15
16const VARINT_MAX_BYTES: u8 = 5;
17const VARLONG_MAX_BYTES: u8 = 10;
18
19pub(crate) fn check_remaining(buf: &Bytes, needed: usize) -> Result<()> {
20    let available = buf.remaining();
21    if available < needed {
22        return Err(PrimitiveErrorKind::InsufficientData { needed, available }.into());
23    }
24    Ok(())
25}
26
27// ---------------------------------------------------------------------------
28// Fixed-width integers / float / bool — big-endian on the wire.
29// ---------------------------------------------------------------------------
30
31/// Read a boolean (1 byte: 0 = false, nonzero = true).
32pub fn read_bool(buf: &mut Bytes) -> Result<bool> {
33    check_remaining(buf, 1)?;
34    Ok(buf.get_u8() != 0)
35}
36
37/// Read a signed 8-bit integer.
38pub fn read_i8(buf: &mut Bytes) -> Result<i8> {
39    check_remaining(buf, 1)?;
40    Ok(buf.get_i8())
41}
42
43/// Read a signed 16-bit integer (big-endian).
44pub fn read_i16(buf: &mut Bytes) -> Result<i16> {
45    check_remaining(buf, 2)?;
46    Ok(buf.get_i16())
47}
48
49/// Read a signed 32-bit integer (big-endian).
50pub fn read_i32(buf: &mut Bytes) -> Result<i32> {
51    check_remaining(buf, 4)?;
52    Ok(buf.get_i32())
53}
54
55/// Read a signed 64-bit integer (big-endian).
56pub fn read_i64(buf: &mut Bytes) -> Result<i64> {
57    check_remaining(buf, 8)?;
58    Ok(buf.get_i64())
59}
60
61/// Read an unsigned 16-bit integer (big-endian).
62pub fn read_u16(buf: &mut Bytes) -> Result<u16> {
63    check_remaining(buf, 2)?;
64    Ok(buf.get_u16())
65}
66
67/// Read an unsigned 32-bit integer (big-endian).
68pub fn read_u32(buf: &mut Bytes) -> Result<u32> {
69    check_remaining(buf, 4)?;
70    Ok(buf.get_u32())
71}
72
73/// Read a 64-bit IEEE-754 float (big-endian).
74pub fn read_f64(buf: &mut Bytes) -> Result<f64> {
75    check_remaining(buf, 8)?;
76    Ok(buf.get_f64())
77}
78
79/// Write a boolean (1 byte: 0 or 1).
80pub fn write_bool(buf: &mut BytesMut, value: bool) {
81    buf.put_u8(u8::from(value));
82}
83
84/// Write a signed 8-bit integer.
85pub fn write_i8(buf: &mut BytesMut, value: i8) {
86    buf.put_i8(value);
87}
88
89/// Write a signed 16-bit integer (big-endian).
90pub fn write_i16(buf: &mut BytesMut, value: i16) {
91    buf.put_i16(value);
92}
93
94/// Write a signed 32-bit integer (big-endian).
95pub fn write_i32(buf: &mut BytesMut, value: i32) {
96    buf.put_i32(value);
97}
98
99/// Write a signed 64-bit integer (big-endian).
100pub fn write_i64(buf: &mut BytesMut, value: i64) {
101    buf.put_i64(value);
102}
103
104/// Write an unsigned 16-bit integer (big-endian).
105pub fn write_u16(buf: &mut BytesMut, value: u16) {
106    buf.put_u16(value);
107}
108
109/// Write an unsigned 32-bit integer (big-endian).
110pub fn write_u32(buf: &mut BytesMut, value: u32) {
111    buf.put_u32(value);
112}
113
114/// Write a 64-bit IEEE-754 float (big-endian).
115pub fn write_f64(buf: &mut BytesMut, value: f64) {
116    buf.put_f64(value);
117}
118
119// ---------------------------------------------------------------------------
120// Varints (Protocol Buffers style, MSB continuation bit).
121// ---------------------------------------------------------------------------
122
123/// Read an unsigned varint (1–5 bytes, `u32` payload).
124pub fn read_unsigned_varint(buf: &mut Bytes) -> Result<u32> {
125    let mut value: u32 = 0;
126    for i in 0..VARINT_MAX_BYTES {
127        check_remaining(buf, 1)?;
128        let byte = buf.get_u8();
129        value |= u32::from(byte & 0x7f) << (u32::from(i) * 7);
130        if byte & 0x80 == 0 {
131            return Ok(value);
132        }
133    }
134    Err(PrimitiveErrorKind::InvalidVarint {
135        max_bytes: VARINT_MAX_BYTES,
136    }
137    .into())
138}
139
140/// Write an unsigned varint (1–5 bytes, `u32` payload).
141pub fn write_unsigned_varint(buf: &mut BytesMut, mut value: u32) {
142    loop {
143        let low = value.to_le_bytes()[0] & 0x7f;
144        value >>= 7;
145        if value == 0 {
146            buf.put_u8(low);
147            return;
148        }
149        buf.put_u8(low | 0x80);
150    }
151}
152
153/// Encoded length of an unsigned varint (1–5 bytes, `u32` payload).
154#[must_use]
155pub const fn unsigned_varint_len(value: u32) -> usize {
156    match value {
157        0x0..=0x7f => 1,
158        0x80..=0x3fff => 2,
159        0x4000..=0x1f_ffff => 3,
160        0x20_0000..=0xfff_ffff => 4,
161        0x1000_0000..=u32::MAX => 5,
162    }
163}
164
165/// Encoded length of an unsigned varlong (1–10 bytes, `u64` payload).
166#[must_use]
167pub const fn unsigned_varlong_len(value: u64) -> usize {
168    match value {
169        0x0..=0x7f => 1,
170        0x80..=0x3fff => 2,
171        0x4000..=0x1f_ffff => 3,
172        0x20_0000..=0xfff_ffff => 4,
173        0x1000_0000..=0x7_ffff_ffff => 5,
174        0x8_0000_0000..=0x3ff_ffff_ffff => 6,
175        0x400_0000_0000..=0x1_ffff_ffff_ffff => 7,
176        0x2_0000_0000_0000..=0xff_ffff_ffff_ffff => 8,
177        0x100_0000_0000_0000..=0x7fff_ffff_ffff_ffff => 9,
178        0x8000_0000_0000_0000..=u64::MAX => 10,
179    }
180}
181
182/// Read an unsigned varlong (1–10 bytes, `u64` payload).
183pub fn read_unsigned_varlong(buf: &mut Bytes) -> Result<u64> {
184    let mut value: u64 = 0;
185    for i in 0..VARLONG_MAX_BYTES {
186        check_remaining(buf, 1)?;
187        let byte = buf.get_u8();
188        value |= u64::from(byte & 0x7f) << (u32::from(i) * 7);
189        if byte & 0x80 == 0 {
190            return Ok(value);
191        }
192    }
193    Err(PrimitiveErrorKind::InvalidVarint {
194        max_bytes: VARLONG_MAX_BYTES,
195    }
196    .into())
197}
198
199/// Write an unsigned varlong (1–10 bytes, `u64` payload).
200pub fn write_unsigned_varlong(buf: &mut BytesMut, mut value: u64) {
201    loop {
202        let low = value.to_le_bytes()[0] & 0x7f;
203        value >>= 7;
204        if value == 0 {
205            buf.put_u8(low);
206            return;
207        }
208        buf.put_u8(low | 0x80);
209    }
210}
211
212/// Read a signed varint (zigzag-decoded `i32`).
213pub fn read_signed_varint(buf: &mut Bytes) -> Result<i32> {
214    let v = read_unsigned_varint(buf)?;
215    let magnitude = (v >> 1).cast_signed();
216    let sign_mask = (v & 1).cast_signed().wrapping_neg();
217    Ok(magnitude ^ sign_mask)
218}
219
220/// Write a signed varint (zigzag-encoded `i32`).
221pub fn write_signed_varint(buf: &mut BytesMut, value: i32) {
222    let encoded = ((value << 1) ^ (value >> 31)).cast_unsigned();
223    write_unsigned_varint(buf, encoded);
224}
225
226/// Encoded length of a signed varint (zigzag-encoded `i32`).
227#[must_use]
228pub const fn signed_varint_len(value: i32) -> usize {
229    let encoded = ((value << 1) ^ (value >> 31)).cast_unsigned();
230    unsigned_varint_len(encoded)
231}
232
233/// Read a signed varlong (zigzag-decoded `i64`).
234pub fn read_signed_varlong(buf: &mut Bytes) -> Result<i64> {
235    let v = read_unsigned_varlong(buf)?;
236    let magnitude = (v >> 1).cast_signed();
237    let sign_mask = (v & 1).cast_signed().wrapping_neg();
238    Ok(magnitude ^ sign_mask)
239}
240
241/// Write a signed varlong (zigzag-encoded `i64`).
242pub fn write_signed_varlong(buf: &mut BytesMut, value: i64) {
243    let encoded = ((value << 1) ^ (value >> 63)).cast_unsigned();
244    write_unsigned_varlong(buf, encoded);
245}
246
247/// Encoded length of a signed varlong (zigzag-encoded `i64`).
248#[must_use]
249pub const fn signed_varlong_len(value: i64) -> usize {
250    let encoded = ((value << 1) ^ (value >> 63)).cast_unsigned();
251    unsigned_varlong_len(encoded)
252}
253
254// ---------------------------------------------------------------------------
255// Array length helpers (used by both fixed-width and compact array encoding).
256// ---------------------------------------------------------------------------
257
258/// Read a non-flexible array length (`i32`). `-1` indicates a null array.
259pub fn read_array_length(buf: &mut Bytes) -> Result<i32> {
260    read_i32(buf)
261}
262
263/// Write a non-flexible array length (`i32`).
264pub fn write_array_length(buf: &mut BytesMut, len: i32) {
265    write_i32(buf, len);
266}
267
268/// Encoded length of a non-flexible array length prefix.
269#[must_use]
270pub const fn array_length_len() -> usize {
271    4
272}
273
274/// Read a compact array length (varint of `len + 1`, `0 → -1` (null)).
275pub fn read_compact_array_length(buf: &mut Bytes) -> Result<i32> {
276    let raw = read_unsigned_varint(buf)?;
277    if raw == 0 {
278        Ok(-1)
279    } else {
280        let len = raw
281            .checked_sub(1)
282            .ok_or(PrimitiveErrorKind::InvalidVarint {
283                max_bytes: VARINT_MAX_BYTES,
284            })?;
285        i32::try_from(len).map_err(|_| PrimitiveErrorKind::LengthOutOfRange { length: len }.into())
286    }
287}
288
289/// Write a compact array length (varint of `len + 1`, negative → `0` (null)).
290pub fn write_compact_array_length(buf: &mut BytesMut, len: i32) {
291    if len < 0 {
292        write_unsigned_varint(buf, 0);
293    } else {
294        let encoded = len.cast_unsigned().saturating_add(1);
295        write_unsigned_varint(buf, encoded);
296    }
297}
298
299/// Encoded length of a compact array length prefix.
300#[must_use]
301pub const fn compact_array_length_len(len: i32) -> usize {
302    if len < 0 {
303        unsigned_varint_len(0)
304    } else {
305        unsigned_varint_len(len.cast_unsigned().saturating_add(1))
306    }
307}
308
309/// Initial `Vec` capacity for a decoded array.
310///
311/// The claimed element count comes off the wire, so it must never be trusted
312/// for allocation — a hostile or corrupt length of `i32::MAX` would otherwise
313/// reserve gigabytes up front and abort the process under `panic = "abort"`.
314/// The claim is clamped by the bytes actually remaining in the buffer (every
315/// element costs at least one wire byte) and a fixed budget; arrays longer
316/// than the budget grow on demand as elements are actually decoded.
317#[must_use]
318pub fn array_read_capacity(len: i32, remaining: usize) -> usize {
319    /// Elements worth of `Vec` capacity we are willing to reserve up front.
320    const MAX_PREALLOC_ELEMENTS: usize = 1024;
321    usize::try_from(len)
322        .unwrap_or(0)
323        .min(remaining)
324        .min(MAX_PREALLOC_ELEMENTS)
325}
326
327#[cfg(test)]
328mod tests {
329    #![allow(
330        clippy::missing_assert_message,
331        reason = "Primitive helper tests keep assertions compact."
332    )]
333
334    use bytes::BytesMut;
335
336    use super::{
337        array_read_capacity, signed_varint_len, signed_varlong_len, write_signed_varint,
338        write_signed_varlong,
339    };
340
341    #[test]
342    fn array_read_capacity_never_trusts_the_claimed_length() {
343        // A hostile length is clamped by the bytes actually remaining.
344        assert_eq!(array_read_capacity(i32::MAX, 7), 7);
345        // Negative (null-array sentinel leaking through) reserves nothing.
346        assert_eq!(array_read_capacity(-1, 100), 0);
347        // A sane length within the budget is used as-is.
348        assert_eq!(array_read_capacity(3, 100), 3);
349        // Huge-but-plausible lengths stop at the fixed budget.
350        assert_eq!(array_read_capacity(1_000_000, usize::MAX), 1024);
351    }
352
353    #[test]
354    fn signed_varint_len_matches_written_bytes() {
355        for value in [0, -1, 1, 63, 64, -64, i32::MAX, i32::MIN] {
356            let mut bytes = BytesMut::new();
357            write_signed_varint(&mut bytes, value);
358
359            assert_eq!(signed_varint_len(value), bytes.len());
360        }
361    }
362
363    #[test]
364    fn signed_varlong_len_matches_written_bytes() {
365        for value in [0, -1, 1, 63, 64, -64, i64::MAX, i64::MIN] {
366            let mut bytes = BytesMut::new();
367            write_signed_varlong(&mut bytes, value);
368
369            assert_eq!(signed_varlong_len(value), bytes.len());
370        }
371    }
372}