Skip to main content

nautilus_serialization/sbe/
cursor.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Zero-copy SBE byte cursor for sequential decoding.
17
18use std::str;
19
20use super::error::{MAX_GROUP_SIZE, SbeDecodeError};
21
22/// Zero-copy SBE byte cursor for sequential decoding.
23///
24/// Wraps a byte slice and tracks position, providing typed read methods
25/// that automatically advance the cursor.
26#[derive(Debug, Clone)]
27pub struct SbeCursor<'a> {
28    buf: &'a [u8],
29    pos: usize,
30}
31
32impl<'a> SbeCursor<'a> {
33    /// Creates a new cursor at position 0.
34    #[must_use]
35    pub const fn new(buf: &'a [u8]) -> Self {
36        Self { buf, pos: 0 }
37    }
38
39    /// Creates a cursor starting at a specific offset.
40    #[must_use]
41    pub const fn new_at(buf: &'a [u8], pos: usize) -> Self {
42        Self { buf, pos }
43    }
44
45    /// Current position in the buffer.
46    #[must_use]
47    pub const fn pos(&self) -> usize {
48        self.pos
49    }
50
51    /// Remaining bytes from current position.
52    #[must_use]
53    pub const fn remaining(&self) -> usize {
54        self.buf.len().saturating_sub(self.pos)
55    }
56
57    /// Returns the underlying buffer.
58    #[must_use]
59    pub const fn buffer(&self) -> &'a [u8] {
60        self.buf
61    }
62
63    /// Returns remaining bytes as a slice.
64    #[must_use]
65    pub fn peek(&self) -> &'a [u8] {
66        &self.buf[self.pos..]
67    }
68
69    /// Ensures at least `n` bytes remain.
70    ///
71    /// # Errors
72    ///
73    /// Returns `BufferTooShort` if fewer than `n` bytes remain.
74    #[inline]
75    pub fn require(&self, n: usize) -> Result<(), SbeDecodeError> {
76        if self.remaining() < n {
77            return Err(SbeDecodeError::BufferTooShort {
78                expected: self.pos + n,
79                actual: self.buf.len(),
80            });
81        }
82        Ok(())
83    }
84
85    /// Advances position by `n` bytes.
86    ///
87    /// # Errors
88    ///
89    /// Returns `BufferTooShort` if fewer than `n` bytes remain.
90    #[inline]
91    pub fn advance(&mut self, n: usize) -> Result<(), SbeDecodeError> {
92        self.require(n)?;
93        self.pos += n;
94        Ok(())
95    }
96
97    /// Skips `n` bytes without bounds checking.
98    ///
99    /// Caller must ensure `n` bytes are available.
100    #[inline]
101    pub fn skip(&mut self, n: usize) {
102        self.pos += n;
103    }
104
105    /// Resets cursor to start of buffer.
106    pub fn reset(&mut self) {
107        self.pos = 0;
108    }
109
110    /// Sets cursor to a specific position.
111    pub fn set_pos(&mut self, pos: usize) {
112        self.pos = pos;
113    }
114
115    /// Reads a u8 and advances by 1 byte.
116    ///
117    /// # Errors
118    ///
119    /// Returns `BufferTooShort` if fewer than 1 byte remains.
120    #[inline]
121    pub fn read_u8(&mut self) -> Result<u8, SbeDecodeError> {
122        self.require(1)?;
123        let value = self.buf[self.pos];
124        self.pos += 1;
125        Ok(value)
126    }
127
128    /// Reads an i8 and advances by 1 byte.
129    ///
130    /// # Errors
131    ///
132    /// Returns `BufferTooShort` if fewer than 1 byte remains.
133    #[inline]
134    pub fn read_i8(&mut self) -> Result<i8, SbeDecodeError> {
135        self.require(1)?;
136        let value = self.buf[self.pos] as i8;
137        self.pos += 1;
138        Ok(value)
139    }
140
141    /// Reads a u16 little-endian and advances by 2 bytes.
142    ///
143    /// # Errors
144    ///
145    /// Returns `BufferTooShort` if fewer than 2 bytes remain.
146    #[inline]
147    pub fn read_u16_le(&mut self) -> Result<u16, SbeDecodeError> {
148        Ok(u16::from_le_bytes(self.read_array::<2>()?))
149    }
150
151    /// Reads an i16 little-endian and advances by 2 bytes.
152    ///
153    /// # Errors
154    ///
155    /// Returns `BufferTooShort` if fewer than 2 bytes remain.
156    #[inline]
157    pub fn read_i16_le(&mut self) -> Result<i16, SbeDecodeError> {
158        Ok(i16::from_le_bytes(self.read_array::<2>()?))
159    }
160
161    /// Reads a u32 little-endian and advances by 4 bytes.
162    ///
163    /// # Errors
164    ///
165    /// Returns `BufferTooShort` if fewer than 4 bytes remain.
166    #[inline]
167    pub fn read_u32_le(&mut self) -> Result<u32, SbeDecodeError> {
168        Ok(u32::from_le_bytes(self.read_array::<4>()?))
169    }
170
171    /// Reads an i32 little-endian and advances by 4 bytes.
172    ///
173    /// # Errors
174    ///
175    /// Returns `BufferTooShort` if fewer than 4 bytes remain.
176    #[inline]
177    pub fn read_i32_le(&mut self) -> Result<i32, SbeDecodeError> {
178        Ok(i32::from_le_bytes(self.read_array::<4>()?))
179    }
180
181    /// Reads a u64 little-endian and advances by 8 bytes.
182    ///
183    /// # Errors
184    ///
185    /// Returns `BufferTooShort` if fewer than 8 bytes remain.
186    #[inline]
187    pub fn read_u64_le(&mut self) -> Result<u64, SbeDecodeError> {
188        Ok(u64::from_le_bytes(self.read_array::<8>()?))
189    }
190
191    /// Reads an i64 little-endian and advances by 8 bytes.
192    ///
193    /// # Errors
194    ///
195    /// Returns `BufferTooShort` if fewer than 8 bytes remain.
196    #[inline]
197    pub fn read_i64_le(&mut self) -> Result<i64, SbeDecodeError> {
198        Ok(i64::from_le_bytes(self.read_array::<8>()?))
199    }
200
201    /// Reads a u128 little-endian and advances by 16 bytes.
202    ///
203    /// # Errors
204    ///
205    /// Returns `BufferTooShort` if fewer than 16 bytes remain.
206    #[inline]
207    pub fn read_u128_le(&mut self) -> Result<u128, SbeDecodeError> {
208        Ok(u128::from_le_bytes(self.read_array::<16>()?))
209    }
210
211    /// Reads an i128 little-endian and advances by 16 bytes.
212    ///
213    /// # Errors
214    ///
215    /// Returns `BufferTooShort` if fewer than 16 bytes remain.
216    #[inline]
217    pub fn read_i128_le(&mut self) -> Result<i128, SbeDecodeError> {
218        Ok(i128::from_le_bytes(self.read_array::<16>()?))
219    }
220
221    /// Reads an optional i64 where `i64::MIN` represents None.
222    ///
223    /// # Errors
224    ///
225    /// Returns `BufferTooShort` if fewer than 8 bytes remain.
226    #[inline]
227    pub fn read_optional_i64_le(&mut self) -> Result<Option<i64>, SbeDecodeError> {
228        let value = self.read_i64_le()?;
229        Ok(if value == i64::MIN { None } else { Some(value) })
230    }
231
232    /// Reads N bytes and advances.
233    ///
234    /// # Errors
235    ///
236    /// Returns `BufferTooShort` if fewer than `n` bytes remain.
237    #[inline]
238    pub fn read_bytes(&mut self, n: usize) -> Result<&'a [u8], SbeDecodeError> {
239        self.require(n)?;
240        let slice = &self.buf[self.pos..self.pos + n];
241        self.pos += n;
242        Ok(slice)
243    }
244
245    // Const-generic slice-to-array conversion lets LLVM lower the read to
246    // a single aligned load after one bounds check, matching the pattern
247    // the compiler recognizes for `from_le_bytes`.
248    #[inline]
249    fn read_array<const N: usize>(&mut self) -> Result<[u8; N], SbeDecodeError> {
250        self.require(N)?;
251        let bytes: [u8; N] = self.buf[self.pos..self.pos + N]
252            .try_into()
253            .expect("slice length matches N");
254        self.pos += N;
255        Ok(bytes)
256    }
257
258    /// Reads a varString8 (1-byte length prefix + UTF-8 data).
259    ///
260    /// Returns empty string if length is 0.
261    ///
262    /// # Errors
263    ///
264    /// Returns `BufferTooShort` if the buffer is too short, or `InvalidUtf8` if the data
265    /// is not valid UTF-8.
266    #[inline]
267    pub fn read_var_string8(&mut self) -> Result<String, SbeDecodeError> {
268        Ok(self.read_var_string8_ref()?.to_owned())
269    }
270
271    /// Reads a varString8 as a &str (zero-copy).
272    ///
273    /// # Errors
274    ///
275    /// Returns `BufferTooShort` if the buffer is too short, or `InvalidUtf8` if the data
276    /// is not valid UTF-8.
277    #[inline]
278    pub fn read_var_string8_ref(&mut self) -> Result<&'a str, SbeDecodeError> {
279        let len = self.read_u8()? as usize;
280        if len == 0 {
281            return Ok("");
282        }
283        self.require(len)?;
284        let s = str::from_utf8(&self.buf[self.pos..self.pos + len])
285            .map_err(|_| SbeDecodeError::InvalidUtf8)?;
286        self.pos += len;
287        Ok(s)
288    }
289
290    /// Reads a varString16 (2-byte length prefix + UTF-8 data).
291    ///
292    /// Returns empty string if length is 0.
293    ///
294    /// # Errors
295    ///
296    /// Returns `BufferTooShort` if the buffer is too short, or `InvalidUtf8` if the data
297    /// is not valid UTF-8.
298    #[inline]
299    pub fn read_var_string16(&mut self) -> Result<String, SbeDecodeError> {
300        Ok(self.read_var_string16_ref()?.to_owned())
301    }
302
303    /// Reads a varString16 as a `&str` (zero-copy).
304    ///
305    /// # Errors
306    ///
307    /// Returns `BufferTooShort` if the buffer is too short, or `InvalidUtf8` if the data
308    /// is not valid UTF-8.
309    #[inline]
310    pub fn read_var_string16_ref(&mut self) -> Result<&'a str, SbeDecodeError> {
311        let len = usize::from(self.read_u16_le()?);
312        if len == 0 {
313            return Ok("");
314        }
315        self.require(len)?;
316        let s = str::from_utf8(&self.buf[self.pos..self.pos + len])
317            .map_err(|_| SbeDecodeError::InvalidUtf8)?;
318        self.pos += len;
319        Ok(s)
320    }
321
322    /// Skips a varData8 field (1-byte length prefix + binary data).
323    ///
324    /// Used for skipping binary fields that should not be decoded as UTF-8.
325    ///
326    /// # Errors
327    ///
328    /// Returns `BufferTooShort` if the buffer is too short.
329    pub fn skip_var_data8(&mut self) -> Result<(), SbeDecodeError> {
330        let len = self.read_u8()? as usize;
331        self.advance(len)
332    }
333
334    /// Reads a varData8 field (1-byte length prefix + binary data).
335    ///
336    /// Returns the raw bytes without UTF-8 decoding.
337    ///
338    /// # Errors
339    ///
340    /// Returns `BufferTooShort` if the buffer is too short.
341    pub fn read_var_bytes8(&mut self) -> Result<Vec<u8>, SbeDecodeError> {
342        let len = self.read_u8()? as usize;
343        Ok(self.read_bytes(len)?.to_vec())
344    }
345
346    /// Skips a varData16 field (2-byte length prefix + binary data).
347    ///
348    /// # Errors
349    ///
350    /// Returns `BufferTooShort` if the buffer is too short.
351    pub fn skip_var_data16(&mut self) -> Result<(), SbeDecodeError> {
352        let len = usize::from(self.read_u16_le()?);
353        self.advance(len)
354    }
355
356    /// Reads a varData16 field (2-byte length prefix + binary data).
357    ///
358    /// Returns the raw bytes without UTF-8 decoding.
359    ///
360    /// # Errors
361    ///
362    /// Returns `BufferTooShort` if the buffer is too short.
363    pub fn read_var_bytes16(&mut self) -> Result<Vec<u8>, SbeDecodeError> {
364        let len = usize::from(self.read_u16_le()?);
365        Ok(self.read_bytes(len)?.to_vec())
366    }
367
368    /// Reads group header (u16 block_length + u32 num_in_group).
369    ///
370    /// Returns (block_length, num_in_group).
371    ///
372    /// # Errors
373    ///
374    /// Returns `BufferTooShort` if fewer than 6 bytes are available and
375    /// `GroupSizeTooLarge` if `num_in_group` exceeds `MAX_GROUP_SIZE`.
376    #[inline]
377    pub fn read_group_header(&mut self) -> Result<(u16, u32), SbeDecodeError> {
378        let block_length = self.read_u16_le()?;
379        let num_in_group = self.read_u32_le()?;
380
381        if num_in_group > MAX_GROUP_SIZE {
382            return Err(SbeDecodeError::GroupSizeTooLarge {
383                count: num_in_group,
384                max: MAX_GROUP_SIZE,
385            });
386        }
387
388        Ok((block_length, num_in_group))
389    }
390
391    /// Reads compact group header (u16 block_length + u16 num_in_group).
392    ///
393    /// Returns (block_length, num_in_group).
394    ///
395    /// # Errors
396    ///
397    /// Returns `BufferTooShort` if fewer than 4 bytes are available and
398    /// `GroupSizeTooLarge` if `num_in_group` exceeds `MAX_GROUP_SIZE`.
399    #[inline]
400    pub fn read_group_header_16(&mut self) -> Result<(u16, u16), SbeDecodeError> {
401        let block_length = self.read_u16_le()?;
402        let num_in_group = self.read_u16_le()?;
403
404        if u32::from(num_in_group) > MAX_GROUP_SIZE {
405            return Err(SbeDecodeError::GroupSizeTooLarge {
406                count: u32::from(num_in_group),
407                max: MAX_GROUP_SIZE,
408            });
409        }
410
411        Ok((block_length, num_in_group))
412    }
413
414    /// Iterates over a group, calling `decode_item` for each element.
415    ///
416    /// The decoder function receives a cursor positioned at the start of each item
417    /// and should decode the item without advancing past `block_length` bytes.
418    ///
419    /// # Errors
420    ///
421    /// Returns `BufferTooShort` if the buffer does not contain all group entries,
422    /// or any error returned by `decode_item`.
423    pub fn read_group<T, F>(
424        &mut self,
425        block_length: u16,
426        num_in_group: u32,
427        mut decode_item: F,
428    ) -> Result<Vec<T>, SbeDecodeError>
429    where
430        F: FnMut(&mut Self) -> Result<T, SbeDecodeError>,
431    {
432        let block_len = block_length as usize;
433        let count = num_in_group as usize;
434
435        // Validate we have enough bytes for all items.
436        self.require(count * block_len)?;
437
438        let mut items = Vec::with_capacity(count);
439        for _ in 0..count {
440            let item_start = self.pos;
441            let item = decode_item(self)?;
442            items.push(item);
443
444            // Advance to next item boundary (respects block_length even if decoder read less).
445            self.pos = item_start + block_len;
446        }
447
448        Ok(items)
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use rstest::rstest;
455
456    use super::*;
457
458    #[rstest]
459    fn test_new_starts_at_zero() {
460        let buf = [1, 2, 3, 4];
461        let cursor = SbeCursor::new(&buf);
462        assert_eq!(cursor.pos(), 0);
463        assert_eq!(cursor.remaining(), 4);
464    }
465
466    #[rstest]
467    fn test_new_at_starts_at_offset() {
468        let buf = [1, 2, 3, 4];
469        let cursor = SbeCursor::new_at(&buf, 2);
470        assert_eq!(cursor.pos(), 2);
471        assert_eq!(cursor.remaining(), 2);
472    }
473
474    #[rstest]
475    fn test_read_u8() {
476        let buf = [0x42, 0xFF];
477        let mut cursor = SbeCursor::new(&buf);
478
479        assert_eq!(cursor.read_u8().unwrap(), 0x42);
480        assert_eq!(cursor.pos(), 1);
481
482        assert_eq!(cursor.read_u8().unwrap(), 0xFF);
483        assert_eq!(cursor.pos(), 2);
484
485        assert!(cursor.read_u8().is_err());
486    }
487
488    #[rstest]
489    fn test_read_i8() {
490        let buf = [0x7F, 0x80]; // 127, -128
491        let mut cursor = SbeCursor::new(&buf);
492
493        assert_eq!(cursor.read_i8().unwrap(), 127);
494        assert_eq!(cursor.read_i8().unwrap(), -128);
495    }
496
497    #[rstest]
498    fn test_read_u16_le() {
499        let buf = [0x34, 0x12]; // 0x1234 in little-endian
500        let mut cursor = SbeCursor::new(&buf);
501
502        assert_eq!(cursor.read_u16_le().unwrap(), 0x1234);
503        assert_eq!(cursor.pos(), 2);
504    }
505
506    #[rstest]
507    fn test_read_i64_le() {
508        let value: i64 = -1_234_567_890_123_456_789;
509        let buf = value.to_le_bytes();
510        let mut cursor = SbeCursor::new(&buf);
511
512        assert_eq!(cursor.read_i64_le().unwrap(), value);
513        assert_eq!(cursor.pos(), 8);
514    }
515
516    #[rstest]
517    #[case::u16(&[0x34][..], 2)]
518    #[case::u32(&[0x34, 0x12, 0x00][..], 4)]
519    #[case::u64(&[0; 7][..], 8)]
520    #[case::u128(&[0; 15][..], 16)]
521    fn test_multi_byte_reads_buffer_too_short(#[case] buf: &[u8], #[case] needed: usize) {
522        let mut cursor = SbeCursor::new(buf);
523        let err = match needed {
524            2 => cursor.read_u16_le().map(|_| ()).unwrap_err(),
525            4 => cursor.read_u32_le().map(|_| ()).unwrap_err(),
526            8 => cursor.read_u64_le().map(|_| ()).unwrap_err(),
527            16 => cursor.read_u128_le().map(|_| ()).unwrap_err(),
528            _ => unreachable!(),
529        };
530
531        assert_eq!(
532            err,
533            SbeDecodeError::BufferTooShort {
534                expected: needed,
535                actual: buf.len()
536            }
537        );
538        assert_eq!(cursor.pos(), 0, "position must not advance on error");
539    }
540
541    #[rstest]
542    fn test_read_optional_i64_null() {
543        let buf = i64::MIN.to_le_bytes();
544        let mut cursor = SbeCursor::new(&buf);
545
546        assert_eq!(cursor.read_optional_i64_le().unwrap(), None);
547    }
548
549    #[rstest]
550    fn test_read_optional_i64_present() {
551        let value: i64 = 12345;
552        let buf = value.to_le_bytes();
553        let mut cursor = SbeCursor::new(&buf);
554
555        assert_eq!(cursor.read_optional_i64_le().unwrap(), Some(12345));
556    }
557
558    #[rstest]
559    fn test_read_var_string8() {
560        let mut buf = vec![5]; // length = 5
561        buf.extend_from_slice(b"hello");
562        let mut cursor = SbeCursor::new(&buf);
563
564        assert_eq!(cursor.read_var_string8().unwrap(), "hello");
565        assert_eq!(cursor.pos(), 6); // 1 + 5
566    }
567
568    #[rstest]
569    fn test_read_var_string8_empty() {
570        let buf = [0]; // length = 0
571        let mut cursor = SbeCursor::new(&buf);
572
573        assert_eq!(cursor.read_var_string8().unwrap(), "");
574        assert_eq!(cursor.pos(), 1);
575    }
576
577    #[rstest]
578    fn test_read_var_string8_invalid_utf8() {
579        let buf = [2, 0xFF, 0xFE]; // length = 2, invalid UTF-8
580        let mut cursor = SbeCursor::new(&buf);
581
582        assert!(matches!(
583            cursor.read_var_string8(),
584            Err(SbeDecodeError::InvalidUtf8)
585        ));
586    }
587
588    #[rstest]
589    fn test_read_group_header() {
590        // block_length = 24, num_in_group = 3
591        let buf = [24, 0, 3, 0, 0, 0];
592        let mut cursor = SbeCursor::new(&buf);
593
594        let (block_len, count) = cursor.read_group_header().unwrap();
595        assert_eq!(block_len, 24);
596        assert_eq!(count, 3);
597        assert_eq!(cursor.pos(), 6);
598    }
599
600    #[rstest]
601    fn test_read_group_header_too_large() {
602        // num_in_group = MAX_GROUP_SIZE + 1
603        let count = MAX_GROUP_SIZE + 1;
604        let mut buf = vec![24, 0]; // block_length = 24
605        buf.extend_from_slice(&count.to_le_bytes());
606        let mut cursor = SbeCursor::new(&buf);
607
608        assert!(matches!(
609            cursor.read_group_header(),
610            Err(SbeDecodeError::GroupSizeTooLarge { .. })
611        ));
612    }
613
614    #[rstest]
615    fn test_read_group() {
616        // 2 items, each 4 bytes containing a u32
617        let mut buf = Vec::new();
618        buf.extend_from_slice(&100u32.to_le_bytes()); // item 0
619        buf.extend_from_slice(&200u32.to_le_bytes()); // item 1
620
621        let mut cursor = SbeCursor::new(&buf);
622        let items: Vec<u32> = cursor
623            .read_group(4, 2, super::SbeCursor::read_u32_le)
624            .unwrap();
625
626        assert_eq!(items, vec![100, 200]);
627        assert_eq!(cursor.pos(), 8);
628    }
629
630    #[rstest]
631    fn test_read_group_respects_block_length() {
632        // 2 items, block_length = 8, but we only read 4 bytes per item
633        let mut buf = Vec::new();
634        buf.extend_from_slice(&100u32.to_le_bytes());
635        buf.extend_from_slice(&[0, 0, 0, 0]); // padding
636        buf.extend_from_slice(&200u32.to_le_bytes());
637        buf.extend_from_slice(&[0, 0, 0, 0]); // padding
638
639        let mut cursor = SbeCursor::new(&buf);
640        let items: Vec<u32> = cursor
641            .read_group(8, 2, super::SbeCursor::read_u32_le)
642            .unwrap();
643
644        assert_eq!(items, vec![100, 200]);
645        assert_eq!(cursor.pos(), 16); // 2 * 8
646    }
647
648    #[rstest]
649    fn test_require_success() {
650        let buf = [1, 2, 3, 4];
651        let cursor = SbeCursor::new(&buf);
652
653        assert!(cursor.require(4).is_ok());
654        assert!(cursor.require(3).is_ok());
655    }
656
657    #[rstest]
658    fn test_require_failure() {
659        let buf = [1, 2];
660        let cursor = SbeCursor::new(&buf);
661
662        let err = cursor.require(3).unwrap_err();
663        assert_eq!(
664            err,
665            SbeDecodeError::BufferTooShort {
666                expected: 3,
667                actual: 2
668            }
669        );
670    }
671
672    #[rstest]
673    fn test_advance() {
674        let buf = [1, 2, 3, 4];
675        let mut cursor = SbeCursor::new(&buf);
676
677        cursor.advance(2).unwrap();
678        assert_eq!(cursor.pos(), 2);
679        assert_eq!(cursor.remaining(), 2);
680
681        assert!(cursor.advance(3).is_err());
682    }
683
684    #[rstest]
685    fn test_peek() {
686        let buf = [1, 2, 3, 4];
687        let mut cursor = SbeCursor::new(&buf);
688
689        assert_eq!(cursor.peek(), &[1, 2, 3, 4]);
690        cursor.advance(2).unwrap();
691        assert_eq!(cursor.peek(), &[3, 4]);
692    }
693
694    #[rstest]
695    fn test_reset() {
696        let buf = [1, 2, 3, 4];
697        let mut cursor = SbeCursor::new(&buf);
698
699        cursor.advance(3).unwrap();
700        assert_eq!(cursor.pos(), 3);
701
702        cursor.reset();
703        assert_eq!(cursor.pos(), 0);
704        assert_eq!(cursor.remaining(), 4);
705    }
706
707    #[rstest]
708    #[case::short(false)]
709    #[case::wide(true)]
710    fn test_var_bytes_preserve_binary_and_position(#[case] wide: bool) {
711        let mut buf = if wide { vec![3, 0] } else { vec![3] };
712        buf.extend_from_slice(&[0xff, 0, 0x80, 0x42]);
713        let mut read = SbeCursor::new(&buf);
714        let mut skip = read.clone();
715
716        let bytes = if wide {
717            skip.skip_var_data16().unwrap();
718            read.read_var_bytes16().unwrap()
719        } else {
720            skip.skip_var_data8().unwrap();
721            read.read_var_bytes8().unwrap()
722        };
723
724        assert_eq!(bytes, [0xff, 0, 0x80]);
725        assert_eq!(read.pos(), buf.len() - 1);
726        assert_eq!(skip.pos(), read.pos());
727        assert_eq!(read.peek(), [0x42]);
728        assert_eq!(skip.peek(), [0x42]);
729    }
730
731    #[rstest]
732    #[case::short(false)]
733    #[case::wide(true)]
734    fn test_var_bytes_reject_truncated_payload(#[case] wide: bool) {
735        let buf = if wide {
736            vec![3, 0, 0xff]
737        } else {
738            vec![3, 0xff]
739        };
740
741        let prefix = if wide { 2 } else { 1 };
742        let mut read = SbeCursor::new(&buf);
743        let mut skip = read.clone();
744
745        let (read_result, skip_result) = if wide {
746            (read.read_var_bytes16(), skip.skip_var_data16())
747        } else {
748            (read.read_var_bytes8(), skip.skip_var_data8())
749        };
750
751        let expected = SbeDecodeError::BufferTooShort {
752            expected: prefix + 3,
753            actual: buf.len(),
754        };
755
756        assert_eq!(read_result, Err(expected.clone()));
757        assert_eq!(skip_result, Err(expected));
758        assert_eq!(read.pos(), prefix);
759        assert_eq!(skip.pos(), prefix);
760    }
761
762    #[rstest]
763    fn test_read_group_propagates_item_error() {
764        let mut cursor = SbeCursor::new(&[1, 2, 3, 4]);
765
766        let result = cursor.read_group(2, 2, |entry| {
767            let value = entry.read_u8()?;
768            if value == 3 {
769                Err(SbeDecodeError::InvalidValue { field: "entry" })
770            } else {
771                Ok(value)
772            }
773        });
774
775        assert_eq!(result, Err(SbeDecodeError::InvalidValue { field: "entry" }));
776        assert_eq!(cursor.pos(), 3);
777    }
778}