//******************************************************************************************************
// SignalIndexCache.cs - Gbtc
//
// Copyright © 2012, 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 MIT License (MIT), 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/MIT
//
// 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:
// ----------------------------------------------------------------------------------------------------
// 05/15/2011 - J. Ritchie Carroll
// Generated original version of source code.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using GSF.Collections;
using GSF.Parsing;
#pragma warning disable 618
namespace GSF.TimeSeries.Transport
{
///
/// Represents a serializable signal ID to index cross reference.
///
///
/// This class is used to create a runtime index to be used for data exchange so that a 16-bit integer
/// is exchanged in the data packets for signal identification instead of the 128-bit Guid signal ID
/// to reduce bandwidth required for signal exchange. This means the total number of unique signal
/// IDs that could be exchanged using this method in a single session is 65,535. This number seems
/// reasonable for the currently envisioned use cases, however, multiple sessions each with their own
/// runtime signal index cache could be established if this is a limitation for a given data set.
///
[Serializable]
public class SignalIndexCache : ISupportBinaryImage
{
#region [ Members ]
// Fields
private Guid m_subscriberID;
// Since measurement keys are statically cached as a global system optimization and the keys
// can be different between two parties exchanging data, the raw measurement key elements are
// cached and exchanged instead of actual measurement key values
private ConcurrentDictionary m_reference;
private Guid[] m_unauthorizedSignalIDs;
///
/// Lookups MeasurementKey.RuntimeID and returns ushort SignalIndex. -1 means it does not exist.
///
[NonSerialized] // SignalID reverse lookup runtime cache (used to speed deserialization)
private IndexedArray m_signalIDCache;
[NonSerialized]
private Encoding m_encoding;
#endregion
#region [ Constructors ]
///
/// Creates a new instance.
///
public SignalIndexCache()
{
m_reference = new ConcurrentDictionary();
m_signalIDCache = new IndexedArray(-1);
}
///
/// Creates a new local system cache from one that was received remotely.
///
/// based data source used to interpret local measurement keys.
/// Deserialized remote signal index cache.
public SignalIndexCache(DataSet dataSource, SignalIndexCache remoteCache)
{
m_subscriberID = remoteCache.SubscriberID;
// If active measurements are defined, interpret signal cache in context of current measurement key definitions
if (dataSource != null && dataSource.Tables.Contains("ActiveMeasurements"))
{
DataTable activeMeasurements = dataSource.Tables["ActiveMeasurements"];
m_reference = new ConcurrentDictionary();
foreach (KeyValuePair signalIndex in remoteCache.Reference)
{
Guid signalID = signalIndex.Value.SignalID;
DataRow[] filteredRows = activeMeasurements.Select("SignalID = '" + signalID.ToString() + "'");
if (filteredRows.Length > 0)
{
DataRow row = filteredRows[0];
MeasurementKey key = MeasurementKey.LookUpOrCreate(signalID, row["ID"].ToNonNullString(MeasurementKey.Undefined.ToString()));
m_reference.TryAdd(signalIndex.Key, key);
}
}
m_unauthorizedSignalIDs = remoteCache.UnauthorizedSignalIDs;
}
else
{
// Just use remote signal index cache as-is if no local configuration exists
m_reference = remoteCache.Reference;
m_unauthorizedSignalIDs = remoteCache.UnauthorizedSignalIDs;
}
}
#endregion
#region [ Properties ]
///
/// Gets or sets the based subscriber ID of this .
///
public Guid SubscriberID
{
get
{
return m_subscriberID;
}
set
{
m_subscriberID = value;
}
}
///
/// Gets or sets integer signal index cross reference dictionary.
///
public ConcurrentDictionary Reference
{
get
{
return m_reference;
}
set
{
m_reference = value;
IndexedArray signalIDCache = new IndexedArray(-1);
foreach (var pair in value)
{
signalIDCache[pair.Value.RuntimeID] = pair.Key;
}
m_signalIDCache = signalIDCache;
}
}
///
/// Gets reference to array of requested input measurement signal IDs that were authorized.
///
public Guid[] AuthorizedSignalIDs
{
get
{
return m_reference.Select(kvp => kvp.Value.SignalID).ToArray();
}
}
///
/// Gets or sets reference to array of requested input measurement signal IDs that were unauthorized.
///
public Guid[] UnauthorizedSignalIDs
{
get
{
return m_unauthorizedSignalIDs;
}
set
{
m_unauthorizedSignalIDs = value;
}
}
///
/// Gets the current maximum integer signal index.
///
public ushort MaximumIndex
{
get
{
if (m_reference.Count == 0)
return 0;
return (ushort)(m_reference.Max(kvp => kvp.Key) + 1);
}
}
///
/// Gets or sets character encoding used to convert strings to binary.
///
public Encoding Encoding
{
get
{
return m_encoding;
}
set
{
m_encoding = value;
}
}
///
/// Gets the length of the binary image.
///
public int BinaryLength
{
get
{
int binaryLength = 0;
if ((object)m_encoding == null)
throw new InvalidOperationException("Attempt to get binary length of signal index cache without setting a character encoding.");
// Byte size of cache
binaryLength += 4;
// Subscriber ID
binaryLength += 16;
// Number of references
binaryLength += 4;
// Each reference
binaryLength += m_reference.Sum(kvp => 2 + 16 + 4 + m_encoding.GetByteCount(kvp.Value.Source) + 4);
// Number of unauthorized IDs
binaryLength += 4;
// Each unauthorized ID
binaryLength += 16 * (m_unauthorizedSignalIDs ?? new Guid[0]).Length;
return binaryLength;
}
}
#endregion
#region [ Methods ]
///
/// Gets runtime signal index for given signal ID.
///
/// The used to lookup associated runtime signal index.
/// Runtime signal index for given .
public ushort GetSignalIndex(MeasurementKey key)
{
int value = m_signalIDCache[key.RuntimeID];
if (value < 0)
return ushort.MaxValue;
return (ushort)value;
}
///
/// Generates binary image of the and copies it into the given buffer, for bytes.
///
/// Buffer used to hold generated binary image of the source object.
/// 0-based starting index in the to start writing.
/// The number of bytes written to the .
public int GenerateBinaryImage(byte[] buffer, int startIndex)
{
Guid[] unauthorizedSignalIDs = m_unauthorizedSignalIDs ?? new Guid[0];
int binaryLength = BinaryLength;
int offset = startIndex;
byte[] bigEndianBuffer;
byte[] unicodeBuffer;
if ((object)m_encoding == null)
throw new InvalidOperationException("Attempt to generate binary image of signal index cache without setting a character encoding.");
buffer.ValidateParameters(startIndex, binaryLength);
// Byte size of cache
bigEndianBuffer = BigEndian.GetBytes(binaryLength);
Buffer.BlockCopy(bigEndianBuffer, 0, buffer, offset, bigEndianBuffer.Length);
offset += bigEndianBuffer.Length;
// Subscriber ID
bigEndianBuffer = EndianOrder.BigEndian.GetBytes(m_subscriberID);
Buffer.BlockCopy(bigEndianBuffer, 0, buffer, offset, bigEndianBuffer.Length);
offset += bigEndianBuffer.Length;
// Number of references
bigEndianBuffer = BigEndian.GetBytes(m_reference.Count);
Buffer.BlockCopy(bigEndianBuffer, 0, buffer, offset, bigEndianBuffer.Length);
offset += bigEndianBuffer.Length;
foreach (KeyValuePair kvp in m_reference)
{
// Signal index
bigEndianBuffer = BigEndian.GetBytes(kvp.Key);
Buffer.BlockCopy(bigEndianBuffer, 0, buffer, offset, bigEndianBuffer.Length);
offset += bigEndianBuffer.Length;
// Signal ID
bigEndianBuffer = EndianOrder.BigEndian.GetBytes(kvp.Value.SignalID);
Buffer.BlockCopy(bigEndianBuffer, 0, buffer, offset, bigEndianBuffer.Length);
offset += bigEndianBuffer.Length;
// Source
unicodeBuffer = m_encoding.GetBytes(kvp.Value.Source);
bigEndianBuffer = BigEndian.GetBytes(unicodeBuffer.Length);
Buffer.BlockCopy(bigEndianBuffer, 0, buffer, offset, bigEndianBuffer.Length);
offset += bigEndianBuffer.Length;
Buffer.BlockCopy(unicodeBuffer, 0, buffer, offset, unicodeBuffer.Length);
offset += unicodeBuffer.Length;
// ID
bigEndianBuffer = BigEndian.GetBytes((uint)kvp.Value.ID);
Buffer.BlockCopy(bigEndianBuffer, 0, buffer, offset, bigEndianBuffer.Length);
offset += bigEndianBuffer.Length;
}
// Number of unauthorized IDs
bigEndianBuffer = BigEndian.GetBytes(unauthorizedSignalIDs.Length);
Buffer.BlockCopy(bigEndianBuffer, 0, buffer, offset, bigEndianBuffer.Length);
offset += bigEndianBuffer.Length;
foreach (Guid signalID in unauthorizedSignalIDs)
{
// Unauthorized ID
bigEndianBuffer = EndianOrder.BigEndian.GetBytes(signalID);
Buffer.BlockCopy(bigEndianBuffer, 0, buffer, offset, bigEndianBuffer.Length);
offset += bigEndianBuffer.Length;
}
return binaryLength;
}
///
/// Initializes the by parsing the specified containing a binary image.
///
/// Buffer containing binary image to parse.
/// 0-based starting index in the to start parsing.
/// Valid number of bytes within to read from .
/// The number of bytes used for initialization in the (i.e., the number of bytes parsed).
public int ParseBinaryImage(byte[] buffer, int startIndex, int length)
{
int binaryLength;
int offset = startIndex;
int referenceCount;
ushort signalIndex;
Guid signalID;
int sourceSize;
string source;
uint id;
int unauthorizedIDCount;
if ((object)m_encoding == null)
throw new InvalidOperationException("Attempt to parse binary image of signal index cache without setting a character encoding.");
buffer.ValidateParameters(startIndex, length);
if (length < 4)
return 0;
// Byte size of cache
binaryLength = BigEndian.ToInt32(buffer, offset);
offset += 4;
if (length < binaryLength)
return 0;
// We know we have enough data so we can empty the reference cache
m_reference.Clear();
// Subscriber ID
m_subscriberID = EndianOrder.BigEndian.ToGuid(buffer, offset);
offset += 16;
// Number of references
referenceCount = BigEndian.ToInt32(buffer, offset);
offset += 4;
for (int i = 0; i < referenceCount; i++)
{
// Signal index
signalIndex = BigEndian.ToUInt16(buffer, offset);
offset += 2;
// Signal ID
signalID = EndianOrder.BigEndian.ToGuid(buffer, offset);
offset += 16;
// Source
sourceSize = BigEndian.ToInt32(buffer, offset);
offset += 4;
source = m_encoding.GetString(buffer, offset, sourceSize);
offset += sourceSize;
// ID
id = BigEndian.ToUInt32(buffer, offset);
offset += 4;
m_reference[signalIndex] = MeasurementKey.LookUpOrCreate(signalID, source, id);
}
// Number of unauthorized IDs
unauthorizedIDCount = BigEndian.ToInt32(buffer, offset);
m_unauthorizedSignalIDs = new Guid[unauthorizedIDCount];
offset += 4;
for (int i = 0; i < unauthorizedIDCount; i++)
{
// Unauthorized ID
m_unauthorizedSignalIDs[i] = EndianOrder.BigEndian.ToGuid(buffer, offset);
offset += 16;
}
return binaryLength;
}
#endregion
}
}