From 00844b53367671e543988e78a726cf6e4aef5371 Mon Sep 17 00:00:00 2001 From: Peter Larson Date: Mon, 26 Jun 2023 10:15:45 -0500 Subject: [PATCH] Proper handling of length defined data fields - use XmlDataLen field to parse XmdData in FIX messags rather than looking for delimiters - added test pr #782 --- QuickFIXn/Message/Message.cs | 50 +++++++++++++++++++++++++++++++++--- RELEASE_NOTES.md | 1 + UnitTests/MessageTests.cs | 48 ++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/QuickFIXn/Message/Message.cs b/QuickFIXn/Message/Message.cs index 5bfc60360..333f286b4 100644 --- a/QuickFIXn/Message/Message.cs +++ b/QuickFIXn/Message/Message.cs @@ -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 @@ -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)) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index dd5097afa..c767ab24c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -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 diff --git a/UnitTests/MessageTests.cs b/UnitTests/MessageTests.cs index a8b5dcfda..315f75ae8 100644 --- a/UnitTests/MessageTests.cs +++ b/UnitTests/MessageTests.cs @@ -13,6 +13,8 @@ public class MessageTests { private IMessageFactory _defaultMsgFactory = new DefaultMessageFactory(); + private const char Nul = Message.SOH; + [Test] public void IdentifyTypeTest() { @@ -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=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 + "" + + 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(delegate { n.FromString(s, true, dd, dd, _defaultMsgFactory); }); + Assert.AreEqual("field not found for tag: 212", ex!.Message); + } [Test]