//******************************************************************************************************
// 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
}
}