use crate::config::SessionConfig;
use crate::message::ResendRequest;
use crate::message::logout::Logout;
use crate::message::reject::Reject;
use crate::message::sequence_reset::SequenceReset;
use crate::message::verification_issue::{CompIdType, MessageError, VerificationIssue};
use crate::session::error::SessionOperationError;
use hotfix_message::Part;
use hotfix_message::field_types::Timestamp;
use hotfix_message::message::Message;
use hotfix_message::session_fields::{
BEGIN_STRING, GAP_FILL_FLAG, MSG_SEQ_NUM, MSG_TYPE, ORIG_SENDING_TIME, POSS_DUP_FLAG,
SENDER_COMP_ID, SENDING_TIME, TARGET_COMP_ID,
};
use std::cmp::Ordering;
use tracing::error;
const SENDING_TIME_THRESHOLD: u64 = 120;
#[derive(Clone, Copy, Debug)]
pub(crate) struct VerificationFlags {
pub(crate) check_too_high: bool,
pub(crate) check_too_low: bool,
pub(crate) check_orig_sending_time: bool,
}
impl VerificationFlags {
pub(crate) fn new(
check_too_high: bool,
check_too_low: bool,
check_orig_sending_time: bool,
) -> Self {
Self {
check_too_high,
check_too_low,
check_orig_sending_time,
}
}
pub(crate) fn requires_sequence_number(&self) -> bool {
self.check_too_high || self.check_too_low
}
pub(crate) fn for_message(message: &Message) -> Result<Self, SessionOperationError> {
let message_type: &str = message
.header()
.get(MSG_TYPE)
.map_err(|_| SessionOperationError::MissingField("MSG_TYPE"))?;
let possible_duplicate = message.header().get::<bool>(POSS_DUP_FLAG).unwrap_or(false);
let flags = match message_type {
ResendRequest::MSG_TYPE | Reject::MSG_TYPE => {
Self::new(false, true, possible_duplicate)
}
Logout::MSG_TYPE => Self::new(false, false, possible_duplicate),
SequenceReset::MSG_TYPE => {
let is_gap_fill: bool = message.get(GAP_FILL_FLAG).unwrap_or(false);
Self::new(is_gap_fill, is_gap_fill, false)
}
_ => Self::new(true, true, possible_duplicate),
};
Ok(flags)
}
}
pub(crate) fn verify_message(
message: &Message,
config: &SessionConfig,
expected_seq_number: Option<u64>,
flags: VerificationFlags,
) -> Result<(), VerificationIssue> {
check_begin_string(message, config.begin_string.as_str())?;
let actual_seq_number: u64 = message.header().get(MSG_SEQ_NUM).unwrap_or_default();
let expected_sender_comp_id: &str = config.target_comp_id.as_str();
check_sender_comp_id(message, actual_seq_number, expected_sender_comp_id)?;
let expected_target_comp_id: &str = config.sender_comp_id.as_str();
check_target_comp_id(message, actual_seq_number, expected_target_comp_id)?;
let sending_time = check_sending_time(message, actual_seq_number)?;
let possible_duplicate = message.header().get::<bool>(POSS_DUP_FLAG).unwrap_or(false);
if flags.check_orig_sending_time {
check_original_sending_time(message, actual_seq_number, sending_time)?;
}
if let Some(expected_seq_number) = expected_seq_number {
check_sequence_number(
actual_seq_number,
expected_seq_number,
possible_duplicate,
flags.check_too_high,
flags.check_too_low,
)?;
}
Ok(())
}
fn check_begin_string(message: &Message, expected_begin_string: &str) -> Result<(), MessageError> {
let begin_string: &str = message.header().get(BEGIN_STRING).unwrap_or("");
if begin_string != expected_begin_string {
return Err(MessageError::IncorrectBeginString(begin_string.to_string()));
}
Ok(())
}
fn check_sending_time(message: &Message, sequence_number: u64) -> Result<Timestamp, MessageError> {
let sending_time = match message.header().get::<Timestamp>(SENDING_TIME) {
Ok(st) => st,
Err(_) => {
return Err(MessageError::SendingTimeMissing {
msg_seq_num: sequence_number,
});
}
};
let now = Timestamp::utc_now();
if let (Some(sending_chrono), Some(now_chrono)) =
(sending_time.to_chrono_utc(), now.to_chrono_utc())
{
let diff = if sending_chrono > now_chrono {
sending_chrono - now_chrono
} else {
now_chrono - sending_chrono
};
if diff.num_seconds() > SENDING_TIME_THRESHOLD as i64 {
return Err(MessageError::SendingTimeAccuracyIssue {
msg_seq_num: sequence_number,
});
}
}
Ok(sending_time)
}
fn check_original_sending_time(
message: &Message,
sequence_number: u64,
sending_time: Timestamp,
) -> Result<(), MessageError> {
match message.header().get::<Timestamp>(ORIG_SENDING_TIME) {
Ok(original_sending_time) => {
if original_sending_time > sending_time {
return Err(MessageError::OriginalSendingTimeAfterSendingTime {
msg_seq_num: sequence_number,
original_sending_time,
sending_time,
});
}
}
Err(err) => {
error!(error = debug(err), "original sending time is missing");
return Err(MessageError::OriginalSendingTimeMissing {
msg_seq_num: sequence_number,
});
}
}
Ok(())
}
fn check_sender_comp_id(
message: &Message,
sequence_number: u64,
expected_comp_id: &str,
) -> Result<(), MessageError> {
let actual_sender_comp_id: &str = message.header().get(SENDER_COMP_ID).unwrap_or("");
if actual_sender_comp_id != expected_comp_id {
return Err(MessageError::IncorrectCompId {
comp_id: actual_sender_comp_id.to_string(),
comp_id_type: CompIdType::Sender,
msg_seq_num: sequence_number,
});
}
Ok(())
}
fn check_sequence_number(
actual_seq_number: u64,
expected_seq_number: u64,
possible_duplicate: bool,
check_too_high: bool,
check_too_low: bool,
) -> Result<(), VerificationIssue> {
match actual_seq_number.cmp(&expected_seq_number) {
Ordering::Greater if check_too_high => {
return Err(VerificationIssue::SequenceGap {
expected: expected_seq_number,
actual: actual_seq_number,
});
}
Ordering::Less if check_too_low => {
return Err(MessageError::SeqNumberTooLow {
expected: expected_seq_number,
actual: actual_seq_number,
possible_duplicate,
}
.into());
}
_ => {}
}
Ok(())
}
fn check_target_comp_id(
message: &Message,
msg_seq_num: u64,
expected_comp_id: &str,
) -> Result<(), MessageError> {
let actual_target_comp_id: &str = message.header().get(TARGET_COMP_ID).unwrap_or("");
if actual_target_comp_id != expected_comp_id {
return Err(MessageError::IncorrectCompId {
comp_id: actual_target_comp_id.to_string(),
comp_id_type: CompIdType::Target,
msg_seq_num,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{Message, SessionConfig, VerificationFlags, verify_message};
use crate::message::sequence_reset::SequenceReset;
use crate::message::verification_issue::{CompIdType, MessageError, VerificationIssue};
use hotfix_message::field_types::Timestamp;
use hotfix_message::{Part, fix44};
fn build_test_config() -> SessionConfig {
SessionConfig {
begin_string: "FIX.4.4".to_string(),
sender_comp_id: "SENDER".to_string(),
target_comp_id: "TARGET".to_string(),
data_dictionary_path: None,
connection_host: "localhost".to_string(),
connection_port: 9999,
tls_config: None,
heartbeat_interval: 0,
logon_timeout: 0,
logout_timeout: 0,
reconnect_interval: 0,
reset_on_logon: false,
schedule: None,
}
}
fn build_test_message(
begin_string: &str,
sender_comp_id: &str,
target_comp_id: &str,
seq_num: u64,
) -> Message {
build_test_message_with_type(begin_string, "D", sender_comp_id, target_comp_id, seq_num)
}
fn build_test_message_with_type(
begin_string: &str,
message_type: &str,
sender_comp_id: &str,
target_comp_id: &str,
seq_num: u64,
) -> Message {
let mut msg = Message::new(begin_string, message_type);
msg.set(fix44::SENDER_COMP_ID, sender_comp_id);
msg.set(fix44::TARGET_COMP_ID, target_comp_id);
msg.set(fix44::MSG_SEQ_NUM, seq_num);
msg.set(fix44::SENDING_TIME, Timestamp::utc_now());
msg
}
#[test]
fn test_creating_flags_for_gap_fill_message_should_skip_check_orig_sending_time() {
let msg = build_test_message_with_type(
"FIX.4.4",
SequenceReset::MSG_TYPE,
"TARGET",
"SENDER",
42,
);
let flags = VerificationFlags::for_message(&msg).unwrap();
assert!(!flags.check_orig_sending_time);
assert!(!flags.check_too_high);
assert!(!flags.check_too_low);
}
#[test]
fn test_creating_flags_for_gap_fill_message_should_skip_check_orig_sending_time_even_for_poss_duplicate()
{
let mut msg = build_test_message_with_type(
"FIX.4.4",
SequenceReset::MSG_TYPE,
"TARGET",
"SENDER",
42,
);
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
let flags = VerificationFlags::for_message(&msg).unwrap();
assert!(!flags.check_orig_sending_time);
assert!(!flags.check_too_high);
assert!(!flags.check_too_low);
}
#[test]
fn test_creating_flags_for_app_messages_does_not_skip_orig_sending_time() {
let mut msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
let flags = VerificationFlags::for_message(&msg).unwrap();
assert!(flags.check_orig_sending_time);
assert!(flags.check_too_high);
assert!(flags.check_too_low);
}
#[test]
fn test_creating_flags_for_app_messages_skip_orig_sending_time_when_it_is_not_poss_dup() {
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
let flags = VerificationFlags::for_message(&msg).unwrap();
assert!(!flags.check_orig_sending_time);
assert!(flags.check_too_high);
assert!(flags.check_too_low);
}
#[test]
fn test_verify_message_happy_path() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(result.is_ok());
}
#[test]
fn test_incorrect_begin_string() {
let config = build_test_config();
let msg = build_test_message("FIX.4.2", "TARGET", "SENDER", 42);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectBeginString(_)
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::IncorrectBeginString(
begin_string,
))) = result
{
assert_eq!(begin_string, "FIX.4.2");
}
}
#[test]
fn test_incorrect_sender_comp_id() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "WRONG_SENDER", "SENDER", 42);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectCompId {
comp_id_type: CompIdType::Sender,
..
}
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::IncorrectCompId {
comp_id,
comp_id_type,
msg_seq_num,
})) = result
{
assert_eq!(comp_id, "WRONG_SENDER");
assert!(matches!(comp_id_type, CompIdType::Sender));
assert_eq!(msg_seq_num, 42);
}
}
#[test]
fn test_incorrect_target_comp_id() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "WRONG_TARGET", 42);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectCompId {
comp_id_type: CompIdType::Target,
..
}
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::IncorrectCompId {
comp_id,
comp_id_type,
msg_seq_num,
})) = result
{
assert_eq!(comp_id, "WRONG_TARGET");
assert!(matches!(comp_id_type, CompIdType::Target));
assert_eq!(msg_seq_num, 42);
}
}
#[test]
fn test_seq_number_too_low() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 40);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::SeqNumberTooLow { .. }
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::SeqNumberTooLow {
expected,
actual,
possible_duplicate,
})) = result
{
assert_eq!(expected, 42);
assert_eq!(actual, 40);
assert!(!possible_duplicate);
}
}
#[test]
fn test_seq_number_too_low_with_poss_dup_flag() {
let config = build_test_config();
let mut msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 40);
let sending_time: Timestamp = msg.header().get(fix44::SENDING_TIME).unwrap();
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
msg.header_mut().set(fix44::ORIG_SENDING_TIME, sending_time);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, true),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::SeqNumberTooLow { .. }
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::SeqNumberTooLow {
expected,
actual,
possible_duplicate,
})) = result
{
assert_eq!(expected, 42);
assert_eq!(actual, 40);
assert!(possible_duplicate);
}
}
#[test]
fn test_seq_number_too_low_with_poss_dup_flag_without_orig_time() {
let config = build_test_config();
let mut msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 40);
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::SeqNumberTooLow { .. }
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::SeqNumberTooLow {
expected,
actual,
possible_duplicate,
})) = result
{
assert_eq!(expected, 42);
assert_eq!(actual, 40);
assert!(possible_duplicate);
}
}
#[test]
fn test_seq_number_too_high() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 50);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(result, Err(VerificationIssue::SequenceGap { .. })));
if let Err(VerificationIssue::SequenceGap { expected, actual }) = result {
assert_eq!(expected, 42);
assert_eq!(actual, 50);
}
}
#[test]
fn test_poss_dup_flag_missing_orig_sending_time() {
let config = build_test_config();
let mut msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, true),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::OriginalSendingTimeMissing { .. }
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::OriginalSendingTimeMissing {
msg_seq_num,
})) = result
{
assert_eq!(msg_seq_num, 42);
}
}
#[test]
fn test_poss_dup_flag_with_valid_orig_sending_time() {
let config = build_test_config();
let mut msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
let orig_time = Timestamp::utc_now();
std::thread::sleep(std::time::Duration::from_millis(10));
let sending_time = Timestamp::utc_now();
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
msg.header_mut().set(fix44::ORIG_SENDING_TIME, orig_time);
msg.header_mut().pop(fix44::SENDING_TIME);
msg.header_mut().set(fix44::SENDING_TIME, sending_time);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, true),
);
assert!(result.is_ok());
}
#[test]
fn test_check_orig_time_is_skippen_when_flag_is_turned_off() {
let config = build_test_config();
let mut msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
std::thread::sleep(std::time::Duration::from_millis(10));
let sending_time = Timestamp::utc_now();
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
msg.header_mut().pop(fix44::SENDING_TIME);
msg.header_mut().set(fix44::SENDING_TIME, sending_time);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(result.is_ok());
}
#[test]
fn test_orig_sending_time_after_sending_time() {
let config = build_test_config();
let mut msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
let sending_time = Timestamp::utc_now();
std::thread::sleep(std::time::Duration::from_millis(10));
let orig_time = Timestamp::utc_now();
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
msg.header_mut().set(fix44::ORIG_SENDING_TIME, orig_time);
msg.header_mut().pop(fix44::SENDING_TIME);
msg.header_mut().set(fix44::SENDING_TIME, sending_time);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, true),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::OriginalSendingTimeAfterSendingTime { .. }
))
));
if let Err(VerificationIssue::InvalidMessage(
MessageError::OriginalSendingTimeAfterSendingTime {
msg_seq_num,
original_sending_time,
sending_time: st,
},
)) = result
{
assert_eq!(msg_seq_num, 42);
assert!(original_sending_time > st);
}
}
#[test]
fn test_check_orig_sending_time_after_sending_time_is_skipped_when_flag_turned_off() {
let config = build_test_config();
let mut msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
let sending_time = Timestamp::utc_now();
std::thread::sleep(std::time::Duration::from_millis(10));
let orig_time = Timestamp::utc_now();
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
msg.header_mut().set(fix44::ORIG_SENDING_TIME, orig_time);
msg.header_mut().pop(fix44::SENDING_TIME);
msg.header_mut().set(fix44::SENDING_TIME, sending_time);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(result.is_ok());
}
#[test]
fn test_poss_dup_flag_with_equal_timestamps() {
let config = build_test_config();
let mut msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
let timestamp = Timestamp::utc_now();
msg.header_mut().set(fix44::POSS_DUP_FLAG, true);
msg.header_mut()
.set(fix44::ORIG_SENDING_TIME, timestamp.clone());
msg.header_mut().pop(fix44::SENDING_TIME);
msg.header_mut().set(fix44::SENDING_TIME, timestamp);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, true),
);
assert!(result.is_ok());
}
#[test]
fn test_missing_begin_string() {
let config = build_test_config();
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::SENDER_COMP_ID, "TARGET");
msg.set(fix44::TARGET_COMP_ID, "SENDER");
msg.set(fix44::MSG_SEQ_NUM, 42u64);
msg.set(fix44::SENDING_TIME, Timestamp::utc_now());
msg.header_mut().pop(fix44::BEGIN_STRING);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectBeginString(_)
))
));
}
#[test]
fn test_missing_sender_comp_id() {
let config = build_test_config();
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::TARGET_COMP_ID, "SENDER");
msg.set(fix44::MSG_SEQ_NUM, 42u64);
msg.set(fix44::SENDING_TIME, Timestamp::utc_now());
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectCompId {
comp_id_type: CompIdType::Sender,
..
}
))
));
}
#[test]
fn test_missing_target_comp_id() {
let config = build_test_config();
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::SENDER_COMP_ID, "TARGET");
msg.set(fix44::MSG_SEQ_NUM, 42u64);
msg.set(fix44::SENDING_TIME, Timestamp::utc_now());
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectCompId {
comp_id_type: CompIdType::Target,
..
}
))
));
}
#[test]
fn test_missing_seq_number() {
let config = build_test_config();
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::SENDER_COMP_ID, "TARGET");
msg.set(fix44::TARGET_COMP_ID, "SENDER");
msg.set(fix44::SENDING_TIME, Timestamp::utc_now());
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::SeqNumberTooLow { .. }
))
));
}
#[test]
fn test_seq_number_zero_when_expecting_one() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 0);
let result = verify_message(
&msg,
&config,
Some(1),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::SeqNumberTooLow { .. }
))
));
}
#[test]
fn test_first_message_with_seq_num_one() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 1);
let result = verify_message(
&msg,
&config,
Some(1),
VerificationFlags::new(true, true, false),
);
assert!(result.is_ok());
}
#[test]
fn test_verification_order_begin_string_checked_first() {
let config = build_test_config();
let msg = build_test_message("FIX.4.2", "TARGET", "SENDER", 100);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectBeginString(_)
))
));
}
#[test]
fn test_verification_order_sender_comp_id_checked_before_target() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "WRONG_SENDER", "WRONG_TARGET", 42);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectCompId {
comp_id_type: CompIdType::Sender,
..
}
))
));
}
#[test]
fn test_missing_sending_time() {
let config = build_test_config();
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::SENDER_COMP_ID, "TARGET");
msg.set(fix44::TARGET_COMP_ID, "SENDER");
msg.set(fix44::MSG_SEQ_NUM, 42u64);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::SendingTimeMissing { .. }
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::SendingTimeMissing {
msg_seq_num,
})) = result
{
assert_eq!(msg_seq_num, 42);
}
}
#[test]
fn test_sending_time_too_far_in_past() {
use chrono::Duration;
let config = build_test_config();
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::SENDER_COMP_ID, "TARGET");
msg.set(fix44::TARGET_COMP_ID, "SENDER");
msg.set(fix44::MSG_SEQ_NUM, 42u64);
let now = chrono::Utc::now();
let past_time = now - Duration::seconds(122);
let past_timestamp: Timestamp = past_time.naive_utc().into();
msg.set(fix44::SENDING_TIME, past_timestamp);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::SendingTimeAccuracyIssue { .. }
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::SendingTimeAccuracyIssue {
msg_seq_num,
})) = result
{
assert_eq!(msg_seq_num, 42);
}
}
#[test]
fn test_sending_time_too_far_in_future() {
use chrono::Duration;
let config = build_test_config();
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::SENDER_COMP_ID, "TARGET");
msg.set(fix44::TARGET_COMP_ID, "SENDER");
msg.set(fix44::MSG_SEQ_NUM, 42u64);
let now = chrono::Utc::now();
let future_time = now + Duration::seconds(122);
let future_timestamp: Timestamp = future_time.naive_utc().into();
msg.set(fix44::SENDING_TIME, future_timestamp);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::SendingTimeAccuracyIssue { .. }
))
));
if let Err(VerificationIssue::InvalidMessage(MessageError::SendingTimeAccuracyIssue {
msg_seq_num,
})) = result
{
assert_eq!(msg_seq_num, 42);
}
}
#[test]
fn test_sending_time_at_threshold_boundary() {
use chrono::Duration;
let config = build_test_config();
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::SENDER_COMP_ID, "TARGET");
msg.set(fix44::TARGET_COMP_ID, "SENDER");
msg.set(fix44::MSG_SEQ_NUM, 42u64);
let now = chrono::Utc::now();
let boundary_time = now - Duration::seconds(120);
let boundary_timestamp: Timestamp = boundary_time.naive_utc().into();
msg.set(fix44::SENDING_TIME, boundary_timestamp);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(result.is_ok());
}
#[test]
fn test_sending_time_within_threshold() {
use chrono::Duration;
let config = build_test_config();
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::SENDER_COMP_ID, "TARGET");
msg.set(fix44::TARGET_COMP_ID, "SENDER");
msg.set(fix44::MSG_SEQ_NUM, 42u64);
let now = chrono::Utc::now();
let valid_time = now - Duration::seconds(60);
let valid_timestamp: Timestamp = valid_time.naive_utc().into();
msg.set(fix44::SENDING_TIME, valid_timestamp);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, true, false),
);
assert!(result.is_ok());
}
#[test]
fn test_seq_number_too_high_skipped_when_check_too_high_false() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 50);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(false, true, false),
);
assert!(result.is_ok());
}
#[test]
fn test_seq_number_too_low_skipped_when_check_too_low_false() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 40);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, false, false),
);
assert!(result.is_ok());
}
#[test]
fn test_all_checks_disabled() {
let config = build_test_config();
let msg_high = build_test_message("FIX.4.4", "TARGET", "SENDER", 50);
assert!(
verify_message(
&msg_high,
&config,
Some(42),
VerificationFlags::new(false, false, false)
)
.is_ok()
);
let msg_low = build_test_message("FIX.4.4", "TARGET", "SENDER", 40);
assert!(
verify_message(
&msg_low,
&config,
Some(42),
VerificationFlags::new(false, false, false)
)
.is_ok()
);
let msg_match = build_test_message("FIX.4.4", "TARGET", "SENDER", 42);
assert!(
verify_message(
&msg_match,
&config,
Some(42),
VerificationFlags::new(false, false, false)
)
.is_ok()
);
}
#[test]
fn test_check_too_high_true_still_catches_too_high() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 50);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(true, false, false),
);
assert!(matches!(result, Err(VerificationIssue::SequenceGap { .. })));
}
#[test]
fn test_check_too_low_true_still_catches_too_low() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "TARGET", "SENDER", 40);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(false, true, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::SeqNumberTooLow { .. }
))
));
}
#[test]
fn test_non_seq_checks_still_applied_when_seq_checks_disabled() {
let config = build_test_config();
let msg = build_test_message("FIX.4.4", "WRONG_SENDER", "SENDER", 42);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(false, false, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectCompId { .. }
))
));
let msg = build_test_message("FIX.4.2", "TARGET", "SENDER", 42);
let result = verify_message(
&msg,
&config,
Some(42),
VerificationFlags::new(false, false, false),
);
assert!(matches!(
result,
Err(VerificationIssue::InvalidMessage(
MessageError::IncorrectBeginString(_)
))
));
}
}