hotfix_encoding/
raw_decoder.rs

1use std::ops::Range;
2
3use crate::config::{Config, GetConfig};
4use crate::error::DecodeError;
5use crate::utils;
6
7/// An immutable view over the contents of a FIX message by a [`RawDecoder`].
8#[derive(Debug)]
9pub struct RawFrame<T> {
10    /// Raw, untouched contents of the message. Includes everything from `BeginString <8>` up to
11    /// `CheckSum <8>`.
12    pub data: T,
13    /// The range of bytes that address the value of `BeginString <8>`.
14    pub begin_string: Range<usize>,
15    /// The range of bytes that address all contents after `MsgType <35>` and before `CheckSum
16    /// <10>`.
17    pub payload: Range<usize>,
18}
19
20impl<T> RawFrame<T>
21where
22    T: AsRef<[u8]>,
23{
24    /// Returns an immutable reference to the raw contents of `self`.
25    ///
26    /// # Examples
27    ///
28    /// ```
29    /// use hotfix_encoding::config::{Config, GetConfig};
30    /// use hotfix_encoding::raw_decoder::RawDecoder;
31    ///
32    /// let mut decoder = RawDecoder::new();
33    /// decoder.config_mut().separator = b'|';
34    /// let data = b"8=FIX.4.2|9=42|35=0|49=A|56=B|34=12|52=20100304-07:59:30|10=022|";
35    /// let message = decoder.decode(data).unwrap();
36    ///
37    /// assert_eq!(message.as_bytes(), data);
38    /// ```
39    pub fn as_bytes(&self) -> &[u8] {
40        self.data.as_ref()
41    }
42
43    /// Returns an immutable reference to the `BeginString <8>` field value of
44    /// `self`.
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use hotfix_encoding::config::{Config, GetConfig};
50    /// use hotfix_encoding::raw_decoder::RawDecoder;
51    ///
52    /// let mut decoder = RawDecoder::new();
53    /// decoder.config_mut().separator = b'|';
54    /// let data = b"8=FIX.4.2|9=42|35=0|49=A|56=B|34=12|52=20100304-07:59:30|10=022|";
55    /// let message = decoder.decode(data).unwrap();
56    ///
57    /// assert_eq!(message.begin_string(), b"FIX.4.2");
58    /// ```
59    pub fn begin_string(&self) -> &[u8] {
60        &self.as_bytes()[self.begin_string.clone()]
61    }
62
63    /// Returns an immutable reference to the payload of `self`. In this
64    /// context, "payload" means all fields besides
65    ///
66    /// - `BeginString <8>`;
67    /// - `BodyLength <9>`;
68    /// - `CheckSum <10>`.
69    ///
70    /// According to this definition, the payload may also contain fields that are
71    /// technically part of `StandardHeader` and `StandardTrailer`, i.e. payload
72    /// and body and *not* synonyms.
73    ///
74    /// ```
75    /// use hotfix_encoding::config::{Config, GetConfig};
76    /// use hotfix_encoding::raw_decoder::RawDecoder;
77    ///
78    /// let mut decoder = RawDecoder::new();
79    /// decoder.config_mut().separator = b'|';
80    /// let data = b"8=FIX.4.2|9=42|35=0|49=A|56=B|34=12|52=20100304-07:59:30|10=022|";
81    /// let message = decoder.decode(data).unwrap();
82    ///
83    /// assert_eq!(message.payload().len(), 42);
84    /// ```
85    pub fn payload(&self) -> &[u8] {
86        &self.as_bytes()[self.payload.clone()]
87    }
88}
89
90/// A bare-bones FIX decoder for low-level message handling.
91///
92/// [`RawDecoder`] is the fundamental building block for building higher-level
93/// FIX decoder. It allows for decoding of arbitrary payloads and only "hides"
94/// `BodyLength (9)` and `CheckSum (10)` to the final user. Everything else is
95/// left to the user to deal with.
96#[derive(Debug, Clone, Default)]
97pub struct RawDecoder<C = Config> {
98    config: C,
99}
100
101impl RawDecoder {
102    /// Creates a new [`RawDecoder`] with default configuration options.
103    pub fn new() -> Self {
104        Self::default()
105    }
106
107    /// Does minimal parsing on `data` and returns a [`RawFrame`] if it's valid.
108    pub fn decode<T>(&self, src: T) -> Result<RawFrame<T>, DecodeError>
109    where
110        T: AsRef<[u8]>,
111    {
112        let data = src.as_ref();
113        let len = data.len();
114        if len < utils::MIN_FIX_MESSAGE_LEN_IN_BYTES {
115            return Err(DecodeError::Invalid);
116        }
117
118        let header_info =
119            HeaderInfo::parse(data, self.config().separator).ok_or(DecodeError::Invalid)?;
120
121        utils::verify_body_length(
122            data,
123            header_info.field_1.end + 1,
124            header_info.nominal_body_len,
125        )?;
126
127        if self.config.verify_checksum && self.config.separator == b'\x01' {
128            utils::verify_checksum(data)?;
129        }
130
131        Ok(RawFrame {
132            data: src,
133            begin_string: header_info.field_0,
134            payload: header_info.field_1.end + 1..len - utils::FIELD_CHECKSUM_LEN_IN_BYTES,
135        })
136    }
137}
138
139impl<C> GetConfig for RawDecoder<C> {
140    type Config = C;
141
142    fn config(&self) -> &C {
143        &self.config
144    }
145
146    fn config_mut(&mut self) -> &mut C {
147        &mut self.config
148    }
149}
150
151#[derive(Debug)]
152pub enum ParserState {
153    Empty,
154    Header(HeaderInfo, usize),
155    Failed,
156}
157
158#[derive(Debug, Clone)]
159pub struct HeaderInfo {
160    pub(crate) field_0: Range<usize>,
161    pub(crate) field_1: Range<usize>,
162    pub(crate) nominal_body_len: usize,
163}
164
165impl HeaderInfo {
166    pub fn parse(data: &[u8], separator: u8) -> Option<Self> {
167        let mut info = Self {
168            field_0: 0..1,
169            field_1: 0..1,
170            nominal_body_len: 0,
171        };
172
173        let mut iterator = data.iter();
174        let mut find_byte = |byte| iterator.position(|b| *b == byte);
175        let mut i = 0;
176
177        i += find_byte(b'=')? + 1;
178        info.field_0.start = i;
179        i += find_byte(separator)?;
180        info.field_0.end = i;
181        i += 1;
182
183        i += find_byte(b'=')? + 1;
184        info.field_1.start = i;
185        i += find_byte(separator)?;
186        info.field_1.end = i;
187
188        for byte in &data[info.field_1.clone()] {
189            info.nominal_body_len = info
190                .nominal_body_len
191                .wrapping_mul(10)
192                .wrapping_add(byte.wrapping_sub(b'0') as usize);
193        }
194
195        Some(info)
196    }
197}
198
199#[cfg(test)]
200mod test {
201    use super::*;
202
203    fn new_decoder() -> RawDecoder {
204        let config = Config {
205            separator: b'|',
206            ..Config::default()
207        };
208
209        let mut decoder = RawDecoder::new();
210        *decoder.config_mut() = config;
211        decoder
212    }
213
214    #[test]
215    fn empty_message_is_invalid() {
216        let decoder = new_decoder();
217        assert!(matches!(
218            decoder.decode(&[] as &[u8]),
219            Err(DecodeError::Invalid)
220        ));
221    }
222
223    #[test]
224    fn sample_message_is_valid() {
225        let decoder = new_decoder();
226        let msg = "8=FIX.4.2|9=40|35=D|49=AFUNDMGR|56=ABROKER|15=USD|59=0|10=091|".as_bytes();
227        let frame = decoder.decode(msg).unwrap();
228        assert_eq!(frame.begin_string(), b"FIX.4.2");
229        assert_eq!(frame.payload(), b"35=D|49=AFUNDMGR|56=ABROKER|15=USD|59=0|");
230    }
231
232    #[test]
233    fn message_with_only_msg_type_tag_is_valid() {
234        let decoder = new_decoder();
235        let msg = "8=?|9=5|35=?|10=183|".as_bytes();
236        let frame = decoder.decode(msg).unwrap();
237        assert_eq!(frame.begin_string(), b"?");
238        assert_eq!(frame.payload(), b"35=?|");
239    }
240
241    #[test]
242    fn message_with_empty_payload_is_invalid() {
243        let decoder = new_decoder();
244        let msg = "8=?|9=5|10=082|".as_bytes();
245        assert!(matches!(decoder.decode(msg), Err(DecodeError::Invalid)));
246    }
247
248    #[test]
249    fn message_with_bad_checksum_is_invalid() {
250        let mut decoder = new_decoder();
251        decoder.config_mut().separator = 0x01;
252        decoder.config_mut().verify_checksum = true;
253        let msg =
254            "8=FIX.4.2|9=40|35=D|49=AFUNDMGR|56=ABROKER|15=USD|59=0|10=000|".replace('|', "\u{01}");
255        assert!(matches!(decoder.decode(&msg), Err(DecodeError::CheckSum)));
256    }
257
258    #[test]
259    fn edge_cases_dont_cause_panic() {
260        let decoder = new_decoder();
261        decoder.decode("8=|9=0|10=225|".as_bytes()).ok();
262        decoder.decode("8=|9=0|10=|".as_bytes()).ok();
263        decoder.decode("8====|9=0|10=|".as_bytes()).ok();
264        decoder.decode("|||9=0|10=|".as_bytes()).ok();
265        decoder.decode("9999999999999".as_bytes()).ok();
266        decoder.decode("-9999999999999".as_bytes()).ok();
267        decoder.decode("==============".as_bytes()).ok();
268        decoder.decode("9999999999999|".as_bytes()).ok();
269        decoder.decode("|999999999999=|".as_bytes()).ok();
270        decoder.decode("|999=999999999999999999|=".as_bytes()).ok();
271    }
272}