diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs
index 128dae377..a44202297 100755
--- a/QuickFIXn/Session.cs
+++ b/QuickFIXn/Session.cs
@@ -463,7 +463,7 @@ public void Next()
if (!IsSessionTime)
{
- if(IsInitiator)
+ if (IsInitiator)
Reset("Out of SessionTime (Session.Next())");
else
Reset("Out of SessionTime (Session.Next())", "Message received outside of session time");
@@ -830,7 +830,7 @@ protected void NextResendRequest(Message resendReq)
{
initializeResendFields(msg);
- if(!ResendApproved(msg, SessionID))
+ if (!ResendApproved(msg, SessionID))
{
continue;
}
@@ -926,7 +926,11 @@ protected void NextSequenceReset(Message sequenceReset)
if (sequenceReset.IsSetField(Fields.Tags.GapFillFlag))
isGapFill = sequenceReset.GetBoolean(Fields.Tags.GapFillFlag);
- if (!Verify(sequenceReset, isGapFill, isGapFill))
+ bool possDupFlag = false;
+ if (sequenceReset.Header.IsSetField(Fields.Tags.PossDupFlag))
+ possDupFlag = sequenceReset.Header.GetBoolean(Fields.Tags.PossDupFlag);
+
+ if (!Verify(sequenceReset, isGapFill, isGapFill && !possDupFlag))
return;
if (sequenceReset.IsSetField(Fields.Tags.NewSeqNo))
@@ -1058,7 +1062,7 @@ public void Reset(string loggedReason)
/// message to put in the Logout message's Text field (ignored if null/empty string)
public void Reset(string loggedReason, string logoutMessage)
{
- if(this.IsLoggedOn)
+ if (this.IsLoggedOn)
GenerateLogout(logoutMessage);
Disconnect("Resetting...");
state_.Reset(loggedReason);
@@ -1150,7 +1154,7 @@ protected void DoPossDup(Message msg)
{
// If config RequiresOrigSendingTime=N, then tolerate SequenceReset messages that lack OrigSendingTime (issue #102).
// (This field doesn't really make sense in this message, so some parties omit it, even though spec requires it.)
- string msgType = msg.Header.GetString(Fields.Tags.MsgType);
+ string msgType = msg.Header.GetString(Fields.Tags.MsgType);
if (msgType == Fields.MsgType.SEQUENCE_RESET && RequiresOrigSendingTime == false)
return;
@@ -1388,7 +1392,7 @@ internal bool GenerateReject(MessageBuilder msgBuilder, FixValues.SessionRejectR
{
return GenerateReject(msgBuilder.RejectableMessage(), reason, 0);
}
-
+
internal bool GenerateReject(MessageBuilder msgBuilder, FixValues.SessionRejectReason reason, int field)
{
return GenerateReject(msgBuilder.RejectableMessage(), reason, field);
@@ -1533,7 +1537,7 @@ protected void InsertSendingTime(FieldMap header)
else
fix42OrAbove = this.SessionID.BeginString.CompareTo(FixValues.BeginString.FIX42) >= 0;
- header.SetField(new Fields.SendingTime(System.DateTime.UtcNow, fix42OrAbove ? TimeStampPrecision : TimeStampPrecision.Second ) );
+ header.SetField(new Fields.SendingTime(System.DateTime.UtcNow, fix42OrAbove ? TimeStampPrecision : TimeStampPrecision.Second));
}
protected void Persist(Message message, string messageString)
@@ -1595,7 +1599,7 @@ protected void InsertOrigSendingTime(FieldMap header, System.DateTime sendingTim
else
fix42OrAbove = this.SessionID.BeginString.CompareTo(FixValues.BeginString.FIX42) >= 0;
- header.SetField(new OrigSendingTime(sendingTime, fix42OrAbove ? TimeStampPrecision : TimeStampPrecision.Second ) );
+ header.SetField(new OrigSendingTime(sendingTime, fix42OrAbove ? TimeStampPrecision : TimeStampPrecision.Second));
}
protected void NextQueued()
{
diff --git a/UnitTests/InitiatorSessionResendMessagesTest.cs b/UnitTests/InitiatorSessionResendMessagesTest.cs
new file mode 100644
index 000000000..69a0246fd
--- /dev/null
+++ b/UnitTests/InitiatorSessionResendMessagesTest.cs
@@ -0,0 +1,213 @@
+using NUnit.Framework;
+using QuickFix.Fields;
+using QuickFix.FIX42;
+using System.Text.RegularExpressions;
+
+namespace UnitTests
+{
+
+ [TestFixture]
+ public class InitiatorSessionResendMessagesTest
+ {
+ MockResponder responder = null;
+
+ QuickFix.SessionID sessionID = null;
+ QuickFix.SessionSettings settings = null;
+ MockApplication application = null;
+ QuickFix.Session session = null;
+ QuickFix.Dictionary config = null;
+ SeqNumType seqNum = 1;
+ Regex msRegex = new Regex(@"\.[\d]{1,3}$");
+ Regex microsecondRegex = new Regex(@"\.[\d]{1,6}$");
+
+ [SetUp]
+ public void Setup()
+ {
+ responder = new MockResponder();
+ sessionID = new QuickFix.SessionID("FIX.4.2", "SENDER", "TARGET");
+ application = new MockApplication();
+ settings = new QuickFix.SessionSettings();
+
+ config = new QuickFix.Dictionary();
+ config.SetBool(QuickFix.SessionSettings.PERSIST_MESSAGES, false);
+ config.SetBool(QuickFix.SessionSettings.RESETSEQUENCE_MESSAGE_REQUIRES_ORIGSENDINGTIME, false);
+ config.SetString(QuickFix.SessionSettings.CONNECTION_TYPE, "initiator");
+ config.SetString(QuickFix.SessionSettings.START_TIME, "00:00:00");
+ config.SetString(QuickFix.SessionSettings.END_TIME, "00:00:00");
+ settings.Set(sessionID, config);
+
+ // initiator
+ session = new QuickFix.Session(true, application, new QuickFix.MemoryStoreFactory(), sessionID,
+ new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(config), 0, new QuickFix.ScreenLogFactory(settings), new QuickFix.DefaultMessageFactory(), "blah");
+ session.SetResponder(responder);
+ session.CheckLatency = false;
+
+ seqNum = 1;
+ }
+
+ public void Logon()
+ {
+ SendLogon(new QuickFix.FIX42.Logon());
+ }
+
+ private void SendLogon(QuickFix.Message msg)
+ {
+ msg.Header.SetField(new QuickFix.Fields.TargetCompID(sessionID.SenderCompID));
+ msg.Header.SetField(new QuickFix.Fields.SenderCompID(sessionID.TargetCompID));
+ msg.Header.SetField(new QuickFix.Fields.MsgSeqNum(seqNum++));
+ msg.Header.SetField(new QuickFix.Fields.SendingTime(System.DateTime.UtcNow));
+ msg.SetField(new QuickFix.Fields.HeartBtInt(1));
+ session.Next(msg.ToString());
+ }
+
+
+ public bool RESENT()
+ {
+ if (responder.dups.Count == 0)
+ return false;
+
+ responder.dups.Dequeue();
+ return true;
+ }
+
+
+ public void SendResendRequest(SeqNumType begin, SeqNumType end)
+ {
+ SendTheMessage(new QuickFix.FIX42.ResendRequest(
+ new QuickFix.Fields.BeginSeqNo(begin),
+ new QuickFix.Fields.EndSeqNo(end)));
+ }
+
+ private void SendTheMessage(QuickFix.Message msg)
+ {
+ SendTheMessage(msg, seqNum++);
+ }
+
+ private void SendTheMessage(QuickFix.Message msg, SeqNumType msgSeqNum, bool possDupFlag=false)
+ {
+ msg.Header.SetField(new QuickFix.Fields.TargetCompID(sessionID.SenderCompID));
+ msg.Header.SetField(new QuickFix.Fields.SenderCompID(sessionID.TargetCompID));
+ msg.Header.SetField(new QuickFix.Fields.MsgSeqNum(msgSeqNum));
+ msg.Header.SetField(new QuickFix.Fields.PossDupFlag(possDupFlag));
+ session.Next(msg.ToString());
+ }
+
+ [Test]
+ public void TestSequenceResetNoGapFillIsProcessed()
+ {
+ // Default is false
+ Assert.That(session.IgnorePossDupResendRequests, Is.EqualTo(false));
+
+ session.Next();// causes Logon send
+ // Logon
+ Logon();
+
+ // quote
+ var quote = new QuickFix.FIX42.Quote();
+
+
+ SendTheMessage(quote);
+ SendTheMessage(quote);
+ SendTheMessage(quote);
+
+ var seqReset = new SequenceReset( new NewSeqNo(seqNum + 10) );
+
+ SendTheMessage(seqReset);
+
+
+ Assert.That(session.NextTargetMsgSeqNum, Is.EqualTo(seqNum +9 ));
+ }
+
+ [Test]
+ public void TestSequenceResetWithGapFillIsProcessed()
+ {
+ // Default is false
+ Assert.That(session.IgnorePossDupResendRequests, Is.EqualTo(false));
+
+ session.Next();// causes Logon send
+ // Logon
+ Logon();
+
+ // quote
+ var quote = new QuickFix.FIX42.Quote();
+
+
+ SendTheMessage(quote);
+ SendTheMessage(quote);
+ SendTheMessage(quote);
+
+ var seqReset = new SequenceReset(new NewSeqNo(seqNum + 10));
+ seqReset.GapFillFlag = new GapFillFlag(true);
+
+ SendTheMessage(seqReset);
+
+ Assert.That(session.NextTargetMsgSeqNum, Is.EqualTo(seqNum + 9));
+ }
+
+ [Test]
+ public void TestSequenceResetDuringResendRequestIsProcessed()
+ {
+ // Default is false
+ Assert.That(session.IgnorePossDupResendRequests, Is.EqualTo(false));
+ session.RequiresOrigSendingTime = false;
+ session.Next(); // causes Logon send
+ // Logon
+ Logon();
+
+ // quote
+ var quote = new QuickFix.FIX42.Quote();
+
+
+ SendTheMessage(quote, 2);
+ SendTheMessage(quote, 3);
+ SendTheMessage(quote, 10); // causes ResendRequest
+
+ Assert.That(session.NextTargetMsgSeqNum, Is.EqualTo(4));
+
+ SendTheMessage(quote, 4);
+ SendTheMessage(quote, 5);
+
+ var seqReset = new SequenceReset(new NewSeqNo(9)); // already been sent 10 so will process it
+
+ SendTheMessage(seqReset, 6);
+ SendTheMessage(quote, 9);
+
+ Assert.That(session.NextTargetMsgSeqNum, Is.EqualTo(11));
+ }
+
+ [Test]
+ public void TestSequenceResetWithGapFillAndPossDupFlagTrueDuringResendRequestIsProcessed()
+ {
+ // Default is false
+ Assert.That(session.IgnorePossDupResendRequests, Is.EqualTo(false));
+ session.RequiresOrigSendingTime = false;
+ session.Next(); // causes Logon send
+ // Logon
+ Logon();
+
+ // quote
+ var quote = new QuickFix.FIX42.Quote();
+
+ SendTheMessage(quote, 2);
+ SendTheMessage(quote, 3);
+ SendTheMessage(quote, 4);
+
+ seqNum = 10;
+ // without changes the arrowed comments scenario happens
+ Logon(); // <--- Will cause resend from 5 and is queued (all cases)
+
+ SendTheMessage(quote, 5, true);
+ SendTheMessage(quote, 6, true);
+ SendTheMessage(quote, 7, true);
+ SendTheMessage(quote, 8, true);
+ SendTheMessage(quote, 9, true); // <--will cause logon message queued above to be replayed AND advance msgSeqNum to 11! (all cases)
+ var seqReset = new SequenceReset(new NewSeqNo(12));
+ seqReset.GapFillFlag = new GapFillFlag(true);
+ SendTheMessage(seqReset, 10, true); // <-- Without change this message will be ignored as too low but poss dup is Y so will NOT throw - in the changed version this WILL be honored
+
+ Assert.That(session.NextTargetMsgSeqNum, Is.EqualTo(12));
+
+ Assert.That(responder.msgLookup.ContainsKey(QuickFix.Fields.MsgType.RESEND_REQUEST) && responder.msgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Count == 1);
+ }
+ }
+}
diff --git a/UnitTests/SessionTest.cs b/UnitTests/SessionTest.cs
index ccfa316f4..b9ff7d3f4 100755
--- a/UnitTests/SessionTest.cs
+++ b/UnitTests/SessionTest.cs
@@ -12,7 +12,7 @@ class MockResponder : QuickFix.IResponder
#region Responder Members
QuickFix.DefaultMessageFactory messageFactory = new QuickFix.DefaultMessageFactory();
-
+
public Dictionary> msgLookup = new Dictionary>();
public Queue dups = new Queue();
@@ -38,7 +38,7 @@ public bool Send(string msgStr)
if (message.Header.IsSetField(possDup))
message.Header.GetField(possDup);
- if (possDup.getValue() && msgType.getValue()!= QuickFix.Fields.MsgType.SEQUENCE_RESET)
+ if (possDup.getValue() && msgType.getValue() != QuickFix.Fields.MsgType.SEQUENCE_RESET)
{
dups.Enqueue(message);
}
@@ -97,7 +97,7 @@ public void ToApp(QuickFix.Message message, QuickFix.SessionID sessionId)
{
if (doNotSendException != null)
throw doNotSendException;
-
+
}
public void FromApp(QuickFix.Message message, QuickFix.SessionID sessionID)
@@ -198,7 +198,7 @@ public void Setup()
// acceptor
session = new QuickFix.Session(false, application, new QuickFix.MemoryStoreFactory(), sessionID,
- new QuickFix.DataDictionaryProvider(),new QuickFix.SessionSchedule(config), 0, logFactory, new QuickFix.DefaultMessageFactory(), "blah");
+ new QuickFix.DataDictionaryProvider(), new QuickFix.SessionSchedule(config), 0, logFactory, new QuickFix.DefaultMessageFactory(), "blah");
session.SetResponder(responder);
session.CheckLatency = false;
@@ -254,15 +254,15 @@ public bool RESENT()
public bool SENT_REJECT()
{
- return responder.msgLookup.ContainsKey(QuickFix.Fields.MsgType.REJECT) &&
- responder.msgLookup[QuickFix.Fields.MsgType.REJECT].Count>0;
+ return responder.msgLookup.ContainsKey(QuickFix.Fields.MsgType.REJECT) &&
+ responder.msgLookup[QuickFix.Fields.MsgType.REJECT].Count > 0;
}
- public bool SENT_HEART_BEAT()
- {
+ public bool SENT_HEART_BEAT()
+ {
return responder.msgLookup.ContainsKey(QuickFix.Fields.MsgType.HEARTBEAT) &&
responder.msgLookup[QuickFix.Fields.MsgType.HEARTBEAT].Count > 0;
- }
+ }
public bool SENT_BUSINESS_REJECT()
{
@@ -317,7 +317,7 @@ public bool SENT_REJECT(int reason, int refTag)
QuickFix.Fields.SessionRejectReason reasonField = new QuickFix.Fields.SessionRejectReason();
msg.GetField(reasonField);
- if(reasonField.getValue() != reason)
+ if (reasonField.getValue() != reason)
return false;
if (!msg.IsSetField(QuickFix.Fields.Tags.RefTagID))
@@ -373,7 +373,7 @@ private void SendTheMessage(QuickFix.Message msg)
[Test]
public void ConditionalTagMissingReject()
- {
+ {
application.fromAppException = new QuickFix.FieldNotFoundException(61);
Logon();
@@ -388,10 +388,10 @@ public void ConditionalTagMissingReject()
public void IncorrectTagValueReject()
{
application.fromAppException = new QuickFix.IncorrectTagValue(54);
-
+
Logon();
SendNOSMessage();
- Assert.That(SENT_REJECT(QuickFix.Fields.SessionRejectReason.VALUE_IS_INCORRECT,54));
+ Assert.That(SENT_REJECT(QuickFix.Fields.SessionRejectReason.VALUE_IS_INCORRECT, 54));
}
@@ -408,7 +408,7 @@ public void UnsupportedMessageReject()
[Test]
public void LogonReject()
{
- application.fromAdminException = new QuickFix.RejectLogon("Failed Logon");
+ application.fromAdminException = new QuickFix.RejectLogon("Failed Logon");
Logon();
Assert.That(SENT_LOGOUT());
@@ -505,7 +505,7 @@ public void TestGapFillOnResend()
public void TestResendSessionLevelReject()
{
Assert.False(session.ResendSessionLevelRejects); // check for correct default
- Logon();
+ Logon();
QuickFix.FIX42.Reject reject = new QuickFix.FIX42.Reject(
new QuickFix.Fields.RefSeqNum(10));
@@ -545,12 +545,12 @@ public void AssertMicrosecondsInTag(string msgType, int tag, bool shouldHaveMicr
public void TestMillisecondsInSendingTimeStamp()
{
// MS in timestamp should default to Y
- Assert.That(session.TimeStampPrecision == QuickFix.Fields.Converters.TimeStampPrecision.Millisecond );
+ Assert.That(session.TimeStampPrecision == QuickFix.Fields.Converters.TimeStampPrecision.Millisecond);
// Ms should show up
Logon();
AssertMsInTag(QuickFix.Fields.MsgType.LOGON, QuickFix.Fields.Tags.SendingTime, true);
-
+
// No ms
session.TimeStampPrecision = QuickFix.Fields.Converters.TimeStampPrecision.Second;
Logon();
@@ -585,7 +585,7 @@ public void TestMicrosecondsInSendingTimeStamp()
// Less than FIX42 - no microseconds in timestamp, even if you tell it to
sessionID = new QuickFix.SessionID(QuickFix.FixValues.BeginString.FIX40, "SENDER", "TARGET");
session.SessionID = sessionID;
- session.TimeStampPrecision = QuickFix.Fields.Converters.TimeStampPrecision.Microsecond;
+ session.TimeStampPrecision = QuickFix.Fields.Converters.TimeStampPrecision.Microsecond;
Logon40();
Assert.That(responder.msgLookup[QuickFix.Fields.MsgType.LOGON].Count == 3);
AssertMicrosecondsInTag(QuickFix.Fields.MsgType.LOGON, QuickFix.Fields.Tags.SendingTime, false);
@@ -595,11 +595,11 @@ public void TestMicrosecondsInSendingTimeStamp()
public void TestMillisecondsInOrigSendingTimeStamp()
{
// MS in timestamp should default
- Assert.That( session.TimeStampPrecision == QuickFix.Fields.Converters.TimeStampPrecision.Millisecond );
-
+ Assert.That(session.TimeStampPrecision == QuickFix.Fields.Converters.TimeStampPrecision.Millisecond);
+
// Logon first
Logon();
-
+
// Do a resend request
SendResendRequest(0, 2);
AssertMsInTag(QuickFix.Fields.MsgType.SEQUENCERESET, QuickFix.Fields.Tags.OrigSendingTime, true);
@@ -789,7 +789,7 @@ public void TestDoesSessionExist()
QuickFix.SessionID validSessionID = new QuickFix.SessionID("FIX.4.2", "SENDER", "TARGET");
Assert.That(QuickFix.Session.DoesSessionExist(invalidSessionID), Is.EqualTo(false));
- Assert.That(QuickFix.Session.DoesSessionExist(validSessionID), Is.EqualTo(true));
+ Assert.That(QuickFix.Session.DoesSessionExist(validSessionID), Is.EqualTo(true));
}
[Test]
@@ -845,7 +845,7 @@ public void TestRequiresOrigSendingTime_Y()
Logon();
QuickFix.FIX42.SequenceReset sr = new QuickFix.FIX42.SequenceReset(new QuickFix.Fields.NewSeqNo(5));
- sr.GapFillFlag = new QuickFix.Fields.GapFillFlag(true);
+ sr.GapFillFlag = new QuickFix.Fields.GapFillFlag(false);
sr.Header.SetField(new QuickFix.Fields.PossDupFlag(true));
sr.Header.SetField(new QuickFix.Fields.MsgSeqNum(seqNum--)); // so it triggers DoTargetTooLow code