Skip to content

Commit 1939e4b

Browse files
authored
Merge pull request connamara#943 from gbirchmeier/tag369bug-issue942
fix connamara#942: field 369 (LastMsgSeqNumProcessed) wrong in ResendRequest message
2 parents f20d12d + cc2554d commit 1939e4b

File tree

5 files changed

+73
-37
lines changed

5 files changed

+73
-37
lines changed

AcceptanceTest/definitions/server/misc/FIX42MaxMessagesInResend.def

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@ E8=FIX.4.29=12035=A34=149=ISLD50=SENDERSUB52=056=TW57=TARGETSUB142=SEND
55

66
# Send a heartbeat with a seqnum that is way too high, should trigger a single resend for the first 2500 msg chunk
77
I8=FIX.4.235=034=760149=TW50=TARGETSUB52=<TIME>369=156=ISLD57=SENDERSUB142=TARGETLOC143=SENDERLOC
8-
E8=FIX.4.29=12135=234=249=ISLD50=SENDERSUB52=056=TW57=TARGETSUB142=SENDERLOC143=TARGETLOC369=17=216=250110=0
8+
E8=FIX.4.29=12435=234=249=ISLD50=SENDERSUB52=056=TW57=TARGETSUB142=SENDERLOC143=TARGETLOC369=76017=216=250110=0
99

1010
# Send a sequence reset message to "fast-forward" to the seqnum of the second chunk, then a heartbeat, should trigger the sending of the second chunk
1111
I8=FIX.4.235=434=249=TW50=TARGETSUB52=<TIME>56=ISLD57=SENDERSUB142=TARGETLOC143=SENDERLOC123=Y36=2501
1212
I8=FIX.4.235=034=250149=TW50=TARGETSUB52=<TIME>369=256=ISLD57=SENDERSUB142=TARGETLOC143=SENDERLOC
13-
E8=FIX.4.29=12735=234=349=ISLD50=SENDERSUB52=056=TW57=TARGETSUB142=SENDERLOC143=TARGETLOC369=25007=250216=500110=0
13+
E8=FIX.4.29=12735=234=349=ISLD50=SENDERSUB52=056=TW57=TARGETSUB142=SENDERLOC143=TARGETLOC369=25017=250216=500110=0
1414

1515
# And again...
1616
I8=FIX.4.235=434=250249=TW50=TARGETSUB52=<TIME>56=ISLD57=SENDERSUB142=TARGETLOC143=SENDERLOC123=Y36=5001
1717
I8=FIX.4.235=034=500149=TW50=TARGETSUB52=<TIME>369=356=ISLD57=SENDERSUB142=TARGETLOC143=SENDERLOC
18-
E8=FIX.4.29=12735=234=449=ISLD50=SENDERSUB52=056=TW57=TARGETSUB142=SENDERLOC143=TARGETLOC369=50007=500216=750110=0
18+
E8=FIX.4.29=12735=234=449=ISLD50=SENDERSUB52=056=TW57=TARGETSUB142=SENDERLOC143=TARGETLOC369=50017=500216=750110=0
1919

2020
# And again...
2121
I8=FIX.4.235=434=500249=TW50=TARGETSUB52=<TIME>56=ISLD57=SENDERSUB142=TARGETLOC143=SENDERLOC123=Y36=7501
2222
I8=FIX.4.235=034=750149=TW50=TARGETSUB52=<TIME>369=456=ISLD57=SENDERSUB142=TARGETLOC143=SENDERLOC
23-
E8=FIX.4.29=12735=234=549=ISLD50=SENDERSUB52=056=TW57=TARGETSUB142=SENDERLOC143=TARGETLOC369=75007=750216=760010=0
23+
E8=FIX.4.29=12735=234=549=ISLD50=SENDERSUB52=056=TW57=TARGETSUB142=SENDERLOC143=TARGETLOC369=75017=750216=760010=0
2424

2525
iDISCONNECT

