Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve original body when resending #624

Merged
merged 2 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading