//******************************************************************************************************// MultiProtocolFrameParser.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:// ----------------------------------------------------------------------------------------------------// 03/16/2006 - J. Ritchie Carroll// Initial version of source generated.// 06/26/2006 - Pinal C. Patel// Changed out the socket code with TcpClient and UdpClient components from GSF.Communication.// 01/31/2007 - J. Ritchie Carroll// Added TCP "server" support to allow listening connections from devices that act as data// clients, e.g., F-NET devices.// 05/23/2007 - Pinal C. Patel// Added member variable 'm_clientConnectionAttempts' to track the number of attempts made for// connecting to the server since this information is no longer provided by the event raised by// any of the Communication Client components.// 07/05/2007 - J. Ritchie Carroll// Wrapped all event raising for frame parsing in Try/Catch so that any exceptions thrown in// consumer event handlers won't have a negative effect on continuous data parsing - exceptions// in consumer event handlers are duly noted and raised through the ParsingException event.// 09/28/2007 - J. Ritchie Carroll// Implemented new disconnect overload on communications client that allows timeout on socket// close to fix an issue related non-responsive threads that "lock-up" after sending connection// commands that attempt to close the socket for remotely connected devices.// 12/14/2007 - J. Ritchie Carroll// Implemented simulated timestamp injection for published frames to allow for real-time// data simulations from archived sample data.// 10/28/2008 - J. Ritchie Carroll// Added support for SEL's UDP_T and UDP_U protocol implementations (UDP_S was already supported),// implementation handled by allowing definition of a "CommandChannel" in the connection string.// 04/27/2009 - J. Ritchie Carroll// Added support for SEL Fast Message protocol.// 02/12/2010 - Pinal C. Patel// Modified to start the IFrameParser object in InitializeFrameParser() instead of Start().// 03/20/2010 - J. Ritchie Carroll// Added property "SkipDisableRealTimeData" to allow consumer to bypass sending the command to// turn off the real-time data stream when automatically starting the data parsing sequence. This// is useful when using UDP multicast that may have many listeners, in these cases you don't want// to disable the stream on startup or shutdown since other applications may be subscribed to the// real-time stream.// 03/21/2010 - J. Ritchie Carroll// Added parsing exception threshold settings and consumer event to handle situation.// 06/13/2010 - J. Ritchie Carroll// Added several more run-time statistics to the frame parser (e.g., missing frames, CRC errors).// 08/10/2010 - J. Ritchie Carroll// Added code to handle high-resolution input timing to support accurate input simulations.// 05/06/2010- Jian (Ryan) Zuo// Updated to exclude non-data frames from frame counts and injected waiting periods.// 05/19/2011 - Ritchie// Added DST file support.// 12/17/2012 - Starlynn Danyelle Gilliam// Modified Header.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Diagnostics;using System.IO;using System.IO.Ports;using System.Net;using System.Net.Sockets;using System.Text;using System.Text.RegularExpressions;using System.Threading;using GSF.Communication;using GSF.Diagnostics;using GSF.IO;using GSF.Parsing;using GSF.PhasorProtocols.IEEEC37_118;using GSF.PhasorProtocols.Macrodyne;using GSF.PhasorProtocols.SelFastMessage;using GSF.Threading;using GSF.TimeSeries;using GSF.Units;using GSF.Units.EE;using TcpClient = GSF.Communication.TcpClient;using UdpClient = GSF.Communication.UdpClient; // Ignore warnings about unused events that are required by IClient#pragma warning disable 67 namespace GSF.PhasorProtocols{ #region [ Enumerations ] /// <summary> /// Phasor data protocols enumeration. /// </summary> [Serializable] public enum PhasorProtocol { /// <summary> /// IEEE C37.118.2-2011 protocol. /// </summary> IEEEC37_118V2, /// <summary> /// IEEE C37.118-2005 protocol. /// </summary> IEEEC37_118V1, /// <summary> /// IEEE C37.118, draft 6 protocol. /// </summary> IEEEC37_118D6, /// <summary> /// IEEE 1344-1995 protocol. /// </summary> IEEE1344, /// <summary> /// BPA PDCstream protocol. /// </summary> BPAPDCstream, /// <summary> /// UTK F-NET protocol. /// </summary> FNET, /// <summary> /// SEL Fast Message protocol. /// </summary> SelFastMessage, /// <summary> /// Macrodyne protocol. /// </summary> Macrodyne, /// <summary> /// IEC 61850-90-5 protocol. /// </summary> IEC61850_90_5 } #endregion /// <summary> /// Protocol independent frame parser. /// </summary> /// <remarks> /// This class takes all protocol frame parsing implementations and reduces them to a single simple-to-use class exposing all /// data through abstract interfaces (e.g., IConfigurationFrame, IDataFrame, etc.) - this way new protocol implementations can /// be added without adversely affecting consuming code. Additionally, this class implements a variety of transport options /// (e.g., TCP, UDP, Serial, etc.) and hides the complexities of this connectivity and internally pushes all data received from /// the selected transport protocol to the selected phasor parsing protocol. /// </remarks> public sealed class MultiProtocolFrameParser : IFrameParser { #region [ Members ] // Nested Types /// <summary> /// Creates an instance of a TCP server that can be shared for multiple devices (e.g., an F-NET server) /// </summary> private sealed class SharedTcpServerReference : IServer { #region [ Members ] // Events /// <summary> /// Occurs when the server is started. /// </summary> public event EventHandler ServerStarted; /// <summary> /// Occurs when the server is stopped. /// </summary> public event EventHandler ServerStopped; /// <summary> /// Occurs when a client connects to the server. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client that connected to the server. /// </remarks> public event EventHandler<EventArgs<Guid>> ClientConnected; /// <summary> /// Occurs when a client disconnects from the server. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client that disconnected from the server. /// </remarks> public event EventHandler<EventArgs<Guid>> ClientDisconnected; /// <summary> /// Occurs when an exception is encountered while a client is connecting. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> encountered when connecting to the client. /// </remarks> public event EventHandler<EventArgs<Exception>> ClientConnectingException; /// <summary> /// Occurs when data is being sent to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client to which the data is being sent. /// </remarks> public event EventHandler<EventArgs<Guid>> SendClientDataStart; /// <summary> /// Occurs when data has been sent to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client to which the data has been sent. /// </remarks> public event EventHandler<EventArgs<Guid>> SendClientDataComplete; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered when sending data to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client to which the data was being sent.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the <see cref="Exception"/> encountered when sending data to a client. /// </remarks> public event EventHandler<EventArgs<Guid, Exception>> SendClientDataException; /// <summary> /// Occurs when unprocessed data has been received from a client. /// </summary> /// <remarks> /// <para> /// This event can be used to receive a notification that client data has arrived. The <see cref="Read"/> method can then be used /// to copy data to an existing buffer. In many cases it will be optimal to use an existing buffer instead of subscribing to the /// <see cref="ReceiveClientDataComplete"/> event, however, the data that is available after calling the <see cref="Read"/> method /// will be the original unprocessed data received by the client, i.e., not optionally decrypted or decompressed data. /// </para> /// <para> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client from which data is received.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the number of bytes received in the buffer from the client. /// </para> /// </remarks> public event EventHandler<EventArgs<Guid, int>> ReceiveClientData; /// <summary> /// Occurs when data received from a client has been processed and is ready for consumption. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2,T3}.Argument1"/> is the ID of the client from which data is received.<br/> /// <see cref="EventArgs{T1,T2,T3}.Argument2"/> is a new buffer containing post-processed data received from the client starting at index zero.<br/> /// <see cref="EventArgs{T1,T2,T3}.Argument3"/> is the number of post-processed bytes received in the buffer from the client. /// </remarks> public event EventHandler<EventArgs<Guid, byte[], int>> ReceiveClientDataComplete; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered when receiving data from a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client from which the data was being received.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the <see cref="Exception"/> encountered when receiving data from a client. /// </remarks> public event EventHandler<EventArgs<Guid, Exception>> ReceiveClientDataException; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered in a user-defined function via an event dispatch. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> thrown by the user-defined function. /// </remarks> public event EventHandler<EventArgs<Exception>> UnhandledUserException; /// <summary> /// Raised after the source object has been properly disposed. /// </summary> public event EventHandler Disposed; // Fields private TcpServer m_tcpServer; private IPAddress m_remoteAddress; private Guid m_clientID; #endregion #region [ Properties ] /// <summary> /// Gets the name of the object providing status information. /// </summary> public string Name => m_tcpServer?.Name; /// <summary> /// Gets the current status details about object providing status information. /// </summary> public string Status => m_tcpServer?.Status ?? string.Empty; /// <summary> /// Gets or sets the data required by the server to initialize. /// </summary> public string ConfigurationString { get; set; } /// <summary> /// Gets or sets the maximum number of clients that can connect to the server. /// </summary> public int MaxClientConnections { get => m_tcpServer?.MaxClientConnections ?? ServerBase.DefaultMaxClientConnections; set { } // Ignore this setting since we need to share } /// <summary> /// Gets or sets the size of the buffer used by the client for receiving data from the server. /// </summary> public int SendBufferSize { get => m_tcpServer?.SendBufferSize ?? ServerBase.DefaultSendBufferSize; set { } // Ignore this setting since we need to share } /// <summary> /// Gets or sets the size of the buffer used by the server for receiving data from the clients. /// </summary> public int ReceiveBufferSize { get; set; } /// <summary> /// Gets or sets the <see cref="Encoding"/> to be used for the text sent to the connected clients. /// </summary> public Encoding TextEncoding { get => m_tcpServer?.TextEncoding; set { } // Ignore this setting since we need to share } /// <summary> /// Gets the current <see cref="ServerState"/>. /// </summary> public ServerState CurrentState => m_tcpServer?.CurrentState ?? ServerState.NotRunning; /// <summary> /// Gets the <see cref="TransportProtocol"/> used by the server for the transportation of data with the clients. /// </summary> public TransportProtocol TransportProtocol => TransportProtocol.Tcp; /// <summary> /// Gets the server's ID. /// </summary> public Guid ServerID => m_tcpServer?.ServerID ?? Guid.Empty; /// <summary> /// Gets the IDs of clients connected to the server. /// </summary> public Guid[] ClientIDs => m_tcpServer?.ClientIDs ?? Array.Empty<Guid>(); /// <summary> /// Gets the <see cref="Time"/> for which the server has been running. /// </summary> public Time RunTime => m_tcpServer?.RunTime ?? 0.0D; /// <summary> /// Gets or sets a boolean value that indicates whether the object is enabled. /// </summary> public bool Enabled { get => m_tcpServer?.Enabled ?? false; // ReSharper disable once ValueParameterNotUsed set { // value parameter not used here intentionally if (m_tcpServer is not null) m_tcpServer.Enabled = true; } } /// <summary> /// Gets a flag that indicates whether the object has been disposed. /// </summary> public bool IsDisposed { get; private set; } #endregion #region [ Methods ] /// <summary> /// Initializes the state of the object. /// </summary> public void Initialize() { Dictionary<string, string> settings = ConfigurationString.ParseKeyValuePairs(); if (!settings.TryGetValue("receiveFrom", out string receiveFromSetting)) return; IPStack ipStack = Transport.GetInterfaceIPStack(settings); m_remoteAddress = Transport.CreateEndPoint(receiveFromSetting, 0, ipStack).Address; } /// <summary> /// Starts the server. /// </summary> public void Start() => GetSharedServer(); /// <summary> /// Stops the server. /// </summary> public void Stop() => ReturnSharedServer(); /// <summary> /// Disconnects a connected client. /// </summary> /// <param name="clientID">ID of the client to be disconnected.</param> public void DisconnectOne(Guid clientID) { if (clientID == m_clientID) m_tcpServer?.DisconnectOne(clientID); } /// <summary> /// Disconnects all of the connected clients. /// </summary> public void DisconnectAll() => m_tcpServer?.DisconnectOne(m_clientID); /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> public void Dispose() { if (IsDisposed) return; try { ReturnSharedServer(); } finally { IsDisposed = true; } } /// <summary> /// Sends data to the specified client synchronously. /// </summary> /// <param name="clientID">ID of the client to which the data is to be sent.</param> /// <param name="data">The buffer that contains the binary data to be sent.</param> /// <param name="offset">The zero-based position in the <paramref name="data"/> at which to begin sending data.</param> /// <param name="length">The number of bytes to be sent from <paramref name="data"/> starting at the <paramref name="offset"/>.</param> public void SendTo(Guid clientID, byte[] data, int offset, int length) => SendToAsync(m_clientID, data, offset, length).WaitOne(); /// <summary> /// Sends data to all of the connected clients synchronously. /// </summary> /// <param name="data">The buffer that contains the binary data to be sent.</param> /// <param name="offset">The zero-based position in the <paramref name="data"/> at which to begin sending data.</param> /// <param name="length">The number of bytes to be sent from <paramref name="data"/> starting at the <paramref name="offset"/>.</param> public void Multicast(byte[] data, int offset, int length) => WaitHandle.WaitAll(MulticastAsync(data, offset, length)); /// <summary> /// Sends data to the specified client asynchronously. /// </summary> /// <param name="clientID">ID of the client to which the data is to be sent.</param> /// <param name="data">The buffer that contains the binary data to be sent.</param> /// <param name="offset">The zero-based position in the <paramref name="data"/> at which to begin sending data.</param> /// <param name="length">The number of bytes to be sent from <paramref name="data"/> starting at the <paramref name="offset"/>.</param> /// <returns><see cref="WaitHandle"/> for the asynchronous operation.</returns> public WaitHandle SendToAsync(Guid clientID, byte[] data, int offset, int length) => m_tcpServer?.SendToAsync(clientID, data, offset, length) ?? new ManualResetEvent(true); /// <summary> /// Sends data to all of the connected clients asynchronously. /// </summary> /// <param name="data">The buffer that contains the binary data to be sent.</param> /// <param name="offset">The zero-based position in the <paramref name="data"/> at which to begin sending data.</param> /// <param name="length">The number of bytes to be sent from <paramref name="data"/> starting at the <paramref name="offset"/>.</param> /// <returns>Array of <see cref="WaitHandle"/> for the asynchronous operation.</returns> public WaitHandle[] MulticastAsync(byte[] data, int offset, int length) => m_tcpServer is null ? Array.Empty<WaitHandle>() : new[] { m_tcpServer.SendToAsync(m_clientID, data, offset, length) }; /// <summary> /// Reads a number of bytes from the current received data buffer and writes those bytes into a byte array at the specified offset. /// </summary> /// <param name="clientID">ID of the client from which data buffer should be read.</param> /// <param name="buffer">Destination buffer used to hold copied bytes.</param> /// <param name="startIndex">0-based starting index into destination <paramref name="buffer"/> to begin writing data.</param> /// <param name="length">The number of bytes to read from current received data buffer and write into <paramref name="buffer"/>.</param> /// <returns>The number of bytes read.</returns> /// <remarks> /// This function should only be called from within the <see cref="ReceiveClientData"/> event handler. Calling this method outside this /// event will have unexpected results. /// </remarks> public int Read(Guid clientID, byte[] buffer, int startIndex, int length) => m_tcpServer?.Read(clientID, buffer, startIndex, length) ?? 0; /// <summary> /// Gets a reference to the shared server listening /// on this server's local end point. /// </summary> /// <returns>A reference to a shared server.</returns> private void GetSharedServer() { const string ConfigurationMismatchError = "Configuration mismatch detected between parsers using shared TCP server: {0}"; lock (s_sharedServers)Lines 1–500 of 3731. Load more
//****************************************************************************************************** // MultiProtocolFrameParser.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: // ---------------------------------------------------------------------------------------------------- // 03/16/2006 - J. Ritchie Carroll // Initial version of source generated. // 06/26/2006 - Pinal C. Patel // Changed out the socket code with TcpClient and UdpClient components from GSF.Communication. // 01/31/2007 - J. Ritchie Carroll // Added TCP "server" support to allow listening connections from devices that act as data // clients, e.g., F-NET devices. // 05/23/2007 - Pinal C. Patel // Added member variable 'm_clientConnectionAttempts' to track the number of attempts made for // connecting to the server since this information is no longer provided by the event raised by // any of the Communication Client components. // 07/05/2007 - J. Ritchie Carroll // Wrapped all event raising for frame parsing in Try/Catch so that any exceptions thrown in // consumer event handlers won't have a negative effect on continuous data parsing - exceptions // in consumer event handlers are duly noted and raised through the ParsingException event. // 09/28/2007 - J. Ritchie Carroll // Implemented new disconnect overload on communications client that allows timeout on socket // close to fix an issue related non-responsive threads that "lock-up" after sending connection // commands that attempt to close the socket for remotely connected devices. // 12/14/2007 - J. Ritchie Carroll // Implemented simulated timestamp injection for published frames to allow for real-time // data simulations from archived sample data. // 10/28/2008 - J. Ritchie Carroll // Added support for SEL's UDP_T and UDP_U protocol implementations (UDP_S was already supported), // implementation handled by allowing definition of a "CommandChannel" in the connection string. // 04/27/2009 - J. Ritchie Carroll // Added support for SEL Fast Message protocol. // 02/12/2010 - Pinal C. Patel // Modified to start the IFrameParser object in InitializeFrameParser() instead of Start(). // 03/20/2010 - J. Ritchie Carroll // Added property "SkipDisableRealTimeData" to allow consumer to bypass sending the command to // turn off the real-time data stream when automatically starting the data parsing sequence. This // is useful when using UDP multicast that may have many listeners, in these cases you don't want // to disable the stream on startup or shutdown since other applications may be subscribed to the // real-time stream. // 03/21/2010 - J. Ritchie Carroll // Added parsing exception threshold settings and consumer event to handle situation. // 06/13/2010 - J. Ritchie Carroll // Added several more run-time statistics to the frame parser (e.g., missing frames, CRC errors). // 08/10/2010 - J. Ritchie Carroll // Added code to handle high-resolution input timing to support accurate input simulations. // 05/06/2010- Jian (Ryan) Zuo // Updated to exclude non-data frames from frame counts and injected waiting periods. // 05/19/2011 - Ritchie // Added DST file support. // 12/17/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Ports; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Threading; using GSF.Communication; using GSF.Diagnostics; using GSF.IO; using GSF.Parsing; using GSF.PhasorProtocols.IEEEC37_118; using GSF.PhasorProtocols.Macrodyne; using GSF.PhasorProtocols.SelFastMessage; using GSF.Threading; using GSF.TimeSeries; using GSF.Units; using GSF.Units.EE; using TcpClient = GSF.Communication.TcpClient; using UdpClient = GSF.Communication.UdpClient; // Ignore warnings about unused events that are required by IClient #pragma warning disable 67 namespace GSF.PhasorProtocols { #region [ Enumerations ] /// <summary> /// Phasor data protocols enumeration. /// </summary> [Serializable] public enum PhasorProtocol { /// <summary> /// IEEE C37.118.2-2011 protocol. /// </summary> IEEEC37_118V2, /// <summary> /// IEEE C37.118-2005 protocol. /// </summary> IEEEC37_118V1, /// <summary> /// IEEE C37.118, draft 6 protocol. /// </summary> IEEEC37_118D6, /// <summary> /// IEEE 1344-1995 protocol. /// </summary> IEEE1344, /// <summary> /// BPA PDCstream protocol. /// </summary> BPAPDCstream, /// <summary> /// UTK F-NET protocol. /// </summary> FNET, /// <summary> /// SEL Fast Message protocol. /// </summary> SelFastMessage, /// <summary> /// Macrodyne protocol. /// </summary> Macrodyne, /// <summary> /// IEC 61850-90-5 protocol. /// </summary> IEC61850_90_5 } #endregion /// <summary> /// Protocol independent frame parser. /// </summary> /// <remarks> /// This class takes all protocol frame parsing implementations and reduces them to a single simple-to-use class exposing all /// data through abstract interfaces (e.g., IConfigurationFrame, IDataFrame, etc.) - this way new protocol implementations can /// be added without adversely affecting consuming code. Additionally, this class implements a variety of transport options /// (e.g., TCP, UDP, Serial, etc.) and hides the complexities of this connectivity and internally pushes all data received from /// the selected transport protocol to the selected phasor parsing protocol. /// </remarks> public sealed class MultiProtocolFrameParser : IFrameParser { #region [ Members ] // Nested Types /// <summary> /// Creates an instance of a TCP server that can be shared for multiple devices (e.g., an F-NET server) /// </summary> private sealed class SharedTcpServerReference : IServer { #region [ Members ] // Events /// <summary> /// Occurs when the server is started. /// </summary> public event EventHandler ServerStarted; /// <summary> /// Occurs when the server is stopped. /// </summary> public event EventHandler ServerStopped; /// <summary> /// Occurs when a client connects to the server. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client that connected to the server. /// </remarks> public event EventHandler<EventArgs<Guid>> ClientConnected; /// <summary> /// Occurs when a client disconnects from the server. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client that disconnected from the server. /// </remarks> public event EventHandler<EventArgs<Guid>> ClientDisconnected; /// <summary> /// Occurs when an exception is encountered while a client is connecting. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> encountered when connecting to the client. /// </remarks> public event EventHandler<EventArgs<Exception>> ClientConnectingException; /// <summary> /// Occurs when data is being sent to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client to which the data is being sent. /// </remarks> public event EventHandler<EventArgs<Guid>> SendClientDataStart; /// <summary> /// Occurs when data has been sent to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client to which the data has been sent. /// </remarks> public event EventHandler<EventArgs<Guid>> SendClientDataComplete; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered when sending data to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client to which the data was being sent.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the <see cref="Exception"/> encountered when sending data to a client. /// </remarks> public event EventHandler<EventArgs<Guid, Exception>> SendClientDataException; /// <summary> /// Occurs when unprocessed data has been received from a client. /// </summary> /// <remarks> /// <para> /// This event can be used to receive a notification that client data has arrived. The <see cref="Read"/> method can then be used /// to copy data to an existing buffer. In many cases it will be optimal to use an existing buffer instead of subscribing to the /// <see cref="ReceiveClientDataComplete"/> event, however, the data that is available after calling the <see cref="Read"/> method /// will be the original unprocessed data received by the client, i.e., not optionally decrypted or decompressed data. /// </para> /// <para> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client from which data is received.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the number of bytes received in the buffer from the client. /// </para> /// </remarks> public event EventHandler<EventArgs<Guid, int>> ReceiveClientData; /// <summary> /// Occurs when data received from a client has been processed and is ready for consumption. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2,T3}.Argument1"/> is the ID of the client from which data is received.<br/> /// <see cref="EventArgs{T1,T2,T3}.Argument2"/> is a new buffer containing post-processed data received from the client starting at index zero.<br/> /// <see cref="EventArgs{T1,T2,T3}.Argument3"/> is the number of post-processed bytes received in the buffer from the client. /// </remarks> public event EventHandler<EventArgs<Guid, byte[], int>> ReceiveClientDataComplete; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered when receiving data from a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client from which the data was being received.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the <see cref="Exception"/> encountered when receiving data from a client. /// </remarks> public event EventHandler<EventArgs<Guid, Exception>> ReceiveClientDataException; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered in a user-defined function via an event dispatch. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> thrown by the user-defined function. /// </remarks> public event EventHandler<EventArgs<Exception>> UnhandledUserException; /// <summary> /// Raised after the source object has been properly disposed. /// </summary> public event EventHandler Disposed; // Fields private TcpServer m_tcpServer; private IPAddress m_remoteAddress; private Guid m_clientID; #endregion #region [ Properties ] /// <summary> /// Gets the name of the object providing status information. /// </summary> public string Name => m_tcpServer?.Name; /// <summary> /// Gets the current status details about object providing status information. /// </summary> public string Status => m_tcpServer?.Status ?? string.Empty; /// <summary> /// Gets or sets the data required by the server to initialize. /// </summary> public string ConfigurationString { get; set; } /// <summary> /// Gets or sets the maximum number of clients that can connect to the server. /// </summary> public int MaxClientConnections { get => m_tcpServer?.MaxClientConnections ?? ServerBase.DefaultMaxClientConnections; set { } // Ignore this setting since we need to share } /// <summary> /// Gets or sets the size of the buffer used by the client for receiving data from the server. /// </summary> public int SendBufferSize { get => m_tcpServer?.SendBufferSize ?? ServerBase.DefaultSendBufferSize; set { } // Ignore this setting since we need to share } /// <summary> /// Gets or sets the size of the buffer used by the server for receiving data from the clients. /// </summary> public int ReceiveBufferSize { get; set; } /// <summary> /// Gets or sets the <see cref="Encoding"/> to be used for the text sent to the connected clients. /// </summary> public Encoding TextEncoding { get => m_tcpServer?.TextEncoding; set { } // Ignore this setting since we need to share } /// <summary> /// Gets the current <see cref="ServerState"/>. /// </summary> public ServerState CurrentState => m_tcpServer?.CurrentState ?? ServerState.NotRunning; /// <summary> /// Gets the <see cref="TransportProtocol"/> used by the server for the transportation of data with the clients. /// </summary> public TransportProtocol TransportProtocol => TransportProtocol.Tcp; /// <summary> /// Gets the server's ID. /// </summary> public Guid ServerID => m_tcpServer?.ServerID ?? Guid.Empty; /// <summary> /// Gets the IDs of clients connected to the server. /// </summary> public Guid[] ClientIDs => m_tcpServer?.ClientIDs ?? Array.Empty<Guid>(); /// <summary> /// Gets the <see cref="Time"/> for which the server has been running. /// </summary> public Time RunTime => m_tcpServer?.RunTime ?? 0.0D; /// <summary> /// Gets or sets a boolean value that indicates whether the object is enabled. /// </summary> public bool Enabled { get => m_tcpServer?.Enabled ?? false; // ReSharper disable once ValueParameterNotUsed set { // value parameter not used here intentionally if (m_tcpServer is not null) m_tcpServer.Enabled = true; } } /// <summary> /// Gets a flag that indicates whether the object has been disposed. /// </summary> public bool IsDisposed { get; private set; } #endregion #region [ Methods ] /// <summary> /// Initializes the state of the object. /// </summary> public void Initialize() { Dictionary<string, string> settings = ConfigurationString.ParseKeyValuePairs(); if (!settings.TryGetValue("receiveFrom", out string receiveFromSetting)) return; IPStack ipStack = Transport.GetInterfaceIPStack(settings); m_remoteAddress = Transport.CreateEndPoint(receiveFromSetting, 0, ipStack).Address; } /// <summary> /// Starts the server. /// </summary> public void Start() => GetSharedServer(); /// <summary> /// Stops the server. /// </summary> public void Stop() => ReturnSharedServer(); /// <summary> /// Disconnects a connected client. /// </summary> /// <param name="clientID">ID of the client to be disconnected.</param> public void DisconnectOne(Guid clientID) { if (clientID == m_clientID) m_tcpServer?.DisconnectOne(clientID); } /// <summary> /// Disconnects all of the connected clients. /// </summary> public void DisconnectAll() => m_tcpServer?.DisconnectOne(m_clientID); /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> public void Dispose() { if (IsDisposed) return; try { ReturnSharedServer(); } finally { IsDisposed = true; } } /// <summary> /// Sends data to the specified client synchronously. /// </summary> /// <param name="clientID">ID of the client to which the data is to be sent.</param> /// <param name="data">The buffer that contains the binary data to be sent.</param> /// <param name="offset">The zero-based position in the <paramref name="data"/> at which to begin sending data.</param> /// <param name="length">The number of bytes to be sent from <paramref name="data"/> starting at the <paramref name="offset"/>.</param> public void SendTo(Guid clientID, byte[] data, int offset, int length) => SendToAsync(m_clientID, data, offset, length).WaitOne(); /// <summary> /// Sends data to all of the connected clients synchronously. /// </summary> /// <param name="data">The buffer that contains the binary data to be sent.</param> /// <param name="offset">The zero-based position in the <paramref name="data"/> at which to begin sending data.</param> /// <param name="length">The number of bytes to be sent from <paramref name="data"/> starting at the <paramref name="offset"/>.</param> public void Multicast(byte[] data, int offset, int length) => WaitHandle.WaitAll(MulticastAsync(data, offset, length)); /// <summary> /// Sends data to the specified client asynchronously. /// </summary> /// <param name="clientID">ID of the client to which the data is to be sent.</param> /// <param name="data">The buffer that contains the binary data to be sent.</param> /// <param name="offset">The zero-based position in the <paramref name="data"/> at which to begin sending data.</param> /// <param name="length">The number of bytes to be sent from <paramref name="data"/> starting at the <paramref name="offset"/>.</param> /// <returns><see cref="WaitHandle"/> for the asynchronous operation.</returns> public WaitHandle SendToAsync(Guid clientID, byte[] data, int offset, int length) => m_tcpServer?.SendToAsync(clientID, data, offset, length) ?? new ManualResetEvent(true); /// <summary> /// Sends data to all of the connected clients asynchronously. /// </summary> /// <param name="data">The buffer that contains the binary data to be sent.</param> /// <param name="offset">The zero-based position in the <paramref name="data"/> at which to begin sending data.</param> /// <param name="length">The number of bytes to be sent from <paramref name="data"/> starting at the <paramref name="offset"/>.</param> /// <returns>Array of <see cref="WaitHandle"/> for the asynchronous operation.</returns> public WaitHandle[] MulticastAsync(byte[] data, int offset, int length) => m_tcpServer is null ? Array.Empty<WaitHandle>() : new[] { m_tcpServer.SendToAsync(m_clientID, data, offset, length) }; /// <summary> /// Reads a number of bytes from the current received data buffer and writes those bytes into a byte array at the specified offset. /// </summary> /// <param name="clientID">ID of the client from which data buffer should be read.</param> /// <param name="buffer">Destination buffer used to hold copied bytes.</param> /// <param name="startIndex">0-based starting index into destination <paramref name="buffer"/> to begin writing data.</param> /// <param name="length">The number of bytes to read from current received data buffer and write into <paramref name="buffer"/>.</param> /// <returns>The number of bytes read.</returns> /// <remarks> /// This function should only be called from within the <see cref="ReceiveClientData"/> event handler. Calling this method outside this /// event will have unexpected results. /// </remarks> public int Read(Guid clientID, byte[] buffer, int startIndex, int length) => m_tcpServer?.Read(clientID, buffer, startIndex, length) ?? 0; /// <summary> /// Gets a reference to the shared server listening /// on this server's local end point. /// </summary> /// <returns>A reference to a shared server.</returns> private void GetSharedServer() { const string ConfigurationMismatchError = "Configuration mismatch detected between parsers using shared TCP server: {0}"; lock (s_sharedServers)