Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UTs to clarify some ResendRequest behavior in Session.cs #941

Merged
merged 1 commit into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions QuickFIXn/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public bool IsNewSession
}
}


/// <summary>
/// Session setting for heartbeat interval (in seconds)
/// </summary>
Expand Down Expand Up @@ -215,6 +214,14 @@ public TimeStampPrecision TimeStampPrecision
/// </summary>
public bool RequiresOrigSendingTime { get; set; }

/// <summary>
/// True if session is waiting for ResendRequest content.
/// If the RR's EndSeqNo is 0 aka infinite, then this becomes
/// false after the first resent message is received.
/// Else it remains true until EndSeqNo is received.
/// </summary>
internal bool IsResendRequested => _state.ResendRequested();

#endregion

public Session(
Expand Down Expand Up @@ -927,19 +934,22 @@ public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = tru
return false;
}

if (_state.ResendRequested())
if (IsResendRequested)
{
ResendRange range = _state.GetResendRange();
if (msgSeqNum >= range.EndSeqNo)
{
Log.OnEvent("ResendRequest for messages FROM: " + range.BeginSeqNo + " TO: " + range.EndSeqNo + " has been satisfied.");
Log.OnEvent(
range.EndSeqNo == 0
? $"ResendRequest for messages FROM: {range.BeginSeqNo} TO: {range.EndSeqNo} has been satisfied."
: $"ResendRequest for messages FROM: {range.BeginSeqNo} TO: {range.EndSeqNo} has been started.");
_state.SetResendRange(0, 0);
}
else if (msgSeqNum >= range.ChunkEndSeqNo)
{
Log.OnEvent("Chunked ResendRequest for messages FROM: " + range.BeginSeqNo + " TO: " + range.ChunkEndSeqNo + " has been satisfied.");
SeqNumType newChunkEndSeqNo = Math.Min(range.EndSeqNo, range.ChunkEndSeqNo + MaxMessagesInResendRequest);
GenerateResendRequestRange(msg.Header.GetString(Fields.Tags.BeginString), range.ChunkEndSeqNo + 1, newChunkEndSeqNo);
GenerateResendRequestRange(msg.Header.GetString(Tags.BeginString), range.ChunkEndSeqNo + 1, newChunkEndSeqNo);
range.ChunkEndSeqNo = newChunkEndSeqNo;
}
}
Expand Down Expand Up @@ -1048,7 +1058,7 @@ protected void DoTargetTooHigh(Message msg, SeqNumType msgSeqNum)
Log.OnEvent("MsgSeqNum too high, expecting " + _state.NextTargetMsgSeqNum + " but received " + msgSeqNum);
_state.Queue(msgSeqNum, msg);

if (_state.ResendRequested())
if (IsResendRequested)
{
ResendRange range = _state.GetResendRange();

Expand Down Expand Up @@ -1156,7 +1166,8 @@ protected bool GenerateResendRequestRange(string beginString, SeqNumType startSe
return false;
}

protected void GenerateResendRequest(string beginString, SeqNumType msgSeqNum)
// internal so it can be unit tested
internal void GenerateResendRequest(string beginString, SeqNumType msgSeqNum)
{
SeqNumType beginSeqNum = _state.NextTargetMsgSeqNum;
SeqNumType endRangeSeqNum = msgSeqNum - 1;
Expand Down
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ What's New

**Non-breaking changes**
* #939 - minor checkTooHigh/checkTooLow refactor in Session.cs (gbirchmeier)
* #941 - clarify ResendRequest-related log message, add UT coverage for Session (gbirchmeier)

### v1.13.0

Expand Down
123 changes: 120 additions & 3 deletions UnitTests/SessionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public bool SENT_REJECT(int reason, int refTag)
return refTagField.Value == refTag;
}

public void SendNOSMessage()
public QuickFix.FIX42.NewOrderSingle CreateNOSMessage(SeqNumType n)
{
QuickFix.FIX42.NewOrderSingle order = new QuickFix.FIX42.NewOrderSingle(
new QuickFix.Fields.ClOrdID("1"),
Expand All @@ -182,9 +182,19 @@ public void SendNOSMessage()

order.Header.SetField(new QuickFix.Fields.TargetCompID(_sessionId.SenderCompID));
order.Header.SetField(new QuickFix.Fields.SenderCompID(_sessionId.TargetCompID));
order.Header.SetField(new QuickFix.Fields.MsgSeqNum(_seqNum++));
order.Header.SetField(new QuickFix.Fields.SendingTime(DateTime.UtcNow));
order.Header.SetField(new QuickFix.Fields.MsgSeqNum(n));
return order;
}

public void SendNOSMessage()
{
_session!.Next(CreateNOSMessage(_seqNum++).ConstructString());
}

_session!.Next(order.ConstructString());
public void SendNOSMessage(SeqNumType n)
{
_session!.Next(CreateNOSMessage(n).ConstructString());
}

public void SendResendRequest(SeqNumType begin, SeqNumType end)
Expand Down Expand Up @@ -824,5 +834,112 @@ public void TestResendRequestMsgSeqNumNotIgnoredWhenNoPersistance()
SendNOSMessage();
Assert.That(!SENT_RESEND_REQUEST());
}

[Test]
public void TestGenerateResendRequest() {
_session!.NextTargetMsgSeqNum = 100;

// <= FIX41
_session.GenerateResendRequest(QuickFix.FixValues.BeginString.FIX41, 125);
Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1));
var rr = _responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue();
Assert.That(rr.GetInt(QuickFix.Fields.Tags.BeginSeqNo), Is.EqualTo(100));
Assert.That(rr.GetInt(QuickFix.Fields.Tags.EndSeqNo), Is.EqualTo(999999));

