Files
qfixpt/quickfix/session.go
2026-03-12 12:14:13 -03:00

951 lines
24 KiB
Go

// 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"
"errors"
"fmt"
"sync"
"time"
"quantex.com/qfixpt/quickfix/datadictionary"
"quantex.com/qfixpt/quickfix/internal"
)
// The Session is the primary FIX abstraction for message communication.
type session struct {
store MessageStore
log Log
sessionID SessionID
messageOut chan<- []byte
messageIn <-chan fixIn
// Application messages are queued up for send here.
toSend [][]byte
// Mutex for access to toSend.
sendMutex sync.Mutex
// Mutex to prevent messages being sent when resendRequest is active
// Must be locked before sendMutex to prevent a potential deadlock
resendMutex sync.RWMutex
sessionEvent chan internal.Event
messageEvent chan bool
application Application
Validator
stateMachine
stateTimer *internal.EventTimer
peerTimer *internal.EventTimer
sentReset bool
stopOnce sync.Once
targetDefaultApplVerID string
admin chan interface{}
internal.SessionSettings
transportDataDictionary *datadictionary.DataDictionary
appDataDictionary *datadictionary.DataDictionary
timestampPrecision TimestampPrecision
lastCheckedResetSeqTime time.Time
}
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.
func (s *session) TargetDefaultApplicationVersionID() string {
return s.targetDefaultApplVerID
}
type connect struct {
messageOut chan<- []byte
messageIn <-chan fixIn
err chan<- error
}
func (s *session) connect(msgIn <-chan fixIn, msgOut chan<- []byte) error {
rep := make(chan error)
s.admin <- connect{
messageOut: msgOut,
messageIn: msgIn,
err: rep,
}
return <-rep
}
type stopReq struct{}
func (s *session) stop() {
// Stop once.
s.stopOnce.Do(func() {
s.admin <- stopReq{}
})
}
type waitChan <-chan interface{}
type waitForInSessionReq struct{ rep chan<- waitChan }
func (s *session) waitForInSessionTime() {
rep := make(chan waitChan)
s.admin <- waitForInSessionReq{rep}
if wait, ok := <-rep; ok {
<-wait
}
}
func (s *session) insertSendingTime(msg *Message) {
sendingTime := time.Now().UTC()
if s.sessionID.BeginString >= BeginStringFIX42 {
msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: sendingTime, Precision: s.timestampPrecision})
} else {
msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: sendingTime, Precision: Seconds})
}
}
func optionallySetID(msg *Message, field Tag, value string) {
if len(value) != 0 {
msg.Header.SetString(field, value)
}
}
func (s *session) fillDefaultHeader(msg *Message, inReplyTo *Message) {
msg.Header.SetString(tagBeginString, s.sessionID.BeginString)
msg.Header.SetString(tagSenderCompID, s.sessionID.SenderCompID)
optionallySetID(msg, tagSenderSubID, s.sessionID.SenderSubID)
optionallySetID(msg, tagSenderLocationID, s.sessionID.SenderLocationID)
msg.Header.SetString(tagTargetCompID, s.sessionID.TargetCompID)
optionallySetID(msg, tagTargetSubID, s.sessionID.TargetSubID)
optionallySetID(msg, tagTargetLocationID, s.sessionID.TargetLocationID)
s.insertSendingTime(msg)
if s.EnableLastMsgSeqNumProcessed {
if inReplyTo != nil {
if lastSeqNum, err := inReplyTo.Header.GetInt(tagMsgSeqNum); err != nil {
s.logError(err)
} else {
msg.Header.SetInt(tagLastMsgSeqNumProcessed, lastSeqNum)
}
} else {
msg.Header.SetInt(tagLastMsgSeqNumProcessed, s.store.NextTargetMsgSeqNum()-1)
}
}
}
func (s *session) shouldSendReset() bool {
if s.sessionID.BeginString < BeginStringFIX41 {
return false
}
return (s.ResetOnLogon || s.ResetOnDisconnect || s.ResetOnLogout) &&
s.store.NextTargetMsgSeqNum() == 1 && s.store.NextSenderMsgSeqNum() == 1
}
func (s *session) sendLogon() error {
return s.sendLogonInReplyTo(s.shouldSendReset(), nil)
}
func (s *session) sendLogonInReplyTo(setResetSeqNum bool, inReplyTo *Message) error {
logon := NewMessage()
logon.Header.SetField(tagMsgType, FIXString("A"))
logon.Header.SetField(tagBeginString, FIXString(s.sessionID.BeginString))
logon.Header.SetField(tagTargetCompID, FIXString(s.sessionID.TargetCompID))
logon.Header.SetField(tagSenderCompID, FIXString(s.sessionID.SenderCompID))
logon.Body.SetField(tagEncryptMethod, FIXString("0"))
logon.Body.SetField(tagHeartBtInt, FIXInt(s.HeartBtInt.Seconds()))
if setResetSeqNum {
logon.Body.SetField(tagResetSeqNumFlag, FIXBoolean(true))
}
if len(s.DefaultApplVerID) > 0 {
logon.Body.SetField(tagDefaultApplVerID, FIXString(s.DefaultApplVerID))
}
// Evaluate tag 789.
if s.EnableNextExpectedMsgSeqNum {
if inReplyTo != nil {
targetWantsNextSeqNumToBe, getErr := inReplyTo.Body.GetInt(tagNextExpectedMsgSeqNum)
if getErr == nil {
actualNextNum := s.store.NextSenderMsgSeqNum()
// // Is the 789 we received too high ??
if targetWantsNextSeqNumToBe > actualNextNum {
// we can't resend what we never sent! something unrecoverable has happened.
return RejectLogon{fmt.Sprintf("Tag 789 (NextExpectedMsgSeqNum) is higher than expected. Expected %d, Received %d", actualNextNum, targetWantsNextSeqNumToBe)}
}
nextSeqNum := s.store.NextTargetMsgSeqNum()
logon.Body.SetField(tagNextExpectedMsgSeqNum, FIXInt(nextSeqNum+1))
}
} else {
// We are sending a logon.
nextseqnum := s.store.NextTargetMsgSeqNum()
logon.Body.SetField(tagNextExpectedMsgSeqNum, FIXInt(nextseqnum+1))
}
}
if err := s.dropAndSendInReplyTo(logon, inReplyTo); err != nil {
return err
}
return nil
}
func (s *session) generateSequenceReset(beginSeqNo int, endSeqNo int, inReplyTo Message) (err error) {
sequenceReset := NewMessage()
s.fillDefaultHeader(sequenceReset, &inReplyTo)
sequenceReset.Header.SetField(tagMsgType, FIXString("4"))
sequenceReset.Header.SetField(tagMsgSeqNum, FIXInt(beginSeqNo))
sequenceReset.Header.SetField(tagPossDupFlag, FIXBoolean(true))
sequenceReset.Body.SetField(tagNewSeqNo, FIXInt(endSeqNo))
sequenceReset.Body.SetField(tagGapFillFlag, FIXBoolean(true))
var origSendingTime FIXString
if err := sequenceReset.Header.GetField(tagSendingTime, &origSendingTime); err == nil {
sequenceReset.Header.SetField(tagOrigSendingTime, origSendingTime)
}
s.application.ToAdmin(sequenceReset, s.sessionID)
msgBytes := sequenceReset.build()
s.EnqueueBytesAndSend(msgBytes)
s.log.OnEventf("Sent SequenceReset TO: %v", endSeqNo)
return
}
func (s *session) buildLogout(reason string) *Message {
logout := NewMessage()
logout.Header.SetField(tagMsgType, FIXString("5"))
logout.Header.SetField(tagBeginString, FIXString(s.sessionID.BeginString))
logout.Header.SetField(tagTargetCompID, FIXString(s.sessionID.TargetCompID))
logout.Header.SetField(tagSenderCompID, FIXString(s.sessionID.SenderCompID))
if reason != "" {
logout.Body.SetField(tagText, FIXString(reason))
}
return logout
}
func (s *session) sendLogout(reason string) error {
return s.sendLogoutInReplyTo(reason, nil)
}
func (s *session) sendLogoutInReplyTo(reason string, inReplyTo *Message) error {
logout := s.buildLogout(reason)
return s.sendInReplyTo(logout, inReplyTo)
}
func (s *session) resend(msg *Message) bool {
msg.Header.SetField(tagPossDupFlag, FIXBoolean(true))
var origSendingTime FIXString
if err := msg.Header.GetField(tagSendingTime, &origSendingTime); err == nil {
msg.Header.SetField(tagOrigSendingTime, origSendingTime)
}
s.insertSendingTime(msg)
return s.application.ToApp(msg, s.sessionID) == nil
}
// queueForSend will validate, persist, and queue the message for send.
func (s *session) queueForSend(msg *Message) error {
s.sendMutex.Lock()
defer s.sendMutex.Unlock()
msgBytes, err := s.prepMessageForSend(msg, nil)
if err != nil {
return err
}
s.toSend = append(s.toSend, msgBytes)
s.notifyMessageOut()
return nil
}
func (s *session) notifyMessageOut() {
select {
case s.messageEvent <- true:
default:
}
}
// 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)
}
func (s *session) sendInReplyTo(msg *Message, inReplyTo *Message) error {
if !s.IsLoggedOn() {
return s.queueForSend(msg)
}
// resendMutex must always be locked before sendMutex to prevent a potential deadlock
s.resendMutex.RLock()
defer s.resendMutex.RUnlock()
s.sendMutex.Lock()
defer s.sendMutex.Unlock()
msgBytes, err := s.prepMessageForSend(msg, inReplyTo)
if err != nil {
return err
}
s.toSend = append(s.toSend, msgBytes)
s.sendQueued(true)
return nil
}
// dropAndReset will drop the send queue and reset the message store.
func (s *session) dropAndReset() error {
s.sendMutex.Lock()
defer s.sendMutex.Unlock()
s.dropQueued()
return s.store.Reset()
}
// 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)
}
func (s *session) dropAndSendInReplyTo(msg *Message, inReplyTo *Message) error {
s.sendMutex.Lock()
defer s.sendMutex.Unlock()
msgBytes, err := s.prepMessageForSend(msg, inReplyTo)
if err != nil {
return err
}
s.dropQueued()
s.toSend = append(s.toSend, msgBytes)
s.sendQueued(true)
return nil
}
func (s *session) prepMessageForSend(msg *Message, inReplyTo *Message) (msgBytes []byte, err error) {
s.fillDefaultHeader(msg, inReplyTo)
seqNum := s.store.NextSenderMsgSeqNum()
msg.Header.SetField(tagMsgSeqNum, FIXInt(seqNum))
msgType, err := msg.Header.GetBytes(tagMsgType)
if err != nil {
return
}
if isAdminMessageType(msgType) {
s.application.ToAdmin(msg, s.sessionID)
if bytes.Equal(msgType, msgTypeLogon) {
var resetSeqNumFlag FIXBoolean
if msg.Body.Has(tagResetSeqNumFlag) {
if err = msg.Body.GetField(tagResetSeqNumFlag, &resetSeqNumFlag); err != nil {
return
}
}
if resetSeqNumFlag.Bool() {
if err = s.store.Reset(); err != nil {
return
}
s.sentReset = true
seqNum = s.store.NextSenderMsgSeqNum()
msg.Header.SetField(tagMsgSeqNum, FIXInt(seqNum))
}
}
} else {
if err = s.application.ToApp(msg, s.sessionID); err != nil {
return
}
}
// Message converted to bytes here.
msgBytes = msg.build()
err = s.persist(seqNum, msgBytes)
return
}
func (s *session) persist(seqNum int, msgBytes []byte) error {
if !s.DisableMessagePersist {
return s.store.SaveMessageAndIncrNextSenderMsgSeqNum(seqNum, msgBytes)
}
return s.store.IncrNextSenderMsgSeqNum()
}
func (s *session) sendQueued(blockUntilSent bool) {
for i, msgBytes := range s.toSend {
if !s.sendBytes(msgBytes, blockUntilSent) {
s.toSend = s.toSend[i:]
s.notifyMessageOut()
return
}
}
s.dropQueued()
}
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(true)
}
func (s *session) sendBytes(msg []byte, blockUntilSent bool) bool {
if s.messageOut == nil {
s.log.OnEventf("Failed to send: disconnected")
return false
}
if blockUntilSent {
s.messageOut <- msg
s.log.OnOutgoing(msg)
s.stateTimer.Reset(s.HeartBtInt)
return true
}
select {
case s.messageOut <- msg:
s.log.OnOutgoing(msg)
s.stateTimer.Reset(s.HeartBtInt)
return true
default:
return false
}
}
func (s *session) doTargetTooHigh(reject targetTooHigh) (nextState resendState, err error) {
s.log.OnEventf("MsgSeqNum too high, expecting %v but received %v", reject.ExpectedTarget, reject.ReceivedTarget)
return s.sendResendRequest(reject.ExpectedTarget, reject.ReceivedTarget-1)
}
func (s *session) sendResendRequest(beginSeq, endSeq int) (nextState resendState, err error) {
nextState.resendRangeEnd = endSeq
resend := NewMessage()
resend.Header.SetBytes(tagMsgType, msgTypeResendRequest)
resend.Body.SetField(tagBeginSeqNo, FIXInt(beginSeq))
var endSeqNo int
if s.ResendRequestChunkSize != 0 {
endSeqNo = beginSeq + s.ResendRequestChunkSize - 1
} else {
endSeqNo = endSeq
}
if endSeqNo < endSeq {
nextState.currentResendRangeEnd = endSeqNo
} else {
if s.sessionID.BeginString < BeginStringFIX42 {
endSeqNo = 999999
} else {
endSeqNo = 0
}
}
resend.Body.SetField(tagEndSeqNo, FIXInt(endSeqNo))
if err = s.send(resend); err != nil {
return
}
s.log.OnEventf("Sent ResendRequest FROM: %v TO: %v", beginSeq, endSeqNo)
return
}
func (s *session) handleLogon(msg *Message) error {
// Grab default app ver id from fixt.1.1 logon.
if s.sessionID.BeginString == BeginStringFIXT11 {
var targetApplVerID FIXString
if err := msg.Body.GetField(tagDefaultApplVerID, &targetApplVerID); err != nil {
return err
}
s.targetDefaultApplVerID = string(targetApplVerID)
}
resetStore := false
if s.InitiateLogon {
s.log.OnEvent("Received logon response")
} else {
s.log.OnEvent("Received logon request")
resetStore = s.ResetOnLogon
if s.RefreshOnLogon {
if err := s.store.Refresh(); err != nil {
return err
}
}
}
nextSenderMsgNumAtLogonReceived := s.store.NextSenderMsgSeqNum()
// Make sure this is a valid session before resetting the store.
if err := s.verifyMsgAgainstAppImpl(msg); err != nil {
return err
}
var resetSeqNumFlag FIXBoolean
if err := msg.Body.GetField(tagResetSeqNumFlag, &resetSeqNumFlag); err == nil {
if resetSeqNumFlag {
if !s.sentReset {
s.log.OnEvent("Logon contains ResetSeqNumFlag=Y, resetting sequence numbers to 1")
resetStore = true
}
}
}
if resetStore {
if err := s.store.Reset(); err != nil {
return err
}
}
// Verify seq num too high but dont check against app implementation since we just did that.
// Don't need to double check.
if err := s.verifyIgnoreSeqNumTooHigh(msg); err != nil {
return err
}
if !s.InitiateLogon {
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")
if err := s.sendLogonInReplyTo(resetSeqNumFlag.Bool(), msg); err != nil {
return err
}
}
s.sentReset = false
s.peerTimer.Reset(time.Duration(float64(1.2) * float64(s.HeartBtInt)))
s.application.OnLogon(s.sessionID)
// Evaluate tag 789 to see if we end up with an implied gapfill/resend.
if s.EnableNextExpectedMsgSeqNum && !msg.Body.Has(tagResetSeqNumFlag) {
targetWantsNextSeqNumToBe, getErr := msg.Body.GetInt(tagNextExpectedMsgSeqNum)
if getErr == nil {
if targetWantsNextSeqNumToBe != nextSenderMsgNumAtLogonReceived {
if !s.DisableMessagePersist {
seqResetErr := s.generateSequenceReset(targetWantsNextSeqNumToBe, nextSenderMsgNumAtLogonReceived+1, *msg)
if seqResetErr != nil {
return seqResetErr
}
} else {
return targetTooHigh{ReceivedTarget: targetWantsNextSeqNumToBe, ExpectedTarget: nextSenderMsgNumAtLogonReceived}
}
}
}
}
if err := s.checkTargetTooHigh(msg); err != nil {
return err
}
return s.store.IncrNextTargetMsgSeqNum()
}
func (s *session) initiateLogout(reason string) (err error) {
return s.initiateLogoutInReplyTo(reason, nil)
}
func (s *session) initiateLogoutInReplyTo(reason string, inReplyTo *Message) (err error) {
if err = s.sendLogoutInReplyTo(reason, inReplyTo); err != nil {
s.logError(err)
return
}
s.log.OnEvent("Inititated logout request")
time.AfterFunc(s.LogoutTimeout, func() { s.sessionEvent <- internal.LogoutTimeout })
return
}
func (s *session) verify(msg *Message) MessageRejectError {
return s.verifySelect(msg, true, true, true)
}
func (s *session) verifyIgnoreSeqNumTooHigh(msg *Message) MessageRejectError {
return s.verifySelect(msg, false, true, false)
}
func (s *session) verifyIgnoreSeqNumTooHighOrLow(msg *Message) MessageRejectError {
return s.verifySelect(msg, false, false, true)
}
func (s *session) verifySelect(msg *Message, checkTooHigh bool, checkTooLow bool, checkAppImpl bool) MessageRejectError {
if reject := s.checkBeginString(msg); reject != nil {
return reject
}
if reject := s.checkCompID(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
}
}
if checkTooHigh {
if reject := s.checkTargetTooHigh(msg); reject != nil {
return reject
}
}
if checkAppImpl {
return s.verifyMsgAgainstAppImpl(msg)
}
return nil
}
func (s *session) verifyMsgAgainstAppImpl(msg *Message) MessageRejectError {
if s.Validator != nil {
if reject := s.Validator.Validate(msg); reject != nil {
return reject
}
}
return s.fromCallback(msg)
}
func (s *session) fromCallback(msg *Message) MessageRejectError {
msgType, err := msg.Header.GetBytes(tagMsgType)
if err != nil {
return err
}
if isAdminMessageType(msgType) {
return s.application.FromAdmin(msg, s.sessionID)
}
return s.application.FromApp(msg, s.sessionID)
}
func (s *session) checkTargetTooLow(msg *Message) MessageRejectError {
if !msg.Header.Has(tagMsgSeqNum) {
return RequiredTagMissing(tagMsgSeqNum)
}
seqNum, err := msg.Header.GetInt(tagMsgSeqNum)
if err != nil {
return err
}
if seqNum < s.store.NextTargetMsgSeqNum() {
return targetTooLow{ReceivedTarget: seqNum, ExpectedTarget: s.store.NextTargetMsgSeqNum()}
}
return nil
}
func (s *session) checkTargetTooHigh(msg *Message) MessageRejectError {
if !msg.Header.Has(tagMsgSeqNum) {
return RequiredTagMissing(tagMsgSeqNum)
}
seqNum, err := msg.Header.GetInt(tagMsgSeqNum)
if err != nil {
return err
}
if seqNum > s.store.NextTargetMsgSeqNum() {
return targetTooHigh{ReceivedTarget: seqNum, ExpectedTarget: s.store.NextTargetMsgSeqNum()}
}
return nil
}
func (s *session) checkCompID(msg *Message) MessageRejectError {
senderCompID, haveSender := msg.Header.GetBytes(tagSenderCompID)
targetCompID, haveTarget := msg.Header.GetBytes(tagTargetCompID)
switch {
case haveSender != nil:
return RequiredTagMissing(tagSenderCompID)
case haveTarget != nil:
return RequiredTagMissing(tagTargetCompID)
case len(targetCompID) == 0:
return TagSpecifiedWithoutAValue(tagTargetCompID)
case len(senderCompID) == 0:
return TagSpecifiedWithoutAValue(tagSenderCompID)
case s.sessionID.SenderCompID != string(targetCompID) || s.sessionID.TargetCompID != string(senderCompID):
return compIDProblem()
}
return nil
}
func (s *session) checkSendingTime(msg *Message) MessageRejectError {
if s.SkipCheckLatency {
return nil
}
if ok := msg.Header.Has(tagSendingTime); !ok {
return RequiredTagMissing(tagSendingTime)
}
sendingTime, err := msg.Header.GetTime(tagSendingTime)
if err != nil {
return err
}
if delta := time.Since(sendingTime); delta <= -1*s.MaxLatency || delta >= s.MaxLatency {
return sendingTimeAccuracyProblem()
}
return nil
}
func (s *session) checkBeginString(msg *Message) MessageRejectError {
switch beginString, err := msg.Header.GetBytes(tagBeginString); {
case err != nil:
return RequiredTagMissing(tagBeginString)
case s.sessionID.BeginString != string(beginString):
return incorrectBeginString{}
}
return nil
}
func (s *session) drainMessageIn() {
s.log.OnEventf("Draining %d messages from inbound channel...", len(s.messageIn))
for {
select {
case fixInc, ok := <-s.messageIn:
if !ok {
return
}
s.Incoming(s, fixInc)
default:
return
}
}
}
func (s *session) doReject(msg *Message, rej MessageRejectError) error {
reply := msg.reverseRoute()
if s.sessionID.BeginString >= BeginStringFIX42 {
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.
}
if refTagID := rej.RefTagID(); refTagID != nil {
reply.Body.SetField(tagRefTagID, FIXInt(*refTagID))
}
}
reply.Body.SetField(tagText, FIXString(rej.Error()))
var msgType FIXString
if err := msg.Header.GetField(tagMsgType, &msgType); err == nil {
reply.Body.SetField(tagRefMsgType, msgType)
}
} else {
reply.Header.SetField(tagMsgType, FIXString("3"))
if refTagID := rej.RefTagID(); refTagID != nil {
reply.Body.SetField(tagText, FIXString(fmt.Sprintf("%s (%d)", rej.Error(), *refTagID)))
} else {
reply.Body.SetField(tagText, FIXString(rej.Error()))
}
}
seqNum := new(FIXInt)
if err := msg.Header.GetField(tagMsgSeqNum, seqNum); err == nil {
reply.Body.SetField(tagRefSeqNum, seqNum)
}
s.log.OnEventf("Message Rejected: %v", rej.Error())
return s.sendInReplyTo(reply, msg)
}
type fixIn struct {
bytes *bytes.Buffer
receiveTime time.Time
}
func (s *session) onDisconnect() {
s.log.OnEvent("Disconnected")
if s.ResetOnDisconnect {
if err := s.dropAndReset(); err != nil {
s.logError(err)
}
}
if s.messageOut != nil {
close(s.messageOut)
s.messageOut = nil
}
// s.messageIn is buffered so we need to drain it before disconnection
s.drainMessageIn()
s.messageIn = nil
}
func (s *session) onAdmin(msg interface{}) {
switch msg := msg.(type) {
case connect:
if s.IsConnected() {
if msg.err != nil {
msg.err <- errors.New("Already connected")
close(msg.err)
}
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)
}
s.messageIn = msg.messageIn
s.messageOut = msg.messageOut
s.sentReset = false
s.Connect(s)
case stopReq:
s.Stop(s)
case waitForInSessionReq:
if !s.IsSessionTime() {
msg.rep <- s.stateMachine.notifyOnInSessionTime
}
close(msg.rep)
}
}
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)))
ticker := time.NewTicker(time.Second)
defer func() {
close(stopChan)
s.stateTimer.Stop()
s.peerTimer.Stop()
ticker.Stop()
}()
for !s.Stopped() {
select {
case msg := <-s.admin:
s.onAdmin(msg)
case <-s.messageEvent:
s.SendAppMessages(s)
case fixIn, ok := <-s.messageIn:
if !ok {
s.Disconnected(s)
} else {
s.Incoming(s, fixIn)
}
case evt := <-s.sessionEvent:
s.Timeout(s, evt)
case now := <-ticker.C:
s.CheckSessionTime(s, now)
s.CheckResetTime(s, now)
}
}
}