use truefix_core::{Field, Message};
use crate::runner::{ExpectMsg, Scenario, SessionTweaks, Step, client_message};
pub const SUITE_VERSIONS: &[&str] = &[
"FIX.4.0",
"FIX.4.1",
"FIX.4.2",
"FIX.4.3",
"FIX.4.4",
"FIX.5.0",
"FIX.5.0SP1",
"FIX.5.0SP2",
"FIX.Latest",
];
fn logon(version: &str, seq: i64, reset: bool) -> Message {
let mut m = client_message(version, "A", seq);
m.body.set(Field::int(98, 0));
m.body.set(Field::int(108, 30));
if reset && version != "FIX.4.0" {
m.body.set(Field::string(141, "Y"));
}
m
}
fn scenario(name: &str, version: &str, steps: Vec<Step>) -> Scenario {
scenario_with(name, version, steps, SessionTweaks::default())
}
fn scenario_with(name: &str, version: &str, steps: Vec<Step>, tweaks: SessionTweaks) -> Scenario {
Scenario {
name: name.to_owned(),
versions: vec![version.to_owned()],
steps,
tweaks,
}
}
fn valid_logon(v: &str) -> Scenario {
scenario(
"1a_ValidLogonWithCorrectMsgSeqNum",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
],
)
}
fn logon_seq_too_high(v: &str) -> Scenario {
scenario(
"1a_ValidLogonMsgSeqNumTooHigh",
v,
vec![
Step::Send(logon(v, 10, false)), Step::Expect(ExpectMsg::of("2").field(7, "1")), ],
)
}
fn msgseqnum_too_high(v: &str) -> Scenario {
scenario(
"2b_MsgSeqNumTooHigh",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(client_message(v, "0", 10)), Step::Expect(ExpectMsg::of("2").field(7, "2")), ],
)
}
fn msgseqnum_too_low(v: &str) -> Scenario {
scenario(
"2c_MsgSeqNumTooLow",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(client_message(v, "0", 1)), Step::Expect(ExpectMsg::of("5")), Step::ExpectDisconnect,
],
)
}
fn received_test_request(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 2);
tr.body.set(Field::string(112, "HELLO"));
scenario(
"4b_ReceivedTestRequest",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "HELLO")),
],
)
}
fn reverse_route(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 2);
tr.body.set(Field::string(112, "ROUTE-ME"));
tr.header.set(Field::string(115, "BROKER")); scenario(
"ReverseRoute",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr),
Step::Expect(
ExpectMsg::of("0")
.field(112, "ROUTE-ME")
.field(128, "BROKER"),
), ],
)
}
fn reverse_route_empty_tags(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 2);
tr.body.set(Field::string(112, "ROUTE-EMPTY"));
tr.header.set(Field::string(115, "")); scenario(
"ReverseRouteWithEmptyRoutingTags",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "ROUTE-EMPTY").field(128, "")),
],
)
}
fn unsolicited_logout(v: &str) -> Scenario {
scenario(
"13b_UnsolicitedLogoutMessage",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(client_message(v, "5", 2)), Step::Expect(ExpectMsg::of("5")),
Step::ExpectDisconnect,
],
)
}
fn too_high_logout_processed_immediately(v: &str) -> Scenario {
scenario(
"NEW85_TooHighLogoutProcessedImmediately",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(client_message(v, "5", 5)),
Step::Expect(ExpectMsg::of("5")),
Step::ExpectDisconnect,
],
)
}
fn resend_request_gap_fill(v: &str) -> Scenario {
let mut rr = client_message(v, "2", 2);
rr.body.set(Field::int(7, 1)); rr.body.set(Field::int(16, 0)); scenario(
"2f_ResendRequestGapFill",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(rr),
Step::Expect(
ExpectMsg::of("4")
.field(123, "Y")
.field(36, "2")
.field(34, "1"),
),
],
)
}
fn sequence_reset_reset(v: &str) -> Scenario {
let mut sr = client_message(v, "4", 99); sr.body.set(Field::int(36, 5)); let mut tr = client_message(v, "1", 5); tr.body.set(Field::string(112, "AFTER-RESET"));
scenario(
"2g_SequenceResetReset",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(sr),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "AFTER-RESET").field(34, "2")),
],
)
}
fn poss_dup_too_low(v: &str) -> Scenario {
let mut dup = client_message(v, "0", 1); dup.header.set(Field::string(43, "Y")); let mut tr = client_message(v, "1", 2);
tr.body.set(Field::string(112, "STILL-UP"));
scenario(
"2m_PossDupMsgSeqNumTooLow",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(dup),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "STILL-UP")),
],
)
}
fn poss_dup_orig_sending_time_after_sending_time(v: &str) -> Scenario {
let mut dup = client_message(v, "0", 1); dup.header.set(Field::string(43, "Y")); dup.header.set(Field::string(122, "20240101-00:00:05")); scenario(
"GAP08_PossDupOrigSendingTimeAfterSendingTime",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(dup),
Step::Expect(ExpectMsg::of("3")),
Step::Expect(ExpectMsg::of("5")), Step::ExpectDisconnect,
],
)
}
fn duplicate_logon_rejected(v: &str) -> Scenario {
scenario(
"GAP18a_DuplicateLogonRejected",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(logon(v, 2, false)),
Step::Expect(ExpectMsg::of("5")), Step::ExpectDisconnect,
],
)
}
fn expect_session_reject_reason(v: &str, msg_type: &str, code: &str) -> ExpectMsg {
let e = ExpectMsg::of(msg_type);
if v == "FIX.4.0" || v == "FIX.4.1" {
e.without_field(373)
} else {
e.field(373, code)
}
}
fn sequence_reset_reset_missing_new_seq_no_rejected(v: &str) -> Scenario {
let sr = client_message(v, "4", 2); scenario(
"BUG06_SequenceResetMissingNewSeqNoRejected",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(sr),
Step::Expect(expect_session_reject_reason(v, "3", "1").field(34, "2")), ],
)
}
fn sequence_reset_gap_fill_missing_new_seq_no_rejected(v: &str) -> Scenario {
let mut sr = client_message(v, "4", 2);
sr.body.set(Field::string(123, "Y")); scenario(
"NEW70_GapFillMissingNewSeqNoRejected",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(sr),
Step::Expect(
expect_session_reject_reason(v, "3", "1") .field(371, "36") .field(34, "2"), ),
],
)
}
fn resend_request_missing_begin_seq_no_rejected(v: &str) -> Scenario {
let mut rr = client_message(v, "2", 2);
rr.body.set(Field::int(16, 5)); scenario(
"BUG22_ResendRequestMissingBeginSeqNoRejected",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(rr),
Step::Expect(
expect_session_reject_reason(v, "3", "1") .field(371, "7") .field(34, "2"), ),
],
)
}
fn resend_request_missing_end_seq_no_rejected(v: &str) -> Scenario {
let mut rr = client_message(v, "2", 2);
rr.body.set(Field::int(7, 1)); scenario(
"BUG22_ResendRequestMissingEndSeqNoRejected",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(rr),
Step::Expect(
expect_session_reject_reason(v, "3", "1") .field(371, "16") .field(34, "2"), ),
],
)
}
fn gap_fill_drained_message_is_validated(v: &str) -> Scenario {
let mut invalid_order = new_order_single(3); invalid_order.body.set(Field::string(54, "Z")); scenario(
"B3_GapFillDrainedMessageIsValidated",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(invalid_order),
Step::Expect(ExpectMsg::of("2").field(7, "2").field(34, "2")), Step::Send(client_message(v, "0", 2)), Step::Expect(ExpectMsg::of("3").field(373, "5").field(34, "3")), ],
)
}
fn dictionary_failure_disconnect_sends_logout(v: &str) -> Scenario {
let mut invalid_order = new_order_single(2);
invalid_order.body.set(Field::string(54, "Z")); scenario_with(
"B5_DictionaryFailureDisconnectSendsLogout",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(invalid_order),
Step::Expect(ExpectMsg::of("3").field(373, "5")), Step::Expect(ExpectMsg::of("5")), Step::ExpectDisconnect,
],
SessionTweaks {
disconnect_on_error: true,
..SessionTweaks::default()
},
)
}
fn dict_invalid_logon_disconnects_without_disconnect_on_error(v: &str) -> Scenario {
let mut bad_logon = client_message(v, "A", 1);
bad_logon.body.set(Field::int(108, 30)); scenario_with(
"NEW63_DictInvalidLogonDisconnectsWithoutDisconnectOnError",
v,
vec![
Step::Send(bad_logon),
Step::Expect(ExpectMsg::of("3")), Step::Expect(ExpectMsg::of("5")), Step::ExpectDisconnect,
],
SessionTweaks {
disconnect_on_error: false,
..SessionTweaks::default()
},
)
}
fn unparseable_sending_time_fails_latency(v: &str) -> Scenario {
let mut bad_logon = logon(v, 1, true);
bad_logon.header.set(Field::string(52, "not-a-timestamp"));
scenario_with(
"B7_UnparseableSendingTimeFailsLatency",
v,
vec![
Step::Send(bad_logon),
Step::Expect(ExpectMsg::of("5").field(58, "SendingTime accuracy problem")),
Step::ExpectDisconnect,
],
SessionTweaks {
check_latency: true,
..SessionTweaks::default()
},
)
}
fn logon_response_carries_reset_flag(v: &str) -> Scenario {
let expect = if v == "FIX.4.0" {
ExpectMsg::of("A").without_field(141)
} else {
ExpectMsg::of("A").field(141, "Y")
};
scenario(
"1d_LogonResponseResetFlag",
v,
vec![Step::Send(logon(v, 1, true)), Step::Expect(expect)],
)
}
fn logon_response_does_not_echo_reset_flag_when_inbound_lacks_it(v: &str) -> Scenario {
scenario(
"BUG28_LogonResponseDoesNotEchoAbsentResetFlag",
v,
vec![
Step::Send(logon(v, 1, false)), Step::Expect(ExpectMsg::of("A").without_field(141)),
],
)
}
fn admin_message_dictionary_invalid_is_rejected(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 2); tr.body.set(Field::string(112, "TESTREQID"));
tr.body.set(Field::string(4321, "not-a-real-tag")); scenario(
"BUG89_AdminMessageDictionaryInvalidIsRejected",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr),
Step::Expect(ExpectMsg::of("3")), ],
)
}
fn missing_msg_seq_num(v: &str) -> Scenario {
let mut m = Message::new();
m.header
.set(Field::string(8, crate::runner::wire_begin_string(v)));
m.header.set(Field::string(35, "1")); m.header.set(Field::string(49, "CLIENT"));
m.header.set(Field::string(56, "SERVER"));
m.header.set(Field::string(52, "20240101-00:00:00"));
m.body.set(Field::string(112, "NOSEQ"));
scenario(
"2_MissingMsgSeqNum",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(m),
Step::Expect(ExpectMsg::of("3")), ],
)
}
fn idle_heartbeat_emitted(v: &str) -> Scenario {
let mut lo = logon(v, 1, true);
lo.body.set(Field::int(108, 1)); scenario(
"0_IdleHeartbeatEmitted",
v,
vec![
Step::Send(lo),
Step::Expect(ExpectMsg::of("A")),
Step::Expect(ExpectMsg::of("1").field(112, "TEST")), Step::Expect(ExpectMsg::of("0")), ],
)
}
fn test_request_on_silence(v: &str) -> Scenario {
let mut lo = logon(v, 1, true);
lo.body.set(Field::int(108, 1));
scenario(
"4_TestRequestOnSilence",
v,
vec![
Step::Send(lo),
Step::Expect(ExpectMsg::of("A")),
Step::Expect(ExpectMsg::of("1").field(112, "TEST")), Step::Expect(ExpectMsg::of("0")), Step::Expect(ExpectMsg::of("0")), Step::ExpectDisconnect, ],
)
}
fn acceptor_initiated_logout(v: &str) -> Scenario {
let mut order = new_order_single(2);
order.body.set(Field::string(11, "LOGOUT")); scenario_with(
"5_AcceptorInitiatedLogout",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("5")), Step::Send(client_message(v, "5", 3)), Step::ExpectDisconnect,
],
SessionTweaks {
executor_app: true,
..SessionTweaks::default()
},
)
}
fn logon_adopts_heartbeat_interval(v: &str) -> Scenario {
let mut lo = logon(v, 1, true);
lo.body.set(Field::int(108, 45)); scenario(
"1c_LogonAdoptsHeartBtInt",
v,
vec![
Step::Send(lo),
Step::Expect(ExpectMsg::of("A").field(108, "45")),
],
)
}
fn out_of_order_queued_then_drained(v: &str) -> Scenario {
let mut tr3 = client_message(v, "1", 3);
tr3.body.set(Field::string(112, "THREE"));
let mut tr2 = client_message(v, "1", 2);
tr2.body.set(Field::string(112, "TWO"));
scenario(
"2x_OutOfOrderQueuedThenDrained",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr3), Step::Expect(ExpectMsg::of("2").field(7, "2")), Step::Send(tr2), Step::Expect(ExpectMsg::of("0").field(112, "TWO")), Step::Expect(ExpectMsg::of("0").field(112, "THREE")), ],
)
}
fn resend_request_not_duplicated(v: &str) -> Scenario {
let mut tr2 = client_message(v, "1", 2);
tr2.body.set(Field::string(112, "TWO"));
scenario(
"2_ResendRequestNotDuplicated",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(client_message(v, "0", 4)), Step::Expect(ExpectMsg::of("2").field(7, "2").field(34, "2")),
Step::Send(client_message(v, "0", 5)), Step::Send(tr2), Step::Expect(ExpectMsg::of("0").field(112, "TWO").field(34, "3")),
],
)
}
fn resend_request_begin_zero_treated_as_one(v: &str) -> Scenario {
let mut rr = client_message(v, "2", 2);
rr.body.set(Field::int(7, 0)); rr.body.set(Field::int(16, 0));
scenario(
"2_ResendRequestBeginZeroTreatedAsOne",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(rr),
Step::Expect(
ExpectMsg::of("4")
.field(123, "Y")
.field(36, "2")
.field(34, "1"),
),
],
)
}
fn resend_request_bounded_end(v: &str) -> Scenario {
let mut rr = client_message(v, "2", 2);
rr.body.set(Field::int(7, 1)); rr.body.set(Field::int(16, 1)); scenario(
"2f3_ResendRequestBoundedEnd",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(rr),
Step::Expect(
ExpectMsg::of("4")
.field(123, "Y")
.field(36, "2")
.field(34, "1"),
),
],
)
}
fn resend_request_too_high_answered_immediately(v: &str) -> Scenario {
let mut rr = client_message(v, "2", 5); rr.body.set(Field::int(7, 1)); rr.body.set(Field::int(16, 1)); let mut after_gap = client_message(v, "1", 6);
after_gap
.body
.set(Field::string(112, "NO-DUPLICATE-RESEND"));
scenario(
"BUG29_ResendRequestTooHighAnsweredImmediately",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(rr),
Step::Expect(ExpectMsg::of("4").field(34, "1")),
Step::Expect(ExpectMsg::of("2").field(7, "2").field(34, "2")),
Step::Send(client_message(v, "0", 2)),
Step::Send(client_message(v, "0", 3)),
Step::Send(client_message(v, "0", 4)),
Step::Send(after_gap),
Step::Expect(
ExpectMsg::of("0")
.field(112, "NO-DUPLICATE-RESEND")
.field(34, "3"),
),
],
)
}
fn sequence_reset_gap_fill_advances(v: &str) -> Scenario {
let mut sr = client_message(v, "4", 2);
sr.body.set(Field::string(123, "Y")); sr.body.set(Field::int(36, 5)); let mut tr = client_message(v, "1", 5);
tr.body.set(Field::string(112, "GAP-FILLED"));
scenario(
"2d_SequenceResetGapFillAdvances",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(sr),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "GAP-FILLED").field(34, "2")),
],
)
}
fn sequence_reset_gap_fill_backward_ignored(v: &str) -> Scenario {
let mut sr = client_message(v, "4", 2);
sr.body.set(Field::string(123, "Y"));
sr.body.set(Field::int(36, 1)); let mut tr = client_message(v, "1", 2); tr.body.set(Field::string(112, "NO-REWIND"));
scenario(
"2e_SequenceResetGapFillBackwardIgnored",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(sr),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "NO-REWIND").field(34, "2")),
],
)
}
fn resend_request_nothing_to_resend(v: &str) -> Scenario {
let mut rr = client_message(v, "2", 2);
rr.body.set(Field::int(7, 5)); rr.body.set(Field::int(16, 0));
let mut tr = client_message(v, "1", 3); tr.body.set(Field::string(112, "ALIVE"));
scenario(
"2h_ResendRequestNothingToResend",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(rr),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "ALIVE").field(34, "2")),
],
)
}
fn reject_message_consumed(v: &str) -> Scenario {
let mut rej = client_message(v, "3", 2);
rej.body.set(Field::int(45, 1)); let mut tr = client_message(v, "1", 3); tr.body.set(Field::string(112, "AFTER-REJECT"));
scenario(
"3a_ReceivedRejectConsumed",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(rej),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "AFTER-REJECT")),
],
)
}
fn heartbeat_consumed(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 3); tr.body.set(Field::string(112, "AFTER-HB"));
scenario(
"0a_ReceivedHeartbeatConsumed",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(client_message(v, "0", 2)), Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "AFTER-HB")),
],
)
}
fn new_order_single(seq: i64) -> Message {
let mut m = client_message("FIX.4.4", "D", seq);
m.body.set(Field::string(11, "ORDER-1")); m.body.set(Field::string(21, "1")); m.body.set(Field::string(55, "AAPL")); m.body.set(Field::string(54, "1")); m.body.set(Field::string(60, "20240101-00:00:00")); m.body.set(Field::string(40, "2")); m
}
fn new_order_single_42(seq: i64) -> Message {
let mut m = client_message("FIX.4.2", "D", seq);
m.body.set(Field::string(11, "ORDER-42")); m.body.set(Field::string(21, "1")); m.body.set(Field::string(55, "AAPL")); m.body.set(Field::string(54, "1")); m.body.set(Field::string(60, "20240101-00:00:00")); m.body.set(Field::int(38, 100)); m.body.set(Field::string(40, "2")); m
}
fn invalid_tag_number_42() -> Scenario {
let mut order = new_order_single_42(2);
order.body.set(Field::string(999, "X")); scenario(
"14a_InvalidTagNumber_42",
"FIX.4.2",
vec![
Step::Send(logon("FIX.4.2", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "0")), ],
)
}
fn tag_not_defined_for_msg_type_42() -> Scenario {
let mut order = new_order_single_42(2);
order.body.set(Field::string(112, "X")); scenario(
"14c_TagNotDefinedForMsgType_42",
"FIX.4.2",
vec![
Step::Send(logon("FIX.4.2", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "2")), ],
)
}
fn tag_specified_without_value_42() -> Scenario {
let mut order = new_order_single_42(2);
order.body.set(Field::new(11, Vec::new())); scenario(
"14d_TagSpecifiedWithoutValue_42",
"FIX.4.2",
vec![
Step::Send(logon("FIX.4.2", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "4")), ],
)
}
fn incorrect_data_format_42() -> Scenario {
let mut order = new_order_single_42(2);
order.body.set(Field::string(38, "abc")); scenario(
"14f_IncorrectDataFormat_42",
"FIX.4.2",
vec![
Step::Send(logon("FIX.4.2", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "6")), ],
)
}
fn valid_new_order_accepted_42() -> Scenario {
let mut tr = client_message("FIX.4.2", "1", 3);
tr.body.set(Field::string(112, "ORDER42-OK"));
scenario(
"14_valid_NewOrderSingleAccepted_42",
"FIX.4.2",
vec![
Step::Send(logon("FIX.4.2", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(new_order_single_42(2)),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "ORDER42-OK")),
],
)
}
fn required_field_missing_42() -> Scenario {
let mut order = new_order_single_42(2);
order.body = {
let mut b = truefix_core::FieldMap::new();
for f in new_order_single_42(2).body.fields() {
if f.tag() != 21 {
b.set(Field::new(f.tag(), f.value_bytes().to_vec()));
}
}
b
};
scenario(
"14b_RequiredFieldMissing_42",
"FIX.4.2",
vec![
Step::Send(logon("FIX.4.2", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "1")), ],
)
}
fn incorrect_enum_value_42() -> Scenario {
let mut order = new_order_single_42(2);
order.body.set(Field::string(54, "Z")); scenario(
"14e_IncorrectEnumValue_42",
"FIX.4.2",
vec![
Step::Send(logon("FIX.4.2", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "5")), ],
)
}
fn valid_new_order_accepted(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 3); tr.body.set(Field::string(112, "ORDER-OK"));
scenario(
"14_valid_NewOrderSingleAccepted",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(new_order_single(2)),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "ORDER-OK")),
],
)
}
fn min_qty_field_accepted(name: &str, v: &str, order: Message) -> Scenario {
let mut tr = client_message(v, "1", 3); tr.body.set(Field::string(112, "MINQTY-OK"));
scenario(
name,
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "MINQTY-OK")),
],
)
}
fn min_qty_field_accepted_44() -> Scenario {
let mut order = new_order_single(2);
order.body.set(Field::int(110, 100)); min_qty_field_accepted("MinQty_FieldAccepted_44", "FIX.4.4", order)
}
fn min_qty_field_accepted_42() -> Scenario {
let mut order = new_order_single_42(2);
order.body.set(Field::int(110, 50)); min_qty_field_accepted("MinQty_FieldAccepted_42", "FIX.4.2", order)
}
fn nos_with_group(seq: i64, group: &[(u32, &str)]) -> Message {
let mut m = new_order_single(seq);
for (t, v) in group {
m.body.set(Field::string(*t, v));
}
m
}
fn group_count_mismatch(v: &str) -> Scenario {
let order = nos_with_group(2, &[(453, "2"), (448, "A"), (447, "1"), (452, "1")]);
scenario(
"14i_RepeatingGroupCountNotEqual",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "16")), ],
)
}
fn empty_value_inside_group(v: &str) -> Scenario {
let order = nos_with_group(2, &[(453, "1"), (448, ""), (447, "1"), (452, "1")]);
scenario(
"NEW69_EmptyValueInsideRepeatingGroup",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(
ExpectMsg::of("3")
.field(371, "448") .field(373, "4"), ),
],
)
}
fn group_out_of_order(v: &str) -> Scenario {
let order = nos_with_group(2, &[(453, "1"), (448, "A"), (452, "1"), (447, "1")]);
scenario(
"14j_OutOfOrderRepeatingGroupMembers",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "15")), ],
)
}
fn nested_group_missing_delimiter(v: &str) -> Scenario {
let order = nos_with_group(
2,
&[
(453, "1"),
(448, "A"),
(447, "1"),
(452, "1"),
(802, "1"),
(803, "1"),
(523, "S"),
],
);
scenario(
"QFJ934_MissingDelimiterNestedRepeatingGroup",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "15")),
],
)
}
fn group_zero_count(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 3);
tr.body.set(Field::string(112, "ZERO-GRP-OK"));
let order = nos_with_group(2, &[(453, "0")]);
scenario(
"21_RepeatingGroupSpecifierWithValueOfZero",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "ZERO-GRP-OK")),
],
)
}
fn app_order_executed(v: &str) -> Scenario {
scenario_with(
"app_NewOrderSingleExecuted",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(new_order_single(2)),
Step::Expect(ExpectMsg::of("8").field(11, "ORDER-1").field(150, "0")),
],
SessionTweaks {
executor_app: true,
..SessionTweaks::default()
},
)
}
fn app_orders_sequenced(v: &str) -> Scenario {
scenario_with(
"app_OrdersOutboundSequenced",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(new_order_single(2)),
Step::Expect(ExpectMsg::of("8").field(34, "2")), Step::Send(new_order_single(3)),
Step::Expect(ExpectMsg::of("8").field(34, "3")), ],
SessionTweaks {
executor_app: true,
..SessionTweaks::default()
},
)
}
fn app_message_resent_as_poss_dup(v: &str) -> Scenario {
let mut rr = client_message(v, "2", 3); rr.body.set(Field::int(7, 2)); rr.body.set(Field::int(16, 2)); scenario_with(
"app_MessageResentAsPossDup",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(new_order_single(2)),
Step::Expect(ExpectMsg::of("8").field(34, "2")), Step::Send(rr),
Step::Expect(ExpectMsg::of("8").field(34, "2").field(43, "Y")),
],
SessionTweaks {
executor_app: true,
..SessionTweaks::default()
},
)
}
fn app_mixed_resend_gapfill_then_possdup(v: &str) -> Scenario {
let mut rr = client_message(v, "2", 3);
rr.body.set(Field::int(7, 1)); rr.body.set(Field::int(16, 2)); scenario_with(
"app_MixedResendGapFillThenPossDup",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(new_order_single(2)),
Step::Expect(ExpectMsg::of("8").field(34, "2")),
Step::Send(rr),
Step::Expect(ExpectMsg::of("4").field(123, "Y").field(36, "2")),
Step::Expect(ExpectMsg::of("8").field(34, "2").field(43, "Y")),
],
SessionTweaks {
executor_app: true,
..SessionTweaks::default()
},
)
}
fn app_resend_veto_produces_gap_fill(v: &str) -> Scenario {
let mut order = new_order_single(2);
order.body.set(Field::string(11, "VETO-RESEND"));
let mut rr = client_message(v, "2", 3); rr.body.set(Field::int(7, 2)); rr.body.set(Field::int(16, 2)); scenario_with(
"GAP07_AppResendVetoProducesGapFill",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("8").field(34, "2")), Step::Send(rr),
Step::Expect(ExpectMsg::of("4").field(123, "Y").field(36, "3")),
],
SessionTweaks {
executor_app: true,
..SessionTweaks::default()
},
)
}
fn required_field_missing(v: &str) -> Scenario {
let mut order = new_order_single(2);
order.body = {
let mut b = truefix_core::FieldMap::new();
for f in new_order_single(2).body.fields() {
if f.tag() != 54 {
b.set(Field::new(f.tag(), f.value_bytes().to_vec()));
}
}
b
};
scenario(
"14b_RequiredFieldMissing",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "1")), ],
)
}
fn incorrect_enum_value(v: &str) -> Scenario {
let mut order = new_order_single(2);
order.body.set(Field::string(54, "Z")); scenario(
"14e_IncorrectEnumValue",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "5")), ],
)
}
fn invalid_tag_number(v: &str) -> Scenario {
let mut order = new_order_single(2);
order.body.set(Field::string(999, "X")); scenario(
"14a_InvalidTagNumber",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "0")), ],
)
}
fn tag_not_defined_for_msg_type(v: &str) -> Scenario {
let mut order = new_order_single(2);
order.body.set(Field::string(112, "X")); scenario(
"14c_TagNotDefinedForMsgType",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "2")), ],
)
}
fn tag_specified_without_value(v: &str) -> Scenario {
let mut order = new_order_single(2);
order.body.set(Field::new(11, Vec::new())); scenario(
"14d_TagSpecifiedWithoutValue",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "4")), ],
)
}
fn incorrect_data_format(v: &str) -> Scenario {
let mut order = new_order_single(2);
order.body.set(Field::string(38, "abc")); scenario(
"14f_IncorrectDataFormat",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "6")), ],
)
}
fn repeated_tag(v: &str) -> Scenario {
let mut order = new_order_single(2);
order.body.add_field(Field::string(11, "ORDER-1-DUP")); scenario(
"14h_RepeatedTag",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "13")), ],
)
}
fn unregistered_msg_type(v: &str) -> Scenario {
scenario(
"2r_UnregisteredMsgType",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(client_message(v, "UU", 2)), Step::Expect(ExpectMsg::of("j")), ],
)
}
fn next_expected_msg_seq_num(v: &str) -> Scenario {
let mut lo = logon(v, 1, true);
lo.body.set(Field::int(789, 1)); scenario_with(
"special_NextExpectedMsgSeqNum",
v,
vec![
Step::Send(lo),
Step::Expect(ExpectMsg::of("A").field(789, "2")),
Step::Expect(ExpectMsg::of("4").field(123, "Y").field(36, "2")),
],
SessionTweaks {
enable_next_expected: true,
..SessionTweaks::default()
},
)
}
fn last_msg_seq_num_processed(v: &str) -> Scenario {
scenario_with(
"special_LastMsgSeqNumProcessed",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A").field(369, "1")),
],
SessionTweaks {
enable_last_processed: true,
..SessionTweaks::default()
},
)
}
fn check_latency_timestamps(v: &str) -> Scenario {
scenario_with(
"special_CheckLatencyStaleSendingTime",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("5").field(58, "SendingTime accuracy problem")),
Step::ExpectDisconnect,
],
SessionTweaks {
check_latency: true,
..SessionTweaks::default()
},
)
}
fn garble_checksum(msg: &Message) -> Vec<u8> {
let mut raw = msg.encode();
let digits_at = match raw.len().checked_sub(4) {
Some(p) => p,
None => return raw,
};
let Some(cs_field_start) = digits_at.checked_sub(3) else {
return raw;
};
let computed: u32 = raw
.get(..cs_field_start)
.map(|pre| pre.iter().map(|&b| u32::from(b)).sum::<u32>() & 0xFF)
.unwrap_or(0);
let wrong = format!("{:03}", (computed + 1) & 0xFF);
if let Some(slot) = raw.get_mut(digits_at..digits_at + 3) {
slot.copy_from_slice(wrong.as_bytes());
}
raw
}
fn garbled_message_dropped(v: &str) -> Scenario {
let garbled = garble_checksum(&client_message(v, "0", 2)); let mut tr = client_message(v, "1", 2); tr.body.set(Field::string(112, "AFTER-GARBLE"));
scenario(
"special_GarbledMessageDropped",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::SendRaw(garbled),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "AFTER-GARBLE")),
],
)
}
fn garbled_message_rejected(v: &str) -> Scenario {
let garbled = garble_checksum(&client_message(v, "0", 2));
let mut tr = client_message(v, "1", 2); tr.body.set(Field::string(112, "AFTER-GARBLE-REJECT"));
scenario_with(
"special_RejectGarbledMessage",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::SendRaw(garbled),
Step::Expect(ExpectMsg::of("3").field(373, "0")), Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "AFTER-GARBLE-REJECT")),
],
SessionTweaks {
reject_garbled: true,
..SessionTweaks::default()
},
)
}
fn resend_request_chunk_size(v: &str) -> Scenario {
scenario_with(
"special_ResendRequestChunkSize",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(client_message(v, "0", 10)), Step::Expect(
ExpectMsg::of("2")
.field(7, "2")
.field(16, "6")
.field(34, "2"),
),
],
SessionTweaks {
resend_chunk_size: 5,
..SessionTweaks::default()
},
)
}
fn chunked_resend_auto_continues(v: &str) -> Scenario {
let mut gf1 = client_message(v, "4", 2);
gf1.body.set(Field::string(123, "Y")); gf1.body.set(Field::int(36, 5)); let mut gf2 = client_message(v, "4", 5);
gf2.body.set(Field::string(123, "Y"));
gf2.body.set(Field::int(36, 8)); let mut gf3 = client_message(v, "4", 8);
gf3.body.set(Field::string(123, "Y"));
gf3.body.set(Field::int(36, 11)); let mut tr = client_message(v, "1", 11); tr.body.set(Field::string(112, "SETTLED"));
scenario_with(
"GAP09_ChunkedResendAutoContinues",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(client_message(v, "0", 10)), Step::Expect(
ExpectMsg::of("2")
.field(7, "2")
.field(16, "4")
.field(34, "2"),
), Step::Send(gf1),
Step::Expect(
ExpectMsg::of("2")
.field(7, "5")
.field(16, "7")
.field(34, "3"),
), Step::Send(gf2),
Step::Expect(
ExpectMsg::of("2")
.field(7, "8")
.field(16, "10")
.field(34, "4"),
), Step::Send(gf3),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "SETTLED").field(34, "5")),
],
SessionTweaks {
resend_chunk_size: 3,
..SessionTweaks::default()
},
)
}
fn invalid_sender_comp_id(v: &str) -> Scenario {
let mut lo = logon(v, 1, true);
lo.header.set(Field::string(49, "WRONG-SENDER"));
scenario_with(
"1c_InvalidSenderCompID",
v,
vec![Step::Send(lo), Step::ExpectDisconnect],
SessionTweaks {
fixed_identity: Some(("SERVER".to_owned(), "CLIENT".to_owned())),
..SessionTweaks::default()
},
)
}
fn invalid_target_comp_id(v: &str) -> Scenario {
let mut lo = logon(v, 1, true);
lo.header.set(Field::string(56, "WRONG-TARGET"));
scenario_with(
"1c_InvalidTargetCompID",
v,
vec![Step::Send(lo), Step::ExpectDisconnect],
SessionTweaks {
fixed_identity: Some(("SERVER".to_owned(), "CLIENT".to_owned())),
..SessionTweaks::default()
},
)
}
fn invalid_logon_wrong_begin_string(v: &str) -> Scenario {
let wrong_begin = if v == "FIX.4.2" { "FIX.4.4" } else { "FIX.4.2" };
let lo = logon(wrong_begin, 1, true);
scenario_with(
"1d_InvalidLogonWrongBeginString",
v,
vec![Step::Send(lo), Step::ExpectDisconnect],
SessionTweaks {
fixed_identity: Some(("SERVER".to_owned(), "CLIENT".to_owned())),
..SessionTweaks::default()
},
)
}
fn invalid_logon_bad_sending_time(v: &str) -> Scenario {
scenario_with(
"1d_InvalidLogonBadSendingTime",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("5").field(58, "SendingTime accuracy problem")),
Step::ExpectDisconnect,
],
SessionTweaks {
check_latency: true,
..SessionTweaks::default()
},
)
}
fn qfj648_negative_heart_bt_int(v: &str) -> Scenario {
let mut lo = logon(v, 1, true);
lo.body.set(Field::int(108, -1)); scenario(
"QFJ648_NegativeHeartBtInt",
v,
vec![
Step::Send(lo),
Step::Expect(ExpectMsg::of("5")), Step::ExpectDisconnect,
],
)
}
fn msgseqnum_correct(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 2); tr.body.set(Field::string(112, "INSEQ"));
scenario(
"2a_MsgSeqNumCorrect",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "INSEQ").field(34, "2")), ],
)
}
fn poss_dup_not_received(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 2);
tr.header.set(Field::string(43, "Y")); tr.body.set(Field::string(112, "NOTADUP"));
scenario(
"2e_PossDupNotReceived",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "NOTADUP").field(34, "2")),
],
)
}
fn seq_reset_new_seq_no_equal(v: &str) -> Scenario {
let mut sr = client_message(v, "4", 99);
sr.body.set(Field::int(36, 2)); let mut tr = client_message(v, "1", 2);
tr.body.set(Field::string(112, "STILL-2"));
scenario(
"10_MsgSeqNumEqual",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(sr),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "STILL-2").field(34, "2")),
],
)
}
fn seq_reset_new_seq_no_less(v: &str) -> Scenario {
let mut sr = client_message(v, "4", 99);
sr.body.set(Field::int(36, 1)); scenario(
"10_MsgSeqNumLess",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(sr),
Step::Expect(expect_session_reject_reason(v, "3", "5").field(34, "2")), ],
)
}
fn seq_reset_gap_fill_new_seq_no_equal(v: &str) -> Scenario {
let mut sr = client_message(v, "4", 2); sr.body.set(Field::string(123, "Y")); sr.body.set(Field::int(36, 3)); let mut tr = client_message(v, "1", 3);
tr.body.set(Field::string(112, "GAPFILL-EQ"));
scenario(
"11b_NewSeqNoEqual",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(sr),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "GAPFILL-EQ").field(34, "2")),
],
)
}
fn reject_resent_message() -> Scenario {
let mut order = new_order_single(2);
order.body = {
let mut b = truefix_core::FieldMap::new();
for f in new_order_single(2).body.fields() {
if f.tag() != 54 {
b.set(Field::new(f.tag(), f.value_bytes().to_vec()));
}
}
b
};
order.header.set(Field::string(43, "Y")); order.header.set(Field::string(122, "20240101-00:00:00")); scenario(
"RejectResentMessage",
"FIX.4.4",
vec![
Step::Send(logon("FIX.4.4", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(order),
Step::Expect(ExpectMsg::of("3").field(373, "1")), ],
)
}
fn begin_string_value_unexpected(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 2);
tr.header.set(Field::string(8, "FIX.9.9"));
scenario(
"2i_BeginStringValueUnexpected",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr),
Step::Expect(ExpectMsg::of("5")), Step::ExpectDisconnect,
],
)
}
fn comp_id_does_not_match_profile(v: &str) -> Scenario {
let mut tr = client_message(v, "1", 2);
tr.header.set(Field::string(49, "IMPOSTER"));
scenario(
"2k_CompIDDoesNotMatchProfile",
v,
vec![
Step::Send(logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr),
Step::Expect(ExpectMsg::of("3")),
Step::Expect(ExpectMsg::of("5")), Step::ExpectDisconnect,
],
)
}
fn fresh_logon(version: &str, seq: i64, reset: bool) -> Message {
let mut m = logon(version, seq, reset);
m.header
.set(Field::utc_timestamp(52, time::OffsetDateTime::now_utc()));
m
}
fn sending_time_value_out_of_range(v: &str) -> Scenario {
let tr = client_message(v, "1", 2); let reject_expect = if matches!(v, "FIX.4.0" | "FIX.4.1") {
ExpectMsg::of("3").field(58, "SendingTime accuracy problem")
} else {
ExpectMsg::of("3")
.field(373, "10")
.field(58, "SendingTime accuracy problem")
};
scenario_with(
"2o_SendingTimeValueOutOfRange",
v,
vec![
Step::Send(fresh_logon(v, 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr),
Step::Expect(reject_expect),
Step::Expect(ExpectMsg::of("5").field(58, "SendingTime accuracy problem")),
Step::ExpectDisconnect,
],
SessionTweaks {
check_latency: true,
..SessionTweaks::default()
},
)
}
fn only_admin_messages() -> Scenario {
let mut tr1 = client_message("FIX.4.4", "1", 2);
tr1.body.set(Field::string(112, "A1"));
let mut tr2 = client_message("FIX.4.4", "1", 3);
tr2.body.set(Field::string(112, "A2"));
scenario(
"8_OnlyAdminMessages",
"FIX.4.4",
vec![
Step::Send(logon("FIX.4.4", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(tr1),
Step::Expect(ExpectMsg::of("0").field(112, "A1")),
Step::Send(tr2),
Step::Expect(ExpectMsg::of("0").field(112, "A2")),
],
)
}
fn only_application_messages() -> Scenario {
scenario_with(
"8_OnlyApplicationMessages",
"FIX.4.4",
vec![
Step::Send(logon("FIX.4.4", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(new_order_single(2)),
Step::Expect(ExpectMsg::of("8")),
Step::Send(new_order_single(3)),
Step::Expect(ExpectMsg::of("8")),
],
SessionTweaks {
executor_app: true,
..SessionTweaks::default()
},
)
}
fn admin_and_application_messages() -> Scenario {
let mut tr = client_message("FIX.4.4", "1", 3);
tr.body.set(Field::string(112, "MIXED"));
scenario_with(
"8_AdminAndApplicationMessages",
"FIX.4.4",
vec![
Step::Send(logon("FIX.4.4", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::Send(new_order_single(2)),
Step::Expect(ExpectMsg::of("8")),
Step::Send(tr),
Step::Expect(ExpectMsg::of("0").field(112, "MIXED")),
Step::Send(new_order_single(4)),
Step::Expect(ExpectMsg::of("8")),
],
SessionTweaks {
executor_app: true,
..SessionTweaks::default()
},
)
}
fn raw_new_order_single(rest: &[(u32, &str)]) -> Vec<u8> {
let body: String = rest.iter().map(|(t, v)| format!("{t}={v}\x01")).collect();
let prefix = format!("8=FIX.4.4\x019={}\x01", body.len());
let pre_checksum = format!("{prefix}{body}");
let checksum: u32 = pre_checksum.bytes().map(u32::from).sum::<u32>() & 0xFF;
format!("{pre_checksum}10={checksum:03}\x01").into_bytes()
}
fn first_three_fields_out_of_order() -> Scenario {
let raw = raw_new_order_single(&[
(49, "CLIENT"),
(35, "D"),
(56, "SERVER"),
(34, "2"),
(52, "20240101-00:00:00"),
(11, "O1"),
(21, "1"),
(55, "AAPL"),
(54, "1"),
(60, "20240101-00:00:00"),
(40, "2"),
]);
scenario_with(
"2t_FirstThreeFieldsOutOfOrder",
"FIX.4.4",
vec![
Step::Send(logon("FIX.4.4", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::SendRaw(raw),
Step::Expect(ExpectMsg::of("3").field(373, "14")), ],
SessionTweaks {
validate_fields_out_of_order: true,
..SessionTweaks::default()
},
)
}
fn header_body_trailer_fields_out_of_order() -> Scenario {
let raw = raw_new_order_single(&[
(35, "D"),
(55, "AAPL"), (49, "CLIENT"), (56, "SERVER"),
(34, "2"),
(52, "20240101-00:00:00"),
(11, "O1"),
(21, "1"),
(54, "1"),
(60, "20240101-00:00:00"),
(40, "2"),
]);
scenario_with(
"14g_HeaderBodyTrailerFieldsOutOfOrder",
"FIX.4.4",
vec![
Step::Send(logon("FIX.4.4", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::SendRaw(raw),
Step::Expect(ExpectMsg::of("3").field(373, "14")), ],
SessionTweaks {
validate_fields_out_of_order: true,
..SessionTweaks::default()
},
)
}
fn header_and_body_fields_ordered_differently() -> Scenario {
let raw = raw_new_order_single(&[
(35, "D"),
(49, "CLIENT"),
(55, "AAPL"), (56, "SERVER"), (11, "O1"),
(34, "2"),
(52, "20240101-00:00:00"),
(21, "1"),
(54, "1"),
(60, "20240101-00:00:00"),
(40, "2"),
]);
scenario_with(
"15_HeaderAndBodyFieldsOrderedDifferently",
"FIX.4.4",
vec![
Step::Send(logon("FIX.4.4", 1, true)),
Step::Expect(ExpectMsg::of("A")),
Step::SendRaw(raw),
Step::Expect(ExpectMsg::of("3").field(373, "14")), ],
SessionTweaks {
validate_fields_out_of_order: true,
..SessionTweaks::default()
},
)
}
pub fn validate_checksum_suite() -> Vec<Scenario> {
vec![
garbled_message_dropped("FIX.4.4"),
garbled_message_rejected("FIX.4.4"),
]
}
pub fn timestamps_suite() -> Vec<Scenario> {
vec![
check_latency_timestamps("FIX.4.4"),
unparseable_sending_time_fails_latency("FIX.4.4"),
]
}
pub fn resynch_suite() -> Vec<Scenario> {
vec![
resend_request_gap_fill("FIX.4.4"),
resend_request_bounded_end("FIX.4.4"),
resend_request_not_duplicated("FIX.4.4"),
resend_request_begin_zero_treated_as_one("FIX.4.4"),
resend_request_nothing_to_resend("FIX.4.4"),
out_of_order_queued_then_drained("FIX.4.4"),
sequence_reset_reset("FIX.4.4"),
sequence_reset_gap_fill_advances("FIX.4.4"),
sequence_reset_gap_fill_backward_ignored("FIX.4.4"),
sequence_reset_gap_fill_missing_new_seq_no_rejected("FIX.4.4"),
resend_request_chunk_size("FIX.4.4"),
chunked_resend_auto_continues("FIX.4.4"),
]
}
pub fn server_suite() -> Vec<Scenario> {
let mut out = Vec::new();
for &v in SUITE_VERSIONS {
out.push(valid_logon(v));
out.push(logon_seq_too_high(v));
out.push(msgseqnum_too_high(v));
out.push(msgseqnum_too_low(v));
out.push(received_test_request(v));
out.push(reverse_route(v));
out.push(reverse_route_empty_tags(v));
out.push(unsolicited_logout(v));
out.push(too_high_logout_processed_immediately(v));
out.push(logon_response_carries_reset_flag(v));
out.push(logon_response_does_not_echo_reset_flag_when_inbound_lacks_it(v));
out.push(logon_adopts_heartbeat_interval(v));
out.push(resend_request_gap_fill(v));
out.push(resend_request_bounded_end(v));
out.push(resend_request_too_high_answered_immediately(v));
out.push(resend_request_not_duplicated(v));
out.push(resend_request_begin_zero_treated_as_one(v));
out.push(out_of_order_queued_then_drained(v));
out.push(sequence_reset_reset(v));
out.push(sequence_reset_gap_fill_advances(v));
out.push(sequence_reset_gap_fill_backward_ignored(v));
out.push(sequence_reset_gap_fill_missing_new_seq_no_rejected(v));
out.push(resend_request_nothing_to_resend(v));
out.push(reject_message_consumed(v));
out.push(heartbeat_consumed(v));
out.push(missing_msg_seq_num(v));
out.push(idle_heartbeat_emitted(v));
out.push(test_request_on_silence(v));
out.push(poss_dup_too_low(v));
out.push(poss_dup_orig_sending_time_after_sending_time(v));
out.push(duplicate_logon_rejected(v));
out.push(invalid_logon_bad_sending_time(v));
out.push(qfj648_negative_heart_bt_int(v));
out.push(msgseqnum_correct(v));
out.push(poss_dup_not_received(v));
out.push(seq_reset_new_seq_no_equal(v));
out.push(seq_reset_new_seq_no_less(v));
out.push(seq_reset_gap_fill_new_seq_no_equal(v));
out.push(begin_string_value_unexpected(v));
out.push(comp_id_does_not_match_profile(v));
out.push(sending_time_value_out_of_range(v));
out.push(sequence_reset_reset_missing_new_seq_no_rejected(v));
out.push(resend_request_missing_begin_seq_no_rejected(v));
out.push(resend_request_missing_end_seq_no_rejected(v));
out.push(invalid_sender_comp_id(v));
out.push(invalid_target_comp_id(v));
out.push(invalid_logon_wrong_begin_string(v));
}
out.push(valid_new_order_accepted_42());
out.push(required_field_missing_42());
out.push(incorrect_enum_value_42());
out.push(invalid_tag_number_42());
out.push(tag_not_defined_for_msg_type_42());
out.push(tag_specified_without_value_42());
out.push(incorrect_data_format_42());
out.push(valid_new_order_accepted("FIX.4.4"));
out.push(group_count_mismatch("FIX.4.4"));
out.push(empty_value_inside_group("FIX.4.4"));
out.push(group_out_of_order("FIX.4.4"));
out.push(nested_group_missing_delimiter("FIX.4.4"));
out.push(group_zero_count("FIX.4.4"));
out.push(app_order_executed("FIX.4.4"));
out.push(app_orders_sequenced("FIX.4.4"));
out.push(app_message_resent_as_poss_dup("FIX.4.4"));
out.push(app_mixed_resend_gapfill_then_possdup("FIX.4.4"));
out.push(acceptor_initiated_logout("FIX.4.4"));
out.push(app_resend_veto_produces_gap_fill("FIX.4.4"));
out.push(required_field_missing("FIX.4.4"));
out.push(incorrect_enum_value("FIX.4.4"));
out.push(invalid_tag_number("FIX.4.4"));
out.push(tag_not_defined_for_msg_type("FIX.4.4"));
out.push(tag_specified_without_value("FIX.4.4"));
out.push(incorrect_data_format("FIX.4.4"));
out.push(repeated_tag("FIX.4.4"));
for &v in crate::runner::FLAT_DICTIONARY_VERSIONS {
out.push(unregistered_msg_type(v));
out.push(admin_message_dictionary_invalid_is_rejected(v));
}
out.push(next_expected_msg_seq_num("FIX.4.4"));
out.push(last_msg_seq_num_processed("FIX.4.4"));
out.push(check_latency_timestamps("FIX.4.4"));
out.push(garbled_message_dropped("FIX.4.4"));
out.push(garbled_message_rejected("FIX.4.4"));
out.push(resend_request_chunk_size("FIX.4.4"));
out.push(chunked_resend_auto_continues("FIX.4.4"));
out.push(first_three_fields_out_of_order());
out.push(header_body_trailer_fields_out_of_order());
out.push(header_and_body_fields_ordered_differently());
out.push(reject_resent_message());
out.push(only_admin_messages());
out.push(only_application_messages());
out.push(admin_and_application_messages());
out.push(gap_fill_drained_message_is_validated("FIX.4.4"));
out.push(dictionary_failure_disconnect_sends_logout("FIX.4.4"));
for &v in crate::runner::FLAT_DICTIONARY_VERSIONS {
out.push(dict_invalid_logon_disconnects_without_disconnect_on_error(
v,
));
}
out.push(unparseable_sending_time_fails_latency("FIX.4.4"));
out.push(min_qty_field_accepted_44());
out.push(min_qty_field_accepted_42());
out
}