forgefix/fix/
decode.rs

1//! Message decoding and parsing
2//!
3//! This module provides tools for decoding information from an array of bytes representing a FIX
4//! message. One of the design choices of ForgeFIX was for message parsing to occur on-demend
5//! instead of automatically. This way, only when necessary are messages decoded, and only what is
6//! necessary will be parsed from the message.
7//!
8//! # Terminalogy
9//! * `message` - An entire FIX message, which is represented as an array of bytes in a [`MsgBuf`].
10//! There are two types of messages: Session and Application. Session message control the FIX
11//! session and are managed entirely by the FIX engine. Application messages are what peers create
12//! and send to each other, and are almost entirely managed by the user. The FIX engine only
13//! gurantees that the user receives the application messages in the correct order.
14//!
15//! * `fields` -- A tag/value pair. The tag and value are connected with an `=`. Multiple fields
16//! are delimited with an `SOH`. A message is just list of fields.
17//!
18//! * `tags` --  A tag is on the left side of the `=` and describes what kind of information the value
19//! represents. All valid FIX tags can be found in the [FIX dictionary], and are represented in the
20//! [`Tags`] enum.
21//!
22//! * `values` -- A value is on the right side of the `=` and contains the actual information for the
23//! field. FIX values are Utf8 encoded and have one of the following types: int, float, String,
24//! char or data (see [FIX dictionary] for more info). Furthermore, for some fields, only a subset
25//! of values for the type are considered valid. These are called value sets for that field. All
26//! value sets are represented by enums in the [generated] module.
27//!
28//! * `SOH` -- The character that delimits the fields in a message. An SOH is represented with ascii
29//! code 1. For displaying, a `|` is often used to show an SOH. In rust, an SOH is represented as a
30//! byte: `b'\x01'`.
31//!
32//! # Decoding
33//!
34//! Parsing of messages is done with the [`parse`] function which depends on a user defined
35//! [`ParserCallback`]. The parse function splits a message into fields, and then tag/value pairs.
36//! These pairs are sent to the callback, where the user defines which to parse.
37//!
38//! The [`Tags`] enum and [`parse_field`] function are tools to support parsing of tags and values.
39//!
40//! # Errors
41//!
42//! If a message is malformed or contains invalid data, then decoding the message will likely cause an error.
43//! The FIX specification recommends being fault tolerant when processing application level
44//! messages. ForgeFIX follows this recommendation which is reflected in the decode error types.
45//!
46//! [`MessageParseError`]: errors that occur when a message fails to meet the FIX spec for message
47//! structure, and the [`parse`] function is not able to split the message into its fields. This
48//! error will always be tripped if any part of the message is malformed.
49//!
50//! [`DecodeError`]: errors for invalid tags and values. A tag can be invalid if it is not a known tag number.
51//! A value can be invalid because it: is invalid UTF-8, cannot be parsed into a rust type, or does not exist in
52//! a value set. This error will only occur when a user attempts to parse an invalid tag or value.
53//!
54//! [`MsgBuf`]: crate::fix::mem::MsgBuf
55//! [FIX dictionary]: https://btobits.com/fixopaedia/fixdic42/index.html
56//! [`Tags`]: crate::fix::generated::Tags
57//! [generated]: crate::fix::generated
58//!
59//! # Example
60//!
61//! ```no_run
62//! use anyhow::{Error, bail, Result};
63//! use forgefix::fix::decode::{ParserCallback, parse_field, parse, MessageParseError};
64//! use forgefix::fix::generated::{Tags, MsgType, ExecType, OrdStatus};
65//!
66//! #[derive(Debug)]
67//! struct ExecutionReportParser<'a> {
68//!     order_id: &'a str,
69//!     order_status: OrdStatus,
70//!     exec_type: ExecType,
71//!     qty_filled: f32,
72//! }
73//!
74//! impl<'a> Default for ExecutionReportParser<'a> {
75//!     fn default() -> Self {
76//!         ExecutionReportParser {
77//!             order_id: Default::default(),
78//!             order_status: OrdStatus::NEW,
79//!             exec_type: ExecType::NEW,
80//!             qty_filled: Default::default(),
81//!         }
82//!     }
83//! }
84//!
85//! impl<'a> ParserCallback<'a> for ExecutionReportParser<'a> {
86//!     type Err = Error;
87//!
88//!     // parse and save any header fields...
89//!     fn header(&mut self, key: u32, value: &'a [u8]) -> Result<bool, Self::Err> {
90//!         if let Ok(Tags::MsgType) = key.try_into() {
91//!             let msg_type = parse_field::<char>(value)?.try_into()?;
92//!             if !matches!(msg_type, MsgType::EXECUTION_REPORT) {
93//!                 bail!("not an execution report message");
94//!             }
95//!         }
96//!         Ok(true)
97//!     }
98//!
99//!     // parse and save any body fields...
100//!     fn body(&mut self, key: u32, value: &'a [u8]) -> Result<bool, Self::Err> {
101//!         match key.try_into() {
102//!             Ok(Tags::OrderID) => self.order_id = std::str::from_utf8(value)?,
103//!             Ok(Tags::OrdStatus) => {
104//!                 self.order_status = parse_field::<char>(value)?.try_into()?;
105//!             }
106//!             Ok(Tags::ExecType) => {
107//!                 self.exec_type = parse_field::<char>(value)?.try_into()?;
108//!             }
109//!             Ok(Tags::CumQty) => self.qty_filled = parse_field::<f32>(value)?,
110//!             _ => {}
111//!         }
112//!         Ok(true)
113//!     }
114//!
115//!     // parse and save any trailer fields...
116//!     fn trailer(&mut self, key: u32, value: &'a [u8]) -> Result<bool, Self::Err> {
117//!         Ok(true)
118//!     }
119//!
120//!     // if the message is malformed, catch the error and handle it...
121//!     fn parse_error(&mut self, err: MessageParseError) -> Result<(), Self::Err> {
122//!         Err(err.into())
123//!     }
124//! }
125//!
126//! # use forgefix::{SessionSettings, FixApplicationInitiator};
127//! #[tokio::main]
128//! async fn main() -> Result<()> {
129//!
130//!     // create SessionSettings...
131//!
132//! #    let settings = SessionSettings::builder()
133//! #        .with_sender_comp_id("my_id")
134//! #        .with_target_comp_id("peer_id")
135//! #        .with_store_path("./store".into())
136//! #        .with_log_dir("./log".into())
137//! #        .with_socket_addr("127.0.0.1:0".parse().unwrap())
138//! #        .build()?;
139//!     let (handle, mut receiver) = FixApplicationInitiator::build(settings)?
140//!         .initiate()
141//!         .await?;
142//!
143//!     tokio::spawn(async move {
144//!         while let Some(msg) = receiver.recv().await {
145//!             let mut callback: ExecutionReportParser = Default::default();
146//!             match parse(&msg[..], &mut callback) {
147//!                 Ok(()) => println!("Received execution report: {:?}", callback),
148//!                 Err(e) => println!("Error parsing execution report: {:?}", e),
149//!             }
150//!         }
151//!     });
152//!
153//!     // run application ...
154//!     # Ok(())
155//! }
156//! ```
157
158use crate::fix::generated::{get_data_ref, Tags};
159use crate::fix::{GarbledMessageType, SessionError};
160use chrono::{DateTime, NaiveDateTime, Utc};
161use lazy_static::lazy_static;
162use std::collections::{BTreeSet, HashMap};
163use std::result;
164use thiserror::Error;
165
166const TIME_FORMAT_SHORT: &str = "%Y%m%d-%H:%M:%S";
167const TIME_FORMAT_LONG: &str = "%Y%m%d-%H:%M:%S%.3f";
168
169lazy_static! {
170    static ref HEADER_FIELDS: BTreeSet<u32> = [
171        8, 9, 35, 49, 56, 115, 128, 90, 91, 34, 50, 142, 57, 143, 116, 129, 145, 43, 97, 52, 122,
172        212, 213, 347, 369, 370,
173    ]
174    .iter()
175    .cloned()
176    .collect();
177    static ref TRAILER_FIELDS: BTreeSet<u32> = [93, 89, 10].iter().cloned().collect();
178}
179
180/// Errors that can occur while splitting a message into fields.
181#[derive(Error, Debug)]
182pub enum MessageParseError {
183    /// The message contained an unexpected byte.
184    ///
185    /// The [`usize`] is the index of the unexpected byte, and the [`Vec<u8>`] will contain the
186    /// entire message.
187    #[error("the value at index {0:?} was unexpected in message {1:?}")]
188    UnexpectedByte(usize, Vec<u8>),
189    /// A length field's value could not be parsed.
190    ///
191    /// The [`u32`] will be the length tag, and the [`Vec<u8>`] will contain its value that could
192    /// not be parsed.
193    #[error("could not parse value {1:?} of length field {0:?}")]
194    BadLengthField(u32, Vec<u8>),
195}
196
197/// Errors that can occur while decoding a FIX message.
198#[derive(Error, Debug)]
199pub enum DecodeError {
200    /// The Message could not be parsed into fields
201    #[error("Message could not be parsed into fields: {0:?}")]
202    BadMessage(#[from] MessageParseError),
203    /// A field contained an unknown tag
204    ///
205    /// The [`u32`] contains the tag value
206    #[error("{0:?} does not match a known Tag")]
207    UnknownTag(u32),
208    /// A field contained invalid utf8
209    #[error("FIX message contained invalid utf8: {0:?}")]
210    Utf8Error(#[from] std::str::Utf8Error),
211    /// A field's value could not be parsed
212    ///
213    /// The [`Vec<u8>`] contains the value
214    #[error("Value {0:?} could not be parsed")]
215    BadValue(Vec<u8>),
216    /// A character field did not match any known variant of a tag
217    ///
218    /// The attempted [`Tags`] and [`char`] are contained in the error
219    #[error("char {1:?} does not match a known variant of {0:?}")]
220    UnknownChar(Tags, char),
221    /// A int field did not match any known variant of a tag
222    ///
223    /// The attempted [`Tags`] and [`u8`] are contained in the error
224    #[error("int {1:?} does not match a known variant of {0:?}")]
225    UnknownInt(Tags, u8),
226}
227
228#[derive(PartialEq, Eq, Debug)]
229enum FieldState {
230    Start,
231    InTag,
232    SeenEquals,
233    InField,
234    Error,
235}
236struct FieldIter<'a> {
237    inner: std::iter::Enumerate<std::slice::Iter<'a, u8>>,
238    msg: &'a [u8],
239    state: FieldState,
240    field_start: usize,
241    tag_accum: u32,
242    field_lengths: HashMap<u32, u32>,
243}
244
245impl<'a> FieldIter<'a> {
246    fn new(msg: &'a [u8]) -> Self {
247        FieldIter {
248            inner: msg.iter().enumerate(),
249            msg,
250            state: FieldState::Start,
251            field_start: 0,
252            tag_accum: 0,
253            field_lengths: HashMap::new(),
254        }
255    }
256
257    fn skip_ahead(&mut self, n: u32) {
258        for _ in 0..n {
259            _ = self.inner.next();
260        }
261    }
262}
263
264impl<'a> Iterator for FieldIter<'a> {
265    type Item = Result<(u32, &'a [u8]), MessageParseError>;
266
267    fn next(&mut self) -> Option<Self::Item> {
268        while let Some((i, b)) = self.inner.next() {
269            let c = *b as char;
270            match (&self.state, c) {
271                (&FieldState::Start, '0'..='9') | (&FieldState::InTag, '0'..='9') => {
272                    if self.state == FieldState::Start {
273                        self.tag_accum = 0;
274                    } else {
275                        self.tag_accum *= 10;
276                    }
277                    self.tag_accum += *b as u32 - '0' as u32;
278                    self.state = FieldState::InTag;
279                }
280                (&FieldState::InTag, '=') => {
281                    self.field_start = i + 1;
282                    if let Some(len) = self.field_lengths.get(&self.tag_accum) {
283                        self.skip_ahead(len - 1);
284                    }
285                    self.state = FieldState::SeenEquals;
286                }
287                (&FieldState::SeenEquals, '\x01') | (&FieldState::InField, '\x01') => {
288                    let curr_value = &self.msg[self.field_start..i];
289                    if let Some(tag) = get_data_ref(self.tag_accum) {
290                        match bytes_to_u32(curr_value) {
291                            Some(val) => {
292                                self.field_lengths.insert(tag, val);
293                            }
294                            None => {
295                                self.state = FieldState::Error;
296                                return Some(Err(MessageParseError::BadLengthField(
297                                    self.tag_accum,
298                                    curr_value.to_vec(),
299                                )));
300                            }
301                        }
302                    }
303                    self.state = FieldState::Start;
304                    return Some(Ok((self.tag_accum, &self.msg[self.field_start..i])));
305                }
306                (&FieldState::SeenEquals, _) => self.state = FieldState::InField,
307                (&FieldState::InField, _) => {}
308                (&FieldState::Error, _) => return None,
309                _ => {
310                    self.state = FieldState::Error;
311                    return Some(Err(MessageParseError::UnexpectedByte(i, self.msg.to_vec())));
312                }
313            }
314        }
315        None
316    }
317}
318
319/// A trait that defines parsing of tag/values in a [`MsgBuf`], and is required to call the [`parse`]
320/// function.
321///
322/// The `ParserCallback` defines methods that get called for certain parsing events.
323///
324/// ## Events
325///
326/// * Header field found -- the [`header`] function is called
327/// * Body field found -- the [`body`] function is called
328/// * Trailer field found -- the [`trailer`] function is called
329/// * A [`MessageParseError`] occurs -- the [`parse_error`] function is called
330///
331/// To see which fields are headers or trailers, see [FIX dictionary]. All other fields are
332/// considered body fields.
333///
334/// ## Return Values
335///
336/// * [`header`], [`body`] and [`trailer`] -- Return `Ok(true)` to signal that parsing should
337/// continue. Return `Ok(false)` to signal that parsing should end. Return `Err` if an error
338/// occured that should cause parsing to stop.
339///
340/// * [`parse_error`] -- Convert the [`MessageParseError`] into a `Result<(), Self::Err>`
341///
342/// [FIX dictionary]: https://btobits.com/fixopaedia/fixdic42/index.html
343/// [`MsgBuf`]: crate::fix::mem::MsgBuf
344/// [`header`]: ParserCallback::header
345/// [`body`]: ParserCallback::body
346/// [`trailer`]: ParserCallback::trailer
347/// [`parse_error`]: ParserCallback::parse_error
348pub trait ParserCallback<'a> {
349    type Err;
350
351    /// Called for any fields in message that are header fields.
352    fn header(&mut self, key: u32, value: &'a [u8]) -> result::Result<bool, Self::Err>;
353
354    /// Called for any fields in message that are body fields
355    fn body(&mut self, key: u32, value: &'a [u8]) -> result::Result<bool, Self::Err>;
356
357    /// Called for any fields in message that are trailer fields
358    fn trailer(&mut self, key: u32, value: &'a [u8]) -> result::Result<bool, Self::Err>;
359
360    /// Called if a [`MessageParseError`] occurs
361    fn parse_error(&mut self, err: MessageParseError) -> result::Result<(), Self::Err>;
362}
363
364/// A default implementation of [`ParserCallback`]
365pub struct NullParserCallback;
366
367impl<'a> ParserCallback<'a> for NullParserCallback {
368    type Err = DecodeError;
369    fn header(&mut self, _key: u32, _value: &'a [u8]) -> Result<bool, DecodeError> {
370        Ok(true)
371    }
372    fn body(&mut self, _key: u32, _value: &'a [u8]) -> Result<bool, DecodeError> {
373        Ok(true)
374    }
375    fn trailer(&mut self, _key: u32, _value: &'a [u8]) -> Result<bool, DecodeError> {
376        Ok(true)
377    }
378    fn parse_error(&mut self, err: MessageParseError) -> Result<(), DecodeError> {
379        Err(err.into())
380    }
381}
382
383/// Parse a [`MsgBuf`] and store values in a [`ParserCallback`]
384///
385/// [`MsgBuf`]: crate::fix::mem::MsgBuf
386///
387/// # Notes
388///
389/// The `parse` function iterates over each field and splits each field into a tag/value pair. Then,
390/// each tag/value pair is passed to the `callback`'s methods.
391///
392/// In the event that splitting a message into fields causes a [`MessageParseError`], the created
393/// error will be passed to the callback.
394///
395/// [`parse`] will return early with `Ok(())` if at any point the callback returns `Ok(false)`.
396/// `Ok(())` will also be returned once all the fields have been iterated over.
397///
398/// # Errors
399///
400/// If at any point the `callback` return an `Err`, [`parse`] will end and return the err.
401///
402/// If at any point a [`MessageParseError`] occurs,
403/// `parse` will call [`ParserCallback::parse_error`] and return its result.
404pub fn parse<'a, T: ParserCallback<'a>>(
405    msg: &'a [u8],
406    callbacks: &mut T,
407) -> result::Result<(), T::Err> {
408    let field_iter = FieldIter::new(msg);
409    for res in field_iter {
410        let (tag, val) = match res {
411            Ok((t, v)) => (t, v),
412            Err(e) => return callbacks.parse_error(e),
413        };
414        let cont = if HEADER_FIELDS.contains(&tag) {
415            callbacks.header(tag, val)?
416        } else if TRAILER_FIELDS.contains(&tag) {
417            callbacks.trailer(tag, val)?
418        } else {
419            callbacks.body(tag, val)?
420        };
421        if !cont {
422            break;
423        }
424    }
425    Ok(())
426}
427
428fn bytes_to_u32(bytes: &[u8]) -> Option<u32> {
429    let mut accum: u32 = 0;
430    for b in bytes.iter() {
431        if *b < b'0' || b'9' < *b {
432            return None;
433        }
434        accum = match accum
435            .checked_mul(10_u32)
436            .and_then(|r| r.checked_add((b - b'0').into()))
437        {
438            Some(v) => v,
439            _ => {
440                return None;
441            }
442        }
443    }
444    Some(accum)
445}
446
447pub(super) fn parse_header(header: &[u8]) -> Result<usize, SessionError> {
448    let prefix = parse_peeked_prefix(header)?;
449    // body_length does not account for the 7 byte checksum (10=xxx|)
450    // and len_end is 1 less that we would like
451    Ok(prefix.body_length - (header.len() - (prefix.len_end + 1)) + 7)
452}
453
454pub(super) struct ParsedPeek {
455    pub msg_type: char,
456    pub len_start: usize,
457    pub len_end: usize,
458    pub fixed_fields_end: usize,
459    pub body_length: usize,
460}
461pub(super) fn parse_peeked_prefix(peeked: &[u8]) -> result::Result<ParsedPeek, SessionError> {
462    const EXPECTED_PREFIX: &[u8] = b"8=FIX.4.2\x019=";
463    if &peeked[..2] == b"8=" && &peeked[2..9] != b"FIX.4.2" {
464        return Err(SessionError::new_garbled_message(
465            String::from("Incorrect BeginString"),
466            GarbledMessageType::BeginStringIssue,
467        ));
468    }
469
470    if &peeked[..EXPECTED_PREFIX.len()] != EXPECTED_PREFIX {
471        return Err(SessionError::new_garbled_message(
472            String::from("BeginString not first"),
473            GarbledMessageType::Other,
474        ));
475    }
476    let mut at = EXPECTED_PREFIX.len();
477    let mut body_length: usize = 0;
478    let mut saw_end = false;
479    for c in peeked[EXPECTED_PREFIX.len()..].iter() {
480        at += 1;
481        match *c as char {
482            '0'..='9' => {
483                body_length =
484                    body_length
485                        .checked_mul(10)
486                        .ok_or(SessionError::new_garbled_message(
487                            String::from("BodyLength too large"),
488                            GarbledMessageType::BodyLengthIssue,
489                        ))?;
490                body_length = body_length.checked_add((*c - (b'0')) as usize).ok_or(
491                    SessionError::new_garbled_message(
492                        String::from("BodyLength too large"),
493                        GarbledMessageType::BodyLengthIssue,
494                    ),
495                )?;
496            }
497            '\x01' => {
498                saw_end = true;
499                break;
500            }
501            _ => {
502                return Err(SessionError::new_garbled_message(
503                    String::from("Illegal character in BodyLength"),
504                    GarbledMessageType::BodyLengthIssue,
505                ));
506            }
507        }
508    }
509    let len_end = at - 1;
510    if !saw_end {
511        return Err(SessionError::new_garbled_message(
512            String::from("BodyLength too large"),
513            GarbledMessageType::BodyLengthIssue,
514        ));
515    }
516
517    let msg_type = if &peeked[at..at + 3] == b"35=" && peeked[at + 4] == b'\x01' {
518        peeked[at + 3]
519    } else {
520        return Err(SessionError::new_garbled_message(
521            String::from("Missing MsgType"),
522            GarbledMessageType::MsgTypeIssue,
523        ));
524    };
525    let fixed_fields_end = at + 5;
526
527    Ok(ParsedPeek {
528        msg_type: msg_type as char,
529        len_start: EXPECTED_PREFIX.len(),
530        len_end,
531        fixed_fields_end,
532        body_length,
533    })
534}
535
536/// Attempts to parse a FIX value into any type that `impl`'s [`FromStr`]
537///
538/// # Primitives
539///
540/// Rust primitives generally `impl` [`FromStr`]. And most FIX data type can be represented by rust primitives. Consider
541/// using the following for each FIX type:
542/// * `int` -- [`i32`], [`u32`]
543/// * `float` -- [`f32`]
544/// * `char` -- [`char`]
545/// * `String` -- [`&str`]*, [`String`]
546/// * `data` -- `&[u8]`
547///
548/// *[`&str`] does not itself `impl` [`FromStr`], so just use [`from_utf8`]. Since [`DecodeError`]
549/// `impl`'s [`From<std::str::Utf8Error>`], the result can easily be converted
550///
551/// # Tags
552///
553/// FIX tags are automatically converted into a [`u32`]. The [`Tags`] enum `impl`'s
554/// [`TryFrom<u32>`].
555///
556/// # Value Sets
557///
558/// All FIX value sets are implemented as enums in the `generated` module. To convert a value into
559/// its enum, first convert to the corresponding primitive ([`char`] or [`u8`]). And then
560/// all enums `impl` [`TryFrom`] for either [`char`] or [`u8`].
561///
562///
563/// [`FromStr`]: std::str::FromStr
564/// [`MsgType`]: crate::fix::generated::MsgType
565/// [`from_utf8`]: std::str::from_utf8
566///
567/// # Example
568///
569/// ```rust
570/// # use forgefix::fix::generated::{EncryptMethod, OrdStatus, MsgType};
571/// # use forgefix::fix::decode::parse_field;
572/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
573///
574///     // MsgType is specified as a char and has a value set
575///     let msg_type_field = b"A";
576///     let msg_type: MsgType = parse_field::<char>(msg_type_field)?.try_into()?;
577///     assert_eq!(msg_type, MsgType::LOGON);
578///
579///     // Prices are floats, so parse into an f32
580///     let price_field = b"1.13";
581///     let price = parse_field::<f32>(price_field)?;
582///     assert_eq!(price, 1.13f32);
583///
584///     // OrdStatus is also specified as a char and has a value set
585///     let ord_status_field = b"0";
586///     let ord_status: OrdStatus = parse_field::<char>(ord_status_field)?.try_into()?;
587///     assert_eq!(ord_status, OrdStatus::NEW);
588///
589///     // To parse into a &str, just use std::str::from_utf8
590///     let order_id_field = b"abc123";
591///     let order_id: &str = std::str::from_utf8(order_id_field)?;
592///     assert_eq!(order_id, "abc123");
593///
594///     // EncryptMethod is specified as an int and has a value set
595///     let encrypt_method_field = b"0";
596///     let encrypt_method: EncryptMethod  = parse_field::<u8>(encrypt_method_field)?.try_into()?;
597///     assert_eq!(encrypt_method, EncryptMethod::NONE);
598///     # Ok(())
599/// # }
600/// ```
601pub fn parse_field<T>(field: &[u8]) -> Result<T, DecodeError>
602where
603    T: std::str::FromStr,
604    <T as std::str::FromStr>::Err: std::fmt::Debug,
605{
606    std::str::from_utf8(field)?
607        .parse::<T>()
608        .map_err(|_| DecodeError::BadValue(field.to_vec()))
609}
610
611pub(super) fn parse_sending_time(sending_time_bytes: &[u8]) -> Result<DateTime<Utc>, DecodeError> {
612    let sending_time_str = std::str::from_utf8(sending_time_bytes)?;
613    let sending_time = NaiveDateTime::parse_from_str(sending_time_str, TIME_FORMAT_SHORT)
614        .or_else(|_| NaiveDateTime::parse_from_str(sending_time_str, TIME_FORMAT_LONG))
615        .map_err(|_| DecodeError::BadValue(sending_time_bytes.to_vec()))?;
616    Ok(sending_time.and_utc())
617}
618
619#[cfg(test)]
620mod test {
621    use super::*;
622    #[test]
623    fn test_body_length_too_long() {
624        if let Ok(_) = parse_peeked_prefix(b"8=FIX.4.2\x019=33333333333333333333333") {
625            assert!(false, "Expected error");
626        };
627    }
628
629    #[test]
630    fn test_bytes_to_u32() {
631        assert_eq!(bytes_to_u32(b"234").unwrap(), 234);
632        assert_eq!(bytes_to_u32(b"0").unwrap(), 0);
633        assert_eq!(
634            bytes_to_u32(b"11111111111111111111111111111111111111").is_none(),
635            true
636        );
637        assert_eq!(bytes_to_u32(b"a").is_none(), true);
638    }
639
640    #[test]
641    fn test_field_iter() {
642        let messages: Vec<&[u8]> = vec![
643            b"8=FIX.4.2\x019=44\x018=A\x0110=123\x01",
644            b"8\x01=FIX.4.2",
645            b"93=6\x018=A\x0189=12\x01456\x0110=123\x01",
646            b"93=6A\x018=A\x0189=12\x01456\x0110=123\x01",
647        ];
648
649        let expected: Vec<Vec<Result<(u32, &[u8]), ()>>> = vec![
650            vec![
651                Ok((8, b"FIX.4.2")),
652                Ok((9, b"44")),
653                Ok((8, b"A")),
654                Ok((10, b"123")),
655            ],
656            vec![Err(())],
657            vec![
658                Ok((93, b"6")),
659                Ok((8, b"A")),
660                Ok((89, b"12\x01456")),
661                Ok((10, b"123")),
662            ],
663            vec![Err(())],
664        ];
665
666        for (msg, ex) in messages.iter().zip(expected.iter()) {
667            let field_iter = FieldIter::new(&msg[..]);
668            for (got, exp) in field_iter.zip(ex.iter()) {
669                if exp.is_err() {
670                    assert!(got.is_err(), "Expected error");
671                } else {
672                    assert_eq!(got.unwrap(), *exp.as_ref().unwrap());
673                }
674            }
675        }
676    }
677}