Skip to main content

krabka_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, and −1
4//! means null. Flexible, compact arrays use an unsigned varint whose value is
5//! `len + 1`, and 0 means null.
6
7use bytes::{Buf, BufMut};
8
9use crate::{
10    ProtocolError,
11    primitives::{
12        fixed::{get_i32, put_i32},
13        varint::{get_uvarint, put_uvarint, uvarint_len},
14    },
15};
16
17/// Writes a non-nullable array-length prefix.
18/// # Panics
19/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
20pub fn put_array_len<B: BufMut>(buf: &mut B, n: usize, flexible: bool) {
21    if flexible {
22        put_uvarint(buf, u32::try_from(n + 1).expect("array too large"));
23    } else {
24        put_i32(buf, i32::try_from(n).expect("array too large"));
25    }
26}
27
28/// Writes a nullable array-length prefix. `None` encodes as −1 for
29/// non-flexible versions, or 0 for flexible versions.
30/// # Panics
31/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
32pub fn put_nullable_array_len<B: BufMut>(buf: &mut B, len: Option<usize>, flexible: bool) {
33    match (flexible, len) {
34        (false, None) => put_i32(buf, -1),
35        (false, Some(n)) => put_i32(buf, i32::try_from(n).expect("array too large")),
36        (true, None) => put_uvarint(buf, 0),
37        (true, Some(n)) => put_uvarint(buf, u32::try_from(n + 1).expect("array too large")),
38    }
39}
40
41/// Number of bytes consumed by a non-nullable array-length prefix.
42#[must_use]
43/// # Panics
44/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
45pub fn array_len_prefix_len(n: usize, flexible: bool) -> usize {
46    if flexible {
47        uvarint_len(u32::try_from(n + 1).unwrap())
48    } else {
49        4
50    }
51}
52
53/// Number of bytes consumed by a nullable array-length prefix.
54#[must_use]
55/// # Panics
56/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
57pub fn nullable_array_len_prefix_len(len: Option<usize>, flexible: bool) -> usize {
58    match (flexible, len) {
59        (false, _) => 4,
60        (true, None) => uvarint_len(0),
61        (true, Some(n)) => uvarint_len(u32::try_from(n + 1).unwrap()),
62    }
63}
64
65/// Reads a non-nullable array length. Returns an error if the encoded value is
66/// null, which is −1 or 0.
67/// # Errors
68/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
69/// # Panics
70/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
71pub fn get_array_len<B: Buf>(buf: &mut B, flexible: bool) -> Result<usize, ProtocolError> {
72    let n = if flexible {
73        let raw = get_uvarint(buf)?;
74        if raw == 0 {
75            return Err(ProtocolError::InvalidValue(
76                "non-nullable array was null (compact encoding)",
77            ));
78        }
79        (raw - 1) as usize
80    } else {
81        let n = get_i32(buf)?;
82        if n < 0 {
83            return Err(ProtocolError::InvalidValue(
84                "non-nullable array had negative length",
85            ));
86        }
87        usize::try_from(n).expect("n is non-negative")
88    };
89    // Every array element occupies at least one byte on the wire, so a
90    // legitimate array of `n` elements always has at least `n` bytes left in
91    // the buffer. Any larger `n` is impossible and indicates a malformed or
92    // hostile frame; reject before a caller can `Vec::with_capacity(n)`.
93    if n > buf.remaining() {
94        return Err(ProtocolError::InvalidValue(
95            "array length exceeds remaining buffer",
96        ));
97    }
98    Ok(n)
99}
100
101/// Reads a nullable array length. Returns `None` when the encoded value is
102/// null, which is −1 for non-flexible versions and 0 for flexible versions.
103/// # Errors
104/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
105/// # Panics
106/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
107pub fn get_nullable_array_len<B: Buf>(
108    buf: &mut B,
109    flexible: bool,
110) -> Result<Option<usize>, ProtocolError> {
111    let n = if flexible {
112        let raw = get_uvarint(buf)?;
113        if raw == 0 {
114            return Ok(None);
115        }
116        (raw - 1) as usize
117    } else {
118        let n = get_i32(buf)?;
119        if n < 0 {
120            return Ok(None);
121        }
122        usize::try_from(n).expect("n is non-negative")
123    };
124    // See `get_array_len`: a length larger than the remaining bytes cannot
125    // describe a real array and must be rejected before pre-allocation.
126    if n > buf.remaining() {
127        return Err(ProtocolError::InvalidValue(
128            "array length exceeds remaining buffer",
129        ));
130    }
131    Ok(Some(n))
132}
133
134#[cfg(test)]
135mod tests {
136
137    use bytes::BytesMut;
138
139    use super::*;
140
141    #[test]
142    fn array_roundtrip_cases() {
143        for (_case, len, flexible, expected_prefix) in [
144            ("non-flex empty", 0, false, &[0, 0, 0, 0][..]),
145            ("non-flex three", 3, false, &[0, 0, 0, 3][..]),
146            ("flex empty", 0, true, &[0x01][..]),
147            ("flex three", 3, true, &[0x04][..]),
148        ] {
149            let mut buf = BytesMut::new();
150            put_array_len(&mut buf, len, flexible);
151            assert2::assert!(&buf[..] == expected_prefix);
152            buf.extend_from_slice(&vec![0; len]);
153            let mut cur = &buf[..];
154            assert2::assert!((get_array_len(&mut cur, flexible).unwrap(), cur.len()) == (len, len));
155        }
156    }
157
158    #[test]
159    fn nullable_array_roundtrip_cases() {
160        for (_case, len, flexible, expected_prefix) in [
161            ("non-flex null", None, false, &[0xFF, 0xFF, 0xFF, 0xFF][..]),
162            ("non-flex some", Some(3), false, &[0, 0, 0, 3][..]),
163            ("flex null", None, true, &[0x00][..]),
164            ("flex some", Some(3), true, &[0x04][..]),
165        ] {
166            let mut buf = BytesMut::new();
167            put_nullable_array_len(&mut buf, len, flexible);
168            assert2::assert!(&buf[..] == expected_prefix);
169            let payload_len = len.unwrap_or(0);
170            buf.extend_from_slice(&vec![0; payload_len]);
171            let mut cur = &buf[..];
172            assert2::assert!(
173                (
174                    get_nullable_array_len(&mut cur, flexible).unwrap(),
175                    cur.len()
176                ) == (len, payload_len)
177            );
178        }
179    }
180
181    // --- prefix_len helpers -------------------------------------------------
182
183    #[test]
184    fn array_len_prefix_len_non_flex() {
185        for (_case, len) in [("empty", 0), ("populated", 100)] {
186            assert2::assert!(array_len_prefix_len(len, false) == 4);
187        }
188    }
189
190    #[test]
191    fn array_len_prefix_len_flex() {
192        // len=0 → varint(1) = 1 byte; len=126 → varint(127) = 1 byte;
193        // len=127 → varint(128) = 2 bytes.
194        for (len, want) in [(0, 1), (126, 1), (127, 2)] {
195            assert2::assert!(array_len_prefix_len(len, true) == want);
196        }
197    }
198
199    #[test]
200    fn nullable_prefix_len_non_flex_always_4() {
201        for (_case, len) in [("null", None), ("some", Some(3))] {
202            assert2::assert!(nullable_array_len_prefix_len(len, false) == 4);
203        }
204    }
205
206    #[test]
207    fn nullable_prefix_len_flex_null_is_1() {
208        // null → varint(0) = 1 byte
209        assert2::assert!(nullable_array_len_prefix_len(None, true) == 1);
210    }
211
212    // --- error cases --------------------------------------------------------
213
214    #[test]
215    fn non_nullable_rejects_null_cases() {
216        for (_case, bytes, flexible) in [
217            ("non-flex", &(-1i32).to_be_bytes()[..], false),
218            ("flex", &[0x00][..], true),
219        ] {
220            let mut cur = bytes;
221            assert2::assert!(matches!(
222                get_array_len(&mut cur, flexible),
223                Err(ProtocolError::InvalidValue(_))
224            ));
225        }
226    }
227
228    // --- pre-allocation DoS bound (length > remaining buffer) ----------------
229
230    #[test]
231    fn rejects_length_exceeding_remaining_cases() {
232        for (_case, nullable, flexible) in [
233            ("non-nullable non-flex", false, false),
234            ("non-nullable flex", false, true),
235            ("nullable non-flex", true, false),
236            ("nullable flex", true, true),
237        ] {
238            let mut buf = BytesMut::new();
239            if nullable {
240                put_nullable_array_len(&mut buf, Some(2_000_000_000), flexible);
241            } else {
242                put_array_len(&mut buf, 2_000_000_000, flexible);
243            }
244            let mut cur = &buf[..];
245            let result = if nullable {
246                get_nullable_array_len(&mut cur, flexible).map(|value| value.unwrap_or(0))
247            } else {
248                get_array_len(&mut cur, flexible)
249            };
250            assert2::assert!(matches!(
251                result,
252                Err(ProtocolError::InvalidValue(
253                    "array length exceeds remaining buffer"
254                ))
255            ));
256        }
257    }
258
259    #[test]
260    fn length_equal_to_remaining_is_accepted() {
261        for (_case, flexible) in [("non-flex", false), ("flex", true)] {
262            let mut buf = BytesMut::new();
263            put_array_len(&mut buf, 5, flexible);
264            buf.extend_from_slice(&[0u8; 5]);
265            let mut cur = &buf[..];
266            assert2::assert!(get_array_len(&mut cur, flexible).unwrap() == 5);
267        }
268    }
269}