QuickFIXn/Message/Message.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -622,11 +622,11 @@ public void Validate()
622622
{
623623
int receivedBodyLength = Header.GetInt(Tags.BodyLength);
624624
if (BodyLength() != receivedBodyLength)
625-
throw new InvalidMessage("Expected BodyLength=" + BodyLength() + ", Received BodyLength=" + receivedBodyLength + ", Message.SeqNum=" + Header.GetInt(Tags.MsgSeqNum));
625+
throw new InvalidMessage("Expected BodyLength=" + BodyLength() + ", Received BodyLength=" + receivedBodyLength + ", Message.SeqNum=" + Header.GetULong(Tags.MsgSeqNum));
626626

627627
int receivedCheckSum = Trailer.GetInt(Tags.CheckSum);
628628
if (CheckSum() != receivedCheckSum)
629-
throw new InvalidMessage("Expected CheckSum=" + CheckSum() + ", Received CheckSum=" + receivedCheckSum + ", Message.SeqNum=" + Header.GetInt(Tags.MsgSeqNum));
629+
throw new InvalidMessage("Expected CheckSum=" + CheckSum() + ", Received CheckSum=" + receivedCheckSum + ", Message.SeqNum=" + Header.GetULong(Tags.MsgSeqNum));
630630
}
631631
catch (FieldNotFoundException e)
632632
{

QuickFIXn/Session.cs

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ public virtual bool Send(Message message)
348348
{
349349
message.Header.RemoveField(Fields.Tags.PossDupFlag);
350350
message.Header.RemoveField(Fields.Tags.OrigSendingTime);
351-
return SendRaw(message, 0);
351+
return SendRaw(message);
352352
}
353353

354354
/// <summary>
@@ -948,8 +948,19 @@ public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = tru
948948
else if (msgSeqNum >= range.ChunkEndSeqNo)
949949
{
950950
Log.OnEvent("Chunked ResendRequest for messages FROM: " + range.BeginSeqNo + " TO: " + range.ChunkEndSeqNo + " has been satisfied.");
951+
SeqNumType newStart = range.ChunkEndSeqNo + 1;
951952
SeqNumType newChunkEndSeqNo = Math.Min(range.EndSeqNo, range.ChunkEndSeqNo + MaxMessagesInResendRequest);
952-
GenerateResendRequestRange(msg.Header.GetString(Tags.BeginString), range.ChunkEndSeqNo + 1, newChunkEndSeqNo);
953+
954+
Message resendRequest = CreateResendRequest(msg.Header.GetString(Tags.BeginString),
955+
newStart, newChunkEndSeqNo);
956+
if (EnableLastMsgSeqNumProcessed)
957+
resendRequest.Header.SetField(new LastMsgSeqNumProcessed(msgSeqNum));
958+
959+
if (SendRaw(resendRequest))
960+
Log.OnEvent($"Sent ResendRequest FROM: {newStart} TO: {newChunkEndSeqNo}");
961+
else
962+
Log.OnEvent($"Error sending ResendRequest ({newStart}, {newChunkEndSeqNo})");
963+
953964
range.ChunkEndSeqNo = newChunkEndSeqNo;
954965
}
955966
}
@@ -1145,28 +1156,18 @@ protected void GenerateBusinessMessageReject(Message message, int err, int field
11451156

11461157
reject.SetField(new Text(reason));
11471158
Log.OnEvent("Reject sent for Message: " + msgSeqNum + " Reason:" + reason);
1148-
SendRaw(reject, 0);
1159+
SendRaw(reject);
11491160
}
11501161

1151-
protected bool GenerateResendRequestRange(string beginString, SeqNumType startSeqNum, SeqNumType endSeqNum)
1162+
private Message CreateResendRequest(string beginString, SeqNumType startSeqNum, SeqNumType endSeqNum)
11521163
{
11531164
Message resendRequest = _msgFactory.Create(beginString, MsgType.RESEND_REQUEST);
1154-
1155-
resendRequest.SetField(new Fields.BeginSeqNo(startSeqNum));
1156-
resendRequest.SetField(new Fields.EndSeqNo(endSeqNum));
1157-
1165+
resendRequest.SetField(new BeginSeqNo(startSeqNum));
1166+
resendRequest.SetField(new EndSeqNo(endSeqNum));
11581167
InitializeHeader(resendRequest);
1159-
if (SendRaw(resendRequest, 0))
1160-
{
1161-
Log.OnEvent("Sent ResendRequest FROM: " + startSeqNum + " TO: " + endSeqNum);
1162-
return true;
1163-
}
1164-
1165-
Log.OnEvent("Error sending ResendRequest (" + startSeqNum + " ," + endSeqNum + ")");
1166-
return false;
1168+
return resendRequest;
11671169
}
11681170

