adding quickfix

This commit is contained in:
Ramiro Paz
2026-03-09 15:35:32 -03:00
parent 0e8fe168ef
commit fe588e92f1
1222 changed files with 1408232 additions and 1 deletions

244
quickfix/initiator.go Normal file
View File

@ -0,0 +1,244 @@
// 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"
"context"
"crypto/tls"
"strings"
"sync"
"time"
"golang.org/x/net/proxy"
)
// Initiator initiates connections and processes messages for all sessions.
type Initiator struct {
app Application
settings *Settings
sessionSettings map[SessionID]*SessionSettings
storeFactory MessageStoreFactory
logFactory LogFactory
globalLog Log
stopChan chan interface{}
wg sync.WaitGroup
sessions map[SessionID]*session
sessionFactory
}
// Start Initiator.
func (i *Initiator) Start() (err error) {
i.stopChan = make(chan interface{})
for sessionID, settings := range i.sessionSettings {
// TODO: move into session factory.
var tlsConfig *tls.Config
if tlsConfig, err = loadTLSConfig(settings); err != nil {
return
}
var dialer proxy.ContextDialer
if dialer, err = loadDialerConfig(settings); err != nil {
return
}
i.wg.Add(1)
go func(sessID SessionID) {
i.handleConnection(i.sessions[sessID], tlsConfig, dialer)
i.wg.Done()
}(sessionID)
}
return
}
// Stop Initiator.
func (i *Initiator) Stop() {
select {
case <-i.stopChan:
// Closed already.
return
default:
}
close(i.stopChan)
i.wg.Wait()
for sessionID := range i.sessionSettings {
err := UnregisterSession(sessionID)
if err != nil {
return
}
}
}
// NewInitiator creates and initializes a new Initiator.
func NewInitiator(app Application, storeFactory MessageStoreFactory, appSettings *Settings, logFactory LogFactory) (*Initiator, error) {
i := &Initiator{
app: app,
storeFactory: storeFactory,
settings: appSettings,
sessionSettings: appSettings.SessionSettings(),
logFactory: logFactory,
sessions: make(map[SessionID]*session),
sessionFactory: sessionFactory{true},
}
var err error
i.globalLog, err = logFactory.Create()
if err != nil {
return i, err
}
for sessionID, s := range i.sessionSettings {
session, err := i.createSession(sessionID, storeFactory, s, logFactory, app)
if err != nil {
return nil, err
}
i.sessions[sessionID] = session
}
return i, nil
}
// 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() {
session.waitForInSessionTime()
close(inSessionTime)
}()
select {
case <-inSessionTime:
case <-i.stopChan:
return false
}
return true
}
// 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):
case <-i.stopChan:
return false
}
return true
}
func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, dialer proxy.ContextDialer) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
session.run()
wg.Done()
}()
defer func() {
session.stop()
wg.Wait()
}()
connectionAttempt := 0
for {
if !i.waitForInSessionTime(session) {
return
}
ctx, cancel := context.WithCancel(context.Background())
// We start a goroutine in order to be able to cancel the dialer mid-connection
// on receiving a stop signal to stop the initiator.
go func() {
select {
case <-i.stopChan:
cancel()
case <-ctx.Done():
return
}
}()
var disconnected chan interface{}
var msgIn chan fixIn
var msgOut chan []byte
address := session.SocketConnectAddress[connectionAttempt%len(session.SocketConnectAddress)]
session.log.OnEventf("Connecting to: %v", address)
netConn, err := dialer.DialContext(ctx, "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
}
tlsConn := tls.Client(netConn, tlsConfig)
if err = tlsConn.Handshake(); err != nil {
session.log.OnEventf("Failed handshake: %v", err)
goto reconnect
}
netConn = tlsConn
}
msgIn = make(chan fixIn, session.InChanCapacity)
msgOut = make(chan []byte)
if err := session.connect(msgIn, msgOut); err != nil {
session.log.OnEventf("Failed to initiate: %v", err)
goto reconnect
}
go readLoop(newParser(bufio.NewReader(netConn)), msgIn, session.log)
disconnected = make(chan interface{})
go func() {
writeLoop(netConn, msgOut, session.log)
if err := netConn.Close(); err != nil {
session.log.OnEvent(err.Error())
}
close(disconnected)
}()
// This ensures we properly cleanup the goroutine and context used for
// dial cancelation after successful connection.
cancel()
select {
case <-disconnected:
case <-i.stopChan:
return
}
reconnect:
cancel()
connectionAttempt++
session.log.OnEventf("Reconnecting in %v", session.ReconnectInterval)
if !i.waitForReconnectInterval(session.ReconnectInterval) {
return
}
}
}