diff --git a/message.go b/message.go index 5bea1d375..550f68fb0 100644 --- a/message.go +++ b/message.go @@ -310,6 +310,7 @@ func parseGroup(mp *msgParser, tags []Tag) { dm := mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1] fields := getGroupFields(mp.msg, tags, mp.appDataDictionary) +parseLoop: for { mp.fieldIndex++ mp.parsedFieldBytes = &mp.msg.fields[mp.fieldIndex] @@ -350,16 +351,15 @@ func parseGroup(mp *msgParser, tags []Tag) { fields = getGroupFields(mp.msg, searchTags, mp.appDataDictionary) continue } - if len(tags) > 1 { - searchTags = tags[:len(tags)-1] - } - // Did this tag occur after a nested group and belongs to the parent group. - if isNumInGroupField(mp.msg, searchTags, mp.appDataDictionary) { - // Add the field member to the group. - dm = append(dm, *mp.parsedFieldBytes) - // Continue parsing the parent group. - fields = getGroupFields(mp.msg, searchTags, mp.appDataDictionary) - continue + // The tag isn't a member of the current group. Walk up: if an + // ancestor group includes it, resume there; otherwise it's body-level. + for len(tags) > 1 { + tags = tags[:len(tags)-1] + fields = getGroupFields(mp.msg, tags, mp.appDataDictionary) + if isGroupMember(mp.parsedFieldBytes.tag, fields) { + dm = append(dm, *mp.parsedFieldBytes) + continue parseLoop + } } // Add the repeating group. mp.msg.Body.add(dm) diff --git a/message_test.go b/message_test.go index 72f11dcd0..7bdd3af37 100644 --- a/message_test.go +++ b/message_test.go @@ -113,6 +113,45 @@ func (s *MessageSuite) TestParseOutOfOrder() { s.Nil(ParseMessage(s.msg, rawMsg)) } +func (s *MessageSuite) TestParseGroup_BodyFieldAfterNestedGroup() { + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) + + // Wire layout (FIX 4.4 MassQuoteAcknowledgement): + // 117=QID QuoteID (body) + // 296=1 NoQuoteSets count (body group) + // 302=SET1 QuoteSetID (inside NoQuoteSets) + // 295=1 NoQuoteEntries count (nested group) + // 299=E1 QuoteEntryID (inside NoQuoteEntries) + // 132=100 BidPx (inside NoQuoteEntries) + // 133=101 OfferPx (inside NoQuoteEntries) + // 297=0 QuoteStatus (BODY level, AFTER the group) + rawMsg := bytes.NewBufferString( + "8=FIX.4.49=6335=b117=QID" + + "296=1302=SET1" + + "295=1299=E1132=100133=101" + + "297=1" + + "10=002") + + err := ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict) + s.Nil(err) + + rebuildBytes := s.msg.build() + expectedBytes := rawMsg.Bytes() + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n +%s\n -%s", rebuildBytes, expectedBytes) + + // NoQuoteSets count (group delimiter) lands in Body as expected. + s.True(s.msg.Body.Has(Tag(296))) + + // QuoteStatus (297) must be a top-level body field per FIX 4.4. + s.True(s.msg.Body.Has(Tag(297)), + "QuoteStatus (297) must land at the body level; it is a body field "+ + "in MassQuoteAcknowledgement, not a member of NoQuoteSets.") + val, verr := s.msg.Body.GetInt(Tag(297)) + s.Nil(verr) + s.Equal(1, val) +} + func (s *MessageSuite) TestBuild() { s.msg.Header.SetField(tagBeginString, FIXString(BeginStringFIX44)) s.msg.Header.SetField(tagMsgType, FIXString("A"))