FixAntenna/NetCore/Configuration/Config.cs (831 lines of code) (raw):
// Copyright (c) 2021 EPAM Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Epam.FixAntenna.NetCore.Common;
using Epam.FixAntenna.NetCore.Common.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Epam.FixAntenna.NetCore.Configuration
{
/// <summary>
/// Engine configuration class.
/// Engine used <c>Configuration.GetGlobalConfiguration</c> method to configure itself,
/// user can change this properties in runtime only for initiator sessions, not for acceptors.
/// </summary>
public class Config : ICloneable
{
public const string InfinityAutoreconnect = "0";
public const string NoAutoreconnect = "-1";
public const string UndefinedAffinity = "-1";
public const string LogonCustomizationStrategyUndefined = "none";
public const string CmeSecureLogonStrategy = "Epam.FixAntenna.NetCore.FixEngine.Session.Impl.CmeSecureLogonStrategy";
/// <summary>
/// Reset sequences on switch to backup.
/// </summary>
[DefaultValue("false")]
public const string ResetOnSwitchToBackup = "resetOnSwitchToBackup";
/// <summary>
/// Reset sequences on switch back to primary connection
/// </summary>
[DefaultValue("false")]
public const string ResetOnSwitchToPrimary = "resetOnSwitchToPrimary";
/// <summary>
/// This parameter switches on mode which prevent sending multiple RR for the same gap
/// </summary>
[DefaultValue("false")]
public const string SwitchOffSendingMultipleResendRequests = "switchOffSendingMultipleResendRequests";
/// <summary>
/// Specifies number of autoreconnect attempts before give up:
/// negative number = no reconnects (NoAutoreconnect), <br/>
/// 0 - infinite number of reconnects (InfinityAutoreconnect), <br/>
/// positive number = number of reconnect attempts <br/>
/// Please use 0 wisely - it means reconnect infinitely
/// </summary>
[DefaultValue(NoAutoreconnect)]
public const string AutoreconnectAttempts = "autoreconnectAttempts";
/// <summary>
/// Allows user to replace storage factory with user own implementation
/// </summary>
[DefaultValue("Epam.FixAntenna.NetCore.FixEngine.Storage.FilesystemStorageFactory")]
public const string StorageFactory = "storageFactory";
/// <summary>
/// Allows user to replace session sequence manager with user own implementation
/// </summary>
[DefaultValue("Epam.FixAntenna.NetCore.FixEngine.Session.StandardSessionSequenceManager")]
public const string SessionSequenceManager = "sessionSequenceManager";
/// <summary>
/// Sends a reject if user application is not available.
/// If the value is false and client application is not available, acts like a "black hole" - accepts and ignores all valid messages.
/// </summary>
[DefaultValue("true")]
public const string SendRejectIfApplicationIsNotAvailable = "sendRejectIfApplicationIsNotAvailable";
/// <summary>
/// Enable auto switching to backup connection, the default value is true.
/// </summary>
[DefaultValue("true")]
public const string EnableAutoSwitchToBackupConnection = "enableAutoSwitchToBackupConnection";
/// <summary>
/// Enable switch to primary connection, default value true
/// </summary>
[DefaultValue("true")]
public const string CyclicSwitchBackupConnection = "cyclicSwitchBackupConnection";
/// <summary>
/// Specifies delay between autoreconnect attempts in milliseconds, default value is 1000ms.
/// </summary>
[DefaultValue("1000")]
public const string AutoreconnectDelayInMs = "autoreconnectDelayInMs";
/// <summary>
/// Suppress session qualifier tag in logon message.
/// </summary>
[DefaultValue("false")]
public const string SuppressSessionQualifierTagInLogonMessage =
"suppressSessionQualifierTagInLogonMessage";
/// <summary>
/// Specifies tag number for session qualifier in logon message.
/// </summary>
[DefaultValue("9012")]
public const string LogonMessageSessionQualifierTag = "logonMessageSessionQualifierTag";
/// <summary>
/// Name of the custom package for admin commands processing
/// This property is using for the extending the count of admin-commands.
/// By default package is null, but if custom commands is present this property should be initialized,
/// for example (autostart.acceptor.commands.package=com.admin.commands).
/// </summary>
public const string AutostartAcceptorCommandPackage = "autostart.acceptor.commands.package";
/// <summary>
/// Disable/enable Nagle's algorithm for TCP sockets.
/// This option has the opposite meaning to TCP_NO_DELAY socket option.
/// With enabled Nagle's algorithm will be better throughput (TcpNoDelay=false)
/// but with disabled option you will get better result for latency on single message (TcpNoDelay=true)
/// </summary>
[DefaultValue("true")]
public const string EnableNagle = "enableNagle";
/// <summary>
/// Limits the maximum number of messages buffered during the resend request.
/// The parameter must be integer and positive.
/// Otherwise the default value for this parameter will be used.
/// </summary>
[DefaultValue("32")]
public const string SequenceResendManagerMessageBufferSize = "sequenceResendManagerMessageBufferSize";
/// <summary>
/// Limits the maximum number of messages during the resend request.
/// If more messages are requested, the reject will be sent in response.
/// The parameter must be integer and not negative.
/// Otherwise the default value for this parameter will be used.
/// </summary>
[DefaultValue("0")]
public const string ResendRequestNumberOfMessagesLimit = "resendRequestNumberOfMessagesLimit";
/// <summary>
/// Toggle on/off validation of fields with tag 8, 9, 35 and 10 values.
/// If "validation=false" then this parameter always reads as false.
/// </summary>
[DefaultValue("true")]
public const string WelformedValidator = "wellformenessValidation";
/// <summary>
/// Toggle on/off validation of allowed message fields.
/// If "validation=false" then this parameter always reads as false.
/// </summary>
[DefaultValue("true")]
public const string AllowedFieldsValidation = "allowedFieldsValidation";
/// <summary>
/// Toggle on/off validation of required message fields.
/// If "validation=false" then this parameter always reads as false.
/// </summary>
[DefaultValue("true")]
public const string RequiredFieldsValidation = "requiredFieldsValidation";
/// <summary>
/// Toggle on/off validation of field values according to defined data types.
/// </summary>
[DefaultValue("true")]
public const string FieldTypeValidation = "fieldTypeValidation";
/// <summary>
/// enable/disable conditional validator,
/// default value is false.
/// </summary>
[DefaultValue("true")]
public const string ConditionalValidation = "conditionalValidation";
/// <summary>
/// Enable/disable group validator,
/// default value is false.
/// </summary>
[DefaultValue("true")]
public const string GroupValidation = "groupValidation";
/// <summary>
/// Toggle on/off validation of duplicated message fields.
/// If "validation=false" then this parameter always reads as false.
/// </summary>
[DefaultValue("true")]
public const string DuplicateFieldsValidation = "duplicateFieldsValidation";
/// <summary>
/// Toggle on/off validation of fields order in message.
/// With this option engine will check that tags from the header, body and trailer were not mixed up.
/// If "validation=false" then this parameter always reads as false.
/// </summary>
[DefaultValue("true")]
public const string FieldOrderValidation = "fieldOrderValidation";
/// <summary>
/// Sending time delay for incoming messages in milliseconds.
/// </summary>
[DefaultValue("120000")]
public const string Delay = "reasonableDelayInMs";
/// <summary>
/// The desired precision of timestamps in appropriate tags of the FIX message.
/// Valid values: Second | Milli | Micro | Nano.
/// </summary>
[DefaultValue("Milli")]
public const string TimestampsPrecisionInTags = "timestampsPrecisionInTags";
/// <summary>
/// Use timestamp with precision defined by timestampsPrecisionInTags option for FIX 4.0 if enabled.
/// </summary>
[DefaultValue("false")]
public const string AllowedSecondsFractionsForFix40 = "allowedSecondsFractionsForFIX40";
/// <summary>
/// Measurement accuracy in milliseconds, default value is 1 ms.
/// </summary>
[DefaultValue("1")]
public const string Accuracy = "measurementAccuracyInMs";
/// <summary>
/// Toggle on/off the check of SendingTime (52) accuracy for received messages.
/// </summary>
[DefaultValue("true")]
public const string CheckSendingTimeAccuracy = "checkSendingTimeAccuracy";
/// <summary>
/// Enable/disable message rejecting, default value is false
/// </summary>
[DefaultValue("false")]
public const string EnableMessageRejecting = "enableMessageRejecting";
/// <summary>
/// Enable/disable message statistic, default value is true
/// </summary>
[DefaultValue("true")]
public const string EnableMessageStatistic = "enableMessageStatistic";
/// <summary>
/// Maximum number of messages in a queue before we pause a pumper thread to let the queued message be sent out.
/// <ul>
/// <li>Set rather high for max performance.</li>
/// <li>Set 1 or pretty low for realtime experience.</li>
/// <li>0 - disable queue control, do not pause the pumper thread.</li>
/// </ul>
/// </summary>
[DefaultValue("0")]
public const string QueueThresholdSize = "queueThresholdSize";
/// <summary>
/// The maximum number of messages in buffer before we
/// write message to transport.
/// NOTE: Value for this property should be always > 0.
/// default value is 10
/// </summary>
[DefaultValue("10")]
public const string MaxMessagesToSendInBatch = "maxMessagesToSendInBatch";
/// <summary>
/// Maximum message size supported by this FIX engine instance.
/// The parameter must be integer and not negative. Otherwise, the default value for this parameter will be used.
/// Should be set to a greater than expected maximum message by approximately 1-5%.
/// <ul>
/// <li>positive number - maximum allowed size of incoming message</li>
/// <li>0 - any size message allowed (not recommended, could lead to OutOfMemoryError if counterparty will send invalid stream).</li>
/// </ul>
/// </summary>
[DefaultValue("1Mb")]
public const string MaxMessageSize = "maxMessageSize";
/// <summary>
/// Validate or not message CheckSum(10)
/// Is relevant only if validateGarbledMessage=true
/// default value is true
/// </summary>
[DefaultValue("true")]
public const string ValidateCheckSum = "validateCheckSum";
/// <summary>
/// Toggle on/off validation garbled message for incoming flow.
/// Validates the existence and order of the following fields: BeginString(8), BodyLength(9), MsgType(35), CheckSum(10).
/// Also validates value of BodyLength(9).
/// default value is true
/// </summary>
[DefaultValue("true")]
public const string ValidateGarbledMessage = "validateGarbledMessage";
/// <summary>
/// Transport will set the additional time mark in nanoseconds for incoming messages right after read data from
/// socket if this option is set to true.
/// AbstractFIXTransport.getLastReadMessageTimeNano() method could return this value.
/// <p/>
/// default value is false
/// </summary>
[DefaultValue("false")]
public const string MarkIncomingMessageTime = "markIncomingMessageTime";
/// <summary>
/// Include last processed sequence 369 tag in every message for FIX versions>4.2
/// </summary>
[DefaultValue("false")]
public const string IncludeLastProcessed = "includeLastProcessed";
/// <summary>
/// Sets the disconnect timeout in seconds for a Logout ack only when waiting for.
/// The Logout ack from the counterparty is caused by the incoming sequence number less then expected.
/// The parameter must be integer and not negative.
/// Otherwise the default value for this parameter will be used.
/// </summary>
[DefaultValue("2")]
public const string ForcedLogoffTimeout = "forcedLogoffTimeout";
/// <summary>
/// Sets the timeout interval after which a connected acceptor session will be timed out
/// and disposed if Logon is not received for this session.
/// default value is 5000
/// </summary>
[DefaultValue("5000")]
public const string LoginWaitTimeout = "loginWaitTimeout";
/// <summary>
/// Sets disconnect timeout in seconds for logoff,
/// default value is equal to session's HeartbeatInterval
/// </summary>
public const string LogoutWaitTimeout = "logoutWaitTimeout";
/// <summary>
/// This parameter specifies whether to process 789-NextExpectedMsgSeqNum tag.
/// If true, outgoing sequence number must be updated by 789-NextExpectedMsgSeqNum tag value.
/// </summary>
[DefaultValue("false")]
public const string HandleSeqnumAtLogon = "handleSeqNumAtLogon";
/// <summary>
/// Check and disconnect session if Logon answer contains other HeartBtInt(108) value than defined in session
/// configuration.
///
/// Default value: true
/// </summary>
[DefaultValue("true")]
public const string DisconnectOnLogonHbtMismatch = "disconnectOnLogonHeartbeatMismatch";
/// <summary>
/// Sets queue mode. Set to "false" for persistent queue (slower but no messages will be lost),
/// "true" for in memory queue (faster but less safe, some messages may be lost).
/// This property makes sense only if FilesystemStorageFactory is set.
/// </summary>
[DefaultValue("false")]
public const string InMemoryQueue = "inMemoryQueue";
/// <summary>
/// Sets persistent queue mode for MMFStorageFactory.
/// Set to "false" for persistent queue (slower but no messages will be lost),
/// "true" for memory mapped queue (faster but less safe, some messages may be lost)
/// </summary>
[DefaultValue("true")]
public const string MemoryMappedQueue = "memoryMappedQueue";
/// <summary>
/// True will enables incoming storage index.
/// Enabled index - messages in incoming storage will be available via API
/// </summary>
public const string IncomingStorageIndexed = "incomingStorageIndexed";
/// <summary>
/// Outgoing storage index.
/// This property makes sense only if FilesystemStorageFactory or MMFStorageFactory is set.
/// Set to "true" to enable outgoing storage index that is to be used in decision making in resend request handler.
/// Enabled index - support resend request, disabled - never resend messages and always send gap fill.
/// </summary>
[DefaultValue("true")]
public const string OutgoingStorageIndexed = "outgoingStorageIndexed";
/// <summary>
/// Sets path to fa home.
/// </summary>
[DefaultValue(".")]
public const string FaHome = "fa.home";
/// <summary>
/// Storage directory could be either absolute path (like /tmp/logs or c:\fixengine\logs)
/// or relative path e.g. logs (this one is relative to the application start directory).
/// </summary>
[DefaultValue("${fa.home}/logs")]
public const string StorageDirectory = "storageDirectory";
/// <summary>
/// Raw tags. List all tags here engine should treat as raw. Raw tag may contain SOH symbol inside it
/// and it should be preceided by rawTagLength field.
/// </summary>
[DefaultValue("96, 91, 213, 349, 351, 353, 355, 357, 359, 361, 363, 365, 446, 619, 622")]
public const string RawTags = "rawTags";
/// <summary>
/// Toggle on/off validation of incoming messages according to base of custom dictionaries
/// Following parameters with the Validation suffix works only if this property set to true.
/// </summary>
[DefaultValue("false")]
public const string Validation = "validation";
/// <summary>
/// Outgoing log filename template. {0} will be replaced with actual sessionID, {1} with actual SenderCompID,
/// {2} with actual TargetCompID and {4} with actual session qualifier.
/// </summary>
[DefaultValue("{0}.out")]
public const string OutgoingLogFile = "outgoingLogFile";
/// <summary>
/// Backup outgoing log filename template. {0} will be replaced with actual sessionID, {1} with SenderCompID,
/// {2} with actual TargetCompID, {3} with timestamp and {4} with actual session qualifier.
/// </summary>
[DefaultValue("{0}-{3}.out")]
public const string BackupOutgoingLogFile = "backupOutgoingLogFile";
/// <summary>
/// Incoming log filename template. {0} will be replaced with actual sessionID, {1} with actual SenderCompID,
/// {2} with actual TargetCompID and {4} with actual session qualifier.
/// </summary>
[DefaultValue("{0}.in")]
public const string IncomingLogFile = "incomingLogFile";
/// <summary>
/// Backup incoming log filename template. {0} will be replaced with actual sessionID, {1} with SenderCompID,
/// {2} with actual TargetCompID, {3} with timestamp and {4} with actual session qualifier.
/// </summary>
[DefaultValue("{0}-{3}.in")]
public const string BackupIncomingLogFile = "backupIncomingLogFile";
/// <summary>
/// Info filename template. {0} will be replaced with actual sessionID, {1} with actual SenderCompID,
/// {2} with actual TargetCompID and {4} with actual session qualifier.
/// </summary>
[DefaultValue("{0}.properties")]
public const string SessionInfoFile = "sessionInfoFile";
/// <summary>
/// Out queue file template. {0} will be replaced with actual sessionID, {1} with actual SenderCompID,
/// {2} with actual TargetCompID and {4} with actual session qualifier.
/// </summary>
[DefaultValue("{0}.outq")]
public const string OutgoingQueueFile = "outgoingQueueFile";
/// <summary>
/// This parameter allow to automatically resolve sequence gap problem (for example, when there is every day sequence reset).
/// Supported values: Always, OneTime, Never.
/// If this parameter is set to:
/// <ul>
/// <li>Always - session will send logon with 34= 1 and 141=Y every times (during connection and reconnection)</li>
/// <li>OneTime - session will send logon with 34= 1 and 141=Y only one time (during connection)</li>
/// <li>Never - this means that user can sets the 34= 1 and 141=Y from session parameters by hand</li>
/// </ul>
/// </summary>
[DefaultValue("Never")]
public const string ForceSeqNumReset = "forceSeqNumReset";
/// <summary>
/// Time zone for prefix in out/in logs
/// </summary>
public const string LogFilesTimeZone = "logFilesTimeZone";
/// <summary>
/// Ability to write timestamps in the in/out log files.
/// Default value true.
/// </summary>
[DefaultValue("true")]
public const string TimestampsInLogs = "timestampsInLogs";
/// <summary>
/// The desired pecision of timestamps in the in/out log files.
/// Valid values: Milli | Micro | Nano.
/// </summary>
[DefaultValue("Milli")]
public const string TimestampsPrecisionInLogs = "timestampsPrecisionInLogs";
/// <summary>
/// The desired pecision of timestamps in names of storage backup files.
/// Valid values: Milli | Micro | Nano.
/// </summary>
[DefaultValue("Milli")]
public const string BackupTimestampsPrecision = "backupTimestampsPrecision";
/// <summary>
/// Specifies the maximum size of the storage file after which the engine creates a new storage file with a different name.
/// Parameter must be integer and not negative.
/// This property makes sense only if SlicedFileStorageFactory is set.
/// </summary>
[DefaultValue("100Mb")]
public const string MaxStorageSliceSize = "maxStorageSliceSize";
/// <summary>
/// Sets the maximum storage grow size in bytes.
/// Parameter must be integer and not negative.
/// </summary>
[DefaultValue("1Mb")]
public const string MaxStorageGrowSize = "maxStorageGrowSize";
/// <summary>
/// Sets the storage grow size in bytes for memory mapped implementation.
/// Parameter must be integer and not negative.
/// This property makes sense only if MMFStorageFactory is set.
/// </summary>
[DefaultValue("100Mb")]
public const string MmfStorageGrowSize = "mmfStorageGrowSize";
/// <summary>
/// Sets the index grow size in bytes for memory mapped implementation.
/// Used only for storage with memory mapped index file.
/// Parameter must be integer and not negative.
/// This property makes sense only if MMFStorageFactory is set and at least one of incomingStorageIndexed or outgoingStorageIndexed is true.
/// </summary>
[DefaultValue("20Mb")]
public const string MmfIndexGrowSize = "mmfIndexGrowSize";
/// <summary>
/// Enable/disable storage grow.
/// Default value: false.
/// </summary>
[DefaultValue("false")]
public const string StorageGrowSize = "storageGrowSize";
/// <summary>
/// Engine's local IP address to send from.
/// It can be used on a multi-homed host for a FIX Engine that will only send IP datagrams from one of its addresses.
/// If this parameter is commented, the engine will send IP datagrams from any/all local addresses.
/// </summary>
public const string ConnectAddress = "connectAddress";
/// <summary>
/// The max requested messages in block. This parameter defines how many messages will be request in one block.
/// The value must be integer and not less than 0.
/// </summary>
[DefaultValue("0")]
public const string MaxRequestResendInBlock = "maxRequestResendInBlock";
/// <summary>
/// The pause before sending application messages from outgoing queue in milliseconds after receiving Logon.
/// This pause is need to handle possible incoming ResendRequest. In other case a bunch of messages with
/// invalid sequence can be sent.
/// The value must be integer and not less than 0.
/// </summary>
[DefaultValue("50")]
public const string MaxDelayToSendAfterLogon = "maxDelayToSendAfterLogon";
/// <summary>
/// This parameter specifies whether to reset sequence number at time defined in resetSequenceTime.
/// </summary>
[DefaultValue("false")]
public const string PerformResetSeqNumTime = "performResetSeqNumTime";
/// <summary>
/// This parameter specifies GMT time when the FIX Engine initiates the reset of sequence numbers.
/// Valid time format: HH:MM:SS
/// </summary>
[DefaultValue("00:00:00")]
public const string ResetSequenceTime = "resetSequenceTime";
/// <summary>
/// Time zone id for resetSequenceTime property.
/// </summary>
[DefaultValue("UTC")]
public const string ResetSequenceTimeZone = "resetSequenceTimeZone";
/// <summary>
/// This parameter specifies cleaning mode for message storage of closed sessions.
/// Valid values: None | Backup | Delete.
/// </summary>
[DefaultValue("None")]
public const string StorageCleanupMode = "storageCleanupMode";
/// <summary>
/// This parameter specifies back-up directory for message logs
/// of closed sessions when storageCleanupMode=backup.
/// Valid values: existent directory name (relative or absolute path)
/// Default value not defined
/// See FA_HOME description in the Configuration section of the
/// FIX Antenna .NET Core User and Developer Manual.
/// </summary>
[DefaultValue("${fa.home}/logs/backup")]
public const string StorageBackupDir = "storageBackupDir";
/// <summary>
/// This parameter specifies whether to reset sequence number after session is closed.
/// Valid values: true | false
/// </summary>
[DefaultValue("false")]
public const string IntraDaySeqnumReset = "intraDaySeqNumReset";
/// <summary>
/// This parameter specifies whether to send ResetSeqNumFlag (141=Y) after sequence rest or not.
/// Valid values: true | false
/// </summary>
[DefaultValue("false")]
public const string IgnoreResetSeqNumFlagOnReset = "ignoreResetSeqNumFlagOnReset";
public const string DesKeyPrefix = "desKey";
public const string PubKeyFile = "pubKeyFile";
public const string SecKeyFile = "secKeyFile";
public const string PubKey = "pgpKey";
/// <summary>
/// This parameter specifies encryption config file name.
/// Valid values: existent valid config file name (relative or absolute path)
/// Default value not defined
/// </summary>
[DefaultValue("${fa.home}/encryption/encryption.cfg")]
public const string EncryptionCfgFile = "encryptionConfig";
/// <summary>
/// This parameter specifies the default value of encryptionMode.
/// Valid values: None | Des | PgpDesMd5
/// </summary>
[DefaultValue("None")]
public const string EncryptionMode = "encryptionMode";
public const string EnableLoggingOfIncomingMessages = "enableLoggingOfIncomingMessages";
/// <summary>
/// This parameter specifies "some reasonable transmission time" of FIX specification, measured in milliseconds.
/// Valid values: positive integer
/// Default value: 200
/// </summary>
[DefaultValue("200")]
public const string HbtReasonableTransmissionTime = "heartbeatReasonableTransmissionTime";
/// <summary>
/// This parameter specifies whether to check the OrigSendingTime(122) field value for incoming possible
/// duplicated messages (PossDupFlag(43) = 'Y').
/// Valid values: true | false
/// Default value: true
/// </summary>
[DefaultValue("true")]
public const string OrigSendingTimeChecking = "origSendingTimeChecking";
/// <summary>
/// Enable this option if it need to handle SequenceReset-GapFill message without PossDupFlag(43).
/// Also this option allow to ignore absence of OrigSendingTime(122) in such message.
/// Valid values: true | false
/// Default value: true
/// </summary>
[DefaultValue("true")]
public const string IgnorePossDupForGapFill = "ignorePossDupForGapFill";
/// <summary>
/// This parameter specifies number of Test Request messages, which will be sent before connection loss
/// is reported when no messages are received from the counterparty.
/// Default value: 1
/// </summary>
[DefaultValue("1")]
public const string TestRequestsNumberUponDisconnection = "testRequestsNumberUponDisconnection";
/// <summary>
/// This parameter specifies whether to issue subsequently duplicates
/// (PossDupFlag(43) = 'Y') of last Resend Request for continuing gaps resting on
/// LastMsgSeqNumProcessed(369) field values of incoming messages.
/// The counterparty then must respond only to the original request or
/// a subsequent duplicate Resend Request if it missed the original.
/// The duplicate(s), otherwise, can be discarded, as it does not have a unique
/// message sequence number of its own.
/// </summary>
[DefaultValue("false")]
public const string AdvancedResendRequestProcessing = "advancedResendRequestProcessing";
/// <summary>
/// This parameter specifies whether respond only to the original request or
/// a subsequent duplicate Resend Request if it missed the original.
/// If this option is disabled, Fix Antenna will respond to any Resend Request.
/// </summary>
[DefaultValue("false")]
public const string SkipDuplicatedResendRequest = "skipDuplicatedResendRequests";
/// <summary>
/// This parameter enables delivery of only those PossDup messages that wasn't received previously.
/// Discarding already processed possDups.
/// </summary>
[DefaultValue("false")]
public const string PossDupSmartDelivery = "possDupSmartDelivery";
/// <summary>
/// This parameter specifies the default Acceptor Strategy.
/// Valid values: subclasses of FixAntenna.NetCore.FixEngine.Acceptor.SessionAcceptorStrategyHandler
/// <br/>
/// Possible values:
/// <br/>
/// <c>FixAntenna.NetCore.FixEngine.Acceptor.SessionAcceptorStrategyHandler</c>
/// <br/>
/// <c>FixAntenna.NetCore.FixEngine.Acceptor.DenyNonRegisteredAcceptorStrategyHandler</c>
/// <br/>
/// <c>FixAntenna.NetCore.FixEngine.Acceptor.DenyNonRegisteredAcceptorStrategyHandler</c>
/// </summary>
[DefaultValue("Epam.FixAntenna.NetCore.FixEngine.Acceptor.AllowNonRegisteredAcceptorStrategyHandler")]
public const string ServerAcceptorStrategy = "serverAcceptorStrategy";
/// <summary>
/// This parameter specifies the way the session will send most of its messages:<br/>
/// Async - session will send all message asynchronously and it will be optimized for this<br/>
/// Sync - session will be optimized to send messages from user thread, but it still can make asynchronous
/// operation and it allows to add messages to internal queue<br/>
/// SyncNoqueue - session sends message only from user thread and doesn't use internal queue. It's impossible to
/// send messages to disconnected session. <br/>
/// <p/>
/// Valid values: Async/Sync/SyncNoqueue
/// Default value: sync
/// </summary>
[DefaultValue("sync")]
public const string PreferredSendingMode = "preferredSendingMode";
/// <summary>
/// This parameter specifies the maximum delay interval on message sending if the internal session queue is full.
/// If the internal session's queue is full then FIX Antenna pause the sending thread till the message
/// pumper thread send some messages and free some space in the queue. If after the delay interval queue still full,
/// then message will be pushed to the queue anyway.
/// <p/>
/// Valid values: positive integer
/// Default value: 1000
/// </summary>
[DefaultValue("1000")]
public const string WaitForMsgQueuingDelay = "waitForMsgQueuingDelay";
/// <summary>
/// This parameter allow to resolve wrong incoming sequence at Logon.
/// When it true - session continue with received seqNum.
/// <p/>
/// Valid values: true/false
/// Default value: false
/// </summary>
[DefaultValue("false")]
public const string IgnoreSeqNumTooLowAtLogon = "ignoreSeqNumTooLowAtLogon";
/// <summary>
/// When disabled prevents outgoing queue reset if client connecting with lower than expected sequence number.
/// Once session is reestablished queued messages will be sent out.
/// Please note that queued messages won't be sent out till session if fully established regardless of that parameter.
/// <para>
/// Default value: true
/// </para>
/// </summary>
[DefaultValue("true")]
public const string ResetQueueOnLowSequenceNum = "resetQueueOnLowSequence";
/// <summary>
/// Enable this option if it need to quiet handle Logout as a first session message.
/// FIX Specification requires that first message should be Logon. In other case it needs to send with answer Logout
/// message warning "First message is not logon". Also sоmetimes first incoming Logout has a wrong sequence
/// (for example if you send Logon with 141=Y). This option allow to skip sending ResendRequest and warning
/// to counterparty.
/// Valid values: true | false
/// Default value: false
/// </summary>
[DefaultValue("false")]
public const string QuietLogonMode = "quietLogonMode";
/// <summary>
/// Sets the timeout interval in seconds for waiting reading thread finishing during session shutdown. Reading thread
/// may be interrupted after this interval if it was blocked.
///
/// The parameter must be integer and not negative. Otherwise, the standard value for this parameter will be used.
/// Default value: session heartbeat interval
/// </summary>
[DefaultValue("-1")]
public const string ReadingThreadShutdownTimeout = "readingThreadShutdownTimeout";
/// <summary>
/// Sets the timeout interval in seconds for waiting writing thread finishing during session shutdown. Writing thread
/// may be interrupted after this interval if it was blocked.
///
/// The parameter must be integer and not negative. Otherwise, the standard value for this parameter will be used.
/// Default value: session heartbeat interval
/// </summary>
[DefaultValue("-1")]
public const string WritingThreadShutdownTimeout = "writingThreadShutdownTimeout";
/// <summary>
/// This option indicate how many similar ResendRequests (for same range of sequences) engine may sends before
/// detecting possible infinite resend loop. This should prevent infinite loop for requesting many times same
/// corrupted messages or if user logic cann't correctly handle some message and every time throws exception.
///
/// The parameter must be integer and not negative. Otherwise, the standard value for this parameter will be used.
/// Default value: 3
/// </summary>
[DefaultValue("3")]
public const string AllowedCountOfSimilarRr = "allowedCountOfSimilarRR";
/// <summary>
/// This parameter specifies cpu id for a thread of session that receives the data from socket.
/// </summary>
[DefaultValue(UndefinedAffinity)]
public const string RecvCpuAffinity = "recvCpuAffinity";
/// <summary>
/// This parameter specifies cpu id for a thread of session that sends the data in socket.
/// </summary>
[DefaultValue(UndefinedAffinity)]
public const string SendCpuAffinity = "sendCpuAffinity";
/// <summary>
/// This parameter specifies cpu id for the threads of session that send and receive the data from/in socket.
/// </summary>
[DefaultValue(UndefinedAffinity)]
public const string CpuAffinity = "cpuAffinity";
/// <summary>
/// This parameter specifies <seealso cref="System.Net.Sockets.Socket.SendBufferSize"/> property.
///
/// Default value is 0, it means the parameter is not specified.
/// </summary>
[DefaultValue("0")]
public const string TcpSendBufferSize = "tcpSendBufferSize";
/// <summary>
/// This parameter specifies <seealso cref="System.Net.Sockets.Socket.ReceiveBufferSize"/> property.
///
/// Default value is 0, it means the parameter is not specified.
/// </summary>
[DefaultValue("0")]
public const string TcpReceiveBufferSize = "tcpReceiveBufferSize";
/// <summary>
/// If option is disabled it should be possible to receive messages with none session specific Comp IDs
/// if option is enabled than all the messages with compIds not aligned to session parameters will be rejected
///
/// Default value is true, it means that consistency check is enabled
/// </summary>
[DefaultValue("true")]
public const string SenderTargetIdConsistencyCheck = "senderTargetIdConsistencyCheck";
/// <summary>
/// This parameter specifies a gap in sequences during connecting, which may be treated as missed sequence reset
/// event by the counterpart. It works if current session has reset sequences and expects message (Logon) with 34=1
/// but counterparty is still sending messages with much higher sequences (they didn't do reset on their side).
/// This option helps to control bidirectional agreed sequence reset events and prevents to request old messages.
/// This option is working only for acceptor session.
/// Default value is 0, it means the check is not going to be performed.
///
/// </summary>
[DefaultValue("0")]
public const string ResetThreshold = "resetThreshold";
/// <summary>
/// This parameter enables slow consumer detection in pumpers
/// </summary>
[DefaultValue("false")]
public const string SlowConsumerDetectionEnabled = "slowConsumerDetectionEnabled";
/// <summary>
/// This parameter used for decision making in slow consumer detection in pumpers.
/// It defined a maximum timeframe for sending a message. If session transport can't send a message during this
/// timeframe it will notify about a slow consumer.
/// </summary>
[DefaultValue("10")]
public const string SlowConsumerWriteDelayThreshold = "slowConsumerWriteDelayThreshold";
/// <summary>
/// Enables throttling checks per message type
/// If this option is enabled, engine counts how many times session receives messages with some message type during throttleCheckingPeriod.
/// If this counter will be greater than the value in throttleChecking.MsgType.threshold, the session will be
/// closed with reason THROTTLING
///
/// Default value: false
/// </summary>
[DefaultValue("false")]
public const string ThrottleCheckingEnabled = "throttleCheckingEnabled";
/// <summary>
/// Defines period common for all throttling per message type checks.
///
/// Default value: 1000 milliseconds
/// </summary>
[DefaultValue("1000")]
public const string ThrottleCheckingPeriod = "throttleCheckingPeriod";
/// <summary>
/// Defines name of keys file. It should be located in classpath, root directory of running application or home directory.
/// </summary>
public const string CmeSecureKeysFile = "cmeSecureKeysFile";
/// <summary>
/// Defines strategy that will be applied for logon messages right before sending.
/// <p/>
/// Valid values: subclasses of <c>FixAntenna.NetCore.FixEngine.Session.Impl.LogonCustomizationStrategy</c>/>
/// <p/>
/// Implemented strategies:
/// <seealso cref="CmeSecureLogonStrategy"/> - It tells engine to use CME secure logon scheme.
/// This strategy requires defined <seealso cref="CmeSecureKeysFile"/>.
/// </summary>
[DefaultValue(LogonCustomizationStrategyUndefined)]
public const string LogonCustomizationStrategy = "logonCustomizationStrategy";
/// <summary>
/// Print socket address to debug log for incoming and outgoing log.
/// If this option is enabled, Antenna will print messages to debug log in format: <br/>
/// [127.0.0.1]>>8=FIX.4.2 | 9=250...
/// </summary>
[DefaultValue("false")]
public const string WriteSocketAddressToLog = "writeSocketAddressToLog";
/// <summary>
/// Determines if sequence numbers should be accepted from the incoming Logon message. The option allows to reduce
/// miscommunication between sides and easier connect after scheduled sequence reset.
/// <p/>
/// The option doesn’t change behavior if the Logon message contains ResetSeqNumFlag(141) equals to “Y” (in this
/// case session sequence numbers will be reset).<p/>
/// The value ‘Schedule’ allows to adopt to the sequence numbers from the incoming Logon message if the reset time
/// is outdated (the session recovers after scheduled reset time). In this case session’s incoming sequence number
/// will be set to the value of MsgSeqNum(34) tag from the incoming Logon and outgoing sequence number become
/// equivalent to NextExpectedMsgSeqNum (789) tag value (if the tag is present) or will be reset to 1.
/// <p/>
/// If the parameter 'ResetSeqNumFromFirstLogon' is set to 'Schedule' on the acceptor's side, then:
/// If the tag ResetSeqNumFlag (141) in the received Logon message is set to 'Y', then:
/// The incoming sequence number must be set to 1
/// The outgoing sequence number must be set to 1
/// If the tag ResetSeqNumFlag (141) in the received Logon message is set to 'N' or missing, then
/// If the sequence numbers reset date and time is outdated, then:
/// The incoming sequence number must be set to the value of the tag MsgSeqNum (34)
/// If the tag NextExpectedMsgSeqNum (789) is specified in the received Logon message, then
/// The outgoing sequence number must be set to the value of the tag NextExpectedMsgSeqNum (789)
/// If the tag NextExpectedMsgSeqNum (789) is missing in the received Logon message, then
/// The outgoing sequence number must be set to the 1
/// If the sequence numbers reset date and time is actual, then:
/// The incoming and outgoing sequence numbers are handled according to the FIX protocol
/// Valid values: Never | Schedule.
/// </summary>
[DefaultValue("Never")]
public const string ResetSeqNumFromFirstLogon = "resetSeqNumFromFirstLogon";
/// <summary>
/// Masked tags. List all tags here engine should hide value with asterisks in logs.
/// </summary>
[DefaultValue("554, 925")]
public const string MaskedTags = "maskedTags";
/// <summary>
/// Turn on CME Enhanced Resend Request logic for filling gaps
/// </summary>
[DefaultValue("false")]
public const string EnhancedCmeResendLogic = "enhancedCmeResendLogic";
#region SSL/TLS related
/// <summary>
/// Requires establishing of secured transport for individual session,
/// or for all sessions, when used on top level configuration.
/// </summary>
[DefaultValue("false")]
public const string RequireSsl = "requireSSL";
/// <summary>
/// Selected SslProtocol as defined in <see cref="System.Security.Authentication.SslProtocols"/>
/// Default value is "None" as recommended by Microsoft - in this case best suitable protocol be used.
/// </summary>
[DefaultValue("None")]
public const string SslProtocol = "sslProtocol";
/// <summary>
/// Name of Certificate.
/// Could be file name, or distinguished name (CN=...) of certificate in case when certificate store is used.
/// </summary>
[DefaultValue("")]
public const string SslCertificate = "sslCertificate";
/// <summary>
/// Password for SSL certificate.
/// </summary>
[DefaultValue("")]
public const string SslCertificatePassword = "sslCertificatePassword";
/// <summary>
/// If true, remote certificate must be validated for successful connection.
/// If false, also disables sslCheckCertificateRevocation.
/// </summary>
[DefaultValue("true")]
public const string SslValidatePeerCertificate = "sslValidatePeerCertificate";
/// <summary>
/// If true and also sslValidatePeerCertificate=true, remote certificate will be checked for revocation.
/// </summary>
[DefaultValue("true")]
public const string SslCheckCertificateRevocation = "sslCheckCertificateRevocation";
/// <summary>
/// Name of CA certificate.
/// Could be file name, or distinguished name (CN=...) of certificate in case when certificate store is used.
/// </summary>
[DefaultValue("")]
public const string SslCaCertificate = "sslCaCertificate";
/// <summary>
/// Used on initiator only. Should match with CN=[serverName] in the acceptor certificate.
/// </summary>
[DefaultValue("")]
public const string SslServerName = "sslServerName";
/// <summary>
/// Acceptor: listening port(s) for SSL/TLS connections.
/// Initiator: ignored.
/// </summary>
[DefaultValue("")]
public const string SslPort = "sslPort";
#endregion
/// <summary>
/// Cron expression to set a scheduled session start.
/// </summary>
public const string TradePeriodBegin = "tradePeriodBegin";
/// <summary>
/// Cron expression to set the end of the scheduled session.
/// </summary>
public const string TradePeriodEnd = "tradePeriodEnd";
/// <summary>
/// Time zone id for tradePeriodBegin and TradePeriodEnd properties.
/// </summary>
[DefaultValue("UTC")]
public const string TradePeriodTimeZone = "tradePeriodTimeZone";
/// <summary>
/// Acceptor: Default listening port(s) for unsecured connections.
/// Initiator: Target port for connection.
/// </summary>
[DefaultValue("")]
public const string Port = "port";
/// <summary>
/// Minimal length of the SeqNum fields. Possible value is integer in range 1..10.
/// If the actual SeqNum length less than defined, leading zeros will be added to the SeqNum fields.
/// </summary>
[DefaultValue("1")]
public const string SeqNumLength = "seqNumLength";
public const string DefaultEngineProperties = "fixengine.properties";
public const string CustomFixVersions = "customFixVersions";
public const string CustomFixVersionPrefix = "customFixVersion.";
public const string CustomFixVersionVersionSuffix = ".fixVersion";
public const string CustomFixVersionFileNameSuffix = ".fileName";
private static readonly ILog Log = LogFactory.GetLog(typeof(Config));
/// <summary>
/// Identifies a maximum HBI in seconds.
/// </summary>
public static int MaxTimeoutValue = int.MaxValue / 1000;
private static readonly TemplatePropertiesWrapper DefaultProperties = new TemplatePropertiesWrapper();
private IDictionary<string, CustomFixVersionConfig> _customFixVersionConfigs = new ConcurrentDictionary<string, CustomFixVersionConfig>();
private readonly TemplatePropertiesWrapper _properties;
static Config()
{
try
{
LoadDefault();
}
catch (Exception e)
{
Log.Fatal("Can't load default properties. " + e.Message, e);
}
}
/// <summary>
/// Create a Configuration based on Map properties.
/// </summary>
/// <param name="map"> properties </param>
public Config(IDictionary<string, string> map)
{
_properties = new TemplatePropertiesWrapper(map);
PrepareCustomFixVersionConfigs();
}
/// <summary>
/// Create a Configuration.
/// Load properties from prop file
/// </summary>
/// <param name="propFileName"> the properties file. </param>
/// <exception cref="ArgumentException"> if file not exists. </exception>
public Config(string propFileName)
{
try
{
_properties = new TemplatePropertiesWrapper(Common.Properties.FromFile(propFileName));
PrepareCustomFixVersionConfigs();
}
catch (IOException e)
{
var message = "Unable to load: " + propFileName;
if (Log.IsDebugEnabled)
{
Log.Warn(message, e);
}
else
{
Log.Warn(message + ". Reason: " + e.Message);
}
throw new ArgumentException(message, e);
}
}
private Config()
{
Properties props = new Properties();
try
{
props = Common.Properties.FromFile(DefaultEngineProperties);
}
catch (Exception)
{
Log.Info("Unable to load user properties: " + DefaultEngineProperties +
". Default properties will be used instead");
}
_properties = new TemplatePropertiesWrapper(props);
PrepareCustomFixVersionConfigs();
}
private Config(Config config)
{
_properties = (TemplatePropertiesWrapper)config._properties.Clone();
_customFixVersionConfigs = new Dictionary<string, CustomFixVersionConfig>(config._customFixVersionConfigs);
}
private void PrepareCustomFixVersionConfigs()
{
var customFixVersions = _properties.GetProperty(CustomFixVersions);
if (string.IsNullOrEmpty(customFixVersions))
{
return;
}
string fixVersion;
string fileName;
foreach (var i in customFixVersions.Split(','))
{
var customFixVersion = i.Trim();
try
{
fixVersion = _properties.GetProperty(string.Concat(CustomFixVersionPrefix, customFixVersion, CustomFixVersionVersionSuffix));
fileName = _properties.GetProperty(string.Concat(CustomFixVersionPrefix, customFixVersion, CustomFixVersionFileNameSuffix));
if (!string.IsNullOrEmpty(fixVersion) && !string.IsNullOrEmpty(fileName))
{
_customFixVersionConfigs[customFixVersion] = new CustomFixVersionConfig(fixVersion, fileName);
}
}
catch (Exception ex)
{
Log.Error("Error occurred while reading custom FIX version: " + customFixVersion, ex);
}
}
}
public CustomFixVersionConfig GetCustomFixVersionConfig(string customFixVersion)
{
return _customFixVersionConfigs.GetValueOrDefault(customFixVersion);
}
public object Clone()
{
return new Config(this);
}
/// <summary>
/// Get global configuration. Method returned default configuration loaded on startup.
/// </summary>
/// <value> instance of global configuration. </value>
public static Config GlobalConfiguration => LazyGlobalConfiguration.Value;
private static readonly Lazy<Config> LazyGlobalConfiguration = new Lazy<Config>(() => new Config());
private static volatile string _configurationDirectory;
/// <summary>
/// Allows setting a directory that will be used to load configuration e.g. to load fixengine.properties.
/// The parameter does not affect the loading of dictionaries.<br/>
/// The order of searching for fixengine.properties will be <br/>
/// 1. Directory defined by this parameter <br/>
/// 2. Current directory <br/>
/// 3. Home directory <br/>
/// 4. Embedded resources inside libraries from method callstack.
/// Set it before reading any configuration.
/// </summary>
public static string ConfigurationDirectory
{
get => _configurationDirectory;
set => _configurationDirectory = value;
}
/// <summary>
/// Setter for properties.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="propertyValue"> the value for property </param>
public virtual void SetProperty(string propertyName, string propertyValue)
{
if (Log.IsTraceEnabled)
{
Log.Trace("Set property " + propertyName + "=" + propertyValue);
}
_properties.Put(propertyName, propertyValue);
PrepareCustomFixVersionConfigs();
}
/// <summary>
/// Setter for properties.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="propertyValue"> the value for property </param>
public virtual void SetProperty(string propertyName, int propertyValue)
{
if (Log.IsTraceEnabled)
{
Log.Trace("Set property " + propertyName + "=" + propertyValue);
}
_properties.Put(propertyName, propertyValue.ToString());
}
public virtual void AddAllProperties(IDictionary<string, string> newProps)
{
foreach (var key in newProps.Keys)
{
SetProperty(key, newProps[key]);
}
PrepareCustomFixVersionConfigs();
}
public virtual void SetAllProperties(IDictionary<string, string> newProps)
{
_properties.Clear();
_customFixVersionConfigs.Clear();
AddAllProperties(newProps);
}
/// <summary>
/// Getter for property.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <returns> value for property or null if property not exists </returns>
public virtual string GetProperty(string propertyName)
{
var value = _properties.GetProperty(propertyName);
if (!string.IsNullOrWhiteSpace(value))
{
return value;
}
ParamSources.Instance.Set(propertyName, ParamSource.Default);
return GetDefaultProperty(propertyName, true);
}
/// <summary>
/// Getter for property.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="validator"> validate value from user config. For invalid value will be return default value. </param>
/// <param name="nullable"> true - for case when property not exist can return null(nullable=true) or throws exception(nullable=false) </param>
/// <returns> value for property. If property not exists can return null or throws exception(depends on <c>nullable</c> value) </returns>
internal virtual string GetProperty(string propertyName, IValidator validator, bool nullable)
{
return GetProperty(propertyName, validator, nullable, false);
}
/// <summary>
/// Getter for property.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="validator"> validate value from user config. For invalid value will be return default value. </param>
/// <param name="nullable"> true - for case when property not exist can return null(nullable=true) or throws exception(nullable=false) </param>
/// <param name="warnInLog"> write warning to log if value from config is not fit to validator </param>
/// <param name="customMessage">customized warning message</param>
/// <returns> value for property. If property not exists can return null or throws exception(depends on <c>nullable</c> value) </returns>
internal virtual string GetProperty(string propertyName, IValidator validator, bool nullable, bool warnInLog, string customMessage = null)
{
var value = _properties.GetProperty(propertyName);
// boolean exceptionalCase = false;
if (!string.IsNullOrWhiteSpace(value))
{
if (validator.Valid(value))
{
return value;
}
// value from user config is not satisfy validator
// try use value from default config
if (warnInLog)
{
if (customMessage != null)
Log.Warn(customMessage);
else
Log.Warn("'" + propertyName + "=" + value + "' value does not satisfy the conditions: " +
validator.ToString() + ". Using default value: '" + GetDefaultProperty(propertyName, true) + "'");
}
}
ParamSources.Instance.Set(propertyName, ParamSource.Default);
value = GetDefaultProperty(propertyName, nullable);
if (!string.IsNullOrWhiteSpace(value) && validator.Valid(value))
{
return value;
}
if (nullable)
{
value = null;
}
else
{
throw new ArgumentException("'" + propertyName + "=" + value +
"' value does not satisfy the demands of the validator");
}
return value;
}
private string GetDefaultProperty(string propertyName, bool nullable)
{
var value = DefaultProperties.GetProperty(propertyName);
if (!nullable && string.IsNullOrEmpty(value))
{
throw new ArgumentException("Property '" + propertyName + "' was not found");
}
ParamSources.Instance.Set(propertyName, ParamSource.Default);
return value;
}
/// <summary>
/// Getter for property.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="defaultValue"> the default value for property </param>
/// <returns> value for property or defaultValue if property not exists </returns>
public virtual string GetProperty(string propertyName, string defaultValue)
{
var value = GetProperty(propertyName);
return value ?? defaultValue;
}
/// <summary>
/// Get property value as boolean.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="defaultValue"> the default value for property </param>
/// <param name="warnToLog"> write warning to log if value from user config is not in range </param>
/// <param name="customMessage">customized message for warning</param>
/// <returns> value for property or defaultValue if property not exists </returns>
public virtual bool GetPropertyAsBoolean(string propertyName, bool defaultValue, bool warnToLog = false, string customMessage = null)
{
var property = GetProperty(propertyName, new ValidatorBoolean(), true, warnToLog, customMessage);
if (property == null)
{
ParamSources.Instance.Set(propertyName, ParamSource.Default);
return defaultValue;
}
return ParseBool(property);
}
/// <summary>
/// Get property value as boolean.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <returns> value for property or false if property not exists </returns>
public virtual bool GetPropertyAsBoolean(string propertyName)
{
return GetPropertyAsBoolean(propertyName, false);
}
/// <summary>
/// Get property value as int.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="defaultValue"> the default value for property </param>
/// <returns> value for property or defaultValue if property not exists </returns>
public virtual int GetPropertyAsInt(string propertyName, int defaultValue)
{
try
{
return ParseInt(GetProperty(propertyName), defaultValue);
}
catch (Exception)
{
return defaultValue;
}
}
/// <summary>
/// Get property value as int. If value is not in range than return default value or throws exception(if default value is not in range too)
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="min"> the minimal value. Value >= min </param>
/// <param name="max"> the maximal value. max >= Value </param>
/// <returns> value for property or defaultValue if property not exists or in rage </returns>
public virtual int GetPropertyAsInt(string propertyName, int min, int max)
{
return ParseInt(GetProperty(propertyName, new ValidatorInteger(min, max), true));
}
/// <summary>
/// Get property value as int. If value is not in range than return default value or throws exception(if default value is not in range too)
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="min"> the minimal value. Value >= min </param>
/// <param name="max"> the maximal value. max >= Value </param>
/// <param name="warnToLog"> write warning to log if value from user config is not in range </param>
/// <param name="customMessage">customized message for warning</param>
/// <returns> value for property or defaultValue if property not exists or in rage </returns>
public virtual int GetPropertyAsInt(string propertyName, int min, int max, bool warnToLog, string customMessage = null)
{
return ParseInt(GetProperty(propertyName, new ValidatorInteger(min, max), false, warnToLog, customMessage));
}
/// <summary>
/// Get property value as int.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <returns> value for property or -1 if property not exists </returns>
public virtual int GetPropertyAsInt(string propertyName)
{
return GetPropertyAsInt(propertyName, -1);
}
/// <summary>
/// Get property value as long.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="defaultValue"> the default value for property </param>
/// <returns> value for property or defaultValue if property not exists </returns>
public virtual long GetPropertyAsLong(string propertyName, long defaultValue)
{
if (long.TryParse(GetProperty(propertyName), out var result))
return result;
return defaultValue;
}
/// <summary>
/// Get property value as long.
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <returns> value for property or -1 if property not exists </returns>
public virtual long GetPropertyAsLong(string propertyName)
{
return GetPropertyAsLong(propertyName, -1L);
}
/// <summary>
/// Get property value as bytes length. Example:
/// <br/>
/// 1=1
/// <br/>
/// 1b=1
/// <br/>
/// 1Kb=1024
/// <br/>
/// 1Mb=1048576
/// <br/>
/// 1Gb=1073741824
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <returns> value for property or -1 if property not exists </returns>
public virtual long GetPropertyAsBytesLength(string propertyName)
{
return GetPropertyAsBytesLength(propertyName, -1);
}
/// <summary>
/// Get property value as bytes length. Example:
/// <br/>
/// 1=1
/// <br/>
/// 1b=1
/// <br/>
/// 1Kb=1024
/// <br/>
/// 1Mb=1048576
/// <br/>
/// 1Gb=1073741824
/// </summary>
/// <param name="propertyName"> the name of property </param>
/// <param name="defaultValue"> the default value for property </param>
/// <returns> value for property or defaultValue if property not exists </returns>
public virtual long GetPropertyAsBytesLength(string propertyName, int defaultValue)
{
try
{
return ParseBytesLength(GetProperty(propertyName), defaultValue);
}
catch (Exception)
{
return defaultValue;
}
}
internal static long ParseBytesLength(string str, int defaultValue)
{
if (string.IsNullOrWhiteSpace(str))
{
return defaultValue;
}
var cleanStr = str.Trim();
var limit = cleanStr.Length;
var tailNotNumber = 0;
while (tailNotNumber < limit)
{
var ch = cleanStr[limit - tailNotNumber - 1];
if (ch < '0' || ch > '9')
{
tailNotNumber++;
}
else
{
break;
}
}
var numValue = ParseInt(cleanStr.Substring(0, cleanStr.Length - tailNotNumber), defaultValue);
if (tailNotNumber == 0 || numValue == defaultValue)
{
// some problem with parse or value not have suffix
return numValue;
}
var sufix = cleanStr.Substring(cleanStr.Length - tailNotNumber).Trim();
const long kiloConst = 1024;
if (sufix.Equals("gb", StringComparison.OrdinalIgnoreCase))
{
return numValue * kiloConst * kiloConst * kiloConst;
}
if (sufix.Equals("mb", StringComparison.OrdinalIgnoreCase))
{
return numValue * kiloConst * kiloConst;
}
if (sufix.Equals("kb", StringComparison.OrdinalIgnoreCase))
{
return numValue * kiloConst;
}
if (sufix.Equals("b", StringComparison.OrdinalIgnoreCase))
{
return numValue;
}
// unsupported sufix
return defaultValue;
}
/// <summary>
/// Parses the <c>integer</c> value from <c>str</c>.
/// </summary>
/// <param name="str"> the string representation of an integer. </param>
internal static int ParseInt(string str, int defaultValue)
{
if (string.IsNullOrWhiteSpace(str))
{
return defaultValue;
}
var start = 0;
for (var i = 0; i < str.Length; i++)
{
if (str[i] != ' ')
{
start = i;
break;
}
}
var length = str.Length - start;
for (var i = str.Length; i > 0; i--)
{
if (str[i - 1] != ' ')
{
length = i - start;
break;
}
}
return ParseInt(str, start, length, defaultValue);
}
/// <summary>
/// Parses the <c>integer</c> value from <c>str</c>.
/// </summary>
/// <param name="str"> the string representation of an integer. </param>
internal static int ParseInt(string str)
{
if (string.IsNullOrWhiteSpace(str))
{
throw new ArgumentNullException(nameof(str));
}
var start = 0;
for (var i = 0; i < str.Length; i++)
{
if (str[i] != ' ')
{
start = i;
break;
}
}
var length = str.Length - start;
for (var i = str.Length; i > 0; i--)
{
if (str[i - 1] != ' ')
{
length = i - start;
break;
}
}
return ParseInt(str, start, length);
}
/// <summary>
/// Parses the <c>integer</c> value from <c>str</c>.
/// <p/>
/// The <c>offset</c> argument is the
/// index of the first char of the substring and the <c>count</c>
/// argument specifies the length of the substring.
/// </summary>
/// <param name="str"> a string representation of an integer. </param>
/// <param name="offset"> the initial offset </param>
/// <param name="count"> the length </param>
/// <exception cref="FormatException"> </exception>
internal static int ParseInt(string str, int offset, int count)
{
var limit = offset + count;
var isNegative = limit > offset && str[offset] == '-';
if (isNegative)
{
offset++;
}
if (limit == offset)
{
throw new FormatException("Can't parse int from value '" + str + "'");
}
var value = 0;
while (offset < limit)
{
var ch = str[offset++];
if (ch < '0' || ch > '9' || value > int.MaxValue / 10)
{
throw new FormatException("Can't parse int from value '" + str + "'");
}
value = value * 10 + (ch - '0');
}
return isNegative ? -value : value;
}
/// <summary>
/// Parses the <c>integer</c> value from <c>str</c>.
/// <p/>
/// The <c>offset</c> argument is the
/// index of the first char of the substring and the <c>count</c>
/// argument specifies the length of the substring.
/// </summary>
/// <param name="str"> a string representation of an integer. </param>
/// <param name="offset"> the initial offset </param>
/// <param name="count"> the length </param>
internal static int ParseInt(string str, int offset, int count, int defaultValue)
{
var limit = offset + count;
var isNegative = limit > offset && str[offset] == '-';
if (isNegative)
{
offset++;
}
if (limit == offset)
{
return defaultValue;
}
int value = 0;
while (offset < limit)
{
var ch = str[offset++];
if (ch < '0' || ch > '9' || value > int.MaxValue / 10)
{
return defaultValue;
}
value = value * 10 + (ch - '0');
}
return isNegative ? -value : value;
}
/// <summary>
/// Parses the <c>boolean</c> value from <c>str</c>.
/// </summary>
/// <param name="str"> the string representation of a boolean. </param>
internal static bool ParseBool(string str)
{
if (string.IsNullOrWhiteSpace(str))
{
throw new ArgumentNullException(nameof(str));
}
str = str.Trim();
var sc = StringComparison.OrdinalIgnoreCase;
if ("Yes".Equals(str, sc) || "True".Equals(str, sc))
{
return true;
}
if ("No".Equals(str, sc) || "False".Equals(str, sc))
{
return false;
}
throw new FormatException("Can't parse bool from value '" + str + "'");
}
/// <summary>
/// Get all properties.
/// </summary>
/// <value> returned value is cloned. </value>
public virtual Dictionary<string, string> Properties
{
get
{
var dict = new Dictionary<string, string>();
foreach (var prop in _properties.GetAllProperties())
{
dict.Add(prop.Key, prop.Value);
}
return dict;
}
}
public override bool Equals(object o)
{
if (this == o)
{
return true;
}
if (o == null || GetType() != o.GetType())
{
return false;
}
var that = (Config)o;
if (!_properties?.Equals(that._properties) ?? that._properties != null)
{
return false;
}
return true;
}
public override int GetHashCode()
{
return _properties != null ? _properties.GetHashCode() : 0;
}
public virtual bool Exists(string propertyName)
{
return _properties.Exists(propertyName);
}
private static void LoadDefault()
{
var cl = typeof(Config);
var fields = cl.GetFields();
foreach (var fd in fields)
{
if (Attribute.IsDefined(fd, typeof(DefaultValue)) && fd.FieldType == typeof(string))
{
var attr = (DefaultValue)Attribute.GetCustomAttribute(fd, typeof(DefaultValue));
DefaultProperties.Put((string)fd.GetValue(null), attr.Value);
}
}
}
[AttributeUsage(AttributeTargets.Field)]
public class DefaultValue : Attribute
{
internal string Value;
public DefaultValue(string value)
{
Value = value;
}
}
internal interface IValidator
{
bool Valid(string value);
}
/// <summary>
/// User value validator for range max >= UserValue >= min
/// </summary>
internal class ValidatorInteger : IValidator
{
internal int Max;
internal int Min;
public ValidatorInteger(int min, int max)
{
Min = min;
Max = max;
}
public virtual bool Valid(string value)
{
int valueInt;
try
{
valueInt = ParseInt(value);
}
catch (Exception)
{
return false;
}
return Max >= valueInt && valueInt >= Min;
}
public override string ToString()
{
return "min=" + Min + ", max=" + Max;
}
}
/// <summary>
/// User values list validator for range max >= UserValue >= min
/// </summary>
internal class ValidatorIntegerList : IValidator
{
internal ValidatorInteger Validator;
public ValidatorIntegerList(int min, int max)
{
Validator = new ValidatorInteger(min, max);
}
public virtual bool Valid(string value)
{
return value.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.All(v => Validator.Valid(v));
}
public override string ToString()
{
return "list of min=" + Validator.Min + ", max=" + Validator.Max;
}
}
/// <summary>
/// User value validator for bool
/// </summary>
internal class ValidatorBoolean : IValidator
{
public ValidatorBoolean()
{
}
public virtual bool Valid(string value)
{
try
{
var valueBool = ParseBool(value);
}
catch (Exception)
{
return false;
}
return true;
}
public override string ToString()
{
return "True|False";
}
}
}
}