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 bytes::BytesMut;
102
103    // --- non-nullable, non-flexible -----------------------------------------
104
105    #[test]
106    fn non_flex_empty_array_roundtrip() {
107        let mut buf = BytesMut::new();
108        put_array_len(&mut buf, 0, false);
109        assert_eq!(buf.len(), 4, "prefix must be 4 bytes");
110        let mut cur = &buf[..];
111        assert_eq!(get_array_len(&mut cur, false).unwrap(), 0);
112        assert!(cur.is_empty());
113    }
114
115    #[test]
116    fn non_flex_three_element_array_roundtrip() {
117        let mut buf = BytesMut::new();
118        put_array_len(&mut buf, 3, false);
119        assert_eq!(buf.len(), 4);
120        let mut cur = &buf[..];
121        assert_eq!(get_array_len(&mut cur, false).unwrap(), 3);
122        assert!(cur.is_empty());
123    }
124
125    // --- non-nullable, flexible (compact) -----------------------------------
126
127    #[test]
128    fn flex_empty_array_roundtrip() {
129        let mut buf = BytesMut::new();
130        put_array_len(&mut buf, 0, true);
131        // len=0 → encode 1 → single byte 0x01
132        assert_eq!(&buf[..], &[0x01]);
133        let mut cur = &buf[..];
134        assert_eq!(get_array_len(&mut cur, true).unwrap(), 0);
135        assert!(cur.is_empty());
136    }
137
138    #[test]
139    fn flex_three_element_array_roundtrip() {
140        let mut buf = BytesMut::new();
141        put_array_len(&mut buf, 3, true);
142        // len=3 → encode 4 → single byte 0x04
143        assert_eq!(&buf[..], &[0x04]);
144        let mut cur = &buf[..];
145        assert_eq!(get_array_len(&mut cur, true).unwrap(), 3);
146        assert!(cur.is_empty());
147    }
148
149    // --- nullable, non-flexible ---------------------------------------------
150
151    #[test]
152    fn non_flex_nullable_null_roundtrip() {
153        let mut buf = BytesMut::new();
154        put_nullable_array_len(&mut buf, None, false);
155        assert_eq!(&buf[..], &[0xFF, 0xFF, 0xFF, 0xFF]); // -1 in big-endian
156        let mut cur = &buf[..];
157        assert_eq!(get_nullable_array_len(&mut cur, false).unwrap(), None);
158        assert!(cur.is_empty());
159    }
160
161    #[test]
162    fn non_flex_nullable_some_roundtrip() {
163        let mut buf = BytesMut::new();
164        put_nullable_array_len(&mut buf, Some(3), false);
165        let mut cur = &buf[..];
166        assert_eq!(get_nullable_array_len(&mut cur, false).unwrap(), Some(3));
167        assert!(cur.is_empty());
168    }
169
170    // --- nullable, flexible -------------------------------------------------
171
172    #[test]
173    fn flex_nullable_null_roundtrip() {
174        let mut buf = BytesMut::new();
175        put_nullable_array_len(&mut buf, None, true);
176        assert_eq!(&buf[..], &[0x00]); // 0 = null in compact encoding
177        let mut cur = &buf[..];
178        assert_eq!(get_nullable_array_len(&mut cur, true).unwrap(), None);
179        assert!(cur.is_empty());
180    }
181
182    #[test]
183    fn flex_nullable_some_roundtrip() {
184        let mut buf = BytesMut::new();
185        put_nullable_array_len(&mut buf, Some(3), true);
186        // Some(3) → encode 4 → 0x04
187        assert_eq!(&buf[..], &[0x04]);
188        let mut cur = &buf[..];
189        assert_eq!(get_nullable_array_len(&mut cur, true).unwrap(), Some(3));
190        assert!(cur.is_empty());
191    }
192
193    // --- prefix_len helpers -------------------------------------------------
194
195    #[test]
196    fn array_len_prefix_len_non_flex() {
197        assert_eq!(array_len_prefix_len(0, false), 4);
198        assert_eq!(array_len_prefix_len(100, false), 4);
199    }
200
201    #[test]
202    fn array_len_prefix_len_flex() {
203        // len=0 → varint(1) = 1 byte; len=126 → varint(127) = 1 byte;
204        // len=127 → varint(128) = 2 bytes.
205        assert_eq!(array_len_prefix_len(0, true), 1);
206        assert_eq!(array_len_prefix_len(126, true), 1);
207        assert_eq!(array_len_prefix_len(127, true), 2);
208    }
209
210    #[test]
211    fn nullable_prefix_len_non_flex_always_4() {
212        assert_eq!(nullable_array_len_prefix_len(None, false), 4);
213        assert_eq!(nullable_array_len_prefix_len(Some(3), false), 4);
214    }
215
216    #[test]
217    fn nullable_prefix_len_flex_null_is_1() {
218        // null → varint(0) = 1 byte
219        assert_eq!(nullable_array_len_prefix_len(None, true), 1);
220    }
221
222    // --- error cases --------------------------------------------------------
223
224    #[test]
225    fn non_nullable_rejects_null_non_flex() {
226        let bytes = (-1i32).to_be_bytes();
227        let mut cur = &bytes[..];
228        assert!(matches!(
229            get_array_len(&mut cur, false),
230            Err(ProtocolError::InvalidValue(_))
231        ));
232    }
233
234    #[test]
235    fn non_nullable_rejects_null_flex() {
236        // varint 0 = null in compact encoding
237        let bytes = [0x00u8];
238        let mut cur = &bytes[..];
239        assert!(matches!(
240            get_array_len(&mut cur, true),
241            Err(ProtocolError::InvalidValue(_))
242        ));
243    }
244}