1169-
// internal so it can be unit tested
11701171
internal void GenerateResendRequest(string beginString, SeqNumType msgSeqNum)
11711172
{
11721173
SeqNumType beginSeqNum = _state.NextTargetMsgSeqNum;
@@ -1185,18 +1186,26 @@ internal void GenerateResendRequest(string beginString, SeqNumType msgSeqNum)
11851186
endChunkSeqNum = endRangeSeqNum;
11861187
}
11871188

1188-
if (!GenerateResendRequestRange(beginString, beginSeqNum, endChunkSeqNum)) {
1189+
Message resendRequest = CreateResendRequest(beginString, beginSeqNum, endChunkSeqNum);
1190+
1191+
if (EnableLastMsgSeqNumProcessed)
1192+
resendRequest.Header.SetField(new LastMsgSeqNumProcessed(msgSeqNum));
1193+
1194+
if (SendRaw(resendRequest))
1195+
{
1196+
Log.OnEvent($"Sent ResendRequest FROM: {beginSeqNum} TO: {endChunkSeqNum}");
1197+
_state.SetResendRange(beginSeqNum, endRangeSeqNum, endChunkSeqNum);
11891198
return;
11901199
}
11911200

1192-
_state.SetResendRange(beginSeqNum, endRangeSeqNum, endChunkSeqNum);
1201+
Log.OnEvent("Error sending ResendRequest (" + beginSeqNum + " ," + endChunkSeqNum + ")");
11931202
}
11941203

11951204
/// <summary>
11961205
/// Create and send a logon
11971206
/// </summary>
11981207
/// <returns>true of logon was successfully sent</returns>
1199-
protected bool GenerateLogon()
1208+
internal bool GenerateLogon()
12001209
{
12011210
Message logon = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.LOGON);
12021211
logon.SetField(new Fields.EncryptMethod(0));
@@ -1214,7 +1223,7 @@ protected bool GenerateLogon()
12141223
InitializeHeader(logon);
12151224
_state.LastReceivedTimeDT = DateTime.UtcNow;
12161225
_state.TestRequestCounter = 0;
1217-
_state.SentLogon = SendRaw(logon, 0);
1226+
_state.SentLogon = SendRaw(logon);
12181227
return _state.SentLogon;
12191228
}
12201229

@@ -1230,7 +1239,7 @@ protected bool GenerateLogon(Message otherLogon)
12301239
logon.Header.SetField(new Fields.LastMsgSeqNumProcessed(otherLogon.Header.GetULong(Tags.MsgSeqNum)));
12311240

12321241
InitializeHeader(logon);
1233-
_state.SentLogon = SendRaw(logon, 0);
1242+
_state.SentLogon = SendRaw(logon);
12341243
return _state.SentLogon;
12351244
}
12361245

@@ -1239,7 +1248,7 @@ public void GenerateTestRequest(string id)
12391248
Message testRequest = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.TEST_REQUEST);
12401249
InitializeHeader(testRequest);
12411250
testRequest.SetField(new Fields.TestReqID(id));
1242-
SendRaw(testRequest, 0);
1251+
SendRaw(testRequest);
12431252
}
12441253

12451254
/// <summary>
@@ -1294,14 +1303,14 @@ private void ImplGenerateLogout(Message? other = null, string? text = null)
12941303
Log.OnEvent("Error: No message sequence number: " + other);
12951304
}
12961305
}
1297-
_state.SentLogout = SendRaw(logout, 0);
1306+
_state.SentLogout = SendRaw(logout);
12981307
}
12991308

13001309
public void GenerateHeartbeat()
13011310
{
13021311
Message heartbeat = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.HEARTBEAT);
13031312
InitializeHeader(heartbeat);
1304-
SendRaw(heartbeat, 0);
1313+
SendRaw(heartbeat);
13051314
}
13061315

13071316
public void GenerateHeartbeat(Message testRequest)
@@ -1318,7 +1327,7 @@ public void GenerateHeartbeat(Message testRequest)
13181327
}
13191328
catch (FieldNotFoundException)
13201329
{ }
1321-
SendRaw(heartbeat, 0);
1330+
SendRaw(heartbeat);
13221331
}
13231332

