Skip to content

Commit

Permalink
Merge pull request quickfixgo#624 from mgatny/main
Browse files Browse the repository at this point in the history
Preserve original body when resending
  • Loading branch information
ackleymi authored Apr 16, 2024
2 parents 3bf2b87 + 827728d commit dc22c5d
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 3 deletions.
2 changes: 1 addition & 1 deletion in_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ func ParseMessageWithDataDictionary(

trailerBytes := []byte{}
foundBody := false
foundTrailer := false
for {
parsedFieldBytes = &msg.fields[fieldIndex]
if xmlDataLen > 0 {
Expand All @@ -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
Expand All @@ -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)]
Expand Down Expand Up @@ -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)
Expand Down
40 changes: 38 additions & 2 deletions message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")))

Expand Down

0 comments on commit dc22c5d

Please sign in to comment.