//******************************************************************************************************
// ChannelFrameBase.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:
// ----------------------------------------------------------------------------------------------------
// 01/14/2005 - J. Ritchie Carroll
// Generated original version of source code.
// 09/15/2009 - Stephen C. Wills
// Added new header and license agreement.
// 12/17/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.Serialization;
using GSF.Parsing;
using GSF.TimeSeries;
// ReSharper disable NonReadonlyMemberInGetHashCode
namespace GSF.PhasorProtocols
{
///
/// Represents the protocol independent common implementation of any frame of data that can be sent or received.
///
///
///
/// This phasor protocol implementation defines a "frame" as a collection of cells (logical units of data).
/// For example, a could be defined as a PMU within a frame of data, a
/// (derived from ), that contains multiple PMU's coming from a PDC.
///
///
/// This class implements the interface so it can be cooperatively integrated into measurement concentration.
/// For more information see the class.
///
///
/// Specific type that the contains.
[Serializable]
public abstract class ChannelFrameBase : ChannelBase, IChannelFrame where T : IChannelCell
{
#region [ Members ]
// Fields
private ushort m_idCode; // Numeric identifier of this frame of data (e.g., ID code of the PDC)
private readonly IChannelCellCollection m_cells; // Collection of "cells" within this frame of data (e.g., PMU's in the PDC frame)
private Ticks m_timestamp; // Time, represented as 100-nanosecond ticks, of this frame of data
private readonly ShortTime m_lifespan; // Elapsed time since creation of this frame of data
private SourceChannel m_source; // Defines source channel (e.g., data or command) for channel frame
private bool m_trustHeaderLength; // Determines if parsed header lengths should be trusted (normally true)
private bool m_validateCheckSum; // Allows bypass of check-sum validation if devices are behaving badly
private bool m_published; // Determines if this frame of data has been published (IFrame.Published)
private int m_sortedMeasurements; // Total measurements published into this frame (IFrame.SortedMeasurements)
private IMeasurement m_lastSortedMeasurement; // Last measurement sorted into this frame (IFrame.LastSortedMeasurement)
#endregion
#region [ Constructors ]
///
/// Creates a new from specified parameters.
///
/// The ID code of this .
/// The reference to the collection of cells for this .
/// The exact timestamp, in , of the data represented by this .
protected ChannelFrameBase(ushort idCode, IChannelCellCollection cells, Ticks timestamp)
{
m_idCode = idCode;
m_cells = cells;
m_timestamp = timestamp;
m_lifespan = ShortTime.Now;
m_trustHeaderLength = true;
m_validateCheckSum = true;
Measurements = new ConcurrentDictionary();
m_sortedMeasurements = -1;
}
///
/// Creates a new from serialization parameters.
///
/// The with populated with data.
/// The source for this deserialization.
protected ChannelFrameBase(SerializationInfo info, StreamingContext context)
{
// Deserialize key frame elements...
m_idCode = info.GetUInt16("idCode");
m_cells = (IChannelCellCollection)info.GetValue("cells", typeof(IChannelCellCollection));
m_timestamp = info.GetInt64("timestamp");
m_lifespan = ShortTime.Now;
m_trustHeaderLength = true;
m_validateCheckSum = true;
Measurements = new ConcurrentDictionary();
m_sortedMeasurements = -1;
}
#endregion
#region [ Properties ]
///
/// Gets the for this .
///
public abstract FundamentalFrameType FrameType { get; }
///
/// Gets or sets the data source identifier for this .
///
public virtual SourceChannel Source
{
get => m_source;
set => m_source = value;
}
///
/// Gets flag that determines if frame image can be queued for publication or should be processed immediately.
///
///
/// Some frames, e.g., a configuration or key frame, may be critical to processing of other frames. In this
/// case, these types of frames should be published immediately so that subsequent frame parsing can have
/// access to needed critical information. All other frames are assumed to be queued by default.
///
public virtual bool AllowQueuedPublication => true;
///
/// Gets the strongly-typed reference to the collection of cells for this .
///
public virtual IChannelCellCollection Cells => m_cells;
// Gets the simple object reference to the cell collection to satisfy IChannelFrame.Cells
object IChannelFrame.Cells => m_cells;
///
/// Keyed measurements in this .
///
///
/// Represents a dictionary of measurements, keyed by .
///
public ConcurrentDictionary Measurements { get; }
///
/// Gets or sets the ID code of this .
///
public virtual ushort IDCode
{
get => m_idCode;
set => m_idCode = value;
}
///
/// Gets or sets exact timestamp, in ticks, of the data represented by this .
///
///
/// The value of this property represents the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001.
///
public virtual Ticks Timestamp
{
get => m_timestamp;
set => m_timestamp = value;
}
///
/// Gets the life-span of this since its creation.
///
public virtual ShortTime Lifespan => m_lifespan;
///
/// Gets timestamp, in ticks, of when this was created.
///
public virtual Ticks CreatedTimestamp => m_lifespan.UtcTime.Ticks;
///
/// Gets UNIX based time representation of the ticks of this .
///
public virtual UnixTimeTag TimeTag => new(Timestamp);
///
/// Gets or sets the parsing state for the this .
///
public new virtual IChannelFrameParsingState State
{
get => base.State as IChannelFrameParsingState;
set
{
base.State = value;
if (value is null)
return;
TrustHeaderLength = value.TrustHeaderLength;
ValidateCheckSum = value.ValidateCheckSum;
}
}
///
/// Gets or sets reference to last that was sorted into this .
///
///
/// This value is used to help monitor slow moving measurements that are being sorted into the .
///
public IMeasurement LastSortedMeasurement
{
get => m_lastSortedMeasurement;
set => m_lastSortedMeasurement = value;
}
///
/// Gets or sets published state of this .
///
public bool Published
{
get => m_published;
set => m_published = value;
}
///
/// Gets or sets total number of measurements that have been sorted into this .
///
public int SortedMeasurements
{
get => m_sortedMeasurements == -1 ? Measurements.Count : m_sortedMeasurements;
set => m_sortedMeasurements = value;
}
///
/// Gets the length of the .
///
///
/// This property is overridden so the length can be extended to include a checksum.
///
public override int BinaryLength
{
get
{
// We override normal binary length so we can extend length to include checksum.
// Also, if frame length was parsed from stream header - we use that length
// instead of the calculated length...
if (ParsedBinaryLength > 0)
return ParsedBinaryLength;
return 2 + base.BinaryLength;
}
}
///
/// Gets the binary image of this .
///
///
/// This property is overridden to include a checksum in the image.
///
public override byte[] BinaryImage
{
get
{
// We override normal binary image to include checksum
byte[] buffer = new byte[BinaryLength];
int index = 0;
// Copy in base image
base.BinaryImage.CopyImage(buffer, ref index, base.BinaryLength);
// Add check sum
AppendChecksum(buffer, index);
return buffer;
}
}
///
/// Gets or sets flag that determines if header lengths should be trusted over parsed byte count.
///
public virtual bool TrustHeaderLength
{
get => m_trustHeaderLength;
set => m_trustHeaderLength = value;
}
///
/// Gets or sets flag that determines if frame's check-sum should be validated - this should normally be left as true.
///
public virtual bool ValidateCheckSum
{
get => m_validateCheckSum;
set => m_validateCheckSum = value;
}
///
/// Gets calculated checksum for this .
///
public ushort Checksum
{
get
{
byte[] buffer = BinaryImage;
return BigEndian.ToUInt16(buffer, buffer.Length - 2);
}
}
///
/// Gets the length of the .
///
///
/// The length of the body image is the combined length of all the .
///
protected override int BodyLength => m_cells.BinaryLength;
///
/// Gets the binary body image of this .
///
///
/// The body image of the is the combined images of all the .
///
protected override byte[] BodyImage => m_cells.BinaryImage();
///
/// Gets the parsed binary length derived from the parsing state, if any.
///
protected int ParsedBinaryLength { get; private set; }
///
/// of string based property names and values for the object.
///
public override Dictionary Attributes
{
get
{
Dictionary baseAttributes = base.Attributes;
baseAttributes.Add("Total Cells", Cells is null ? "null" : Cells.Count.ToString());
baseAttributes.Add("Fundamental Frame Type", $"{(int)FrameType}: {FrameType}");
baseAttributes.Add("ID Code", IDCode.ToString());
baseAttributes.Add("Published", Published.ToString());
baseAttributes.Add("Ticks", ((long)Timestamp).ToString());
baseAttributes.Add("Timestamp", ((DateTime)Timestamp).ToString("yyyy-MM-dd HH:mm:ss.fff"));
baseAttributes.Add("Trusting Header Length", TrustHeaderLength.ToString());
baseAttributes.Add("Validating Check-sum", ValidateCheckSum.ToString());
return baseAttributes;
}
}
#endregion
#region [ Methods ]
///
/// Parses the binary image.
///
/// Binary image to parse.
/// Start index into to begin parsing.
/// Length of valid data within .
/// The length of the data that was parsed.
///
/// This method is overridden to validate the checksum in the .
///
/// Invalid binary image detected - check sum did not match.
public override int ParseBinaryImage(byte[] buffer, int startIndex, int length)
{
// We use data length parsed from data stream if available - in many cases we'll have to as we won't enough
// information about cell contents at this early parsing stage
ParsedBinaryLength = State.ParsedBinaryLength;
// Normal binary image parsing is overridden for a frame so that checksum can be validated
if (!ChecksumIsValid(buffer, startIndex))
{
// If user selects incorrect protocol, image may be very large - so we don't log the image
// byte[] binaryImageErr = buffer.BlockCopy(startIndex, length);
// + BitConverter.ToString(binaryImageErr)
throw new CrcException($"Invalid binary image detected - check sum of {GetType().Name} did not match");
}
if (TrustHeaderLength)
{
// Normally one would expect image size returned should match parsed image size - but it's possible that these may
// differ, so we assume the parsed length header length is better of the two values. This can accommodate the case
// where some devices add padding to the end of the frame.
base.ParseBinaryImage(buffer, startIndex, length);
return State.ParsedBinaryLength;
}
// Include 2 bytes for CRC in returned parsed length
return base.ParseBinaryImage(buffer, startIndex, length) + 2;
}
///
/// Parses the binary body image.
///
/// Binary image to parse.
/// Start index into to begin parsing.
/// Length of valid data within .
/// The length of the data that was parsed.
///
/// The body image of the is parsed to create a collection of .
///
protected override int ParseBodyImage(byte[] buffer, int startIndex, int length)
{
IChannelFrameParsingState state = State;
int index = startIndex;
// Parse all frame cells
for (int x = 0; x < state.CellCount; x++)
{
T cell = state.CreateNewCell(this, state, x, buffer, index, out int parsedLength);
m_cells.Add(cell);
index += parsedLength;
}
return index - startIndex;
}
///
/// Determines if checksum in the is valid.
///
/// Buffer image to validate.
/// Start index into to perform checksum.
/// Flag that determines if checksum over is valid.
///
/// Default implementation expects 2-byte big-endian ordered checksum. Override method if protocol checksum
/// implementation is different.
///
protected virtual bool ChecksumIsValid(byte[] buffer, int startIndex)
{
if (ValidateCheckSum)
{
int sumLength = BinaryLength - 2;
return BigEndian.ToUInt16(buffer, startIndex + sumLength) == CalculateChecksum(buffer, startIndex, sumLength);
}
return true;
}
///
/// Appends checksum onto starting at .
///
/// Buffer image on which to append checksum.
/// Index into where checksum should be appended.
///
/// Default implementation encodes checksum in big-endian order and expects buffer size large enough to accommodate
/// 2-byte checksum representation. Override method if protocol expectations are different.
///
protected virtual void AppendChecksum(byte[] buffer, int startIndex)
{
BigEndian.CopyBytes(CalculateChecksum(buffer, 0, startIndex), buffer, startIndex);
}
///
/// Calculates checksum of given .
///
/// Buffer image over which to calculate checksum.
/// Start index into to calculate checksum.
/// Length of data within to calculate checksum.
/// Checksum over specified portion of .
///
/// Override with needed checksum calculation for particular protocol.
///
/// This example provides a CRC-CCITT checksum:
///
/// using GSF.IO.Checksums;
///
/// protected override ushort CalculateChecksum(byte[] buffer, int offset, int length)
/// {
/// // Return calculated CRC-CCITT over given buffer...
/// return buffer.CrcCCITTChecksum(offset, length);
/// }
///
///
///
protected abstract ushort CalculateChecksum(byte[] buffer, int offset, int length);
///
/// Compares the with an .
///
/// The to compare with the current .
/// A 32-bit signed integer that indicates the relative order of the objects being compared.
/// This frame implementation compares itself by timestamp.
public virtual int CompareTo(IFrame other)
{
// We sort frames by timestamp
return m_timestamp.CompareTo(other.Timestamp);
}
///
/// Compares the with the specified .
///
/// The to compare with the current .
/// A 32-bit signed integer that indicates the relative order of the objects being compared.
/// is not an .
/// This frame implementation compares itself by timestamp.
public virtual int CompareTo(object obj)
{
if (obj is IFrame other)
return CompareTo(other);
throw new ArgumentException("Frame can only be compared with other IFrames...");
}
///
/// Determines whether the specified is equal to the current .
///
/// The to compare with the current .
///
/// true if the specified is equal to the current ;
/// otherwise, false.
///
/// This frame implementation compares itself by timestamp.
public virtual bool Equals(IFrame other)
{
return CompareTo(other) == 0;
}
///
/// Determines whether the specified is equal to the current .
///
/// The to compare with the current .
///
/// true if the specified is equal to the current ;
/// otherwise, false.
///
/// is not an .
public override bool Equals(object obj)
{
if (obj is IFrame other)
return Equals(other);
return false;
}
///
/// Returns the hash code for this instance.
///
/// A 32-bit signed integer hash code.
public override int GetHashCode() =>
m_timestamp.GetHashCode();
///
/// Populates a with the data needed to serialize the target object.
///
/// The to populate with data.
/// The destination for this serialization.
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
// Add key frame elements for serialization...
info.AddValue("idCode", m_idCode);
info.AddValue("cells", m_cells, typeof(IChannelCellCollection));
info.AddValue("timestamp", (long)m_timestamp);
}
#endregion
}
}