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
167    #[test]
168    fn borrowed_decode_zero_copy() {
169        let bytes = [0x06u8, b'k', b'a', b'f', b'k', b'a'];
170        let mut cur: &[u8] = &bytes;
171        let s = get_compact_string_borrowed(&mut cur).unwrap();
172        assert_eq!(s, "kafka");
173        // Pointer identity: `s` points inside `bytes`.
174        let bytes_ptr = bytes.as_ptr() as usize;
175        let s_ptr = s.as_ptr() as usize;
176        assert!(s_ptr >= bytes_ptr && s_ptr < bytes_ptr + bytes.len());
177    }
178
179    #[test]
180    fn get_string_borrowed_roundtrip() {
181        // INT16(5) + "kafka"
182        let bytes = [0x00u8, 0x05, b'k', b'a', b'f', b'k', b'a'];
183        let mut cur: &[u8] = &bytes;
184        let s = get_string_borrowed(&mut cur).unwrap();
185        assert_eq!(s, "kafka");
186        assert!(cur.is_empty());
187        // zero-copy: pointer is inside bytes
188        assert!(s.as_ptr() as usize >= bytes.as_ptr() as usize);
189    }
190
191    #[test]
192    fn get_string_borrowed_null_is_error() {
193        // INT16(-1)
194        let bytes = [0xFFu8, 0xFF];
195        let mut cur: &[u8] = &bytes;
196        assert!(matches!(
197            get_string_borrowed(&mut cur),
198            Err(ProtocolError::InvalidValue(_))
199        ));
200    }
201
202    #[test]
203    fn get_nullable_string_borrowed_null() {
204        let bytes = [0xFFu8, 0xFF];
205        let mut cur: &[u8] = &bytes;
206        assert_eq!(get_nullable_string_borrowed(&mut cur).unwrap(), None);
207        assert!(cur.is_empty());
208    }
209
210    #[test]
211    fn get_nullable_string_borrowed_some() {
212        let bytes = [0x00u8, 0x03, b'f', b'o', b'o'];
213        let mut cur: &[u8] = &bytes;
214        assert_eq!(get_nullable_string_borrowed(&mut cur).unwrap(), Some("foo"));
215        assert!(cur.is_empty());
216    }
217
218    #[test]
219    fn get_bytes_borrowed_roundtrip() {
220        // INT32(3) + [1,2,3]
221        let bytes = [0x00u8, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03];
222        let mut cur: &[u8] = &bytes;
223        let b = get_bytes_borrowed(&mut cur).unwrap();
224        assert_eq!(b, &[1u8, 2, 3]);
225        assert!(cur.is_empty());
226    }
227
228    #[test]
229    fn get_bytes_borrowed_null_is_error() {
230        let bytes = [0xFFu8, 0xFF, 0xFF, 0xFF]; // -1 as INT32
231        let mut cur: &[u8] = &bytes;
232        assert!(matches!(
233            get_bytes_borrowed(&mut cur),
234            Err(ProtocolError::InvalidValue(_))
235        ));
236    }
237
238    #[test]
239    fn get_nullable_bytes_borrowed_null() {
240        let bytes = [0xFFu8, 0xFF, 0xFF, 0xFF];
241        let mut cur: &[u8] = &bytes;
242        assert_eq!(get_nullable_bytes_borrowed(&mut cur).unwrap(), None);
243    }
244
245    #[test]
246    fn get_compact_bytes_borrowed_roundtrip() {
247        // UVARINT(4) = length+1=4 → 3 bytes; [0xAA, 0xBB, 0xCC]
248        let bytes = [0x04u8, 0xAA, 0xBB, 0xCC];
249        let mut cur: &[u8] = &bytes;
250        let b = get_compact_bytes_borrowed(&mut cur).unwrap();
251        assert_eq!(b, &[0xAAu8, 0xBB, 0xCC]);
252        assert!(cur.is_empty());
253    }
254
255    #[test]
256    fn get_compact_bytes_borrowed_null_is_error() {
257        let bytes = [0x00u8]; // varint 0 = null
258        let mut cur: &[u8] = &bytes;
259        assert!(matches!(
260            get_compact_bytes_borrowed(&mut cur),
261            Err(ProtocolError::InvalidValue(_))
262        ));
263    }
264
265    #[test]
266    fn get_compact_nullable_bytes_borrowed_null() {
267        let bytes = [0x00u8];
268        let mut cur: &[u8] = &bytes;
269        assert_eq!(get_compact_nullable_bytes_borrowed(&mut cur).unwrap(), None);
270    }
271
272    #[test]
273    fn get_compact_nullable_bytes_borrowed_some() {
274        // UVARINT(3) = length+1=3 → 2 bytes; [0x01, 0x02]
275        let bytes = [0x03u8, 0x01, 0x02];
276        let mut cur: &[u8] = &bytes;
277        assert_eq!(
278            get_compact_nullable_bytes_borrowed(&mut cur).unwrap(),
279            Some(&[0x01u8, 0x02][..])
280        );
281        assert!(cur.is_empty());
282    }
283}