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 an error 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 an error 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 an error 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 an error 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 an error 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 an error 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 an error 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 an error 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 an error 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 an error 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 an error 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 an error 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 an error 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 an error 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 an error if the buffer is too short, or if the data is not valid UTF-8.
265    #[inline]
266    pub fn read_var_string8(&mut self) -> Result<String, SbeDecodeError> {
267        Ok(self.read_var_string8_ref()?.to_owned())
268    }
269
270    /// Reads a varString8 as a &str (zero-copy).
271    ///
272    /// # Errors
273    ///
274    /// Returns an error if the buffer is too short, or if the data is not valid UTF-8.
275    #[inline]
276    pub fn read_var_string8_ref(&mut self) -> Result<&'a str, SbeDecodeError> {
277        let len = self.read_u8()? as usize;
278        if len == 0 {
279            return Ok("");
280        }
281        self.require(len)?;
282        let s = str::from_utf8(&self.buf[self.pos..self.pos + len])
283            .map_err(|_| SbeDecodeError::InvalidUtf8)?;
284        self.pos += len;
285        Ok(s)
286    }
287
288    /// Reads a varString16 (2-byte length prefix + UTF-8 data).
289    ///
290    /// Returns empty string if length is 0.
291    ///
292    /// # Errors
293    ///
294    /// Returns an error if the buffer is too short, or if the data is not valid UTF-8.
295    #[inline]
296    pub fn read_var_string16(&mut self) -> Result<String, SbeDecodeError> {
297        Ok(self.read_var_string16_ref()?.to_owned())
298    }
299
300    /// Reads a varString16 as a `&str` (zero-copy).
301    ///
302    /// # Errors
303    ///
304    /// Returns an error if the buffer is too short, or if the data is not valid UTF-8.
305    #[inline]
306    pub fn read_var_string16_ref(&mut self) -> Result<&'a str, SbeDecodeError> {
307        let len = usize::from(self.read_u16_le()?);
308        if len == 0 {
309            return Ok("");
310        }
311        self.require(len)?;
312        let s = str::from_utf8(&self.buf[self.pos..self.pos + len])
313            .map_err(|_| SbeDecodeError::InvalidUtf8)?;
314        self.pos += len;
315        Ok(s)
316    }
317
318    /// Skips a varData8 field (1-byte length prefix + binary data).
319    ///
320    /// Used for skipping binary fields that should not be decoded as UTF-8.
321    ///
322    /// # Errors
323    ///
324    /// Returns an error if the buffer is too short.
325    pub fn skip_var_data8(&mut self) -> Result<(), SbeDecodeError> {
326        let len = self.read_u8()? as usize;
327        self.advance(len)
328    }
329
330    /// Reads a varData8 field (1-byte length prefix + binary data).
331    ///
332    /// Returns the raw bytes without UTF-8 decoding.
333    ///
334    /// # Errors
335    ///
336    /// Returns an error if the buffer is too short.
337    pub fn read_var_bytes8(&mut self) -> Result<Vec<u8>, SbeDecodeError> {
338        let len = self.read_u8()? as usize;
339        Ok(self.read_bytes(len)?.to_vec())
340    }
341
342    /// Skips a varData16 field (2-byte length prefix + binary data).
343    ///
344    /// # Errors
345    ///
346    /// Returns an error if the buffer is too short.
347    pub fn skip_var_data16(&mut self) -> Result<(), SbeDecodeError> {
348        let len = usize::from(self.read_u16_le()?);
349        self.advance(len)
350    }
351
352    /// Reads a varData16 field (2-byte length prefix + binary data).
353    ///
354    /// Returns the raw bytes without UTF-8 decoding.
355    ///
356    /// # Errors
357    ///
358    /// Returns an error if the buffer is too short.
359    pub fn read_var_bytes16(&mut self) -> Result<Vec<u8>, SbeDecodeError> {
360        let len = usize::from(self.read_u16_le()?);
361        Ok(self.read_bytes(len)?.to_vec())
362    }
363
364    /// Reads group header (u16 block_length + u32 num_in_group).
365    ///
366    /// Returns (block_length, num_in_group).
367    ///
368    /// # Errors
369    ///
370    /// Returns an error if fewer than 6 bytes are available or `num_in_group` exceeds
371    /// `MAX_GROUP_SIZE`.
372    #[inline]
373    pub fn read_group_header(&mut self) -> Result<(u16, u32), SbeDecodeError> {
374        let block_length = self.read_u16_le()?;
375        let num_in_group = self.read_u32_le()?;
376
377        if num_in_group > MAX_GROUP_SIZE {
378            return Err(SbeDecodeError::GroupSizeTooLarge {
379                count: num_in_group,
380                max: MAX_GROUP_SIZE,
381            });
382        }
383
384        Ok((block_length, num_in_group))
385    }
386
387    /// Reads compact group header (u16 block_length + u16 num_in_group).
388    ///
389    /// Returns (block_length, num_in_group).
390    ///
391    /// # Errors
392    ///
393    /// Returns an error if fewer than 4 bytes are available or `num_in_group` exceeds
394    /// `MAX_GROUP_SIZE`.
395    #[inline]
396    pub fn read_group_header_16(&mut self) -> Result<(u16, u16), SbeDecodeError> {
397        let block_length = self.read_u16_le()?;
398        let num_in_group = self.read_u16_le()?;
399
400        if u32::from(num_in_group) > MAX_GROUP_SIZE {
401            return Err(SbeDecodeError::GroupSizeTooLarge {
402                count: u32::from(num_in_group),
403                max: MAX_GROUP_SIZE,
404            });
405        }
406
407        Ok((block_length, num_in_group))
408    }
409
410    /// Iterates over a group, calling `decode_item` for each element.
411    ///
412    /// The decoder function receives a cursor positioned at the start of each item
413    /// and should decode the item without advancing past `block_length` bytes.
414    ///
415    /// # Errors
416    ///
417    /// Returns an error if the buffer does not contain all group entries or `decode_item` fails.
418    pub fn read_group<T, F>(
419        &mut self,
420        block_length: u16,
421        num_in_group: u32,
422        mut decode_item: F,
423    ) -> Result<Vec<T>, SbeDecodeError>
424    where
425        F: FnMut(&mut Self) -> Result<T, SbeDecodeError>,
426    {
427        let block_len = block_length as usize;
428        let count = num_in_group as usize;
429
430        // Validate we have enough bytes for all items.
431        self.require(count * block_len)?;
432
433        let mut items = Vec::with_capacity(count);
434        for _ in 0..count {
435            let item_start = self.pos;
436            let item = decode_item(self)?;
437            items.push(item);
438
439            // Advance to next item boundary (respects block_length even if decoder read less).
440            self.pos = item_start + block_len;
441        }
442
443        Ok(items)
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use rstest::rstest;
450
451    use super::*;
452
453    #[rstest]
454    fn test_new_starts_at_zero() {
455        let buf = [1, 2, 3, 4];
456        let cursor = SbeCursor::new(&buf);
457        assert_eq!(cursor.pos(), 0);
458        assert_eq!(cursor.remaining(), 4);
459    }
460
461    #[rstest]
462    fn test_new_at_starts_at_offset() {
463        let buf = [1, 2, 3, 4];
464        let cursor = SbeCursor::new_at(&buf, 2);
465        assert_eq!(cursor.pos(), 2);
466        assert_eq!(cursor.remaining(), 2);
467    }
468
469    #[rstest]
470    fn test_read_u8() {
471        let buf = [0x42, 0xFF];
472        let mut cursor = SbeCursor::new(&buf);
473
474        assert_eq!(cursor.read_u8().unwrap(), 0x42);
475        assert_eq!(cursor.pos(), 1);
476
477        assert_eq!(cursor.read_u8().unwrap(), 0xFF);
478        assert_eq!(cursor.pos(), 2);
479
480        assert!(cursor.read_u8().is_err());
481    }
482
483    #[rstest]
484    fn test_read_i8() {
485        let buf = [0x7F, 0x80]; // 127, -128
486        let mut cursor = SbeCursor::new(&buf);
487
488        assert_eq!(cursor.read_i8().unwrap(), 127);
489        assert_eq!(cursor.read_i8().unwrap(), -128);
490    }
491
492    #[rstest]
493    fn test_read_u16_le() {
494        let buf = [0x34, 0x12]; // 0x1234 in little-endian
495        let mut cursor = SbeCursor::new(&buf);
496
497        assert_eq!(cursor.read_u16_le().unwrap(), 0x1234);
498        assert_eq!(cursor.pos(), 2);
499    }
500
501    #[rstest]
502    fn test_read_i64_le() {
503        let value: i64 = -1_234_567_890_123_456_789;
504        let buf = value.to_le_bytes();
505        let mut cursor = SbeCursor::new(&buf);
506
507        assert_eq!(cursor.read_i64_le().unwrap(), value);
508        assert_eq!(cursor.pos(), 8);
509    }
510
511    #[rstest]
512    #[case::u16(&[0x34][..], 2)]
513    #[case::u32(&[0x34, 0x12, 0x00][..], 4)]
514    #[case::u64(&[0; 7][..], 8)]
515    #[case::u128(&[0; 15][..], 16)]
516    fn test_multi_byte_reads_buffer_too_short(#[case] buf: &[u8], #[case] needed: usize) {
517        let mut cursor = SbeCursor::new(buf);
518        let err = match needed {
519            2 => cursor.read_u16_le().map(|_| ()).unwrap_err(),
520            4 => cursor.read_u32_le().map(|_| ()).unwrap_err(),
521            8 => cursor.read_u64_le().map(|_| ()).unwrap_err(),
522            16 => cursor.read_u128_le().map(|_| ()).unwrap_err(),
523            _ => unreachable!(),
524        };
525
526        assert_eq!(
527            err,
528            SbeDecodeError::BufferTooShort {
529                expected: needed,
530                actual: buf.len()
531            }
532        );
533        assert_eq!(cursor.pos(), 0, "position must not advance on error");
534    }
535
536    #[rstest]
537    fn test_read_optional_i64_null() {
538        let buf = i64::MIN.to_le_bytes();
539        let mut cursor = SbeCursor::new(&buf);
540
541        assert_eq!(cursor.read_optional_i64_le().unwrap(), None);
542    }
543
544    #[rstest]
545    fn test_read_optional_i64_present() {
546        let value: i64 = 12345;
547        let buf = value.to_le_bytes();
548        let mut cursor = SbeCursor::new(&buf);
549
550        assert_eq!(cursor.read_optional_i64_le().unwrap(), Some(12345));
551    }
552
553    #[rstest]
554    fn test_read_var_string8() {
555        let mut buf = vec![5]; // length = 5
556        buf.extend_from_slice(b"hello");
557        let mut cursor = SbeCursor::new(&buf);
558
559        assert_eq!(cursor.read_var_string8().unwrap(), "hello");
560        assert_eq!(cursor.pos(), 6); // 1 + 5
561    }
562
563    #[rstest]
564    fn test_read_var_string8_empty() {
565        let buf = [0]; // length = 0
566        let mut cursor = SbeCursor::new(&buf);
567
568        assert_eq!(cursor.read_var_string8().unwrap(), "");
569        assert_eq!(cursor.pos(), 1);
570    }
571
572    #[rstest]
573    fn test_read_var_string8_invalid_utf8() {
574        let buf = [2, 0xFF, 0xFE]; // length = 2, invalid UTF-8
575        let mut cursor = SbeCursor::new(&buf);
576
577        assert!(matches!(
578            cursor.read_var_string8(),
579            Err(SbeDecodeError::InvalidUtf8)
580        ));
581    }
582
583    #[rstest]
584    fn test_read_group_header() {
585        // block_length = 24, num_in_group = 3
586        let buf = [24, 0, 3, 0, 0, 0];
587        let mut cursor = SbeCursor::new(&buf);
588
589        let (block_len, count) = cursor.read_group_header().unwrap();
590        assert_eq!(block_len, 24);
591        assert_eq!(count, 3);
592        assert_eq!(cursor.pos(), 6);
593    }
594
595    #[rstest]
596    fn test_read_group_header_too_large() {
597        // num_in_group = MAX_GROUP_SIZE + 1
598        let count = MAX_GROUP_SIZE + 1;
599        let mut buf = vec![24, 0]; // block_length = 24
600        buf.extend_from_slice(&count.to_le_bytes());
601        let mut cursor = SbeCursor::new(&buf);
602
603        assert!(matches!(
604            cursor.read_group_header(),
605            Err(SbeDecodeError::GroupSizeTooLarge { .. })
606        ));
607    }
608
609    #[rstest]
610    fn test_read_group() {
611        // 2 items, each 4 bytes containing a u32
612        let mut buf = Vec::new();
613        buf.extend_from_slice(&100u32.to_le_bytes()); // item 0
614        buf.extend_from_slice(&200u32.to_le_bytes()); // item 1
615
616        let mut cursor = SbeCursor::new(&buf);
617        let items: Vec<u32> = cursor
618            .read_group(4, 2, super::SbeCursor::read_u32_le)
619            .unwrap();
620
621        assert_eq!(items, vec![100, 200]);
622        assert_eq!(cursor.pos(), 8);
623    }
624
625    #[rstest]
626    fn test_read_group_respects_block_length() {
627        // 2 items, block_length = 8, but we only read 4 bytes per item
628        let mut buf = Vec::new();
629        buf.extend_from_slice(&100u32.to_le_bytes());
630        buf.extend_from_slice(&[0, 0, 0, 0]); // padding
631        buf.extend_from_slice(&200u32.to_le_bytes());
632        buf.extend_from_slice(&[0, 0, 0, 0]); // padding
633
634        let mut cursor = SbeCursor::new(&buf);
635        let items: Vec<u32> = cursor
636            .read_group(8, 2, super::SbeCursor::read_u32_le)
637            .unwrap();
638
639        assert_eq!(items, vec![100, 200]);
640        assert_eq!(cursor.pos(), 16); // 2 * 8
641    }
642
643    #[rstest]
644    fn test_require_success() {
645        let buf = [1, 2, 3, 4];
646        let cursor = SbeCursor::new(&buf);
647
648        assert!(cursor.require(4).is_ok());
649        assert!(cursor.require(3).is_ok());
650    }
651
652    #[rstest]
653    fn test_require_failure() {
654        let buf = [1, 2];
655        let cursor = SbeCursor::new(&buf);
656
657        let err = cursor.require(3).unwrap_err();
658        assert_eq!(
659            err,
660            SbeDecodeError::BufferTooShort {
661                expected: 3,
662                actual: 2
663            }
664        );
665    }
666
667    #[rstest]
668    fn test_advance() {
669        let buf = [1, 2, 3, 4];
670        let mut cursor = SbeCursor::new(&buf);
671
672        cursor.advance(2).unwrap();
673        assert_eq!(cursor.pos(), 2);
674        assert_eq!(cursor.remaining(), 2);
675
676        assert!(cursor.advance(3).is_err());
677    }
678
679    #[rstest]
680    fn test_peek() {
681        let buf = [1, 2, 3, 4];
682        let mut cursor = SbeCursor::new(&buf);
683
684        assert_eq!(cursor.peek(), &[1, 2, 3, 4]);
685        cursor.advance(2).unwrap();
686        assert_eq!(cursor.peek(), &[3, 4]);
687    }
688
689    #[rstest]
690    fn test_reset() {
691        let buf = [1, 2, 3, 4];
692        let mut cursor = SbeCursor::new(&buf);
693
694        cursor.advance(3).unwrap();
695        assert_eq!(cursor.pos(), 3);
696
697        cursor.reset();
698        assert_eq!(cursor.pos(), 0);
699        assert_eq!(cursor.remaining(), 4);
700    }
701
702    #[rstest]
703    #[case::short(false)]
704    #[case::wide(true)]
705    fn test_var_bytes_preserve_binary_and_position(#[case] wide: bool) {
706        let mut buf = if wide { vec![3, 0] } else { vec![3] };
707        buf.extend_from_slice(&[0xff, 0, 0x80, 0x42]);
708        let mut read = SbeCursor::new(&buf);
709        let mut skip = read.clone();
710
711        let bytes = if wide {
712            skip.skip_var_data16().unwrap();
713            read.read_var_bytes16().unwrap()
714        } else {
715            skip.skip_var_data8().unwrap();
716            read.read_var_bytes8().unwrap()
717        };
718
719        assert_eq!(bytes, [0xff, 0, 0x80]);
720        assert_eq!(read.pos(), buf.len() - 1);
721        assert_eq!(skip.pos(), read.pos());
722        assert_eq!(read.peek(), [0x42]);
723        assert_eq!(skip.peek(), [0x42]);
724    }
725
726    #[rstest]
727    #[case::short(false)]
728    #[case::wide(true)]
729    fn test_var_bytes_reject_truncated_payload(#[case] wide: bool) {
730        let buf = if wide {
731            vec![3, 0, 0xff]
732        } else {
733            vec![3, 0xff]
734        };
735
736        let prefix = if wide { 2 } else { 1 };
737        let mut read = SbeCursor::new(&buf);
738        let mut skip = read.clone();
739
740        let (read_result, skip_result) = if wide {
741            (read.read_var_bytes16(), skip.skip_var_data16())
742        } else {
743            (read.read_var_bytes8(), skip.skip_var_data8())
744        };
745
746        let expected = SbeDecodeError::BufferTooShort {
747            expected: prefix + 3,
748            actual: buf.len(),
749        };
750
751        assert_eq!(read_result, Err(expected.clone()));
752        assert_eq!(skip_result, Err(expected));
753        assert_eq!(read.pos(), prefix);
754        assert_eq!(skip.pos(), prefix);
755    }
756
757    #[rstest]
758    fn test_read_group_propagates_item_error() {
759        let mut cursor = SbeCursor::new(&[1, 2, 3, 4]);
760
761        let result = cursor.read_group(2, 2, |entry| {
762            let value = entry.read_u8()?;
763            if value == 3 {
764                Err(SbeDecodeError::InvalidValue { field: "entry" })
765            } else {
766                Ok(value)
767            }
768        });
769
770        assert_eq!(result, Err(SbeDecodeError::InvalidValue { field: "entry" }));
771        assert_eq!(cursor.pos(), 3);
772    }
773}