Skip to main content

crabka_protocol/primitives/
string_bytes_borrowed.rs

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