Skip to content

Commit

Permalink
Make body-less messages work, specifically 35=n
Browse files Browse the repository at this point in the history
* Change DataDictionary class to also check the tag 35 enum when determining if msg type is valid
* Put some null-protection around calls to DataDictionary.Message [] accessor
* ATs of course
* Renamed 35=n to "XML_NON_FIX" in the tag 35 enum in all DD xmls
  • Loading branch information
gbirchmeier committed Dec 9, 2024
1 parent 9e7f0aa commit 88e09ad
Show file tree
Hide file tree
Showing 16 changed files with 170 additions and 25 deletions.
19 changes: 17 additions & 2 deletions AcceptanceTest/ATApplication.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using QuickFix;
using QuickFix.Fields;

namespace AcceptanceTest
{
Expand Down Expand Up @@ -110,6 +111,18 @@ private void ProcessNOS(Message message, SessionID sessionId)
Session.SendToTarget(echo, sessionId);
}

private void AcknowledgeXmlMessage(Message msg, SessionID sessionId) {
string beginString = Message.ExtractBeginString(msg.ToString());
string seqNo = msg.Header.GetString(34);

Message response = new();
response.Header.SetField(new BeginString(beginString));
response.Header.SetField(new MsgType("B"));
response.SetField(new Headline($"Successfully received 'n' message with seqNo={seqNo}"));
response.SetField(new NoLinesOfText(0));

Session.SendToTarget(response, sessionId);
}

public void OnMessage(QuickFix.FIX41.News news, SessionID sessionId) { ProcessNews(news, sessionId); }
public void OnMessage(QuickFix.FIX42.News news, SessionID sessionId) { ProcessNews(news, sessionId); }
Expand Down Expand Up @@ -173,8 +186,10 @@ public void FromApp(Message message, SessionID sessionId)
}
}

public void FromAdmin(Message message, SessionID sessionId)
{ }
public void FromAdmin(Message message, SessionID sessionId) {
if (message.Header.GetString(35) == "n")
AcknowledgeXmlMessage(message, sessionId);
}

public void ToAdmin(Message message, SessionID sessionId) { }
public void ToApp(Message message, SessionID sessionId) { }
Expand Down
6 changes: 3 additions & 3 deletions AcceptanceTest/ReflectorClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ internal class ReflectorClient : IReflector
{
private static readonly Regex s_timeRegex = new(@"\<TIME([+-]\d+)\>", RegexOptions.Compiled);
private static readonly Regex s_beginStringRegex = new(@"^8=.*?[\001]", RegexOptions.Compiled);
private static readonly Regex s_bodyLengthRegex = new(@"[\001]9=.*?[\001]", RegexOptions.Compiled);
private static readonly Regex s_checksumRegex = new(@"[\001]10=.*[\001]$", RegexOptions.Compiled);
private static readonly Regex s_bodyLengthRegex = new(@"^8=[^\001]*[\001]9=[^\001]*", RegexOptions.Compiled);
private static readonly Regex s_checksumRegex = new(@"[\001]10=[0-9]+[\001]$", RegexOptions.Compiled);
private static readonly Dictionary<string, Regex> s_expectPatterns = new()
{
{ "10", new Regex(@"\d{3}", RegexOptions.Compiled) },
Expand Down Expand Up @@ -217,4 +217,4 @@ public void Dispose()
{
ShutdownSocket();
}
}
}
18 changes: 18 additions & 0 deletions AcceptanceTest/definitions/server/fix43/ReceiveXmlMessage.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Engine can receive complex type 'n' messages
# and is not confused if the xml content contains SOH or FIX tags

iCONNECT
I8=FIX.4.335=A34=149=TW52=<TIME>56=ISLD98=0108=6
E8=FIX.4.335=A34=149=ISLD52=00000000-00:00:00.00056=TW98=0108=610=0

# Receive encapsulated ExecutionReport and get News response.
# (Need the News response to ensure that FIX parses it and passes it up to the app;
# without this response, the test will false pass)
I8=FIX.4.335=n34=249=TW52=<TIME>56=ISLD212=55213=<RTRF>8=FIX.4.39=37735=834=208blah blah blah</RTRF>
E8=FIX.4.335=B34=249=ISLD52=00000000-00:00:00.00056=TW33=0148=Successfully received 'n' message with seqNo=210=0

# logout message and response
I8=FIX.4.335=534=349=TW52=<TIME>56=ISLD
E8=FIX.4.335=534=349=ISLD52=00000000-00:00:00.00056=TW10=0
eDISCONNECT

