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