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