Skip to content

Commit

Permalink
UTs to clarify some ResendRequest behavior
Browse files Browse the repository at this point in the history
also clarify a log message
  • Loading branch information
gbirchmeier committed Mar 3, 2025
1 parent bc2c1d7 commit 1a3bfe0
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 9 deletions.
20 changes: 14 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,11 @@ public TimeStampPrecision TimeStampPrecision
/// </summary>
public bool RequiresOrigSendingTime { get; set; }

/// <summary>
/// True if session is waiting for a ResendRequest content to finish.
/// </summary>
internal bool IsResendRequested => _state.ResendRequested();

#endregion

public Session(
Expand Down Expand Up @@ -927,19 +931,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 +1055,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 +1163,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)
* #nnn - 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}");
}
}
}

0 comments on commit 1a3bfe0

Please sign in to comment.