From 7fadeb5cafaa95a08f9ccc0a2c88a2eb36b012f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 23:57:50 +0000 Subject: [PATCH 01/58] Bump golang.org/x/crypto from 0.15.0 to 0.17.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2f5cf33c4..3f81d2bc1 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect - golang.org/x/crypto v0.15.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index f3c70cab6..2f4e1aba7 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= From 359401580fa443d182a333e763ca2f8741db5a40 Mon Sep 17 00:00:00 2001 From: Alexandros Kyriakakis Date: Wed, 31 Jan 2024 18:35:00 +0200 Subject: [PATCH 02/58] TRD-1749: Escape send queued when blocked on connection side (#18) --- session.go | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/session.go b/session.go index b359245e2..db5198e04 100644 --- a/session.go +++ b/session.go @@ -235,12 +235,16 @@ func (s *session) queueForSend(msg *Message) error { s.toSend = append(s.toSend, msgBytes) + s.notifyMessageOut() + + return nil +} + +func (s *session) notifyMessageOut() { select { case s.messageEvent <- true: default: } - - return nil } // send will validate, persist, queue the message. If the session is logged on, send all messages in the queue. @@ -347,8 +351,12 @@ func (s *session) persist(seqNum int, msgBytes []byte) error { } func (s *session) sendQueued() { - for _, msgBytes := range s.toSend { - s.sendBytes(msgBytes) + for i, msgBytes := range s.toSend { + if !s.sendBytes(msgBytes) { + s.toSend = s.toSend[i:] + s.notifyMessageOut() + return + } } s.dropQueued() @@ -366,15 +374,20 @@ func (s *session) EnqueueBytesAndSend(msg []byte) { s.sendQueued() } -func (s *session) sendBytes(msg []byte) { +func (s *session) sendBytes(msg []byte) bool { if s.messageOut == nil { s.log.OnEventf("Failed to send: disconnected") - return + return false } - s.log.OnOutgoing(msg) - s.messageOut <- msg - s.stateTimer.Reset(s.HeartBtInt) + select { + case s.messageOut <- msg: + s.log.OnOutgoing(msg) + s.stateTimer.Reset(s.HeartBtInt) + return true + default: + return false + } } func (s *session) doTargetTooHigh(reject targetTooHigh) (nextState resendState, err error) { From d7f296817876225b1063f0e4e3ac5ed6800cc7fa Mon Sep 17 00:00:00 2001 From: AlexandrosKyriakakis Date: Tue, 30 Jan 2024 19:08:45 +0200 Subject: [PATCH 03/58] Escape send queued when blocked on connection side use default instead of a timer refactor send queued In case of Log On messages block until sent --- logon_state_test.go | 4 ++-- session.go | 46 ++++++++++++++++++++++++++++++++------------- session_state.go | 2 +- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/logon_state_test.go b/logon_state_test.go index 96afc2e90..ee47a2be6 100644 --- a/logon_state_test.go +++ b/logon_state_test.go @@ -333,7 +333,7 @@ func (s *LogonStateTestSuite) TestFixMsgInLogonSeqNumTooHigh() { s.Require().Nil(err) s.MessageType(string(msgTypeLogon), sentMessage) - s.session.sendQueued() + s.session.sendQueued(true) s.MessageType(string(msgTypeResendRequest), s.MockApp.lastToAdmin) s.FieldEquals(tagBeginSeqNo, 1, s.MockApp.lastToAdmin.Body) @@ -373,7 +373,7 @@ func (s *LogonStateTestSuite) TestFixMsgInLogonSeqNumTooLow() { s.Require().Nil(err) s.MessageType(string(msgTypeLogout), sentMessage) - s.session.sendQueued() + s.session.sendQueued(true) s.MessageType(string(msgTypeLogout), s.MockApp.lastToAdmin) s.FieldEquals(tagText, "MsgSeqNum too low, expecting 2 but received 1", s.MockApp.lastToAdmin.Body) } diff --git a/session.go b/session.go index b359245e2..1bf60b122 100644 --- a/session.go +++ b/session.go @@ -235,12 +235,16 @@ func (s *session) queueForSend(msg *Message) error { s.toSend = append(s.toSend, msgBytes) + s.notifyMessageOut() + + return nil +} + +func (s *session) notifyMessageOut() { select { case s.messageEvent <- true: default: } - - return nil } // send will validate, persist, queue the message. If the session is logged on, send all messages in the queue. @@ -261,7 +265,7 @@ func (s *session) sendInReplyTo(msg *Message, inReplyTo *Message) error { } s.toSend = append(s.toSend, msgBytes) - s.sendQueued() + s.sendQueued(true) return nil } @@ -290,7 +294,7 @@ func (s *session) dropAndSendInReplyTo(msg *Message, inReplyTo *Message) error { s.dropQueued() s.toSend = append(s.toSend, msgBytes) - s.sendQueued() + s.sendQueued(true) return nil } @@ -346,9 +350,13 @@ func (s *session) persist(seqNum int, msgBytes []byte) error { return s.store.IncrNextSenderMsgSeqNum() } -func (s *session) sendQueued() { - for _, msgBytes := range s.toSend { - s.sendBytes(msgBytes) +func (s *session) sendQueued(blockUntilSent bool) { + for i, msgBytes := range s.toSend { + if !s.sendBytes(msgBytes, blockUntilSent) { + s.toSend = s.toSend[i:] + s.notifyMessageOut() + return + } } s.dropQueued() @@ -363,18 +371,30 @@ func (s *session) EnqueueBytesAndSend(msg []byte) { defer s.sendMutex.Unlock() s.toSend = append(s.toSend, msg) - s.sendQueued() + s.sendQueued(true) } -func (s *session) sendBytes(msg []byte) { +func (s *session) sendBytes(msg []byte, blockUntilSent bool) bool { if s.messageOut == nil { s.log.OnEventf("Failed to send: disconnected") - return + return false + } + + if blockUntilSent { + s.messageOut <- msg + s.log.OnOutgoing(msg) + s.stateTimer.Reset(s.HeartBtInt) + return true } - s.log.OnOutgoing(msg) - s.messageOut <- msg - s.stateTimer.Reset(s.HeartBtInt) + select { + case s.messageOut <- msg: + s.log.OnOutgoing(msg) + s.stateTimer.Reset(s.HeartBtInt) + return true + default: + return false + } } func (s *session) doTargetTooHigh(reject targetTooHigh) (nextState resendState, err error) { diff --git a/session_state.go b/session_state.go index 230ac8613..527556209 100644 --- a/session_state.go +++ b/session_state.go @@ -105,7 +105,7 @@ func (sm *stateMachine) SendAppMessages(session *session) { defer session.sendMutex.Unlock() if session.IsLoggedOn() { - session.sendQueued() + session.sendQueued(false) } else { session.dropQueued() } From 6eaa8ce004539ced3f1919ad7c22620d566814db Mon Sep 17 00:00:00 2001 From: Alexandros Kyriakakis Date: Thu, 1 Feb 2024 14:58:37 +0200 Subject: [PATCH 04/58] Bug: For Logon/Logout/Resend/SequenceReset messages we should wait until sent (#21) ## Issue When log on request was queued because cannot be send due to connection not ready, Then it goes through the [main session loop](https://github.com/alpacahq/quickfix/blob/5dcde41f3e6e68e93e1fd381761cd9c7843aec43/session.go#L821-L841) which leads to [SendAppMessages](https://github.com/alpacahq/quickfix/blob/5dcde41f3e6e68e93e1fd381761cd9c7843aec43/session_state.go#L107-L111) But, since session is not logged on yet because the message queued is the log on message, we drop the `toSend` queued messages so we lose the log on message itself. ## Proposed solution For important messages like Logon/Logout/Resend/SequenceReset we block until sent. --- logon_state_test.go | 4 ++-- session.go | 19 +++++++++++++------ session_state.go | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/logon_state_test.go b/logon_state_test.go index 96afc2e90..ee47a2be6 100644 --- a/logon_state_test.go +++ b/logon_state_test.go @@ -333,7 +333,7 @@ func (s *LogonStateTestSuite) TestFixMsgInLogonSeqNumTooHigh() { s.Require().Nil(err) s.MessageType(string(msgTypeLogon), sentMessage) - s.session.sendQueued() + s.session.sendQueued(true) s.MessageType(string(msgTypeResendRequest), s.MockApp.lastToAdmin) s.FieldEquals(tagBeginSeqNo, 1, s.MockApp.lastToAdmin.Body) @@ -373,7 +373,7 @@ func (s *LogonStateTestSuite) TestFixMsgInLogonSeqNumTooLow() { s.Require().Nil(err) s.MessageType(string(msgTypeLogout), sentMessage) - s.session.sendQueued() + s.session.sendQueued(true) s.MessageType(string(msgTypeLogout), s.MockApp.lastToAdmin) s.FieldEquals(tagText, "MsgSeqNum too low, expecting 2 but received 1", s.MockApp.lastToAdmin.Body) } diff --git a/session.go b/session.go index db5198e04..1bf60b122 100644 --- a/session.go +++ b/session.go @@ -265,7 +265,7 @@ func (s *session) sendInReplyTo(msg *Message, inReplyTo *Message) error { } s.toSend = append(s.toSend, msgBytes) - s.sendQueued() + s.sendQueued(true) return nil } @@ -294,7 +294,7 @@ func (s *session) dropAndSendInReplyTo(msg *Message, inReplyTo *Message) error { s.dropQueued() s.toSend = append(s.toSend, msgBytes) - s.sendQueued() + s.sendQueued(true) return nil } @@ -350,9 +350,9 @@ func (s *session) persist(seqNum int, msgBytes []byte) error { return s.store.IncrNextSenderMsgSeqNum() } -func (s *session) sendQueued() { +func (s *session) sendQueued(blockUntilSent bool) { for i, msgBytes := range s.toSend { - if !s.sendBytes(msgBytes) { + if !s.sendBytes(msgBytes, blockUntilSent) { s.toSend = s.toSend[i:] s.notifyMessageOut() return @@ -371,15 +371,22 @@ func (s *session) EnqueueBytesAndSend(msg []byte) { defer s.sendMutex.Unlock() s.toSend = append(s.toSend, msg) - s.sendQueued() + s.sendQueued(true) } -func (s *session) sendBytes(msg []byte) bool { +func (s *session) sendBytes(msg []byte, blockUntilSent bool) bool { if s.messageOut == nil { s.log.OnEventf("Failed to send: disconnected") return false } + if blockUntilSent { + s.messageOut <- msg + s.log.OnOutgoing(msg) + s.stateTimer.Reset(s.HeartBtInt) + return true + } + select { case s.messageOut <- msg: s.log.OnOutgoing(msg) diff --git a/session_state.go b/session_state.go index 230ac8613..527556209 100644 --- a/session_state.go +++ b/session_state.go @@ -105,7 +105,7 @@ func (sm *stateMachine) SendAppMessages(session *session) { defer session.sendMutex.Unlock() if session.IsLoggedOn() { - session.sendQueued() + session.sendQueued(false) } else { session.dropQueued() } From de36465571d9345dd905fdea5d52d7b204dfe4ca Mon Sep 17 00:00:00 2001 From: albert Date: Wed, 10 Apr 2024 13:49:19 +1000 Subject: [PATCH 05/58] add test for multiple repeating groups in a message partly revert change in #368 due to breaking the unit test added in this PR --- validation.go | 1 - validation_test.go | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/validation.go b/validation.go index 72bb06628..58fbea80e 100644 --- a/validation.go +++ b/validation.go @@ -246,7 +246,6 @@ func validateVisitGroupField(fieldDef *datadictionary.FieldDef, fieldStack []Tag if childDefs[0].Required() { return fieldStack, RequiredTagMissing(Tag(childDefs[0].Tag())) } - fieldStack = fieldStack[1:] } childDefs = childDefs[1:] diff --git a/validation_test.go b/validation_test.go index fc97944b7..85bbaf4a2 100644 --- a/validation_test.go +++ b/validation_test.go @@ -74,6 +74,7 @@ func TestValidate(t *testing.T) { tcInvalidTagCheckDisabledFixT(), tcInvalidTagCheckEnabled(), tcInvalidTagCheckEnabledFixT(), + tcMultipleRepeatingGroupFields(), } msg := NewMessage() @@ -923,6 +924,17 @@ func tcFloatValidationFixT() validateTest { } } +func tcMultipleRepeatingGroupFields() validateTest { + dict, _ := datadictionary.Parse("spec/FIX43.xml") + validator := NewValidator(defaultValidatorSettings, dict, nil) + 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"), + DoNotExpectReject: true, + } +} + func TestValidateVisitField(t *testing.T) { fieldType0 := datadictionary.NewFieldType("myfield", 11, "STRING") fieldDef0 := &datadictionary.FieldDef{FieldType: fieldType0} From aa39aa8ed3367c5cff029ad284f50c5a416d77cc Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Tue, 16 Apr 2024 16:18:39 -0500 Subject: [PATCH 06/58] Update linter --- .github/workflows/ci.yaml | 105 +++++++++++++++++++------------------- .golangci.yml | 7 ++- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d589126a6..c42e674b0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,61 +21,60 @@ jobs: name: Linter runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' - cache: false - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: - version: v1.51 + version: v1.57.2 - build: - name: build - runs-on: ubuntu-latest - strategy: - matrix: - go: [1.21] - store-type: - - - - memory - - file - - mongo - fix-version: - - - - fix40 - - fix41 - - fix42 - - fix43 - - fix44 - - fix50 - - fix50sp1 - - fix50sp2 - steps: - - name: Setup - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go }} - - name: Check out source - uses: actions/checkout@v2 - - name: Start MongoDB - uses: supercharge/mongodb-github-action@1.8.0 - with: - mongodb-replica-set: replicaset - - name: Install ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.0' - - name: Unit test - env: - FIX_TEST: ${{ matrix.fix-version }} - STORE_TYPE: ${{ matrix.store-type }} - run: if [ -z $FIX_TEST ] && [ -z $STORE_TYPE ]; then make build-src && make test-ci; fi - - name: Acceptance test - env: - GO111MODULE: on - MONGODB_TEST_CXN: mongodb://localhost:27017 - FIX_TEST: ${{ matrix.fix-version }} - STORE_TYPE: ${{ matrix.store-type }} - run: if [ $FIX_TEST ] && [ $STORE_TYPE ]; then make generate-ci && make build && make $FIX_TEST; fi + # build: + # name: build + # runs-on: ubuntu-latest + # strategy: + # matrix: + # go: [1.21] + # store-type: + # - + # - memory + # - file + # - mongo + # fix-version: + # - + # - fix40 + # - fix41 + # - fix42 + # - fix43 + # - fix44 + # - fix50 + # - fix50sp1 + # - fix50sp2 + # steps: + # - name: Setup + # uses: actions/setup-go@v2 + # with: + # go-version: ${{ matrix.go }} + # - name: Check out source + # uses: actions/checkout@v2 + # - name: Start MongoDB + # uses: supercharge/mongodb-github-action@1.8.0 + # with: + # mongodb-replica-set: replicaset + # - name: Install ruby + # uses: ruby/setup-ruby@v1 + # with: + # ruby-version: '3.0' + # - name: Unit test + # env: + # FIX_TEST: ${{ matrix.fix-version }} + # STORE_TYPE: ${{ matrix.store-type }} + # run: if [ -z $FIX_TEST ] && [ -z $STORE_TYPE ]; then make build-src && make test-ci; fi + # - name: Acceptance test + # env: + # GO111MODULE: on + # MONGODB_TEST_CXN: mongodb://localhost:27017 + # FIX_TEST: ${{ matrix.fix-version }} + # STORE_TYPE: ${{ matrix.store-type }} + # run: if [ $FIX_TEST ] && [ $STORE_TYPE ]; then make generate-ci && make build && make $FIX_TEST; fi diff --git a/.golangci.yml b/.golangci.yml index b1bd0cccd..4d61e0126 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,11 @@ run: timeout: 10m - skip-dirs: + # skip-dirs: + # - gen + # - vendor + +issues: + exclude-dirs: - gen - vendor From a203213139ac511bef263901ec1f1452d9cd3e95 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Wed, 17 Apr 2024 21:22:52 -0500 Subject: [PATCH 07/58] Statify updated linter rules --- .github/workflows/ci.yaml | 96 ++++++++++++++++---------------- .golangci.yml | 1 + Makefile | 2 +- datadictionary/datadictionary.go | 2 +- logon_state.go | 2 +- logout_state.go | 2 +- memorystore.go | 2 +- message.go | 28 +++++----- null_log.go | 10 ++-- quickfix_test.go | 14 ++--- session_factory_test.go | 2 +- store/sql/sqlstore.go | 6 +- validation.go | 6 +- 13 files changed, 87 insertions(+), 86 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c42e674b0..4d58289a2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,51 +30,51 @@ jobs: with: version: v1.57.2 - # build: - # name: build - # runs-on: ubuntu-latest - # strategy: - # matrix: - # go: [1.21] - # store-type: - # - - # - memory - # - file - # - mongo - # fix-version: - # - - # - fix40 - # - fix41 - # - fix42 - # - fix43 - # - fix44 - # - fix50 - # - fix50sp1 - # - fix50sp2 - # steps: - # - name: Setup - # uses: actions/setup-go@v2 - # with: - # go-version: ${{ matrix.go }} - # - name: Check out source - # uses: actions/checkout@v2 - # - name: Start MongoDB - # uses: supercharge/mongodb-github-action@1.8.0 - # with: - # mongodb-replica-set: replicaset - # - name: Install ruby - # uses: ruby/setup-ruby@v1 - # with: - # ruby-version: '3.0' - # - name: Unit test - # env: - # FIX_TEST: ${{ matrix.fix-version }} - # STORE_TYPE: ${{ matrix.store-type }} - # run: if [ -z $FIX_TEST ] && [ -z $STORE_TYPE ]; then make build-src && make test-ci; fi - # - name: Acceptance test - # env: - # GO111MODULE: on - # MONGODB_TEST_CXN: mongodb://localhost:27017 - # FIX_TEST: ${{ matrix.fix-version }} - # STORE_TYPE: ${{ matrix.store-type }} - # run: if [ $FIX_TEST ] && [ $STORE_TYPE ]; then make generate-ci && make build && make $FIX_TEST; fi + build: + name: build + runs-on: ubuntu-latest + strategy: + matrix: + go: [1.21] + store-type: + - + - memory + - file + - mongo + fix-version: + - + - fix40 + - fix41 + - fix42 + - fix43 + - fix44 + - fix50 + - fix50sp1 + - fix50sp2 + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Check out source + uses: actions/checkout@v2 + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.8.0 + with: + mongodb-replica-set: replicaset + - name: Install ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' + - name: Unit test + env: + FIX_TEST: ${{ matrix.fix-version }} + STORE_TYPE: ${{ matrix.store-type }} + run: if [ -z $FIX_TEST ] && [ -z $STORE_TYPE ]; then make build-src && make test-ci; fi + - name: Acceptance test + env: + GO111MODULE: on + MONGODB_TEST_CXN: mongodb://localhost:27017 + FIX_TEST: ${{ matrix.fix-version }} + STORE_TYPE: ${{ matrix.store-type }} + run: if [ $FIX_TEST ] && [ $STORE_TYPE ]; then make generate-ci && make build && make $FIX_TEST; fi diff --git a/.golangci.yml b/.golangci.yml index 4d61e0126..b0bd0347b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,6 @@ run: timeout: 10m + # deprecated config - # skip-dirs: # - gen # - vendor diff --git a/Makefile b/Makefile index 9d2f26817..b05c5d72f 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ test: linters-install: @golangci-lint --version >/dev/null 2>&1 || { \ echo "installing linting tools..."; \ - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.50.1; \ + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.57.2; \ } lint: linters-install diff --git a/datadictionary/datadictionary.go b/datadictionary/datadictionary.go index b032d078e..218439497 100644 --- a/datadictionary/datadictionary.go +++ b/datadictionary/datadictionary.go @@ -315,7 +315,7 @@ func Parse(path string) (*DataDictionary, error) { func ParseSrc(xmlSrc io.Reader) (*DataDictionary, error) { doc := new(XMLDoc) decoder := xml.NewDecoder(xmlSrc) - decoder.CharsetReader = func(encoding string, input io.Reader) (io.Reader, error) { + decoder.CharsetReader = func(_ string, input io.Reader) (io.Reader, error) { return input, nil } diff --git a/logon_state.go b/logon_state.go index 05c970838..7ad8b6602 100644 --- a/logon_state.go +++ b/logon_state.go @@ -68,7 +68,7 @@ func (s logonState) Timeout(session *session, e internal.Event) (nextState sessi return s } -func (s logonState) Stop(session *session) (nextState sessionState) { +func (s logonState) Stop(_ *session) (nextState sessionState) { return latentState{} } diff --git a/logout_state.go b/logout_state.go index cee8e9bd8..04defce54 100644 --- a/logout_state.go +++ b/logout_state.go @@ -40,6 +40,6 @@ func (state logoutState) Timeout(session *session, event internal.Event) (nextSt return state } -func (state logoutState) Stop(session *session) (nextstate sessionState) { +func (state logoutState) Stop(_ *session) (nextstate sessionState) { return state } diff --git a/memorystore.go b/memorystore.go index 5773f09b5..2379016e2 100644 --- a/memorystore.go +++ b/memorystore.go @@ -109,7 +109,7 @@ func (store *memoryStore) GetMessages(beginSeqNum, endSeqNum int) ([][]byte, err type memoryStoreFactory struct{} -func (f memoryStoreFactory) Create(sessionID SessionID) (MessageStore, error) { +func (f memoryStoreFactory) Create(_ SessionID) (MessageStore, error) { m := new(memoryStore) if err := m.Reset(); err != nil { return m, errors.Wrap(err, "reset") diff --git a/message.go b/message.go index 566b48eb7..73da79169 100644 --- a/message.go +++ b/message.go @@ -152,7 +152,7 @@ func ParseMessageWithDataDictionary( msg *Message, rawMessage *bytes.Buffer, transportDataDictionary *datadictionary.DataDictionary, - applicationDataDictionary *datadictionary.DataDictionary, + _ *datadictionary.DataDictionary, ) (err error) { msg.Header.Clear() msg.Body.Clear() @@ -322,7 +322,7 @@ func (m *Message) IsMsgTypeOf(msgType string) bool { func (m *Message) reverseRoute() *Message { reverseMsg := NewMessage() - copy := func(src Tag, dest Tag) { + copyFunc := func(src Tag, dest Tag) { var field FIXString if m.Header.GetField(src, &field) == nil { if len(field) != 0 { @@ -331,25 +331,25 @@ func (m *Message) reverseRoute() *Message { } } - copy(tagSenderCompID, tagTargetCompID) - copy(tagSenderSubID, tagTargetSubID) - copy(tagSenderLocationID, tagTargetLocationID) + copyFunc(tagSenderCompID, tagTargetCompID) + copyFunc(tagSenderSubID, tagTargetSubID) + copyFunc(tagSenderLocationID, tagTargetLocationID) - copy(tagTargetCompID, tagSenderCompID) - copy(tagTargetSubID, tagSenderSubID) - copy(tagTargetLocationID, tagSenderLocationID) + copyFunc(tagTargetCompID, tagSenderCompID) + copyFunc(tagTargetSubID, tagSenderSubID) + copyFunc(tagTargetLocationID, tagSenderLocationID) - copy(tagOnBehalfOfCompID, tagDeliverToCompID) - copy(tagOnBehalfOfSubID, tagDeliverToSubID) - copy(tagDeliverToCompID, tagOnBehalfOfCompID) - copy(tagDeliverToSubID, tagOnBehalfOfSubID) + copyFunc(tagOnBehalfOfCompID, tagDeliverToCompID) + copyFunc(tagOnBehalfOfSubID, tagDeliverToSubID) + copyFunc(tagDeliverToCompID, tagOnBehalfOfCompID) + copyFunc(tagDeliverToSubID, tagOnBehalfOfSubID) // Tags added in 4.1. var beginString FIXString if m.Header.GetField(tagBeginString, &beginString) == nil { if string(beginString) != BeginStringFIX40 { - copy(tagOnBehalfOfLocationID, tagDeliverToLocationID) - copy(tagDeliverToLocationID, tagOnBehalfOfLocationID) + copyFunc(tagOnBehalfOfLocationID, tagDeliverToLocationID) + copyFunc(tagDeliverToLocationID, tagOnBehalfOfLocationID) } } diff --git a/null_log.go b/null_log.go index 106dcae42..c6c0e4327 100644 --- a/null_log.go +++ b/null_log.go @@ -17,17 +17,17 @@ package quickfix type nullLog struct{} -func (l nullLog) OnIncoming([]byte) {} -func (l nullLog) OnOutgoing([]byte) {} -func (l nullLog) OnEvent(string) {} -func (l nullLog) OnEventf(format string, a ...interface{}) {} +func (l nullLog) OnIncoming([]byte) {} +func (l nullLog) OnOutgoing([]byte) {} +func (l nullLog) OnEvent(string) {} +func (l nullLog) OnEventf(_ string, _ ...interface{}) {} type nullLogFactory struct{} func (nullLogFactory) Create() (Log, error) { return nullLog{}, nil } -func (nullLogFactory) CreateSessionLog(sessionID SessionID) (Log, error) { +func (nullLogFactory) CreateSessionLog(_ SessionID) (Log, error) { return nullLog{}, nil } diff --git a/quickfix_test.go b/quickfix_test.go index beb8792bc..db308b48e 100644 --- a/quickfix_test.go +++ b/quickfix_test.go @@ -85,18 +85,18 @@ type MockApp struct { lastToApp *Message } -func (e *MockApp) OnCreate(sessionID SessionID) { +func (e *MockApp) OnCreate(_ SessionID) { } -func (e *MockApp) OnLogon(sessionID SessionID) { +func (e *MockApp) OnLogon(_ SessionID) { e.Called() } -func (e *MockApp) OnLogout(sessionID SessionID) { +func (e *MockApp) OnLogout(_ SessionID) { e.Called() } -func (e *MockApp) FromAdmin(msg *Message, sessionID SessionID) (reject MessageRejectError) { +func (e *MockApp) FromAdmin(_ *Message, _ SessionID) (reject MessageRejectError) { if err, ok := e.Called().Get(0).(MessageRejectError); ok { return err } @@ -104,7 +104,7 @@ func (e *MockApp) FromAdmin(msg *Message, sessionID SessionID) (reject MessageRe return nil } -func (e *MockApp) ToAdmin(msg *Message, sessionID SessionID) { +func (e *MockApp) ToAdmin(msg *Message, _ SessionID) { e.Called() if e.decorateToAdmin != nil { @@ -114,12 +114,12 @@ func (e *MockApp) ToAdmin(msg *Message, sessionID SessionID) { e.lastToAdmin = msg } -func (e *MockApp) ToApp(msg *Message, sessionID SessionID) (err error) { +func (e *MockApp) ToApp(msg *Message, _ SessionID) (err error) { e.lastToApp = msg return e.Called().Error(0) } -func (e *MockApp) FromApp(msg *Message, sessionID SessionID) (reject MessageRejectError) { +func (e *MockApp) FromApp(_ *Message, _ SessionID) (reject MessageRejectError) { if err, ok := e.Called().Get(0).(MessageRejectError); ok { return err } diff --git a/session_factory_test.go b/session_factory_test.go index ada14350c..d14c5b221 100644 --- a/session_factory_test.go +++ b/session_factory_test.go @@ -373,7 +373,7 @@ func (s *SessionFactorySuite) TestInvalidWeekdays() { } for _, testcase := range testcases { - s.T().Run(testcase.label, func(t *testing.T) { + s.T().Run(testcase.label, func(_ *testing.T) { s.SessionSettings.Set(config.Weekdays, testcase.input) _, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) diff --git a/store/sql/sqlstore.go b/store/sql/sqlstore.go index c54ac517f..7550e3012 100644 --- a/store/sql/sqlstore.go +++ b/store/sql/sqlstore.go @@ -50,10 +50,10 @@ func sqlString(raw string, placeholder placeholderFunc) string { return raw } idx := 0 - return rePlaceholder.ReplaceAllStringFunc(raw, func(s string) string { - new := placeholder(idx) + return rePlaceholder.ReplaceAllStringFunc(raw, func(_ string) string { + p := placeholder(idx) idx++ - return new + return p }) } diff --git a/validation.go b/validation.go index 72bb06628..3dddbf00f 100644 --- a/validation.go +++ b/validation.go @@ -149,7 +149,7 @@ func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings Va return nil } -func validateMsgType(d *datadictionary.DataDictionary, msgType string, msg *Message) MessageRejectError { +func validateMsgType(d *datadictionary.DataDictionary, msgType string, _ *Message) MessageRejectError { if _, validMsgType := d.Messages[msgType]; !validMsgType { return InvalidMessageType() } @@ -296,7 +296,7 @@ func validateRequired(transportDD *datadictionary.DataDictionary, appDD *datadic return nil } -func validateRequiredFieldMap(msg *Message, requiredTags map[int]struct{}, fieldMap FieldMap) MessageRejectError { +func validateRequiredFieldMap(_ *Message, requiredTags map[int]struct{}, fieldMap FieldMap) MessageRejectError { for required := range requiredTags { requiredTag := Tag(required) if !fieldMap.Has(requiredTag) { @@ -328,7 +328,7 @@ func validateFields(transportDD *datadictionary.DataDictionary, appDD *datadicti return nil } -func validateField(d *datadictionary.DataDictionary, validFields datadictionary.TagSet, field TagValue) MessageRejectError { +func validateField(d *datadictionary.DataDictionary, _ datadictionary.TagSet, field TagValue) MessageRejectError { if len(field.value) == 0 { return TagSpecifiedWithoutAValue(field.tag) } From 96baad84471a4e621eff4a45165bdc275020c105 Mon Sep 17 00:00:00 2001 From: yanghao Date: Mon, 22 Apr 2024 08:57:12 -0400 Subject: [PATCH 08/58] feat: change proxyproto pkg to support proxy proto v2 --- accepter_test.go | 2 +- acceptor.go | 2 +- go.mod | 1 + go.sum | 2 ++ 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/accepter_test.go b/accepter_test.go index 0d9f6487b..54bfff845 100644 --- a/accepter_test.go +++ b/accepter_test.go @@ -21,7 +21,7 @@ import ( "github.com/quickfixgo/quickfix/config" - proxyproto "github.com/armon/go-proxyproto" + proxyproto "github.com/pires/go-proxyproto" "github.com/stretchr/testify/assert" ) diff --git a/acceptor.go b/acceptor.go index 879cdb17f..968be9efb 100644 --- a/acceptor.go +++ b/acceptor.go @@ -25,7 +25,7 @@ import ( "strconv" "sync" - proxyproto "github.com/armon/go-proxyproto" + proxyproto "github.com/pires/go-proxyproto" "github.com/quickfixgo/quickfix/config" ) diff --git a/go.mod b/go.mod index 2f5cf33c4..2b50e4c02 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/armon/go-proxyproto v0.1.0 github.com/mattn/go-sqlite3 v1.14.18 + github.com/pires/go-proxyproto v0.7.0 github.com/pkg/errors v0.9.1 github.com/shopspring/decimal v1.3.1 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index f3c70cab6..d178baaab 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ= github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= +github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= From 1518a203051b33370b28586a75a842eb844bd366 Mon Sep 17 00:00:00 2001 From: yanghao Date: Mon, 22 Apr 2024 09:25:05 -0400 Subject: [PATCH 09/58] fix: try to fix linter failed --- validation_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/validation_test.go b/validation_test.go index 85bbaf4a2..861dd863e 100644 --- a/validation_test.go +++ b/validation_test.go @@ -928,10 +928,10 @@ func tcMultipleRepeatingGroupFields() validateTest { dict, _ := datadictionary.Parse("spec/FIX43.xml") validator := NewValidator(defaultValidatorSettings, dict, nil) 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"), - DoNotExpectReject: true, + 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"), + DoNotExpectReject: true, } } From ad9438a454524977960bf99ab09cfabcf9ed8c07 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Tue, 23 Apr 2024 15:33:40 -0500 Subject: [PATCH 10/58] Update deps --- go.mod | 11 +++++------ go.sum | 31 ++++++++++--------------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 79325ae45..8b36cf024 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,13 @@ module github.com/quickfixgo/quickfix go 1.21 require ( - github.com/armon/go-proxyproto v0.1.0 - github.com/mattn/go-sqlite3 v1.14.18 + github.com/mattn/go-sqlite3 v1.14.22 github.com/pires/go-proxyproto v0.7.0 github.com/pkg/errors v0.9.1 - github.com/shopspring/decimal v1.3.1 + github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.8.4 - go.mongodb.org/mongo-driver v1.13.0 - golang.org/x/net v0.18.0 + go.mongodb.org/mongo-driver v1.15.0 + golang.org/x/net v0.24.0 ) require ( @@ -25,7 +24,7 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.22.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 759546724..a7e47992c 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,11 @@ -github.com/armon/go-proxyproto v0.1.0 h1:TWWcSsjco7o2itn6r25/5AqKBiWmsiuzsUDLT/MTl7k= -github.com/armon/go-proxyproto v0.1.0/go.mod h1:Xj90dce2VKbHzRAeiVQAMBtj4M5oidoXJ8lmgyW21mw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= @@ -18,9 +14,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= -github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ= github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= @@ -29,8 +24,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= @@ -45,26 +40,23 @@ github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.13.0 h1:67DgFFjYOCMWdtTEmKFpV3ffWlFnh+CYZ8ZS/tXWUfY= -go.mongodb.org/mongo-driver v1.13.0/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= +go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= +go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= @@ -72,7 +64,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -80,10 +71,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 2f15490245bd902038f62934a1f08fb448bcd850 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Tue, 23 Apr 2024 15:48:53 -0500 Subject: [PATCH 11/58] v0.9.2 release notes --- CHANGELOG.md | 8 ++++++++ fix_decimal_test.go | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a17b6aff..157000a68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ +## 0.9.2 (April 23, 2024) + +### BUG FIXES +* Prevent message queue blocking in the case of network connection trouble https://github.com/quickfixgo/quickfix/pull/615 https://github.com/quickfixgo/quickfix/pull/628 +* Corrects validation of multiple repeating groups with different fields https://github.com/quickfixgo/quickfix/pull/623 + ## 0.9.1 (April 15, 2024) + +### BUG FIXES * Preserve original body when resending https://github.com/quickfixgo/quickfix/pull/624 ## 0.9.0 (November 13, 2023) diff --git a/fix_decimal_test.go b/fix_decimal_test.go index f6fc3d53c..7c8d3a265 100644 --- a/fix_decimal_test.go +++ b/fix_decimal_test.go @@ -60,7 +60,7 @@ func TestFIXDecimalRead(t *testing.T) { require.Equal(t, test.expectError, err != nil) if !test.expectError { - assert.True(t, test.expected.Equals(field.Decimal), "Expected %s got %s", test.expected, field.Decimal) + assert.True(t, test.expected.Equal(field.Decimal), "Expected %s got %s", test.expected, field.Decimal) } } } From e2a2f27797497e8d713ada1609e7c829db8253c3 Mon Sep 17 00:00:00 2001 From: yanghao Date: Thu, 25 Apr 2024 05:03:37 -0400 Subject: [PATCH 12/58] feat: AllowUnknownMessageFields & CheckUserDefinedFields --- config/configuration.go | 2 + session_factory.go | 12 +++ validation.go | 80 ++++++++++++++----- validation_test.go | 170 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 244 insertions(+), 20 deletions(-) diff --git a/config/configuration.go b/config/configuration.go index 2c73ac369..b5fa7c28a 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -66,6 +66,8 @@ const ( MaxLatency string = "MaxLatency" PersistMessages string = "PersistMessages" RejectInvalidMessage string = "RejectInvalidMessage" + AllowUnknownMessageFields string = "AllowUnknownMessageFields" + CheckUserDefinedFields string = "CheckUserDefinedFields" DynamicSessions string = "DynamicSessions" DynamicQualifier string = "DynamicQualifier" ) diff --git a/session_factory.go b/session_factory.go index f28e552bd..37818deb9 100644 --- a/session_factory.go +++ b/session_factory.go @@ -99,6 +99,18 @@ func (f sessionFactory) newSession( } } + if settings.HasSetting(config.AllowUnknownMessageFields) { + if validatorSettings.AllowUnknownMessageFields, err = settings.BoolSetting(config.AllowUnknownMessageFields); err != nil { + return + } + } + + if settings.HasSetting(config.CheckUserDefinedFields) { + if validatorSettings.CheckUserDefinedFields, err = settings.BoolSetting(config.CheckUserDefinedFields); err != nil { + return + } + } + if sessionID.IsFIXT() { if s.DefaultApplVerID, err = settings.Setting(config.DefaultApplVerID); err != nil { return diff --git a/validation.go b/validation.go index 23564ca3d..e9b718138 100644 --- a/validation.go +++ b/validation.go @@ -19,6 +19,10 @@ import ( "github.com/quickfixgo/quickfix/datadictionary" ) +const ( + UserDefinedTagMin int = 5000 +) + // Validator validates a FIX message. type Validator interface { Validate(*Message) MessageRejectError @@ -26,15 +30,19 @@ type Validator interface { // ValidatorSettings describe validation behavior. type ValidatorSettings struct { - CheckFieldsOutOfOrder bool - RejectInvalidMessage bool + CheckFieldsOutOfOrder bool + RejectInvalidMessage bool + AllowUnknownMessageFields bool + CheckUserDefinedFields bool } // Default configuration for message validation. // See http://www.quickfixengine.org/quickfix/doc/html/configuration.html. var defaultValidatorSettings = ValidatorSettings{ - CheckFieldsOutOfOrder: true, - RejectInvalidMessage: true, + CheckFieldsOutOfOrder: true, + RejectInvalidMessage: true, + AllowUnknownMessageFields: false, + CheckUserDefinedFields: true, } type fixValidator struct { @@ -109,11 +117,11 @@ func validateFIX(d *datadictionary.DataDictionary, settings ValidatorSettings, m } if settings.RejectInvalidMessage { - if err := validateFields(d, d, msgType, msg); err != nil { + if err := validateFields(d, d, settings, msgType, msg); err != nil { return err } - if err := validateWalk(d, d, msgType, msg); err != nil { + if err := validateWalk(d, d, settings, msgType, msg); err != nil { return err } } @@ -137,11 +145,11 @@ func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings Va } if settings.RejectInvalidMessage { - if err := validateFields(transportDD, appDD, msgType, msg); err != nil { + if err := validateFields(transportDD, appDD, settings, msgType, msg); err != nil { return err } - if err := validateWalk(transportDD, appDD, msgType, msg); err != nil { + if err := validateWalk(transportDD, appDD, settings, msgType, msg); err != nil { return err } } @@ -156,7 +164,7 @@ func validateMsgType(d *datadictionary.DataDictionary, msgType string, _ *Messag return nil } -func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, msgType string, msg *Message) MessageRejectError { +func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError { remainingFields := msg.fields iteratedTags := make(datadictionary.TagSet) @@ -178,15 +186,19 @@ func validateWalk(transportDD *datadictionary.DataDictionary, appDD *datadiction messageDef = appDD.Messages[msgType] } - if fieldDef, ok = messageDef.Fields[int(tag)]; !ok { - return TagNotDefinedForThisMessageType(tag) - } - if _, duplicate := iteratedTags[int(tag)]; duplicate { return tagAppearsMoreThanOnce(tag) } iteratedTags.Add(int(tag)) + if fieldDef, ok = messageDef.Fields[int(tag)]; !ok { + if !checkFieldNotDefined(settings, tag) { + return TagNotDefinedForThisMessageType(tag) + } + remainingFields = remainingFields[1:] + continue + } + if remainingFields, err = validateVisitField(fieldDef, remainingFields); err != nil { return err } @@ -306,19 +318,24 @@ func validateRequiredFieldMap(_ *Message, requiredTags map[int]struct{}, fieldMa return nil } -func validateFields(transportDD *datadictionary.DataDictionary, appDD *datadictionary.DataDictionary, msgType string, message *Message) MessageRejectError { +func validateFields(transportDD *datadictionary.DataDictionary, + appDD *datadictionary.DataDictionary, + settings ValidatorSettings, + msgType string, + message *Message, +) MessageRejectError { for _, field := range message.fields { switch { case field.tag.IsHeader(): - if err := validateField(transportDD, transportDD.Header.Tags, field); err != nil { + if err := validateField(transportDD, settings, transportDD.Header.Tags, field); err != nil { return err } case field.tag.IsTrailer(): - if err := validateField(transportDD, transportDD.Trailer.Tags, field); err != nil { + if err := validateField(transportDD, settings, transportDD.Trailer.Tags, field); err != nil { return err } default: - if err := validateField(appDD, appDD.Messages[msgType].Tags, field); err != nil { + if err := validateField(appDD, settings, appDD.Messages[msgType].Tags, field); err != nil { return err } } @@ -327,15 +344,39 @@ func validateFields(transportDD *datadictionary.DataDictionary, appDD *datadicti return nil } -func validateField(d *datadictionary.DataDictionary, _ datadictionary.TagSet, field TagValue) MessageRejectError { +func getFieldType(d *datadictionary.DataDictionary, field int) (*datadictionary.FieldType, bool) { + fieldType, isMessageField := d.FieldTypeByTag[field] + return fieldType, isMessageField +} + +func checkFieldNotDefined(settings ValidatorSettings, field Tag) bool { + fail := false + if int(field) < UserDefinedTagMin { + fail = !settings.AllowUnknownMessageFields + } else { + fail = settings.CheckUserDefinedFields + } + return !fail +} + +func validateField(d *datadictionary.DataDictionary, + settings ValidatorSettings, + _ datadictionary.TagSet, + field TagValue, +) MessageRejectError { if len(field.value) == 0 { return TagSpecifiedWithoutAValue(field.tag) } - if _, valid := d.FieldTypeByTag[int(field.tag)]; !valid { + fieldType, isMessageField := getFieldType(d, int(field.tag)) + if !isMessageField && !checkFieldNotDefined(settings, field.tag) { return InvalidTagNumber(field.tag) } + if !isMessageField { + return nil + } + allowedValues := d.FieldTypeByTag[int(field.tag)].Enums if len(allowedValues) != 0 { if _, validValue := allowedValues[string(field.value)]; !validValue { @@ -343,7 +384,6 @@ func validateField(d *datadictionary.DataDictionary, _ datadictionary.TagSet, fi } } - fieldType := d.FieldTypeByTag[int(field.tag)] var prototype FieldValue switch fieldType.Type { case "MULTIPLESTRINGVALUE", "MULTIPLEVALUESTRING": diff --git a/validation_test.go b/validation_test.go index 861dd863e..1647dc94e 100644 --- a/validation_test.go +++ b/validation_test.go @@ -74,6 +74,14 @@ func TestValidate(t *testing.T) { tcInvalidTagCheckDisabledFixT(), tcInvalidTagCheckEnabled(), tcInvalidTagCheckEnabledFixT(), + tcAllowUnknownMessageFieldsEnabled(), + tcAllowUnknownMessageFieldsEnabledFixT(), + tcAllowUnknownMessageFieldsDisabled(), + tcAllowUnknownMessageFieldsDisabledFixT(), + tcCheckUserDefinedFieldsEnabled(), + tcCheckUserDefinedFieldsEnabledFixT(), + tcCheckUserDefinedFieldsDisabled(), + tcCheckUserDefinedFieldsDisabledFixT(), tcMultipleRepeatingGroupFields(), } @@ -786,6 +794,168 @@ func tcInvalidTagCheckEnabledFixT() validateTest { } } +func tcAllowUnknownMessageFieldsEnabled() validateTest { + dict, _ := datadictionary.Parse("spec/FIX40.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.AllowUnknownMessageFields = true + validator := NewValidator(customValidatorSettings, dict, nil) + + builder := createFIX40NewOrderSingle() + tag := Tag(41) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Allow Unknown Message Fields - Enabled", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: true, + } +} + +func tcAllowUnknownMessageFieldsEnabledFixT() validateTest { + tDict, _ := datadictionary.Parse("spec/FIXT11.xml") + appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.AllowUnknownMessageFields = true + validator := NewValidator(customValidatorSettings, appDict, tDict) + + builder := createFIX50SP2NewOrderSingle() + tag := Tag(41) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Allow Unknown Message Fields - Enabled FIXT", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: true, + } +} + +func tcAllowUnknownMessageFieldsDisabled() validateTest { + dict, _ := datadictionary.Parse("spec/FIX40.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.AllowUnknownMessageFields = false + validator := NewValidator(customValidatorSettings, dict, nil) + + builder := createFIX40NewOrderSingle() + tag := Tag(41) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Allow Unknown Message Fields - Disabled", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: false, + ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType, + ExpectedRefTagID: &tag, + } +} + +func tcAllowUnknownMessageFieldsDisabledFixT() validateTest { + tDict, _ := datadictionary.Parse("spec/FIXT11.xml") + appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.RejectInvalidMessage = true + validator := NewValidator(customValidatorSettings, appDict, tDict) + + builder := createFIX50SP2NewOrderSingle() + tag := Tag(41) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Allow Unknown Message Fields - Disabled FIXT", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: false, + ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType, + ExpectedRefTagID: &tag, + } +} + +func tcCheckUserDefinedFieldsEnabled() validateTest { + dict, _ := datadictionary.Parse("spec/FIX40.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.CheckUserDefinedFields = true + validator := NewValidator(customValidatorSettings, dict, nil) + + builder := createFIX40NewOrderSingle() + tag := Tag(9999) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Check User Defined Fields - Enabled", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: false, + ExpectedRefTagID: &tag, + } +} + +func tcCheckUserDefinedFieldsEnabledFixT() validateTest { + tDict, _ := datadictionary.Parse("spec/FIXT11.xml") + appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.RejectInvalidMessage = true + validator := NewValidator(customValidatorSettings, appDict, tDict) + + builder := createFIX50SP2NewOrderSingle() + tag := Tag(9999) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Check User Defined Fields - Enabled FIXT", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: false, + ExpectedRefTagID: &tag, + } +} + +func tcCheckUserDefinedFieldsDisabled() validateTest { + dict, _ := datadictionary.Parse("spec/FIX40.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.CheckUserDefinedFields = false + validator := NewValidator(customValidatorSettings, dict, nil) + + builder := createFIX40NewOrderSingle() + tag := Tag(9999) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Check User Defined Fields - Disabled", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: true, + } +} + +func tcCheckUserDefinedFieldsDisabledFixT() validateTest { + tDict, _ := datadictionary.Parse("spec/FIXT11.xml") + appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml") + customValidatorSettings := defaultValidatorSettings + customValidatorSettings.CheckUserDefinedFields = false + validator := NewValidator(customValidatorSettings, appDict, tDict) + + builder := createFIX50SP2NewOrderSingle() + tag := Tag(9999) + builder.Body.SetField(tag, FIXString("hello")) + msgBytes := builder.build() + + return validateTest{ + TestName: "Check User Defined Fields - Disabled FIXT", + Validator: validator, + MessageBytes: msgBytes, + DoNotExpectReject: true, + } +} + func tcTagSpecifiedOutOfRequiredOrderDisabledHeader() validateTest { dict, _ := datadictionary.Parse("spec/FIX40.xml") customValidatorSettings := defaultValidatorSettings From 74fd67b4acc4dd8ca03d92dec7e565fc39f96f0a Mon Sep 17 00:00:00 2001 From: yanghao Date: Thu, 25 Apr 2024 05:11:31 -0400 Subject: [PATCH 13/58] change options to keep same with quickfixj --- config/configuration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/configuration.go b/config/configuration.go index b5fa7c28a..bf3044dba 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -66,8 +66,8 @@ const ( MaxLatency string = "MaxLatency" PersistMessages string = "PersistMessages" RejectInvalidMessage string = "RejectInvalidMessage" - AllowUnknownMessageFields string = "AllowUnknownMessageFields" - CheckUserDefinedFields string = "CheckUserDefinedFields" + AllowUnknownMessageFields string = "AllowUnknownMsgFields" + CheckUserDefinedFields string = "ValidateUserDefinedFields" DynamicSessions string = "DynamicSessions" DynamicQualifier string = "DynamicQualifier" ) From b9e203b4538236a168b9a09866b1f00b6e12f887 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Thu, 25 Apr 2024 17:09:02 -0500 Subject: [PATCH 14/58] Corrects ResetOnLogon for initiators --- Makefile | 24 +++++++++++++++--------- session.go | 3 +-- session_state.go | 8 ++++++++ session_test.go | 10 +++++++--- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index b05c5d72f..b68843f3c 100644 --- a/Makefile +++ b/Makefile @@ -28,24 +28,30 @@ lint: linters-install # --------------------------------------------------------------- # Targets related to running acceptance tests - +ifdef STORE_TYPE +STORE := $(STORE_TYPE) +else +STORE := memory +endif + build-test-srv: - cd _test; go build -o echo_server ./test-server/ + cd _test; go build -v -o echo_server ./test-server/ fix40: - cd _test; ./runat.sh $@.cfg 5001 $(STORE_TYPE) "definitions/server/$@/*.def" + cd _test; ./runat.sh $@.cfg 5001 $(STORE) "definitions/server/$@/*.def" fix41: - cd _test; ./runat.sh $@.cfg 5002 $(STORE_TYPE) "definitions/server/$@/*.def" + cd _test; ./runat.sh $@.cfg 5002 $(STORE) "definitions/server/$@/*.def" fix42: - cd _test; ./runat.sh $@.cfg 5003 $(STORE_TYPE) "definitions/server/$@/*.def" + cd _test; ./runat.sh $@.cfg 5003 $(STORE) "definitions/server/$@/*.def" fix43: - cd _test; ./runat.sh $@.cfg 5004 $(STORE_TYPE) "definitions/server/$@/*.def" + cd _test; ./runat.sh $@.cfg 5004 $(STORE) "definitions/server/$@/*.def" fix44: - cd _test; ./runat.sh $@.cfg 5005 $(STORE_TYPE) "definitions/server/$@/*.def" + cd _test; ./runat.sh $@.cfg 5005 $(STORE) "definitions/server/$@/*.def" fix50: - cd _test; ./runat.sh $@.cfg 5006 $(STORE_TYPE) "definitions/server/$@/*.def" + cd _test; ./runat.sh $@.cfg 5006 $(STORE) "definitions/server/$@/*.def" fix50sp1: - cd _test; ./runat.sh $@.cfg 5007 $(STORE_TYPE) "definitions/server/$@/*.def" + cd _test; ./runat.sh $@.cfg 5007 $(STORE) "definitions/server/$@/*.def" fix50sp2: - cd _test; ./runat.sh $@.cfg 5008 $(STORE_TYPE) "definitions/server/$@/*.def" + cd _test; ./runat.sh $@.cfg 5008 $(STORE) "definitions/server/$@/*.def" ACCEPT_SUITE=fix40 fix41 fix42 fix43 fix44 fix50 fix50sp1 fix50sp2 accept: $(ACCEPT_SUITE) diff --git a/session.go b/session.go index 1bf60b122..5ab8c5edc 100644 --- a/session.go +++ b/session.go @@ -310,8 +310,6 @@ func (s *session) prepMessageForSend(msg *Message, inReplyTo *Message) (msgBytes } if isAdminMessageType(msgType) { - s.application.ToAdmin(msg, s.sessionID) - if bytes.Equal(msgType, msgTypeLogon) { var resetSeqNumFlag FIXBoolean if msg.Body.Has(tagResetSeqNumFlag) { @@ -330,6 +328,7 @@ func (s *session) prepMessageForSend(msg *Message, inReplyTo *Message) (msgBytes msg.Header.SetField(tagMsgSeqNum, FIXInt(seqNum)) } } + s.application.ToAdmin(msg, s.sessionID) } else { if err = s.application.ToApp(msg, s.sessionID); err != nil { return diff --git a/session_state.go b/session_state.go index 527556209..2c8b84d3c 100644 --- a/session_state.go +++ b/session_state.go @@ -49,6 +49,14 @@ func (sm *stateMachine) Connect(session *session) { return } } + + if session.ResetOnLogon { + if err := session.store.Reset(); err != nil { + session.logError(err) + return + } + } + session.log.OnEvent("Sending logon request") if err := session.sendLogon(); err != nil { session.logError(err) diff --git a/session_test.go b/session_test.go index b9a70c9b5..9308ec4f0 100644 --- a/session_test.go +++ b/session_test.go @@ -673,14 +673,18 @@ func (s *SessionSuite) TestInitiateLogonResetSeqNumFlag() { } s.session.State = latentState{} s.session.HeartBtInt = time.Duration(45) * time.Second + s.Require().Nil(s.store.Reset()) + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() + s.session.ResetOnLogon = true s.session.InitiateLogon = true + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + s.MockApp.On("ToAdmin") - s.MockApp.decorateToAdmin = func(msg *Message) { - msg.Body.SetField(tagResetSeqNumFlag, FIXBoolean(true)) - } s.session.onAdmin(adminMsg) s.MockApp.AssertExpectations(s.T()) From d5914471817c475393c8abc07cc0a0e97b4b0678 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Wed, 1 May 2024 12:40:12 -0500 Subject: [PATCH 15/58] Failing unit test for message with repeating group --- message_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/message_test.go b/message_test.go index fa60b3fd1..392690f92 100644 --- a/message_test.go +++ b/message_test.go @@ -146,6 +146,29 @@ func (s *MessageSuite) TestReBuild() { s.True(bytes.Equal(s.msg.bodyBytes, expectedBodyBytes), "Incorrect body bytes, got %s", string(s.msg.bodyBytes)) } +func (s *MessageSuite) TestRebuildWithRepeatingGroup() { + + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) + + // Given message bytes from a valid string + 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)) + + // And then rebuild the message bytes + rebuildBytes := s.msg.build() + + // this method does result in test passing. + //s.msg.buildWithBodyBytes(s.msg.bodyBytes) + + expectedBytes := []byte("8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST347=UTF-844=1211=139761=1010040021=1386=1336=NOPL55=SYMABC54=160=20231231-20:19:4138=140=259=0453=1448=4501447=D452=28354=6355=Public10=104") + + // Then the bytes should be the same with repeating group properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n +%s\n -%s", rebuildBytes, expectedBytes) +} + 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" From 66598652962cae72b89271ecc5310f0bd4442639 Mon Sep 17 00:00:00 2001 From: SNORRIS721 Date: Fri, 3 May 2024 10:36:01 -0500 Subject: [PATCH 16/58] Unregistering sessions on initiator close --- initiator.go | 8 ++++++++ session_factory_test.go | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/initiator.go b/initiator.go index 1b48ce4ce..b8735e388 100644 --- a/initiator.go +++ b/initiator.go @@ -73,6 +73,14 @@ func (i *Initiator) Stop() { default: } close(i.stopChan) + + for sessionID := range i.sessionSettings { + err := UnregisterSession(sessionID) + if err != nil { + return + } + } + i.wg.Wait() } diff --git a/session_factory_test.go b/session_factory_test.go index d14c5b221..349e7580c 100644 --- a/session_factory_test.go +++ b/session_factory_test.go @@ -472,6 +472,23 @@ func (s *SessionFactorySuite) TestNewSessionBuildInitiators() { s.Equal("127.0.0.1:5000", session.SocketConnectAddress[0]) } +func (s *SessionFactorySuite) TestDuplicateSession() { + s.sessionFactory.BuildInitiators = true + s.SessionSettings.Set(config.HeartBtInt, "34") + s.SessionSettings.Set(config.SocketConnectHost, "127.0.0.1") + s.SessionSettings.Set(config.SocketConnectPort, "5000") + + session, err := s.createSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.Nil(err) + s.True(session.InitiateLogon) + _, err = s.createSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.NotNil(err) + s.Equal("Duplicate SessionID", err.Error()) + UnregisterSession(s.SessionID) + _, err = s.createSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.Nil(err) +} + func (s *SessionFactorySuite) TestNewSessionBuildAcceptors() { s.sessionFactory.BuildInitiators = false s.SessionSettings.Set(config.HeartBtInt, "34") From cf3fb987067e504e3c9105e95161008d496f5f55 Mon Sep 17 00:00:00 2001 From: SNORRIS721 Date: Fri, 3 May 2024 13:50:51 -0500 Subject: [PATCH 17/58] Unregistering sessions on acceptor stop --- acceptor.go | 7 +++++++ initiator.go | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/acceptor.go b/acceptor.go index 968be9efb..da4cd9bb0 100644 --- a/acceptor.go +++ b/acceptor.go @@ -144,6 +144,13 @@ func (a *Acceptor) Stop() { session.stop() } a.sessionGroup.Wait() + + for sessionID := range a.sessions { + err := UnregisterSession(sessionID) + if err != nil { + return + } + } } // RemoteAddr gets remote IP address for a given session. diff --git a/initiator.go b/initiator.go index b8735e388..eeff30fc0 100644 --- a/initiator.go +++ b/initiator.go @@ -74,14 +74,14 @@ func (i *Initiator) Stop() { } close(i.stopChan) + i.wg.Wait() + for sessionID := range i.sessionSettings { err := UnregisterSession(sessionID) if err != nil { return } } - - i.wg.Wait() } // NewInitiator creates and initializes a new Initiator. From 668e1f4e7693f03d969711cb1fe430ac91dfb9f2 Mon Sep 17 00:00:00 2001 From: SNORRIS721 Date: Wed, 8 May 2024 12:36:14 -0500 Subject: [PATCH 18/58] Changing filestore.offsets from map[int]msgDef to sync.Map --- store/file/filestore.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/store/file/filestore.go b/store/file/filestore.go index 4a3dadb5d..43c647a97 100644 --- a/store/file/filestore.go +++ b/store/file/filestore.go @@ -22,6 +22,7 @@ import ( "path" "strconv" "strings" + "sync" "time" "github.com/pkg/errors" @@ -42,7 +43,7 @@ type fileStoreFactory struct { type fileStore struct { sessionID quickfix.SessionID cache quickfix.MessageStore - offsets map[int]msgDef + offsets sync.Map bodyFname string headerFname string sessionFname string @@ -106,7 +107,7 @@ func newFileStore(sessionID quickfix.SessionID, dirname string, fileSync bool) ( store := &fileStore{ sessionID: sessionID, cache: memStore, - offsets: make(map[int]msgDef), + offsets: sync.Map{}, bodyFname: path.Join(dirname, fmt.Sprintf("%s.%s", sessionPrefix, "body")), headerFname: path.Join(dirname, fmt.Sprintf("%s.%s", sessionPrefix, "header")), sessionFname: path.Join(dirname, fmt.Sprintf("%s.%s", sessionPrefix, "session")), @@ -206,7 +207,7 @@ func (store *fileStore) populateCache() (creationTimePopulated bool, err error) if cnt, err := fmt.Fscanf(tmpHeaderFile, "%d,%d,%d\n", &seqNum, &offset, &size); err != nil || cnt != 3 { break } - store.offsets[seqNum] = msgDef{offset: offset, size: size} + store.offsets.Store(seqNum, msgDef{offset: offset, size: size}) } } @@ -347,7 +348,7 @@ func (store *fileStore) SaveMessage(seqNum int, msg []byte) error { } } - store.offsets[seqNum] = msgDef{offset: offset, size: len(msg)} + store.offsets.Store(seqNum, msgDef{offset: offset, size: len(msg)}) return nil } @@ -360,7 +361,7 @@ func (store *fileStore) SaveMessageAndIncrNextSenderMsgSeqNum(seqNum int, msg [] } func (store *fileStore) getMessage(seqNum int) (msg []byte, found bool, err error) { - msgInfo, found := store.offsets[seqNum] + msgInfo, found := store.offsets.Load(seqNum) if !found { return } From ff456d7fc377bc7527c76561e5d099943104ea3c Mon Sep 17 00:00:00 2001 From: SNORRIS721 Date: Wed, 8 May 2024 13:04:25 -0500 Subject: [PATCH 19/58] fixing msgdef type error --- store/file/filestore.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/store/file/filestore.go b/store/file/filestore.go index 43c647a97..1c6e26889 100644 --- a/store/file/filestore.go +++ b/store/file/filestore.go @@ -361,10 +361,11 @@ func (store *fileStore) SaveMessageAndIncrNextSenderMsgSeqNum(seqNum int, msg [] } func (store *fileStore) getMessage(seqNum int) (msg []byte, found bool, err error) { - msgInfo, found := store.offsets.Load(seqNum) + msgInfoTemp, found := store.offsets.Load(seqNum) if !found { return } + msgInfo := msgInfoTemp.(msgDef) msg = make([]byte, msgInfo.size) if _, err = store.bodyFile.ReadAt(msg, msgInfo.offset); err != nil { From 60846b973de7105a88300b660d2db3a64f7a05c1 Mon Sep 17 00:00:00 2001 From: SNORRIS721 Date: Wed, 8 May 2024 15:51:32 -0500 Subject: [PATCH 20/58] adding error handling to type cast --- store/file/filestore.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/store/file/filestore.go b/store/file/filestore.go index 1c6e26889..0cc1e3c33 100644 --- a/store/file/filestore.go +++ b/store/file/filestore.go @@ -365,7 +365,10 @@ func (store *fileStore) getMessage(seqNum int) (msg []byte, found bool, err erro if !found { return } - msgInfo := msgInfoTemp.(msgDef) + msgInfo, ok := msgInfoTemp.(msgDef) + if !ok { + return nil, true, fmt.Errorf("incorrect msgInfo type while reading file: %s", store.bodyFname) + } msg = make([]byte, msgInfo.size) if _, err = store.bodyFile.ReadAt(msg, msgInfo.offset); err != nil { From fbe0cd7163d9284bacf2238c673e878c04fa520d Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Thu, 9 May 2024 13:52:37 -0500 Subject: [PATCH 21/58] v0.9.3 release notes --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 157000a68..512df8d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.9.3 (May 9, 2024) + +### BUG FIXES +* Change filestore.offsets from map[int]msgDef to sync.Map https://github.com/quickfixgo/quickfix/pull/639 +* Unregister sessions on stop https://github.com/quickfixgo/quickfix/pull/637 +* Corrects ResetOnLogon behavior for initiators https://github.com/quickfixgo/quickfix/pull/635 + +### FEATURES +* Add AllowUnknownMessageFields & CheckUserDefinedFields settings as included in QuickFIX/J https://github.com/quickfixgo/quickfix/pull/632 + ## 0.9.2 (April 23, 2024) ### BUG FIXES From 6889cf10c445cd62f6de53165d2ec27ebc01d167 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Thu, 16 May 2024 13:46:42 -0500 Subject: [PATCH 22/58] Adds proper parsing for repeating groups to keep group field order --- in_session.go | 6 +- message.go | 283 ++++++++++++++++++++++++++++++++++++++---------- message_test.go | 228 ++++++++++++++++++++++++++++++++++++-- session.go | 1 + 4 files changed, 452 insertions(+), 66 deletions(-) diff --git a/in_session.go b/in_session.go index f157f3ea2..6f189bf49 100644 --- a/in_session.go +++ b/in_session.go @@ -232,7 +232,11 @@ func (state inSession) resendMessages(session *session, beginSeqNo, endSeqNo int nextSeqNum := seqNum msg := NewMessage() for _, msgBytes := range msgs { - _ = ParseMessageWithDataDictionary(msg, bytes.NewBuffer(msgBytes), session.transportDataDictionary, session.appDataDictionary) + err = ParseMessageWithDataDictionary(msg, bytes.NewBuffer(msgBytes), session.transportDataDictionary, session.appDataDictionary) + if err != nil { + session.log.OnEventf("Resend Msg Parse Error: %v, %v", err.Error(), bytes.NewBuffer(msgBytes).String()) + return // We cant continue with a message that cant be parsed correctly. + } msgType, _ := msg.Header.GetBytes(tagMsgType) sentMessageSeqNum, _ := msg.Header.GetInt(tagMsgSeqNum) diff --git a/message.go b/message.go index 73da79169..26a6b90c1 100644 --- a/message.go +++ b/message.go @@ -27,6 +27,19 @@ import ( // Header is first section of a FIX Message. type Header struct{ FieldMap } +// msgparser contains message parsing vars needed to parse a string into a message. +type msgParser struct { + msg *Message + transportDataDictionary *datadictionary.DataDictionary + appDataDictionary *datadictionary.DataDictionary + rawBytes []byte + fieldIndex int + parsedFieldBytes *TagValue + trailerBytes []byte + foundBody bool + foundTrailer bool +} + // in the message header, the first 3 tags in the message header must be 8,9,35. func headerFieldOrdering(i, j Tag) bool { var ordering = func(t Tag) uint32 { @@ -152,116 +165,127 @@ func ParseMessageWithDataDictionary( msg *Message, rawMessage *bytes.Buffer, transportDataDictionary *datadictionary.DataDictionary, - _ *datadictionary.DataDictionary, + appDataDictionary *datadictionary.DataDictionary, ) (err error) { - msg.Header.Clear() - msg.Body.Clear() - msg.Trailer.Clear() - msg.rawMessage = rawMessage + // Create msgparser before we go any further. + mp := &msgParser{ + msg: msg, + transportDataDictionary: transportDataDictionary, + appDataDictionary: appDataDictionary, + } + mp.msg.rawMessage = rawMessage + mp.rawBytes = rawMessage.Bytes() - rawBytes := rawMessage.Bytes() + return doParsing(mp) +} + +// doParsing executes the message parsing process. +func doParsing(mp *msgParser) (err error) { + // Initialize for parsing. + mp.msg.Header.Clear() + mp.msg.Body.Clear() + mp.msg.Trailer.Clear() - // Allocate fields in one chunk. + // Allocate expected message fields in one chunk. fieldCount := 0 - for _, b := range rawBytes { + for _, b := range mp.rawBytes { if b == '\001' { fieldCount++ } } - if fieldCount == 0 { - return parseError{OrigError: fmt.Sprintf("No Fields detected in %s", string(rawBytes))} + return parseError{OrigError: fmt.Sprintf("No Fields detected in %s", string(mp.rawBytes))} } - - if cap(msg.fields) < fieldCount { - msg.fields = make([]TagValue, fieldCount) + if cap(mp.msg.fields) < fieldCount { + mp.msg.fields = make([]TagValue, fieldCount) } else { - msg.fields = msg.fields[0:fieldCount] + mp.msg.fields = mp.msg.fields[0:fieldCount] } - fieldIndex := 0 - // Message must start with begin string, body length, msg type. - if rawBytes, err = extractSpecificField(&msg.fields[fieldIndex], tagBeginString, rawBytes); err != nil { + // Get begin string. + if mp.rawBytes, err = extractSpecificField(&mp.msg.fields[mp.fieldIndex], tagBeginString, mp.rawBytes); err != nil { return } + mp.msg.Header.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) - msg.Header.add(msg.fields[fieldIndex : fieldIndex+1]) - fieldIndex++ - - parsedFieldBytes := &msg.fields[fieldIndex] - if rawBytes, err = extractSpecificField(parsedFieldBytes, tagBodyLength, rawBytes); err != nil { + // Get body length. + mp.fieldIndex++ + mp.parsedFieldBytes = &mp.msg.fields[mp.fieldIndex] + if mp.rawBytes, err = extractSpecificField(mp.parsedFieldBytes, tagBodyLength, mp.rawBytes); err != nil { return } + mp.msg.Header.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) - msg.Header.add(msg.fields[fieldIndex : fieldIndex+1]) - fieldIndex++ - - parsedFieldBytes = &msg.fields[fieldIndex] - if rawBytes, err = extractSpecificField(parsedFieldBytes, tagMsgType, rawBytes); err != nil { + // Get msg type. + mp.fieldIndex++ + mp.parsedFieldBytes = &mp.msg.fields[mp.fieldIndex] + if mp.rawBytes, err = extractSpecificField(mp.parsedFieldBytes, tagMsgType, mp.rawBytes); err != nil { return } + mp.msg.Header.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) + + // Start parsing. + mp.fieldIndex++ xmlDataLen := 0 xmlDataMsg := false - - msg.Header.add(msg.fields[fieldIndex : fieldIndex+1]) - fieldIndex++ - - trailerBytes := []byte{} - foundBody := false - foundTrailer := false + mp.trailerBytes = []byte{} + mp.foundBody = false + mp.foundTrailer = false for { - parsedFieldBytes = &msg.fields[fieldIndex] + mp.parsedFieldBytes = &mp.msg.fields[mp.fieldIndex] if xmlDataLen > 0 { - rawBytes, err = extractXMLDataField(parsedFieldBytes, rawBytes, xmlDataLen) + mp.rawBytes, err = extractXMLDataField(mp.parsedFieldBytes, mp.rawBytes, xmlDataLen) xmlDataLen = 0 xmlDataMsg = true } else { - rawBytes, err = extractField(parsedFieldBytes, rawBytes) + mp.rawBytes, err = extractField(mp.parsedFieldBytes, mp.rawBytes) } if err != nil { return } switch { - case isHeaderField(parsedFieldBytes.tag, transportDataDictionary): - msg.Header.add(msg.fields[fieldIndex : fieldIndex+1]) - case isTrailerField(parsedFieldBytes.tag, transportDataDictionary): - msg.Trailer.add(msg.fields[fieldIndex : fieldIndex+1]) - foundTrailer = true + case isHeaderField(mp.parsedFieldBytes.tag, mp.transportDataDictionary): + mp.msg.Header.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) + case isTrailerField(mp.parsedFieldBytes.tag, mp.transportDataDictionary): + mp.msg.Trailer.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) + mp.foundTrailer = true + case isNumInGroupField(mp.msg, []Tag{mp.parsedFieldBytes.tag}, mp.appDataDictionary): + parseGroup(mp, []Tag{mp.parsedFieldBytes.tag}) default: - foundBody = true - trailerBytes = rawBytes - msg.Body.add(msg.fields[fieldIndex : fieldIndex+1]) + mp.foundBody = true + mp.trailerBytes = mp.rawBytes + mp.msg.Body.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) } - if parsedFieldBytes.tag == tagCheckSum { + if mp.parsedFieldBytes.tag == tagCheckSum { break } - if !foundBody { - msg.bodyBytes = rawBytes + if !mp.foundBody { + mp.msg.bodyBytes = mp.rawBytes } - if parsedFieldBytes.tag == tagXMLDataLen { - xmlDataLen, _ = msg.Header.GetInt(tagXMLDataLen) + if mp.parsedFieldBytes.tag == tagXMLDataLen { + xmlDataLen, _ = mp.msg.Header.GetInt(tagXMLDataLen) } - fieldIndex++ + mp.fieldIndex++ } // This will happen if there are no fields in the body - if foundTrailer && !foundBody { - trailerBytes = rawBytes - msg.bodyBytes = nil + if mp.foundTrailer && !mp.foundBody { + mp.trailerBytes = mp.rawBytes + mp.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)] + if len(mp.msg.bodyBytes) > len(mp.trailerBytes) { + mp.msg.bodyBytes = mp.msg.bodyBytes[:len(mp.msg.bodyBytes)-len(mp.trailerBytes)] } length := 0 - for _, field := range msg.fields { + for _, field := range mp.msg.fields { switch field.tag { case tagBeginString, tagBodyLength, tagCheckSum: // Tags do not contribute to length. default: @@ -269,7 +293,7 @@ func ParseMessageWithDataDictionary( } } - bodyLength, err := msg.Header.GetInt(tagBodyLength) + bodyLength, err := mp.msg.Header.GetInt(tagBodyLength) if err != nil { err = parseError{OrigError: err.Error()} } else if length != bodyLength && !xmlDataMsg { @@ -279,6 +303,149 @@ func ParseMessageWithDataDictionary( return } +// parseGroup iterates through a repeating group to maintain correct order of those fields. +func parseGroup(mp *msgParser, tags []Tag) { + mp.foundBody = true + dm := mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1] + fields := getGroupFields(mp.msg, tags, mp.appDataDictionary) + + for { + mp.fieldIndex++ + mp.parsedFieldBytes = &mp.msg.fields[mp.fieldIndex] + mp.rawBytes, _ = extractField(mp.parsedFieldBytes, mp.rawBytes) + mp.trailerBytes = mp.rawBytes + + // Is this field a member for the group. + if isGroupMember(mp.parsedFieldBytes.tag, fields) { + // Is this field a nested repeating group. + if isNumInGroupField(mp.msg, append(tags, mp.parsedFieldBytes.tag), mp.appDataDictionary) { + dm = append(dm, *mp.parsedFieldBytes) + tags = append(tags, mp.parsedFieldBytes.tag) + fields = getGroupFields(mp.msg, tags, mp.appDataDictionary) + continue + } + // Add the field member to the group. + dm = append(dm, *mp.parsedFieldBytes) + } else if isHeaderField(mp.parsedFieldBytes.tag, mp.transportDataDictionary) { + // Found a header tag for some reason.. + mp.msg.Body.add(dm) + mp.msg.Header.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) + break + } else if isTrailerField(mp.parsedFieldBytes.tag, mp.transportDataDictionary) { + // Found the trailer at the end of the message. + mp.msg.Body.add(dm) + mp.msg.Trailer.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) + mp.foundTrailer = true + break + } else { + // Found a body field outside the group. + searchTags := []Tag{mp.parsedFieldBytes.tag} + // Is this a new group not inside the existing group. + if isNumInGroupField(mp.msg, searchTags, mp.appDataDictionary) { + // Add the current repeating group. + mp.msg.Body.add(dm) + // Cycle again with the new group. + dm = mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1] + fields = getGroupFields(mp.msg, searchTags, mp.appDataDictionary) + continue + } else { + 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 + } + // Add the repeating group. + mp.msg.Body.add(dm) + // Add the next body field. + mp.msg.Body.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) + } + break + } + } +} + +// isNumInGroupField evaluates if this tag is the start of a repeating group. +// tags slice will contain multiple tags if the tag in question is found while processing a group already. +func isNumInGroupField(msg *Message, tags []Tag, appDataDictionary *datadictionary.DataDictionary) bool { + if appDataDictionary != nil { + msgt, err := msg.MsgType() + if err != nil { + return false + } + mm, ok := appDataDictionary.Messages[msgt] + if ok { + fields := mm.Fields + for idx, tag := range tags { + fd, ok := fields[int(tag)] + if ok { + if idx == len(tags) - 1 { + if len(fd.Fields) > 0 { + return true + } + } else { + // Map nested fields. + newFields := make(map[int]*datadictionary.FieldDef) + for _, ff := range fd.Fields { + newFields[ff.Tag()] = ff + } + fields = newFields + } + } + } + } + } + return false +} + +// getGroupFields gets the relevant fields for parsing a repeating group if this tag is the start of a repeating group. +// tags slice will contain multiple tags if the tag in question is found while processing a group already. +func getGroupFields(msg *Message, tags []Tag, appDataDictionary *datadictionary.DataDictionary) (fields []*datadictionary.FieldDef) { + if appDataDictionary != nil { + msgt, err := msg.MsgType() + if err != nil { + return + } + mm, ok := appDataDictionary.Messages[msgt] + if ok { + fields := mm.Fields + for idx, tag := range tags { + fd, ok := fields[int(tag)] + if ok { + if idx == len(tags) - 1 { + if len(fd.Fields) > 0 { + return fd.Fields + } + } else { + // Map nested fields. + newFields := make(map[int]*datadictionary.FieldDef) + for _, ff := range fd.Fields { + newFields[ff.Tag()] = ff + } + fields = newFields + } + } + } + } + } + return +} + +// isGroupMember evaluates if this tag belongs to a repeating group. +func isGroupMember(tag Tag, fields []*datadictionary.FieldDef) bool { + for _, f := range fields{ + if f.Tag() == int(tag) { + return true + } + } + return false +} + func isHeaderField(tag Tag, dataDict *datadictionary.DataDictionary) bool { if tag.IsHeader() { return true diff --git a/message_test.go b/message_test.go index 392690f92..13d3e3eac 100644 --- a/message_test.go +++ b/message_test.go @@ -146,12 +146,35 @@ func (s *MessageSuite) TestReBuild() { s.True(bytes.Equal(s.msg.bodyBytes, expectedBodyBytes), "Incorrect body bytes, got %s", string(s.msg.bodyBytes)) } -func (s *MessageSuite) TestRebuildWithRepeatingGroup() { +func (s *MessageSuite) TestRebuildOneRepeatingGroupWithDictionary() { + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) + + // Given message bytes from a valid string with a 453 repeating group. + rawMsg := bytes.NewBufferString( + "8=FIX.4.49=16535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=1448=4501447=D452=28"+ + "10=026") + + // When we parse it into a message + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + // And then rebuild the message bytes + rebuildBytes := s.msg.build() + expectedBytes := []byte( + "8=FIX.4.49=16535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=1448=4501447=D452=28"+ + "10=026") + + // Then the bytes should have repeating groups properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) +} + +func (s *MessageSuite) TestRebuildTwoRepeatingGroupsWithDictionary() { dict, dictErr := datadictionary.Parse("spec/FIX44.xml") s.Nil(dictErr) - // Given message bytes from a valid string + // Given message bytes from a valid string with a 386 repeating group and a 453 repeating group. 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 @@ -159,14 +182,205 @@ func (s *MessageSuite) TestRebuildWithRepeatingGroup() { // And then rebuild the message bytes rebuildBytes := s.msg.build() + expectedBytes := []byte("8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST347=UTF-81=1010040011=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41354=6355=Public386=1336=NOPL453=1448=4501447=D452=2810=104") + + // Then the bytes should have repeating groups properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) +} - // this method does result in test passing. - //s.msg.buildWithBodyBytes(s.msg.bodyBytes) +func (s *MessageSuite) TestRebuildOneRepeatingGroupWithTwoMembersWithDictionary() { + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) - expectedBytes := []byte("8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST347=UTF-844=1211=139761=1010040021=1386=1336=NOPL55=SYMABC54=160=20231231-20:19:4138=140=259=0453=1448=4501447=D452=28354=6355=Public10=104") + // Given message bytes from a valid string with a 453 repeating group that has 2 child groups. + rawMsg := bytes.NewBufferString( + "8=FIX.4.49=18735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=2448=4501447=D452=28448=4502447=D452=28"+ + "10=044") - // Then the bytes should be the same with repeating group properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n +%s\n -%s", rebuildBytes, expectedBytes) + // When we parse it into a message + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + + // And then rebuild the message bytes + rebuildBytes := s.msg.build() + expectedBytes := []byte( + "8=FIX.4.49=18735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=2448=4501447=D452=28448=4502447=D452=28"+ + "10=044") + + // Then the bytes should have repeating groups properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) +} + +func (s *MessageSuite) TestRebuildTwoSequentialRepeatingGroupWithDictionary() { + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) + + // Given message bytes from a valid string with both a 78 and 453 repeating group that each have 2 child groups. + rawMsg := bytes.NewBufferString( + "8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=279=acct179=acct2453=2448=4501447=D452=28448=4502447=D452=28"+ + "10=243") + + // When we parse it into a message + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + + // And then rebuild the message bytes + rebuildBytes := s.msg.build() + expectedBytes := []byte( + "8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=279=acct179=acct2453=2448=4501447=D452=28448=4502447=D452=28"+ + "10=243") + + // Then the bytes should have repeating groups properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) +} + +func (s *MessageSuite) TestRebuildNestedRepeatingGroupWithDictionary() { + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) + + // Given message bytes from a valid string with a 78 repeating group that has + // a nested 539 group and then another 80 tag in the 78 group + rawMsg := bytes.NewBufferString( + "8=FIX.4.49=17735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid80=100"+ + "10=206") + + // When we parse it into a message + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + + // And then rebuild the message bytes + rebuildBytes := s.msg.build() + expectedBytes := []byte( + "8=FIX.4.49=17735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid80=100"+ + "10=206") + + // Then the bytes should have repeating groups properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) +} + +func (s *MessageSuite) TestRebuildDoubleNestedRepeatingGroupWithDictionary() { + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) + + // Given message bytes from a valid string with a 78 repeating group that has a + // double nested 539 and then 804 groups and then another 80 tag in the 78 group + rawMsg := bytes.NewBufferString( + "8=FIX.4.49=20235=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid80=100"+ + "10=117") + + // When we parse it into a message + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + + // And then rebuild the message bytes + rebuildBytes := s.msg.build() + expectedBytes := []byte( + "8=FIX.4.49=20235=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid80=100"+ + "10=117") + + // Then the bytes should have repeating groups properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) +} + +func (s *MessageSuite) TestRebuildDoubleNestedThenAnotherRepeatingGroupWithDictionary() { + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) + + // Given message bytes from a valid string with a 78 repeating group that has a double nested 539 and then 804 groups + // and then another repeating group 453 with two children. + rawMsg := bytes.NewBufferString( + "8=FIX.4.49=24535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid453=2448=4501447=D452=28448=4502447=D452=28"+ + "10=106") + + // When we parse it into a message + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + + // And then rebuild the message bytes + rebuildBytes := s.msg.build() + expectedBytes := []byte( + "8=FIX.4.49=24535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid453=2448=4501447=D452=28448=4502447=D452=28"+ + "10=106") + + // Then the bytes should have repeating groups properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) +} + +func (s *MessageSuite) TestRebuildDoubleNestedThenBodyTagThenAnotherRepeatingGroupWithDictionary() { + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) + + // Given message bytes from a valid string with a 78 repeating group that has a double nested 539 and then 804 groups + // then a 376 body tag and then another repeating group 453 with two children. + rawMsg := bytes.NewBufferString( + "8=FIX.4.49=25635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid376=compid453=2448=4501447=D452=28448=4502447=D452=28"+ + "10=198") + + // When we parse it into a message + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + + // And then rebuild the message bytes + rebuildBytes := s.msg.build() + expectedBytes := []byte( + "8=FIX.4.49=25635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid376=compid453=2448=4501447=D452=28448=4502447=D452=28"+ + "10=198") + + // Then the bytes should have repeating groups properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) +} + +func (s *MessageSuite) TestRebuildDoubleNestedWithTwoMembersRepeatingGroupWithDictionary() { + dict, dictErr := datadictionary.Parse("spec/FIX44.xml") + s.Nil(dictErr) + + // Given message bytes from a valid string with a 78 repeating group that + // has a double nested 539 and then 804 groups all with two children. + rawMsg := bytes.NewBufferString( + "8=FIX.4.49=40635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41"+ + "78=179=acct1"+ + "539=2"+ + "524=nestedid"+ + "804=2"+ + "545=doublenestedid"+ + "545=doublenestedid2"+ + "524=nestedid2"+ + "804=2"+ + "545=doublenestedid"+ + "545=doublenestedid2"+ + "79=acct2"+ + "539=2"+ + "524=nestedid"+ + "804=2"+ + "545=doublenestedid"+ + "545=doublenestedid2"+ + "524=nestedid2"+ + "804=2"+ + "545=doublenestedid"+ + "545=doublenestedid2"+ + "10=046") + + // When we parse it into a message + s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) + + // And then rebuild the message bytes + rebuildBytes := s.msg.build() + expectedBytes := []byte( + "8=FIX.4.49=40635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41"+ + "78=179=acct1539=2524=nestedid804=2545=doublenestedid545=doublenestedid2524=nestedid2804=2545=doublenestedid545=doublenestedid2"+ + "79=acct2539=2524=nestedid804=2545=doublenestedid545=doublenestedid2524=nestedid2804=2545=doublenestedid545=doublenestedid2"+ + "10=046") + + // Then the bytes should have repeating groups properly ordered + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) } func (s *MessageSuite) TestReBuildWithRepeatingGroupForResend() { diff --git a/session.go b/session.go index 5ab8c5edc..29d0b89e9 100644 --- a/session.go +++ b/session.go @@ -335,6 +335,7 @@ func (s *session) prepMessageForSend(msg *Message, inReplyTo *Message) (msgBytes } } + // Message converted to bytes here. msgBytes = msg.build() err = s.persist(seqNum, msgBytes) From 3517c8b55165a224e4ad7cd06d64d2ef689715c1 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Thu, 16 May 2024 14:57:43 -0500 Subject: [PATCH 23/58] Linter fixes --- Makefile | 4 ++ message.go | 63 ++++++++-------- message_test.go | 186 ++++++++++++++++++++++++------------------------ 3 files changed, 128 insertions(+), 125 deletions(-) diff --git a/Makefile b/Makefile index b68843f3c..75a60a66e 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,10 @@ linters-install: lint: linters-install golangci-lint run +# An easy way to run the linter without going through the install process - +# docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.57.2 golangci-lint run -v +# See https://golangci-lint.run/welcome/install/ for more details. + # --------------------------------------------------------------- # Targets related to running acceptance tests - diff --git a/message.go b/message.go index 26a6b90c1..11afd7669 100644 --- a/message.go +++ b/message.go @@ -29,15 +29,15 @@ type Header struct{ FieldMap } // msgparser contains message parsing vars needed to parse a string into a message. type msgParser struct { - msg *Message + msg *Message transportDataDictionary *datadictionary.DataDictionary - appDataDictionary *datadictionary.DataDictionary - rawBytes []byte - fieldIndex int - parsedFieldBytes *TagValue - trailerBytes []byte - foundBody bool - foundTrailer bool + appDataDictionary *datadictionary.DataDictionary + rawBytes []byte + fieldIndex int + parsedFieldBytes *TagValue + trailerBytes []byte + foundBody bool + foundTrailer bool } // in the message header, the first 3 tags in the message header must be 8,9,35. @@ -169,9 +169,9 @@ func ParseMessageWithDataDictionary( ) (err error) { // Create msgparser before we go any further. mp := &msgParser{ - msg: msg, + msg: msg, transportDataDictionary: transportDataDictionary, - appDataDictionary: appDataDictionary, + appDataDictionary: appDataDictionary, } mp.msg.rawMessage = rawMessage mp.rawBytes = rawMessage.Bytes() @@ -225,7 +225,6 @@ func doParsing(mp *msgParser) (err error) { } mp.msg.Header.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) - // Start parsing. mp.fieldIndex++ xmlDataLen := 0 @@ -308,7 +307,7 @@ func parseGroup(mp *msgParser, tags []Tag) { mp.foundBody = true dm := mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1] fields := getGroupFields(mp.msg, tags, mp.appDataDictionary) - + for { mp.fieldIndex++ mp.parsedFieldBytes = &mp.msg.fields[mp.fieldIndex] @@ -327,7 +326,7 @@ func parseGroup(mp *msgParser, tags []Tag) { // Add the field member to the group. dm = append(dm, *mp.parsedFieldBytes) } else if isHeaderField(mp.parsedFieldBytes.tag, mp.transportDataDictionary) { - // Found a header tag for some reason.. + // Found a header tag for some reason.. mp.msg.Body.add(dm) mp.msg.Header.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) break @@ -348,23 +347,23 @@ func parseGroup(mp *msgParser, tags []Tag) { dm = mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1] fields = getGroupFields(mp.msg, searchTags, mp.appDataDictionary) continue - } else { - 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 - } - // Add the repeating group. - mp.msg.Body.add(dm) - // Add the next body field. - mp.msg.Body.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) } + 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 + } + // Add the repeating group. + mp.msg.Body.add(dm) + // Add the next body field. + mp.msg.Body.add(mp.msg.fields[mp.fieldIndex : mp.fieldIndex+1]) + break } } @@ -384,7 +383,7 @@ func isNumInGroupField(msg *Message, tags []Tag, appDataDictionary *datadictiona for idx, tag := range tags { fd, ok := fields[int(tag)] if ok { - if idx == len(tags) - 1 { + if idx == len(tags)-1 { if len(fd.Fields) > 0 { return true } @@ -417,7 +416,7 @@ func getGroupFields(msg *Message, tags []Tag, appDataDictionary *datadictionary. for idx, tag := range tags { fd, ok := fields[int(tag)] if ok { - if idx == len(tags) - 1 { + if idx == len(tags)-1 { if len(fd.Fields) > 0 { return fd.Fields } @@ -438,7 +437,7 @@ func getGroupFields(msg *Message, tags []Tag, appDataDictionary *datadictionary. // isGroupMember evaluates if this tag belongs to a repeating group. func isGroupMember(tag Tag, fields []*datadictionary.FieldDef) bool { - for _, f := range fields{ + for _, f := range fields { if f.Tag() == int(tag) { return true } diff --git a/message_test.go b/message_test.go index 13d3e3eac..b02508cd9 100644 --- a/message_test.go +++ b/message_test.go @@ -152,9 +152,9 @@ func (s *MessageSuite) TestRebuildOneRepeatingGroupWithDictionary() { // Given message bytes from a valid string with a 453 repeating group. rawMsg := bytes.NewBufferString( - "8=FIX.4.49=16535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=1448=4501447=D452=28"+ - "10=026") + "8=FIX.4.49=16535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=1448=4501447=D452=28" + + "10=026") // When we parse it into a message s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) @@ -162,12 +162,12 @@ func (s *MessageSuite) TestRebuildOneRepeatingGroupWithDictionary() { // And then rebuild the message bytes rebuildBytes := s.msg.build() expectedBytes := []byte( - "8=FIX.4.49=16535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=1448=4501447=D452=28"+ - "10=026") - + "8=FIX.4.49=16535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=1448=4501447=D452=28" + + "10=026") + // Then the bytes should have repeating groups properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes) } func (s *MessageSuite) TestRebuildTwoRepeatingGroupsWithDictionary() { @@ -183,9 +183,9 @@ func (s *MessageSuite) TestRebuildTwoRepeatingGroupsWithDictionary() { // And then rebuild the message bytes rebuildBytes := s.msg.build() expectedBytes := []byte("8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST347=UTF-81=1010040011=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41354=6355=Public386=1336=NOPL453=1448=4501447=D452=2810=104") - + // Then the bytes should have repeating groups properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes) } func (s *MessageSuite) TestRebuildOneRepeatingGroupWithTwoMembersWithDictionary() { @@ -194,9 +194,9 @@ func (s *MessageSuite) TestRebuildOneRepeatingGroupWithTwoMembersWithDictionary( // Given message bytes from a valid string with a 453 repeating group that has 2 child groups. rawMsg := bytes.NewBufferString( - "8=FIX.4.49=18735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=2448=4501447=D452=28448=4502447=D452=28"+ - "10=044") + "8=FIX.4.49=18735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=2448=4501447=D452=28448=4502447=D452=28" + + "10=044") // When we parse it into a message s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) @@ -204,12 +204,12 @@ func (s *MessageSuite) TestRebuildOneRepeatingGroupWithTwoMembersWithDictionary( // And then rebuild the message bytes rebuildBytes := s.msg.build() expectedBytes := []byte( - "8=FIX.4.49=18735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=2448=4501447=D452=28448=4502447=D452=28"+ - "10=044") - + "8=FIX.4.49=18735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41453=2448=4501447=D452=28448=4502447=D452=28" + + "10=044") + // Then the bytes should have repeating groups properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes) } func (s *MessageSuite) TestRebuildTwoSequentialRepeatingGroupWithDictionary() { @@ -218,9 +218,9 @@ func (s *MessageSuite) TestRebuildTwoSequentialRepeatingGroupWithDictionary() { // Given message bytes from a valid string with both a 78 and 453 repeating group that each have 2 child groups. rawMsg := bytes.NewBufferString( - "8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=279=acct179=acct2453=2448=4501447=D452=28448=4502447=D452=28"+ - "10=243") + "8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=279=acct179=acct2453=2448=4501447=D452=28448=4502447=D452=28" + + "10=243") // When we parse it into a message s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) @@ -228,24 +228,24 @@ func (s *MessageSuite) TestRebuildTwoSequentialRepeatingGroupWithDictionary() { // And then rebuild the message bytes rebuildBytes := s.msg.build() expectedBytes := []byte( - "8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=279=acct179=acct2453=2448=4501447=D452=28448=4502447=D452=28"+ - "10=243") - + "8=FIX.4.49=21035=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=279=acct179=acct2453=2448=4501447=D452=28448=4502447=D452=28" + + "10=243") + // Then the bytes should have repeating groups properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes) } func (s *MessageSuite) TestRebuildNestedRepeatingGroupWithDictionary() { dict, dictErr := datadictionary.Parse("spec/FIX44.xml") s.Nil(dictErr) - // Given message bytes from a valid string with a 78 repeating group that has + // Given message bytes from a valid string with a 78 repeating group that has // a nested 539 group and then another 80 tag in the 78 group rawMsg := bytes.NewBufferString( - "8=FIX.4.49=17735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid80=100"+ - "10=206") + "8=FIX.4.49=17735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid80=100" + + "10=206") // When we parse it into a message s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) @@ -253,24 +253,24 @@ func (s *MessageSuite) TestRebuildNestedRepeatingGroupWithDictionary() { // And then rebuild the message bytes rebuildBytes := s.msg.build() expectedBytes := []byte( - "8=FIX.4.49=17735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid80=100"+ - "10=206") - + "8=FIX.4.49=17735=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid80=100" + + "10=206") + // Then the bytes should have repeating groups properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes) } func (s *MessageSuite) TestRebuildDoubleNestedRepeatingGroupWithDictionary() { dict, dictErr := datadictionary.Parse("spec/FIX44.xml") s.Nil(dictErr) - // Given message bytes from a valid string with a 78 repeating group that has a + // Given message bytes from a valid string with a 78 repeating group that has a // double nested 539 and then 804 groups and then another 80 tag in the 78 group rawMsg := bytes.NewBufferString( - "8=FIX.4.49=20235=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid80=100"+ - "10=117") + "8=FIX.4.49=20235=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid80=100" + + "10=117") // When we parse it into a message s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) @@ -278,24 +278,24 @@ func (s *MessageSuite) TestRebuildDoubleNestedRepeatingGroupWithDictionary() { // And then rebuild the message bytes rebuildBytes := s.msg.build() expectedBytes := []byte( - "8=FIX.4.49=20235=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid80=100"+ - "10=117") - + "8=FIX.4.49=20235=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid80=100" + + "10=117") + // Then the bytes should have repeating groups properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes) } func (s *MessageSuite) TestRebuildDoubleNestedThenAnotherRepeatingGroupWithDictionary() { dict, dictErr := datadictionary.Parse("spec/FIX44.xml") s.Nil(dictErr) - // Given message bytes from a valid string with a 78 repeating group that has a double nested 539 and then 804 groups + // Given message bytes from a valid string with a 78 repeating group that has a double nested 539 and then 804 groups // and then another repeating group 453 with two children. rawMsg := bytes.NewBufferString( - "8=FIX.4.49=24535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid453=2448=4501447=D452=28448=4502447=D452=28"+ - "10=106") + "8=FIX.4.49=24535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid453=2448=4501447=D452=28448=4502447=D452=28" + + "10=106") // When we parse it into a message s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) @@ -303,24 +303,24 @@ func (s *MessageSuite) TestRebuildDoubleNestedThenAnotherRepeatingGroupWithDicti // And then rebuild the message bytes rebuildBytes := s.msg.build() expectedBytes := []byte( - "8=FIX.4.49=24535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid453=2448=4501447=D452=28448=4502447=D452=28"+ - "10=106") - + "8=FIX.4.49=24535=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid453=2448=4501447=D452=28448=4502447=D452=28" + + "10=106") + // Then the bytes should have repeating groups properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes) } func (s *MessageSuite) TestRebuildDoubleNestedThenBodyTagThenAnotherRepeatingGroupWithDictionary() { dict, dictErr := datadictionary.Parse("spec/FIX44.xml") s.Nil(dictErr) - // Given message bytes from a valid string with a 78 repeating group that has a double nested 539 and then 804 groups + // Given message bytes from a valid string with a 78 repeating group that has a double nested 539 and then 804 groups // then a 376 body tag and then another repeating group 453 with two children. rawMsg := bytes.NewBufferString( - "8=FIX.4.49=25635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid376=compid453=2448=4501447=D452=28448=4502447=D452=28"+ - "10=198") + "8=FIX.4.49=25635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid376=compid453=2448=4501447=D452=28448=4502447=D452=28" + + "10=198") // When we parse it into a message s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) @@ -328,44 +328,44 @@ func (s *MessageSuite) TestRebuildDoubleNestedThenBodyTagThenAnotherRepeatingGro // And then rebuild the message bytes rebuildBytes := s.msg.build() expectedBytes := []byte( - "8=FIX.4.49=25635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid376=compid453=2448=4501447=D452=28448=4502447=D452=28"+ - "10=198") - + "8=FIX.4.49=25635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:4178=179=acct1539=1524=nestedid804=1545=doublenestedid376=compid453=2448=4501447=D452=28448=4502447=D452=28" + + "10=198") + // Then the bytes should have repeating groups properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes) } func (s *MessageSuite) TestRebuildDoubleNestedWithTwoMembersRepeatingGroupWithDictionary() { dict, dictErr := datadictionary.Parse("spec/FIX44.xml") s.Nil(dictErr) - // Given message bytes from a valid string with a 78 repeating group that + // Given message bytes from a valid string with a 78 repeating group that // has a double nested 539 and then 804 groups all with two children. rawMsg := bytes.NewBufferString( - "8=FIX.4.49=40635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41"+ - "78=179=acct1"+ - "539=2"+ - "524=nestedid"+ - "804=2"+ - "545=doublenestedid"+ - "545=doublenestedid2"+ - "524=nestedid2"+ - "804=2"+ - "545=doublenestedid"+ - "545=doublenestedid2"+ - "79=acct2"+ - "539=2"+ - "524=nestedid"+ - "804=2"+ - "545=doublenestedid"+ - "545=doublenestedid2"+ - "524=nestedid2"+ - "804=2"+ - "545=doublenestedid"+ - "545=doublenestedid2"+ - "10=046") + "8=FIX.4.49=40635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41" + + "78=179=acct1" + + "539=2" + + "524=nestedid" + + "804=2" + + "545=doublenestedid" + + "545=doublenestedid2" + + "524=nestedid2" + + "804=2" + + "545=doublenestedid" + + "545=doublenestedid2" + + "79=acct2" + + "539=2" + + "524=nestedid" + + "804=2" + + "545=doublenestedid" + + "545=doublenestedid2" + + "524=nestedid2" + + "804=2" + + "545=doublenestedid" + + "545=doublenestedid2" + + "10=046") // When we parse it into a message s.Nil(ParseMessageWithDataDictionary(s.msg, rawMsg, dict, dict)) @@ -373,14 +373,14 @@ func (s *MessageSuite) TestRebuildDoubleNestedWithTwoMembersRepeatingGroupWithDi // And then rebuild the message bytes rebuildBytes := s.msg.build() expectedBytes := []byte( - "8=FIX.4.49=40635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST"+ - "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41"+ - "78=179=acct1539=2524=nestedid804=2545=doublenestedid545=doublenestedid2524=nestedid2804=2545=doublenestedid545=doublenestedid2"+ - "79=acct2539=2524=nestedid804=2545=doublenestedid545=doublenestedid2524=nestedid2804=2545=doublenestedid545=doublenestedid2"+ - "10=046") - + "8=FIX.4.49=40635=D34=249=0100150=01001a52=20231231-20:19:4156=TEST" + + "1=acct111=1397621=138=140=244=1254=155=SYMABC59=060=20231231-20:19:41" + + "78=179=acct1539=2524=nestedid804=2545=doublenestedid545=doublenestedid2524=nestedid2804=2545=doublenestedid545=doublenestedid2" + + "79=acct2539=2524=nestedid804=2545=doublenestedid545=doublenestedid2524=nestedid2804=2545=doublenestedid545=doublenestedid2" + + "10=046") + // Then the bytes should have repeating groups properly ordered - s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes,) + s.True(bytes.Equal(expectedBytes, rebuildBytes), "Unexpected bytes,\n expected: %s\n but got: %s", expectedBytes, rebuildBytes) } func (s *MessageSuite) TestReBuildWithRepeatingGroupForResend() { From 525b62c13ef17a7b31851720d09fcb241b490fe1 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Fri, 17 May 2024 10:14:29 -0500 Subject: [PATCH 24/58] Adds log to readLoop --- acceptor.go | 2 +- connection.go | 3 ++- initiator.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/acceptor.go b/acceptor.go index da4cd9bb0..f5b9b281c 100644 --- a/acceptor.go +++ b/acceptor.go @@ -356,7 +356,7 @@ func (a *Acceptor) handleConnection(netConn net.Conn) { go func() { msgIn <- fixIn{msgBytes, parser.lastRead} - readLoop(parser, msgIn) + readLoop(parser, msgIn, a.globalLog) }() writeLoop(netConn, msgOut, a.globalLog) diff --git a/connection.go b/connection.go index c006560f4..99a4c465e 100644 --- a/connection.go +++ b/connection.go @@ -30,12 +30,13 @@ func writeLoop(connection io.Writer, messageOut chan []byte, log Log) { } } -func readLoop(parser *parser, msgIn chan fixIn) { +func readLoop(parser *parser, msgIn chan fixIn, log Log) { defer close(msgIn) for { msg, err := parser.ReadMessage() if err != nil { + log.OnEvent(err.Error()) return } msgIn <- fixIn{msg, parser.lastRead} diff --git a/initiator.go b/initiator.go index eeff30fc0..8f7a76200 100644 --- a/initiator.go +++ b/initiator.go @@ -198,7 +198,7 @@ func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, di goto reconnect } - go readLoop(newParser(bufio.NewReader(netConn)), msgIn) + go readLoop(newParser(bufio.NewReader(netConn)), msgIn, session.log) disconnected = make(chan interface{}) go func() { writeLoop(netConn, msgOut, session.log) From 035d82eccae9091a139fad760c50c8b229665d5f Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Fri, 17 May 2024 10:30:57 -0500 Subject: [PATCH 25/58] Adds nullLog --- connection_internal_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connection_internal_test.go b/connection_internal_test.go index 1ee4d13e8..081b3c110 100644 --- a/connection_internal_test.go +++ b/connection_internal_test.go @@ -45,7 +45,7 @@ func TestReadLoop(t *testing.T) { stream := "hello8=FIX.4.09=5blah10=103garbage8=FIX.4.09=4foo10=103" parser := newParser(strings.NewReader(stream)) - go readLoop(parser, msgIn) + go readLoop(parser, msgIn, nullLog{}) var tests = []struct { expectedMsg string From 7e71cbc851bbb33e0fc43d5cab9c3dd16d3b2615 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Wed, 29 May 2024 23:24:07 -0500 Subject: [PATCH 26/58] v0.9.4 release notes --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 512df8d89..258e21aff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.9.4 (May 29, 2024) + +### ENHANCEMENTS +* Adds log to readLoop just like writeLoop https://github.com/quickfixgo/quickfix/pull/642 + +### BUG FIXES +* Maintain repeating group field order when parsing messages https://github.com/quickfixgo/quickfix/pull/636 + ## 0.9.3 (May 9, 2024) ### BUG FIXES From c855c338bde47832b273f6f6b39458f04c1d2d25 Mon Sep 17 00:00:00 2001 From: momin-javed Date: Fri, 14 Jun 2024 15:31:04 +0530 Subject: [PATCH 27/58] Do not increment targetMsgSeqNum for out of sequence logout and test requests --- in_session.go | 25 +++++++++++++++++++++---- in_session_test.go | 17 +++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/in_session.go b/in_session.go index 6f189bf49..62304d005 100644 --- a/in_session.go +++ b/in_session.go @@ -102,14 +102,23 @@ func (state inSession) handleLogout(session *session, msg *Message) (nextState s session.log.OnEvent("Received logout response") } - if err := session.store.IncrNextTargetMsgSeqNum(); err != nil { - session.logError(err) - } - if session.ResetOnLogout { if err := session.dropAndReset(); err != nil { session.logError(err) } + return latentState{} + } + + if err := session.checkTargetTooLow(msg); err != nil { + return latentState{} + } + + if err := session.checkTargetTooHigh(msg); err != nil { + return latentState{} + } + + if err := session.store.IncrNextTargetMsgSeqNum(); err != nil { + session.logError(err) } return latentState{} @@ -131,6 +140,14 @@ func (state inSession) handleTestRequest(session *session, msg *Message) (nextSt } } + if err := session.checkTargetTooLow(msg); err != nil { + return state + } + + if err := session.checkTargetTooHigh(msg); err != nil { + return state + } + if err := session.store.IncrNextTargetMsgSeqNum(); err != nil { return handleStateError(session, err) } diff --git a/in_session_test.go b/in_session_test.go index aa9f87084..8973e4ef0 100644 --- a/in_session_test.go +++ b/in_session_test.go @@ -96,6 +96,23 @@ func (s *InSessionTestSuite) TestLogoutResetOnLogout() { s.NoMessageQueued() } +func (s *InSessionTestSuite) TestLogoutTargetTooHigh() { + s.MessageFactory.seqNum = 5 + + s.MockApp.On("FromAdmin").Return(nil) + s.MockApp.On("ToAdmin") + s.MockApp.On("OnLogout") + s.session.fixMsgIn(s.session, s.Logout()) + + s.MockApp.AssertExpectations(s.T()) + s.State(latentState{}) + + s.LastToAdminMessageSent() + s.MessageType(string(msgTypeLogout), s.MockApp.lastToAdmin) + s.NextTargetMsgSeqNum(1) + s.NextSenderMsgSeqNum(2) +} + func (s *InSessionTestSuite) TestTimeoutNeedHeartbeat() { s.MockApp.On("ToAdmin").Return(nil) s.session.Timeout(s.session, internal.NeedHeartbeat) From a60af80ed30435dd26a636423ffa1cc6432ad3ca Mon Sep 17 00:00:00 2001 From: momin-javed Date: Wed, 26 Jun 2024 00:45:06 +0530 Subject: [PATCH 28/58] Revert redundant TestRequest change --- in_session.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/in_session.go b/in_session.go index 62304d005..816b4b240 100644 --- a/in_session.go +++ b/in_session.go @@ -140,14 +140,6 @@ func (state inSession) handleTestRequest(session *session, msg *Message) (nextSt } } - if err := session.checkTargetTooLow(msg); err != nil { - return state - } - - if err := session.checkTargetTooHigh(msg); err != nil { - return state - } - if err := session.store.IncrNextTargetMsgSeqNum(); err != nil { return handleStateError(session, err) } From 91700d11896963ab5ec1377eec037e2ec822be0f Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Tue, 2 Jul 2024 13:26:08 -0500 Subject: [PATCH 29/58] Port config documentation into proper go doc format. --- config/configuration.go | 1042 ++++++++++++++++++++++++++++++++++++--- config/doc.go | 409 --------------- 2 files changed, 975 insertions(+), 476 deletions(-) delete mode 100644 config/doc.go diff --git a/config/configuration.go b/config/configuration.go index bf3044dba..f524f5005 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -1,73 +1,981 @@ +/* +Package config declares settings for configuring QuickFIX/Go. + +A quickfix acceptor or initiator can maintain multiple FIX +sessions. A FIX session is defined in QuickFIX as a unique +combination of a BeginString (the FIX version number), a +SenderCompID (your ID), and a TargetCompID (the +ID of your counterparty). A SessionQualifier can also be +used in rare cases to disambiguate otherwise identical sessions. + +The quickfix.ParseSettings(reader io.Reader) func will pull settings +out of any stream, most commonly, a file stream. +If you decide to write your own components, +(storage for a particular database, a new kind of connector +etc...), you may also use the session settings to store settings +for your custom component. + +A settings file is set up with two types of headings, a +[DEFAULT] heading and a [SESSION] heading. + +[DEFAULT] is where you can define settings that all sessions use by default. + +[SESSION] tells QuickFIX/Go that a new Session is being defined. + +If you do not provide a setting that QuickFIX/Go needs, it will throw an error +telling you what setting is missing or improperly formatted. + +# Sample Configuration Settings File: + + # default settings for sessions + [DEFAULT] + ConnectionType=initiator + ReconnectInterval=60 + SenderCompID=TW + + # session definition + [SESSION] + # inherit ConnectionType, ReconnectInterval and SenderCompID from default + BeginString=FIX.4.1 + TargetCompID=ARCA + StartTime=12:30:00 + EndTime=23:30:00 + HeartBtInt=20 + SocketConnectPort=9823 + SocketConnectHost=123.123.123.123 + DataDictionary=somewhere/FIX41.xml + + [SESSION] + BeginString=FIX.4.0 + TargetCompID=ISLD + StartTime=12:00:00 + EndTime=23:00:00 + HeartBtInt=30 + SocketConnectPort=8323 + SocketConnectHost=23.23.23.23 + DataDictionary=somewhere/FIX40.xml + + [SESSION] + BeginString=FIX.4.2 + TargetCompID=INCA + StartTime=12:30:00 + EndTime=21:30:00 + # overide default setting for RecconnectInterval + ReconnectInterval=30 + HeartBtInt=30 + SocketConnectPort=6523 + SocketConnectHost=3.3.3.3 + # (optional) alternate connection ports and hosts to cycle through on failover + SocketConnectPort1=8392 + SocketConnectHost1=8.8.8.8 + SocketConnectPort2=2932 + SocketConnectHost2=12.12.12.12 + DataDictionary=somewhere/FIX42.xml +*/ package config -// NOTE: Additions to this file should be made to both config/doc.go and http://www.quickfixgo.org/docs/ +const ( + // Session settings. + + // BeginString is the version of FIX this session should use. + // + // Required: Yes + // + // Default: N/A + // + // Valid Values: + // - FIXT.1.1 + // - FIX.4.4 + // - FIX.4.3 + // - FIX.4.2 + // - FIX.4.1 + // - FIX.4.0 + BeginString string = "BeginString" + + // SenderCompID is your ID as associated with this FIX session. + // + // Required: Yes, unless configuring an acceptor with DynamicSessions=Y + // + // Default: N/A + // + // Valid Values: + // - A case-sensitive alpha-numeric string. + SenderCompID string = "SenderCompID" + + // SenderSubID is your subID as associated with this FIX session. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A case-sensitive alpha-numeric string. + SenderSubID string = "SenderSubID" + + // SenderLocationID is your locationID as associated with this FIX session. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A case-sensitive alpha-numeric string. + SenderLocationID string = "SenderLocationID" + + // TargetCompID is the counterparty's ID as associated with this FIX session. + // + // Required: Yes, unless configuring an acceptor with DynamicSessions=Y + // + // Default: N/A + // + // Valid Values: + // - A case-sensitive alpha-numeric string. + TargetCompID string = "TargetCompID" + + // TargetSubID is the counterparty's subID as associated with this FIX session. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A case-sensitive alpha-numeric string. + TargetSubID string = "TargetSubID" + + // TargetLocationID is the counterparty's locationID as associated with this FIX session. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A case-sensitive alpha-numeric string. + TargetLocationID string = "TargetLocationID" + + // SessionQualifier is an additional qualifier to disambiguate otherwise identical sessions. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A case-sensitive alpha-numeric string. + SessionQualifier string = "SessionQualifier" + + // DefaultApplVerID specifies the default application version ID for the session. + // This can either be the ApplVerID enum (see the ApplVerID field) or the BeginString for the default version. + // + // Required: Only for FIXT 1.1 (and newer). Ignored for earlier transport versions. + // + // Default: N/A + // + // Valid Values: + // - FIX.5.0SP2 + // - FIX.5.0SP1 + // - FIX.5.0 + // - FIX.4.4 + // - FIX.4.3 + // - FIX.4.2 + // - FIX.4.1 + // - FIX.4.0 + // - 9 + // - 8 + // - 7 + // - 6 + // - 5 + // - 4 + // - 3 + // - 2 + DefaultApplVerID string = "DefaultApplVerID" + + // StartTime is the time of day that this FIX session becomes activated. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A time in the format of HH:MM:SS, time is represented in time zone configured by TimeZone + StartTime string = "StartTime" + + // EndTime is the time of day that this FIX session becomes deactivated. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A time in the format of HH:MM:SS, time is represented in time zone configured by TimeZone + EndTime string = "EndTime" + + // StartDay is the starting day of week for the session, + // used to configure week-long sessions, + // and can be used in combination with StartTime. + // Incompatible with the Weekdays setting. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - Full day of week in English, or 3 letter abbreviation (i.e. Monday and Mon are valid) + StartDay string = "StartDay" + + // EndDay is the ending day of week for the session, + // used to configure week-long sessions, + // and can be used in combination with EndTime. + // Incompatible with the Weekdays setting. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - Full day of week in English, or 3 letter abbreviation (i.e. Monday and Mon are valid) + EndDay string = "EndDay" + + // Weekdays is for daily sessions that are only active on specific days of the week. + // Use in combination with StartTime and EndTime. + // Incompatible with StartDay and EndDay. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - Comma delimited list of days of the week in English, or 3 letter abbreviation (e.g. "Monday,Tuesday,Wednesday" or "Mon,Tue,Wed" would both be valid values). + Weekdays string = "Weekdays" + + // TimeZone sets the time zone for this session; if specified, StartTime and EndTime will be converted from this zone to UTC. + // Times in messages will still be set to UTC as this is required by FIX specifications. + // + // Required: No + // + // Default: UTC + // + // Valid Values: + // - IANA Time zone ID (America/New_York, Asia/Tokyo, Europe/London, etc.) + // - Local (The zone on host) + TimeZone string = "TimeZone" + + // TimeStampPrecision determines precision for timestamps in (Orig)SendingTime fields in outbound messages. + // Only available for FIX.4.2 and greater, FIX versions earlier than FIX.4.2 will use timestamp resolution in seconds. + // + // Required: No + // + // Default: MILLIS + // + // Valid Values: + // - SECONDS + // - MILLIS + // - MICROS + // - NANOS + TimeStampPrecision string = "TimeStampPrecision" + + // ResetOnLogon determines if sequence numbers should be reset when receiving a logon request. + // Valid for FIX Acceptors only. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + ResetOnLogon string = "ResetOnLogon" + + // RefreshOnLogon determines if session state should be restored from persistence layer when logging on. + // Useful for creating hot failover sessions. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + RefreshOnLogon string = "RefreshOnLogon" + + // ResetOnLogout determines if sequence numbers should be reset to 1 after a normal logout termination. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + ResetOnLogout string = "ResetOnLogout" + + // ResetOnDisconnect determines if sequence numbers should be reset to 1 after an abnormal termination. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + ResetOnDisconnect string = "ResetOnDisconnect" +) + +const ( + // Validation settings. + + // DataDictionary is the path to an XML definition file for validating incoming FIX messages. + // If this is not set, only basic message validation will be done. + // If messages contain repeating groups, this is required to parse them correctly. + // This setting should only be used with FIX transport versions older than FIXT.1.1. + // See TransportDataDictionary and AppDataDictionary for FIXT.1.1 messages. + // Value must be a path to a valid XML data dictionary file. + // + // QuickFIX/Go repo contains the following standard dictionaries in the spec/ directory: + // - FIX44.xml + // - FIX43.xml + // - FIX42.xml + // - FIX41.xml + // - FIX40.xml + // + // Required: No, but strongly recommended + // + // Default: No dictionary, and QuickFIX/Go does not attempt to load any standard dictionaries + // + // Valid Values: + // - A filepath to a XML file with read access. + DataDictionary string = "DataDictionary" + + // TransportDataDictionary is the path to an XML definition file for validating admin (transport) messages. + // This setting is only valid for FIXT.1.1 (or newer) sessions. + // See DataDictionary for older transport versions (FIX.4.0 - FIX.4.4) for additional information. + // Value must be a path to a valid XML data dictionary file. + // + // QuickFIX/Go repo contains the following standard dictionary in the spec/ directory + // - FIXT1.1.xml + // + // Required: No, but strongly recommended. Required if also using AppDataDictionary + // + // Default: No dictionary, and QuickFIX/Go does not attempt to load any standard dictionaries + // + // Valid Values: + // - A filepath to a XML file with read access. + TransportDataDictionary string = "TransportDataDictionary" + + // AppDataDictionary is the path to an XML definition file for validating application messages. + // This setting is only valid for FIXT.1.1 (or newer) sessions. + // See DataDictionary for older transport versions (FIX.4.0 - FIX.4.4) for additional information. + // Value must be a path to a valid XML data dictionary file. + // + // This setting supports the possibility of a custom application data dictionary for each session. + // This setting can be used as a prefix to specify multiple application dictionaries for the FIXT transport. + // + // For example: + // DefaultApplVerID=FIX.4.2 + // # For default application version ID + // AppDataDictionary=FIX42.xml + // # For nondefault application version ID + // # Use BeginString suffix for app version + // AppDataDictionary.FIX.4.4=FIX44.xml + // + // QuickFIX/Go repo contains the following standard dictionaries in the spec/ directory + // - FIX50SP2.xml + // - FIX50SP1.xml + // - FIX50.xml + // - FIX44.xml + // - FIX43.xml + // - FIX42.xml + // - FIX41.xml + // - FIX40.xml + // + // Required: No, but strongly recommended. Required if also using TransportDataDictionary + // + // Default: No dictionary, and QuickFIX/Go does not attempt to load any standard dictionaries + // + // Valid Values: + // - A filepath to a XML file with read access. + AppDataDictionary string = "AppDataDictionary" + + // RejectInvalidMessage is set by detault to Y, meaning that on reception of a message + // that fails data dictionary validation, a reject will be issued to the counter-party in responnse. + // + // Required: No + // + // Default: Y + // + // Valid Values: + // - Y + // - N + RejectInvalidMessage string = "RejectInvalidMessage" + + // AllowUnknownMessageFields is set by default to N, meaning that non user-defined fields (field with tag < 5000) + // will be rejected if they are not defined in the data dictionary, + // or are present in messages they do not belong to. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + AllowUnknownMessageFields string = "AllowUnknownMsgFields" + + // CheckUserDefinedFields if set to N, user-defined fields (field with tag >= 5000) will not be rejected + // if they are not defined in the data dictionary, or are present in messages they do not belong to. + // + // Required: No + // + // Default: Y + // + // Valid Values: + // - Y + // - N + CheckUserDefinedFields string = "ValidateUserDefinedFields" + + // ValidateFieldsOutOfOrder if set to N, fields that are out of order (i.e. body fields in the header, or header fields in the body) + // will not be rejected. Useful for connecting to systems which do not properly order fields. + // + // Required: No + // + // Default: Y + // + // Valid Values: + // - Y + // - N + ValidateFieldsOutOfOrder string = "ValidateFieldsOutOfOrder" + + // 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. + // + // Required: No + // + // Default: Y + // + // Valid Values: + // - Y + // - N + CheckLatency string = "CheckLatency" + + // MaxLatency if CheckLatency is set to Y, this defines the number of seconds latency allowed for a message to be processed. + // Value must be positive integer. + // + // Required: No + // + // Default: 120 + // + // Valid Values: + // - Any positive integer + MaxLatency string = "MaxLatency" +) -// Const configuration settings. const ( - BeginString string = "BeginString" - SenderCompID string = "SenderCompID" - SenderSubID string = "SenderSubID" - SenderLocationID string = "SenderLocationID" - TargetCompID string = "TargetCompID" - TargetSubID string = "TargetSubID" - TargetLocationID string = "TargetLocationID" - SessionQualifier string = "SessionQualifier" - SocketAcceptHost string = "SocketAcceptHost" - SocketAcceptPort string = "SocketAcceptPort" - SocketConnectHost string = "SocketConnectHost" - SocketConnectPort string = "SocketConnectPort" - SocketPrivateKeyFile string = "SocketPrivateKeyFile" - SocketCertificateFile string = "SocketCertificateFile" - SocketCAFile string = "SocketCAFile" - SocketInsecureSkipVerify string = "SocketInsecureSkipVerify" - SocketServerName string = "SocketServerName" - SocketMinimumTLSVersion string = "SocketMinimumTLSVersion" - SocketTimeout string = "SocketTimeout" - SocketUseSSL string = "SocketUseSSL" - ProxyType string = "ProxyType" - ProxyHost string = "ProxyHost" - ProxyPort string = "ProxyPort" - ProxyUser string = "ProxyUser" - ProxyPassword string = "ProxyPassword" - UseTCPProxy string = "UseTCPProxy" - DefaultApplVerID string = "DefaultApplVerID" - StartTime string = "StartTime" - EndTime string = "EndTime" - StartDay string = "StartDay" - EndDay string = "EndDay" - Weekdays string = "Weekdays" - TimeZone string = "TimeZone" - DataDictionary string = "DataDictionary" - TransportDataDictionary string = "TransportDataDictionary" - AppDataDictionary string = "AppDataDictionary" - ResetOnLogon string = "ResetOnLogon" - RefreshOnLogon string = "RefreshOnLogon" - ResetOnLogout string = "ResetOnLogout" - ResetOnDisconnect string = "ResetOnDisconnect" - ReconnectInterval string = "ReconnectInterval" - LogoutTimeout string = "LogoutTimeout" - LogonTimeout string = "LogonTimeout" - HeartBtInt string = "HeartBtInt" - HeartBtIntOverride string = "HeartBtIntOverride" - FileLogPath string = "FileLogPath" - FileStorePath string = "FileStorePath" - FileStoreSync string = "FileStoreSync" - SQLStoreDriver string = "SQLStoreDriver" - SQLStoreDataSourceName string = "SQLStoreDataSourceName" - SQLStoreConnMaxLifetime string = "SQLStoreConnMaxLifetime" - MongoStoreConnection string = "MongoStoreConnection" - MongoStoreDatabase string = "MongoStoreDatabase" - MongoStoreReplicaSet string = "MongoStoreReplicaSet" - ValidateFieldsOutOfOrder string = "ValidateFieldsOutOfOrder" - ResendRequestChunkSize string = "ResendRequestChunkSize" + // Initiator-only settings. + + // ReconnectInterval defines the time between reconnection attempts in seconds. + // Only used for initiators. + // Value must be positive integer. + // + // Required: No + // + // Default: 30 + // + // Valid Values: + // - Any positive integer + ReconnectInterval string = "ReconnectInterval" + + // LogoutTimeout defines the number of seconds to wait for a logout response before disconnecting. + // Only used for initiators. + // Value must be positive integer. + // + // Required: No + // + // Default: 2 + // + // Valid Values: + // - Any positive integer + LogoutTimeout string = "LogoutTimeout" + + // LogonTimeout defines the number of seconds to wait for a logon response before disconnecting. + // Only used for initiators. + // Value must be positive integer. + // + // Required: No + // + // Default: 10 + // + // Valid Values: + // - Any positive integer + LogonTimeout string = "LogonTimeout" + + // HeartBtInt sets the FIX session heartbeat interval in seconds. + // Only used for initiators (unless acceptor sets HeartBtIntOverride to Y). + // Value must be positive integer. + // + // Required: Yes for initiators or acceptors with HeartBtIntOverride + // + // Default: None + // + // Valid Values: + // - Any positive integer + HeartBtInt string = "HeartBtInt" + + // SocketConnectHost sets the host to attempt to connect to. + // In config files you can also set SocketConnectHost where n is a positive integer. + // This allows for alternate socket hosts for connecting to a session for failover. + // (i.e.) SocketConnectHost1, SocketConnectHost2... must be consecutive and have a matching SocketConnectPort. + // + // Required: Yes for initiators + // + // Default: None + // + // Valid Values: + // - A valid IPv4 or IPv6 address or a domain name + SocketConnectHost string = "SocketConnectHost" + + // SocketConnectPort sets the socket port for connecting to a session. + // In config files you can also set SocketConnectPort where n is a positive integer. + // This allows for alternate socket ports for connecting to a session for failover. + // (i.e.) SocketConnectPort1, SocketConnectPort2... must be consecutive and have a matching SocketConnectHost. + // + // Required: Yes for initiators + // + // Default: None + // + // Valid Values: + // - A positive integer + SocketConnectPort string = "SocketConnectPort" + + // SocketTimeout sets the duration of timeout for TLS handshake. + // Only used for initiators. + // + // Example Values: + // - SocketTimeout=30s # 30 seconds + // - SocketTimeout=60m # 60 minutes + // + // Required: No + // + // Default: 0 (no timeout) + // + // Valid Values: + // - A valid go time.Duration + SocketTimeout string = "SocketTimeout" + + // ProxyType sets the type of proxy server to connect to. + // Only used for initiators. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - socks + ProxyType string = "ProxyType" + + // ProxyHost provides the address of the proxy server to connect to. + // Only used for initiators. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A valid IPv4 or IPv6 address or a domain name + ProxyHost string = "ProxyHost" + + // ProxyPort provides the port of the proxy server to connect to. + // Only used for initiators. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - Any positive integer + ProxyPort string = "ProxyPort" + + // ProxyUser sets the username for the proxy server connection. + // Only used for initiators. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - Any string + ProxyUser string = "ProxyUser" + + // ProxyPassword sets the password for the proxy server connection. + // Only used for initiators. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - Any string + ProxyPassword string = "ProxyPassword" +) + +const ( + // Acceptor-only settings. + + // SocketAcceptHost sets the address for listening on incoming connections. + // Used for acceptors only. + // + // Common examples: + // - 127.0.0.1 (for testing on localhost) + // - 0.0.0.0 (explicitly specify every available interface) + // + // Required: No + // + // Default: Listens on all available network interfaces. + // + // Valid Values: + // - A valid IPv4 or IPv6 address or a domain name + SocketAcceptHost string = "SocketAcceptHost" + + // SocketAcceptPort sets the socket port for listening to incoming connections. + // Used for acceptors only. + // + // Required: Yes for acceptors + // + // Default: None + // + // Valid Values: + // - A positive integer, representing a valid open socket port + SocketAcceptPort string = "SocketAcceptPort" + + // HeartBtIntOverride if set to Y, will use the HeartBtInt value in the acceptor's config file for the heartbeat interval rather than what the initiator dictates. + // Used for acceptors only. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + HeartBtIntOverride string = "HeartBtIntOverride" + + // UseTCPProxy if set to Y, use TCP proxy for servers listening behind HAProxy of Amazon ELB load balancers. + // The server can then receive the address of the client instead of the load balancer's. + // Used for acceptors only. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + UseTCPProxy string = "UseTCPProxy" + + // DynamicSessions if set to Y, allows sessions to connect to this acceptor + // without explicitly stating SenderCompID/TargetCompID in the config file for the acceptor. + // Used for acceptors only. + // + // Use DynamicQualifier=Y if initiator client sessions are using the same SenderCompID. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + DynamicSessions string = "DynamicSessions" + + // DynamicQualifier is used in conjunction with DynamicSessions. + // If set to Y, allows sessions to connect to this acceptor + // when initiator client sessions are using the same SenderCompID. + // Used for acceptors only. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + DynamicQualifier string = "DynamicQualifier" +) + +const ( + // Security settings. + + // SocketPrivateKeyFile is the filepath for the private key to use for secure TLS connections. + // Must be used with SocketCertificateFile. + // Must contain PEM encoded data. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A filepath to a file with read access. + SocketPrivateKeyFile string = "SocketPrivateKeyFile" + + // SocketCertificateFile is the filepath for the certificate to use for secure TLS connections. + // Must be used with SocketPrivateKeyFile. + // Must contain PEM encoded data. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A filepath to a file with read access. + SocketCertificateFile string = "SocketCertificateFile" + + // SocketCAFile is an optional filepath for a root CA to use for secure TLS connections. + // For acceptors, client certificates will be verified against this CA. + // For initiators, clients will use the CA to verify the server certificate. + // If not configurated, initiators will verify the server certificate using the host's root CA set. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A filepath to a file with read access. + SocketCAFile string = "SocketCAFile" + + // SocketInsecureSkipVerify controls whether a client verifies the server's certificate chain and host name. + // If SocketInsecureSkipVerify is set to Y, crypto/tls accepts any certificate presented by the server and any host name in that certificate. + // In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is used. + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + SocketInsecureSkipVerify string = "SocketInsecureSkipVerify" + + // SocketServerName sets the expected server name on a returned certificate, unless SocketInsecureSkipVerify is true. + // This is for the TLS Server Name Indication extension. + // Only used for initiators. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - Any string + SocketServerName string = "SocketServerName" + + // SocketMinimumTLSVersion specifies the minimum TLS version to use when creating a secure connection. + // + // Required: No + // + // Default: TLS12. + // + // Valid Values: + // - SSL30 + // - TLS10 + // - TLS11 + // - TLS12 + SocketMinimumTLSVersion string = "SocketMinimumTLSVersion" + + // SocketUseSSL if set to Y, an initiator will use TLS even if client certificates are not present. + // It is set to N by default, meaning TLS will not be used if SocketPrivateKeyFile or SocketCertificateFile are not supplied. + // + // This is used for initiators in the case where you do not need to provide client certificates, but still need to use + // a TLS connection and should verify the server certificate(s). + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N + SocketUseSSL string = "SocketUseSSL" +) + +const ( + // Logging settings. + + // FileLogPath sets the directory path in which to write log files to. + // This will create the directory path if it does not already exist. + // FileLogPath is only relevant if also using quickfix.NewFileLogFactory(..) in code + // when creating your LogFactory for your initiator or acceptor. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A valid path + FileLogPath string = "FileLogPath" +) + +const ( + // Storage settings. + + // PersistMessages if set to N, no messages will be persisted. + // This will force QuickFIX/Go to always send GapFills instead of resending messages. + // Use this if you know you never want to resend a message. + // This is useful for market data streams when logging all incoming messages is not important. + // + // Required: No + // + // Default: Y + // + // Valid Values: + // - Y + // - N + PersistMessages string = "PersistMessages" + + // FileStorePath sets the directory path in which to write sequence number and message files. + // This will create the directory path if it does not already exist. + // FileStorePath is only relevant if also using file.NewStoreFactory(..) in code + // when creating your MessageStoreFactory for your initiator or acceptor. + // + // Required: Only if using files as your MessageStore + // + // Default: N/A + // + // Valid Values: + // - A valid path + FileStorePath string = "FileStorePath" + + // FileStoreSync controls whether the FileStore syncs to the hard drive on every write. + // It's safer to sync, but it's also much slower. + // FileStoreSync is only relevant if also using file.NewStoreFactory(..) in code + // when creating your MessageStoreFactory for your initiator or acceptor. + // + // Required: No + // + // Default: Y + // + // Valid Values: + // - Y + // - N + FileStoreSync string = "FileStoreSync" + + // SQLStoreDriver sets the name of the database driver to use (see https://go.dev/wiki/SQLDrivers for the list of available drivers). + // SQLStoreDriver is only relevant if also using sql.NewStoreFactory(..) in code + // when creating your MessageStoreFactory for your initiator or acceptor. + // + // Required: Only if using a sql db as your MessageStore + // + // Default: N/A + // + // Valid Values: + // - See https://go.dev/wiki/SQLDrivers + SQLStoreDriver string = "SQLStoreDriver" + + // SQLStoreDataSourceName sets the driver-specific data source name of the database to use. + // This usually consists of at least a database name and connection information. + // SQLStoreDataSourceName is only relevant if also using sql.NewStoreFactory(..) in code + // when creating your MessageStoreFactory for your initiator or acceptor. + // + // See https://pkg.go.dev/database/sql#Open for more information. + // + // Required: Only if using a sql db as your MessageStore + // + // Default: N/A + // + // Valid Values: + // - A string correspondinng to a datasource + SQLStoreDataSourceName string = "SQLStoreDataSourceName" + + // SQLStoreConnMaxLifetime sets the maximum duration of time that a database connection may be reused. + // See https://pkg.go.dev/database/sql#DB.SetConnMaxLifetime for more information. + // + // If your database server has a config option to close inactive connections after some duration (e.g. MySQL "wait_timeout"), + // set SQLStoreConnMaxLifetime to a value less than that duration. + // + // Example Values: + // - SQLStoreConnMaxLifetime=14400s # 14400 seconds + // - SQLStoreConnMaxLifetime=2h45m # 2 hours and 45 minutes + // + // SQLStoreConnMaxLifetime is only relevant if also using sql.NewStoreFactory(..) in code + // when creating your MessageStoreFactory for your initiator or acceptor. + // + // Required: No + // + // Default: 0 (forever) + // + // Valid Values: + // - A valid go time.Duration + SQLStoreConnMaxLifetime string = "SQLStoreConnMaxLifetime" + + // MongoStoreConnection sets the MongoDB connection URL to use. + // + // See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Connect for more information. + // + // MongoStoreConnection is only relevant if also using mongo.NewStoreFactory(..) in code + // when creating your MessageStoreFactory for your initiator or acceptor. + // + // Required: Only if using MongoDB as your MessageStore + // + // Default: N/A + // + // Valid Values: + // - A string representing a MongoDB connection + MongoStoreConnection string = "MongoStoreConnection" + + // MongoStoreDatabase sets the MongoDB-specific name of the database to use. + // + // See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Connect for more information. + // + // MongoStoreDatabase is only relevant if also using mongo.NewStoreFactory(..) in code + // when creating your MessageStoreFactory for your initiator or acceptor. + // + // Required: Only if using MongoDB as your MessageStore + // + // Default: N/A + // + // Valid Values: + // - A string corresponding to a MongoDB database + MongoStoreDatabase string = "MongoStoreDatabase" + + // MongoStoreReplicaSet sets the MongoDB replica set to use. + // This is optional. + // + // See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Connect for more information. + // + // MongoStoreReplicaSet is only relevant if also using mongo.NewStoreFactory(..) in code + // when creating your MessageStoreFactory for your initiator or acceptor. + // + // Required: No + // + // Default: N/A + // + // Valid Values: + // - A string corresponding to a MongoDB replica set + MongoStoreReplicaSet string = "MongoStoreReplicaSet" +) + +const ( + // Misc settings. + + // ResendRequestChunkSize is a setting to limit the size of a resend request in case of missing messages. + // This is useful when the remote FIX engine does not allow to ask for more than messages for a ResendRequest. + // + // I.e. if the ResendRequestChunkSize is set to 5 and a gap of 7 messages is detected, a first resend request will be sent for 5 messages. + // When this gap has been filled, another resend request for 2 messages will be sent. + // If the ResendRequestChunkSize is set to 0, only one ResendRequest for all the missing messages will be sent. + // + // Required: No + // + // Default: 0 (do not chunk resends) + // + // Valid Values: + // - A positive integer + ResendRequestChunkSize string = "ResendRequestChunkSize" + + // EnableLastMsgSeqNumProcessed tells the FIX engine to add the last message sequence number processed + // to outgoing message headers (using optional tag 369). + // + // Required: No + // + // Default: N + // + // Valid Values: + // - Y + // - N EnableLastMsgSeqNumProcessed string = "EnableLastMsgSeqNumProcessed" - CheckLatency string = "CheckLatency" - TimeStampPrecision string = "TimeStampPrecision" - MaxLatency string = "MaxLatency" - PersistMessages string = "PersistMessages" - RejectInvalidMessage string = "RejectInvalidMessage" - AllowUnknownMessageFields string = "AllowUnknownMsgFields" - CheckUserDefinedFields string = "ValidateUserDefinedFields" - DynamicSessions string = "DynamicSessions" - DynamicQualifier string = "DynamicQualifier" ) diff --git a/config/doc.go b/config/doc.go deleted file mode 100644 index 911a4458a..000000000 --- a/config/doc.go +++ /dev/null @@ -1,409 +0,0 @@ -/* -Package config declares application and session settings for QuickFIX/Go - -# BeginString - -Version of FIX this session should use. Valid values: - - FIXT.1.1 - FIX.4.4 - FIX.4.3 - FIX.4.2 - FIX.4.1 - FIX.4.0 - -# SenderCompID - -Your ID as associated with this FIX session. Value is case-sensitive alpha-numeric string. - -# SenderSubID - -(Optional) Your subID as associated with this FIX session. Value is case-sensitive alpha-numeric string. - -# SenderLocationID - -(Optional) Your locationID as associated with this FIX session. Value is case-sensitive alpha-numeric string. - -# TargetCompID - -Counter parties ID as associated with this FIX session. Value is case-sensitive alpha-numeric string. - -# TargetSubID - -(Optional) Counterparty's subID as associated with this FIX session. Value is case-sensitive alpha-numeric string. - -# TargetLocationID - -(Optional) Counterparty's locationID as associated with this FIX session. Value is case-sensitive alpha-numeric string. - -# SessionQualifier - -Additional qualifier to disambiguate otherwise identical sessions. Value is case-sensitive alpha-numeric string. - -# DefaultApplVerID - -Required only for FIXT 1.1 (and newer). Ignored for earlier transport versions. Specifies the default application version ID for the session. This can either be the ApplVerID enum (see the ApplVerID field) or the BeginString for the default version. Valid Values: - - FIX.5.0SP2 - FIX.5.0SP1 - FIX.5.0 - FIX.4.4 - FIX.4.3 - FIX.4.2 - FIX.4.1 - FIX.4.0 - 9 - 8 - 7 - 6 - 5 - 4 - 3 - 2 - -# TimeZone - -Time zone for this session; if specified, the session start and end will be converted from this zone to UTC. Valid Values: - - IANA Time zone ID (America/New_York, Asia/Tokyo, Europe/London, etc.) - Local (The Zone on host) - -Defaults to UTC. - -# StartTime - -Time of day that this FIX session becomes activated. Valid Values: - - time in the format of HH:MM:SS, time is represented in time zone configured by TimeZone - -# EndTime - -Time of day that this FIX session becomes deactivated. Valid Values: - - time in the format of HH:MM:SS, time is represented in time zone configured by TimeZone - -# StartDay - -For week long sessions, the starting day of week for the session. Use in combination with StartTime. Incompatible with Weekdays. Valid Values: - - Full day of week in English, or 3 letter abbreviation (i.e. Monday and Mon are valid) - -# EndDay - -For week long sessions, the ending day of week for the session. Use in combination with EndTime. Incompatible with Weekdays. Valid Values: - - Full day of week in English, or 3 letter abbreviation (i.e. Monday and Mon are valid) - -# Weekdays - -For daily sessions that are only active on specific days of the week. Use in combination with StartTime and EndTime. Incompatible with StartDay and EndDay. Valid Values: - - Comma delimited list of days of the week in English, or 3 letter abbreviation (e.g. "Monday,Tuesday,Wednesday" or "Mon,Tue,Wed" would both be valid values). - -# EnableLastMsgSeqNumProcessed - -Add the last message sequence number processed in the header (optional tag 369). Valid Values: - - Y - N - -Defaults to N. - -# ResendRequestChunkSize - -Setting to limit the size of a resend request in case of missing messages. This is useful when the remote FIX engine does not allow to ask for more than n message for a ResendRequest. E.g. if the ResendRequestChunkSize is set to 5 and a gap of 7 messages is detected, a first resend request will be sent for 5 messages. When this gap has been filled, another resend request for 2 messages will be sent. If the ResendRequestChunkSize is set to 0, only one ResendRequest for all the missing messages will be sent. Value must be positive integer. Defaults to 0 (disables splitting). - -# ResetOnLogon - -Determines if sequence numbers should be reset when receiving a logon request. Acceptors only. Valid Values: - - Y - N - -Defaults to N. - -# ResetOnLogout - -Determines if sequence numbers should be reset to 1 after a normal logout termination. Valid Values: - - Y - N - -Defaults to N. - -# ResetOnDisconnect - -Determines if sequence numbers should be reset to 1 after an abnormal termination. Valid Values: - - Y - N - -Defaults to N. - -# RefreshOnLogon - -Determines if session state should be restored from persistence layer when logging on. Useful for creating hot failover sessions. Valid Values: - - Y - N - -Defaults to N. - -# TimeStampPrecision - -Determines precision for timestamps in (Orig)SendingTime fields that are sent out. Only available for FIX.4.2 and greater, FIX versions earlier than FIX.4.2 will use timestamp resolution in seconds. Valid Values: - - SECONDS - MILLIS - MICROS - NANOS - -Defaults to MILLIS. - -# Validation - -The following settings are specific to message validation. - -# DataDictionary - -XML definition file for validating incoming FIX messages. If no DataDictionary is supplied, only basic message validation will be done. - -This setting should only be used with FIX transport versions older than FIXT.1.1. See TransportDataDictionary and AppDataDictionary for FIXT.1.1 settings. Value must be a valid XML data dictionary file. QuickFIX/Go comes with the following defaults in the spec directory - - FIX44.xml - FIX43.xml - FIX42.xml - FIX41.xml - FIX40.xml - -# TransportDataDictionary - -XML definition file for validating admin (transport) messages. This setting is only valid for FIXT.1.1 (or newer) sessions. See DataDictionary for older transport versions (FIX.4.0 - FIX.4.4) for additional information. Value must be a valid XML data dictionary file, QuickFIX/Go comes with the following defaults in the spec directory - - FIXT1.1.xml - -# AppDataDictionary - -XML definition file for validating application messages. This setting is only valid for FIXT.1.1 (or newer) sessions. See DataDictionary for older transport versions (FIX.4.0 - FIX.4.4) for additional information. - -This setting supports the possibility of a custom application data dictionary for each session. This setting would only be used with FIXT 1.1 and new transport protocols. This setting can be used as a prefix to specify multiple application dictionaries for the FIXT transport. For example: - - DefaultApplVerID=FIX.4.2 - # For default application version ID - AppDataDictionary=FIX42.xml - # For nondefault application version ID - # Use BeginString suffix for app version - AppDataDictionary.FIX.4.4=FIX44.xml - -Value must be a valid XML data dictionary file. QuickFIX/Go comes with the following defaults in the spec directory - - FIX50SP2.xml - FIX50SP1.xml - FIX50.xml - FIX44.xml - FIX43.xml - FIX42.xml - FIX41.xml - FIX40.xml - -# ValidateFieldsOutOfOrder - -If set to N, fields that are out of order (i.e. body fields in the header, or header fields in the body) will not be rejected. Useful for connecting to systems which do not properly order fields. Valid Values: - - Y - N - -Defaults to Y. - -# RejectInvalidMessage - -If RejectInvalidMessage is set to N, zero errors will be thrown on reception of message that fails data dictionary validation. Valid Values: - - Y - N - -Defaults to Y. - -# CheckLatency - -If set to Y, messages must be received from the counterparty 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. Valid Values: - - Y - N - -Defaults to Y. - -# MaxLatency - -If CheckLatency is set to Y, this defines the number of seconds latency allowed for a message to be processed. Value must be positive integer. - -Defaults to 120. - -# ReconnectInterval - -Time between reconnection attempts in seconds. Only used for initiators. Value must be positive integer. - -# Defaults to 30 - -# LogoutTimeout - -Session setting for logout timeout in seconds. Only used for initiators. Value must be positive integer. - -# Defaults to 2 - -# LogonTimeout - -Session setting for logon timeout in seconds. Only used for initiators. Value must be positive integer. - -# Defaults to 10 - -# HeartBtInt - -Heartbeat interval in seconds. Only used for initiators (unless HeartBtIntOverride is Y). Value must be positive integer. - -# HeartBtIntOverride - -If set to Y, will use the HeartBtInt interval rather than what the initiator dictates. Only used for acceptors. Valid Values: - - Y - N - -Defaults to N. - -# SocketConnectPort - -Socket port for connecting to a session. Only used for initiators. Must be positive integer - -# SocketConnectHost - -Host to connect to. Only used for initiators. Value must be a valid IPv4 or IPv6 address or a domain name - -SocketConnectPort - -Alternate socket ports for connecting to a session for failover, where n is a positive integer. (i.e.) SocketConnectPort1, SocketConnectPort2... must be consecutive and have a matching SocketConnectHost[n]. Value must be a positive integer. - -SocketConnectHost - -Alternate socket hosts for connecting to a session for failover, where n is a positive integer. (i.e.) SocketConnectHost1, SocketConnectHost2... must be consecutive and have a matching SocketConnectPort[n]. Value must be a valid IPv4 or IPv6 address or a domain name - -# SocketTimeout - -Duration of timeout for TLS handshake. Only used for initiators. - -Example Values: - - SocketTimeout=30s # 30 seconds - SocketTimeout=60m # 60 minutes - -Defaults to 0(means nothing timeout). - -# SocketAcceptHost - -Socket host address for listening on incoming connections, only used for acceptors. By default acceptors listen on all available interfaces. - -# SocketAcceptPort - -Socket port for listening to incoming connections, only used for acceptors. Value must be a positive integer, valid open socket port. - -# SocketPrivateKeyFile - -Private key to use for secure TLS connections. Must be used with SocketCertificateFile. - -# SocketCertificateFile - -Certificate to use for secure TLS connections. Must be used with SocketPrivateKeyFile. - -# SocketCAFile - -Optional root CA to use for secure TLS connections. For acceptors, client certificates will be verified against this CA. For initiators, clients will use the CA to verify the server certificate. If not configurated, initiators will verify the server certificate using the host's root CA set. - -# SocketServerName - -The expected server name on a returned certificate, unless SocketInsecureSkipVerify is true. This is for the TLS Server Name Indication extension. Initiator only. - -# SocketMinimumTLSVersion - -Specify the Minimum TLS version to use when creating a secure connection. The valid choices are SSL30, TLS10, TLS11, TLS12. Defaults to TLS12. - -# SocketUseSSL - -Use SSL for initiators even if client certificates are not present. If set to N or omitted, TLS will not be used if SocketPrivateKeyFile or SocketCertificateFile are not supplied. - -# ProxyType - -Proxy type. Valid Values: - - socks - -# ProxyHost - -# Proxy server IP address in the format of x.x.x.x or a domain name - -# ProxyPort - -# Proxy server port - -# ProxyUser - -# Proxy user - -# ProxyPassword - -# Proxy password - -# UseTCPProxy - -Use TCP proxy for servers listening behind HAProxy of Amazon ELB load balancers. The server can then receive the address of the client instead of the load balancer's. Valid Values: - - Y - N - -# PersistMessages - -If set to N, no messages will be persisted. This will force QuickFIX/Go to always send GapFills instead of resending messages. Use this if you know you never want to resend a message. Useful for market data streams. Valid Values: - - Y - N - -Defaults to Y. - -# FileLogPath - -Directory to store logs. Value must be valid directory for storing files, application must have write access. - -# FileStorePath - -Directory to store sequence number and message files. Only used with FileStoreFactory. - -# MongoStoreConnection - -The MongoDB connection URL to use (see https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Connect for more info). Only used with MongoStoreFactory. - -# MongoStoreDatabase - -The MongoDB-specific name of the database to use. Only used with MongoStoreFactory. - -# MongoStoreReplicaSet - -The MongoDB-specific name of the replica set to use. Optional, only used with MongoStoreFactory. - -# SQLStoreDriver - -The name of the database driver to use (see https://github.com/golang/go/wiki/SQLDrivers for the list of available drivers). Only used with SqlStoreFactory. - -# SQLStoreDataSourceName - -The driver-specific data source name of the database to use. Only used with SqlStoreFactory. - -# SQLStoreConnMaxLifetime - -SetConnMaxLifetime sets the maximum duration of time that a database connection may be reused (see https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime). Defaults to zero, which causes connections to be reused forever. Only used with SqlStoreFactory. - -If your database server has a config option to close inactive connections after some duration (e.g. MySQL "wait_timeout"), set SQLConnMaxLifetime to a value less than that duration. - -Example Values: - - SQLConnMaxLifetime=14400s # 14400 seconds - SQLConnMaxLifetime=2h45m # 2 hours and 45 minutes -*/ -package config From 1c2db3a7131eb08f7e5294e46fb7a3d2aa872c57 Mon Sep 17 00:00:00 2001 From: Michael Ackley Date: Wed, 3 Jul 2024 15:42:19 -0500 Subject: [PATCH 30/58] Adds resendreqchunksize and lastseqnumprocessed test suites, refactors test runners --- .github/workflows/ci.yaml | 218 ++++++++++++++++-- Makefile | 22 +- _test/cfg/lastseqnumprocessed/fix42.cfg | 11 + _test/cfg/lastseqnumprocessed/fix43.cfg | 11 + _test/cfg/lastseqnumprocessed/fix44.cfg | 11 + _test/cfg/lastseqnumprocessed/fix50.cfg | 14 ++ _test/cfg/lastseqnumprocessed/fix50sp1.cfg | 14 ++ _test/cfg/lastseqnumprocessed/fix50sp2.cfg | 14 ++ _test/cfg/resendreqchunksize/fix40.cfg | 11 + _test/cfg/resendreqchunksize/fix41.cfg | 11 + _test/{ => cfg/resendreqchunksize}/fix42.cfg | 1 + _test/cfg/resendreqchunksize/fix43.cfg | 11 + _test/cfg/resendreqchunksize/fix44.cfg | 11 + _test/cfg/resendreqchunksize/fix50.cfg | 14 ++ _test/cfg/resendreqchunksize/fix50sp1.cfg | 14 ++ _test/cfg/resendreqchunksize/fix50sp2.cfg | 14 ++ _test/{ => cfg/server}/fix40.cfg | 0 _test/{ => cfg/server}/fix41.cfg | 0 _test/cfg/server/fix42.cfg | 10 + _test/{ => cfg/server}/fix43.cfg | 0 _test/{ => cfg/server}/fix44.cfg | 0 _test/{ => cfg/server}/fix50.cfg | 0 _test/{ => cfg/server}/fix50sp1.cfg | 0 _test/{ => cfg/server}/fix50sp2.cfg | 0 .../fix42/LastProcessedMsgSeqNum.def | 21 ++ .../fix43/LastProcessedMsgSeqNum.def | 21 ++ .../fix44/LastProcessedMsgSeqNum.def | 21 ++ .../fix50/LastProcessedMsgSeqNum.def | 21 ++ .../fix50sp1/LastProcessedMsgSeqNum.def | 21 ++ .../fix50sp2/LastProcessedMsgSeqNum.def | 21 ++ .../SequenceGapFollowedByMessageResent.def | 37 +++ ...eGapFollowedBySequenceResetWithGapFill.def | 41 ++++ .../SequenceGapFollowedByMessageResent.def | 37 +++ ...eGapFollowedBySequenceResetWithGapFill.def | 41 ++++ .../SequenceGapFollowedByMessageResent.def | 37 +++ ...eGapFollowedBySequenceResetWithGapFill.def | 41 ++++ .../SequenceGapFollowedByMessageResent.def | 37 +++ ...eGapFollowedBySequenceResetWithGapFill.def | 41 ++++ .../SequenceGapFollowedByMessageResent.def | 37 +++ ...eGapFollowedBySequenceResetWithGapFill.def | 41 ++++ .../SequenceGapFollowedByMessageResent.def | 37 +++ ...eGapFollowedBySequenceResetWithGapFill.def | 41 ++++ .../SequenceGapFollowedByMessageResent.def | 37 +++ ...eGapFollowedBySequenceResetWithGapFill.def | 41 ++++ .../SequenceGapFollowedByMessageResent.def | 37 +++ ...eGapFollowedBySequenceResetWithGapFill.def | 41 ++++ resend_state.go | 16 ++ 47 files changed, 1157 insertions(+), 21 deletions(-) create mode 100644 _test/cfg/lastseqnumprocessed/fix42.cfg create mode 100644 _test/cfg/lastseqnumprocessed/fix43.cfg create mode 100644 _test/cfg/lastseqnumprocessed/fix44.cfg create mode 100644 _test/cfg/lastseqnumprocessed/fix50.cfg create mode 100644 _test/cfg/lastseqnumprocessed/fix50sp1.cfg create mode 100644 _test/cfg/lastseqnumprocessed/fix50sp2.cfg create mode 100644 _test/cfg/resendreqchunksize/fix40.cfg create mode 100644 _test/cfg/resendreqchunksize/fix41.cfg rename _test/{ => cfg/resendreqchunksize}/fix42.cfg (87%) create mode 100644 _test/cfg/resendreqchunksize/fix43.cfg create mode 100644 _test/cfg/resendreqchunksize/fix44.cfg create mode 100644 _test/cfg/resendreqchunksize/fix50.cfg create mode 100644 _test/cfg/resendreqchunksize/fix50sp1.cfg create mode 100644 _test/cfg/resendreqchunksize/fix50sp2.cfg rename _test/{ => cfg/server}/fix40.cfg (100%) rename _test/{ => cfg/server}/fix41.cfg (100%) create mode 100644 _test/cfg/server/fix42.cfg rename _test/{ => cfg/server}/fix43.cfg (100%) rename _test/{ => cfg/server}/fix44.cfg (100%) rename _test/{ => cfg/server}/fix50.cfg (100%) rename _test/{ => cfg/server}/fix50sp1.cfg (100%) rename _test/{ => cfg/server}/fix50sp2.cfg (100%) create mode 100644 _test/definitions/lastseqnumprocessed/fix42/LastProcessedMsgSeqNum.def create mode 100644 _test/definitions/lastseqnumprocessed/fix43/LastProcessedMsgSeqNum.def create mode 100644 _test/definitions/lastseqnumprocessed/fix44/LastProcessedMsgSeqNum.def create mode 100644 _test/definitions/lastseqnumprocessed/fix50/LastProcessedMsgSeqNum.def create mode 100644 _test/definitions/lastseqnumprocessed/fix50sp1/LastProcessedMsgSeqNum.def create mode 100644 _test/definitions/lastseqnumprocessed/fix50sp2/LastProcessedMsgSeqNum.def create mode 100644 _test/definitions/resendreqchunksize/fix40/SequenceGapFollowedByMessageResent.def create mode 100644 _test/definitions/resendreqchunksize/fix40/SequenceGapFollowedBySequenceResetWithGapFill.def create mode 100644 _test/definitions/resendreqchunksize/fix41/SequenceGapFollowedByMessageResent.def create mode 100644 _test/definitions/resendreqchunksize/fix41/SequenceGapFollowedBySequenceResetWithGapFill.def create mode 100644 _test/definitions/resendreqchunksize/fix42/SequenceGapFollowedByMessageResent.def create mode 100644 _test/definitions/resendreqchunksize/fix42/SequenceGapFollowedBySequenceResetWithGapFill.def create mode 100644 _test/definitions/resendreqchunksize/fix43/SequenceGapFollowedByMessageResent.def create mode 100644 _test/definitions/resendreqchunksize/fix43/SequenceGapFollowedBySequenceResetWithGapFill.def create mode 100644 _test/definitions/resendreqchunksize/fix44/SequenceGapFollowedByMessageResent.def create mode 100644 _test/definitions/resendreqchunksize/fix44/SequenceGapFollowedBySequenceResetWithGapFill.def create mode 100644 _test/definitions/resendreqchunksize/fix50/SequenceGapFollowedByMessageResent.def create mode 100644 _test/definitions/resendreqchunksize/fix50/SequenceGapFollowedBySequenceResetWithGapFill.def create mode 100644 _test/definitions/resendreqchunksize/fix50sp1/SequenceGapFollowedByMessageResent.def create mode 100644 _test/definitions/resendreqchunksize/fix50sp1/SequenceGapFollowedBySequenceResetWithGapFill.def create mode 100644 _test/definitions/resendreqchunksize/fix50sp2/SequenceGapFollowedByMessageResent.def create mode 100644 _test/definitions/resendreqchunksize/fix50sp2/SequenceGapFollowedBySequenceResetWithGapFill.def diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4d58289a2..a1ff334be 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,18 +31,118 @@ jobs: version: v1.57.2 build: - name: build + name: Build Source + runs-on: ubuntu-latest + strategy: + matrix: + go: [1.21] + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Check out source + uses: actions/checkout@v2 + - name: Build + run: make build-src + + unittest: + name: Unit Tests + runs-on: ubuntu-latest + strategy: + matrix: + go: [1.21] + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Check out source + uses: actions/checkout@v2 + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.8.0 + with: + mongodb-replica-set: replicaset + - name: Unit test + env: + MONGODB_TEST_CXN: mongodb://localhost:27017 + run: make test-ci + + servermem: + name: Server MemoryStore Suite + runs-on: ubuntu-latest + strategy: + matrix: + go: [1.21] + fix-version: + - fix40 + - fix41 + - fix42 + - fix43 + - fix44 + - fix50 + - fix50sp1 + - fix50sp2 + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Check out source + uses: actions/checkout@v2 + - name: Install ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' + - name: Acceptance test + env: + GO111MODULE: on + FIX_TEST: ${{ matrix.fix-version }} + STORE_TYPE: memory + ACCEPTANCE_SET: server + run: make generate-ci && make build && make $FIX_TEST + + serverfile: + name: Server FileStore Suite + runs-on: ubuntu-latest + strategy: + matrix: + go: [1.21] + fix-version: + - fix40 + - fix41 + - fix42 + - fix43 + - fix44 + - fix50 + - fix50sp1 + - fix50sp2 + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Check out source + uses: actions/checkout@v2 + - name: Install ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' + - name: Acceptance test + env: + GO111MODULE: on + FIX_TEST: ${{ matrix.fix-version }} + STORE_TYPE: file + ACCEPTANCE_SET: server + run: make generate-ci && make build && make $FIX_TEST + + servermongo: + name: Server MongoStore Suite runs-on: ubuntu-latest strategy: matrix: go: [1.21] - store-type: - - - - memory - - file - - mongo fix-version: - - - fix40 - fix41 - fix42 @@ -66,15 +166,107 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.0' - - name: Unit test + - name: Acceptance test + env: + GO111MODULE: on + MONGODB_TEST_CXN: mongodb://localhost:27017 + FIX_TEST: ${{ matrix.fix-version }} + STORE_TYPE: mongo + ACCEPTANCE_SET: server + run: make generate-ci && make build && make $FIX_TEST + + resendreqchunksize: + name: ResendRequestChunkSize Suite + runs-on: ubuntu-latest + strategy: + matrix: + go: [1.21] + fix-version: + - fix40 + - fix41 + - fix42 + - fix43 + - fix44 + - fix50 + - fix50sp1 + - fix50sp2 + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Check out source + uses: actions/checkout@v2 + - name: Install ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' + - name: Acceptance test env: + GO111MODULE: on FIX_TEST: ${{ matrix.fix-version }} - STORE_TYPE: ${{ matrix.store-type }} - run: if [ -z $FIX_TEST ] && [ -z $STORE_TYPE ]; then make build-src && make test-ci; fi + STORE_TYPE: memory + ACCEPTANCE_SET: resendreqchunksize + run: make generate-ci && make build && make $FIX_TEST + + lastseqnumprocessed: + name: LastSeqNumProcessed Suite + runs-on: ubuntu-latest + strategy: + matrix: + go: [1.21] + fix-version: + - fix42 + - fix43 + - fix44 + - fix50 + - fix50sp1 + - fix50sp2 + steps: + - name: Setup + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Check out source + uses: actions/checkout@v2 + - name: Install ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' - name: Acceptance test env: GO111MODULE: on - MONGODB_TEST_CXN: mongodb://localhost:27017 FIX_TEST: ${{ matrix.fix-version }} - STORE_TYPE: ${{ matrix.store-type }} - run: if [ $FIX_TEST ] && [ $STORE_TYPE ]; then make generate-ci && make build && make $FIX_TEST; fi + STORE_TYPE: memory + ACCEPTANCE_SET: lastseqnumprocessed + run: make generate-ci && make build && make $FIX_TEST + + # nextexpectedseqnum: + # name: NextExpectedSeqNum Suite + # runs-on: ubuntu-latest + # strategy: + # matrix: + # go: [1.21] + # fix-version: + # - fix44 + # - fix50 + # - fix50sp1 + # - fix50sp2 + # steps: + # - name: Setup + # uses: actions/setup-go@v2 + # with: + # go-version: ${{ matrix.go }} + # - name: Check out source + # uses: actions/checkout@v2 + # - name: Install ruby + # uses: ruby/setup-ruby@v1 + # with: + # ruby-version: '3.0' + # - name: Acceptance test + # env: + # GO111MODULE: on + # FIX_TEST: ${{ matrix.fix-version }} + # STORE_TYPE: memory + # ACCEPTANCE_SET: nextexpectedseqnum + # run: make generate-ci && make build && make $FIX_TEST \ No newline at end of file diff --git a/Makefile b/Makefile index 75a60a66e..8d3c2c6cc 100644 --- a/Makefile +++ b/Makefile @@ -38,24 +38,30 @@ else STORE := memory endif +ifdef ACCEPTANCE_SET +TEST_SET := $(ACCEPTANCE_SET) +else +TEST_SET := server +endif + build-test-srv: cd _test; go build -v -o echo_server ./test-server/ fix40: - cd _test; ./runat.sh $@.cfg 5001 $(STORE) "definitions/server/$@/*.def" + cd _test; ./runat.sh cfg/$(TEST_SET)/$@.cfg 5001 $(STORE) "definitions/$(TEST_SET)/$@/*.def" fix41: - cd _test; ./runat.sh $@.cfg 5002 $(STORE) "definitions/server/$@/*.def" + cd _test; ./runat.sh cfg/$(TEST_SET)/$@.cfg 5002 $(STORE) "definitions/$(TEST_SET)/$@/*.def" fix42: - cd _test; ./runat.sh $@.cfg 5003 $(STORE) "definitions/server/$@/*.def" + cd _test; ./runat.sh cfg/$(TEST_SET)/$@.cfg 5003 $(STORE) "definitions/$(TEST_SET)/$@/*.def" fix43: - cd _test; ./runat.sh $@.cfg 5004 $(STORE) "definitions/server/$@/*.def" + cd _test; ./runat.sh cfg/$(TEST_SET)/$@.cfg 5004 $(STORE) "definitions/$(TEST_SET)/$@/*.def" fix44: - cd _test; ./runat.sh $@.cfg 5005 $(STORE) "definitions/server/$@/*.def" + cd _test; ./runat.sh cfg/$(TEST_SET)/$@.cfg 5005 $(STORE) "definitions/$(TEST_SET)/$@/*.def" fix50: - cd _test; ./runat.sh $@.cfg 5006 $(STORE) "definitions/server/$@/*.def" + cd _test; ./runat.sh cfg/$(TEST_SET)/$@.cfg 5006 $(STORE) "definitions/$(TEST_SET)/$@/*.def" fix50sp1: - cd _test; ./runat.sh $@.cfg 5007 $(STORE) "definitions/server/$@/*.def" + cd _test; ./runat.sh cfg/$(TEST_SET)/$@.cfg 5007 $(STORE) "definitions/$(TEST_SET)/$@/*.def" fix50sp2: - cd _test; ./runat.sh $@.cfg 5008 $(STORE) "definitions/server/$@/*.def" + cd _test; ./runat.sh cfg/$(TEST_SET)/$@.cfg 5008 $(STORE) "definitions/$(TEST_SET)/$@/*.def" ACCEPT_SUITE=fix40 fix41 fix42 fix43 fix44 fix50 fix50sp1 fix50sp2 accept: $(ACCEPT_SUITE) diff --git a/_test/cfg/lastseqnumprocessed/fix42.cfg b/_test/cfg/lastseqnumprocessed/fix42.cfg new file mode 100644 index 000000000..1ed8e572a --- /dev/null +++ b/_test/cfg/lastseqnumprocessed/fix42.cfg @@ -0,0 +1,11 @@ +[DEFAULT] +SocketAcceptPort=5003 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIX.4.2 +DataDictionary=../spec/FIX42.xml +EnableLastMsgSeqNumProcessed=Y \ No newline at end of file diff --git a/_test/cfg/lastseqnumprocessed/fix43.cfg b/_test/cfg/lastseqnumprocessed/fix43.cfg new file mode 100644 index 000000000..4675e6b5d --- /dev/null +++ b/_test/cfg/lastseqnumprocessed/fix43.cfg @@ -0,0 +1,11 @@ +[DEFAULT] +SocketAcceptPort=5004 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIX.4.3 +DataDictionary=../spec/FIX43.xml +EnableLastMsgSeqNumProcessed=Y \ No newline at end of file diff --git a/_test/cfg/lastseqnumprocessed/fix44.cfg b/_test/cfg/lastseqnumprocessed/fix44.cfg new file mode 100644 index 000000000..0f3cc8c24 --- /dev/null +++ b/_test/cfg/lastseqnumprocessed/fix44.cfg @@ -0,0 +1,11 @@ +[DEFAULT] +SocketAcceptPort=5005 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIX.4.4 +DataDictionary=../spec/FIX44.xml +EnableLastMsgSeqNumProcessed=Y \ No newline at end of file diff --git a/_test/cfg/lastseqnumprocessed/fix50.cfg b/_test/cfg/lastseqnumprocessed/fix50.cfg new file mode 100644 index 000000000..f3c7c37ad --- /dev/null +++ b/_test/cfg/lastseqnumprocessed/fix50.cfg @@ -0,0 +1,14 @@ +[DEFAULT] +SocketAcceptPort=5006 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIXT.1.1 +SessionQualifier=FIX50 +DefaultApplVerID=FIX.5.0 +TransportDataDictionary=../spec/FIXT11.xml +AppDataDictionary=../spec/FIX50.xml +EnableLastMsgSeqNumProcessed=Y \ No newline at end of file diff --git a/_test/cfg/lastseqnumprocessed/fix50sp1.cfg b/_test/cfg/lastseqnumprocessed/fix50sp1.cfg new file mode 100644 index 000000000..4757e9e93 --- /dev/null +++ b/_test/cfg/lastseqnumprocessed/fix50sp1.cfg @@ -0,0 +1,14 @@ +[DEFAULT] +SocketAcceptPort=5007 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIXT.1.1 +SessionQualifier=FIX50SP1 +DefaultApplVerID=FIX.5.0SP1 +TransportDataDictionary=../spec/FIXT11.xml +AppDataDictionary=../spec/FIX50SP1.xml +EnableLastMsgSeqNumProcessed=Y \ No newline at end of file diff --git a/_test/cfg/lastseqnumprocessed/fix50sp2.cfg b/_test/cfg/lastseqnumprocessed/fix50sp2.cfg new file mode 100644 index 000000000..1ae2c09b1 --- /dev/null +++ b/_test/cfg/lastseqnumprocessed/fix50sp2.cfg @@ -0,0 +1,14 @@ +[DEFAULT] +SocketAcceptPort=5008 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIXT.1.1 +SessionQualifier=FIX50SP2 +DefaultApplVerID=FIX.5.0SP2 +TransportDataDictionary=../spec/FIXT11.xml +AppDataDictionary=../spec/FIX50SP2.xml +EnableLastMsgSeqNumProcessed=Y \ No newline at end of file diff --git a/_test/cfg/resendreqchunksize/fix40.cfg b/_test/cfg/resendreqchunksize/fix40.cfg new file mode 100644 index 000000000..a2b8210f2 --- /dev/null +++ b/_test/cfg/resendreqchunksize/fix40.cfg @@ -0,0 +1,11 @@ +[DEFAULT] +SocketAcceptPort=5001 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIX.4.0 +DataDictionary=../spec/FIX40.xml +ResendRequestChunkSize=5 \ No newline at end of file diff --git a/_test/cfg/resendreqchunksize/fix41.cfg b/_test/cfg/resendreqchunksize/fix41.cfg new file mode 100644 index 000000000..07ad38aa4 --- /dev/null +++ b/_test/cfg/resendreqchunksize/fix41.cfg @@ -0,0 +1,11 @@ +[DEFAULT] +SocketAcceptPort=5002 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIX.4.1 +DataDictionary=../spec/FIX41.xml +ResendRequestChunkSize=5 \ No newline at end of file diff --git a/_test/fix42.cfg b/_test/cfg/resendreqchunksize/fix42.cfg similarity index 87% rename from _test/fix42.cfg rename to _test/cfg/resendreqchunksize/fix42.cfg index 3bbfe829f..b6bdb2318 100644 --- a/_test/fix42.cfg +++ b/_test/cfg/resendreqchunksize/fix42.cfg @@ -8,3 +8,4 @@ FileLogPath=tmp [SESSION] BeginString=FIX.4.2 DataDictionary=../spec/FIX42.xml +ResendRequestChunkSize=5 \ No newline at end of file diff --git a/_test/cfg/resendreqchunksize/fix43.cfg b/_test/cfg/resendreqchunksize/fix43.cfg new file mode 100644 index 000000000..875ac7d1d --- /dev/null +++ b/_test/cfg/resendreqchunksize/fix43.cfg @@ -0,0 +1,11 @@ +[DEFAULT] +SocketAcceptPort=5004 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIX.4.3 +DataDictionary=../spec/FIX43.xml +ResendRequestChunkSize=5 \ No newline at end of file diff --git a/_test/cfg/resendreqchunksize/fix44.cfg b/_test/cfg/resendreqchunksize/fix44.cfg new file mode 100644 index 000000000..ad88660aa --- /dev/null +++ b/_test/cfg/resendreqchunksize/fix44.cfg @@ -0,0 +1,11 @@ +[DEFAULT] +SocketAcceptPort=5005 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIX.4.4 +DataDictionary=../spec/FIX44.xml +ResendRequestChunkSize=5 \ No newline at end of file diff --git a/_test/cfg/resendreqchunksize/fix50.cfg b/_test/cfg/resendreqchunksize/fix50.cfg new file mode 100644 index 000000000..516096b28 --- /dev/null +++ b/_test/cfg/resendreqchunksize/fix50.cfg @@ -0,0 +1,14 @@ +[DEFAULT] +SocketAcceptPort=5006 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIXT.1.1 +SessionQualifier=FIX50 +DefaultApplVerID=FIX.5.0 +TransportDataDictionary=../spec/FIXT11.xml +AppDataDictionary=../spec/FIX50.xml +ResendRequestChunkSize=5 \ No newline at end of file diff --git a/_test/cfg/resendreqchunksize/fix50sp1.cfg b/_test/cfg/resendreqchunksize/fix50sp1.cfg new file mode 100644 index 000000000..b89038936 --- /dev/null +++ b/_test/cfg/resendreqchunksize/fix50sp1.cfg @@ -0,0 +1,14 @@ +[DEFAULT] +SocketAcceptPort=5007 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIXT.1.1 +SessionQualifier=FIX50SP1 +DefaultApplVerID=FIX.5.0SP1 +TransportDataDictionary=../spec/FIXT11.xml +AppDataDictionary=../spec/FIX50SP1.xml +ResendRequestChunkSize=5 \ No newline at end of file diff --git a/_test/cfg/resendreqchunksize/fix50sp2.cfg b/_test/cfg/resendreqchunksize/fix50sp2.cfg new file mode 100644 index 000000000..5bbaed9a3 --- /dev/null +++ b/_test/cfg/resendreqchunksize/fix50sp2.cfg @@ -0,0 +1,14 @@ +[DEFAULT] +SocketAcceptPort=5008 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIXT.1.1 +SessionQualifier=FIX50SP2 +DefaultApplVerID=FIX.5.0SP2 +TransportDataDictionary=../spec/FIXT11.xml +AppDataDictionary=../spec/FIX50SP2.xml +ResendRequestChunkSize=5 \ No newline at end of file diff --git a/_test/fix40.cfg b/_test/cfg/server/fix40.cfg similarity index 100% rename from _test/fix40.cfg rename to _test/cfg/server/fix40.cfg diff --git a/_test/fix41.cfg b/_test/cfg/server/fix41.cfg similarity index 100% rename from _test/fix41.cfg rename to _test/cfg/server/fix41.cfg diff --git a/_test/cfg/server/fix42.cfg b/_test/cfg/server/fix42.cfg new file mode 100644 index 000000000..b81521c27 --- /dev/null +++ b/_test/cfg/server/fix42.cfg @@ -0,0 +1,10 @@ +[DEFAULT] +SocketAcceptPort=5003 +SenderCompID=ISLD +TargetCompID=TW +ResetOnLogon=Y +FileLogPath=tmp + +[SESSION] +BeginString=FIX.4.2 +DataDictionary=../spec/FIX42.xml \ No newline at end of file diff --git a/_test/fix43.cfg b/_test/cfg/server/fix43.cfg similarity index 100% rename from _test/fix43.cfg rename to _test/cfg/server/fix43.cfg diff --git a/_test/fix44.cfg b/_test/cfg/server/fix44.cfg similarity index 100% rename from _test/fix44.cfg rename to _test/cfg/server/fix44.cfg diff --git a/_test/fix50.cfg b/_test/cfg/server/fix50.cfg similarity index 100% rename from _test/fix50.cfg rename to _test/cfg/server/fix50.cfg diff --git a/_test/fix50sp1.cfg b/_test/cfg/server/fix50sp1.cfg similarity index 100% rename from _test/fix50sp1.cfg rename to _test/cfg/server/fix50sp1.cfg diff --git a/_test/fix50sp2.cfg b/_test/cfg/server/fix50sp2.cfg similarity index 100% rename from _test/fix50sp2.cfg rename to _test/cfg/server/fix50sp2.cfg diff --git a/_test/definitions/lastseqnumprocessed/fix42/LastProcessedMsgSeqNum.def b/_test/definitions/lastseqnumprocessed/fix42/LastProcessedMsgSeqNum.def new file mode 100644 index 000000000..1486575da --- /dev/null +++ b/_test/definitions/lastseqnumprocessed/fix42/LastProcessedMsgSeqNum.def @@ -0,0 +1,21 @@ +iCONNECT + +I8=FIX.4.235=A34=149=TW52=