diff --git a/.devcontainer/Dockerfile.dev b/.devcontainer/Dockerfile.dev
new file mode 100644
index 000000000..14c7119a9
--- /dev/null
+++ b/.devcontainer/Dockerfile.dev
@@ -0,0 +1 @@
+FROM mcr.microsoft.com/vscode/devcontainers/go:0-1.18
\ No newline at end of file
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 000000000..2530677e2
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,29 @@
+{
+ "name": "Quickfix/Go Development",
+ "dockerComposeFile": "docker-compose.yml",
+ "service": "app",
+ "workspaceFolder": "/workspace",
+ "shutdownAction": "stopCompose",
+ "runArgs": [
+ "--cap-add=SYS_PTRACE",
+ "--security-opt",
+ "seccomp=unconfined"
+ ],
+ "features": {
+ "ruby": "latest"
+ },
+ "customizations": {
+ "vscode": {
+ "settings": {
+ "go.toolsManagement.checkForUpdates": "local",
+ "go.useLanguageServer": true,
+ "go.gopath": "/go"
+ },
+ "extensions": [
+ "golang.Go",
+ "mongodb.mongodb-vscode"
+ ]
+ }
+ },
+ "remoteUser": "vscode"
+}
\ No newline at end of file
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
new file mode 100644
index 000000000..d8482fa76
--- /dev/null
+++ b/.devcontainer/docker-compose.yml
@@ -0,0 +1,38 @@
+version: '3.8'
+
+services:
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile.dev
+ volumes:
+ - ..:/workspace:cached
+ - /var/run/docker.sock:/var/run/docker.sock
+ # Overrides default command so things don't shut down after the process ends.
+ command: sleep infinity
+
+ # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
+ network_mode: service:db
+
+ # Uncomment the next line to use a non-root user for all processes.
+ # user: node
+
+ # Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
+ # (Adding the "ports" property to this file will not forward from a Codespace.)
+
+ db:
+ image: bitnami/mongodb:latest
+ restart: unless-stopped
+ volumes:
+ - mongodb-data:/data/db
+ ports:
+ - 27017:27017
+ environment:
+ MONGODB_REPLICA_SET_MODE: primary
+ ALLOW_EMPTY_PASSWORD: 'yes'
+
+ # Add "forwardPorts": ["27017"] to **devcontainer.json** to forward MongoDB locally.
+ # (Adding the "ports" property to this file will not forward from a Codespace.)
+
+volumes:
+ mongodb-data:
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..d921d0ffd
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,7 @@
+version: 2
+updates:
+- package-ecosystem: gomod
+ directory: "/"
+ schedule:
+ interval: daily
+ open-pull-requests-limit: 10
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 000000000..709a7468d
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,77 @@
+name: CI
+on:
+ push:
+ tags:
+ - v*
+ branches:
+ - master
+ - main
+ pull_request:
+ branches:
+ - master
+ - main
+permissions:
+ contents: read
+
+jobs:
+ golangci:
+ permissions:
+ contents: read # for actions/checkout to fetch code
+ pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
+ name: Linter
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout source code
+ uses: actions/checkout@v2
+ - name: Setup Go
+ uses: actions/setup-go@v2
+ with:
+ go-version: '1.18'
+ - name: Install golangci-lint
+ run: |
+ curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
+ tar -xf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
+ sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
+ rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
+ env:
+ GOLANGCI_LINT_VERSION: '1.50.1'
+ - name: Run Lint
+ run: make lint
+
+ build:
+ name: build
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ go: [1.18]
+ 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: Test
+ env:
+ GO111MODULE: on
+ MONGODB_TEST_CXN: mongodb://localhost:27017
+ FIX_TEST: ${{ matrix.fix-version }}
+ run: if [ -z $FIX_TEST ]; then make build-src && make test-ci; else make generate-ci && make build && make $FIX_TEST; fi
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 000000000..4e51f8c12
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ main ]
+ schedule:
+ - cron: '42 21 * * 3'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'go' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+ # Learn more:
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.gitignore b/.gitignore
index c6137fd7a..ff2f9b241 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*~
*.swp
*.swo
+.idea
vendor
_test/test
_test/echo_server
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 000000000..cf2a5fff9
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,28 @@
+run:
+ timeout: 10m
+ skip-dirs:
+ - gen
+ - vendor
+
+linters:
+ disable-all: true
+ enable:
+ - dupl
+ - gofmt
+ - goimports
+ - gosimple
+ - govet
+ - ineffassign
+ - misspell
+ - revive
+ - unused
+ - staticcheck
+ - godot
+
+linters-settings:
+ gofmt:
+ simplify: true
+ goimports:
+ local-prefixes: github.com/quickfixgo/quickfix
+ dupl:
+ threshold: 400
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 55955e15c..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-language: go
-sudo: false
-
-go:
- - 1.9
- - tip
-
-services:
- - mongodb
-
-env:
- global:
- - MONGODB_TEST_CXN=localhost
- matrix:
- - FIX_TEST=
- - FIX_TEST=fix40
- - FIX_TEST=fix41
- - FIX_TEST=fix42
- - FIX_TEST=fix43
- - FIX_TEST=fix44
- - FIX_TEST=fix50
- - FIX_TEST=fix50sp1
- - FIX_TEST=fix50sp2
-
-matrix:
- allow_failures:
- - go: tip
-
-install:
- - go get -u github.com/golang/dep/cmd/dep
- - dep ensure
-
-script: make generate; if [ -z "$FIX_TEST" ]; then make build; make; else make build_accept; make $FIX_TEST; fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcc635229..72d127e10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,53 @@
+## 0.8.0 (October 25, 2023)
+
+ENHANCEMENTS
+
+* Remove tag from field map [GH 544]
+* Add message.Bytes() to avoid string conversion [GH 546]
+* Check RejectInvalidMessage on FIXT validation [GH 572]
+
+BUG FIXES
+
+* Fix repeating group read tags lost [GH 462]
+* Acceptance test result must be predictable [GH 578]
+* Makes event timer stop idempotent [GH 580, 581]
+* Added WaitGroup Wait in Initiator [GH 584]
+
+## 0.7.0 (January 2, 2023)
+
+FEATURES
+
+* PersistMessages Config [GH 297]
+* MaxLatency [GH 242]
+* ResetOnDisconnect Configuration [GH 68]
+* Support for High Precision Timestamps [GH 288]
+* LogonTimeout [GH 295]
+* LogoutTimeout [GH 296]
+* Socks Proxy [GH 375]
+
+ENHANCEMENTS
+
+* Add SocketUseSSL parameter to allow SSL/TLS without client certs [GH 311]
+* Support for RejectInvalidMessage configuration [GH 336]
+* Add deep copy for Messages [GH 338]
+* Add Go Module support [GH 340]
+* Support timeout on ssl connection [GH 347, 349]
+* Dynamic Sessions [GH 521]
+* Upgrade Mongo Driver to support transactions [GH 527]
+
+BUG FIXES
+
+* header and trailer templates use rootpath [GH 302]
+* Initiator stop panic if stop chan's already closed [GH 359]
+* Connection closed when inbound logon has a too-low sequence number [GH 369]
+* TLS server name config [GH 384]
+* Fix concurrent map write [GH 436]
+* Race condition during bilateral initial resend request [GH 439]
+* Deadlock when disconnecting dynamic session [GH 524]
+* Align session's ticker with round second [GH 533]
+* Seqnum persist and increment fix [GH 528]
+
+
## 0.6.0 (August 14, 2017)
FEATURES
diff --git a/Gopkg.lock b/Gopkg.lock
deleted file mode 100644
index 4d6e80a80..000000000
--- a/Gopkg.lock
+++ /dev/null
@@ -1,93 +0,0 @@
-# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
-
-
-[[projects]]
- digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
- name = "github.com/davecgh/go-spew"
- packages = ["spew"]
- pruneopts = ""
- revision = "346938d642f2ec3594ed81d874461961cd0faa76"
- version = "v1.1.0"
-
-[[projects]]
- branch = "master"
- digest = "1:e9ffb9315dce0051beb757d0f0fc25db57c4da654efc4eada4ea109c2d9da815"
- name = "github.com/globalsign/mgo"
- packages = [
- ".",
- "bson",
- "internal/json",
- "internal/sasl",
- "internal/scram",
- ]
- pruneopts = ""
- revision = "eeefdecb41b842af6dc652aaea4026e8403e62df"
-
-[[projects]]
- digest = "1:1cc12f4618ce8d71ca28ef3708f4e98e1318ab6f06ecfffb6781b893f271c89c"
- name = "github.com/mattn/go-sqlite3"
- packages = ["."]
- pruneopts = ""
- revision = "ca5e3819723d8eeaf170ad510e7da1d6d2e94a08"
- version = "v1.2.0"
-
-[[projects]]
- digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
- name = "github.com/pmezard/go-difflib"
- packages = ["difflib"]
- pruneopts = ""
- revision = "792786c7400a136282c1664665ae0a8db921c6c2"
- version = "v1.0.0"
-
-[[projects]]
- branch = "master"
- digest = "1:68a81aa25065b50a4bf1ffd115ff3634704f61f675d0140b31492e9fcca55421"
- name = "github.com/shopspring/decimal"
- packages = ["."]
- pruneopts = ""
- revision = "aed1bfe463fa3c9cc268d60dcc1491db613bff7e"
-
-[[projects]]
- branch = "master"
- digest = "1:ed7ac53c7d59041f27964d3f04e021b45ecb5f23c842c84d778a7f1fb67e2ce9"
- name = "github.com/stretchr/objx"
- packages = ["."]
- pruneopts = ""
- revision = "1a9d0bb9f541897e62256577b352fdbc1fb4fd94"
-
-[[projects]]
- digest = "1:3926a4ec9a4ff1a072458451aa2d9b98acd059a45b38f7335d31e06c3d6a0159"
- name = "github.com/stretchr/testify"
- packages = [
- "assert",
- "mock",
- "require",
- "suite",
- ]
- pruneopts = ""
- revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
- version = "v1.1.4"
-
-[[projects]]
- branch = "master"
- digest = "1:898bc7c802c1e0c20cecd65811e90b7b9bc5651b4a07aefd159451bfb200b2b3"
- name = "golang.org/x/net"
- packages = ["context"]
- pruneopts = ""
- revision = "a04bdaca5b32abe1c069418fb7088ae607de5bd0"
-
-[solve-meta]
- analyzer-name = "dep"
- analyzer-version = 1
- input-imports = [
- "github.com/globalsign/mgo",
- "github.com/globalsign/mgo/bson",
- "github.com/mattn/go-sqlite3",
- "github.com/shopspring/decimal",
- "github.com/stretchr/testify/assert",
- "github.com/stretchr/testify/mock",
- "github.com/stretchr/testify/require",
- "github.com/stretchr/testify/suite",
- ]
- solver-name = "gps-cdcl"
- solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
deleted file mode 100644
index 3385a5e25..000000000
--- a/Gopkg.toml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-# Gopkg.toml example
-#
-# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
-# for detailed Gopkg.toml documentation.
-#
-# required = ["github.com/user/thing/cmd/thing"]
-# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
-#
-# [[constraint]]
-# name = "github.com/user/project"
-# version = "1.0.0"
-#
-# [[constraint]]
-# name = "github.com/user/project2"
-# branch = "dev"
-# source = "github.com/myfork/project2"
-#
-# [[override]]
-# name = "github.com/x/y"
-# version = "2.4.0"
-
-
-[[constraint]]
- name = "github.com/mattn/go-sqlite3"
- version = "1.2.0"
-
-[[constraint]]
- name = "github.com/shopspring/decimal"
- branch = "master"
-
-[[constraint]]
- name = "github.com/stretchr/testify"
- version = "1.1.4"
diff --git a/LICENSE.txt b/LICENSE
similarity index 95%
rename from LICENSE.txt
rename to LICENSE
index d9eb36409..273a59dad 100644
--- a/LICENSE.txt
+++ b/LICENSE
@@ -1,6 +1,6 @@
The QuickFIX Software License, Version 1.0
-Copyright (c) 2001-2010 quickfixengine.org All rights
+Copyright (c) 2001- quickfixengine.org All rights
reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/Makefile b/Makefile
index d513ded8a..2e0799d43 100644
--- a/Makefile
+++ b/Makefile
@@ -1,32 +1,35 @@
+
all: vet test
-generate:
- mkdir -p gen; cd gen; go run ../cmd/generate-fix/generate-fix.go ../spec/*.xml
+clean:
+ rm -rf gen
-generate-dist:
- cd ..; go run quickfix/cmd/generate-fix/generate-fix.go quickfix/spec/*.xml
+generate: clean
+ mkdir -p gen; cd gen; go run ../cmd/generate-fix/generate-fix.go -pkg-root=github.com/quickfixgo/quickfix/gen ../spec/*.xml
fmt:
- go fmt `go list ./... | grep -v quickfix/gen`
+ gofmt -l -w -s $(shell find . -type f -name '*.go')
vet:
go vet `go list ./... | grep -v quickfix/gen`
-lint:
- go get github.com/golang/lint/golint
- golint .
-
test:
- go test -v -cover . ./datadictionary ./internal
+ MONGODB_TEST_CXN=mongodb://db:27017 go test -v -cover . ./datadictionary ./internal
-_build_all:
- go build -v `go list ./...`
+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; \
+ }
-build_accept:
- cd _test; go build -o echo_server
+lint: linters-install
+ golangci-lint run
-build: _build_all build_accept
+# ---------------------------------------------------------------
+# Targets related to running acceptance tests -
+build-test-srv:
+ cd _test; go build -o echo_server ./test-server/
fix40:
cd _test; ./runat.sh $@.cfg 5001 "definitions/server/$@/*.def"
fix41:
@@ -48,3 +51,20 @@ ACCEPT_SUITE=fix40 fix41 fix42 fix43 fix44 fix50 fix50sp1 fix50sp2
accept: $(ACCEPT_SUITE)
.PHONY: test $(ACCEPT_SUITE)
+# ---------------------------------------------------------------
+
+# ---------------------------------------------------------------
+# These targets are specific to the Github CI Runner -
+
+build-src:
+ go build -v `go list ./...`
+
+build: build-src build-test-srv
+
+test-ci:
+ go test -v -cover . ./datadictionary ./internal
+
+generate-ci: clean
+ mkdir -p gen; cd gen; go run ../cmd/generate-fix/generate-fix.go -pkg-root=github.com/quickfixgo/quickfix/gen ../spec/$(shell echo $(FIX_TEST) | tr '[:lower:]' '[:upper:]').xml;
+
+# ---------------------------------------------------------------
diff --git a/README.md b/README.md
index f9a59056f..350c7f6c9 100644
--- a/README.md
+++ b/README.md
@@ -1,87 +1,119 @@
-QuickFIX/Go
-===========
+# QuickFIX/Go
-[![GoDoc](https://godoc.org/github.com/quickfixgo/quickfix?status.png)](https://godoc.org/github.com/quickfixgo/quickfix) [![Build Status](https://travis-ci.org/quickfixgo/quickfix.svg?branch=master)](https://travis-ci.org/quickfixgo/quickfix) [![Go Report Card](https://goreportcard.com/badge/github.com/quickfixgo/quickfix)](https://goreportcard.com/report/github.com/quickfixgo/quickfix)
-
-- Website: http://www.quickfixgo.org
-- Mailing list: [Google Groups](https://groups.google.com/forum/#!forum/quickfixgo)
+[![Build Status](https://github.com/quickfixgo/quickfix/workflows/CI/badge.svg)](https://github.com/quickfixgo/quickfix/actions) [![GoDoc](https://godoc.org/github.com/quickfixgo/quickfix?status.png)](https://godoc.org/github.com/quickfixgo/quickfix) [![Go Report Card](https://goreportcard.com/badge/github.com/quickfixgo/quickfix)](https://goreportcard.com/report/github.com/quickfixgo/quickfix)
Open Source [FIX Protocol](http://www.fixprotocol.org/) library implemented in Go
-Getting Started and Documentation
----------------------------------
+## About
+
QuickFIX/Go is a FIX Protocol Community implementation for the Go programming language .
-* [User Manual](http://quickfixgo.org/docs)
-* [API Documentation](https://godoc.org/github.com/quickfixgo/quickfix)
+
+ 100% free and open source with a liberal license
+ Supports FIX versions 4.0 - 5.0SP2
+ Runs on any hardware and operating system supported by Go (1.18+ required)
+ Spec driven run-time message validation
+ Spec driven code generation of type-safe FIX messages, fields, and repeating groups
+ Support for protocol customizations
+ Session state storage options: SQL, MongoDB, On-disk, or In-memory
+ Logging options: File, Screen
+ Failover and High Availability
+ Daily and weekly scheduling of session connections
+ Integrated support for SSL communicaitons
+ Automated unit and acceptance tests
+ Commercial Support available
+
-### Installation
+
+
-To install QuickFIX/Go, use `go get`:
+## Installation
-```sh
-$ go get github.com/quickfixgo/quickfix
+With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import
+
+```
+import "github.com/quickfixgo/quickfix"
```
-### Staying up to date
+to your code, and then `go [build|run|test]` will automatically fetch the necessary dependencies.
-To update QuickFIX/Go to the latest version, use `go get -u github.com/quickfixgo/quickfix`.
+Otherwise, run the following Go command to install the `quickfix` package:
-### Example Apps
+```sh
+go get -u github.com/quickfixgo/quickfix
+```
-See [examples](https://github.com/quickfixgo/examples) for some simple examples of using QuickFIX/Go.
+## Getting Started
-### FIX Message Generation
+* [QuickFIX User Manual](http://quickfixgo.org/docs)
+* [Go API Documentation](https://godoc.org/github.com/quickfixgo/quickfix)
+* See [examples](https://github.com/quickfixgo/examples) for some simple examples of using QuickFIX/Go.
-QuickFIX/Go includes separate packages for tags, fields, enums, messages, and message components generated from the FIX 4.0 - FIX5.0SP2 specs. See:
+## FIX Messaging Model
+To send and receive messages, your application will need a few additional packages.
-* [github.com/quickfixgo/tag](https://github.com/quickfixgo/tag)
-* [github.com/quickfixgo/field](https://github.com/quickfixgo/field)
-* [github.com/quickfixgo/enum](https://github.com/quickfixgo/enum)
-* [github.com/quickfixgo/fix40](https://github.com/quickfixgo/fix40)
-* [github.com/quickfixgo/fix41](https://github.com/quickfixgo/fix41)
-* [github.com/quickfixgo/fix42](https://github.com/quickfixgo/fix42)
-* [github.com/quickfixgo/fix43](https://github.com/quickfixgo/fix43)
-* [github.com/quickfixgo/fix44](https://github.com/quickfixgo/fix44)
-* [github.com/quickfixgo/fix50](https://github.com/quickfixgo/fix50)
-* [github.com/quickfixgo/fix50sp1](https://github.com/quickfixgo/fix50sp1)
-* [github.com/quickfixgo/fix50sp2](https://github.com/quickfixgo/fix50sp2)
-* [github.com/quickfixgo/fixt11](https://github.com/quickfixgo/fixt11)
+QuickFIX/Go maintains separate packages for tags, fields, enums, messages, and message components auto-generated from the FIX 4.0 - FIX5.0SP2 XML specifications-
+
+* [Tag](https://github.com/quickfixgo/tag)
+* [Field](https://github.com/quickfixgo/field)
+* [Enum](https://github.com/quickfixgo/enum)
+* [FIX 4.0](https://github.com/quickfixgo/fix40)
+* [FIX 4.1](https://github.com/quickfixgo/fix41)
+* [FIX 4.2](https://github.com/quickfixgo/fix42)
+* [FIX 4.3](https://github.com/quickfixgo/fix43)
+* [FIX 4.4](https://github.com/quickfixgo/fix44)
+* [FIX 5.0](https://github.com/quickfixgo/fix50)
+* [FIX 5.0 SP1](https://github.com/quickfixgo/fix50sp1)
+* [FIX 5.0 SP2](https://github.com/quickfixgo/fix50sp2)
+* [FIXT 1.1](https://github.com/quickfixgo/fixt11)
For most FIX applications, these generated resources are sufficient. Custom FIX applications may generate source specific to the FIX spec of that application using the `generate-fix` tool included with QuickFIX/Go.
Following installation, `generate-fix` is installed to `$GOPATH/bin/generate-fix`. Run `$GOPATH/bin/generate-fix --help` for usage instructions.
-Developing QuickFIX/Go
-----------------------
+## General Support
+Github Discussions
-If you wish to work on QuickFIX/Go itself, you will first need [Go](http://www.golang.org) installed on your machine (version 1.6+ is *required*).
+Our Github Discussions Board is free, public, and easily searchable. It’s the preferred method of user support from the QuickFIX/Go team.
-For local dev first make sure Go is properly installed, including setting up a [GOPATH](http://golang.org/doc/code.html#GOPATH).
+
Please provide as much detail as you can when asking a question, and include relevant configurations and code snippets.
-Next, using [Git](https://git-scm.com/), clone this repository into `$GOPATH/src/github.com/quickfixgo/quickfix`.
+FIX Protocol
-### Installing Dependencies
+More information about the FIX protocol can be found at the FIX Protocol website .
-QuickFIX/Go uses [dep](https://github.com/golang/dep) to manage the vendored dependencies. Install dep with `go get`:
+
Bugs and Issues
-```sh
-$ go get -u github.com/golang/dep/cmd/dep
-```
+Bugs and issues can be submitted by anyone through our GitHub repository issues list.
-Run `dep ensure` to install the correct versioned dependencies into `vendor/`, which Go 1.6+ automatically recognizes and loads.
+Note: Please do not submit questions or help requests to the issues list. It is for bugs and issues. If you need help, please use the Discussions board as described above and you’ll be able to send your question to the entire community.
+
+GitHub Issues
+
+Please provide sample code, logs, and a description of the problem when the issue is submitted.
+
+We will try to address new issues as quickly as possible, and we welcome contributions for bug fixes and new features!
+
+## Commercial Support
+Connamara Systems offers commercial support for developers who are integrating any of the QuickFIX implementations (Go, C++, Java, .NET). The support is offered in 10-hour bundles and grants developers access, via telephone or email, to the team that created QuickFIX/Go, QuickFIX/n, and are maintainers of QuickFIX.
+
+In addition to offering QuickFIX support, Connamara delivers Made-To-Measure Trading Solutions by bridging the gap between buy and build. By using internally developed trading platform components, Connamara delivers the best of off-the-shelf ISV solutions and custom application development. Coupled with Connamara’s unique licensing model, trading firms can get the best of both build and buy.
-```sh
-$ $GOPATH/bin/dep ensure
-```
-**Note:** No vendored dependencies are included in the QuickFIX/Go source.
+## Contributing
+
+If you wish to work on QuickFIX/Go itself, you will need [Docker](https://docs.docker.com/get-docker/) and [VSCode](https://code.visualstudio.com/download) on your machine.
+
+* Clone the repo and open it with VSCode with Docker running
+* This repo comes with vscode devcontainer configs in `./.devcontainer/`
+* Click the pop-up to re-open the project in the Dev Container
+* This opens the project in a docker container pre-configured with everything you need
### Build and Test
The default make target runs [go vet](https://godoc.org/golang.org/x/tools/cmd/vet) and unit tests.
```sh
-$ make
+make
```
If this exits with exit status 0, then everything is working!
@@ -91,7 +123,7 @@ If this exits with exit status 0, then everything is working!
Generated code from the FIX40-FIX50SP2 specs are available as separate repos under the [QuickFIX/Go organization](https://github.com/quickfixgo). The source specifications for this generated code is located in `spec/`. Generated code can be identified by the `.generated.go` suffix. Any changes to generated code must be captured by changes to source in `cmd/generate-fix`. After making changes to the code generator source, run the following to re-generate the source
```sh
-$ make generate-dist
+make generate
```
If you are making changes to the generated code, please create Pull Requests for these changes for the affected repos.
@@ -100,55 +132,21 @@ If you are making changes to the generated code, please create Pull Requests for
QuickFIX/Go has a comprehensive acceptance test suite covering the FIX protocol. These are the same tests used across all QuickFIX implementations.
-QuickFIX/Go acceptance tests depend on ruby in path.
+QuickFIX/Go acceptance tests depend on ruby in path, if you are using the dev container, it is already installed
To run acceptance tests,
- # generate code locally
- make generate
-
- # build acceptance test rig
- make build_accept
-
- # run acceptance tests
- make accept
-
-### Dependencies
-
-If you are developing QuickFIX/Go, there are a few tasks you might need to perform related to dependencies.
-
-#### Adding a dependency
-
-If you are adding a dependency, you will need to update the dep manifest in the same Pull Request as the code that depends on it. You should do this in a separate commit from your code, as this makes PR review easier and Git history simpler to read in the future.
-
-To add a dependency:
-
-1. Add the dependency using `dep`:
-```bash
-$ dep ensure -add github.com/foo/bar
-```
-2. Review the changes in git and commit them.
-
-#### Updating a dependency
-
-To update a dependency to the latest version allowed by constraints in `Gopkg.toml`:
-
-1. Run:
-```bash
-$ dep ensure -update github.com/foo/bar
-```
-2. Review the changes in git and commit them.
+```sh
+# generate code locally
+make generate
-To change the allowed version/branch/revision of a dependency:
+# build acceptance test rig
+make build-test-srv
-1. Manually edit `Gopkg.toml`
-2. Run:
-```bash
-$ dep ensure
+# run acceptance tests
+make accept
```
-3. Review the changes in git and commit them.
-Licensing
----------
+## Licensing
-This software is available under the QuickFIX Software License. Please see the [LICENSE.txt](https://github.com/quickfixgo/quickfix/blob/master/LICENSE.txt) for the terms specified by the QuickFIX Software License.
+This software is available under the QuickFIX Software License. Please see the [LICENSE.txt](https://github.com/quickfixgo/quickfix/blob/main/LICENSE.txt) for the terms specified by the QuickFIX Software License.
diff --git a/_sql/embed.go b/_sql/embed.go
new file mode 100644
index 000000000..f61fe5110
--- /dev/null
+++ b/_sql/embed.go
@@ -0,0 +1,6 @@
+package sql
+
+import "embed"
+
+//go:embed mssql mysql oracle postgresql sqlite3
+var FS embed.FS
diff --git a/_test/ReflectorClient.rb b/_test/ReflectorClient.rb
index c22858984..acc8acc72 100644
--- a/_test/ReflectorClient.rb
+++ b/_test/ReflectorClient.rb
@@ -67,11 +67,15 @@ def @reflector.waitConnectAction(cid)
end
def @reflector.waitDisconnectAction(cid)
- socket = @sockets[cid]
- if IO.select([socket], nil, nil, 10) == nil then
- raise "Connection hangs after ten seconds."
- elsif !socket.eof? then
- raise "Expected disconnection, got data"
+ begin
+ socket = @sockets[cid]
+ if IO.select([socket], nil, nil, 10) == nil then
+ raise "Connection hangs after ten seconds."
+ elsif !socket.eof? then
+ raise "Expected disconnection, got data"
+ end
+ rescue Errno::ECONNRESET
+ # Ignore, server has already disconnected the socket
end
end
diff --git a/_test/ReflectorServer.rb b/_test/ReflectorServer.rb
index 184c77ca7..43113157d 100644
--- a/_test/ReflectorServer.rb
+++ b/_test/ReflectorServer.rb
@@ -58,10 +58,14 @@ def @reflector.waitConnectAction(cid)
end
def @reflector.waitDisconnectAction(cid)
- if IO.select([@socket], nil, nil, 10) == nil then
- raise "Connection hangs after five seconds."
- elsif !@socket.eof? then
- raise "Expected disconnection, got data"
+ begin
+ if IO.select([@socket], nil, nil, 10) == nil then
+ raise "Connection hangs after five seconds."
+ elsif !@socket.eof? then
+ raise "Expected disconnection, got data"
+ end
+ rescue Errno::ECONNRESET
+ # Ignore, client has already disconnected the socket
end
end
diff --git a/_test/definitions/server/fix50sp1/14i_RepeatingGroupCountNotEqual.def b/_test/definitions/server/fix50sp1/14i_RepeatingGroupCountNotEqual.def
index e223b0410..5fce36436 100644
--- a/_test/definitions/server/fix50sp1/14i_RepeatingGroupCountNotEqual.def
+++ b/_test/definitions/server/fix50sp1/14i_RepeatingGroupCountNotEqual.def
@@ -11,7 +11,7 @@ E8=FIXT.1.19=6735=A34=149=ISLD52=00000000-00:00:00.00056=TW98=0108=2113
#------------------------
#New order message with incorrect repeating group "count". NoTradingSessions (386)
-I8=FIXT.1.135=D34=249=TW52=56=ISLD11=ID21=140=154=138=200.0055=INTC386=3336=PRE-OPEN336=AFTER-HOURS60=
+I8=FIXT.1.135=D34=249=TW52=56=ISLD11=ID21=140=154=138=200.0055=INTC386=3336=3336=660=
# expect a reject
E8=FIXT.1.19=12535=334=249=ISLD52=00000000-00:00:00.00056=TW45=258=Incorrect NumInGroup count for repeating group371=386372=D373=1610=0
@@ -23,4 +23,4 @@ E8=FIXT.1.19=12535=334=249=ISLD52=00000000-00:00:00.00056=TW45=258=Incor
I8=FIXT.1.135=534=349=TW52=56=ISLD
E8=FIXT.1.19=4935=534=349=ISLD52=00000000-00:00:00.00056=TW10=0
-eDISCONNECT
\ No newline at end of file
+eDISCONNECT
diff --git a/_test/definitions/server/fix50sp2/14i_RepeatingGroupCountNotEqual.def b/_test/definitions/server/fix50sp2/14i_RepeatingGroupCountNotEqual.def
index 26b914506..ff90c7e62 100644
--- a/_test/definitions/server/fix50sp2/14i_RepeatingGroupCountNotEqual.def
+++ b/_test/definitions/server/fix50sp2/14i_RepeatingGroupCountNotEqual.def
@@ -11,7 +11,7 @@ E8=FIXT.1.19=6735=A34=149=ISLD52=00000000-00:00:00.00056=TW98=0108=2113
#------------------------
#New order message with incorrect repeating group "count". NoTradingSessions (386)
-I8=FIXT.1.135=D34=249=TW52=56=ISLD11=ID21=140=154=138=200.0055=INTC386=3336=PRE-OPEN336=AFTER-HOURS60=
+I8=FIXT.1.135=D34=249=TW52=56=ISLD11=ID21=140=154=138=200.0055=INTC386=3336=3336=660=
# expect a reject
E8=FIXT.1.19=12535=334=249=ISLD52=00000000-00:00:00.00056=TW45=258=Incorrect NumInGroup count for repeating group371=386372=D373=1610=0
@@ -23,4 +23,4 @@ E8=FIXT.1.19=12535=334=249=ISLD52=00000000-00:00:00.00056=TW45=258=Incor
I8=FIXT.1.135=534=349=TW52=56=ISLD
E8=FIXT.1.19=4935=534=349=ISLD52=00000000-00:00:00.00056=TW10=0
-eDISCONNECT
\ No newline at end of file
+eDISCONNECT
diff --git a/_test/echo_server.go b/_test/test-server/main.go
similarity index 91%
rename from _test/echo_server.go
rename to _test/test-server/main.go
index ddd729789..b70dca258 100644
--- a/_test/echo_server.go
+++ b/_test/test-server/main.go
@@ -1,6 +1,7 @@
package main
import (
+ "bytes"
"fmt"
"io/ioutil"
"log"
@@ -8,8 +9,8 @@ import (
"os/signal"
"github.com/quickfixgo/quickfix"
- "github.com/quickfixgo/quickfix/gen/field"
- "github.com/quickfixgo/quickfix/gen/tag"
+ field "github.com/quickfixgo/quickfix/gen/field"
+ tag "github.com/quickfixgo/quickfix/gen/tag"
)
var router *quickfix.MessageRouter = quickfix.NewMessageRouter()
@@ -57,7 +58,8 @@ func (e *EchoApplication) processMsg(msg *quickfix.Message, sessionID quickfix.S
}
sessionOrderID := sessionID.String() + orderID.String()
- if possResend.FIXBoolean {
+
+ if bytes.Equal(possResend.Write(), []byte("Y")) {
if e.OrderIds[sessionOrderID] {
return nil
}
@@ -67,7 +69,7 @@ func (e *EchoApplication) processMsg(msg *quickfix.Message, sessionID quickfix.S
}
reply := copyMessage(msg)
- if possResend.FIXBoolean {
+ if bytes.Equal(possResend.Write(), []byte("Y")) {
reply.Header.Set(possResend)
}
@@ -109,6 +111,7 @@ func main() {
router.AddRoute(quickfix.ApplVerIDFIX50SP1, "d", app.processMsg)
router.AddRoute(quickfix.ApplVerIDFIX50SP2, "d", app.processMsg)
+ fmt.Println("starting test server")
cfg, err := os.Open(os.Args[1])
if err != nil {
fmt.Printf("Error opening %v, %v\n", os.Args[1], err)
@@ -139,9 +142,10 @@ func main() {
return
}
- interrupt := make(chan os.Signal)
- signal.Notify(interrupt)
- <-interrupt
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+ <-c
+ fmt.Println("stopping test server")
acceptor.Stop()
}
diff --git a/accepter_test.go b/accepter_test.go
new file mode 100644
index 000000000..0d9f6487b
--- /dev/null
+++ b/accepter_test.go
@@ -0,0 +1,85 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
+package quickfix
+
+import (
+ "net"
+ "testing"
+
+ "github.com/quickfixgo/quickfix/config"
+
+ proxyproto "github.com/armon/go-proxyproto"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAcceptor_Start(t *testing.T) {
+ sessionSettings := NewSessionSettings()
+ sessionSettings.Set(config.BeginString, BeginStringFIX42)
+ sessionSettings.Set(config.SenderCompID, "sender")
+ sessionSettings.Set(config.TargetCompID, "target")
+
+ settingsWithTCPProxy := NewSettings()
+ settingsWithTCPProxy.GlobalSettings().Set("UseTCPProxy", "Y")
+
+ settingsWithNoTCPProxy := NewSettings()
+ settingsWithNoTCPProxy.GlobalSettings().Set("UseTCPProxy", "N")
+
+ genericSettings := NewSettings()
+
+ const (
+ GenericListener = iota
+ ProxyListener
+ )
+
+ acceptorStartTests := []struct {
+ name string
+ settings *Settings
+ listenerType int
+ }{
+ {"with TCP proxy set", settingsWithTCPProxy, ProxyListener},
+ {"with no TCP proxy set", settingsWithNoTCPProxy, GenericListener},
+ {"no TCP proxy configuration set", genericSettings, GenericListener},
+ }
+
+ for _, tt := range acceptorStartTests {
+ t.Run(tt.name, func(t *testing.T) {
+ tt.settings.GlobalSettings().Set("SocketAcceptPort", "5001")
+ if _, err := tt.settings.AddSession(sessionSettings); err != nil {
+ assert.Nil(t, err)
+ }
+
+ acceptor := &Acceptor{settings: tt.settings}
+ if err := acceptor.Start(); err != nil {
+ assert.NotNil(t, err)
+ }
+ assert.Len(t, acceptor.listeners, 1)
+
+ for _, listener := range acceptor.listeners {
+ if tt.listenerType == ProxyListener {
+ _, ok := listener.(*proxyproto.Listener)
+ assert.True(t, ok)
+ }
+
+ if tt.listenerType == GenericListener {
+ _, ok := listener.(*net.TCPListener)
+ assert.True(t, ok)
+ }
+ }
+
+ acceptor.Stop()
+ })
+ }
+}
diff --git a/acceptor.go b/acceptor.go
index c9138355f..879cdb17f 100644
--- a/acceptor.go
+++ b/acceptor.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -10,90 +25,158 @@ import (
"strconv"
"sync"
+ proxyproto "github.com/armon/go-proxyproto"
+
"github.com/quickfixgo/quickfix/config"
)
-//Acceptor accepts connections from FIX clients and manages the associated sessions.
+// Acceptor accepts connections from FIX clients and manages the associated sessions.
type Acceptor struct {
- app Application
- settings *Settings
- logFactory LogFactory
- storeFactory MessageStoreFactory
- globalLog Log
- sessions map[SessionID]*session
- sessionGroup sync.WaitGroup
- listener net.Listener
- listenerShutdown sync.WaitGroup
+ app Application
+ settings *Settings
+ logFactory LogFactory
+ storeFactory MessageStoreFactory
+ globalLog Log
+ sessions map[SessionID]*session
+ sessionGroup sync.WaitGroup
+ listenerShutdown sync.WaitGroup
+ dynamicSessions bool
+ dynamicQualifier bool
+ dynamicQualifierCount int
+ dynamicSessionChan chan *session
+ sessionAddr sync.Map
+ sessionHostPort map[SessionID]int
+ listeners map[string]net.Listener
+ connectionValidator ConnectionValidator
sessionFactory
}
-//Start accepting connections.
-func (a *Acceptor) Start() error {
+// ConnectionValidator is an interface allowing to implement a custom authentication logic.
+type ConnectionValidator interface {
+ // Validate the connection for validity. This can be a part of authentication process.
+ // For example, you may tie up a SenderCompID to an IP range, or to a specific TLS certificate as a part of mTLS.
+ Validate(netConn net.Conn, session SessionID) error
+}
+
+// Start accepting connections.
+func (a *Acceptor) Start() (err error) {
socketAcceptHost := ""
if a.settings.GlobalSettings().HasSetting(config.SocketAcceptHost) {
- var err error
if socketAcceptHost, err = a.settings.GlobalSettings().Setting(config.SocketAcceptHost); err != nil {
- return err
+ return
}
}
- socketAcceptPort, err := a.settings.GlobalSettings().IntSetting(config.SocketAcceptPort)
- if err != nil {
- return err
+ a.sessionHostPort = make(map[SessionID]int)
+ a.listeners = make(map[string]net.Listener)
+ for sessionID, sessionSettings := range a.settings.SessionSettings() {
+ if sessionSettings.HasSetting(config.SocketAcceptPort) {
+ if a.sessionHostPort[sessionID], err = sessionSettings.IntSetting(config.SocketAcceptPort); err != nil {
+ return
+ }
+ } else if a.sessionHostPort[sessionID], err = a.settings.GlobalSettings().IntSetting(config.SocketAcceptPort); err != nil {
+ return
+ }
+ address := net.JoinHostPort(socketAcceptHost, strconv.Itoa(a.sessionHostPort[sessionID]))
+ a.listeners[address] = nil
}
var tlsConfig *tls.Config
if tlsConfig, err = loadTLSConfig(a.settings.GlobalSettings()); err != nil {
- return err
+ return
}
- address := net.JoinHostPort(socketAcceptHost, strconv.Itoa(socketAcceptPort))
- if tlsConfig != nil {
- if a.listener, err = tls.Listen("tcp", address, tlsConfig); err != nil {
- return err
+ var useTCPProxy bool
+ if a.settings.GlobalSettings().HasSetting(config.UseTCPProxy) {
+ if useTCPProxy, err = a.settings.GlobalSettings().BoolSetting(config.UseTCPProxy); err != nil {
+ return
}
- } else {
- if a.listener, err = net.Listen("tcp", address); err != nil {
- return err
+ }
+
+ for address := range a.listeners {
+ if tlsConfig != nil {
+ if a.listeners[address], err = tls.Listen("tcp", address, tlsConfig); err != nil {
+ return
+ }
+ } else if a.listeners[address], err = net.Listen("tcp", address); err != nil {
+ return
+ } else if useTCPProxy {
+ a.listeners[address] = &proxyproto.Listener{Listener: a.listeners[address]}
}
}
- for sessionID := range a.sessions {
- session := a.sessions[sessionID]
+ for _, s := range a.sessions {
+ a.sessionGroup.Add(1)
+ go func(s *session) {
+ s.run()
+ a.sessionGroup.Done()
+ }(s)
+ }
+ if a.dynamicSessions {
+ a.dynamicSessionChan = make(chan *session)
a.sessionGroup.Add(1)
go func() {
- session.run()
+ a.dynamicSessionsLoop()
a.sessionGroup.Done()
}()
}
-
- a.listenerShutdown.Add(1)
- go a.listenForConnections()
- return nil
+ a.listenerShutdown.Add(len(a.listeners))
+ for _, listener := range a.listeners {
+ go a.listenForConnections(listener)
+ }
+ return
}
-//Stop logs out existing sessions, close their connections, and stop accepting new connections.
+// Stop logs out existing sessions, close their connections, and stop accepting new connections.
func (a *Acceptor) Stop() {
defer func() {
_ = recover() // suppress sending on closed channel error
}()
- a.listener.Close()
+ for _, listener := range a.listeners {
+ listener.Close()
+ }
a.listenerShutdown.Wait()
+ if a.dynamicSessions {
+ close(a.dynamicSessionChan)
+ }
for _, session := range a.sessions {
session.stop()
}
a.sessionGroup.Wait()
}
-//NewAcceptor creates and initializes a new Acceptor.
+// RemoteAddr gets remote IP address for a given session.
+func (a *Acceptor) RemoteAddr(sessionID SessionID) (net.Addr, bool) {
+ addr, ok := a.sessionAddr.Load(sessionID)
+ if !ok || addr == nil {
+ return nil, false
+ }
+ val, ok := addr.(net.Addr)
+ return val, ok
+}
+
+// NewAcceptor creates and initializes a new Acceptor.
func NewAcceptor(app Application, storeFactory MessageStoreFactory, settings *Settings, logFactory LogFactory) (a *Acceptor, err error) {
a = &Acceptor{
- app: app,
- storeFactory: storeFactory,
- settings: settings,
- logFactory: logFactory,
- sessions: make(map[SessionID]*session),
+ app: app,
+ storeFactory: storeFactory,
+ settings: settings,
+ logFactory: logFactory,
+ sessions: make(map[SessionID]*session),
+ sessionHostPort: make(map[SessionID]int),
+ listeners: make(map[string]net.Listener),
+ }
+ if a.settings.GlobalSettings().HasSetting(config.DynamicSessions) {
+ if a.dynamicSessions, err = settings.globalSettings.BoolSetting(config.DynamicSessions); err != nil {
+ return
+ }
+
+ if a.settings.GlobalSettings().HasSetting(config.DynamicQualifier) {
+ if a.dynamicQualifier, err = settings.globalSettings.BoolSetting(config.DynamicQualifier); err != nil {
+ return
+ }
+ }
}
if a.globalLog, err = logFactory.Create(); err != nil {
@@ -116,11 +199,11 @@ func NewAcceptor(app Application, storeFactory MessageStoreFactory, settings *Se
return
}
-func (a *Acceptor) listenForConnections() {
+func (a *Acceptor) listenForConnections(listener net.Listener) {
defer a.listenerShutdown.Done()
for {
- netConn, err := a.listener.Accept()
+ netConn, err := listener.Accept()
if err != nil {
return
}
@@ -220,17 +303,47 @@ func (a *Acceptor) handleConnection(netConn net.Conn) {
SenderCompID: string(targetCompID), SenderSubID: string(targetSubID), SenderLocationID: string(targetLocationID),
TargetCompID: string(senderCompID), TargetSubID: string(senderSubID), TargetLocationID: string(senderLocationID),
}
- session, ok := a.sessions[sessID]
- if !ok {
+
+ localConnectionPort := netConn.LocalAddr().(*net.TCPAddr).Port
+ if expectedPort, ok := a.sessionHostPort[sessID]; ok && expectedPort != localConnectionPort {
a.globalLog.OnEventf("Session %v not found for incoming message: %s", sessID, msgBytes)
return
}
+ // We have a session ID and a network connection. This seems to be a good place for any custom authentication logic.
+ if a.connectionValidator != nil {
+ if err := a.connectionValidator.Validate(netConn, sessID); err != nil {
+ a.globalLog.OnEventf("Unable to validate a connection for session %v: %v", sessID, err.Error())
+ return
+ }
+ }
+
+ if a.dynamicQualifier {
+ a.dynamicQualifierCount++
+ sessID.Qualifier = strconv.Itoa(a.dynamicQualifierCount)
+ }
+ session, ok := a.sessions[sessID]
+ if !ok {
+ if !a.dynamicSessions {
+ a.globalLog.OnEventf("Session %v not found for incoming message: %s", sessID, msgBytes)
+ return
+ }
+ dynamicSession, err := a.sessionFactory.createSession(sessID, a.storeFactory, a.settings.globalSettings.clone(), a.logFactory, a.app)
+ if err != nil {
+ a.globalLog.OnEventf("Dynamic session %v failed to create: %v", sessID, err)
+ return
+ }
+ a.dynamicSessionChan <- dynamicSession
+ session = dynamicSession
+ defer session.stop()
+ }
+
+ a.sessionAddr.Store(sessID, netConn.RemoteAddr())
msgIn := make(chan fixIn)
msgOut := make(chan []byte)
if err := session.connect(msgIn, msgOut); err != nil {
- a.globalLog.OnEventf("Unable to accept %v", err.Error())
+ a.globalLog.OnEventf("Unable to accept session %v connection: %v", sessID, err.Error())
return
}
@@ -241,3 +354,63 @@ func (a *Acceptor) handleConnection(netConn net.Conn) {
writeLoop(netConn, msgOut, a.globalLog)
}
+
+func (a *Acceptor) dynamicSessionsLoop() {
+ var id int
+ var sessions = map[int]*session{}
+ var complete = make(chan int)
+ defer close(complete)
+LOOP:
+ for {
+ select {
+ case session, ok := <-a.dynamicSessionChan:
+ if !ok {
+ for _, oldSession := range sessions {
+ oldSession.stop()
+ }
+ break LOOP
+ }
+ id++
+ sessionID := id
+ sessions[sessionID] = session
+ go func() {
+ session.run()
+ err := UnregisterSession(session.sessionID)
+ if err != nil {
+ a.globalLog.OnEventf("Unregister dynamic session %v failed: %v", session.sessionID, err)
+ return
+ }
+ complete <- sessionID
+ }()
+ case id := <-complete:
+ session, ok := sessions[id]
+ if ok {
+ a.sessionAddr.Delete(session.sessionID)
+ delete(sessions, id)
+ } else {
+ a.globalLog.OnEventf("Missing dynamic session %v!", id)
+ }
+ }
+ }
+
+ if len(sessions) == 0 {
+ return
+ }
+
+ for id := range complete {
+ delete(sessions, id)
+ if len(sessions) == 0 {
+ return
+ }
+ }
+}
+
+// SetConnectionValidator sets an optional connection validator.
+// Use it when you need a custom authentication logic that includes lower level interactions,
+// like mTLS auth or IP whitelistening.
+// To remove a previously set validator call it with a nil value:
+//
+// a.SetConnectionValidator(nil)
+func (a *Acceptor) SetConnectionValidator(validator ConnectionValidator) {
+ a.connectionValidator = validator
+}
diff --git a/application.go b/application.go
index 09b3b49a3..1d12419d3 100644
--- a/application.go
+++ b/application.go
@@ -1,26 +1,41 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
-//The Application interface should be implemented by FIX Applications.
-//This is the primary interface for processing messages from a FIX Session.
+// Application interface should be implemented by FIX Applications.
+// This is the primary interface for processing messages from a FIX Session.
type Application interface {
- //Notification of a session begin created.
+ // OnCreate notification of a session begin created.
OnCreate(sessionID SessionID)
- //Notification of a session successfully logging on.
+ // OnLogon notification of a session successfully logging on.
OnLogon(sessionID SessionID)
- //Notification of a session logging off or disconnecting.
+ // OnLogout notification of a session logging off or disconnecting.
OnLogout(sessionID SessionID)
- //Notification of admin message being sent to target.
+ // ToAdmin notification of admin message being sent to target.
ToAdmin(message *Message, sessionID SessionID)
- //Notification of app message being sent to target.
+ // ToApp notification of app message being sent to target.
ToApp(message *Message, sessionID SessionID) error
- //Notification of admin message being received from target.
+ // FromAdmin notification of admin message being received from target.
FromAdmin(message *Message, sessionID SessionID) MessageRejectError
- //Notification of app message being received from target.
+ // FromApp notification of app message being received from target.
FromApp(message *Message, sessionID SessionID) MessageRejectError
}
diff --git a/begin_string.go b/begin_string.go
index 74c2a3df5..641ea7c6f 100644
--- a/begin_string.go
+++ b/begin_string.go
@@ -1,6 +1,21 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
-//FIX BeginString string values
+// FIX BeginString string values.
const (
BeginStringFIX40 = "FIX.4.0"
BeginStringFIX41 = "FIX.4.1"
diff --git a/cmd/generate-fix/generate-fix.go b/cmd/generate-fix/generate-fix.go
index 5ec5ff47a..a24d39771 100644
--- a/cmd/generate-fix/generate-fix.go
+++ b/cmd/generate-fix/generate-fix.go
@@ -122,13 +122,25 @@ func main() {
usage()
}
- specs := make([]*datadictionary.DataDictionary, flag.NArg())
- for i, dataDictPath := range flag.Args() {
- var err error
- specs[i], err = datadictionary.Parse(dataDictPath)
+ args := flag.Args()
+ if len(args) == 1 {
+ dictpath := args[0]
+ if strings.Contains(dictpath, "FIX50SP1") {
+ args = append(args, strings.Replace(dictpath, "FIX50SP1", "FIXT11", -1))
+ } else if strings.Contains(dictpath, "FIX50SP2") {
+ args = append(args, strings.Replace(dictpath, "FIX50SP2", "FIXT11", -1))
+ } else if strings.Contains(dictpath, "FIX50") {
+ args = append(args, strings.Replace(dictpath, "FIX50", "FIXT11", -1))
+ }
+ }
+ specs := []*datadictionary.DataDictionary{}
+
+ for _, dataDictPath := range args {
+ spec, err := datadictionary.Parse(dataDictPath)
if err != nil {
log.Fatalf("Error Parsing %v: %v", dataDictPath, err)
}
+ specs = append(specs, spec)
}
internal.BuildGlobalFieldTypes(specs)
@@ -152,7 +164,7 @@ func main() {
}
switch pkg {
- //uses fixt11 header/trailer
+ // Uses fixt11 header/trailer.
case "fix50", "fix50sp1", "fix50sp2":
default:
waitGroup.Add(1)
diff --git a/cmd/generate-fix/internal/generate.go b/cmd/generate-fix/internal/generate.go
index 9fbefea29..3dc428980 100644
--- a/cmd/generate-fix/internal/generate.go
+++ b/cmd/generate-fix/internal/generate.go
@@ -13,6 +13,7 @@ import (
var (
useFloat = flag.Bool("use-float", false, "By default, FIX float fields are represented as arbitrary-precision fixed-point decimal numbers. Set to 'true' to instead generate FIX float fields as float64 values.")
+ pkgRoot = flag.String("pkg-root", "github.com/quickfixgo", "Set a string here to provide a custom import path for generated packages.")
tabWidth = 8
printerMode = printer.UseSpaces | printer.TabIndent
)
diff --git a/cmd/generate-fix/internal/globals.go b/cmd/generate-fix/internal/globals.go
index 45e49467d..bf935b4db 100644
--- a/cmd/generate-fix/internal/globals.go
+++ b/cmd/generate-fix/internal/globals.go
@@ -14,7 +14,7 @@ var (
GlobalFieldTypes []*datadictionary.FieldType
)
-//sort fieldtypes by name
+// Sort fieldtypes by name.
type byFieldName []*datadictionary.FieldType
func (n byFieldName) Len() int { return len(n) }
@@ -36,14 +36,14 @@ func BuildGlobalFieldTypes(specs []*datadictionary.DataDictionary) {
for _, spec := range specs {
for _, field := range spec.FieldTypeByTag {
if oldField, ok := globalFieldTypesLookup[field.Name()]; ok {
- //merge old enums with new
+ // Merge old enums with new.
if len(oldField.Enums) > 0 && field.Enums == nil {
field.Enums = make(map[string]datadictionary.Enum)
}
for enumVal, enum := range oldField.Enums {
if _, ok := field.Enums[enumVal]; !ok {
- //Verify an existing enum doesn't have the same description. Keep newer enum
+ // Verify an existing enum doesn't have the same description. Keep newer enum.
okToKeepEnum := true
for _, newEnum := range field.Enums {
if newEnum.Description == enum.Description {
diff --git a/cmd/generate-fix/internal/helpers.go b/cmd/generate-fix/internal/helpers.go
index 7b4a96fe1..8fe4cd4c6 100644
--- a/cmd/generate-fix/internal/helpers.go
+++ b/cmd/generate-fix/internal/helpers.go
@@ -1,21 +1,6 @@
package internal
-import (
- "os"
- "path/filepath"
- "strings"
-)
-
// getImportPathRoot returns the root path to use in import statements.
-// The root path is determined by stripping "$GOPATH/src/" from the current
-// working directory. For example, when generating code within the QuickFIX/Go
-// source tree, the returned root path will be "github.com/quickfixgo/quickfix".
func getImportPathRoot() string {
- pwd, err := os.Getwd()
- if err != nil {
- panic(err)
- }
- goSrcPath := filepath.Join(os.Getenv("GOPATH"), "src")
- importPathRoot := filepath.ToSlash(strings.Replace(pwd, goSrcPath, "", 1))
- return strings.TrimLeft(importPathRoot, "/")
-}
\ No newline at end of file
+ return *pkgRoot
+}
diff --git a/cmd/generate-fix/internal/template_helpers.go b/cmd/generate-fix/internal/template_helpers.go
index 55de54481..932f4792b 100644
--- a/cmd/generate-fix/internal/template_helpers.go
+++ b/cmd/generate-fix/internal/template_helpers.go
@@ -22,6 +22,27 @@ func checkIfDecimalImportRequiredForFields(fTypes []*datadictionary.FieldType) (
return
}
+func checkIfTimeImportRequiredForFields(fTypes []*datadictionary.FieldType) (ok bool, err error) {
+ var t string
+ for _, fType := range fTypes {
+ t, err = quickfixType(fType)
+ if err != nil {
+ return
+ }
+
+ var vt string
+ if vt, err = quickfixValueType(t); err != nil {
+ return
+ }
+
+ if vt == "time.Time" {
+ return true, nil
+ }
+ }
+
+ return
+}
+
func checkFieldDecimalRequired(f *datadictionary.FieldDef) (required bool, err error) {
var globalType *datadictionary.FieldType
if globalType, err = getGlobalFieldType(f); err != nil {
@@ -285,7 +306,7 @@ func routerBeginString(spec *datadictionary.DataDictionary) (routerBeginString s
routerBeginString = "FIXT.1.1"
case spec.Major != 5 && spec.ServicePack == 0:
routerBeginString = fmt.Sprintf("FIX.%v.%v", spec.Major, spec.Minor)
- //ApplVerID enums
+ // ApplVerID enums.
case spec.Major == 2:
routerBeginString = "0"
case spec.Major == 3:
diff --git a/cmd/generate-fix/internal/templates.go b/cmd/generate-fix/internal/templates.go
index 95aa5276e..72fb24b2c 100644
--- a/cmd/generate-fix/internal/templates.go
+++ b/cmd/generate-fix/internal/templates.go
@@ -26,6 +26,7 @@ func init() {
"getGlobalFieldType": getGlobalFieldType,
"collectExtraImports": collectExtraImports,
"checkIfDecimalImportRequiredForFields": checkIfDecimalImportRequiredForFields,
+ "checkIfTimeImportRequiredForFields": checkIfTimeImportRequiredForFields,
"checkIfEnumImportRequired": checkIfEnumImportRequired,
}
@@ -57,7 +58,7 @@ Set{{ .Name }}(f {{ .Name }}RepeatingGroup){
{{ define "setters" }}
{{ range .Fields }}
-//Set{{ .Name }} sets {{ .Name }}, Tag {{ .Tag }}
+// Set{{ .Name }} sets {{ .Name }}, Tag {{ .Tag }}.
func ({{ template "receiver" }} {{ $.Name }}) {{ if .IsGroup }}{{ template "groupsetter" . }}{{ else }}{{ template "fieldsetter" . }}{{ end }}
{{ end }}{{ end }}
@@ -97,13 +98,13 @@ Get{{ .Name }}() ({{ .Name }}RepeatingGroup, quickfix.MessageRejectError) {
{{ define "getters" }}
{{ range .Fields }}
-//Get{{ .Name }} gets {{ .Name }}, Tag {{ .Tag }}
+// Get{{ .Name }} gets {{ .Name }}, Tag {{ .Tag }}.
func ({{ template "receiver" }} {{ $.Name }}) {{if .IsGroup}}{{ template "groupgetter" . }}{{ else }}{{ template "fieldvaluegetter" .}}{{ end }}
{{ end }}{{ end }}
{{ define "hasers" }}
{{range .Fields}}
-//Has{{ .Name}} returns true if {{ .Name}} is present, Tag {{ .Tag}}
+// Has{{ .Name}} returns true if {{ .Name}} is present, Tag {{ .Tag}}.
func ({{ template "receiver" }} {{ $.Name }}) Has{{ .Name}}() bool {
return {{ template "receiver" }}.Has(tag.{{ .Name}})
}
@@ -121,7 +122,7 @@ quickfix.GroupTemplate{
{{ define "groups" }}
{{ range .Fields }}
{{ if .IsGroup }}
-//{{ .Name }} is a repeating group element, Tag {{ .Tag }}
+// {{ .Name }} is a repeating group element, Tag {{ .Tag }}.
type {{ .Name }} struct {
*quickfix.Group
}
@@ -131,24 +132,24 @@ type {{ .Name }} struct {
{{ template "hasers" . }}
{{ template "groups" . }}
-//{{ .Name }}RepeatingGroup is a repeating group, Tag {{ .Tag }}
+// {{ .Name }}RepeatingGroup is a repeating group, Tag {{ .Tag }}.
type {{ .Name }}RepeatingGroup struct {
*quickfix.RepeatingGroup
}
-//New{{ .Name }}RepeatingGroup returns an initialized, {{ .Name }}RepeatingGroup
+// New{{ .Name }}RepeatingGroup returns an initialized, {{ .Name }}RepeatingGroup.
func New{{ .Name }}RepeatingGroup() {{ .Name }}RepeatingGroup {
return {{ .Name }}RepeatingGroup{
quickfix.NewRepeatingGroup(tag.{{ .Name }}, {{ template "group_template" .Fields }})}
}
-//Add create and append a new {{ .Name }} to this group
+// Add create and append a new {{ .Name }} to this group.
func ({{ template "receiver" }} {{ .Name }}RepeatingGroup) Add() {{ .Name }} {
g := {{ template "receiver" }}.RepeatingGroup.Add()
return {{ .Name }}{g}
}
-//Get returns the ith {{ .Name }} in the {{ .Name }}RepeatinGroup
+// Get returns the ith {{ .Name }} in the {{ .Name }}RepeatinGroup.
func ({{ template "receiver" }} {{ .Name}}RepeatingGroup) Get(i int) {{ .Name }} {
return {{ .Name }}{ {{ template "receiver" }}.RepeatingGroup.Get(i) }
}
@@ -174,12 +175,12 @@ import(
"{{ importRootPath }}/tag"
)
-//Header is the {{ .Package }} Header type
+// Header is the {{ .Package }} Header type.
type Header struct {
*quickfix.Header
}
-//NewHeader returns a new, initialized Header instance
+// NewHeader returns a new, initialized Header instance.
func NewHeader(header *quickfix.Header) (h Header) {
h.Header = header
h.SetBeginString("{{ beginString .FIXSpec }}")
@@ -209,7 +210,7 @@ import(
"{{ importRootPath }}/tag"
)
-//Trailer is the {{ .Package }} Trailer type
+// Trailer is the {{ .Package }} Trailer type.
type Trailer struct {
*quickfix.Trailer
}
@@ -238,7 +239,7 @@ import(
"{{ importRootPath }}/tag"
)
-//{{ .Name }} is the {{ .FIXPackage }} {{ .Name }} type, MsgType = {{ .MsgType }}
+// {{ .Name }} is the {{ .FIXPackage }} {{ .Name }} type, MsgType = {{ .MsgType }}.
type {{ .Name }} struct {
{{ .TransportPackage }}.Header
*quickfix.Body
@@ -246,7 +247,7 @@ type {{ .Name }} struct {
Message *quickfix.Message
}
-//FromMessage creates a {{ .Name }} from a quickfix.Message instance
+// FromMessage creates a {{ .Name }} from a quickfix.Message instance.
func FromMessage(m *quickfix.Message) {{ .Name }} {
return {{ .Name }}{
Header: {{ .TransportPackage}}.Header{&m.Header},
@@ -256,13 +257,13 @@ func FromMessage(m *quickfix.Message) {{ .Name }} {
}
}
-//ToMessage returns a quickfix.Message instance
+// ToMessage returns a quickfix.Message instance.
func (m {{ .Name }}) ToMessage() *quickfix.Message {
return m.Message
}
{{ $required_fields := requiredFields .MessageDef -}}
-//New returns a {{ .Name }} initialized with the required fields for {{ .Name }}
+// New returns a {{ .Name }} initialized with the required fields for {{ .Name }}.
func New({{template "field_args" $required_fields }}) (m {{ .Name }}) {
m.Message = quickfix.NewMessage()
m.Header = {{ .TransportPackage }}.NewHeader(&m.Message.Header)
@@ -277,10 +278,10 @@ func New({{template "field_args" $required_fields }}) (m {{ .Name }}) {
return
}
-//A RouteOut is the callback type that should be implemented for routing Message
+// A RouteOut is the callback type that should be implemented for routing Message.
type RouteOut func(msg {{ .Name }}, sessionID quickfix.SessionID) quickfix.MessageRejectError
-//Route returns the beginstring, message type, and MessageRoute for this Message type
+// Route returns the beginstring, message type, and MessageRoute for this Message type.
func Route(router RouteOut) (string, string, quickfix.MessageRoute) {
r:=func(msg *quickfix.Message, sessionID quickfix.SessionID) quickfix.MessageRejectError {
return router(FromMessage(msg), sessionID)
@@ -312,35 +313,35 @@ import(
"{{ importRootPath }}/enum"
"{{ importRootPath }}/tag"
{{ if checkIfDecimalImportRequiredForFields . }} "github.com/shopspring/decimal" {{ end }}
- "time"
+{{ if checkIfTimeImportRequiredForFields . }} "time" {{ end }}
)
{{ range . }}
{{- $base_type := quickfixType . -}}
{{ if and .Enums (ne $base_type "FIXBoolean") }}
-//{{ .Name }}Field is a enum.{{ .Name }} field
+// {{ .Name }}Field is a enum.{{ .Name }} field.
type {{ .Name }}Field struct { quickfix.FIXString }
{{ else }}
-//{{ .Name }}Field is a {{ .Type }} field
+// {{ .Name }}Field is a {{ .Type }} field.
type {{ .Name }}Field struct { quickfix.{{ $base_type }} }
{{ end }}
-//Tag returns tag.{{ .Name }} ({{ .Tag }})
+// Tag returns tag.{{ .Name }} ({{ .Tag }}).
func (f {{ .Name }}Field) Tag() quickfix.Tag { return tag.{{ .Name }} }
{{ if eq $base_type "FIXUTCTimestamp" }}
-//New{{ .Name }} returns a new {{ .Name }}Field initialized with val
+// New{{ .Name }} returns a new {{ .Name }}Field initialized with val.
func New{{ .Name }}(val time.Time) {{ .Name }}Field {
return New{{ .Name }}WithPrecision(val, quickfix.Millis)
}
-//New{{ .Name }}NoMillis returns a new {{ .Name }}Field initialized with val without millisecs
+// New{{ .Name }}NoMillis returns a new {{ .Name }}Field initialized with val without millisecs.
func New{{ .Name }}NoMillis(val time.Time) {{ .Name }}Field {
return New{{ .Name }}WithPrecision(val, quickfix.Seconds)
}
-//New{{ .Name }}WithPrecision returns a new {{ .Name }}Field initialized with val of specified precision
+// New{{ .Name }}WithPrecision returns a new {{ .Name }}Field initialized with val of specified precision.
func New{{ .Name }}WithPrecision(val time.Time, precision quickfix.TimestampPrecision) {{ .Name }}Field {
return {{ .Name }}Field{ quickfix.FIXUTCTimestamp{ Time: val, Precision: precision } }
}
@@ -350,12 +351,12 @@ func New{{ .Name }}(val enum.{{ .Name }}) {{ .Name }}Field {
return {{ .Name }}Field{ quickfix.FIXString(val) }
}
{{ else if eq $base_type "FIXDecimal" }}
-//New{{ .Name }} returns a new {{ .Name }}Field initialized with val and scale
+// New{{ .Name }} returns a new {{ .Name }}Field initialized with val and scale.
func New{{ .Name }}(val decimal.Decimal, scale int32) {{ .Name }}Field {
return {{ .Name }}Field{ quickfix.FIXDecimal{ Decimal: val, Scale: scale} }
}
{{ else }}
-//New{{ .Name }} returns a new {{ .Name }}Field initialized with val
+// New{{ .Name }} returns a new {{ .Name }}Field initialized with val.
func New{{ .Name }}(val {{ quickfixValueType $base_type }}) {{ .Name }}Field {
return {{ .Name }}Field{ quickfix.{{ $base_type }}(val) }
}
@@ -386,7 +387,7 @@ func (f {{ .Name }}Field) Value() ({{ quickfixValueType $base_type }}) {
package enum
{{ range $ft := . }}
{{ if $ft.Enums }}
-//Enum values for {{ $ft.Name }}
+// {{ $ft.Name }} field enumeration values.
type {{ $ft.Name }} string
const(
{{ range $ft.Enums }}
diff --git a/config/configuration.go b/config/configuration.go
index 06ad4a2e0..1d70f75c7 100644
--- a/config/configuration.go
+++ b/config/configuration.go
@@ -1,8 +1,8 @@
package config
-//NOTE: Additions to this file should be made to both config/doc.go and http://www.quickfixgo.org/docs/
+// NOTE: Additions to this file should be made to both config/doc.go and http://www.quickfixgo.org/docs/
-//Const configuration settings
+// Const configuration settings.
const (
BeginString string = "BeginString"
SenderCompID string = "SenderCompID"
@@ -20,9 +20,16 @@ const (
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"
@@ -37,14 +44,19 @@ const (
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"
EnableLastMsgSeqNumProcessed string = "EnableLastMsgSeqNumProcessed"
@@ -53,4 +65,6 @@ const (
MaxLatency string = "MaxLatency"
PersistMessages string = "PersistMessages"
RejectInvalidMessage string = "RejectInvalidMessage"
+ DynamicSessions string = "DynamicSessions"
+ DynamicQualifier string = "DynamicQualifier"
)
diff --git a/config/doc.go b/config/doc.go
index abfa7c9b5..7da502416 100644
--- a/config/doc.go
+++ b/config/doc.go
@@ -1,244 +1,274 @@
/*
Package config declares application and session settings for QuickFIX/Go
-BeginString
+# 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
+ FIXT.1.1
+ FIX.4.4
+ FIX.4.3
+ FIX.4.2
+ FIX.4.1
+ FIX.4.0
-SenderCompID
+# SenderCompID
Your ID as associated with this FIX session. Value is case-sensitive alpha-numeric string.
-SenderSubID
+# SenderSubID
(Optional) Your subID as associated with this FIX session. Value is case-sensitive alpha-numeric string.
-SenderLocationID
+# SenderLocationID
(Optional) Your locationID as associated with this FIX session. Value is case-sensitive alpha-numeric string.
-TargetCompID
+# TargetCompID
Counter parties ID as associated with this FIX session. Value is case-sensitive alpha-numeric string.
-TargetSubID
+# TargetSubID
(Optional) Counterparty's subID as associated with this FIX session. Value is case-sensitive alpha-numeric string.
-TargetLocationID
+# TargetLocationID
(Optional) Counterparty's locationID as associated with this FIX session. Value is case-sensitive alpha-numeric string.
-SessionQualifier
+# SessionQualifier
Additional qualifier to disambiguate otherwise identical sessions. Value is case-sensitive alpha-numeric string.
-DefaultApplVerID
+# 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
+ 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)
+ IANA Time zone ID (America/New_York, Asia/Tokyo, Europe/London, etc.)
+ Local (The Zone on host)
Defaults to UTC.
-StartTime
+# 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
+ time in the format of HH:MM:SS, time is represented in time zone configured by TimeZone
-EndTime
+# 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
+ time in the format of HH:MM:SS, time is represented in time zone configured by TimeZone
-StartDay
+# StartDay
For week long sessions, the starting day of week for the session. Use in combination with StartTime. Valid Values:
- Full day of week in English, or 3 letter abbreviation (i.e. Monday and Mon are valid)
+ Full day of week in English, or 3 letter abbreviation (i.e. Monday and Mon are valid)
-EndDay
+# EndDay
For week long sessions, the ending day of week for the session. Use in combination with EndTime. Valid Values:
- Full day of week in English, or 3 letter abbreviation (i.e. Monday and Mon are valid)
+ Full day of week in English, or 3 letter abbreviation (i.e. Monday and Mon are valid)
-EnableLastMsgSeqNumProcessed
+# EnableLastMsgSeqNumProcessed
Add the last message sequence number processed in the header (optional tag 369). Valid Values:
- Y
- N
+
+ Y
+ N
Defaults to N.
-ResendRequestChunkSize
+# 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
+# ResetOnLogon
Determines if sequence numbers should be reset when receiving a logon request. Acceptors only. Valid Values:
- Y
- N
+
+ Y
+ N
Defaults to N.
-ResetOnLogout
+# ResetOnLogout
Determines if sequence numbers should be reset to 1 after a normal logout termination. Valid Values:
- Y
- N
+
+ Y
+ N
Defaults to N.
-ResetOnDisconnect
+# ResetOnDisconnect
Determines if sequence numbers should be reset to 1 after an abnormal termination. Valid Values:
- Y
- N
+
+ Y
+ N
Defaults to N.
-RefreshOnLogon
+# RefreshOnLogon
Determines if session state should be restored from persistence layer when logging on. Useful for creating hot failover sessions. Valid Values:
- Y
- N
+
+ Y
+ N
Defaults to N.
-TimeStampPrecision
+# 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
+
+ SECONDS
+ MILLIS
+ MICROS
+ NANOS
Defaults to MILLIS.
-Validation
+# Validation
The following settings are specific to message validation.
-DataDictionary
+# 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
+ FIX44.xml
+ FIX43.xml
+ FIX42.xml
+ FIX41.xml
+ FIX40.xml
-TransportDataDictionary
+# 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
+ FIXT1.1.xml
-AppDataDictionary
+# 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
+ 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
+ FIX50SP2.xml
+ FIX50SP1.xml
+ FIX50.xml
+ FIX44.xml
+ FIX43.xml
+ FIX42.xml
+ FIX41.xml
+ FIX40.xml
-ValidateFieldsOutOfOrder
+# 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
+
+ Y
+ N
Defaults to Y.
-RejectInvalidMessage
+# 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
+
+ Y
+ N
Defaults to Y.
-CheckLatency
+# 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
+
+ Y
+ N
Defaults to Y.
-MaxLatency
+# 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
+# ReconnectInterval
Time between reconnection attempts in seconds. Only used for initiators. Value must be positive integer.
-Defaults to 30
+# Defaults to 30
+
+# LogoutTimeout
+
+Session setting for logout timeout in seconds. Only used for initiators. Value must be positive integer.
-HeartBtInt
+# Defaults to 2
-Heartbeat interval in seconds. Only used for initiators. Value must be positive integer.
+# LogonTimeout
-SocketConnectPort
+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
+# SocketConnectHost
Host to connect to. Only used for initiators. Value must be a valid IPv4 or IPv6 address or a domain name
@@ -250,84 +280,124 @@ 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
+# SocketTimeout
Duration of timeout for TLS handshake. Only used for initiators.
Example Values:
- SocketTimeout=30s # 30 seconds
- SocketTimeout=60m # 60 minutes
+
+ SocketTimeout=30s # 30 seconds
+ SocketTimeout=60m # 60 minutes
Defaults to 0(means nothing timeout).
-SocketAcceptHost
+# SocketAcceptHost
Socket host address for listening on incoming connections, only used for acceptors. By default acceptors listen on all available interfaces.
-SocketAcceptPort
+# SocketAcceptPort
Socket port for listening to incoming connections, only used for acceptors. Value must be a positive integer, valid open socket port.
-SocketPrivateKeyFile
+# SocketPrivateKeyFile
Private key to use for secure TLS connections. Must be used with SocketCertificateFile.
-SocketCertificateFile
+# SocketCertificateFile
Certificate to use for secure TLS connections. Must be used with SocketPrivateKeyFile.
-SocketCAFile
+# 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.
-SocketMinimumTLSVersion
+# 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
+# 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.
-PersistMessages
+# 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
+
+ Y
+ N
Defaults to Y.
-FileLogPath
+# FileLogPath
Directory to store logs. Value must be valid directory for storing files, application must have write access.
-FileStorePath
+# FileStorePath
Directory to store sequence number and message files. Only used with FileStoreFactory.
-MongoStoreConnection
+# MongoStoreConnection
-The MongoDB connection URL to use (see https://godoc.org/github.com/globalsign/mgo#Dial for the URL Format). Only used with MongoStoreFactory.
+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
+# MongoStoreDatabase
The MongoDB-specific name of the database to use. Only used with MongoStoreFactory.
-SQLStoreDriver
+# 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
+# SQLStoreDataSourceName
The driver-specific data source name of the database to use. Only used with SqlStoreFactory.
-SQLStoreConnMaxLifetime
+# 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
+
+ SQLConnMaxLifetime=14400s # 14400 seconds
+ SQLConnMaxLifetime=2h45m # 2 hours and 45 minutes
*/
package config
diff --git a/connection.go b/connection.go
index 4e5768a36..c006560f4 100644
--- a/connection.go
+++ b/connection.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import "io"
diff --git a/connection_internal_test.go b/connection_internal_test.go
index 54a9f347f..1ee4d13e8 100644
--- a/connection_internal_test.go
+++ b/connection_internal_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/datadictionary/component_type_test.go b/datadictionary/component_type_test.go
index 6de2b345a..c9774764b 100644
--- a/datadictionary/component_type_test.go
+++ b/datadictionary/component_type_test.go
@@ -3,8 +3,9 @@ package datadictionary_test
import (
"testing"
- "github.com/quickfixgo/quickfix/datadictionary"
"github.com/stretchr/testify/assert"
+
+ "github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewComponentType(t *testing.T) {
diff --git a/datadictionary/datadictionary.go b/datadictionary/datadictionary.go
index d051d4810..b032d078e 100644
--- a/datadictionary/datadictionary.go
+++ b/datadictionary/datadictionary.go
@@ -1,13 +1,15 @@
-//Package datadictionary provides support for parsing and organizing FIX Data Dictionaries
+// Package datadictionary provides support for parsing and organizing FIX Data Dictionaries
package datadictionary
import (
"encoding/xml"
"io"
"os"
+
+ "github.com/pkg/errors"
)
-//DataDictionary models FIX messages, components, and fields.
+// DataDictionary models FIX messages, components, and fields.
type DataDictionary struct {
FIXType string
Major int
@@ -21,20 +23,20 @@ type DataDictionary struct {
Trailer *MessageDef
}
-//MessagePart can represent a Field, Repeating Group, or Component
+// MessagePart can represent a Field, Repeating Group, or Component.
type MessagePart interface {
Name() string
Required() bool
}
-//messagePartWithFields is a MessagePart with multiple Fields
+// messagePartWithFields is a MessagePart with multiple Fields.
type messagePartWithFields interface {
MessagePart
Fields() []*FieldDef
RequiredFields() []*FieldDef
}
-//ComponentType is a grouping of fields.
+// ComponentType is a grouping of fields.
type ComponentType struct {
name string
parts []MessagePart
@@ -43,7 +45,7 @@ type ComponentType struct {
requiredParts []MessagePart
}
-//NewComponentType returns an initialized component type
+// NewComponentType returns an initialized component type.
func NewComponentType(name string, parts []MessagePart) *ComponentType {
comp := ComponentType{
name: name,
@@ -75,37 +77,37 @@ func NewComponentType(name string, parts []MessagePart) *ComponentType {
return &comp
}
-//Name returns the name of this component type
+// Name returns the name of this component type.
func (c ComponentType) Name() string { return c.name }
-//Fields returns all fields contained in this component. Includes fields
-//encapsulated in components of this component
+// Fields returns all fields contained in this component. Includes fields
+// encapsulated in components of this component.
func (c ComponentType) Fields() []*FieldDef { return c.fields }
-//RequiredFields returns those fields that are required for this component
+// RequiredFields returns those fields that are required for this component.
func (c ComponentType) RequiredFields() []*FieldDef { return c.requiredFields }
-//RequiredParts returns those parts that are required for this component
+// RequiredParts returns those parts that are required for this component.
func (c ComponentType) RequiredParts() []MessagePart { return c.requiredParts }
-// Parts returns all parts in declaration order contained in this component
+// Parts returns all parts in declaration order contained in this component.
func (c ComponentType) Parts() []MessagePart { return c.parts }
-//TagSet is set for tags.
+// TagSet is set for tags.
type TagSet map[int]struct{}
-//Add adds a tag to the tagset.
+// Add adds a tag to the tagset.
func (t TagSet) Add(tag int) {
t[tag] = struct{}{}
}
-//Component is a Component as it appears in a given MessageDef
+// Component is a Component as it appears in a given MessageDef.
type Component struct {
*ComponentType
required bool
}
-//NewComponent returns an initialized Component instance
+// NewComponent returns an initialized Component instance.
func NewComponent(ct *ComponentType, required bool) *Component {
return &Component{
ComponentType: ct,
@@ -113,16 +115,16 @@ func NewComponent(ct *ComponentType, required bool) *Component {
}
}
-//Required returns true if this component is required for the containing
-//MessageDef
+// Required returns true if this component is required for the containing
+// MessageDef.
func (c Component) Required() bool { return c.required }
-//Field models a field or repeating group in a message
+// Field models a field or repeating group in a message.
type Field interface {
Tag() int
}
-//FieldDef models a field belonging to a message.
+// FieldDef models a field belonging to a message.
type FieldDef struct {
*FieldType
required bool
@@ -133,7 +135,7 @@ type FieldDef struct {
requiredFields []*FieldDef
}
-//NewFieldDef returns an initialized FieldDef
+// NewFieldDef returns an initialized FieldDef.
func NewFieldDef(fieldType *FieldType, required bool) *FieldDef {
return &FieldDef{
FieldType: fieldType,
@@ -141,7 +143,7 @@ func NewFieldDef(fieldType *FieldType, required bool) *FieldDef {
}
}
-//NewGroupFieldDef returns an initialized FieldDef for a repeating group
+// NewGroupFieldDef returns an initialized FieldDef for a repeating group.
func NewGroupFieldDef(fieldType *FieldType, required bool, parts []MessagePart) *FieldDef {
field := FieldDef{
FieldType: fieldType,
@@ -176,21 +178,21 @@ func NewGroupFieldDef(fieldType *FieldType, required bool, parts []MessagePart)
return &field
}
-//Required returns true if this FieldDef is required for the containing
-//MessageDef
+// Required returns true if this FieldDef is required for the containing
+// MessageDef.
func (f FieldDef) Required() bool { return f.required }
-//IsGroup is true if the field is a repeating group.
+// IsGroup is true if the field is a repeating group.
func (f FieldDef) IsGroup() bool {
return len(f.Fields) > 0
}
-//RequiredParts returns those parts that are required for this FieldDef. IsGroup
-//must return true
+// RequiredParts returns those parts that are required for this FieldDef. IsGroup
+// must return true.
func (f FieldDef) RequiredParts() []MessagePart { return f.requiredParts }
-//RequiredFields returns those fields that are required for this FieldDef. IsGroup
-//must return true
+// RequiredFields returns those fields that are required for this FieldDef. IsGroup
+// must return true.
func (f FieldDef) RequiredFields() []*FieldDef { return f.requiredFields }
func (f FieldDef) childTags() []int {
@@ -198,32 +200,13 @@ func (f FieldDef) childTags() []int {
for _, f := range f.Fields {
tags = append(tags, f.Tag())
- for _, t := range f.childTags() {
- tags = append(tags, t)
- }
+ tags = append(tags, f.childTags()...)
}
return tags
}
-func (f FieldDef) requiredChildTags() []int {
- var tags []int
-
- for _, f := range f.Fields {
- if !f.Required() {
- continue
- }
-
- tags = append(tags, f.Tag())
- for _, t := range f.requiredChildTags() {
- tags = append(tags, t)
- }
- }
-
- return tags
-}
-
-//FieldType holds information relating to a field. Includes Tag, type, and enums, if defined.
+// FieldType holds information relating to a field. Includes Tag, type, and enums, if defined.
type FieldType struct {
name string
tag int
@@ -231,7 +214,7 @@ type FieldType struct {
Enums map[string]Enum
}
-//NewFieldType returns a pointer to an initialized FieldType
+// NewFieldType returns a pointer to an initialized FieldType.
func NewFieldType(name string, tag int, fixType string) *FieldType {
return &FieldType{
name: name,
@@ -240,25 +223,24 @@ func NewFieldType(name string, tag int, fixType string) *FieldType {
}
}
-//Name returns the name for this FieldType
+// Name returns the name for this FieldType.
func (f FieldType) Name() string { return f.name }
-//Tag returns the tag for this fieldType
+// Tag returns the tag for this fieldType.
func (f FieldType) Tag() int { return f.tag }
-//Enum is a container for value and description.
+// Enum is a container for value and description.
type Enum struct {
Value string
Description string
}
-//MessageDef can apply to header, trailer, or body of a FIX Message.
+// MessageDef can apply to header, trailer, or body of a FIX Message.
type MessageDef struct {
Name string
MsgType string
Fields map[int]*FieldDef
- //Parts are the MessageParts of contained in this MessageDef in declaration
- //order
+ // Parts are the MessageParts of contained in this MessageDef in declaration order.
Parts []MessagePart
requiredParts []MessagePart
@@ -266,10 +248,10 @@ type MessageDef struct {
Tags TagSet
}
-//RequiredParts returns those parts that are required for this Message
+// RequiredParts returns those parts that are required for this Message.
func (m MessageDef) RequiredParts() []MessagePart { return m.requiredParts }
-//NewMessageDef returns a pointer to an initialized MessageDef
+// NewMessageDef returns a pointer to an initialized MessageDef.
func NewMessageDef(name, msgType string, parts []MessagePart) *MessageDef {
msg := MessageDef{
Name: name,
@@ -300,8 +282,8 @@ func NewMessageDef(name, msgType string, parts []MessagePart) *MessageDef {
switch pType := part.(type) {
case messagePartWithFields:
for _, f := range pType.Fields() {
- //field if required in component is required in message only if
- //component is required
+ // Field if required in component is required in message only if
+ // component is required.
processField(f, pType.Required())
}
@@ -316,27 +298,34 @@ func NewMessageDef(name, msgType string, parts []MessagePart) *MessageDef {
return &msg
}
-//Parse loads and and build a datadictionary instance from an xml file.
+// Parse loads and build a datadictionary instance from an xml file.
func Parse(path string) (*DataDictionary, error) {
var xmlFile *os.File
- xmlFile, err := os.Open(path)
+ var err error
+ xmlFile, err = os.Open(path)
if err != nil {
- return nil, err
+ return nil, errors.Wrapf(err, "problem opening file: %v", path)
}
defer xmlFile.Close()
+ return ParseSrc(xmlFile)
+}
+
+// ParseSrc loads and build a datadictionary instance from an xml source.
+func ParseSrc(xmlSrc io.Reader) (*DataDictionary, error) {
doc := new(XMLDoc)
- decoder := xml.NewDecoder(xmlFile)
+ decoder := xml.NewDecoder(xmlSrc)
decoder.CharsetReader = func(encoding string, input io.Reader) (io.Reader, error) {
return input, nil
}
+
if err := decoder.Decode(doc); err != nil {
- return nil, err
+ return nil, errors.Wrapf(err, "problem parsing XML file")
}
b := new(builder)
- var dict *DataDictionary
- if dict, err = b.build(doc); err != nil {
+ dict, err := b.build(doc)
+ if err != nil {
return nil, err
}
diff --git a/datadictionary/datadictionary_test.go b/datadictionary/datadictionary_test.go
index 4179716c5..11d98bfca 100644
--- a/datadictionary/datadictionary_test.go
+++ b/datadictionary/datadictionary_test.go
@@ -82,7 +82,7 @@ func TestFieldsByTag(t *testing.T) {
func TestEnumFieldsByTag(t *testing.T) {
d, _ := dict()
- f, _ := d.FieldTypeByTag[658]
+ f := d.FieldTypeByTag[658]
var tests = []struct {
Value string
@@ -141,7 +141,7 @@ func TestDataDictionaryTrailer(t *testing.T) {
func TestMessageRequiredTags(t *testing.T) {
d, _ := dict()
- nos, _ := d.Messages["D"]
+ nos := d.Messages["D"]
var tests = []struct {
*MessageDef
@@ -169,7 +169,7 @@ func TestMessageRequiredTags(t *testing.T) {
func TestMessageTags(t *testing.T) {
d, _ := dict()
- nos, _ := d.Messages["D"]
+ nos := d.Messages["D"]
var tests = []struct {
*MessageDef
diff --git a/datadictionary/field_def_test.go b/datadictionary/field_def_test.go
index 9c0a68abb..cb80fa131 100644
--- a/datadictionary/field_def_test.go
+++ b/datadictionary/field_def_test.go
@@ -3,8 +3,9 @@ package datadictionary_test
import (
"testing"
- "github.com/quickfixgo/quickfix/datadictionary"
"github.com/stretchr/testify/assert"
+
+ "github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewFieldDef(t *testing.T) {
diff --git a/datadictionary/field_type_test.go b/datadictionary/field_type_test.go
index 2a4328dff..00c651eef 100644
--- a/datadictionary/field_type_test.go
+++ b/datadictionary/field_type_test.go
@@ -3,8 +3,9 @@ package datadictionary_test
import (
"testing"
- "github.com/quickfixgo/quickfix/datadictionary"
"github.com/stretchr/testify/assert"
+
+ "github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewFieldType(t *testing.T) {
diff --git a/datadictionary/group_field_def_test.go b/datadictionary/group_field_def_test.go
index e1e963298..87147ce55 100644
--- a/datadictionary/group_field_def_test.go
+++ b/datadictionary/group_field_def_test.go
@@ -3,8 +3,9 @@ package datadictionary_test
import (
"testing"
- "github.com/quickfixgo/quickfix/datadictionary"
"github.com/stretchr/testify/assert"
+
+ "github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewGroupField(t *testing.T) {
diff --git a/datadictionary/message_def_test.go b/datadictionary/message_def_test.go
index 666751fed..73de16452 100644
--- a/datadictionary/message_def_test.go
+++ b/datadictionary/message_def_test.go
@@ -3,8 +3,9 @@ package datadictionary_test
import (
"testing"
- "github.com/quickfixgo/quickfix/datadictionary"
"github.com/stretchr/testify/assert"
+
+ "github.com/quickfixgo/quickfix/datadictionary"
)
func TestNewMessageDef(t *testing.T) {
diff --git a/datadictionary/xml.go b/datadictionary/xml.go
index 7471a75bc..a25e5b4e0 100644
--- a/datadictionary/xml.go
+++ b/datadictionary/xml.go
@@ -4,7 +4,7 @@ import (
"encoding/xml"
)
-//XMLDoc is the unmarshalled root of a FIX Dictionary.
+// XMLDoc is the unmarshalled root of a FIX Dictionary.
type XMLDoc struct {
Type string `xml:"type,attr"`
Major string `xml:"major,attr"`
@@ -27,7 +27,7 @@ type XMLComponent struct {
Members []*XMLComponentMember `xml:",any"`
}
-//XMLField represents the fields/field xml element.
+// XMLField represents the fields/field xml element.
type XMLField struct {
Number int `xml:"number,attr"`
Name string `xml:"name,attr"`
@@ -35,13 +35,13 @@ type XMLField struct {
Values []*XMLValue `xml:"value"`
}
-//XMLValue represents the fields/field/value xml element.
+// XMLValue represents the fields/field/value xml element.
type XMLValue struct {
Enum string `xml:"enum,attr"`
Description string `xml:"description,attr"`
}
-//XMLComponentMember represents child elements of header, trailer, messages/message, and components/component elements
+// XMLComponentMember represents child elements of header, trailer, messages/message, and components/component elements.
type XMLComponentMember struct {
XMLName xml.Name
Name string `xml:"name,attr"`
diff --git a/dialer.go b/dialer.go
new file mode 100644
index 000000000..d1654aa31
--- /dev/null
+++ b/dialer.go
@@ -0,0 +1,72 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
+package quickfix
+
+import (
+ "fmt"
+ "net"
+
+ "golang.org/x/net/proxy"
+
+ "github.com/quickfixgo/quickfix/config"
+)
+
+func loadDialerConfig(settings *SessionSettings) (dialer proxy.Dialer, err error) {
+ stdDialer := &net.Dialer{}
+ if settings.HasSetting(config.SocketTimeout) {
+ if stdDialer.Timeout, err = settings.DurationSetting(config.SocketTimeout); err != nil {
+ return
+ }
+ }
+ dialer = stdDialer
+
+ if !settings.HasSetting(config.ProxyType) {
+ return
+ }
+
+ var proxyType string
+ if proxyType, err = settings.Setting(config.ProxyType); err != nil {
+ return
+ }
+
+ switch proxyType {
+ case "socks":
+ var proxyHost string
+ var proxyPort int
+ if proxyHost, err = settings.Setting(config.ProxyHost); err != nil {
+ return
+ } else if proxyPort, err = settings.IntSetting(config.ProxyPort); err != nil {
+ return
+ }
+
+ proxyAuth := new(proxy.Auth)
+ if settings.HasSetting(config.ProxyUser) {
+ if proxyAuth.User, err = settings.Setting(config.ProxyUser); err != nil {
+ return
+ }
+ }
+ if settings.HasSetting(config.ProxyPassword) {
+ if proxyAuth.Password, err = settings.Setting(config.ProxyPassword); err != nil {
+ return
+ }
+ }
+
+ dialer, err = proxy.SOCKS5("tcp", fmt.Sprintf("%s:%d", proxyHost, proxyPort), proxyAuth, dialer)
+ default:
+ err = fmt.Errorf("unsupported proxy type %s", proxyType)
+ }
+ return
+}
diff --git a/dialer_test.go b/dialer_test.go
new file mode 100644
index 000000000..5f8599c1e
--- /dev/null
+++ b/dialer_test.go
@@ -0,0 +1,92 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
+package quickfix
+
+import (
+ "net"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/config"
+)
+
+type DialerTestSuite struct {
+ suite.Suite
+ settings *Settings
+}
+
+func TestDialerTestSuite(t *testing.T) {
+ suite.Run(t, new(DialerTestSuite))
+}
+
+func (s *DialerTestSuite) SetupTest() {
+ s.settings = NewSettings()
+}
+
+func (s *DialerTestSuite) TestLoadDialerNoSettings() {
+ dialer, err := loadDialerConfig(s.settings.GlobalSettings())
+ s.Require().Nil(err)
+
+ stdDialer, ok := dialer.(*net.Dialer)
+ s.Require().True(ok)
+ s.Require().NotNil(stdDialer)
+ s.Zero(stdDialer.Timeout)
+}
+
+func (s *DialerTestSuite) TestLoadDialerWithTimeout() {
+ s.settings.GlobalSettings().Set(config.SocketTimeout, "10s")
+ dialer, err := loadDialerConfig(s.settings.GlobalSettings())
+ s.Require().Nil(err)
+
+ stdDialer, ok := dialer.(*net.Dialer)
+ s.Require().True(ok)
+ s.Require().NotNil(stdDialer)
+ s.EqualValues(10*time.Second, stdDialer.Timeout)
+}
+
+func (s *DialerTestSuite) TestLoadDialerInvalidProxy() {
+ s.settings.GlobalSettings().Set(config.ProxyType, "totallyinvalidproxytype")
+ _, err := loadDialerConfig(s.settings.GlobalSettings())
+ s.Require().NotNil(err)
+}
+
+func (s *DialerTestSuite) TestLoadDialerSocksProxy() {
+ s.settings.GlobalSettings().Set(config.ProxyType, "socks")
+ s.settings.GlobalSettings().Set(config.ProxyHost, "localhost")
+ s.settings.GlobalSettings().Set(config.ProxyPort, "31337")
+ dialer, err := loadDialerConfig(s.settings.GlobalSettings())
+ s.Require().Nil(err)
+ s.Require().NotNil(dialer)
+
+ _, ok := dialer.(*net.Dialer)
+ s.Require().False(ok)
+}
+
+func (s *DialerTestSuite) TestLoadDialerSocksProxyInvalidHost() {
+ s.settings.GlobalSettings().Set(config.ProxyType, "socks")
+ s.settings.GlobalSettings().Set(config.ProxyPort, "31337")
+ _, err := loadDialerConfig(s.settings.GlobalSettings())
+ s.Require().NotNil(err)
+}
+
+func (s *DialerTestSuite) TestLoadDialerSocksProxyInvalidPort() {
+ s.settings.GlobalSettings().Set(config.ProxyType, "socks")
+ s.settings.GlobalSettings().Set(config.ProxyHost, "localhost")
+ _, err := loadDialerConfig(s.settings.GlobalSettings())
+ s.Require().NotNil(err)
+}
diff --git a/doc.go b/doc.go
index 76f7b7b71..3d53535c3 100644
--- a/doc.go
+++ b/doc.go
@@ -1,6 +1,19 @@
-/*
-Package quickfix is a full featured messaging engine for the FIX protocol. It is a 100% Go open source implementation of the popular C++ QuickFIX engine (http://quickfixengine.org).
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
-User manual and additional information available at http://quickfixgo.org
-*/
+// Package quickfix is a full featured messaging engine for the FIX protocol. It is a 100% Go open source implementation of the popular C++ QuickFIX engine (http://quickfixengine.org).
+//
+// User manual and additional information available at https://quickfixgo.org
package quickfix
diff --git a/errors.go b/errors.go
index af949ebd4..e9d120991 100644
--- a/errors.go
+++ b/errors.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -5,10 +20,10 @@ import (
"fmt"
)
-//ErrDoNotSend is a convenience error to indicate a DoNotSend in ToApp
+// ErrDoNotSend is a convenience error to indicate a DoNotSend in ToApp.
var ErrDoNotSend = errors.New("Do Not Send")
-//rejectReason enum values.
+// rejectReason enum values.
const (
rejectReasonInvalidTagNumber = 0
rejectReasonRequiredTagMissing = 1
@@ -27,61 +42,73 @@ const (
rejectReasonIncorrectNumInGroupCountForRepeatingGroup = 16
)
-//MessageRejectError is a type of error that can correlate to a message reject.
+// MessageRejectError is a type of error that can correlate to a message reject.
type MessageRejectError interface {
error
- //RejectReason, tag 373 for session rejects, tag 380 for business rejects.
+ // RejectReason, tag 373 for session rejects, tag 380 for business rejects.
RejectReason() int
+ BusinessRejectRefID() string
RefTagID() *Tag
IsBusinessReject() bool
}
-//RejectLogon indicates the application is rejecting permission to logon. Implements MessageRejectError
+// RejectLogon indicates the application is rejecting permission to logon. Implements MessageRejectError.
type RejectLogon struct {
Text string
}
func (e RejectLogon) Error() string { return e.Text }
-//RefTagID implements MessageRejectError
+// RefTagID implements MessageRejectError.
func (RejectLogon) RefTagID() *Tag { return nil }
-//RejectReason implements MessageRejectError
+// RejectReason implements MessageRejectError.
func (RejectLogon) RejectReason() int { return 0 }
-//IsBusinessReject implements MessageRejectError
+// BusinessRejectRefID implements MessageRejectError.
+func (RejectLogon) BusinessRejectRefID() string { return "" }
+
+// IsBusinessReject implements MessageRejectError.
func (RejectLogon) IsBusinessReject() bool { return false }
type messageRejectError struct {
- rejectReason int
- text string
- refTagID *Tag
- isBusinessReject bool
+ rejectReason int
+ text string
+ businessRejectRefID string
+ refTagID *Tag
+ isBusinessReject bool
}
-func (e messageRejectError) Error() string { return e.text }
-func (e messageRejectError) RefTagID() *Tag { return e.refTagID }
-func (e messageRejectError) RejectReason() int { return e.rejectReason }
-func (e messageRejectError) IsBusinessReject() bool { return e.isBusinessReject }
+func (e messageRejectError) Error() string { return e.text }
+func (e messageRejectError) RefTagID() *Tag { return e.refTagID }
+func (e messageRejectError) RejectReason() int { return e.rejectReason }
+func (e messageRejectError) BusinessRejectRefID() string { return e.businessRejectRefID }
+func (e messageRejectError) IsBusinessReject() bool { return e.isBusinessReject }
-//NewMessageRejectError returns a MessageRejectError with the given error message, reject reason, and optional reftagid
+// NewMessageRejectError returns a MessageRejectError with the given error message, reject reason, and optional reftagid.
func NewMessageRejectError(err string, rejectReason int, refTagID *Tag) MessageRejectError {
return messageRejectError{text: err, rejectReason: rejectReason, refTagID: refTagID}
}
-//NewBusinessMessageRejectError returns a MessageRejectError with the given error mesage, reject reason, and optional reftagid.
-//Reject is treated as a business level reject
+// NewBusinessMessageRejectError returns a MessageRejectError with the given error mesage, reject reason, and optional reftagid.
+// Reject is treated as a business level reject.
func NewBusinessMessageRejectError(err string, rejectReason int, refTagID *Tag) MessageRejectError {
return messageRejectError{text: err, rejectReason: rejectReason, refTagID: refTagID, isBusinessReject: true}
}
-//IncorrectDataFormatForValue returns an error indicating a field that cannot be parsed as the type required.
+// NewBusinessMessageRejectErrorWithRefID returns a MessageRejectError with the given error mesage, reject reason, refID, and optional reftagid.
+// Reject is treated as a business level reject.
+func NewBusinessMessageRejectErrorWithRefID(err string, rejectReason int, businessRejectRefID string, refTagID *Tag) MessageRejectError {
+ return messageRejectError{text: err, rejectReason: rejectReason, refTagID: refTagID, businessRejectRefID: businessRejectRefID, isBusinessReject: true}
+}
+
+// IncorrectDataFormatForValue returns an error indicating a field that cannot be parsed as the type required.
func IncorrectDataFormatForValue(tag Tag) MessageRejectError {
return NewMessageRejectError("Incorrect data format for value", rejectReasonIncorrectDataFormatForValue, &tag)
}
-//repeatingGroupFieldsOutOfOrder returns an error indicating a problem parsing repeating groups fields
+// repeatingGroupFieldsOutOfOrder returns an error indicating a problem parsing repeating groups fields.
func repeatingGroupFieldsOutOfOrder(tag Tag, reason string) MessageRejectError {
if reason != "" {
reason = fmt.Sprintf("Repeating group fields out of order (%s)", reason)
@@ -91,73 +118,73 @@ func repeatingGroupFieldsOutOfOrder(tag Tag, reason string) MessageRejectError {
return NewMessageRejectError(reason, rejectReasonRepeatingGroupFieldsOutOfOrder, &tag)
}
-//ValueIsIncorrect returns an error indicating a field with value that is not valid.
+// ValueIsIncorrect returns an error indicating a field with value that is not valid.
func ValueIsIncorrect(tag Tag) MessageRejectError {
return NewMessageRejectError("Value is incorrect (out of range) for this tag", rejectReasonValueIsIncorrect, &tag)
}
-//ConditionallyRequiredFieldMissing indicates that the requested field could not be found in the FIX message.
+// ConditionallyRequiredFieldMissing indicates that the requested field could not be found in the FIX message.
func ConditionallyRequiredFieldMissing(tag Tag) MessageRejectError {
return NewBusinessMessageRejectError(fmt.Sprintf("Conditionally Required Field Missing (%d)", tag), rejectReasonConditionallyRequiredFieldMissing, &tag)
}
-//valueIsIncorrectNoTag returns an error indicating a field with value that is not valid.
-//FIXME: to be compliant with legacy tests, for certain value issues, do not include reftag? (11c_NewSeqNoLess)
+// valueIsIncorrectNoTag returns an error indicating a field with value that is not valid.
+// FIXME: to be compliant with legacy tests, for certain value issues, do not include reftag? (11c_NewSeqNoLess).
func valueIsIncorrectNoTag() MessageRejectError {
return NewMessageRejectError("Value is incorrect (out of range) for this tag", rejectReasonValueIsIncorrect, nil)
}
-//InvalidMessageType returns an error to indicate an invalid message type
+// InvalidMessageType returns an error to indicate an invalid message type.
func InvalidMessageType() MessageRejectError {
return NewMessageRejectError("Invalid MsgType", rejectReasonInvalidMsgType, nil)
}
-//UnsupportedMessageType returns an error to indicate an unhandled message.
+// UnsupportedMessageType returns an error to indicate an unhandled message.
func UnsupportedMessageType() MessageRejectError {
return NewBusinessMessageRejectError("Unsupported Message Type", rejectReasonUnsupportedMessageType, nil)
}
-//TagNotDefinedForThisMessageType returns an error for an invalid tag appearing in a message.
+// TagNotDefinedForThisMessageType returns an error for an invalid tag appearing in a message.
func TagNotDefinedForThisMessageType(tag Tag) MessageRejectError {
return NewMessageRejectError("Tag not defined for this message type", rejectReasonTagNotDefinedForThisMessageType, &tag)
}
-//tagAppearsMoreThanOnce return an error for multiple tags in a message not detected as a repeating group.
+// tagAppearsMoreThanOnce return an error for multiple tags in a message not detected as a repeating group.
func tagAppearsMoreThanOnce(tag Tag) MessageRejectError {
return NewMessageRejectError("Tag appears more than once", rejectReasonTagAppearsMoreThanOnce, &tag)
}
-//RequiredTagMissing returns a validation error when a required field cannot be found in a message.
+// RequiredTagMissing returns a validation error when a required field cannot be found in a message.
func RequiredTagMissing(tag Tag) MessageRejectError {
return NewMessageRejectError("Required tag missing", rejectReasonRequiredTagMissing, &tag)
}
-//incorrectNumInGroupCountForRepeatingGroup returns a validation error when the num in group value for a group does not match actual group size.
+// incorrectNumInGroupCountForRepeatingGroup returns a validation error when the num in group value for a group does not match actual group size.
func incorrectNumInGroupCountForRepeatingGroup(tag Tag) MessageRejectError {
return NewMessageRejectError("Incorrect NumInGroup count for repeating group", rejectReasonIncorrectNumInGroupCountForRepeatingGroup, &tag)
}
-//tagSpecifiedOutOfRequiredOrder returns validation error when the group order does not match the spec.
+// tagSpecifiedOutOfRequiredOrder returns validation error when the group order does not match the spec.
func tagSpecifiedOutOfRequiredOrder(tag Tag) MessageRejectError {
return NewMessageRejectError("Tag specified out of required order", rejectReasonTagSpecifiedOutOfRequiredOrder, &tag)
}
-//TagSpecifiedWithoutAValue returns a validation error for when a field has no value.
+// TagSpecifiedWithoutAValue returns a validation error for when a field has no value.
func TagSpecifiedWithoutAValue(tag Tag) MessageRejectError {
return NewMessageRejectError("Tag specified without a value", rejectReasonTagSpecifiedWithoutAValue, &tag)
}
-//InvalidTagNumber returns a validation error for messages with invalid tags.
+// InvalidTagNumber returns a validation error for messages with invalid tags.
func InvalidTagNumber(tag Tag) MessageRejectError {
return NewMessageRejectError("Invalid tag number", rejectReasonInvalidTagNumber, &tag)
}
-//compIDProblem creates a reject for msg where msg has invalid comp id values.
+// compIDProblem creates a reject for msg where msg has invalid comp id values.
func compIDProblem() MessageRejectError {
return NewMessageRejectError("CompID problem", rejectReasonCompIDProblem, nil)
}
-//sendingTimeAccuracyProblem creates a reject for a msg with stale or invalid sending time.
+// sendingTimeAccuracyProblem creates a reject for a msg with stale or invalid sending time.
func sendingTimeAccuracyProblem() MessageRejectError {
return NewMessageRejectError("SendingTime accuracy problem", rejectReasonSendingTimeAccuracyProblem, nil)
}
diff --git a/errors_test.go b/errors_test.go
index 56554af51..c5b77d9b6 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -52,6 +67,33 @@ func TestNewBusinessMessageRejectError(t *testing.T) {
}
}
+func TestNewBusinessMessageRejectErrorWithRefID(t *testing.T) {
+ var (
+ expectedErrorString = "Custom error"
+ expectedRejectReason = 5
+ expectedbusinessRejectRefID = "1"
+ expectedRefTagID Tag = 44
+ expectedIsBusinessReject = true
+ )
+ msgRej := NewBusinessMessageRejectErrorWithRefID(expectedErrorString, expectedRejectReason, expectedbusinessRejectRefID, &expectedRefTagID)
+
+ if strings.Compare(msgRej.Error(), expectedErrorString) != 0 {
+ t.Errorf("expected: %s, got: %s\n", expectedErrorString, msgRej.Error())
+ }
+ if msgRej.RejectReason() != expectedRejectReason {
+ t.Errorf("expected: %d, got: %d\n", expectedRejectReason, msgRej.RejectReason())
+ }
+ if strings.Compare(msgRej.BusinessRejectRefID(), expectedbusinessRejectRefID) != 0 {
+ t.Errorf("expected: %s, got: %s\n", expectedbusinessRejectRefID, msgRej.BusinessRejectRefID())
+ }
+ if *msgRej.RefTagID() != expectedRefTagID {
+ t.Errorf("expected: %d, got: %d\n", expectedRefTagID, msgRej.RefTagID())
+ }
+ if msgRej.IsBusinessReject() != expectedIsBusinessReject {
+ t.Error("Expected IsBusinessReject to be true\n")
+ }
+}
+
func TestIncorrectDataFormatForValue(t *testing.T) {
var (
expectedErrorString = "Incorrect data format for value"
diff --git a/field.go b/field.go
index 044e31418..9dc00dac7 100644
--- a/field.go
+++ b/field.go
@@ -1,48 +1,64 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
-//FieldValueWriter is an interface for writing field values
+// FieldValueWriter is an interface for writing field values.
type FieldValueWriter interface {
- //Writes out the contents of the FieldValue to a []byte
+ // Write writes out the contents of the FieldValue to a `[]byte`.
Write() []byte
}
-//FieldValueReader is an interface for reading field values
+// FieldValueReader is an interface for reading field values.
type FieldValueReader interface {
- //Reads the contents of the []byte into FieldValue. Returns an error if there are issues in the data processing
+ // Read reads the contents of the `[]byte` into FieldValue.
+ // Returns an error if there are issues in the data processing.
Read([]byte) error
}
-//The FieldValue interface is used to write/extract typed field values to/from raw bytes
+// The FieldValue interface is used to write/extract typed field values to/from raw bytes.
type FieldValue interface {
FieldValueWriter
FieldValueReader
}
-//FieldWriter is an interface for a writing a field
+// FieldWriter is an interface for a writing a field.
type FieldWriter interface {
Tag() Tag
FieldValueWriter
}
-//Field is the interface implemented by all typed Fields in a Message
+// Field is the interface implemented by all typed Fields in a Message.
type Field interface {
FieldWriter
FieldValueReader
}
-//FieldGroupWriter is an interface for writing a FieldGroup
+// FieldGroupWriter is an interface for writing a FieldGroup.
type FieldGroupWriter interface {
Tag() Tag
Write() []TagValue
}
-//FieldGroupReader is an interface for reading a FieldGroup
+// FieldGroupReader is an interface for reading a FieldGroup.
type FieldGroupReader interface {
Tag() Tag
Read([]TagValue) ([]TagValue, error)
}
-//FieldGroup is the interface implemented by all typed Groups in a Message
+// FieldGroup is the interface implemented by all typed Groups in a Message.
type FieldGroup interface {
Tag() Tag
Write() []TagValue
diff --git a/field_map.go b/field_map.go
index 4284fc280..18216f81c 100644
--- a/field_map.go
+++ b/field_map.go
@@ -1,12 +1,28 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"bytes"
"sort"
+ "sync"
"time"
)
-//field stores a slice of TagValues
+// field stores a slice of TagValues.
type field []TagValue
func fieldTag(f field) Tag {
@@ -23,7 +39,7 @@ func writeField(f field, buffer *bytes.Buffer) {
}
}
-// tagOrder true if tag i should occur before tag j
+// tagOrder true if tag i should occur before tag j.
type tagOrder func(i, j Tag) bool
type tagSort struct {
@@ -35,13 +51,14 @@ func (t tagSort) Len() int { return len(t.tags) }
func (t tagSort) Swap(i, j int) { t.tags[i], t.tags[j] = t.tags[j], t.tags[i] }
func (t tagSort) Less(i, j int) bool { return t.compare(t.tags[i], t.tags[j]) }
-//FieldMap is a collection of fix fields that make up a fix message.
+// FieldMap is a collection of fix fields that make up a fix message.
type FieldMap struct {
tagLookup map[Tag]field
tagSort
+ rwLock *sync.RWMutex
}
-// ascending tags
+// ascending tags.
func normalFieldOrder(i, j Tag) bool { return i < j }
func (m *FieldMap) init() {
@@ -49,12 +66,16 @@ func (m *FieldMap) init() {
}
func (m *FieldMap) initWithOrdering(ordering tagOrder) {
+ m.rwLock = &sync.RWMutex{}
m.tagLookup = make(map[Tag]field)
m.compare = ordering
}
-//Tags returns all of the Field Tags in this FieldMap
+// Tags returns all of the Field Tags in this FieldMap.
func (m FieldMap) Tags() []Tag {
+ m.rwLock.RLock()
+ defer m.rwLock.RUnlock()
+
tags := make([]Tag, 0, len(m.tagLookup))
for t := range m.tagLookup {
tags = append(tags, t)
@@ -63,19 +84,25 @@ func (m FieldMap) Tags() []Tag {
return tags
}
-//Get parses out a field in this FieldMap. Returned reject may indicate the field is not present, or the field value is invalid.
+// Get parses out a field in this FieldMap. Returned reject may indicate the field is not present, or the field value is invalid.
func (m FieldMap) Get(parser Field) MessageRejectError {
return m.GetField(parser.Tag(), parser)
}
-//Has returns true if the Tag is present in this FieldMap
+// Has returns true if the Tag is present in this FieldMap.
func (m FieldMap) Has(tag Tag) bool {
+ m.rwLock.RLock()
+ defer m.rwLock.RUnlock()
+
_, ok := m.tagLookup[tag]
return ok
}
-//GetField parses of a field with Tag tag. Returned reject may indicate the field is not present, or the field value is invalid.
+// GetField parses of a field with Tag tag. Returned reject may indicate the field is not present, or the field value is invalid.
func (m FieldMap) GetField(tag Tag, parser FieldValueReader) MessageRejectError {
+ m.rwLock.RLock()
+ defer m.rwLock.RUnlock()
+
f, ok := m.tagLookup[tag]
if !ok {
return ConditionallyRequiredFieldMissing(tag)
@@ -88,8 +115,11 @@ func (m FieldMap) GetField(tag Tag, parser FieldValueReader) MessageRejectError
return nil
}
-//GetBytes is a zero-copy GetField wrapper for []bytes fields
+// GetBytes is a zero-copy GetField wrapper for []bytes fields.
func (m FieldMap) GetBytes(tag Tag) ([]byte, MessageRejectError) {
+ m.rwLock.RLock()
+ defer m.rwLock.RUnlock()
+
f, ok := m.tagLookup[tag]
if !ok {
return nil, ConditionallyRequiredFieldMissing(tag)
@@ -98,7 +128,7 @@ func (m FieldMap) GetBytes(tag Tag) ([]byte, MessageRejectError) {
return f[0].value, nil
}
-//GetBool is a GetField wrapper for bool fields
+// GetBool is a GetField wrapper for bool fields.
func (m FieldMap) GetBool(tag Tag) (bool, MessageRejectError) {
var val FIXBoolean
if err := m.GetField(tag, &val); err != nil {
@@ -107,7 +137,7 @@ func (m FieldMap) GetBool(tag Tag) (bool, MessageRejectError) {
return bool(val), nil
}
-//GetInt is a GetField wrapper for int fields
+// GetInt is a GetField wrapper for int fields.
func (m FieldMap) GetInt(tag Tag) (int, MessageRejectError) {
bytes, err := m.GetBytes(tag)
if err != nil {
@@ -122,8 +152,11 @@ func (m FieldMap) GetInt(tag Tag) (int, MessageRejectError) {
return int(val), err
}
-//GetTime is a GetField wrapper for utc timestamp fields
+// GetTime is a GetField wrapper for utc timestamp fields.
func (m FieldMap) GetTime(tag Tag) (t time.Time, err MessageRejectError) {
+ m.rwLock.RLock()
+ defer m.rwLock.RUnlock()
+
bytes, err := m.GetBytes(tag)
if err != nil {
return
@@ -137,7 +170,7 @@ func (m FieldMap) GetTime(tag Tag) (t time.Time, err MessageRejectError) {
return val.Time, err
}
-//GetString is a GetField wrapper for string fields
+// GetString is a GetField wrapper for string fields.
func (m FieldMap) GetString(tag Tag) (string, MessageRejectError) {
var val FIXString
if err := m.GetField(tag, &val); err != nil {
@@ -146,8 +179,11 @@ func (m FieldMap) GetString(tag Tag) (string, MessageRejectError) {
return string(val), nil
}
-//GetGroup is a Get function specific to Group Fields.
+// GetGroup is a Get function specific to Group Fields.
func (m FieldMap) GetGroup(parser FieldGroupReader) MessageRejectError {
+ m.rwLock.RLock()
+ defer m.rwLock.RUnlock()
+
f, ok := m.tagLookup[parser.Tag()]
if !ok {
return ConditionallyRequiredFieldMissing(parser.Tag())
@@ -163,44 +199,58 @@ func (m FieldMap) GetGroup(parser FieldGroupReader) MessageRejectError {
return nil
}
-//SetField sets the field with Tag tag
+// SetField sets the field with Tag tag.
func (m *FieldMap) SetField(tag Tag, field FieldValueWriter) *FieldMap {
return m.SetBytes(tag, field.Write())
}
-//SetBytes sets bytes
+// SetBytes sets bytes.
func (m *FieldMap) SetBytes(tag Tag, value []byte) *FieldMap {
f := m.getOrCreate(tag)
initField(f, tag, value)
return m
}
-//SetBool is a SetField wrapper for bool fields
+// SetBool is a SetField wrapper for bool fields.
func (m *FieldMap) SetBool(tag Tag, value bool) *FieldMap {
return m.SetField(tag, FIXBoolean(value))
}
-//SetInt is a SetField wrapper for int fields
+// SetInt is a SetField wrapper for int fields.
func (m *FieldMap) SetInt(tag Tag, value int) *FieldMap {
v := FIXInt(value)
return m.SetBytes(tag, v.Write())
}
-//SetString is a SetField wrapper for string fields
+// SetString is a SetField wrapper for string fields.
func (m *FieldMap) SetString(tag Tag, value string) *FieldMap {
return m.SetBytes(tag, []byte(value))
}
-//Clear purges all fields from field map
+// Remove removes a tag from field map.
+func (m *FieldMap) Remove(tag Tag) {
+ m.rwLock.Lock()
+ defer m.rwLock.Unlock()
+
+ delete(m.tagLookup, tag)
+}
+
+// Clear purges all fields from field map.
func (m *FieldMap) Clear() {
+ m.rwLock.Lock()
+ defer m.rwLock.Unlock()
+
m.tags = m.tags[0:0]
for k := range m.tagLookup {
delete(m.tagLookup, k)
}
}
-//CopyInto overwrites the given FieldMap with this one
+// CopyInto overwrites the given FieldMap with this one.
func (m *FieldMap) CopyInto(to *FieldMap) {
+ m.rwLock.RLock()
+ defer m.rwLock.RUnlock()
+
to.tagLookup = make(map[Tag]field)
for tag, f := range m.tagLookup {
clone := make(field, 1)
@@ -213,6 +263,9 @@ func (m *FieldMap) CopyInto(to *FieldMap) {
}
func (m *FieldMap) add(f field) {
+ m.rwLock.Lock()
+ defer m.rwLock.Unlock()
+
t := fieldTag(f)
if _, ok := m.tagLookup[t]; !ok {
m.tags = append(m.tags, t)
@@ -222,6 +275,9 @@ func (m *FieldMap) add(f field) {
}
func (m *FieldMap) getOrCreate(tag Tag) field {
+ m.rwLock.Lock()
+ defer m.rwLock.Unlock()
+
if f, ok := m.tagLookup[tag]; ok {
f = f[:1]
return f
@@ -233,15 +289,18 @@ func (m *FieldMap) getOrCreate(tag Tag) field {
return f
}
-//Set is a setter for fields
+// Set is a setter for fields.
func (m *FieldMap) Set(field FieldWriter) *FieldMap {
f := m.getOrCreate(field.Tag())
initField(f, field.Tag(), field.Write())
return m
}
-//SetGroup is a setter specific to group fields
+// SetGroup is a setter specific to group fields.
func (m *FieldMap) SetGroup(field FieldGroupWriter) *FieldMap {
+ m.rwLock.Lock()
+ defer m.rwLock.Unlock()
+
_, ok := m.tagLookup[field.Tag()]
if !ok {
m.tags = append(m.tags, field.Tag())
@@ -256,6 +315,9 @@ func (m *FieldMap) sortedTags() []Tag {
}
func (m FieldMap) write(buffer *bytes.Buffer) {
+ m.rwLock.Lock()
+ defer m.rwLock.Unlock()
+
for _, tag := range m.sortedTags() {
if f, ok := m.tagLookup[tag]; ok {
writeField(f, buffer)
@@ -264,11 +326,14 @@ func (m FieldMap) write(buffer *bytes.Buffer) {
}
func (m FieldMap) total() int {
+ m.rwLock.RLock()
+ defer m.rwLock.RUnlock()
+
total := 0
for _, fields := range m.tagLookup {
for _, tv := range fields {
switch tv.tag {
- case tagCheckSum: //tag does not contribute to total
+ case tagCheckSum: // Tag does not contribute to total.
default:
total += tv.total()
}
@@ -279,11 +344,14 @@ func (m FieldMap) total() int {
}
func (m FieldMap) length() int {
+ m.rwLock.RLock()
+ defer m.rwLock.RUnlock()
+
length := 0
for _, fields := range m.tagLookup {
for _, tv := range fields {
switch tv.tag {
- case tagBeginString, tagBodyLength, tagCheckSum: //tags do not contribute to length
+ case tagBeginString, tagBodyLength, tagCheckSum: // Tags do not contribute to length.
default:
length += tv.length()
}
diff --git a/field_map_test.go b/field_map_test.go
index c984f7c45..0e9078734 100644
--- a/field_map_test.go
+++ b/field_map_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -157,7 +172,7 @@ func TestFieldMap_CopyInto(t *testing.T) {
assert.Equal(t, "a", s)
// old fields cleared
- s, err = fMapB.GetString(3)
+ _, err = fMapB.GetString(3)
assert.NotNil(t, err)
// check that ordering is overwritten
@@ -175,3 +190,15 @@ func TestFieldMap_CopyInto(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, "a", s)
}
+
+func TestFieldMap_Remove(t *testing.T) {
+ var fMap FieldMap
+ fMap.init()
+
+ fMap.SetField(1, FIXString("hello"))
+ fMap.SetField(2, FIXString("world"))
+
+ fMap.Remove(1)
+ assert.False(t, fMap.Has(1))
+ assert.True(t, fMap.Has(2))
+}
diff --git a/file_log.go b/file_log.go
index 02f27b001..85eefab1d 100644
--- a/file_log.go
+++ b/file_log.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -35,8 +50,8 @@ type fileLogFactory struct {
sessionLogPaths map[SessionID]string
}
-//NewFileLogFactory creates an instance of LogFactory that writes messages and events to file.
-//The location of global and session log files is configured via FileLogPath.
+// NewFileLogFactory creates an instance of LogFactory that writes messages and events to file.
+// The location of global and session log files is configured via FileLogPath.
func NewFileLogFactory(settings *Settings) (LogFactory, error) {
logFactory := fileLogFactory{}
diff --git a/file_log_test.go b/file_log_test.go
index b807dca42..b303ec290 100644
--- a/file_log_test.go
+++ b/file_log_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -11,7 +26,7 @@ import (
func TestFileLog_NewFileLogFactory(t *testing.T) {
- factory, err := NewFileLogFactory(NewSettings())
+ _, err := NewFileLogFactory(NewSettings())
if err == nil {
t.Error("Should expect error when settings have no file log path")
@@ -39,7 +54,7 @@ SessionQualifier=BS
stringReader := strings.NewReader(cfg)
settings, _ := ParseSettings(stringReader)
- factory, err = NewFileLogFactory(settings)
+ factory, err := NewFileLogFactory(settings)
if err != nil {
t.Error("Did not expect error", err)
diff --git a/filestore.go b/filestore.go
index aa2f7cde3..120ba83c0 100644
--- a/filestore.go
+++ b/filestore.go
@@ -1,13 +1,31 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"fmt"
+ "io"
"io/ioutil"
"os"
"path"
"strconv"
"time"
+ "github.com/pkg/errors"
+
"github.com/quickfixgo/quickfix/config"
)
@@ -34,27 +52,45 @@ type fileStore struct {
sessionFile *os.File
senderSeqNumsFile *os.File
targetSeqNumsFile *os.File
+ fileSync bool
}
-// NewFileStoreFactory returns a file-based implementation of MessageStoreFactory
+// NewFileStoreFactory returns a file-based implementation of MessageStoreFactory.
func NewFileStoreFactory(settings *Settings) MessageStoreFactory {
return fileStoreFactory{settings: settings}
}
-// Create creates a new FileStore implementation of the MessageStore interface
+// Create creates a new FileStore implementation of the MessageStore interface.
func (f fileStoreFactory) Create(sessionID SessionID) (msgStore MessageStore, err error) {
+ globalSettings := f.settings.GlobalSettings()
+ dynamicSessions, _ := globalSettings.BoolSetting(config.DynamicSessions)
+
sessionSettings, ok := f.settings.SessionSettings()[sessionID]
if !ok {
- return nil, fmt.Errorf("unknown session: %v", sessionID)
+ if dynamicSessions {
+ sessionSettings = globalSettings
+ } else {
+ return nil, fmt.Errorf("unknown session: %v", sessionID)
+ }
}
+
dirname, err := sessionSettings.Setting(config.FileStorePath)
if err != nil {
return nil, err
}
- return newFileStore(sessionID, dirname)
+ var fsync bool
+ if sessionSettings.HasSetting(config.FileStoreSync) {
+ fsync, err = sessionSettings.BoolSetting(config.FileStoreSync)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ fsync = true //existing behavior is to fsync writes
+ }
+ return newFileStore(sessionID, dirname, fsync)
}
-func newFileStore(sessionID SessionID, dirname string) (*fileStore, error) {
+func newFileStore(sessionID SessionID, dirname string, fileSync bool) (*fileStore, error) {
if err := os.MkdirAll(dirname, os.ModePerm); err != nil {
return nil, err
}
@@ -70,6 +106,7 @@ func newFileStore(sessionID SessionID, dirname string) (*fileStore, error) {
sessionFname: path.Join(dirname, fmt.Sprintf("%s.%s", sessionPrefix, "session")),
senderSeqNumsFname: path.Join(dirname, fmt.Sprintf("%s.%s", sessionPrefix, "senderseqnums")),
targetSeqNumsFname: path.Join(dirname, fmt.Sprintf("%s.%s", sessionPrefix, "targetseqnums")),
+ fileSync: fileSync,
}
if err := store.Refresh(); err != nil {
@@ -79,11 +116,14 @@ func newFileStore(sessionID SessionID, dirname string) (*fileStore, error) {
return store, nil
}
-// Reset deletes the store files and sets the seqnums back to 1
+// Reset deletes the store files and sets the seqnums back to 1.
func (store *fileStore) Reset() error {
- store.cache.Reset()
+ if err := store.cache.Reset(); err != nil {
+ return errors.Wrap(err, "cache reset")
+ }
+
if err := store.Close(); err != nil {
- return err
+ return errors.Wrap(err, "close")
}
if err := removeFile(store.bodyFname); err != nil {
return err
@@ -103,9 +143,12 @@ func (store *fileStore) Reset() error {
return store.Refresh()
}
-// Refresh closes the store files and then reloads from them
+// Refresh closes the store files and then reloads from them.
func (store *fileStore) Refresh() (err error) {
- store.cache.Reset()
+ if err = store.cache.Reset(); err != nil {
+ err = errors.Wrap(err, "cache reset")
+ return
+ }
if err = store.Close(); err != nil {
return err
@@ -138,8 +181,13 @@ func (store *fileStore) Refresh() (err error) {
}
}
- store.SetNextSenderMsgSeqNum(store.NextSenderMsgSeqNum())
- store.SetNextTargetMsgSeqNum(store.NextTargetMsgSeqNum())
+ if err := store.SetNextSenderMsgSeqNum(store.NextSenderMsgSeqNum()); err != nil {
+ return errors.Wrap(err, "set next sender")
+ }
+
+ if err := store.SetNextTargetMsgSeqNum(store.NextTargetMsgSeqNum()); err != nil {
+ return errors.Wrap(err, "set next target")
+ }
return nil
}
@@ -166,13 +214,17 @@ func (store *fileStore) populateCache() (creationTimePopulated bool, err error)
if senderSeqNumBytes, err := ioutil.ReadFile(store.senderSeqNumsFname); err == nil {
if senderSeqNum, err := strconv.Atoi(string(senderSeqNumBytes)); err == nil {
- store.cache.SetNextSenderMsgSeqNum(senderSeqNum)
+ if err = store.cache.SetNextSenderMsgSeqNum(senderSeqNum); err != nil {
+ return creationTimePopulated, errors.Wrap(err, "cache set next sender")
+ }
}
}
if targetSeqNumBytes, err := ioutil.ReadFile(store.targetSeqNumsFname); err == nil {
if targetSeqNum, err := strconv.Atoi(string(targetSeqNumBytes)); err == nil {
- store.cache.SetNextTargetMsgSeqNum(targetSeqNum)
+ if err = store.cache.SetNextTargetMsgSeqNum(targetSeqNum); err != nil {
+ return creationTimePopulated, errors.Wrap(err, "cache set next target")
+ }
}
}
@@ -180,7 +232,7 @@ func (store *fileStore) populateCache() (creationTimePopulated bool, err error)
}
func (store *fileStore) setSession() error {
- if _, err := store.sessionFile.Seek(0, os.SEEK_SET); err != nil {
+ if _, err := store.sessionFile.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("unable to rewind file: %s: %s", store.sessionFname, err.Error())
}
@@ -191,90 +243,112 @@ func (store *fileStore) setSession() error {
if _, err := store.sessionFile.Write(data); err != nil {
return fmt.Errorf("unable to write to file: %s: %s", store.sessionFname, err.Error())
}
- if err := store.sessionFile.Sync(); err != nil {
- return fmt.Errorf("unable to flush file: %s: %s", store.sessionFname, err.Error())
+ if store.fileSync {
+ if err := store.sessionFile.Sync(); err != nil {
+ return fmt.Errorf("unable to flush file: %s: %s", store.sessionFname, err.Error())
+ }
}
return nil
}
func (store *fileStore) setSeqNum(f *os.File, seqNum int) error {
- if _, err := f.Seek(0, os.SEEK_SET); err != nil {
+ if _, err := f.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("unable to rewind file: %s: %s", f.Name(), err.Error())
}
if _, err := fmt.Fprintf(f, "%019d", seqNum); err != nil {
return fmt.Errorf("unable to write to file: %s: %s", f.Name(), err.Error())
}
- if err := f.Sync(); err != nil {
- return fmt.Errorf("unable to flush file: %s: %s", f.Name(), err.Error())
+ if store.fileSync {
+ if err := f.Sync(); err != nil {
+ return fmt.Errorf("unable to flush file: %s: %s", f.Name(), err.Error())
+ }
}
return nil
}
-// NextSenderMsgSeqNum returns the next MsgSeqNum that will be sent
+// NextSenderMsgSeqNum returns the next MsgSeqNum that will be sent.
func (store *fileStore) NextSenderMsgSeqNum() int {
return store.cache.NextSenderMsgSeqNum()
}
-// NextTargetMsgSeqNum returns the next MsgSeqNum that should be received
+// NextTargetMsgSeqNum returns the next MsgSeqNum that should be received.
func (store *fileStore) NextTargetMsgSeqNum() int {
return store.cache.NextTargetMsgSeqNum()
}
-// SetNextSenderMsgSeqNum sets the next MsgSeqNum that will be sent
+// SetNextSenderMsgSeqNum sets the next MsgSeqNum that will be sent.
func (store *fileStore) SetNextSenderMsgSeqNum(next int) error {
- store.cache.SetNextSenderMsgSeqNum(next)
+ if err := store.cache.SetNextSenderMsgSeqNum(next); err != nil {
+ return errors.Wrap(err, "cache")
+ }
return store.setSeqNum(store.senderSeqNumsFile, next)
}
-// SetNextTargetMsgSeqNum sets the next MsgSeqNum that should be received
+// SetNextTargetMsgSeqNum sets the next MsgSeqNum that should be received.
func (store *fileStore) SetNextTargetMsgSeqNum(next int) error {
- store.cache.SetNextTargetMsgSeqNum(next)
+ if err := store.cache.SetNextTargetMsgSeqNum(next); err != nil {
+ return errors.Wrap(err, "cache")
+ }
return store.setSeqNum(store.targetSeqNumsFile, next)
}
-// IncrNextSenderMsgSeqNum increments the next MsgSeqNum that will be sent
+// IncrNextSenderMsgSeqNum increments the next MsgSeqNum that will be sent.
func (store *fileStore) IncrNextSenderMsgSeqNum() error {
- store.cache.IncrNextSenderMsgSeqNum()
+ if err := store.cache.IncrNextSenderMsgSeqNum(); err != nil {
+ return errors.Wrap(err, "cache")
+ }
return store.setSeqNum(store.senderSeqNumsFile, store.cache.NextSenderMsgSeqNum())
}
-// IncrNextTargetMsgSeqNum increments the next MsgSeqNum that should be received
+// IncrNextTargetMsgSeqNum increments the next MsgSeqNum that should be received.
func (store *fileStore) IncrNextTargetMsgSeqNum() error {
- store.cache.IncrNextTargetMsgSeqNum()
+ if err := store.cache.IncrNextTargetMsgSeqNum(); err != nil {
+ return errors.Wrap(err, "cache")
+ }
return store.setSeqNum(store.targetSeqNumsFile, store.cache.NextTargetMsgSeqNum())
}
-// CreationTime returns the creation time of the store
+// CreationTime returns the creation time of the store.
func (store *fileStore) CreationTime() time.Time {
return store.cache.CreationTime()
}
func (store *fileStore) SaveMessage(seqNum int, msg []byte) error {
- offset, err := store.bodyFile.Seek(0, os.SEEK_END)
+ offset, err := store.bodyFile.Seek(0, io.SeekEnd)
if err != nil {
return fmt.Errorf("unable to seek to end of file: %s: %s", store.bodyFname, err.Error())
}
- if _, err := store.headerFile.Seek(0, os.SEEK_END); err != nil {
+ if _, err := store.headerFile.Seek(0, io.SeekEnd); err != nil {
return fmt.Errorf("unable to seek to end of file: %s: %s", store.headerFname, err.Error())
}
if _, err := fmt.Fprintf(store.headerFile, "%d,%d,%d\n", seqNum, offset, len(msg)); err != nil {
return fmt.Errorf("unable to write to file: %s: %s", store.headerFname, err.Error())
}
- store.offsets[seqNum] = msgDef{offset: offset, size: len(msg)}
-
if _, err := store.bodyFile.Write(msg); err != nil {
return fmt.Errorf("unable to write to file: %s: %s", store.bodyFname, err.Error())
}
- if err := store.bodyFile.Sync(); err != nil {
- return fmt.Errorf("unable to flush file: %s: %s", store.bodyFname, err.Error())
- }
- if err := store.headerFile.Sync(); err != nil {
- return fmt.Errorf("unable to flush file: %s: %s", store.headerFname, err.Error())
+ if store.fileSync {
+ if err := store.bodyFile.Sync(); err != nil {
+ return fmt.Errorf("unable to flush file: %s: %s", store.bodyFname, err.Error())
+ }
+ if err := store.headerFile.Sync(); err != nil {
+ return fmt.Errorf("unable to flush file: %s: %s", store.headerFname, err.Error())
+ }
}
+
+ store.offsets[seqNum] = msgDef{offset: offset, size: len(msg)}
return nil
}
+func (store *fileStore) SaveMessageAndIncrNextSenderMsgSeqNum(seqNum int, msg []byte) error {
+ err := store.SaveMessage(seqNum, msg)
+ if err != nil {
+ return err
+ }
+ return store.IncrNextSenderMsgSeqNum()
+}
+
func (store *fileStore) getMessage(seqNum int) (msg []byte, found bool, err error) {
msgInfo, found := store.offsets[seqNum]
if !found {
@@ -303,7 +377,7 @@ func (store *fileStore) GetMessages(beginSeqNum, endSeqNum int) ([][]byte, error
return msgs, nil
}
-// Close closes the store's files
+// Close closes the store's files.
func (store *fileStore) Close() error {
if err := closeFile(store.bodyFile); err != nil {
return err
diff --git a/filestore_test.go b/filestore_test.go
index 56fc8ec68..3713ac8a9 100644
--- a/filestore_test.go
+++ b/filestore_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -12,7 +27,7 @@ import (
"github.com/stretchr/testify/suite"
)
-// FileStoreTestSuite runs all tests in the MessageStoreTestSuite against the FileStore implementation
+// FileStoreTestSuite runs all tests in the MessageStoreTestSuite against the FileStore implementation.
type FileStoreTestSuite struct {
MessageStoreTestSuite
fileStoreRootPath string
diff --git a/fileutil.go b/fileutil.go
index 9fa11c964..0698a3a97 100644
--- a/fileutil.go
+++ b/fileutil.go
@@ -1,9 +1,26 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"fmt"
"os"
"strings"
+
+ "github.com/pkg/errors"
)
func sessionIDFilenamePrefix(s SessionID) string {
@@ -30,7 +47,7 @@ func sessionIDFilenamePrefix(s SessionID) string {
return strings.Join(fname, "-")
}
-// closeFile behaves like Close, except that no error is returned if the file does not exist
+// closeFile behaves like Close, except that no error is returned if the file does not exist.
func closeFile(f *os.File) error {
if f != nil {
if err := f.Close(); err != nil {
@@ -42,16 +59,15 @@ func closeFile(f *os.File) error {
return nil
}
-// removeFile behaves like os.Remove, except that no error is returned if the file does not exist
+// removeFile behaves like os.Remove, except that no error is returned if the file does not exist.
func removeFile(fname string) error {
- err := os.Remove(fname)
- if (err != nil) && !os.IsNotExist(err) {
- return err
+ if err := os.Remove(fname); (err != nil) && !os.IsNotExist(err) {
+ return errors.Wrapf(err, "remove %v", fname)
}
return nil
}
-// openOrCreateFile opens a file for reading and writing, creating it if necessary
+// openOrCreateFile opens a file for reading and writing, creating it if necessary.
func openOrCreateFile(fname string, perm os.FileMode) (f *os.File, err error) {
if f, err = os.OpenFile(fname, os.O_RDWR, perm); err != nil {
if f, err = os.OpenFile(fname, os.O_RDWR|os.O_CREATE, perm); err != nil {
diff --git a/fileutil_test.go b/fileutil_test.go
index 4817c7862..0f9095364 100644
--- a/fileutil_test.go
+++ b/fileutil_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -53,6 +68,7 @@ func TestOpenOrCreateFile(t *testing.T) {
// Then it should be created
f, err := openOrCreateFile(fname, 0664)
+ require.Nil(t, err)
requireFileExists(t, fname)
// When the file already exists
diff --git a/fix_boolean.go b/fix_boolean.go
index 5cf53b572..73e4fc168 100644
--- a/fix_boolean.go
+++ b/fix_boolean.go
@@ -1,13 +1,28 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"errors"
)
-//FIXBoolean is a FIX Boolean value, implements FieldValue.
+// FIXBoolean is a FIX Boolean value, implements FieldValue.
type FIXBoolean bool
-//Bool converts the FIXBoolean value to bool
+// Bool converts the FIXBoolean value to bool.
func (f FIXBoolean) Bool() bool { return bool(f) }
func (f *FIXBoolean) Read(bytes []byte) error {
diff --git a/fix_boolean_test.go b/fix_boolean_test.go
index 150d8c0d6..c02747834 100644
--- a/fix_boolean_test.go
+++ b/fix_boolean_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/fix_bytes.go b/fix_bytes.go
index 5e3bb1300..b998843c3 100644
--- a/fix_bytes.go
+++ b/fix_bytes.go
@@ -1,6 +1,21 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
-//FIXBytes is a generic FIX field value, implements FieldValue. Enables zero copy read from a FieldMap
+// FIXBytes is a generic FIX field value, implements FieldValue. Enables zero copy read from a FieldMap.
type FIXBytes []byte
func (f *FIXBytes) Read(bytes []byte) (err error) {
diff --git a/fix_bytes_test.go b/fix_bytes_test.go
index 4dc94b56b..0855bdd16 100644
--- a/fix_bytes_test.go
+++ b/fix_bytes_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/fix_decimal.go b/fix_decimal.go
index d6f8ab1cd..a435c79fc 100644
--- a/fix_decimal.go
+++ b/fix_decimal.go
@@ -1,12 +1,27 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import "github.com/shopspring/decimal"
-//FIXDecimal is a FIX Float Value that implements an arbitrary precision fixed-point decimal. Implements FieldValue
+// FIXDecimal is a FIX Float Value that implements an arbitrary precision fixed-point decimal. Implements FieldValue.
type FIXDecimal struct {
decimal.Decimal
- //Scale is the number of digits after the decimal point when Writing the field value as a FIX value
+ // Scale is the number of digits after the decimal point when Writing the field value as a FIX value.
Scale int32
}
diff --git a/fix_decimal_test.go b/fix_decimal_test.go
index 1785d6e67..f6fc3d53c 100644
--- a/fix_decimal_test.go
+++ b/fix_decimal_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/fix_float.go b/fix_float.go
index 10122936a..1d4b310c2 100644
--- a/fix_float.go
+++ b/fix_float.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -5,10 +20,10 @@ import (
"strconv"
)
-//FIXFloat is a FIX Float Value, implements FieldValue
+// FIXFloat is a FIX Float Value, implements FieldValue.
type FIXFloat float64
-//Float64 converts the FIXFloat value to float64
+// Float64 converts the FIXFloat value to float64.
func (f FIXFloat) Float64() float64 { return float64(f) }
func (f *FIXFloat) Read(bytes []byte) error {
@@ -17,7 +32,7 @@ func (f *FIXFloat) Read(bytes []byte) error {
return err
}
- //strconv allows values like "+100.00", which is not allowed for FIX float types
+ // `strconv` allows values like "+100.00", which is not allowed for FIX float types.
for _, b := range bytes {
if b != '.' && b != '-' && !isDecimal(b) {
return fmt.Errorf("invalid value %v", string(bytes))
diff --git a/fix_float_test.go b/fix_float_test.go
index b31bdbb60..6b75eaf11 100644
--- a/fix_float_test.go
+++ b/fix_float_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/fix_int.go b/fix_int.go
index b3695116e..6bd968003 100644
--- a/fix_int.go
+++ b/fix_int.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -6,15 +21,15 @@ import (
)
const (
- //-
+ // ASCII - char.
asciiMinus = 45
- //ascii numbers 0-9
+ // ASCII numbers 0-9.
ascii0 = 48
ascii9 = 57
)
-//atoi is similar to the function in strconv, but is tuned for ints appearing in FIX field types.
+// atoi is similar to the function in strconv, but is tuned for ints appearing in FIX field types.
func atoi(d []byte) (int, error) {
if d[0] == asciiMinus {
n, err := parseUInt(d[1:])
@@ -24,7 +39,7 @@ func atoi(d []byte) (int, error) {
return parseUInt(d)
}
-//parseUInt is similar to the function in strconv, but is tuned for ints appearing in FIX field types.
+// parseUInt is similar to the function in strconv, but is tuned for ints appearing in FIX field types.
func parseUInt(d []byte) (n int, err error) {
if len(d) == 0 {
err = errors.New("empty bytes")
@@ -43,10 +58,10 @@ func parseUInt(d []byte) (n int, err error) {
return
}
-//FIXInt is a FIX Int Value, implements FieldValue
+// FIXInt is a FIX Int Value, implements FieldValue.
type FIXInt int
-//Int converts the FIXInt value to int
+// Int converts the FIXInt value to int.
func (f FIXInt) Int() int { return int(f) }
func (f *FIXInt) Read(bytes []byte) error {
diff --git a/fix_int_test.go b/fix_int_test.go
index 173cb5ee5..caa9b7840 100644
--- a/fix_int_test.go
+++ b/fix_int_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -32,6 +47,6 @@ func BenchmarkFIXInt_Read(b *testing.B) {
var field FIXInt
for i := 0; i < b.N; i++ {
- field.Read(intBytes)
+ _ = field.Read(intBytes)
}
}
diff --git a/fix_string.go b/fix_string.go
index bfa54ae06..b4fe18a3d 100644
--- a/fix_string.go
+++ b/fix_string.go
@@ -1,6 +1,21 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
-//FIXString is a FIX String Value, implements FieldValue
+// FIXString is a FIX String Value, implements FieldValue.
type FIXString string
func (f FIXString) String() string {
diff --git a/fix_string_test.go b/fix_string_test.go
index f2672f39e..4f3712333 100644
--- a/fix_string_test.go
+++ b/fix_string_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -29,6 +44,8 @@ func TestFIXStringRead(t *testing.T) {
expectError bool
}{
{[]byte("blah"), "blah", false},
+ {nil, "", false},
+ {[]byte(""), "", false},
}
for _, test := range tests {
diff --git a/fix_utc_timestamp.go b/fix_utc_timestamp.go
index 82c2c2f7d..ebd40cb06 100644
--- a/fix_utc_timestamp.go
+++ b/fix_utc_timestamp.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -5,10 +20,10 @@ import (
"time"
)
-//TimestampPrecision defines the precision used by FIXUTCTimestamp
+// TimestampPrecision defines the precision used by FIXUTCTimestamp.
type TimestampPrecision int
-//All TimestampPrecisions supported by FIX
+// All TimestampPrecisions supported by FIX.
const (
Millis TimestampPrecision = iota
Seconds
@@ -16,7 +31,7 @@ const (
Nanos
)
-//FIXUTCTimestamp is a FIX UTC Timestamp value, implements FieldValue
+// FIXUTCTimestamp is a FIX UTC Timestamp value, implements FieldValue.
type FIXUTCTimestamp struct {
time.Time
Precision TimestampPrecision
@@ -31,12 +46,12 @@ const (
func (f *FIXUTCTimestamp) Read(bytes []byte) (err error) {
switch len(bytes) {
- //seconds
+ // Seconds.
case 17:
f.Time, err = time.Parse(utcTimestampSecondsFormat, string(bytes))
f.Precision = Seconds
- //millis
+ // Millis.
case 21:
f.Time, err = time.Parse(utcTimestampMillisFormat, string(bytes))
f.Precision = Millis
diff --git a/fix_utc_timestamp_test.go b/fix_utc_timestamp_test.go
index b29039587..da2d0ecb9 100644
--- a/fix_utc_timestamp_test.go
+++ b/fix_utc_timestamp_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix_test
import (
diff --git a/go.mod b/go.mod
new file mode 100644
index 000000000..3e582a557
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,32 @@
+module github.com/quickfixgo/quickfix
+
+go 1.18
+
+require (
+ github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a
+ github.com/mattn/go-sqlite3 v1.14.17
+ github.com/pkg/errors v0.9.1
+ github.com/shopspring/decimal v1.3.1
+ github.com/stretchr/testify v1.8.4
+ go.mongodb.org/mongo-driver v1.12.1
+ golang.org/x/net v0.14.0
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/klauspost/compress v1.15.12 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/montanaflynn/stats v0.6.6 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/stretchr/objx v0.5.0 // indirect
+ github.com/xdg-go/pbkdf2 v1.0.0 // indirect
+ 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.12.0 // indirect
+ golang.org/x/sync v0.1.0 // indirect
+ golang.org/x/text v0.12.0 // indirect
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 000000000..65bb5e85b
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,98 @@
+github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a h1:AP/vsCIvJZ129pdm9Ek7bH7yutN3hByqsMoNrWAxRQc=
+github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
+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=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+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.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
+github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+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/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/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=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+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.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
+go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
+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.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
+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.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+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=
+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=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/in_session.go b/in_session.go
index d4dfc701d..50666a703 100644
--- a/in_session.go
+++ b/in_session.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -145,7 +160,7 @@ func (state inSession) handleSequenceReset(session *session, msg *Message) (next
return handleStateError(session, err)
}
case newSeqNo < expectedSeqNum:
- //FIXME: to be compliant with legacy tests, do not include tag in reftagid? (11c_NewSeqNoLess)
+ // FIXME: to be compliant with legacy tests, do not include tag in reftagid? (11c_NewSeqNoLess).
if err := session.doReject(msg, valueIsIncorrectNoTag()); err != nil {
return handleStateError(session, err)
}
@@ -239,7 +254,7 @@ func (state inSession) resendMessages(session *session, beginSeqNo, endSeqNo int
session.log.OnEventf("Resending Message: %v", sentMessageSeqNum)
msgBytes = msg.build()
- session.sendBytes(msgBytes)
+ session.EnqueueBytesAndSend(msgBytes)
seqNum = sentMessageSeqNum + 1
nextSeqNum = seqNum
@@ -261,7 +276,7 @@ func (state inSession) processReject(session *session, msg *Message, rej Message
var nextState resendState
switch currentState := session.State.(type) {
case resendState:
- //assumes target too high reject already sent
+ // Assumes target too high reject already sent.
nextState = currentState
default:
var err error
@@ -275,8 +290,6 @@ func (state inSession) processReject(session *session, msg *Message, rej Message
}
nextState.messageStash[TypedError.ReceivedTarget] = msg
- //do not reclaim stashed message
- msg.keepMessage = true
return nextState
@@ -382,7 +395,7 @@ func (state *inSession) generateSequenceReset(session *session, beginSeqNo int,
msgBytes := sequenceReset.build()
- session.sendBytes(msgBytes)
+ session.EnqueueBytesAndSend(msgBytes)
session.log.OnEventf("Sent SequenceReset TO: %v", endSeqNo)
return
diff --git a/in_session_test.go b/in_session_test.go
index c8c0b1523..aa9f87084 100644
--- a/in_session_test.go
+++ b/in_session_test.go
@@ -1,11 +1,27 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"testing"
"time"
- "github.com/quickfixgo/quickfix/internal"
"github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/internal"
)
type InSessionTestSuite struct {
@@ -317,7 +333,7 @@ func (s *InSessionTestSuite) TestFIXMsgInResendRequestDoNotSendApp() {
s.MockApp.AssertNumberOfCalls(s.T(), "ToApp", 1)
s.NextSenderMsgSeqNum(4)
- //NOTE: a cheat here, need to reset mock
+ // NOTE: a cheat here, need to reset mock.
s.MockApp = MockApp{}
s.MockApp.On("FromAdmin").Return(nil)
s.MockApp.On("ToApp").Return(ErrDoNotSend)
diff --git a/initiator.go b/initiator.go
index 73b8c37ac..7831d1e6f 100644
--- a/initiator.go
+++ b/initiator.go
@@ -1,16 +1,31 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"bufio"
"crypto/tls"
- "net"
+ "strings"
"sync"
"time"
- "github.com/quickfixgo/quickfix/config"
+ "golang.org/x/net/proxy"
)
-//Initiator initiates connections and processes messages for all sessions.
+// Initiator initiates connections and processes messages for all sessions.
type Initiator struct {
app Application
settings *Settings
@@ -24,38 +39,37 @@ type Initiator struct {
sessionFactory
}
-//Start Initiator.
+// Start Initiator.
func (i *Initiator) Start() (err error) {
i.stopChan = make(chan interface{})
for sessionID, settings := range i.sessionSettings {
- //TODO: move into session factory
+ // TODO: move into session factory.
var tlsConfig *tls.Config
if tlsConfig, err = loadTLSConfig(settings); err != nil {
return
}
- dialTimeout := time.Duration(0)
- if settings.HasSetting(config.SocketTimeout) {
- if dialTimeout, err = settings.DurationSetting(config.SocketTimeout); err != nil {
- return
- }
+ var dialer proxy.Dialer
+ if dialer, err = loadDialerConfig(settings); err != nil {
+ return
}
+
i.wg.Add(1)
go func(sessID SessionID) {
- i.handleConnection(i.sessions[sessID], tlsConfig, dialTimeout)
+ i.handleConnection(i.sessions[sessID], tlsConfig, dialer)
i.wg.Done()
}(sessionID)
}
-
+ i.wg.Wait()
return
}
-//Stop Initiator.
+// Stop Initiator.
func (i *Initiator) Stop() {
select {
case <-i.stopChan:
- //closed already
+ // Closed already.
return
default:
}
@@ -63,7 +77,7 @@ func (i *Initiator) Stop() {
i.wg.Wait()
}
-//NewInitiator creates and initializes a new Initiator.
+// NewInitiator creates and initializes a new Initiator.
func NewInitiator(app Application, storeFactory MessageStoreFactory, appSettings *Settings, logFactory LogFactory) (*Initiator, error) {
i := &Initiator{
app: app,
@@ -93,7 +107,7 @@ func NewInitiator(app Application, storeFactory MessageStoreFactory, appSettings
return i, nil
}
-//waitForInSessionTime returns true if the session is in session, false if the handler should stop
+// waitForInSessionTime returns true if the session is in session, false if the handler should stop.
func (i *Initiator) waitForInSessionTime(session *session) bool {
inSessionTime := make(chan interface{})
go func() {
@@ -110,7 +124,7 @@ func (i *Initiator) waitForInSessionTime(session *session) bool {
return true
}
-//watiForReconnectInterval returns true if a reconnect should be re-attempted, false if handler should stop
+// waitForReconnectInterval returns true if a reconnect should be re-attempted, false if handler should stop.
func (i *Initiator) waitForReconnectInterval(reconnectInterval time.Duration) bool {
select {
case <-time.After(reconnectInterval):
@@ -121,7 +135,7 @@ func (i *Initiator) waitForReconnectInterval(reconnectInterval time.Duration) bo
return true
}
-func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, dialTimeout time.Duration) {
+func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, dialer proxy.Dialer) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
@@ -148,27 +162,26 @@ func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, di
address := session.SocketConnectAddress[connectionAttempt%len(session.SocketConnectAddress)]
session.log.OnEventf("Connecting to: %v", address)
- var netConn net.Conn
- if tlsConfig != nil {
- tlsConn, err := tls.DialWithDialer(&net.Dialer{Timeout: dialTimeout}, "tcp", address, tlsConfig)
- if err != nil {
- session.log.OnEventf("Failed to connect: %v", err)
- goto reconnect
+ netConn, err := dialer.Dial("tcp", address)
+ if err != nil {
+ session.log.OnEventf("Failed to connect: %v", err)
+ goto reconnect
+ } else if tlsConfig != nil {
+ // Unless InsecureSkipVerify is true, server name config is required for TLS
+ // to verify the received certificate
+ if !tlsConfig.InsecureSkipVerify && len(tlsConfig.ServerName) == 0 {
+ serverName := address
+ if c := strings.LastIndex(serverName, ":"); c > 0 {
+ serverName = serverName[:c]
+ }
+ tlsConfig.ServerName = serverName
}
-
- err = tlsConn.Handshake()
- if err != nil {
- session.log.OnEventf("Failed handshake:%v", err)
+ tlsConn := tls.Client(netConn, tlsConfig)
+ if err = tlsConn.Handshake(); err != nil {
+ session.log.OnEventf("Failed handshake: %v", err)
goto reconnect
}
netConn = tlsConn
- } else {
- var err error
- netConn, err = net.Dial("tcp", address)
- if err != nil {
- session.log.OnEventf("Failed to connect: %v", err)
- goto reconnect
- }
}
msgIn = make(chan fixIn)
diff --git a/internal/buffer_pool.go b/internal/buffer_pool.go
deleted file mode 100644
index f0be7c7e5..000000000
--- a/internal/buffer_pool.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package internal
-
-import (
- "bytes"
- "sync"
-)
-
-//BufferPool is an concurrently safe pool for byte buffers. Used to constructing inbound messages and writing outbound messages
-type BufferPool struct {
- b []*bytes.Buffer
- sync.Mutex
-}
-
-//Get returns a buffer from the pool, or creates a new buffer if the pool is empty
-func (p *BufferPool) Get() (buf *bytes.Buffer) {
- p.Lock()
- if len(p.b) > 0 {
- buf, p.b = p.b[len(p.b)-1], p.b[:len(p.b)-1]
- } else {
- buf = new(bytes.Buffer)
- }
-
- p.Unlock()
-
- return
-}
-
-//Put returns adds a buffer to the pool
-func (p *BufferPool) Put(buf *bytes.Buffer) {
- if buf == nil {
- panic("Nil Buffer inserted into pool")
- }
-
- p.Lock()
- p.b = append(p.b, buf)
- p.Unlock()
-}
diff --git a/internal/event.go b/internal/event.go
index 5c6af8db4..f737050d8 100644
--- a/internal/event.go
+++ b/internal/event.go
@@ -1,15 +1,15 @@
package internal
-//Event is an abstraction for session events
+// Event is an abstraction for session events.
type Event int
const (
- //PeerTimeout indicates the session peer has become unresponsive
+ // PeerTimeout indicates the session peer has become unresponsive.
PeerTimeout Event = iota
- //NeedHeartbeat indicates the session should send a heartbeat
+ // NeedHeartbeat indicates the session should send a heartbeat.
NeedHeartbeat
- //LogonTimeout indicates the peer has not sent a logon request
+ // LogonTimeout indicates the peer has not sent a logon request.
LogonTimeout
- //LogoutTimeout indicates the peer has not sent a logout request
+ // LogoutTimeout indicates the peer has not sent a logout request.
LogoutTimeout
)
diff --git a/internal/event_timer.go b/internal/event_timer.go
index 0b7630cbc..442fb60e8 100644
--- a/internal/event_timer.go
+++ b/internal/event_timer.go
@@ -10,6 +10,7 @@ type EventTimer struct {
timer *time.Timer
done chan struct{}
wg sync.WaitGroup
+ once sync.Once
}
func NewEventTimer(task func()) *EventTimer {
@@ -45,7 +46,10 @@ func (t *EventTimer) Stop() {
return
}
- close(t.done)
+ t.once.Do(func() {
+ close(t.done)
+ })
+
t.wg.Wait()
}
diff --git a/internal/event_timer_test.go b/internal/event_timer_test.go
new file mode 100644
index 000000000..452fef6ce
--- /dev/null
+++ b/internal/event_timer_test.go
@@ -0,0 +1,27 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
+package internal
+
+import (
+ "testing"
+)
+
+func TestEventTimer_Stop_idempotent(*testing.T) {
+ t := NewEventTimer(func() {})
+
+ t.Stop()
+ t.Stop()
+}
diff --git a/internal/session_settings.go b/internal/session_settings.go
index f5d7f9a35..e003f54e4 100644
--- a/internal/session_settings.go
+++ b/internal/session_settings.go
@@ -2,13 +2,14 @@ package internal
import "time"
-//SessionSettings stores all of the configuration for a given session
+// SessionSettings stores all of the configuration for a given session.
type SessionSettings struct {
ResetOnLogon bool
RefreshOnLogon bool
ResetOnLogout bool
ResetOnDisconnect bool
HeartBtInt time.Duration
+ HeartBtIntOverride bool
SessionTime *TimeRange
InitiateLogon bool
ResendRequestChunkSize int
@@ -17,10 +18,12 @@ type SessionSettings struct {
MaxLatency time.Duration
DisableMessagePersist bool
- //required on logon for FIX.T.1 messages
+ // Required on logon for FIX.T.1 messages.
DefaultApplVerID string
- //specific to initiators
+ // Specific to initiators.
ReconnectInterval time.Duration
+ LogoutTimeout time.Duration
+ LogonTimeout time.Duration
SocketConnectAddress []string
}
diff --git a/internal/time_range.go b/internal/time_range.go
index b7fa4569b..7c2ef10f6 100644
--- a/internal/time_range.go
+++ b/internal/time_range.go
@@ -1,11 +1,12 @@
package internal
import (
- "errors"
"time"
+
+ "github.com/pkg/errors"
)
-//TimeOfDay represents the time of day
+// TimeOfDay represents the time of day.
type TimeOfDay struct {
hour, minute, second int
d time.Duration
@@ -13,9 +14,7 @@ type TimeOfDay struct {
const shortForm = "15:04:05"
-var errParseTime = errors.New("Time must be in the format HH:MM:SS")
-
-//NewTimeOfDay returns a newly initialized TimeOfDay
+// NewTimeOfDay returns a newly initialized TimeOfDay.
func NewTimeOfDay(hour, minute, second int) TimeOfDay {
d := time.Duration(second)*time.Second +
time.Duration(minute)*time.Minute +
@@ -24,29 +23,29 @@ func NewTimeOfDay(hour, minute, second int) TimeOfDay {
return TimeOfDay{hour: hour, minute: minute, second: second, d: d}
}
-//ParseTimeOfDay parses a TimeOfDay from a string in the format HH:MM:SS
+// ParseTimeOfDay parses a TimeOfDay from a string in the format HH:MM:SS.
func ParseTimeOfDay(str string) (TimeOfDay, error) {
t, err := time.Parse(shortForm, str)
if err != nil {
- return TimeOfDay{}, errParseTime
+ return TimeOfDay{}, errors.Wrap(err, "time must be in the format HH:MM:SS")
}
return NewTimeOfDay(t.Clock()), nil
}
-//TimeRange represents a time band in a given time zone
+// TimeRange represents a time band in a given time zone.
type TimeRange struct {
startTime, endTime TimeOfDay
startDay, endDay *time.Weekday
loc *time.Location
}
-//NewUTCTimeRange returns a time range in UTC
+// NewUTCTimeRange returns a time range in UTC.
func NewUTCTimeRange(start, end TimeOfDay) *TimeRange {
return NewTimeRangeInLocation(start, end, time.UTC)
}
-//NewTimeRangeInLocation returns a time range in a given location
+// NewTimeRangeInLocation returns a time range in a given location.
func NewTimeRangeInLocation(start, end TimeOfDay, loc *time.Location) *TimeRange {
if loc == nil {
panic("time: missing Location in call to NewTimeRangeInLocation")
@@ -55,12 +54,12 @@ func NewTimeRangeInLocation(start, end TimeOfDay, loc *time.Location) *TimeRange
return &TimeRange{startTime: start, endTime: end, loc: loc}
}
-//NewUTCWeekRange returns a weekly TimeRange
+// NewUTCWeekRange returns a weekly TimeRange.
func NewUTCWeekRange(startTime, endTime TimeOfDay, startDay, endDay time.Weekday) *TimeRange {
return NewWeekRangeInLocation(startTime, endTime, startDay, endDay, time.UTC)
}
-//NewWeekRangeInLocation returns a time range in a given location
+// NewWeekRangeInLocation returns a time range in a given location.
func NewWeekRangeInLocation(startTime, endTime TimeOfDay, startDay, endDay time.Weekday, loc *time.Location) *TimeRange {
r := NewTimeRangeInLocation(startTime, endTime, loc)
r.startDay = &startDay
@@ -121,7 +120,7 @@ func (r *TimeRange) isInWeekRange(t time.Time) bool {
return true
}
-//IsInRange returns true if time t is within in the time range
+// IsInRange returns true if time t is within in the time range.
func (r *TimeRange) IsInRange(t time.Time) bool {
if r == nil {
return true
@@ -134,7 +133,7 @@ func (r *TimeRange) IsInRange(t time.Time) bool {
return r.isInTimeRange(t)
}
-//IsInSameRange determines if two points in time are in the same time range
+// IsInSameRange determines if two points in time are in the same time range.
func (r *TimeRange) IsInSameRange(t1, t2 time.Time) bool {
if r == nil {
return true
diff --git a/internal/time_range_test.go b/internal/time_range_test.go
index df392118a..a28a495f5 100644
--- a/internal/time_range_test.go
+++ b/internal/time_range_test.go
@@ -374,6 +374,7 @@ func TestTimeRangeIsInSameRangeWithDay(t *testing.T) {
time1 = time.Date(2004, time.July, 27, 3, 0, 0, 0, time.UTC)
time2 = time.Date(2004, time.July, 27, 3, 0, 0, 0, time.UTC)
+ assert.True(t, NewUTCWeekRange(startTime, endTime, startDay, endDay).IsInSameRange(time1, time2))
time1 = time.Date(2004, time.July, 26, 10, 0, 0, 0, time.UTC)
time2 = time.Date(2004, time.July, 27, 3, 0, 0, 0, time.UTC)
diff --git a/latent_state.go b/latent_state.go
index e6e5bece4..133509c7b 100644
--- a/latent_state.go
+++ b/latent_state.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import "github.com/quickfixgo/quickfix/internal"
diff --git a/latent_state_test.go b/latent_state_test.go
index db6e925b6..e3379a4e8 100644
--- a/latent_state_test.go
+++ b/latent_state_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/log.go b/log.go
index 6527e92dc..dbf6726fc 100644
--- a/log.go
+++ b/log.go
@@ -1,25 +1,40 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
-//Log is a generic interface for logging FIX messages and events.
+// Log is a generic interface for logging FIX messages and events.
type Log interface {
- //log incoming fix message
+ // OnIncoming log incoming fix message.
OnIncoming([]byte)
- //log outgoing fix message
+ // OnOutgoing log outgoing fix message.
OnOutgoing([]byte)
- //log fix event
+ // OnEvent log fix event.
OnEvent(string)
- //log fix event according to format specifier
+ // OnEventf log fix event according to format specifier.
OnEventf(string, ...interface{})
}
-//The LogFactory interface creates global and session specific Log instances
+// The LogFactory interface creates global and session specific Log instances.
type LogFactory interface {
- //global log
+ // Create global log.
Create() (Log, error)
- //session specific log
+ // CreateSessionLog session specific log.
CreateSessionLog(sessionID SessionID) (Log, error)
}
diff --git a/logon_state.go b/logon_state.go
index 351fa629c..05c970838 100644
--- a/logon_state.go
+++ b/logon_state.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -24,23 +39,15 @@ func (s logonState) FixMsgIn(session *session, msg *Message) (nextState sessionS
if err := session.handleLogon(msg); err != nil {
switch err := err.(type) {
case RejectLogon:
- session.log.OnEvent(err.Text)
- logout := session.buildLogout(err.Text)
-
- if err := session.dropAndSendInReplyTo(logout, msg); err != nil {
- session.logError(err)
- }
+ return shutdownWithReason(session, msg, true, err.Error())
- if err := session.store.IncrNextTargetMsgSeqNum(); err != nil {
- session.logError(err)
- }
-
- return latentState{}
+ case targetTooLow:
+ return shutdownWithReason(session, msg, false, err.Error())
case targetTooHigh:
var tooHighErr error
if nextState, tooHighErr = session.doTargetTooHigh(err); tooHighErr != nil {
- return handleStateError(session, tooHighErr)
+ return shutdownWithReason(session, msg, false, tooHighErr.Error())
}
return
@@ -64,3 +71,20 @@ func (s logonState) Timeout(session *session, e internal.Event) (nextState sessi
func (s logonState) Stop(session *session) (nextState sessionState) {
return latentState{}
}
+
+func shutdownWithReason(session *session, msg *Message, incrNextTargetMsgSeqNum bool, reason string) (nextState sessionState) {
+ session.log.OnEvent(reason)
+ logout := session.buildLogout(reason)
+
+ if err := session.dropAndSendInReplyTo(logout, msg); err != nil {
+ session.logError(err)
+ }
+
+ if incrNextTargetMsgSeqNum {
+ if err := session.store.IncrNextTargetMsgSeqNum(); err != nil {
+ session.logError(err)
+ }
+ }
+
+ return latentState{}
+}
diff --git a/logon_state_test.go b/logon_state_test.go
index 08da2e710..96afc2e90 100644
--- a/logon_state_test.go
+++ b/logon_state_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -5,8 +20,9 @@ import (
"testing"
"time"
- "github.com/quickfixgo/quickfix/internal"
"github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/internal"
)
type LogonStateTestSuite struct {
@@ -76,12 +92,14 @@ func (s *LogonStateTestSuite) TestFixMsgInLogon() {
s.MockApp.On("FromAdmin").Return(nil)
s.MockApp.On("OnLogon")
s.MockApp.On("ToAdmin")
+ s.Zero(s.session.HeartBtInt)
s.fixMsgIn(s.session, logon)
s.MockApp.AssertExpectations(s.T())
s.State(inSession{})
- s.Equal(32*time.Second, s.session.HeartBtInt)
+ s.Equal(32*time.Second, s.session.HeartBtInt) // Should be written from logon message.
+ s.False(s.session.HeartBtIntOverride)
s.LastToAdminMessageSent()
s.MessageType(string(msgTypeLogon), s.MockApp.lastToAdmin)
@@ -91,6 +109,35 @@ func (s *LogonStateTestSuite) TestFixMsgInLogon() {
s.NextSenderMsgSeqNum(3)
}
+func (s *LogonStateTestSuite) TestFixMsgInLogonHeartBtIntOverride() {
+ s.IncrNextSenderMsgSeqNum()
+ s.MessageFactory.seqNum = 1
+ s.IncrNextTargetMsgSeqNum()
+
+ logon := s.Logon()
+ logon.Body.SetField(tagHeartBtInt, FIXInt(32))
+
+ s.MockApp.On("FromAdmin").Return(nil)
+ s.MockApp.On("OnLogon")
+ s.MockApp.On("ToAdmin")
+ s.session.HeartBtIntOverride = true
+ s.session.HeartBtInt = time.Second
+ s.fixMsgIn(s.session, logon)
+
+ s.MockApp.AssertExpectations(s.T())
+
+ s.State(inSession{})
+ s.Equal(time.Second, s.session.HeartBtInt) // Should not have changed.
+ s.True(s.session.HeartBtIntOverride)
+
+ s.LastToAdminMessageSent()
+ s.MessageType(string(msgTypeLogon), s.MockApp.lastToAdmin)
+ s.FieldEquals(tagHeartBtInt, 1, s.MockApp.lastToAdmin.Body)
+
+ s.NextTargetMsgSeqNum(3)
+ s.NextSenderMsgSeqNum(3)
+}
+
func (s *LogonStateTestSuite) TestFixMsgInLogonEnableLastMsgSeqNumProcessed() {
s.session.EnableLastMsgSeqNumProcessed = true
@@ -277,7 +324,7 @@ func (s *LogonStateTestSuite) TestFixMsgInLogonSeqNumTooHigh() {
s.State(resendState{})
s.NextTargetMsgSeqNum(1)
- //session should send logon, and then queues resend request for send
+ // Session should send logon, and then queues resend request for send.
s.MockApp.AssertNumberOfCalls(s.T(), "ToAdmin", 2)
msgBytesSent, ok := s.Receiver.LastMessage()
s.Require().True(ok)
@@ -302,3 +349,31 @@ func (s *LogonStateTestSuite) TestFixMsgInLogonSeqNumTooHigh() {
s.State(inSession{})
s.NextTargetMsgSeqNum(7)
}
+
+func (s *LogonStateTestSuite) TestFixMsgInLogonSeqNumTooLow() {
+ s.IncrNextSenderMsgSeqNum()
+ s.IncrNextTargetMsgSeqNum()
+
+ logon := s.Logon()
+ logon.Body.SetField(tagHeartBtInt, FIXInt(32))
+ logon.Header.SetInt(tagMsgSeqNum, 1)
+
+ s.MockApp.On("ToAdmin")
+ s.NextTargetMsgSeqNum(2)
+ s.fixMsgIn(s.session, logon)
+
+ s.State(latentState{})
+ s.NextTargetMsgSeqNum(2)
+
+ s.MockApp.AssertNumberOfCalls(s.T(), "ToAdmin", 1)
+ msgBytesSent, ok := s.Receiver.LastMessage()
+ s.Require().True(ok)
+ sentMessage := NewMessage()
+ err := ParseMessage(sentMessage, bytes.NewBuffer(msgBytesSent))
+ s.Require().Nil(err)
+ s.MessageType(string(msgTypeLogout), sentMessage)
+
+ s.session.sendQueued()
+ 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/logout_state.go b/logout_state.go
index 071512ce1..cee8e9bd8 100644
--- a/logout_state.go
+++ b/logout_state.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import "github.com/quickfixgo/quickfix/internal"
diff --git a/logout_state_test.go b/logout_state_test.go
index ce1ae84b6..f414bc85f 100644
--- a/logout_state_test.go
+++ b/logout_state_test.go
@@ -1,10 +1,26 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"testing"
- "github.com/quickfixgo/quickfix/internal"
"github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/internal"
)
type LogoutStateTestSuite struct {
diff --git a/message.go b/message.go
index f800cbebf..36aef1dda 100644
--- a/message.go
+++ b/message.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -9,10 +24,10 @@ import (
"github.com/quickfixgo/quickfix/datadictionary"
)
-//Header is first section of a FIX Message
+// Header is first section of a FIX Message.
type Header struct{ FieldMap }
-//in the message header, the first 3 tags in the message header must be 8,9,35
+// 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 {
switch t {
@@ -40,23 +55,23 @@ func headerFieldOrdering(i, j Tag) bool {
return i < j
}
-//Init initializes the Header instance
+// Init initializes the Header instance.
func (h *Header) Init() {
h.initWithOrdering(headerFieldOrdering)
}
-//Body is the primary application section of a FIX message
+// Body is the primary application section of a FIX message.
type Body struct{ FieldMap }
-//Init initializes the FIX message
+// Init initializes the FIX message.
func (b *Body) Init() {
b.init()
}
-//Trailer is the last section of a FIX message
+// Trailer is the last section of a FIX message.
type Trailer struct{ FieldMap }
-// In the trailer, CheckSum (tag 10) must be last
+// In the trailer, CheckSum (tag 10) must be last.
func trailerFieldOrdering(i, j Tag) bool {
switch {
case i == tagCheckSum:
@@ -68,43 +83,40 @@ func trailerFieldOrdering(i, j Tag) bool {
return i < j
}
-//Init initializes the FIX message
+// Init initializes the FIX message.
func (t *Trailer) Init() {
t.initWithOrdering(trailerFieldOrdering)
}
-//Message is a FIX Message abstraction.
+// Message is a FIX Message abstraction.
type Message struct {
Header Header
Trailer Trailer
Body Body
- //ReceiveTime is the time that this message was read from the socket connection
+ // ReceiveTime is the time that this message was read from the socket connection.
ReceiveTime time.Time
rawMessage *bytes.Buffer
- //slice of Bytes corresponding to the message body
+ // Slice of Bytes corresponding to the message body.
bodyBytes []byte
- //field bytes as they appear in the raw message
+ // Field bytes as they appear in the raw message.
fields []TagValue
-
- //flag is true if this message should not be returned to pool after use
- keepMessage bool
}
-//ToMessage returns the message itself
+// ToMessage returns the message itself.
func (m *Message) ToMessage() *Message { return m }
-//parseError is returned when bytes cannot be parsed as a FIX message.
+// parseError is returned when bytes cannot be parsed as a FIX message.
type parseError struct {
OrigError string
}
func (e parseError) Error() string { return fmt.Sprintf("error parsing message: %s", e.OrigError) }
-//NewMessage returns a newly initialized Message instance
+// NewMessage returns a newly initialized Message instance.
func NewMessage() *Message {
m := new(Message)
m.Header.Init()
@@ -114,7 +126,7 @@ func NewMessage() *Message {
return m
}
-// CopyInto erases the dest messages and copies the curreny message content
+// CopyInto erases the dest messages and copies the currency message content
// into it.
func (m *Message) CopyInto(to *Message) {
m.Header.CopyInto(&to.Header.FieldMap)
@@ -130,12 +142,12 @@ func (m *Message) CopyInto(to *Message) {
}
}
-//ParseMessage constructs a Message from a byte slice wrapping a FIX message.
+// ParseMessage constructs a Message from a byte slice wrapping a FIX message.
func ParseMessage(msg *Message, rawMessage *bytes.Buffer) (err error) {
return ParseMessageWithDataDictionary(msg, rawMessage, nil, nil)
}
-//ParseMessageWithDataDictionary constructs a Message from a byte slice wrapping a FIX message using an optional session and application DataDictionary for reference.
+// ParseMessageWithDataDictionary constructs a Message from a byte slice wrapping a FIX message using an optional session and application DataDictionary for reference.
func ParseMessageWithDataDictionary(
msg *Message,
rawMessage *bytes.Buffer,
@@ -149,7 +161,7 @@ func ParseMessageWithDataDictionary(
rawBytes := rawMessage.Bytes()
- //allocate fields in one chunk
+ // Allocate fields in one chunk.
fieldCount := 0
for _, b := range rawBytes {
if b == '\001' {
@@ -169,7 +181,7 @@ func ParseMessageWithDataDictionary(
fieldIndex := 0
- //message must start with begin string, body length, msg type
+ // Message must start with begin string, body length, msg type.
if rawBytes, err = extractSpecificField(&msg.fields[fieldIndex], tagBeginString, rawBytes); err != nil {
return
}
@@ -190,6 +202,9 @@ func ParseMessageWithDataDictionary(
return
}
+ xmlDataLen := 0
+ xmlDataMsg := false
+
msg.Header.add(msg.fields[fieldIndex : fieldIndex+1])
fieldIndex++
@@ -197,7 +212,13 @@ func ParseMessageWithDataDictionary(
foundBody := false
for {
parsedFieldBytes = &msg.fields[fieldIndex]
- rawBytes, err = extractField(parsedFieldBytes, rawBytes)
+ if xmlDataLen > 0 {
+ rawBytes, err = extractXMLDataField(parsedFieldBytes, rawBytes, xmlDataLen)
+ xmlDataLen = 0
+ xmlDataMsg = true
+ } else {
+ rawBytes, err = extractField(parsedFieldBytes, rawBytes)
+ }
if err != nil {
return
}
@@ -220,10 +241,13 @@ func ParseMessageWithDataDictionary(
msg.bodyBytes = rawBytes
}
+ if parsedFieldBytes.tag == tagXMLDataLen {
+ xmlDataLen, _ = msg.Header.GetInt(tagXMLDataLen)
+ }
fieldIndex++
}
- //body length would only be larger than trailer if fields out of order
+ // 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)]
}
@@ -231,7 +255,7 @@ func ParseMessageWithDataDictionary(
length := 0
for _, field := range msg.fields {
switch field.tag {
- case tagBeginString, tagBodyLength, tagCheckSum: //tags do not contribute to length
+ case tagBeginString, tagBodyLength, tagCheckSum: // Tags do not contribute to length.
default:
length += field.length()
}
@@ -240,12 +264,11 @@ func ParseMessageWithDataDictionary(
bodyLength, err := msg.Header.GetInt(tagBodyLength)
if err != nil {
err = parseError{OrigError: err.Error()}
- } else if length != bodyLength {
+ } else if length != bodyLength && !xmlDataMsg {
err = parseError{OrigError: fmt.Sprintf("Incorrect Message Length, expected %d, got %d", bodyLength, length)}
}
return
-
}
func isHeaderField(tag Tag, dataDict *datadictionary.DataDictionary) bool {
@@ -274,7 +297,7 @@ func isTrailerField(tag Tag, dataDict *datadictionary.DataDictionary) bool {
return ok
}
-// MsgType returns MsgType (tag 35) field's value
+// MsgType returns MsgType (tag 35) field's value.
func (m *Message) MsgType() (string, MessageRejectError) {
return m.Header.GetString(tagMsgType)
}
@@ -287,7 +310,7 @@ func (m *Message) IsMsgTypeOf(msgType string) bool {
return false
}
-//reverseRoute returns a message builder with routing header fields initialized as the reverse of this message.
+// reverseRoute returns a message builder with routing header fields initialized as the reverse of this message.
func (m *Message) reverseRoute() *Message {
reverseMsg := NewMessage()
@@ -313,7 +336,7 @@ func (m *Message) reverseRoute() *Message {
copy(tagDeliverToCompID, tagOnBehalfOfCompID)
copy(tagDeliverToSubID, tagOnBehalfOfSubID)
- //tags added in 4.1
+ // Tags added in 4.1.
var beginString FIXString
if m.Header.GetField(tagBeginString, &beginString) == nil {
if string(beginString) != BeginStringFIX40 {
@@ -338,6 +361,19 @@ func extractSpecificField(field *TagValue, expectedTag Tag, buffer []byte) (remB
return
}
+func extractXMLDataField(parsedFieldBytes *TagValue, buffer []byte, dataLen int) (remBytes []byte, err error) {
+ endIndex := bytes.IndexByte(buffer, '=')
+ if endIndex == -1 {
+ err = parseError{OrigError: "extractField: No Trailing Delim in " + string(buffer)}
+ remBytes = buffer
+ return
+ }
+ endIndex += dataLen + 1
+
+ err = parsedFieldBytes.parse(buffer[:endIndex+1])
+ return buffer[(endIndex + 1):], err
+}
+
func extractField(parsedFieldBytes *TagValue, buffer []byte) (remBytes []byte, err error) {
endIndex := bytes.IndexByte(buffer, '\001')
if endIndex == -1 {
@@ -350,6 +386,14 @@ func extractField(parsedFieldBytes *TagValue, buffer []byte) (remBytes []byte, e
return buffer[(endIndex + 1):], err
}
+func (m *Message) Bytes() []byte {
+ if m.rawMessage != nil {
+ return m.rawMessage.Bytes()
+ }
+
+ return m.build()
+}
+
func (m *Message) String() string {
if m.rawMessage != nil {
return m.rawMessage.String()
@@ -362,7 +406,7 @@ func formatCheckSum(value int) string {
return fmt.Sprintf("%03d", value)
}
-//Build constructs a []byte from a Message instance
+// Build constructs a []byte from a Message instance.
func (m *Message) build() []byte {
m.cook()
diff --git a/message_pool.go b/message_pool.go
deleted file mode 100644
index 519062516..000000000
--- a/message_pool.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package quickfix
-
-type messagePool struct {
- m []*Message
-}
-
-func (p *messagePool) New() *Message {
- msg := NewMessage()
- return msg
-}
-
-func (p *messagePool) Get() (msg *Message) {
- if len(p.m) > 0 {
- msg, p.m = p.m[len(p.m)-1], p.m[:len(p.m)-1]
- } else {
- msg = p.New()
- }
-
- msg.keepMessage = false
-
- return
-}
-
-func (p *messagePool) Put(msg *Message) {
- p.m = append(p.m, msg)
-}
diff --git a/message_router.go b/message_router.go
index 8cc76bf73..c640f3e4d 100644
--- a/message_router.go
+++ b/message_router.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
type routeKey struct {
@@ -5,7 +20,7 @@ type routeKey struct {
MsgType string
}
-//FIX ApplVerID string values
+// FIX ApplVerID string values.
const (
ApplVerIDFIX27 = "0"
ApplVerIDFIX30 = "1"
@@ -19,25 +34,25 @@ const (
ApplVerIDFIX50SP2 = "9"
)
-//A MessageRoute is a function that can process a fromApp/fromAdmin callback
+// A MessageRoute is a function that can process a fromApp/fromAdmin callback.
type MessageRoute func(msg *Message, sessionID SessionID) MessageRejectError
-//A MessageRouter is a mutex for MessageRoutes
+// A MessageRouter is a mutex for MessageRoutes.
type MessageRouter struct {
routes map[routeKey]MessageRoute
}
-//NewMessageRouter returns an initialized MessageRouter instance
+// NewMessageRouter returns an initialized MessageRouter instance.
func NewMessageRouter() *MessageRouter {
return &MessageRouter{routes: make(map[routeKey]MessageRoute)}
}
-//AddRoute adds a route to the MessageRouter instance keyed to begin string and msgType.
+// AddRoute adds a route to the MessageRouter instance keyed to begin string and msgType.
func (c MessageRouter) AddRoute(beginString string, msgType string, router MessageRoute) {
c.routes[routeKey{beginString, msgType}] = router
}
-//Route may be called from the fromApp/fromAdmin callbacks. Messages that cannot be routed will be rejected with UnsupportedMessageType.
+// Route may be called from the fromApp/fromAdmin callbacks. Messages that cannot be routed will be rejected with UnsupportedMessageType.
func (c MessageRouter) Route(msg *Message, sessionID SessionID) MessageRejectError {
beginString, err := msg.Header.GetBytes(tagBeginString)
if err != nil {
diff --git a/message_router_test.go b/message_router_test.go
index 93779b464..b2e7b59f5 100644
--- a/message_router_test.go
+++ b/message_router_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/message_test.go b/message_test.go
index 3766c9221..9bb223751 100644
--- a/message_test.go
+++ b/message_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -5,16 +20,17 @@ import (
"reflect"
"testing"
- "github.com/quickfixgo/quickfix/datadictionary"
"github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/datadictionary"
)
func BenchmarkParseMessage(b *testing.B) {
rawMsg := bytes.NewBufferString("8=FIX.4.29=10435=D34=249=TW52=20140515-19:49:56.65956=ISLD11=10021=140=154=155=TSLA60=00010101-00:00:00.00010=039")
- var msg Message
+ msg := NewMessage()
for i := 0; i < b.N; i++ {
- _ = ParseMessage(&msg, rawMsg)
+ _ = ParseMessage(msg, rawMsg)
}
}
@@ -31,6 +47,16 @@ func (s *MessageSuite) SetupTest() {
s.msg = NewMessage()
}
+func TestXMLNonFIX(t *testing.T) {
+ rawMsg := bytes.NewBufferString("8=FIX.4.29=37235=n34=25512369=148152=20200522-07:05:33.75649=CME50=G56=OAEAAAN57=TRADE_CAPTURE143=US,IL212=261213=8=FIX.4.29=22535=BZ34=6549369=651852=20200522-07:05:33.74649=CME50=G56=9Q5000N57=DUMMY143=US,IL11=ACP159013113373460=20200522-07:05:33.734533=0893=Y1028=Y1300=991369=99612:325081373=31374=91375=15979=159013113373461769710=167 10=245\"")
+ msg := NewMessage()
+ _ = ParseMessage(msg, rawMsg)
+
+ if !msg.Header.Has(tagXMLData) {
+ t.Error("Expected xmldata tag")
+ }
+}
+
func (s *MessageSuite) TestParseMessageEmpty() {
rawMsg := bytes.NewBufferString("")
@@ -82,7 +108,7 @@ func (s *MessageSuite) TestParseMessageWithDataDictionary() {
}
func (s *MessageSuite) TestParseOutOfOrder() {
- //allow fields out of order, save for validation
+ // Allow fields out of order, save for validation.
rawMsg := bytes.NewBufferString("8=FIX.4.09=8135=D11=id21=338=10040=154=155=MSFT34=249=TW52=20140521-22:07:0956=ISLD10=250")
s.Nil(ParseMessage(s.msg, rawMsg))
}
@@ -158,7 +184,7 @@ func (s *MessageSuite) TestReverseRouteIgnoreEmpty() {
}
func (s *MessageSuite) TestReverseRouteFIX40() {
- //onbehalfof/deliverto location id not supported in fix 4.0
+ // The onbehalfof/deliverto location id not supported in fix 4.0.
s.Nil(ParseMessage(s.msg, bytes.NewBufferString("8=FIX.4.09=17135=D34=249=TW50=KK52=20060102-15:04:0556=ISLD57=AP144=BB115=JCD116=CS128=MG129=CB142=JV143=RY145=BH11=ID21=338=10040=w54=155=INTC60=20060102-15:04:0510=123")))
builder := s.msg.reverseRoute()
@@ -196,12 +222,14 @@ func (s *MessageSuite) TestCopyIntoMessage() {
s.Nil(ParseMessage(s.msg, bytes.NewBufferString(newMsgString)))
s.True(s.msg.IsMsgTypeOf("A"))
s.Equal(s.msg.String(), newMsgString)
+ s.Equal(string(s.msg.Bytes()), newMsgString)
// clear the source buffer also
msgBuf.Reset()
s.True(dest.IsMsgTypeOf("D"))
s.Equal(dest.String(), renderedString)
+ s.Equal(string(dest.Bytes()), renderedString)
}
func checkFieldInt(s *MessageSuite, fields FieldMap, tag, expected int) {
diff --git a/mongostore.go b/mongostore.go
index 76789dbe9..290cdc236 100644
--- a/mongostore.go
+++ b/mongostore.go
@@ -1,11 +1,29 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
+ "context"
"fmt"
"time"
- "github.com/globalsign/mgo"
- "github.com/globalsign/mgo/bson"
+ "github.com/pkg/errors"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
"github.com/quickfixgo/quickfix/config"
)
@@ -21,17 +39,18 @@ type mongoStore struct {
cache *memoryStore
mongoURL string
mongoDatabase string
- db *mgo.Session
+ db *mongo.Client
messagesCollection string
sessionsCollection string
+ allowTransactions bool
}
-// NewMongoStoreFactory returns a mongo-based implementation of MessageStoreFactory
+// NewMongoStoreFactory returns a mongo-based implementation of MessageStoreFactory.
func NewMongoStoreFactory(settings *Settings) MessageStoreFactory {
return NewMongoStoreFactoryPrefixed(settings, "")
}
-// NewMongoStoreFactoryPrefixed returns a mongo-based implementation of MessageStoreFactory, with prefix on collections
+// NewMongoStoreFactoryPrefixed returns a mongo-based implementation of MessageStoreFactory, with prefix on collections.
func NewMongoStoreFactoryPrefixed(settings *Settings, collectionsPrefix string) MessageStoreFactory {
return mongoStoreFactory{
settings: settings,
@@ -40,11 +59,18 @@ func NewMongoStoreFactoryPrefixed(settings *Settings, collectionsPrefix string)
}
}
-// Create creates a new MongoStore implementation of the MessageStore interface
+// Create creates a new MongoStore implementation of the MessageStore interface.
func (f mongoStoreFactory) Create(sessionID SessionID) (msgStore MessageStore, err error) {
+ globalSettings := f.settings.GlobalSettings()
+ dynamicSessions, _ := globalSettings.BoolSetting(config.DynamicSessions)
+
sessionSettings, ok := f.settings.SessionSettings()[sessionID]
if !ok {
- return nil, fmt.Errorf("unknown session: %v", sessionID)
+ if dynamicSessions {
+ sessionSettings = globalSettings
+ } else {
+ return nil, fmt.Errorf("unknown session: %v", sessionID)
+ }
}
mongoConnectionURL, err := sessionSettings.Setting(config.MongoStoreConnection)
if err != nil {
@@ -54,10 +80,16 @@ func (f mongoStoreFactory) Create(sessionID SessionID) (msgStore MessageStore, e
if err != nil {
return nil, err
}
- return newMongoStore(sessionID, mongoConnectionURL, mongoDatabase, f.messagesCollection, f.sessionsCollection)
+
+ // Optional.
+ mongoReplicaSet, _ := sessionSettings.Setting(config.MongoStoreReplicaSet)
+
+ return newMongoStore(sessionID, mongoConnectionURL, mongoDatabase, mongoReplicaSet, f.messagesCollection, f.sessionsCollection)
}
-func newMongoStore(sessionID SessionID, mongoURL string, mongoDatabase string, messagesCollection string, sessionsCollection string) (store *mongoStore, err error) {
+func newMongoStore(sessionID SessionID, mongoURL, mongoDatabase, mongoReplicaSet, messagesCollection, sessionsCollection string) (store *mongoStore, err error) {
+
+ allowTransactions := len(mongoReplicaSet) > 0
store = &mongoStore{
sessionID: sessionID,
cache: &memoryStore{},
@@ -65,10 +97,18 @@ func newMongoStore(sessionID SessionID, mongoURL string, mongoDatabase string, m
mongoDatabase: mongoDatabase,
messagesCollection: messagesCollection,
sessionsCollection: sessionsCollection,
+ allowTransactions: allowTransactions,
}
- store.cache.Reset()
- if store.db, err = mgo.Dial(mongoURL); err != nil {
+ if err = store.cache.Reset(); err != nil {
+ err = errors.Wrap(err, "cache reset")
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ store.db, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoURL).SetDirect(len(mongoReplicaSet) == 0).SetReplicaSet(mongoReplicaSet))
+ if err != nil {
return
}
err = store.populateCache()
@@ -91,14 +131,14 @@ func generateMessageFilter(s *SessionID) (messageFilter *mongoQuickFixEntryData)
}
type mongoQuickFixEntryData struct {
- //Message specific data
+ // Message specific data.
Msgseq int `bson:"msgseq,omitempty"`
Message []byte `bson:"message,omitempty"`
- //Session specific data
+ // Session specific data.
CreationTime time.Time `bson:"creation_time,omitempty"`
IncomingSeqNum int `bson:"incoming_seq_num,omitempty"`
OutgoingSeqNum int `bson:"outgoing_seq_num,omitempty"`
- //Indexed data
+ // Indexed data.
BeginString string `bson:"begin_string"`
SessionQualifier string `bson:"session_qualifier"`
SenderCompID string `bson:"sender_comp_id"`
@@ -109,10 +149,10 @@ type mongoQuickFixEntryData struct {
TargetLocID string `bson:"target_loc_id"`
}
-// Reset deletes the store records and sets the seqnums back to 1
+// Reset deletes the store records and sets the seqnums back to 1.
func (store *mongoStore) Reset() error {
msgFilter := generateMessageFilter(&store.sessionID)
- _, err := store.db.DB(store.mongoDatabase).C(store.messagesCollection).RemoveAll(msgFilter)
+ _, err := store.db.Database(store.mongoDatabase).Collection(store.messagesCollection).DeleteMany(context.Background(), msgFilter)
if err != nil {
return err
@@ -126,12 +166,12 @@ func (store *mongoStore) Reset() error {
sessionUpdate.CreationTime = store.cache.CreationTime()
sessionUpdate.IncomingSeqNum = store.cache.NextTargetMsgSeqNum()
sessionUpdate.OutgoingSeqNum = store.cache.NextSenderMsgSeqNum()
- err = store.db.DB(store.mongoDatabase).C(store.sessionsCollection).Update(msgFilter, sessionUpdate)
+ _, err = store.db.Database(store.mongoDatabase).Collection(store.sessionsCollection).UpdateOne(context.Background(), msgFilter, bson.M{"$set": sessionUpdate})
return err
}
-// Refresh reloads the store from the database
+// Refresh reloads the store from the database.
func (store *mongoStore) Refresh() error {
if err := store.cache.Reset(); err != nil {
return err
@@ -139,78 +179,96 @@ func (store *mongoStore) Refresh() error {
return store.populateCache()
}
-func (store *mongoStore) populateCache() (err error) {
+func (store *mongoStore) populateCache() error {
msgFilter := generateMessageFilter(&store.sessionID)
- query := store.db.DB(store.mongoDatabase).C(store.sessionsCollection).Find(msgFilter)
+ res := store.db.Database(store.mongoDatabase).Collection(store.sessionsCollection).FindOne(context.Background(), msgFilter)
+ if res.Err() != nil && res.Err() != mongo.ErrNoDocuments {
+ return errors.Wrap(res.Err(), "query")
+ }
- if cnt, err := query.Count(); err == nil && cnt > 0 {
+ if res.Err() != mongo.ErrNoDocuments {
// session record found, load it
sessionData := &mongoQuickFixEntryData{}
- err = query.One(&sessionData)
- if err == nil {
- store.cache.creationTime = sessionData.CreationTime
- store.cache.SetNextTargetMsgSeqNum(sessionData.IncomingSeqNum)
- store.cache.SetNextSenderMsgSeqNum(sessionData.OutgoingSeqNum)
+ if err := res.Decode(&sessionData); err != nil {
+ return errors.Wrap(err, "decode")
}
- } else if err == nil && cnt == 0 {
- // session record not found, create it
- msgFilter.CreationTime = store.cache.creationTime
- msgFilter.IncomingSeqNum = store.cache.NextTargetMsgSeqNum()
- msgFilter.OutgoingSeqNum = store.cache.NextSenderMsgSeqNum()
- err = store.db.DB(store.mongoDatabase).C(store.sessionsCollection).Insert(msgFilter)
+
+ store.cache.creationTime = sessionData.CreationTime
+ if err := store.cache.SetNextTargetMsgSeqNum(sessionData.IncomingSeqNum); err != nil {
+ return errors.Wrap(err, "cache set next target")
+ }
+
+ if err := store.cache.SetNextSenderMsgSeqNum(sessionData.OutgoingSeqNum); err != nil {
+ return errors.Wrap(err, "cache set next sender")
+ }
+
+ return nil
}
- return
+
+ // session record not found, create it
+ msgFilter.CreationTime = store.cache.creationTime
+ msgFilter.IncomingSeqNum = store.cache.NextTargetMsgSeqNum()
+ msgFilter.OutgoingSeqNum = store.cache.NextSenderMsgSeqNum()
+
+ if _, err := store.db.Database(store.mongoDatabase).Collection(store.sessionsCollection).InsertOne(context.Background(), msgFilter); err != nil {
+ return errors.Wrap(err, "insert")
+ }
+ return nil
}
-// NextSenderMsgSeqNum returns the next MsgSeqNum that will be sent
+// NextSenderMsgSeqNum returns the next MsgSeqNum that will be sent.
func (store *mongoStore) NextSenderMsgSeqNum() int {
return store.cache.NextSenderMsgSeqNum()
}
-// NextTargetMsgSeqNum returns the next MsgSeqNum that should be received
+// NextTargetMsgSeqNum returns the next MsgSeqNum that should be received.
func (store *mongoStore) NextTargetMsgSeqNum() int {
return store.cache.NextTargetMsgSeqNum()
}
-// SetNextSenderMsgSeqNum sets the next MsgSeqNum that will be sent
+// SetNextSenderMsgSeqNum sets the next MsgSeqNum that will be sent.
func (store *mongoStore) SetNextSenderMsgSeqNum(next int) error {
msgFilter := generateMessageFilter(&store.sessionID)
sessionUpdate := generateMessageFilter(&store.sessionID)
sessionUpdate.IncomingSeqNum = store.cache.NextTargetMsgSeqNum()
sessionUpdate.OutgoingSeqNum = next
sessionUpdate.CreationTime = store.cache.CreationTime()
- if err := store.db.DB(store.mongoDatabase).C(store.sessionsCollection).Update(msgFilter, sessionUpdate); err != nil {
+ if _, err := store.db.Database(store.mongoDatabase).Collection(store.sessionsCollection).UpdateOne(context.Background(), msgFilter, bson.M{"$set": sessionUpdate}); err != nil {
return err
}
return store.cache.SetNextSenderMsgSeqNum(next)
}
-// SetNextTargetMsgSeqNum sets the next MsgSeqNum that should be received
+// SetNextTargetMsgSeqNum sets the next MsgSeqNum that should be received.
func (store *mongoStore) SetNextTargetMsgSeqNum(next int) error {
msgFilter := generateMessageFilter(&store.sessionID)
sessionUpdate := generateMessageFilter(&store.sessionID)
sessionUpdate.IncomingSeqNum = next
sessionUpdate.OutgoingSeqNum = store.cache.NextSenderMsgSeqNum()
sessionUpdate.CreationTime = store.cache.CreationTime()
- if err := store.db.DB(store.mongoDatabase).C(store.sessionsCollection).Update(msgFilter, sessionUpdate); err != nil {
+ if _, err := store.db.Database(store.mongoDatabase).Collection(store.sessionsCollection).UpdateOne(context.Background(), msgFilter, bson.M{"$set": sessionUpdate}); err != nil {
return err
}
return store.cache.SetNextTargetMsgSeqNum(next)
}
-// IncrNextSenderMsgSeqNum increments the next MsgSeqNum that will be sent
+// IncrNextSenderMsgSeqNum increments the next MsgSeqNum that will be sent.
func (store *mongoStore) IncrNextSenderMsgSeqNum() error {
- store.cache.IncrNextSenderMsgSeqNum()
+ if err := store.cache.IncrNextSenderMsgSeqNum(); err != nil {
+ return errors.Wrap(err, "cache incr")
+ }
return store.SetNextSenderMsgSeqNum(store.cache.NextSenderMsgSeqNum())
}
-// IncrNextTargetMsgSeqNum increments the next MsgSeqNum that should be received
+// IncrNextTargetMsgSeqNum increments the next MsgSeqNum that should be received.
func (store *mongoStore) IncrNextTargetMsgSeqNum() error {
- store.cache.IncrNextTargetMsgSeqNum()
+ if err := store.cache.IncrNextTargetMsgSeqNum(); err != nil {
+ return errors.Wrap(err, "cache incr")
+ }
return store.SetNextTargetMsgSeqNum(store.cache.NextTargetMsgSeqNum())
}
-// CreationTime returns the creation time of the store
+// CreationTime returns the creation time of the store.
func (store *mongoStore) CreationTime() time.Time {
return store.cache.CreationTime()
}
@@ -219,13 +277,59 @@ func (store *mongoStore) SaveMessage(seqNum int, msg []byte) (err error) {
msgFilter := generateMessageFilter(&store.sessionID)
msgFilter.Msgseq = seqNum
msgFilter.Message = msg
- err = store.db.DB(store.mongoDatabase).C(store.messagesCollection).Insert(msgFilter)
+ _, err = store.db.Database(store.mongoDatabase).Collection(store.messagesCollection).InsertOne(context.Background(), msgFilter)
return
}
+func (store *mongoStore) SaveMessageAndIncrNextSenderMsgSeqNum(seqNum int, msg []byte) error {
+
+ if !store.allowTransactions {
+ err := store.SaveMessage(seqNum, msg)
+ if err != nil {
+ return err
+ }
+ return store.IncrNextSenderMsgSeqNum()
+ }
+
+ // If the mongodb supports replicasets, perform this operation as a transaction instead-
+ var next int
+ err := store.db.UseSession(context.Background(), func(sessionCtx mongo.SessionContext) error {
+ if err := sessionCtx.StartTransaction(); err != nil {
+ return err
+ }
+
+ msgFilter := generateMessageFilter(&store.sessionID)
+ msgFilter.Msgseq = seqNum
+ msgFilter.Message = msg
+ _, err := store.db.Database(store.mongoDatabase).Collection(store.messagesCollection).InsertOne(sessionCtx, msgFilter)
+ if err != nil {
+ return err
+ }
+
+ next = store.cache.NextSenderMsgSeqNum() + 1
+
+ msgFilter = generateMessageFilter(&store.sessionID)
+ sessionUpdate := generateMessageFilter(&store.sessionID)
+ sessionUpdate.IncomingSeqNum = store.cache.NextTargetMsgSeqNum()
+ sessionUpdate.OutgoingSeqNum = next
+ sessionUpdate.CreationTime = store.cache.CreationTime()
+ _, err = store.db.Database(store.mongoDatabase).Collection(store.sessionsCollection).UpdateOne(sessionCtx, msgFilter, bson.M{"$set": sessionUpdate})
+ if err != nil {
+ return err
+ }
+
+ return sessionCtx.CommitTransaction(context.Background())
+ })
+ if err != nil {
+ return err
+ }
+
+ return store.cache.SetNextSenderMsgSeqNum(next)
+}
+
func (store *mongoStore) GetMessages(beginSeqNum, endSeqNum int) (msgs [][]byte, err error) {
msgFilter := generateMessageFilter(&store.sessionID)
- //Marshal into database form
+ // Marshal into database form.
msgFilterBytes, err := bson.Marshal(msgFilter)
if err != nil {
return
@@ -235,24 +339,35 @@ func (store *mongoStore) GetMessages(beginSeqNum, endSeqNum int) (msgs [][]byte,
if err != nil {
return
}
- //Modify the query to use a range for the sequence filter
+ // Modify the query to use a range for the sequence filter.
seqFilter["msgseq"] = bson.M{
"$gte": beginSeqNum,
"$lte": endSeqNum,
}
+ sortOpt := options.Find().SetSort(bson.D{{Key: "msgseq", Value: 1}})
+ cursor, err := store.db.Database(store.mongoDatabase).Collection(store.messagesCollection).Find(context.Background(), seqFilter, sortOpt)
+ if err != nil {
+ return
+ }
- iter := store.db.DB(store.mongoDatabase).C(store.messagesCollection).Find(seqFilter).Sort("msgseq").Iter()
- for iter.Next(msgFilter) {
+ for cursor.Next(context.Background()) {
+ if err = cursor.Decode(&msgFilter); err != nil {
+ return
+ }
msgs = append(msgs, msgFilter.Message)
}
- err = iter.Close()
+
+ err = cursor.Close(context.Background())
return
}
-// Close closes the store's database connection
+// Close closes the store's database connection.
func (store *mongoStore) Close() error {
if store.db != nil {
- store.db.Close()
+ err := store.db.Disconnect(context.Background())
+ if err != nil {
+ return errors.Wrap(err, "error disconnecting from database")
+ }
store.db = nil
}
return nil
diff --git a/mongostore_test.go b/mongostore_test.go
index 21ca84a8d..4a8089f1c 100644
--- a/mongostore_test.go
+++ b/mongostore_test.go
@@ -1,16 +1,32 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"fmt"
- "github.com/stretchr/testify/require"
- "github.com/stretchr/testify/suite"
"log"
"os"
"strings"
"testing"
+
+ "github.com/stretchr/testify/require"
+ "github.com/stretchr/testify/suite"
)
-// MongoStoreTestSuite runs all tests in the MessageStoreTestSuite against the MongoStore implementation
+// MongoStoreTestSuite runs all tests in the MessageStoreTestSuite against the MongoStore implementation.
type MongoStoreTestSuite struct {
MessageStoreTestSuite
}
@@ -22,6 +38,7 @@ func (suite *MongoStoreTestSuite) SetupTest() {
suite.T().SkipNow()
}
mongoDatabase := "automated_testing_database"
+ mongoReplicaSet := "replicaset"
// create settings
sessionID := SessionID{BeginString: "FIX.4.4", SenderCompID: "SENDER", TargetCompID: "TARGET"}
@@ -29,11 +46,12 @@ func (suite *MongoStoreTestSuite) SetupTest() {
[DEFAULT]
MongoStoreConnection=%s
MongoStoreDatabase=%s
+MongoStoreReplicaSet=%s
[SESSION]
BeginString=%s
SenderCompID=%s
-TargetCompID=%s`, mongoDbCxn, mongoDatabase, sessionID.BeginString, sessionID.SenderCompID, sessionID.TargetCompID)))
+TargetCompID=%s`, mongoDbCxn, mongoDatabase, mongoReplicaSet, sessionID.BeginString, sessionID.SenderCompID, sessionID.TargetCompID)))
require.Nil(suite.T(), err)
// create store
@@ -44,7 +62,10 @@ TargetCompID=%s`, mongoDbCxn, mongoDatabase, sessionID.BeginString, sessionID.Se
}
func (suite *MongoStoreTestSuite) TearDownTest() {
- suite.msgStore.Close()
+ if suite.msgStore != nil {
+ err := suite.msgStore.Close()
+ require.Nil(suite.T(), err)
+ }
}
func TestMongoStoreTestSuite(t *testing.T) {
diff --git a/msg_type.go b/msg_type.go
index 03faed698..823d9a752 100644
--- a/msg_type.go
+++ b/msg_type.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import "bytes"
@@ -10,7 +25,7 @@ var msgTypeReject = []byte("3")
var msgTypeSequenceReset = []byte("4")
var msgTypeLogout = []byte("5")
-//isAdminMessageType returns true if the message type is a session level message.
+// isAdminMessageType returns true if the message type is a session level message.
func isAdminMessageType(m []byte) bool {
switch {
case bytes.Equal(msgTypeHeartbeat, m),
diff --git a/not_session_time.go b/not_session_time.go
index 2e3b04c3d..058159b99 100644
--- a/not_session_time.go
+++ b/not_session_time.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import "github.com/quickfixgo/quickfix/internal"
diff --git a/not_session_time_test.go b/not_session_time_test.go
index 21a2c8f22..736e8efc9 100644
--- a/not_session_time_test.go
+++ b/not_session_time_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/null_log.go b/null_log.go
index 3d6f02ba1..106dcae42 100644
--- a/null_log.go
+++ b/null_log.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
type nullLog struct{}
@@ -16,7 +31,7 @@ func (nullLogFactory) CreateSessionLog(sessionID SessionID) (Log, error) {
return nullLog{}, nil
}
-//NewNullLogFactory creates an instance of LogFactory that returns no-op loggers.
+// NewNullLogFactory creates an instance of LogFactory that returns no-op loggers.
func NewNullLogFactory() LogFactory {
return nullLogFactory{}
}
diff --git a/parser.go b/parser.go
index 8816345d4..a3ec73993 100644
--- a/parser.go
+++ b/parser.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -5,18 +20,14 @@ import (
"errors"
"io"
"time"
-
- "github.com/quickfixgo/quickfix/internal"
)
const (
defaultBufSize = 4096
)
-var bufferPool internal.BufferPool
-
type parser struct {
- //buffer is a slice of bigBuffer
+ // Buffer is a slice of bigBuffer.
bigBuffer, buffer []byte
reader io.Reader
lastRead time.Time
@@ -30,16 +41,16 @@ func (p *parser) readMore() (int, error) {
if len(p.buffer) == cap(p.buffer) {
var newBuffer []byte
switch {
- //initialize the parser
+ // Initialize the parser.
case len(p.bigBuffer) == 0:
p.bigBuffer = make([]byte, defaultBufSize)
newBuffer = p.bigBuffer[0:0]
- //shift buffer back to the start of bigBuffer
+ // Shift buffer back to the start of bigBuffer.
case 2*len(p.buffer) <= len(p.bigBuffer):
newBuffer = p.bigBuffer[0:len(p.buffer)]
- //reallocate big buffer with enough space to shift buffer
+ // Reallocate big buffer with enough space to shift buffer.
default:
p.bigBuffer = make([]byte, 2*len(p.buffer))
newBuffer = p.bigBuffer[0:len(p.buffer)]
@@ -145,7 +156,7 @@ func (p *parser) ReadMessage() (msgBytes *bytes.Buffer, err error) {
return
}
- msgBytes = bufferPool.Get()
+ msgBytes = new(bytes.Buffer)
msgBytes.Reset()
msgBytes.Write(p.buffer[:index])
p.buffer = p.buffer[index:]
diff --git a/parser_test.go b/parser_test.go
index ab9c086a7..34bca808d 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -13,7 +28,7 @@ func BenchmarkParser_ReadMessage(b *testing.B) {
for i := 0; i < b.N; i++ {
reader := strings.NewReader(stream)
parser := newParser(reader)
- parser.ReadMessage()
+ _, _ = parser.ReadMessage()
}
}
diff --git a/pending_timeout.go b/pending_timeout.go
index 87154dddd..170af8a57 100644
--- a/pending_timeout.go
+++ b/pending_timeout.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import "github.com/quickfixgo/quickfix/internal"
diff --git a/pending_timeout_test.go b/pending_timeout_test.go
index a9e4bf8f1..954a79c7c 100644
--- a/pending_timeout_test.go
+++ b/pending_timeout_test.go
@@ -1,10 +1,26 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"testing"
- "github.com/quickfixgo/quickfix/internal"
"github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/internal"
)
type PendingTimeoutTestSuite struct {
diff --git a/quickfix_test.go b/quickfix_test.go
index 2f511b77d..beb8792bc 100644
--- a/quickfix_test.go
+++ b/quickfix_test.go
@@ -1,12 +1,28 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"time"
- "github.com/quickfixgo/quickfix/internal"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/internal"
)
type QuickFIXSuite struct {
@@ -51,7 +67,7 @@ func (s *QuickFIXSuite) MessageEqualsBytes(expectedBytes []byte, msg *Message) {
s.Equal(string(actualBytes), string(expectedBytes))
}
-//MockStore wraps a memory store and mocks Refresh for convenience
+// MockStore wraps a memory store and mocks Refresh for convenience.
type MockStore struct {
mock.Mock
memoryStore
diff --git a/registry.go b/registry.go
index 3c1fb2af8..291d44bee 100644
--- a/registry.go
+++ b/registry.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -10,12 +25,12 @@ var sessions = make(map[SessionID]*session)
var errDuplicateSessionID = errors.New("Duplicate SessionID")
var errUnknownSession = errors.New("Unknown session")
-//Messagable is a Message or something that can be converted to a Message
+// Messagable is a Message or something that can be converted to a Message.
type Messagable interface {
ToMessage() *Message
}
-//Send determines the session to send Messagable using header fields BeginString, TargetCompID, SenderCompID
+// Send determines the session to send Messagable using header fields BeginString, TargetCompID, SenderCompID.
func Send(m Messagable) (err error) {
msg := m.ToMessage()
var beginString FIXString
@@ -30,8 +45,7 @@ func Send(m Messagable) (err error) {
var senderCompID FIXString
if err := msg.Header.GetField(tagSenderCompID, &senderCompID); err != nil {
-
- return nil
+ return err
}
sessionID := SessionID{BeginString: string(beginString), TargetCompID: string(targetCompID), SenderCompID: string(senderCompID)}
@@ -39,7 +53,7 @@ func Send(m Messagable) (err error) {
return SendToTarget(msg, sessionID)
}
-//SendToTarget sends a message based on the sessionID. Convenient for use in FromApp since it provides a session ID for incoming messages
+// SendToTarget sends a message based on the sessionID. Convenient for use in FromApp since it provides a session ID for incoming messages.
func SendToTarget(m Messagable, sessionID SessionID) error {
msg := m.ToMessage()
session, ok := lookupSession(sessionID)
@@ -50,7 +64,7 @@ func SendToTarget(m Messagable, sessionID SessionID) error {
return session.queueForSend(msg)
}
-//UnregisterSession removes a session from the set of known sessions
+// UnregisterSession removes a session from the set of known sessions.
func UnregisterSession(sessionID SessionID) error {
sessionsLock.Lock()
defer sessionsLock.Unlock()
diff --git a/repeating_group.go b/repeating_group.go
index d3bc4a58c..853cc7d35 100644
--- a/repeating_group.go
+++ b/repeating_group.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -6,20 +21,20 @@ import (
"strconv"
)
-//GroupItem interface is used to construct repeating group templates
+// GroupItem interface is used to construct repeating group templates.
type GroupItem interface {
- //Tag returns the tag identifying this GroupItem
+ // Tag returns the tag identifying this GroupItem.
Tag() Tag
- //Parameter to Read is tagValues. For most fields, only the first tagValue will be required.
- //The length of the slice extends from the tagValue mapped to the field to be read through the
- //following fields. This can be useful for GroupItems made up of repeating groups.
+ // Read Parameter to Read is tagValues. For most fields, only the first tagValue will be required.
+ // The length of the slice extends from the tagValue mapped to the field to be read through the
+ // following fields. This can be useful for GroupItems made up of repeating groups.
//
- //The Read function returns the remaining tagValues not processed by the GroupItem. If there was a
- //problem reading the field, an error may be returned
+ // The Read function returns the remaining tagValues not processed by the GroupItem. If there was a
+ // problem reading the field, an error may be returned.
Read([]TagValue) ([]TagValue, error)
- //Clone makes a copy of this GroupItem
+ // Clone makes a copy of this GroupItem.
Clone() GroupItem
}
@@ -38,15 +53,15 @@ func (t protoGroupElement) Read(tv []TagValue) ([]TagValue, error) {
func (t protoGroupElement) Clone() GroupItem { return t }
-//GroupElement returns a GroupItem made up of a single field
+// GroupElement returns a GroupItem made up of a single field.
func GroupElement(tag Tag) GroupItem {
return protoGroupElement{tag: tag}
}
-//GroupTemplate specifies the group item order for a RepeatingGroup
+// GroupTemplate specifies the group item order for a RepeatingGroup.
type GroupTemplate []GroupItem
-//Clone makes a copy of this GroupTemplate
+// Clone makes a copy of this GroupTemplate.
func (gt GroupTemplate) Clone() GroupTemplate {
clone := make(GroupTemplate, len(gt))
for i := range gt {
@@ -56,17 +71,17 @@ func (gt GroupTemplate) Clone() GroupTemplate {
return clone
}
-//Group is a group of fields occurring in a repeating group
+// Group is a group of fields occurring in a repeating group.
type Group struct{ FieldMap }
-//RepeatingGroup is a FIX Repeating Group type
+// RepeatingGroup is a FIX Repeating Group type.
type RepeatingGroup struct {
tag Tag
template GroupTemplate
groups []*Group
}
-//NewRepeatingGroup returns an initilized RepeatingGroup instance
+// NewRepeatingGroup returns an initilized RepeatingGroup instance.
func NewRepeatingGroup(tag Tag, template GroupTemplate) *RepeatingGroup {
return &RepeatingGroup{
tag: tag,
@@ -74,12 +89,12 @@ func NewRepeatingGroup(tag Tag, template GroupTemplate) *RepeatingGroup {
}
}
-//Tag returns the Tag for this repeating Group
+// Tag returns the Tag for this repeating Group.
func (f RepeatingGroup) Tag() Tag {
return f.tag
}
-//Clone makes a copy of this RepeatingGroup (tag, template)
+// Clone makes a copy of this RepeatingGroup (tag, template).
func (f RepeatingGroup) Clone() GroupItem {
return &RepeatingGroup{
tag: f.tag,
@@ -87,17 +102,17 @@ func (f RepeatingGroup) Clone() GroupItem {
}
}
-//Len returns the number of Groups in this RepeatingGroup
+// Len returns the number of Groups in this RepeatingGroup.
func (f RepeatingGroup) Len() int {
return len(f.groups)
}
-//Get returns the ith group in this RepeatingGroup
+// Get returns the ith group in this RepeatingGroup.
func (f RepeatingGroup) Get(i int) *Group {
return f.groups[i]
}
-//Add appends a new group to the RepeatingGroup and returns the new Group
+// Add appends a new group to the RepeatingGroup and returns the new Group.
func (f *RepeatingGroup) Add() *Group {
g := new(Group)
g.initWithOrdering(f.groupTagOrder())
@@ -106,20 +121,21 @@ func (f *RepeatingGroup) Add() *Group {
return g
}
-//Write returns tagValues for all Items in the repeating group ordered by
-//Group sequence and Group template order
+// Write returns tagValues for all Items in the repeating group ordered by
+// Group sequence and Group template order.
func (f RepeatingGroup) Write() []TagValue {
- tvs := make([]TagValue, 1, 1)
+ tvs := make([]TagValue, 1)
tvs[0].init(f.tag, []byte(strconv.Itoa(len(f.groups))))
for _, group := range f.groups {
tags := group.sortedTags()
-
+ group.rwLock.RLock()
for _, tag := range tags {
if fields, ok := group.tagLookup[tag]; ok {
tvs = append(tvs, fields...)
}
}
+ group.rwLock.RUnlock()
}
return tvs
@@ -144,8 +160,8 @@ func (f RepeatingGroup) groupTagOrder() tagOrder {
}
return func(i, j Tag) bool {
- orderi := math.MaxInt64
- orderj := math.MaxInt64
+ orderi := math.MaxInt32
+ orderj := math.MaxInt32
if iIndex, ok := tagMap[i]; ok {
orderi = iIndex
@@ -199,7 +215,10 @@ func (f *RepeatingGroup) Read(tv []TagValue) ([]TagValue, error) {
f.groups = append(f.groups, group)
}
+ group.rwLock.Lock()
group.tagLookup[tvRange[0].tag] = tvRange
+ group.tags = append(group.tags, gi.Tag())
+ group.rwLock.Unlock()
}
if len(f.groups) != expectedGroupSize {
diff --git a/repeating_group_test.go b/repeating_group_test.go
index d43d38d79..abd316d78 100644
--- a/repeating_group_test.go
+++ b/repeating_group_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -97,16 +112,16 @@ func TestRepeatingGroup_ReadError(t *testing.T) {
}{
{
[]TagValue{
- TagValue{value: []byte("1")},
- TagValue{tag: Tag(2), value: []byte("not in template")},
- TagValue{tag: Tag(1), value: []byte("hello")},
+ {value: []byte("1")},
+ {tag: Tag(2), value: []byte("not in template")},
+ {tag: Tag(1), value: []byte("hello")},
}, 0},
{
[]TagValue{
- TagValue{value: []byte("2")},
- TagValue{tag: Tag(1), value: []byte("hello")},
- TagValue{tag: Tag(2), value: []byte("not in template")},
- TagValue{tag: Tag(1), value: []byte("hello")},
+ {value: []byte("2")},
+ {tag: Tag(1), value: []byte("hello")},
+ {tag: Tag(2), value: []byte("not in template")},
+ {tag: Tag(1), value: []byte("hello")},
}, 1}}
for _, s := range tests {
@@ -128,29 +143,29 @@ func TestRepeatingGroup_Read(t *testing.T) {
tv []TagValue
expectedGroupTvs [][]TagValue
}{
- {singleFieldTemplate, []TagValue{TagValue{value: []byte("0")}},
+ {singleFieldTemplate, []TagValue{{value: []byte("0")}},
[][]TagValue{}},
- {singleFieldTemplate, []TagValue{TagValue{value: []byte("1")}, TagValue{tag: Tag(1), value: []byte("hello")}},
+ {singleFieldTemplate, []TagValue{{value: []byte("1")}, {tag: Tag(1), value: []byte("hello")}},
[][]TagValue{{TagValue{tag: Tag(1), value: []byte("hello")}}}},
{singleFieldTemplate,
- []TagValue{TagValue{value: []byte("1")},
- TagValue{tag: Tag(1), value: []byte("hello")},
- TagValue{tag: Tag(2), value: []byte("not in group")}},
+ []TagValue{{value: []byte("1")},
+ {tag: Tag(1), value: []byte("hello")},
+ {tag: Tag(2), value: []byte("not in group")}},
[][]TagValue{
{TagValue{tag: Tag(1), value: []byte("hello")}}}},
{singleFieldTemplate,
- []TagValue{TagValue{value: []byte("2")},
- TagValue{tag: Tag(1), value: []byte("hello")},
- TagValue{tag: Tag(1), value: []byte("world")}},
+ []TagValue{{value: []byte("2")},
+ {tag: Tag(1), value: []byte("hello")},
+ {tag: Tag(1), value: []byte("world")}},
[][]TagValue{
{TagValue{tag: Tag(1), value: []byte("hello")}},
{TagValue{tag: Tag(1), value: []byte("world")}},
}},
{multiFieldTemplate,
[]TagValue{
- TagValue{value: []byte("2")},
- TagValue{tag: Tag(1), value: []byte("hello")},
- TagValue{tag: Tag(1), value: []byte("goodbye")}, TagValue{tag: Tag(2), value: []byte("cruel")}, TagValue{tag: Tag(3), value: []byte("world")},
+ {value: []byte("2")},
+ {tag: Tag(1), value: []byte("hello")},
+ {tag: Tag(1), value: []byte("goodbye")}, {tag: Tag(2), value: []byte("cruel")}, {tag: Tag(3), value: []byte("world")},
},
[][]TagValue{
{TagValue{tag: Tag(1), value: []byte("hello")}},
@@ -158,10 +173,10 @@ func TestRepeatingGroup_Read(t *testing.T) {
}},
{multiFieldTemplate,
[]TagValue{
- TagValue{value: []byte("3")},
- TagValue{tag: Tag(1), value: []byte("hello")},
- TagValue{tag: Tag(1), value: []byte("goodbye")}, TagValue{tag: Tag(2), value: []byte("cruel")}, TagValue{tag: Tag(3), value: []byte("world")},
- TagValue{tag: Tag(1), value: []byte("another")},
+ {value: []byte("3")},
+ {tag: Tag(1), value: []byte("hello")},
+ {tag: Tag(1), value: []byte("goodbye")}, {tag: Tag(2), value: []byte("cruel")}, {tag: Tag(3), value: []byte("world")},
+ {tag: Tag(1), value: []byte("another")},
},
[][]TagValue{
{TagValue{tag: Tag(1), value: []byte("hello")}},
@@ -185,6 +200,8 @@ func TestRepeatingGroup_Read(t *testing.T) {
for _, expected := range test.expectedGroupTvs[g] {
var actual FIXString
require.Nil(t, group.GetField(expected.tag, &actual))
+ require.NotNil(t, group.tags)
+ require.Equal(t, len(group.tags), len(group.tagLookup))
if !bytes.Equal(expected.value, []byte(actual)) {
t.Errorf("%v, %v: expected %s, got %s", g, expected.tag, expected.value, actual)
@@ -200,11 +217,11 @@ func TestRepeatingGroup_ReadRecursive(t *testing.T) {
f := NewRepeatingGroup(Tag(1), parentTemplate)
_, err := f.Read([]TagValue{
- TagValue{value: []byte("2")},
- TagValue{tag: Tag(2), value: []byte("hello")},
- TagValue{tag: 3, value: []byte("1")}, TagValue{tag: 4, value: []byte("foo")},
- TagValue{tag: Tag(2), value: []byte("world")},
- TagValue{tag: 3, value: []byte("2")}, TagValue{tag: 4, value: []byte("foo")}, TagValue{tag: 4, value: []byte("bar")}, TagValue{tag: 5, value: []byte("fubar")},
+ {value: []byte("2")},
+ {tag: Tag(2), value: []byte("hello")},
+ {tag: 3, value: []byte("1")}, {tag: 4, value: []byte("foo")},
+ {tag: Tag(2), value: []byte("world")},
+ {tag: 3, value: []byte("2")}, {tag: 4, value: []byte("foo")}, {tag: 4, value: []byte("bar")}, {tag: 5, value: []byte("fubar")},
})
require.Nil(t, err)
diff --git a/resend_state.go b/resend_state.go
index a89d3f697..8a46e36e3 100644
--- a/resend_state.go
+++ b/resend_state.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import "github.com/quickfixgo/quickfix/internal"
@@ -17,8 +32,8 @@ func (s resendState) Timeout(session *session, event internal.Event) (nextState
case inSession:
nextState = s
case pendingTimeout:
- //wrap pendingTimeout in resend. prevents us falling back to inSession if recovering
- //from pendingTimeout
+ // Wrap pendingTimeout in resend. prevents us falling back to inSession if recovering
+ // from pendingTimeout.
nextState = pendingTimeout{s}
}
@@ -54,9 +69,6 @@ func (s resendState) FixMsgIn(session *session, msg *Message) (nextState session
delete(s.messageStash, targetSeqNum)
- //return stashed message to pool
- session.returnToPool(msg)
-
nextState = inSession{}.FixMsgIn(session, msg)
if !nextState.IsLoggedOn() {
return
diff --git a/resend_state_test.go b/resend_state_test.go
index aa13cc871..d6bd0eb50 100644
--- a/resend_state_test.go
+++ b/resend_state_test.go
@@ -1,10 +1,27 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"testing"
+ "time"
- "github.com/quickfixgo/quickfix/internal"
"github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/internal"
)
type resendStateTestSuite struct {
@@ -60,7 +77,7 @@ func (s *resendStateTestSuite) TestTimeoutUnchangedNeedHeartbeat() {
func (s *resendStateTestSuite) TestFixMsgIn() {
s.session.State = inSession{}
- //in session expects seq number 1, send too high
+ // In session expects seq number 1, send too high.
s.MessageFactory.SetNextSeqNum(2)
s.MockApp.On("ToAdmin")
@@ -97,7 +114,7 @@ func (s *resendStateTestSuite) TestFixMsgIn() {
func (s *resendStateTestSuite) TestFixMsgInSequenceReset() {
s.session.State = inSession{}
- //in session expects seq number 1, send too high
+ // In session expects seq number 1, send too high.
s.MessageFactory.SetNextSeqNum(3)
s.MockApp.On("ToAdmin")
@@ -129,7 +146,7 @@ func (s *resendStateTestSuite) TestFixMsgInResendChunk() {
s.session.State = inSession{}
s.ResendRequestChunkSize = 2
- //in session expects seq number 1, send too high
+ // In session expects seq number 1, send too high.
s.MessageFactory.SetNextSeqNum(4)
s.MockApp.On("ToAdmin")
@@ -173,3 +190,32 @@ func (s *resendStateTestSuite) TestFixMsgInResendChunk() {
s.FieldEquals(tagBeginSeqNo, 3, s.MockApp.lastToAdmin.Body)
s.FieldEquals(tagEndSeqNo, 0, s.MockApp.lastToAdmin.Body)
}
+
+// TestFixMsgResendWithOldSendingTime tests that we suspend staleness checks during replay
+// as a replayed message may be arbitrarily old.
+func (s *resendStateTestSuite) TestFixMsgResendWithOldSendingTime() {
+ s.session.State = inSession{}
+ s.ResendRequestChunkSize = 2
+
+ // In session expects seq number 1, send too high.
+ s.MessageFactory.SetNextSeqNum(4)
+ s.MockApp.On("ToAdmin")
+
+ msgSeqNum4 := s.NewOrderSingle()
+ s.fixMsgIn(s.session, msgSeqNum4)
+
+ s.MockApp.AssertExpectations(s.T())
+ s.State(resendState{})
+ s.LastToAdminMessageSent()
+ s.MessageType(string(msgTypeResendRequest), s.MockApp.lastToAdmin)
+ s.FieldEquals(tagBeginSeqNo, 1, s.MockApp.lastToAdmin.Body)
+ s.FieldEquals(tagEndSeqNo, 2, s.MockApp.lastToAdmin.Body)
+ s.NextTargetMsgSeqNum(1)
+
+ msgSeqNum5 := s.NewOrderSingle()
+ // Set the sending time far enough in the past to trip the staleness check.
+ msgSeqNum5.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: time.Now().Add(-s.MaxLatency)})
+ s.fixMsgIn(s.session, msgSeqNum5)
+ s.State(resendState{})
+ s.NextTargetMsgSeqNum(1)
+}
diff --git a/screen_log.go b/screen_log.go
index 0fcd4ebdb..c17d67ecb 100644
--- a/screen_log.go
+++ b/screen_log.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -40,7 +55,7 @@ func (screenLogFactory) CreateSessionLog(sessionID SessionID) (Log, error) {
return log, nil
}
-//NewScreenLogFactory creates an instance of LogFactory that writes messages and events to stdout.
+// NewScreenLogFactory creates an instance of LogFactory that writes messages and events to stdout.
func NewScreenLogFactory() LogFactory {
return screenLogFactory{}
}
diff --git a/session.go b/session.go
index 204fa99d5..b359245e2 100644
--- a/session.go
+++ b/session.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -11,7 +26,7 @@ import (
"github.com/quickfixgo/quickfix/internal"
)
-//The Session is the primary FIX abstraction for message communication
+// The Session is the primary FIX abstraction for message communication.
type session struct {
store MessageStore
@@ -21,20 +36,21 @@ type session struct {
messageOut chan<- []byte
messageIn <-chan fixIn
- //application messages are queued up for send here
+ // Application messages are queued up for send here.
toSend [][]byte
- //mutex for access to toSend
+ // Mutex for access to toSend.
sendMutex sync.Mutex
sessionEvent chan internal.Event
messageEvent chan bool
application Application
- validator
+ Validator
stateMachine
stateTimer *internal.EventTimer
peerTimer *internal.EventTimer
sentReset bool
+ stopOnce sync.Once
targetDefaultApplVerID string
@@ -43,7 +59,6 @@ type session struct {
transportDataDictionary *datadictionary.DataDictionary
appDataDictionary *datadictionary.DataDictionary
- messagePool
timestampPrecision TimestampPrecision
}
@@ -51,8 +66,8 @@ func (s *session) logError(err error) {
s.log.OnEvent(err.Error())
}
-//TargetDefaultApplicationVersionID returns the default application version ID for messages received by this version.
-//Applicable for For FIX.T.1 sessions.
+// TargetDefaultApplicationVersionID returns the default application version ID for messages received by this version.
+// Applicable for For FIX.T.1 sessions.
func (s *session) TargetDefaultApplicationVersionID() string {
return s.targetDefaultApplVerID
}
@@ -77,7 +92,10 @@ func (s *session) connect(msgIn <-chan fixIn, msgOut chan<- []byte) error {
type stopReq struct{}
func (s *session) stop() {
- s.admin <- stopReq{}
+ // Stop once.
+ s.stopOnce.Do(func() {
+ s.admin <- stopReq{}
+ })
}
type waitChan <-chan interface{}
@@ -205,7 +223,7 @@ func (s *session) resend(msg *Message) bool {
return s.application.ToApp(msg, s.sessionID) == nil
}
-//queueForSend will validate, persist, and queue the message for send
+// queueForSend will validate, persist, and queue the message for send.
func (s *session) queueForSend(msg *Message) error {
s.sendMutex.Lock()
defer s.sendMutex.Unlock()
@@ -225,7 +243,7 @@ func (s *session) queueForSend(msg *Message) error {
return nil
}
-//send will validate, persist, queue the message. If the session is logged on, send all messages in the queue
+// send will validate, persist, queue the message. If the session is logged on, send all messages in the queue.
func (s *session) send(msg *Message) error {
return s.sendInReplyTo(msg, nil)
}
@@ -248,7 +266,7 @@ func (s *session) sendInReplyTo(msg *Message, inReplyTo *Message) error {
return nil
}
-//dropAndReset will drop the send queue and reset the message store
+// dropAndReset will drop the send queue and reset the message store.
func (s *session) dropAndReset() error {
s.sendMutex.Lock()
defer s.sendMutex.Unlock()
@@ -257,7 +275,7 @@ func (s *session) dropAndReset() error {
return s.store.Reset()
}
-//dropAndSend will validate and persist the message, then drops the send queue and sends the message.
+// dropAndSend will validate and persist the message, then drops the send queue and sends the message.
func (s *session) dropAndSend(msg *Message) error {
return s.dropAndSendInReplyTo(msg, nil)
}
@@ -322,9 +340,7 @@ func (s *session) prepMessageForSend(msg *Message, inReplyTo *Message) (msgBytes
func (s *session) persist(seqNum int, msgBytes []byte) error {
if !s.DisableMessagePersist {
- if err := s.store.SaveMessage(seqNum, msgBytes); err != nil {
- return err
- }
+ return s.store.SaveMessageAndIncrNextSenderMsgSeqNum(seqNum, msgBytes)
}
return s.store.IncrNextSenderMsgSeqNum()
@@ -342,7 +358,20 @@ func (s *session) dropQueued() {
s.toSend = s.toSend[:0]
}
+func (s *session) EnqueueBytesAndSend(msg []byte) {
+ s.sendMutex.Lock()
+ defer s.sendMutex.Unlock()
+
+ s.toSend = append(s.toSend, msg)
+ s.sendQueued()
+}
+
func (s *session) sendBytes(msg []byte) {
+ if s.messageOut == nil {
+ s.log.OnEventf("Failed to send: disconnected")
+ return
+ }
+
s.log.OnOutgoing(msg)
s.messageOut <- msg
s.stateTimer.Reset(s.HeartBtInt)
@@ -387,7 +416,7 @@ func (s *session) sendResendRequest(beginSeq, endSeq int) (nextState resendState
}
func (s *session) handleLogon(msg *Message) error {
- //Grab default app ver id from fixt.1.1 logon
+ // Grab default app ver id from fixt.1.1 logon.
if s.sessionID.BeginString == BeginStringFIXT11 {
var targetApplVerID FIXString
@@ -433,9 +462,11 @@ func (s *session) handleLogon(msg *Message) error {
}
if !s.InitiateLogon {
- var heartBtInt FIXInt
- if err := msg.Body.GetField(tagHeartBtInt, &heartBtInt); err == nil {
- s.HeartBtInt = time.Duration(heartBtInt) * time.Second
+ if !s.HeartBtIntOverride {
+ var heartBtInt FIXInt
+ if err := msg.Body.GetField(tagHeartBtInt, &heartBtInt); err == nil {
+ s.HeartBtInt = time.Duration(heartBtInt) * time.Second
+ }
}
s.log.OnEvent("Responding to logon request")
@@ -465,8 +496,7 @@ func (s *session) initiateLogoutInReplyTo(reason string, inReplyTo *Message) (er
return
}
s.log.OnEvent("Inititated logout request")
- time.AfterFunc(time.Duration(2)*time.Second, func() { s.sessionEvent <- internal.LogoutTimeout })
-
+ time.AfterFunc(s.LogoutTimeout, func() { s.sessionEvent <- internal.LogoutTimeout })
return
}
@@ -491,10 +521,14 @@ func (s *session) verifySelect(msg *Message, checkTooHigh bool, checkTooLow bool
return reject
}
- if reject := s.checkSendingTime(msg); reject != nil {
- return reject
+ switch s.stateMachine.State.(type) {
+ case resendState:
+ //Don't check staleness of a replay
+ default:
+ if reject := s.checkSendingTime(msg); reject != nil {
+ return reject
+ }
}
-
if checkTooLow {
if reject := s.checkTargetTooLow(msg); reject != nil {
return reject
@@ -507,8 +541,8 @@ func (s *session) verifySelect(msg *Message, checkTooHigh bool, checkTooLow bool
}
}
- if s.validator != nil {
- if reject := s.validator.Validate(msg); reject != nil {
+ if s.Validator != nil {
+ if reject := s.Validator.Validate(msg); reject != nil {
return reject
}
}
@@ -623,13 +657,16 @@ func (s *session) doReject(msg *Message, rej MessageRejectError) error {
if rej.IsBusinessReject() {
reply.Header.SetField(tagMsgType, FIXString("j"))
reply.Body.SetField(tagBusinessRejectReason, FIXInt(rej.RejectReason()))
+ if refID := rej.BusinessRejectRefID(); refID != "" {
+ reply.Body.SetField(tagBusinessRejectRefID, FIXString(refID))
+ }
} else {
reply.Header.SetField(tagMsgType, FIXString("3"))
switch {
default:
reply.Body.SetField(tagSessionRejectReason, FIXInt(rej.RejectReason()))
case rej.RejectReason() > rejectReasonInvalidMsgType && s.sessionID.BeginString == BeginStringFIX42:
- //fix42 knows up to invalid msg type
+ // Fix42 knows up to invalid msg type.
}
if refTagID := rej.RefTagID(); refTagID != nil {
@@ -666,14 +703,6 @@ type fixIn struct {
receiveTime time.Time
}
-func (s *session) returnToPool(msg *Message) {
- s.messagePool.Put(msg)
- if msg.rawMessage != nil {
- bufferPool.Put(msg.rawMessage)
- msg.rawMessage = nil
- }
-}
-
func (s *session) onDisconnect() {
s.log.OnEvent("Disconnected")
if s.ResetOnDisconnect {
@@ -703,6 +732,15 @@ func (s *session) onAdmin(msg interface{}) {
return
}
+ if !s.IsSessionTime() {
+ s.handleDisconnectState(s)
+ if msg.err != nil {
+ msg.err <- errors.New("Connection outside of session time")
+ close(msg.err)
+ }
+ return
+ }
+
if msg.err != nil {
close(msg.err)
}
@@ -726,12 +764,34 @@ func (s *session) onAdmin(msg interface{}) {
func (s *session) run() {
s.Start(s)
+ var stopChan = make(chan struct{})
+ s.stateTimer = internal.NewEventTimer(func() {
+ select {
+ // Deadlock in write to chan s.sessionEvent after s.Stopped()==true and end of loop session.go:766 because no reader of chan s.sessionEvent.
+ case s.sessionEvent <- internal.NeedHeartbeat:
+ case <-stopChan:
+ }
+ })
+ s.peerTimer = internal.NewEventTimer(func() {
+ select {
+ // Deadlock in write to chan s.sessionEvent after s.Stopped()==true and end of loop session.go:766 because no reader of chan s.sessionEvent.
+ case s.sessionEvent <- internal.PeerTimeout:
+ case <-stopChan:
+ }
+
+ })
+
+ // Without this sleep the ticker will be aligned at the millisecond which
+ // corresponds to the creation of the session. If the session creation
+ // happened at 07:00:00.678 and the session StartTime is 07:30:00, any new
+ // connection received between 07:30:00.000 and 07:30:00.677 will be
+ // rejected. Aligning the ticker with a round second fixes that.
+ time.Sleep(time.Until(time.Now().Truncate(time.Second).Add(time.Second)))
- s.stateTimer = internal.NewEventTimer(func() { s.sessionEvent <- internal.NeedHeartbeat })
- s.peerTimer = internal.NewEventTimer(func() { s.sessionEvent <- internal.PeerTimeout })
ticker := time.NewTicker(time.Second)
defer func() {
+ close(stopChan)
s.stateTimer.Stop()
s.peerTimer.Stop()
ticker.Stop()
diff --git a/session_factory.go b/session_factory.go
index 36e294837..3a6be68a5 100644
--- a/session_factory.go
+++ b/session_factory.go
@@ -1,11 +1,27 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
- "errors"
"net"
"strconv"
"time"
+ "github.com/pkg/errors"
+
"github.com/quickfixgo/quickfix/config"
"github.com/quickfixgo/quickfix/datadictionary"
"github.com/quickfixgo/quickfix/internal"
@@ -41,11 +57,11 @@ var applVerIDLookup = map[string]string{
}
type sessionFactory struct {
- //True if building sessions that initiate logon
+ // True if building sessions that initiate logon.
BuildInitiators bool
}
-//Creates Session, associates with internal session registry
+// Creates Session, associates with internal session registry.
func (f sessionFactory) createSession(
sessionID SessionID, storeFactory MessageStoreFactory, settings *SessionSettings,
logFactory LogFactory, application Application,
@@ -91,7 +107,7 @@ func (f sessionFactory) newSession(
s.DefaultApplVerID = applVerID
}
- //If the transport or app data dictionary setting is set, the other also needs to be set.
+ // If the transport or app data dictionary setting is set, the other also needs to be set.
if settings.HasSetting(config.TransportDataDictionary) || settings.HasSetting(config.AppDataDictionary) {
var transportDataDictionaryPath, appDataDictionaryPath string
if transportDataDictionaryPath, err = settings.Setting(config.TransportDataDictionary); err != nil {
@@ -103,14 +119,22 @@ func (f sessionFactory) newSession(
}
if s.transportDataDictionary, err = datadictionary.Parse(transportDataDictionaryPath); err != nil {
+ err = errors.Wrapf(
+ err, "problem parsing XML datadictionary path '%v' for setting '%v",
+ settings.settings[config.TransportDataDictionary], config.TransportDataDictionary,
+ )
return
}
if s.appDataDictionary, err = datadictionary.Parse(appDataDictionaryPath); err != nil {
+ err = errors.Wrapf(
+ err, "problem parsing XML datadictionary path '%v' for setting '%v",
+ settings.settings[config.AppDataDictionary], config.AppDataDictionary,
+ )
return
}
- s.validator = &fixtValidator{s.transportDataDictionary, s.appDataDictionary, validatorSettings}
+ s.Validator = NewValidator(validatorSettings, s.appDataDictionary, s.transportDataDictionary)
}
} else if settings.HasSetting(config.DataDictionary) {
var dataDictionaryPath string
@@ -119,10 +143,14 @@ func (f sessionFactory) newSession(
}
if s.appDataDictionary, err = datadictionary.Parse(dataDictionaryPath); err != nil {
+ err = errors.Wrapf(
+ err, "problem parsing XML datadictionary path '%v' for setting '%v",
+ settings.settings[config.DataDictionary], config.DataDictionary,
+ )
return
}
- s.validator = &fixValidator{s.appDataDictionary, validatorSettings}
+ s.Validator = NewValidator(validatorSettings, s.appDataDictionary, nil)
}
if settings.HasSetting(config.ResetOnLogon) {
@@ -198,10 +226,18 @@ func (f sessionFactory) newSession(
var start, end internal.TimeOfDay
if start, err = internal.ParseTimeOfDay(startTimeStr); err != nil {
+ err = errors.Wrapf(
+ err, "problem parsing time of day '%v' for setting '%v",
+ settings.settings[config.StartTime], config.StartTime,
+ )
return
}
if end, err = internal.ParseTimeOfDay(endTimeStr); err != nil {
+ err = errors.Wrapf(
+ err, "problem parsing time of day '%v' for setting '%v",
+ settings.settings[config.EndTime], config.EndTime,
+ )
return
}
@@ -214,6 +250,10 @@ func (f sessionFactory) newSession(
loc, err = time.LoadLocation(locStr)
if err != nil {
+ err = errors.Wrapf(
+ err, "problem parsing time zone '%v' for setting '%v",
+ settings.settings[config.TimeZone], config.TimeZone,
+ )
return
}
}
@@ -286,6 +326,8 @@ func (f sessionFactory) newSession(
if err = f.buildInitiatorSettings(s, settings); err != nil {
return
}
+ } else if err = f.buildAcceptorSettings(s, settings); err != nil {
+ return
}
if s.log, err = logFactory.CreateSessionLog(s.sessionID); err != nil {
@@ -303,19 +345,20 @@ func (f sessionFactory) newSession(
return
}
+func (f sessionFactory) buildAcceptorSettings(session *session, settings *SessionSettings) error {
+ if err := f.buildHeartBtIntSettings(session, settings, false); err != nil {
+ return err
+ }
+ return nil
+}
+
func (f sessionFactory) buildInitiatorSettings(session *session, settings *SessionSettings) error {
session.InitiateLogon = true
- heartBtInt, err := settings.IntSetting(config.HeartBtInt)
- if err != nil {
+ if err := f.buildHeartBtIntSettings(session, settings, true); err != nil {
return err
}
- if heartBtInt <= 0 {
- return errors.New("Heartbeat must be greater than zero")
- }
- session.HeartBtInt = time.Duration(heartBtInt) * time.Second
-
session.ReconnectInterval = 30 * time.Second
if settings.HasSetting(config.ReconnectInterval) {
@@ -331,6 +374,36 @@ func (f sessionFactory) buildInitiatorSettings(session *session, settings *Sessi
session.ReconnectInterval = time.Duration(interval) * time.Second
}
+ session.LogoutTimeout = 2 * time.Second
+ if settings.HasSetting(config.LogoutTimeout) {
+
+ timeout, err := settings.IntSetting(config.LogoutTimeout)
+ if err != nil {
+ return err
+ }
+
+ if timeout <= 0 {
+ return errors.New("LogoutTimeout must be greater than zero")
+ }
+
+ session.LogoutTimeout = time.Duration(timeout) * time.Second
+ }
+
+ session.LogonTimeout = 10 * time.Second
+ if settings.HasSetting(config.LogonTimeout) {
+
+ timeout, err := settings.IntSetting(config.LogonTimeout)
+ if err != nil {
+ return err
+ }
+
+ if timeout <= 0 {
+ return errors.New("LogonTimeout must be greater than zero")
+ }
+
+ session.LogonTimeout = time.Duration(timeout) * time.Second
+ }
+
return f.configureSocketConnectAddress(session, settings)
}
@@ -364,3 +437,23 @@ func (f sessionFactory) configureSocketConnectAddress(session *session, settings
i++
}
}
+
+func (f sessionFactory) buildHeartBtIntSettings(session *session, settings *SessionSettings, mustProvide bool) (err error) {
+ if settings.HasSetting(config.HeartBtIntOverride) {
+ if session.HeartBtIntOverride, err = settings.BoolSetting(config.HeartBtIntOverride); err != nil {
+ return
+ }
+ }
+
+ if session.HeartBtIntOverride || mustProvide {
+ var heartBtInt int
+ if heartBtInt, err = settings.IntSetting(config.HeartBtInt); err != nil {
+ return
+ } else if heartBtInt <= 0 {
+ err = errors.New("Heartbeat must be greater than zero")
+ return
+ }
+ session.HeartBtInt = time.Duration(heartBtInt) * time.Second
+ }
+ return
+}
diff --git a/session_factory_test.go b/session_factory_test.go
index 7db6cfd87..db99302f1 100644
--- a/session_factory_test.go
+++ b/session_factory_test.go
@@ -1,12 +1,28 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"testing"
"time"
+ "github.com/stretchr/testify/suite"
+
"github.com/quickfixgo/quickfix/config"
"github.com/quickfixgo/quickfix/internal"
- "github.com/stretchr/testify/suite"
)
type SessionFactorySuite struct {
@@ -51,6 +67,7 @@ func (s *SessionFactorySuite) TestDefaults() {
s.Equal(Millis, session.timestampPrecision)
s.Equal(120*time.Second, session.MaxLatency)
s.False(session.DisableMessagePersist)
+ s.False(session.HeartBtIntOverride)
}
func (s *SessionFactorySuite) TestResetOnLogon() {
@@ -129,7 +146,7 @@ func (s *SessionFactorySuite) TestResendRequestChunkSize() {
s.Equal(2500, session.ResendRequestChunkSize)
s.SessionSettings.Set(config.ResendRequestChunkSize, "notanint")
- session, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.NotNil(err)
}
@@ -353,12 +370,52 @@ func (s *SessionFactorySuite) TestNewSessionBuildInitiators() {
s.True(session.InitiateLogon)
s.Equal(34*time.Second, session.HeartBtInt)
s.Equal(30*time.Second, session.ReconnectInterval)
+ s.Equal(10*time.Second, session.LogonTimeout)
+ s.Equal(2*time.Second, session.LogoutTimeout)
s.Equal("127.0.0.1:5000", session.SocketConnectAddress[0])
}
+func (s *SessionFactorySuite) TestNewSessionBuildAcceptors() {
+ s.sessionFactory.BuildInitiators = false
+ s.SessionSettings.Set(config.HeartBtInt, "34")
+
+ session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.Nil(err)
+ s.False(session.InitiateLogon)
+ s.Zero(session.HeartBtInt)
+ s.False(session.HeartBtIntOverride)
+
+ s.SessionSettings.Set(config.HeartBtIntOverride, "Y")
+ session, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.Nil(err)
+ s.False(session.InitiateLogon)
+ s.Equal(34*time.Second, session.HeartBtInt)
+ s.True(session.HeartBtIntOverride)
+}
+
func (s *SessionFactorySuite) TestNewSessionBuildInitiatorsValidHeartBtInt() {
s.sessionFactory.BuildInitiators = true
+ _, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "HeartBtInt should be required for acceptors with override defined")
+
+ s.SessionSettings.Set(config.HeartBtInt, "not a number")
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "HeartBtInt must be a number")
+
+ s.SessionSettings.Set(config.HeartBtInt, "0")
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "HeartBtInt must be greater than zero")
+
+ s.SessionSettings.Set(config.HeartBtInt, "-20")
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "HeartBtInt must be greater than zero")
+}
+
+func (s *SessionFactorySuite) TestNewSessionBuildAcceptorsValidHeartBtInt() {
+ s.sessionFactory.BuildInitiators = false
+
+ s.SessionSettings.Set(config.HeartBtIntOverride, "Y")
_, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.NotNil(err, "HeartBtInt should be required for initiators")
@@ -399,6 +456,54 @@ func (s *SessionFactorySuite) TestNewSessionBuildInitiatorsValidReconnectInterva
s.NotNil(err, "ReconnectInterval must be greater than zero")
}
+func (s *SessionFactorySuite) TestNewSessionBuildInitiatorsValidLogoutTimeout() {
+ s.sessionFactory.BuildInitiators = true
+ s.SessionSettings.Set(config.HeartBtInt, "34")
+ s.SessionSettings.Set(config.SocketConnectHost, "127.0.0.1")
+ s.SessionSettings.Set(config.SocketConnectPort, "3000")
+
+ s.SessionSettings.Set(config.LogoutTimeout, "45")
+ session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.Nil(err)
+ s.Equal(45*time.Second, session.LogoutTimeout)
+
+ s.SessionSettings.Set(config.LogoutTimeout, "not a number")
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "LogoutTimeout must be a number")
+
+ s.SessionSettings.Set(config.LogoutTimeout, "0")
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "LogoutTimeout must be greater than zero")
+
+ s.SessionSettings.Set(config.LogoutTimeout, "-20")
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "LogoutTimeout must be greater than zero")
+}
+
+func (s *SessionFactorySuite) TestNewSessionBuildInitiatorsValidLogonTimeout() {
+ s.sessionFactory.BuildInitiators = true
+ s.SessionSettings.Set(config.HeartBtInt, "34")
+ s.SessionSettings.Set(config.SocketConnectHost, "127.0.0.1")
+ s.SessionSettings.Set(config.SocketConnectPort, "3000")
+
+ s.SessionSettings.Set(config.LogonTimeout, "45")
+ session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.Nil(err)
+ s.Equal(45*time.Second, session.LogonTimeout)
+
+ s.SessionSettings.Set(config.LogonTimeout, "not a number")
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "LogonTimeout must be a number")
+
+ s.SessionSettings.Set(config.LogonTimeout, "0")
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "LogonTimeout must be greater than zero")
+
+ s.SessionSettings.Set(config.LogonTimeout, "-20")
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ s.NotNil(err, "LogonTimeout must be greater than zero")
+}
+
func (s *SessionFactorySuite) TestConfigureSocketConnectAddress() {
sess := new(session)
err := s.configureSocketConnectAddress(sess, s.SessionSettings)
@@ -468,7 +573,7 @@ func (s *SessionFactorySuite) TestConfigureSocketConnectAddressMulti() {
func (s *SessionFactorySuite) TestNewSessionTimestampPrecision() {
s.SessionSettings.Set(config.TimeStampPrecision, "blah")
- session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ _, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.NotNil(err)
var tests = []struct {
@@ -483,7 +588,7 @@ func (s *SessionFactorySuite) TestNewSessionTimestampPrecision() {
for _, test := range tests {
s.SessionSettings.Set(config.TimeStampPrecision, test.config)
- session, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.Nil(err)
s.Equal(session.timestampPrecision, test.precision)
@@ -492,19 +597,19 @@ func (s *SessionFactorySuite) TestNewSessionTimestampPrecision() {
func (s *SessionFactorySuite) TestNewSessionMaxLatency() {
s.SessionSettings.Set(config.MaxLatency, "not a number")
- session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ _, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.NotNil(err, "MaxLatency must be a number")
s.SessionSettings.Set(config.MaxLatency, "-20")
- session, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.NotNil(err, "MaxLatency must be positive")
s.SessionSettings.Set(config.MaxLatency, "0")
- session, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ _, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.NotNil(err, "MaxLatency must be positive")
s.SessionSettings.Set(config.MaxLatency, "20")
- session, err = s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
+ session, err := s.newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App)
s.Nil(err)
s.Equal(session.MaxLatency, 20*time.Second)
}
diff --git a/session_id.go b/session_id.go
index a261d3789..c4dfb3513 100644
--- a/session_id.go
+++ b/session_id.go
@@ -1,13 +1,28 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import "bytes"
-// SessionID is a unique identifer of a Session
+// SessionID is a unique identifier of a Session.
type SessionID struct {
BeginString, TargetCompID, TargetSubID, TargetLocationID, SenderCompID, SenderSubID, SenderLocationID, Qualifier string
}
-//IsFIXT returns true if the SessionID has a FIXT BeginString
+// IsFIXT returns true if the SessionID has a FIXT BeginString.
func (s SessionID) IsFIXT() bool {
return s.BeginString == BeginStringFIXT11
}
diff --git a/session_id_test.go b/session_id_test.go
index 6c5eae3b2..3ea9a22e8 100644
--- a/session_id_test.go
+++ b/session_id_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/session_rejects.go b/session_rejects.go
index ba9e6ec23..4ea16b61e 100644
--- a/session_rejects.go
+++ b/session_rejects.go
@@ -1,15 +1,30 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"fmt"
)
-//IncorrectBeginString is a message reject specific to incorrect begin strings.
+// IncorrectBeginString is a message reject specific to incorrect begin strings.
type incorrectBeginString struct{ messageRejectError }
func (e incorrectBeginString) Error() string { return "Incorrect BeginString" }
-//targetTooHigh is a MessageReject where the sequence number is larger than expected.
+// targetTooHigh is a MessageReject where the sequence number is larger than expected.
type targetTooHigh struct {
messageRejectError
ReceivedTarget int
@@ -20,7 +35,7 @@ func (e targetTooHigh) Error() string {
return fmt.Sprintf("MsgSeqNum too high, expecting %d but received %d", e.ExpectedTarget, e.ReceivedTarget)
}
-//targetTooLow is a MessageReject where the sequence number is less than expected.
+// targetTooLow is a MessageReject where the sequence number is less than expected.
type targetTooLow struct {
messageRejectError
ReceivedTarget int
diff --git a/session_settings.go b/session_settings.go
index 130bf9667..323ac6234 100644
--- a/session_settings.go
+++ b/session_settings.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -6,12 +21,12 @@ import (
"time"
)
-//SessionSettings maps session settings to values with typed accessors.
+// SessionSettings maps session settings to values with typed accessors.
type SessionSettings struct {
settings map[string]string
}
-//ConditionallyRequiredSetting indicates a missing setting
+// ConditionallyRequiredSetting indicates a missing setting.
type ConditionallyRequiredSetting struct {
Setting string
}
@@ -20,7 +35,7 @@ func (e ConditionallyRequiredSetting) Error() string {
return fmt.Sprintf("Conditionally Required Setting: %v", e.Setting)
}
-//IncorrectFormatForSetting indicates a setting that is incorrectly formatted
+// IncorrectFormatForSetting indicates a setting that is incorrectly formatted.
type IncorrectFormatForSetting struct {
Setting, Value string
Err error
@@ -30,12 +45,12 @@ func (e IncorrectFormatForSetting) Error() string {
return fmt.Sprintf("%q is invalid for %s", e.Value, e.Setting)
}
-//Init initializes or resets SessionSettings
+// Init initializes or resets SessionSettings.
func (s *SessionSettings) Init() {
s.settings = make(map[string]string)
}
-//NewSessionSettings returns a newly initialized SessionSettings instance
+// NewSessionSettings returns a newly initialized SessionSettings instance.
func NewSessionSettings() *SessionSettings {
s := &SessionSettings{}
s.Init()
@@ -43,9 +58,9 @@ func NewSessionSettings() *SessionSettings {
return s
}
-//Set assigns a value to a setting on SessionSettings.
+// Set assigns a value to a setting on SessionSettings.
func (s *SessionSettings) Set(setting string, val string) {
- //lazy init
+ // Lazy init.
if s.settings == nil {
s.Init()
}
@@ -53,13 +68,13 @@ func (s *SessionSettings) Set(setting string, val string) {
s.settings[setting] = val
}
-//HasSetting returns true if a setting is set, false if not
+// HasSetting returns true if a setting is set, false if not.
func (s *SessionSettings) HasSetting(setting string) bool {
_, ok := s.settings[setting]
return ok
}
-//Setting is a settings string accessor. Returns an error if the setting is missing.
+// Setting is a settings string accessor. Returns an error if the setting is missing.
func (s *SessionSettings) Setting(setting string) (string, error) {
val, ok := s.settings[setting]
if !ok {
@@ -69,7 +84,7 @@ func (s *SessionSettings) Setting(setting string) (string, error) {
return val, nil
}
-//IntSetting returns the requested setting parsed as an int. Returns an errror if the setting is not set or cannot be parsed as an int.
+// IntSetting returns the requested setting parsed as an int. Returns an errror if the setting is not set or cannot be parsed as an int.
func (s *SessionSettings) IntSetting(setting string) (val int, err error) {
stringVal, err := s.Setting(setting)
@@ -84,8 +99,8 @@ func (s *SessionSettings) IntSetting(setting string) (val int, err error) {
return
}
-//DurationSetting returns the requested setting parsed as a time.Duration.
-//Returns an error if the setting is not set or cannot be parsed as a time.Duration.
+// DurationSetting returns the requested setting parsed as a time.Duration.
+// Returns an error if the setting is not set or cannot be parsed as a time.Duration.
func (s *SessionSettings) DurationSetting(setting string) (val time.Duration, err error) {
stringVal, err := s.Setting(setting)
@@ -100,7 +115,7 @@ func (s *SessionSettings) DurationSetting(setting string) (val time.Duration, er
return
}
-//BoolSetting returns the requested setting parsed as a boolean. Returns an errror if the setting is not set or cannot be parsed as a bool.
+// BoolSetting returns the requested setting parsed as a boolean. Returns an error if the setting is not set or cannot be parsed as a bool.
func (s SessionSettings) BoolSetting(setting string) (bool, error) {
stringVal, err := s.Setting(setting)
diff --git a/session_settings_test.go b/session_settings_test.go
index a28f60529..7fa9a94ad 100644
--- a/session_settings_test.go
+++ b/session_settings_test.go
@@ -1,8 +1,24 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
- "github.com/quickfixgo/quickfix/config"
"testing"
+
+ "github.com/quickfixgo/quickfix/config"
)
func TestSessionSettings_StringSettings(t *testing.T) {
diff --git a/session_state.go b/session_state.go
index 1f076f221..230ac8613 100644
--- a/session_state.go
+++ b/session_state.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -22,28 +37,27 @@ func (sm *stateMachine) Start(s *session) {
}
func (sm *stateMachine) Connect(session *session) {
- if !sm.IsSessionTime() {
- session.log.OnEvent("Connection outside of session time")
- sm.handleDisconnectState(session)
+ // No special logon logic needed for FIX Acceptors.
+ if !session.InitiateLogon {
+ sm.setState(session, logonState{})
return
}
- if session.InitiateLogon {
- if session.RefreshOnLogon {
- if err := session.store.Refresh(); err != nil {
- session.logError(err)
- return
- }
- }
-
- session.log.OnEvent("Sending logon request")
- if err := session.sendLogon(); err != nil {
+ if session.RefreshOnLogon {
+ if err := session.store.Refresh(); err != nil {
session.logError(err)
return
}
}
+ session.log.OnEvent("Sending logon request")
+ if err := session.sendLogon(); err != nil {
+ session.logError(err)
+ return
+ }
sm.setState(session, logonState{})
+ // Fire logon timeout event after the pre-configured delay period.
+ time.AfterFunc(session.LogonTimeout, func() { session.sessionEvent <- internal.LogonTimeout })
}
func (sm *stateMachine) Stop(session *session) {
@@ -69,7 +83,7 @@ func (sm *stateMachine) Incoming(session *session, m fixIn) {
session.log.OnIncoming(m.bytes.Bytes())
- msg := session.messagePool.Get()
+ msg := NewMessage()
if err := ParseMessageWithDataDictionary(msg, m.bytes, session.transportDataDictionary, session.appDataDictionary); err != nil {
session.log.OnEventf("Msg Parse Error: %v, %q", err.Error(), m.bytes)
} else {
@@ -77,9 +91,6 @@ func (sm *stateMachine) Incoming(session *session, m fixIn) {
sm.fixMsgIn(session, msg)
}
- if !msg.keepMessage {
- session.returnToPool(msg)
- }
session.peerTimer.Reset(time.Duration(float64(1.2) * float64(session.HeartBtInt)))
}
@@ -194,32 +205,32 @@ func handleStateError(s *session, err error) sessionState {
return latentState{}
}
-//sessionState is the current state of the session state machine. The session state determines how the session responds to
-//incoming messages, timeouts, and requests to send application messages.
+// sessionState is the current state of the session state machine. The session state determines how the session responds to
+// incoming messages, timeouts, and requests to send application messages.
type sessionState interface {
- //FixMsgIn is called by the session on incoming messages from the counter party. The return type is the next session state
- //following message processing
+ // FixMsgIn is called by the session on incoming messages from the counter party.
+ // The return type is the next session state following message processing.
FixMsgIn(*session, *Message) (nextState sessionState)
- //Timeout is called by the session on a timeout event.
+ // Timeout is called by the session on a timeout event.
Timeout(*session, internal.Event) (nextState sessionState)
- //IsLoggedOn returns true if state is logged on an in session, false otherwise
+ // IsLoggedOn returns true if state is logged on an in session, false otherwise.
IsLoggedOn() bool
- //IsConnected returns true if the state is connected
+ // IsConnected returns true if the state is connected.
IsConnected() bool
- //IsSessionTime returns true if the state is in session time
+ // IsSessionTime returns true if the state is in session time.
IsSessionTime() bool
- //ShutdownNow terminates the session state immediately
+ // ShutdownNow terminates the session state immediately.
ShutdownNow(*session)
- //Stop triggers a clean stop
+ // Stop triggers a clean stop.
Stop(*session) (nextState sessionState)
- //debugging convenience
+ // Stringer debugging convenience.
fmt.Stringer
}
diff --git a/session_test.go b/session_test.go
index 33c7cf417..457b5edd3 100644
--- a/session_test.go
+++ b/session_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -70,7 +85,7 @@ func (s *SessionSuite) TestInsertSendingTime() {
Precision TimestampPrecision
ExpectedPrecision TimestampPrecision
}{
- {BeginStringFIX40, Millis, Seconds}, //config is ignored for fix < 4.2
+ {BeginStringFIX40, Millis, Seconds}, // Config is ignored for fix < 4.2.
{BeginStringFIX41, Millis, Seconds},
{BeginStringFIX42, Millis, Millis},
@@ -170,7 +185,7 @@ func (s *SessionSuite) TestCheckTargetTooHigh() {
s.Require().NotNil(err, "sequence number too high should return an error")
s.IsType(targetTooHigh{}, err)
- //spot on
+ // Spot on.
msg.Header.SetField(tagMsgSeqNum, FIXInt(45))
s.Nil(s.session.checkTargetTooHigh(msg))
}
@@ -217,13 +232,13 @@ func (s *SessionSuite) TestCheckTargetTooLow() {
s.Require().NotNil(err, "sequence number is required")
s.Equal(rejectReasonRequiredTagMissing, err.RejectReason())
- //too low
+ // Too low.
msg.Header.SetField(tagMsgSeqNum, FIXInt(43))
err = s.session.checkTargetTooLow(msg)
s.NotNil(err, "sequence number too low should return error")
s.IsType(targetTooLow{}, err)
- //spot on
+ // Spot on.
msg.Header.SetField(tagMsgSeqNum, FIXInt(45))
s.Nil(s.session.checkTargetTooLow(msg))
}
@@ -238,34 +253,34 @@ func (s *SessionSuite) TestShouldSendReset() {
NextTargetMsgSeqNum int
Expected bool
}{
- {BeginStringFIX40, true, false, false, 1, 1, false}, //ResetSeqNumFlag not available < fix41
+ {BeginStringFIX40, true, false, false, 1, 1, false}, // ResetSeqNumFlag not available < fix41.
- {BeginStringFIX41, true, false, false, 1, 1, true}, //session must be configured to reset on logon
+ {BeginStringFIX41, true, false, false, 1, 1, true}, // Session must be configured to reset on logon.
{BeginStringFIX42, true, false, false, 1, 1, true},
{BeginStringFIX43, true, false, false, 1, 1, true},
{BeginStringFIX44, true, false, false, 1, 1, true},
{BeginStringFIXT11, true, false, false, 1, 1, true},
- {BeginStringFIX41, false, true, false, 1, 1, true}, //or disconnect
+ {BeginStringFIX41, false, true, false, 1, 1, true}, // Or disconnect.
{BeginStringFIX42, false, true, false, 1, 1, true},
{BeginStringFIX43, false, true, false, 1, 1, true},
{BeginStringFIX44, false, true, false, 1, 1, true},
{BeginStringFIXT11, false, true, false, 1, 1, true},
- {BeginStringFIX41, false, false, true, 1, 1, true}, //or logout
+ {BeginStringFIX41, false, false, true, 1, 1, true}, // Or logout.
{BeginStringFIX42, false, false, true, 1, 1, true},
{BeginStringFIX43, false, false, true, 1, 1, true},
{BeginStringFIX44, false, false, true, 1, 1, true},
{BeginStringFIXT11, false, false, true, 1, 1, true},
- {BeginStringFIX41, true, true, false, 1, 1, true}, //or combo
+ {BeginStringFIX41, true, true, false, 1, 1, true}, // Or combo.
{BeginStringFIX42, false, true, true, 1, 1, true},
{BeginStringFIX43, true, false, true, 1, 1, true},
{BeginStringFIX44, true, true, true, 1, 1, true},
- {BeginStringFIX41, false, false, false, 1, 1, false}, //or will not be set
+ {BeginStringFIX41, false, false, false, 1, 1, false}, // Or will not be set.
- {BeginStringFIX41, true, false, false, 1, 10, false}, //session seq numbers should be reset at the time of check
+ {BeginStringFIX41, true, false, false, 1, 10, false}, // Session seq numbers should be reset at the time of check.
{BeginStringFIX42, true, false, false, 2, 1, false},
{BeginStringFIX43, true, false, false, 14, 100, false},
}
@@ -276,8 +291,8 @@ func (s *SessionSuite) TestShouldSendReset() {
s.session.ResetOnDisconnect = test.ResetOnDisconnect
s.session.ResetOnLogout = test.ResetOnLogout
- s.MockStore.SetNextSenderMsgSeqNum(test.NextSenderMsgSeqNum)
- s.MockStore.SetNextTargetMsgSeqNum(test.NextTargetMsgSeqNum)
+ s.Require().Nil(s.MockStore.SetNextSenderMsgSeqNum(test.NextSenderMsgSeqNum))
+ s.Require().Nil(s.MockStore.SetNextTargetMsgSeqNum(test.NextTargetMsgSeqNum))
s.Equal(s.shouldSendReset(), test.Expected)
}
@@ -930,7 +945,7 @@ func (suite *SessionSendTestSuite) TestDropAndSendDropsQueue() {
suite.MessageType(string(msgTypeLogon), msg)
suite.FieldEquals(tagMsgSeqNum, 3, msg.Header)
- //only one message sent
+ // Only one message sent.
suite.LastToAdminMessageSent()
suite.NoMessageSent()
}
@@ -944,7 +959,7 @@ func (suite *SessionSendTestSuite) TestDropAndSendDropsQueueWithReset() {
suite.NoMessageSent()
suite.MockApp.On("ToAdmin")
- suite.MockStore.Reset()
+ suite.Require().Nil(suite.MockStore.Reset())
require.Nil(suite.T(), suite.dropAndSend(suite.Logon()))
suite.MockApp.AssertExpectations(suite.T())
msg := suite.MockApp.lastToAdmin
@@ -952,7 +967,7 @@ func (suite *SessionSendTestSuite) TestDropAndSendDropsQueueWithReset() {
suite.MessageType(string(msgTypeLogon), msg)
suite.FieldEquals(tagMsgSeqNum, 1, msg.Header)
- //only one message sent
+ // Only one message sent.
suite.LastToAdminMessageSent()
suite.NoMessageSent()
}
diff --git a/settings.go b/settings.go
index 870cf3d71..f457f9e2f 100644
--- a/settings.go
+++ b/settings.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -10,13 +25,13 @@ import (
"github.com/quickfixgo/quickfix/config"
)
-//The Settings type represents a collection of global and session settings.
+// The Settings type represents a collection of global and session settings.
type Settings struct {
globalSettings *SessionSettings
sessionSettings map[SessionID]*SessionSettings
}
-//Init initializes or resets a Settings instance
+// Init initializes or resets a Settings instance.
func (s *Settings) Init() {
s.globalSettings = NewSessionSettings()
s.sessionSettings = make(map[SessionID]*SessionSettings)
@@ -28,7 +43,7 @@ func (s *Settings) lazyInit() {
}
}
-//NewSettings creates a Settings instance
+// NewSettings creates a Settings instance.
func NewSettings() *Settings {
s := &Settings{}
s.Init()
@@ -75,8 +90,8 @@ func sessionIDFromSessionSettings(globalSettings *SessionSettings, sessionSettin
return sessionID
}
-//ParseSettings creates and initializes a Settings instance with config parsed from a Reader.
-//Returns error if the config is has parse errors
+// ParseSettings creates and initializes a Settings instance with config parsed from a Reader.
+// Returns error if the config is has parse errors.
func ParseSettings(reader io.Reader) (*Settings, error) {
s := NewSettings()
@@ -130,13 +145,13 @@ func ParseSettings(reader io.Reader) (*Settings, error) {
return s, err
}
-//GlobalSettings are default setting inherited by all session settings.
+// GlobalSettings are default setting inherited by all session settings.
func (s *Settings) GlobalSettings() *SessionSettings {
s.lazyInit()
return s.globalSettings
}
-//SessionSettings return all session settings overlaying globalsettings.
+// SessionSettings return all session settings overlaying globalsettings.
func (s *Settings) SessionSettings() map[SessionID]*SessionSettings {
allSessionSettings := make(map[SessionID]*SessionSettings)
@@ -149,7 +164,7 @@ func (s *Settings) SessionSettings() map[SessionID]*SessionSettings {
return allSessionSettings
}
-//AddSession adds Session Settings to Settings instance. Returns an error if session settings with duplicate sessionID has already been added
+// AddSession adds Session Settings to Settings instance. Returns an error if session settings with duplicate sessionID has already been added.
func (s *Settings) AddSession(sessionSettings *SessionSettings) (SessionID, error) {
s.lazyInit()
diff --git a/settings_test.go b/settings_test.go
index b9c219760..9ac8d9dcb 100644
--- a/settings_test.go
+++ b/settings_test.go
@@ -1,13 +1,29 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"strings"
"testing"
- "github.com/quickfixgo/quickfix/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/config"
)
func TestSettings_New(t *testing.T) {
diff --git a/sqlstore.go b/sqlstore.go
index b16b0a74d..5e492be2c 100644
--- a/sqlstore.go
+++ b/sqlstore.go
@@ -1,10 +1,28 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"database/sql"
"fmt"
+ "regexp"
"time"
+ "github.com/pkg/errors"
+
"github.com/quickfixgo/quickfix/config"
)
@@ -19,19 +37,48 @@ type sqlStore struct {
sqlDataSourceName string
sqlConnMaxLifetime time.Duration
db *sql.DB
+ placeholder placeholderFunc
+}
+
+type placeholderFunc func(int) string
+
+var rePlaceholder = regexp.MustCompile(`\?`)
+
+func sqlString(raw string, placeholder placeholderFunc) string {
+ if placeholder == nil {
+ return raw
+ }
+ idx := 0
+ return rePlaceholder.ReplaceAllStringFunc(raw, func(s string) string {
+ new := placeholder(idx)
+ idx++
+ return new
+ })
}
-// NewSQLStoreFactory returns a sql-based implementation of MessageStoreFactory
+func postgresPlaceholder(i int) string {
+ return fmt.Sprintf("$%d", i+1)
+}
+
+// NewSQLStoreFactory returns a sql-based implementation of MessageStoreFactory.
func NewSQLStoreFactory(settings *Settings) MessageStoreFactory {
return sqlStoreFactory{settings: settings}
}
-// Create creates a new SQLStore implementation of the MessageStore interface
+// Create creates a new SQLStore implementation of the MessageStore interface.
func (f sqlStoreFactory) Create(sessionID SessionID) (msgStore MessageStore, err error) {
+ globalSettings := f.settings.GlobalSettings()
+ dynamicSessions, _ := globalSettings.BoolSetting(config.DynamicSessions)
+
sessionSettings, ok := f.settings.SessionSettings()[sessionID]
if !ok {
- return nil, fmt.Errorf("unknown session: %v", sessionID)
+ if dynamicSessions {
+ sessionSettings = globalSettings
+ } else {
+ return nil, fmt.Errorf("unknown session: %v", sessionID)
+ }
}
+
sqlDriver, err := sessionSettings.Setting(config.SQLStoreDriver)
if err != nil {
return nil, err
@@ -58,7 +105,14 @@ func newSQLStore(sessionID SessionID, driver string, dataSourceName string, conn
sqlDataSourceName: dataSourceName,
sqlConnMaxLifetime: connMaxLifetime,
}
- store.cache.Reset()
+ if err = store.cache.Reset(); err != nil {
+ err = errors.Wrap(err, "cache reset")
+ return
+ }
+
+ if store.sqlDriver == "postgres" {
+ store.placeholder = postgresPlaceholder
+ }
if store.db, err = sql.Open(store.sqlDriver, store.sqlDataSourceName); err != nil {
return nil, err
@@ -75,13 +129,13 @@ func newSQLStore(sessionID SessionID, driver string, dataSourceName string, conn
return store, nil
}
-// Reset deletes the store records and sets the seqnums back to 1
+// Reset deletes the store records and sets the seqnums back to 1.
func (store *sqlStore) Reset() error {
s := store.sessionID
- _, err := store.db.Exec(`DELETE FROM messages
+ _, err := store.db.Exec(sqlString(`DELETE FROM messages
WHERE beginstring=? AND session_qualifier=?
AND sendercompid=? AND sendersubid=? AND senderlocid=?
- AND targetcompid=? AND targetsubid=? AND targetlocid=?`,
+ AND targetcompid=? AND targetsubid=? AND targetlocid=?`, store.placeholder),
s.BeginString, s.Qualifier,
s.SenderCompID, s.SenderSubID, s.SenderLocationID,
s.TargetCompID, s.TargetSubID, s.TargetLocationID)
@@ -93,11 +147,11 @@ func (store *sqlStore) Reset() error {
return err
}
- _, err = store.db.Exec(`UPDATE sessions
+ _, err = store.db.Exec(sqlString(`UPDATE sessions
SET creation_time=?, incoming_seqnum=?, outgoing_seqnum=?
WHERE beginstring=? AND session_qualifier=?
AND sendercompid=? AND sendersubid=? AND senderlocid=?
- AND targetcompid=? AND targetsubid=? AND targetlocid=?`,
+ AND targetcompid=? AND targetsubid=? AND targetlocid=?`, store.placeholder),
store.cache.CreationTime(), store.cache.NextTargetMsgSeqNum(), store.cache.NextSenderMsgSeqNum(),
s.BeginString, s.Qualifier,
s.SenderCompID, s.SenderSubID, s.SenderLocationID,
@@ -106,7 +160,7 @@ func (store *sqlStore) Reset() error {
return err
}
-// Refresh reloads the store from the database
+// Refresh reloads the store from the database.
func (store *sqlStore) Refresh() error {
if err := store.cache.Reset(); err != nil {
return err
@@ -114,26 +168,30 @@ func (store *sqlStore) Refresh() error {
return store.populateCache()
}
-func (store *sqlStore) populateCache() (err error) {
+func (store *sqlStore) populateCache() error {
s := store.sessionID
var creationTime time.Time
var incomingSeqNum, outgoingSeqNum int
- row := store.db.QueryRow(`SELECT creation_time, incoming_seqnum, outgoing_seqnum
+ row := store.db.QueryRow(sqlString(`SELECT creation_time, incoming_seqnum, outgoing_seqnum
FROM sessions
WHERE beginstring=? AND session_qualifier=?
AND sendercompid=? AND sendersubid=? AND senderlocid=?
- AND targetcompid=? AND targetsubid=? AND targetlocid=?`,
+ AND targetcompid=? AND targetsubid=? AND targetlocid=?`, store.placeholder),
s.BeginString, s.Qualifier,
s.SenderCompID, s.SenderSubID, s.SenderLocationID,
s.TargetCompID, s.TargetSubID, s.TargetLocationID)
- err = row.Scan(&creationTime, &incomingSeqNum, &outgoingSeqNum)
+ err := row.Scan(&creationTime, &incomingSeqNum, &outgoingSeqNum)
// session record found, load it
if err == nil {
store.cache.creationTime = creationTime
- store.cache.SetNextTargetMsgSeqNum(incomingSeqNum)
- store.cache.SetNextSenderMsgSeqNum(outgoingSeqNum)
+ if err = store.cache.SetNextTargetMsgSeqNum(incomingSeqNum); err != nil {
+ return errors.Wrap(err, "cache set next target")
+ }
+ if err = store.cache.SetNextSenderMsgSeqNum(outgoingSeqNum); err != nil {
+ return errors.Wrap(err, "cache set next sender")
+ }
return nil
}
@@ -143,12 +201,12 @@ func (store *sqlStore) populateCache() (err error) {
}
// session record not found, create it
- _, err = store.db.Exec(`INSERT INTO sessions (
+ _, err = store.db.Exec(sqlString(`INSERT INTO sessions (
creation_time, incoming_seqnum, outgoing_seqnum,
beginstring, session_qualifier,
sendercompid, sendersubid, senderlocid,
targetcompid, targetsubid, targetlocid)
- VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, store.placeholder),
store.cache.creationTime,
store.cache.NextTargetMsgSeqNum(),
store.cache.NextSenderMsgSeqNum(),
@@ -159,23 +217,23 @@ func (store *sqlStore) populateCache() (err error) {
return err
}
-// NextSenderMsgSeqNum returns the next MsgSeqNum that will be sent
+// NextSenderMsgSeqNum returns the next MsgSeqNum that will be sent.
func (store *sqlStore) NextSenderMsgSeqNum() int {
return store.cache.NextSenderMsgSeqNum()
}
-// NextTargetMsgSeqNum returns the next MsgSeqNum that should be received
+// NextTargetMsgSeqNum returns the next MsgSeqNum that should be received.
func (store *sqlStore) NextTargetMsgSeqNum() int {
return store.cache.NextTargetMsgSeqNum()
}
-// SetNextSenderMsgSeqNum sets the next MsgSeqNum that will be sent
+// SetNextSenderMsgSeqNum sets the next MsgSeqNum that will be sent.
func (store *sqlStore) SetNextSenderMsgSeqNum(next int) error {
s := store.sessionID
- _, err := store.db.Exec(`UPDATE sessions SET outgoing_seqnum = ?
+ _, err := store.db.Exec(sqlString(`UPDATE sessions SET outgoing_seqnum = ?
WHERE beginstring=? AND session_qualifier=?
AND sendercompid=? AND sendersubid=? AND senderlocid=?
- AND targetcompid=? AND targetsubid=? AND targetlocid=?`,
+ AND targetcompid=? AND targetsubid=? AND targetlocid=?`, store.placeholder),
next, s.BeginString, s.Qualifier,
s.SenderCompID, s.SenderSubID, s.SenderLocationID,
s.TargetCompID, s.TargetSubID, s.TargetLocationID)
@@ -185,13 +243,13 @@ func (store *sqlStore) SetNextSenderMsgSeqNum(next int) error {
return store.cache.SetNextSenderMsgSeqNum(next)
}
-// SetNextTargetMsgSeqNum sets the next MsgSeqNum that should be received
+// SetNextTargetMsgSeqNum sets the next MsgSeqNum that should be received.
func (store *sqlStore) SetNextTargetMsgSeqNum(next int) error {
s := store.sessionID
- _, err := store.db.Exec(`UPDATE sessions SET incoming_seqnum = ?
+ _, err := store.db.Exec(sqlString(`UPDATE sessions SET incoming_seqnum = ?
WHERE beginstring=? AND session_qualifier=?
AND sendercompid=? AND sendersubid=? AND senderlocid=?
- AND targetcompid=? AND targetsubid=? AND targetlocid=?`,
+ AND targetcompid=? AND targetsubid=? AND targetlocid=?`, store.placeholder),
next, s.BeginString, s.Qualifier,
s.SenderCompID, s.SenderSubID, s.SenderLocationID,
s.TargetCompID, s.TargetSubID, s.TargetLocationID)
@@ -201,19 +259,23 @@ func (store *sqlStore) SetNextTargetMsgSeqNum(next int) error {
return store.cache.SetNextTargetMsgSeqNum(next)
}
-// IncrNextSenderMsgSeqNum increments the next MsgSeqNum that will be sent
+// IncrNextSenderMsgSeqNum increments the next MsgSeqNum that will be sent.
func (store *sqlStore) IncrNextSenderMsgSeqNum() error {
- store.cache.IncrNextSenderMsgSeqNum()
+ if err := store.cache.IncrNextSenderMsgSeqNum(); err != nil {
+ return errors.Wrap(err, "cache incr next")
+ }
return store.SetNextSenderMsgSeqNum(store.cache.NextSenderMsgSeqNum())
}
-// IncrNextTargetMsgSeqNum increments the next MsgSeqNum that should be received
+// IncrNextTargetMsgSeqNum increments the next MsgSeqNum that should be received.
func (store *sqlStore) IncrNextTargetMsgSeqNum() error {
- store.cache.IncrNextTargetMsgSeqNum()
+ if err := store.cache.IncrNextTargetMsgSeqNum(); err != nil {
+ return errors.Wrap(err, "cache incr next")
+ }
return store.SetNextTargetMsgSeqNum(store.cache.NextTargetMsgSeqNum())
}
-// CreationTime returns the creation time of the store
+// CreationTime returns the creation time of the store.
func (store *sqlStore) CreationTime() time.Time {
return store.cache.CreationTime()
}
@@ -221,12 +283,12 @@ func (store *sqlStore) CreationTime() time.Time {
func (store *sqlStore) SaveMessage(seqNum int, msg []byte) error {
s := store.sessionID
- _, err := store.db.Exec(`INSERT INTO messages (
+ _, err := store.db.Exec(sqlString(`INSERT INTO messages (
msgseqnum, message,
beginstring, session_qualifier,
sendercompid, sendersubid, senderlocid,
targetcompid, targetsubid, targetlocid)
- VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, store.placeholder),
seqNum, string(msg),
s.BeginString, s.Qualifier,
s.SenderCompID, s.SenderSubID, s.SenderLocationID,
@@ -235,15 +297,58 @@ func (store *sqlStore) SaveMessage(seqNum int, msg []byte) error {
return err
}
+func (store *sqlStore) SaveMessageAndIncrNextSenderMsgSeqNum(seqNum int, msg []byte) error {
+ s := store.sessionID
+
+ tx, err := store.db.Begin()
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ _, err = tx.Exec(sqlString(`INSERT INTO messages (
+ msgseqnum, message,
+ beginstring, session_qualifier,
+ sendercompid, sendersubid, senderlocid,
+ targetcompid, targetsubid, targetlocid)
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, store.placeholder),
+ seqNum, string(msg),
+ s.BeginString, s.Qualifier,
+ s.SenderCompID, s.SenderSubID, s.SenderLocationID,
+ s.TargetCompID, s.TargetSubID, s.TargetLocationID)
+ if err != nil {
+ return err
+ }
+
+ next := store.cache.NextSenderMsgSeqNum() + 1
+ _, err = tx.Exec(sqlString(`UPDATE sessions SET outgoing_seqnum = ?
+ WHERE beginstring=? AND session_qualifier=?
+ AND sendercompid=? AND sendersubid=? AND senderlocid=?
+ AND targetcompid=? AND targetsubid=? AND targetlocid=?`, store.placeholder),
+ next, s.BeginString, s.Qualifier,
+ s.SenderCompID, s.SenderSubID, s.SenderLocationID,
+ s.TargetCompID, s.TargetSubID, s.TargetLocationID)
+ if err != nil {
+ return err
+ }
+
+ err = tx.Commit()
+ if err != nil {
+ return err
+ }
+
+ return store.cache.SetNextSenderMsgSeqNum(next)
+}
+
func (store *sqlStore) GetMessages(beginSeqNum, endSeqNum int) ([][]byte, error) {
s := store.sessionID
var msgs [][]byte
- rows, err := store.db.Query(`SELECT message FROM messages
+ rows, err := store.db.Query(sqlString(`SELECT message FROM messages
WHERE beginstring=? AND session_qualifier=?
AND sendercompid=? AND sendersubid=? AND senderlocid=?
AND targetcompid=? AND targetsubid=? AND targetlocid=?
AND msgseqnum>=? AND msgseqnum<=?
- ORDER BY msgseqnum`,
+ ORDER BY msgseqnum`, store.placeholder),
s.BeginString, s.Qualifier,
s.SenderCompID, s.SenderSubID, s.SenderLocationID,
s.TargetCompID, s.TargetSubID, s.TargetLocationID,
@@ -268,7 +373,7 @@ func (store *sqlStore) GetMessages(beginSeqNum, endSeqNum int) ([][]byte, error)
return msgs, nil
}
-// Close closes the store's database connection
+// Close closes the store's database connection.
func (store *sqlStore) Close() error {
if store.db != nil {
store.db.Close()
diff --git a/sqlstore_test.go b/sqlstore_test.go
index b252b2c25..512c951fc 100644
--- a/sqlstore_test.go
+++ b/sqlstore_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -16,7 +31,7 @@ import (
"github.com/stretchr/testify/suite"
)
-// SqlStoreTestSuite runs all tests in the MessageStoreTestSuite against the SqlStore implementation
+// SqlStoreTestSuite runs all tests in the MessageStoreTestSuite against the SqlStore implementation.
type SQLStoreTestSuite struct {
MessageStoreTestSuite
sqlStoreRootPath string
@@ -60,6 +75,11 @@ TargetCompID=%s`, sqlDriver, sqlDsn, sessionID.BeginString, sessionID.SenderComp
require.Nil(suite.T(), err)
}
+func (suite *SQLStoreTestSuite) TestSqlPlaceholderReplacement() {
+ got := sqlString("A ? B ? C ?", postgresPlaceholder)
+ suite.Equal("A $1 B $2 C $3", got)
+}
+
func (suite *SQLStoreTestSuite) TearDownTest() {
suite.msgStore.Close()
os.RemoveAll(suite.sqlStoreRootPath)
diff --git a/store.go b/store.go
index 837bbca13..c56e1896e 100644
--- a/store.go
+++ b/store.go
@@ -1,8 +1,27 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
-import "time"
+import (
+ "time"
+
+ "github.com/pkg/errors"
+)
-//The MessageStore interface provides methods to record and retrieve messages for resend purposes
+// The MessageStore interface provides methods to record and retrieve messages for resend purposes.
type MessageStore interface {
NextSenderMsgSeqNum() int
NextTargetMsgSeqNum() int
@@ -16,6 +35,7 @@ type MessageStore interface {
CreationTime() time.Time
SaveMessage(seqNum int, msg []byte) error
+ SaveMessageAndIncrNextSenderMsgSeqNum(seqNum int, msg []byte) error
GetMessages(beginSeqNum, endSeqNum int) ([][]byte, error)
Refresh() error
@@ -24,7 +44,7 @@ type MessageStore interface {
Close() error
}
-//The MessageStoreFactory interface is used by session to create a session specific message store
+// The MessageStoreFactory interface is used by session to create a session specific message store.
type MessageStoreFactory interface {
Create(sessionID SessionID) (MessageStore, error)
}
@@ -75,12 +95,12 @@ func (store *memoryStore) Reset() error {
}
func (store *memoryStore) Refresh() error {
- //nop, nothing to refresh
+ // NOP, nothing to refresh.
return nil
}
func (store *memoryStore) Close() error {
- //nop, nothing to close
+ // NOP, nothing to close.
return nil
}
@@ -93,6 +113,14 @@ func (store *memoryStore) SaveMessage(seqNum int, msg []byte) error {
return nil
}
+func (store *memoryStore) SaveMessageAndIncrNextSenderMsgSeqNum(seqNum int, msg []byte) error {
+ err := store.SaveMessage(seqNum, msg)
+ if err != nil {
+ return err
+ }
+ return store.IncrNextSenderMsgSeqNum()
+}
+
func (store *memoryStore) GetMessages(beginSeqNum, endSeqNum int) ([][]byte, error) {
var msgs [][]byte
for seqNum := beginSeqNum; seqNum <= endSeqNum; seqNum++ {
@@ -107,9 +135,11 @@ type memoryStoreFactory struct{}
func (f memoryStoreFactory) Create(sessionID SessionID) (MessageStore, error) {
m := new(memoryStore)
- m.Reset()
+ if err := m.Reset(); err != nil {
+ return m, errors.Wrap(err, "reset")
+ }
return m, nil
}
-//NewMemoryStoreFactory returns a MessageStoreFactory instance that created in-memory MessageStores
+// NewMemoryStoreFactory returns a MessageStoreFactory instance that created in-memory MessageStores.
func NewMemoryStoreFactory() MessageStoreFactory { return memoryStoreFactory{} }
diff --git a/store_test.go b/store_test.go
index a61ccbb05..075b3fd6c 100644
--- a/store_test.go
+++ b/store_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -9,13 +24,13 @@ import (
"github.com/stretchr/testify/suite"
)
-// MessageStoreTestSuite is the suite of all tests that should be run against all MessageStore implementations
+// MessageStoreTestSuite is the suite of all tests that should be run against all MessageStore implementations.
type MessageStoreTestSuite struct {
suite.Suite
msgStore MessageStore
}
-// MemoryStoreTestSuite runs all tests in the MessageStoreTestSuite against the MemoryStore implementation
+// MemoryStoreTestSuite runs all tests in the MessageStoreTestSuite against the MemoryStore implementation.
type MemoryStoreTestSuite struct {
MessageStoreTestSuite
}
@@ -30,60 +45,91 @@ func TestMemoryStoreTestSuite(t *testing.T) {
suite.Run(t, new(MemoryStoreTestSuite))
}
-func (suite *MessageStoreTestSuite) TestMessageStore_SetNextMsgSeqNum_Refresh_IncrNextMsgSeqNum() {
- t := suite.T()
-
+func (s *MessageStoreTestSuite) TestMessageStore_SetNextMsgSeqNum_Refresh_IncrNextMsgSeqNum() {
// Given a MessageStore with the following sender and target seqnums
- suite.msgStore.SetNextSenderMsgSeqNum(867)
- suite.msgStore.SetNextTargetMsgSeqNum(5309)
+ s.Require().Nil(s.msgStore.SetNextSenderMsgSeqNum(867))
+ s.Require().Nil(s.msgStore.SetNextTargetMsgSeqNum(5309))
// When the store is refreshed from its backing store
- suite.msgStore.Refresh()
+ s.Require().Nil(s.msgStore.Refresh())
// Then the sender and target seqnums should still be
- assert.Equal(t, 867, suite.msgStore.NextSenderMsgSeqNum())
- assert.Equal(t, 5309, suite.msgStore.NextTargetMsgSeqNum())
+ s.Equal(867, s.msgStore.NextSenderMsgSeqNum())
+ s.Equal(5309, s.msgStore.NextTargetMsgSeqNum())
// When the sender and target seqnums are incremented
- require.Nil(t, suite.msgStore.IncrNextSenderMsgSeqNum())
- require.Nil(t, suite.msgStore.IncrNextTargetMsgSeqNum())
+ s.Require().Nil(s.msgStore.IncrNextSenderMsgSeqNum())
+ s.Require().Nil(s.msgStore.IncrNextTargetMsgSeqNum())
// Then the sender and target seqnums should be
- assert.Equal(t, 868, suite.msgStore.NextSenderMsgSeqNum())
- assert.Equal(t, 5310, suite.msgStore.NextTargetMsgSeqNum())
+ s.Equal(868, s.msgStore.NextSenderMsgSeqNum())
+ s.Equal(5310, s.msgStore.NextTargetMsgSeqNum())
// When the store is refreshed from its backing store
- suite.msgStore.Refresh()
+ s.Require().Nil(s.msgStore.Refresh())
// Then the sender and target seqnums should still be
- assert.Equal(t, 868, suite.msgStore.NextSenderMsgSeqNum())
- assert.Equal(t, 5310, suite.msgStore.NextTargetMsgSeqNum())
+ s.Equal(868, s.msgStore.NextSenderMsgSeqNum())
+ s.Equal(5310, s.msgStore.NextTargetMsgSeqNum())
}
-func (suite *MessageStoreTestSuite) TestMessageStore_Reset() {
- t := suite.T()
-
+func (s *MessageStoreTestSuite) TestMessageStore_Reset() {
// Given a MessageStore with the following sender and target seqnums
- suite.msgStore.SetNextSenderMsgSeqNum(1234)
- suite.msgStore.SetNextTargetMsgSeqNum(5678)
+ s.Require().Nil(s.msgStore.SetNextSenderMsgSeqNum(1234))
+ s.Require().Nil(s.msgStore.SetNextTargetMsgSeqNum(5678))
// When the store is reset
- require.Nil(t, suite.msgStore.Reset())
+ s.Require().Nil(s.msgStore.Reset())
// Then the sender and target seqnums should be
- assert.Equal(t, 1, suite.msgStore.NextSenderMsgSeqNum())
- assert.Equal(t, 1, suite.msgStore.NextTargetMsgSeqNum())
+ s.Equal(1, s.msgStore.NextSenderMsgSeqNum())
+ s.Equal(1, s.msgStore.NextTargetMsgSeqNum())
// When the store is refreshed from its backing store
- suite.msgStore.Refresh()
+ s.Require().Nil(s.msgStore.Refresh())
// Then the sender and target seqnums should still be
- assert.Equal(t, 1, suite.msgStore.NextSenderMsgSeqNum())
- assert.Equal(t, 1, suite.msgStore.NextTargetMsgSeqNum())
+ s.Equal(1, s.msgStore.NextSenderMsgSeqNum())
+ s.Equal(1, s.msgStore.NextTargetMsgSeqNum())
}
-func (suite *MessageStoreTestSuite) TestMessageStore_SaveMessage_GetMessage() {
- t := suite.T()
+func (s *MessageStoreTestSuite) TestMessageStore_SaveMessage_GetMessage() {
+ // Given the following saved messages
+ expectedMsgsBySeqNum := map[int]string{
+ 1: "In the frozen land of Nador",
+ 2: "they were forced to eat Robin's minstrels",
+ 3: "and there was much rejoicing",
+ }
+ for seqNum, msg := range expectedMsgsBySeqNum {
+ s.Require().Nil(s.msgStore.SaveMessage(seqNum, []byte(msg)))
+ }
+
+ // When the messages are retrieved from the MessageStore
+ actualMsgs, err := s.msgStore.GetMessages(1, 3)
+ s.Require().Nil(err)
+
+ // Then the messages should be
+ s.Require().Len(actualMsgs, 3)
+ s.Equal(expectedMsgsBySeqNum[1], string(actualMsgs[0]))
+ s.Equal(expectedMsgsBySeqNum[2], string(actualMsgs[1]))
+ s.Equal(expectedMsgsBySeqNum[3], string(actualMsgs[2]))
+
+ // When the store is refreshed from its backing store
+ s.Require().Nil(s.msgStore.Refresh())
+
+ // And the messages are retrieved from the MessageStore
+ actualMsgs, err = s.msgStore.GetMessages(1, 3)
+ s.Require().Nil(err)
+
+ // Then the messages should still be
+ s.Require().Len(actualMsgs, 3)
+ s.Equal(expectedMsgsBySeqNum[1], string(actualMsgs[0]))
+ s.Equal(expectedMsgsBySeqNum[2], string(actualMsgs[1]))
+ s.Equal(expectedMsgsBySeqNum[3], string(actualMsgs[2]))
+}
+
+func (s *MessageStoreTestSuite) TestMessageStore_SaveMessage_AndIncrement_GetMessage() {
+ s.Require().Nil(s.msgStore.SetNextSenderMsgSeqNum(420))
// Given the following saved messages
expectedMsgsBySeqNum := map[int]string{
@@ -92,49 +138,52 @@ func (suite *MessageStoreTestSuite) TestMessageStore_SaveMessage_GetMessage() {
3: "and there was much rejoicing",
}
for seqNum, msg := range expectedMsgsBySeqNum {
- require.Nil(t, suite.msgStore.SaveMessage(seqNum, []byte(msg)))
+ s.Require().Nil(s.msgStore.SaveMessageAndIncrNextSenderMsgSeqNum(seqNum, []byte(msg)))
}
+ s.Equal(423, s.msgStore.NextSenderMsgSeqNum())
// When the messages are retrieved from the MessageStore
- actualMsgs, err := suite.msgStore.GetMessages(1, 3)
- require.Nil(t, err)
+ actualMsgs, err := s.msgStore.GetMessages(1, 3)
+ s.Require().Nil(err)
// Then the messages should be
- require.Len(t, actualMsgs, 3)
- assert.Equal(t, expectedMsgsBySeqNum[1], string(actualMsgs[0]))
- assert.Equal(t, expectedMsgsBySeqNum[2], string(actualMsgs[1]))
- assert.Equal(t, expectedMsgsBySeqNum[3], string(actualMsgs[2]))
+ s.Require().Len(actualMsgs, 3)
+ s.Equal(expectedMsgsBySeqNum[1], string(actualMsgs[0]))
+ s.Equal(expectedMsgsBySeqNum[2], string(actualMsgs[1]))
+ s.Equal(expectedMsgsBySeqNum[3], string(actualMsgs[2]))
// When the store is refreshed from its backing store
- suite.msgStore.Refresh()
+ s.Require().Nil(s.msgStore.Refresh())
// And the messages are retrieved from the MessageStore
- actualMsgs, err = suite.msgStore.GetMessages(1, 3)
- require.Nil(t, err)
+ actualMsgs, err = s.msgStore.GetMessages(1, 3)
+ s.Require().Nil(err)
+
+ s.Equal(423, s.msgStore.NextSenderMsgSeqNum())
// Then the messages should still be
- require.Len(t, actualMsgs, 3)
- assert.Equal(t, expectedMsgsBySeqNum[1], string(actualMsgs[0]))
- assert.Equal(t, expectedMsgsBySeqNum[2], string(actualMsgs[1]))
- assert.Equal(t, expectedMsgsBySeqNum[3], string(actualMsgs[2]))
+ s.Require().Len(actualMsgs, 3)
+ s.Equal(expectedMsgsBySeqNum[1], string(actualMsgs[0]))
+ s.Equal(expectedMsgsBySeqNum[2], string(actualMsgs[1]))
+ s.Equal(expectedMsgsBySeqNum[3], string(actualMsgs[2]))
}
-func (suite *MessageStoreTestSuite) TestMessageStore_GetMessages_EmptyStore() {
+func (s *MessageStoreTestSuite) TestMessageStore_GetMessages_EmptyStore() {
// When messages are retrieved from an empty store
- messages, err := suite.msgStore.GetMessages(1, 2)
- require.Nil(suite.T(), err)
+ messages, err := s.msgStore.GetMessages(1, 2)
+ require.Nil(s.T(), err)
// Then no messages should be returned
- require.Empty(suite.T(), messages, "Did not expect messages from empty store")
+ require.Empty(s.T(), messages, "Did not expect messages from empty store")
}
-func (suite *MessageStoreTestSuite) TestMessageStore_GetMessages_VariousRanges() {
- t := suite.T()
+func (s *MessageStoreTestSuite) TestMessageStore_GetMessages_VariousRanges() {
+ t := s.T()
// Given the following saved messages
- require.Nil(t, suite.msgStore.SaveMessage(1, []byte("hello")))
- require.Nil(t, suite.msgStore.SaveMessage(2, []byte("cruel")))
- require.Nil(t, suite.msgStore.SaveMessage(3, []byte("world")))
+ require.Nil(t, s.msgStore.SaveMessage(1, []byte("hello")))
+ require.Nil(t, s.msgStore.SaveMessage(2, []byte("cruel")))
+ require.Nil(t, s.msgStore.SaveMessage(3, []byte("world")))
// When the following requests are made to the store
var testCases = []struct {
@@ -154,7 +203,7 @@ func (suite *MessageStoreTestSuite) TestMessageStore_GetMessages_VariousRanges()
// Then the returned messages should be
for _, tc := range testCases {
- actualMsgs, err := suite.msgStore.GetMessages(tc.beginSeqNo, tc.endSeqNo)
+ actualMsgs, err := s.msgStore.GetMessages(tc.beginSeqNo, tc.endSeqNo)
require.Nil(t, err)
require.Len(t, actualMsgs, len(tc.expectedBytes))
for i, expectedMsg := range tc.expectedBytes {
@@ -163,12 +212,12 @@ func (suite *MessageStoreTestSuite) TestMessageStore_GetMessages_VariousRanges()
}
}
-func (suite *MessageStoreTestSuite) TestMessageStore_CreationTime() {
- assert.False(suite.T(), suite.msgStore.CreationTime().IsZero())
+func (s *MessageStoreTestSuite) TestMessageStore_CreationTime() {
+ s.False(s.msgStore.CreationTime().IsZero())
t0 := time.Now()
- suite.msgStore.Reset()
+ s.Require().Nil(s.msgStore.Reset())
t1 := time.Now()
- require.True(suite.T(), suite.msgStore.CreationTime().After(t0))
- require.True(suite.T(), suite.msgStore.CreationTime().Before(t1))
+ s.Require().True(s.msgStore.CreationTime().After(t0))
+ s.Require().True(s.msgStore.CreationTime().Before(t1))
}
diff --git a/tag.go b/tag.go
index 800375e34..01b28356f 100644
--- a/tag.go
+++ b/tag.go
@@ -1,6 +1,21 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
-//Tag is a typed int representing a FIX tag
+// Tag is a typed int representing a FIX tag.
type Tag int
const (
@@ -43,6 +58,7 @@ const (
tagBusinessRejectReason Tag = 380
tagSessionRejectReason Tag = 373
tagRefMsgType Tag = 372
+ tagBusinessRejectRefID Tag = 379
tagRefTagID Tag = 371
tagRefSeqNum Tag = 45
tagEncryptMethod Tag = 98
@@ -60,7 +76,7 @@ const (
tagCheckSum Tag = 10
)
-//IsTrailer returns true if tag belongs in the message trailer
+// IsTrailer returns true if tag belongs in the message trailer.
func (t Tag) IsTrailer() bool {
switch t {
case tagSignatureLength, tagSignature, tagCheckSum:
@@ -69,7 +85,7 @@ func (t Tag) IsTrailer() bool {
return false
}
-//IsHeader returns true if tag belongs in the message header
+// IsHeader returns true if tag belongs in the message header.
func (t Tag) IsHeader() bool {
switch t {
case tagBeginString,
diff --git a/tag_value.go b/tag_value.go
index 762ea233a..6e721862f 100644
--- a/tag_value.go
+++ b/tag_value.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -6,7 +21,7 @@ import (
"strconv"
)
-//TagValue is a low-level FIX field abstraction
+// TagValue is a low-level FIX field abstraction.
type TagValue struct {
tag Tag
value []byte
diff --git a/tag_value_test.go b/tag_value_test.go
index 48883ed85..2c9ccc811 100644
--- a/tag_value_test.go
+++ b/tag_value_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
diff --git a/tls.go b/tls.go
index ef6846f61..a86a49129 100644
--- a/tls.go
+++ b/tls.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -18,6 +33,14 @@ func loadTLSConfig(settings *SessionSettings) (tlsConfig *tls.Config, err error)
}
}
+ var serverName string
+ if settings.HasSetting(config.SocketServerName) {
+ serverName, err = settings.Setting(config.SocketServerName)
+ if err != nil {
+ return
+ }
+ }
+
insecureSkipVerify := false
if settings.HasSetting(config.SocketInsecureSkipVerify) {
insecureSkipVerify, err = settings.BoolSetting(config.SocketInsecureSkipVerify)
@@ -27,48 +50,40 @@ func loadTLSConfig(settings *SessionSettings) (tlsConfig *tls.Config, err error)
}
if !settings.HasSetting(config.SocketPrivateKeyFile) && !settings.HasSetting(config.SocketCertificateFile) {
- if allowSkipClientCerts {
- tlsConfig = defaultTLSConfig()
- tlsConfig.InsecureSkipVerify = insecureSkipVerify
+ if !allowSkipClientCerts {
+ return
}
- return
- }
-
- privateKeyFile, err := settings.Setting(config.SocketPrivateKeyFile)
- if err != nil {
- return
- }
-
- certificateFile, err := settings.Setting(config.SocketCertificateFile)
- if err != nil {
- return
}
tlsConfig = defaultTLSConfig()
- tlsConfig.Certificates = make([]tls.Certificate, 1)
+ tlsConfig.ServerName = serverName
tlsConfig.InsecureSkipVerify = insecureSkipVerify
+ setMinVersionExplicit(settings, tlsConfig)
- minVersion := "TLS12"
- if settings.HasSetting(config.SocketMinimumTLSVersion) {
- minVersion, err = settings.Setting(config.SocketMinimumTLSVersion)
+ if settings.HasSetting(config.SocketPrivateKeyFile) || settings.HasSetting(config.SocketCertificateFile) {
+
+ var privateKeyFile string
+ var certificateFile string
+
+ privateKeyFile, err = settings.Setting(config.SocketPrivateKeyFile)
if err != nil {
return
}
- switch minVersion {
- case "SSL30":
- tlsConfig.MinVersion = tls.VersionSSL30
- case "TLS10":
- tlsConfig.MinVersion = tls.VersionTLS10
- case "TLS11":
- tlsConfig.MinVersion = tls.VersionTLS11
- case "TLS12":
- tlsConfig.MinVersion = tls.VersionTLS12
+ certificateFile, err = settings.Setting(config.SocketCertificateFile)
+ if err != nil {
+ return
+ }
+
+ tlsConfig.Certificates = make([]tls.Certificate, 1)
+
+ if tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(certificateFile, privateKeyFile); err != nil {
+ return
}
}
- if tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(certificateFile, privateKeyFile); err != nil {
- return
+ if !allowSkipClientCerts {
+ tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
if !settings.HasSetting(config.SocketCAFile) {
@@ -93,12 +108,11 @@ func loadTLSConfig(settings *SessionSettings) (tlsConfig *tls.Config, err error)
tlsConfig.RootCAs = certPool
tlsConfig.ClientCAs = certPool
- tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
return
}
-//defaultTLSConfig brought to you by https://github.com/gtank/cryptopasta/
+// defaultTLSConfig brought to you by https://github.com/gtank/cryptopasta/
func defaultTLSConfig() *tls.Config {
return &tls.Config{
// Avoids most of the memorably-named TLS attacks
@@ -112,3 +126,24 @@ func defaultTLSConfig() *tls.Config {
},
}
}
+
+func setMinVersionExplicit(settings *SessionSettings, tlsConfig *tls.Config) {
+ if settings.HasSetting(config.SocketMinimumTLSVersion) {
+ minVersion, err := settings.Setting(config.SocketMinimumTLSVersion)
+ if err != nil {
+ return
+ }
+
+ switch minVersion {
+ case "SSL30":
+ //nolint:staticcheck // SA1019 min version ok
+ tlsConfig.MinVersion = tls.VersionSSL30
+ case "TLS10":
+ tlsConfig.MinVersion = tls.VersionTLS10
+ case "TLS11":
+ tlsConfig.MinVersion = tls.VersionTLS11
+ case "TLS12":
+ tlsConfig.MinVersion = tls.VersionTLS12
+ }
+ }
+}
diff --git a/tls_test.go b/tls_test.go
index 3ddbddeaf..0bae6cd54 100644
--- a/tls_test.go
+++ b/tls_test.go
@@ -1,11 +1,27 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"crypto/tls"
"testing"
- "github.com/quickfixgo/quickfix/config"
"github.com/stretchr/testify/suite"
+
+ "github.com/quickfixgo/quickfix/config"
)
type TLSTestSuite struct {
@@ -60,7 +76,7 @@ func (s *TLSTestSuite) TestLoadTLSNoCA() {
s.Len(tlsConfig.Certificates, 1)
s.Nil(tlsConfig.RootCAs)
s.Nil(tlsConfig.ClientCAs)
- s.Equal(tls.NoClientCert, tlsConfig.ClientAuth)
+ s.Equal(tls.RequireAndVerifyClientCert, tlsConfig.ClientAuth)
}
func (s *TLSTestSuite) TestLoadTLSWithBadCA() {
@@ -87,6 +103,57 @@ func (s *TLSTestSuite) TestLoadTLSWithCA() {
s.Equal(tls.RequireAndVerifyClientCert, tlsConfig.ClientAuth)
}
+func (s *TLSTestSuite) TestLoadTLSWithOnlyCA() {
+ s.settings.GlobalSettings().Set(config.SocketUseSSL, "Y")
+ s.settings.GlobalSettings().Set(config.SocketCAFile, s.CAFile)
+
+ tlsConfig, err := loadTLSConfig(s.settings.GlobalSettings())
+ s.Nil(err)
+ s.NotNil(tlsConfig)
+
+ s.NotNil(tlsConfig.RootCAs)
+ s.NotNil(tlsConfig.ClientCAs)
+}
+
+func (s *TLSTestSuite) TestLoadTLSWithoutSSLWithOnlyCA() {
+ s.settings.GlobalSettings().Set(config.SocketCAFile, s.CAFile)
+
+ tlsConfig, err := loadTLSConfig(s.settings.GlobalSettings())
+ s.Nil(err)
+ s.Nil(tlsConfig)
+}
+
+func (s *TLSTestSuite) TestLoadTLSAllowSkipClientCerts() {
+ s.settings.GlobalSettings().Set(config.SocketUseSSL, "Y")
+
+ tlsConfig, err := loadTLSConfig(s.settings.GlobalSettings())
+ s.Nil(err)
+ s.NotNil(tlsConfig)
+
+ s.Equal(tls.NoClientCert, tlsConfig.ClientAuth)
+}
+
+func (s *TLSTestSuite) TestServerNameUseSSL() {
+ s.settings.GlobalSettings().Set(config.SocketUseSSL, "Y")
+ s.settings.GlobalSettings().Set(config.SocketServerName, "DummyServerNameUseSSL")
+
+ tlsConfig, err := loadTLSConfig(s.settings.GlobalSettings())
+ s.Nil(err)
+ s.NotNil(tlsConfig)
+ s.Equal("DummyServerNameUseSSL", tlsConfig.ServerName)
+}
+
+func (s *TLSTestSuite) TestServerNameWithCerts() {
+ s.settings.GlobalSettings().Set(config.SocketPrivateKeyFile, s.PrivateKeyFile)
+ s.settings.GlobalSettings().Set(config.SocketCertificateFile, s.CertificateFile)
+ s.settings.GlobalSettings().Set(config.SocketServerName, "DummyServerNameWithCerts")
+
+ tlsConfig, err := loadTLSConfig(s.settings.GlobalSettings())
+ s.Nil(err)
+ s.NotNil(tlsConfig)
+ s.Equal("DummyServerNameWithCerts", tlsConfig.ServerName)
+}
+
func (s *TLSTestSuite) TestInsecureSkipVerify() {
s.settings.GlobalSettings().Set(config.SocketInsecureSkipVerify, "Y")
@@ -129,6 +196,7 @@ func (s *TLSTestSuite) TestMinimumTLSVersion() {
s.Nil(err)
s.NotNil(tlsConfig)
+ //nolint:staticcheck
s.Equal(tlsConfig.MinVersion, uint16(tls.VersionSSL30))
// TLS10
diff --git a/validation.go b/validation.go
index 7520d4b32..72bb06628 100644
--- a/validation.go
+++ b/validation.go
@@ -1,37 +1,69 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
"github.com/quickfixgo/quickfix/datadictionary"
)
-type validator interface {
+// Validator validates a FIX message.
+type Validator interface {
Validate(*Message) MessageRejectError
}
-type validatorSettings struct {
+// ValidatorSettings describe validation behavior.
+type ValidatorSettings struct {
CheckFieldsOutOfOrder bool
RejectInvalidMessage bool
}
-//Default configuration for message validation.
-//See http://www.quickfixengine.org/quickfix/doc/html/configuration.html.
-var defaultValidatorSettings = validatorSettings{
+// Default configuration for message validation.
+// See http://www.quickfixengine.org/quickfix/doc/html/configuration.html.
+var defaultValidatorSettings = ValidatorSettings{
CheckFieldsOutOfOrder: true,
RejectInvalidMessage: true,
}
type fixValidator struct {
dataDictionary *datadictionary.DataDictionary
- settings validatorSettings
+ settings ValidatorSettings
}
type fixtValidator struct {
transportDataDictionary *datadictionary.DataDictionary
appDataDictionary *datadictionary.DataDictionary
- settings validatorSettings
+ settings ValidatorSettings
}
-//Validate tests the message against the provided data dictionary.
+// NewValidator creates a FIX message validator from the given data dictionaries.
+func NewValidator(settings ValidatorSettings, appDataDictionary, transportDataDictionary *datadictionary.DataDictionary) Validator {
+ if transportDataDictionary != nil {
+ return &fixtValidator{
+ transportDataDictionary: transportDataDictionary,
+ appDataDictionary: appDataDictionary,
+ settings: settings,
+ }
+ }
+ return &fixValidator{
+ dataDictionary: appDataDictionary,
+ settings: settings,
+ }
+}
+
+// Validate tests the message against the provided data dictionary.
func (v *fixValidator) Validate(msg *Message) MessageRejectError {
if !msg.Header.Has(tagMsgType) {
return RequiredTagMissing(tagMsgType)
@@ -44,8 +76,8 @@ func (v *fixValidator) Validate(msg *Message) MessageRejectError {
return validateFIX(v.dataDictionary, v.settings, msgType, msg)
}
-//Validate tests the message against the provided transport and app data dictionaries.
-//If the message is an admin message, it will be validated against the transport data dictionary.
+// Validate tests the message against the provided transport and app data dictionaries.
+// If the message is an admin message, it will be validated against the transport data dictionary.
func (v *fixtValidator) Validate(msg *Message) MessageRejectError {
if !msg.Header.Has(tagMsgType) {
return RequiredTagMissing(tagMsgType)
@@ -61,7 +93,7 @@ func (v *fixtValidator) Validate(msg *Message) MessageRejectError {
return validateFIXT(v.transportDataDictionary, v.appDataDictionary, v.settings, msgType, msg)
}
-func validateFIX(d *datadictionary.DataDictionary, settings validatorSettings, msgType string, msg *Message) MessageRejectError {
+func validateFIX(d *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError {
if err := validateMsgType(d, msgType, msg); err != nil {
return err
}
@@ -89,7 +121,7 @@ func validateFIX(d *datadictionary.DataDictionary, settings validatorSettings, m
return nil
}
-func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings validatorSettings, msgType string, msg *Message) MessageRejectError {
+func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings ValidatorSettings, msgType string, msg *Message) MessageRejectError {
if err := validateMsgType(appDD, msgType, msg); err != nil {
return err
}
@@ -104,19 +136,21 @@ func validateFIXT(transportDD, appDD *datadictionary.DataDictionary, settings va
}
}
- if err := validateWalk(transportDD, appDD, msgType, msg); err != nil {
- return err
- }
+ if settings.RejectInvalidMessage {
+ if err := validateFields(transportDD, appDD, msgType, msg); err != nil {
+ return err
+ }
- if err := validateFields(transportDD, appDD, msgType, msg); err != nil {
- return err
+ if err := validateWalk(transportDD, appDD, msgType, msg); err != nil {
+ return err
+ }
}
return nil
}
func validateMsgType(d *datadictionary.DataDictionary, msgType string, msg *Message) MessageRejectError {
- if _, validMsgType := d.Messages[msgType]; validMsgType == false {
+ if _, validMsgType := d.Messages[msgType]; !validMsgType {
return InvalidMessageType()
}
return nil
@@ -192,13 +226,13 @@ func validateVisitGroupField(fieldDef *datadictionary.FieldDef, fieldStack []Tag
for len(fieldStack) > 0 {
- //start of repeating group
+ // Start of repeating group.
if int(fieldStack[0].tag) == fieldDef.Fields[0].Tag() {
childDefs = fieldDef.Fields
groupCount++
}
- //group complete
+ // Group complete.
if len(childDefs) == 0 {
break
}
diff --git a/validation_test.go b/validation_test.go
index 7e5a7a3bc..fc97944b7 100644
--- a/validation_test.go
+++ b/validation_test.go
@@ -1,3 +1,18 @@
+// Copyright (c) quickfixengine.org All rights reserved.
+//
+// This file may be distributed under the terms of the quickfixengine.org
+// license as defined by quickfixengine.org and appearing in the file
+// LICENSE included in the packaging of this file.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+// PARTICULAR PURPOSE.
+//
+// See http://www.quickfixengine.org/LICENSE for licensing information.
+//
+// Contact ask@quickfixengine.org if any conditions of this licensing
+// are not clear to you.
+
package quickfix
import (
@@ -5,13 +20,14 @@ import (
"testing"
"time"
- "github.com/quickfixgo/quickfix/datadictionary"
"github.com/stretchr/testify/assert"
+
+ "github.com/quickfixgo/quickfix/datadictionary"
)
type validateTest struct {
TestName string
- Validator validator
+ Validator Validator
MessageBytes []byte
ExpectedRejectReason int
ExpectedRefTagID *Tag
@@ -21,24 +37,43 @@ type validateTest struct {
func TestValidate(t *testing.T) {
var tests = []validateTest{
tcInvalidTagNumberHeader(),
+ tcInvalidTagNumberHeaderFixT(),
tcInvalidTagNumberBody(),
+ tcInvalidTagNumberBodyFixT(),
tcInvalidTagNumberTrailer(),
+ tcInvalidTagNumberTrailerFixT(),
tcTagSpecifiedWithoutAValue(),
+ tcTagSpecifiedWithoutAValueFixT(),
tcInvalidMsgType(),
+ tcInvalidMsgTypeFixT(),
tcValueIsIncorrect(),
+ tcValueIsIncorrectFixT(),
tcIncorrectDataFormatForValue(),
+ tcIncorrectDataFormatForValueFixT(),
tcTagSpecifiedOutOfRequiredOrderHeader(),
+ tcTagSpecifiedOutOfRequiredOrderHeaderFixT(),
tcTagSpecifiedOutOfRequiredOrderTrailer(),
+ tcTagSpecifiedOutOfRequiredOrderTrailerFixT(),
tcTagSpecifiedOutOfRequiredOrderDisabledHeader(),
+ tcTagSpecifiedOutOfRequiredOrderDisabledHeaderFixT(),
tcTagSpecifiedOutOfRequiredOrderDisabledTrailer(),
+ tcTagSpecifiedOutOfRequiredOrderDisabledTrailerFixT(),
tcTagAppearsMoreThanOnce(),
+ tcTagAppearsMoreThanOnceFixT(),
tcFloatValidation(),
+ tcFloatValidationFixT(),
tcTagNotDefinedForMessage(),
+ tcTagNotDefinedForMessageFixT(),
tcTagIsDefinedForMessage(),
+ tcTagIsDefinedForMessageFixT(),
tcFieldNotFoundBody(),
+ tcFieldNotFoundBodyFixT(),
tcFieldNotFoundHeader(),
+ tcFieldNotFoundHeaderFixT(),
tcInvalidTagCheckDisabled(),
+ tcInvalidTagCheckDisabledFixT(),
tcInvalidTagCheckEnabled(),
+ tcInvalidTagCheckEnabledFixT(),
}
msg := NewMessage()
@@ -65,13 +100,13 @@ func TestValidate(t *testing.T) {
switch {
case reject.RefTagID() == nil && test.ExpectedRefTagID == nil:
- //ok, expected and actual ref tag not set
+ // OK, expected and actual ref tag not set.
case reject.RefTagID() != nil && test.ExpectedRefTagID == nil:
t.Errorf("%v: Unexpected RefTag '%v'", test.TestName, *reject.RefTagID())
case reject.RefTagID() == nil && test.ExpectedRefTagID != nil:
t.Errorf("%v: Expected RefTag '%v'", test.TestName, *test.ExpectedRefTagID)
case *reject.RefTagID() == *test.ExpectedRefTagID:
- //ok, tags equal
+ // OK, tags equal.
default:
t.Errorf("%v: Expected RefTag '%v' got '%v'", test.TestName, *test.ExpectedRefTagID, *reject.RefTagID())
}
@@ -124,9 +159,33 @@ func createFIX43NewOrderSingle() *Message {
return msg
}
+func createFIX50SP2NewOrderSingle() *Message {
+ msg := NewMessage()
+ msg.Header.SetField(tagMsgType, FIXString("D"))
+ msg.Header.SetField(tagBeginString, FIXString("FIXT.1.1"))
+ msg.Header.SetField(tagBodyLength, FIXString("0"))
+ msg.Header.SetField(tagSenderCompID, FIXString("0"))
+ msg.Header.SetField(tagTargetCompID, FIXString("0"))
+ msg.Header.SetField(tagMsgSeqNum, FIXString("0"))
+ msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: time.Now()})
+
+ msg.Body.SetField(Tag(11), FIXString("A"))
+ msg.Body.SetField(Tag(21), FIXString("1"))
+ msg.Body.SetField(Tag(55), FIXString("A"))
+ msg.Body.SetField(Tag(54), FIXString("1"))
+ msg.Body.SetField(Tag(40), FIXString("1"))
+ msg.Body.SetField(Tag(38), FIXInt(5))
+ msg.Body.SetField(Tag(60), FIXUTCTimestamp{Time: time.Now(), Precision: Micros})
+ msg.Body.SetField(Tag(100), FIXString("0"))
+
+ msg.Trailer.SetField(tagCheckSum, FIXString("000"))
+
+ return msg
+}
+
func tcInvalidTagNumberHeader() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
invalidHeaderFieldMessage := createFIX40NewOrderSingle()
tag := Tag(9999)
invalidHeaderFieldMessage.Header.SetField(tag, FIXString("hello"))
@@ -140,9 +199,28 @@ func tcInvalidTagNumberHeader() validateTest {
ExpectedRefTagID: &tag,
}
}
+
+func tcInvalidTagNumberHeaderFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ invalidHeaderFieldMessage := createFIX50SP2NewOrderSingle()
+ tag := Tag(9999)
+ invalidHeaderFieldMessage.Header.SetField(tag, FIXString("hello"))
+ msgBytes := invalidHeaderFieldMessage.build()
+
+ return validateTest{
+ TestName: "Invalid Tag Number Header FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonInvalidTagNumber,
+ ExpectedRefTagID: &tag,
+ }
+}
+
func tcInvalidTagNumberBody() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
invalidBodyFieldMessage := createFIX40NewOrderSingle()
tag := Tag(9999)
invalidBodyFieldMessage.Body.SetField(tag, FIXString("hello"))
@@ -157,9 +235,27 @@ func tcInvalidTagNumberBody() validateTest {
}
}
+func tcInvalidTagNumberBodyFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ invalidBodyFieldMessage := createFIX50SP2NewOrderSingle()
+ tag := Tag(9999)
+ invalidBodyFieldMessage.Body.SetField(tag, FIXString("hello"))
+ msgBytes := invalidBodyFieldMessage.build()
+
+ return validateTest{
+ TestName: "Invalid Tag Number Body FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonInvalidTagNumber,
+ ExpectedRefTagID: &tag,
+ }
+}
+
func tcInvalidTagNumberTrailer() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
invalidTrailerFieldMessage := createFIX40NewOrderSingle()
tag := Tag(9999)
invalidTrailerFieldMessage.Trailer.SetField(tag, FIXString("hello"))
@@ -174,9 +270,27 @@ func tcInvalidTagNumberTrailer() validateTest {
}
}
+func tcInvalidTagNumberTrailerFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ invalidTrailerFieldMessage := createFIX50SP2NewOrderSingle()
+ tag := Tag(9999)
+ invalidTrailerFieldMessage.Trailer.SetField(tag, FIXString("hello"))
+ msgBytes := invalidTrailerFieldMessage.build()
+
+ return validateTest{
+ TestName: "Invalid Tag Number Trailer FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonInvalidTagNumber,
+ ExpectedRefTagID: &tag,
+ }
+}
+
func tcTagNotDefinedForMessage() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
invalidMsg := createFIX40NewOrderSingle()
tag := Tag(41)
invalidMsg.Body.SetField(tag, FIXString("hello"))
@@ -191,10 +305,28 @@ func tcTagNotDefinedForMessage() validateTest {
}
}
+func tcTagNotDefinedForMessageFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ invalidMsg := createFIX50SP2NewOrderSingle()
+ tag := Tag(41)
+ invalidMsg.Body.SetField(tag, FIXString("hello"))
+ msgBytes := invalidMsg.build()
+
+ return validateTest{
+ TestName: "Tag Not Defined For Message FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType,
+ ExpectedRefTagID: &tag,
+ }
+}
+
func tcTagIsDefinedForMessage() validateTest {
- //compare to tcTagIsNotDefinedForMessage
+ // Compare to `tcTagIsNotDefinedForMessage`.
dict, _ := datadictionary.Parse("spec/FIX43.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
validMsg := createFIX43NewOrderSingle()
msgBytes := validMsg.build()
@@ -206,9 +338,25 @@ func tcTagIsDefinedForMessage() validateTest {
}
}
+func tcTagIsDefinedForMessageFixT() validateTest {
+ // Compare to `tcTagIsNotDefinedForMessage`.
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ validMsg := createFIX50SP2NewOrderSingle()
+ msgBytes := validMsg.build()
+
+ return validateTest{
+ TestName: "TagIsDefinedForMessage FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ DoNotExpectReject: true,
+ }
+}
+
func tcFieldNotFoundBody() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
invalidMsg1 := NewMessage()
invalidMsg1.Header.SetField(tagMsgType, FIXString("D")).
SetField(tagBeginString, FIXString("FIX.4.0")).
@@ -226,9 +374,7 @@ func tcFieldNotFoundBody() validateTest {
SetField(Tag(38), FIXString("A"))
tag := Tag(40)
- //ord type is required
- //invalidMsg1.Body.SetField(Tag(40), "A"))
-
+ // Ord type is required. invalidMsg1.Body.SetField(Tag(40), "A")).
msgBytes := invalidMsg1.build()
return validateTest{
@@ -240,9 +386,43 @@ func tcFieldNotFoundBody() validateTest {
}
}
+func tcFieldNotFoundBodyFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ invalidMsg1 := NewMessage()
+ invalidMsg1.Header.SetField(tagMsgType, FIXString("D")).
+ SetField(tagBeginString, FIXString("FIXT.1.1")).
+ SetField(tagBodyLength, FIXString("0")).
+ SetField(tagSenderCompID, FIXString("0")).
+ SetField(tagTargetCompID, FIXString("0")).
+ SetField(tagMsgSeqNum, FIXString("0")).
+ SetField(tagSendingTime, FIXUTCTimestamp{Time: time.Now()})
+ invalidMsg1.Trailer.SetField(tagCheckSum, FIXString("000"))
+
+ invalidMsg1.Body.SetField(Tag(11), FIXString("A")).
+ SetField(Tag(21), FIXString("A")).
+ SetField(Tag(55), FIXString("A")).
+ SetField(Tag(54), FIXString("A")).
+ SetField(Tag(38), FIXString("A")).
+ SetField(Tag(60), FIXUTCTimestamp{Time: time.Now()})
+
+ tag := Tag(40)
+ // Ord type is required. invalidMsg1.Body.SetField(Tag(40), "A")).
+ msgBytes := invalidMsg1.build()
+
+ return validateTest{
+ TestName: "FieldNotFoundBody FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonRequiredTagMissing,
+ ExpectedRefTagID: &tag,
+ }
+}
+
func tcFieldNotFoundHeader() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
invalidMsg2 := NewMessage()
invalidMsg2.Trailer.SetField(tagCheckSum, FIXString("000"))
@@ -258,9 +438,8 @@ func tcFieldNotFoundHeader() validateTest {
SetField(tagSenderCompID, FIXString("0")).
SetField(tagTargetCompID, FIXString("0")).
SetField(tagMsgSeqNum, FIXString("0"))
- //sending time is required
- //invalidMsg2.Header.FieldMap.SetField(tag.SendingTime, "0"))
+ // Sending time is required. invalidMsg2.Header.FieldMap.SetField(tag.SendingTime, "0")).
tag := tagSendingTime
msgBytes := invalidMsg2.build()
@@ -273,9 +452,42 @@ func tcFieldNotFoundHeader() validateTest {
}
}
+func tcFieldNotFoundHeaderFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+
+ invalidMsg2 := NewMessage()
+ invalidMsg2.Trailer.SetField(tagCheckSum, FIXString("000"))
+ invalidMsg2.Body.SetField(Tag(11), FIXString("A")).
+ SetField(Tag(21), FIXString("A")).
+ SetField(Tag(55), FIXString("A")).
+ SetField(Tag(54), FIXString("A")).
+ SetField(Tag(38), FIXString("A"))
+
+ invalidMsg2.Header.SetField(tagMsgType, FIXString("D")).
+ SetField(tagBeginString, FIXString("FIXT.1.1")).
+ SetField(tagBodyLength, FIXString("0")).
+ SetField(tagSenderCompID, FIXString("0")).
+ SetField(tagTargetCompID, FIXString("0")).
+ SetField(tagMsgSeqNum, FIXString("0"))
+
+ // Sending time is required. invalidMsg2.Header.FieldMap.SetField(tag.SendingTime, "0")).
+ tag := tagSendingTime
+ msgBytes := invalidMsg2.build()
+
+ return validateTest{
+ TestName: "FieldNotFoundHeader FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonRequiredTagMissing,
+ ExpectedRefTagID: &tag,
+ }
+}
+
func tcTagSpecifiedWithoutAValue() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
builder := createFIX40NewOrderSingle()
bogusTag := Tag(109)
@@ -291,9 +503,28 @@ func tcTagSpecifiedWithoutAValue() validateTest {
}
}
+func tcTagSpecifiedWithoutAValueFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ builder := createFIX50SP2NewOrderSingle()
+
+ bogusTag := Tag(109)
+ builder.Body.SetField(bogusTag, FIXString(""))
+ msgBytes := builder.build()
+
+ return validateTest{
+ TestName: "Tag SpecifiedWithoutAValue FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonTagSpecifiedWithoutAValue,
+ ExpectedRefTagID: &bogusTag,
+ }
+}
+
func tcInvalidMsgType() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
builder := createFIX40NewOrderSingle()
builder.Header.SetField(tagMsgType, FIXString("z"))
msgBytes := builder.build()
@@ -306,9 +537,25 @@ func tcInvalidMsgType() validateTest {
}
}
+func tcInvalidMsgTypeFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ builder := createFIX50SP2NewOrderSingle()
+ builder.Header.SetField(tagMsgType, FIXString("zz"))
+ msgBytes := builder.build()
+
+ return validateTest{
+ TestName: "Invalid MsgType FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonInvalidMsgType,
+ }
+}
+
func tcValueIsIncorrect() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
tag := Tag(21)
builder := createFIX40NewOrderSingle()
@@ -324,9 +571,28 @@ func tcValueIsIncorrect() validateTest {
}
}
+func tcValueIsIncorrectFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+
+ tag := Tag(21)
+ builder := createFIX50SP2NewOrderSingle()
+ builder.Body.SetField(tag, FIXString("4"))
+ msgBytes := builder.build()
+
+ return validateTest{
+ TestName: "ValueIsIncorrect FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonValueIsIncorrect,
+ ExpectedRefTagID: &tag,
+ }
+}
+
func tcIncorrectDataFormatForValue() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
builder := createFIX40NewOrderSingle()
tag := Tag(38)
builder.Body.SetField(tag, FIXString("+200.00"))
@@ -341,13 +607,31 @@ func tcIncorrectDataFormatForValue() validateTest {
}
}
+func tcIncorrectDataFormatForValueFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ builder := createFIX50SP2NewOrderSingle()
+ tag := Tag(38)
+ builder.Body.SetField(tag, FIXString("+200.00"))
+ msgBytes := builder.build()
+
+ return validateTest{
+ TestName: "IncorrectDataFormatForValue FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonIncorrectDataFormatForValue,
+ ExpectedRefTagID: &tag,
+ }
+}
+
func tcTagSpecifiedOutOfRequiredOrderHeader() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
builder := createFIX40NewOrderSingle()
tag := tagOnBehalfOfCompID
- //should be in header
+ // Should be in header.
builder.Body.SetField(tag, FIXString("CWB"))
msgBytes := builder.build()
@@ -360,13 +644,33 @@ func tcTagSpecifiedOutOfRequiredOrderHeader() validateTest {
}
}
+func tcTagSpecifiedOutOfRequiredOrderHeaderFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+
+ builder := createFIX50SP2NewOrderSingle()
+ tag := tagOnBehalfOfCompID
+ // Should be in header.
+ builder.Body.SetField(tag, FIXString("CWB"))
+ msgBytes := builder.build()
+
+ return validateTest{
+ TestName: "Tag specified out of required order in Header FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonTagSpecifiedOutOfRequiredOrder,
+ ExpectedRefTagID: &tag,
+ }
+}
+
func tcTagSpecifiedOutOfRequiredOrderTrailer() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
builder := createFIX40NewOrderSingle()
tag := tagSignature
- //should be in trailer
+ // Should be in trailer.
builder.Body.SetField(tag, FIXString("SIG"))
msgBytes := builder.build()
@@ -380,10 +684,32 @@ func tcTagSpecifiedOutOfRequiredOrderTrailer() validateTest {
}
}
+func tcTagSpecifiedOutOfRequiredOrderTrailerFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+
+ builder := createFIX50SP2NewOrderSingle()
+ tag := tagSignature
+ // Should be in trailer.
+ builder.Body.SetField(tag, FIXString("SIG"))
+ msgBytes := builder.build()
+
+ refTag := Tag(100)
+ return validateTest{
+ TestName: "Tag specified out of required order in Trailer FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ ExpectedRejectReason: rejectReasonTagSpecifiedOutOfRequiredOrder,
+ ExpectedRefTagID: &refTag,
+ }
+}
+
func tcInvalidTagCheckDisabled() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
- validator.settings.RejectInvalidMessage = false
+ customValidatorSettings := defaultValidatorSettings
+ customValidatorSettings.RejectInvalidMessage = false
+ validator := NewValidator(customValidatorSettings, dict, nil)
builder := createFIX40NewOrderSingle()
tag := Tag(9999)
@@ -398,10 +724,31 @@ func tcInvalidTagCheckDisabled() validateTest {
}
}
+func tcInvalidTagCheckDisabledFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ customValidatorSettings := defaultValidatorSettings
+ customValidatorSettings.RejectInvalidMessage = false
+ validator := NewValidator(customValidatorSettings, appDict, tDict)
+
+ builder := createFIX50SP2NewOrderSingle()
+ tag := Tag(9999)
+ builder.Body.SetField(tag, FIXString("hello"))
+ msgBytes := builder.build()
+
+ return validateTest{
+ TestName: "Invalid Tag Check - Disabled FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ DoNotExpectReject: true,
+ }
+}
+
func tcInvalidTagCheckEnabled() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
- validator.settings.RejectInvalidMessage = true
+ customValidatorSettings := defaultValidatorSettings
+ customValidatorSettings.RejectInvalidMessage = true
+ validator := NewValidator(customValidatorSettings, dict, nil)
builder := createFIX40NewOrderSingle()
tag := Tag(9999)
@@ -413,18 +760,40 @@ func tcInvalidTagCheckEnabled() validateTest {
Validator: validator,
MessageBytes: msgBytes,
DoNotExpectReject: false,
- ExpectedRefTagID: &tag,
+ ExpectedRefTagID: &tag,
+ }
+}
+
+func tcInvalidTagCheckEnabledFixT() 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: "Invalid Tag Check - Enabled FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ DoNotExpectReject: false,
+ ExpectedRefTagID: &tag,
}
}
func tcTagSpecifiedOutOfRequiredOrderDisabledHeader() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
- validator.settings.CheckFieldsOutOfOrder = false
+ customValidatorSettings := defaultValidatorSettings
+ customValidatorSettings.CheckFieldsOutOfOrder = false
+ validator := NewValidator(customValidatorSettings, dict, nil)
builder := createFIX40NewOrderSingle()
tag := tagOnBehalfOfCompID
- //should be in header
+ // Should be in header.
builder.Body.SetField(tag, FIXString("CWB"))
msgBytes := builder.build()
@@ -436,14 +805,36 @@ func tcTagSpecifiedOutOfRequiredOrderDisabledHeader() validateTest {
}
}
+func tcTagSpecifiedOutOfRequiredOrderDisabledHeaderFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ customValidatorSettings := defaultValidatorSettings
+ customValidatorSettings.CheckFieldsOutOfOrder = false
+ validator := NewValidator(customValidatorSettings, appDict, tDict)
+
+ builder := createFIX50SP2NewOrderSingle()
+ tag := tagOnBehalfOfCompID
+ // Should be in header.
+ builder.Body.SetField(tag, FIXString("CWB"))
+ msgBytes := builder.build()
+
+ return validateTest{
+ TestName: "Tag specified out of required order in Header - Disabled FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ DoNotExpectReject: true,
+ }
+}
+
func tcTagSpecifiedOutOfRequiredOrderDisabledTrailer() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
- validator.settings.CheckFieldsOutOfOrder = false
+ customValidatorSettings := defaultValidatorSettings
+ customValidatorSettings.CheckFieldsOutOfOrder = false
+ validator := NewValidator(customValidatorSettings, dict, nil)
builder := createFIX40NewOrderSingle()
tag := tagSignature
- //should be in trailer
+ // Should be in trailer.
builder.Body.SetField(tag, FIXString("SIG"))
msgBytes := builder.build()
@@ -455,15 +846,51 @@ func tcTagSpecifiedOutOfRequiredOrderDisabledTrailer() validateTest {
}
}
+func tcTagSpecifiedOutOfRequiredOrderDisabledTrailerFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ customValidatorSettings := defaultValidatorSettings
+ customValidatorSettings.CheckFieldsOutOfOrder = false
+ validator := NewValidator(customValidatorSettings, appDict, tDict)
+
+ builder := createFIX50SP2NewOrderSingle()
+ tag := tagSignature
+ // Should be in trailer.
+ builder.Body.SetField(tag, FIXString("SIG"))
+ msgBytes := builder.build()
+
+ return validateTest{
+ TestName: "Tag specified out of required order in Trailer - Disabled FIXT",
+ Validator: validator,
+ MessageBytes: msgBytes,
+ DoNotExpectReject: true,
+ }
+}
+
func tcTagAppearsMoreThanOnce() validateTest {
dict, _ := datadictionary.Parse("spec/FIX40.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
+ tag := Tag(40)
+
+ return validateTest{
+ TestName: "Tag appears more than once",
+ Validator: validator,
+ MessageBytes: []byte("8=FIX.4.09=10735=D34=249=TW52=20060102-15:04:0556=ISLD11=ID21=140=140=254=138=20055=INTC60=20060102-15:04:0510=234"),
+ ExpectedRejectReason: rejectReasonTagAppearsMoreThanOnce,
+ ExpectedRefTagID: &tag,
+ }
+}
+
+func tcTagAppearsMoreThanOnceFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
tag := Tag(40)
return validateTest{
- TestName: "Tag appears more than once",
- Validator: validator,
- MessageBytes: []byte("8=FIX.4.09=10735=D34=249=TW52=20060102-15:04:0556=ISLD11=ID21=140=140=254=138=20055=INTC60=20060102-15:04:0510=234"),
+ TestName: "Tag appears more than once FIXT",
+ Validator: validator,
+ MessageBytes: []byte("8=FIXT.1.19=10735=D34=249=TW52=20060102-15:04:0556=ISLD11=ID21=140=140=254=138=20055=INTC60=20060102-15:04:0510=234"),
ExpectedRejectReason: rejectReasonTagAppearsMoreThanOnce,
ExpectedRefTagID: &tag,
}
@@ -471,12 +898,26 @@ func tcTagAppearsMoreThanOnce() validateTest {
func tcFloatValidation() validateTest {
dict, _ := datadictionary.Parse("spec/FIX42.xml")
- validator := &fixValidator{dict, defaultValidatorSettings}
+ validator := NewValidator(defaultValidatorSettings, dict, nil)
tag := Tag(38)
return validateTest{
- TestName: "FloatValidation",
- Validator: validator,
- MessageBytes: []byte("8=FIX.4.29=10635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID21=140=154=138=+200.0055=INTC60=20140329-22:38:4510=178"),
+ TestName: "FloatValidation",
+ Validator: validator,
+ MessageBytes: []byte("8=FIX.4.29=10635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID21=140=154=138=+200.0055=INTC60=20140329-22:38:4510=178"),
+ ExpectedRejectReason: rejectReasonIncorrectDataFormatForValue,
+ ExpectedRefTagID: &tag,
+ }
+}
+
+func tcFloatValidationFixT() validateTest {
+ tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
+ appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
+ validator := NewValidator(defaultValidatorSettings, appDict, tDict)
+ tag := Tag(38)
+ return validateTest{
+ TestName: "FloatValidation FIXT",
+ Validator: validator,
+ MessageBytes: []byte("8=FIXT.1.19=10635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID21=140=154=138=+200.0055=INTC60=20140329-22:38:4510=178"),
ExpectedRejectReason: rejectReasonIncorrectDataFormatForValue,
ExpectedRefTagID: &tag,
}
@@ -519,27 +960,27 @@ func TestValidateVisitField(t *testing.T) {
expectReject bool
expectedRejectReason int
}{
- //non-repeating
+ // Non-repeating.
{expectedRemFields: 0,
fieldDef: fieldDef0,
fields: []TagValue{field}},
- //single field group
+ // Single field group.
{expectedRemFields: 0,
fieldDef: groupFieldDef,
fields: []TagValue{groupID, repField1}},
- //multiple field group
+ // Multiple field group.
{expectedRemFields: 0,
fieldDef: groupFieldDef,
fields: []TagValue{groupID, repField1, repField2}},
- //test with trailing tag not in group
+ // Test with trailing tag not in group.
{expectedRemFields: 1,
fieldDef: groupFieldDef,
fields: []TagValue{groupID, repField1, repField2, field}},
- //repeats
+ // Repeats.
{expectedRemFields: 1,
fieldDef: groupFieldDef,
fields: []TagValue{groupID2, repField1, repField2, repField1, repField2, field}},
- //REJECT: group size declared > actual group size
+ // REJECT: group size declared > actual group size.
{expectReject: true,
fieldDef: groupFieldDef,
fields: []TagValue{groupID3, repField1, repField2, repField1, repField2, field},
@@ -550,7 +991,7 @@ func TestValidateVisitField(t *testing.T) {
fields: []TagValue{groupID3, repField1, repField1, field},
expectedRejectReason: rejectReasonIncorrectNumInGroupCountForRepeatingGroup,
},
- //REJECT: group size declared < actual group size
+ // REJECT: group size declared < actual group size.
{expectReject: true,
fieldDef: groupFieldDef,
fields: []TagValue{groupID, repField1, repField2, repField1, repField2, field},