/// <summary> /// Creates a new <see cref="DataSubscriber"/>. /// </summary> public DataSubscriber() { m_registerStatisticsOperation = new LongSynchronizedOperation(HandleDeviceStatisticsRegistration) { IsBackground = true }; m_requests = new List<ServerCommand>(); m_synchronizeMetadataOperation = new LongSynchronizedOperation(SynchronizeMetadata) { IsBackground = true }; m_encoding = Encoding.Unicode; m_operationalModes = DefaultOperationalModes; m_metadataSynchronizationTimeout = DefaultMetadataSynchronizationTimeout; m_allowedParsingExceptions = DefaultAllowedParsingExceptions; m_parsingExceptionWindow = DefaultParsingExceptionWindow; string loggingPath = FilePath.GetDirectoryName(FilePath.GetAbsolutePath(DefaultLoggingPath)); if (Directory.Exists(loggingPath)) m_loggingPath = loggingPath; // Default to not using transactions for meta-data on SQL server (helps avoid deadlocks) try { using (AdoDataConnection database = new AdoDataConnection("systemSettings")) { m_useTransactionForMetadata = database.DatabaseType != DatabaseType.SQLServer; } } catch { m_useTransactionForMetadata = DefaultUseTransactionForMetadata; } DataLossInterval = 10.0D; m_bufferBlockCache = new List<BufferBlockMeasurement>(); m_useLocalClockAsRealTime = true; }
/// <summary> /// Creates a new <see cref="DataSubscriber"/>. /// </summary> public DataSubscriber() { m_requests = new List<ServerCommand>(); m_encoding = Encoding.Unicode; m_operationalModes = DefaultOperationalModes; DataLossInterval = 10.0D; }
/// <summary> /// Initializes <see cref="DataSubscriber"/>. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> settings = Settings; string setting; double interval; // Setup connection to data publishing server with or without authentication required if (settings.TryGetValue("requireAuthentication", out setting)) m_requireAuthentication = setting.ParseBoolean(); else m_requireAuthentication = false; if (m_requireAuthentication) { if (!settings.TryGetValue("sharedSecret", out m_sharedSecret) && !string.IsNullOrWhiteSpace(m_sharedSecret)) throw new ArgumentException("The \"sharedSecret\" setting must defined when authentication is required."); if (!settings.TryGetValue("authenticationID", out m_authenticationID) && !string.IsNullOrWhiteSpace(m_authenticationID)) throw new ArgumentException("The \"authenticationID\" setting must defined when authentication is required."); } // Check if synchronize metadata is disabled. if (settings.TryGetValue("synchronizeMetadata", out setting)) m_synchronizeMetadata = setting.ParseBoolean(); else m_synchronizeMetadata = true; // by default, we will always perform this. // Check if measurements for this connection should be marked as "internal" - i.e., owned and allowed for proxy if (settings.TryGetValue("internal", out setting)) m_internal = setting.ParseBoolean(); // See if user has opted for different operational modes if (!(settings.TryGetValue("operationalModes", out setting) && Enum.TryParse(setting, true, out m_operationalModes))) m_operationalModes = DefaultOperationalModes; // Check if we should be using the alternate binary format for communications with the publisher if (settings.TryGetValue("operationalModes", out setting)) m_operationalModes = (OperationalModes)uint.Parse(setting); // Check if user wants to request that publisher use millisecond resolution to conserve bandwidth if (settings.TryGetValue("useMillisecondResolution", out setting)) m_useMillisecondResolution = setting.ParseBoolean(); // Define auto connect setting if (settings.TryGetValue("autoConnect", out setting)) m_autoConnect = setting.ParseBoolean(); // Define data loss interval if (settings.TryGetValue("dataLossInterval", out setting) && double.TryParse(setting, out interval)) DataLossInterval = interval; if (m_autoConnect) { // Connect to local events when automatically engaging connection cycle ConnectionAuthenticated += DataSubscriber_ConnectionAuthenticated; MetaDataReceived += DataSubscriber_MetaDataReceived; // If active measurements are defined, attempt to defined desired subscription points from there if (DataSource != null && (object)DataSource.Tables != null && DataSource.Tables.Contains("ActiveMeasurements")) { try { // Filter to points associated with this subscriber that have been requested for subscription, are enabled and not owned locally DataRow[] filteredRows = DataSource.Tables["ActiveMeasurements"].Select("Subscribed <> 0"); List<IMeasurement> subscribedMeasurements = new List<IMeasurement>(); MeasurementKey key; Guid signalID; foreach (DataRow row in filteredRows) { // Create a new measurement for the provided field level information Measurement measurement = new Measurement(); // Parse primary measurement identifier signalID = row["SignalID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType<Guid>(); // Set measurement key if defined if (MeasurementKey.TryParse(row["ID"].ToString(), signalID, out key)) measurement.Key = key; // Assign other attributes measurement.ID = signalID; measurement.TagName = row["PointTag"].ToNonNullString(); measurement.Multiplier = double.Parse(row["Multiplier"].ToString()); measurement.Adder = double.Parse(row["Adder"].ToString()); subscribedMeasurements.Add(measurement); } if (subscribedMeasurements.Count > 0) { // Combine subscribed output measurement with any existing output measurement and return unique set if (OutputMeasurements == null) OutputMeasurements = subscribedMeasurements.ToArray(); else OutputMeasurements = subscribedMeasurements.Concat(OutputMeasurements).Distinct().ToArray(); } } catch (Exception ex) { // Errors here may not be catastrophic, this simply limits the auto-assignment of input measurement keys desired for subscription OnProcessException(new InvalidOperationException(string.Format("Failed to define subscribed measurements: {0}", ex.Message), ex)); } } } // Create a new TCP client TcpClient commandChannel = new TcpClient(); // Initialize default settings commandChannel.ConnectionString = "server=localhost:6165"; commandChannel.PayloadAware = true; commandChannel.PersistSettings = false; commandChannel.MaxConnectionAttempts = 1; // Assign command channel client reference and attach to needed events this.CommandChannel = commandChannel; // Get proper connection string - either from specified command channel // or from base connection string if (settings.TryGetValue("commandChannel", out setting)) commandChannel.ConnectionString = setting; else commandChannel.ConnectionString = ConnectionString; // Register subscriber with the statistics engine StatisticsEngine.Register(this, "Subscriber", "SUB"); Initialized = true; }
/// <summary> /// Initializes <see cref="DataSubscriber"/>. /// </summary> public override void Initialize() { base.Initialize(); Dictionary<string, string> settings = Settings; string setting; OperationalModes operationalModes; int metadataSynchronizationTimeout; double interval; int bufferSize; // Setup connection to data publishing server with or without authentication required if (settings.TryGetValue("requireAuthentication", out setting)) RequireAuthentication = setting.ParseBoolean(); // Set the security mode if explicitly defined if (settings.TryGetValue("securityMode", out setting)) m_securityMode = (SecurityMode)Enum.Parse(typeof(SecurityMode), setting); if (settings.TryGetValue("useZeroMQChannel", out setting)) m_useZeroMQChannel = setting.ParseBoolean(); // TODO: Remove this exception when CURVE is enabled in GSF ZeroMQ library if (m_useZeroMQChannel && m_securityMode == SecurityMode.TLS) throw new ArgumentException("CURVE security settings are not yet available for GSF ZeroMQ client channel."); // Settings specific to Gateway security if (m_securityMode == SecurityMode.Gateway) { if (!settings.TryGetValue("sharedSecret", out m_sharedSecret) || string.IsNullOrWhiteSpace(m_sharedSecret)) throw new ArgumentException("The \"sharedSecret\" setting must be defined when using Gateway security mode."); if (!settings.TryGetValue("authenticationID", out m_authenticationID) || string.IsNullOrWhiteSpace(m_authenticationID)) throw new ArgumentException("The \"authenticationID\" setting must be defined when using Gateway security mode."); } // Settings specific to Transport Layer Security if (m_securityMode == SecurityMode.TLS) { if (!settings.TryGetValue("localCertificate", out m_localCertificate) || !File.Exists(m_localCertificate)) m_localCertificate = GetLocalCertificate(); if (!settings.TryGetValue("remoteCertificate", out m_remoteCertificate) || !RemoteCertificateExists()) throw new ArgumentException("The \"remoteCertificate\" setting must be defined and certificate file must exist when using TLS security mode."); if (!settings.TryGetValue("validPolicyErrors", out setting) || !Enum.TryParse(setting, out m_validPolicyErrors)) m_validPolicyErrors = SslPolicyErrors.None; if (!settings.TryGetValue("validChainFlags", out setting) || !Enum.TryParse(setting, out m_validChainFlags)) m_validChainFlags = X509ChainStatusFlags.NoError; if (settings.TryGetValue("checkCertificateRevocation", out setting) && !string.IsNullOrWhiteSpace(setting)) m_checkCertificateRevocation = setting.ParseBoolean(); else m_checkCertificateRevocation = true; } // Check if measurements for this connection should be marked as "internal" - i.e., owned and allowed for proxy if (settings.TryGetValue("internal", out setting)) m_internal = setting.ParseBoolean(); // See if user has opted for different operational modes if (settings.TryGetValue("operationalModes", out setting) && Enum.TryParse(setting, true, out operationalModes)) m_operationalModes = operationalModes; // Check if user has explicitly defined the ReceiveInternalMetadata flag if (settings.TryGetValue("receiveInternalMetadata", out setting)) ReceiveInternalMetadata = setting.ParseBoolean(); // Check if user has explicitly defined the ReceiveExternalMetadata flag if (settings.TryGetValue("receiveExternalMetadata", out setting)) ReceiveExternalMetadata = setting.ParseBoolean(); // Check if user has defined a meta-data synchronization timeout if (settings.TryGetValue("metadataSynchronizationTimeout", out setting) && int.TryParse(setting, out metadataSynchronizationTimeout)) m_metadataSynchronizationTimeout = metadataSynchronizationTimeout; // Check if user has defined a flag for using a transaction during meta-data synchronization if (settings.TryGetValue("useTransactionForMetadata", out setting)) m_useTransactionForMetadata = setting.ParseBoolean(); // Check if user wants to request that publisher use millisecond resolution to conserve bandwidth if (settings.TryGetValue("useMillisecondResolution", out setting)) m_useMillisecondResolution = setting.ParseBoolean(); // Check if user wants to request that publisher remove NaN from the data stream to conserve bandwidth if (settings.TryGetValue("requestNaNValueFilter", out setting)) m_requestNaNValueFilter = setting.ParseBoolean(); // Check if user has defined any meta-data filter expressions if (settings.TryGetValue("metadataFilters", out setting)) m_metadataFilters = setting; // Define auto connect setting if (settings.TryGetValue("autoConnect", out setting)) { m_autoConnect = setting.ParseBoolean(); if (m_autoConnect) m_autoSynchronizeMetadata = true; } // Check if synchronize meta-data is explicitly enabled or disabled if (settings.TryGetValue("synchronizeMetadata", out setting)) m_autoSynchronizeMetadata = setting.ParseBoolean(); // Define data loss interval if (settings.TryGetValue("dataLossInterval", out setting) && double.TryParse(setting, out interval)) DataLossInterval = interval; // Define buffer size if (!settings.TryGetValue("bufferSize", out setting) || !int.TryParse(setting, out bufferSize)) bufferSize = ClientBase.DefaultReceiveBufferSize; if (settings.TryGetValue("useLocalClockAsRealTime", out setting)) m_useLocalClockAsRealTime = setting.ParseBoolean(); if (m_autoConnect) { // Connect to local events when automatically engaging connection cycle ConnectionAuthenticated += DataSubscriber_ConnectionAuthenticated; MetaDataReceived += DataSubscriber_MetaDataReceived; // Update output measurements to include "subscribed" points UpdateOutputMeasurements(true); } else if (m_autoSynchronizeMetadata) { // Output measurements do not include "subscribed" points, // but should still be filtered if applicable TryFilterOutputMeasurements(); } if (m_securityMode != SecurityMode.TLS) { if (m_useZeroMQChannel) { // Create a new ZeroMQ Dealer ZeroMQClient commandChannel = new ZeroMQClient(); // Initialize default settings commandChannel.PersistSettings = false; commandChannel.MaxConnectionAttempts = 1; commandChannel.ReceiveBufferSize = bufferSize; commandChannel.SendBufferSize = bufferSize; // Assign command channel client reference and attach to needed events CommandChannel = commandChannel; } else { // Create a new TCP client TcpClient commandChannel = new TcpClient(); // Initialize default settings commandChannel.PayloadAware = true; commandChannel.PersistSettings = false; commandChannel.MaxConnectionAttempts = 1; commandChannel.ReceiveBufferSize = bufferSize; commandChannel.SendBufferSize = bufferSize; // Assign command channel client reference and attach to needed events CommandChannel = commandChannel; } } else { if (m_useZeroMQChannel) { // Create a new ZeroMQ Dealer with CURVE security enabled ZeroMQClient commandChannel = new ZeroMQClient(); // Initialize default settings commandChannel.PersistSettings = false; commandChannel.MaxConnectionAttempts = 1; commandChannel.ReceiveBufferSize = bufferSize; commandChannel.SendBufferSize = bufferSize; // TODO: Parse certificate and pass keys to ZeroMQClient for CURVE security // Assign command channel client reference and attach to needed events CommandChannel = commandChannel; } else { // Create a new TLS client and certificate checker TlsClient commandChannel = new TlsClient(); SimpleCertificateChecker certificateChecker = new SimpleCertificateChecker(); // Set up certificate checker certificateChecker.TrustedCertificates.Add(new X509Certificate2(FilePath.GetAbsolutePath(m_remoteCertificate))); certificateChecker.ValidPolicyErrors = m_validPolicyErrors; certificateChecker.ValidChainFlags = m_validChainFlags; // Initialize default settings commandChannel.PayloadAware = true; commandChannel.PersistSettings = false; commandChannel.MaxConnectionAttempts = 1; commandChannel.CertificateFile = FilePath.GetAbsolutePath(m_localCertificate); commandChannel.CheckCertificateRevocation = m_checkCertificateRevocation; commandChannel.CertificateChecker = certificateChecker; commandChannel.ReceiveBufferSize = bufferSize; commandChannel.SendBufferSize = bufferSize; // Assign command channel client reference and attach to needed events CommandChannel = commandChannel; } } // Get proper connection string - either from specified command channel or from base connection string if (settings.TryGetValue("commandChannel", out setting)) m_commandChannel.ConnectionString = setting; else m_commandChannel.ConnectionString = ConnectionString; // Get logging path, if any has been defined if (settings.TryGetValue("loggingPath", out setting)) { setting = FilePath.GetDirectoryName(FilePath.GetAbsolutePath(setting)); if (Directory.Exists(setting)) m_loggingPath = setting; else OnStatusMessage("WARNING: Logging path \"{0}\" not found, defaulting to \"{1}\"...", setting, FilePath.GetAbsolutePath("")); } // Initialize data gap recovery processing, if requested if (settings.TryGetValue("dataGapRecovery", out setting)) { // Example connection string for data gap recovery: // dataGapRecovery={enabled=true; recoveryStartDelay=10.0; minimumRecoverySpan=0.0; maximumRecoverySpan=3600.0} Dictionary<string, string> dataGapSettings = setting.ParseKeyValuePairs(); if (dataGapSettings.TryGetValue("enabled", out setting) && setting.ParseBoolean()) { // Remove dataGapRecovery connection setting from command channel connection string, if defined there. // This will prevent any recursive data gap recovery operations from being established: Dictionary<string, string> connectionSettings = m_commandChannel.ConnectionString.ParseKeyValuePairs(); connectionSettings.Remove("dataGapRecovery"); connectionSettings.Remove("autoConnect"); connectionSettings.Remove("synchronizeMetadata"); connectionSettings.Remove("outputMeasurements"); // Note that the data gap recoverer will connect on the same command channel port as // the real-time subscriber (TCP only) m_dataGapRecoveryEnabled = true; m_dataGapRecoverer = new DataGapRecoverer(); m_dataGapRecoverer.SourceConnectionName = Name; m_dataGapRecoverer.DataSource = DataSource; m_dataGapRecoverer.ConnectionString = string.Join("; ", string.Format("autoConnect=false; synchronizeMetadata=false{0}", string.IsNullOrWhiteSpace(m_loggingPath) ? "" : "; loggingPath=" + m_loggingPath), dataGapSettings.JoinKeyValuePairs(), connectionSettings.JoinKeyValuePairs()); m_dataGapRecoverer.FilterExpression = this.OutputMeasurementKeys().Select(key => key.SignalID.ToString()).ToDelimitedString(';'); m_dataGapRecoverer.RecoveredMeasurements += m_dataGapRecoverer_RecoveredMeasurements; m_dataGapRecoverer.StatusMessage += m_dataGapRecoverer_StatusMessage; m_dataGapRecoverer.ProcessException += m_dataGapRecoverer_ProcessException; m_dataGapRecoverer.Initialize(); } else { m_dataGapRecoveryEnabled = false; } } else { m_dataGapRecoveryEnabled = false; } // Register subscriber with the statistics engine StatisticsEngine.Register(this, "Subscriber", "SUB"); StatisticsEngine.Calculated += (sender, args) => ResetMeasurementsPerSecondCounters(); Initialized = true; }