18 changes: 18 additions & 0 deletions AcceptanceTest/definitions/server/fix44/ReceiveXmlMessage.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Engine can receive complex type 'n' messages
# and is not confused if the xml content contains SOH or FIX tags

iCONNECT
I8=FIX.4.435=A34=149=TW52=<TIME>56=ISLD98=0108=6
E8=FIX.4.435=A34=149=ISLD52=00000000-00:00:00.00056=TW98=0108=610=0

# Receive encapsulated ExecutionReport and get News response.
# (Need the News response to ensure that FIX parses it and passes it up to the app;
# without this response, the test will false pass)
I8=FIX.4.435=n34=249=TW52=<TIME>56=ISLD212=55213=<RTRF>8=FIX.4.39=37735=834=208blah blah blah</RTRF>
E8=FIX.4.435=B34=249=ISLD52=00000000-00:00:00.00056=TW33=0148=Successfully received 'n' message with seqNo=210=0

# logout message and response
I8=FIX.4.435=534=349=TW52=<TIME>56=ISLD
E8=FIX.4.435=534=349=ISLD52=00000000-00:00:00.00056=TW10=0
eDISCONNECT

18 changes: 18 additions & 0 deletions AcceptanceTest/definitions/server/fix50/ReceiveXmlMessage.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Engine can receive complex type 'n' messages
# and is not confused if the xml content contains SOH or FIX tags

iCONNECT
I8=FIXT.1.135=A34=149=TW52=<TIME>56=ISLD98=0108=61137=7
E8=FIXT.1.135=A34=149=ISLD52=00000000-00:00:00.00056=TW98=0108=61137=710=0

# Receive encapsulated ExecutionReport and get News response.
# (Need the News response to ensure that FIX parses it and passes it up to the app;
# without this response, the test will false pass)
I8=FIXT.1.135=n34=249=TW52=<TIME>56=ISLD212=55213=<RTRF>8=FIX.4.39=37735=834=208blah blah blah</RTRF>
E8=FIXT.1.135=B34=249=ISLD52=00000000-00:00:00.00056=TW33=0148=Successfully received 'n' message with seqNo=210=0

# logout message and response
I8=FIXT.1.135=534=349=TW52=<TIME>56=ISLD
E8=FIXT.1.135=534=349=ISLD52=00000000-00:00:00.00056=TW10=0
eDISCONNECT

18 changes: 18 additions & 0 deletions AcceptanceTest/definitions/server/fix50sp1/ReceiveXmlMessage.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Engine can receive complex type 'n' messages
# and is not confused if the xml content contains SOH or FIX tags

iCONNECT
I8=FIXT.1.135=A34=149=TW52=<TIME>56=ISLD98=0108=61137=8
E8=FIXT.1.135=A34=149=ISLD52=00000000-00:00:00.00056=TW98=0108=61137=810=0

# Receive encapsulated ExecutionReport and get News response.
# (Need the News response to ensure that FIX parses it and passes it up to the app;
# without this response, the test will false pass)
I8=FIXT.1.135=n34=249=TW52=<TIME>56=ISLD212=55213=<RTRF>8=FIX.4.39=37735=834=208blah blah blah</RTRF>
E8=FIXT.1.135=B34=249=ISLD52=00000000-00:00:00.00056=TW33=0148=Successfully received 'n' message with seqNo=210=0

# logout message and response
I8=FIXT.1.135=534=349=TW52=<TIME>56=ISLD
E8=FIXT.1.135=534=349=ISLD52=00000000-00:00:00.00056=TW10=0
eDISCONNECT

18 changes: 18 additions & 0 deletions AcceptanceTest/definitions/server/fix50sp2/ReceiveXmlMessage.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Engine can receive complex type 'n' messages
# and is not confused if the xml content contains SOH or FIX tags

iCONNECT
I8=FIXT.1.135=A34=149=TW52=<TIME>56=ISLD98=0108=61137=9
E8=FIXT.1.135=A34=149=ISLD52=00000000-00:00:00.00056=TW98=0108=61137=910=0

# Receive encapsulated ExecutionReport and get News response.
# (Need the News response to ensure that FIX parses it and passes it up to the app;
# without this response, the test will false pass)
I8=FIXT.1.135=n34=249=TW52=<TIME>56=ISLD212=55213=<RTRF>8=FIX.4.39=37735=834=208blah blah blah</RTRF>
E8=FIXT.1.135=B34=249=ISLD52=00000000-00:00:00.00056=TW33=0148=Successfully received 'n' message with seqNo=210=0