// >= FIX42
_session.GenerateResendRequest(QuickFix.FixValues.BeginString.FIX42, 125);
Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1));
rr = _responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue();
Assert.That(rr.GetInt(QuickFix.Fields.Tags.BeginSeqNo), Is.EqualTo(100));
Assert.That(rr.GetInt(QuickFix.Fields.Tags.EndSeqNo), Is.EqualTo(0));

// Max resend is set: request is greater than max resend
_session.MaxMessagesInResendRequest = 100;
_session.GenerateResendRequest(QuickFix.FixValues.BeginString.FIX42, 225);
Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1));
rr = _responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue();
Assert.That(rr.GetInt(QuickFix.Fields.Tags.BeginSeqNo), Is.EqualTo(100));
Assert.That(rr.GetInt(QuickFix.Fields.Tags.EndSeqNo), Is.EqualTo(199));

// Max resend is set: request is lesser than max resend
_session.GenerateResendRequest(QuickFix.FixValues.BeginString.FIX42, 175);
Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1));
rr = _responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue();
Assert.That(rr.GetInt(QuickFix.Fields.Tags.BeginSeqNo), Is.EqualTo(100));
Assert.That(rr.GetInt(QuickFix.Fields.Tags.EndSeqNo), Is.EqualTo(174));
}

[Test]
public void TestBasicResendRequest_NoMax() {
// just a regular boring resend request scenario, when MaxMessagesInResendRequest is unset

// Setup: Establish connection. Server sends Seq too high, causing client to ResendRequest.
Logon();
SendNOSMessage();
Assert.That(_session!.NextTargetMsgSeqNum, Is.EqualTo(3));

SendNOSMessage(6);
Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1));

var resendRequest =
(QuickFix.FIX42.ResendRequest)_responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue();
Assert.That(resendRequest.BeginSeqNo.Value, Is.EqualTo(3));
Assert.That(resendRequest.EndSeqNo.Value, Is.EqualTo(0)); // 0 is infinity, but actually we just want until 5

Assert.That(_session.IsResendRequested, Is.True);

// Server sends 3 resent messages and 2 new messages
// (client already got seq=6)
foreach (SeqNumType i in new[] { 3, 4, 5, 7, 8 }) {
var msg = CreateNOSMessage(i);
if (i < 7) {
msg.Header.SetField(new QuickFix.Fields.PossDupFlag(true));
msg.Header.SetField(new QuickFix.Fields.OrigSendingTime(DateTime.MinValue)); // (value doesn't matter for test)
_session.Next(msg.ConstructString());
}
_session.Next(msg.ConstructString());

// When EndSeqNo is 0, we mark the ResendResend as 'finished' after the first resent message is received
Assert.That(_session.IsResendRequested, Is.False, $"seq num {i}");
}
}

[Test]
public void TestBasicResendRequest_WithMax() {
// just a regular boring resend request scenario, when MaxMessagesInResendRequest is configured

// Setup: Establish connection. Server sends Seq too high, causing client to ResendRequest.
_session!.MaxMessagesInResendRequest = 100;
Logon();
SendNOSMessage();
Assert.That(_session!.NextTargetMsgSeqNum, Is.EqualTo(3));

SendNOSMessage(6);
Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1));

var resendRequest =
(QuickFix.FIX42.ResendRequest)_responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue();
Assert.That(resendRequest.BeginSeqNo.Value, Is.EqualTo(3));
Assert.That(resendRequest.EndSeqNo.Value, Is.EqualTo(5));

Assert.That(_session.IsResendRequested, Is.True);

// Server sends 3 resent messages and 2 new messages
// (client already got seq=6)
foreach (SeqNumType i in new[] { 3, 4, 5, 7, 8 }) {
var msg = CreateNOSMessage(i);
if (i < 7) {
msg.Header.SetField(new QuickFix.Fields.PossDupFlag(true));
msg.Header.SetField(new QuickFix.Fields.OrigSendingTime(DateTime.MinValue)); // (value doesn't matter for test)
_session.Next(msg.ConstructString());
}
_session.Next(msg.ConstructString());

if (i < 5)
Assert.That(_session.IsResendRequested, Is.True, $"seq num {i}");
else
Assert.That(_session.IsResendRequested, Is.False, $"seq num {i}");
}
}
}