diff --git a/in_session.go b/in_session.go index 50666a703..f157f3ea2 100644 --- a/in_session.go +++ b/in_session.go @@ -253,7 +253,7 @@ func (state inSession) resendMessages(session *session, beginSeqNo, endSeqNo int } session.log.OnEventf("Resending Message: %v", sentMessageSeqNum) - msgBytes = msg.build() + msgBytes = msg.buildWithBodyBytes(msg.bodyBytes) // workaround for maintaining repeating group field order session.EnqueueBytesAndSend(msgBytes) seqNum = sentMessageSeqNum + 1 diff --git a/message.go b/message.go index 36aef1dda..566b48eb7 100644 --- a/message.go +++ b/message.go @@ -210,6 +210,7 @@ func ParseMessageWithDataDictionary( trailerBytes := []byte{} foundBody := false + foundTrailer := false for { parsedFieldBytes = &msg.fields[fieldIndex] if xmlDataLen > 0 { @@ -228,6 +229,7 @@ func ParseMessageWithDataDictionary( msg.Header.add(msg.fields[fieldIndex : fieldIndex+1]) case isTrailerField(parsedFieldBytes.tag, transportDataDictionary): msg.Trailer.add(msg.fields[fieldIndex : fieldIndex+1]) + foundTrailer = true default: foundBody = true trailerBytes = rawBytes @@ -247,6 +249,12 @@ func ParseMessageWithDataDictionary( fieldIndex++ } + // This will happen if there are no fields in the body + if foundTrailer && !foundBody { + trailerBytes = rawBytes + msg.bodyBytes = nil + } + // Body length would only be larger than trailer if fields out of order. if len(msg.bodyBytes) > len(trailerBytes) { msg.bodyBytes = msg.bodyBytes[:len(msg.bodyBytes)-len(trailerBytes)] @@ -417,6 +425,21 @@ func (m *Message) build() []byte { return b.Bytes() } +// Constructs a []byte from a Message instance, using the given bodyBytes. +// This is a workaround for the fact that we currently rely on the generated Message types to properly serialize/deserialize RepeatingGroups. +// In other words, we cannot go from bytes to a Message then back to bytes, which is exactly what we need to do in the case of a Resend. +// This func lets us pull the Message from the Store, parse it, update the Header, and then build it back into bytes using the original Body. +// Note: The only standard non-Body group is NoHops. If that is used in the Header, this workaround may fail. +func (m *Message) buildWithBodyBytes(bodyBytes []byte) []byte { + m.cook() + + var b bytes.Buffer + m.Header.write(&b) + b.Write(bodyBytes) + m.Trailer.write(&b) + return b.Bytes() +} + func (m *Message) cook() { bodyLength := m.Header.length() + m.Body.length() + m.Trailer.length() m.Header.SetInt(tagBodyLength, bodyLength) diff --git a/message_test.go b/message_test.go index 9bb223751..fa60b3fd1 100644 --- a/message_test.go +++ b/message_test.go @@ -133,18 +133,54 @@ func (s *MessageSuite) TestReBuild() { s.msg.Header.SetField(tagOrigSendingTime, FIXString("20140515-19:49:56.659")) s.msg.Header.SetField(tagSendingTime, FIXString("20140615-19:49:56")) + s.msg.Header.SetField(tagPossDupFlag, FIXBoolean(true)) rebuildBytes := s.msg.build() - expectedBytes := []byte("8=FIX.4.29=12635=D34=249=TW52=20140615-19:49:5656=ISLD122=20140515-19:49:56.65911=10021=140=154=155=TSLA60=00010101-00:00:00.00010=128") + expectedBytes := []byte("8=FIX.4.29=13135=D34=243=Y49=TW52=20140615-19:49:5656=ISLD122=20140515-19:49:56.65911=10021=140=154=155=TSLA60=00010101-00:00:00.00010=122") - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n +%s\n-%s", rebuildBytes, expectedBytes) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n +%s\n -%s", rebuildBytes, expectedBytes) expectedBodyBytes := []byte("11=10021=140=154=155=TSLA60=00010101-00:00:00.000") s.True(bytes.Equal(s.msg.bodyBytes, expectedBodyBytes), "Incorrect body bytes, got %s", string(s.msg.bodyBytes)) } +func (s *MessageSuite) TestReBuildWithRepeatingGroupForResend() { + // Given the following message with a repeating group + origHeader := "8=FIXT.1.19=16135=834=349=ISLD52=20240415-03:43:17.92356=TW" + origBody := "6=1.0011=114=1.0017=131=1.0032=1.0037=138=1.0039=254=155=1150=2151=0.00453=1448=xyzzy447=D452=1" + origTrailer := "10=014" + rawMsg := bytes.NewBufferString(origHeader + origBody + origTrailer) + + // When I reparse the message from the store during a resend request + s.Nil(ParseMessage(s.msg, rawMsg)) + + // And I update the headers for resend + s.msg.Header.SetField(tagOrigSendingTime, FIXString("20240415-03:43:17.923")) + s.msg.Header.SetField(tagSendingTime, FIXString("20240415-14:41:23.456")) + s.msg.Header.SetField(tagPossDupFlag, FIXBoolean(true)) + + // When I rebuild the message + rebuildBytes := s.msg.build() + + // Then the repeating groups will not be in the correct order in the rebuilt message (note tags 447, 448, 452, 453) + expectedBytes := []byte("8=FIXT.1.19=19235=834=343=Y49=ISLD52=20240415-14:41:23.45656=TW122=20240415-03:43:17.9236=1.0011=114=1.0017=131=1.0032=1.0037=138=1.0039=254=155=1150=2151=0.00453=1448=xyzzy447=D452=110=018") + s.False(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but was: %s", expectedBytes, rebuildBytes) + expectedOutOfOrderBytes := []byte("8=FIXT.1.19=19235=834=343=Y49=ISLD52=20240415-14:41:23.45656=TW122=20240415-03:43:17.9236=1.0011=114=1.0017=131=1.0032=1.0037=138=1.0039=254=155=1150=2151=0.00447=D448=xyzzy452=1453=110=018") + s.True(bytes.Equal(expectedOutOfOrderBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but was: %s", expectedOutOfOrderBytes, rebuildBytes) + + // But the bodyBytes will still be correct + origBodyBytes := []byte(origBody) + s.True(bytes.Equal(origBodyBytes, s.msg.bodyBytes), "Incorrect body bytes, \n expected: %s\n but was: %s", origBodyBytes, s.msg.bodyBytes) + + // So when I combine the updated header + the original bodyBytes + the as-is trailer + resendBytes := s.msg.buildWithBodyBytes(s.msg.bodyBytes) + + // Then the reparsed, rebuilt message will retain the correct ordering of repeating group tags during resend + s.True(bytes.Equal(expectedBytes, resendBytes), "Unexpected bytes,\n expected: %s\n but was: %s", expectedBytes, resendBytes) +} + func (s *MessageSuite) TestReverseRoute() { s.Nil(ParseMessage(s.msg, bytes.NewBufferString("8=FIX.4.29=17135=D34=249=TW50=KK52=20060102-15:04:0556=ISLD57=AP144=BB115=JCD116=CS128=MG129=CB142=JV143=RY145=BH11=ID21=338=10040=w54=155=INTC60=20060102-15:04:0510=123")))