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}