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 #782
  • Loading branch information
larsope authored and gbirchmeier committed Feb 13, 2024
1 parent 6bdc40b commit 00844b5
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 3 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 (ArgumentOutOfRangeException e)
{
throw new MessageParseError($"Error at position ({pos}) while parsing msg ({msgstr})", e);
}
catch (OverflowException e)
{
throw new MessageParseError($"Error at position ({pos}) while parsing msg ({msgstr})", e);
}
catch (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 @@ -66,6 +66,7 @@ What's New
* #697 - new SocketIgnoreProxy setting (ABSJ415)
* #740 - Capture inner exception messages when handling authentication exceptions (rars)
* #833 - Add Try/Catch logic to SocketInitiator.OnStart() (Falcz)
* #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
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 00844b5

Please sign in to comment.