564 lines · 25.3 KB · C#
RawDownload
//******************************************************************************************************//  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 NonReadonlyMemberInGetHashCodenamespace GSF.PhasorProtocols{    /// <summary>    /// Represents the protocol independent common implementation of any frame of data that can be sent or received.    /// </summary>    /// <remarks>    /// <para>    /// This phasor protocol implementation defines a "frame" as a collection of cells (logical units of data).    /// For example, a <see cref="DataCellBase"/> could be defined as a PMU within a frame of data, a <see cref="DataFrameBase"/>    /// (derived from <see cref="ChannelFrameBase{T}"/>), that contains multiple PMU's coming from a PDC.    /// </para>    /// <para>    /// This class implements the <see cref="IFrame"/> interface so it can be cooperatively integrated into measurement concentration.    /// For more information see the <see cref="ConcentratorBase"/> class.    /// </para>    /// </remarks>    /// <typeparam name="T">Specific <see cref="IChannelCell"/> type that the <see cref="ChannelFrameBase{T}"/> contains.</typeparam>    [Serializable]    public abstract class ChannelFrameBase<T> : ChannelBase, IChannelFrame<T> 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<T> 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 ]         /// <summary>        /// Creates a new <see cref="ChannelFrameBase{T}"/> from specified parameters.        /// </summary>        /// <param name="idCode">The ID code of this <see cref="ChannelFrameBase{T}"/>.</param>        /// <param name="cells">The reference to the collection of cells for this <see cref="ChannelFrameBase{T}"/>.</param>        /// <param name="timestamp">The exact timestamp, in <see cref="Ticks"/>, of the data represented by this <see cref="ChannelFrameBase{T}"/>.</param>        protected ChannelFrameBase(ushort idCode, IChannelCellCollection<T> 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<MeasurementKey, IMeasurement>();            m_sortedMeasurements = -1;        }         /// <summary>        /// Creates a new <see cref="ChannelFrameBase{T}"/> from serialization parameters.        /// </summary>        /// <param name="info">The <see cref="SerializationInfo"/> with populated with data.</param>        /// <param name="context">The source <see cref="StreamingContext"/> for this deserialization.</param>        protected ChannelFrameBase(SerializationInfo info, StreamingContext context)        {            // Deserialize key frame elements...            m_idCode = info.GetUInt16("idCode");            m_cells = (IChannelCellCollection<T>)info.GetValue("cells", typeof(IChannelCellCollection<T>));            m_timestamp = info.GetInt64("timestamp");            m_lifespan = ShortTime.Now;            m_trustHeaderLength = true;            m_validateCheckSum = true;            Measurements = new ConcurrentDictionary<MeasurementKey, IMeasurement>();            m_sortedMeasurements = -1;        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets the <see cref="FundamentalFrameType"/> for this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        public abstract FundamentalFrameType FrameType { get; }         /// <summary>        /// Gets or sets the data source identifier for this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        public virtual SourceChannel Source        {            get => m_source;            set => m_source = value;        }         /// <summary>        /// Gets flag that determines if frame image can be queued for publication or should be processed immediately.        /// </summary>        /// <remarks>        /// 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.        /// </remarks>        public virtual bool AllowQueuedPublication => true;         /// <summary>        /// Gets the strongly-typed reference to the collection of cells for this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        public virtual IChannelCellCollection<T> Cells => m_cells;         // Gets the simple object reference to the cell collection to satisfy IChannelFrame.Cells        object IChannelFrame.Cells => m_cells;         /// <summary>        /// Keyed measurements in this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        /// <remarks>        /// Represents a dictionary of measurements, keyed by <see cref="MeasurementKey"/>.        /// </remarks>        public ConcurrentDictionary<MeasurementKey, IMeasurement> Measurements { get; }         /// <summary>        /// Gets or sets the ID code of this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        public virtual ushort IDCode        {            get => m_idCode;            set => m_idCode = value;        }         /// <summary>        /// Gets or sets exact timestamp, in ticks, of the data represented by this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        /// <remarks>        /// The value of this property represents the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001.        /// </remarks>        public virtual Ticks Timestamp        {            get => m_timestamp;            set => m_timestamp = value;        }         /// <summary>        /// Gets the life-span of this <see cref="ChannelFrameBase{T}"/> since its creation.        /// </summary>        public virtual ShortTime Lifespan => m_lifespan;         /// <summary>        /// Gets timestamp, in ticks, of when this <see cref="ChannelFrameBase{T}"/> was created.        /// </summary>        public virtual Ticks CreatedTimestamp => m_lifespan.UtcTime.Ticks;         /// <summary>        /// Gets UNIX based time representation of the ticks of this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        public virtual UnixTimeTag TimeTag => new(Timestamp);         /// <summary>        /// Gets or sets the parsing state for the this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        public new virtual IChannelFrameParsingState<T> State        {            get => base.State as IChannelFrameParsingState<T>;            set            {                base.State = value;                 if (value is null)                    return;                 TrustHeaderLength = value.TrustHeaderLength;                ValidateCheckSum = value.ValidateCheckSum;            }        }         /// <summary>        /// Gets or sets reference to last <see cref="IMeasurement"/> that was sorted into this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        /// <remarks>        /// This value is used to help monitor slow moving measurements that are being sorted into the <see cref="ChannelFrameBase{T}"/>.        /// </remarks>        public IMeasurement LastSortedMeasurement        {            get => m_lastSortedMeasurement;            set => m_lastSortedMeasurement = value;        }         /// <summary>        /// Gets or sets published state of this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        public bool Published        {            get => m_published;            set => m_published = value;        }         /// <summary>        /// Gets or sets total number of measurements that have been sorted into this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        public int SortedMeasurements        {            get => m_sortedMeasurements == -1 ? Measurements.Count : m_sortedMeasurements;            set => m_sortedMeasurements = value;        }         /// <summary>        /// Gets the length of the <see cref="BinaryImage"/>.        /// </summary>        /// <remarks>        /// This property is overridden so the length can be extended to include a checksum.        /// </remarks>        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;            }        }         /// <summary>        /// Gets the binary image of this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        /// <remarks>        /// This property is overridden to include a checksum in the image.        /// </remarks>        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;            }        }         /// <summary>        /// Gets or sets flag that determines if header lengths should be trusted over parsed byte count.        /// </summary>        public virtual bool TrustHeaderLength        {            get => m_trustHeaderLength;            set => m_trustHeaderLength = value;        }         /// <summary>        /// Gets or sets flag that determines if frame's check-sum should be validated - this should normally be left as <c>true</c>.        /// </summary>        public virtual bool ValidateCheckSum        {            get => m_validateCheckSum;            set => m_validateCheckSum = value;        }         /// <summary>        /// Gets calculated checksum for this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        public ushort Checksum        {            get            {                byte[] buffer = BinaryImage;                return BigEndian.ToUInt16(buffer, buffer.Length - 2);            }        }         /// <summary>        /// Gets the length of the <see cref="BodyImage"/>.        /// </summary>        /// <remarks>        /// The length of the <see cref="ChannelFrameBase{T}"/> body image is the combined length of all the <see cref="Cells"/>.        /// </remarks>        protected override int BodyLength => m_cells.BinaryLength;         /// <summary>        /// Gets the binary body image of this <see cref="ChannelFrameBase{T}"/>.        /// </summary>        /// <remarks>        /// The body image of the <see cref="ChannelFrameBase{T}"/> is the combined images of all the <see cref="Cells"/>.        /// </remarks>        protected override byte[] BodyImage => m_cells.BinaryImage();         /// <summary>        /// Gets the parsed binary length derived from the parsing state, if any.        /// </summary>        protected int ParsedBinaryLength { get; private set; }         /// <summary>        /// <see cref="Dictionary{TKey,TValue}"/> of string based property names and values for the <see cref="ChannelFrameBase{T}"/> object.        /// </summary>        public override Dictionary<string, string> Attributes        {            get            {                Dictionary<string, string> 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 ]         /// <summary>        /// Parses the binary image.        /// </summary>        /// <param name="buffer">Binary image to parse.</param>        /// <param name="startIndex">Start index into <paramref name="buffer"/> to begin parsing.</param>        /// <param name="length">Length of valid data within <paramref name="buffer"/>.</param>        /// <returns>The length of the data that was parsed.</returns>        /// <remarks>        /// This method is overridden to validate the checksum in the <see cref="ChannelFrameBase{T}"/>.        /// </remarks>        /// <exception cref="InvalidOperationException">Invalid binary image detected - check sum did not match.</exception>        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;        }         /// <summary>        /// Parses the binary body image.        /// </summary>        /// <param name="buffer">Binary image to parse.</param>        /// <param name="startIndex">Start index into <paramref name="buffer"/> to begin parsing.</param>        /// <param name="length">Length of valid data within <paramref name="buffer"/>.</param>        /// <returns>The length of the data that was parsed.</returns>        /// <remarks>        /// The body image of the <see cref="ChannelFrameBase{T}"/> is parsed to create a collection of <see cref="Cells"/>.        /// </remarks>        protected override int ParseBodyImage(byte[] buffer, int startIndex, int length)        {            IChannelFrameParsingState<T> 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;        }         /// <summary>        /// Determines if checksum in the <paramref name="buffer"/> is valid.        /// </summary>        /// <param name="buffer">Buffer image to validate.</param>        /// <param name="startIndex">Start index into <paramref name="buffer"/> to perform checksum.</param>        /// <returns>Flag that determines if checksum over <paramref name="buffer"/> is valid.</returns>        /// <remarks>        /// Default implementation expects 2-byte big-endian ordered checksum. Override method if protocol checksum        /// implementation is different.        /// </remarks>        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;        }         /// <summary>        /// Appends checksum onto <paramref name="buffer"/> starting at <paramref name="startIndex"/>.        /// </summary>        /// <param name="buffer">Buffer image on which to append checksum.</param>        /// <param name="startIndex">Index into <paramref name="buffer"/> where checksum should be appended.</param>        /// <remarks>        /// 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.        /// </remarks>        protected virtual void AppendChecksum(byte[] buffer, int startIndex)        {            BigEndian.CopyBytes(CalculateChecksum(buffer, 0, startIndex), buffer, startIndex);        }         /// <summary>        /// Calculates checksum of given <paramref name="buffer"/>.        /// </summary>        /// <param name="buffer">Buffer image over which to calculate checksum.</param>        /// <param name="offset">Start index into <paramref name="buffer"/> to calculate checksum.</param>        /// <param name="length">Length of data within <paramref name="buffer"/> to calculate checksum.</param>        /// <returns>Checksum over specified portion of <paramref name="buffer"/>.</returns>        /// <remarks>        /// Override with needed checksum calculation for particular protocol.        /// <example>        /// This example provides a CRC-CCITT checksum:        /// <code>        /// 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);        /// }        /// </code>        /// </example>        /// </remarks>        protected abstract ushort CalculateChecksum(byte[] buffer, int offset, int length);         /// <summary>        /// Compares the <see cref="ChannelFrameBase{T}"/> with an <see cref="IFrame"/>.        /// </summary>        /// <param name="other">The <see cref="IFrame"/> to compare with the current <see cref="ChannelFrameBase{T}"/>.</param>        /// <returns>A 32-bit signed integer that indicates the relative order of the objects being compared.</returns>        /// <remarks>This frame implementation compares itself by timestamp.</remarks>        public virtual int CompareTo(IFrame other)        {            // We sort frames by timestamp            return m_timestamp.CompareTo(other.Timestamp);        }         /// <summary>        /// Compares the <see cref="ChannelFrameBase{T}"/> with the specified <see cref="Object"/>.        /// </summary>        /// <param name="obj">The <see cref="Object"/> to compare with the current <see cref="ChannelFrameBase{T}"/>.</param>        /// <returns>A 32-bit signed integer that indicates the relative order of the objects being compared.</returns>

Lines 1–500 of 564.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t