mirror of
https://github.com/PepperDash/PepperDashCore.git
synced 2026-02-16 21:24:43 +00:00
Merge remote-tracking branch 'origin/release' into bugfiix/pdc-3
# Conflicts: # Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -18,9 +18,7 @@ using Crestron.SimplSharp.CrestronSockets;
|
|||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core
|
||||||
{
|
{
|
||||||
#region GenericSocketStatusChangeEventArgs
|
|
||||||
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
|
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
|
||||||
|
|
||||||
public class GenericSocketStatusChageEventArgs : EventArgs
|
public class GenericSocketStatusChageEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public ISocketStatus Client { get; private set; }
|
public ISocketStatus Client { get; private set; }
|
||||||
@@ -32,44 +30,68 @@ namespace PepperDash.Core
|
|||||||
Client = client;
|
Client = client;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region DynamicTCPServerStateChangedEventArgs
|
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
|
||||||
public delegate void DynamicTCPServerStateChangedEventDelegate(object server);
|
public class GenericTcpServerStateChangedEventArgs : EventArgs
|
||||||
|
|
||||||
public class DynamicTCPServerStateChangedEventArgs : EventArgs
|
|
||||||
{
|
{
|
||||||
public bool Secure { get; private set; }
|
public ServerState State { get; private set; }
|
||||||
public object Server { get; private set; }
|
|
||||||
|
|
||||||
public DynamicTCPServerStateChangedEventArgs() { }
|
public GenericTcpServerStateChangedEventArgs() { }
|
||||||
|
|
||||||
public DynamicTCPServerStateChangedEventArgs(object server, bool secure)
|
public GenericTcpServerStateChangedEventArgs(ServerState state)
|
||||||
{
|
{
|
||||||
Secure = secure;
|
State = state;
|
||||||
Server = server;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region DynamicTCPSocketStatusChangeEventDelegate
|
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
|
||||||
public delegate void DynamicTCPSocketStatusChangeEventDelegate(object server);
|
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
||||||
|
|
||||||
public class DynamicTCPSocketStatusChangeEventArgs : EventArgs
|
|
||||||
{
|
{
|
||||||
public bool Secure { get; private set; }
|
public object Socket { get; private set; }
|
||||||
public object Server { get; private set; }
|
public uint ReceivedFromClientIndex { get; private set; }
|
||||||
|
public SocketStatus ClientStatus { get; set; }
|
||||||
|
|
||||||
public DynamicTCPSocketStatusChangeEventArgs() { }
|
public GenericTcpServerSocketStatusChangeEventArgs() { }
|
||||||
|
|
||||||
public DynamicTCPSocketStatusChangeEventArgs(object server, bool secure)
|
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
|
||||||
{
|
{
|
||||||
Secure = secure;
|
Socket = socket;
|
||||||
Server = server;
|
ClientStatus = clientStatus;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
|
||||||
|
{
|
||||||
|
Socket = socket;
|
||||||
|
ReceivedFromClientIndex = clientIndex;
|
||||||
|
ClientStatus = clientStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
||||||
|
{
|
||||||
|
public uint ReceivedFromClientIndex { get; private set; }
|
||||||
|
public string Text { get; private set; }
|
||||||
|
|
||||||
|
public GenericTcpServerCommMethodReceiveTextArgs(string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
ReceivedFromClientIndex = clientIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public bool IsReady;
|
||||||
|
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
||||||
|
{
|
||||||
|
IsReady = isReady;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,371 +0,0 @@
|
|||||||
/*PepperDash Technology Corp.
|
|
||||||
JAG
|
|
||||||
Copyright: 2017
|
|
||||||
------------------------------------
|
|
||||||
***Notice of Ownership and Copyright***
|
|
||||||
The material in which this notice appears is the property of PepperDash Technology Corporation,
|
|
||||||
which claims copyright under the laws of the United States of America in the entire body of material
|
|
||||||
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
|
|
||||||
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
|
|
||||||
PepperDash Technology Corporation reserves all rights under applicable laws.
|
|
||||||
------------------------------------ */
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
public class GenericSecureTcpIpClient : Device, ISocketStatus, IAutoReconnect
|
|
||||||
{
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
|
||||||
|
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
|
||||||
|
|
||||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties & Variables
|
|
||||||
/// <summary>
|
|
||||||
/// Address of server
|
|
||||||
/// </summary>
|
|
||||||
public string Hostname { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Port on server
|
|
||||||
/// </summary>
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper
|
|
||||||
/// </summary>
|
|
||||||
public ushort UPort
|
|
||||||
{
|
|
||||||
get { return Convert.ToUInt16(Port); }
|
|
||||||
set { Port = Convert.ToInt32(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
|
|
||||||
/// </summary>
|
|
||||||
public bool SharedKeyRequired { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for requires shared key bool
|
|
||||||
/// </summary>
|
|
||||||
public ushort USharedKeyRequired
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == 1)
|
|
||||||
SharedKeyRequired = true;
|
|
||||||
else
|
|
||||||
SharedKeyRequired = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
|
||||||
/// </summary>
|
|
||||||
public string SharedKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// flag to show the client is waiting for the server to send the shared key
|
|
||||||
/// </summary>
|
|
||||||
private bool WaitingForSharedKeyResponse { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to 2000
|
|
||||||
/// </summary>
|
|
||||||
public int BufferSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool showing if socket is connected
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return (Client != null) && (Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for IsConnected
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsConnected
|
|
||||||
{
|
|
||||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client socket status Read only
|
|
||||||
/// </summary>
|
|
||||||
public SocketStatus ClientStatus
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Client == null)
|
|
||||||
return SocketStatus.SOCKET_STATUS_NO_CONNECT;
|
|
||||||
return Client.ClientStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
|
||||||
/// and IsConnected would be true when this == 2.
|
|
||||||
/// </summary>
|
|
||||||
public ushort UStatus
|
|
||||||
{
|
|
||||||
get { return (ushort)ClientStatus; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Status text shows the message associated with socket status
|
|
||||||
/// </summary>
|
|
||||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// bool to track if auto reconnect should be set on the socket
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoReconnect { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for AutoReconnect
|
|
||||||
/// </summary>
|
|
||||||
public ushort UAutoReconnect
|
|
||||||
{
|
|
||||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
|
||||||
set { AutoReconnect = value == 1; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
|
||||||
/// </summary>
|
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flag Set only when the disconnect method is called.
|
|
||||||
/// </summary>
|
|
||||||
bool DisconnectCalledByUser;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connected bool
|
|
||||||
/// </summary>
|
|
||||||
public bool Connected
|
|
||||||
{
|
|
||||||
get { return (Client != null) && (Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED); }
|
|
||||||
}
|
|
||||||
|
|
||||||
CTimer RetryTimer; //private Timer for auto reconnect
|
|
||||||
|
|
||||||
SecureTCPClient Client; //Secure Client Class
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
//Base class constructor
|
|
||||||
public GenericSecureTcpIpClient(string key, string address, int port, int bufferSize)
|
|
||||||
: base(key)
|
|
||||||
{
|
|
||||||
Hostname = address;
|
|
||||||
Port = port;
|
|
||||||
BufferSize = bufferSize;
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
}
|
|
||||||
|
|
||||||
//base class constructor
|
|
||||||
public GenericSecureTcpIpClient()
|
|
||||||
: base("Uninitialized DynamicTcpClient")
|
|
||||||
{
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
BufferSize = 2000;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
/// <summary>
|
|
||||||
/// Just to help S+ set the key
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize(string key)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles closing this up when the program shuts down
|
|
||||||
/// </summary>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType == eProgramStatusEventType.Stopping)
|
|
||||||
{
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Program stopping. Closing connection");
|
|
||||||
Client.DisconnectFromServer();
|
|
||||||
Client.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
|
|
||||||
/// </summary>
|
|
||||||
public void Connect()
|
|
||||||
{
|
|
||||||
if (IsConnected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "DynamicTcpClient '{0}': No address set", Key);
|
|
||||||
ErrorLog.Warn(string.Format("DynamicTcpClient '{0}': No address set", Key));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Port < 1 || Port > 65535)
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "DynamicTcpClient '{0}': Invalid port", Key);
|
|
||||||
ErrorLog.Warn(string.Format("DynamicTcpClient '{0}': Invalid port", Key));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "DynamicTcpClient '{0}': No Shared Key set", Key);
|
|
||||||
ErrorLog.Warn(string.Format("DynamicTcpClient '{0}': No Shared Key set", Key));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Client = new SecureTCPClient(Hostname, Port, BufferSize);
|
|
||||||
Client.SocketStatusChange += SecureClient_SocketStatusChange;
|
|
||||||
DisconnectCalledByUser = false;
|
|
||||||
if (SharedKeyRequired)
|
|
||||||
WaitingForSharedKeyResponse = true;
|
|
||||||
Client.ConnectToServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disconnect client. Does not dispose.
|
|
||||||
/// </summary>
|
|
||||||
public void Disconnect()
|
|
||||||
{
|
|
||||||
DisconnectCalledByUser = true;
|
|
||||||
if(Client != null)
|
|
||||||
Client.DisconnectFromServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// callback after connection made
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="o"></param>
|
|
||||||
void ConnectToServerCallback(object o)
|
|
||||||
{
|
|
||||||
Client.ConnectToServer();
|
|
||||||
if (Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
WaitAndTryReconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from Socket Status change if auto reconnect and socket disconnected (Not disconnected by user)
|
|
||||||
/// </summary>
|
|
||||||
void WaitAndTryReconnect()
|
|
||||||
{
|
|
||||||
Client.DisconnectFromServer();
|
|
||||||
Debug.Console(2, "Attempting reconnect, status={0}", Client.ClientStatus);
|
|
||||||
|
|
||||||
if (!DisconnectCalledByUser)
|
|
||||||
RetryTimer = new CTimer(ConnectToServerCallback, AutoReconnectIntervalMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receive callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="numBytes"></param>
|
|
||||||
void SecureReceive(SecureTCPClient client, int numBytes)
|
|
||||||
{
|
|
||||||
if (numBytes > 0)
|
|
||||||
{
|
|
||||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
|
||||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
|
||||||
if (WaitingForSharedKeyResponse && SharedKeyRequired)
|
|
||||||
{
|
|
||||||
if (str != (SharedKey + "\n"))
|
|
||||||
{
|
|
||||||
WaitingForSharedKeyResponse = false;
|
|
||||||
client.DisconnectFromServer();
|
|
||||||
CrestronConsole.PrintLine("Client {0} was disconnected from server because the server did not respond with a matching shared key after connection", Key);
|
|
||||||
ErrorLog.Error("Client {0} was disconnected from server because the server did not respond with a matching shared key after connection", Key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
WaitingForSharedKeyResponse = false;
|
|
||||||
CrestronConsole.PrintLine("Client {0} successfully connected to the server and received the Shared Key. Ready for communication", Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var bytesHandler = BytesReceived;
|
|
||||||
if (bytesHandler != null)
|
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
|
||||||
var textHandler = TextReceived;
|
|
||||||
if (textHandler != null)
|
|
||||||
{
|
|
||||||
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.ReceiveDataAsync(SecureReceive);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// General send method
|
|
||||||
/// </summary>
|
|
||||||
public void SendText(string text)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
|
||||||
Client.SendData(bytes, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SendBytes(byte[] bytes)
|
|
||||||
{
|
|
||||||
Client.SendData(bytes, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SocketStatusChange Callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="clientSocketStatus"></param>
|
|
||||||
void SecureClient_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
|
||||||
if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED && !DisconnectCalledByUser && AutoReconnect)
|
|
||||||
WaitAndTryReconnect();
|
|
||||||
|
|
||||||
// Probably doesn't need to be a switch since all other cases were eliminated
|
|
||||||
switch (clientSocketStatus)
|
|
||||||
{
|
|
||||||
case SocketStatus.SOCKET_STATUS_CONNECTED:
|
|
||||||
client.ReceiveDataAsync(SecureReceive);
|
|
||||||
if(SharedKeyRequired)
|
|
||||||
SendText(SharedKey + "\n");
|
|
||||||
DisconnectCalledByUser = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var handler = ConnectionChange;
|
|
||||||
if (handler != null)
|
|
||||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,739 @@
|
|||||||
|
/*PepperDash Technology Corp.
|
||||||
|
JAG
|
||||||
|
Copyright: 2017
|
||||||
|
------------------------------------
|
||||||
|
***Notice of Ownership and Copyright***
|
||||||
|
The material in which this notice appears is the property of PepperDash Technology Corporation,
|
||||||
|
which claims copyright under the laws of the United States of America in the entire body of material
|
||||||
|
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
|
||||||
|
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
|
||||||
|
PepperDash Technology Corporation reserves all rights under applicable laws.
|
||||||
|
------------------------------------ */
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Band aid delegate for choked server
|
||||||
|
/// </summary>
|
||||||
|
internal delegate void ConnectionHasHungCallbackDelegate();
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
//public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||||
|
|
||||||
|
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
|
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ConnectionChange;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is something of a band-aid callback. If the client times out during the connection process, because the server
|
||||||
|
/// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help
|
||||||
|
/// keep a watch on the server and reset it if necessary.
|
||||||
|
/// </summary>
|
||||||
|
internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require
|
||||||
|
/// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericTcpServerClientReadyForcommunicationsEventArgs> ClientReadyForCommunications;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Variables
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Address of server
|
||||||
|
/// </summary>
|
||||||
|
public string Hostname { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Port on server
|
||||||
|
/// </summary>
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper
|
||||||
|
/// </summary>
|
||||||
|
public ushort UPort
|
||||||
|
{
|
||||||
|
get { return Convert.ToUInt16(Port); }
|
||||||
|
set { Port = Convert.ToInt32(value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
|
||||||
|
/// </summary>
|
||||||
|
public bool SharedKeyRequired { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for requires shared key bool
|
||||||
|
/// </summary>
|
||||||
|
public ushort USharedKeyRequired
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == 1)
|
||||||
|
SharedKeyRequired = true;
|
||||||
|
else
|
||||||
|
SharedKeyRequired = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
||||||
|
/// </summary>
|
||||||
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// flag to show the client is waiting for the server to send the shared key
|
||||||
|
/// </summary>
|
||||||
|
private bool WaitingForSharedKeyResponse { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to 2000
|
||||||
|
/// </summary>
|
||||||
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Semaphore on connect method
|
||||||
|
/// </summary>
|
||||||
|
bool IsTryingToConnect;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Client != null)
|
||||||
|
return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsConnected
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsConnected
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is ready for communication after shared key exchange
|
||||||
|
/// </summary>
|
||||||
|
public bool IsReadyForCommunication { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsReadyForCommunication
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsReadyForCommunication
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsReadyForCommunication ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client socket status Read only
|
||||||
|
/// </summary>
|
||||||
|
public SocketStatus ClientStatus
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Client != null)
|
||||||
|
return Client.ClientStatus;
|
||||||
|
else
|
||||||
|
return SocketStatus.SOCKET_STATUS_NO_CONNECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
||||||
|
/// and IsConnected would be true when this == 2.
|
||||||
|
/// </summary>
|
||||||
|
public ushort UStatus
|
||||||
|
{
|
||||||
|
get { return (ushort)ClientStatus; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status text shows the message associated with socket status
|
||||||
|
/// </summary>
|
||||||
|
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// bool to track if auto reconnect should be set on the socket
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoReconnect { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for AutoReconnect
|
||||||
|
/// </summary>
|
||||||
|
public ushort UAutoReconnect
|
||||||
|
{
|
||||||
|
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
||||||
|
set { AutoReconnect = value == 1; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
||||||
|
/// </summary>
|
||||||
|
public int AutoReconnectIntervalMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag Set only when the disconnect method is called.
|
||||||
|
/// </summary>
|
||||||
|
bool DisconnectCalledByUser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// private Timer for auto reconnect
|
||||||
|
/// </summary>
|
||||||
|
CTimer RetryTimer;
|
||||||
|
|
||||||
|
|
||||||
|
public bool HeartbeatEnabled { get; set; }
|
||||||
|
public ushort UHeartbeatEnabled
|
||||||
|
{
|
||||||
|
get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
|
||||||
|
set { HeartbeatEnabled = value == 1; }
|
||||||
|
}
|
||||||
|
public string HeartbeatString = "heartbeat";
|
||||||
|
public int HeartbeatInterval = 50000;
|
||||||
|
CTimer HeartbeatSendTimer;
|
||||||
|
CTimer HeartbeatAckTimer;
|
||||||
|
/// <summary>
|
||||||
|
/// Used to force disconnection on a dead connect attempt
|
||||||
|
/// </summary>
|
||||||
|
CTimer ConnectFailTimer;
|
||||||
|
CTimer WaitForSharedKey;
|
||||||
|
private int ConnectionCount;
|
||||||
|
/// <summary>
|
||||||
|
/// Internal secure client
|
||||||
|
/// </summary>
|
||||||
|
SecureTCPClient Client;
|
||||||
|
|
||||||
|
bool ProgramIsStopping;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
//Base class constructor
|
||||||
|
public GenericSecureTcpIpClient_ForServer(string key, string address, int port, int bufferSize)
|
||||||
|
: base(key)
|
||||||
|
{
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
Hostname = address;
|
||||||
|
Port = port;
|
||||||
|
BufferSize = bufferSize;
|
||||||
|
AutoReconnectIntervalMs = 5000;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//base class constructor
|
||||||
|
public GenericSecureTcpIpClient_ForServer()
|
||||||
|
: base("Uninitialized DynamicTcpClient")
|
||||||
|
{
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
AutoReconnectIntervalMs = 5000;
|
||||||
|
BufferSize = 2000;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Just to help S+ set the key
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles closing this up when the program shuts down
|
||||||
|
/// </summary>
|
||||||
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
|
{
|
||||||
|
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
|
||||||
|
ProgramIsStopping = true;
|
||||||
|
Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
|
||||||
|
/// </summary>
|
||||||
|
public void Connect()
|
||||||
|
{
|
||||||
|
ConnectionCount++;
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Attempting connect Count:{0}", ConnectionCount);
|
||||||
|
|
||||||
|
|
||||||
|
if (IsConnected)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsTryingToConnect)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsTryingToConnect = true;
|
||||||
|
if (RetryTimer != null)
|
||||||
|
{
|
||||||
|
RetryTimer.Stop();
|
||||||
|
RetryTimer = null;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(Hostname))
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Port < 1 || Port > 65535)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up previous client
|
||||||
|
if (Client != null)
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
DisconnectCalledByUser = false;
|
||||||
|
|
||||||
|
Client = new SecureTCPClient(Hostname, Port, BufferSize);
|
||||||
|
Client.SocketStatusChange += Client_SocketStatusChange;
|
||||||
|
Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
|
||||||
|
Client.AddressClientConnectedTo = Hostname;
|
||||||
|
Client.PortNumber = Port;
|
||||||
|
// SecureClient = c;
|
||||||
|
|
||||||
|
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||||
|
|
||||||
|
ConnectFailTimer = new CTimer(o =>
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||||
|
if (IsTryingToConnect)
|
||||||
|
{
|
||||||
|
IsTryingToConnect = false;
|
||||||
|
|
||||||
|
//if (ConnectionHasHungCallback != null)
|
||||||
|
//{
|
||||||
|
// ConnectionHasHungCallback();
|
||||||
|
//}
|
||||||
|
//SecureClient.DisconnectFromServer();
|
||||||
|
//CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Making Connection Count:{0}", ConnectionCount);
|
||||||
|
Client.ConnectToServerAsync(o =>
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||||
|
|
||||||
|
if (ConnectFailTimer != null)
|
||||||
|
{
|
||||||
|
ConnectFailTimer.Stop();
|
||||||
|
}
|
||||||
|
IsTryingToConnect = false;
|
||||||
|
|
||||||
|
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||||
|
o.ReceiveDataAsync(Receive);
|
||||||
|
|
||||||
|
if (SharedKeyRequired)
|
||||||
|
{
|
||||||
|
WaitingForSharedKeyResponse = true;
|
||||||
|
WaitForSharedKey = new CTimer(timer =>
|
||||||
|
{
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||||
|
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
||||||
|
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
||||||
|
o.DisconnectFromServer();
|
||||||
|
//CheckClosedAndTryReconnect();
|
||||||
|
//OnClientReadyForcommunications(false); // Should send false event
|
||||||
|
}, 15000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
||||||
|
CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
|
||||||
|
IsTryingToConnect = false;
|
||||||
|
CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Disconnect Called");
|
||||||
|
|
||||||
|
DisconnectCalledByUser = true;
|
||||||
|
if (IsConnected)
|
||||||
|
{
|
||||||
|
Client.DisconnectFromServer();
|
||||||
|
|
||||||
|
}
|
||||||
|
if (RetryTimer != null)
|
||||||
|
{
|
||||||
|
RetryTimer.Stop();
|
||||||
|
RetryTimer = null;
|
||||||
|
}
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal call to close up client. ALWAYS use this when disconnecting.
|
||||||
|
/// </summary>
|
||||||
|
void Cleanup()
|
||||||
|
{
|
||||||
|
IsTryingToConnect = false;
|
||||||
|
|
||||||
|
if (Client != null)
|
||||||
|
{
|
||||||
|
//SecureClient.DisconnectFromServer();
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||||
|
Client.SocketStatusChange -= Client_SocketStatusChange;
|
||||||
|
Client.Dispose();
|
||||||
|
Client = null;
|
||||||
|
}
|
||||||
|
if (ConnectFailTimer != null)
|
||||||
|
{
|
||||||
|
ConnectFailTimer.Stop();
|
||||||
|
ConnectFailTimer.Dispose();
|
||||||
|
ConnectFailTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>ff
|
||||||
|
/// Called from Connect failure or Socket Status change if
|
||||||
|
/// auto reconnect and socket disconnected (Not disconnected by user)
|
||||||
|
/// </summary>
|
||||||
|
void CheckClosedAndTryReconnect()
|
||||||
|
{
|
||||||
|
if (Client != null)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Cleaning up remotely closed/failed connection.");
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
if (!DisconnectCalledByUser && AutoReconnect)
|
||||||
|
{
|
||||||
|
var halfInterval = AutoReconnectIntervalMs / 2;
|
||||||
|
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Attempting reconnect in {0} ms, randomized", rndTime);
|
||||||
|
if (RetryTimer != null)
|
||||||
|
{
|
||||||
|
RetryTimer.Stop();
|
||||||
|
RetryTimer = null;
|
||||||
|
}
|
||||||
|
RetryTimer = new CTimer(o => Connect(), rndTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Receive callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="numBytes"></param>
|
||||||
|
void Receive(SecureTCPClient client, int numBytes)
|
||||||
|
{
|
||||||
|
if (numBytes > 0)
|
||||||
|
{
|
||||||
|
string str = string.Empty;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||||
|
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||||
|
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
|
||||||
|
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
||||||
|
{
|
||||||
|
|
||||||
|
if (SharedKeyRequired && str == "SharedKey:")
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Server asking for shared key, sending");
|
||||||
|
SendText(SharedKey + "\n");
|
||||||
|
}
|
||||||
|
else if (SharedKeyRequired && str == "Shared Key Match")
|
||||||
|
{
|
||||||
|
StopWaitForSharedKeyTimer();
|
||||||
|
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Shared key confirmed. Ready for communication");
|
||||||
|
OnClientReadyForcommunications(true); // Successful key exchange
|
||||||
|
}
|
||||||
|
else if (SharedKeyRequired == false && IsReadyForCommunication == false)
|
||||||
|
{
|
||||||
|
OnClientReadyForcommunications(true); // Key not required
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
//var bytesHandler = BytesReceived;
|
||||||
|
//if (bytesHandler != null)
|
||||||
|
// bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
|
var textHandler = TextReceived;
|
||||||
|
if (textHandler != null)
|
||||||
|
textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
client.ReceiveDataAsync(Receive);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeartbeatStart()
|
||||||
|
{
|
||||||
|
if (HeartbeatEnabled)
|
||||||
|
{
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Starting Heartbeat");
|
||||||
|
if (HeartbeatSendTimer == null)
|
||||||
|
{
|
||||||
|
|
||||||
|
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
||||||
|
}
|
||||||
|
if (HeartbeatAckTimer == null)
|
||||||
|
{
|
||||||
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void HeartbeatStop()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (HeartbeatSendTimer != null)
|
||||||
|
{
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stoping Heartbeat Send");
|
||||||
|
HeartbeatSendTimer.Stop();
|
||||||
|
HeartbeatSendTimer = null;
|
||||||
|
}
|
||||||
|
if (HeartbeatAckTimer != null)
|
||||||
|
{
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stoping Heartbeat Ack");
|
||||||
|
HeartbeatAckTimer.Stop();
|
||||||
|
HeartbeatAckTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void SendHeartbeat(object notused)
|
||||||
|
{
|
||||||
|
this.SendText(HeartbeatString);
|
||||||
|
Debug.Console(2, this, "Sending Heartbeat");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//private method to check heartbeat requirements and start or reset timer
|
||||||
|
string checkHeartbeat(string received)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (HeartbeatEnabled)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(HeartbeatString))
|
||||||
|
{
|
||||||
|
var remainingText = received.Replace(HeartbeatString, "");
|
||||||
|
var noDelimiter = received.Trim(new char[] { '\r', '\n' });
|
||||||
|
if (noDelimiter.Contains(HeartbeatString))
|
||||||
|
{
|
||||||
|
if (HeartbeatAckTimer != null)
|
||||||
|
{
|
||||||
|
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
|
}
|
||||||
|
Debug.Console(1, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||||
|
return remainingText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||||
|
}
|
||||||
|
return received;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void HeartbeatAckTimerFail(object o)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
if (IsConnected)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||||
|
SendText("Heartbeat not received by server, closing connection");
|
||||||
|
CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
void StopWaitForSharedKeyTimer()
|
||||||
|
{
|
||||||
|
if (WaitForSharedKey != null)
|
||||||
|
{
|
||||||
|
WaitForSharedKey.Stop();
|
||||||
|
WaitForSharedKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// General send method
|
||||||
|
/// </summary>
|
||||||
|
public void SendText(string text)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
Client.SendDataAsync(bytes, bytes.Length, (c, n) =>
|
||||||
|
{
|
||||||
|
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
||||||
|
if (n <= 0)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public void SendBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
if (bytes.Length > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
Client.SendData(bytes, bytes.Length);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SocketStatusChange Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="clientSocketStatus"></param>
|
||||||
|
void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus)
|
||||||
|
{
|
||||||
|
if (ProgramIsStopping)
|
||||||
|
{
|
||||||
|
ProgramIsStopping = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||||
|
|
||||||
|
OnConnectionChange();
|
||||||
|
// The client could be null or disposed by this time...
|
||||||
|
if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
HeartbeatStop();
|
||||||
|
OnClientReadyForcommunications(false); // socket has gone low
|
||||||
|
CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper for ConnectionChange event
|
||||||
|
/// </summary>
|
||||||
|
void OnConnectionChange()
|
||||||
|
{
|
||||||
|
var handler = ConnectionChange;
|
||||||
|
if (handler != null)
|
||||||
|
ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to fire ClientReadyForCommunications event
|
||||||
|
/// </summary>
|
||||||
|
void OnClientReadyForcommunications(bool isReady)
|
||||||
|
{
|
||||||
|
IsReadyForCommunication = isReady;
|
||||||
|
if (this.IsReadyForCommunication) { HeartbeatStart(); }
|
||||||
|
var handler = ClientReadyForCommunications;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core
|
||||||
{
|
{
|
||||||
@@ -24,20 +26,61 @@ namespace PepperDash.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event for Receiving text
|
/// Event for Receiving text
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event for client connection socket status change
|
/// Event for client connection socket status change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<DynamicTCPSocketStatusChangeEventArgs> ClientConnectionChange;
|
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ClientConnectionChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event for Server State Change
|
/// Event for Server State Change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<DynamicTCPServerStateChangedEventArgs> ServerStateChange;
|
public event EventHandler<GenericTcpServerStateChangedEventArgs> ServerStateChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire
|
||||||
|
/// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ServerClientReadyForCommunications;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A band aid event to notify user that the server has choked.
|
||||||
|
/// </summary>
|
||||||
|
public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; }
|
||||||
|
|
||||||
|
public delegate void ServerHasChokedCallbackDelegate();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Properties/Variables
|
#region Properties/Variables
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
CCriticalSection ServerCCSection = new CCriticalSection();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A bandaid client that monitors whether the server is reachable
|
||||||
|
/// </summary>
|
||||||
|
GenericSecureTcpIpClient_ForServer MonitorClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timer to operate the bandaid monitor client in a loop.
|
||||||
|
/// </summary>
|
||||||
|
CTimer MonitorClientTimer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
int MonitorClientFailureCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 3 by default
|
||||||
|
/// </summary>
|
||||||
|
public int MonitorClientMaxFailureCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text representation of the Socket Status enum values for the server
|
/// Text representation of the Socket Status enum values for the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -45,10 +88,10 @@ namespace PepperDash.Core
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Server != null)
|
if (SecureServer != null)
|
||||||
return Server.State.ToString();
|
return SecureServer.State.ToString();
|
||||||
else
|
|
||||||
return ServerState.SERVER_NOT_LISTENING.ToString();
|
return ServerState.SERVER_NOT_LISTENING.ToString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -58,7 +101,16 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsConnected
|
public bool IsConnected
|
||||||
{
|
{
|
||||||
get { return (Server != null) && (Server.State == ServerState.SERVER_CONNECTED); }
|
get
|
||||||
|
{
|
||||||
|
if (SecureServer != null)
|
||||||
|
return (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED;
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||||
|
//(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED :
|
||||||
|
// (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -74,7 +126,16 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsListening
|
public bool IsListening
|
||||||
{
|
{
|
||||||
get { return (Server != null) && (Server.State == ServerState.SERVER_LISTENING); }
|
get
|
||||||
|
{
|
||||||
|
if (SecureServer != null)
|
||||||
|
return (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
//return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||||
|
//(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING :
|
||||||
|
// (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -93,8 +154,8 @@ namespace PepperDash.Core
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Server != null)
|
if (SecureServer != null)
|
||||||
return (ushort)Server.NumberOfClientsConnected;
|
return (ushort)SecureServer.NumberOfClientsConnected;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,11 +236,13 @@ namespace PepperDash.Core
|
|||||||
//private timers for Heartbeats per client
|
//private timers for Heartbeats per client
|
||||||
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||||
|
|
||||||
//flags to show the server is waiting for client at index to send the shared key
|
//flags to show the secure server is waiting for client at index to send the shared key
|
||||||
List<uint> WaitingForSharedKey = new List<uint>();
|
List<uint> WaitingForSharedKey = new List<uint>();
|
||||||
|
|
||||||
|
List<uint> ClientReadyAfterKeyExchange = new List<uint>();
|
||||||
|
|
||||||
//Store the connected client indexes
|
//Store the connected client indexes
|
||||||
List<uint> ConnectedClientsIndexes = new List<uint>();
|
public List<uint> ConnectedClientsIndexes = new List<uint>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defaults to 2000
|
/// Defaults to 2000
|
||||||
@@ -192,23 +255,72 @@ namespace PepperDash.Core
|
|||||||
private bool ServerStopped { get; set; }
|
private bool ServerStopped { get; set; }
|
||||||
|
|
||||||
//Servers
|
//Servers
|
||||||
SecureTCPServer Server;
|
SecureTCPServer SecureServer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
bool ProgramIsStopping;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// constructor
|
/// constructor S+ Does not accept a key. Use initialze with key to set the debug key on this device. If using with + make sure to set all properties manually.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GenericSecureTcpIpServer()
|
public GenericSecureTcpIpServer()
|
||||||
: base("Uninitialized Dynamic TCP Server")
|
: base("Uninitialized Dynamic TCP Server")
|
||||||
{
|
{
|
||||||
|
HeartbeatRequiredIntervalInSeconds = 15;
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
BufferSize = 2000;
|
BufferSize = 2000;
|
||||||
|
MonitorClientMaxFailureCount = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// constructor with debug key set at instantiation. Make sure to set all properties before listening.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
public GenericSecureTcpIpServer(string key)
|
||||||
|
: base("Uninitialized Dynamic TCP Server")
|
||||||
|
{
|
||||||
|
HeartbeatRequiredIntervalInSeconds = 15;
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
BufferSize = 2000;
|
||||||
|
MonitorClientMaxFailureCount = 3;
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contstructor that sets all properties by calling the initialize method with a config object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverConfigObject"></param>
|
||||||
|
public GenericSecureTcpIpServer(TcpServerConfigObject serverConfigObject)
|
||||||
|
: base("Uninitialized Dynamic TCP Server")
|
||||||
|
{
|
||||||
|
HeartbeatRequiredIntervalInSeconds = 15;
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
BufferSize = 2000;
|
||||||
|
MonitorClientMaxFailureCount = 3;
|
||||||
|
Initialize(serverConfigObject);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods - Server Actions
|
#region Methods - Server Actions
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnects all clients and stops the server
|
||||||
|
/// </summary>
|
||||||
|
public void KillServer()
|
||||||
|
{
|
||||||
|
ServerStopped = true;
|
||||||
|
if (MonitorClient != null)
|
||||||
|
{
|
||||||
|
MonitorClient.Disconnect();
|
||||||
|
}
|
||||||
|
DisconnectAllClientsForShutdown();
|
||||||
|
StopListening();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -218,37 +330,83 @@ namespace PepperDash.Core
|
|||||||
Key = key;
|
Key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Initialize(TcpServerConfigObject serverConfigObject)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key))
|
||||||
|
{
|
||||||
|
Key = serverConfigObject.Key;
|
||||||
|
MaxClients = serverConfigObject.MaxClients;
|
||||||
|
Port = serverConfigObject.Port;
|
||||||
|
SharedKeyRequired = serverConfigObject.SharedKeyRequired;
|
||||||
|
SharedKey = serverConfigObject.SharedKey;
|
||||||
|
HeartbeatRequired = serverConfigObject.HeartbeatRequired;
|
||||||
|
HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds;
|
||||||
|
HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch;
|
||||||
|
BufferSize = serverConfigObject.BufferSize;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start listening on the specified port
|
/// Start listening on the specified port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Listen()
|
public void Listen()
|
||||||
{
|
{
|
||||||
|
ServerCCSection.Enter();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Server '{0}': Invalid port", Key);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
{
|
{
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Server '{0}': No Shared Key set", Key);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (IsListening)
|
if (IsListening)
|
||||||
return;
|
return;
|
||||||
Server = new SecureTCPServer(Port, MaxClients);
|
|
||||||
Server.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SocketStatusChange);
|
if (SecureServer == null)
|
||||||
|
{
|
||||||
|
SecureServer = new SecureTCPServer(Port, MaxClients);
|
||||||
|
SecureServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5);
|
||||||
|
SecureServer.HandshakeTimeout = 30;
|
||||||
|
SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
KillServer();
|
||||||
|
SecureServer.PortNumber = Port;
|
||||||
|
}
|
||||||
ServerStopped = false;
|
ServerStopped = false;
|
||||||
Server.WaitForConnectionAsync(IPAddress.Any, ConnectCallback);
|
SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||||
onServerStateChange();
|
OnServerStateChange(SecureServer.State);
|
||||||
Debug.Console(2, "Server Status: {0}, Socket Status: {1}\r\n", Server.State.ToString(), Server.ServerSocketStatus);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus);
|
||||||
|
|
||||||
|
// StartMonitorClient();
|
||||||
|
|
||||||
|
|
||||||
|
ServerCCSection.Leave();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ErrorLog.Error("Error with Dynamic Server: {0}", ex.ToString());
|
ServerCCSection.Leave();
|
||||||
|
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,23 +415,80 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopListening()
|
public void StopListening()
|
||||||
{
|
{
|
||||||
Debug.Console(2, "Stopping Listener");
|
try
|
||||||
if (Server != null)
|
{
|
||||||
Server.Stop();
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
|
||||||
|
if (SecureServer != null)
|
||||||
|
{
|
||||||
|
SecureServer.Stop();
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State);
|
||||||
|
//SecureServer = null;
|
||||||
|
}
|
||||||
|
|
||||||
ServerStopped = true;
|
ServerStopped = true;
|
||||||
onServerStateChange();
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Stopped");
|
||||||
|
|
||||||
|
OnServerStateChange(SecureServer.State);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnects Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
public void DisconnectClient(uint client)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SecureServer.Disconnect(client);
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnect All Clients
|
/// Disconnect All Clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DisconnectAllClients()
|
public void DisconnectAllClientsForShutdown()
|
||||||
{
|
{
|
||||||
Debug.Console(2, "Disconnecting All Clients");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
|
||||||
if (Server != null)
|
if (SecureServer != null)
|
||||||
Server.DisconnectAll();
|
{
|
||||||
onConnectionChange();
|
SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange;
|
||||||
onServerStateChange(); //State shows both listening and connected
|
foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly
|
||||||
|
{
|
||||||
|
var i = index;
|
||||||
|
if (!SecureServer.ClientConnected(index))
|
||||||
|
continue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SecureServer.Disconnect(i);
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
|
||||||
|
ConnectedClientsIndexes.Clear();
|
||||||
|
|
||||||
|
if (!ProgramIsStopping)
|
||||||
|
{
|
||||||
|
OnConnectionChange();
|
||||||
|
OnServerStateChange(SecureServer.State); //State shows both listening and connected
|
||||||
|
}
|
||||||
|
|
||||||
|
// var o = new { };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -281,12 +496,30 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
public void BroadcastText(string text)
|
public void BroadcastText(string text)
|
||||||
|
{
|
||||||
|
CCriticalSection CCBroadcast = new CCriticalSection();
|
||||||
|
CCBroadcast.Enter();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Count > 0)
|
if (ConnectedClientsIndexes.Count > 0)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
foreach (uint i in ConnectedClientsIndexes)
|
foreach (uint i in ConnectedClientsIndexes)
|
||||||
Server.SendDataAsync(i, b, b.Length, SendDataAsyncCallback);
|
{
|
||||||
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i)))
|
||||||
|
{
|
||||||
|
SocketErrorCodes error = SecureServer.SendDataAsync(i, b, b.Length, (x, y, z) => { });
|
||||||
|
if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
||||||
|
Debug.Console(0, error.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CCBroadcast.Leave();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CCBroadcast.Leave();
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,19 +529,34 @@ namespace PepperDash.Core
|
|||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
public void SendTextToClient(string text, uint clientIndex)
|
public void SendTextToClient(string text, uint clientIndex)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
Server.SendDataAsync(clientIndex, b, b.Length, SendDataAsyncCallback);
|
if (SecureServer != null && SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||||
|
SecureServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//private method to check heartbeat requirements and start or reset timer
|
//private method to check heartbeat requirements and start or reset timer
|
||||||
void checkHeartbeat(uint clientIndex, string received)
|
string checkHeartbeat(uint clientIndex, string received)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (HeartbeatRequired)
|
if (HeartbeatRequired)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(HeartbeatStringToMatch))
|
if (!string.IsNullOrEmpty(HeartbeatStringToMatch))
|
||||||
{
|
{
|
||||||
if (received == HeartbeatStringToMatch)
|
var remainingText = received.Replace(HeartbeatStringToMatch, "");
|
||||||
|
var noDelimiter = received.Trim(new char[] { '\r', '\n' });
|
||||||
|
if (noDelimiter.Contains(HeartbeatStringToMatch))
|
||||||
{
|
{
|
||||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
@@ -317,6 +565,10 @@ namespace PepperDash.Core
|
|||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
}
|
}
|
||||||
|
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||||
|
// Return Heartbeat
|
||||||
|
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
||||||
|
return remainingText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -328,108 +580,195 @@ namespace PepperDash.Core
|
|||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
}
|
}
|
||||||
|
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||||
|
}
|
||||||
|
return received;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetClientIPAddress(uint clientIndex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
|
||||||
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||||
|
{
|
||||||
|
var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
|
||||||
|
return ipa;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods - Callbacks
|
#region Methods - HeartbeatTimer Callback
|
||||||
/// <summary>
|
|
||||||
/// Callback to disconnect if heartbeat timer finishes without being reset
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="o"></param>
|
|
||||||
void HeartbeatTimer_CallbackFunction(object o)
|
void HeartbeatTimer_CallbackFunction(object o)
|
||||||
{
|
{
|
||||||
uint clientIndex = (uint)o;
|
uint clientIndex = 99999;
|
||||||
|
string address = string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
clientIndex = (uint)o;
|
||||||
|
address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
|
||||||
string address = Server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||||
|
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
||||||
ErrorLog.Error("Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address);
|
|
||||||
Debug.Console(2, "Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address);
|
|
||||||
|
|
||||||
|
if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||||
Server.Disconnect(clientIndex);
|
|
||||||
|
var discoResult = SecureServer.Disconnect(clientIndex);
|
||||||
|
//Debug.Console(1, this, "{0}", discoResult);
|
||||||
|
|
||||||
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
{
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Stop();
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Dispose();
|
||||||
HeartbeatTimerDictionary.Remove(clientIndex);
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Socket Status Changed Callbacks
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TCP Server Socket Status Change Callback
|
/// Secure Server Socket Status Changed Callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="server"></param>
|
/// <param name="mySecureTCPServer"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
/// <param name="serverSocketStatus"></param>
|
/// <param name="serverSocketStatus"></param>
|
||||||
void SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
||||||
{
|
{
|
||||||
Debug.Console(2, "Client at {0} ServerSocketStatus {1}",
|
try
|
||||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString());
|
|
||||||
if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
{
|
||||||
if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex))
|
|
||||||
WaitingForSharedKey.Add(clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
ConnectedClientsIndexes.Add(clientIndex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
ConnectedClientsIndexes.Remove(clientIndex);
|
ConnectedClientsIndexes.Remove(clientIndex);
|
||||||
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
{
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Stop();
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Dispose();
|
||||||
HeartbeatTimerDictionary.Remove(clientIndex);
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
}
|
}
|
||||||
if (Server.ServerSocketStatus.ToString() != Status)
|
if (ClientReadyAfterKeyExchange.Contains(clientIndex))
|
||||||
onConnectionChange();
|
ClientReadyAfterKeyExchange.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
|
||||||
|
}
|
||||||
|
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods Connected Callbacks
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TCP Client Connected to Server Callback
|
/// Secure TCP Client Connected to Secure Server Callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mySecureTCPServer"></param>
|
/// <param name="mySecureTCPServer"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
void ConnectCallback(SecureTCPServer mySecureTCPServer, uint clientIndex)
|
void SecureConnectCallback(SecureTCPServer server, uint clientIndex)
|
||||||
{
|
{
|
||||||
if (mySecureTCPServer.ClientConnected(clientIndex))
|
try
|
||||||
{
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||||
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
||||||
|
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
|
if (clientIndex != 0)
|
||||||
|
{
|
||||||
|
if (server.ClientConnected(clientIndex))
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
{
|
||||||
|
ConnectedClientsIndexes.Add(clientIndex);
|
||||||
|
}
|
||||||
if (SharedKeyRequired)
|
if (SharedKeyRequired)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n");
|
if (!WaitingForSharedKey.Contains(clientIndex))
|
||||||
mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, SendDataAsyncCallback);
|
{
|
||||||
Debug.Console(2, "Sent Shared Key to client at {0}", mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
WaitingForSharedKey.Add(clientIndex);
|
||||||
|
}
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
||||||
|
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
}
|
}
|
||||||
if (HeartbeatRequired)
|
if (HeartbeatRequired)
|
||||||
{
|
{
|
||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
|
||||||
}
|
|
||||||
mySecureTCPServer.ReceiveDataAsync(clientIndex, ReceivedDataAsyncCallback);
|
|
||||||
if (mySecureTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
|
||||||
mySecureTCPServer.WaitForConnectionAsync(IPAddress.Any, ConnectCallback);
|
|
||||||
}
|
|
||||||
if (mySecureTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
|
||||||
mySecureTCPServer.WaitForConnectionAsync(IPAddress.Any, ConnectCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send Data Asyc Callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mySecureTCPServer"></param>
|
|
||||||
/// <param name="clientIndex"></param>
|
|
||||||
/// <param name="numberOfBytesSent"></param>
|
|
||||||
void SendDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesSent)
|
|
||||||
{
|
{
|
||||||
//Seems there is nothing to do here
|
HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
|
||||||
|
if (!ServerStopped)
|
||||||
|
{
|
||||||
|
server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
|
||||||
|
}
|
||||||
|
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
|
||||||
|
// server.State,
|
||||||
|
// MaxClients,
|
||||||
|
// ServerStopped);
|
||||||
|
if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Waiting for next connection");
|
||||||
|
server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Send/Receive Callbacks
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Received Data Async Callback
|
/// Secure Received Data Async Callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mySecureTCPServer"></param>
|
/// <param name="mySecureTCPServer"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
/// <param name="numberOfBytesReceived"></param>
|
/// <param name="numberOfBytesReceived"></param>
|
||||||
void ReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived)
|
void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived)
|
||||||
{
|
{
|
||||||
if (numberOfBytesReceived > 0)
|
if (numberOfBytesReceived > 0)
|
||||||
{
|
{
|
||||||
string received = "Nothing";
|
string received = "Nothing";
|
||||||
|
try
|
||||||
|
{
|
||||||
byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
||||||
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
||||||
if (WaitingForSharedKey.Contains(clientIndex))
|
if (WaitingForSharedKey.Contains(clientIndex))
|
||||||
@@ -439,65 +778,207 @@ namespace PepperDash.Core
|
|||||||
if (received != SharedKey)
|
if (received != SharedKey)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||||
Debug.Console(2, "Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||||
ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
mySecureTCPServer.SendData(clientIndex, b, b.Length);
|
||||||
mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, null);
|
|
||||||
mySecureTCPServer.Disconnect(clientIndex);
|
mySecureTCPServer.Disconnect(clientIndex);
|
||||||
|
WaitingForSharedKey.Remove(clientIndex);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (mySecureTCPServer.NumberOfClientsConnected > 0)
|
if (mySecureTCPServer.NumberOfClientsConnected > 0)
|
||||||
mySecureTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||||
WaitingForSharedKey.Remove(clientIndex);
|
WaitingForSharedKey.Remove(clientIndex);
|
||||||
byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication");
|
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
||||||
mySecureTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null);
|
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
||||||
mySecureTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
//var address = mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
//Debug.Console(1, this, "Secure Server Listening on Port: {0}, client IP: {1}, Client Index: {4}, NumberOfBytesReceived: {2}, Received: {3}\r\n",
|
||||||
|
// mySecureTCPServer.PortNumber.ToString(), address , numberOfBytesReceived.ToString(), received, clientIndex.ToString());
|
||||||
|
if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
||||||
|
onTextReceived(received, clientIndex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
mySecureTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
|
||||||
Debug.Console(2, "Server Listening on Port: {0}, client IP: {1}, NumberOfBytesReceived: {2}, Received: {3}\r\n",
|
|
||||||
mySecureTCPServer.PortNumber, mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), numberOfBytesReceived, received);
|
|
||||||
onTextReceived(received);
|
|
||||||
}
|
}
|
||||||
checkHeartbeat(clientIndex, received);
|
|
||||||
}
|
}
|
||||||
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
mySecureTCPServer.ReceiveDataAsync(clientIndex, ReceivedDataAsyncCallback);
|
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods - EventHelpers/Callbacks
|
#region Methods - EventHelpers/Callbacks
|
||||||
|
|
||||||
//Private Helper method to call the Connection Change Event
|
//Private Helper method to call the Connection Change Event
|
||||||
void onConnectionChange()
|
void onConnectionChange(uint clientIndex, SocketStatus clientStatus)
|
||||||
|
{
|
||||||
|
if (clientIndex != 0) //0 is error not valid client change
|
||||||
{
|
{
|
||||||
var handler = ClientConnectionChange;
|
var handler = ClientConnectionChange;
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
handler(this, new DynamicTCPSocketStatusChangeEventArgs(Server, false));
|
{
|
||||||
|
handler(this, new GenericTcpServerSocketStatusChangeEventArgs(SecureServer, clientIndex, clientStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Helper method to call the Connection Change Event
|
||||||
|
void OnConnectionChange()
|
||||||
|
{
|
||||||
|
if (ProgramIsStopping)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var handler = ClientConnectionChange;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, new GenericTcpServerSocketStatusChangeEventArgs());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Private Helper Method to call the Text Received Event
|
//Private Helper Method to call the Text Received Event
|
||||||
void onTextReceived(string text)
|
void onTextReceived(string text, uint clientIndex)
|
||||||
{
|
{
|
||||||
var handler = TextReceived;
|
var handler = TextReceived;
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
handler(this, new GenericCommMethodReceiveTextArgs(text));
|
handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Private Helper Method to call the Server State Change Event
|
//Private Helper Method to call the Server State Change Event
|
||||||
void onServerStateChange()
|
void OnServerStateChange(ServerState state)
|
||||||
{
|
{
|
||||||
|
if (ProgramIsStopping)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
var handler = ServerStateChange;
|
var handler = ServerStateChange;
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
handler(this, new DynamicTCPServerStateChangedEventArgs(Server, false));
|
{
|
||||||
|
handler(this, new GenericTcpServerStateChangedEventArgs(state));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Private Event Handler method to handle the closing of connections when the program stops
|
/// <summary>
|
||||||
|
/// Private Event Handler method to handle the closing of connections when the program stops
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="programEventType"></param>
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
{
|
{
|
||||||
if (programEventType == eProgramStatusEventType.Stopping)
|
if (programEventType == eProgramStatusEventType.Stopping)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Program stopping. Closing server");
|
ProgramIsStopping = true;
|
||||||
DisconnectAllClients();
|
// kill bandaid things
|
||||||
StopListening();
|
if (MonitorClientTimer != null)
|
||||||
|
MonitorClientTimer.Stop();
|
||||||
|
if (MonitorClient != null)
|
||||||
|
MonitorClient.Disconnect();
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
|
||||||
|
KillServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation
|
||||||
|
void OnServerClientReadyForCommunications(uint clientIndex)
|
||||||
|
{
|
||||||
|
ClientReadyAfterKeyExchange.Add(clientIndex);
|
||||||
|
var handler = ServerClientReadyForCommunications;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new GenericTcpServerSocketStatusChangeEventArgs(
|
||||||
|
this, clientIndex, SecureServer.GetServerSocketStatusForSpecificClient(clientIndex)));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Monitor Client
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the monitor client cycle. Timed wait, then call RunMonitorClient
|
||||||
|
/// </summary>
|
||||||
|
void StartMonitorClient()
|
||||||
|
{
|
||||||
|
if (MonitorClientTimer != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
void RunMonitorClient()
|
||||||
|
{
|
||||||
|
MonitorClient = new GenericSecureTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000);
|
||||||
|
MonitorClient.SharedKeyRequired = this.SharedKeyRequired;
|
||||||
|
MonitorClient.SharedKey = this.SharedKey;
|
||||||
|
MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback;
|
||||||
|
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
||||||
|
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
|
||||||
|
|
||||||
|
MonitorClient.Connect();
|
||||||
|
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
void StopMonitorClient()
|
||||||
|
{
|
||||||
|
if (MonitorClient == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm;
|
||||||
|
MonitorClient.Disconnect();
|
||||||
|
MonitorClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// On monitor connect, restart the operation
|
||||||
|
/// </summary>
|
||||||
|
void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.IsReady)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
|
||||||
|
MonitorClientTimer.Stop();
|
||||||
|
MonitorClientTimer = null;
|
||||||
|
MonitorClientFailureCount = 0;
|
||||||
|
CrestronEnvironment.Sleep(2000);
|
||||||
|
StopMonitorClient();
|
||||||
|
StartMonitorClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the client hangs, add to counter and maybe fire the choke event
|
||||||
|
/// </summary>
|
||||||
|
void MonitorClientHasHungCallback()
|
||||||
|
{
|
||||||
|
MonitorClientFailureCount++;
|
||||||
|
MonitorClientTimer.Stop();
|
||||||
|
MonitorClientTimer = null;
|
||||||
|
StopMonitorClient();
|
||||||
|
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||||
|
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
||||||
|
StartMonitorClient();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error,
|
||||||
|
"\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************",
|
||||||
|
MonitorClientMaxFailureCount);
|
||||||
|
|
||||||
|
var handler = ServerHasChoked;
|
||||||
|
if (handler != null)
|
||||||
|
handler();
|
||||||
|
// Some external thing is in charge here. Expected reset of program
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -0,0 +1,739 @@
|
|||||||
|
/*PepperDash Technology Corp.
|
||||||
|
JAG
|
||||||
|
Copyright: 2017
|
||||||
|
------------------------------------
|
||||||
|
***Notice of Ownership and Copyright***
|
||||||
|
The material in which this notice appears is the property of PepperDash Technology Corporation,
|
||||||
|
which claims copyright under the laws of the United States of America in the entire body of material
|
||||||
|
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
|
||||||
|
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
|
||||||
|
PepperDash Technology Corporation reserves all rights under applicable laws.
|
||||||
|
------------------------------------ */
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Band aid delegate for choked server
|
||||||
|
/// </summary>
|
||||||
|
internal delegate void ConnectionHasHungCallbackDelegate();
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
//public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||||
|
|
||||||
|
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
|
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ConnectionChange;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is something of a band-aid callback. If the client times out during the connection process, because the server
|
||||||
|
/// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help
|
||||||
|
/// keep a watch on the server and reset it if necessary.
|
||||||
|
/// </summary>
|
||||||
|
internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require
|
||||||
|
/// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericTcpServerClientReadyForcommunicationsEventArgs> ClientReadyForCommunications;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Variables
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Address of server
|
||||||
|
/// </summary>
|
||||||
|
public string Hostname { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Port on server
|
||||||
|
/// </summary>
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper
|
||||||
|
/// </summary>
|
||||||
|
public ushort UPort
|
||||||
|
{
|
||||||
|
get { return Convert.ToUInt16(Port); }
|
||||||
|
set { Port = Convert.ToInt32(value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
|
||||||
|
/// </summary>
|
||||||
|
public bool SharedKeyRequired { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for requires shared key bool
|
||||||
|
/// </summary>
|
||||||
|
public ushort USharedKeyRequired
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == 1)
|
||||||
|
SharedKeyRequired = true;
|
||||||
|
else
|
||||||
|
SharedKeyRequired = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
||||||
|
/// </summary>
|
||||||
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// flag to show the client is waiting for the server to send the shared key
|
||||||
|
/// </summary>
|
||||||
|
private bool WaitingForSharedKeyResponse { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to 2000
|
||||||
|
/// </summary>
|
||||||
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Semaphore on connect method
|
||||||
|
/// </summary>
|
||||||
|
bool IsTryingToConnect;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Client != null)
|
||||||
|
return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsConnected
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsConnected
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is ready for communication after shared key exchange
|
||||||
|
/// </summary>
|
||||||
|
public bool IsReadyForCommunication { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsReadyForCommunication
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsReadyForCommunication
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsReadyForCommunication ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client socket status Read only
|
||||||
|
/// </summary>
|
||||||
|
public SocketStatus ClientStatus
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Client != null)
|
||||||
|
return Client.ClientStatus;
|
||||||
|
else
|
||||||
|
return SocketStatus.SOCKET_STATUS_NO_CONNECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
||||||
|
/// and IsConnected would be true when this == 2.
|
||||||
|
/// </summary>
|
||||||
|
public ushort UStatus
|
||||||
|
{
|
||||||
|
get { return (ushort)ClientStatus; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status text shows the message associated with socket status
|
||||||
|
/// </summary>
|
||||||
|
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// bool to track if auto reconnect should be set on the socket
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoReconnect { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for AutoReconnect
|
||||||
|
/// </summary>
|
||||||
|
public ushort UAutoReconnect
|
||||||
|
{
|
||||||
|
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
||||||
|
set { AutoReconnect = value == 1; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
||||||
|
/// </summary>
|
||||||
|
public int AutoReconnectIntervalMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag Set only when the disconnect method is called.
|
||||||
|
/// </summary>
|
||||||
|
bool DisconnectCalledByUser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// private Timer for auto reconnect
|
||||||
|
/// </summary>
|
||||||
|
CTimer RetryTimer;
|
||||||
|
|
||||||
|
|
||||||
|
public bool HeartbeatEnabled { get; set; }
|
||||||
|
public ushort UHeartbeatEnabled
|
||||||
|
{
|
||||||
|
get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
|
||||||
|
set { HeartbeatEnabled = value == 1; }
|
||||||
|
}
|
||||||
|
public string HeartbeatString = "heartbeat";
|
||||||
|
public int HeartbeatInterval = 50000;
|
||||||
|
CTimer HeartbeatSendTimer;
|
||||||
|
CTimer HeartbeatAckTimer;
|
||||||
|
/// <summary>
|
||||||
|
/// Used to force disconnection on a dead connect attempt
|
||||||
|
/// </summary>
|
||||||
|
CTimer ConnectFailTimer;
|
||||||
|
CTimer WaitForSharedKey;
|
||||||
|
private int ConnectionCount;
|
||||||
|
/// <summary>
|
||||||
|
/// Internal secure client
|
||||||
|
/// </summary>
|
||||||
|
TCPClient Client;
|
||||||
|
|
||||||
|
bool ProgramIsStopping;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
//Base class constructor
|
||||||
|
public GenericTcpIpClient_ForServer(string key, string address, int port, int bufferSize)
|
||||||
|
: base(key)
|
||||||
|
{
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
Hostname = address;
|
||||||
|
Port = port;
|
||||||
|
BufferSize = bufferSize;
|
||||||
|
AutoReconnectIntervalMs = 5000;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//base class constructor
|
||||||
|
public GenericTcpIpClient_ForServer()
|
||||||
|
: base("Uninitialized DynamicTcpClient")
|
||||||
|
{
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
AutoReconnectIntervalMs = 5000;
|
||||||
|
BufferSize = 2000;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Just to help S+ set the key
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles closing this up when the program shuts down
|
||||||
|
/// </summary>
|
||||||
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
|
{
|
||||||
|
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
|
||||||
|
ProgramIsStopping = true;
|
||||||
|
Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
|
||||||
|
/// </summary>
|
||||||
|
public void Connect()
|
||||||
|
{
|
||||||
|
ConnectionCount++;
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Attempting connect Count:{0}", ConnectionCount);
|
||||||
|
|
||||||
|
|
||||||
|
if (IsConnected)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsTryingToConnect)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsTryingToConnect = true;
|
||||||
|
if (RetryTimer != null)
|
||||||
|
{
|
||||||
|
RetryTimer.Stop();
|
||||||
|
RetryTimer = null;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(Hostname))
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Port < 1 || Port > 65535)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up previous client
|
||||||
|
if (Client != null)
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
DisconnectCalledByUser = false;
|
||||||
|
|
||||||
|
Client = new TCPClient(Hostname, Port, BufferSize);
|
||||||
|
Client.SocketStatusChange += Client_SocketStatusChange;
|
||||||
|
Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
|
||||||
|
Client.AddressClientConnectedTo = Hostname;
|
||||||
|
Client.PortNumber = Port;
|
||||||
|
// SecureClient = c;
|
||||||
|
|
||||||
|
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||||
|
|
||||||
|
ConnectFailTimer = new CTimer(o =>
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||||
|
if (IsTryingToConnect)
|
||||||
|
{
|
||||||
|
IsTryingToConnect = false;
|
||||||
|
|
||||||
|
//if (ConnectionHasHungCallback != null)
|
||||||
|
//{
|
||||||
|
// ConnectionHasHungCallback();
|
||||||
|
//}
|
||||||
|
//SecureClient.DisconnectFromServer();
|
||||||
|
//CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Making Connection Count:{0}", ConnectionCount);
|
||||||
|
Client.ConnectToServerAsync(o =>
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||||
|
|
||||||
|
if (ConnectFailTimer != null)
|
||||||
|
{
|
||||||
|
ConnectFailTimer.Stop();
|
||||||
|
}
|
||||||
|
IsTryingToConnect = false;
|
||||||
|
|
||||||
|
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||||
|
o.ReceiveDataAsync(Receive);
|
||||||
|
|
||||||
|
if (SharedKeyRequired)
|
||||||
|
{
|
||||||
|
WaitingForSharedKeyResponse = true;
|
||||||
|
WaitForSharedKey = new CTimer(timer =>
|
||||||
|
{
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||||
|
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
||||||
|
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
||||||
|
o.DisconnectFromServer();
|
||||||
|
//CheckClosedAndTryReconnect();
|
||||||
|
//OnClientReadyForcommunications(false); // Should send false event
|
||||||
|
}, 15000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
||||||
|
CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
|
||||||
|
IsTryingToConnect = false;
|
||||||
|
CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Disconnect Called");
|
||||||
|
|
||||||
|
DisconnectCalledByUser = true;
|
||||||
|
if (IsConnected)
|
||||||
|
{
|
||||||
|
Client.DisconnectFromServer();
|
||||||
|
|
||||||
|
}
|
||||||
|
if (RetryTimer != null)
|
||||||
|
{
|
||||||
|
RetryTimer.Stop();
|
||||||
|
RetryTimer = null;
|
||||||
|
}
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal call to close up client. ALWAYS use this when disconnecting.
|
||||||
|
/// </summary>
|
||||||
|
void Cleanup()
|
||||||
|
{
|
||||||
|
IsTryingToConnect = false;
|
||||||
|
|
||||||
|
if (Client != null)
|
||||||
|
{
|
||||||
|
//SecureClient.DisconnectFromServer();
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||||
|
Client.SocketStatusChange -= Client_SocketStatusChange;
|
||||||
|
Client.Dispose();
|
||||||
|
Client = null;
|
||||||
|
}
|
||||||
|
if (ConnectFailTimer != null)
|
||||||
|
{
|
||||||
|
ConnectFailTimer.Stop();
|
||||||
|
ConnectFailTimer.Dispose();
|
||||||
|
ConnectFailTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>ff
|
||||||
|
/// Called from Connect failure or Socket Status change if
|
||||||
|
/// auto reconnect and socket disconnected (Not disconnected by user)
|
||||||
|
/// </summary>
|
||||||
|
void CheckClosedAndTryReconnect()
|
||||||
|
{
|
||||||
|
if (Client != null)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Cleaning up remotely closed/failed connection.");
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
if (!DisconnectCalledByUser && AutoReconnect)
|
||||||
|
{
|
||||||
|
var halfInterval = AutoReconnectIntervalMs / 2;
|
||||||
|
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Attempting reconnect in {0} ms, randomized", rndTime);
|
||||||
|
if (RetryTimer != null)
|
||||||
|
{
|
||||||
|
RetryTimer.Stop();
|
||||||
|
RetryTimer = null;
|
||||||
|
}
|
||||||
|
RetryTimer = new CTimer(o => Connect(), rndTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Receive callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="numBytes"></param>
|
||||||
|
void Receive(TCPClient client, int numBytes)
|
||||||
|
{
|
||||||
|
if (numBytes > 0)
|
||||||
|
{
|
||||||
|
string str = string.Empty;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||||
|
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||||
|
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
|
||||||
|
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
||||||
|
{
|
||||||
|
|
||||||
|
if (SharedKeyRequired && str == "SharedKey:")
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Server asking for shared key, sending");
|
||||||
|
SendText(SharedKey + "\n");
|
||||||
|
}
|
||||||
|
else if (SharedKeyRequired && str == "Shared Key Match")
|
||||||
|
{
|
||||||
|
StopWaitForSharedKeyTimer();
|
||||||
|
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Shared key confirmed. Ready for communication");
|
||||||
|
OnClientReadyForcommunications(true); // Successful key exchange
|
||||||
|
}
|
||||||
|
else if (SharedKeyRequired == false && IsReadyForCommunication == false)
|
||||||
|
{
|
||||||
|
OnClientReadyForcommunications(true); // Key not required
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
//var bytesHandler = BytesReceived;
|
||||||
|
//if (bytesHandler != null)
|
||||||
|
// bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
|
var textHandler = TextReceived;
|
||||||
|
if (textHandler != null)
|
||||||
|
textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
client.ReceiveDataAsync(Receive);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeartbeatStart()
|
||||||
|
{
|
||||||
|
if (HeartbeatEnabled)
|
||||||
|
{
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Starting Heartbeat");
|
||||||
|
if (HeartbeatSendTimer == null)
|
||||||
|
{
|
||||||
|
|
||||||
|
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
||||||
|
}
|
||||||
|
if (HeartbeatAckTimer == null)
|
||||||
|
{
|
||||||
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void HeartbeatStop()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (HeartbeatSendTimer != null)
|
||||||
|
{
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stoping Heartbeat Send");
|
||||||
|
HeartbeatSendTimer.Stop();
|
||||||
|
HeartbeatSendTimer = null;
|
||||||
|
}
|
||||||
|
if (HeartbeatAckTimer != null)
|
||||||
|
{
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stoping Heartbeat Ack");
|
||||||
|
HeartbeatAckTimer.Stop();
|
||||||
|
HeartbeatAckTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void SendHeartbeat(object notused)
|
||||||
|
{
|
||||||
|
this.SendText(HeartbeatString);
|
||||||
|
Debug.Console(2, this, "Sending Heartbeat");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//private method to check heartbeat requirements and start or reset timer
|
||||||
|
string checkHeartbeat(string received)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (HeartbeatEnabled)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(HeartbeatString))
|
||||||
|
{
|
||||||
|
var remainingText = received.Replace(HeartbeatString, "");
|
||||||
|
var noDelimiter = received.Trim(new char[] { '\r', '\n' });
|
||||||
|
if (noDelimiter.Contains(HeartbeatString))
|
||||||
|
{
|
||||||
|
if (HeartbeatAckTimer != null)
|
||||||
|
{
|
||||||
|
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
|
}
|
||||||
|
Debug.Console(1, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||||
|
return remainingText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||||
|
}
|
||||||
|
return received;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void HeartbeatAckTimerFail(object o)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
if (IsConnected)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||||
|
SendText("Heartbeat not received by server, closing connection");
|
||||||
|
CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
void StopWaitForSharedKeyTimer()
|
||||||
|
{
|
||||||
|
if (WaitForSharedKey != null)
|
||||||
|
{
|
||||||
|
WaitForSharedKey.Stop();
|
||||||
|
WaitForSharedKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// General send method
|
||||||
|
/// </summary>
|
||||||
|
public void SendText(string text)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
Client.SendDataAsync(bytes, bytes.Length, (c, n) =>
|
||||||
|
{
|
||||||
|
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
||||||
|
if (n <= 0)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public void SendBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
if (bytes.Length > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
Client.SendData(bytes, bytes.Length);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SocketStatusChange Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="clientSocketStatus"></param>
|
||||||
|
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
|
||||||
|
{
|
||||||
|
if (ProgramIsStopping)
|
||||||
|
{
|
||||||
|
ProgramIsStopping = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||||
|
|
||||||
|
OnConnectionChange();
|
||||||
|
// The client could be null or disposed by this time...
|
||||||
|
if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
HeartbeatStop();
|
||||||
|
OnClientReadyForcommunications(false); // socket has gone low
|
||||||
|
CheckClosedAndTryReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper for ConnectionChange event
|
||||||
|
/// </summary>
|
||||||
|
void OnConnectionChange()
|
||||||
|
{
|
||||||
|
var handler = ConnectionChange;
|
||||||
|
if (handler != null)
|
||||||
|
ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to fire ClientReadyForCommunications event
|
||||||
|
/// </summary>
|
||||||
|
void OnClientReadyForcommunications(bool isReady)
|
||||||
|
{
|
||||||
|
IsReadyForCommunication = isReady;
|
||||||
|
if (this.IsReadyForCommunication) { HeartbeatStart(); }
|
||||||
|
var handler = ClientReadyForCommunications;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core
|
||||||
{
|
{
|
||||||
@@ -24,20 +26,61 @@ namespace PepperDash.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event for Receiving text
|
/// Event for Receiving text
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event for client connection socket status change
|
/// Event for client connection socket status change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<DynamicTCPSocketStatusChangeEventArgs> ClientConnectionChange;
|
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ClientConnectionChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event for Server State Change
|
/// Event for Server State Change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<DynamicTCPServerStateChangedEventArgs> ServerStateChange;
|
public event EventHandler<GenericTcpServerStateChangedEventArgs> ServerStateChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire
|
||||||
|
/// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ServerClientReadyForCommunications;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A band aid event to notify user that the server has choked.
|
||||||
|
/// </summary>
|
||||||
|
public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; }
|
||||||
|
|
||||||
|
public delegate void ServerHasChokedCallbackDelegate();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Properties/Variables
|
#region Properties/Variables
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
CCriticalSection ServerCCSection = new CCriticalSection();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A bandaid client that monitors whether the server is reachable
|
||||||
|
/// </summary>
|
||||||
|
GenericTcpIpClient_ForServer MonitorClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timer to operate the bandaid monitor client in a loop.
|
||||||
|
/// </summary>
|
||||||
|
CTimer MonitorClientTimer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
int MonitorClientFailureCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 3 by default
|
||||||
|
/// </summary>
|
||||||
|
public int MonitorClientMaxFailureCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text representation of the Socket Status enum values for the server
|
/// Text representation of the Socket Status enum values for the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -45,10 +88,10 @@ namespace PepperDash.Core
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Server != null)
|
if (myTcpServer != null)
|
||||||
return Server.State.ToString();
|
return myTcpServer.State.ToString();
|
||||||
else
|
|
||||||
return ServerState.SERVER_NOT_LISTENING.ToString();
|
return ServerState.SERVER_NOT_LISTENING.ToString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -58,7 +101,16 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsConnected
|
public bool IsConnected
|
||||||
{
|
{
|
||||||
get { return (Server != null) && (Server.State == ServerState.SERVER_CONNECTED); }
|
get
|
||||||
|
{
|
||||||
|
if (myTcpServer != null)
|
||||||
|
return (myTcpServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED;
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||||
|
//(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED :
|
||||||
|
// (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -74,7 +126,16 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsListening
|
public bool IsListening
|
||||||
{
|
{
|
||||||
get { return (Server != null) && (Server.State == ServerState.SERVER_LISTENING); }
|
get
|
||||||
|
{
|
||||||
|
if (myTcpServer != null)
|
||||||
|
return (myTcpServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
//return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||||
|
//(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING :
|
||||||
|
// (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -93,8 +154,8 @@ namespace PepperDash.Core
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Server != null)
|
if (myTcpServer != null)
|
||||||
return (ushort)Server.NumberOfClientsConnected;
|
return (ushort)myTcpServer.NumberOfClientsConnected;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,11 +236,13 @@ namespace PepperDash.Core
|
|||||||
//private timers for Heartbeats per client
|
//private timers for Heartbeats per client
|
||||||
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||||
|
|
||||||
//flags to show the server is waiting for client at index to send the shared key
|
//flags to show the secure server is waiting for client at index to send the shared key
|
||||||
List<uint> WaitingForSharedKey = new List<uint>();
|
List<uint> WaitingForSharedKey = new List<uint>();
|
||||||
|
|
||||||
|
List<uint> ClientReadyAfterKeyExchange = new List<uint>();
|
||||||
|
|
||||||
//Store the connected client indexes
|
//Store the connected client indexes
|
||||||
List<uint> ConnectedClientsIndexes = new List<uint>();
|
public List<uint> ConnectedClientsIndexes = new List<uint>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defaults to 2000
|
/// Defaults to 2000
|
||||||
@@ -192,23 +255,72 @@ namespace PepperDash.Core
|
|||||||
private bool ServerStopped { get; set; }
|
private bool ServerStopped { get; set; }
|
||||||
|
|
||||||
//Servers
|
//Servers
|
||||||
TCPServer Server;
|
TCPServer myTcpServer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
bool ProgramIsStopping;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// constructor
|
/// constructor S+ Does not accept a key. Use initialze with key to set the debug key on this device. If using with + make sure to set all properties manually.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GenericTcpIpServer()
|
public GenericTcpIpServer()
|
||||||
: base("Uninitialized Dynamic TCP Server")
|
: base("Uninitialized Dynamic TCP Server")
|
||||||
{
|
{
|
||||||
|
HeartbeatRequiredIntervalInSeconds = 15;
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
BufferSize = 2000;
|
BufferSize = 2000;
|
||||||
|
MonitorClientMaxFailureCount = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// constructor with debug key set at instantiation. Make sure to set all properties before listening.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
public GenericTcpIpServer(string key)
|
||||||
|
: base("Uninitialized Dynamic TCP Server")
|
||||||
|
{
|
||||||
|
HeartbeatRequiredIntervalInSeconds = 15;
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
BufferSize = 2000;
|
||||||
|
MonitorClientMaxFailureCount = 3;
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contstructor that sets all properties by calling the initialize method with a config object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverConfigObject"></param>
|
||||||
|
public GenericTcpIpServer(TcpServerConfigObject serverConfigObject)
|
||||||
|
: base("Uninitialized Dynamic TCP Server")
|
||||||
|
{
|
||||||
|
HeartbeatRequiredIntervalInSeconds = 15;
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
BufferSize = 2000;
|
||||||
|
MonitorClientMaxFailureCount = 3;
|
||||||
|
Initialize(serverConfigObject);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods - Server Actions
|
#region Methods - Server Actions
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnects all clients and stops the server
|
||||||
|
/// </summary>
|
||||||
|
public void KillServer()
|
||||||
|
{
|
||||||
|
ServerStopped = true;
|
||||||
|
if (MonitorClient != null)
|
||||||
|
{
|
||||||
|
MonitorClient.Disconnect();
|
||||||
|
}
|
||||||
|
DisconnectAllClientsForShutdown();
|
||||||
|
StopListening();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -218,37 +330,84 @@ namespace PepperDash.Core
|
|||||||
Key = key;
|
Key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Initialize(TcpServerConfigObject serverConfigObject)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key))
|
||||||
|
{
|
||||||
|
Key = serverConfigObject.Key;
|
||||||
|
MaxClients = serverConfigObject.MaxClients;
|
||||||
|
Port = serverConfigObject.Port;
|
||||||
|
SharedKeyRequired = serverConfigObject.SharedKeyRequired;
|
||||||
|
SharedKey = serverConfigObject.SharedKey;
|
||||||
|
HeartbeatRequired = serverConfigObject.HeartbeatRequired;
|
||||||
|
HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds;
|
||||||
|
HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch;
|
||||||
|
BufferSize = serverConfigObject.BufferSize;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start listening on the specified port
|
/// Start listening on the specified port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Listen()
|
public void Listen()
|
||||||
{
|
{
|
||||||
|
ServerCCSection.Enter();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Server '{0}': Invalid port", Key);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
{
|
{
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Server '{0}': No Shared Key set", Key);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (IsListening)
|
if (IsListening)
|
||||||
return;
|
return;
|
||||||
Server = new TCPServer(Port, MaxClients);
|
|
||||||
Server.SocketStatusChange += new TCPServerSocketStatusChangeEventHandler(SocketStatusChange);
|
if (myTcpServer == null)
|
||||||
|
{
|
||||||
|
myTcpServer = new TCPServer(Port, MaxClients);
|
||||||
|
myTcpServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5);
|
||||||
|
|
||||||
|
// myTcpServer.HandshakeTimeout = 30;
|
||||||
|
myTcpServer.SocketStatusChange += new TCPServerSocketStatusChangeEventHandler(TcpServer_SocketStatusChange);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
KillServer();
|
||||||
|
myTcpServer.PortNumber = Port;
|
||||||
|
}
|
||||||
ServerStopped = false;
|
ServerStopped = false;
|
||||||
Server.WaitForConnectionAsync(IPAddress.Any, ConnectCallback);
|
myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||||
onServerStateChange();
|
OnServerStateChange(myTcpServer.State);
|
||||||
Debug.Console(2, "Server Status: {0}, Socket Status: {1}\r\n", Server.State.ToString(), Server.ServerSocketStatus);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus);
|
||||||
|
|
||||||
|
// StartMonitorClient();
|
||||||
|
|
||||||
|
|
||||||
|
ServerCCSection.Leave();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ErrorLog.Error("Error with Dynamic Server: {0}", ex.ToString());
|
ServerCCSection.Leave();
|
||||||
|
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,23 +416,80 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopListening()
|
public void StopListening()
|
||||||
{
|
{
|
||||||
Debug.Console(2, "Stopping Listener");
|
try
|
||||||
if (Server != null)
|
{
|
||||||
Server.Stop();
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
|
||||||
|
if (myTcpServer != null)
|
||||||
|
{
|
||||||
|
myTcpServer.Stop();
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State);
|
||||||
|
//SecureServer = null;
|
||||||
|
}
|
||||||
|
|
||||||
ServerStopped = true;
|
ServerStopped = true;
|
||||||
onServerStateChange();
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Stopped");
|
||||||
|
|
||||||
|
OnServerStateChange(myTcpServer.State);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnects Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
public void DisconnectClient(uint client)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
myTcpServer.Disconnect(client);
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnect All Clients
|
/// Disconnect All Clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DisconnectAllClients()
|
public void DisconnectAllClientsForShutdown()
|
||||||
{
|
{
|
||||||
Debug.Console(2, "Disconnecting All Clients");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
|
||||||
if (Server != null)
|
if (myTcpServer != null)
|
||||||
Server.DisconnectAll();
|
{
|
||||||
onConnectionChange();
|
myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange;
|
||||||
onServerStateChange(); //State shows both listening and connected
|
foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly
|
||||||
|
{
|
||||||
|
var i = index;
|
||||||
|
if (!myTcpServer.ClientConnected(index))
|
||||||
|
continue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
myTcpServer.Disconnect(i);
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
|
||||||
|
ConnectedClientsIndexes.Clear();
|
||||||
|
|
||||||
|
if (!ProgramIsStopping)
|
||||||
|
{
|
||||||
|
OnConnectionChange();
|
||||||
|
OnServerStateChange(myTcpServer.State); //State shows both listening and connected
|
||||||
|
}
|
||||||
|
|
||||||
|
// var o = new { };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -281,12 +497,30 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
public void BroadcastText(string text)
|
public void BroadcastText(string text)
|
||||||
|
{
|
||||||
|
CCriticalSection CCBroadcast = new CCriticalSection();
|
||||||
|
CCBroadcast.Enter();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Count > 0)
|
if (ConnectedClientsIndexes.Count > 0)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
foreach (uint i in ConnectedClientsIndexes)
|
foreach (uint i in ConnectedClientsIndexes)
|
||||||
Server.SendDataAsync(i, b, b.Length, SendDataAsyncCallback);
|
{
|
||||||
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i)))
|
||||||
|
{
|
||||||
|
SocketErrorCodes error = myTcpServer.SendDataAsync(i, b, b.Length, (x, y, z) => { });
|
||||||
|
if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
||||||
|
Debug.Console(0, error.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CCBroadcast.Leave();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CCBroadcast.Leave();
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,19 +530,34 @@ namespace PepperDash.Core
|
|||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
public void SendTextToClient(string text, uint clientIndex)
|
public void SendTextToClient(string text, uint clientIndex)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
Server.SendDataAsync(clientIndex, b, b.Length, SendDataAsyncCallback);
|
if (myTcpServer != null && myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||||
|
myTcpServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//private method to check heartbeat requirements and start or reset timer
|
//private method to check heartbeat requirements and start or reset timer
|
||||||
void checkHeartbeat(uint clientIndex, string received)
|
string checkHeartbeat(uint clientIndex, string received)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (HeartbeatRequired)
|
if (HeartbeatRequired)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(HeartbeatStringToMatch))
|
if (!string.IsNullOrEmpty(HeartbeatStringToMatch))
|
||||||
{
|
{
|
||||||
if (received == HeartbeatStringToMatch)
|
var remainingText = received.Replace(HeartbeatStringToMatch, "");
|
||||||
|
var noDelimiter = received.Trim(new char[] { '\r', '\n' });
|
||||||
|
if (noDelimiter.Contains(HeartbeatStringToMatch))
|
||||||
{
|
{
|
||||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
@@ -317,6 +566,10 @@ namespace PepperDash.Core
|
|||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
}
|
}
|
||||||
|
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||||
|
// Return Heartbeat
|
||||||
|
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
||||||
|
return remainingText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -328,109 +581,196 @@ namespace PepperDash.Core
|
|||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
}
|
}
|
||||||
|
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||||
|
}
|
||||||
|
return received;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetClientIPAddress(uint clientIndex)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
|
||||||
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||||
|
{
|
||||||
|
var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
|
||||||
|
return ipa;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods - Callbacks
|
#region Methods - HeartbeatTimer Callback
|
||||||
/// <summary>
|
|
||||||
/// Callback to disconnect if heartbeat timer finishes without being reset
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="o"></param>
|
|
||||||
void HeartbeatTimer_CallbackFunction(object o)
|
void HeartbeatTimer_CallbackFunction(object o)
|
||||||
{
|
{
|
||||||
uint clientIndex = (uint)o;
|
uint clientIndex = 99999;
|
||||||
|
string address = string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
clientIndex = (uint)o;
|
||||||
|
address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
|
||||||
string address = Server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||||
|
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
||||||
ErrorLog.Error("Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address);
|
|
||||||
Debug.Console(2, "Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address);
|
|
||||||
|
|
||||||
|
if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||||
Server.Disconnect(clientIndex);
|
|
||||||
|
var discoResult = myTcpServer.Disconnect(clientIndex);
|
||||||
|
//Debug.Console(1, this, "{0}", discoResult);
|
||||||
|
|
||||||
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
{
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Stop();
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Dispose();
|
||||||
HeartbeatTimerDictionary.Remove(clientIndex);
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Socket Status Changed Callbacks
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TCP Server Socket Status Change Callback
|
/// Secure Server Socket Status Changed Callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="server"></param>
|
/// <param name="mySecureTCPServer"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
/// <param name="serverSocketStatus"></param>
|
/// <param name="serverSocketStatus"></param>
|
||||||
void SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
void TcpServer_SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
||||||
{
|
{
|
||||||
Debug.Console(2, "Client at {0} ServerSocketStatus {1}",
|
try
|
||||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString());
|
|
||||||
if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
{
|
||||||
if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex))
|
|
||||||
WaitingForSharedKey.Add(clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
ConnectedClientsIndexes.Add(clientIndex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
ConnectedClientsIndexes.Remove(clientIndex);
|
ConnectedClientsIndexes.Remove(clientIndex);
|
||||||
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
{
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Stop();
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Dispose();
|
||||||
HeartbeatTimerDictionary.Remove(clientIndex);
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
}
|
}
|
||||||
if (Server.ServerSocketStatus.ToString() != Status)
|
if (ClientReadyAfterKeyExchange.Contains(clientIndex))
|
||||||
onConnectionChange();
|
ClientReadyAfterKeyExchange.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
|
||||||
|
}
|
||||||
|
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods Connected Callbacks
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TCP Client Connected to Server Callback
|
/// Secure TCP Client Connected to Secure Server Callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="myTCPServer"></param>
|
/// <param name="mySecureTCPServer"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
void ConnectCallback(TCPServer myTCPServer, uint clientIndex)
|
void TcpConnectCallback(TCPServer server, uint clientIndex)
|
||||||
{
|
{
|
||||||
if (myTCPServer.ClientConnected(clientIndex))
|
try
|
||||||
{
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||||
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
||||||
|
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
|
if (clientIndex != 0)
|
||||||
|
{
|
||||||
|
if (server.ClientConnected(clientIndex))
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
{
|
||||||
|
ConnectedClientsIndexes.Add(clientIndex);
|
||||||
|
}
|
||||||
if (SharedKeyRequired)
|
if (SharedKeyRequired)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n");
|
if (!WaitingForSharedKey.Contains(clientIndex))
|
||||||
myTCPServer.SendDataAsync(clientIndex, b, b.Length, SendDataAsyncCallback);
|
{
|
||||||
Debug.Console(2, "Sent Shared Key to client at {0}", myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
WaitingForSharedKey.Add(clientIndex);
|
||||||
|
}
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
||||||
|
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
}
|
}
|
||||||
if (HeartbeatRequired)
|
if (HeartbeatRequired)
|
||||||
{
|
{
|
||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
|
||||||
}
|
|
||||||
myTCPServer.ReceiveDataAsync(clientIndex, ReceivedDataAsyncCallback);
|
|
||||||
if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
|
||||||
myTCPServer.WaitForConnectionAsync(IPAddress.Any, ConnectCallback);
|
|
||||||
}
|
|
||||||
if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
|
||||||
myTCPServer.WaitForConnectionAsync(IPAddress.Any, ConnectCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send Data Asyc Callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="myTCPServer"></param>
|
|
||||||
/// <param name="clientIndex"></param>
|
|
||||||
/// <param name="numberOfBytesSent"></param>
|
|
||||||
void SendDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesSent)
|
|
||||||
{
|
{
|
||||||
//Seems there is nothing to do here
|
HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
|
||||||
|
if (!ServerStopped)
|
||||||
|
{
|
||||||
|
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
|
||||||
|
}
|
||||||
|
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
|
||||||
|
// server.State,
|
||||||
|
// MaxClients,
|
||||||
|
// ServerStopped);
|
||||||
|
if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Waiting for next connection");
|
||||||
|
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Send/Receive Callbacks
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Received Data Async Callback
|
/// Secure Received Data Async Callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="myTCPServer"></param>
|
/// <param name="mySecureTCPServer"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
/// <param name="numberOfBytesReceived"></param>
|
/// <param name="numberOfBytesReceived"></param>
|
||||||
void ReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived)
|
void TcpServerReceivedDataAsyncCallback(TCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived)
|
||||||
{
|
{
|
||||||
if (numberOfBytesReceived > 0)
|
if (numberOfBytesReceived > 0)
|
||||||
{
|
{
|
||||||
string received = "Nothing";
|
string received = "Nothing";
|
||||||
byte[] bytes = myTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
try
|
||||||
|
{
|
||||||
|
byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
||||||
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
||||||
if (WaitingForSharedKey.Contains(clientIndex))
|
if (WaitingForSharedKey.Contains(clientIndex))
|
||||||
{
|
{
|
||||||
@@ -439,65 +779,207 @@ namespace PepperDash.Core
|
|||||||
if (received != SharedKey)
|
if (received != SharedKey)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||||
Debug.Console(2, "Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||||
ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
mySecureTCPServer.SendData(clientIndex, b, b.Length);
|
||||||
myTCPServer.SendDataAsync(clientIndex, b, b.Length, null);
|
mySecureTCPServer.Disconnect(clientIndex);
|
||||||
myTCPServer.Disconnect(clientIndex);
|
|
||||||
}
|
|
||||||
if (myTCPServer.NumberOfClientsConnected > 0)
|
|
||||||
myTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
|
||||||
WaitingForSharedKey.Remove(clientIndex);
|
WaitingForSharedKey.Remove(clientIndex);
|
||||||
byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication");
|
return;
|
||||||
myTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null);
|
|
||||||
myTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
|
||||||
}
|
}
|
||||||
else
|
if (mySecureTCPServer.NumberOfClientsConnected > 0)
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
|
||||||
|
WaitingForSharedKey.Remove(clientIndex);
|
||||||
|
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
||||||
|
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
||||||
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//var address = mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
//Debug.Console(1, this, "Secure Server Listening on Port: {0}, client IP: {1}, Client Index: {4}, NumberOfBytesReceived: {2}, Received: {3}\r\n",
|
||||||
|
// mySecureTCPServer.PortNumber.ToString(), address , numberOfBytesReceived.ToString(), received, clientIndex.ToString());
|
||||||
|
if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
||||||
|
onTextReceived(received, clientIndex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
myTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
|
||||||
Debug.Console(2, "Server Listening on Port: {0}, client IP: {1}, NumberOfBytesReceived: {2}, Received: {3}\r\n",
|
|
||||||
myTCPServer.PortNumber, myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), numberOfBytesReceived, received);
|
|
||||||
onTextReceived(received);
|
|
||||||
}
|
}
|
||||||
checkHeartbeat(clientIndex, received);
|
|
||||||
}
|
}
|
||||||
if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
myTCPServer.ReceiveDataAsync(clientIndex, ReceivedDataAsyncCallback);
|
mySecureTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods - EventHelpers/Callbacks
|
#region Methods - EventHelpers/Callbacks
|
||||||
|
|
||||||
//Private Helper method to call the Connection Change Event
|
//Private Helper method to call the Connection Change Event
|
||||||
void onConnectionChange()
|
void onConnectionChange(uint clientIndex, SocketStatus clientStatus)
|
||||||
|
{
|
||||||
|
if (clientIndex != 0) //0 is error not valid client change
|
||||||
{
|
{
|
||||||
var handler = ClientConnectionChange;
|
var handler = ClientConnectionChange;
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
handler(this, new DynamicTCPSocketStatusChangeEventArgs(Server, false));
|
{
|
||||||
|
handler(this, new GenericTcpServerSocketStatusChangeEventArgs(myTcpServer, clientIndex, clientStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Helper method to call the Connection Change Event
|
||||||
|
void OnConnectionChange()
|
||||||
|
{
|
||||||
|
if (ProgramIsStopping)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var handler = ClientConnectionChange;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, new GenericTcpServerSocketStatusChangeEventArgs());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Private Helper Method to call the Text Received Event
|
//Private Helper Method to call the Text Received Event
|
||||||
void onTextReceived(string text)
|
void onTextReceived(string text, uint clientIndex)
|
||||||
{
|
{
|
||||||
var handler = TextReceived;
|
var handler = TextReceived;
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
handler(this, new GenericCommMethodReceiveTextArgs(text));
|
handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Private Helper Method to call the Server State Change Event
|
//Private Helper Method to call the Server State Change Event
|
||||||
void onServerStateChange()
|
void OnServerStateChange(ServerState state)
|
||||||
{
|
{
|
||||||
|
if (ProgramIsStopping)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
var handler = ServerStateChange;
|
var handler = ServerStateChange;
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
handler(this, new DynamicTCPServerStateChangedEventArgs(Server, false));
|
{
|
||||||
|
handler(this, new GenericTcpServerStateChangedEventArgs(state));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Private Event Handler method to handle the closing of connections when the program stops
|
/// <summary>
|
||||||
|
/// Private Event Handler method to handle the closing of connections when the program stops
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="programEventType"></param>
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
{
|
{
|
||||||
if (programEventType == eProgramStatusEventType.Stopping)
|
if (programEventType == eProgramStatusEventType.Stopping)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Program stopping. Closing server");
|
ProgramIsStopping = true;
|
||||||
DisconnectAllClients();
|
// kill bandaid things
|
||||||
StopListening();
|
if (MonitorClientTimer != null)
|
||||||
|
MonitorClientTimer.Stop();
|
||||||
|
if (MonitorClient != null)
|
||||||
|
MonitorClient.Disconnect();
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
|
||||||
|
KillServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation
|
||||||
|
void OnServerClientReadyForCommunications(uint clientIndex)
|
||||||
|
{
|
||||||
|
ClientReadyAfterKeyExchange.Add(clientIndex);
|
||||||
|
var handler = ServerClientReadyForCommunications;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new GenericTcpServerSocketStatusChangeEventArgs(
|
||||||
|
this, clientIndex, myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex)));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Monitor Client
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the monitor client cycle. Timed wait, then call RunMonitorClient
|
||||||
|
/// </summary>
|
||||||
|
void StartMonitorClient()
|
||||||
|
{
|
||||||
|
if (MonitorClientTimer != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
void RunMonitorClient()
|
||||||
|
{
|
||||||
|
MonitorClient = new GenericTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000);
|
||||||
|
MonitorClient.SharedKeyRequired = this.SharedKeyRequired;
|
||||||
|
MonitorClient.SharedKey = this.SharedKey;
|
||||||
|
MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback;
|
||||||
|
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
||||||
|
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
||||||
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
|
||||||
|
|
||||||
|
MonitorClient.Connect();
|
||||||
|
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
void StopMonitorClient()
|
||||||
|
{
|
||||||
|
if (MonitorClient == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm;
|
||||||
|
MonitorClient.Disconnect();
|
||||||
|
MonitorClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// On monitor connect, restart the operation
|
||||||
|
/// </summary>
|
||||||
|
void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.IsReady)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
|
||||||
|
MonitorClientTimer.Stop();
|
||||||
|
MonitorClientTimer = null;
|
||||||
|
MonitorClientFailureCount = 0;
|
||||||
|
CrestronEnvironment.Sleep(2000);
|
||||||
|
StopMonitorClient();
|
||||||
|
StartMonitorClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the client hangs, add to counter and maybe fire the choke event
|
||||||
|
/// </summary>
|
||||||
|
void MonitorClientHasHungCallback()
|
||||||
|
{
|
||||||
|
MonitorClientFailureCount++;
|
||||||
|
MonitorClientTimer.Stop();
|
||||||
|
MonitorClientTimer = null;
|
||||||
|
StopMonitorClient();
|
||||||
|
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||||
|
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
||||||
|
StartMonitorClient();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error,
|
||||||
|
"\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************",
|
||||||
|
MonitorClientMaxFailureCount);
|
||||||
|
|
||||||
|
var handler = ServerHasChoked;
|
||||||
|
if (handler != null)
|
||||||
|
handler();
|
||||||
|
// Some external thing is in charge here. Expected reset of program
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
public class TcpServerConfigObject
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public bool Secure { get; set; }
|
||||||
|
public ushort MaxClients { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
public bool SharedKeyRequired { get; set; }
|
||||||
|
public string SharedKey { get; set; }
|
||||||
|
public bool HeartbeatRequired { get; set; }
|
||||||
|
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
||||||
|
public string HeartbeatStringToMatch { get; set; }
|
||||||
|
public int BufferSize { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,9 +64,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="CommunicationExtras.cs" />
|
<Compile Include="CommunicationExtras.cs" />
|
||||||
<Compile Include="Comm\ControlPropertiesConfig.cs" />
|
<Compile Include="Comm\ControlPropertiesConfig.cs" />
|
||||||
|
<Compile Include="Comm\GenericTcpIpClient_ForServer.cs" />
|
||||||
<Compile Include="Comm\GenericHttpSseClient.cs" />
|
<Compile Include="Comm\GenericHttpSseClient.cs" />
|
||||||
<Compile Include="Comm\GenericSecureTcpIpServer.cs" />
|
<Compile Include="Comm\GenericSecureTcpIpServer.cs" />
|
||||||
<Compile Include="Comm\GenericSecureTcpIpClient.cs">
|
<Compile Include="Comm\GenericSecureTcpIpClient_ForServer.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Comm\eControlMethods.cs" />
|
<Compile Include="Comm\eControlMethods.cs" />
|
||||||
@@ -75,6 +76,7 @@
|
|||||||
<Compile Include="Comm\EventArgs.cs" />
|
<Compile Include="Comm\EventArgs.cs" />
|
||||||
<Compile Include="Comm\GenericSshClient.cs" />
|
<Compile Include="Comm\GenericSshClient.cs" />
|
||||||
<Compile Include="Comm\GenericUdpServer.cs" />
|
<Compile Include="Comm\GenericUdpServer.cs" />
|
||||||
|
<Compile Include="Comm\TcpServerConfigObject.cs" />
|
||||||
<Compile Include="Config\PortalConfigReader.cs" />
|
<Compile Include="Config\PortalConfigReader.cs" />
|
||||||
<Compile Include="CoreInterfaces.cs" />
|
<Compile Include="CoreInterfaces.cs" />
|
||||||
<Compile Include="JsonToSimpl\JsonToSimplPortalFileMaster.cs" />
|
<Compile Include="JsonToSimpl\JsonToSimplPortalFileMaster.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user