//******************************************************************************************************
// DataSubscriber.cs - Gbtc
//
// Copyright © 2010, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the Eclipse Public License -v 1.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.opensource.org/licenses/eclipse-1.0.php
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 08/20/2010 - J. Ritchie Carroll
// Generated original version of source code.
// 02/07/2012 - Mehulbhai Thakkar
// Modified SynchronizeMetadata to filter devices by original source and modified insert query
// to populate OriginalSource value. Added to flag to optionally avoid metadata synchronization.
//
//******************************************************************************************************
using GSF.Collections;
using GSF.Communication;
//using GSF.Data;
using GSF.IO;
using GSF.Reflection;
//using GSF.Security.Cryptography;
using GSF.TimeSeries.Adapters;
//using GSF.TimeSeries.Statistics;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml;
namespace GSF.TimeSeries.Transport
{
///
/// Represents a data subscribing client that will connect to a data publisher for a data subscription.
///
[Description("DataSubscriber: client that subscribes to a publishing server for a streaming data.")]
[EditorBrowsable(EditorBrowsableState.Advanced)] // Normally defined as an input device protocol
public class DataSubscriber : InputAdapterBase
{
#region [ Members ]
// Nested Types
// Local measurement concentrator
private class LocalConcentrator : ConcentratorBase
{
#region [ Members ]
// Fields
private DataSubscriber m_parent;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Creates a new local concentrator.
///
public LocalConcentrator(DataSubscriber parent)
{
m_parent = parent;
}
#endregion
#region [ Methods ]
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
m_parent = null;
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
}
///
/// Publish of time-aligned collection of values that arrived within the
/// concentrator's defined .
///
/// of measurements with the same timestamp that arrived within that are ready for processing.
/// Index of within a second ranging from zero to - 1.
protected override void PublishFrame(IFrame frame, int index)
{
// Publish locally sorted measurements
if ((object)m_parent != null)
m_parent.OnNewMeasurements(frame.Measurements.Values);
}
#endregion
}
// Constants
private const int EvenKey = 0; // Even key/IV index
private const int OddKey = 1; // Odd key/IV index
private const int KeyIndex = 0; // Index of cipher key component in keyIV array
private const int IVIndex = 1; // Index of initialization vector component in keyIV array
private const long MissingCacheWarningInterval = 20000000;
// Events
///
/// Occurs when client connection to the data publication server is established.
///
public event EventHandler ConnectionEstablished;
///
/// Occurs when client connection to the data publication server is terminated.
///
public event EventHandler ConnectionTerminated;
///
/// Occurs when client connection to the data publication server has successfully authenticated.
///
public event EventHandler ConnectionAuthenticated;
///
/// Occurs when client receives requested meta-data transmitted by data publication server.
///
public event EventHandler> MetaDataReceived;
///
/// Occurs when first measurement is transmitted by data publication server.
///
public event EventHandler> DataStartTime;
///
/// Indicates that processing for an input adapter (via temporal session) has completed.
///
///
/// This event is expected to only be raised when an input adapter has been designed to process
/// a finite amount of data, e.g., reading a historical range of data during temporal procesing.
///
public new event EventHandler> ProcessingComplete;
// Fields
private TcpClient m_commandChannel;
private UdpClient m_dataChannel;
private LocalConcentrator m_localConcentrator;
private System.Timers.Timer m_dataStreamMonitor;
private long m_commandChannelConnectionAttempts;
private long m_dataChannelConnectionAttempts;
private volatile SignalIndexCache m_remoteSignalIndexCache;
private volatile SignalIndexCache m_signalIndexCache;
private volatile long[] m_baseTimeOffsets;
private volatile int m_timeIndex;
private volatile byte[][][] m_keyIVs;
private volatile int m_cipherIndex;
private volatile bool m_authenticated;
private volatile bool m_subscribed;
private volatile int m_lastBytesReceived;
private long m_monitoredBytesReceived;
private long m_totalBytesReceived;
private long m_lastMissingCacheWarning;
private Guid m_nodeID;
private int m_gatewayProtocolID;
private List m_requests;
private bool m_synchronizedSubscription;
private bool m_requireAuthentication;
private bool m_useMillisecondResolution;
private bool m_autoConnect;
private string m_sharedSecret;
private string m_authenticationID;
private bool m_includeTime;
private bool m_synchronizeMetadata;
private bool m_internal;
private OperationalModes m_operationalModes;
private Encoding m_encoding;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Creates a new .
///
public DataSubscriber()
{
m_requests = new List();
m_encoding = Encoding.Unicode;
DataLossInterval = 10.0D;
}
#endregion
#region [ Properties ]
///
/// Gets or sets flag that determines if requires subscribers to authenticate before making data requests.
///
public bool RequireAuthentication
{
get
{
return m_requireAuthentication;
}
set
{
m_requireAuthentication = value;
}
}
///
/// Gets or sets flag that determines if should attempt to autoconnection to using defined connection settings.
///
public bool AutoConnect
{
get
{
return m_autoConnect;
}
set
{
m_autoConnect = value;
}
}
///
/// Gets or sets flag that informs publisher if base time-offsets can use millisecond resolution to conserve bandwidth.
///
[Obsolete("SubscriptionInfo object defines this parameter.", false)]
public bool UseMillisecondResolution
{
get
{
return m_useMillisecondResolution;
}
set
{
m_useMillisecondResolution = value;
}
}
///
/// Gets flag that determines if this has successfully authenticated with the .
///
public bool Authenticated
{
get
{
return m_authenticated;
}
}
///
/// Gets total data packet bytes received during this session.
///
public long TotalBytesReceived
{
get
{
return m_totalBytesReceived;
}
}
///
/// Gets or sets data loss monitoring interval, in seconds. Set to zero to disable monitoring.
///
public double DataLossInterval
{
get
{
if ((object)m_dataStreamMonitor != null)
return m_dataStreamMonitor.Interval / 1000.0D;
return 0.0D;
}
set
{
if (value > 0.0D)
{
if ((object)m_dataStreamMonitor == null)
{
// Create data stream monitoring timer
m_dataStreamMonitor = new System.Timers.Timer();
m_dataStreamMonitor.Elapsed += m_dataStreamMonitor_Elapsed;
m_dataStreamMonitor.AutoReset = true;
m_dataStreamMonitor.Enabled = false;
}
// Set user specified interval
m_dataStreamMonitor.Interval = value * 1000.0D;
}
else
{
// Disable data monitor
if ((object)m_dataStreamMonitor != null)
{
m_dataStreamMonitor.Elapsed -= m_dataStreamMonitor_Elapsed;
m_dataStreamMonitor.Dispose();
}
m_dataStreamMonitor = null;
}
}
}
///
/// Gets or sets a set of flags that define ways in
/// which the subscriber and publisher communicate.
///
public OperationalModes OperationalModes
{
get
{
return m_operationalModes;
}
set
{
OperationalEncoding operationalEncoding;
m_operationalModes = value;
operationalEncoding = (OperationalEncoding)(value & OperationalModes.EncodingMask);
m_encoding = GetCharacterEncoding(operationalEncoding);
}
}
///
/// Gets or sets the used by the subscriber and publisher.
///
public OperationalEncoding OperationalEncoding
{
get
{
return (OperationalEncoding)(m_operationalModes & OperationalModes.EncodingMask);
}
set
{
m_operationalModes &= ~OperationalModes.EncodingMask;
m_operationalModes |= (OperationalModes)value;
m_encoding = GetCharacterEncoding(value);
}
}
///
/// Gets the flag indicating if this adapter supports temporal processing.
///
///
/// Although the data subscriber provisions support for temporal processing by receiving historical data from a remote source,
/// the adapter opens sockets and does not need to be engaged within an actual temporal , therefore
/// this method returns false to make sure the adapter doesn't get instantiated within a temporal session.
///
public override bool SupportsTemporalProcessing
{
get
{
return false;
}
}
///
/// Gets or sets the desired processing interval, in milliseconds, for the adapter.
///
///
/// With the exception of the values of -1 and 0, this value specifies the desired processing interval for data, i.e.,
/// basically a delay, or timer interval, overwhich to process data. A value of -1 means to use the default processing
/// interval while a value of 0 means to process data as fast as possible.
///
public override int ProcessingInterval
{
get
{
return base.ProcessingInterval;
}
set
{
base.ProcessingInterval = value;
// Request server update the processing interval
SendServerCommand(ServerCommand.UpdateProcessingInterval, EndianOrder.BigEndian.GetBytes(value));
}
}
///
/// Gets the status of this .
///
///
/// Derived classes should provide current status information about the adapter for display purposes.
///
public override string Status
{
get
{
StringBuilder status = new StringBuilder();
status.AppendFormat(" Subscription mode: {0}", m_synchronizedSubscription ? "Remotely Synchronized" : (object)m_localConcentrator == null ? "Unsynchronized" : "Locally Synchronized");
status.AppendLine();
status.AppendFormat(" Pending command requests: {0}", m_requests.Count);
status.AppendLine();
status.AppendFormat(" Authenticated: {0}", m_authenticated);
status.AppendLine();
status.AppendFormat(" Subscribed: {0}", m_subscribed);
status.AppendLine();
status.AppendFormat(" Data packet security: {0}", m_keyIVs == null ? "unencrypted" : "encrypted");
status.AppendLine();
if (DataLossInterval > 0.0D)
status.AppendFormat("No data reconnect interval: {0} seconds", DataLossInterval.ToString("0.000"));
else
status.Append("No data reconnect interval: disabled");
status.AppendLine();
if ((object)m_dataChannel != null)
{
status.AppendLine();
status.AppendLine("Data Channel Status".CenterText(50));
status.AppendLine("-------------------".CenterText(50));
status.Append(m_dataChannel.Status);
}
if ((object)m_commandChannel != null)
{
status.AppendLine();
status.AppendLine("Command Channel Status".CenterText(50));
status.AppendLine("----------------------".CenterText(50));
status.Append(m_commandChannel.Status);
}
if ((object)m_localConcentrator != null)
{
status.AppendLine();
status.AppendLine("Local Concentrator Status".CenterText(50));
status.AppendLine("-------------------------".CenterText(50));
status.Append(m_localConcentrator.Status);
}
status.Append(base.Status);
return status.ToString();
}
}
///
/// Gets a flag that determines if this uses an asynchronous connection.
///
protected override bool UseAsyncConnect
{
get
{
return true;
}
}
///
/// Gets or sets reference to data channel, attaching and/or detaching to events as needed.
///
protected UdpClient DataChannel
{
get
{
return m_dataChannel;
}
set
{
if (m_dataChannel != null)
{
// Detach from events on existing data channel reference
m_dataChannel.ConnectionException -= m_dataChannel_ConnectionException;
m_dataChannel.ConnectionAttempt -= m_dataChannel_ConnectionAttempt;
m_dataChannel.ReceiveData -= m_dataChannel_ReceiveData;
m_dataChannel.ReceiveDataException -= m_dataChannel_ReceiveDataException;
if (m_dataChannel != value)
m_dataChannel.Dispose();
}
// Assign new data channel reference
m_dataChannel = value;
if (m_dataChannel != null)
{
// Attach to desired events on new data channel reference
m_dataChannel.ConnectionException += m_dataChannel_ConnectionException;
m_dataChannel.ConnectionAttempt += m_dataChannel_ConnectionAttempt;
m_dataChannel.ReceiveData += m_dataChannel_ReceiveData;
m_dataChannel.ReceiveDataException += m_dataChannel_ReceiveDataException;
}
}
}
///
/// Gets or sets reference to command channel, attaching and/or detaching to events as needed.
///
protected TcpClient CommandChannel
{
get
{
return m_commandChannel;
}
set
{
if (m_commandChannel != null)
{
// Detach from events on existing command channel reference
m_commandChannel.ConnectionAttempt -= m_commandChannel_ConnectionAttempt;
m_commandChannel.ConnectionEstablished -= m_commandChannel_ConnectionEstablished;
m_commandChannel.ConnectionException -= m_commandChannel_ConnectionException;
m_commandChannel.ConnectionTerminated -= m_commandChannel_ConnectionTerminated;
m_commandChannel.ReceiveData -= m_commandChannel_ReceiveData;
m_commandChannel.ReceiveDataException -= m_commandChannel_ReceiveDataException;
m_commandChannel.SendDataException -= m_commandChannel_SendDataException;
if (m_commandChannel != value)
m_commandChannel.Dispose();
}
// Assign new command channel reference
m_commandChannel = value;
if (m_commandChannel != null)
{
// Attach to desired events on new command channel reference
m_commandChannel.ConnectionAttempt += m_commandChannel_ConnectionAttempt;
m_commandChannel.ConnectionEstablished += m_commandChannel_ConnectionEstablished;
m_commandChannel.ConnectionException += m_commandChannel_ConnectionException;
m_commandChannel.ConnectionTerminated += m_commandChannel_ConnectionTerminated;
m_commandChannel.ReceiveData += m_commandChannel_ReceiveData;
m_commandChannel.ReceiveDataException += m_commandChannel_ReceiveDataException;
m_commandChannel.SendDataException += m_commandChannel_SendDataException;
}
}
}
#endregion
#region [ Methods ]
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
{
DataLossInterval = 0.0D;
CommandChannel = null;
DataChannel = null;
DisposeLocalConcentrator();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
}
///
/// Initializes .
///
public override void Initialize()
{
base.Initialize();
Dictionary 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) && !m_sharedSecret.IsNullOrWhiteSpace())
throw new ArgumentException("The \"sharedSecret\" setting must defined when authentication is required.");
if (!settings.TryGetValue("authenticationID", out m_authenticationID) && !m_authenticationID.IsNullOrWhiteSpace())
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();
// 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 subscribedMeasurements = new List();
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();
// 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;
}
///
/// Authenticates subcriber to a data publisher.
///
/// Shared secret used to look up private crypto key and initialization vector.
/// Authentication ID that publisher will use to validate subscriber identity.
/// true if authentication transmission was successful; otherwise false.
public virtual bool Authenticate(string sharedSecret, string authenticationID)
{
return false;
// if (!authenticationID.IsNullOrWhiteSpace())
// {
// try
// {
// MemoryStream buffer = new MemoryStream();
// byte[] salt = new byte[DataPublisher.CipherSaltLength];
// byte[] bytes;
//
// // Generate some random prefix data to make sure auth key transmission is always unique
// GSF.Security.Cryptography.Random.GetBytes(salt);
//
// // Get encoded bytes of authentication key
// bytes = salt.Combine(m_encoding.GetBytes(authenticationID));
//
// // Encrypt authentication key
// bytes = bytes.Encrypt(sharedSecret, CipherStrength.Aes256);
//
// // Write encoded authentication key length into buffer
// buffer.Write(EndianOrder.BigEndian.GetBytes(bytes.Length), 0, 4);
//
// // Encode encrypted authentication key into buffer
// buffer.Write(bytes, 0, bytes.Length);
//
// // Send authentication command to server with associated command buffer
// return SendServerCommand(ServerCommand.Authenticate, buffer.ToArray());
// }
// catch (Exception ex)
// {
// OnProcessException(new InvalidOperationException("Exception occurred while trying to authenticate publisher subscription: " + ex.Message, ex));
// }
// }
// else
// OnProcessException(new InvalidOperationException("Cannot authenticate subscription without a connection string."));
//
// return false;
}
///
/// Subscribes (or re-subscribes) to a data publisher for a set of data points.
///
/// Configuration object that defines the subscription.
/// true if subscribe transmission was successful; otherwise false.
public bool Subscribe(SubscriptionInfo info)
{
if (info is SynchronizedSubscriptionInfo)
return SynchronizedSubscribe((SynchronizedSubscriptionInfo)info);
if (info is UnsynchronizedSubscriptionInfo)
return UnsynchronizedSubscribe((UnsynchronizedSubscriptionInfo)info);
throw new NotSupportedException("Type of subscription used is not supported");
}
///
/// Subscribes (or re-subscribes) to a data publisher for a synchronized set of data points.
///
/// Configuration object that defines the subscription.
/// true if subscribe transmission was successful; otherwise false.
public bool SynchronizedSubscribe(SynchronizedSubscriptionInfo info)
{
StringBuilder connectionString = new StringBuilder();
AssemblyInfo assemblyInfo = AssemblyInfo.ExecutingAssembly;
// Dispose of any previously established local concentrator
DisposeLocalConcentrator();
if (info.RemotelySynchronized)
{
connectionString.AppendFormat("framesPerSecond={0};", info.FramesPerSecond);
connectionString.AppendFormat("lagTime={0};", info.LagTime);
connectionString.AppendFormat("leadTime={0};", info.LeadTime);
connectionString.AppendFormat("includeTime=false;");
connectionString.AppendFormat("useLocalClockAsRealTime={0};", info.UseLocalClockAsRealTime);
connectionString.AppendFormat("ignoreBadTimestamps={0};", info.IgnoreBadTimestamps);
connectionString.AppendFormat("allowSortsByArrival={0};", info.AllowSortsByArrival);
connectionString.AppendFormat("timeResolution={0};", info.TimeResolution);
connectionString.AppendFormat("allowPreemptivePublishing={0};", info.AllowPreemptivePublishing);
connectionString.AppendFormat("downsamplingMethod={0};", info.DownsamplingMethod.ToString());
connectionString.AppendFormat("processingInterval={0};", info.ProcessingInterval);
connectionString.AppendFormat("assemblyInfo={{source={0};version={1}.{2}.{3};buildDate={4}}};", assemblyInfo.Name, assemblyInfo.Version.Major, assemblyInfo.Version.Minor, assemblyInfo.Version.Build, assemblyInfo.BuildDate.ToString("yyyy-MM-dd HH:mm:ss"));
if (!info.FilterExpression.IsNullOrWhiteSpace())
connectionString.AppendFormat("inputMeasurementKeys={{{0}}};", info.FilterExpression);
if (info.UdpDataChannel)
connectionString.AppendFormat("dataChannel={{localport={0}}};", info.DataChannelLocalPort);
if (!info.StartTime.IsNullOrWhiteSpace())
connectionString.AppendFormat("startTimeConstraint={0};", info.StartTime);
if (!info.StopTime.IsNullOrWhiteSpace())
connectionString.AppendFormat("stopTimeConstraint={0};", info.StopTime);
if (!info.ConstraintParameters.IsNullOrWhiteSpace())
connectionString.AppendFormat("timeConstraintParameters={0};", info.ConstraintParameters);
if (!info.WaitHandleNames.IsNullOrWhiteSpace())
{
connectionString.AppendFormat("waitHandleNames={0};", info.WaitHandleNames);
connectionString.AppendFormat("waitHandleTimeout={0};", info.WaitHandleTimeout);
}
if (!info.ExtraConnectionStringParameters.IsNullOrWhiteSpace())
connectionString.AppendFormat("{0};", info.ExtraConnectionStringParameters);
return Subscribe(true, info.UseCompactMeasurementFormat, connectionString.ToString());
}
else
{
// Locally concentrated subscription simply uses an unsynchronized subscription and concentrates the
// measurements on the subscriber side
if (Subscribe(FromLocallySynchronizedInfo(info)))
{
// Establish a local concentrator to synchronize received measurements
LocalConcentrator localConcentrator = new LocalConcentrator(this);
localConcentrator.ProcessException += m_localConcentrator_ProcessException;
localConcentrator.FramesPerSecond = info.FramesPerSecond;
localConcentrator.LagTime = info.LagTime;
localConcentrator.LeadTime = info.LeadTime;
localConcentrator.UseLocalClockAsRealTime = info.UseLocalClockAsRealTime;
localConcentrator.IgnoreBadTimestamps = info.IgnoreBadTimestamps;
localConcentrator.AllowSortsByArrival = info.AllowSortsByArrival;
localConcentrator.TimeResolution = info.TimeResolution;
localConcentrator.AllowPreemptivePublishing = info.AllowPreemptivePublishing;
localConcentrator.DownsamplingMethod = info.DownsamplingMethod;
localConcentrator.UsePrecisionTimer = false;
// Parse time constraints, if defined
DateTime startTimeConstraint = !info.StartTime.IsNullOrWhiteSpace() ? AdapterBase.ParseTimeTag(info.StartTime) : DateTime.MinValue;
DateTime stopTimeConstraint = !info.StopTime.IsNullOrWhiteSpace() ? AdapterBase.ParseTimeTag(info.StopTime) : DateTime.MaxValue;
// When processing historical data, timestamps should not be evaluated for reasonability
if (startTimeConstraint != DateTime.MinValue || stopTimeConstraint != DateTime.MaxValue)
{
localConcentrator.PerformTimestampReasonabilityCheck = false;
localConcentrator.LeadTime = double.MaxValue;
}
// Assign alternate processing interval, if defined
if (info.ProcessingInterval != -1)
localConcentrator.ProcessingInterval = info.ProcessingInterval;
// Start local concentrator
localConcentrator.Start();
// Move concentrator to member variable
Interlocked.Exchange(ref m_localConcentrator, localConcentrator);
return true;
}
return false;
}
}
///
/// Subscribes (or re-subscribes) to a data publisher for an unsynchronized set of data points.
///
/// Configuration object that defines the subscription.
/// true if subscribe transmission was successful; otherwise false.
public bool UnsynchronizedSubscribe(UnsynchronizedSubscriptionInfo info)
{
// Dispose of any previously established local concentrator
DisposeLocalConcentrator();
StringBuilder connectionString = new StringBuilder();
AssemblyInfo assemblyInfo = AssemblyInfo.ExecutingAssembly;
connectionString.AppendFormat("trackLatestMeasurements={0};", info.Throttled);
connectionString.AppendFormat("publishInterval={0};", info.PublishInterval);
connectionString.AppendFormat("includeTime={0};", info.IncludeTime);
connectionString.AppendFormat("lagTime={0};", info.LagTime);
connectionString.AppendFormat("leadTime={0};", info.LeadTime);
connectionString.AppendFormat("useLocalClockAsRealTime={0};", info.UseLocalClockAsRealTime);
connectionString.AppendFormat("processingInterval={0};", info.ProcessingInterval);
connectionString.AppendFormat("useMillisecondResolution={0};", info.UseMillisecondResolution);
connectionString.AppendFormat("assemblyInfo={{source={0};version={1}.{2}.{3};buildDate={4}}};", assemblyInfo.Name, assemblyInfo.Version.Major, assemblyInfo.Version.Minor, assemblyInfo.Version.Build, assemblyInfo.BuildDate.ToString("yyyy-MM-dd HH:mm:ss"));
if (!info.FilterExpression.IsNullOrWhiteSpace())
connectionString.AppendFormat("inputMeasurementKeys={{{0}}};", info.FilterExpression);
if (info.UdpDataChannel)
connectionString.AppendFormat("dataChannel={{localport={0}}};", info.DataChannelLocalPort);
if (!info.StartTime.IsNullOrWhiteSpace())
connectionString.AppendFormat("startTimeConstraint={0};", info.StartTime);
if (!info.StopTime.IsNullOrWhiteSpace())
connectionString.AppendFormat("stopTimeConstraint={0};", info.StopTime);
if (!info.ConstraintParameters.IsNullOrWhiteSpace())
connectionString.AppendFormat("timeConstraintParameters={0};", info.ConstraintParameters);
if (!info.WaitHandleNames.IsNullOrWhiteSpace())
{
connectionString.AppendFormat("waitHandleNames={0};", info.WaitHandleNames);
connectionString.AppendFormat("waitHandleTimeout={0};", info.WaitHandleTimeout);
}
if (!info.ExtraConnectionStringParameters.IsNullOrWhiteSpace())
connectionString.AppendFormat("{0};", info.ExtraConnectionStringParameters);
// Make sure not to monitor for data loss any faster than downsample time on throttled connections - additionally
// you will want to make sure data stream monitor is twice lagtime to allow time for initial points to arrive.
if (info.Throttled && (object)m_dataStreamMonitor != null && m_dataStreamMonitor.Interval / 1000.0D < info.LagTime)
m_dataStreamMonitor.Interval = 2.0D * info.LagTime * 1000.0D;
// Set millisecond resolution member variable for compact measurement parsing
m_useMillisecondResolution = info.UseMillisecondResolution;
return Subscribe(false, info.UseCompactMeasurementFormat, connectionString.ToString());
}
public bool RemotelySynchronizedSubscribe(bool compactFormat, int framesPerSecond, double lagTime, double leadTime, string filterExpression)
{
return RemotelySynchronizedSubscribe(compactFormat, framesPerSecond, lagTime, leadTime, filterExpression, null, false, false, true, Ticks.PerMillisecond, true, DownsamplingMethod.LastReceived, null, null, null, -1, null, 0);
}
///
/// Subscribes (or re-subscribes) to a data publisher for a remotely synchronized set of data points.
///
/// Boolean value that determines if the compact measurement format should be used. Set to false for full fidelity measurement serialization; otherwise set to true for bandwidth conservation.
/// The desired number of data frames per second.
/// Allowed past time deviation tolerance, in seconds (can be subsecond).
/// Allowed future time deviation tolerance, in seconds (can be subsecond).
/// Filtering expression that defines the measurements that are being subscribed.
/// Desired UDP return data channel connection string to use for data packet transmission. Set to null to use TCP channel for data transmission.
/// Boolean value that determines whether or not to use the local clock time as real-time.
/// Boolean value that determines if bad timestamps (as determined by measurement's timestamp quality) should be ignored when sorting measurements.
/// Gets or sets flag that determines whether or not to allow incoming measurements with bad timestamps to be sorted by arrival time.
/// Gets or sets the maximum time resolution, in ticks, to use when sorting measurements by timestamps into their proper destination frame.
/// Gets or sets flag that allows system to preemptively publish frames assuming all expected measurements have arrived.
/// Gets the total number of downsampled measurements processed by the concentrator.
/// Defines a relative or exact start time for the temporal constraint to use for historical playback.
/// Defines a relative or exact stop time for the temporal constraint to use for historical playback.
/// Defines any temporal parameters related to the constraint to use for historical playback.
/// Defines the desired processing interval milliseconds, i.e., historical play back speed, to use when temporal constraints are defined.
/// Comma separated list of wait handle names used to establish external event wait handles needed for inter-adapter synchronization.
/// Maximum wait time for external events, in milliseconds, before proceeding.
/// true if subscribe transmission was successful; otherwise false.
///
///
/// When the or temporal processing contraints are defined (i.e., not null), this
/// specifies the start and stop time over which the subscriber session will process data. Passing in null for the
/// and specifies the the subscriber session will process data in standard, i.e., real-time, operation.
///
///
/// With the exception of the values of -1 and 0, the value specifies the desired historical playback data
/// processing interval in milliseconds. This is basically a delay, or timer interval, overwhich to process data. Setting this value to -1 means
/// to use the default processing interval while setting the value to 0 means to process data as fast as possible.
///
///
/// The and parameters can be specified in one of the
/// following formats:
///
///
/// Time Format
/// Format Description
///
/// -
/// 12-30-2000 23:59:59.033
/// Absolute date and time.
///
/// -
/// *
/// Evaluates to .
///
/// -
/// *-20s
/// Evaluates to 20 seconds before .
///
/// -
/// *-10m
/// Evaluates to 10 minutes before .
///
/// -
/// *-1h
/// Evaluates to 1 hour before .
///
/// -
/// *-1d
/// Evaluates to 1 day before .
///
///
///
///
[Obsolete("Preferred method uses SubscriptionInfo object to subscribe.", false)]
public virtual bool RemotelySynchronizedSubscribe(bool compactFormat, int framesPerSecond, double lagTime, double leadTime, string filterExpression, string dataChannel, bool useLocalClockAsRealTime, bool ignoreBadTimestamps, bool allowSortsByArrival, long timeResolution, bool allowPreemptivePublishing, DownsamplingMethod downsamplingMethod, string startTime, string stopTime, string constraintParameters, int processingInterval, string waitHandleNames, int waitHandleTimeout)
{
// Dispose of any previously established local concentrator
DisposeLocalConcentrator();
StringBuilder connectionString = new StringBuilder();
AssemblyInfo assemblyInfo = AssemblyInfo.ExecutingAssembly;
connectionString.AppendFormat("framesPerSecond={0}; ", framesPerSecond);
connectionString.AppendFormat("lagTime={0}; ", lagTime);
connectionString.AppendFormat("leadTime={0}; ", leadTime);
connectionString.AppendFormat("inputMeasurementKeys={{{0}}}; ", filterExpression.ToNonNullString());
connectionString.AppendFormat("dataChannel={{{0}}}; ", dataChannel.ToNonNullString());
connectionString.AppendFormat("includeTime=false; ");
connectionString.AppendFormat("useLocalClockAsRealTime={0}; ", useLocalClockAsRealTime);
connectionString.AppendFormat("ignoreBadTimestamps={0}; ", ignoreBadTimestamps);
connectionString.AppendFormat("allowSortsByArrival={0}; ", allowSortsByArrival);
connectionString.AppendFormat("timeResolution={0}; ", (long)timeResolution);
connectionString.AppendFormat("allowPreemptivePublishing={0}; ", allowPreemptivePublishing);
connectionString.AppendFormat("downsamplingMethod={0}; ", downsamplingMethod.ToString());
connectionString.AppendFormat("startTimeConstraint={0}; ", startTime.ToNonNullString());
connectionString.AppendFormat("stopTimeConstraint={0}; ", stopTime.ToNonNullString());
connectionString.AppendFormat("timeConstraintParameters={0}; ", constraintParameters.ToNonNullString());
connectionString.AppendFormat("processingInterval={0}; ", processingInterval);
connectionString.AppendFormat("assemblyInfo={{source={0}; version={1}.{2}.{3}; buildDate={4}}}", assemblyInfo.Name, assemblyInfo.Version.Major, assemblyInfo.Version.Minor, assemblyInfo.Version.Build, assemblyInfo.BuildDate.ToString("yyyy-MM-dd HH:mm:ss"));
if (!waitHandleNames.IsNullOrWhiteSpace())
{
connectionString.AppendFormat("; waitHandleNames={0}", waitHandleNames);
connectionString.AppendFormat("; waitHandleTimeout={0}", waitHandleTimeout);
}
return Subscribe(true, compactFormat, connectionString.ToString());
}
public bool LocallySynchronizedSubscribe(bool compactFormat, int framesPerSecond, double lagTime, double leadTime, string filterExpression)
{
return LocallySynchronizedSubscribe(compactFormat, framesPerSecond, lagTime, leadTime, filterExpression, null, false, false, true, Ticks.PerMillisecond, true, DownsamplingMethod.LastReceived, null, null, null, -1, null, 0);
}
///
/// Subscribes (or re-subscribes) to a data publisher for a locally synchronized set of data points.
///
/// Boolean value that determines if the compact measurement format should be used. Set to false for full fidelity measurement serialization; otherwise set to true for bandwidth conservation.
/// The desired number of data frames per second.
/// Allowed past time deviation tolerance, in seconds (can be subsecond).
/// Allowed future time deviation tolerance, in seconds (can be subsecond).
/// Filtering expression that defines the measurements that are being subscribed.
/// Desired UDP return data channel connection string to use for data packet transmission. Set to null to use TCP channel for data transmission.
/// Boolean value that determines whether or not to use the local clock time as real-time.
/// Boolean value that determines if bad timestamps (as determined by measurement's timestamp quality) should be ignored when sorting measurements.
/// Gets or sets flag that determines whether or not to allow incoming measurements with bad timestamps to be sorted by arrival time.
/// Gets or sets the maximum time resolution, in ticks, to use when sorting measurements by timestamps into their proper destination frame.
/// Gets or sets flag that allows system to preemptively publish frames assuming all expected measurements have arrived.
/// Gets the total number of downsampled measurements processed by the concentrator.
/// Defines a relative or exact start time for the temporal constraint to use for historical playback.
/// Defines a relative or exact stop time for the temporal constraint to use for historical playback.
/// Defines any temporal parameters related to the constraint to use for historical playback.
/// Defines the desired processing interval milliseconds, i.e., historical play back speed, to use when temporal constraints are defined.
/// Comma separated list of wait handle names used to establish external event wait handles needed for inter-adapter synchronization.
/// Maximum wait time for external events, in milliseconds, before proceeding.
/// true if subscribe transmission was successful; otherwise false.
///
///
/// When the or temporal processing contraints are defined (i.e., not null), this
/// specifies the start and stop time over which the subscriber session will process data. Passing in null for the
/// and specifies the the subscriber session will process data in standard, i.e., real-time, operation.
///
///
/// With the exception of the values of -1 and 0, the value specifies the desired historical playback data
/// processing interval in milliseconds. This is basically a delay, or timer interval, overwhich to process data. Setting this value to -1 means
/// to use the default processing interval while setting the value to 0 means to process data as fast as possible.
///
///
/// The and parameters can be specified in one of the
/// following formats:
///
///
/// Time Format
/// Format Description
///
/// -
/// 12-30-2000 23:59:59.033
/// Absolute date and time.
///
/// -
/// *
/// Evaluates to .
///
/// -
/// *-20s
/// Evaluates to 20 seconds before .
///
/// -
/// *-10m
/// Evaluates to 10 minutes before .
///
/// -
/// *-1h
/// Evaluates to 1 hour before .
///
/// -
/// *-1d
/// Evaluates to 1 day before .
///
///
///
///
[Obsolete("Preferred method uses SubscriptionInfo object to subscribe.", false)]
public virtual bool LocallySynchronizedSubscribe(bool compactFormat, int framesPerSecond, double lagTime, double leadTime, string filterExpression, string dataChannel, bool useLocalClockAsRealTime, bool ignoreBadTimestamps, bool allowSortsByArrival, long timeResolution, bool allowPreemptivePublishing, DownsamplingMethod downsamplingMethod, string startTime, string stopTime, string constraintParameters, int processingInterval, string waitHandleNames, int waitHandleTimeout)
{
// Dispose of any previously established local concentrator
DisposeLocalConcentrator();
// Establish a local concentrator to synchronize received measurements
m_localConcentrator = new LocalConcentrator(this);
m_localConcentrator.ProcessException += m_localConcentrator_ProcessException;
m_localConcentrator.FramesPerSecond = framesPerSecond;
m_localConcentrator.LagTime = lagTime;
m_localConcentrator.LeadTime = leadTime;
m_localConcentrator.UseLocalClockAsRealTime = useLocalClockAsRealTime;
m_localConcentrator.IgnoreBadTimestamps = ignoreBadTimestamps;
m_localConcentrator.AllowSortsByArrival = allowSortsByArrival;
m_localConcentrator.TimeResolution = timeResolution;
m_localConcentrator.AllowPreemptivePublishing = allowPreemptivePublishing;
m_localConcentrator.DownsamplingMethod = downsamplingMethod;
m_localConcentrator.UsePrecisionTimer = false;
// Parse time constraints, if defined
DateTime startTimeConstraint = !startTime.IsNullOrWhiteSpace() ? AdapterBase.ParseTimeTag(startTime) : DateTime.MinValue;
DateTime stopTimeConstraint = !stopTime.IsNullOrWhiteSpace() ? AdapterBase.ParseTimeTag(stopTime) : DateTime.MaxValue;
// When processing historical data, timestamps should not be evaluated for reasonability
if (startTimeConstraint != DateTime.MinValue || stopTimeConstraint != DateTime.MaxValue)
{
m_localConcentrator.PerformTimestampReasonabilityCheck = false;
m_localConcentrator.LeadTime = double.MaxValue;
}
// Assign alternate processing interval, if defined
if (processingInterval != -1)
m_localConcentrator.ProcessingInterval = processingInterval;
// Initiate unsynchronized subscribe
StringBuilder connectionString = new StringBuilder();
AssemblyInfo assemblyInfo = AssemblyInfo.ExecutingAssembly;
connectionString.AppendFormat("trackLatestMeasurements={0}; ", false);
connectionString.AppendFormat("inputMeasurementKeys={{{0}}}; ", filterExpression.ToNonNullString());
connectionString.AppendFormat("dataChannel={{{0}}}; ", dataChannel.ToNonNullString());
connectionString.AppendFormat("includeTime={0}; ", true);
connectionString.AppendFormat("lagTime={0}; ", 10.0D);
connectionString.AppendFormat("leadTime={0}; ", 5.0D);
connectionString.AppendFormat("useLocalClockAsRealTime={0}; ", false);
connectionString.AppendFormat("startTimeConstraint={0}; ", startTime.ToNonNullString());
connectionString.AppendFormat("stopTimeConstraint={0}; ", stopTime.ToNonNullString());
connectionString.AppendFormat("timeConstraintParameters={0}; ", constraintParameters.ToNonNullString());
connectionString.AppendFormat("processingInterval={0}; ", processingInterval);
connectionString.AppendFormat("useMillisecondResolution={0}; ", m_useMillisecondResolution);
connectionString.AppendFormat("assemblyInfo={{source={0}; version={1}.{2}.{3}; buildDate={4}}}", assemblyInfo.Name, assemblyInfo.Version.Major, assemblyInfo.Version.Minor, assemblyInfo.Version.Build, assemblyInfo.BuildDate.ToString("yyyy-MM-dd HH:mm:ss"));
if (!waitHandleNames.IsNullOrWhiteSpace())
{
connectionString.AppendFormat("; waitHandleNames={0}", waitHandleNames);
connectionString.AppendFormat("; waitHandleTimeout={0}", waitHandleTimeout);
}
// Start subscription process
if (Subscribe(false, compactFormat, connectionString.ToString()))
{
// If subscription succeeds, start local concentrator
m_localConcentrator.Start();
return true;
}
return false;
}
public bool UnsynchronizedSubscribe(bool compactFormat, bool throttled, string filterExpression)
{
return UnsynchronizedSubscribe(compactFormat, throttled, filterExpression, null, true, 10.0D, 5.0D, false, null, null, null, -1, null, 0);
}
///
/// Subscribes (or re-subscribes) to a data publisher for an unsynchronized set of data points.
///
/// Boolean value that determines if the compact measurement format should be used. Set to false for full fidelity measurement serialization; otherwise set to true for bandwidth conservation.
/// Boolean value that determines if data should be throttled at a set transmission interval or sent on change.
/// Filtering expression that defines the measurements that are being subscribed.
/// Desired UDP return data channel connection string to use for data packet transmission. Set to null to use TCP channel for data transmission.
/// Boolean value that determines if time is a necessary component in streaming data.
/// When is true, defines the data transmission speed in seconds (can be subsecond).
/// When is true, defines the allowed time deviation tolerance to real-time in seconds (can be subsecond).
/// When is true, defines boolean value that determines whether or not to use the local clock time as real-time. Set to false to use latest received measurement timestamp as real-time.
/// Defines a relative or exact start time for the temporal constraint to use for historical playback.
/// Defines a relative or exact stop time for the temporal constraint to use for historical playback.
/// Defines any temporal parameters related to the constraint to use for historical playback.
/// Defines the desired processing interval milliseconds, i.e., historical play back speed, to use when temporal constraints are defined.
/// Comma separated list of wait handle names used to establish external event wait handles needed for inter-adapter synchronization.
/// Maximum wait time for external events, in milliseconds, before proceeding.
/// true if subscribe transmission was successful; otherwise false.
///
///
/// When the or temporal processing contraints are defined (i.e., not null), this
/// specifies the start and stop time over which the subscriber session will process data. Passing in null for the
/// and specifies the the subscriber session will process data in standard, i.e., real-time, operation.
///
///
/// With the exception of the values of -1 and 0, the value specifies the desired historical playback data
/// processing interval in milliseconds. This is basically a delay, or timer interval, overwhich to process data. Setting this value to -1 means
/// to use the default processing interval while setting the value to 0 means to process data as fast as possible.
///
///
/// The and parameters can be specified in one of the
/// following formats:
///
///
/// Time Format
/// Format Description
///
/// -
/// 12-30-2000 23:59:59.033
/// Absolute date and time.
///
/// -
/// *
/// Evaluates to .
///
/// -
/// *-20s
/// Evaluates to 20 seconds before .
///
/// -
/// *-10m
/// Evaluates to 10 minutes before .
///
/// -
/// *-1h
/// Evaluates to 1 hour before .
///
/// -
/// *-1d
/// Evaluates to 1 day before .
///
///
///
///
[Obsolete("Preferred method uses SubscriptionInfo object to subscribe.", false)]
public virtual bool UnsynchronizedSubscribe(bool compactFormat, bool throttled, string filterExpression, string dataChannel, bool includeTime, double lagTime, double leadTime, bool useLocalClockAsRealTime, string startTime, string stopTime, string constraintParameters, int processingInterval, string waitHandleNames, int waitHandleTimeout)
{
// Dispose of any previously established local concentrator
DisposeLocalConcentrator();
StringBuilder connectionString = new StringBuilder();
AssemblyInfo assemblyInfo = AssemblyInfo.ExecutingAssembly;
connectionString.AppendFormat("trackLatestMeasurements={0}; ", throttled);
connectionString.AppendFormat("inputMeasurementKeys={{{0}}}; ", filterExpression.ToNonNullString());
connectionString.AppendFormat("dataChannel={{{0}}}; ", dataChannel.ToNonNullString());
connectionString.AppendFormat("includeTime={0}; ", includeTime);
connectionString.AppendFormat("lagTime={0}; ", lagTime);
connectionString.AppendFormat("leadTime={0}; ", leadTime);
connectionString.AppendFormat("useLocalClockAsRealTime={0}; ", useLocalClockAsRealTime);
connectionString.AppendFormat("startTimeConstraint={0}; ", startTime.ToNonNullString());
connectionString.AppendFormat("stopTimeConstraint={0}; ", stopTime.ToNonNullString());
connectionString.AppendFormat("timeConstraintParameters={0}; ", constraintParameters.ToNonNullString());
connectionString.AppendFormat("processingInterval={0}; ", processingInterval);
connectionString.AppendFormat("useMillisecondResolution={0}; ", m_useMillisecondResolution);
connectionString.AppendFormat("assemblyInfo={{source={0}; version={1}.{2}.{3}; buildDate={4}}}", assemblyInfo.Name, assemblyInfo.Version.Major, assemblyInfo.Version.Minor, assemblyInfo.Version.Build, assemblyInfo.BuildDate.ToString("yyyy-MM-dd HH:mm:ss"));
if (!waitHandleNames.IsNullOrWhiteSpace())
{
connectionString.AppendFormat("; waitHandleNames={0}", waitHandleNames);
connectionString.AppendFormat("; waitHandleTimeout={0}", waitHandleTimeout);
}
// Make sure not to monitor for data loss any faster than downsample time on throttled connections - additionally
// you will want to make sure data stream monitor is twice lagtime to allow time for initial points to arrive.
if (throttled && (object)m_dataStreamMonitor != null && m_dataStreamMonitor.Interval / 1000.0D < lagTime)
m_dataStreamMonitor.Interval = 2.0D * lagTime * 1000.0D;
return Subscribe(false, compactFormat, connectionString.ToString());
}
///
/// Subscribes (or re-subscribes) to a data publisher for a set of data points.
///
/// Boolean value that determines if subscription should be remotely synchronized - note that data publisher may not allow remote synchronization.
/// Boolean value that determines if the compact measurement format should be used. Set to false for full fidelity measurement serialization; otherwise set to true for bandwidth conservation.
/// Connection string that defines required and optional parameters for the subscription.
/// true if subscribe transmission was successful; otherwise false.
public virtual bool Subscribe(bool remotelySynchronized, bool compactFormat, string connectionString)
{
bool success = false;
if (!connectionString.IsNullOrWhiteSpace())
{
try
{
// Parse connection string to see if it contains a data channel definition
Dictionary settings = connectionString.ParseKeyValuePairs();
UdpClient dataChannel = null;
string setting;
// Track specified time inclusion for later deserialization
if (settings.TryGetValue("includeTime", out setting))
m_includeTime = setting.ParseBoolean();
else
m_includeTime = true;
settings.TryGetValue("dataChannel", out setting);
if (!setting.IsNullOrWhiteSpace())
{
dataChannel = new UdpClient(setting);
dataChannel.ReceiveBufferSize = ushort.MaxValue;
dataChannel.MaxConnectionAttempts = -1;
dataChannel.ConnectAsync();
}
// Assign data channel client reference and attach to needed events
this.DataChannel = dataChannel;
// Setup subcription packet
MemoryStream buffer = new MemoryStream();
DataPacketFlags flags = DataPacketFlags.NoFlags;
byte[] bytes;
if (remotelySynchronized)
flags |= DataPacketFlags.Synchronized;
if (compactFormat)
flags |= DataPacketFlags.Compact;
// Write data packet flags into buffer
buffer.WriteByte((byte)flags);
// Get encoded bytes of connection string
bytes = m_encoding.GetBytes(connectionString);
// Write encoded connection string length into buffer
buffer.Write(EndianOrder.BigEndian.GetBytes(bytes.Length), 0, 4);
// Encode connection string into buffer
buffer.Write(bytes, 0, bytes.Length);
// Cache subscribed synchronization state
m_synchronizedSubscription = remotelySynchronized;
// Send subscribe server command with associated command buffer
success = SendServerCommand(ServerCommand.Subscribe, buffer.ToArray());
}
catch (Exception ex)
{
OnProcessException(new InvalidOperationException("Exception occurred while trying to make publisher subscription: " + ex.Message, ex));
}
}
else
OnProcessException(new InvalidOperationException("Cannot make publisher subscription without a connection string."));
return success;
}
///
/// Unsubscribes from a data publisher.
///
/// true if unsubscribe transmission was successful; otherwise false.
public virtual bool Unsubscribe()
{
// Send unsubscribe server command
return SendServerCommand(ServerCommand.Unsubscribe, null);
}
///
/// Returns the measurements signal IDs that were authorized after the last successful subscription request.
///
[AdapterCommand("Gets authorized signal IDs from last subscription request.")]
public virtual Guid[] GetAuthorizedSignalIDs()
{
if (m_signalIndexCache != null)
return m_signalIndexCache.AuthorizedSignalIDs;
return new Guid[0];
}
///
/// Returns the measurements signal IDs that were unauthorized after the last successful subscription request.
///
[AdapterCommand("Gets unauthorized signal IDs from last subscription request.")]
public virtual Guid[] GetUnauthorizedSignalIDs()
{
if (m_signalIndexCache != null)
return m_signalIndexCache.UnauthorizedSignalIDs;
return new Guid[0];
}
///
/// Initiate a metadata refresh.
///
[AdapterCommand("Initiates a metadata refresh.")]
public virtual void RefreshMetadata()
{
SendServerCommand(ServerCommand.MetaDataRefresh);
}
public bool SendServerCommand(ServerCommand commandCode)
{
return SendServerCommand(commandCode, null);
}
///
/// Sends a server command to the publisher connection.
///
/// to send.
/// Command data to send.
/// true if transmission was successful; otherwise false.
public virtual bool SendServerCommand(ServerCommand commandCode, byte[] data)
{
if (m_commandChannel != null && m_commandChannel.CurrentState == ClientState.Connected)
{
try
{
MemoryStream commandPacket = new MemoryStream();
// Write command code into command packet
commandPacket.WriteByte((byte)commandCode);
// Write command buffer into command packet
if (data != null && data.Length > 0)
commandPacket.Write(data, 0, data.Length);
// Send command packet to publisher
m_commandChannel.SendAsync(commandPacket.ToArray());
// Track server command in pending request queue
lock (m_requests)
{
// Make sure a pending request does not already exist
int index = m_requests.BinarySearch(commandCode);
if (index < 0)
{
// Add the new server command to the request list
m_requests.Add(commandCode);
// Make sure requests are sorted to allow for binary searching
m_requests.Sort();
}
}
return true;
}
catch (Exception ex)
{
OnProcessException(new InvalidOperationException(string.Format("Exception occurred while trying to send server command \"{0}\" to publisher: {1}", commandCode, ex.Message), ex));
}
}
else
OnProcessException(new InvalidOperationException(string.Format("Subscriber is currently unconnected. Cannot send server command \"{0}\" to publisher.", commandCode)));
return false;
}
///
/// Attempts to connect to this .
///
protected override void AttemptConnection()
{
m_commandChannelConnectionAttempts = 0;
m_dataChannelConnectionAttempts = 0;
m_commandChannel.ConnectAsync();
m_authenticated = false;
m_subscribed = false;
m_keyIVs = null;
m_totalBytesReceived = 0L;
m_monitoredBytesReceived = 0L;
m_lastBytesReceived = 0;
}
///
/// Attempts to disconnect from this .
///
protected override void AttemptDisconnection()
{
// Stop data stream monitor
if ((object)m_dataStreamMonitor != null)
m_dataStreamMonitor.Enabled = false;
// Disconnect command channel
if ((object)m_commandChannel != null)
m_commandChannel.Disconnect();
}
///
/// Gets a short one-line status of this .
///
/// Maximum length of the status message.
/// Text of the status message.
public override string GetShortStatus(int maxLength)
{
if (m_commandChannel != null && m_commandChannel.CurrentState == ClientState.Connected)
return string.Format("Subscriber is connected and receiving {0} data points", m_synchronizedSubscription ? "synchronized" : "unsynchronized").CenterText(maxLength);
return "Subscriber is not connected.".CenterText(maxLength);
}
///
/// Get message from string based response.
///
/// Response buffer.
/// Start index of response message.
/// Length of response message.
/// Decoded response string.
protected string InterpretResponseMessage(byte[] buffer, int startIndex, int length)
{
return m_encoding.GetString(buffer, startIndex, length);
}
// Restarts the subscriber.
private void Restart()
{
try
{
base.Start();
}
catch (Exception ex)
{
OnProcessException(ex);
}
}
private void ProcessServerResponse(byte[] buffer, int length)
{
// Currently this work is done on the async socket completion thread, make sure work to be done is timely and if the response processing
// is coming in via the command channel and needs to send a command back to the server, it should be done on a separate thread...
if (buffer != null && length > 0)
{
try
{
ServerResponse responseCode = (ServerResponse)buffer[0];
ServerCommand commandCode = (ServerCommand)buffer[1];
int responseLength = EndianOrder.BigEndian.ToInt32(buffer, 2);
int responseIndex = 6;
bool solicited = false;
byte[][][] keyIVs;
// See if this was a solicited response to a requested server command
if (responseCode.IsSolicited())
{
lock (m_requests)
{
int index = m_requests.BinarySearch(commandCode);
if (index >= 0)
{
solicited = true;
m_requests.RemoveAt(index);
}
}
// Disconnect any established UDP data channel upon successful unsubscribe
if (solicited && commandCode == ServerCommand.Unsubscribe && responseCode == ServerResponse.Succeeded)
DataChannel = null;
}
switch (responseCode)
{
case ServerResponse.Succeeded:
if (solicited)
{
switch (commandCode)
{
case ServerCommand.Authenticate:
OnStatusMessage("Success code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength));
m_authenticated = true;
OnConnectionAuthenticated();
break;
case ServerCommand.Subscribe:
OnStatusMessage("Success code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength));
m_subscribed = true;
if ((object)m_dataStreamMonitor != null)
m_dataStreamMonitor.Enabled = true;
break;
case ServerCommand.Unsubscribe:
OnStatusMessage("Success code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength));
m_subscribed = false;
if ((object)m_dataStreamMonitor != null)
m_dataStreamMonitor.Enabled = false;
break;
case ServerCommand.RotateCipherKeys:
OnStatusMessage("Success code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength));
break;
case ServerCommand.MetaDataRefresh:
OnStatusMessage("Success code received in response to server command \"{0}\": latest meta-data received.", commandCode);
OnMetaDataReceived(DeserializeMetadata(buffer.BlockCopy(responseIndex, responseLength)));
break;
}
}
else
{
switch (commandCode)
{
case ServerCommand.MetaDataRefresh:
// Meta-data refresh may be unsolicited
OnStatusMessage("Received server confirmation for unsolicited request to \"{0}\" command: latest meta-data received.", commandCode);
OnMetaDataReceived(DeserializeMetadata(buffer.BlockCopy(responseIndex, responseLength)));
break;
case ServerCommand.RotateCipherKeys:
// Key rotation may be unsolicited
OnStatusMessage("Received server confirmation for unsolicited request to \"{0}\" command: {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength));
break;
case ServerCommand.Subscribe:
OnStatusMessage("Received unsolicited response to \"{0}\" command: {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength));
break;
default:
OnProcessException(new InvalidOperationException("Publisher sent a success code for an unsolicited server command: " + commandCode));
break;
}
}
break;
case ServerResponse.Failed:
if (solicited)
OnStatusMessage("Failure code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength));
else
OnProcessException(new InvalidOperationException("Publisher sent a failed code for an unsolicited server command: " + commandCode));
break;
case ServerResponse.DataPacket:
long now = DateTime.UtcNow.Ticks;
// Deserialize data packet
List measurements = new List();
DataPacketFlags flags;
Ticks timestamp = 0;
int count;
// Track total data packet bytes received from any channel
m_totalBytesReceived += m_lastBytesReceived;
m_monitoredBytesReceived += m_lastBytesReceived;
// Get data packet flags
flags = (DataPacketFlags)buffer[responseIndex];
responseIndex++;
bool synchronizedMeasurements = ((byte)(flags & DataPacketFlags.Synchronized) > 0);
bool compactMeasurementFormat = ((byte)(flags & DataPacketFlags.Compact) > 0);
int cipherIndex = (flags & DataPacketFlags.CipherIndex) > 0 ? 1 : 0;
// Decrypt data packet payload if keys are available
// if (m_keyIVs != null)
// {
// // Get a local copy of volatile keyIVs reference since this can change at any time
// keyIVs = m_keyIVs;
//
// // Decrypt payload portion of data packet
// buffer = Common.SymmetricAlgorithm.Decrypt(buffer, responseIndex, responseLength - 1, keyIVs[cipherIndex][0], keyIVs[cipherIndex][1]);
// responseIndex = 0;
// responseLength = buffer.Length;
// }
// Synchronized packets contain a frame level timestamp
if (synchronizedMeasurements)
{
timestamp = EndianOrder.BigEndian.ToInt64(buffer, responseIndex);
responseIndex += 8;
}
// Deserialize number of measurements that follow
count = EndianOrder.BigEndian.ToInt32(buffer, responseIndex);
responseIndex += 4;
// Deserialize measurements
for (int i = 0; i < count; i++)
{
if (!compactMeasurementFormat)
{
// Deserialize full measurement format
SerializableMeasurement measurement = new SerializableMeasurement(m_encoding);
responseIndex += measurement.ParseBinaryImage(buffer, responseIndex, responseLength - responseIndex);
measurements.Add(measurement);
}
else if ((object)m_signalIndexCache != null)
{
// Deserialize compact measurement format
CompactMeasurement measurement = new CompactMeasurement(m_signalIndexCache, m_includeTime, m_baseTimeOffsets, m_timeIndex, m_useMillisecondResolution);
responseIndex += measurement.ParseBinaryImage(buffer, responseIndex, responseLength - responseIndex);
// Apply timestamp from frame if not included in transmission
if (!measurement.IncludeTime)
measurement.Timestamp = timestamp;
measurements.Add(measurement);
}
else if (m_lastMissingCacheWarning + MissingCacheWarningInterval < now)
{
if (m_lastMissingCacheWarning != 0L)
{
// Warning message for missing signal index cache
OnStatusMessage("WARNING: Signal index cache has not arrived. No compact measurements can be parsed.");
}
m_lastMissingCacheWarning = now;
}
}
// Provide new measurements to local concentrator, if defined, otherwise directly expose them to the consumer
if ((object)m_localConcentrator != null)
m_localConcentrator.SortMeasurements(measurements);
else
OnNewMeasurements(measurements);
break;
case ServerResponse.DataStartTime:
// Raise data start time event
OnDataStartTime(EndianOrder.BigEndian.ToInt64(buffer, responseIndex));
break;
case ServerResponse.ProcessingComplete:
// Raise input processing completed event
OnProcessingComplete(InterpretResponseMessage(buffer, responseIndex, responseLength));
break;
case ServerResponse.UpdateSignalIndexCache:
// Deserialize new signal index cache
m_remoteSignalIndexCache = DeserializeSignalIndexCache(buffer.BlockCopy(responseIndex, responseLength));
m_signalIndexCache = new SignalIndexCache(DataSource, m_remoteSignalIndexCache);
break;
case ServerResponse.UpdateBaseTimes:
// Get active time index
m_timeIndex = EndianOrder.BigEndian.ToInt32(buffer, responseIndex);
responseIndex += 4;
// Deserialize new base time offsets
m_baseTimeOffsets = new long[] { EndianOrder.BigEndian.ToInt64(buffer, responseIndex), EndianOrder.BigEndian.ToInt64(buffer, responseIndex + 8) };
break;
case ServerResponse.UpdateCipherKeys:
// Get active cipher index
m_cipherIndex = buffer[responseIndex++];
// Extract remaining response
byte[] bytes = buffer.BlockCopy(responseIndex, responseLength - 1);
// // Decrypt response payload if subscription is authenticated
// if (m_authenticated)
// bytes = bytes.Decrypt(m_sharedSecret, CipherStrength.Aes256);
// Deserialize new cipher keys
keyIVs = new byte[2][][];
keyIVs[EvenKey] = new byte[2][];
keyIVs[OddKey] = new byte[2][];
int index = 0;
int bufferLen;
// Read even key size
bufferLen = EndianOrder.BigEndian.ToInt32(bytes, index);
index += 4;
// Read even key
keyIVs[EvenKey][KeyIndex] = new byte[bufferLen];
Buffer.BlockCopy(bytes, index, keyIVs[EvenKey][KeyIndex], 0, bufferLen);
index += bufferLen;
// Read even initialization vector size
bufferLen = EndianOrder.BigEndian.ToInt32(bytes, index);
index += 4;
// Read even initialization vector
keyIVs[EvenKey][IVIndex] = new byte[bufferLen];
Buffer.BlockCopy(bytes, index, keyIVs[EvenKey][IVIndex], 0, bufferLen);
index += bufferLen;
// Read odd key size
bufferLen = EndianOrder.BigEndian.ToInt32(bytes, index);
index += 4;
// Read odd key
keyIVs[OddKey][KeyIndex] = new byte[bufferLen];
Buffer.BlockCopy(bytes, index, keyIVs[OddKey][KeyIndex], 0, bufferLen);
index += bufferLen;
// Read odd initialization vector size
bufferLen = EndianOrder.BigEndian.ToInt32(bytes, index);
index += 4;
// Read odd initialization vector
keyIVs[OddKey][IVIndex] = new byte[bufferLen];
Buffer.BlockCopy(bytes, index, keyIVs[OddKey][IVIndex], 0, bufferLen);
index += bufferLen;
// Exchange keys
m_keyIVs = keyIVs;
OnStatusMessage("Successfully established new cipher keys for data packet transmissions.");
break;
}
}
catch (Exception ex)
{
OnProcessException(new InvalidOperationException("Failed to process publisher response packet due to exception: " + ex.Message, ex));
}
}
}
// Handles auto-connection subscription initialization
private void StartSubscription()
{
StringBuilder filterExpression = new StringBuilder();
string dataChannel = null;
// If TCP command channel is defined separately, then base connection string defines data channel
if (Settings.ContainsKey("commandChannel"))
dataChannel = ConnectionString;
if (OutputMeasurements != null)
{
foreach (IMeasurement measurement in OutputMeasurements)
{
if (filterExpression.Length > 0)
filterExpression.Append(';');
// Subscribe by associated Guid...
filterExpression.Append(measurement.ID.ToString());
}
// Start unsynchronized subscription
UnsynchronizedSubscribe(true, false, filterExpression.ToString(), dataChannel, true, 10.0D, 5.0D, false, null, null, null, -1, null, 0);
}
else
{
OnStatusMessage("WARNING: No measurements are currently defined for subscription.");
}
// Initiate meta-data refresh
if (m_synchronizeMetadata)
SendServerCommand(ServerCommand.MetaDataRefresh);
}
///
/// Handles auto-connection metadata synchronization to local system.
///
/// metadata collection passed into state parameter.
///
/// This function is normally called from thread pool since synchronization can take some time.
///
protected virtual void SynchronizeMetadata(object state)
{
// try
// {
// DataSet metadata = state as DataSet;
//
// if (metadata != null)
// {
// // Track total meta-data synchronization process time
// Ticks startTime = DateTime.UtcNow.Ticks;
//
// // Open the configuration database using settings found in the config file
// AdoDataConnection database = new AdoDataConnection("systemSettings");
// IDbConnection connection = database.Connection;
// string guidPrefix = database.DatabaseType == DatabaseType.Access ? "{" : "'";
// string guidSuffix = database.DatabaseType == DatabaseType.Access ? "}" : "'";
//
// // Query the actual record ID based on the known run-time ID for this subscriber device
// int parentID = Convert.ToInt32(connection.ExecuteScalar(string.Format("SELECT SourceID FROM Runtime WHERE ID = {0} AND SourceTable='Device'", ID)));
//
// // Validate that the subscriber device is marked as a concentrator (we are about to associate children devices with it)
// if (!connection.ExecuteScalar(string.Format("SELECT IsConcentrator FROM Device WHERE ID = {0}", parentID)).ToString().ParseBoolean())
// connection.ExecuteNonQuery(string.Format("UPDATE Device SET IsConcentrator = 1 WHERE ID = {0}", parentID));
//
// // Determine the active node ID - we cache this since this value won't change for the lifetime of this class
// if (m_nodeID == Guid.Empty)
// m_nodeID = new Guid(connection.ExecuteScalar(string.Format("SELECT NodeID FROM IaonInputAdapter WHERE ID = {0}", ID)).ToString());
//
// // Determine the protocol record auto-inc ID value for the gateway transport protocol (GEP) - this value is also cached since it shouldn't change for the lifetime of this class
// if (m_gatewayProtocolID == 0)
// m_gatewayProtocolID = int.Parse(connection.ExecuteScalar("SELECT ID FROM Protocol WHERE Acronym='GatewayTransport'").ToString());
//
// // Prefix all children devices with the name of the parent since the same device names could appear in different connections (helps keep device names unique)
// string sourcePrefix = Name + "!";
// Dictionary deviceIDs = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
// string selectSql, insertSql, updateSql, deleteSql, deviceAcronym, signalTypeAcronym;
// int deviceID;
//
// // Check to see if data for the "DeviceDetail" table was included in the meta-data
// if (metadata.Tables.Contains("DeviceDetail"))
// {
// List uniqueIDs = new List();
//
// foreach (DataRow row in metadata.Tables["DeviceDetail"].Rows)
// {
// Guid uniqueID = new Guid(row.Field