Skip to main content

ironsbe_core/
header.rs

1//! SBE message header types.
2//!
3//! This module provides the standard SBE header structures:
4//! - [`MessageHeader`] - 8-byte message header
5//! - [`GroupHeader`] - 4-byte repeating group header
6//! - [`VarDataHeader`] - Variable-length data header
7
8use crate::buffer::{ReadBuffer, WriteBuffer};
9
10/// Standard SBE message header (8 bytes).
11///
12/// The message header precedes every SBE message and contains:
13/// - Block length: Size of the root block in bytes
14/// - Template ID: Message type identifier
15/// - Schema ID: Schema identifier
16/// - Version: Schema version number
17///
18/// # Wire Format
19/// ```text
20/// +0: blockLength  (u16, 2 bytes)
21/// +2: templateId   (u16, 2 bytes)
22/// +4: schemaId     (u16, 2 bytes)
23/// +6: version      (u16, 2 bytes)
24/// ```
25#[repr(C, packed)]
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
27pub struct MessageHeader {
28    /// Length of the root block in bytes.
29    pub block_length: u16,
30    /// Message template identifier.
31    pub template_id: u16,
32    /// Schema identifier.
33    pub schema_id: u16,
34    /// Schema version number.
35    pub version: u16,
36}
37
38impl MessageHeader {
39    /// Encoded length of the message header in bytes.
40    pub const ENCODED_LENGTH: usize = 8;
41
42    /// Creates a new message header with the specified values.
43    ///
44    /// # Arguments
45    /// * `block_length` - Length of the root block in bytes
46    /// * `template_id` - Message template identifier
47    /// * `schema_id` - Schema identifier
48    /// * `version` - Schema version number
49    #[must_use]
50    pub const fn new(block_length: u16, template_id: u16, schema_id: u16, version: u16) -> Self {
51        Self {
52            block_length,
53            template_id,
54            schema_id,
55            version,
56        }
57    }
58
59    /// Wraps a buffer and decodes the message header at the given offset.
60    ///
61    /// # Arguments
62    /// * `buffer` - Buffer to read from
63    /// * `offset` - Byte offset to start reading
64    ///
65    /// # Panics
66    /// Panics if the buffer is too short.
67    #[inline(always)]
68    #[must_use]
69    pub fn wrap<B: ReadBuffer + ?Sized>(buffer: &B, offset: usize) -> Self {
70        Self {
71            block_length: buffer.get_u16_le(offset),
72            template_id: buffer.get_u16_le(offset + 2),
73            schema_id: buffer.get_u16_le(offset + 4),
74            version: buffer.get_u16_le(offset + 6),
75        }
76    }
77
78    /// Encodes the message header to the buffer at the given offset.
79    ///
80    /// # Arguments
81    /// * `buffer` - Buffer to write to
82    /// * `offset` - Byte offset to start writing
83    #[inline(always)]
84    pub fn encode<B: WriteBuffer + ?Sized>(&self, buffer: &mut B, offset: usize) {
85        buffer.put_u16_le(offset, self.block_length);
86        buffer.put_u16_le(offset + 2, self.template_id);
87        buffer.put_u16_le(offset + 4, self.schema_id);
88        buffer.put_u16_le(offset + 6, self.version);
89    }
90
91    /// Returns the total message size (header + block).
92    #[must_use]
93    pub const fn message_size(&self) -> usize {
94        Self::ENCODED_LENGTH + self.block_length as usize
95    }
96}
97
98/// Repeating group header (4 bytes).
99///
100/// The group header precedes each repeating group and contains:
101/// - Block length: Size of each group entry in bytes
102/// - Num in group: Number of entries in the group
103///
104/// # Wire Format
105/// ```text
106/// +0: blockLength  (u16, 2 bytes)
107/// +2: numInGroup   (u16, 2 bytes)
108/// ```
109#[repr(C, packed)]
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
111pub struct GroupHeader {
112    /// Length of each group entry in bytes.
113    pub block_length: u16,
114    /// Number of entries in the group.
115    pub num_in_group: u16,
116}
117
118impl GroupHeader {
119    /// Encoded length of the group header in bytes.
120    pub const ENCODED_LENGTH: usize = 4;
121
122    /// Creates a new group header with the specified values.
123    ///
124    /// # Arguments
125    /// * `block_length` - Length of each group entry in bytes
126    /// * `num_in_group` - Number of entries in the group
127    #[must_use]
128    pub const fn new(block_length: u16, num_in_group: u16) -> Self {
129        Self {
130            block_length,
131            num_in_group,
132        }
133    }
134
135    /// Wraps a buffer and decodes the group header at the given offset.
136    ///
137    /// # Arguments
138    /// * `buffer` - Buffer to read from
139    /// * `offset` - Byte offset to start reading
140    ///
141    /// # Panics
142    /// Panics if the buffer is too short.
143    #[inline(always)]
144    #[must_use]
145    pub fn wrap<B: ReadBuffer + ?Sized>(buffer: &B, offset: usize) -> Self {
146        Self {
147            block_length: buffer.get_u16_le(offset),
148            num_in_group: buffer.get_u16_le(offset + 2),
149        }
150    }
151
152    /// Encodes the group header to the buffer at the given offset.
153    ///
154    /// # Arguments
155    /// * `buffer` - Buffer to write to
156    /// * `offset` - Byte offset to start writing
157    #[inline(always)]
158    pub fn encode<B: WriteBuffer + ?Sized>(&self, buffer: &mut B, offset: usize) {
159        buffer.put_u16_le(offset, self.block_length);
160        buffer.put_u16_le(offset + 2, self.num_in_group);
161    }
162
163    /// Returns the total size of the group (header + all entries).
164    #[must_use]
165    pub const fn group_size(&self) -> usize {
166        Self::ENCODED_LENGTH + (self.block_length as usize * self.num_in_group as usize)
167    }
168
169    /// Returns true if the group is empty.
170    #[must_use]
171    pub const fn is_empty(&self) -> bool {
172        self.num_in_group == 0
173    }
174}
175
176/// Variable-length data header.
177///
178/// The var data header precedes variable-length data fields and contains
179/// the length of the data that follows.
180///
181/// # Wire Format
182/// ```text
183/// +0: length (u16, 2 bytes) - for standard varDataEncoding
184/// ```
185///
186/// Note: Some schemas may use u8 or u32 for the length field.
187#[repr(C, packed)]
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
189pub struct VarDataHeader {
190    /// Length of the variable data in bytes.
191    pub length: u16,
192}
193
194impl VarDataHeader {
195    /// Encoded length of the var data header in bytes (u16 length).
196    pub const ENCODED_LENGTH: usize = 2;
197
198    /// Creates a new var data header with the specified length.
199    ///
200    /// # Arguments
201    /// * `length` - Length of the variable data in bytes
202    #[must_use]
203    pub const fn new(length: u16) -> Self {
204        Self { length }
205    }
206
207    /// Wraps a buffer and decodes the var data header at the given offset.
208    ///
209    /// # Arguments
210    /// * `buffer` - Buffer to read from
211    /// * `offset` - Byte offset to start reading
212    ///
213    /// # Panics
214    /// Panics if the buffer is too short.
215    #[inline(always)]
216    #[must_use]
217    pub fn wrap<B: ReadBuffer + ?Sized>(buffer: &B, offset: usize) -> Self {
218        Self {
219            length: buffer.get_u16_le(offset),
220        }
221    }
222
223    /// Encodes the var data header to the buffer at the given offset.
224    ///
225    /// # Arguments
226    /// * `buffer` - Buffer to write to
227    /// * `offset` - Byte offset to start writing
228    #[inline(always)]
229    pub fn encode<B: WriteBuffer + ?Sized>(&self, buffer: &mut B, offset: usize) {
230        buffer.put_u16_le(offset, self.length);
231    }
232
233    /// Returns the total size (header + data).
234    #[must_use]
235    pub const fn total_size(&self) -> usize {
236        Self::ENCODED_LENGTH + self.length as usize
237    }
238
239    /// Returns true if the data is empty.
240    #[must_use]
241    pub const fn is_empty(&self) -> bool {
242        self.length == 0
243    }
244}
245
246/// Variable-length data header with u8 length (1 byte).
247#[repr(C, packed)]
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249pub struct VarDataHeader8 {
250    /// Length of the variable data in bytes.
251    pub length: u8,
252}
253
254impl VarDataHeader8 {
255    /// Encoded length of the var data header in bytes.
256    pub const ENCODED_LENGTH: usize = 1;
257
258    /// Creates a new var data header with the specified length.
259    #[must_use]
260    pub const fn new(length: u8) -> Self {
261        Self { length }
262    }
263
264    /// Wraps a buffer and decodes the var data header at the given offset.
265    #[inline(always)]
266    #[must_use]
267    pub fn wrap<B: ReadBuffer + ?Sized>(buffer: &B, offset: usize) -> Self {
268        Self {
269            length: buffer.get_u8(offset),
270        }
271    }
272
273    /// Encodes the var data header to the buffer at the given offset.
274    #[inline(always)]
275    pub fn encode<B: WriteBuffer + ?Sized>(&self, buffer: &mut B, offset: usize) {
276        buffer.put_u8(offset, self.length);
277    }
278}
279
280/// Variable-length data header with u32 length (4 bytes).
281#[repr(C, packed)]
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub struct VarDataHeader32 {
284    /// Length of the variable data in bytes.
285    pub length: u32,
286}
287
288impl VarDataHeader32 {
289    /// Encoded length of the var data header in bytes.
290    pub const ENCODED_LENGTH: usize = 4;
291
292    /// Creates a new var data header with the specified length.
293    #[must_use]
294    pub const fn new(length: u32) -> Self {
295        Self { length }
296    }
297
298    /// Wraps a buffer and decodes the var data header at the given offset.
299    #[inline(always)]
300    #[must_use]
301    pub fn wrap<B: ReadBuffer + ?Sized>(buffer: &B, offset: usize) -> Self {
302        Self {
303            length: buffer.get_u32_le(offset),
304        }
305    }
306
307    /// Encodes the var data header to the buffer at the given offset.
308    #[inline(always)]
309    pub fn encode<B: WriteBuffer + ?Sized>(&self, buffer: &mut B, offset: usize) {
310        buffer.put_u32_le(offset, self.length);
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317    use crate::buffer::AlignedBuffer;
318
319    #[test]
320    fn test_message_header_encode_decode() {
321        let mut buf: AlignedBuffer<16> = AlignedBuffer::new();
322        let header = MessageHeader::new(64, 1, 100, 1);
323
324        header.encode(&mut buf, 0);
325        let decoded = MessageHeader::wrap(&buf, 0);
326
327        assert_eq!(header, decoded);
328        assert_eq!({ decoded.block_length }, 64);
329        assert_eq!({ decoded.template_id }, 1);
330        assert_eq!({ decoded.schema_id }, 100);
331        assert_eq!({ decoded.version }, 1);
332    }
333
334    #[test]
335    fn test_message_header_size() {
336        assert_eq!(MessageHeader::ENCODED_LENGTH, 8);
337        let header = MessageHeader::new(64, 1, 100, 1);
338        assert_eq!(header.message_size(), 72);
339    }
340
341    #[test]
342    fn test_group_header_encode_decode() {
343        let mut buf: AlignedBuffer<16> = AlignedBuffer::new();
344        let header = GroupHeader::new(32, 5);
345
346        header.encode(&mut buf, 0);
347        let decoded = GroupHeader::wrap(&buf, 0);
348
349        assert_eq!(header, decoded);
350        assert_eq!({ decoded.block_length }, 32);
351        assert_eq!({ decoded.num_in_group }, 5);
352    }
353
354    #[test]
355    fn test_group_header_size() {
356        assert_eq!(GroupHeader::ENCODED_LENGTH, 4);
357        let header = GroupHeader::new(32, 5);
358        assert_eq!(header.group_size(), 4 + 32 * 5);
359        assert!(!header.is_empty());
360
361        let empty = GroupHeader::new(32, 0);
362        assert!(empty.is_empty());
363    }
364
365    #[test]
366    fn test_var_data_header_encode_decode() {
367        let mut buf: AlignedBuffer<16> = AlignedBuffer::new();
368        let header = VarDataHeader::new(256);
369
370        header.encode(&mut buf, 0);
371        let decoded = VarDataHeader::wrap(&buf, 0);
372
373        assert_eq!(header, decoded);
374        assert_eq!({ decoded.length }, 256);
375        assert_eq!(decoded.total_size(), 258);
376    }
377
378    #[test]
379    fn test_var_data_header8() {
380        let mut buf: AlignedBuffer<16> = AlignedBuffer::new();
381        let header = VarDataHeader8::new(100);
382
383        header.encode(&mut buf, 0);
384        let decoded = VarDataHeader8::wrap(&buf, 0);
385
386        assert_eq!(header, decoded);
387        assert_eq!(VarDataHeader8::ENCODED_LENGTH, 1);
388    }
389
390    #[test]
391    fn test_var_data_header32() {
392        let mut buf: AlignedBuffer<16> = AlignedBuffer::new();
393        let header = VarDataHeader32::new(1_000_000);
394
395        header.encode(&mut buf, 0);
396        let decoded = VarDataHeader32::wrap(&buf, 0);
397
398        assert_eq!(header, decoded);
399        assert_eq!(VarDataHeader32::ENCODED_LENGTH, 4);
400    }
401
402    #[test]
403    fn test_header_wire_format() {
404        let mut buf: AlignedBuffer<16> = AlignedBuffer::new();
405        let header = MessageHeader::new(0x0102, 0x0304, 0x0506, 0x0708);
406        header.encode(&mut buf, 0);
407
408        // Verify little-endian encoding
409        assert_eq!(buf.get_u8(0), 0x02); // block_length low byte
410        assert_eq!(buf.get_u8(1), 0x01); // block_length high byte
411        assert_eq!(buf.get_u8(2), 0x04); // template_id low byte
412        assert_eq!(buf.get_u8(3), 0x03); // template_id high byte
413        assert_eq!(buf.get_u8(4), 0x06); // schema_id low byte
414        assert_eq!(buf.get_u8(5), 0x05); // schema_id high byte
415        assert_eq!(buf.get_u8(6), 0x08); // version low byte
416        assert_eq!(buf.get_u8(7), 0x07); // version high byte
417    }
418}