Skip to content

Commit

Permalink
Proper handling of length defined data fields
Browse files Browse the repository at this point in the history
- use XmlDataLen field to parse XmdData in FIX messags rather than looking for delimiters
- added test

pr connamara#782
  • Loading branch information
larsope authored and gbirchmeier committed Feb 12, 2024
1 parent 6bf770f commit d13062d
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 12 deletions.
50 changes: 47 additions & 3 deletions QuickFIXn/Message/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,39 @@ public static MsgType IdentifyType(string fixstring)
return new MsgType(GetMsgType(fixstring));
}

public static int ExtractFieldTag(string msgstr, int pos)
{
int tagend = msgstr.IndexOf('=', pos);
int tag = Convert.ToInt32(msgstr.Substring(pos, tagend - pos));
return tag;
}

public static StringField ExtractDataField(string msgstr, int dataLength, ref int pos)
{
try
{
int tagend = msgstr.IndexOf('=', pos);
int tag = Convert.ToInt32(msgstr.Substring(pos, tagend - pos));
pos = tagend + 1;
StringField field = new StringField(tag, msgstr.Substring(pos, dataLength));

pos += dataLength + 1;
return field;
}
catch (System.ArgumentOutOfRangeException e)
{
throw new MessageParseError("Error at position (" + pos + ") while parsing msg (" + msgstr + ")", e);
}
catch (System.OverflowException e)
{
throw new MessageParseError("Error at position (" + pos + ") while parsing msg (" + msgstr + ")", e);
}
catch (System.FormatException e)
{
throw new MessageParseError("Error at position (" + pos + ") while parsing msg (" + msgstr + ")", e);
}
}

