//****************************************************************************************************** // 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 { /// /// 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. /// /// /// Frame parsers are implemented as a write-only streams so that data can come from any source.
/// See 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. ///
/// Frame type identifier used to distinguish frames. public abstract class FrameParserBase : MultiSourceFrameImageParserBase>, IFrameParser { #region [ Members ] // Events /// /// Occurs when any has been received. /// /// /// is the that was received. /// is the flag to determine if frame publication should continue. /// public event EventHandler> ReceivedChannelFrame; // Derived classes will typically also expose events to provide instances to the protocol specific final derived channel frames /// /// Occurs when a has been received. /// /// /// is the that was received. /// public event EventHandler> ReceivedConfigurationFrame; /// /// Occurs when a has been received. /// /// /// is the that was received. /// public event EventHandler> ReceivedDataFrame; /// /// Occurs when a has been received. /// /// /// is the that was received. /// public event EventHandler> ReceivedHeaderFrame; /// /// Occurs when a has been received. /// /// /// is the that was received. /// /// Command frames are normally sent, not received, but there is nothing that prevents this. /// /// public event EventHandler> ReceivedCommandFrame; /// /// Occurs when an undetermined has been received. /// /// /// is the undetermined that was received. /// public event EventHandler> ReceivedUndeterminedFrame; /// /// Occurs when a frame image has been received. /// /// /// is the of the frame buffer image that was received.
/// is the length of the frame image that was received. ///
public event EventHandler> ReceivedFrameImage; /// /// Occurs when a frame image has been received, event includes buffer. /// /// /// /// is the of the frame buffer image that was received.
/// is the buffer that contains the frame image that was received.
/// is the offset into the buffer that contains the frame image that was received.
/// is the length of data in the buffer that contains the frame image that was received. ///
/// /// Consumers should use the more efficient event if the buffer is not needed. /// ///
public event EventHandler> ReceivedFrameBufferImage; /// /// Occurs when a device sends a notification that its configuration has changed. /// public event EventHandler ConfigurationChanged; // Fields private AsyncQueue> m_frameImageQueue; private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new . /// /// Frame types that should perform check-sum validation; default to /// Determines if header lengths should be trusted over parsed byte count. 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 ] /// /// Gets the number of redundant frames in each packet. /// /// /// /// 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 n /// packets, where n is the number of redundant frames per packet. /// /// /// This base class returns 0, as most protocols do not support redundant frames. /// /// public virtual int RedundantFramesPerPacket => 0; /// /// Gets or sets current used for parsing 's encountered in the data stream from a device. /// /// /// If a has been parsed, this will return a reference to the parsed frame. Consumer can manually assign a /// to start parsing data if one has not been encountered in the stream. /// public abstract IConfigurationFrame ConfigurationFrame { get; set; } /// /// Gets or sets any connection specific that may be needed for parsing. /// public virtual IConnectionParameters ConnectionParameters { get; set; } /// /// Gets or sets flags that determine if check-sums for specified frames should be validated. /// /// /// It is expected that this will normally be set to . /// public CheckSumValidationFrameTypes CheckSumValidationFrameTypes { get; set; } /// /// Gets flag based on property that determines if configuration frames are selected for check-sum validation. /// protected bool ValidateConfigurationFrameCheckSum => (CheckSumValidationFrameTypes & CheckSumValidationFrameTypes.ConfigurationFrame) > 0; /// /// Gets flag based on property that determines if data frames are selected for check-sum validation. /// protected bool ValidateDataFrameCheckSum => (CheckSumValidationFrameTypes & CheckSumValidationFrameTypes.DataFrame) > 0; /// /// Gets flag based on property that determines if header frames are selected for check-sum validation. /// protected bool ValidateHeaderFrameCheckSum => (CheckSumValidationFrameTypes & CheckSumValidationFrameTypes.HeaderFrame) > 0; /// /// Gets flag based on property that determines if command frames are selected for check-sum validation. /// protected bool ValidateCommandFrameCheckSum => (CheckSumValidationFrameTypes & CheckSumValidationFrameTypes.DataFrame) > 0; /// /// Gets or sets flag that determines if header lengths should be trusted over parsed byte count. /// /// /// It is expected that this will normally be left as true /// public bool TrustHeaderLength { get; set; } /// /// Gets current descriptive status of the . /// 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 ] /// /// 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) { // 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(). } } } /// /// Raises the event. /// /// to send to event. protected virtual void OnReceivedConfigurationFrame(IConfigurationFrame frame) => ReceivedConfigurationFrame?.Invoke(this, new EventArgs(frame)); /// /// Raises the event. /// /// to send to event. protected virtual void OnReceivedDataFrame(IDataFrame frame) => ReceivedDataFrame?.Invoke(this, new EventArgs(frame)); /// /// Raises the event. /// /// to send to event. protected virtual void OnReceivedHeaderFrame(IHeaderFrame frame) => ReceivedHeaderFrame?.Invoke(this, new EventArgs(frame)); /// /// Raises the event. /// /// to send to event. protected virtual void OnReceivedCommandFrame(ICommandFrame frame) => ReceivedCommandFrame?.Invoke(this, new EventArgs(frame)); /// /// Raises the event. /// /// to send to event. protected virtual void OnReceivedUndeterminedFrame(IChannelFrame frame) => ReceivedUndeterminedFrame?.Invoke(this, new EventArgs(frame)); /// /// Raises the and event. /// /// to send to event. /// Frame buffer image to send to event. /// Offset into frame buffer image to send to event. /// Length of data in frame buffer image to send to event. 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(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> { 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(frameType, bufferSegment, 0, bufferSegment.Length)); } catch (Exception ex) { OnParsingException(ex); } } else { m_frameImageQueue.Enqueue(new EventArgs(frameType, bufferSegment, 0, bufferSegment.Length)); } } /// /// Raises the event. /// protected virtual void OnConfigurationChanged() => ConfigurationChanged?.Invoke(this, EventArgs.Empty); /// /// Casts the parsed to its specific implementation (i.e., , , or ). /// /// that was parsed by that implements protocol specific common frame header interface. protected virtual void OnReceivedChannelFrame(IChannelFrame frame) { if (ReceivedChannelFrame is not null) { EventArgs 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; } } /// /// Handles unknown frame types. /// /// Unknown frame ID. 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> e) => OnReceivedChannelFrame(e.Argument as IChannelFrame); // Handles output type not found error from base class event "OutputTypeNotFound" private void base_OutputTypeNotFound(object sender, EventArgs e) => OnUnknownFrameTypeEncountered(e.Argument); // Handles duplicate type handler encountered warning from base class event "DuplicateTypeHandlerEncountered" private void base_DuplicateTypeHandlerEncountered(object sender, EventArgs 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 } }