Skip to main content

krabka_protocol/primitives/
string_bytes_borrowed.rs

1use crate::{
2    ProtocolError,
3    primitives::{
4        fixed::{get_i16, get_i32},
5        varint::get_uvarint,
6    },
7};
8
9/// Decode a non-flexible `STRING` that borrows from the input buffer.
10///
11/// The wire format is an INT16 length of 0 or more, then `length` UTF-8 bytes.
12/// A length of −1 means null, which is an error here.
13///
14/// # Errors
15///
16/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
17/// # Panics
18/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
19pub fn get_string_borrowed<'de>(buf: &mut &'de [u8]) -> Result<&'de str, ProtocolError> {
20    let len = get_i16(buf)?;
21    if len < 0 {
22        return Err(ProtocolError::InvalidValue("non-nullable STRING was null"));
23    }
24    let n = usize::try_from(len).expect("non-negative string length must fit in usize");
25    if buf.len() < n {
26        return Err(ProtocolError::UnexpectedEof {
27            needed: n - buf.len(),
28        });
29    }
30    let (head, tail) = buf.split_at(n);
31    *buf = tail;
32    std::str::from_utf8(head).map_err(ProtocolError::InvalidUtf8)
33}
34
35/// Decode a nullable, non-flexible `STRING` that borrows from the input buffer.
36///
37/// # Errors
38///
39/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
40/// # Panics
41/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
42pub fn get_nullable_string_borrowed<'de>(
43    buf: &mut &'de [u8],
44) -> Result<Option<&'de str>, ProtocolError> {
45    let len = get_i16(buf)?;
46    if len < 0 {
47        return Ok(None);
48    }
49    let n = usize::try_from(len).expect("non-negative string length must fit in usize");
50    if buf.len() < n {
51        return Err(ProtocolError::UnexpectedEof {
52            needed: n - buf.len(),
53        });
54    }
55    let (head, tail) = buf.split_at(n);
56    *buf = tail;
57    Ok(Some(
58        std::str::from_utf8(head).map_err(ProtocolError::InvalidUtf8)?,
59    ))
60}
61
62/// Decode a `COMPACT_STRING` that borrows from the input buffer.
63///
64/// This function needs a contiguous buffer, that is, a `&[u8]`.
65///
66/// # Errors
67///
68/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
69pub fn get_compact_string_borrowed<'de>(buf: &mut &'de [u8]) -> Result<&'de str, ProtocolError> {
70    let raw = get_uvarint(buf)?;
71    if raw == 0 {
72        return Err(ProtocolError::InvalidValue(
73            "non-nullable COMPACT_STRING was null",
74        ));
75    }
76    let n = (raw - 1) as usize;
77    if buf.len() < n {
78        return Err(ProtocolError::UnexpectedEof {
79            needed: n - buf.len(),
80        });
81    }
82    let (head, tail) = buf.split_at(n);
83    *buf = tail;
84    std::str::from_utf8(head).map_err(ProtocolError::InvalidUtf8)
85}
86
87/// # Errors
88/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
89pub fn get_compact_nullable_string_borrowed<'de>(
90    buf: &mut &'de [u8],
91) -> Result<Option<&'de str>, ProtocolError> {
92    let raw = get_uvarint(buf)?;
93    if raw == 0 {
94        return Ok(None);
95    }
96    let n = (raw - 1) as usize;
97    if buf.len() < n {
98        return Err(ProtocolError::UnexpectedEof {
99            needed: n - buf.len(),
100        });
101    }
102    let (head, tail) = buf.split_at(n);
103    *buf = tail;
104    Ok(Some(
105        std::str::from_utf8(head).map_err(ProtocolError::InvalidUtf8)?,
106    ))
107}
108
109/// Decode non-flexible `BYTES` that borrow from the input buffer.
110///
111/// The wire format is an INT32 length, then `length` bytes. A length of −1 means
112/// null, which is an error here.
113///
114/// # Errors
115///
116/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
117/// # Panics
118/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
119pub fn get_bytes_borrowed<'de>(buf: &mut &'de [u8]) -> Result<&'de [u8], ProtocolError> {
120    let len = get_i32(buf)?;
121    if len < 0 {
122        return Err(ProtocolError::InvalidValue("non-nullable BYTES was null"));
123    }
124    let n = usize::try_from(len).expect("non-negative bytes length must fit in usize");
125    if buf.len() < n {
126        return Err(ProtocolError::UnexpectedEof {
127            needed: n - buf.len(),
128        });
129    }
130    let (head, tail) = buf.split_at(n);
131    *buf = tail;
132    Ok(head)
133}
134
135/// Decode nullable, non-flexible `BYTES` that borrow from the input buffer.
136///
137/// # Errors
138///
139/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
140/// # Panics
141/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
142pub fn get_nullable_bytes_borrowed<'de>(
143    buf: &mut &'de [u8],
144) -> Result<Option<&'de [u8]>, ProtocolError> {
145    let len = get_i32(buf)?;
146    if len < 0 {
147        return Ok(None);
148    }
149    let n = usize::try_from(len).expect("non-negative bytes length must fit in usize");
150    if buf.len() < n {
151        return Err(ProtocolError::UnexpectedEof {
152            needed: n - buf.len(),
153        });
154    }
155    let (head, tail) = buf.split_at(n);
156    *buf = tail;
157    Ok(Some(head))
158}
159
160/// Decode flexible `COMPACT_BYTES` that borrow from the input buffer.
161///
162/// # Errors
163///
164/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
165pub fn get_compact_bytes_borrowed<'de>(buf: &mut &'de [u8]) -> Result<&'de [u8], ProtocolError> {
166    let raw = get_uvarint(buf)?;
167    if raw == 0 {
168        return Err(ProtocolError::InvalidValue(
169            "non-nullable COMPACT_BYTES was null",
170        ));
171    }
172    let n = (raw - 1) as usize;
173    if buf.len() < n {
174        return Err(ProtocolError::UnexpectedEof {
175            needed: n - buf.len(),
176        });
177    }
178    let (head, tail) = buf.split_at(n);
179    *buf = tail;
180    Ok(head)
181}
182
183/// Decode nullable, flexible `COMPACT_BYTES` that borrow from the input buffer.
184///
185/// # Errors
186///
187/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
188pub fn get_compact_nullable_bytes_borrowed<'de>(
189    buf: &mut &'de [u8],
190) -> Result<Option<&'de [u8]>, ProtocolError> {
191    let raw = get_uvarint(buf)?;
192    if raw == 0 {
193        return Ok(None);
194    }
195    let n = (raw - 1) as usize;
196    if buf.len() < n {
197        return Err(ProtocolError::UnexpectedEof {
198            needed: n - buf.len(),
199        });
200    }
201    let (head, tail) = buf.split_at(n);
202    *buf = tail;
203    Ok(Some(head))
204}
205
206#[cfg(test)]
207mod tests {
208    use assert2::check;
209
210    use super::*;
211
212    #[test]
213    fn borrowed_decode_zero_copy() {
214        let bytes = [0x06u8, b'k', b'a', b'f', b'k', b'a'];
215        let mut cur: &[u8] = &bytes;
216        let s = get_compact_string_borrowed(&mut cur).unwrap();
217        assert2::assert!(s == "kafka");
218        // Pointer identity: `s` points inside `bytes`.
219        let bytes_ptr = bytes.as_ptr() as usize;
220        let s_ptr = s.as_ptr() as usize;
221        assert2::assert!(s_ptr >= bytes_ptr && s_ptr < bytes_ptr + bytes.len());
222    }
223
224    #[test]
225    fn get_string_borrowed_roundtrip() {
226        // INT16(5) + "kafka"
227        let bytes = [0x00u8, 0x05, b'k', b'a', b'f', b'k', b'a'];
228        let mut cur: &[u8] = &bytes;
229        let s = get_string_borrowed(&mut cur).unwrap();
230        check!(s == "kafka");
231        check!(cur.is_empty());
232        // zero-copy: pointer is inside bytes
233        check!(s.as_ptr() as usize >= bytes.as_ptr() as usize);
234    }
235
236    #[test]
237    fn get_string_borrowed_null_is_error() {
238        // INT16(-1)
239        let bytes = [0xFFu8, 0xFF];
240        let mut cur: &[u8] = &bytes;
241        assert2::assert!(matches!(
242            get_string_borrowed(&mut cur),
243            Err(ProtocolError::InvalidValue(_))
244        ));
245    }
246
247    #[test]
248    fn get_nullable_string_borrowed_null() {
249        let bytes = [0xFFu8, 0xFF];
250        let mut cur: &[u8] = &bytes;
251        assert2::assert!(get_nullable_string_borrowed(&mut cur).unwrap() == None);
252        assert2::assert!(cur.is_empty());
253    }
254
255    #[test]
256    fn get_nullable_string_borrowed_some() {
257        let bytes = [0x00u8, 0x03, b'f', b'o', b'o'];
258        let mut cur: &[u8] = &bytes;
259        assert2::assert!(get_nullable_string_borrowed(&mut cur).unwrap() == Some("foo"));
260        assert2::assert!(cur.is_empty());
261    }
262
263    #[test]
264    fn get_bytes_borrowed_roundtrip() {
265        // INT32(3) + [1,2,3]
266        let bytes = [0x00u8, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03];
267        let mut cur: &[u8] = &bytes;
268        let b = get_bytes_borrowed(&mut cur).unwrap();
269        assert2::assert!(b == &[1u8, 2, 3]);
270        assert2::assert!(cur.is_empty());
271    }
272
273    #[test]
274    fn get_bytes_borrowed_null_is_error() {
275        let bytes = [0xFFu8, 0xFF, 0xFF, 0xFF]; // -1 as INT32
276        let mut cur: &[u8] = &bytes;
277        assert2::assert!(matches!(
278            get_bytes_borrowed(&mut cur),
279            Err(ProtocolError::InvalidValue(_))
280        ));
281    }
282
283    #[test]
284    fn get_nullable_bytes_borrowed_null() {
285        let bytes = [0xFFu8, 0xFF, 0xFF, 0xFF];
286        let mut cur: &[u8] = &bytes;
287        assert2::assert!(get_nullable_bytes_borrowed(&mut cur).unwrap() == None);
288    }
289
290    #[test]
291    fn get_compact_bytes_borrowed_roundtrip() {
292        // UVARINT(4) = length+1=4 → 3 bytes; [0xAA, 0xBB, 0xCC]
293        let bytes = [0x04u8, 0xAA, 0xBB, 0xCC];
294        let mut cur: &[u8] = &bytes;
295        let b = get_compact_bytes_borrowed(&mut cur).unwrap();
296        assert2::assert!(b == &[0xAAu8, 0xBB, 0xCC]);
297        assert2::assert!(cur.is_empty());
298    }
299
300    #[test]
301    fn get_compact_bytes_borrowed_null_is_error() {
302        let bytes = [0x00u8]; // varint 0 = null
303        let mut cur: &[u8] = &bytes;
304        assert2::assert!(matches!(
305            get_compact_bytes_borrowed(&mut cur),
306            Err(ProtocolError::InvalidValue(_))
307        ));
308    }
309
310    #[test]
311    fn get_compact_nullable_bytes_borrowed_null() {
312        let bytes = [0x00u8];
313        let mut cur: &[u8] = &bytes;
314        assert2::assert!(get_compact_nullable_bytes_borrowed(&mut cur).unwrap() == None);
315    }
316
317    #[test]
318    fn get_compact_nullable_bytes_borrowed_some() {
319        // UVARINT(3) = length+1=3 → 2 bytes; [0x01, 0x02]
320        let bytes = [0x03u8, 0x01, 0x02];
321        let mut cur: &[u8] = &bytes;
322        assert2::assert!(
323            get_compact_nullable_bytes_borrowed(&mut cur).unwrap() == Some(&[0x01u8, 0x02][..])
324        );
325        assert2::assert!(cur.is_empty());
326    }
327}