Skip to main content

ironfix_core/
message.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 27/1/26
5******************************************************************************/
6
7//! Message types and traits for FIX protocol.
8//!
9//! This module provides:
10//! - [`RawMessage`]: Zero-copy view into a FIX message buffer
11//! - [`OwnedMessage`]: Owned message for storage and cross-thread transfer
12//! - [`MsgType`]: Enumeration of FIX message types
13//! - [`FixMessage`]: Trait for typed message access
14
15use crate::error::DecodeError;
16use crate::field::FieldRef;
17use bytes::Bytes;
18use serde::{Deserialize, Serialize};
19use smallvec::SmallVec;
20use std::fmt;
21use std::ops::Range;
22
23/// Standard FIX message types.
24///
25/// This enum covers the most common administrative and application messages.
26/// Custom or less common message types can be represented as `Custom(String)`.
27#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
28pub enum MsgType {
29    /// Heartbeat (0) - Session level.
30    #[default]
31    Heartbeat,
32    /// Test Request (1) - Session level.
33    TestRequest,
34    /// Resend Request (2) - Session level.
35    ResendRequest,
36    /// Reject (3) - Session level.
37    Reject,
38    /// Sequence Reset (4) - Session level.
39    SequenceReset,
40    /// Logout (5) - Session level.
41    Logout,
42    /// Indication of Interest (6).
43    IndicationOfInterest,
44    /// Advertisement (7).
45    Advertisement,
46    /// Execution Report (8).
47    ExecutionReport,
48    /// Order Cancel Reject (9).
49    OrderCancelReject,
50    /// Logon (A) - Session level.
51    Logon,
52    /// News (B).
53    News,
54    /// Email (C).
55    Email,
56    /// New Order Single (D).
57    NewOrderSingle,
58    /// New Order List (E).
59    NewOrderList,
60    /// Order Cancel Request (F).
61    OrderCancelRequest,
62    /// Order Cancel/Replace Request (G).
63    OrderCancelReplaceRequest,
64    /// Order Status Request (H).
65    OrderStatusRequest,
66    /// Allocation Instruction (J).
67    AllocationInstruction,
68    /// List Cancel Request (K).
69    ListCancelRequest,
70    /// List Execute (L).
71    ListExecute,
72    /// List Status Request (M).
73    ListStatusRequest,
74    /// List Status (N).
75    ListStatus,
76    /// Allocation Instruction Ack (P).
77    AllocationInstructionAck,
78    /// Don't Know Trade (Q).
79    DontKnowTrade,
80    /// Quote Request (R).
81    QuoteRequest,
82    /// Quote (S).
83    Quote,
84    /// Settlement Instructions (T).
85    SettlementInstructions,
86    /// Market Data Request (V).
87    MarketDataRequest,
88    /// Market Data Snapshot/Full Refresh (W).
89    MarketDataSnapshotFullRefresh,
90    /// Market Data Incremental Refresh (X).
91    MarketDataIncrementalRefresh,
92    /// Market Data Request Reject (Y).
93    MarketDataRequestReject,
94    /// Quote Cancel (Z).
95    QuoteCancel,
96    /// Quote Status Request (a).
97    QuoteStatusRequest,
98    /// Mass Quote Acknowledgement (b).
99    MassQuoteAcknowledgement,
100    /// Security Definition Request (c).
101    SecurityDefinitionRequest,
102    /// Security Definition (d).
103    SecurityDefinition,
104    /// Security Status Request (e).
105    SecurityStatusRequest,
106    /// Security Status (f).
107    SecurityStatus,
108    /// Trading Session Status Request (g).
109    TradingSessionStatusRequest,
110    /// Trading Session Status (h).
111    TradingSessionStatus,
112    /// Mass Quote (i).
113    MassQuote,
114    /// Business Message Reject (j).
115    BusinessMessageReject,
116    /// Bid Request (k).
117    BidRequest,
118    /// Bid Response (l).
119    BidResponse,
120    /// List Strike Price (m).
121    ListStrikePrice,
122    /// XML Message (n).
123    XmlMessage,
124    /// Registration Instructions (o).
125    RegistrationInstructions,
126    /// Registration Instructions Response (p).
127    RegistrationInstructionsResponse,
128    /// Order Mass Cancel Request (q).
129    OrderMassCancelRequest,
130    /// Order Mass Cancel Report (r).
131    OrderMassCancelReport,
132    /// New Order Cross (s).
133    NewOrderCross,
134    /// Cross Order Cancel/Replace Request (t).
135    CrossOrderCancelReplaceRequest,
136    /// Cross Order Cancel Request (u).
137    CrossOrderCancelRequest,
138    /// Security Type Request (v).
139    SecurityTypeRequest,
140    /// Security Types (w).
141    SecurityTypes,
142    /// Security List Request (x).
143    SecurityListRequest,
144    /// Security List (y).
145    SecurityList,
146    /// Derivative Security List Request (z).
147    DerivativeSecurityListRequest,
148    /// Custom or unknown message type.
149    Custom(String),
150}
151
152impl std::str::FromStr for MsgType {
153    type Err = std::convert::Infallible;
154
155    /// Creates a MsgType from a string value.
156    ///
157    /// # Arguments
158    /// * `s` - The message type string (e.g., "D" for NewOrderSingle)
159    fn from_str(s: &str) -> Result<Self, Self::Err> {
160        Ok(match s {
161            "0" => Self::Heartbeat,
162            "1" => Self::TestRequest,
163            "2" => Self::ResendRequest,
164            "3" => Self::Reject,
165            "4" => Self::SequenceReset,
166            "5" => Self::Logout,
167            "6" => Self::IndicationOfInterest,
168            "7" => Self::Advertisement,
169            "8" => Self::ExecutionReport,
170            "9" => Self::OrderCancelReject,
171            "A" => Self::Logon,
172            "B" => Self::News,
173            "C" => Self::Email,
174            "D" => Self::NewOrderSingle,
175            "E" => Self::NewOrderList,
176            "F" => Self::OrderCancelRequest,
177            "G" => Self::OrderCancelReplaceRequest,
178            "H" => Self::OrderStatusRequest,
179            "J" => Self::AllocationInstruction,
180            "K" => Self::ListCancelRequest,
181            "L" => Self::ListExecute,
182            "M" => Self::ListStatusRequest,
183            "N" => Self::ListStatus,
184            "P" => Self::AllocationInstructionAck,
185            "Q" => Self::DontKnowTrade,
186            "R" => Self::QuoteRequest,
187            "S" => Self::Quote,
188            "T" => Self::SettlementInstructions,
189            "V" => Self::MarketDataRequest,
190            "W" => Self::MarketDataSnapshotFullRefresh,
191            "X" => Self::MarketDataIncrementalRefresh,
192            "Y" => Self::MarketDataRequestReject,
193            "Z" => Self::QuoteCancel,
194            "a" => Self::QuoteStatusRequest,
195            "b" => Self::MassQuoteAcknowledgement,
196            "c" => Self::SecurityDefinitionRequest,
197            "d" => Self::SecurityDefinition,
198            "e" => Self::SecurityStatusRequest,
199            "f" => Self::SecurityStatus,
200            "g" => Self::TradingSessionStatusRequest,
201            "h" => Self::TradingSessionStatus,
202            "i" => Self::MassQuote,
203            "j" => Self::BusinessMessageReject,
204            "k" => Self::BidRequest,
205            "l" => Self::BidResponse,
206            "m" => Self::ListStrikePrice,
207            "n" => Self::XmlMessage,
208            "o" => Self::RegistrationInstructions,
209            "p" => Self::RegistrationInstructionsResponse,
210            "q" => Self::OrderMassCancelRequest,
211            "r" => Self::OrderMassCancelReport,
212            "s" => Self::NewOrderCross,
213            "t" => Self::CrossOrderCancelReplaceRequest,
214            "u" => Self::CrossOrderCancelRequest,
215            "v" => Self::SecurityTypeRequest,
216            "w" => Self::SecurityTypes,
217            "x" => Self::SecurityListRequest,
218            "y" => Self::SecurityList,
219            "z" => Self::DerivativeSecurityListRequest,
220            other => Self::Custom(other.to_string()),
221        })
222    }
223}
224
225impl MsgType {
226    /// Returns the string representation of this message type.
227    #[must_use]
228    pub fn as_str(&self) -> &str {
229        match self {
230            Self::Heartbeat => "0",
231            Self::TestRequest => "1",
232            Self::ResendRequest => "2",
233            Self::Reject => "3",
234            Self::SequenceReset => "4",
235            Self::Logout => "5",
236            Self::IndicationOfInterest => "6",
237            Self::Advertisement => "7",
238            Self::ExecutionReport => "8",
239            Self::OrderCancelReject => "9",
240            Self::Logon => "A",
241            Self::News => "B",
242            Self::Email => "C",
243            Self::NewOrderSingle => "D",
244            Self::NewOrderList => "E",
245            Self::OrderCancelRequest => "F",
246            Self::OrderCancelReplaceRequest => "G",
247            Self::OrderStatusRequest => "H",
248            Self::AllocationInstruction => "J",
249            Self::ListCancelRequest => "K",
250            Self::ListExecute => "L",
251            Self::ListStatusRequest => "M",
252            Self::ListStatus => "N",
253            Self::AllocationInstructionAck => "P",
254            Self::DontKnowTrade => "Q",
255            Self::QuoteRequest => "R",
256            Self::Quote => "S",
257            Self::SettlementInstructions => "T",
258            Self::MarketDataRequest => "V",
259            Self::MarketDataSnapshotFullRefresh => "W",
260            Self::MarketDataIncrementalRefresh => "X",
261            Self::MarketDataRequestReject => "Y",
262            Self::QuoteCancel => "Z",
263            Self::QuoteStatusRequest => "a",
264            Self::MassQuoteAcknowledgement => "b",
265            Self::SecurityDefinitionRequest => "c",
266            Self::SecurityDefinition => "d",
267            Self::SecurityStatusRequest => "e",
268            Self::SecurityStatus => "f",
269            Self::TradingSessionStatusRequest => "g",
270            Self::TradingSessionStatus => "h",
271            Self::MassQuote => "i",
272            Self::BusinessMessageReject => "j",
273            Self::BidRequest => "k",
274            Self::BidResponse => "l",
275            Self::ListStrikePrice => "m",
276            Self::XmlMessage => "n",
277            Self::RegistrationInstructions => "o",
278            Self::RegistrationInstructionsResponse => "p",
279            Self::OrderMassCancelRequest => "q",
280            Self::OrderMassCancelReport => "r",
281            Self::NewOrderCross => "s",
282            Self::CrossOrderCancelReplaceRequest => "t",
283            Self::CrossOrderCancelRequest => "u",
284            Self::SecurityTypeRequest => "v",
285            Self::SecurityTypes => "w",
286            Self::SecurityListRequest => "x",
287            Self::SecurityList => "y",
288            Self::DerivativeSecurityListRequest => "z",
289            Self::Custom(s) => s.as_str(),
290        }
291    }
292
293    /// Returns true if this is an administrative message.
294    #[must_use]
295    pub fn is_admin(&self) -> bool {
296        matches!(
297            self,
298            Self::Heartbeat
299                | Self::TestRequest
300                | Self::ResendRequest
301                | Self::Reject
302                | Self::SequenceReset
303                | Self::Logout
304                | Self::Logon
305        )
306    }
307
308    /// Returns true if this is an application message.
309    #[must_use]
310    pub fn is_app(&self) -> bool {
311        !self.is_admin()
312    }
313}
314
315impl fmt::Display for MsgType {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        write!(f, "{}", self.as_str())
318    }
319}
320
321/// Zero-copy view into a FIX message buffer.
322///
323/// This struct holds references to the original message buffer,
324/// avoiding allocation during parsing. Fields are stored as
325/// offset ranges into the buffer.
326#[derive(Debug, Clone)]
327pub struct RawMessage<'a> {
328    /// The complete message buffer.
329    buffer: &'a [u8],
330    /// Range of the BeginString field value.
331    begin_string: Range<usize>,
332    /// Range of the message body (after BodyLength, before checksum).
333    body: Range<usize>,
334    /// The parsed message type.
335    msg_type: MsgType,
336    /// Parsed field references (tag and value ranges).
337    fields: SmallVec<[FieldRef<'a>; 32]>,
338}
339
340impl<'a> RawMessage<'a> {
341    /// Creates a new RawMessage from parsed components.
342    ///
343    /// # Arguments
344    /// * `buffer` - The complete message buffer
345    /// * `begin_string` - Range of the BeginString value
346    /// * `body` - Range of the message body
347    /// * `msg_type` - The parsed message type
348    /// * `fields` - Parsed field references
349    #[must_use]
350    pub fn new(
351        buffer: &'a [u8],
352        begin_string: Range<usize>,
353        body: Range<usize>,
354        msg_type: MsgType,
355        fields: SmallVec<[FieldRef<'a>; 32]>,
356    ) -> Self {
357        Self {
358            buffer,
359            begin_string,
360            body,
361            msg_type,
362            fields,
363        }
364    }
365
366    /// Returns the complete message buffer.
367    #[inline]
368    #[must_use]
369    pub const fn buffer(&self) -> &'a [u8] {
370        self.buffer
371    }
372
373    /// Returns the BeginString value (e.g., "FIX.4.4").
374    #[must_use]
375    pub fn begin_string(&self) -> &'a str {
376        std::str::from_utf8(&self.buffer[self.begin_string.clone()]).unwrap_or("")
377    }
378
379    /// Returns the message type.
380    #[inline]
381    #[must_use]
382    pub fn msg_type(&self) -> &MsgType {
383        &self.msg_type
384    }
385
386    /// Returns an iterator over all fields.
387    #[inline]
388    pub fn fields(&self) -> impl Iterator<Item = &FieldRef<'a>> {
389        self.fields.iter()
390    }
391
392    /// Returns the number of fields in the message.
393    #[inline]
394    #[must_use]
395    pub fn field_count(&self) -> usize {
396        self.fields.len()
397    }
398
399    /// Gets a field by tag number.
400    ///
401    /// # Arguments
402    /// * `tag` - The field tag number
403    ///
404    /// # Returns
405    /// The first field with the given tag, or `None` if not found.
406    #[must_use]
407    pub fn get_field(&self, tag: u32) -> Option<&FieldRef<'a>> {
408        self.fields.iter().find(|f| f.tag == tag)
409    }
410
411    /// Gets a field value as a string.
412    ///
413    /// # Arguments
414    /// * `tag` - The field tag number
415    ///
416    /// # Returns
417    /// The field value as a string, or `None` if not found or invalid UTF-8.
418    #[must_use]
419    pub fn get_field_str(&self, tag: u32) -> Option<&'a str> {
420        self.get_field(tag).and_then(|f| f.as_str().ok())
421    }
422
423    /// Gets a field value parsed as the specified type.
424    ///
425    /// # Arguments
426    /// * `tag` - The field tag number
427    ///
428    /// # Errors
429    /// Returns `DecodeError` if the field is not found or cannot be parsed.
430    pub fn get_field_as<T: std::str::FromStr>(&self, tag: u32) -> Result<T, DecodeError> {
431        self.get_field(tag)
432            .ok_or(DecodeError::MissingRequiredField { tag })?
433            .parse()
434    }
435
436    /// Returns the message body range.
437    #[inline]
438    #[must_use]
439    pub fn body_range(&self) -> &Range<usize> {
440        &self.body
441    }
442
443    /// Returns the message length in bytes.
444    #[inline]
445    #[must_use]
446    pub fn len(&self) -> usize {
447        self.buffer.len()
448    }
449
450    /// Returns true if the message is empty.
451    #[inline]
452    #[must_use]
453    pub fn is_empty(&self) -> bool {
454        self.buffer.is_empty()
455    }
456
457    /// Converts this borrowed message to an owned message.
458    #[must_use]
459    pub fn to_owned(&self) -> OwnedMessage {
460        OwnedMessage::from_raw(self)
461    }
462}
463
464/// Owned FIX message for storage and cross-thread transfer.
465///
466/// Unlike [`RawMessage`], this struct owns its data and can be
467/// safely sent across threads or stored for later use.
468#[derive(Debug, Clone)]
469pub struct OwnedMessage {
470    /// The complete message buffer.
471    buffer: Bytes,
472    /// The parsed message type.
473    msg_type: MsgType,
474    /// Field offsets: (tag, value_range).
475    field_offsets: Vec<(u32, Range<usize>)>,
476}
477
478impl OwnedMessage {
479    /// Creates an OwnedMessage from a RawMessage.
480    ///
481    /// # Arguments
482    /// * `raw` - The raw message to copy
483    #[must_use]
484    pub fn from_raw(raw: &RawMessage<'_>) -> Self {
485        let buffer = Bytes::copy_from_slice(raw.buffer);
486        let field_offsets = raw
487            .fields
488            .iter()
489            .map(|f| {
490                let start = f.value.as_ptr() as usize - raw.buffer.as_ptr() as usize;
491                let end = start + f.value.len();
492                (f.tag, start..end)
493            })
494            .collect();
495
496        Self {
497            buffer,
498            msg_type: raw.msg_type.clone(),
499            field_offsets,
500        }
501    }
502
503    /// Creates an OwnedMessage from raw bytes.
504    ///
505    /// # Arguments
506    /// * `buffer` - The message bytes
507    /// * `msg_type` - The message type
508    /// * `field_offsets` - Field tag and value range pairs
509    #[must_use]
510    pub fn new(buffer: Bytes, msg_type: MsgType, field_offsets: Vec<(u32, Range<usize>)>) -> Self {
511        Self {
512            buffer,
513            msg_type,
514            field_offsets,
515        }
516    }
517
518    /// Returns the message type.
519    #[inline]
520    #[must_use]
521    pub fn msg_type(&self) -> &MsgType {
522        &self.msg_type
523    }
524
525    /// Returns the message bytes.
526    #[inline]
527    #[must_use]
528    pub fn as_bytes(&self) -> &[u8] {
529        &self.buffer
530    }
531
532    /// Returns the message length in bytes.
533    #[inline]
534    #[must_use]
535    pub fn len(&self) -> usize {
536        self.buffer.len()
537    }
538
539    /// Returns true if the message is empty.
540    #[inline]
541    #[must_use]
542    pub fn is_empty(&self) -> bool {
543        self.buffer.is_empty()
544    }
545
546    /// Gets a field value by tag.
547    ///
548    /// # Arguments
549    /// * `tag` - The field tag number
550    ///
551    /// # Returns
552    /// The field value bytes, or `None` if not found.
553    #[must_use]
554    pub fn get_field(&self, tag: u32) -> Option<&[u8]> {
555        self.field_offsets
556            .iter()
557            .find(|(t, _)| *t == tag)
558            .map(|(_, range)| &self.buffer[range.clone()])
559    }
560
561    /// Gets a field value as a string.
562    ///
563    /// # Arguments
564    /// * `tag` - The field tag number
565    ///
566    /// # Returns
567    /// The field value as a string, or `None` if not found or invalid UTF-8.
568    #[must_use]
569    pub fn get_field_str(&self, tag: u32) -> Option<&str> {
570        self.get_field(tag)
571            .and_then(|b| std::str::from_utf8(b).ok())
572    }
573
574    /// Returns the number of fields.
575    #[inline]
576    #[must_use]
577    pub fn field_count(&self) -> usize {
578        self.field_offsets.len()
579    }
580
581    /// Consumes the message and returns the underlying buffer.
582    #[must_use]
583    pub fn into_bytes(self) -> Bytes {
584        self.buffer
585    }
586}
587
588/// Trait for typed FIX message access.
589///
590/// This trait is implemented by generated message types to provide
591/// type-safe encoding and decoding.
592pub trait FixMessage: Sized {
593    /// The message type string (e.g., "D" for NewOrderSingle).
594    const MSG_TYPE: &'static str;
595
596    /// Decodes a message from a raw message.
597    ///
598    /// # Arguments
599    /// * `raw` - The raw message to decode
600    ///
601    /// # Errors
602    /// Returns `DecodeError` if the message cannot be decoded.
603    fn from_raw(raw: &RawMessage<'_>) -> Result<Self, DecodeError>;
604
605    /// Encodes the message to a buffer.
606    ///
607    /// # Arguments
608    /// * `buf` - The buffer to write to
609    ///
610    /// # Errors
611    /// Returns `EncodeError` if the message cannot be encoded.
612    fn encode(&self, buf: &mut Vec<u8>) -> Result<(), crate::error::EncodeError>;
613}
614
615#[cfg(test)]
616mod tests {
617    use super::*;
618
619    #[test]
620    fn test_msg_type_from_str() {
621        assert_eq!("0".parse::<MsgType>().unwrap(), MsgType::Heartbeat);
622        assert_eq!("A".parse::<MsgType>().unwrap(), MsgType::Logon);
623        assert_eq!("D".parse::<MsgType>().unwrap(), MsgType::NewOrderSingle);
624        assert_eq!("8".parse::<MsgType>().unwrap(), MsgType::ExecutionReport);
625    }
626
627    #[test]
628    fn test_msg_type_as_str() {
629        assert_eq!(MsgType::Heartbeat.as_str(), "0");
630        assert_eq!(MsgType::Logon.as_str(), "A");
631        assert_eq!(MsgType::NewOrderSingle.as_str(), "D");
632    }
633
634    #[test]
635    fn test_msg_type_is_admin() {
636        assert!(MsgType::Heartbeat.is_admin());
637        assert!(MsgType::Logon.is_admin());
638        assert!(MsgType::Logout.is_admin());
639        assert!(!MsgType::NewOrderSingle.is_admin());
640        assert!(!MsgType::ExecutionReport.is_admin());
641    }
642
643    #[test]
644    fn test_msg_type_custom() {
645        let custom: MsgType = "XX".parse().unwrap();
646        assert!(matches!(custom, MsgType::Custom(_)));
647        assert_eq!(custom.as_str(), "XX");
648    }
649
650    #[test]
651    fn test_owned_message_field_access() {
652        // Buffer: "8=FIX.4.4\x0135=D\x0149=SENDER\x01"
653        // Offsets: 8=FIX.4.4 (0-9), \x01 (9), 35=D (10-13), \x01 (14), 49=SENDER (15-23), \x01 (24)
654        // FIX.4.4 is at 2..9, D is at 13..14, SENDER is at 18..24
655        let buffer = Bytes::from_static(b"8=FIX.4.4\x0135=D\x0149=SENDER\x01");
656        let field_offsets = vec![(8, 2..9), (35, 13..14), (49, 18..24)];
657        let msg = OwnedMessage::new(buffer, MsgType::NewOrderSingle, field_offsets);
658
659        assert_eq!(msg.get_field_str(8), Some("FIX.4.4"));
660        assert_eq!(msg.get_field_str(35), Some("D"));
661        assert_eq!(msg.get_field_str(49), Some("SENDER"));
662        assert_eq!(msg.get_field_str(999), None);
663    }
664}