Skip to main content

crabka_protocol/primitives/
array.rs

1//! Wire-level helpers for `[]<elem>` and nullable `[]<elem>` arrays.
2//!
3//! Non-flexible arrays use a 4-byte big-endian `INT32` length prefix (−1 for
4//! null).  Flexible (compact) arrays use an unsigned varint whose value is
5//! `len + 1` (0 means null).
6
7use bytes::{Buf, BufMut};
8
9use crate::ProtocolError;
10use crate::primitives::fixed::{get_i32, put_i32};
11use crate::primitives::varint::{get_uvarint, put_uvarint, uvarint_len};
12
13/// Write a non-nullable array-length prefix.
14pub fn put_array_len<B: BufMut>(buf: &mut B, n: usize, flexible: bool) {
15    if flexible {
16        put_uvarint(buf, u32::try_from(n + 1).expect("array too large"));
17    } else {
18        put_i32(buf, i32::try_from(n).expect("array too large"));
19    }
20}
21
22/// Write a nullable array-length prefix.  `None` encodes as −1 (non-flex) or
23/// 0 (flex).
24pub fn put_nullable_array_len<B: BufMut>(buf: &mut B, len: Option<usize>, flexible: bool) {
25    match (flexible, len) {
26        (false, None) => put_i32(buf, -1),
27        (false, Some(n)) => put_i32(buf, i32::try_from(n).expect("array too large")),
28        (true, None) => put_uvarint(buf, 0),
29        (true, Some(n)) => put_uvarint(buf, u32::try_from(n + 1).expect("array too large")),
30    }
31}
32
33/// Number of bytes consumed by a non-nullable array-length prefix.
34#[must_use]
35pub fn array_len_prefix_len(n: usize, flexible: bool) -> usize {
36    if flexible {
37        uvarint_len(u32::try_from(n + 1).unwrap())
38    } else {
39        4
40    }
41}
42
43/// Number of bytes consumed by a nullable array-length prefix.
44#[must_use]
45pub fn nullable_array_len_prefix_len(len: Option<usize>, flexible: bool) -> usize {
46    match (flexible, len) {
47        (false, _) => 4,
48        (true, None) => uvarint_len(0),
49        (true, Some(n)) => uvarint_len(u32::try_from(n + 1).unwrap()),
50    }
51}
52
53/// Read a non-nullable array length.  Returns an error if the encoded value is
54/// null (−1 / 0).
55pub fn get_array_len<B: Buf>(buf: &mut B, flexible: bool) -> Result<usize, ProtocolError> {
56    if flexible {
57        let raw = get_uvarint(buf)?;
58        if raw == 0 {
59            return Err(ProtocolError::InvalidValue(
60                "non-nullable array was null (compact encoding)",
61            ));
62        }
63        Ok((raw - 1) as usize)
64    } else {
65        let n = get_i32(buf)?;
66        if n < 0 {
67            return Err(ProtocolError::InvalidValue(
68                "non-nullable array had negative length",
69            ));
70        }
71        Ok(usize::try_from(n).expect("n is non-negative"))
72    }
73}
74
75/// Read a nullable array length.  Returns `None` when the encoded value is
76/// null (−1 non-flex, 0 flex).
77pub fn get_nullable_array_len<B: Buf>(
78    buf: &mut B,
79    flexible: bool,
80) -> Result<Option<usize>, ProtocolError> {
81    if flexible {
82        let raw = get_uvarint(buf)?;
83        if raw == 0 {
84            Ok(None)
85        } else {
86            Ok(Some((raw - 1) as usize))
87        }
88    } else {
89        let n = get_i32(buf)?;
90        if n < 0 {
91            Ok(None)
92        } else {
93            Ok(Some(usize::try_from(n).expect("n is non-negative")))
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use assert2::assert;
102    use bytes::BytesMut;
103
104    // --- non-nullable, non-flexible -----------------------------------------
105
106    #[test]
107    fn non_flex_empty_array_roundtrip() {
108        let mut buf = BytesMut::new();
109        put_array_len(&mut buf, 0, false);
110        assert!(buf.len() == 4, "prefix must be 4 bytes");
111        let mut cur = &buf[..];
112        assert!(get_array_len(&mut cur, false).unwrap() == 0);
113        assert!(cur.is_empty());
114    }
115
116    #[test]
117    fn non_flex_three_element_array_roundtrip() {
118        let mut buf = BytesMut::new();
119        put_array_len(&mut buf, 3, false);
120        assert!(buf.len() == 4);
121        let mut cur = &buf[..];
122        assert!(get_array_len(&mut cur, false).unwrap() == 3);
123        assert!(cur.is_empty());
124    }
125
126    // --- non-nullable, flexible (compact) -----------------------------------
127
128    #[test]
129    fn flex_empty_array_roundtrip() {
130        let mut buf = BytesMut::new();
131        put_array_len(&mut buf, 0, true);
132        // len=0 → encode 1 → single byte 0x01
133        assert!(&buf[..] == &[0x01]);
134        let mut cur = &buf[..];
135        assert!(get_array_len(&mut cur, true).unwrap() == 0);
136        assert!(cur.is_empty());
137    }
138
139    #[test]
140    fn flex_three_element_array_roundtrip() {
141        let mut buf = BytesMut::new();
142        put_array_len(&mut buf, 3, true);
143        // len=3 → encode 4 → single byte 0x04
144        assert!(&buf[..] == &[0x04]);
145        let mut cur = &buf[..];
146        assert!(get_array_len(&mut cur, true).unwrap() == 3);
147        assert!(cur.is_empty());
148    }
149
150    // --- nullable, non-flexible ---------------------------------------------
151
152    #[test]
153    fn non_flex_nullable_null_roundtrip() {
154        let mut buf = BytesMut::new();
155        put_nullable_array_len(&mut buf, None, false);
156        assert!(&buf[..] == &[0xFF, 0xFF, 0xFF, 0xFF]); // -1 in big-endian
157        let mut cur = &buf[..];
158        assert!(get_nullable_array_len(&mut cur, false).unwrap() == None);
159        assert!(cur.is_empty());
160    }
161
162    #[test]
163    fn non_flex_nullable_some_roundtrip() {
164        let mut buf = BytesMut::new();
165        put_nullable_array_len(&mut buf, Some(3), false);
166        let mut cur = &buf[..];
167        assert!(get_nullable_array_len(&mut cur, false).unwrap() == Some(3));
168        assert!(cur.is_empty());
169    }
170
171    // --- nullable, flexible -------------------------------------------------
172
173    #[test]
174    fn flex_nullable_null_roundtrip() {
175        let mut buf = BytesMut::new();
176        put_nullable_array_len(&mut buf, None, true);
177        assert!(&buf[..] == &[0x00]); // 0 = null in compact encoding
178        let mut cur = &buf[..];
179        assert!(get_nullable_array_len(&mut cur, true).unwrap() == None);
180        assert!(cur.is_empty());
181    }
182
183    #[test]
184    fn flex_nullable_some_roundtrip() {
185        let mut buf = BytesMut::new();
186        put_nullable_array_len(&mut buf, Some(3), true);
187        // Some(3) → encode 4 → 0x04
188        assert!(&buf[..] == &[0x04]);
189        let mut cur = &buf[..];
190        assert!(get_nullable_array_len(&mut cur, true).unwrap() == Some(3));
191        assert!(cur.is_empty());
192    }
193
194    // --- prefix_len helpers -------------------------------------------------
195
196    #[test]
197    fn array_len_prefix_len_non_flex() {
198        assert!(array_len_prefix_len(0, false) == 4);
199        assert!(array_len_prefix_len(100, false) == 4);
200    }
201
202    #[test]
203    fn array_len_prefix_len_flex() {
204        // len=0 → varint(1) = 1 byte; len=126 → varint(127) = 1 byte;
205        // len=127 → varint(128) = 2 bytes.
206        assert!(array_len_prefix_len(0, true) == 1);
207        assert!(array_len_prefix_len(126, true) == 1);
208        assert!(array_len_prefix_len(127, true) == 2);
209    }
210
211    #[test]
212    fn nullable_prefix_len_non_flex_always_4() {
213        assert!(nullable_array_len_prefix_len(None, false) == 4);
214        assert!(nullable_array_len_prefix_len(Some(3), false) == 4);
215    }
216
217    #[test]
218    fn nullable_prefix_len_flex_null_is_1() {
219        // null → varint(0) = 1 byte
220        assert!(nullable_array_len_prefix_len(None, true) == 1);
221    }
222
223    // --- error cases --------------------------------------------------------
224
225    #[test]
226    fn non_nullable_rejects_null_non_flex() {
227        let bytes = (-1i32).to_be_bytes();
228        let mut cur = &bytes[..];
229        assert!(matches!(
230            get_array_len(&mut cur, false),
231            Err(ProtocolError::InvalidValue(_))
232        ));
233    }
234
235    #[test]
236    fn non_nullable_rejects_null_flex() {
237        // varint 0 = null in compact encoding
238        let bytes = [0x00u8];
239        let mut cur = &bytes[..];
240        assert!(matches!(
241            get_array_len(&mut cur, true),
242            Err(ProtocolError::InvalidValue(_))
243        ));
244    }
245}