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
222    #[test]
223    fn test_decode_error_display() {
224        let err = DecodeError::BufferTooShort {
225            required: 100,
226            available: 50,
227        };
228        assert!(err.to_string().contains("100"));
229        assert!(err.to_string().contains("50"));
230
231        let err = DecodeError::TemplateMismatch {
232            expected: 1,
233            actual: 2,
234        };
235        assert!(err.to_string().contains("expected 1"));
236        assert!(err.to_string().contains("actual 2"));
237    }
238
239    #[test]
240    fn test_decode_error_equality() {
241        let err1 = DecodeError::TemplateMismatch {
242            expected: 1,
243            actual: 2,
244        };
245        let err2 = DecodeError::TemplateMismatch {
246            expected: 1,
247            actual: 2,
248        };
249        assert_eq!(err1, err2);
250    }
251}