public static StringField ExtractField(string msgstr, ref int pos)
{
try
Expand Down Expand Up @@ -314,9 +347,20 @@ public void FromString(

while (pos < msgstr.Length)
{
StringField f = ExtractField(msgstr, ref pos);

if (validate && count < 3 && Header.HEADER_FIELD_ORDER[count++] != f.Tag)
StringField? f = null;

int fieldTag = ExtractFieldTag(msgstr, pos);
if (fieldTag == Tags.XmlData)
{
if (IsHeaderField(Tags.XmlDataLen))
f = ExtractDataField(msgstr, Header.GetInt(Tags.XmlDataLen), ref pos);
else if (IsSetField(Tags.XmlDataLen))
f = ExtractDataField(msgstr, GetInt(Tags.XmlDataLen), ref pos);
}

f ??= ExtractField(msgstr, ref pos);

if (validate && (count < 3) && (Header.HEADER_FIELD_ORDER[count++] != f.Tag))
throw new InvalidMessage("Header fields out of order");

if (IsHeaderField(f.Tag, transportDict))
Expand Down
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ What's New
* #711 - fix explicit 0.0.0.0 address binding (bohdanstefaniuk)
* #823 - get rid of IOIQty enums in FIX5 DDs, allow free string (gbirchmeier)
* #786 - rewrite HttpServer: better HTML, no crash on errors (gbirchmeier)
* #782 - proper handling of XmlData field (larsope)

### v1.11.2:
* same as v1.11.1, but I fixed the readme in the pushed nuget packages
Expand Down
18 changes: 9 additions & 9 deletions UnitTests/DataDictionaryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class DataDictionaryTests
{
private QuickFix.IMessageFactory _defaultMsgFactory = new QuickFix.DefaultMessageFactory();

private const char NUL = Message.SOH;
private const char Nul = Message.SOH;

[Test]
public void VersionTest()
Expand Down Expand Up @@ -307,11 +307,11 @@ public void CheckGroupCountTest()

QuickFix.FIX42.NewOrderSingle n = new QuickFix.FIX42.NewOrderSingle();

string s = "8=FIX.4.2" + NUL + "9=148" + NUL + "35=D" + NUL + "34=2" + NUL + "49=TW" + NUL + "52=20111011-15:06:23.103" + NUL + "56=ISLD" + NUL
+ "11=ID" + NUL + "21=1" + NUL + "40=1" + NUL + "54=1" + NUL + "38=200.00" + NUL + "55=INTC" + NUL
+ "386=3" + NUL + "336=PRE-OPEN" + NUL + "336=AFTER-HOURS" + NUL
+ "60=20111011-15:06:23.103" + NUL
+ "10=35" + NUL;
string s = "8=FIX.4.2" + Nul + "9=148" + Nul + "35=D" + Nul + "34=2" + Nul + "49=TW" + Nul + "52=20111011-15:06:23.103" + Nul + "56=ISLD" + Nul
+ "11=ID" + Nul + "21=1" + Nul + "40=1" + Nul + "54=1" + Nul + "38=200.00" + Nul + "55=INTC" + Nul
+ "386=3" + Nul + "336=PRE-OPEN" + Nul + "336=AFTER-HOURS" + Nul
+ "60=20111011-15:06:23.103" + Nul
+ "10=35" + Nul;

n.FromString(s, true, dd, dd, _defaultMsgFactory);

Expand Down Expand Up @@ -352,9 +352,9 @@ public void ValidateWithRepeatingGroupTest()
dd.LoadFIXSpec("FIX42");
QuickFix.FIX42.MessageFactory f = new QuickFix.FIX42.MessageFactory();

string msgStr = "8=FIX.4.2" + NUL + "9=87" + NUL + "35=B" + NUL + "34=3" + NUL + "49=CLIENT1" + NUL
+ "52=20111012-22:15:55.474" + NUL + "56=EXECUTOR" + NUL + "148=AAAAAAA" + NUL
+ "33=2" + NUL + "58=L1" + NUL + "58=L2" + NUL + "10=016" + NUL;
string msgStr = "8=FIX.4.2" + Nul + "9=87" + Nul + "35=B" + Nul + "34=3" + Nul + "49=CLIENT1" + Nul
+ "52=20111012-22:15:55.474" + Nul + "56=EXECUTOR" + Nul + "148=AAAAAAA" + Nul
+ "33=2" + Nul + "58=L1" + Nul + "58=L2" + Nul + "10=016" + Nul;

QuickFix.Fields.MsgType msgType = Message.IdentifyType(msgStr);
string beginString = Message.ExtractBeginString(msgStr);
Expand Down
48 changes: 48 additions & 0 deletions UnitTests/MessageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class MessageTests
{
private IMessageFactory _defaultMsgFactory = new DefaultMessageFactory();

private const char Nul = Message.SOH;

[Test]
public void IdentifyTypeTest()
{
Expand Down Expand Up @@ -372,6 +374,52 @@ public void NestedRepeatingGroupParseGroupTest()
Assert.That(subGrp.GetString(Tags.PartySubID), Is.EqualTo("OHAI123"));
}

[Test]
public void ReadXmlDataTest() {
// Use tag 212/XmlDataLen to properly read 213/XmlData

QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary();
dd.LoadFIXSpec("FIX42");

QuickFix.FIX42.NewOrderSingle n = new QuickFix.FIX42.NewOrderSingle();

string s = "8=FIX.4.2" + Nul + "9=495" + Nul + "35=n" + Nul + "34=31420" + Nul + "369=1003" + Nul +
"52=20200701-20:34:33.978" + Nul + "49=CME" + Nul + "50=84" +
Nul + "56=DUMMY11" + Nul + "57=SID1" + Nul + "143=US,IL" + Nul + "212=392" + Nul +
"213=<RTRF>8=FIX.4.2" + Nul + "9=356" + Nul + "35=8" + Nul + "34=36027" + Nul +
"369=18623" + Nul + "52=20200701-20:34:33.977" + Nul + "49=CME" + Nul + "50=84" + Nul +
"56=M2L000N" + Nul + "57=DUMMY" + Nul + "143=US,IL" + Nul + "1=00331" + Nul +
"6=0" + Nul + "11=ACP1593635673935" + Nul + "14=0" + Nul + "17=84618:1342652" + Nul + "20=0" +
Nul + "37=84778833500" + Nul + "38=10" + Nul + "39=0" + Nul + "40=2" + Nul +
"41=0" + Nul + "44=139.203125" + Nul + "48=204527" + Nul + "54=1" + Nul + "55=ZN" + Nul +
"59=0" + Nul + "60=20200701-20:34:33.976" + Nul + "107=ZNH1" + Nul + "150=0" + Nul +
"151=10" + Nul + "167=FUT" + Nul + "432=20200701" + Nul + "1028=Y" + Nul + "1031=Y" + Nul +
"5979=1593635673976364291" + Nul + "9717=ACP1593635673935" + Nul + "10=124" + Nul + "</RTRF>" +
Nul + "10=028" + Nul;

n.FromString(s, true, dd, dd, _defaultMsgFactory);

//verify that the data field was read correctly
Assert.AreEqual(n.Header.GetInt(212), n.Header.GetString(213).Length);
}

[Test]
public void XmlDataWithoutLengthTest() {
QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary();
dd.LoadFIXSpec("FIX42");

QuickFix.FIX42.NewOrderSingle n = new QuickFix.FIX42.NewOrderSingle();

string s = "8=FIX.4.2" + Nul + "9=495" + Nul + "35=n" + Nul + "34=31420" + Nul + "369=1003" + Nul +
"52=20200701-20:34:33.978" + Nul + "49=CME" + Nul + "50=84" +
Nul + "56=DUMMY11" + Nul + "57=SID1" + Nul + "143=US,IL" + Nul +
"213=oops my length field 212 is missing" +
Nul + "10=028" + Nul;

FieldNotFoundException ex =
Assert.Throws<FieldNotFoundException>(delegate { n.FromString(s, true, dd, dd, _defaultMsgFactory); });
Assert.AreEqual("field not found for tag: 212", ex!.Message);
}


[Test]
Expand Down

0 comments on commit d13062d

Please sign in to comment.