# logout message and response
I8=FIXT.1.135=534=349=TW52=<TIME>56=ISLD
E8=FIXT.1.135=534=349=ISLD52=00000000-00:00:00.00056=TW10=0
eDISCONNECT

51 changes: 38 additions & 13 deletions QuickFIXn/DataDictionary/DataDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ public class DataDictionary
public string? MajorVersion { get; private set; }
public string? MinorVersion { get; private set; }
public string? Version { get; private set; }
public readonly Dictionary<int, DDField> FieldsByTag = new();
public readonly Dictionary<string, DDField> FieldsByName = new();

public readonly Dictionary<string, DDMap> Messages = new();
public readonly Dictionary<string, DDField> FieldsByName = new();

// TODO: Audit these Dict usages for unprotected [] lookups.
public readonly Dictionary<int, DDField> FieldsByTag = new();
private readonly Dictionary<string, XmlNode> _componentsByName = new();

public bool CheckFieldsOutOfOrder { get; set; }
Expand Down Expand Up @@ -118,10 +121,19 @@ public static void CheckHasNoRepeatedTags(FieldMap map)
: null;
}

/// <summary>
/// If DD defines this message type, returns with no side effect; else throws InvalidMessageType.
/// </summary>
/// <param name="msgType"></param>
/// <exception cref="InvalidMessageType"></exception>
public void CheckMsgType(string msgType)
{
if (!Messages.ContainsKey(msgType))
throw new InvalidMessageType();
if (Messages.ContainsKey(msgType))
return;
if (FieldsByTag[35].EnumDict.ContainsKey(msgType))
return;
throw new InvalidMessageType();

}

public void CheckHasRequired(Message message, string msgType)
Expand All @@ -138,10 +150,13 @@ public void CheckHasRequired(Message message, string msgType)
throw new RequiredTagMissing(field);
}

foreach (int field in Messages[msgType].ReqFields)
if (Messages.TryGetValue(msgType, out DDMap? ddmap))
{
if (!message.IsSetField(field))
throw new RequiredTagMissing(field);
foreach (int field in ddmap.ReqFields)
{
if (!message.IsSetField(field))
throw new RequiredTagMissing(field);
}
}

/* FIXME TODO group stuff
Expand Down Expand Up @@ -183,13 +198,23 @@ public void Iterate(FieldMap map, string msgType)
}

// check contents of each group
foreach (int groupTag in map.GetGroupTags())
{
for (int i = 1; i <= map.GroupCount(groupTag); i++)
List<int> groupTagList = map.GetGroupTags();
if (groupTagList.Count > 0) {
if (!Messages.TryGetValue(msgType, out DDMap? msgDdMap))
{
Group g = map.GetGroup(i, groupTag);
DDGrp ddg = this.Messages[msgType].GetGroup(groupTag);
IterateGroup(g, ddg, msgType);
// This shouldn't be possible
throw new MessageParseError(
$"Cannot locate msgType='{msgType}' message groups in DataDictionary (Please report this error)");
}

foreach (int groupTag in groupTagList)
{
for (int i = 1; i <= map.GroupCount(groupTag); i++)
{
Group g = map.GetGroup(i, groupTag);
DDGrp ddg = msgDdMap.GetGroup(groupTag);
IterateGroup(g, ddg, msgType);
}
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions QuickFIXn/Fields/Fields.cs
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ public MsgType(string val)
public const string BID_REQUEST = "k";
public const string BID_RESPONSE = "l";
public const string LIST_STRIKE_PRICE = "m";
public const string XML_MESSAGE = "n";
public const string XML_NON_FIX = "n";
public const string REGISTRATION_INSTRUCTIONS = "o";
public const string REGISTRATION_INSTRUCTIONS_RESPONSE = "p";
public const string ORDER_MASS_CANCEL_REQUEST = "q";
Expand Down Expand Up @@ -859,7 +859,6 @@ public MsgType(string val)
public const string BIDREQUEST = "k";
public const string BIDRESPONSE = "l";
public const string LISTSTRIKEPRICE = "m";
public const string XML_NON_FIX = "n";
public const string REGISTRATIONINSTRUCTIONS = "o";
public const string REGISTRATIONINSTRUCTIONSRESPONSE = "p";
public const string ORDERMASSCANCELREQUEST = "q";
Expand Down
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ What's New
* #900 - correct CompositeLog to use IFactory.CreateNonSessionLog when appropriate (gbirchmeier)
* #891 - make NonSessionLog implement IDisposable and fix the IOException (VAllens)
* #893 - Upgrade the unit testing framework to the latest version and remove obsolete Assert methods (VAllens)
* #900 - A fix to make body-less messages work, specifically 35=n aka XMLnonFIX (gbirchmeier)

### v1.12.0

Expand Down
15 changes: 15 additions & 0 deletions UnitTests/DataDictionaryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,21 @@ public void ParseThroughComments()
Assert.That(news.GetGroup(33).IsField(355), Is.True); // EncodedText
}

[Test]
public void TestCheckMsgType() {
DataDictionary dd = new DataDictionary();
dd.LoadFIXSpec("FIX44");

Assert.Throws(typeof(InvalidMessageType), delegate { dd.CheckMsgType("foo"); });

// True check, case 1: DD has a <message> definition for this one
Assert.DoesNotThrow(delegate { dd.CheckMsgType("B"); });

// True check, case 2: Type is in field 35, but there is no Message type
Assert.That(dd.Messages, Does.Not.ContainKey("n")); // Sanity check; if fails, fix the test.
Assert.DoesNotThrow(delegate { dd.CheckMsgType("n"); });
}

private static XmlNode MakeNode(string xmlString)
{
XmlDocument doc = new XmlDocument();
Expand Down
2 changes: 1 addition & 1 deletion spec/fix/FIX43.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2404,7 +2404,7 @@
<value enum="k" description="BID_REQUEST" />
<value enum="l" description="BID_RESPONSE" />
<value enum="m" description="LIST_STRIKE_PRICE" />
<value enum="n" description="XML_MESSAGE" />
<value enum="n" description="XML_NON_FIX" />
<value enum="o" description="REGISTRATION_INSTRUCTIONS" />
<value enum="p" description="REGISTRATION_INSTRUCTIONS_RESPONSE" />
<value enum="q" description="ORDER_MASS_CANCEL_REQUEST" />
Expand Down
2 changes: 1 addition & 1 deletion spec/fix/FIX44.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4160,7 +4160,7 @@
<value enum="k" description="BID_REQUEST" />
<value enum="l" description="BID_RESPONSE" />
<value enum="m" description="LIST_STRIKE_PRICE" />
<value enum="n" description="XML_MESSAGE" />
<value enum="n" description="XML_NON_FIX" />
<value enum="o" description="REGISTRATION_INSTRUCTIONS" />
<value enum="p" description="REGISTRATION_INSTRUCTIONS_RESPONSE" />
<value enum="q" description="ORDER_MASS_CANCEL_REQUEST" />
Expand Down
2 changes: 1 addition & 1 deletion spec/fix/FIX50.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4647,7 +4647,7 @@
<value enum='L' description='LIST_EXECUTE' />
<value enum='m' description='LIST_STRIKE_PRICE' />
<value enum='M' description='LIST_STATUS_REQUEST' />
<value enum='n' description='XML_MESSAGE' />
<value enum='n' description='XML_NON_FIX' />
<value enum='N' description='LIST_STATUS' />
<value enum='o' description='REGISTRATION_INSTRUCTIONS' />
<value enum='p' description='REGISTRATION_INSTRUCTIONS_RESPONSE' />
Expand Down
2 changes: 1 addition & 1 deletion spec/fix/FIX50SP1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5382,7 +5382,7 @@
<value enum='L' description='LIST_EXECUTE' />
<value enum='m' description='LIST_STRIKE_PRICE' />
<value enum='M' description='LIST_STATUS_REQUEST' />
<value enum='n' description='XML_MESSAGE' />
<value enum='n' description='XML_NON_FIX' />
<value enum='N' description='LIST_STATUS' />
<value enum='o' description='REGISTRATION_INSTRUCTIONS' />
<value enum='p' description='REGISTRATION_INSTRUCTIONS_RESPONSE' />
Expand Down
2 changes: 1 addition & 1 deletion spec/fix/FIXT11.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
<value enum='k' description='BID_REQUEST'/>
<value enum='l' description='BID_RESPONSE'/>
<value enum='m' description='LIST_STRIKE_PRICE'/>
<value enum='n' description='XML_MESSAGE'/>
<value enum='n' description='XML_NON_FIX'/>
<value enum='o' description='REGISTRATION_INSTRUCTIONS'/>
<value enum='p' description='REGISTRATION_INSTRUCTIONS_RESPONSE'/>
<value enum='q' description='ORDER_MASS_CANCEL_REQUEST'/>
Expand Down

0 comments on commit 88e09ad

Please sign in to comment.