From c6cca73b1c65b4f0350e08b493671458438c3e19 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Tue, 14 Apr 2026 15:38:33 -0500 Subject: [PATCH] the final file-scoped namespace changes --- QuickFIXn/AbstractInitiator.cs | 697 +++--- QuickFIXn/ClientHandlerThread.cs | 173 +- QuickFIXn/Session.cs | 2945 ++++++++++++------------ QuickFIXn/SessionState.cs | 705 +++--- QuickFIXn/SocketSettings.cs | 433 ++-- QuickFIXn/ThreadedSocketAcceptor.cs | 715 +++--- QuickFIXn/ThreadedSocketReactor.cs | 283 ++- QuickFIXn/Transport/SocketInitiator.cs | 385 ++-- QuickFIXn/Transport/StreamFactory.cs | 213 +- 9 files changed, 3270 insertions(+), 3279 deletions(-) diff --git a/QuickFIXn/AbstractInitiator.cs b/QuickFIXn/AbstractInitiator.cs index 770e3c7b3..f85868bc6 100644 --- a/QuickFIXn/AbstractInitiator.cs +++ b/QuickFIXn/AbstractInitiator.cs @@ -5,432 +5,431 @@ using QuickFix.Logger; using QuickFix.Store; -namespace QuickFix +namespace QuickFix; + +public abstract class AbstractInitiator : IInitiator { - public abstract class AbstractInitiator : IInitiator + // from constructor + private readonly SessionSettings _settings; + + private readonly object _sync = new(); + private readonly Dictionary _sessions = new(); + private readonly HashSet _sessionIDs = []; + private readonly HashSet _pending = []; + private readonly HashSet _connected = []; + private readonly HashSet _disconnected = []; + private readonly SessionFactory _sessionFactory; + private Thread? _thread; + + internal readonly IQuickFixLoggerFactory QfLoggerFactory; + private readonly LogFactoryAdapter? _logFactoryAdapter; + + public bool IsStopped { get; private set; } = true; + + protected AbstractInitiator( + IApplication app, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILogFactory? logFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) + : this( + app, + storeFactory, + settings, + logFactoryNullable is null + ? NullQuickFixLoggerFactory.Instance + : new LogFactoryAdapter(logFactoryNullable), + messageFactoryNullable) + { } + + protected AbstractInitiator( + IApplication app, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILoggerFactory? loggerFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) + : this( + app, + storeFactory, + settings, + loggerFactoryNullable is null + ? NullQuickFixLoggerFactory.Instance + : new MelQuickFixLoggerFactory(loggerFactoryNullable), + messageFactoryNullable) + { } + + private AbstractInitiator( + IApplication app, + IMessageStoreFactory storeFactory, + SessionSettings settings, + IQuickFixLoggerFactory qfLoggerFactory, + IMessageFactory? messageFactoryNullable = null) { - // from constructor - private readonly SessionSettings _settings; - - private readonly object _sync = new(); - private readonly Dictionary _sessions = new(); - private readonly HashSet _sessionIDs = new(); - private readonly HashSet _pending = new(); - private readonly HashSet _connected = new(); - private readonly HashSet _disconnected = new(); - private readonly SessionFactory _sessionFactory; - private Thread? _thread; - - internal readonly IQuickFixLoggerFactory QfLoggerFactory; - private readonly LogFactoryAdapter? _logFactoryAdapter; - - public bool IsStopped { get; private set; } = true; - - protected AbstractInitiator( - IApplication app, - IMessageStoreFactory storeFactory, - SessionSettings settings, - ILogFactory? logFactoryNullable = null, - IMessageFactory? messageFactoryNullable = null) - : this( - app, - storeFactory, - settings, - logFactoryNullable is null - ? NullQuickFixLoggerFactory.Instance - : new LogFactoryAdapter(logFactoryNullable), - messageFactoryNullable) - { } - - protected AbstractInitiator( - IApplication app, - IMessageStoreFactory storeFactory, - SessionSettings settings, - ILoggerFactory? loggerFactoryNullable = null, - IMessageFactory? messageFactoryNullable = null) - : this( - app, - storeFactory, - settings, - loggerFactoryNullable is null - ? NullQuickFixLoggerFactory.Instance - : new MelQuickFixLoggerFactory(loggerFactoryNullable), - messageFactoryNullable) - { } - - private AbstractInitiator( - IApplication app, - IMessageStoreFactory storeFactory, - SessionSettings settings, - IQuickFixLoggerFactory qfLoggerFactory, - IMessageFactory? messageFactoryNullable = null) + _settings = settings; + if (qfLoggerFactory is LogFactoryAdapter lfa) { - _settings = settings; - if (qfLoggerFactory is LogFactoryAdapter lfa) - { - // LogFactoryAdapter is only created in the constructor that takes ILogFactory, - // which means we own it and must save a ref to it so we can dispose it later. - // Any other loggerFactory is owned by someone else - // so we'll leave the dispose up to them. - _logFactoryAdapter = lfa; - } - var msgFactory = messageFactoryNullable ?? new DefaultMessageFactory(); - _sessionFactory = new SessionFactory(app, storeFactory, qfLoggerFactory, msgFactory); - QfLoggerFactory = qfLoggerFactory; - - HashSet definedSessions = _settings.GetSessions(); - if (0 == definedSessions.Count) - throw new ConfigError("No sessions defined"); + // LogFactoryAdapter is only created in the constructor that takes ILogFactory, + // which means we own it and must save a ref to it so we can dispose it later. + // Any other loggerFactory is owned by someone else + // so we'll leave the dispose up to them. + _logFactoryAdapter = lfa; } + var msgFactory = messageFactoryNullable ?? new DefaultMessageFactory(); + _sessionFactory = new SessionFactory(app, storeFactory, qfLoggerFactory, msgFactory); + QfLoggerFactory = qfLoggerFactory; + + HashSet definedSessions = _settings.GetSessions(); + if (0 == definedSessions.Count) + throw new ConfigError("No sessions defined"); + } + + public void Start() + { + if (_disposed) + throw new ObjectDisposedException(this.GetType().Name); - public void Start() + // create all sessions + foreach (SessionID sessionId in _settings.GetSessions()) { - if (_disposed) - throw new ObjectDisposedException(this.GetType().Name); + SettingsDictionary dict = _settings.Get(sessionId); + CreateSession(sessionId, dict); + } - // create all sessions - foreach (SessionID sessionId in _settings.GetSessions()) - { - SettingsDictionary dict = _settings.Get(sessionId); - CreateSession(sessionId, dict); - } + if (0 == _sessions.Count) + throw new ConfigError("No sessions defined for initiator"); - if (0 == _sessions.Count) - throw new ConfigError("No sessions defined for initiator"); + // start it up + IsStopped = false; + OnConfigure(_settings); + _thread = new Thread(OnStart); + _thread.Start(); + } - // start it up - IsStopped = false; - OnConfigure(_settings); - _thread = new Thread(OnStart); - _thread.Start(); - } + /// + /// Add new session as an ad-hoc (dynamic) operation + /// + /// ID of new session + /// config settings for new session + /// true if session added successfully, false if session already exists or is not an initiator + public bool AddSession(SessionID sessionId, SettingsDictionary dict) + { + lock (_settings) + if (!_settings.Has(sessionId)) // session won't be in settings if ad-hoc creation after startup + _settings.Set(sessionId, dict); // need to to this here to merge in default config settings + else + return false; // session already exists - /// - /// Add new session as an ad-hoc (dynamic) operation - /// - /// ID of new session - /// config settings for new session - /// true if session added successfully, false if session already exists or is not an initiator - public bool AddSession(SessionID sessionId, SettingsDictionary dict) - { - lock (_settings) - if (!_settings.Has(sessionId)) // session won't be in settings if ad-hoc creation after startup - _settings.Set(sessionId, dict); // need to to this here to merge in default config settings - else - return false; // session already exists + if (CreateSession(sessionId, dict)) + return true; - if (CreateSession(sessionId, dict)) - return true; + lock (_settings) // failed to create new session + _settings.Remove(sessionId); + return false; + } - lock (_settings) // failed to create new session - _settings.Remove(sessionId); - return false; + /// + /// Create session, either at start-up or as an ad-hoc operation + /// + /// ID of new session + /// config settings for new session + /// true if session added successfully, false if session already exists or is not an initiator + private bool CreateSession(SessionID sessionId, SettingsDictionary dict) + { + if (dict.GetString(SessionSettings.CONNECTION_TYPE) == "initiator" && !_sessionIDs.Contains(sessionId)) + { + Session session = _sessionFactory.Create(sessionId, dict); + lock (_sync) + { + _sessionIDs.Add(sessionId); + _sessions[sessionId] = session; + SetDisconnected(sessionId); + } + return true; } + return false; + } - /// - /// Create session, either at start-up or as an ad-hoc operation - /// - /// ID of new session - /// config settings for new session - /// true if session added successfully, false if session already exists or is not an initiator - private bool CreateSession(SessionID sessionId, SettingsDictionary dict) + /// + /// Ad-hoc removal of an existing session + /// + /// ID of session to be removed + /// if true, force disconnection and removal of session even if it has an active connection + /// true if session removed or not already present; false if could not be removed due to an active connection + public bool RemoveSession(SessionID sessionId, bool terminateActiveSession) + { + Session? session = null; + bool disconnectRequired = false; + lock (_sync) { - if (dict.GetString(SessionSettings.CONNECTION_TYPE) == "initiator" && !_sessionIDs.Contains(sessionId)) + if (_sessionIDs.Contains(sessionId)) { - Session session = _sessionFactory.Create(sessionId, dict); - lock (_sync) - { - _sessionIDs.Add(sessionId); - _sessions[sessionId] = session; + session = _sessions[sessionId]; + if (session.IsLoggedOn && !terminateActiveSession) + return false; + _sessions.Remove(sessionId); + disconnectRequired = IsConnected(sessionId) || IsPending(sessionId); + if (disconnectRequired) SetDisconnected(sessionId); - } - return true; + _disconnected.Remove(sessionId); + _sessionIDs.Remove(sessionId); } - return false; } + lock (_settings) + _settings.Remove(sessionId); + if (disconnectRequired) + session?.Disconnect("Dynamic session removal"); + OnRemove(sessionId); // ensure session's reader thread is gone before we dispose session + session?.Dispose(); + + return true; + } - /// - /// Ad-hoc removal of an existing session - /// - /// ID of session to be removed - /// if true, force disconnection and removal of session even if it has an active connection - /// true if session removed or not already present; false if could not be removed due to an active connection - public bool RemoveSession(SessionID sessionId, bool terminateActiveSession) + /// + /// Logout existing session and close connection. Attempt graceful disconnect first. + /// + public void Stop() + { + Stop(false); + } + + /// + /// Logout existing session and close connection + /// + /// If true, terminate immediately. + public void Stop(bool force) + { + if (_disposed) + throw new ObjectDisposedException(this.GetType().Name); + + if (IsStopped) + return; + + lock (_sync) { - Session? session = null; - bool disconnectRequired = false; - lock (_sync) + foreach (SessionID sessionId in _connected) { - if (_sessionIDs.Contains(sessionId)) + Session? session = Session.LookupSession(sessionId); + if (session is not null && session.IsEnabled) { - session = _sessions[sessionId]; - if (session.IsLoggedOn && !terminateActiveSession) - return false; - _sessions.Remove(sessionId); - disconnectRequired = IsConnected(sessionId) || IsPending(sessionId); - if (disconnectRequired) - SetDisconnected(sessionId); - _disconnected.Remove(sessionId); - _sessionIDs.Remove(sessionId); + session.Logout(); } } - lock (_settings) - _settings.Remove(sessionId); - if (disconnectRequired) - session?.Disconnect("Dynamic session removal"); - OnRemove(sessionId); // ensure session's reader thread is gone before we dispose session - session?.Dispose(); + } - return true; + if (!force) + { + // TODO change this duration to always exceed LogoutTimeout setting + for (int second = 0; (second < 10) && IsLoggedOn; ++second) + Thread.Sleep(1000); } - /// - /// Logout existing session and close connection. Attempt graceful disconnect first. - /// - public void Stop() + lock (_sync) { - Stop(false); + HashSet connectedSessionIDs = new HashSet(_connected); + foreach (SessionID sessionId in connectedSessionIDs) { + Session? session = Session.LookupSession(sessionId); + if (session is not null) + SetDisconnected(session.SessionID); + } } - /// - /// Logout existing session and close connection - /// - /// If true, terminate immediately. - public void Stop(bool force) + IsStopped = true; + OnStop(); + + // Give OnStop() time to finish its business + _thread?.Join(5000); + _thread = null; + + // dispose all sessions and clear all session sets + lock (_sync) { - if (_disposed) - throw new ObjectDisposedException(this.GetType().Name); + foreach (Session s in _sessions.Values) + s.Dispose(); + + _sessions.Clear(); + _sessionIDs.Clear(); + _pending.Clear(); + _connected.Clear(); + _disconnected.Clear(); + } - if (IsStopped) - return; + } + public bool IsLoggedOn + { + get + { lock (_sync) { foreach (SessionID sessionId in _connected) { Session? session = Session.LookupSession(sessionId); - if (session is not null && session.IsEnabled) - { - session.Logout(); - } + return session is not null && session.IsLoggedOn; } } - if (!force) - { - // TODO change this duration to always exceed LogoutTimeout setting - for (int second = 0; (second < 10) && IsLoggedOn; ++second) - Thread.Sleep(1000); - } - - lock (_sync) - { - HashSet connectedSessionIDs = new HashSet(_connected); - foreach (SessionID sessionId in connectedSessionIDs) { - Session? session = Session.LookupSession(sessionId); - if (session is not null) - SetDisconnected(session.SessionID); - } - } - - IsStopped = true; - OnStop(); - - // Give OnStop() time to finish its business - _thread?.Join(5000); - _thread = null; - - // dispose all sessions and clear all session sets - lock (_sync) - { - foreach (Session s in _sessions.Values) - s.Dispose(); - - _sessions.Clear(); - _sessionIDs.Clear(); - _pending.Clear(); - _connected.Clear(); - _disconnected.Clear(); - } - + return false; } + } - public bool IsLoggedOn + #region Virtual Methods + + /// + /// Override this to configure additional implemenation-specific settings + /// + /// + protected virtual void OnConfigure(SessionSettings settings) + { } + + /// + /// Implement this to provide custom reaction behavior to an ad-hoc session removal. + /// (This is called after the session is removed.) + /// + /// ID of session that was removed + protected virtual void OnRemove(SessionID sessionId) + { } + + #endregion + + #region Abstract Methods + + /// + /// Implemented to start connecting to targets. + /// + protected abstract void OnStart(); + /// + /// Implemented to connect and poll for events. + /// + /// + /// + protected abstract bool OnPoll(double timeout); + /// + /// Implemented to stop a running initiator. + /// + protected abstract void OnStop(); + + /// + /// Implemented to connect a session to its target. + /// + /// + /// + protected abstract void DoConnect(Session session, QuickFix.SettingsDictionary settings); + + #endregion + + #region Protected Methods + + protected void Connect() + { + lock (_sync) { - get + HashSet disconnectedSessions = new HashSet(_disconnected); + foreach (SessionID sessionId in disconnectedSessions) { - lock (_sync) + Session? session = Session.LookupSession(sessionId); + if (session is not null && session.IsEnabled) { - foreach (SessionID sessionId in _connected) - { - Session? session = Session.LookupSession(sessionId); - return session is not null && session.IsLoggedOn; - } + if (session.IsNewSession) + session.Reset("New session"); + if (session.IsSessionTime) + DoConnect(session, _settings.Get(sessionId)); } - - return false; } } + } - #region Virtual Methods - - /// - /// Override this to configure additional implemenation-specific settings - /// - /// - protected virtual void OnConfigure(SessionSettings settings) - { } - - /// - /// Implement this to provide custom reaction behavior to an ad-hoc session removal. - /// (This is called after the session is removed.) - /// - /// ID of session that was removed - protected virtual void OnRemove(SessionID sessionId) - { } - - #endregion - - #region Abstract Methods - - /// - /// Implemented to start connecting to targets. - /// - protected abstract void OnStart(); - /// - /// Implemented to connect and poll for events. - /// - /// - /// - protected abstract bool OnPoll(double timeout); - /// - /// Implemented to stop a running initiator. - /// - protected abstract void OnStop(); - - /// - /// Implemented to connect a session to its target. - /// - /// - /// - protected abstract void DoConnect(Session session, QuickFix.SettingsDictionary settings); - - #endregion - - #region Protected Methods - - protected void Connect() + protected void SetPending(SessionID sessionId) + { + lock (_sync) { - lock (_sync) - { - HashSet disconnectedSessions = new HashSet(_disconnected); - foreach (SessionID sessionId in disconnectedSessions) - { - Session? session = Session.LookupSession(sessionId); - if (session is not null && session.IsEnabled) - { - if (session.IsNewSession) - session.Reset("New session"); - if (session.IsSessionTime) - DoConnect(session, _settings.Get(sessionId)); - } - } - } + _pending.Add(sessionId); + _connected.Remove(sessionId); + _disconnected.Remove(sessionId); } + } - protected void SetPending(SessionID sessionId) + protected void SetConnected(SessionID sessionId) + { + lock (_sync) { - lock (_sync) - { - _pending.Add(sessionId); - _connected.Remove(sessionId); - _disconnected.Remove(sessionId); - } + _pending.Remove(sessionId); + _connected.Add(sessionId); + _disconnected.Remove(sessionId); } + } - protected void SetConnected(SessionID sessionId) + protected void SetDisconnected(SessionID sessionId) + { + lock (_sync) { - lock (_sync) + if (_sessionIDs.Contains(sessionId)) { _pending.Remove(sessionId); - _connected.Add(sessionId); - _disconnected.Remove(sessionId); - } - } - - protected void SetDisconnected(SessionID sessionId) - { - lock (_sync) - { - if (_sessionIDs.Contains(sessionId)) - { - _pending.Remove(sessionId); - _connected.Remove(sessionId); - _disconnected.Add(sessionId); - } + _connected.Remove(sessionId); + _disconnected.Add(sessionId); } } + } - protected bool IsPending(SessionID sessionId) + protected bool IsPending(SessionID sessionId) + { + lock (_sync) { - lock (_sync) - { - return _pending.Contains(sessionId); - } + return _pending.Contains(sessionId); } + } - protected bool IsConnected(SessionID sessionId) + protected bool IsConnected(SessionID sessionId) + { + lock (_sync) { - lock (_sync) - { - return _connected.Contains(sessionId); - } + return _connected.Contains(sessionId); } + } - protected bool IsDisconnected(SessionID sessionId) + protected bool IsDisconnected(SessionID sessionId) + { + lock (_sync) { - lock (_sync) - { - return _disconnected.Contains(sessionId); - } + return _disconnected.Contains(sessionId); } + } - #endregion - + #endregion - /// - /// Get the SessionIDs for the sessions managed by this initiator. - /// - /// the SessionIDs for the sessions managed by this initiator - public HashSet GetSessionIDs() - { - return new HashSet(_sessions.Keys); - } - private bool _disposed = false; - /// - /// Any subclasses of AbstractInitiator should override this if they have resources to dispose - /// that aren't already covered in its OnStop() handler. - /// Any override should call base.Dispose(disposing). - /// - /// - protected virtual void Dispose(bool disposing) - { - if (_disposed) return; - if (disposing) - { - this.Stop(); - _logFactoryAdapter?.Dispose(); - } - _disposed = true; - } + /// + /// Get the SessionIDs for the sessions managed by this initiator. + /// + /// the SessionIDs for the sessions managed by this initiator + public HashSet GetSessionIDs() + { + return new HashSet(_sessions.Keys); + } - public void Dispose() + private bool _disposed = false; + /// + /// Any subclasses of AbstractInitiator should override this if they have resources to dispose + /// that aren't already covered in its OnStop() handler. + /// Any override should call base.Dispose(disposing). + /// + /// + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + if (disposing) { - Dispose(true); - GC.SuppressFinalize(this); + this.Stop(); + _logFactoryAdapter?.Dispose(); } + _disposed = true; + } - ~AbstractInitiator() => Dispose(false); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); } + + ~AbstractInitiator() => Dispose(false); } diff --git a/QuickFIXn/ClientHandlerThread.cs b/QuickFIXn/ClientHandlerThread.cs index f2afa0a88..488d2c942 100755 --- a/QuickFIXn/ClientHandlerThread.cs +++ b/QuickFIXn/ClientHandlerThread.cs @@ -3,117 +3,116 @@ using System; using QuickFix.Logger; -namespace QuickFix +namespace QuickFix; + +/// +/// Created by a ThreadedSocketReactor to handle a client connection. +/// Each ClientHandlerThread has a SocketReader which reads +/// from the socket. +/// +internal class ClientHandlerThread : IResponder, IDisposable { - /// - /// Created by a ThreadedSocketReactor to handle a client connection. - /// Each ClientHandlerThread has a SocketReader which reads - /// from the socket. - /// - internal class ClientHandlerThread : IResponder, IDisposable + internal class ExitedEventArgs : EventArgs { - internal class ExitedEventArgs : EventArgs - { - public ClientHandlerThread ClientHandlerThread { get; private set; } + public ClientHandlerThread ClientHandlerThread { get; private set; } - public ExitedEventArgs(ClientHandlerThread clientHandlerThread) - { - ClientHandlerThread = clientHandlerThread; - } + public ExitedEventArgs(ClientHandlerThread clientHandlerThread) + { + ClientHandlerThread = clientHandlerThread; } + } - internal delegate void ExitedEventHandler(object sender, ExitedEventArgs e); - internal event ExitedEventHandler? Exited; + internal delegate void ExitedEventHandler(object sender, ExitedEventArgs e); + internal event ExitedEventHandler? Exited; - public long Id { get; private set; } + public long Id { get; private set; } - private Thread? _thread = null; - private volatile bool _isShutdownRequested = false; - private readonly SocketReader _socketReader; + private Thread? _thread = null; + private volatile bool _isShutdownRequested = false; + private readonly SocketReader _socketReader; - internal ClientHandlerThread( - TcpClient tcpClient, - long clientId, - SocketSettings socketSettings, - AcceptorSocketDescriptor? acceptorDescriptor, - IQuickFixLoggerFactory loggerFactory - ) { - Id = clientId; - _socketReader = new SocketReader(tcpClient, socketSettings, this, acceptorDescriptor, loggerFactory); - } + internal ClientHandlerThread( + TcpClient tcpClient, + long clientId, + SocketSettings socketSettings, + AcceptorSocketDescriptor? acceptorDescriptor, + IQuickFixLoggerFactory loggerFactory + ) { + Id = clientId; + _socketReader = new SocketReader(tcpClient, socketSettings, this, acceptorDescriptor, loggerFactory); + } - public void Start() - { - _thread = new Thread(Run); - _thread.Start(); - } + public void Start() + { + _thread = new Thread(Run); + _thread.Start(); + } - public void Shutdown(string reason) - { - // TODO - need the reason param? - _isShutdownRequested = true; - } + public void Shutdown(string reason) + { + // TODO - need the reason param? + _isShutdownRequested = true; + } - public void Join() - { - if (_thread is null) - return; - if (_thread.IsAlive) - _thread.Join(5000); - _thread = null; - } + public void Join() + { + if (_thread is null) + return; + if (_thread.IsAlive) + _thread.Join(5000); + _thread = null; + } - private void Run() + private void Run() + { + while (!_isShutdownRequested) { - while (!_isShutdownRequested) + try { - try - { - _socketReader.Read(); - } - catch (Exception e) - { - Shutdown(e.Message); - } + _socketReader.Read(); + } + catch (Exception e) + { + Shutdown(e.Message); } - - OnExited(); } - private void OnExited() { - Exited?.Invoke(this, new ExitedEventArgs(this)); - } + OnExited(); + } - #region Responder Members + private void OnExited() { + Exited?.Invoke(this, new ExitedEventArgs(this)); + } - public bool Send(string data) - { - return _socketReader.Send(data) > 0; - } + #region Responder Members - public void Disconnect() - { - Shutdown("Disconnected"); - } + public bool Send(string data) + { + return _socketReader.Send(data) > 0; + } - #endregion + public void Disconnect() + { + Shutdown("Disconnected"); + } - ~ClientHandlerThread() => Dispose(false); - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + #endregion + + ~ClientHandlerThread() => Dispose(false); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - private bool _disposed = false; - protected virtual void Dispose(bool disposing) + private bool _disposed = false; + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + if (disposing) { - if (_disposed) return; - if (disposing) - { - _socketReader.Dispose(); - } - _disposed = true; + _socketReader.Dispose(); } + _disposed = true; } } diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 6e0b1a659..51c348046 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -8,1810 +8,1809 @@ using QuickFix.Store; using QuickFix.Util; -namespace QuickFix +namespace QuickFix; + +/// +/// The Session is the primary FIX abstraction for message communication. +/// It performs sequencing and error recovery and represents a communication +/// channel to a counterparty. Sessions are independent of specific communication +/// layer connections. A Session is defined as starting with message sequence number +/// of 1 and ending when the session is reset. The Session could span many sequential +/// connections (it cannot operate on multiple connections simultaneously). +/// +public class Session : IDisposable { - /// - /// The Session is the primary FIX abstraction for message communication. - /// It performs sequencing and error recovery and represents a communication - /// channel to a counterparty. Sessions are independent of specific communication - /// layer connections. A Session is defined as starting with message sequence number - /// of 1 and ending when the session is reset. The Session could span many sequential - /// connections (it cannot operate on multiple connections simultaneously). - /// - public class Session : IDisposable - { - private static readonly Dictionary Sessions = new(); - private static readonly HashSet AdminMsgTypes = ["0", "A", "1", "2", "3", "4", "5"]; - - private readonly object _sync = new(); - private IResponder? _responder; - private readonly SessionSchedule _schedule; - private readonly SessionState _state; - private readonly IMessageFactory _msgFactory; - private readonly bool _appDoesEarlyIntercept; - - private const LogLevel MessagesLogLevel = LogLevel.Information; - - #region Properties - - // state - public IMessageStore MessageStore => _state.MessageStore; - public ILogger Log => _state.Log; - public bool IsInitiator => _state.IsInitiator; - public bool IsAcceptor => !_state.IsInitiator; - public bool IsEnabled => _state.IsEnabled; - public bool IsSessionTime => _schedule.IsSessionTime(DateTime.UtcNow); - public bool IsLoggedOn => ReceivedLogon && SentLogon; - public bool SentLogon => _state.SentLogon; - public bool ReceivedLogon => _state.ReceivedLogon; - - public bool IsNewSession + private static readonly Dictionary Sessions = new(); + private static readonly HashSet AdminMsgTypes = ["0", "A", "1", "2", "3", "4", "5"]; + + private readonly object _sync = new(); + private IResponder? _responder; + private readonly SessionSchedule _schedule; + private readonly SessionState _state; + private readonly IMessageFactory _msgFactory; + private readonly bool _appDoesEarlyIntercept; + + private const LogLevel MessagesLogLevel = LogLevel.Information; + + #region Properties + + // state + public IMessageStore MessageStore => _state.MessageStore; + public ILogger Log => _state.Log; + public bool IsInitiator => _state.IsInitiator; + public bool IsAcceptor => !_state.IsInitiator; + public bool IsEnabled => _state.IsEnabled; + public bool IsSessionTime => _schedule.IsSessionTime(DateTime.UtcNow); + public bool IsLoggedOn => ReceivedLogon && SentLogon; + public bool SentLogon => _state.SentLogon; + public bool ReceivedLogon => _state.ReceivedLogon; + + public bool IsNewSession + { + get { - get - { - DateTime? creationTime = _state.CreationTime; - return creationTime.HasValue == false - || _schedule.IsNewSession(creationTime.Value, DateTime.UtcNow); - } + DateTime? creationTime = _state.CreationTime; + return creationTime.HasValue == false + || _schedule.IsNewSession(creationTime.Value, DateTime.UtcNow); } + } - /// - /// Session setting for heartbeat interval (in seconds) - /// - public int HeartBtInt => _state.HeartBtInt; - - /// - /// Session setting for enabling message latency checks - /// - public bool CheckLatency { get; set; } - - /// - /// Session setting for maximum message latency (in seconds) - /// - public int MaxLatency { get; set; } - - /// - /// Send a logout if counterparty times out and does not heartbeat - /// in response to a TestRequeset. Defaults to false - /// - public bool SendLogoutBeforeTimeoutDisconnect { get; set; } - - /// - /// Gets or sets the next expected outgoing sequence number - /// - public SeqNumType NextSenderMsgSeqNum - { - get => _state.NextSenderMsgSeqNum; - set => _state.NextSenderMsgSeqNum = value; - } + /// + /// Session setting for heartbeat interval (in seconds) + /// + public int HeartBtInt => _state.HeartBtInt; - /// - /// Gets or sets the next expected incoming sequence number - /// - public SeqNumType NextTargetMsgSeqNum - { - get => _state.NextTargetMsgSeqNum; - set => _state.NextTargetMsgSeqNum = value; - } + /// + /// Session setting for enabling message latency checks + /// + public bool CheckLatency { get; set; } - /// - /// Logon timeout in seconds - /// - public int LogonTimeout - { - get => _state.LogonTimeout; - set => _state.LogonTimeout = value; - } + /// + /// Session setting for maximum message latency (in seconds) + /// + public int MaxLatency { get; set; } - /// - /// Logout timeout in seconds - /// - public int LogoutTimeout - { - get => _state.LogoutTimeout; - set => _state.LogoutTimeout = value; - } + /// + /// Send a logout if counterparty times out and does not heartbeat + /// in response to a TestRequeset. Defaults to false + /// + public bool SendLogoutBeforeTimeoutDisconnect { get; set; } - // unsynchronized properties - /// - /// Whether to persist messages or not. Setting to false forces quickfix - /// to always send GapFills instead of resending messages. - /// - public bool PersistMessages { get; set; } - - /// - /// Determines if session state should be restored from persistance - /// layer when logging on. Useful for creating hot failover sessions. - /// - public bool RefreshOnLogon { get; set; } - - /// - /// Reset sequence numbers on logon request - /// - public bool ResetOnLogon { get; set; } - - /// - /// Reset sequence numbers to 1 after a normal logout - /// - public bool ResetOnLogout { get; set; } - - /// - /// Reset sequence numbers to 1 after abnormal termination - /// - public bool ResetOnDisconnect { get; set; } - - /// - /// Whether to send redundant resend requests - /// - public bool SendRedundantResendRequests { get; set; } - - /// - /// Whether to resend session level rejects (msg type '3') when servicing a resend request - /// - public bool ResendSessionLevelRejects { get; set; } - - /// - /// Whether to validate length and checksum of messages - /// - public bool ValidateLengthAndChecksum { get; set; } - - /// - /// Whether to validates Comp IDs for each message - /// - public bool CheckCompID { get; set; } - - /// - /// Gets or sets the time stamp precision. - /// - /// - /// The time stamp precision. - /// - public TimeStampPrecision TimeStampPrecision - { - get; - set; - } + /// + /// Gets or sets the next expected outgoing sequence number + /// + public SeqNumType NextSenderMsgSeqNum + { + get => _state.NextSenderMsgSeqNum; + set => _state.NextSenderMsgSeqNum = value; + } - /// - /// Adds the last message sequence number processed in the header (tag 369) - /// - public bool EnableLastMsgSeqNumProcessed { get; set; } - - /// - /// Ignores resend requests marked poss dup - /// - public bool IgnorePossDupResendRequests { get; set; } - - /// - /// Sets a maximum number of messages to request in a resend request. - /// - public SeqNumType MaxMessagesInResendRequest { get; set; } - - /// - /// This is the FIX field value, e.g. "6" for FIX44 - /// - public ApplVerID? TargetDefaultApplVerId { get; set; } - - /// - /// This is the FIX field value, e.g. "6" for FIX44 - /// - public string SenderDefaultApplVerID { get; set; } - - public SessionID SessionID { get; set; } - public IApplication Application { get; } - public DataDictionaryProvider DataDictionaryProvider { get; } - public DataDictionary.DataDictionary SessionDataDictionary { get; } - public DataDictionary.DataDictionary ApplicationDataDictionary { get; } - - /// - /// Returns whether the Session has a Responder. This method is synchronized - /// - public bool HasResponder { get { Thread.MemoryBarrier(); return _responder is not null; } } - - /// - /// Returns whether the Sessions will allow ResetSequence messages sent as - /// part of a resend request (PossDup=Y) to omit the OrigSendingTime - /// - public bool RequiresOrigSendingTime { get; set; } - - /// - /// True if session is waiting for ResendRequest content. - /// If the RR's EndSeqNo is 0 aka infinite, then this becomes - /// false after the first resent message is received. - /// Else it remains true until EndSeqNo is received. - /// - internal bool IsResendRequested => _state.IsResendRequested(); - - public bool CmeEnhancedResend { get; set; } - - public int[] RedactFieldsInLogs { get; set; } = []; - public string RedactionLogText { get; set; } = ""; - - #endregion - - internal Session( - bool isInitiator, - IApplication app, - IMessageStoreFactory storeFactory, - SessionID sessId, - DataDictionaryProvider dataDictProvider, - SessionSchedule sessionSchedule, - int heartBtInt, - IQuickFixLoggerFactory loggerFactory, - IMessageFactory msgFactory, - string senderDefaultApplVerId) - { - _schedule = sessionSchedule; - _msgFactory = msgFactory; - _appDoesEarlyIntercept = app is IApplicationExt; - - Application = app; - SessionID = sessId; - DataDictionaryProvider = new DataDictionaryProvider(dataDictProvider); - SenderDefaultApplVerID = senderDefaultApplVerId; - - SessionDataDictionary = DataDictionaryProvider.GetSessionDataDictionary(SessionID.BeginString); - ApplicationDataDictionary = SessionID.IsFIXT - ? DataDictionaryProvider.GetApplicationDataDictionary(SenderDefaultApplVerID) - : SessionDataDictionary; - - ILogger logger = loggerFactory.CreateSessionLogger(sessId); - - _state = new SessionState(isInitiator, logger, heartBtInt, storeFactory.Create(sessId)); - - // Configuration defaults. - // Will be overridden by the SessionFactory with values in the user's configuration. - PersistMessages = true; - ResetOnDisconnect = false; - SendRedundantResendRequests = false; - ResendSessionLevelRejects = false; - ValidateLengthAndChecksum = true; - CheckCompID = true; - TimeStampPrecision = TimeStampPrecision.Millisecond; - EnableLastMsgSeqNumProcessed = false; - MaxMessagesInResendRequest = 0; - SendLogoutBeforeTimeoutDisconnect = false; - IgnorePossDupResendRequests = false; - RequiresOrigSendingTime = true; - CheckLatency = true; - MaxLatency = 120; - - if (!IsSessionTime) - Reset("Out of SessionTime (Session construction)"); - else if (IsNewSession) - Reset("New session"); + /// + /// Gets or sets the next expected incoming sequence number + /// + public SeqNumType NextTargetMsgSeqNum + { + get => _state.NextTargetMsgSeqNum; + set => _state.NextTargetMsgSeqNum = value; + } - lock (Sessions) - { - Sessions[SessionID] = this; - } + /// + /// Logon timeout in seconds + /// + public int LogonTimeout + { + get => _state.LogonTimeout; + set => _state.LogonTimeout = value; + } - Application.OnCreate(SessionID); - Log.Log(LogLevel.Information, "Created session"); - } + /// + /// Logout timeout in seconds + /// + public int LogoutTimeout + { + get => _state.LogoutTimeout; + set => _state.LogoutTimeout = value; + } - #region Static Methods + // unsynchronized properties + /// + /// Whether to persist messages or not. Setting to false forces quickfix + /// to always send GapFills instead of resending messages. + /// + public bool PersistMessages { get; set; } - /// - /// Looks up a Session by its SessionID - /// - /// the SessionID of the Session - /// the Session if found, else returns null - public static Session? LookupSession(SessionID sessionId) - { - lock (Sessions) { - if (Sessions.TryGetValue(sessionId, out Session? result)) - return result; - } + /// + /// Determines if session state should be restored from persistance + /// layer when logging on. Useful for creating hot failover sessions. + /// + public bool RefreshOnLogon { get; set; } - return null; - } + /// + /// Reset sequence numbers on logon request + /// + public bool ResetOnLogon { get; set; } - /// - /// Looks up a Session by its SessionID - /// - /// the SessionID of the Session - /// the true if Session exists, false otherwise - public static bool DoesSessionExist(SessionID sessionId) - { - return LookupSession(sessionId) is not null; - } + /// + /// Reset sequence numbers to 1 after a normal logout + /// + public bool ResetOnLogout { get; set; } - /// - /// Sends a message to the session specified by the provider session ID. - /// - /// FIX message - /// target SessionID - /// true if send was successful, false otherwise - public static bool SendToTarget(Message message, SessionID sessionId) - { - message.SetSessionID(sessionId); - Session? session = Session.LookupSession(sessionId); - if (session is null) - throw new SessionNotFound(sessionId); - return session.Send(message); - } + /// + /// Reset sequence numbers to 1 after abnormal termination + /// + public bool ResetOnDisconnect { get; set; } - /// - /// Send to session indicated by header fields in message - /// - /// - /// - public static bool SendToTarget(Message message) - { - return SendToTarget(message, message.GetSessionID(message)); - } + /// + /// Whether to send redundant resend requests + /// + public bool SendRedundantResendRequests { get; set; } + + /// + /// Whether to resend session level rejects (msg type '3') when servicing a resend request + /// + public bool ResendSessionLevelRejects { get; set; } - #endregion + /// + /// Whether to validate length and checksum of messages + /// + public bool ValidateLengthAndChecksum { get; set; } - /// - /// Sends a message via the session indicated by the header fields - /// - /// message to send - /// true if was sent successfully - public virtual bool Send(Message message) - { - message.Header.RemoveField(Fields.Tags.PossDupFlag); - message.Header.RemoveField(Fields.Tags.OrigSendingTime); - return SendRaw(message); + /// + /// Whether to validates Comp IDs for each message + /// + public bool CheckCompID { get; set; } + + /// + /// Gets or sets the time stamp precision. + /// + /// + /// The time stamp precision. + /// + public TimeStampPrecision TimeStampPrecision + { + get; + set; + } + + /// + /// Adds the last message sequence number processed in the header (tag 369) + /// + public bool EnableLastMsgSeqNumProcessed { get; set; } + + /// + /// Ignores resend requests marked poss dup + /// + public bool IgnorePossDupResendRequests { get; set; } + + /// + /// Sets a maximum number of messages to request in a resend request. + /// + public SeqNumType MaxMessagesInResendRequest { get; set; } + + /// + /// This is the FIX field value, e.g. "6" for FIX44 + /// + public ApplVerID? TargetDefaultApplVerId { get; set; } + + /// + /// This is the FIX field value, e.g. "6" for FIX44 + /// + public string SenderDefaultApplVerID { get; set; } + + public SessionID SessionID { get; set; } + public IApplication Application { get; } + public DataDictionaryProvider DataDictionaryProvider { get; } + public DataDictionary.DataDictionary SessionDataDictionary { get; } + public DataDictionary.DataDictionary ApplicationDataDictionary { get; } + + /// + /// Returns whether the Session has a Responder. This method is synchronized + /// + public bool HasResponder { get { Thread.MemoryBarrier(); return _responder is not null; } } + + /// + /// Returns whether the Sessions will allow ResetSequence messages sent as + /// part of a resend request (PossDup=Y) to omit the OrigSendingTime + /// + public bool RequiresOrigSendingTime { get; set; } + + /// + /// True if session is waiting for ResendRequest content. + /// If the RR's EndSeqNo is 0 aka infinite, then this becomes + /// false after the first resent message is received. + /// Else it remains true until EndSeqNo is received. + /// + internal bool IsResendRequested => _state.IsResendRequested(); + + public bool CmeEnhancedResend { get; set; } + + public int[] RedactFieldsInLogs { get; set; } = []; + public string RedactionLogText { get; set; } = ""; + + #endregion + + internal Session( + bool isInitiator, + IApplication app, + IMessageStoreFactory storeFactory, + SessionID sessId, + DataDictionaryProvider dataDictProvider, + SessionSchedule sessionSchedule, + int heartBtInt, + IQuickFixLoggerFactory loggerFactory, + IMessageFactory msgFactory, + string senderDefaultApplVerId) + { + _schedule = sessionSchedule; + _msgFactory = msgFactory; + _appDoesEarlyIntercept = app is IApplicationExt; + + Application = app; + SessionID = sessId; + DataDictionaryProvider = new DataDictionaryProvider(dataDictProvider); + SenderDefaultApplVerID = senderDefaultApplVerId; + + SessionDataDictionary = DataDictionaryProvider.GetSessionDataDictionary(SessionID.BeginString); + ApplicationDataDictionary = SessionID.IsFIXT + ? DataDictionaryProvider.GetApplicationDataDictionary(SenderDefaultApplVerID) + : SessionDataDictionary; + + ILogger logger = loggerFactory.CreateSessionLogger(sessId); + + _state = new SessionState(isInitiator, logger, heartBtInt, storeFactory.Create(sessId)); + + // Configuration defaults. + // Will be overridden by the SessionFactory with values in the user's configuration. + PersistMessages = true; + ResetOnDisconnect = false; + SendRedundantResendRequests = false; + ResendSessionLevelRejects = false; + ValidateLengthAndChecksum = true; + CheckCompID = true; + TimeStampPrecision = TimeStampPrecision.Millisecond; + EnableLastMsgSeqNumProcessed = false; + MaxMessagesInResendRequest = 0; + SendLogoutBeforeTimeoutDisconnect = false; + IgnorePossDupResendRequests = false; + RequiresOrigSendingTime = true; + CheckLatency = true; + MaxLatency = 120; + + if (!IsSessionTime) + Reset("Out of SessionTime (Session construction)"); + else if (IsNewSession) + Reset("New session"); + + lock (Sessions) + { + Sessions[SessionID] = this; + } + + Application.OnCreate(SessionID); + Log.Log(LogLevel.Information, "Created session"); + } + + #region Static Methods + + /// + /// Looks up a Session by its SessionID + /// + /// the SessionID of the Session + /// the Session if found, else returns null + public static Session? LookupSession(SessionID sessionId) + { + lock (Sessions) { + if (Sessions.TryGetValue(sessionId, out Session? result)) + return result; } - /// - /// Sends a message - /// - /// - /// - public bool Send(string message) + return null; + } + + /// + /// Looks up a Session by its SessionID + /// + /// the SessionID of the Session + /// the true if Session exists, false otherwise + public static bool DoesSessionExist(SessionID sessionId) + { + return LookupSession(sessionId) is not null; + } + + /// + /// Sends a message to the session specified by the provider session ID. + /// + /// FIX message + /// target SessionID + /// true if send was successful, false otherwise + public static bool SendToTarget(Message message, SessionID sessionId) + { + message.SetSessionID(sessionId); + Session? session = Session.LookupSession(sessionId); + if (session is null) + throw new SessionNotFound(sessionId); + return session.Send(message); + } + + /// + /// Send to session indicated by header fields in message + /// + /// + /// + public static bool SendToTarget(Message message) + { + return SendToTarget(message, message.GetSessionID(message)); + } + + #endregion + + /// + /// Sends a message via the session indicated by the header fields + /// + /// message to send + /// true if was sent successfully + public virtual bool Send(Message message) + { + message.Header.RemoveField(Fields.Tags.PossDupFlag); + message.Header.RemoveField(Fields.Tags.OrigSendingTime); + return SendRaw(message); + } + + /// + /// Sends a message + /// + /// + /// + public bool Send(string message) + { + lock (_sync) { - lock (_sync) - { - if (_responder is null) - return false; + if (_responder is null) + return false; - if (Log.IsEnabled(MessagesLogLevel)) + if (Log.IsEnabled(MessagesLogLevel)) + { + using (Log.BeginScope(new Dictionary + { + { "MessageType", Message.GetMsgType(message) } + })) { - using (Log.BeginScope(new Dictionary - { - { "MessageType", Message.GetMsgType(message) } - })) - { - Log.Log(MessagesLogLevel, LogEventIds.OutgoingMessage, "{Message}", - LogAssist.RedactSensitiveFields(message, RedactFieldsInLogs, RedactionLogText)); - } + Log.Log(MessagesLogLevel, LogEventIds.OutgoingMessage, "{Message}", + LogAssist.RedactSensitiveFields(message, RedactFieldsInLogs, RedactionLogText)); } - - return _responder.Send(message); } - } - /// - /// Sets some internal state variables to enable the session. - /// - public void Logon() - { - _state.IsEnabled = true; - _state.LogoutReason = ""; + return _responder.Send(message); } + } - /// - /// Sets some internal state variables to disable the session. - /// Users will be disconnected on next cycle. - /// (This function is for actively initiating a logout, - /// it is NOT for processing a logout request from the counterparty.) - /// - public void Logout(string reason = "") - { - _state.IsEnabled = false; - _state.LogoutReason = reason; - } + /// + /// Sets some internal state variables to enable the session. + /// + public void Logon() + { + _state.IsEnabled = true; + _state.LogoutReason = ""; + } + + /// + /// Sets some internal state variables to disable the session. + /// Users will be disconnected on next cycle. + /// (This function is for actively initiating a logout, + /// it is NOT for processing a logout request from the counterparty.) + /// + public void Logout(string reason = "") + { + _state.IsEnabled = false; + _state.LogoutReason = reason; + } - /// - /// Logs out from session and closes the network connection - /// - /// - public void Disconnect(string reason) + /// + /// Logs out from session and closes the network connection + /// + /// + public void Disconnect(string reason) + { + lock (_sync) { - lock (_sync) + if (_responder is not null) { - if (_responder is not null) - { - Log.Log(LogLevel.Information, "Session {SessionID} disconnecting: {Reason}", SessionID, reason); - _responder.Disconnect(); - _responder = null; - } - else - { - Log.Log(LogLevel.Information, "Session {SessionID} already disconnected: {Reason}", - SessionID, reason); - } - - if (_state.ReceivedLogon || _state.SentLogon) - { - _state.ReceivedLogon = false; - _state.SentLogon = false; - Application.OnLogout(SessionID); - } + Log.Log(LogLevel.Information, "Session {SessionID} disconnecting: {Reason}", SessionID, reason); + _responder.Disconnect(); + _responder = null; + } + else + { + Log.Log(LogLevel.Information, "Session {SessionID} already disconnected: {Reason}", + SessionID, reason); + } - _state.SentLogout = false; - _state.ReceivedReset = false; - _state.SentReset = false; - _state.ClearQueue(); - _state.LogoutReason = ""; - if (ResetOnDisconnect) - _state.Reset("ResetOnDisconnect"); - _state.ResetResendRange(); + if (_state.ReceivedLogon || _state.SentLogon) + { + _state.ReceivedLogon = false; + _state.SentLogon = false; + Application.OnLogout(SessionID); } + + _state.SentLogout = false; + _state.ReceivedReset = false; + _state.SentReset = false; + _state.ClearQueue(); + _state.LogoutReason = ""; + if (ResetOnDisconnect) + _state.Reset("ResetOnDisconnect"); + _state.ResetResendRange(); } + } - /// - /// There's no message to process, but check the session state to see if there's anything to do - /// (e.g. send heartbeat, logout at end of session, etc) - /// - public void Next() + /// + /// There's no message to process, but check the session state to see if there's anything to do + /// (e.g. send heartbeat, logout at end of session, etc) + /// + public void Next() + { + if (!HasResponder) + return; + + if (!IsSessionTime) + { + if(IsInitiator) + Reset("Out of SessionTime (Session.Next())"); + else + Reset("Out of SessionTime (Session.Next())", "Message received outside of session time"); + return; + } + + if (IsNewSession) + _state.Reset("New session (detected in Next())"); + + if (!IsEnabled) { - if (!HasResponder) + if (!IsLoggedOn) return; - if (!IsSessionTime) + if (!_state.SentLogout) { - if(IsInitiator) - Reset("Out of SessionTime (Session.Next())"); - else - Reset("Out of SessionTime (Session.Next())", "Message received outside of session time"); - return; + Log.Log(LogLevel.Information, "Initiated logout request"); + GenerateLogout(_state.LogoutReason); } + } - if (IsNewSession) - _state.Reset("New session (detected in Next())"); - - if (!IsEnabled) + if (!_state.ReceivedLogon) + { + if (_state.ShouldSendLogon && IsTimeToGenerateLogon()) { - if (!IsLoggedOn) - return; + if (GenerateLogon()) + Log.Log(LogLevel.Information, "Initiated logon request"); + else + Log.Log(LogLevel.Error, "Error during logon request initiation"); - if (!_state.SentLogout) - { - Log.Log(LogLevel.Information, "Initiated logout request"); - GenerateLogout(_state.LogoutReason); - } } - - if (!_state.ReceivedLogon) + else if (_state.SentLogon && _state.LogonTimedOut()) { - if (_state.ShouldSendLogon && IsTimeToGenerateLogon()) - { - if (GenerateLogon()) - Log.Log(LogLevel.Information, "Initiated logon request"); - else - Log.Log(LogLevel.Error, "Error during logon request initiation"); - - } - else if (_state.SentLogon && _state.LogonTimedOut()) - { - Disconnect("Timed out waiting for logon response"); - } - return; + Disconnect("Timed out waiting for logon response"); } + return; + } - if (0 == _state.HeartBtInt) - return; + if (0 == _state.HeartBtInt) + return; - if (_state.LogoutTimedOut()) - Disconnect("Timed out waiting for logout response"); + if (_state.LogoutTimedOut()) + Disconnect("Timed out waiting for logout response"); - if (_state.WithinHeartbeat()) - return; + if (_state.WithinHeartbeat()) + return; - if (_state.TimedOut()) + if (_state.TimedOut()) + { + if (SendLogoutBeforeTimeoutDisconnect) + GenerateLogout(); + Disconnect("Timed out waiting for heartbeat"); + } + else + { + if (_state.NeedTestRequest()) { - if (SendLogoutBeforeTimeoutDisconnect) - GenerateLogout(); - Disconnect("Timed out waiting for heartbeat"); + GenerateTestRequest("TEST"); + _state.TestRequestCounter += 1; + Log.Log(LogLevel.Information, "Sent test request TEST"); } - else + else if (_state.NeedHeartbeat()) { - if (_state.NeedTestRequest()) - { - GenerateTestRequest("TEST"); - _state.TestRequestCounter += 1; - Log.Log(LogLevel.Information, "Sent test request TEST"); - } - else if (_state.NeedHeartbeat()) - { - GenerateHeartbeat(); - } + GenerateHeartbeat(); } } + } - /// - /// Process a message (in string form) from the counterparty - /// - /// - public void Next(string msgStr) - { - NextMessage(msgStr); - _state.LastProcessedMessageWasQueued = false; - NextQueued(); - } + /// + /// Process a message (in string form) from the counterparty + /// + /// + public void Next(string msgStr) + { + NextMessage(msgStr); + _state.LastProcessedMessageWasQueued = false; + NextQueued(); + } - /// - /// Process a message (in string form) from the counterparty - /// - /// - private void NextMessage(string msgStr) + /// + /// Process a message (in string form) from the counterparty + /// + /// + private void NextMessage(string msgStr) + { + try { - try + if (Log.IsEnabled(MessagesLogLevel)) { - if (Log.IsEnabled(MessagesLogLevel)) + using (Log.BeginScope(new Dictionary + { + { "MessageType", Message.GetMsgType(msgStr) } + })) { - using (Log.BeginScope(new Dictionary - { - { "MessageType", Message.GetMsgType(msgStr) } - })) - { - Log.Log(MessagesLogLevel, LogEventIds.IncomingMessage, "{Message}", - LogAssist.RedactSensitiveFields(msgStr, RedactFieldsInLogs, RedactionLogText)); - } + Log.Log(MessagesLogLevel, LogEventIds.IncomingMessage, "{Message}", + LogAssist.RedactSensitiveFields(msgStr, RedactFieldsInLogs, RedactionLogText)); } - } catch (Exception) - { - Log.Log(MessagesLogLevel, LogEventIds.IncomingMessage, "{Message}", - LogAssist.RedactSensitiveFields(msgStr, RedactFieldsInLogs, RedactionLogText)); } - - MessageBuilder msgBuilder = new MessageBuilder( - msgStr, - SenderDefaultApplVerID, - ValidateLengthAndChecksum, - SessionDataDictionary, - ApplicationDataDictionary, - _msgFactory); - - Next(msgBuilder); + } catch (Exception) + { + Log.Log(MessagesLogLevel, LogEventIds.IncomingMessage, "{Message}", + LogAssist.RedactSensitiveFields(msgStr, RedactFieldsInLogs, RedactionLogText)); } - /// - /// Process a message from the counterparty. - /// - /// - internal void Next(MessageBuilder msgBuilder) - { - if (!IsSessionTime) - { - Reset("Out of SessionTime (Session.Next(message))", "Message received outside of session time"); - return; - } + MessageBuilder msgBuilder = new MessageBuilder( + msgStr, + SenderDefaultApplVerID, + ValidateLengthAndChecksum, + SessionDataDictionary, + ApplicationDataDictionary, + _msgFactory); - if (IsNewSession) - _state.Reset("New session (detected in Next(Message))"); + Next(msgBuilder); + } - Message? message = null; // declared outside of try-block so that catch-blocks can use it + /// + /// Process a message from the counterparty. + /// + /// + internal void Next(MessageBuilder msgBuilder) + { + if (!IsSessionTime) + { + Reset("Out of SessionTime (Session.Next(message))", "Message received outside of session time"); + return; + } - try - { - message = msgBuilder.Build(); + if (IsNewSession) + _state.Reset("New session (detected in Next(Message))"); - if (_appDoesEarlyIntercept) - ((IApplicationExt)Application).FromEarlyIntercept(message, SessionID); + Message? message = null; // declared outside of try-block so that catch-blocks can use it - string msgType = msgBuilder.MsgType.Value; - string beginString = msgBuilder.BeginString; + try + { + message = msgBuilder.Build(); - if (!beginString.Equals(SessionID.BeginString)) - throw new UnsupportedVersion(beginString); + if (_appDoesEarlyIntercept) + ((IApplicationExt)Application).FromEarlyIntercept(message, SessionID); + string msgType = msgBuilder.MsgType.Value; + string beginString = msgBuilder.BeginString; - if (MsgType.LOGON.Equals(msgType)) { - TargetDefaultApplVerId = SessionID.IsFIXT - ? new ApplVerID(message.GetString(Fields.Tags.DefaultApplVerID)) - : Message.GetApplVerID(beginString); - } + if (!beginString.Equals(SessionID.BeginString)) + throw new UnsupportedVersion(beginString); - if (SessionID.IsFIXT && !Message.IsAdminMsgType(msgType)) - { - DataDictionary.DataDictionary.Validate(message, SessionDataDictionary, ApplicationDataDictionary, beginString, msgType); - } - else - { - DataDictionary.DataDictionary.Validate(message, SessionDataDictionary, SessionDataDictionary, beginString, msgType); - } - if (MsgType.LOGON.Equals(msgType)) - NextLogon(message); - else if (MsgType.LOGOUT.Equals(msgType)) - NextLogout(message); - else if (!IsLoggedOn) - Disconnect($"Received msg type '{msgType}' when not logged on"); - else if (MsgType.HEARTBEAT.Equals(msgType)) - NextHeartbeat(message); - else if (MsgType.TEST_REQUEST.Equals(msgType)) - NextTestRequest(message); - else if (MsgType.SEQUENCE_RESET.Equals(msgType)) - NextSequenceReset(message); - else if (MsgType.RESEND_REQUEST.Equals(msgType)) - NextResendRequest(message); - else - { - if (!Verify(message)) - return; - _state.IncrNextTargetMsgSeqNum(); - } + if (MsgType.LOGON.Equals(msgType)) { + TargetDefaultApplVerId = SessionID.IsFIXT + ? new ApplVerID(message.GetString(Fields.Tags.DefaultApplVerID)) + : Message.GetApplVerID(beginString); + } + if (SessionID.IsFIXT && !Message.IsAdminMsgType(msgType)) + { + DataDictionary.DataDictionary.Validate(message, SessionDataDictionary, ApplicationDataDictionary, beginString, msgType); } - catch (InvalidMessage e) + else { - Log.Log(LogLevel.Information, "{Message}", e.Message); - - try - { - if (MsgType.LOGON.Equals(msgBuilder.MsgType.Value)) - Disconnect("Logon message is not valid"); - } - catch (MessageParseError) - { } - - throw; + DataDictionary.DataDictionary.Validate(message, SessionDataDictionary, SessionDataDictionary, beginString, msgType); } - catch (TagException e) + + if (MsgType.LOGON.Equals(msgType)) + NextLogon(message); + else if (MsgType.LOGOUT.Equals(msgType)) + NextLogout(message); + else if (!IsLoggedOn) + Disconnect($"Received msg type '{msgType}' when not logged on"); + else if (MsgType.HEARTBEAT.Equals(msgType)) + NextHeartbeat(message); + else if (MsgType.TEST_REQUEST.Equals(msgType)) + NextTestRequest(message); + else if (MsgType.SEQUENCE_RESET.Equals(msgType)) + NextSequenceReset(message); + else if (MsgType.RESEND_REQUEST.Equals(msgType)) + NextResendRequest(message); + else { - if (e.InnerException is not null) - Log.Log(LogLevel.Error, "{Message}", e.InnerException.Message); - GenerateReject(msgBuilder, e.sessionRejectReason, e.Field); + if (!Verify(message)) + return; + _state.IncrNextTargetMsgSeqNum(); } - catch (UnsupportedVersion uvx) + + } + catch (InvalidMessage e) + { + Log.Log(LogLevel.Information, "{Message}", e.Message); + + try { - if (MsgType.LOGOUT.Equals(msgBuilder.MsgType.Value)) - { - NextLogout(message!); - } - else - { - Log.Log(LogLevel.Error, uvx, "{Message}", uvx.ToString()); - GenerateLogout(uvx.Message); - _state.IncrNextTargetMsgSeqNum(); - } + if (MsgType.LOGON.Equals(msgBuilder.MsgType.Value)) + Disconnect("Logon message is not valid"); } - catch (MessageFactoryNotFound mfnf) + catch (MessageParseError) + { } + + throw; + } + catch (TagException e) + { + if (e.InnerException is not null) + Log.Log(LogLevel.Error, "{Message}", e.InnerException.Message); + GenerateReject(msgBuilder, e.sessionRejectReason, e.Field); + } + catch (UnsupportedVersion uvx) + { + if (MsgType.LOGOUT.Equals(msgBuilder.MsgType.Value)) { - if (MsgType.LOGOUT.Equals(msgBuilder.MsgType.Value)) - { - NextLogout(message!); - } - else - { - // We shouldn't send that question to the counterparty, but local devs should see it - Log.Log(LogLevel.Error, mfnf, - "{Message} (Did you forget a message package dependency?)", mfnf.ToString()); - GenerateLogout(mfnf.Message); - _state.IncrNextTargetMsgSeqNum(); - } + NextLogout(message!); } - catch (UnsupportedMessageType e) + else { - Log.Log(LogLevel.Error, e, "Unsupported message type: {Message}", e.Message); - GenerateBusinessMessageReject(message!, Fields.BusinessRejectReason.UNKNOWN_MESSAGE_TYPE, 0); + Log.Log(LogLevel.Error, uvx, "{Message}", uvx.ToString()); + GenerateLogout(uvx.Message); + _state.IncrNextTargetMsgSeqNum(); } - catch (FieldNotFoundException e) + } + catch (MessageFactoryNotFound mfnf) + { + if (MsgType.LOGOUT.Equals(msgBuilder.MsgType.Value)) { - Log.Log(LogLevel.Warning, e, "Rejecting invalid message, field not found: {Message}", e.Message); - if (string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX42) >= 0 && message!.IsApp()) - { - GenerateBusinessMessageReject(message, Fields.BusinessRejectReason.CONDITIONALLY_REQUIRED_FIELD_MISSING, e.Field); - } - else - { - if (MsgType.LOGON.Equals(msgBuilder.MsgType.Value)) - { - Log.Log(LogLevel.Error, "Required field missing from logon"); - Disconnect("Required field missing from logon"); - } - else - GenerateReject(msgBuilder, new QuickFix.FixValues.SessionRejectReason(SessionRejectReason.REQUIRED_TAG_MISSING, "Required Tag Missing"), e.Field); - } + NextLogout(message!); } - catch (RejectLogon e) + else { - GenerateLogout(e.Message); - Disconnect(e.ToString()); + // We shouldn't send that question to the counterparty, but local devs should see it + Log.Log(LogLevel.Error, mfnf, + "{Message} (Did you forget a message package dependency?)", mfnf.ToString()); + GenerateLogout(mfnf.Message); + _state.IncrNextTargetMsgSeqNum(); } - - Next(); } - - protected void NextLogon(Message logon) + catch (UnsupportedMessageType e) { - _state.ReceivedReset = logon.IsSetField(ResetSeqNumFlag.TAG) && logon.GetBoolean(ResetSeqNumFlag.TAG); - - if (_state.ReceivedReset) + Log.Log(LogLevel.Error, e, "Unsupported message type: {Message}", e.Message); + GenerateBusinessMessageReject(message!, Fields.BusinessRejectReason.UNKNOWN_MESSAGE_TYPE, 0); + } + catch (FieldNotFoundException e) + { + Log.Log(LogLevel.Warning, e, "Rejecting invalid message, field not found: {Message}", e.Message); + if (string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX42) >= 0 && message!.IsApp()) { - Log.Log(LogLevel.Information, "Sequence numbers reset due to ResetSeqNumFlag=Y"); - if (!_state.SentReset) + GenerateBusinessMessageReject(message, Fields.BusinessRejectReason.CONDITIONALLY_REQUIRED_FIELD_MISSING, e.Field); + } + else + { + if (MsgType.LOGON.Equals(msgBuilder.MsgType.Value)) { - _state.Reset("Reset requested by counterparty"); + Log.Log(LogLevel.Error, "Required field missing from logon"); + Disconnect("Required field missing from logon"); } + else + GenerateReject(msgBuilder, new QuickFix.FixValues.SessionRejectReason(SessionRejectReason.REQUIRED_TAG_MISSING, "Required Tag Missing"), e.Field); } + } + catch (RejectLogon e) + { + GenerateLogout(e.Message); + Disconnect(e.ToString()); + } - if (IsAcceptor && ResetOnLogon) - _state.Reset("ResetOnLogon"); - if (RefreshOnLogon) - Refresh(); - - if (!Verify(logon, false, true)) - return; + Next(); + } - if (!IsGoodTime(logon)) - { - Log.Log(LogLevel.Error, "Logon has bad sending time"); - Disconnect("bad sending time"); - return; - } + protected void NextLogon(Message logon) + { + _state.ReceivedReset = logon.IsSetField(ResetSeqNumFlag.TAG) && logon.GetBoolean(ResetSeqNumFlag.TAG); - _state.ReceivedLogon = true; - Log.Log(LogLevel.Information, "Received logon"); - if (IsAcceptor) + if (_state.ReceivedReset) + { + Log.Log(LogLevel.Information, "Sequence numbers reset due to ResetSeqNumFlag=Y"); + if (!_state.SentReset) { - int heartBtInt = logon.GetInt(Fields.Tags.HeartBtInt); - _state.HeartBtInt = heartBtInt; - GenerateLogon(logon); - Log.Log(LogLevel.Information, "Responding to logon request; heartbeat is {HeartBtInt} seconds", - heartBtInt); + _state.Reset("Reset requested by counterparty"); } + } - _state.SentReset = false; - _state.ReceivedReset = false; + if (IsAcceptor && ResetOnLogon) + _state.Reset("ResetOnLogon"); + if (RefreshOnLogon) + Refresh(); - SeqNumType msgSeqNum = logon.Header.GetULong(Fields.Tags.MsgSeqNum); - if (IsTargetTooHigh(msgSeqNum) && !_state.ReceivedReset) - { - DoTargetTooHigh(logon, msgSeqNum); - } - else - { - _state.IncrNextTargetMsgSeqNum(); - } + if (!Verify(logon, false, true)) + return; - if (IsLoggedOn) - Application.OnLogon(SessionID); + if (!IsGoodTime(logon)) + { + Log.Log(LogLevel.Error, "Logon has bad sending time"); + Disconnect("bad sending time"); + return; } - protected void NextTestRequest(Message testRequest) + _state.ReceivedLogon = true; + Log.Log(LogLevel.Information, "Received logon"); + if (IsAcceptor) { - if (!Verify(testRequest)) - return; - GenerateHeartbeat(testRequest); - _state.IncrNextTargetMsgSeqNum(); + int heartBtInt = logon.GetInt(Fields.Tags.HeartBtInt); + _state.HeartBtInt = heartBtInt; + GenerateLogon(logon); + Log.Log(LogLevel.Information, "Responding to logon request; heartbeat is {HeartBtInt} seconds", + heartBtInt); } - protected void NextResendRequest(Message resendReq) + _state.SentReset = false; + _state.ReceivedReset = false; + + SeqNumType msgSeqNum = logon.Header.GetULong(Fields.Tags.MsgSeqNum); + if (IsTargetTooHigh(msgSeqNum) && !_state.ReceivedReset) { - if (!Verify(resendReq, false, false)) - return; - try { - SeqNumType msgSeqNum; - if (!(IgnorePossDupResendRequests && resendReq.Header.IsSetField(Tags.PossDupFlag))) + DoTargetTooHigh(logon, msgSeqNum); + } + else + { + _state.IncrNextTargetMsgSeqNum(); + } + + if (IsLoggedOn) + Application.OnLogon(SessionID); + } + + protected void NextTestRequest(Message testRequest) + { + if (!Verify(testRequest)) + return; + GenerateHeartbeat(testRequest); + _state.IncrNextTargetMsgSeqNum(); + } + + protected void NextResendRequest(Message resendReq) + { + if (!Verify(resendReq, false, false)) + return; + try { + SeqNumType msgSeqNum; + if (!(IgnorePossDupResendRequests && resendReq.Header.IsSetField(Tags.PossDupFlag))) + { + SeqNumType begSeqNo = resendReq.GetULong(Fields.Tags.BeginSeqNo); + SeqNumType endSeqNo = resendReq.GetULong(Fields.Tags.EndSeqNo); + Log.Log(LogLevel.Information, "Got resend request from {BeginSeqNo} to {EndSeqNo}", + begSeqNo, endSeqNo); + + if (endSeqNo == 999999 || endSeqNo == 0) + { + endSeqNo = _state.NextSenderMsgSeqNum - 1; + } + + if (!PersistMessages) + { + endSeqNo++; + SeqNumType next = _state.NextSenderMsgSeqNum; + if (endSeqNo > next) + endSeqNo = next; + GenerateSequenceReset(resendReq, begSeqNo, endSeqNo); + msgSeqNum = resendReq.Header.GetULong(Tags.MsgSeqNum); + if (!IsTargetTooHigh(msgSeqNum) && !IsTargetTooLow(msgSeqNum)) + { + _state.IncrNextTargetMsgSeqNum(); + } + return; + } + + List messages = new List(); + _state.Get(begSeqNo, endSeqNo, messages); + SeqNumType current = begSeqNo; + SeqNumType begin = 0; + foreach (string msgStr in messages) { - SeqNumType begSeqNo = resendReq.GetULong(Fields.Tags.BeginSeqNo); - SeqNumType endSeqNo = resendReq.GetULong(Fields.Tags.EndSeqNo); - Log.Log(LogLevel.Information, "Got resend request from {BeginSeqNo} to {EndSeqNo}", - begSeqNo, endSeqNo); + Message msg = new Message(); + msg.FromString(msgStr, true, SessionDataDictionary, ApplicationDataDictionary, _msgFactory, ignoreBody: false); + msgSeqNum = msg.Header.GetULong(Tags.MsgSeqNum); - if (endSeqNo == 999999 || endSeqNo == 0) + if (current != msgSeqNum && begin == 0) { - endSeqNo = _state.NextSenderMsgSeqNum - 1; + begin = current; } - if (!PersistMessages) + if (IsAdminMessage(msg) && !(ResendSessionLevelRejects && msg.Header.GetString(Tags.MsgType) == MsgType.REJECT)) { - endSeqNo++; - SeqNumType next = _state.NextSenderMsgSeqNum; - if (endSeqNo > next) - endSeqNo = next; - GenerateSequenceReset(resendReq, begSeqNo, endSeqNo); - msgSeqNum = resendReq.Header.GetULong(Tags.MsgSeqNum); - if (!IsTargetTooHigh(msgSeqNum) && !IsTargetTooLow(msgSeqNum)) + if (begin == 0) { - _state.IncrNextTargetMsgSeqNum(); + begin = msgSeqNum; } - return; } - - List messages = new List(); - _state.Get(begSeqNo, endSeqNo, messages); - SeqNumType current = begSeqNo; - SeqNumType begin = 0; - foreach (string msgStr in messages) + else { - Message msg = new Message(); - msg.FromString(msgStr, true, SessionDataDictionary, ApplicationDataDictionary, _msgFactory, ignoreBody: false); - msgSeqNum = msg.Header.GetULong(Tags.MsgSeqNum); - if (current != msgSeqNum && begin == 0) + InitializeResendFields(msg); + if(!ResendApproved(msg, SessionID)) { - begin = current; + continue; } - if (IsAdminMessage(msg) && !(ResendSessionLevelRejects && msg.Header.GetString(Tags.MsgType) == MsgType.REJECT)) - { - if (begin == 0) - { - begin = msgSeqNum; - } - } - else + if (begin != 0) { - - InitializeResendFields(msg); - if(!ResendApproved(msg, SessionID)) - { - continue; - } - - if (begin != 0) - { - GenerateSequenceReset(resendReq, begin, msgSeqNum); - } - Send(msg.ConstructString()); - begin = 0; + GenerateSequenceReset(resendReq, begin, msgSeqNum); } - current = msgSeqNum + 1; - } - - SeqNumType nextSeqNum = _state.NextSenderMsgSeqNum; - if (++endSeqNo > nextSeqNum) - { - endSeqNo = nextSeqNum; - } - - if (begin == 0) - { - begin = current; + Send(msg.ConstructString()); + begin = 0; } + current = msgSeqNum + 1; + } - if (endSeqNo > begin) - { - GenerateSequenceReset(resendReq, begin, endSeqNo); - } + SeqNumType nextSeqNum = _state.NextSenderMsgSeqNum; + if (++endSeqNo > nextSeqNum) + { + endSeqNo = nextSeqNum; } - msgSeqNum = resendReq.Header.GetULong(Tags.MsgSeqNum); - if (!IsTargetTooHigh(msgSeqNum) && !IsTargetTooLow(msgSeqNum)) + + if (begin == 0) { - _state.IncrNextTargetMsgSeqNum(); + begin = current; } + if (endSeqNo > begin) + { + GenerateSequenceReset(resendReq, begin, endSeqNo); + } } - catch (Exception e) + msgSeqNum = resendReq.Header.GetULong(Tags.MsgSeqNum); + if (!IsTargetTooHigh(msgSeqNum) && !IsTargetTooLow(msgSeqNum)) { - Log.Log(LogLevel.Error, e, "ERROR during resend request {Message}", e.Message); + _state.IncrNextTargetMsgSeqNum(); } - } - private bool ResendApproved(Message msg, SessionID sessionId) + } + catch (Exception e) { - try - { - Application.ToApp(msg, sessionId); - } - catch (DoNotSend) - { - return false; - } - - return true; + Log.Log(LogLevel.Error, e, "ERROR during resend request {Message}", e.Message); } + } - protected void NextLogout(Message logout) + private bool ResendApproved(Message msg, SessionID sessionId) + { + try { - if (!Verify(logout, false, false)) - return; + Application.ToApp(msg, sessionId); + } + catch (DoNotSend) + { + return false; + } - string disconnectReason; + return true; + } - if (_state.SentLogout) - { - // We initiated the logout, and this is the response. - disconnectReason = "Received logout response"; - Log.Log(LogLevel.Information, "{Message}", disconnectReason); + protected void NextLogout(Message logout) + { + if (!Verify(logout, false, false)) + return; - _state.IncrNextTargetMsgSeqNum(); - if (ResetOnLogout) { - _state.Reset("ResetOnLogout"); - } - } - else - { - // Counterparty is initiating the logout - disconnectReason = "Received logout request"; - Log.Log(LogLevel.Information, "{Message}", disconnectReason); - GenerateLogout(logout); - Log.Log(LogLevel.Information, "Sending logout response"); + string disconnectReason; + if (_state.SentLogout) + { + // We initiated the logout, and this is the response. + disconnectReason = "Received logout response"; + Log.Log(LogLevel.Information, "{Message}", disconnectReason); - _state.IncrNextTargetMsgSeqNum(); - if(ResetOnLogout) - _state.Reset("ResetOnLogout"); - else if (CmeEnhancedResend && logout.IsSetField(789) && logout.GetInt(789) == 1) { - // Reset, but preserve target seqnum - SeqNumType n = _state.NextTargetMsgSeqNum; - _state.Reset("Session reset because CME Logout has tag 789=1"); - NextTargetMsgSeqNum = n; - } + _state.IncrNextTargetMsgSeqNum(); + if (ResetOnLogout) { + _state.Reset("ResetOnLogout"); } + } + else + { + // Counterparty is initiating the logout + disconnectReason = "Received logout request"; + Log.Log(LogLevel.Information, "{Message}", disconnectReason); + GenerateLogout(logout); + Log.Log(LogLevel.Information, "Sending logout response"); + - Disconnect(disconnectReason); + _state.IncrNextTargetMsgSeqNum(); + if(ResetOnLogout) + _state.Reset("ResetOnLogout"); + else if (CmeEnhancedResend && logout.IsSetField(789) && logout.GetInt(789) == 1) { + // Reset, but preserve target seqnum + SeqNumType n = _state.NextTargetMsgSeqNum; + _state.Reset("Session reset because CME Logout has tag 789=1"); + NextTargetMsgSeqNum = n; + } } - protected void NextHeartbeat(Message heartbeat) + Disconnect(disconnectReason); + } + + protected void NextHeartbeat(Message heartbeat) + { + if (!Verify(heartbeat)) + return; + _state.IncrNextTargetMsgSeqNum(); + } + + // using this variable is hacky, but I didn't come up with a better solution + private bool _didHandleCmeEnhancedResendGapFill = false; + + protected void NextSequenceReset(Message sequenceReset) + { + _didHandleCmeEnhancedResendGapFill = false; + + bool isGapFill = false; + if (sequenceReset.IsSetField(Tags.GapFillFlag)) + isGapFill = sequenceReset.GetBoolean(Tags.GapFillFlag); + + SeqNumType newSeqNo = sequenceReset.GetULong(Tags.NewSeqNo); + Log.Log(LogLevel.Information, "Received SequenceRequest FROM: {NextTargetMsgSeqNum} TO: {NewSeqNo}", + _state.NextTargetMsgSeqNum, newSeqNo); + + if (newSeqNo < _state.NextTargetMsgSeqNum) { - if (!Verify(heartbeat)) - return; - _state.IncrNextTargetMsgSeqNum(); + GenerateReject(sequenceReset, FixValues.SessionRejectReason.VALUE_IS_INCORRECT); + return; } - // using this variable is hacky, but I didn't come up with a better solution - private bool _didHandleCmeEnhancedResendGapFill = false; + // This may possibly set _didHandleCmeEnhancedResendGapFill=true if CmeEnhancedResend mode enabled + if (!Verify(sequenceReset, isGapFill, isGapFill)) + return; - protected void NextSequenceReset(Message sequenceReset) + if (_didHandleCmeEnhancedResendGapFill) { _didHandleCmeEnhancedResendGapFill = false; + // Return now, and don't proceed to set NextTargetMsgSeqNum in the next block! + return; + } - bool isGapFill = false; - if (sequenceReset.IsSetField(Tags.GapFillFlag)) - isGapFill = sequenceReset.GetBoolean(Tags.GapFillFlag); + if (newSeqNo > _state.NextTargetMsgSeqNum) + { + _state.NextTargetMsgSeqNum = newSeqNo; + } + } - SeqNumType newSeqNo = sequenceReset.GetULong(Tags.NewSeqNo); - Log.Log(LogLevel.Information, "Received SequenceRequest FROM: {NextTargetMsgSeqNum} TO: {NewSeqNo}", - _state.NextTargetMsgSeqNum, newSeqNo); + private void HandleCmeEnhancedResendGapFill(Message seqReset) + { + ResendRange range = _state.GetResendRange(); + SeqNumType newSeqNo = seqReset.GetULong(Tags.NewSeqNo); - if (newSeqNo < _state.NextTargetMsgSeqNum) + if (range.ChunkEndSeqNo == ResendRange.NOT_SET || range.ChunkEndSeqNo == range.EndSeqNo) + { + // We're either in a non-chunk situation or we're in the final chunk. + if (newSeqNo > range.ActualEndSeqNo) { - GenerateReject(sequenceReset, FixValues.SessionRejectReason.VALUE_IS_INCORRECT); - return; + Log.Log(LogLevel.Information, + "ResendRequest for messages FROM: {BeginSeqNo} TO: {EndSeqNo} has been satisfied (by a gap fill).", + range.BeginSeqNo, range.EndSeqNo); + _state.ResetResendRange(); } - - // This may possibly set _didHandleCmeEnhancedResendGapFill=true if CmeEnhancedResend mode enabled - if (!Verify(sequenceReset, isGapFill, isGapFill)) - return; - - if (_didHandleCmeEnhancedResendGapFill) + } + else // we're in a non-final chunk + { + SeqNumType max = range.ChunkEndSeqNo + 1; + if (newSeqNo > max) { - _didHandleCmeEnhancedResendGapFill = false; - // Return now, and don't proceed to set NextTargetMsgSeqNum in the next block! - return; + Log.Log(LogLevel.Information, + "The SequenceReset's NewSeqNo ({NewSeqNo}) is higher than my request ({BeginSeqNo}-{ChunkEndSeqNo}). Per CME's expected behavior, I'll use {max}.", + newSeqNo, range.BeginSeqNo, range.ChunkEndSeqNo, max); + newSeqNo = max; } - - if (newSeqNo > _state.NextTargetMsgSeqNum) + if (newSeqNo == max) { - _state.NextTargetMsgSeqNum = newSeqNo; - } - } + Log.Log(LogLevel.Information, + "Chunked ResendRequest for messages FROM: {BeginSeqNo} TO: {ChunkEndSeqNo} has been satisfied (by a gap fill).", + range.BeginSeqNo, range.ChunkEndSeqNo); + SeqNumType newStart = max; + SeqNumType newChunkEnd = Math.Min(range.EndSeqNo, newStart + MaxMessagesInResendRequest); // TODO we can +1 this + + Message resendRequest = CreateResendRequest(seqReset.Header.GetString(Tags.BeginString), + newStart, newChunkEnd); + if (EnableLastMsgSeqNumProcessed) + resendRequest.Header.SetField(new LastMsgSeqNumProcessed(seqReset.Header.GetULong(Tags.MsgSeqNum))); - private void HandleCmeEnhancedResendGapFill(Message seqReset) - { - ResendRange range = _state.GetResendRange(); - SeqNumType newSeqNo = seqReset.GetULong(Tags.NewSeqNo); + if (SendRaw(resendRequest)) + Log.Log(LogLevel.Information, "Sent ResendRequest FROM: {NewStart} TO: {NewChunkEnd}", newStart, newChunkEnd); + else + Log.Log(LogLevel.Information, "Error sending ResendRequest ({NewStart}, {NewChunkEnd})", newStart, newChunkEnd); - if (range.ChunkEndSeqNo == ResendRange.NOT_SET || range.ChunkEndSeqNo == range.EndSeqNo) - { - // We're either in a non-chunk situation or we're in the final chunk. - if (newSeqNo > range.ActualEndSeqNo) - { - Log.Log(LogLevel.Information, - "ResendRequest for messages FROM: {BeginSeqNo} TO: {EndSeqNo} has been satisfied (by a gap fill).", - range.BeginSeqNo, range.EndSeqNo); - _state.ResetResendRange(); - } + range.UpdateChunk(newStart, newChunkEnd); } - else // we're in a non-final chunk + else if (newSeqNo >= range.BeginSeqNo) { - SeqNumType max = range.ChunkEndSeqNo + 1; - if (newSeqNo > max) - { - Log.Log(LogLevel.Information, - "The SequenceReset's NewSeqNo ({NewSeqNo}) is higher than my request ({BeginSeqNo}-{ChunkEndSeqNo}). Per CME's expected behavior, I'll use {max}.", - newSeqNo, range.BeginSeqNo, range.ChunkEndSeqNo, max); - newSeqNo = max; - } - if (newSeqNo == max) - { - Log.Log(LogLevel.Information, - "Chunked ResendRequest for messages FROM: {BeginSeqNo} TO: {ChunkEndSeqNo} has been satisfied (by a gap fill).", - range.BeginSeqNo, range.ChunkEndSeqNo); - SeqNumType newStart = max; - SeqNumType newChunkEnd = Math.Min(range.EndSeqNo, newStart + MaxMessagesInResendRequest); // TODO we can +1 this - - Message resendRequest = CreateResendRequest(seqReset.Header.GetString(Tags.BeginString), - newStart, newChunkEnd); - if (EnableLastMsgSeqNumProcessed) - resendRequest.Header.SetField(new LastMsgSeqNumProcessed(seqReset.Header.GetULong(Tags.MsgSeqNum))); - - if (SendRaw(resendRequest)) - Log.Log(LogLevel.Information, "Sent ResendRequest FROM: {NewStart} TO: {NewChunkEnd}", newStart, newChunkEnd); - else - Log.Log(LogLevel.Information, "Error sending ResendRequest ({NewStart}, {NewChunkEnd})", newStart, newChunkEnd); - - range.UpdateChunk(newStart, newChunkEnd); - } - else if (newSeqNo >= range.BeginSeqNo) - { - range.MarkAsStarted(); - } + range.MarkAsStarted(); } + } - if(newSeqNo > _state.NextTargetMsgSeqNum) - _state.NextTargetMsgSeqNum = newSeqNo; + if(newSeqNo > _state.NextTargetMsgSeqNum) + _state.NextTargetMsgSeqNum = newSeqNo; - _didHandleCmeEnhancedResendGapFill = true; - } + _didHandleCmeEnhancedResendGapFill = true; + } + + public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = true) + { + string msgType; - public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = true) + try { - string msgType; + msgType = msg.Header.GetString(Fields.Tags.MsgType); + string senderCompId = msg.Header.GetString(Fields.Tags.SenderCompID); + string targetCompId = msg.Header.GetString(Fields.Tags.TargetCompID); - try + if (!IsCorrectCompId(senderCompId, targetCompId)) + { + GenerateReject(msg, FixValues.SessionRejectReason.COMPID_PROBLEM); + GenerateLogout(); + return false; + } + + if (checkTooHigh || checkTooLow) { - msgType = msg.Header.GetString(Fields.Tags.MsgType); - string senderCompId = msg.Header.GetString(Fields.Tags.SenderCompID); - string targetCompId = msg.Header.GetString(Fields.Tags.TargetCompID); + SeqNumType msgSeqNum = msg.Header.GetULong(Fields.Tags.MsgSeqNum); - if (!IsCorrectCompId(senderCompId, targetCompId)) + if (checkTooHigh && IsTargetTooHigh(msgSeqNum)) { - GenerateReject(msg, FixValues.SessionRejectReason.COMPID_PROBLEM); - GenerateLogout(); + DoTargetTooHigh(msg, msgSeqNum); return false; } + if (checkTooLow && IsTargetTooLow(msgSeqNum)) + { + if (_state.LastProcessedMessageWasQueued + && msg.Header.GetString(35) == MsgType.SEQUENCE_RESET + && msg.GetBoolean(Tags.GapFillFlag)) + { + Log.Log(LogLevel.Warning, + "SequenceReset-GapFill 34={MsgSeqNum} is too low (expected {NextTargetMsgSeqNum}), but in this case I'm going to obey it anyway", + msgSeqNum, _state.NextTargetMsgSeqNum); + // This is an uncommon situation, see #309 + } + else + { + DoTargetTooLow(msg, msgSeqNum); + return false; + } + } - if (checkTooHigh || checkTooLow) + if (IsResendRequested) { - SeqNumType msgSeqNum = msg.Header.GetULong(Fields.Tags.MsgSeqNum); + ResendRange range = _state.GetResendRange(); - if (checkTooHigh && IsTargetTooHigh(msgSeqNum)) + if (CmeEnhancedResend && msgType == MsgType.SEQUENCE_RESET && msg.GetBoolean(Tags.GapFillFlag)) { - DoTargetTooHigh(msg, msgSeqNum); - return false; + HandleCmeEnhancedResendGapFill(msg); } - if (checkTooLow && IsTargetTooLow(msgSeqNum)) + else if (msgSeqNum >= range.ActualEndSeqNo) { - if (_state.LastProcessedMessageWasQueued - && msg.Header.GetString(35) == MsgType.SEQUENCE_RESET - && msg.GetBoolean(Tags.GapFillFlag)) - { - Log.Log(LogLevel.Warning, - "SequenceReset-GapFill 34={MsgSeqNum} is too low (expected {NextTargetMsgSeqNum}), but in this case I'm going to obey it anyway", - msgSeqNum, _state.NextTargetMsgSeqNum); - // This is an uncommon situation, see #309 - } - else - { - DoTargetTooLow(msg, msgSeqNum); - return false; - } + Log.Log(LogLevel.Information, + "ResendRequest for messages FROM: {BeginSeqNo} TO: {EndSeqNo} has been satisfied.", + range.BeginSeqNo, range.EndSeqNo); + _state.ResetResendRange(); } - - if (IsResendRequested) + else if (msgSeqNum >= range.ChunkEndSeqNo) { - ResendRange range = _state.GetResendRange(); - - if (CmeEnhancedResend && msgType == MsgType.SEQUENCE_RESET && msg.GetBoolean(Tags.GapFillFlag)) - { - HandleCmeEnhancedResendGapFill(msg); - } - else if (msgSeqNum >= range.ActualEndSeqNo) + Log.Log(LogLevel.Information, + "Chunked ResendRequest for messages FROM: {BeginSeqNo} TO: {ChunkEndSeqNo} has been satisfied", + range.BeginSeqNo, range.ChunkEndSeqNo); + SeqNumType newStart = range.ChunkEndSeqNo + 1; + SeqNumType newChunkEndSeqNo = Math.Min(range.EndSeqNo, range.ChunkEndSeqNo + MaxMessagesInResendRequest); // TODO we can +1 this + + Message resendRequest = CreateResendRequest(msg.Header.GetString(Tags.BeginString), + newStart, newChunkEndSeqNo); + if (EnableLastMsgSeqNumProcessed) + resendRequest.Header.SetField(new LastMsgSeqNumProcessed(msgSeqNum)); + + if (SendRaw(resendRequest)) { Log.Log(LogLevel.Information, - "ResendRequest for messages FROM: {BeginSeqNo} TO: {EndSeqNo} has been satisfied.", - range.BeginSeqNo, range.EndSeqNo); - _state.ResetResendRange(); + "Sent ResendRequest FROM: {NewStart} TO: {NewChunkEndSeqNo}", + newStart, newChunkEndSeqNo); } - else if (msgSeqNum >= range.ChunkEndSeqNo) + else { Log.Log(LogLevel.Information, - "Chunked ResendRequest for messages FROM: {BeginSeqNo} TO: {ChunkEndSeqNo} has been satisfied", - range.BeginSeqNo, range.ChunkEndSeqNo); - SeqNumType newStart = range.ChunkEndSeqNo + 1; - SeqNumType newChunkEndSeqNo = Math.Min(range.EndSeqNo, range.ChunkEndSeqNo + MaxMessagesInResendRequest); // TODO we can +1 this - - Message resendRequest = CreateResendRequest(msg.Header.GetString(Tags.BeginString), + "Error sending ResendRequest ({NewStart}, {NewChunkEndSeqNo})", newStart, newChunkEndSeqNo); - if (EnableLastMsgSeqNumProcessed) - resendRequest.Header.SetField(new LastMsgSeqNumProcessed(msgSeqNum)); - - if (SendRaw(resendRequest)) - { - Log.Log(LogLevel.Information, - "Sent ResendRequest FROM: {NewStart} TO: {NewChunkEndSeqNo}", - newStart, newChunkEndSeqNo); - } - else - { - Log.Log(LogLevel.Information, - "Error sending ResendRequest ({NewStart}, {NewChunkEndSeqNo})", - newStart, newChunkEndSeqNo); - } - - range.UpdateChunk(newStart, newChunkEndSeqNo); } - else if (msgSeqNum >= range.BeginSeqNo) - { - range.MarkAsStarted(); - } - } - } - if (!IsGoodTime(msg)) - { - Log.Log(LogLevel.Error, "Sending time accuracy problem"); - GenerateReject(msg, FixValues.SessionRejectReason.SENDING_TIME_ACCURACY_PROBLEM); - GenerateLogout(); - return false; + range.UpdateChunk(newStart, newChunkEndSeqNo); + } + else if (msgSeqNum >= range.BeginSeqNo) + { + range.MarkAsStarted(); + } } } - catch (Exception e) - { - Log.Log(LogLevel.Error, e, "Verify failed: {Message}", e.Message); - Disconnect("Verify failed: " + e.Message); - return false; - } - - _state.LastReceivedTimeDT = DateTime.UtcNow; - _state.TestRequestCounter = 0; - - if (Message.IsAdminMsgType(msgType)) - Application.FromAdmin(msg, SessionID); - else - Application.FromApp(msg, SessionID); - return true; - } - - public void SetResponder(IResponder responder) - { - if (!IsSessionTime) - Reset("Out of SessionTime (Session.SetResponder)"); - - lock (_sync) + if (!IsGoodTime(msg)) { - _responder = responder; + Log.Log(LogLevel.Error, "Sending time accuracy problem"); + GenerateReject(msg, FixValues.SessionRejectReason.SENDING_TIME_ACCURACY_PROBLEM); + GenerateLogout(); + return false; } } - - public void Refresh() - { - _state.Refresh(); - } - - /// - /// Send a logout, disconnect, and reset session state - /// - /// message to log - /// value to put in the Logout message's Text field (ignored if null/empty string) - public void Reset(string loggedReason, string? logoutMessage = null) - { - if(IsLoggedOn) - GenerateLogout(logoutMessage); - Disconnect("Resetting..."); - _state.Reset(loggedReason); - } - - private void InitializeResendFields(Message message) - { - FieldMap header = message.Header; - DateTime sendingTime = header.GetDateTime(Fields.Tags.SendingTime); - InsertOrigSendingTime(header, sendingTime); - header.SetField(new Fields.PossDupFlag(true)); - InsertSendingTime(header); - } - - protected bool ShouldSendReset() + catch (Exception e) { - return string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX41) >= 0 - && (ResetOnLogon || ResetOnLogout || ResetOnDisconnect) - && _state.NextSenderMsgSeqNum == 1 - && _state.NextTargetMsgSeqNum == 1; + Log.Log(LogLevel.Error, e, "Verify failed: {Message}", e.Message); + Disconnect("Verify failed: " + e.Message); + return false; } - protected bool IsCorrectCompId(string senderCompId, string targetCompId) - { - if (!CheckCompID) - return true; - return SessionID.SenderCompID.Equals(targetCompId) - && SessionID.TargetCompID.Equals(senderCompId); - } + _state.LastReceivedTimeDT = DateTime.UtcNow; + _state.TestRequestCounter = 0; - /// TODO - this fn always returns true-- should it ever be false? - protected bool IsTimeToGenerateLogon() - { - return true; - } + if (Message.IsAdminMsgType(msgType)) + Application.FromAdmin(msg, SessionID); + else + Application.FromApp(msg, SessionID); - protected bool IsTargetTooHigh(SeqNumType msgSeqNum) - { - return msgSeqNum > _state.NextTargetMsgSeqNum; - } + return true; + } - protected bool IsTargetTooLow(SeqNumType msgSeqNum) - { - return msgSeqNum < _state.NextTargetMsgSeqNum; - } + public void SetResponder(IResponder responder) + { + if (!IsSessionTime) + Reset("Out of SessionTime (Session.SetResponder)"); - protected void DoTargetTooHigh(Message msg, SeqNumType msgSeqNum) + lock (_sync) { - string beginString = msg.Header.GetString(Fields.Tags.BeginString); - - Log.Log(LogLevel.Warning, "MsgSeqNum too high, expecting {NextSeqNum} but received {MsgSeqNum}", _state.NextTargetMsgSeqNum, msgSeqNum); - _state.Queue(msgSeqNum, msg); - - if (IsResendRequested) - { - ResendRange range = _state.GetResendRange(); - - if (CmeEnhancedResend && !range.IsResendStarted) - { - return; // don't send another ResendRequest - } - - if (!SendRedundantResendRequests && msgSeqNum >= range.BeginSeqNo) - { - Log.Log(LogLevel.Information, - "Already sent ResendRequest FROM: {BeginSeqNo} TO: {EndSeqNo}. Not sending another", - range.BeginSeqNo, range.EndSeqNo); - return; - } - } - - GenerateResendRequest(beginString, msgSeqNum); + _responder = responder; } + } - protected void DoTargetTooLow(Message msg, SeqNumType msgSeqNum) - { - bool possDupFlag = false; - if (msg.Header.IsSetField(Fields.Tags.PossDupFlag)) - possDupFlag = msg.Header.GetBoolean(Fields.Tags.PossDupFlag); - - if (!possDupFlag) - { - string err = "MsgSeqNum too low, expecting " + _state.NextTargetMsgSeqNum + " but received " + msgSeqNum; - GenerateLogout(err); - throw new QuickFIXException(err); - } + public void Refresh() + { + _state.Refresh(); + } - DoPossDup(msg); - } + /// + /// Send a logout, disconnect, and reset session state + /// + /// message to log + /// value to put in the Logout message's Text field (ignored if null/empty string) + public void Reset(string loggedReason, string? logoutMessage = null) + { + if(IsLoggedOn) + GenerateLogout(logoutMessage); + Disconnect("Resetting..."); + _state.Reset(loggedReason); + } - /// - /// Validates a message where PossDupFlag=Y - /// - /// - protected void DoPossDup(Message msg) - { - // If config RequiresOrigSendingTime=N, then tolerate SequenceReset messages that lack OrigSendingTime (issue #102). - // (This field doesn't really make sense in this message, so some parties omit it, even though spec requires it.) - string msgType = msg.Header.GetString(Fields.Tags.MsgType); - if (msgType == Fields.MsgType.SEQUENCE_RESET && RequiresOrigSendingTime == false) - return; + private void InitializeResendFields(Message message) + { + FieldMap header = message.Header; + DateTime sendingTime = header.GetDateTime(Fields.Tags.SendingTime); + InsertOrigSendingTime(header, sendingTime); + header.SetField(new Fields.PossDupFlag(true)); + InsertSendingTime(header); + } - // Reject if messages don't have OrigSendingTime set - if (!msg.Header.IsSetField(Fields.Tags.OrigSendingTime)) - { - GenerateReject(msg, FixValues.SessionRejectReason.REQUIRED_TAG_MISSING, Fields.Tags.OrigSendingTime); - return; - } + protected bool ShouldSendReset() + { + return string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX41) >= 0 + && (ResetOnLogon || ResetOnLogout || ResetOnDisconnect) + && _state.NextSenderMsgSeqNum == 1 + && _state.NextTargetMsgSeqNum == 1; + } - // Ensure sendingTime is later than OrigSendingTime, else reject and logout - DateTime origSendingTime = msg.Header.GetDateTime(Fields.Tags.OrigSendingTime); - DateTime sendingTime = msg.Header.GetDateTime(Fields.Tags.SendingTime); - System.TimeSpan tmSpan = origSendingTime - sendingTime; + protected bool IsCorrectCompId(string senderCompId, string targetCompId) + { + if (!CheckCompID) + return true; + return SessionID.SenderCompID.Equals(targetCompId) + && SessionID.TargetCompID.Equals(senderCompId); + } - if (tmSpan.TotalSeconds > 0) - { - GenerateReject(msg, FixValues.SessionRejectReason.SENDING_TIME_ACCURACY_PROBLEM); - GenerateLogout(); - } - } + /// TODO - this fn always returns true-- should it ever be false? + protected bool IsTimeToGenerateLogon() + { + return true; + } - protected void GenerateBusinessMessageReject(Message message, int err, int field) - { - string msgType = message.Header.GetString(Tags.MsgType); - SeqNumType msgSeqNum = message.Header.GetULong(Tags.MsgSeqNum); - string reason = FixValues.BusinessRejectReason.RejText[err]; - Message reject; - if (string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX42) >= 0) - { - reject = _msgFactory.Create(SessionID.BeginString, MsgType.BUSINESS_MESSAGE_REJECT); - reject.SetField(new RefMsgType(msgType)); - reject.SetField(new BusinessRejectReason(err)); - } - else - { - reject = _msgFactory.Create(SessionID.BeginString, MsgType.REJECT); - char[] reasonArray = reason.ToLower().ToCharArray(); - reasonArray[0] = char.ToUpper(reasonArray[0]); - reason = new string(reasonArray); - } - InitializeHeader(reject); - reject.SetField(new RefSeqNum(msgSeqNum)); - _state.IncrNextTargetMsgSeqNum(); + protected bool IsTargetTooHigh(SeqNumType msgSeqNum) + { + return msgSeqNum > _state.NextTargetMsgSeqNum; + } + protected bool IsTargetTooLow(SeqNumType msgSeqNum) + { + return msgSeqNum < _state.NextTargetMsgSeqNum; + } - reject.SetField(new Text(reason)); - Log.Log(LogLevel.Information, "Reject sent for Message: {MsgSeqNum} Reason: {Reason}", msgSeqNum, reason); - SendRaw(reject); - } + protected void DoTargetTooHigh(Message msg, SeqNumType msgSeqNum) + { + string beginString = msg.Header.GetString(Fields.Tags.BeginString); - private Message CreateResendRequest(string beginString, SeqNumType startSeqNum, SeqNumType endSeqNum) - { - Message resendRequest = _msgFactory.Create(beginString, MsgType.RESEND_REQUEST); - resendRequest.SetField(new BeginSeqNo(startSeqNum)); - resendRequest.SetField(new EndSeqNo(endSeqNum)); - InitializeHeader(resendRequest); - return resendRequest; - } + Log.Log(LogLevel.Warning, "MsgSeqNum too high, expecting {NextSeqNum} but received {MsgSeqNum}", _state.NextTargetMsgSeqNum, msgSeqNum); + _state.Queue(msgSeqNum, msg); - internal void GenerateResendRequest(string beginString, SeqNumType msgSeqNum) + if (IsResendRequested) { - SeqNumType beginSeqNum = _state.NextTargetMsgSeqNum; - SeqNumType endRangeSeqNum = msgSeqNum - 1; - SeqNumType endChunkSeqNum; - if (MaxMessagesInResendRequest > 0) - { - endChunkSeqNum = Math.Min(endRangeSeqNum, beginSeqNum + MaxMessagesInResendRequest - 1); - } - else + ResendRange range = _state.GetResendRange(); + + if (CmeEnhancedResend && !range.IsResendStarted) { - if (string.CompareOrdinal(beginString, FixValues.BeginString.FIX42) >= 0) - endRangeSeqNum = 0; - else if (string.CompareOrdinal(beginString, FixValues.BeginString.FIX41) <= 0) - endRangeSeqNum = 999999; - endChunkSeqNum = endRangeSeqNum; + return; // don't send another ResendRequest } - Message resendRequest = CreateResendRequest(beginString, beginSeqNum, endChunkSeqNum); - if (EnableLastMsgSeqNumProcessed) - resendRequest.Header.SetField(new LastMsgSeqNumProcessed(msgSeqNum)); - - if (SendRaw(resendRequest)) + if (!SendRedundantResendRequests && msgSeqNum >= range.BeginSeqNo) { - Log.Log(LogLevel.Information, "Sent ResendRequest FROM: {BeginSeqNum} TO: {EndChunkSeqNum}", - beginSeqNum, endChunkSeqNum); - _state.SetResendRange(beginSeqNum, endRangeSeqNum, msgSeqNum, resendRequest, - endChunkSeqNum==0 ? ResendRange.NOT_SET : endChunkSeqNum); + Log.Log(LogLevel.Information, + "Already sent ResendRequest FROM: {BeginSeqNo} TO: {EndSeqNo}. Not sending another", + range.BeginSeqNo, range.EndSeqNo); return; } - - Log.Log(LogLevel.Error, "Error sending ResendRequest ({BeginSeqNum} ,{EndChunkSeqNum})", - beginSeqNum, endChunkSeqNum); } - /// - /// Create and send a logon - /// - /// true of logon was successfully sent - internal bool GenerateLogon() + GenerateResendRequest(beginString, msgSeqNum); + } + + protected void DoTargetTooLow(Message msg, SeqNumType msgSeqNum) + { + bool possDupFlag = false; + if (msg.Header.IsSetField(Fields.Tags.PossDupFlag)) + possDupFlag = msg.Header.GetBoolean(Fields.Tags.PossDupFlag); + + if (!possDupFlag) { - Message logon = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.LOGON); - logon.SetField(new Fields.EncryptMethod(0)); - logon.SetField(new Fields.HeartBtInt(_state.HeartBtInt)); - - if (SessionID.IsFIXT) - logon.SetField(new Fields.DefaultApplVerID(SenderDefaultApplVerID)); - if (RefreshOnLogon) - Refresh(); - if (ResetOnLogon) - _state.Reset("ResetOnLogon"); - if (ShouldSendReset()) - logon.SetField(new Fields.ResetSeqNumFlag(true)); - - InitializeHeader(logon); - _state.LastReceivedTimeDT = DateTime.UtcNow; - _state.TestRequestCounter = 0; - _state.SentLogon = SendRaw(logon); - return _state.SentLogon; + string err = "MsgSeqNum too low, expecting " + _state.NextTargetMsgSeqNum + " but received " + msgSeqNum; + GenerateLogout(err); + throw new QuickFIXException(err); } - protected bool GenerateLogon(Message otherLogon) - { - Message logon = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.LOGON); - logon.SetField(new Fields.EncryptMethod(0)); - logon.SetField(new Fields.HeartBtInt(otherLogon.GetInt(Tags.HeartBtInt))); + DoPossDup(msg); + } - if (SessionID.IsFIXT) - logon.SetField(new Fields.DefaultApplVerID(SenderDefaultApplVerID)); - if (EnableLastMsgSeqNumProcessed) - logon.Header.SetField(new Fields.LastMsgSeqNumProcessed(otherLogon.Header.GetULong(Tags.MsgSeqNum))); + /// + /// Validates a message where PossDupFlag=Y + /// + /// + protected void DoPossDup(Message msg) + { + // If config RequiresOrigSendingTime=N, then tolerate SequenceReset messages that lack OrigSendingTime (issue #102). + // (This field doesn't really make sense in this message, so some parties omit it, even though spec requires it.) + string msgType = msg.Header.GetString(Fields.Tags.MsgType); + if (msgType == Fields.MsgType.SEQUENCE_RESET && RequiresOrigSendingTime == false) + return; - InitializeHeader(logon); - _state.SentLogon = SendRaw(logon); - return _state.SentLogon; + // Reject if messages don't have OrigSendingTime set + if (!msg.Header.IsSetField(Fields.Tags.OrigSendingTime)) + { + GenerateReject(msg, FixValues.SessionRejectReason.REQUIRED_TAG_MISSING, Fields.Tags.OrigSendingTime); + return; } - public void GenerateTestRequest(string id) + // Ensure sendingTime is later than OrigSendingTime, else reject and logout + DateTime origSendingTime = msg.Header.GetDateTime(Fields.Tags.OrigSendingTime); + DateTime sendingTime = msg.Header.GetDateTime(Fields.Tags.SendingTime); + System.TimeSpan tmSpan = origSendingTime - sendingTime; + + if (tmSpan.TotalSeconds > 0) { - Message testRequest = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.TEST_REQUEST); - InitializeHeader(testRequest); - testRequest.SetField(new Fields.TestReqID(id)); - SendRaw(testRequest); + GenerateReject(msg, FixValues.SessionRejectReason.SENDING_TIME_ACCURACY_PROBLEM); + GenerateLogout(); } + } - /// - /// Send a basic Logout message - /// - /// - public void GenerateLogout() + protected void GenerateBusinessMessageReject(Message message, int err, int field) + { + string msgType = message.Header.GetString(Tags.MsgType); + SeqNumType msgSeqNum = message.Header.GetULong(Tags.MsgSeqNum); + string reason = FixValues.BusinessRejectReason.RejText[err]; + Message reject; + if (string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX42) >= 0) { - ImplGenerateLogout(); + reject = _msgFactory.Create(SessionID.BeginString, MsgType.BUSINESS_MESSAGE_REJECT); + reject.SetField(new RefMsgType(msgType)); + reject.SetField(new BusinessRejectReason(err)); } - - /// - /// Send a Logout message - /// - /// written into the Text field - /// - private void GenerateLogout(string? text) + else { - ImplGenerateLogout(text: text); + reject = _msgFactory.Create(SessionID.BeginString, MsgType.REJECT); + char[] reasonArray = reason.ToLower().ToCharArray(); + reasonArray[0] = char.ToUpper(reasonArray[0]); + reason = new string(reasonArray); } + InitializeHeader(reject); + reject.SetField(new RefSeqNum(msgSeqNum)); + _state.IncrNextTargetMsgSeqNum(); - /// - /// Send a Logout message - /// - /// used to fill MsgSeqNum field, if configuration requires it - /// - private void GenerateLogout(Message other) + + reject.SetField(new Text(reason)); + Log.Log(LogLevel.Information, "Reject sent for Message: {MsgSeqNum} Reason: {Reason}", msgSeqNum, reason); + SendRaw(reject); + } + + private Message CreateResendRequest(string beginString, SeqNumType startSeqNum, SeqNumType endSeqNum) + { + Message resendRequest = _msgFactory.Create(beginString, MsgType.RESEND_REQUEST); + resendRequest.SetField(new BeginSeqNo(startSeqNum)); + resendRequest.SetField(new EndSeqNo(endSeqNum)); + InitializeHeader(resendRequest); + return resendRequest; + } + + internal void GenerateResendRequest(string beginString, SeqNumType msgSeqNum) + { + SeqNumType beginSeqNum = _state.NextTargetMsgSeqNum; + SeqNumType endRangeSeqNum = msgSeqNum - 1; + SeqNumType endChunkSeqNum; + if (MaxMessagesInResendRequest > 0) { - ImplGenerateLogout(other: other); + endChunkSeqNum = Math.Min(endRangeSeqNum, beginSeqNum + MaxMessagesInResendRequest - 1); } - - /// - /// Common implementation for variant GenerateLogout() function interfaces - /// - /// used to fill MsgSeqNum field, if configuration requires it; ignored if null - /// written into the Text field; ignored if empty/null - /// - private void ImplGenerateLogout(Message? other = null, string? text = null) + else { - Message logout = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.LOGOUT); - InitializeHeader(logout); - if (!string.IsNullOrEmpty(text)) - logout.SetField(new Fields.Text(text)); - if (other is not null && EnableLastMsgSeqNumProcessed) - { - try - { - logout.Header.SetField(new Fields.LastMsgSeqNumProcessed(other.Header.GetULong(Tags.MsgSeqNum))); - } - catch (FieldNotFoundException e) - { - Log.Log(LogLevel.Error, e, "Error: No message sequence number: {Other}", other); - } - } - _state.SentLogout = SendRaw(logout); + if (string.CompareOrdinal(beginString, FixValues.BeginString.FIX42) >= 0) + endRangeSeqNum = 0; + else if (string.CompareOrdinal(beginString, FixValues.BeginString.FIX41) <= 0) + endRangeSeqNum = 999999; + endChunkSeqNum = endRangeSeqNum; } - public void GenerateHeartbeat() + Message resendRequest = CreateResendRequest(beginString, beginSeqNum, endChunkSeqNum); + if (EnableLastMsgSeqNumProcessed) + resendRequest.Header.SetField(new LastMsgSeqNumProcessed(msgSeqNum)); + + if (SendRaw(resendRequest)) { - Message heartbeat = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.HEARTBEAT); - InitializeHeader(heartbeat); - SendRaw(heartbeat); + Log.Log(LogLevel.Information, "Sent ResendRequest FROM: {BeginSeqNum} TO: {EndChunkSeqNum}", + beginSeqNum, endChunkSeqNum); + _state.SetResendRange(beginSeqNum, endRangeSeqNum, msgSeqNum, resendRequest, + endChunkSeqNum==0 ? ResendRange.NOT_SET : endChunkSeqNum); + return; } - public void GenerateHeartbeat(Message testRequest) + Log.Log(LogLevel.Error, "Error sending ResendRequest ({BeginSeqNum} ,{EndChunkSeqNum})", + beginSeqNum, endChunkSeqNum); + } + + /// + /// Create and send a logon + /// + /// true of logon was successfully sent + internal bool GenerateLogon() + { + Message logon = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.LOGON); + logon.SetField(new Fields.EncryptMethod(0)); + logon.SetField(new Fields.HeartBtInt(_state.HeartBtInt)); + + if (SessionID.IsFIXT) + logon.SetField(new Fields.DefaultApplVerID(SenderDefaultApplVerID)); + if (RefreshOnLogon) + Refresh(); + if (ResetOnLogon) + _state.Reset("ResetOnLogon"); + if (ShouldSendReset()) + logon.SetField(new Fields.ResetSeqNumFlag(true)); + + InitializeHeader(logon); + _state.LastReceivedTimeDT = DateTime.UtcNow; + _state.TestRequestCounter = 0; + _state.SentLogon = SendRaw(logon); + return _state.SentLogon; + } + + protected bool GenerateLogon(Message otherLogon) + { + Message logon = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.LOGON); + logon.SetField(new Fields.EncryptMethod(0)); + logon.SetField(new Fields.HeartBtInt(otherLogon.GetInt(Tags.HeartBtInt))); + + if (SessionID.IsFIXT) + logon.SetField(new Fields.DefaultApplVerID(SenderDefaultApplVerID)); + if (EnableLastMsgSeqNumProcessed) + logon.Header.SetField(new Fields.LastMsgSeqNumProcessed(otherLogon.Header.GetULong(Tags.MsgSeqNum))); + + InitializeHeader(logon); + _state.SentLogon = SendRaw(logon); + return _state.SentLogon; + } + + public void GenerateTestRequest(string id) + { + Message testRequest = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.TEST_REQUEST); + InitializeHeader(testRequest); + testRequest.SetField(new Fields.TestReqID(id)); + SendRaw(testRequest); + } + + /// + /// Send a basic Logout message + /// + /// + public void GenerateLogout() + { + ImplGenerateLogout(); + } + + /// + /// Send a Logout message + /// + /// written into the Text field + /// + private void GenerateLogout(string? text) + { + ImplGenerateLogout(text: text); + } + + /// + /// Send a Logout message + /// + /// used to fill MsgSeqNum field, if configuration requires it + /// + private void GenerateLogout(Message other) + { + ImplGenerateLogout(other: other); + } + + /// + /// Common implementation for variant GenerateLogout() function interfaces + /// + /// used to fill MsgSeqNum field, if configuration requires it; ignored if null + /// written into the Text field; ignored if empty/null + /// + private void ImplGenerateLogout(Message? other = null, string? text = null) + { + Message logout = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.LOGOUT); + InitializeHeader(logout); + if (!string.IsNullOrEmpty(text)) + logout.SetField(new Fields.Text(text)); + if (other is not null && EnableLastMsgSeqNumProcessed) { - Message heartbeat = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.HEARTBEAT); - InitializeHeader(heartbeat); try { - heartbeat.SetField(new Fields.TestReqID(testRequest.GetString(Fields.Tags.TestReqID))); - if (EnableLastMsgSeqNumProcessed) - { - heartbeat.Header.SetField(new Fields.LastMsgSeqNumProcessed(testRequest.Header.GetULong(Tags.MsgSeqNum))); - } + logout.Header.SetField(new Fields.LastMsgSeqNumProcessed(other.Header.GetULong(Tags.MsgSeqNum))); + } + catch (FieldNotFoundException e) + { + Log.Log(LogLevel.Error, e, "Error: No message sequence number: {Other}", other); } - catch (FieldNotFoundException) - { } - SendRaw(heartbeat); } + _state.SentLogout = SendRaw(logout); + } + + public void GenerateHeartbeat() + { + Message heartbeat = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.HEARTBEAT); + InitializeHeader(heartbeat); + SendRaw(heartbeat); + } - internal void GenerateReject(MessageBuilder msgBuilder, FixValues.SessionRejectReason reason, int field) + public void GenerateHeartbeat(Message testRequest) + { + Message heartbeat = _msgFactory.Create(SessionID.BeginString, Fields.MsgType.HEARTBEAT); + InitializeHeader(heartbeat); + try { - GenerateReject(msgBuilder.RejectableMessage(), reason, field); + heartbeat.SetField(new Fields.TestReqID(testRequest.GetString(Fields.Tags.TestReqID))); + if (EnableLastMsgSeqNumProcessed) + { + heartbeat.Header.SetField(new Fields.LastMsgSeqNumProcessed(testRequest.Header.GetULong(Tags.MsgSeqNum))); + } } + catch (FieldNotFoundException) + { } + SendRaw(heartbeat); + } - public void GenerateReject(Message message, FixValues.SessionRejectReason reason, int field = 0) - { - string beginString = SessionID.BeginString; + internal void GenerateReject(MessageBuilder msgBuilder, FixValues.SessionRejectReason reason, int field) + { + GenerateReject(msgBuilder.RejectableMessage(), reason, field); + } - Message reject = _msgFactory.Create(beginString, Fields.MsgType.REJECT); - reject.ReverseRoute(message.Header); - InitializeHeader(reject); + public void GenerateReject(Message message, FixValues.SessionRejectReason reason, int field = 0) + { + string beginString = SessionID.BeginString; - string msgType = message.Header.IsSetField(Fields.Tags.MsgType) - ? message.Header.GetString(Fields.Tags.MsgType) : ""; + Message reject = _msgFactory.Create(beginString, Fields.MsgType.REJECT); + reject.ReverseRoute(message.Header); + InitializeHeader(reject); - SeqNumType msgSeqNum = 0; - if (message.Header.IsSetField(Fields.Tags.MsgSeqNum)) - { - try - { - msgSeqNum = message.Header.GetULong(Fields.Tags.MsgSeqNum); - reject.SetField(new Fields.RefSeqNum(msgSeqNum)); - } - catch (Exception ex) - { - Log.Log(LogLevel.Error, ex, "Exception while setting RefSeqNum: {Exception}", ex); - } - } + string msgType = message.Header.IsSetField(Fields.Tags.MsgType) + ? message.Header.GetString(Fields.Tags.MsgType) : ""; - if (string.CompareOrdinal(beginString, FixValues.BeginString.FIX42) >= 0) + SeqNumType msgSeqNum = 0; + if (message.Header.IsSetField(Fields.Tags.MsgSeqNum)) + { + try { - if (msgType.Length > 0) - reject.SetField(new Fields.RefMsgType(msgType)); - if ((FixValues.BeginString.FIX42.Equals(beginString) && reason.Value <= FixValues.SessionRejectReason.INVALID_MSGTYPE.Value) - || string.CompareOrdinal(beginString, FixValues.BeginString.FIX42) > 0) - { - reject.SetField(new Fields.SessionRejectReason(reason.Value)); - } + msgSeqNum = message.Header.GetULong(Fields.Tags.MsgSeqNum); + reject.SetField(new Fields.RefSeqNum(msgSeqNum)); } - if (!MsgType.LOGON.Equals(msgType) - && !MsgType.SEQUENCE_RESET.Equals(msgType) - && msgSeqNum == _state.NextTargetMsgSeqNum) + catch (Exception ex) { - _state.IncrNextTargetMsgSeqNum(); + Log.Log(LogLevel.Error, ex, "Exception while setting RefSeqNum: {Exception}", ex); } + } - if (0 != field || FixValues.SessionRejectReason.INVALID_TAG_NUMBER.Equals(reason)) - { - if (FixValues.SessionRejectReason.INVALID_MSGTYPE.Equals(reason)) - { - if (string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX43) >= 0) - PopulateRejectReason(reject, reason.Description); - else - PopulateSessionRejectReason(reject, field, reason.Description, false); - } - else - PopulateSessionRejectReason(reject, field, reason.Description, true); - - Log.Log(LogLevel.Warning, "Message {MsgSeqNum} Rejected: {Reason} (Field={Field})", msgSeqNum, reason.Description, field); - } - else + if (string.CompareOrdinal(beginString, FixValues.BeginString.FIX42) >= 0) + { + if (msgType.Length > 0) + reject.SetField(new Fields.RefMsgType(msgType)); + if ((FixValues.BeginString.FIX42.Equals(beginString) && reason.Value <= FixValues.SessionRejectReason.INVALID_MSGTYPE.Value) + || string.CompareOrdinal(beginString, FixValues.BeginString.FIX42) > 0) { - PopulateRejectReason(reject, reason.Description); - Log.Log(LogLevel.Error, "Message {MsgSeqNum} Rejected: {Reason}", msgSeqNum, reason.Value); + reject.SetField(new Fields.SessionRejectReason(reason.Value)); } - - if (!_state.ReceivedLogon) - throw new QuickFIXException("Tried to send a reject while not logged on"); - - SendRaw(reject); + } + if (!MsgType.LOGON.Equals(msgType) + && !MsgType.SEQUENCE_RESET.Equals(msgType) + && msgSeqNum == _state.NextTargetMsgSeqNum) + { + _state.IncrNextTargetMsgSeqNum(); } - protected void PopulateSessionRejectReason(Message reject, int field, string text, bool includeFieldInfo) + if (0 != field || FixValues.SessionRejectReason.INVALID_TAG_NUMBER.Equals(reason)) { - if (string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX42) >= 0) - { - reject.SetField(new Fields.RefTagID(field)); - reject.SetField(new Fields.Text(text)); - } - else + if (FixValues.SessionRejectReason.INVALID_MSGTYPE.Equals(reason)) { - if (includeFieldInfo) - reject.SetField(new Fields.Text(text + " (" + field + ")")); + if (string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX43) >= 0) + PopulateRejectReason(reject, reason.Description); else - reject.SetField(new Fields.Text(text)); + PopulateSessionRejectReason(reject, field, reason.Description, false); } + else + PopulateSessionRejectReason(reject, field, reason.Description, true); + + Log.Log(LogLevel.Warning, "Message {MsgSeqNum} Rejected: {Reason} (Field={Field})", msgSeqNum, reason.Description, field); + } + else + { + PopulateRejectReason(reject, reason.Description); + Log.Log(LogLevel.Error, "Message {MsgSeqNum} Rejected: {Reason}", msgSeqNum, reason.Value); } - protected void PopulateRejectReason(Message reject, string text) + if (!_state.ReceivedLogon) + throw new QuickFIXException("Tried to send a reject while not logged on"); + + SendRaw(reject); + } + + protected void PopulateSessionRejectReason(Message reject, int field, string text, bool includeFieldInfo) + { + if (string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX42) >= 0) { + reject.SetField(new Fields.RefTagID(field)); reject.SetField(new Fields.Text(text)); } - - protected void InitializeHeader(Message m, SeqNumType msgSeqNum = 0) + else { - _state.LastSentTimeDT = DateTime.UtcNow; - m.Header.SetField(new Fields.BeginString(SessionID.BeginString)); - m.Header.SetField(new Fields.SenderCompID(SessionID.SenderCompID)); - if (SessionID.IsSet(SessionID.SenderSubID)) - m.Header.SetField(new Fields.SenderSubID(SessionID.SenderSubID)); - if (SessionID.IsSet(SessionID.SenderLocationID)) - m.Header.SetField(new Fields.SenderLocationID(SessionID.SenderLocationID)); - m.Header.SetField(new Fields.TargetCompID(SessionID.TargetCompID)); - if (SessionID.IsSet(SessionID.TargetSubID)) - m.Header.SetField(new Fields.TargetSubID(SessionID.TargetSubID)); - if (SessionID.IsSet(SessionID.TargetLocationID)) - m.Header.SetField(new Fields.TargetLocationID(SessionID.TargetLocationID)); - - if (msgSeqNum > 0) - m.Header.SetField(new Fields.MsgSeqNum(msgSeqNum)); + if (includeFieldInfo) + reject.SetField(new Fields.Text(text + " (" + field + ")")); else - m.Header.SetField(new Fields.MsgSeqNum(_state.NextSenderMsgSeqNum)); + reject.SetField(new Fields.Text(text)); + } + } - if (EnableLastMsgSeqNumProcessed && !m.Header.IsSetField(Tags.LastMsgSeqNumProcessed)) - m.Header.SetField(new LastMsgSeqNumProcessed(NextTargetMsgSeqNum - 1)); + protected void PopulateRejectReason(Message reject, string text) + { + reject.SetField(new Fields.Text(text)); + } - InsertSendingTime(m.Header); - } + protected void InitializeHeader(Message m, SeqNumType msgSeqNum = 0) + { + _state.LastSentTimeDT = DateTime.UtcNow; + m.Header.SetField(new Fields.BeginString(SessionID.BeginString)); + m.Header.SetField(new Fields.SenderCompID(SessionID.SenderCompID)); + if (SessionID.IsSet(SessionID.SenderSubID)) + m.Header.SetField(new Fields.SenderSubID(SessionID.SenderSubID)); + if (SessionID.IsSet(SessionID.SenderLocationID)) + m.Header.SetField(new Fields.SenderLocationID(SessionID.SenderLocationID)); + m.Header.SetField(new Fields.TargetCompID(SessionID.TargetCompID)); + if (SessionID.IsSet(SessionID.TargetSubID)) + m.Header.SetField(new Fields.TargetSubID(SessionID.TargetSubID)); + if (SessionID.IsSet(SessionID.TargetLocationID)) + m.Header.SetField(new Fields.TargetLocationID(SessionID.TargetLocationID)); + + if (msgSeqNum > 0) + m.Header.SetField(new Fields.MsgSeqNum(msgSeqNum)); + else + m.Header.SetField(new Fields.MsgSeqNum(_state.NextSenderMsgSeqNum)); + + if (EnableLastMsgSeqNumProcessed && !m.Header.IsSetField(Tags.LastMsgSeqNumProcessed)) + m.Header.SetField(new LastMsgSeqNumProcessed(NextTargetMsgSeqNum - 1)); + + InsertSendingTime(m.Header); + } - private bool IsFix42OrAbove() { - return SessionID.BeginString == FixValues.BeginString.FIXT11 - || string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX42) >= 0; - } + private bool IsFix42OrAbove() { + return SessionID.BeginString == FixValues.BeginString.FIXT11 + || string.CompareOrdinal(SessionID.BeginString, FixValues.BeginString.FIX42) >= 0; + } - protected void InsertSendingTime(FieldMap header) - { - header.SetField(new Fields.SendingTime( - DateTime.UtcNow, IsFix42OrAbove() ? TimeStampPrecision : TimeStampPrecision.Second ) ); - } + protected void InsertSendingTime(FieldMap header) + { + header.SetField(new Fields.SendingTime( + DateTime.UtcNow, IsFix42OrAbove() ? TimeStampPrecision : TimeStampPrecision.Second ) ); + } - protected void Persist(Message message, string messageString) + protected void Persist(Message message, string messageString) + { + if (PersistMessages) { - if (PersistMessages) - { - SeqNumType msgSeqNum = message.Header.GetULong(Fields.Tags.MsgSeqNum); - _state.Set(msgSeqNum, messageString); - } - _state.IncrNextSenderMsgSeqNum(); + SeqNumType msgSeqNum = message.Header.GetULong(Fields.Tags.MsgSeqNum); + _state.Set(msgSeqNum, messageString); } + _state.IncrNextSenderMsgSeqNum(); + } - protected bool IsGoodTime(Message msg) - { - if (!CheckLatency) - return true; + protected bool IsGoodTime(Message msg) + { + if (!CheckLatency) + return true; - var sendingTime = msg.Header.GetDateTime(Fields.Tags.SendingTime); - TimeSpan tmSpan = DateTime.UtcNow - sendingTime; - return Math.Abs(tmSpan.TotalSeconds) <= MaxLatency; - } + var sendingTime = msg.Header.GetDateTime(Fields.Tags.SendingTime); + TimeSpan tmSpan = DateTime.UtcNow - sendingTime; + return Math.Abs(tmSpan.TotalSeconds) <= MaxLatency; + } - private void GenerateSequenceReset(Message receivedMessage, SeqNumType beginSeqNo, SeqNumType endSeqNo) + private void GenerateSequenceReset(Message receivedMessage, SeqNumType beginSeqNo, SeqNumType endSeqNo) + { + string beginString = SessionID.BeginString; + Message sequenceReset = _msgFactory.Create(beginString, Fields.MsgType.SEQUENCE_RESET); + InitializeHeader(sequenceReset); + SeqNumType newSeqNo = endSeqNo; + sequenceReset.Header.SetField(new PossDupFlag(true)); + InsertOrigSendingTime(sequenceReset.Header, sequenceReset.Header.GetDateTime(Tags.SendingTime)); + + sequenceReset.Header.SetField(new MsgSeqNum(beginSeqNo)); + sequenceReset.SetField(new NewSeqNo(newSeqNo)); + sequenceReset.SetField(new GapFillFlag(true)); + if (EnableLastMsgSeqNumProcessed) { - string beginString = SessionID.BeginString; - Message sequenceReset = _msgFactory.Create(beginString, Fields.MsgType.SEQUENCE_RESET); - InitializeHeader(sequenceReset); - SeqNumType newSeqNo = endSeqNo; - sequenceReset.Header.SetField(new PossDupFlag(true)); - InsertOrigSendingTime(sequenceReset.Header, sequenceReset.Header.GetDateTime(Tags.SendingTime)); - - sequenceReset.Header.SetField(new MsgSeqNum(beginSeqNo)); - sequenceReset.SetField(new NewSeqNo(newSeqNo)); - sequenceReset.SetField(new GapFillFlag(true)); - if (EnableLastMsgSeqNumProcessed) + try { - try - { - sequenceReset.Header.SetField(new Fields.LastMsgSeqNumProcessed(receivedMessage.Header.GetULong(Tags.MsgSeqNum))); - } - catch (FieldNotFoundException e) - { - Log.Log(LogLevel.Error, e, "Error: Received message without MsgSeqNum: {ReceivedMessage}", - receivedMessage); - } + sequenceReset.Header.SetField(new Fields.LastMsgSeqNumProcessed(receivedMessage.Header.GetULong(Tags.MsgSeqNum))); + } + catch (FieldNotFoundException e) + { + Log.Log(LogLevel.Error, e, "Error: Received message without MsgSeqNum: {ReceivedMessage}", + receivedMessage); } - SendRaw(sequenceReset, beginSeqNo); - Log.Log(LogLevel.Information, "Sent SequenceReset TO: {NewSeqNo}", newSeqNo); } + SendRaw(sequenceReset, beginSeqNo); + Log.Log(LogLevel.Information, "Sent SequenceReset TO: {NewSeqNo}", newSeqNo); + } + + protected void InsertOrigSendingTime(FieldMap header, DateTime sendingTime) + { + header.SetField(new OrigSendingTime( + sendingTime, IsFix42OrAbove() ? TimeStampPrecision : TimeStampPrecision.Second ) ); + } - protected void InsertOrigSendingTime(FieldMap header, DateTime sendingTime) + protected void NextQueued() + { + while (NextQueued(_state.MessageStore.NextTargetMsgSeqNum)) { - header.SetField(new OrigSendingTime( - sendingTime, IsFix42OrAbove() ? TimeStampPrecision : TimeStampPrecision.Second ) ); + // continue } + } + + protected bool NextQueued(SeqNumType num) + { + Message? msg = _state.Dequeue(num); - protected void NextQueued() + if (msg is not null) { - while (NextQueued(_state.MessageStore.NextTargetMsgSeqNum)) + Log.Log(LogLevel.Information, "Processing queued message: {Num}", num); + + string msgType = msg.Header.GetString(Tags.MsgType); + if (msgType.Equals(MsgType.LOGON) || msgType.Equals(MsgType.RESEND_REQUEST)) + { + _state.IncrNextTargetMsgSeqNum(); + } + else { - // continue + NextMessage(msg.ConstructString()); } + _state.LastProcessedMessageWasQueued = true; + return true; } + return false; + } + + private static bool IsAdminMessage(Message msg) + { + var msgType = msg.Header.GetString(Fields.Tags.MsgType); + return AdminMsgTypes.Contains(msgType); + } - protected bool NextQueued(SeqNumType num) + /// + /// Update the header as needed and send the message + /// + /// + /// if non-zero, set this seqno in the message (else leave existing value alone) + /// + protected bool SendRaw(Message message, SeqNumType seqNum = 0UL) + { + lock (_sync) { - Message? msg = _state.Dequeue(num); + string msgType = message.Header.GetString(Fields.Tags.MsgType); - if (msg is not null) - { - Log.Log(LogLevel.Information, "Processing queued message: {Num}", num); + InitializeHeader(message, seqNum); - string msgType = msg.Header.GetString(Tags.MsgType); - if (msgType.Equals(MsgType.LOGON) || msgType.Equals(MsgType.RESEND_REQUEST)) + if (Message.IsAdminMsgType(msgType)) + { + try { - _state.IncrNextTargetMsgSeqNum(); + Application.ToAdmin(message, SessionID); } - else + catch (DoNotSend) { - NextMessage(msg.ConstructString()); + return false; } - _state.LastProcessedMessageWasQueued = true; - return true; - } - return false; - } - - private static bool IsAdminMessage(Message msg) - { - var msgType = msg.Header.GetString(Fields.Tags.MsgType); - return AdminMsgTypes.Contains(msgType); - } - - /// - /// Update the header as needed and send the message - /// - /// - /// if non-zero, set this seqno in the message (else leave existing value alone) - /// - protected bool SendRaw(Message message, SeqNumType seqNum = 0UL) - { - lock (_sync) - { - string msgType = message.Header.GetString(Fields.Tags.MsgType); - InitializeHeader(message, seqNum); - - if (Message.IsAdminMsgType(msgType)) + if (MsgType.LOGON.Equals(msgType) && !_state.ReceivedReset) { - try - { - Application.ToAdmin(message, SessionID); - } - catch (DoNotSend) - { - return false; - } - - if (MsgType.LOGON.Equals(msgType) && !_state.ReceivedReset) + Fields.ResetSeqNumFlag resetSeqNumFlag = new Fields.ResetSeqNumFlag(false); + if (message.IsSetField(resetSeqNumFlag)) + message.GetField(resetSeqNumFlag); + if (resetSeqNumFlag.Value) { - Fields.ResetSeqNumFlag resetSeqNumFlag = new Fields.ResetSeqNumFlag(false); - if (message.IsSetField(resetSeqNumFlag)) - message.GetField(resetSeqNumFlag); - if (resetSeqNumFlag.Value) - { - _state.Reset("ResetSeqNumFlag"); - message.Header.SetField(new Fields.MsgSeqNum(_state.NextSenderMsgSeqNum)); - } - _state.SentReset = resetSeqNumFlag.Value; + _state.Reset("ResetSeqNumFlag"); + message.Header.SetField(new Fields.MsgSeqNum(_state.NextSenderMsgSeqNum)); } + _state.SentReset = resetSeqNumFlag.Value; } - else + } + else + { + try { - try - { - Application.ToApp(message, SessionID); - } - catch (DoNotSend) - { - return false; - } + Application.ToApp(message, SessionID); + } + catch (DoNotSend) + { + return false; } - - string messageString = message.ConstructString(); - if (0UL == seqNum) - Persist(message, messageString); - return Send(messageString); } - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); + string messageString = message.ConstructString(); + if (0UL == seqNum) + Persist(message, messageString); + return Send(messageString); } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - public bool Disposed { get; private set; } = false; + public bool Disposed { get; private set; } = false; - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (Disposed) + return; + if (disposing) { - if (Disposed) - return; - if (disposing) + _state.Dispose(); + lock (Sessions) { - _state.Dispose(); - lock (Sessions) - { - Sessions.Remove(SessionID); - } + Sessions.Remove(SessionID); } - Disposed = true; } - - ~Session() => Dispose(false); + Disposed = true; } + + ~Session() => Dispose(false); } diff --git a/QuickFIXn/SessionState.cs b/QuickFIXn/SessionState.cs index 39bd8c38d..38ce466d7 100755 --- a/QuickFIXn/SessionState.cs +++ b/QuickFIXn/SessionState.cs @@ -4,420 +4,419 @@ using QuickFix.Store; using MessagesBySeqNum = System.Collections.Generic.Dictionary; -namespace QuickFix +namespace QuickFix; + +// v2 TODO - consider making this internal +/// +/// Used by the session communications code. Not intended to be used by applications. +/// +public class SessionState : IDisposable { - // v2 TODO - consider making this internal + #region Private Members + + private readonly object _sync = new(); + private bool _isEnabled = true; + private bool _receivedLogon = false; + private bool _receivedReset = false; + private bool _sentLogon = false; + private bool _sentLogout = false; + private bool _sentReset = false; + private string _logoutReason = ""; + private int _testRequestCounter = 0; + private int _heartBtInt = 0; + private int _heartBtIntAsMilliSecs = 0; + private DateTime _lastReceivedTimeDt = DateTime.MinValue; + private DateTime _lastSentTimeDt = DateTime.MinValue; + private int _logonTimeout = 10; + private long _logonTimeoutAsMilliSecs = 10 * 1000; + private int _logoutTimeout = 2; + private long _logoutTimeoutAsMilliSecs = 2 * 1000; + private readonly ResendRange _resendRange = new (); + private MessagesBySeqNum _msgQueue = new (); + + #endregion + + #region Unsynchronized Properties + + public IMessageStore MessageStore + { get; } + + public bool IsInitiator + { get; set; } + + public bool ShouldSendLogon => IsInitiator && !SentLogon; + + public ILogger Log { get; } + /// - /// Used by the session communications code. Not intended to be used by applications. + /// True if the last message processed was an admin message from the queue. + /// Needed for an obscure SequenceReset scenario (see issue #390). /// - public class SessionState : IDisposable - { - #region Private Members - - private readonly object _sync = new object(); - private bool _isEnabled = true; - private bool _receivedLogon = false; - private bool _receivedReset = false; - private bool _sentLogon = false; - private bool _sentLogout = false; - private bool _sentReset = false; - private string _logoutReason = ""; - private int _testRequestCounter = 0; - private int _heartBtInt = 0; - private int _heartBtIntAsMilliSecs = 0; - private DateTime _lastReceivedTimeDt = DateTime.MinValue; - private DateTime _lastSentTimeDt = DateTime.MinValue; - private int _logonTimeout = 10; - private long _logonTimeoutAsMilliSecs = 10 * 1000; - private int _logoutTimeout = 2; - private long _logoutTimeoutAsMilliSecs = 2 * 1000; - private readonly ResendRange _resendRange = new (); - private MessagesBySeqNum _msgQueue = new (); - - #endregion - - #region Unsynchronized Properties - - public IMessageStore MessageStore - { get; } + internal bool LastProcessedMessageWasQueued { get; set; } - public bool IsInitiator - { get; set; } + #endregion - public bool ShouldSendLogon => IsInitiator && !SentLogon; + #region Synchronized Properties - public ILogger Log { get; } - - /// - /// True if the last message processed was an admin message from the queue. - /// Needed for an obscure SequenceReset scenario (see issue #390). - /// - internal bool LastProcessedMessageWasQueued { get; set; } - - #endregion - - #region Synchronized Properties - - public bool IsEnabled - { - get { lock (_sync) { return _isEnabled; } } - set { lock (_sync) { _isEnabled = value; } } - } + public bool IsEnabled + { + get { lock (_sync) { return _isEnabled; } } + set { lock (_sync) { _isEnabled = value; } } + } - public bool ReceivedLogon - { - get { lock (_sync) { return _receivedLogon; } } - set { lock (_sync) { _receivedLogon = value; } } - } + public bool ReceivedLogon + { + get { lock (_sync) { return _receivedLogon; } } + set { lock (_sync) { _receivedLogon = value; } } + } - public bool ReceivedReset - { - get { lock (_sync) { return _receivedReset; } } - set { lock (_sync) { _receivedReset = value; } } - } + public bool ReceivedReset + { + get { lock (_sync) { return _receivedReset; } } + set { lock (_sync) { _receivedReset = value; } } + } - public bool SentLogon - { - get { lock (_sync) { return _sentLogon; } } - set { lock (_sync) { _sentLogon = value; } } - } + public bool SentLogon + { + get { lock (_sync) { return _sentLogon; } } + set { lock (_sync) { _sentLogon = value; } } + } - public bool SentLogout - { - get { lock (_sync) { return _sentLogout; } } - set { lock (_sync) { _sentLogout = value; } } - } + public bool SentLogout + { + get { lock (_sync) { return _sentLogout; } } + set { lock (_sync) { _sentLogout = value; } } + } - public bool SentReset - { - get { lock (_sync) { return _sentReset; } } - set { lock (_sync) { _sentReset = value; } } - } + public bool SentReset + { + get { lock (_sync) { return _sentReset; } } + set { lock (_sync) { _sentReset = value; } } + } - public string LogoutReason - { - get { lock (_sync) { return _logoutReason; } } - set { lock (_sync) { _logoutReason = value; } } - } + public string LogoutReason + { + get { lock (_sync) { return _logoutReason; } } + set { lock (_sync) { _logoutReason = value; } } + } - public int TestRequestCounter - { - get { lock (_sync) { return _testRequestCounter; } } - set { lock (_sync) { _testRequestCounter = value; } } - } + public int TestRequestCounter + { + get { lock (_sync) { return _testRequestCounter; } } + set { lock (_sync) { _testRequestCounter = value; } } + } - public int HeartBtInt - { - get { lock (_sync) { return _heartBtInt; } } - set { lock (_sync) { _heartBtInt = value; _heartBtIntAsMilliSecs = 1000 * value; } } - } + public int HeartBtInt + { + get { lock (_sync) { return _heartBtInt; } } + set { lock (_sync) { _heartBtInt = value; _heartBtIntAsMilliSecs = 1000 * value; } } + } - public int HeartBtIntAsMilliSecs - { - get { lock (_sync) { return _heartBtIntAsMilliSecs; } } - } + public int HeartBtIntAsMilliSecs + { + get { lock (_sync) { return _heartBtIntAsMilliSecs; } } + } - public DateTime LastReceivedTimeDT - { - get { lock (_sync) { return _lastReceivedTimeDt; } } - set { lock (_sync) { _lastReceivedTimeDt = value; } } - } + public DateTime LastReceivedTimeDT + { + get { lock (_sync) { return _lastReceivedTimeDt; } } + set { lock (_sync) { _lastReceivedTimeDt = value; } } + } - public DateTime LastSentTimeDT - { - get { lock (_sync) { return _lastSentTimeDt; } } - set { lock (_sync) { _lastSentTimeDt = value; } } - } + public DateTime LastSentTimeDT + { + get { lock (_sync) { return _lastSentTimeDt; } } + set { lock (_sync) { _lastSentTimeDt = value; } } + } - public int LogonTimeout - { - get { lock (_sync) { return _logonTimeout; } } - set { lock (_sync) { _logonTimeout = value; _logonTimeoutAsMilliSecs = 1000 * value; } } - } + public int LogonTimeout + { + get { lock (_sync) { return _logonTimeout; } } + set { lock (_sync) { _logonTimeout = value; _logonTimeoutAsMilliSecs = 1000 * value; } } + } - public long LogonTimeoutAsMilliSecs - { - get { lock (_sync) { return _logonTimeoutAsMilliSecs; } } - } + public long LogonTimeoutAsMilliSecs + { + get { lock (_sync) { return _logonTimeoutAsMilliSecs; } } + } - public int LogoutTimeout - { - get { lock (_sync) { return _logoutTimeout; } } - set { lock (_sync) { _logoutTimeout = value; _logoutTimeoutAsMilliSecs = 1000 * value; } } - } + public int LogoutTimeout + { + get { lock (_sync) { return _logoutTimeout; } } + set { lock (_sync) { _logoutTimeout = value; _logoutTimeoutAsMilliSecs = 1000 * value; } } + } - public long LogoutTimeoutAsMilliSecs - { - get { lock (_sync) { return _logoutTimeoutAsMilliSecs; } } - } + public long LogoutTimeoutAsMilliSecs + { + get { lock (_sync) { return _logoutTimeoutAsMilliSecs; } } + } - private MessagesBySeqNum MsgQueue - { - get { lock (_sync) { return _msgQueue; } } - set { lock (_sync) { _msgQueue = value; } } - } + private MessagesBySeqNum MsgQueue + { + get { lock (_sync) { return _msgQueue; } } + set { lock (_sync) { _msgQueue = value; } } + } - #endregion + #endregion - internal SessionState(bool isInitiator, ILogger logger, int heartBtInt, IMessageStore messageStore) - { - Log = logger; - HeartBtInt = heartBtInt; - IsInitiator = isInitiator; - _lastReceivedTimeDt = DateTime.UtcNow; - _lastSentTimeDt = DateTime.UtcNow; - MessageStore = messageStore; - } + internal SessionState(bool isInitiator, ILogger logger, int heartBtInt, IMessageStore messageStore) + { + Log = logger; + HeartBtInt = heartBtInt; + IsInitiator = isInitiator; + _lastReceivedTimeDt = DateTime.UtcNow; + _lastSentTimeDt = DateTime.UtcNow; + MessageStore = messageStore; + } - /// - /// All time args are in milliseconds - /// - /// current system time - /// last received time - /// number of milliseconds to wait for a Logon from the counterparty - /// - public static bool LogonTimedOut(DateTime now, long logonTimeout, DateTime lastReceivedTime) - { - return now.Subtract(lastReceivedTime).TotalMilliseconds >= logonTimeout; - } - public bool LogonTimedOut() - { - return LogonTimedOut(DateTime.UtcNow, LogonTimeoutAsMilliSecs, LastReceivedTimeDT); - } + /// + /// All time args are in milliseconds + /// + /// current system time + /// last received time + /// number of milliseconds to wait for a Logon from the counterparty + /// + public static bool LogonTimedOut(DateTime now, long logonTimeout, DateTime lastReceivedTime) + { + return now.Subtract(lastReceivedTime).TotalMilliseconds >= logonTimeout; + } + public bool LogonTimedOut() + { + return LogonTimedOut(DateTime.UtcNow, LogonTimeoutAsMilliSecs, LastReceivedTimeDT); + } - /// - /// All time args are in milliseconds - /// - /// current system datetime - /// heartbeat interval in milliseconds - /// last received datetime - /// true if timed out - public static bool TimedOut(DateTime now, int heartBtIntMillis, DateTime lastReceivedTime) - { - double elapsed = now.Subtract(lastReceivedTime).TotalMilliseconds; - return elapsed >= (2.4 * heartBtIntMillis); - } - public bool TimedOut() - { - return TimedOut(DateTime.UtcNow, HeartBtIntAsMilliSecs, LastReceivedTimeDT); - } + /// + /// All time args are in milliseconds + /// + /// current system datetime + /// heartbeat interval in milliseconds + /// last received datetime + /// true if timed out + public static bool TimedOut(DateTime now, int heartBtIntMillis, DateTime lastReceivedTime) + { + double elapsed = now.Subtract(lastReceivedTime).TotalMilliseconds; + return elapsed >= (2.4 * heartBtIntMillis); + } + public bool TimedOut() + { + return TimedOut(DateTime.UtcNow, HeartBtIntAsMilliSecs, LastReceivedTimeDT); + } - /// - /// All time args are in milliseconds - /// - /// current system time - /// true if a Logout has been sent to the counterparty, otherwise false - /// number of milliseconds to wait for a Logout from the counterparty - /// last sent time - /// - public static bool LogoutTimedOut(DateTime now, bool sentLogout, long logoutTimeout, DateTime lastSentTime) - { - return sentLogout && (now.Subtract(lastSentTime).TotalMilliseconds >= logoutTimeout); - } - public bool LogoutTimedOut() - { - return LogoutTimedOut(DateTime.UtcNow, SentLogout, LogoutTimeoutAsMilliSecs, LastSentTimeDT); - } + /// + /// All time args are in milliseconds + /// + /// current system time + /// true if a Logout has been sent to the counterparty, otherwise false + /// number of milliseconds to wait for a Logout from the counterparty + /// last sent time + /// + public static bool LogoutTimedOut(DateTime now, bool sentLogout, long logoutTimeout, DateTime lastSentTime) + { + return sentLogout && (now.Subtract(lastSentTime).TotalMilliseconds >= logoutTimeout); + } + public bool LogoutTimedOut() + { + return LogoutTimedOut(DateTime.UtcNow, SentLogout, LogoutTimeoutAsMilliSecs, LastSentTimeDT); + } - /// - /// All time args are in milliseconds - /// - /// current system time - /// heartbeat interval in milliseconds - /// last received time - /// test request counter - /// true if test request is needed - public static bool NeedTestRequest(DateTime now, int heartBtIntMillis, DateTime lastReceivedTime, int testRequestCounter) - { - double elapsedMilliseconds = now.Subtract(lastReceivedTime).TotalMilliseconds; - return elapsedMilliseconds >= (1.2 * ((testRequestCounter + 1) * heartBtIntMillis)); - } - public bool NeedTestRequest() - { - return NeedTestRequest(DateTime.UtcNow, HeartBtIntAsMilliSecs, LastReceivedTimeDT, TestRequestCounter); - } + /// + /// All time args are in milliseconds + /// + /// current system time + /// heartbeat interval in milliseconds + /// last received time + /// test request counter + /// true if test request is needed + public static bool NeedTestRequest(DateTime now, int heartBtIntMillis, DateTime lastReceivedTime, int testRequestCounter) + { + double elapsedMilliseconds = now.Subtract(lastReceivedTime).TotalMilliseconds; + return elapsedMilliseconds >= (1.2 * ((testRequestCounter + 1) * heartBtIntMillis)); + } + public bool NeedTestRequest() + { + return NeedTestRequest(DateTime.UtcNow, HeartBtIntAsMilliSecs, LastReceivedTimeDT, TestRequestCounter); + } - /// - /// All time args are in milliseconds - /// - /// current system time - /// heartbeat interval in milliseconds - /// last sent time - /// test request counter - /// true if heartbeat is needed - public static bool NeedHeartbeat(DateTime now, int heartBtIntMillis, DateTime lastSentTime, int testRequestCounter) - { - double elapsed = now.Subtract(lastSentTime).TotalMilliseconds; - return (elapsed >= Convert.ToDouble(heartBtIntMillis)) && (0 == testRequestCounter); - } - public bool NeedHeartbeat() - { - return NeedHeartbeat(DateTime.UtcNow, HeartBtIntAsMilliSecs, LastSentTimeDT, TestRequestCounter); - } + /// + /// All time args are in milliseconds + /// + /// current system time + /// heartbeat interval in milliseconds + /// last sent time + /// test request counter + /// true if heartbeat is needed + public static bool NeedHeartbeat(DateTime now, int heartBtIntMillis, DateTime lastSentTime, int testRequestCounter) + { + double elapsed = now.Subtract(lastSentTime).TotalMilliseconds; + return (elapsed >= Convert.ToDouble(heartBtIntMillis)) && (0 == testRequestCounter); + } + public bool NeedHeartbeat() + { + return NeedHeartbeat(DateTime.UtcNow, HeartBtIntAsMilliSecs, LastSentTimeDT, TestRequestCounter); + } - /// - /// All time args are in milliseconds - /// - /// current system time - /// heartbeat interval in milliseconds - /// last sent time - /// last received time - /// true if within heartbeat interval - public static bool WithinHeartbeat(DateTime now, int heartBtIntMillis, DateTime lastSentTime, DateTime lastReceivedTime) - { - return (now.Subtract(lastSentTime).TotalMilliseconds < Convert.ToDouble(heartBtIntMillis)) - && (now.Subtract(lastReceivedTime).TotalMilliseconds < Convert.ToDouble(heartBtIntMillis)); - } - public bool WithinHeartbeat() - { - return WithinHeartbeat(DateTime.UtcNow, HeartBtIntAsMilliSecs, LastSentTimeDT, LastReceivedTimeDT); - } + /// + /// All time args are in milliseconds + /// + /// current system time + /// heartbeat interval in milliseconds + /// last sent time + /// last received time + /// true if within heartbeat interval + public static bool WithinHeartbeat(DateTime now, int heartBtIntMillis, DateTime lastSentTime, DateTime lastReceivedTime) + { + return (now.Subtract(lastSentTime).TotalMilliseconds < Convert.ToDouble(heartBtIntMillis)) + && (now.Subtract(lastReceivedTime).TotalMilliseconds < Convert.ToDouble(heartBtIntMillis)); + } + public bool WithinHeartbeat() + { + return WithinHeartbeat(DateTime.UtcNow, HeartBtIntAsMilliSecs, LastSentTimeDT, LastReceivedTimeDT); + } - public ResendRange GetResendRange() - { - return _resendRange; - } + public ResendRange GetResendRange() + { + return _resendRange; + } - public void Get(SeqNumType begSeqNo, SeqNumType endSeqNo, List messages) + public void Get(SeqNumType begSeqNo, SeqNumType endSeqNo, List messages) + { + lock (_sync) { - lock (_sync) - { - MessageStore.Get(begSeqNo, endSeqNo, messages); - } + MessageStore.Get(begSeqNo, endSeqNo, messages); } + } - public void SetResendRange( - SeqNumType begin, SeqNumType end, SeqNumType trigger, - Message resendRequest, SeqNumType chunkEnd = ResendRange.NOT_SET) - { - _resendRange.Set(begin, end, trigger, resendRequest, chunkEnd); - } + public void SetResendRange( + SeqNumType begin, SeqNumType end, SeqNumType trigger, + Message resendRequest, SeqNumType chunkEnd = ResendRange.NOT_SET) + { + _resendRange.Set(begin, end, trigger, resendRequest, chunkEnd); + } - internal void ResetResendRange() - { - _resendRange.Reset(); - } + internal void ResetResendRange() + { + _resendRange.Reset(); + } - public bool IsResendRequested() - { - return !(_resendRange.BeginSeqNo == 0 && _resendRange.EndSeqNo == 0); - } + public bool IsResendRequested() + { + return !(_resendRange.BeginSeqNo == 0 && _resendRange.EndSeqNo == 0); + } - public void Queue(SeqNumType msgSeqNum, Message msg) - { - MsgQueue.TryAdd(msgSeqNum, msg); - } + public void Queue(SeqNumType msgSeqNum, Message msg) + { + MsgQueue.TryAdd(msgSeqNum, msg); + } - public void ClearQueue() - { - MsgQueue.Clear(); - } + public void ClearQueue() + { + MsgQueue.Clear(); + } - public QuickFix.Message? Dequeue(SeqNumType num) - { - return MsgQueue.Remove(num, out Message? msg) ? msg : null; - } + public QuickFix.Message? Dequeue(SeqNumType num) + { + return MsgQueue.Remove(num, out Message? msg) ? msg : null; + } - public Message? Retrieve(SeqNumType msgSeqNum) - { - return MsgQueue.Remove(msgSeqNum, out Message? msg) ? msg : null; - } + public Message? Retrieve(SeqNumType msgSeqNum) + { + return MsgQueue.Remove(msgSeqNum, out Message? msg) ? msg : null; + } - /// - /// All time values are displayed in milliseconds. - /// - /// a string that represents the session state - public override string ToString() - { - return new System.Text.StringBuilder("SessionState ") - .Append("[ Now=").Append(DateTime.UtcNow) - .Append(", HeartBtInt=").Append(HeartBtIntAsMilliSecs) - .Append(", LastSentTime=").Append(LastSentTimeDT) - .Append(", LastReceivedTime=").Append(LastReceivedTimeDT) - .Append(", TestRequestCounter=").Append(TestRequestCounter) - .Append(", WithinHeartbeat=").Append(WithinHeartbeat()) - .Append(", NeedHeartbeat=").Append(NeedHeartbeat()) - .Append(", NeedTestRequest=").Append(NeedTestRequest()) - .Append(", ResendRange=").Append(GetResendRange()) - .Append(" ]").ToString(); + /// + /// All time values are displayed in milliseconds. + /// + /// a string that represents the session state + public override string ToString() + { + return new System.Text.StringBuilder("SessionState ") + .Append("[ Now=").Append(DateTime.UtcNow) + .Append(", HeartBtInt=").Append(HeartBtIntAsMilliSecs) + .Append(", LastSentTime=").Append(LastSentTimeDT) + .Append(", LastReceivedTime=").Append(LastReceivedTimeDT) + .Append(", TestRequestCounter=").Append(TestRequestCounter) + .Append(", WithinHeartbeat=").Append(WithinHeartbeat()) + .Append(", NeedHeartbeat=").Append(NeedHeartbeat()) + .Append(", NeedTestRequest=").Append(NeedTestRequest()) + .Append(", ResendRange=").Append(GetResendRange()) + .Append(" ]").ToString(); - } + } - #region MessageStore-manipulating Members + #region MessageStore-manipulating Members - public bool Set(SeqNumType msgSeqNum, string msg) - { - lock (_sync) { return MessageStore.Set(msgSeqNum, msg); } - } + public bool Set(SeqNumType msgSeqNum, string msg) + { + lock (_sync) { return MessageStore.Set(msgSeqNum, msg); } + } - public SeqNumType NextSenderMsgSeqNum - { - get { lock (_sync) { return MessageStore.NextSenderMsgSeqNum; } } - set { lock (_sync) { MessageStore.NextSenderMsgSeqNum = value; } } - } + public SeqNumType NextSenderMsgSeqNum + { + get { lock (_sync) { return MessageStore.NextSenderMsgSeqNum; } } + set { lock (_sync) { MessageStore.NextSenderMsgSeqNum = value; } } + } - public SeqNumType NextTargetMsgSeqNum - { - get { lock (_sync) { return MessageStore.NextTargetMsgSeqNum; } } - set { lock (_sync) { MessageStore.NextTargetMsgSeqNum = value; } } - } + public SeqNumType NextTargetMsgSeqNum + { + get { lock (_sync) { return MessageStore.NextTargetMsgSeqNum; } } + set { lock (_sync) { MessageStore.NextTargetMsgSeqNum = value; } } + } - public void IncrNextSenderMsgSeqNum() - { - lock (_sync) { MessageStore.IncrNextSenderMsgSeqNum(); } - } + public void IncrNextSenderMsgSeqNum() + { + lock (_sync) { MessageStore.IncrNextSenderMsgSeqNum(); } + } - public void IncrNextTargetMsgSeqNum() - { - lock (_sync) { MessageStore.IncrNextTargetMsgSeqNum(); } - } + public void IncrNextTargetMsgSeqNum() + { + lock (_sync) { MessageStore.IncrNextTargetMsgSeqNum(); } + } - public DateTime? CreationTime + public DateTime? CreationTime + { + get { - get - { - lock (_sync) { return MessageStore.CreationTime; } - } + lock (_sync) { return MessageStore.CreationTime; } } + } - /// - /// Reset the MessageStore - /// - /// this is for the log statement - public void Reset(string reason) + /// + /// Reset the MessageStore + /// + /// this is for the log statement + public void Reset(string reason) + { + lock (_sync) { - lock (_sync) - { - MessageStore.Reset(); - Log.Log(LogLevel.Information, "Session reset: {Reason}", reason); - } + MessageStore.Reset(); + Log.Log(LogLevel.Information, "Session reset: {Reason}", reason); } + } - public void Refresh() - { - lock (_sync) { MessageStore.Refresh(); } - } + public void Refresh() + { + lock (_sync) { MessageStore.Refresh(); } + } - #endregion + #endregion - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - private bool _disposed = false; - protected virtual void Dispose(bool disposing) + private bool _disposed = false; + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + if (disposing) { - if (_disposed) return; - if (disposing) - { - MessageStore.Dispose(); - } - _disposed = true; + MessageStore.Dispose(); } - - ~SessionState() => Dispose(false); + _disposed = true; } + + ~SessionState() => Dispose(false); } diff --git a/QuickFIXn/SocketSettings.cs b/QuickFIXn/SocketSettings.cs index c64f3b981..3565a6126 100644 --- a/QuickFIXn/SocketSettings.cs +++ b/QuickFIXn/SocketSettings.cs @@ -1,248 +1,247 @@ using System; using System.Security.Authentication; -namespace QuickFix +namespace QuickFix; + +/// +/// Keep all socket settings in one place +/// +/// +/// Property setters are internal so they can be set in tests, otherwise the settings should +/// be set using the function +/// +public class SocketSettings : ICloneable { + #region Socket Settings + + /// + /// Gets a value that specifies whether the is using the Nagle algorithm. + /// + /// + /// false if the socket uses the Nagle algorithm; otherwise, true. The default is false. + /// + public bool SocketNodelay { get; internal set; } + + /// + /// Gets the size of the receive buffer. + /// + /// + /// The size, in bytes, of the receive buffer. A null value defaults to 8192. + /// + public int? SocketReceiveBufferSize { get; internal set; } + + /// + /// Gets the size of the send buffer. + /// + /// + /// The size, in bytes, of the send buffer. A null value defaults to 8192. + /// + public int? SocketSendBufferSize { get; internal set; } + + /// + /// Gets the amount of time in milliseconds after which a synchronous + /// Receive() call will time out. + /// + /// + /// The time-out value, in milliseconds. A null, 0 or -1 + /// value indicates an infinite time-out period. + /// + public int? SocketReceiveTimeout { get; internal set; } + + /// + /// Gets the amount of time in milliseconds after which a synchronous + /// Send() call will time out. + /// + /// + /// The time-out value, in milliseconds. A null, 0 or -1 + /// value indicates an infinite time-out period. + /// + public int? SocketSendTimeout { get; internal set; } + + /// + /// Gets a value that specifies whether the proxy server settings should be ignored. + /// + /// + /// false if connection should use proxy; otherwise for ignoring proxy, true. The default is false. + /// + public bool SocketIgnoreProxy { get; internal set; } + #endregion + + #region SSL Settings + + /// + /// Gets the Server's Common Name (CN) . + /// + /// + /// The common name is the name of the Server's certificate and it is usually + /// the DNS name of the server. + /// + public string? ServerCommonName { get; internal set; } + + /// + /// Gets a value indicating whether certificates of the other endpoint should be validated. + /// + /// + /// true if certificates should be validated; otherwise, false. + /// + public bool ValidateCertificates { get; internal set; } + + /// + /// Gets the path to the client/server-certificate. + /// + /// + /// The certificate path. + /// + public string? CertificatePath { get; internal set; } + + /// + /// Gets the certificate password. + /// + /// + /// The certificate password. + /// + public string? CertificatePassword { get; internal set; } + /// - /// Keep all socket settings in one place + /// Gets the SSL protocol to use (for initiator) or accept (for acceptor) + /// + /// + /// The SSL protocol. + /// + public SslProtocols SslProtocol { get; internal set; } + + /// + /// Gets a value indicating whether the check for certificate revocation (use CRL) + /// + /// + /// true if certificate revocation status should be checked; otherwise, false. + /// + public bool CheckCertificateRevocation { get; internal set; } + + /// + /// Gets a value indicating whether SSL should be used for the socket. + /// + /// + /// true if SSL should be enabled; otherwise, false. + /// + public bool UseSSL { get; private set; } + + /// + /// Path to .cer with the public part of the Certificate CA to validate clients against (acceptor setting). + /// + /// + /// The CA certificate path. + /// + public string? CACertificatePath { get; set; } + + /// + /// Gets a value indicating whether client certificate are required (acceptor setting). + /// + /// + /// true if [require client certificate]; otherwise, false. + /// + public bool RequireClientCertificate { get; internal set; } + + #endregion + + /// + /// Initializes a new instance of the class. + /// Here we setup the default values + /// + public SocketSettings() + { + ValidateCertificates = true; + SslProtocol = SslProtocols.None; + CheckCertificateRevocation = true; + RequireClientCertificate = true; + SocketNodelay = true; + SocketIgnoreProxy = false; + } + + /// + /// Setup socket settings based on settings specified in dictionary /// /// - /// Property setters are internal so they can be set in tests, otherwise the settings should - /// be set using the function + /// used "Configure" as name since it is used in a lot of other places, + /// alternative names are ReadSettings or FromDictionary /// - public class SocketSettings : ICloneable + /// the dictionary to read the settings from + public void Configure(QuickFix.SettingsDictionary settingsDictionary) { - #region Socket Settings - - /// - /// Gets a value that specifies whether the is using the Nagle algorithm. - /// - /// - /// false if the socket uses the Nagle algorithm; otherwise, true. The default is false. - /// - public bool SocketNodelay { get; internal set; } - - /// - /// Gets the size of the receive buffer. - /// - /// - /// The size, in bytes, of the receive buffer. A null value defaults to 8192. - /// - public int? SocketReceiveBufferSize { get; internal set; } - - /// - /// Gets the size of the send buffer. - /// - /// - /// The size, in bytes, of the send buffer. A null value defaults to 8192. - /// - public int? SocketSendBufferSize { get; internal set; } - - /// - /// Gets the amount of time in milliseconds after which a synchronous - /// Receive() call will time out. - /// - /// - /// The time-out value, in milliseconds. A null, 0 or -1 - /// value indicates an infinite time-out period. - /// - public int? SocketReceiveTimeout { get; internal set; } - - /// - /// Gets the amount of time in milliseconds after which a synchronous - /// Send() call will time out. - /// - /// - /// The time-out value, in milliseconds. A null, 0 or -1 - /// value indicates an infinite time-out period. - /// - public int? SocketSendTimeout { get; internal set; } - - /// - /// Gets a value that specifies whether the proxy server settings should be ignored. - /// - /// - /// false if connection should use proxy; otherwise for ignoring proxy, true. The default is false. - /// - public bool SocketIgnoreProxy { get; internal set; } - #endregion - - #region SSL Settings - - /// - /// Gets the Server's Common Name (CN) . - /// - /// - /// The common name is the name of the Server's certificate and it is usually - /// the DNS name of the server. - /// - public string? ServerCommonName { get; internal set; } - - /// - /// Gets a value indicating whether certificates of the other endpoint should be validated. - /// - /// - /// true if certificates should be validated; otherwise, false. - /// - public bool ValidateCertificates { get; internal set; } - - /// - /// Gets the path to the client/server-certificate. - /// - /// - /// The certificate path. - /// - public string? CertificatePath { get; internal set; } - - /// - /// Gets the certificate password. - /// - /// - /// The certificate password. - /// - public string? CertificatePassword { get; internal set; } - - /// - /// Gets the SSL protocol to use (for initiator) or accept (for acceptor) - /// - /// - /// The SSL protocol. - /// - public SslProtocols SslProtocol { get; internal set; } - - /// - /// Gets a value indicating whether the check for certificate revocation (use CRL) - /// - /// - /// true if certificate revocation status should be checked; otherwise, false. - /// - public bool CheckCertificateRevocation { get; internal set; } - - /// - /// Gets a value indicating whether SSL should be used for the socket. - /// - /// - /// true if SSL should be enabled; otherwise, false. - /// - public bool UseSSL { get; private set; } - - /// - /// Path to .cer with the public part of the Certificate CA to validate clients against (acceptor setting). - /// - /// - /// The CA certificate path. - /// - public string? CACertificatePath { get; set; } - - /// - /// Gets a value indicating whether client certificate are required (acceptor setting). - /// - /// - /// true if [require client certificate]; otherwise, false. - /// - public bool RequireClientCertificate { get; internal set; } - - #endregion - - /// - /// Initializes a new instance of the class. - /// Here we setup the default values - /// - public SocketSettings() - { - ValidateCertificates = true; - SslProtocol = SslProtocols.None; - CheckCertificateRevocation = true; - RequireClientCertificate = true; - SocketNodelay = true; - SocketIgnoreProxy = false; - } + if (settingsDictionary.Has(SessionSettings.SOCKET_IGNORE_PROXY)) + SocketIgnoreProxy = settingsDictionary.GetBool(SessionSettings.SOCKET_IGNORE_PROXY); - /// - /// Setup socket settings based on settings specified in dictionary - /// - /// - /// used "Configure" as name since it is used in a lot of other places, - /// alternative names are ReadSettings or FromDictionary - /// - /// the dictionary to read the settings from - public void Configure(QuickFix.SettingsDictionary settingsDictionary) - { - if (settingsDictionary.Has(SessionSettings.SOCKET_IGNORE_PROXY)) - SocketIgnoreProxy = settingsDictionary.GetBool(SessionSettings.SOCKET_IGNORE_PROXY); + if (settingsDictionary.Has(SessionSettings.SOCKET_NODELAY)) + SocketNodelay = settingsDictionary.GetBool(SessionSettings.SOCKET_NODELAY); - if (settingsDictionary.Has(SessionSettings.SOCKET_NODELAY)) - SocketNodelay = settingsDictionary.GetBool(SessionSettings.SOCKET_NODELAY); + if (settingsDictionary.Has(SessionSettings.SOCKET_RECEIVE_BUFFER_SIZE)) + SocketReceiveBufferSize = settingsDictionary.GetInt(SessionSettings.SOCKET_RECEIVE_BUFFER_SIZE); - if (settingsDictionary.Has(SessionSettings.SOCKET_RECEIVE_BUFFER_SIZE)) - SocketReceiveBufferSize = settingsDictionary.GetInt(SessionSettings.SOCKET_RECEIVE_BUFFER_SIZE); + if (settingsDictionary.Has(SessionSettings.SOCKET_SEND_BUFFER_SIZE)) + SocketSendBufferSize = settingsDictionary.GetInt(SessionSettings.SOCKET_SEND_BUFFER_SIZE); - if (settingsDictionary.Has(SessionSettings.SOCKET_SEND_BUFFER_SIZE)) - SocketSendBufferSize = settingsDictionary.GetInt(SessionSettings.SOCKET_SEND_BUFFER_SIZE); + if (settingsDictionary.Has(SessionSettings.SOCKET_RECEIVE_TIMEOUT)) + SocketReceiveTimeout = settingsDictionary.GetInt(SessionSettings.SOCKET_RECEIVE_TIMEOUT); - if (settingsDictionary.Has(SessionSettings.SOCKET_RECEIVE_TIMEOUT)) - SocketReceiveTimeout = settingsDictionary.GetInt(SessionSettings.SOCKET_RECEIVE_TIMEOUT); + if (settingsDictionary.Has(SessionSettings.SOCKET_SEND_TIMEOUT)) + SocketSendTimeout = settingsDictionary.GetInt(SessionSettings.SOCKET_SEND_TIMEOUT); - if (settingsDictionary.Has(SessionSettings.SOCKET_SEND_TIMEOUT)) - SocketSendTimeout = settingsDictionary.GetInt(SessionSettings.SOCKET_SEND_TIMEOUT); + if (settingsDictionary.Has(SessionSettings.SSL_SERVERNAME)) + ServerCommonName = settingsDictionary.GetString(SessionSettings.SSL_SERVERNAME); - if (settingsDictionary.Has(SessionSettings.SSL_SERVERNAME)) - ServerCommonName = settingsDictionary.GetString(SessionSettings.SSL_SERVERNAME); + if (settingsDictionary.Has(SessionSettings.SSL_CA_CERTIFICATE)) + CACertificatePath = settingsDictionary.GetString(SessionSettings.SSL_CA_CERTIFICATE); - if (settingsDictionary.Has(SessionSettings.SSL_CA_CERTIFICATE)) - CACertificatePath = settingsDictionary.GetString(SessionSettings.SSL_CA_CERTIFICATE); + if (settingsDictionary.Has(SessionSettings.SSL_CERTIFICATE)) + CertificatePath = settingsDictionary.GetString(SessionSettings.SSL_CERTIFICATE); - if (settingsDictionary.Has(SessionSettings.SSL_CERTIFICATE)) - CertificatePath = settingsDictionary.GetString(SessionSettings.SSL_CERTIFICATE); + if (settingsDictionary.Has(SessionSettings.SSL_CERTIFICATE_PASSWORD)) + CertificatePassword = settingsDictionary.GetString(SessionSettings.SSL_CERTIFICATE_PASSWORD); - if (settingsDictionary.Has(SessionSettings.SSL_CERTIFICATE_PASSWORD)) - CertificatePassword = settingsDictionary.GetString(SessionSettings.SSL_CERTIFICATE_PASSWORD); + if (settingsDictionary.Has(SessionSettings.SSL_VALIDATE_CERTIFICATES)) + ValidateCertificates = settingsDictionary.GetBool(SessionSettings.SSL_VALIDATE_CERTIFICATES); - if (settingsDictionary.Has(SessionSettings.SSL_VALIDATE_CERTIFICATES)) - ValidateCertificates = settingsDictionary.GetBool(SessionSettings.SSL_VALIDATE_CERTIFICATES); + if (settingsDictionary.Has(SessionSettings.SSL_CHECK_CERTIFICATE_REVOCATION)) + { + // can only be true if ValdateCertificates is true (this is noted in the config docs) + CheckCertificateRevocation = ValidateCertificates && settingsDictionary.GetBool(SessionSettings.SSL_CHECK_CERTIFICATE_REVOCATION); + } - if (settingsDictionary.Has(SessionSettings.SSL_CHECK_CERTIFICATE_REVOCATION)) - { - // can only be true if ValdateCertificates is true (this is noted in the config docs) - CheckCertificateRevocation = ValidateCertificates && settingsDictionary.GetBool(SessionSettings.SSL_CHECK_CERTIFICATE_REVOCATION); - } + // Use setting for client certificate check if one exist + // otherwise enable client certificate check if a ca certificate is specified + if (settingsDictionary.Has(SessionSettings.SSL_REQUIRE_CLIENT_CERTIFICATE)) + RequireClientCertificate = settingsDictionary.GetBool(SessionSettings.SSL_REQUIRE_CLIENT_CERTIFICATE); - // Use setting for client certificate check if one exist - // otherwise enable client certificate check if a ca certificate is specified - if (settingsDictionary.Has(SessionSettings.SSL_REQUIRE_CLIENT_CERTIFICATE)) - RequireClientCertificate = settingsDictionary.GetBool(SessionSettings.SSL_REQUIRE_CLIENT_CERTIFICATE); + // Use setting for SSL if one exist + // otherwise enable ssl if certificate path is specified + if (settingsDictionary.Has(SessionSettings.SSL_ENABLE)) + UseSSL = settingsDictionary.GetBool(SessionSettings.SSL_ENABLE); + else + UseSSL = !string.IsNullOrEmpty(CertificatePath); - // Use setting for SSL if one exist - // otherwise enable ssl if certificate path is specified - if (settingsDictionary.Has(SessionSettings.SSL_ENABLE)) - UseSSL = settingsDictionary.GetBool(SessionSettings.SSL_ENABLE); - else - UseSSL = !string.IsNullOrEmpty(CertificatePath); + if (settingsDictionary.Has(SessionSettings.SSL_PROTOCOLS)) + { + var protocolString = settingsDictionary.GetString(SessionSettings.SSL_PROTOCOLS); - if (settingsDictionary.Has(SessionSettings.SSL_PROTOCOLS)) + try + { + SslProtocol = (System.Security.Authentication.SslProtocols) + Enum.Parse(typeof(System.Security.Authentication.SslProtocols), protocolString, true); + } + catch (Exception) { - var protocolString = settingsDictionary.GetString(SessionSettings.SSL_PROTOCOLS); - - try - { - SslProtocol = (System.Security.Authentication.SslProtocols) - Enum.Parse(typeof(System.Security.Authentication.SslProtocols), protocolString, true); - } - catch (Exception) - { - // TODO: figure out a way to log this somehow (even though it's not likely to occur) - } + // TODO: figure out a way to log this somehow (even though it's not likely to occur) } } + } - object ICloneable.Clone() - { - return Clone(); - } + object ICloneable.Clone() + { + return Clone(); + } - public SocketSettings Clone() - { - return (SocketSettings)MemberwiseClone(); - } + public SocketSettings Clone() + { + return (SocketSettings)MemberwiseClone(); } } diff --git a/QuickFIXn/ThreadedSocketAcceptor.cs b/QuickFIXn/ThreadedSocketAcceptor.cs index 292ab7dd3..8bb1f25bb 100755 --- a/QuickFIXn/ThreadedSocketAcceptor.cs +++ b/QuickFIXn/ThreadedSocketAcceptor.cs @@ -7,463 +7,462 @@ using QuickFix.Logger; using QuickFix.Store; -namespace QuickFix +namespace QuickFix; + +/// +/// Acceptor implementation - with threads +/// Creates a ThreadedSocketReactor for every listening endpoint. +/// +public class ThreadedSocketAcceptor : IAcceptor { + private const int TenSecondsInTicks = 10000; + + private readonly Dictionary _sessions = new(); + private readonly SessionSettings _settings; + private readonly Dictionary _socketDescriptorForAddress = new(); + private readonly SessionFactory _sessionFactory; + private bool _disposed = false; + private readonly object _sync = new(); + private readonly IQuickFixLoggerFactory _qfLoggerFactory; + private readonly LogFactoryAdapter? _logFactoryAdapter; + + /// + /// Create a ThreadedSocketAcceptor (with a legacy ILogFactory) + /// + /// + /// + /// + /// If null, a NullQuickFixLoggerFactory (which produces no logs) will be used. + /// If null, a DefaultMessageFactory will be created (using settings parameters) + public ThreadedSocketAcceptor( + IApplication application, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILogFactory? logFactory = null, + IMessageFactory? messageFactory = null) + : this( + application, + storeFactory, + settings, + logFactory is null ? NullQuickFixLoggerFactory.Instance : new LogFactoryAdapter(logFactory), + messageFactory) + { } + /// - /// Acceptor implementation - with threads - /// Creates a ThreadedSocketReactor for every listening endpoint. + /// Create a ThreadedSocketAcceptor /// - public class ThreadedSocketAcceptor : IAcceptor + /// + /// + /// + /// If null, a NullQuickFixLoggerFactory (which produces no logs) will be used. + /// If null, a DefaultMessageFactory will be created (using settings parameters) + public ThreadedSocketAcceptor( + IApplication application, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILoggerFactory? loggerFactory = null, + IMessageFactory? messageFactory = null) + : this( + application, + storeFactory, + settings, + loggerFactory is null + ? NullQuickFixLoggerFactory.Instance + : new MelQuickFixLoggerFactory(loggerFactory), + messageFactory) + { } + + private ThreadedSocketAcceptor( + IApplication application, + IMessageStoreFactory storeFactory, + SessionSettings settings, + IQuickFixLoggerFactory qfLoggerFactory, + IMessageFactory? messageFactory = null) { - private const int TenSecondsInTicks = 10000; - - private readonly Dictionary _sessions = new(); - private readonly SessionSettings _settings; - private readonly Dictionary _socketDescriptorForAddress = new(); - private readonly SessionFactory _sessionFactory; - private bool _disposed = false; - private readonly object _sync = new(); - private readonly IQuickFixLoggerFactory _qfLoggerFactory; - private readonly LogFactoryAdapter? _logFactoryAdapter; - - /// - /// Create a ThreadedSocketAcceptor (with a legacy ILogFactory) - /// - /// - /// - /// - /// If null, a NullQuickFixLoggerFactory (which produces no logs) will be used. - /// If null, a DefaultMessageFactory will be created (using settings parameters) - public ThreadedSocketAcceptor( - IApplication application, - IMessageStoreFactory storeFactory, - SessionSettings settings, - ILogFactory? logFactory = null, - IMessageFactory? messageFactory = null) - : this( - application, - storeFactory, - settings, - logFactory is null ? NullQuickFixLoggerFactory.Instance : new LogFactoryAdapter(logFactory), - messageFactory) - { } - - /// - /// Create a ThreadedSocketAcceptor - /// - /// - /// - /// - /// If null, a NullQuickFixLoggerFactory (which produces no logs) will be used. - /// If null, a DefaultMessageFactory will be created (using settings parameters) - public ThreadedSocketAcceptor( - IApplication application, - IMessageStoreFactory storeFactory, - SessionSettings settings, - ILoggerFactory? loggerFactory = null, - IMessageFactory? messageFactory = null) - : this( - application, - storeFactory, - settings, - loggerFactory is null - ? NullQuickFixLoggerFactory.Instance - : new MelQuickFixLoggerFactory(loggerFactory), - messageFactory) - { } - - private ThreadedSocketAcceptor( - IApplication application, - IMessageStoreFactory storeFactory, - SessionSettings settings, - IQuickFixLoggerFactory qfLoggerFactory, - IMessageFactory? messageFactory = null) + if (qfLoggerFactory is LogFactoryAdapter lfa) { - if (qfLoggerFactory is LogFactoryAdapter lfa) - { - // LogFactoryAdapter is only ever created in the constructor marked obsolete, which means we own it and - // must save a ref to it so we can dispose it later. Any other loggerFactory is owned by someone else - // so we'll leave the dispose up to them. This should be removed eventually together with the old ILog - // and ILogFactory. - _logFactoryAdapter = lfa; - } - IMessageFactory mf = messageFactory ?? new DefaultMessageFactory(); - _settings = settings; - _sessionFactory = new SessionFactory(application, storeFactory, qfLoggerFactory, mf); - _qfLoggerFactory = qfLoggerFactory; + // LogFactoryAdapter is only ever created in the constructor marked obsolete, which means we own it and + // must save a ref to it so we can dispose it later. Any other loggerFactory is owned by someone else + // so we'll leave the dispose up to them. This should be removed eventually together with the old ILog + // and ILogFactory. + _logFactoryAdapter = lfa; + } + IMessageFactory mf = messageFactory ?? new DefaultMessageFactory(); + _settings = settings; + _sessionFactory = new SessionFactory(application, storeFactory, qfLoggerFactory, mf); + _qfLoggerFactory = qfLoggerFactory; - try - { - foreach (SessionID sessionId in settings.GetSessions()) - { - SettingsDictionary dict = settings.Get(sessionId); - CreateSession(sessionId, dict); - } - } - catch (Exception e) + try + { + foreach (SessionID sessionId in settings.GetSessions()) { - throw new ConfigError(e.Message, e); + SettingsDictionary dict = settings.Get(sessionId); + CreateSession(sessionId, dict); } } + catch (Exception e) + { + throw new ConfigError(e.Message, e); + } + } - #region Private Methods + #region Private Methods - private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(SettingsDictionary dict) - { - int port = Convert.ToInt32(dict.GetLong(SessionSettings.SOCKET_ACCEPT_PORT)); - SocketSettings socketSettings = new SocketSettings(); + private AcceptorSocketDescriptor GetAcceptorSocketDescriptor(SettingsDictionary dict) + { + int port = Convert.ToInt32(dict.GetLong(SessionSettings.SOCKET_ACCEPT_PORT)); + SocketSettings socketSettings = new SocketSettings(); - IPEndPoint socketEndPoint; - if (dict.Has(SessionSettings.SOCKET_ACCEPT_HOST)) - { - string host = dict.GetString(SessionSettings.SOCKET_ACCEPT_HOST); + IPEndPoint socketEndPoint; + if (dict.Has(SessionSettings.SOCKET_ACCEPT_HOST)) + { + string host = dict.GetString(SessionSettings.SOCKET_ACCEPT_HOST); - if (IsAnyIpAddress(host)) - { - socketEndPoint = new IPEndPoint(IPAddress.Any, port); - } - else - { - IPAddress[] addrs = Dns.GetHostAddresses(host); - socketEndPoint = new IPEndPoint(addrs[0], port); - // Set hostname (if it is not already configured) - socketSettings.ServerCommonName ??= host; - } - } - else + if (IsAnyIpAddress(host)) { socketEndPoint = new IPEndPoint(IPAddress.Any, port); } - - socketSettings.Configure(dict); - - if (!_socketDescriptorForAddress.TryGetValue(socketEndPoint, out var descriptor)) + else { - descriptor = new AcceptorSocketDescriptor(socketEndPoint, socketSettings, _qfLoggerFactory); - _socketDescriptorForAddress[socketEndPoint] = descriptor; + IPAddress[] addrs = Dns.GetHostAddresses(host); + socketEndPoint = new IPEndPoint(addrs[0], port); + // Set hostname (if it is not already configured) + socketSettings.ServerCommonName ??= host; } - - return descriptor; } - - /// - /// Checks if the host address is the "any" address in either IPv4 (0.0.0.0) or IPv6 (::) - /// - /// - /// - private static bool IsAnyIpAddress(string host) + else { - return IPAddress.TryParse(host, out IPAddress? address) && - (address.Equals(IPAddress.Any) || - address.Equals(IPAddress.IPv6Any)); + socketEndPoint = new IPEndPoint(IPAddress.Any, port); } - /// - /// Create session, either at start-up or as an ad-hoc operation - /// - /// ID of new session - /// config settings for new session - /// true if session added successfully, false if session already exists or is not an acceptor - private bool CreateSession(SessionID sessionId, SettingsDictionary dict) + socketSettings.Configure(dict); + + if (!_socketDescriptorForAddress.TryGetValue(socketEndPoint, out var descriptor)) { - if (!_sessions.ContainsKey(sessionId)) - { - string connectionType = dict.GetString(SessionSettings.CONNECTION_TYPE); - if ("acceptor" == connectionType) - { - AcceptorSocketDescriptor descriptor = GetAcceptorSocketDescriptor(dict); - Session session = _sessionFactory.Create(sessionId, dict); - descriptor.AcceptSession(session); - _sessions[sessionId] = session; - - // start SocketReactor if it was created via AddSession call - // and if acceptor is already started - if (IsStarted && !_disposed) - { - descriptor.SocketReactor.Start(); - } - - return true; - } - } - return false; + descriptor = new AcceptorSocketDescriptor(socketEndPoint, socketSettings, _qfLoggerFactory); + _socketDescriptorForAddress[socketEndPoint] = descriptor; } - private void StartAcceptingConnections() + return descriptor; + } + + /// + /// Checks if the host address is the "any" address in either IPv4 (0.0.0.0) or IPv6 (::) + /// + /// + /// + private static bool IsAnyIpAddress(string host) + { + return IPAddress.TryParse(host, out IPAddress? address) && + (address.Equals(IPAddress.Any) || + address.Equals(IPAddress.IPv6Any)); + } + + /// + /// Create session, either at start-up or as an ad-hoc operation + /// + /// ID of new session + /// config settings for new session + /// true if session added successfully, false if session already exists or is not an acceptor + private bool CreateSession(SessionID sessionId, SettingsDictionary dict) + { + if (!_sessions.ContainsKey(sessionId)) { - lock (_sync) + string connectionType = dict.GetString(SessionSettings.CONNECTION_TYPE); + if ("acceptor" == connectionType) { - foreach (AcceptorSocketDescriptor socketDescriptor in _socketDescriptorForAddress.Values) + AcceptorSocketDescriptor descriptor = GetAcceptorSocketDescriptor(dict); + Session session = _sessionFactory.Create(sessionId, dict); + descriptor.AcceptSession(session); + _sessions[sessionId] = session; + + // start SocketReactor if it was created via AddSession call + // and if acceptor is already started + if (IsStarted && !_disposed) { - socketDescriptor.SocketReactor.Start(); + descriptor.SocketReactor.Start(); } + + return true; } } + return false; + } - private void StopAcceptingConnections() + private void StartAcceptingConnections() + { + lock (_sync) { - lock (_sync) + foreach (AcceptorSocketDescriptor socketDescriptor in _socketDescriptorForAddress.Values) { - foreach (AcceptorSocketDescriptor socketDescriptor in _socketDescriptorForAddress.Values) - { - socketDescriptor.SocketReactor.Shutdown(); - } + socketDescriptor.SocketReactor.Start(); } } + } - private void LogonAllSessions() + private void StopAcceptingConnections() + { + lock (_sync) { - foreach (Session session in _sessions.Values) + foreach (AcceptorSocketDescriptor socketDescriptor in _socketDescriptorForAddress.Values) { - try - { - session.Logon(); - } - catch (Exception e) - { - session.Log.Log(LogLevel.Error, e, "Error during logon of Session {SessionID}: {Message}", - session.SessionID, e.Message); - } + socketDescriptor.SocketReactor.Shutdown(); } } + } - private void LogoutAllSessions(bool force) + private void LogonAllSessions() + { + foreach (Session session in _sessions.Values) { - foreach (Session session in _sessions.Values) + try { - try - { - session.Logout(); - } - catch (Exception e) - { - session.Log.Log(LogLevel.Error, e, "Error during logout of Session {SessionID}: {Message}", - session.SessionID, e.Message); - } + session.Logon(); } - - if (force && IsLoggedOn) + catch (Exception e) { - DisconnectSessions("Forcibly disconnecting sessions"); + session.Log.Log(LogLevel.Error, e, "Error during logon of Session {SessionID}: {Message}", + session.SessionID, e.Message); } - - if (!force) - WaitForLogout(); } + } - private void WaitForLogout() + private void LogoutAllSessions(bool force) + { + foreach (Session session in _sessions.Values) { - int start = Environment.TickCount; - using( var resetEvent = new ManualResetEvent( false ) ) + try { - while (IsLoggedOn && (Environment.TickCount - start) < TenSecondsInTicks) - { - resetEvent.WaitOne( 100 ); - } + session.Logout(); + } + catch (Exception e) + { + session.Log.Log(LogLevel.Error, e, "Error during logout of Session {SessionID}: {Message}", + session.SessionID, e.Message); } - DisconnectSessions("Logout timeout, force disconnect"); } - private void DisconnectSessions(string disconnectMessage) + if (force && IsLoggedOn) { - foreach (Session session in _sessions.Values) - { - try - { - if (session.IsLoggedOn) - session.Disconnect(disconnectMessage); - } - catch (Exception e) - { - session.Log.Log(LogLevel.Error, e, "Error during disconnect of Session {SessionID}, {Message}", - session.SessionID, e.Message); - } - } + DisconnectSessions("Forcibly disconnecting sessions"); } - private void DisposeSessions() + if (!force) + WaitForLogout(); + } + + private void WaitForLogout() + { + int start = Environment.TickCount; + using( var resetEvent = new ManualResetEvent( false ) ) { - foreach (var session in _sessions.Values) + while (IsLoggedOn && (Environment.TickCount - start) < TenSecondsInTicks) { - session.Dispose(); + resetEvent.WaitOne( 100 ); } } + DisconnectSessions("Logout timeout, force disconnect"); + } - #endregion - - #region Acceptor Members - - public void Start() + private void DisconnectSessions(string disconnectMessage) + { + foreach (Session session in _sessions.Values) { - if (_disposed) - throw new ObjectDisposedException(GetType().Name); - - lock (_sync) + try { - if (!IsStarted) - { - LogonAllSessions(); - StartAcceptingConnections(); - IsStarted = true; - } + if (session.IsLoggedOn) + session.Disconnect(disconnectMessage); + } + catch (Exception e) + { + session.Log.Log(LogLevel.Error, e, "Error during disconnect of Session {SessionID}, {Message}", + session.SessionID, e.Message); } } + } - public void Stop() + private void DisposeSessions() + { + foreach (var session in _sessions.Values) { - Stop(false); + session.Dispose(); } + } - public void Stop(bool force) - { - ObjectDisposedException.ThrowIf(_disposed, this); + #endregion - lock( _sync ) - { - if( IsStarted ) - { - IsStarted = false; - LogoutAllSessions(force); - StopAcceptingConnections(); - } - } - } + #region Acceptor Members - /// - /// Check whether any sessions are logged on - /// - /// true if any session is logged on, else false - public bool IsLoggedOn + public void Start() + { + if (_disposed) + throw new ObjectDisposedException(GetType().Name); + + lock (_sync) { - get + if (!IsStarted) { - return _sessions.Values.Any(session => session.IsLoggedOn); + LogonAllSessions(); + StartAcceptingConnections(); + IsStarted = true; } } + } + + public void Stop() + { + Stop(false); + } - /// - /// Gets a value indicating whether this instance is started. - /// - /// - /// true if this instance is started; otherwise, false. - /// - public bool IsStarted { get; private set; } = false; - - /// - /// (For use by Unit Tests) - /// Gets a value indicating whether this instance is started. - /// - /// - /// true if this instance is started; otherwise, false. - /// - internal bool AreSocketsRunning + public void Stop(bool force) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock( _sync ) { - get + if( IsStarted ) { - return _socketDescriptorForAddress.All( s => s.Value.SocketReactor.IsRunning ); + IsStarted = false; + LogoutAllSessions(force); + StopAcceptingConnections(); } } + } - /// - /// Get the SessionIDs for the sessions managed by this acceptor. - /// - /// the SessionIDs for the sessions managed by this acceptor - public HashSet GetSessionIDs() + /// + /// Check whether any sessions are logged on + /// + /// true if any session is logged on, else false + public bool IsLoggedOn + { + get { - return new HashSet(_sessions.Keys); + return _sessions.Values.Any(session => session.IsLoggedOn); } + } + + /// + /// Gets a value indicating whether this instance is started. + /// + /// + /// true if this instance is started; otherwise, false. + /// + public bool IsStarted { get; private set; } = false; - /// - /// TODO: not yet implemented - /// - /// - public Dictionary GetAcceptorAddresses() + /// + /// (For use by Unit Tests) + /// Gets a value indicating whether this instance is started. + /// + /// + /// true if this instance is started; otherwise, false. + /// + internal bool AreSocketsRunning + { + get { - throw new NotImplementedException(); + return _socketDescriptorForAddress.All( s => s.Value.SocketReactor.IsRunning ); } + } - /// - /// Add new session as an ad-oc (dynamic) operation - /// - /// ID of new session - /// config settings for new session - /// true if session added successfully, false if session already exists or is not an acceptor - public bool AddSession(SessionID sessionId, SettingsDictionary dict) - { - lock (_settings) - if (!_settings.Has(sessionId)) // session won't be in settings if ad-hoc creation after startup - _settings.Set(sessionId, dict); // need to to this here to merge in default config settings - else - return false; // session already exists + /// + /// Get the SessionIDs for the sessions managed by this acceptor. + /// + /// the SessionIDs for the sessions managed by this acceptor + public HashSet GetSessionIDs() + { + return new HashSet(_sessions.Keys); + } - if (CreateSession(sessionId, dict)) - return true; + /// + /// TODO: not yet implemented + /// + /// + public Dictionary GetAcceptorAddresses() + { + throw new NotImplementedException(); + } - lock (_settings) // failed to create session, so remove from settings - _settings.Remove(sessionId); - return false; - } + /// + /// Add new session as an ad-oc (dynamic) operation + /// + /// ID of new session + /// config settings for new session + /// true if session added successfully, false if session already exists or is not an acceptor + public bool AddSession(SessionID sessionId, SettingsDictionary dict) + { + lock (_settings) + if (!_settings.Has(sessionId)) // session won't be in settings if ad-hoc creation after startup + _settings.Set(sessionId, dict); // need to to this here to merge in default config settings + else + return false; // session already exists + if (CreateSession(sessionId, dict)) + return true; - /// - /// Ad-hoc removal of an existing session - /// - /// ID of session to be removed - /// if true, force disconnection and removal of session even if it has an active connection - /// true if session removed or not already present; false if could not be removed due to an active connection - public bool RemoveSession(SessionID sessionId, bool terminateActiveSession) + lock (_settings) // failed to create session, so remove from settings + _settings.Remove(sessionId); + return false; + } + + + /// + /// Ad-hoc removal of an existing session + /// + /// ID of session to be removed + /// if true, force disconnection and removal of session even if it has an active connection + /// true if session removed or not already present; false if could not be removed due to an active connection + public bool RemoveSession(SessionID sessionId, bool terminateActiveSession) + { + if (_sessions.TryGetValue(sessionId, out var session)) { - if (_sessions.TryGetValue(sessionId, out var session)) - { - if (session.IsLoggedOn && !terminateActiveSession) - return false; - session.Disconnect("Dynamic session removal"); - foreach (AcceptorSocketDescriptor descriptor in _socketDescriptorForAddress.Values) - if (descriptor.RemoveSession(sessionId)) - break; - _sessions.Remove(sessionId); - session.Dispose(); - lock (_settings) - _settings.Remove(sessionId); - } - return true; + if (session.IsLoggedOn && !terminateActiveSession) + return false; + session.Disconnect("Dynamic session removal"); + foreach (AcceptorSocketDescriptor descriptor in _socketDescriptorForAddress.Values) + if (descriptor.RemoveSession(sessionId)) + break; + _sessions.Remove(sessionId); + session.Dispose(); + lock (_settings) + _settings.Remove(sessionId); } + return true; + } - #endregion + #endregion - /// - /// Any subclasses of ThreadedSocketAcceptor should override this if they have resources to dispose - /// Any override should call base.Dispose(disposing). - /// - /// true if called from Dispose() - protected void Dispose(bool disposing) + /// + /// Any subclasses of ThreadedSocketAcceptor should override this if they have resources to dispose + /// Any override should call base.Dispose(disposing). + /// + /// true if called from Dispose() + protected void Dispose(bool disposing) + { + if(_disposed) { return; } + try { - if(_disposed) { return; } - try - { - Stop(); - DisposeSessions(); - _logFactoryAdapter?.Dispose(); - _sessions.Clear(); - _disposed = true; - } - catch (ObjectDisposedException) - { - // ignore - } + Stop(); + DisposeSessions(); + _logFactoryAdapter?.Dispose(); + _sessions.Clear(); + _disposed = true; } - /// - /// Disposes created sessions - /// - /// - /// To simply stop the acceptor without disposing sessions, use Stop() or Stop(bool) - /// - public void Dispose() + catch (ObjectDisposedException) { - Dispose( true ); - GC.SuppressFinalize( this ); + // ignore } - - ~ThreadedSocketAcceptor() => Dispose(false); } + /// + /// Disposes created sessions + /// + /// + /// To simply stop the acceptor without disposing sessions, use Stop() or Stop(bool) + /// + public void Dispose() + { + Dispose( true ); + GC.SuppressFinalize( this ); + } + + ~ThreadedSocketAcceptor() => Dispose(false); } diff --git a/QuickFIXn/ThreadedSocketReactor.cs b/QuickFIXn/ThreadedSocketReactor.cs index f8ba564e9..3a0e4e631 100755 --- a/QuickFIXn/ThreadedSocketReactor.cs +++ b/QuickFIXn/ThreadedSocketReactor.cs @@ -5,189 +5,188 @@ using Microsoft.Extensions.Logging; using QuickFix.Logger; -namespace QuickFix +namespace QuickFix; + +// TODO v2.0 - consider changing to internal + +/// +/// Handles incoming connections on a single endpoint. When a socket connection +/// is accepted, a ClientHandlerThread is created to handle the connection +/// +public class ThreadedSocketReactor { - // TODO v2.0 - consider changing to internal + public enum State { RUNNING, SHUTDOWN_REQUESTED, SHUTDOWN_COMPLETE } + + public State ReactorState => _state; /// - /// Handles incoming connections on a single endpoint. When a socket connection - /// is accepted, a ClientHandlerThread is created to handle the connection + /// Gets a value indicating whether this instance is running. /// - public class ThreadedSocketReactor - { - public enum State { RUNNING, SHUTDOWN_REQUESTED, SHUTDOWN_COMPLETE } - - public State ReactorState => _state; + /// + /// true if this instance is running; otherwise, false. + /// + public bool IsRunning => ReactorState == State.RUNNING; - /// - /// Gets a value indicating whether this instance is running. - /// - /// - /// true if this instance is running; otherwise, false. - /// - public bool IsRunning => ReactorState == State.RUNNING; + private readonly object _sync = new (); + private State _state = State.SHUTDOWN_COMPLETE; + private long _nextClientId = 0; + private readonly Dictionary _clientThreads = new (); + private readonly TcpListener _tcpListener; + private readonly SocketSettings _socketSettings; + private readonly AcceptorSocketDescriptor? _acceptorSocketDescriptor; + private readonly ILogger _nonSessionLog; + private readonly IQuickFixLoggerFactory _loggerFactory; - private readonly object _sync = new (); - private State _state = State.SHUTDOWN_COMPLETE; - private long _nextClientId = 0; - private readonly Dictionary _clientThreads = new (); - private readonly TcpListener _tcpListener; - private readonly SocketSettings _socketSettings; - private readonly AcceptorSocketDescriptor? _acceptorSocketDescriptor; - private readonly ILogger _nonSessionLog; - private readonly IQuickFixLoggerFactory _loggerFactory; - - internal ThreadedSocketReactor( - IPEndPoint serverSocketEndPoint, - SocketSettings socketSettings, - AcceptorSocketDescriptor? acceptorSocketDescriptor, - IQuickFixLoggerFactory loggerFactory) - { - _socketSettings = socketSettings; - _tcpListener = new TcpListener(serverSocketEndPoint); - _acceptorSocketDescriptor = acceptorSocketDescriptor; - _loggerFactory = loggerFactory; - _nonSessionLog = loggerFactory.CreateNonSessionLogger(); - } + internal ThreadedSocketReactor( + IPEndPoint serverSocketEndPoint, + SocketSettings socketSettings, + AcceptorSocketDescriptor? acceptorSocketDescriptor, + IQuickFixLoggerFactory loggerFactory) + { + _socketSettings = socketSettings; + _tcpListener = new TcpListener(serverSocketEndPoint); + _acceptorSocketDescriptor = acceptorSocketDescriptor; + _loggerFactory = loggerFactory; + _nonSessionLog = loggerFactory.CreateNonSessionLogger(); + } - public void Start() + public void Start() + { + lock (_sync) { - lock (_sync) + if( State.SHUTDOWN_COMPLETE == _state ) { - if( State.SHUTDOWN_COMPLETE == _state ) + try { - try - { - _tcpListener.Start(); - _state = State.RUNNING; - _tcpListener.BeginAcceptTcpClient(AcceptTcpClientCallback, _tcpListener); - } - catch (Exception e) { - _nonSessionLog.Log(LogLevel.Error, e, "Error starting listener"); - throw; - } + _tcpListener.Start(); + _state = State.RUNNING; + _tcpListener.BeginAcceptTcpClient(AcceptTcpClientCallback, _tcpListener); + } + catch (Exception e) { + _nonSessionLog.Log(LogLevel.Error, e, "Error starting listener"); + throw; } } } + } - public void Shutdown() + public void Shutdown() + { + lock (_sync) { - lock (_sync) + if (IsRunning) { - if (IsRunning) + try { - try - { - _state = State.SHUTDOWN_REQUESTED; - _tcpListener.Server.Close(); - _tcpListener.Stop(); - ShutdownClientHandlerThreads(); - } - catch (Exception e) - { - _nonSessionLog.Log(LogLevel.Error, e, "Error while closing server socket"); - } + _state = State.SHUTDOWN_REQUESTED; + _tcpListener.Server.Close(); + _tcpListener.Stop(); + ShutdownClientHandlerThreads(); + } + catch (Exception e) + { + _nonSessionLog.Log(LogLevel.Error, e, "Error while closing server socket"); } } } + } - private void AcceptTcpClientCallback( IAsyncResult ar ) - { - // If socket is in process of shutting down then a client may - // still be able to connect until it has shutdown - // but really we don't want to do anything with the connection so just ignore it - if (!IsRunning) - return; + private void AcceptTcpClientCallback( IAsyncResult ar ) + { + // If socket is in process of shutting down then a client may + // still be able to connect until it has shutdown + // but really we don't want to do anything with the connection so just ignore it + if (!IsRunning) + return; - TcpListener listener = (TcpListener)ar.AsyncState!; - try - { - TcpClient client = listener.EndAcceptTcpClient(ar); - ApplySocketOptions(client, _socketSettings); - ClientHandlerThread t = new ClientHandlerThread( - client, _nextClientId++, _socketSettings, _acceptorSocketDescriptor, _loggerFactory); - t.Exited += OnClientHandlerThreadExited; - lock (_sync) - { - _clientThreads.Add(t.Id, t); - } - // FIXME set the client thread's exception handler here - t.Start(); - } - catch (Exception e) - { - if (IsRunning) - _nonSessionLog.Log(LogLevel.Error, e, "Error accepting connection"); - } - if( IsRunning ) + TcpListener listener = (TcpListener)ar.AsyncState!; + try + { + TcpClient client = listener.EndAcceptTcpClient(ar); + ApplySocketOptions(client, _socketSettings); + ClientHandlerThread t = new ClientHandlerThread( + client, _nextClientId++, _socketSettings, _acceptorSocketDescriptor, _loggerFactory); + t.Exited += OnClientHandlerThreadExited; + lock (_sync) { - listener.BeginAcceptTcpClient( AcceptTcpClientCallback, listener ); + _clientThreads.Add(t.Id, t); } + // FIXME set the client thread's exception handler here + t.Start(); + } + catch (Exception e) + { + if (IsRunning) + _nonSessionLog.Log(LogLevel.Error, e, "Error accepting connection"); + } + if( IsRunning ) + { + listener.BeginAcceptTcpClient( AcceptTcpClientCallback, listener ); } + } - internal void OnClientHandlerThreadExited(object sender, ClientHandlerThread.ExitedEventArgs e) + internal void OnClientHandlerThreadExited(object sender, ClientHandlerThread.ExitedEventArgs e) + { + lock(_sync) { - lock(_sync) + if(_clientThreads.TryGetValue(e.ClientHandlerThread.Id, out var t)) { - if(_clientThreads.TryGetValue(e.ClientHandlerThread.Id, out var t)) - { - _clientThreads.Remove(t.Id); - t.Dispose(); - } + _clientThreads.Remove(t.Id); + t.Dispose(); } } + } - /// - /// Apply socket options from settings - /// - /// - /// - public static void ApplySocketOptions(TcpClient client, SocketSettings socketSettings) + /// + /// Apply socket options from settings + /// + /// + /// + public static void ApplySocketOptions(TcpClient client, SocketSettings socketSettings) + { + client.LingerState = new LingerOption(false, 0); + client.NoDelay = socketSettings.SocketNodelay; + if (socketSettings.SocketReceiveBufferSize.HasValue) { - client.LingerState = new LingerOption(false, 0); - client.NoDelay = socketSettings.SocketNodelay; - if (socketSettings.SocketReceiveBufferSize.HasValue) - { - client.ReceiveBufferSize = socketSettings.SocketReceiveBufferSize.Value; - } - if (socketSettings.SocketSendBufferSize.HasValue) - { - client.SendBufferSize = socketSettings.SocketSendBufferSize.Value; - } - if (socketSettings.SocketReceiveTimeout.HasValue) - { - client.ReceiveTimeout = socketSettings.SocketReceiveTimeout.Value; - } - if (socketSettings.SocketSendTimeout.HasValue) - { - client.SendTimeout = socketSettings.SocketSendTimeout.Value; - } + client.ReceiveBufferSize = socketSettings.SocketReceiveBufferSize.Value; + } + if (socketSettings.SocketSendBufferSize.HasValue) + { + client.SendBufferSize = socketSettings.SocketSendBufferSize.Value; + } + if (socketSettings.SocketReceiveTimeout.HasValue) + { + client.ReceiveTimeout = socketSettings.SocketReceiveTimeout.Value; + } + if (socketSettings.SocketSendTimeout.HasValue) + { + client.SendTimeout = socketSettings.SocketSendTimeout.Value; } + } - private void ShutdownClientHandlerThreads() + private void ShutdownClientHandlerThreads() + { + lock (_sync) { - lock (_sync) + if (State.SHUTDOWN_COMPLETE != _state) { - if (State.SHUTDOWN_COMPLETE != _state) + foreach (ClientHandlerThread t in _clientThreads.Values) { - foreach (ClientHandlerThread t in _clientThreads.Values) + t.Exited -= OnClientHandlerThreadExited; + t.Shutdown("reactor is shutting down"); + try { - t.Exited -= OnClientHandlerThreadExited; - t.Shutdown("reactor is shutting down"); - try - { - t.Join(); - } - catch (Exception e) - { - _nonSessionLog.Log(LogLevel.Error, e, "Error shutting down"); - } - t.Dispose(); + t.Join(); } - _clientThreads.Clear(); - _state = State.SHUTDOWN_COMPLETE; + catch (Exception e) + { + _nonSessionLog.Log(LogLevel.Error, e, "Error shutting down"); + } + t.Dispose(); } + _clientThreads.Clear(); + _state = State.SHUTDOWN_COMPLETE; } } } diff --git a/QuickFIXn/Transport/SocketInitiator.cs b/QuickFIXn/Transport/SocketInitiator.cs index 5e6af2a84..683267cc6 100644 --- a/QuickFIXn/Transport/SocketInitiator.cs +++ b/QuickFIXn/Transport/SocketInitiator.cs @@ -9,248 +9,247 @@ using QuickFix.Logger; using QuickFix.Store; -namespace QuickFix.Transport +namespace QuickFix.Transport; + +/// +/// Initiates connections and uses a single thread to process messages for all sessions. +/// +public class SocketInitiator : AbstractInitiator { - /// - /// Initiates connections and uses a single thread to process messages for all sessions. - /// - public class SocketInitiator : AbstractInitiator + private volatile bool _shutdownRequested = false; + private DateTime _lastConnectTimeDt = DateTime.MinValue; + private int _reconnectInterval = 30; + private readonly SocketSettings _socketSettings = new(); + private readonly Dictionary _threads = new(); + private readonly Dictionary _sessionToHostNum = new(); + private readonly object _sync = new(); + private readonly ILogger _nonSessionLog; + + public SocketInitiator( + IApplication application, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILogFactory? logFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) + : base(application, storeFactory, settings, logFactoryNullable, messageFactoryNullable) { - private volatile bool _shutdownRequested = false; - private DateTime _lastConnectTimeDt = DateTime.MinValue; - private int _reconnectInterval = 30; - private readonly SocketSettings _socketSettings = new(); - private readonly Dictionary _threads = new(); - private readonly Dictionary _sessionToHostNum = new(); - private readonly object _sync = new(); - private readonly ILogger _nonSessionLog; + _nonSessionLog = QfLoggerFactory.CreateNonSessionLogger(); + } - public SocketInitiator( - IApplication application, - IMessageStoreFactory storeFactory, - SessionSettings settings, - ILogFactory? logFactoryNullable = null, - IMessageFactory? messageFactoryNullable = null) - : base(application, storeFactory, settings, logFactoryNullable, messageFactoryNullable) - { - _nonSessionLog = QfLoggerFactory.CreateNonSessionLogger(); - } + public SocketInitiator( + IApplication application, + IMessageStoreFactory storeFactory, + SessionSettings settings, + ILoggerFactory? loggerFactoryNullable = null, + IMessageFactory? messageFactoryNullable = null) + : base(application, storeFactory, settings, loggerFactoryNullable, messageFactoryNullable) + { + _nonSessionLog = QfLoggerFactory.CreateNonSessionLogger(); + } - public SocketInitiator( - IApplication application, - IMessageStoreFactory storeFactory, - SessionSettings settings, - ILoggerFactory? loggerFactoryNullable = null, - IMessageFactory? messageFactoryNullable = null) - : base(application, storeFactory, settings, loggerFactoryNullable, messageFactoryNullable) - { - _nonSessionLog = QfLoggerFactory.CreateNonSessionLogger(); - } + public static void SocketInitiatorThreadStart(object? socketInitiatorThread) + { + SocketInitiatorThread? t = socketInitiatorThread as SocketInitiatorThread; + if (t == null) return; - public static void SocketInitiatorThreadStart(object? socketInitiatorThread) + try { - SocketInitiatorThread? t = socketInitiatorThread as SocketInitiatorThread; - if (t == null) return; - - try - { - t.Connect(); - t.Initiator.SetConnected(t.Session.SessionID); - t.Session.Log.Log(LogLevel.Information, "Connection succeeded"); - t.Session.Next(); - while (t.Read()) { - } - } - catch (IOException ex) // Can be exception when connecting, during ssl authentication or when reading - { - LogThreadStartConnectionFailed(t, ex); - } - catch (SocketException ex) - { - LogThreadStartConnectionFailed(t, ex); - } - catch (System.Security.Authentication.AuthenticationException ex) // some certificate problems - { - LogThreadStartConnectionFailed(t, ex); + t.Connect(); + t.Initiator.SetConnected(t.Session.SessionID); + t.Session.Log.Log(LogLevel.Information, "Connection succeeded"); + t.Session.Next(); + while (t.Read()) { } - catch (Exception ex) - { - LogThreadStartConnectionFailed(t, ex); - } - - t.Initiator.RemoveThread(t); - t.Initiator.SetDisconnected(t.Session.SessionID); } - - private static void LogThreadStartConnectionFailed(SocketInitiatorThread t, Exception e) { - if (t.Session.Disposed) - { - t.NonSessionLog.Log(LogLevel.Error, e, "Connection failed [session {SessionID}]: {Message}", - t.Session.SessionID, e.Message); - return; - } - t.Session.Log.Log(LogLevel.Error, e, "Connection failed: {Message}", e.Message); - } - - private void AddThread(SocketInitiatorThread thread) + catch (IOException ex) // Can be exception when connecting, during ssl authentication or when reading { - lock (_sync) - { - _threads[thread.Session.SessionID] = thread; - } + LogThreadStartConnectionFailed(t, ex); } - - private void RemoveThread(SocketInitiatorThread thread) + catch (SocketException ex) { - RemoveThread(thread.Session.SessionID); + LogThreadStartConnectionFailed(t, ex); } - - private void RemoveThread(SessionID sessionId) + catch (System.Security.Authentication.AuthenticationException ex) // some certificate problems { - // We can come in here on the thread being removed, and on another thread too in the case - // of dynamic session removal, so make sure we won't deadlock... - if (Monitor.TryEnter(_sync)) - { - if (_threads.TryGetValue(sessionId, out var thread)) - { - try - { - thread.Join(); - } - catch { } - _threads.Remove(sessionId); - } - Monitor.Exit(_sync); - } + LogThreadStartConnectionFailed(t, ex); } - - private IPEndPoint GetNextSocketEndPoint(SessionID sessionId, SettingsDictionary settings) + catch (Exception ex) { - if (!_sessionToHostNum.TryGetValue(sessionId, out var num)) - num = 0; - - string hostKey = SessionSettings.SOCKET_CONNECT_HOST + num; - string portKey = SessionSettings.SOCKET_CONNECT_PORT + num; - if (!settings.Has(hostKey) || !settings.Has(portKey)) - { - num = 0; - hostKey = SessionSettings.SOCKET_CONNECT_HOST; - portKey = SessionSettings.SOCKET_CONNECT_PORT; - } + LogThreadStartConnectionFailed(t, ex); + } - try - { - var hostName = settings.GetString(hostKey); - IPAddress[] addrs = Dns.GetHostAddresses(hostName); - int port = System.Convert.ToInt32(settings.GetLong(portKey)); - _sessionToHostNum[sessionId] = ++num; + t.Initiator.RemoveThread(t); + t.Initiator.SetDisconnected(t.Session.SessionID); + } - _socketSettings.ServerCommonName = hostName; - return new IPEndPoint(addrs.First(a => a.AddressFamily == AddressFamily.InterNetwork), port); - } - catch (Exception e) - { - throw new ConfigError(e.Message, e); - } + private static void LogThreadStartConnectionFailed(SocketInitiatorThread t, Exception e) { + if (t.Session.Disposed) + { + t.NonSessionLog.Log(LogLevel.Error, e, "Connection failed [session {SessionID}]: {Message}", + t.Session.SessionID, e.Message); + return; } + t.Session.Log.Log(LogLevel.Error, e, "Connection failed: {Message}", e.Message); + } - #region Initiator Methods - - /// - /// handle other socket options like TCP_NO_DELAY here - /// - /// - protected override void OnConfigure(SessionSettings settings) + private void AddThread(SocketInitiatorThread thread) + { + lock (_sync) { - try - { - _reconnectInterval = Convert.ToInt32(settings.Get().GetLong(SessionSettings.RECONNECT_INTERVAL)); - } - catch (Exception) - { } + _threads[thread.Session.SessionID] = thread; + } + } - // TODO: Don't know if this is required in order to handle settings in the general section - _socketSettings.Configure(settings.Get()); - } + private void RemoveThread(SocketInitiatorThread thread) + { + RemoveThread(thread.Session.SessionID); + } - protected override void OnStart() + private void RemoveThread(SessionID sessionId) + { + // We can come in here on the thread being removed, and on another thread too in the case + // of dynamic session removal, so make sure we won't deadlock... + if (Monitor.TryEnter(_sync)) { - _shutdownRequested = false; - - while(!_shutdownRequested) + if (_threads.TryGetValue(sessionId, out var thread)) { try { - double reconnectIntervalAsMilliseconds = 1000 * _reconnectInterval; - DateTime nowDt = DateTime.UtcNow; - - if (nowDt.Subtract(_lastConnectTimeDt).TotalMilliseconds >= reconnectIntervalAsMilliseconds) - { - Connect(); - _lastConnectTimeDt = nowDt; - } - } - catch (Exception e) - { - _nonSessionLog.Log(LogLevel.Error, e, "Failed to start: {Message}", e.Message); + thread.Join(); } - - Thread.Sleep(1 * 1000); + catch { } + _threads.Remove(sessionId); } + Monitor.Exit(_sync); } + } + + private IPEndPoint GetNextSocketEndPoint(SessionID sessionId, SettingsDictionary settings) + { + if (!_sessionToHostNum.TryGetValue(sessionId, out var num)) + num = 0; - /// - /// Ad-hoc session removal - /// - /// ID of session being removed - protected override void OnRemove(SessionID sessionId) + string hostKey = SessionSettings.SOCKET_CONNECT_HOST + num; + string portKey = SessionSettings.SOCKET_CONNECT_PORT + num; + if (!settings.Has(hostKey) || !settings.Has(portKey)) { - RemoveThread(sessionId); + num = 0; + hostKey = SessionSettings.SOCKET_CONNECT_HOST; + portKey = SessionSettings.SOCKET_CONNECT_PORT; } - protected override bool OnPoll(double timeout) + try { - throw new NotImplementedException("FIXME - SocketInitiator.OnPoll not implemented!"); + var hostName = settings.GetString(hostKey); + IPAddress[] addrs = Dns.GetHostAddresses(hostName); + int port = System.Convert.ToInt32(settings.GetLong(portKey)); + _sessionToHostNum[sessionId] = ++num; + + _socketSettings.ServerCommonName = hostName; + return new IPEndPoint(addrs.First(a => a.AddressFamily == AddressFamily.InterNetwork), port); + } + catch (Exception e) + { + throw new ConfigError(e.Message, e); } + } - protected override void OnStop() + #region Initiator Methods + + /// + /// handle other socket options like TCP_NO_DELAY here + /// + /// + protected override void OnConfigure(SessionSettings settings) + { + try { - _shutdownRequested = true; + _reconnectInterval = Convert.ToInt32(settings.Get().GetLong(SessionSettings.RECONNECT_INTERVAL)); } + catch (Exception) + { } + + // TODO: Don't know if this is required in order to handle settings in the general section + _socketSettings.Configure(settings.Get()); + } - protected override void DoConnect(Session session, SettingsDictionary settings) + protected override void OnStart() + { + _shutdownRequested = false; + + while(!_shutdownRequested) { try { - if (!session.IsSessionTime) - return; - - IPEndPoint socketEndPoint = GetNextSocketEndPoint(session.SessionID, settings); - SetPending(session.SessionID); - session.Log.Log(LogLevel.Information, "Connecting to {Address} on port {Port}", - socketEndPoint.Address, socketEndPoint.Port); + double reconnectIntervalAsMilliseconds = 1000 * _reconnectInterval; + DateTime nowDt = DateTime.UtcNow; - //Setup socket settings based on current section - var socketSettings = _socketSettings.Clone(); - socketSettings.Configure(settings); - - // Create a Ssl-SocketInitiatorThread if a certificate is given - SocketInitiatorThread t = new SocketInitiatorThread( - this, session, socketEndPoint, socketSettings, QfLoggerFactory); - t.Start(); - AddThread(t); + if (nowDt.Subtract(_lastConnectTimeDt).TotalMilliseconds >= reconnectIntervalAsMilliseconds) + { + Connect(); + _lastConnectTimeDt = nowDt; + } } - catch (Exception e) { - session.Log.Log(LogLevel.Error, e, "Connection error: {Message}", e.Message); + catch (Exception e) + { + _nonSessionLog.Log(LogLevel.Error, e, "Failed to start: {Message}", e.Message); } + + Thread.Sleep(1 * 1000); } + } - #endregion + /// + /// Ad-hoc session removal + /// + /// ID of session being removed + protected override void OnRemove(SessionID sessionId) + { + RemoveThread(sessionId); + } + + protected override bool OnPoll(double timeout) + { + throw new NotImplementedException("FIXME - SocketInitiator.OnPoll not implemented!"); + } - protected override void Dispose(bool disposing) + protected override void OnStop() + { + _shutdownRequested = true; + } + + protected override void DoConnect(Session session, SettingsDictionary settings) + { + try { - // nothing additional to do for this subclass - base.Dispose(disposing); + if (!session.IsSessionTime) + return; + + IPEndPoint socketEndPoint = GetNextSocketEndPoint(session.SessionID, settings); + SetPending(session.SessionID); + session.Log.Log(LogLevel.Information, "Connecting to {Address} on port {Port}", + socketEndPoint.Address, socketEndPoint.Port); + + //Setup socket settings based on current section + var socketSettings = _socketSettings.Clone(); + socketSettings.Configure(settings); + + // Create a Ssl-SocketInitiatorThread if a certificate is given + SocketInitiatorThread t = new SocketInitiatorThread( + this, session, socketEndPoint, socketSettings, QfLoggerFactory); + t.Start(); + AddThread(t); + } + catch (Exception e) { + session.Log.Log(LogLevel.Error, e, "Connection error: {Message}", e.Message); } } + + #endregion + + protected override void Dispose(bool disposing) + { + // nothing additional to do for this subclass + base.Dispose(disposing); + } } diff --git a/QuickFIXn/Transport/StreamFactory.cs b/QuickFIXn/Transport/StreamFactory.cs index 04a1e98f7..7861724ab 100644 --- a/QuickFIXn/Transport/StreamFactory.cs +++ b/QuickFIXn/Transport/StreamFactory.cs @@ -6,132 +6,131 @@ using System.Text; using QuickFix.Logger; -namespace QuickFix.Transport +namespace QuickFix.Transport; + +/// +/// StreamFactory is responsible for initiating for communication. +/// If any SSL setup is required it is performed here +/// +internal static class StreamFactory { - /// - /// StreamFactory is responsible for initiating for communication. - /// If any SSL setup is required it is performed here - /// - internal static class StreamFactory + private static Socket? CreateTunnelThruProxy(string destIp, int destPort, string destHostName) { - private static Socket? CreateTunnelThruProxy(string destIp, int destPort, string destHostName) - { - string destUriWithPort = $"{destIp}:{destPort}"; - UriBuilder uriBuilder = new UriBuilder(destUriWithPort); - Uri destUri = uriBuilder.Uri; - IWebProxy webProxy = WebRequest.DefaultWebProxy ?? WebRequest.GetSystemWebProxy(); + string destUriWithPort = $"{destIp}:{destPort}"; + UriBuilder uriBuilder = new UriBuilder(destUriWithPort); + Uri destUri = uriBuilder.Uri; + IWebProxy webProxy = WebRequest.DefaultWebProxy ?? WebRequest.GetSystemWebProxy(); - try - { - if (webProxy.IsBypassed(destUri)) - return null; - } - catch (PlatformNotSupportedException) - { - // .NET Core doesn't support IWebProxy.IsBypassed - // (because .NET Core doesn't have access to Windows-specific services, of course) + try + { + if (webProxy.IsBypassed(destUri)) return null; - } + } + catch (PlatformNotSupportedException) + { + // .NET Core doesn't support IWebProxy.IsBypassed + // (because .NET Core doesn't have access to Windows-specific services, of course) + return null; + } - Uri? proxyUri = webProxy.GetProxy(destUri); - if (proxyUri is null) - return null; + Uri? proxyUri = webProxy.GetProxy(destUri); + if (proxyUri is null) + return null; + + IPAddress[] proxyEntry = Dns.GetHostAddresses(proxyUri.Host); + int iPort = proxyUri.Port; + IPAddress address = proxyEntry.First(a => a.AddressFamily == AddressFamily.InterNetwork); + IPEndPoint proxyEndPoint = new IPEndPoint(address, iPort); + Socket socketThruProxy = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socketThruProxy.Connect(proxyEndPoint); + + string proxyMsg = $"CONNECT {destHostName}:{destPort} HTTP/1.1\nHost: {destHostName}:{destPort}\n\n"; + byte[] buffer = Encoding.ASCII.GetBytes(proxyMsg); + byte[] buffer12 = new byte[500]; + socketThruProxy.Send(buffer, buffer.Length, 0); + socketThruProxy.Receive(buffer12, 500, 0); + string data = Encoding.ASCII.GetString(buffer12); + int index = data.IndexOf("200", StringComparison.Ordinal); + + if (index < 0) + throw new ApplicationException( + $"Connection failed to {destUriWithPort} through proxy server {proxyUri}."); + + return socketThruProxy; + } - IPAddress[] proxyEntry = Dns.GetHostAddresses(proxyUri.Host); - int iPort = proxyUri.Port; - IPAddress address = proxyEntry.First(a => a.AddressFamily == AddressFamily.InterNetwork); - IPEndPoint proxyEndPoint = new IPEndPoint(address, iPort); - Socket socketThruProxy = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - socketThruProxy.Connect(proxyEndPoint); - - string proxyMsg = $"CONNECT {destHostName}:{destPort} HTTP/1.1\nHost: {destHostName}:{destPort}\n\n"; - byte[] buffer = Encoding.ASCII.GetBytes(proxyMsg); - byte[] buffer12 = new byte[500]; - socketThruProxy.Send(buffer, buffer.Length, 0); - socketThruProxy.Receive(buffer12, 500, 0); - string data = Encoding.ASCII.GetString(buffer12); - int index = data.IndexOf("200", StringComparison.Ordinal); - - if (index < 0) - throw new ApplicationException( - $"Connection failed to {destUriWithPort} through proxy server {proxyUri}."); - - return socketThruProxy; - } + /// + /// Connect to the specified endpoint and return a stream that can be used to communicate with it. (for initiator) + /// + /// The endpoint. + /// The socket settings. + /// + /// an opened and initiated stream which can be read and written to + internal static Stream CreateClientStream(IPEndPoint endpoint, SocketSettings settings, IQuickFixLoggerFactory loggerFactory) + { + Socket? socket = null; - /// - /// Connect to the specified endpoint and return a stream that can be used to communicate with it. (for initiator) - /// - /// The endpoint. - /// The socket settings. - /// - /// an opened and initiated stream which can be read and written to - internal static Stream CreateClientStream(IPEndPoint endpoint, SocketSettings settings, IQuickFixLoggerFactory loggerFactory) + if (!settings.SocketIgnoreProxy) { - Socket? socket = null; - - if (!settings.SocketIgnoreProxy) - { - if (settings.ServerCommonName is null) { - throw new ConfigError( - $"{SessionSettings.SOCKET_IGNORE_PROXY}=Y needs {SessionSettings.SSL_SERVERNAME} to be set"); - } - // If system has configured a proxy for this config, use it. - socket = CreateTunnelThruProxy(endpoint.Address.ToString(), endpoint.Port, settings.ServerCommonName); + if (settings.ServerCommonName is null) { + throw new ConfigError( + $"{SessionSettings.SOCKET_IGNORE_PROXY}=Y needs {SessionSettings.SSL_SERVERNAME} to be set"); } + // If system has configured a proxy for this config, use it. + socket = CreateTunnelThruProxy(endpoint.Address.ToString(), endpoint.Port, settings.ServerCommonName); + } - // No proxy. Set up a regular socket. - if (socket is null) - { - socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - socket.NoDelay = settings.SocketNodelay; - if (settings.SocketReceiveBufferSize.HasValue) - { - socket.ReceiveBufferSize = settings.SocketReceiveBufferSize.Value; - } - if (settings.SocketSendBufferSize.HasValue) - { - socket.SendBufferSize = settings.SocketSendBufferSize.Value; - } - socket.Connect(endpoint); - } - if (settings.SocketReceiveTimeout.HasValue) + // No proxy. Set up a regular socket. + if (socket is null) + { + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.NoDelay = settings.SocketNodelay; + if (settings.SocketReceiveBufferSize.HasValue) { - socket.ReceiveTimeout = settings.SocketReceiveTimeout.Value; + socket.ReceiveBufferSize = settings.SocketReceiveBufferSize.Value; } - if (settings.SocketSendTimeout.HasValue) + if (settings.SocketSendBufferSize.HasValue) { - socket.SendTimeout = settings.SocketSendTimeout.Value; + socket.SendBufferSize = settings.SocketSendBufferSize.Value; } + socket.Connect(endpoint); + } + if (settings.SocketReceiveTimeout.HasValue) + { + socket.ReceiveTimeout = settings.SocketReceiveTimeout.Value; + } + if (settings.SocketSendTimeout.HasValue) + { + socket.SendTimeout = settings.SocketSendTimeout.Value; + } - Stream stream = new NetworkStream(socket, true); - - if (settings.UseSSL) - stream = new SslStreamFactory(settings, loggerFactory).CreateClientStreamAndAuthenticate(stream); + Stream stream = new NetworkStream(socket, true); - return stream; - } + if (settings.UseSSL) + stream = new SslStreamFactory(settings, loggerFactory).CreateClientStreamAndAuthenticate(stream); - /// - /// Initiate communication to the remote client and return a stream that can be used to communicate with it. (for acceptor) - /// - /// The TCP client. - /// The socket settings. - /// - /// an opened and initiated stream which can be read and written to - /// tcp client must be connected in order to get stream;tcpClient - internal static Stream CreateServerStream(TcpClient tcpClient, SocketSettings settings, IQuickFixLoggerFactory loggerFactory) - { - if (tcpClient.Connected == false) - throw new ArgumentException("tcp client must be connected in order to get stream", nameof(tcpClient)); + return stream; + } - Stream stream = tcpClient.GetStream(); - if (settings.UseSSL) - { - stream = new SslStreamFactory(settings, loggerFactory).CreateServerStreamAndAuthenticate(stream); - } + /// + /// Initiate communication to the remote client and return a stream that can be used to communicate with it. (for acceptor) + /// + /// The TCP client. + /// The socket settings. + /// + /// an opened and initiated stream which can be read and written to + /// tcp client must be connected in order to get stream;tcpClient + internal static Stream CreateServerStream(TcpClient tcpClient, SocketSettings settings, IQuickFixLoggerFactory loggerFactory) + { + if (tcpClient.Connected == false) + throw new ArgumentException("tcp client must be connected in order to get stream", nameof(tcpClient)); - return stream; + Stream stream = tcpClient.GetStream(); + if (settings.UseSSL) + { + stream = new SslStreamFactory(settings, loggerFactory).CreateServerStreamAndAuthenticate(stream); } + + return stream; } }