diff --git a/config/configuration.go b/config/configuration.go index 6282bab1e..3a663fcea 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -467,6 +467,18 @@ const ( // - N ValidateFieldsHaveValues string = "ValidateFieldsHaveValues" + // ValidateChecksum if set to N, the checksum verification of incoming messages will be skipped. + // Useful when dealing with legacy systems or proxies that might malform the checksum. + // + // Required: No + // + // Default: Y + // + // Valid Values: + // - Y + // - N + ValidateChecksum string = "ValidateChecksum" + // CheckLatency if set to Y, messages must be received from the counter-party within a defined number of seconds. // It is useful to turn this off if a system uses localtime for it's timestamps instead of GMT. // diff --git a/in_session.go b/in_session.go index 0ed53200c..b0b9742b7 100644 --- a/in_session.go +++ b/in_session.go @@ -239,7 +239,8 @@ func (state inSession) resendMessages(session *session, beginSeqNo, endSeqNo int nextSeqNum := seqNum msg := NewMessage() err := session.store.IterateMessages(beginSeqNo, endSeqNo, func(msgBytes []byte) error { - err := ParseMessageWithDataDictionary(msg, bytes.NewBuffer(msgBytes), session.transportDataDictionary, session.appDataDictionary) + skipChecksum := !session.SessionSettings.ValidateChecksum + err := ParseMessageWithDataDictionary(msg, bytes.NewBuffer(msgBytes), session.transportDataDictionary, session.appDataDictionary, skipChecksum) if err != nil { session.log.OnEventf("Resend Msg Parse Error: %v, %v", err.Error(), bytes.NewBuffer(msgBytes).String()) return err // We cant continue with a message that cant be parsed correctly. diff --git a/internal/session_settings.go b/internal/session_settings.go index c45134822..d1ee6f013 100644 --- a/internal/session_settings.go +++ b/internal/session_settings.go @@ -22,6 +22,7 @@ type SessionSettings struct { ResetSeqTime time.Time EnableResetSeqTime bool InChanCapacity int + ValidateChecksum bool // Required on logon for FIX.T.1 messages. DefaultApplVerID string diff --git a/message.go b/message.go index 35e2ff675..e33a9cfa8 100644 --- a/message.go +++ b/message.go @@ -38,6 +38,7 @@ type msgParser struct { trailerBytes []byte foundBody bool foundTrailer bool + skipChecksum bool } // in the message header, the first 3 tags in the message header must be 8,9,35. @@ -157,7 +158,7 @@ func (m *Message) CopyInto(to *Message) { // ParseMessage constructs a Message from a byte slice wrapping a FIX message. func ParseMessage(msg *Message, rawMessage *bytes.Buffer) (err error) { - return ParseMessageWithDataDictionary(msg, rawMessage, nil, nil) + return ParseMessageWithDataDictionary(msg, rawMessage, nil, nil, false) } // ParseMessageWithDataDictionary constructs a Message from a byte slice wrapping a FIX message using an optional session and application DataDictionary for reference. @@ -166,12 +167,14 @@ func ParseMessageWithDataDictionary( rawMessage *bytes.Buffer, transportDataDictionary *datadictionary.DataDictionary, appDataDictionary *datadictionary.DataDictionary, + skipChecksum bool, ) (err error) { // Create msgparser before we go any further. mp := &msgParser{ msg: msg, transportDataDictionary: transportDataDictionary, appDataDictionary: appDataDictionary, + skipChecksum: skipChecksum, } mp.msg.rawMessage = rawMessage mp.rawBytes = rawMessage.Bytes() @@ -297,8 +300,35 @@ func doParsing(mp *msgParser) (err error) { bodyLength, err := mp.msg.Header.getIntNoLock(tagBodyLength) if err != nil { err = parseError{OrigError: err.Error()} + return } else if length != bodyLength && !xmlDataMsg { err = parseError{OrigError: fmt.Sprintf("Incorrect Message Length, expected %d, got %d", bodyLength, length)} + return + } + + calculatedCheckSum := 0 + for i := 0; i <= mp.fieldIndex; i++ { + if mp.msg.fields[i].tag == tagCheckSum { + continue + } + calculatedCheckSum += mp.msg.fields[i].total() + } + + calculatedCheckSum = calculatedCheckSum % 256 + + if !mp.skipChecksum { + var expectedCheckSumStr string + expectedCheckSumStr, err = mp.msg.Trailer.getStringNoLock(tagCheckSum) + if err != nil { + err = parseError{OrigError: "CheckSum tag missing"} + return + } + + if expectedCheckSumStr != formatCheckSum(calculatedCheckSum) { + err = parseError{ + OrigError: fmt.Sprintf("Expected CheckSum=%s, Received CheckSum=%s", formatCheckSum(calculatedCheckSum), expectedCheckSumStr), + } + } } return diff --git a/message_router_test.go b/message_router_test.go index b2e7b59f5..1b1f8f7e4 100644 --- a/message_router_test.go +++ b/message_router_test.go @@ -115,7 +115,7 @@ func (suite *MessageRouterTestSuite) SetupTest() { } func (suite *MessageRouterTestSuite) TestNoRoute() { - suite.givenTheMessage([]byte("8=FIX.4.39=8735=D49=TW34=356=ISLD52=20160421-14:43:5040=160=20160421-14:43:5054=121=311=id10=235")) + suite.givenTheMessage([]byte("8=FIX.4.39=8735=D49=TW34=356=ISLD52=20160421-14:43:5040=160=20160421-14:43:5054=121=311=id10=236")) rej := suite.Route(suite.msg, suite.sessionID) suite.verifyMessageNotRouted() @@ -123,17 +123,29 @@ func (suite *MessageRouterTestSuite) TestNoRoute() { } func (suite *MessageRouterTestSuite) TestNoRouteWhitelistedMessageTypes() { - var tests = []string{"0", "A", "1", "2", "3", "4", "5", "j"} + var tests = []struct { + msgType string + checksum string + }{ + {"0", "216"}, + {"A", "233"}, + {"1", "217"}, + {"2", "218"}, + {"3", "219"}, + {"4", "220"}, + {"5", "221"}, + {"j", "018"}, + } for _, test := range tests { suite.SetupTest() - msg := fmt.Sprintf("8=FIX.4.39=8735=%v49=TW34=356=ISLD52=20160421-14:43:5040=160=20160421-14:43:5054=121=311=id10=235", test) + msg := fmt.Sprintf("8=FIX.4.39=8735=%s49=TW34=356=ISLD52=20160421-14:43:5040=160=20160421-14:43:5054=121=311=id10=%s", test.msgType, test.checksum) suite.givenTheMessage([]byte(msg)) rej := suite.Route(suite.msg, suite.sessionID) suite.verifyMessageNotRouted() - suite.Nil(rej, "Message type '%v' should not be rejected by the MessageRouter", test) + suite.Nil(rej, "Message type '%s' should not be rejected by the MessageRouter", test.msgType) } } @@ -174,7 +186,7 @@ func (suite *MessageRouterTestSuite) TestRouteFIXT50AppWithApplVerID() { suite.givenTheRoute(ApplVerIDFIX50, "D") suite.givenTheRoute(ApplVerIDFIX50SP1, "D") - suite.givenTheMessage([]byte("8=FIXT.1.19=8935=D49=TW34=356=ISLD52=20160424-16:48:261128=740=160=20160424-16:48:2611=id21=310=120")) + suite.givenTheMessage([]byte("8=FIXT.1.19=8935=D49=TW34=356=ISLD52=20160424-16:48:261128=740=160=20160424-16:48:2611=id21=310=192")) rej := suite.Route(suite.msg, suite.sessionID) suite.verifyMessageRoutedBy(ApplVerIDFIX50, "D") suite.Nil(rej) @@ -185,7 +197,7 @@ func (suite *MessageRouterTestSuite) TestRouteFIXTAppWithApplVerID() { suite.givenTheRoute(ApplVerIDFIX50, "D") suite.givenTheRoute(ApplVerIDFIX50SP1, "D") - suite.givenTheMessage([]byte("8=FIXT.1.19=8935=D49=TW34=356=ISLD52=20160424-16:48:261128=840=160=20160424-16:48:2611=id21=310=120")) + suite.givenTheMessage([]byte("8=FIXT.1.19=8935=D49=TW34=356=ISLD52=20160424-16:48:261128=840=160=20160424-16:48:2611=id21=310=193")) rej := suite.Route(suite.msg, suite.sessionID) suite.verifyMessageRoutedBy(ApplVerIDFIX50SP1, "D") suite.Nil(rej) diff --git a/message_test.go b/message_test.go index b02508cd9..ff05e99e0 100644 --- a/message_test.go +++ b/message_test.go @@ -65,7 +65,7 @@ func (s *MessageSuite) TestParseMessageEmpty() { } func (s *MessageSuite) TestParseMessage() { - rawMsg := bytes.NewBufferString("8=FIX.4.29=10435=D34=249=TW52=20140515-19:49:56.65956=ISLD11=10021=140=154=155=TSLA60=00010101-00:00:00.00010=039") + rawMsg := bytes.NewBufferString("8=FIX.4.29=10435=D34=249=TW52=20140515-19:49:56.65956=ISLD11=10021=140=154=155=TSLA60=00010101-00:00:00.00010=051") err := ParseMessage(s.msg, rawMsg) s.Nil(err) @@ -99,14 +99,30 @@ func (s *MessageSuite) TestParseMessageWithDataDictionary() { 5050: nil, }, } - rawMsg := bytes.NewBufferString("8=FIX.4.29=12635=D34=249=TW52=20140515-19:49:56.65956=ISLD10030=CUST11=10021=140=154=155=TSLA60=00010101-00:00:00.0005050=HELLO10=039") + rawMsg := bytes.NewBufferString("8=FIX.4.29=12635=D34=249=TW52=20140515-19:49:56.65956=ISLD10030=CUST11=10021=140=154=155=TSLA60=00010101-00:00:00.0005050=HELLO10=036") - err := ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict) + err := ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false) s.Nil(err) s.FieldEquals(Tag(10030), "CUST", s.msg.Header) s.FieldEquals(Tag(5050), "HELLO", s.msg.Trailer) } +func (s *MessageSuite) TestParseMessageSkipChecksum() { + rawMsg := bytes.NewBufferString("8=FIX.4.29=12635=D34=249=TW52=20140515-19:49:56.65956=ISLD10030=CUST11=10021=140=154=155=TSLA60=00010101-00:00:00.0005050=HELLO10=035") + + // Test Strict Mode (skipChecksum = false) -> Should Fail + err := ParseMessageWithDataDictionary(s.msg, rawMsg, nil, nil, false) + s.NotNil(err, "Expected error when validating invalid checksum") + s.Contains(err.Error(), "Expected CheckSum") + + // Test Lenient Mode (skipChecksum = true) -> Should Pass + s.msg = NewMessage() + rawMsg = bytes.NewBufferString("8=FIX.4.29=12635=D34=249=TW52=20140515-19:49:56.65956=ISLD10030=CUST11=10021=140=154=155=TSLA60=00010101-00:00:00.0005050=HELLO10=035") + + err = ParseMessageWithDataDictionary(s.msg, rawMsg, nil, nil, true) + s.Nil(err, "Expected no error when skipping checksum validation") +} + func (s *MessageSuite) TestParseOutOfOrder() { // Allow fields out of order, save for validation. rawMsg := bytes.NewBufferString("8=FIX.4.09=8135=D11=id21=338=10040=154=155=MSFT34=249=TW52=20140521-22:07:0956=ISLD10=250") @@ -127,7 +143,7 @@ func (s *MessageSuite) TestBuild() { } func (s *MessageSuite) TestReBuild() { - rawMsg := bytes.NewBufferString("8=FIX.4.29=10435=D34=249=TW52=20140515-19:49:56.65956=ISLD11=10021=140=154=155=TSLA60=00010101-00:00:00.00010=039") + rawMsg := bytes.NewBufferString("8=FIX.4.29=10435=D34=249=TW52=20140515-19:49:56.65956=ISLD11=10021=140=154=155=TSLA60=00010101-00:00:00.00010=051") s.Nil(ParseMessage(s.msg, rawMsg)) @@ -157,7 +173,7 @@ func (s *MessageSuite) TestRebuildOneRepeatingGroupWithDictionary() { "10=026") // When we parse it into a message - s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false)) // And then rebuild the message bytes rebuildBytes := s.msg.build() @@ -178,7 +194,7 @@ func (s *MessageSuite) TestRebuildTwoRepeatingGroupsWithDictionary() { rawMsg := bytes.NewBufferString("8=FIX.4.49=21035=D34=2347=UTF-852=20231231-20:19:4149=0100150=01001a56=TEST44=1211=139761=1010040021=1386=1336=NOPL55=SYMABC54=160=20231231-20:19:4138=140=259=0453=1448=4501447=D452=28354=6355=Public10=104") // When we parse it into a message - s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false)) // And then rebuild the message bytes rebuildBytes := s.msg.build() @@ -199,7 +215,7 @@ func (s *MessageSuite) TestRebuildOneRepeatingGroupWithTwoMembersWithDictionary( "10=044") // When we parse it into a message - s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false)) // And then rebuild the message bytes rebuildBytes := s.msg.build() @@ -223,7 +239,7 @@ func (s *MessageSuite) TestRebuildTwoSequentialRepeatingGroupWithDictionary() { "10=243") // When we parse it into a message - s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false)) // And then rebuild the message bytes rebuildBytes := s.msg.build() @@ -248,7 +264,7 @@ func (s *MessageSuite) TestRebuildNestedRepeatingGroupWithDictionary() { "10=206") // When we parse it into a message - s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false)) // And then rebuild the message bytes rebuildBytes := s.msg.build() @@ -273,7 +289,7 @@ func (s *MessageSuite) TestRebuildDoubleNestedRepeatingGroupWithDictionary() { "10=117") // When we parse it into a message - s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false)) // And then rebuild the message bytes rebuildBytes := s.msg.build() @@ -298,7 +314,7 @@ func (s *MessageSuite) TestRebuildDoubleNestedThenAnotherRepeatingGroupWithDicti "10=106") // When we parse it into a message - s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false)) // And then rebuild the message bytes rebuildBytes := s.msg.build() @@ -323,7 +339,7 @@ func (s *MessageSuite) TestRebuildDoubleNestedThenBodyTagThenAnotherRepeatingGro "10=198") // When we parse it into a message - s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false)) // And then rebuild the message bytes rebuildBytes := s.msg.build() @@ -368,7 +384,7 @@ func (s *MessageSuite) TestRebuildDoubleNestedWithTwoMembersRepeatingGroupWithDi "10=046") // When we parse it into a message - s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict, false)) // And then rebuild the message bytes rebuildBytes := s.msg.build() @@ -419,7 +435,7 @@ func (s *MessageSuite) TestReBuildWithRepeatingGroupForResend() { } 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"))) + 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=033"))) builder := s.msg.reverseRoute() @@ -450,7 +466,7 @@ func (s *MessageSuite) TestReverseRoute() { } func (s *MessageSuite) TestReverseRouteIgnoreEmpty() { - s.Nil(ParseMessage(s.msg, bytes.NewBufferString("8=FIX.4.09=12835=D34=249=TW52=20060102-15:04:0556=ISLD115=116=CS128=MG129=CB11=ID21=338=10040=w54=155=INTC60=20060102-15:04:0510=123"))) + s.Nil(ParseMessage(s.msg, bytes.NewBufferString("8=FIX.4.09=12835=D34=249=TW52=20060102-15:04:0556=ISLD115=116=CS128=MG129=CB11=ID21=338=10040=w54=155=INTC60=20060102-15:04:0510=041"))) builder := s.msg.reverseRoute() s.False(builder.Header.Has(tagDeliverToCompID), "Should not reverse if empty") @@ -458,7 +474,7 @@ func (s *MessageSuite) TestReverseRouteIgnoreEmpty() { func (s *MessageSuite) TestReverseRouteFIX40() { // The onbehalfof/deliverto location id not supported in fix 4.0. - s.Nil(ParseMessage(s.msg, bytes.NewBufferString("8=FIX.4.09=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"))) + s.Nil(ParseMessage(s.msg, bytes.NewBufferString("8=FIX.4.09=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=031"))) builder := s.msg.reverseRoute() @@ -468,7 +484,7 @@ func (s *MessageSuite) TestReverseRouteFIX40() { } func (s *MessageSuite) TestCopyIntoMessage() { - msgString := "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" + msgString := "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=033" msgBuf := bytes.NewBufferString(msgString) s.Nil(ParseMessage(s.msg, msgBuf)) diff --git a/repeating_group_test.go b/repeating_group_test.go index abd316d78..e7d5db481 100644 --- a/repeating_group_test.go +++ b/repeating_group_test.go @@ -230,7 +230,7 @@ func TestRepeatingGroup_ReadRecursive(t *testing.T) { func TestRepeatingGroup_ReadComplete(t *testing.T) { - rawMsg := bytes.NewBufferString("8=FIXT.1.19=26835=W34=711849=TEST52=20151027-18:41:52.69856=TST22=9948=TSTX15262=7268=4269=4270=0.07499272=20151027273=18:41:52.698269=7270=0.07501272=20151027273=18:41:52.698269=8270=0.07494272=20151027273=18:41:52.698269=B271=60272=20151027273=18:41:52.69810=163") + rawMsg := bytes.NewBufferString("8=FIXT.1.19=26835=W34=711849=TEST52=20151027-18:41:52.69856=TST22=9948=TSTX15262=7268=4269=4270=0.07499272=20151027273=18:41:52.698269=7270=0.07501272=20151027273=18:41:52.698269=8270=0.07494272=20151027273=18:41:52.698269=B271=60272=20151027273=18:41:52.69810=094") msg := NewMessage() err := ParseMessage(msg, rawMsg) diff --git a/session_factory.go b/session_factory.go index 1b79f5ee1..2ace30a9e 100644 --- a/session_factory.go +++ b/session_factory.go @@ -216,6 +216,14 @@ func (f sessionFactory) newSession( } } + if settings.HasSetting(config.ValidateChecksum) { + if s.ValidateChecksum, err = settings.BoolSetting(config.ValidateChecksum); err != nil { + return + } + } else { + s.ValidateChecksum = true + } + if settings.HasSetting(config.CheckLatency) { var doCheckLatency bool if doCheckLatency, err = settings.BoolSetting(config.CheckLatency); err != nil { diff --git a/session_factory_test.go b/session_factory_test.go index 349e7580c..1731f5e71 100644 --- a/session_factory_test.go +++ b/session_factory_test.go @@ -426,6 +426,32 @@ func (s *SessionFactorySuite) TestStartEndDayWithWeekdaysError() { s.NotNil(err) } +func (s *SessionFactorySuite) TestValidateChecksum() { + var tests = []struct { + setting string + expected bool + }{ + {"Y", true}, + {"N", false}, + } + + for _, test := range tests { + s.SetupTest() + s.SessionSettings.Set(config.ValidateChecksum, test.setting) + + session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.Nil(err) + s.NotNil(session) + + s.Equal(test.expected, session.ValidateChecksum, "Failed to set ValidateChecksum from config") + } + + s.SetupTest() + session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.Nil(err) + s.True(session.ValidateChecksum, "Default ValidateChecksum should be true") +} + func (s *SessionFactorySuite) TestDefaultApplVerID() { s.SessionID = SessionID{BeginString: BeginStringFIXT11, TargetCompID: "TW", SenderCompID: "ISLD"} diff --git a/session_state.go b/session_state.go index 6fe4dded7..e32d61ee5 100644 --- a/session_state.go +++ b/session_state.go @@ -92,7 +92,8 @@ func (sm *stateMachine) Incoming(session *session, m fixIn) { session.log.OnIncoming(m.bytes.Bytes()) msg := NewMessage() - if err := ParseMessageWithDataDictionary(msg, m.bytes, session.transportDataDictionary, session.appDataDictionary); err != nil { + skipChecksum := !session.SessionSettings.ValidateChecksum + if err := ParseMessageWithDataDictionary(msg, m.bytes, session.transportDataDictionary, session.appDataDictionary, skipChecksum); err != nil { session.log.OnEventf("Msg Parse Error: %v, %q", err.Error(), m.bytes) } else { msg.ReceiveTime = m.receiveTime diff --git a/session_test.go b/session_test.go index 2ea5c3508..aa3ca76e9 100644 --- a/session_test.go +++ b/session_test.go @@ -544,6 +544,29 @@ func (s *SessionSuite) TestIncomingNotInSessionTime() { } } +func (s *SessionSuite) TestIncomingValidateChecksum() { + s.session.State = inSession{} + s.Require().Nil(s.session.store.Reset()) + + msg := s.NewOrderSingle() + msg.Header.SetField(tagMsgSeqNum, FIXInt(1)) + msgBytes := msg.build() + + // Corrupt the checksum in the bytes directly + // The message ends with "10=XXX\x01". We flip a bit in the last digit. + checksumDigitIndex := len(msgBytes) - 2 + msgBytes[checksumDigitIndex] ^= 1 + + s.session.ValidateChecksum = true + s.session.Incoming(s.session, fixIn{bytes: bytes.NewBuffer(msgBytes)}) + s.MockApp.AssertNotCalled(s.T(), "FromApp") + + s.session.ValidateChecksum = false + s.MockApp.On("FromApp").Return(nil) + s.session.Incoming(s.session, fixIn{bytes: bytes.NewBuffer(msgBytes)}) + s.MockApp.AssertExpectations(s.T()) +} + func (s *SessionSuite) TestSendAppMessagesNotInSessionTime() { var tests = []struct { before sessionState diff --git a/validation_test.go b/validation_test.go index 7973464d1..e8ec8e4e3 100644 --- a/validation_test.go +++ b/validation_test.go @@ -20,8 +20,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/quickfixgo/quickfix/datadictionary" ) @@ -32,6 +30,7 @@ type validateTest struct { ExpectedRejectReason int ExpectedRefTagID *Tag DoNotExpectReject bool + ExpectParseError bool } func TestValidate(t *testing.T) { @@ -86,42 +85,54 @@ func TestValidate(t *testing.T) { tcCheckUserDefinedFieldsDisabled(), tcCheckUserDefinedFieldsDisabledFixT(), tcMultipleRepeatingGroupFields(), + tcCheckSumValidation(), } msg := NewMessage() for _, test := range tests { - assert.Nil(t, ParseMessage(msg, bytes.NewBuffer(test.MessageBytes))) - reject := test.Validator.Validate(msg) + t.Run(test.TestName, func(t *testing.T) { + err := ParseMessage(msg, bytes.NewBuffer(test.MessageBytes)) + + if test.ExpectParseError { + if err == nil { + t.Errorf("%v: Expected ParseMessage to fail, but it succeeded", test.TestName) + } + return + } - switch { - case reject == nil && test.DoNotExpectReject: - continue + if err != nil { + t.Fatalf("ParseMessage failed: %v", err) + } + reject := test.Validator.Validate(msg) - case reject != nil && test.DoNotExpectReject: - t.Errorf("%v: Unexpected reject: %v", test.TestName, reject) - continue + switch { + case reject == nil && test.DoNotExpectReject: + return - case reject == nil: - t.Errorf("%v: Expected reject", test.TestName) - continue - } + case reject != nil && test.DoNotExpectReject: + t.Errorf("%v: Unexpected reject: %v", test.TestName, reject) + return - if reject.RejectReason() != test.ExpectedRejectReason { - t.Errorf("%v: Expected reason %v got %v", test.TestName, test.ExpectedRejectReason, reject.RejectReason()) - } + case reject == nil: + t.Errorf("%v: Expected reject", test.TestName) + return + } - switch { - case reject.RefTagID() == nil && test.ExpectedRefTagID == nil: - // OK, expected and actual ref tag not set. - case reject.RefTagID() != nil && test.ExpectedRefTagID == nil: - t.Errorf("%v: Unexpected RefTag '%v'", test.TestName, *reject.RefTagID()) - case reject.RefTagID() == nil && test.ExpectedRefTagID != nil: - t.Errorf("%v: Expected RefTag '%v'", test.TestName, *test.ExpectedRefTagID) - case *reject.RefTagID() == *test.ExpectedRefTagID: - // OK, tags equal. - default: - t.Errorf("%v: Expected RefTag '%v' got '%v'", test.TestName, *test.ExpectedRefTagID, *reject.RefTagID()) - } + if reject.RejectReason() != test.ExpectedRejectReason { + t.Errorf("%v: Expected reason %v got %v", test.TestName, test.ExpectedRejectReason, reject.RejectReason()) + } + + switch { + case reject.RefTagID() == nil && test.ExpectedRefTagID == nil: + case reject.RefTagID() != nil && test.ExpectedRefTagID == nil: + t.Errorf("%v: Unexpected RefTag '%v'", test.TestName, *reject.RefTagID()) + case reject.RefTagID() == nil && test.ExpectedRefTagID != nil: + t.Errorf("%v: Expected RefTag '%v'", test.TestName, *test.ExpectedRefTagID) + case *reject.RefTagID() == *test.ExpectedRefTagID: + default: + t.Errorf("%v: Expected RefTag '%v' got '%v'", test.TestName, *test.ExpectedRefTagID, *reject.RefTagID()) + } + }) } } @@ -201,6 +212,7 @@ func tcInvalidTagNumberHeader() validateTest { invalidHeaderFieldMessage := createFIX40NewOrderSingle() tag := Tag(9999) invalidHeaderFieldMessage.Header.SetField(tag, FIXString("hello")) + invalidHeaderFieldMessage.cook() msgBytes := invalidHeaderFieldMessage.build() return validateTest{ @@ -1109,7 +1121,7 @@ func tcTagAppearsMoreThanOnce() validateTest { return validateTest{ TestName: "Tag appears more than once", Validator: validator, - MessageBytes: []byte("8=FIX.4.09=10735=D34=249=TW52=20060102-15:04:0556=ISLD11=ID21=140=140=254=138=20055=INTC60=20060102-15:04:0510=234"), + MessageBytes: []byte("8=FIX.4.09=10735=D34=249=TW52=20060102-15:04:0556=ISLD11=ID21=140=140=254=138=20055=INTC60=20060102-15:04:0510=166"), ExpectedRejectReason: rejectReasonTagAppearsMoreThanOnce, ExpectedRefTagID: &tag, } @@ -1124,7 +1136,7 @@ func tcTagAppearsMoreThanOnceFixT() validateTest { return validateTest{ TestName: "Tag appears more than once FIXT", Validator: validator, - MessageBytes: []byte("8=FIXT.1.19=10735=D34=249=TW52=20060102-15:04:0556=ISLD11=ID21=140=140=254=138=20055=INTC60=20060102-15:04:0510=234"), + MessageBytes: []byte("8=FIXT.1.19=10735=D34=249=TW52=20060102-15:04:0556=ISLD11=ID21=140=140=254=138=20055=INTC60=20060102-15:04:0510=248"), ExpectedRejectReason: rejectReasonTagAppearsMoreThanOnce, ExpectedRefTagID: &tag, } @@ -1151,7 +1163,7 @@ func tcFloatValidationFixT() validateTest { return validateTest{ TestName: "FloatValidation FIXT", Validator: validator, - MessageBytes: []byte("8=FIXT.1.19=10635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID21=140=154=138=+200.0055=INTC60=20140329-22:38:4510=178"), + MessageBytes: []byte("8=FIXT.1.19=10635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID21=140=154=138=+200.0055=INTC60=20140329-22:38:4510=002"), ExpectedRejectReason: rejectReasonIncorrectDataFormatForValue, ExpectedRefTagID: &tag, } @@ -1163,11 +1175,22 @@ func tcMultipleRepeatingGroupFields() validateTest { return validateTest{ TestName: "Multiple repeating group fields in a message", Validator: validator, - MessageBytes: []byte("8=FIX.4.39=17635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID453=2448=PARTYID452=3523=SUBID448=PARTYID2452=378=179=ACCOUNT80=121=140=154=138=20055=INTC60=20140329-22:38:4510=178"), + MessageBytes: []byte("8=FIX.4.39=17635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID453=2448=PARTYID452=3523=SUBID448=PARTYID2452=378=179=ACCOUNT80=121=140=154=138=20055=INTC60=20140329-22:38:4510=012"), DoNotExpectReject: true, } } +func tcCheckSumValidation() validateTest { + dict, _ := datadictionary.Parse("spec/FIX43.xml") + validator := NewValidator(defaultValidatorSettings, dict, nil) + return validateTest{ + TestName: "Checksum is incorrect", + Validator: validator, + MessageBytes: []byte("8=FIX.4.39=17635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID453=2448=PARTYID452=3523=SUBID448=PARTYID2452=378=179=ACCOUNT80=121=140=154=138=20055=INTC60=20140329-22:38:4510=006"), + ExpectParseError: true, + } +} + func TestValidateVisitField(t *testing.T) { fieldType0 := datadictionary.NewFieldType("myfield", 11, "STRING") fieldDef0 := &datadictionary.FieldDef{FieldType: fieldType0}