22use hotfix_message:: error:: EncodingError as EncodeError ;
33pub use hotfix_message:: field_types:: Timestamp ;
44pub ( crate ) use hotfix_message:: message:: { Config , Message } ;
5- use hotfix_message:: session_fields:: { MSG_SEQ_NUM , SENDER_COMP_ID , SENDING_TIME , TARGET_COMP_ID } ;
5+ use hotfix_message:: session_fields:: {
6+ MSG_SEQ_NUM , ORIG_SENDING_TIME , POSS_DUP_FLAG , SENDER_COMP_ID , SENDING_TIME , TARGET_COMP_ID ,
7+ } ;
68pub use hotfix_message:: { Part , RepeatingGroup } ;
79
810pub mod business_reject;
@@ -20,12 +22,75 @@ pub mod verification_error;
2022pub use parser:: RawFixMessage ;
2123pub use resend_request:: ResendRequest ;
2224
25+ use heartbeat:: Heartbeat ;
26+ use logon:: Logon ;
27+ use logout:: Logout ;
28+ use reject:: Reject ;
29+ use sequence_reset:: SequenceReset ;
30+ use test_request:: TestRequest ;
31+
32+ static ADMIN_TYPES : [ & str ; 7 ] = [
33+ Logon :: MSG_TYPE ,
34+ Heartbeat :: MSG_TYPE ,
35+ TestRequest :: MSG_TYPE ,
36+ ResendRequest :: MSG_TYPE ,
37+ Reject :: MSG_TYPE ,
38+ SequenceReset :: MSG_TYPE ,
39+ Logout :: MSG_TYPE ,
40+ ] ;
41+
42+ pub fn is_admin ( message_type : & str ) -> bool {
43+ ADMIN_TYPES . contains ( & message_type)
44+ }
45+
2346pub trait OutboundMessage : Clone + Send + ' static {
2447 fn write ( & self , msg : & mut Message ) ;
2548
2649 fn message_type ( & self ) -> & str ;
2750}
2851
52+ /// Prepares a FIX message for resend per the FIX spec (PossDupFlag logic).
53+ ///
54+ /// Behaviour:
55+ /// - On first resend (no PossDupFlag Y / no OrigSendingTime):
56+ /// * Move current SendingTime(52) to OrigSendingTime(122)
57+ /// * Set SendingTime(52) to the current sending time (may be equal if clock granularity causes no change)
58+ /// * Set PossDupFlag(43)=Y
59+ /// - On subsequent resends (already marked possible duplicate and has OrigSendingTime):
60+ /// * Refresh SendingTime(52) to current time (value may or may not differ from previous)
61+ pub fn prepare_message_for_resend ( msg : & mut Message ) -> Result < ( ) , & ' static str > {
62+ let header = msg. header_mut ( ) ;
63+
64+ if header. get_raw ( SENDING_TIME ) . is_none ( ) {
65+ return Err ( "Missing SendingTime (52)" ) ;
66+ }
67+
68+ let already_poss_dup = header. get :: < bool > ( POSS_DUP_FLAG ) . unwrap_or ( false ) ;
69+ let has_orig_sending_time = header. get_raw ( ORIG_SENDING_TIME ) . is_some ( ) ;
70+
71+ if already_poss_dup && has_orig_sending_time {
72+ // Subsequent resend: refresh SendingTime only
73+ return if header. pop ( SENDING_TIME ) . is_some ( ) {
74+ header. set ( SENDING_TIME , Timestamp :: utc_now ( ) ) ;
75+ Ok ( ( ) )
76+ } else {
77+ Err ( "Failed to extract previous SendingTime" )
78+ } ;
79+ }
80+
81+ // First resend path
82+ if let Some ( original_sending_time_field) = header. pop ( SENDING_TIME ) {
83+ let original_ts = Timestamp :: parse ( & original_sending_time_field. data )
84+ . ok_or ( "Invalid original SendingTime format" ) ?;
85+ header. set ( ORIG_SENDING_TIME , original_ts) ;
86+ header. set ( SENDING_TIME , Timestamp :: utc_now ( ) ) ;
87+ header. set ( POSS_DUP_FLAG , true ) ;
88+ Ok ( ( ) )
89+ } else {
90+ Err ( "Failed to extract original SendingTime" )
91+ }
92+ }
93+
2994pub fn generate_message (
3095 begin_string : & str ,
3196 sender_comp_id : & str ,
0 commit comments