Skip to main content

ironsbe_core/
decoder.rs

1//! Decoder traits for SBE messages.
2//!
3//! This module provides the [`SbeDecoder`] trait for zero-copy message decoding.
4
5use crate::header::MessageHeader;
6
7/// Error type for decoding operations.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum DecodeError {
10    /// Buffer is too short for the message.
11    BufferTooShort {
12        /// Required buffer size in bytes.
13        required: usize,
14        /// Available buffer size in bytes.
15        available: usize,
16    },
17    /// Template ID does not match expected value.
18    TemplateMismatch {
19        /// Expected template ID.
20        expected: u16,
21        /// Actual template ID found.
22        actual: u16,
23    },
24    /// Schema ID does not match expected value.
25    SchemaMismatch {
26        /// Expected schema ID.
27        expected: u16,
28        /// Actual schema ID found.
29        actual: u16,
30    },
31    /// Invalid enum value encountered.
32    InvalidEnumValue {
33        /// Field tag/ID.
34        tag: u16,
35        /// Invalid value.
36        value: u64,
37    },
38    /// Invalid UTF-8 encoding.
39    InvalidUtf8 {
40        /// Byte offset where invalid UTF-8 was found.
41        offset: usize,
42    },
43    /// Version is not supported.
44    UnsupportedVersion {
45        /// Message version.
46        version: u16,
47        /// Minimum supported version.
48        min_supported: u16,
49    },
50}
51
52impl std::fmt::Display for DecodeError {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            Self::BufferTooShort {
56                required,
57                available,
58            } => {
59                write!(
60                    f,
61                    "buffer too short: required {} bytes, available {} bytes",
62                    required, available
63                )
64            }
65            Self::TemplateMismatch { expected, actual } => {
66                write!(
67                    f,
68                    "template mismatch: expected {}, actual {}",
69                    expected, actual
70                )
71            }
72            Self::SchemaMismatch { expected, actual } => {
73                write!(
74                    f,
75                    "schema mismatch: expected {}, actual {}",
76                    expected, actual
77                )
78            }
79            Self::InvalidEnumValue { tag, value } => {
80                write!(f, "invalid enum value: tag {}, value {}", tag, value)
81            }
82            Self::InvalidUtf8 { offset } => {
83                write!(f, "invalid UTF-8 at offset {}", offset)
84            }
85            Self::UnsupportedVersion {
86                version,
87                min_supported,
88            } => {
89                write!(
90                    f,
91                    "unsupported version: {} (min supported: {})",
92                    version, min_supported
93                )
94            }
95        }
96    }
97}
98
99impl std::error::Error for DecodeError {}
100
101/// Trait for zero-copy SBE message decoders.
102///
103/// Implementations wrap a byte buffer and provide field accessors that
104/// read directly from the buffer without copying data.
105///
106/// # Type Parameters
107/// * `'a` - Lifetime of the underlying buffer
108///
109/// # Example
110/// ```ignore
111/// // Generated decoder usage
112/// let decoder = NewOrderSingleDecoder::wrap(&buffer, 0, SCHEMA_VERSION);
113/// let symbol = decoder.symbol();
114/// let quantity = decoder.quantity();
115/// ```
116pub trait SbeDecoder<'a>: Sized {
117    /// Schema template ID for this message type.
118    const TEMPLATE_ID: u16;
119
120    /// Schema ID.
121    const SCHEMA_ID: u16;
122
123    /// Schema version.
124    const SCHEMA_VERSION: u16;
125
126    /// Block length (fixed portion size in bytes).
127    const BLOCK_LENGTH: u16;
128
129    /// Wraps a buffer to decode a message (zero-copy).
130    ///
131    /// # Arguments
132    /// * `buffer` - Byte buffer containing the message
133    /// * `offset` - Byte offset where the message starts (after header)
134    /// * `acting_version` - Version to use for decoding (for compatibility)
135    ///
136    /// # Returns
137    /// A decoder instance wrapping the buffer.
138    fn wrap(buffer: &'a [u8], offset: usize, acting_version: u16) -> Self;
139
140    /// Returns the encoded length of the message in bytes.
141    ///
142    /// This includes the header and all fixed/variable portions.
143    fn encoded_length(&self) -> usize;
144
145    /// Validates that the message header matches the expected template.
146    ///
147    /// # Arguments
148    /// * `header` - Message header to validate
149    ///
150    /// # Errors
151    /// Returns an error if the template ID or schema ID doesn't match.
152    fn validate_header(header: &MessageHeader) -> Result<(), DecodeError> {
153        if header.template_id != Self::TEMPLATE_ID {
154            return Err(DecodeError::TemplateMismatch {
155                expected: Self::TEMPLATE_ID,
156                actual: header.template_id,
157            });
158        }
159        if header.schema_id != Self::SCHEMA_ID {
160            return Err(DecodeError::SchemaMismatch {
161                expected: Self::SCHEMA_ID,
162                actual: header.schema_id,
163            });
164        }
165        Ok(())
166    }
167
168    /// Decodes a message from a buffer, including header validation.
169    ///
170    /// # Arguments
171    /// * `buffer` - Byte buffer containing the message (starting with header)
172    ///
173    /// # Errors
174    /// Returns an error if the header is invalid or buffer is too short.
175    fn decode(buffer: &'a [u8]) -> Result<Self, DecodeError> {
176        if buffer.len() < MessageHeader::ENCODED_LENGTH {
177            return Err(DecodeError::BufferTooShort {
178                required: MessageHeader::ENCODED_LENGTH,
179                available: buffer.len(),
180            });
181        }
182
183        let header = MessageHeader::wrap(buffer, 0);
184        Self::validate_header(&header)?;
185
186        let required_len = MessageHeader::ENCODED_LENGTH + header.block_length as usize;
187        if buffer.len() < required_len {
188            return Err(DecodeError::BufferTooShort {
189                required: required_len,
190                available: buffer.len(),
191            });
192        }
193
194        Ok(Self::wrap(
195            buffer,
196            MessageHeader::ENCODED_LENGTH,
197            header.version,
198        ))
199    }
200}
201
202/// Trait for message dispatching based on template ID.
203///
204/// This trait allows routing messages to appropriate handlers based on
205/// the template ID in the message header.
206pub trait MessageDispatch {
207    /// Dispatches a message to the appropriate handler.
208    ///
209    /// # Arguments
210    /// * `header` - Message header
211    /// * `buffer` - Full message buffer (including header)
212    ///
213    /// # Returns
214    /// Result indicating success or failure of dispatch.
215    fn dispatch(&self, header: &MessageHeader, buffer: &[u8]) -> Result<(), DecodeError>;
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use crate::buffer::{AlignedBuffer, ReadBuffer};
222
223    #[test]
224    fn test_decode_error_display_buffer_too_short() {
225        let err = DecodeError::BufferTooShort {
226            required: 100,
227            available: 50,
228        };
229        let msg = err.to_string();
230        assert!(msg.contains("100"));
231        assert!(msg.contains("50"));
232        assert!(msg.contains("buffer too short"));
233    }
234
235    #[test]
236    fn test_decode_error_display_template_mismatch() {
237        let err = DecodeError::TemplateMismatch {
238            expected: 1,
239            actual: 2,
240        };
241        let msg = err.to_string();
242        assert!(msg.contains("expected 1"));
243        assert!(msg.contains("actual 2"));
244        assert!(msg.contains("template mismatch"));
245    }
246
247    #[test]
248    fn test_decode_error_display_schema_mismatch() {
249        let err = DecodeError::SchemaMismatch {
250            expected: 100,
251            actual: 200,
252        };
253        let msg = err.to_string();
254        assert!(msg.contains("100"));
255        assert!(msg.contains("200"));
256        assert!(msg.contains("schema mismatch"));
257    }
258
259    #[test]
260    fn test_decode_error_display_invalid_enum() {
261        let err = DecodeError::InvalidEnumValue { tag: 55, value: 99 };
262        let msg = err.to_string();
263        assert!(msg.contains("55"));
264        assert!(msg.contains("99"));
265        assert!(msg.contains("invalid enum"));
266    }
267
268    #[test]
269    fn test_decode_error_display_invalid_utf8() {
270        let err = DecodeError::InvalidUtf8 { offset: 42 };
271        let msg = err.to_string();
272        assert!(msg.contains("42"));
273        assert!(msg.contains("UTF-8"));
274    }
275
276    #[test]
277    fn test_decode_error_display_unsupported_version() {
278        let err = DecodeError::UnsupportedVersion {
279            version: 5,
280            min_supported: 1,
281        };
282        let msg = err.to_string();
283        assert!(msg.contains("5"));
284        assert!(msg.contains("1"));
285        assert!(msg.contains("unsupported version"));
286    }
287
288    #[test]
289    fn test_decode_error_equality() {
290        let err1 = DecodeError::TemplateMismatch {
291            expected: 1,
292            actual: 2,
293        };
294        let err2 = DecodeError::TemplateMismatch {
295            expected: 1,
296            actual: 2,
297        };
298        assert_eq!(err1, err2);
299
300        let err3 = DecodeError::TemplateMismatch {
301            expected: 1,
302            actual: 3,
303        };
304        assert_ne!(err1, err3);
305    }
306
307    #[test]
308    fn test_decode_error_clone() {
309        let err = DecodeError::BufferTooShort {
310            required: 100,
311            available: 50,
312        };
313        let cloned = err.clone();
314        assert_eq!(err, cloned);
315    }
316
317    #[test]
318    fn test_decode_error_debug() {
319        let err = DecodeError::InvalidEnumValue { tag: 1, value: 2 };
320        let debug_str = format!("{:?}", err);
321        assert!(debug_str.contains("InvalidEnumValue"));
322    }
323
324    /// Test decoder implementation for testing purposes.
325    struct TestDecoder<'a> {
326        buffer: &'a [u8],
327        offset: usize,
328        #[allow(dead_code)]
329        acting_version: u16,
330    }
331
332    impl<'a> SbeDecoder<'a> for TestDecoder<'a> {
333        const TEMPLATE_ID: u16 = 1;
334        const SCHEMA_ID: u16 = 100;
335        const SCHEMA_VERSION: u16 = 1;
336        const BLOCK_LENGTH: u16 = 16;
337
338        fn wrap(buffer: &'a [u8], offset: usize, acting_version: u16) -> Self {
339            Self {
340                buffer,
341                offset,
342                acting_version,
343            }
344        }
345
346        fn encoded_length(&self) -> usize {
347            MessageHeader::ENCODED_LENGTH + Self::BLOCK_LENGTH as usize
348        }
349    }
350
351    #[test]
352    fn test_validate_header_success() {
353        let header = MessageHeader::new(16, 1, 100, 1);
354        assert!(TestDecoder::validate_header(&header).is_ok());
355    }
356
357    #[test]
358    fn test_validate_header_template_mismatch() {
359        let header = MessageHeader::new(16, 99, 100, 1);
360        let result = TestDecoder::validate_header(&header);
361        assert!(matches!(result, Err(DecodeError::TemplateMismatch { .. })));
362    }
363
364    #[test]
365    fn test_validate_header_schema_mismatch() {
366        let header = MessageHeader::new(16, 1, 999, 1);
367        let result = TestDecoder::validate_header(&header);
368        assert!(matches!(result, Err(DecodeError::SchemaMismatch { .. })));
369    }
370
371    #[test]
372    fn test_decode_buffer_too_short_for_header() {
373        let buffer = [0u8; 4]; // Less than header size
374        let result = TestDecoder::decode(&buffer);
375        assert!(matches!(result, Err(DecodeError::BufferTooShort { .. })));
376    }
377
378    #[test]
379    fn test_decode_buffer_too_short_for_message() {
380        let mut buffer = AlignedBuffer::<16>::new();
381        let header = MessageHeader::new(100, 1, 100, 1); // block_length = 100
382        header.encode(&mut buffer, 0);
383
384        let result = TestDecoder::decode(buffer.as_slice());
385        assert!(matches!(result, Err(DecodeError::BufferTooShort { .. })));
386    }
387
388    #[test]
389    fn test_decode_success() {
390        let mut buffer = AlignedBuffer::<32>::new();
391        let header = MessageHeader::new(16, 1, 100, 1);
392        header.encode(&mut buffer, 0);
393
394        let result = TestDecoder::decode(buffer.as_slice());
395        assert!(result.is_ok());
396        let decoder = result.unwrap();
397        assert_eq!(decoder.encoded_length(), 24); // 8 header + 16 block
398    }
399
400    #[test]
401    fn test_decoder_wrap() {
402        let buffer = [0u8; 32];
403        let decoder = TestDecoder::wrap(&buffer, 8, 1);
404        assert_eq!(decoder.offset, 8);
405        assert_eq!(decoder.buffer.len(), 32);
406    }
407}