13241333
internal void GenerateReject(MessageBuilder msgBuilder, FixValues.SessionRejectReason reason, int field)
@@ -1391,7 +1400,7 @@ public void GenerateReject(Message message, FixValues.SessionRejectReason reason
13911400
if (!_state.ReceivedLogon)
13921401
throw new QuickFIXException("Tried to send a reject while not logged on");
13931402

1394-
SendRaw(reject, 0);
1403+
SendRaw(reject);
13951404
}
13961405

13971406
protected void PopulateSessionRejectReason(Message reject, int field, string text, bool includeFieldInfo)
@@ -1541,7 +1550,13 @@ private static bool IsAdminMessage(Message msg)
15411550
return AdminMsgTypes.Contains(msgType);
15421551
}
15431552

1544-
protected bool SendRaw(Message message, SeqNumType seqNum)
1553+
/// <summary>
1554+
/// Update the header as needed and send the message
1555+
/// </summary>
1556+
/// <param name="message"></param>
1557+
/// <param name="seqNum">if non-zero, set this seqno in the message (else leave existing value alone)</param>
1558+
/// <returns></returns>
1559+
protected bool SendRaw(Message message, SeqNumType seqNum = 0UL)
15451560
{
15461561
lock (_sync)
15471562
{
@@ -1586,7 +1601,7 @@ protected bool SendRaw(Message message, SeqNumType seqNum)
15861601
}
15871602

15881603
string messageString = message.ConstructString();
1589-
if (0 == seqNum)
1604+
if (0UL == seqNum)
15901605
Persist(message, messageString);
15911606
return Send(messageString);
15921607
}

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ What's New
2020
* #939 - minor checkTooHigh/checkTooLow refactor in Session.cs (gbirchmeier)
2121
* #941 - clarify ResendRequest-related log message, add UT coverage for Session (gbirchmeier)
2222
* #895 - fix: When SSLCACertificate is empty an error is logged and it fails to start (dckorben)
23+
* #942 - fix #942: field 369 (LastMsgSeqNumProcessed) wrong in ResendRequest message (gbirchmeier)
2324

2425
### v1.13.0
2526

UnitTests/SessionTest.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ public void Logon40()
6666
SendLogon(new QuickFix.FIX40.Logon());
6767
}
6868

69-
private void SendLogon(QuickFix.Message msg)
69+
private void SendLogon(QuickFix.Message msg, SeqNumType n = 0)
7070
{
7171
msg.Header.SetField(new QuickFix.Fields.TargetCompID(_sessionId.SenderCompID));
7272
msg.Header.SetField(new QuickFix.Fields.SenderCompID(_sessionId.TargetCompID));
73-
msg.Header.SetField(new QuickFix.Fields.MsgSeqNum(_seqNum++));
73+
msg.Header.SetField(new QuickFix.Fields.MsgSeqNum(n == 0 ? _seqNum++ : n));
7474
msg.Header.SetField(new QuickFix.Fields.SendingTime(DateTime.UtcNow));
7575
msg.SetField(new QuickFix.Fields.HeartBtInt(1));
7676
_session!.Next(msg.ConstructString());
@@ -523,6 +523,26 @@ public void TestLastMsgSeqNumProcessed()
523523
Assert.That(lastSeqNumProcessed == 1);
524524
}
525525

526+
[Test]
527+
public void TestLastMsgSeqNumProcessedAfterTooHighLogon()
528+
{
529+
// issue #942
530+
_session!.EnableLastMsgSeqNumProcessed = true;
531+
_session.NextTargetMsgSeqNum = 666;
532+
533+
// Logon initiated by session
534+
_session.GenerateLogon();
535+
536+
// Logon response with seqnum too high
537+
var logonMsg = new QuickFix.FIX42.Logon();
538+
SendLogon(logonMsg, 800);
539+
540+
// Session responds with a ResendRequest
541+
Assert.That(_responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Count, Is.EqualTo(1));
542+
QuickFix.Message rr = _responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue();
543+
Assert.That(rr.Header.GetULong(QuickFix.Fields.Tags.LastMsgSeqNumProcessed), Is.EqualTo(800));
544+
}
545+
526546
[Test]
527547
public void TestMaxMessagesInResendRequest()
528548
{

0 commit comments

Comments
 (0)