452 lines · 22.9 KB · C#
RawDownload
//******************************************************************************************************//  FrameParserBase.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://  ----------------------------------------------------------------------------------------------------//  02/12/2007 - J. Ritchie Carroll//       Generated original version of source code.//  12/16/2008 - J. Ritchie Carroll//       Converted class to inherit from FrameImageParserBase.//  08/07/2009 - Josh L. Patterson//       Edited Comments.//  09/15/2009 - Stephen C. Wills//       Added new header and license agreement.//  05/25/2012 - J. Ritchie Carroll//       Fixed an issue with publication of the frame buffer image so that when connection tester//       serializes these frames they will not overlap when parsing large data sets.//  12/17/2012 - Starlynn Danyelle Gilliam//       Modified Header.////****************************************************************************************************** using System;using System.Text;using GSF.Collections;using GSF.Parsing; namespace GSF.PhasorProtocols{    /// <summary>    /// Represents a frame parser that defines the basic functionality for a protocol to parse a binary data stream and return the parsed data via events.    /// </summary>    /// <remarks>    /// Frame parsers are implemented as a write-only streams so that data can come from any source.<br/>    /// See <see cref="MultiSourceFrameImageParserBase{TSourceIdentifier,TTypeIdentifier,TOutputType}"/> for more detail.    /// Note that using the multi-source frame image parser base allows buffers to come in from different channels and still be    /// parsed correctly.    /// </remarks>    /// <typeparam name="TFrameIdentifier">Frame type identifier used to distinguish frames.</typeparam>    public abstract class FrameParserBase<TFrameIdentifier> : MultiSourceFrameImageParserBase<SourceChannel, TFrameIdentifier, ISupportSourceIdentifiableFrameImage<SourceChannel, TFrameIdentifier>>, IFrameParser    {        #region [ Members ]         // Events         /// <summary>        /// Occurs when any <see cref="IChannelFrame"/> has been received.        /// </summary>        /// <remarks>        /// <see cref="EventArgs{T1,T2}.Argument1"/> is the <see cref="IChannelFrame"/> that was received.        /// <see cref="EventArgs{T1,T2}.Argument2"/> is the flag to determine if frame publication should continue.        /// </remarks>        public event EventHandler<EventArgs<IChannelFrame, bool>> ReceivedChannelFrame;         // Derived classes will typically also expose events to provide instances to the protocol specific final derived channel frames         /// <summary>        /// Occurs when a <see cref="IConfigurationFrame"/> has been received.        /// </summary>        /// <remarks>        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="IConfigurationFrame"/> that was received.        /// </remarks>        public event EventHandler<EventArgs<IConfigurationFrame>> ReceivedConfigurationFrame;         /// <summary>        /// Occurs when a <see cref="IDataFrame"/> has been received.        /// </summary>        /// <remarks>        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="IDataFrame"/> that was received.        /// </remarks>        public event EventHandler<EventArgs<IDataFrame>> ReceivedDataFrame;         /// <summary>        /// Occurs when a <see cref="IHeaderFrame"/> has been received.        /// </summary>        /// <remarks>        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="IHeaderFrame"/> that was received.        /// </remarks>        public event EventHandler<EventArgs<IHeaderFrame>> ReceivedHeaderFrame;         /// <summary>        /// Occurs when a <see cref="ICommandFrame"/> has been received.        /// </summary>        /// <remarks>        /// <see cref="EventArgs{T}.Argument"/> is the <see cref="ICommandFrame"/> that was received.        /// <para>        /// Command frames are normally sent, not received, but there is nothing that prevents this.        /// </para>        /// </remarks>        public event EventHandler<EventArgs<ICommandFrame>> ReceivedCommandFrame;         /// <summary>        /// Occurs when an undetermined <see cref="IChannelFrame"/> has been received.        /// </summary>        /// <remarks>        /// <see cref="EventArgs{T}.Argument"/> is the undetermined <see cref="IChannelFrame"/> that was received.        /// </remarks>        public event EventHandler<EventArgs<IChannelFrame>> ReceivedUndeterminedFrame;         /// <summary>        /// Occurs when a frame image has been received.        /// </summary>        /// <remarks>        /// <see cref="EventArgs{T1,T2}.Argument1"/> is the <see cref="FundamentalFrameType"/> of the frame buffer image that was received.<br/>        /// <see cref="EventArgs{T1,T2}.Argument2"/> is the length of the frame image that was received.        /// </remarks>        public event EventHandler<EventArgs<FundamentalFrameType, int>> ReceivedFrameImage;         /// <summary>        /// Occurs when a frame image has been received, event includes buffer.        /// </summary>        /// <remarks>        /// <para>        /// <see cref="EventArgs{T1,T2,T3,T4}.Argument1"/> is the <see cref="FundamentalFrameType"/> of the frame buffer image that was received.<br/>        /// <see cref="EventArgs{T1,T2,T3,T4}.Argument2"/> is the buffer that contains the frame image that was received.<br/>        /// <see cref="EventArgs{T1,T2,T3,T4}.Argument3"/> is the offset into the buffer that contains the frame image that was received.<br/>        /// <see cref="EventArgs{T1,T2,T3,T4}.Argument4"/> is the length of data in the buffer that contains the frame image that was received.        /// </para>        /// <para>        /// Consumers should use the more efficient <see cref="ReceivedFrameImage"/> event if the buffer is not needed.        /// </para>        /// </remarks>        public event EventHandler<EventArgs<FundamentalFrameType, byte[], int, int>> ReceivedFrameBufferImage;         /// <summary>        /// Occurs when a device sends a notification that its configuration has changed.        /// </summary>        public event EventHandler ConfigurationChanged;         // Fields        private AsyncQueue<EventArgs<FundamentalFrameType, byte[], int, int>> m_frameImageQueue;        private bool m_disposed;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new <see cref="FrameParserBase{TypeIndentifier}"/>.        /// </summary>        /// <param name="checkSumValidationFrameTypes">Frame types that should perform check-sum validation; default to <see cref="CheckSumValidationFrameTypes.AllFrames"/></param>        /// <param name="trustHeaderLength">Determines if header lengths should be trusted over parsed byte count.</param>        protected FrameParserBase(CheckSumValidationFrameTypes checkSumValidationFrameTypes, bool trustHeaderLength)        {            // We attach to base class DataParsed event to automatically redirect and cast channel frames to their specific output events            DataParsed += base_DataParsed;            DuplicateTypeHandlerEncountered += base_DuplicateTypeHandlerEncountered;            OutputTypeNotFound += base_OutputTypeNotFound;             CheckSumValidationFrameTypes = checkSumValidationFrameTypes;            TrustHeaderLength = trustHeaderLength;        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets the number of redundant frames in each packet.        /// </summary>        /// <remarks>        /// <para>        /// This value is used when calculating statistics. It is assumed that for each        /// frame that is received, that frame will be included in the next <c>n</c>        /// packets, where <c>n</c> is the number of redundant frames per packet.        /// </para>        /// <para>        /// This base class returns 0, as most protocols do not support redundant frames.        /// </para>        /// </remarks>        public virtual int RedundantFramesPerPacket => 0;         /// <summary>        /// Gets or sets current <see cref="IConfigurationFrame"/> used for parsing <see cref="IDataFrame"/>'s encountered in the data stream from a device.        /// </summary>        /// <remarks>        /// If a <see cref="IConfigurationFrame"/> has been parsed, this will return a reference to the parsed frame. Consumer can manually assign a        /// <see cref="IConfigurationFrame"/> to start parsing data if one has not been encountered in the stream.        /// </remarks>        public abstract IConfigurationFrame ConfigurationFrame { get; set; }         /// <summary>        /// Gets or sets any connection specific <see cref="IConnectionParameters"/> that may be needed for parsing.        /// </summary>        public virtual IConnectionParameters ConnectionParameters { get; set; }         /// <summary>        /// Gets or sets flags that determine if check-sums for specified frames should be validated.        /// </summary>        /// <remarks>        /// It is expected that this will normally be set to <see cref="CheckSumValidationFrameTypes.AllFrames"/>.        /// </remarks>        public CheckSumValidationFrameTypes CheckSumValidationFrameTypes { get; set; }         /// <summary>        /// Gets flag based on <see cref="CheckSumValidationFrameTypes"/> property that determines if configuration frames are selected for check-sum validation.        /// </summary>        protected bool ValidateConfigurationFrameCheckSum => (CheckSumValidationFrameTypes & CheckSumValidationFrameTypes.ConfigurationFrame) > 0;         /// <summary>        /// Gets flag based on <see cref="CheckSumValidationFrameTypes"/> property that determines if data frames are selected for check-sum validation.        /// </summary>        protected bool ValidateDataFrameCheckSum => (CheckSumValidationFrameTypes & CheckSumValidationFrameTypes.DataFrame) > 0;         /// <summary>        /// Gets flag based on <see cref="CheckSumValidationFrameTypes"/> property that determines if header frames are selected for check-sum validation.        /// </summary>        protected bool ValidateHeaderFrameCheckSum => (CheckSumValidationFrameTypes & CheckSumValidationFrameTypes.HeaderFrame) > 0;         /// <summary>        /// Gets flag based on <see cref="CheckSumValidationFrameTypes"/> property that determines if command frames are selected for check-sum validation.        /// </summary>        protected bool ValidateCommandFrameCheckSum => (CheckSumValidationFrameTypes & CheckSumValidationFrameTypes.DataFrame) > 0;         /// <summary>        /// Gets or sets flag that determines if header lengths should be trusted over parsed byte count.        /// </summary>        /// <remarks>        /// It is expected that this will normally be left as <c>true</c>        /// </remarks>        public bool TrustHeaderLength { get; set; }         /// <summary>        /// Gets current descriptive status of the <see cref="FrameParserBase{TypeIndentifier}"/>.        /// </summary>        public override string Status        {            get            {                StringBuilder status = new();                 status.Append(base.Status);                status.AppendLine($"     Received config frame: {(ConfigurationFrame is null ? "No" : "Yes")}");                 if (ConfigurationFrame is not null)                {                    status.AppendLine($"   Devices in config frame: {ConfigurationFrame.Cells.Count:N0} total - ");                     foreach (IConfigurationCell cell in ConfigurationFrame.Cells)                        status.AppendLine($"               ({cell.IDCode:00000}) {cell.StationName.PadRight(16)}{(string.IsNullOrEmpty(cell.IDLabel) ? "" : $" [{ cell.IDLabel}]")}");                     status.AppendLine($"     Configured frame rate: {ConfigurationFrame.FrameRate}");                }                 status.AppendLine($"    Trusting header length: {TrustHeaderLength}");                status.AppendLine($"Check-sum validation types: {CheckSumValidationFrameTypes}");                status.AppendLine($"       Queued frame images: {m_frameImageQueue?.Count ?? 0:N0}");                 return status.ToString();            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Releases the unmanaged resources used by the <see cref="FrameParserBase{TypeIndentifier}"/> object and optionally releases the managed resources.        /// </summary>        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>        protected override void Dispose(bool disposing)        {            if (!m_disposed)            {                try                {                    if (disposing)                    {                        // Detach from base class events                        DataParsed -= base_DataParsed;                        DuplicateTypeHandlerEncountered -= base_DuplicateTypeHandlerEncountered;                        OutputTypeNotFound -= base_OutputTypeNotFound;                    }                }                finally                {                    m_disposed = true;          // Prevent duplicate dispose.                    base.Dispose(disposing);    // Call base class Dispose().                }            }        }         /// <summary>        /// Raises the <see cref="ReceivedConfigurationFrame"/> event.        /// </summary>        /// <param name="frame"><see cref="IConfigurationFrame"/> to send to <see cref="ReceivedConfigurationFrame"/> event.</param>        protected virtual void OnReceivedConfigurationFrame(IConfigurationFrame frame) =>             ReceivedConfigurationFrame?.Invoke(this, new EventArgs<IConfigurationFrame>(frame));         /// <summary>        /// Raises the <see cref="ReceivedDataFrame"/> event.        /// </summary>        /// <param name="frame"><see cref="IDataFrame"/> to send to <see cref="ReceivedDataFrame"/> event.</param>        protected virtual void OnReceivedDataFrame(IDataFrame frame) =>             ReceivedDataFrame?.Invoke(this, new EventArgs<IDataFrame>(frame));         /// <summary>        /// Raises the <see cref="ReceivedHeaderFrame"/> event.        /// </summary>        /// <param name="frame"><see cref="IHeaderFrame"/> to send to <see cref="ReceivedHeaderFrame"/> event.</param>        protected virtual void OnReceivedHeaderFrame(IHeaderFrame frame) =>             ReceivedHeaderFrame?.Invoke(this, new EventArgs<IHeaderFrame>(frame));         /// <summary>        /// Raises the <see cref="ReceivedCommandFrame"/> event.        /// </summary>        /// <param name="frame"><see cref="ICommandFrame"/> to send to <see cref="ReceivedCommandFrame"/> event.</param>        protected virtual void OnReceivedCommandFrame(ICommandFrame frame) =>             ReceivedCommandFrame?.Invoke(this, new EventArgs<ICommandFrame>(frame));         /// <summary>        /// Raises the <see cref="ReceivedUndeterminedFrame"/> event.        /// </summary>        /// <param name="frame"><see cref="IChannelFrame"/> to send to <see cref="ReceivedUndeterminedFrame"/> event.</param>        protected virtual void OnReceivedUndeterminedFrame(IChannelFrame frame) =>             ReceivedUndeterminedFrame?.Invoke(this, new EventArgs<IChannelFrame>(frame));         /// <summary>        /// Raises the <see cref="ReceivedFrameImage"/> and <see cref="ReceivedFrameBufferImage"/> event.        /// </summary>        /// <param name="frameType"><see cref="FundamentalFrameType"/> to send to <see cref="ReceivedFrameBufferImage"/> event.</param>        /// <param name="buffer">Frame buffer image to send to <see cref="ReceivedFrameBufferImage"/> event.</param>        /// <param name="offset">Offset into frame buffer image to send to <see cref="ReceivedFrameBufferImage"/> event.</param>        /// <param name="length">Length of data in frame buffer image to send to <see cref="ReceivedFrameBufferImage"/> event.</param>        protected virtual void OnReceivedFrameBufferImage(FundamentalFrameType frameType, byte[] buffer, int offset, int length)        {            // It is more light-weight for consumers to attach to the "ReceivedFrameImage" event if they don't need the buffer            ReceivedFrameImage?.Invoke(this, new EventArgs<FundamentalFrameType, int>(frameType, length));             if (ReceivedFrameBufferImage is null)                return;             // Since this event is called from an async socket operation, these events can be processed simultaneously, especially            // when the consuming event may take time to process this data (e.g., writing the frame to a capture file for replay),            // so we queue these events up for asynchronous serial processing            if (m_frameImageQueue is null)            {                m_frameImageQueue = new AsyncQueue<EventArgs<FundamentalFrameType, byte[], int, int>>                {                    ProcessItemFunction = frameImage => ReceivedFrameBufferImage(this, frameImage)                };                 m_frameImageQueue.ProcessException += (sender, e) => OnParsingException(e.Argument);            }             // We don't own the provided buffer and don't know what the consumer will do with it, so we create            // a copy of the relevant buffer segment and enqueue this for processing            byte[] bufferSegment = buffer.BlockCopy(offset, length);             if (OptimizationOptions.DisableAsyncQueueInProtocolParsing)            {                try                {                    ReceivedFrameBufferImage?.Invoke(this, new EventArgs<FundamentalFrameType, byte[], int, int>(frameType, bufferSegment, 0, bufferSegment.Length));                }                catch (Exception ex)                {                    OnParsingException(ex);                }            }            else            {                m_frameImageQueue.Enqueue(new EventArgs<FundamentalFrameType, byte[], int, int>(frameType, bufferSegment, 0, bufferSegment.Length));            }        }         /// <summary>        /// Raises the <see cref="ConfigurationChanged"/> event.        /// </summary>        protected virtual void OnConfigurationChanged() =>             ConfigurationChanged?.Invoke(this, EventArgs.Empty);         /// <summary>        /// Casts the parsed <see cref="IChannelFrame"/> to its specific implementation (i.e., <see cref="IDataFrame"/>, <see cref="IConfigurationFrame"/>, <see cref="ICommandFrame"/> or <see cref="IHeaderFrame"/>).        /// </summary>        /// <param name="frame"><see cref="IChannelFrame"/> that was parsed by <see cref="FrameImageParserBase{TTypeIdentifier,TOutputType}"/> that implements protocol specific common frame header interface.</param>        protected virtual void OnReceivedChannelFrame(IChannelFrame frame)        {            if (ReceivedChannelFrame is not null)            {                EventArgs<IChannelFrame, bool> args = new(frame, true);                                ReceivedChannelFrame(this, args);                 if (!args.Argument2)                    return;            }             switch (frame)            {                // Process frame types in order of likely occurrence                case IDataFrame dataFrame:                    // Frame was a data frame                    OnReceivedDataFrame(dataFrame);                    break;                case IConfigurationFrame configFrame:                    // Frame was a configuration frame                    OnReceivedConfigurationFrame(configFrame);                    break;                case IHeaderFrame headerFrame:                    // Frame was a header frame                    OnReceivedHeaderFrame(headerFrame);                    break;                case ICommandFrame commandFrame:                    // Frame was a command frame                    OnReceivedCommandFrame(commandFrame);                    break;                default:                    // Frame type was undetermined                    OnReceivedUndeterminedFrame(frame);                    break;            }        }         /// <summary>        /// Handles unknown frame types.        /// </summary>        /// <param name="frameType">Unknown frame ID.</param>        protected virtual void OnUnknownFrameTypeEncountered(TFrameIdentifier frameType) =>             OnParsingException(new InvalidOperationException($"WARNING: Encountered an undefined frame type identifier \"{frameType}\". Output was not parsed."));         // Handle reception of data from base class event "DataParsed". Note that by attaching to base class event instead of overriding        // OnDataParsed event raiser we allow frame implementations to control whether or not publication happens from a new thread pool        // thread or from existing parsing thread by simply overriding the AllowQueuedPublication boolean property. Normally configuration        // frames are published as soon as they are parsed to make sure needed parsing information is available as quickly as possible.        // All other frames are queued for processing by default to allow for better processor distribution of mapping/routing work load.        private void base_DataParsed(object sender, EventArgs<ISupportSourceIdentifiableFrameImage<SourceChannel, TFrameIdentifier>> e) =>             OnReceivedChannelFrame(e.Argument as IChannelFrame);         // Handles output type not found error from base class event "OutputTypeNotFound"        private void base_OutputTypeNotFound(object sender, EventArgs<TFrameIdentifier> e) =>             OnUnknownFrameTypeEncountered(e.Argument);         // Handles duplicate type handler encountered warning from base class event "DuplicateTypeHandlerEncountered"        private void base_DuplicateTypeHandlerEncountered(object sender, EventArgs<Type, TFrameIdentifier> e) =>             OnParsingException(new InvalidOperationException($"WARNING: Duplicate frame type identifier \"{e.Argument2}\" encountered for parsing type {e.Argument1.FullName} during initialization. Only the first defined type for this identifier will ever be parsed."));         #endregion    }}

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