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}