Skip to main content

nautilus_serialization/sbe/
primitives.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//! Generic SBE primitive decoders.
17
18use super::{MAX_GROUP_SIZE, SbeCursor, SbeDecodeError};
19
20/// Group header encoding (u16 block length + u32 count).
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct GroupSizeEncoding {
23    /// Encoded block length of each group entry.
24    pub block_length: u16,
25    /// Number of entries in the group.
26    pub num_in_group: u32,
27}
28
29impl GroupSizeEncoding {
30    /// Encoded length in bytes.
31    pub const ENCODED_LENGTH: usize = 6;
32
33    /// Decodes a group header from `buf`.
34    ///
35    /// # Errors
36    ///
37    /// Returns `BufferTooShort` if fewer than 6 bytes are available and
38    /// `GroupSizeTooLarge` when `num_in_group` exceeds [`MAX_GROUP_SIZE`].
39    pub fn decode(buf: &[u8]) -> Result<Self, SbeDecodeError> {
40        if buf.len() < Self::ENCODED_LENGTH {
41            return Err(SbeDecodeError::BufferTooShort {
42                expected: Self::ENCODED_LENGTH,
43                actual: buf.len(),
44            });
45        }
46
47        let num_in_group = u32::from_le_bytes([buf[2], buf[3], buf[4], buf[5]]);
48        if num_in_group > MAX_GROUP_SIZE {
49            return Err(SbeDecodeError::GroupSizeTooLarge {
50                count: num_in_group,
51                max: MAX_GROUP_SIZE,
52            });
53        }
54
55        Ok(Self {
56            block_length: u16::from_le_bytes([buf[0], buf[1]]),
57            num_in_group,
58        })
59    }
60}
61
62/// Compact group header encoding (u16 block length + u16 count).
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct GroupSize16Encoding {
65    /// Encoded block length of each group entry.
66    pub block_length: u16,
67    /// Number of entries in the group.
68    pub num_in_group: u16,
69}
70
71impl GroupSize16Encoding {
72    /// Encoded length in bytes.
73    pub const ENCODED_LENGTH: usize = 4;
74
75    /// Decodes a compact group header from `buf`.
76    ///
77    /// # Errors
78    ///
79    /// Returns `BufferTooShort` if fewer than 4 bytes are available and
80    /// `GroupSizeTooLarge` when `num_in_group` exceeds [`MAX_GROUP_SIZE`].
81    pub fn decode(buf: &[u8]) -> Result<Self, SbeDecodeError> {
82        if buf.len() < Self::ENCODED_LENGTH {
83            return Err(SbeDecodeError::BufferTooShort {
84                expected: Self::ENCODED_LENGTH,
85                actual: buf.len(),
86            });
87        }
88
89        let num_in_group = u16::from_le_bytes([buf[2], buf[3]]);
90        if u32::from(num_in_group) > MAX_GROUP_SIZE {
91            return Err(SbeDecodeError::GroupSizeTooLarge {
92                count: u32::from(num_in_group),
93                max: MAX_GROUP_SIZE,
94            });
95        }
96
97        Ok(Self {
98            block_length: u16::from_le_bytes([buf[0], buf[1]]),
99            num_in_group,
100        })
101    }
102}
103
104/// Decodes a varString8 field (u8 length + UTF-8 bytes).
105///
106/// Returns the decoded `&str` and number of bytes consumed.
107///
108/// # Errors
109///
110/// Returns `BufferTooShort` when the buffer does not contain the full field and
111/// `InvalidUtf8` when the payload bytes are not valid UTF-8.
112pub fn decode_var_string8(buf: &[u8]) -> Result<(&str, usize), SbeDecodeError> {
113    let mut cursor = SbeCursor::new(buf);
114    let value = cursor.read_var_string8_ref()?;
115    Ok((value, cursor.pos()))
116}
117
118#[cfg(test)]
119mod tests {
120    use rstest::rstest;
121
122    use super::*;
123
124    #[rstest]
125    fn test_group_size_decode_too_short() {
126        let err = GroupSizeEncoding::decode(&[0, 0, 0]).unwrap_err();
127        assert_eq!(
128            err,
129            SbeDecodeError::BufferTooShort {
130                expected: 6,
131                actual: 3
132            }
133        );
134    }
135
136    #[rstest]
137    fn test_group_size_decode_too_large() {
138        let mut buf = [0u8; GroupSizeEncoding::ENCODED_LENGTH];
139        buf[2..6].copy_from_slice(&(MAX_GROUP_SIZE + 1).to_le_bytes());
140        let err = GroupSizeEncoding::decode(&buf).unwrap_err();
141        assert_eq!(
142            err,
143            SbeDecodeError::GroupSizeTooLarge {
144                count: MAX_GROUP_SIZE + 1,
145                max: MAX_GROUP_SIZE
146            }
147        );
148    }
149
150    #[rstest]
151    fn test_group_size_16_decode_too_large() {
152        let mut buf = [0u8; GroupSize16Encoding::ENCODED_LENGTH];
153        buf[2..4].copy_from_slice(&(MAX_GROUP_SIZE as u16 + 1).to_le_bytes());
154        let err = GroupSize16Encoding::decode(&buf).unwrap_err();
155        assert_eq!(
156            err,
157            SbeDecodeError::GroupSizeTooLarge {
158                count: MAX_GROUP_SIZE + 1,
159                max: MAX_GROUP_SIZE
160            }
161        );
162    }
163
164    #[rstest]
165    fn test_decode_var_string8_valid() {
166        let buf = [5u8, b'H', b'E', b'L', b'L', b'O'];
167        let (s, consumed) = decode_var_string8(&buf).unwrap();
168        assert_eq!(s, "HELLO");
169        assert_eq!(consumed, 6);
170    }
171
172    #[rstest]
173    fn test_decode_var_string8_invalid_utf8() {
174        let buf = [2u8, 0xFF, 0xFF];
175        let err = decode_var_string8(&buf).unwrap_err();
176        assert_eq!(err, SbeDecodeError::InvalidUtf8);
177    }
178
179    #[rstest]
180    #[case(0)]
181    #[case(1)]
182    #[case(MAX_GROUP_SIZE)]
183    fn test_group_headers_preserve_fields(#[case] count: u32) {
184        let block_length = 0x1234_u16;
185        let mut wide = block_length.to_le_bytes().to_vec();
186        wide.extend_from_slice(&count.to_le_bytes());
187        let mut compact = block_length.to_le_bytes().to_vec();
188        compact.extend_from_slice(&u16::try_from(count).unwrap().to_le_bytes());
189
190        assert_eq!(
191            GroupSizeEncoding::decode(&wide),
192            Ok(GroupSizeEncoding {
193                block_length,
194                num_in_group: count,
195            })
196        );
197        assert_eq!(
198            GroupSize16Encoding::decode(&compact),
199            Ok(GroupSize16Encoding {
200                block_length,
201                num_in_group: count as u16,
202            })
203        );
204    }
205
206    #[rstest]
207    #[case(0)]
208    #[case(1)]
209    #[case(2)]
210    #[case(3)]
211    fn test_group_size_16_decode_too_short(#[case] len: usize) {
212        assert_eq!(
213            GroupSize16Encoding::decode(&[0; 4][..len]),
214            Err(SbeDecodeError::BufferTooShort {
215                expected: 4,
216                actual: len
217            })
218        );
219    }
220}