mirror of
https://github.com/PepperDash/PepperDashCore.git
synced 2026-02-16 21:24:43 +00:00
Changes for pull request: two changes to auto reconnect on TCPclient. Line 259 if not disconnect called by user. and Line 317 add AutoReconnect to the If statement. Added 3 new class files. GenericSecureTcpIpClient, GenericSecureTcpIpServer, and GenericTcpIpServer. Lastly, I updated the name of the EventArgs.cs to be generic, and added the new class event args to that file.
This commit is contained in:
Binary file not shown.
684
Pepperdash Core/Pepperdash Core/Comm/DynamicTCPServer.cs
Normal file
684
Pepperdash Core/Pepperdash Core/Comm/DynamicTCPServer.cs
Normal file
@@ -0,0 +1,684 @@
|
|||||||
|
/*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 Crestron.SimplSharp;
|
||||||
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
using PepperDash.Core;
|
||||||
|
|
||||||
|
namespace DynamicTCP
|
||||||
|
{
|
||||||
|
public class DynamicTCPServer : Device
|
||||||
|
{
|
||||||
|
#region Events
|
||||||
|
/// <summary>
|
||||||
|
/// Event for Receiving text
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<CopyCoreForSimplpGenericCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event for client connection socket status change
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DynamicTCPSocketStatusChangeEventArgs> ClientConnectionChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event for Server State Change
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DynamicTCPServerStateChangedEventArgs> ServerStateChange;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties/Variables
|
||||||
|
/// <summary>
|
||||||
|
/// Secure or unsecure TCP server. Defaults to Unsecure or standard TCP server without SSL
|
||||||
|
/// </summary>
|
||||||
|
public bool Secure { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ Helper for Secure bool. Parameter in SIMPL+ so there is no get, one way set from simpl+ Param to property in func main of SIMPL+
|
||||||
|
/// </summary>
|
||||||
|
public ushort USecure
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == 1)
|
||||||
|
Secure = true;
|
||||||
|
else if (value == 0)
|
||||||
|
Secure = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text representation of the Socket Status enum values for the server
|
||||||
|
/// </summary>
|
||||||
|
public string Status
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Secure ? SecureServer != null : UnsecureServer != null)
|
||||||
|
return Secure ? SecureServer.State.ToString() : UnsecureServer.State.ToString();
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||||
|
(Secure ? SecureServer.State == ServerState.SERVER_CONNECTED : UnsecureServer.State == ServerState.SERVER_CONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsConnected
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsConnected
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsListening
|
||||||
|
{
|
||||||
|
get { return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||||
|
(Secure ? SecureServer.State == ServerState.SERVER_LISTENING : UnsecureServer.State == ServerState.SERVER_LISTENING); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsConnected
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsListening
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsListening ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable
|
||||||
|
/// <summary>
|
||||||
|
/// Number of clients currently connected.
|
||||||
|
/// </summary>
|
||||||
|
public ushort NumberOfClientsConnected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Secure ? SecureServer != null : UnsecureServer != null)
|
||||||
|
return Secure ? (ushort)SecureServer.NumberOfClientsConnected : (ushort)UnsecureServer.NumberOfClientsConnected;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Port Server should listen on
|
||||||
|
/// </summary>
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for Port
|
||||||
|
/// </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. Must be set the same in the client, and if true shared keys must be identical on server/client
|
||||||
|
/// </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.
|
||||||
|
/// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called
|
||||||
|
/// </summary>
|
||||||
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received
|
||||||
|
/// </summary>
|
||||||
|
public bool HeartbeatRequired { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ Helper for Heartbeat Required
|
||||||
|
/// </summary>
|
||||||
|
public ushort UHeartbeatRequired
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == 1)
|
||||||
|
HeartbeatRequired = true;
|
||||||
|
else
|
||||||
|
HeartbeatRequired = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
||||||
|
/// </summary>
|
||||||
|
public int HeartbeatRequiredIntervalMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simpl+ Heartbeat Analog value in seconds
|
||||||
|
/// </summary>
|
||||||
|
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// String to Match for heartbeat. If null or empty any string will reset heartbeat timer
|
||||||
|
/// </summary>
|
||||||
|
public string HeartbeatStringToMatch { get; set; }
|
||||||
|
|
||||||
|
//private timers for Heartbeats per client
|
||||||
|
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||||
|
|
||||||
|
//flags to show the secure server is waiting for client at index to send the shared key
|
||||||
|
List<uint> WaitingForSharedKey = new List<uint>();
|
||||||
|
|
||||||
|
//Store the connected client indexes
|
||||||
|
List<uint> ConnectedClientsIndexes = new List<uint>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to 2000
|
||||||
|
/// </summary>
|
||||||
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private flag to note that the server has stopped intentionally
|
||||||
|
/// </summary>
|
||||||
|
private bool ServerStopped { get; set; }
|
||||||
|
|
||||||
|
//Servers
|
||||||
|
SecureTCPServer SecureServer;
|
||||||
|
TCPServer UnsecureServer;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
/// <summary>
|
||||||
|
/// constructor
|
||||||
|
/// </summary>
|
||||||
|
public DynamicTCPServer()
|
||||||
|
: base("Uninitialized Dynamic TCP Server")
|
||||||
|
{
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
BufferSize = 2000;
|
||||||
|
Secure = false;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Server Actions
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
public void Initialize(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start listening on the specified port
|
||||||
|
/// </summary>
|
||||||
|
public void Listen()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Port < 1 || Port > 65535)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericSecureTcpClient '{0}': Invalid port", Key);
|
||||||
|
ErrorLog.Warn(string.Format("GenericSecureTcpClient '{0}': Invalid port", Key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericSecureTcpClient '{0}': No Shared Key set", Key);
|
||||||
|
ErrorLog.Warn(string.Format("GenericSecureTcpClient '{0}': No Shared Key set", Key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsListening)
|
||||||
|
return;
|
||||||
|
if (Secure)
|
||||||
|
{
|
||||||
|
SecureServer = new SecureTCPServer(Port, MaxClients);
|
||||||
|
SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange);
|
||||||
|
ServerStopped = false;
|
||||||
|
SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||||
|
onServerStateChange();
|
||||||
|
Debug.Console(2, "Secure Server Status: {0}, Socket Status: {1}\r\n", SecureServer.State.ToString(), SecureServer.ServerSocketStatus);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnsecureServer = new TCPServer(Port, MaxClients);
|
||||||
|
UnsecureServer.SocketStatusChange += new TCPServerSocketStatusChangeEventHandler(UnsecureServer_SocketStatusChange);
|
||||||
|
ServerStopped = false;
|
||||||
|
UnsecureServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback);
|
||||||
|
onServerStateChange();
|
||||||
|
Debug.Console(2, "Unsecure Server Status: {0}, Socket Status: {1}\r\n", UnsecureServer.State.ToString(), UnsecureServer.ServerSocketStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorLog.Error("Error with Dynamic Server: {0}", ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop Listeneing
|
||||||
|
/// </summary>
|
||||||
|
public void StopListening()
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Stopping Listener");
|
||||||
|
if (SecureServer != null)
|
||||||
|
SecureServer.Stop();
|
||||||
|
if (UnsecureServer != null)
|
||||||
|
UnsecureServer.Stop();
|
||||||
|
ServerStopped = true;
|
||||||
|
onServerStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect All Clients
|
||||||
|
/// </summary>
|
||||||
|
public void DisconnectAllClients()
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Disconnecting All Clients");
|
||||||
|
if (SecureServer != null)
|
||||||
|
SecureServer.DisconnectAll();
|
||||||
|
if (UnsecureServer != null)
|
||||||
|
UnsecureServer.DisconnectAll();
|
||||||
|
onConnectionChange();
|
||||||
|
onServerStateChange(); //State shows both listening and connected
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast text from server to all connected clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
public void BroadcastText(string text)
|
||||||
|
{
|
||||||
|
if (ConnectedClientsIndexes.Count > 0)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
if (Secure)
|
||||||
|
foreach (uint i in ConnectedClientsIndexes)
|
||||||
|
SecureServer.SendDataAsync(i, b, b.Length, SecureSendDataAsyncCallback);
|
||||||
|
else
|
||||||
|
foreach (uint i in ConnectedClientsIndexes)
|
||||||
|
UnsecureServer.SendDataAsync(i, b, b.Length, UnsecureSendDataAsyncCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not sure this is useful in library, maybe Pro??
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
public void SendTextToClient(string text, uint clientIndex)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
if (Secure)
|
||||||
|
SecureServer.SendDataAsync(clientIndex, b, b.Length, SecureSendDataAsyncCallback);
|
||||||
|
else
|
||||||
|
UnsecureServer.SendDataAsync(clientIndex, b, b.Length, UnsecureSendDataAsyncCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private method to check heartbeat requirements and start or reset timer
|
||||||
|
void checkHeartbeat(uint clientIndex, string received)
|
||||||
|
{
|
||||||
|
if (HeartbeatRequired)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(HeartbeatStringToMatch))
|
||||||
|
{
|
||||||
|
if (received == HeartbeatStringToMatch)
|
||||||
|
{
|
||||||
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - HeartbeatTimer Callback
|
||||||
|
|
||||||
|
void HeartbeatTimer_CallbackFunction(object o)
|
||||||
|
{
|
||||||
|
uint clientIndex = (uint)o;
|
||||||
|
|
||||||
|
string address = Secure ? SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex) :
|
||||||
|
UnsecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(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);
|
||||||
|
|
||||||
|
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||||
|
|
||||||
|
if (Secure)
|
||||||
|
SecureServer.Disconnect(clientIndex);
|
||||||
|
else
|
||||||
|
UnsecureServer.Disconnect(clientIndex);
|
||||||
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Socket Status Changed Callbacks
|
||||||
|
/// <summary>
|
||||||
|
/// Secure Server Socket Status Changed Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mySecureTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="serverSocketStatus"></param>
|
||||||
|
void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Client at {0} ServerSocketStatus {1}",
|
||||||
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString());
|
||||||
|
if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex))
|
||||||
|
WaitingForSharedKey.Add(clientIndex);
|
||||||
|
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
ConnectedClientsIndexes.Add(clientIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
ConnectedClientsIndexes.Remove(clientIndex);
|
||||||
|
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
if(SecureServer.ServerSocketStatus.ToString() != Status)
|
||||||
|
onConnectionChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TCP Server (Unsecure) Socket Status Change Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mySecureTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="serverSocketStatus"></param>
|
||||||
|
void UnsecureServer_SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Client at {0} ServerSocketStatus {1}",
|
||||||
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString());
|
||||||
|
if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex))
|
||||||
|
WaitingForSharedKey.Add(clientIndex);
|
||||||
|
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
ConnectedClientsIndexes.Add(clientIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
ConnectedClientsIndexes.Remove(clientIndex);
|
||||||
|
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
if (UnsecureServer.ServerSocketStatus.ToString() != Status)
|
||||||
|
onConnectionChange();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods Connected Callbacks
|
||||||
|
/// <summary>
|
||||||
|
/// Secure TCP Client Connected to Secure Server Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mySecureTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
void SecureConnectCallback(SecureTCPServer mySecureTCPServer, uint clientIndex)
|
||||||
|
{
|
||||||
|
if (mySecureTCPServer.ClientConnected(clientIndex))
|
||||||
|
{
|
||||||
|
if (SharedKeyRequired)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n");
|
||||||
|
mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, SecureSendDataAsyncCallback);
|
||||||
|
Debug.Console(2, "Sent Shared Key to client at {0}", mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
|
}
|
||||||
|
if (HeartbeatRequired)
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
|
}
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||||
|
if (mySecureTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||||
|
mySecureTCPServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsecure TCP Client Connected to Unsecure Server Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="myTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
void UnsecureConnectCallback(TCPServer myTCPServer, uint clientIndex)
|
||||||
|
{
|
||||||
|
if (myTCPServer.ClientConnected(clientIndex))
|
||||||
|
{
|
||||||
|
if (SharedKeyRequired)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n");
|
||||||
|
myTCPServer.SendDataAsync(clientIndex, b, b.Length, UnsecureSendDataAsyncCallback);
|
||||||
|
Debug.Console(2, "Sent Shared Key to client at {0}", myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
|
}
|
||||||
|
if (HeartbeatRequired)
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
|
}
|
||||||
|
myTCPServer.ReceiveDataAsync(clientIndex, UnsecureReceivedDataAsyncCallback);
|
||||||
|
if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||||
|
myTCPServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback);
|
||||||
|
}
|
||||||
|
if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||||
|
myTCPServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Send/Receive Callbacks
|
||||||
|
/// <summary>
|
||||||
|
/// Secure Send Data Async Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mySecureTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="numberOfBytesSent"></param>
|
||||||
|
void SecureSendDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesSent)
|
||||||
|
{
|
||||||
|
//Seems there is nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsecure Send Data Asyc Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="myTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="numberOfBytesSent"></param>
|
||||||
|
void UnsecureSendDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesSent)
|
||||||
|
{
|
||||||
|
//Seems there is nothing to do here
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Secure Received Data Async Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mySecureTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="numberOfBytesReceived"></param>
|
||||||
|
void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived)
|
||||||
|
{
|
||||||
|
if (numberOfBytesReceived > 0)
|
||||||
|
{
|
||||||
|
string received = "Nothing";
|
||||||
|
byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
||||||
|
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
||||||
|
if (WaitingForSharedKey.Contains(clientIndex))
|
||||||
|
{
|
||||||
|
received = received.Replace("\r", "");
|
||||||
|
received = received.Replace("\n", "");
|
||||||
|
if (received != SharedKey)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
||||||
|
mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, null);
|
||||||
|
mySecureTCPServer.Disconnect(clientIndex);
|
||||||
|
}
|
||||||
|
if (mySecureTCPServer.NumberOfClientsConnected > 0)
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback);
|
||||||
|
WaitingForSharedKey.Remove(clientIndex);
|
||||||
|
byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication");
|
||||||
|
mySecureTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null);
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback);
|
||||||
|
Debug.Console(2, "Secure 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)
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsecure Received Data Async Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="myTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="numberOfBytesReceived"></param>
|
||||||
|
void UnsecureReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived)
|
||||||
|
{
|
||||||
|
if (numberOfBytesReceived > 0)
|
||||||
|
{
|
||||||
|
string received = "Nothing";
|
||||||
|
byte[] bytes = myTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
||||||
|
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
||||||
|
if (WaitingForSharedKey.Contains(clientIndex))
|
||||||
|
{
|
||||||
|
received = received.Replace("\r", "");
|
||||||
|
received = received.Replace("\n", "");
|
||||||
|
if (received != SharedKey)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
||||||
|
myTCPServer.SendDataAsync(clientIndex, b, b.Length, null);
|
||||||
|
myTCPServer.Disconnect(clientIndex);
|
||||||
|
}
|
||||||
|
if (myTCPServer.NumberOfClientsConnected > 0)
|
||||||
|
myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback);
|
||||||
|
WaitingForSharedKey.Remove(clientIndex);
|
||||||
|
byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication");
|
||||||
|
myTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null);
|
||||||
|
myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback);
|
||||||
|
Debug.Console(2, "Secure 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)
|
||||||
|
myTCPServer.ReceiveDataAsync(clientIndex, UnsecureReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - EventHelpers/Callbacks
|
||||||
|
//Private Helper method to call the Connection Change Event
|
||||||
|
void onConnectionChange()
|
||||||
|
{
|
||||||
|
var handler = ClientConnectionChange;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
if (Secure)
|
||||||
|
handler(this, new DynamicTCPSocketStatusChangeEventArgs(SecureServer, Secure));
|
||||||
|
else
|
||||||
|
handler(this, new DynamicTCPSocketStatusChangeEventArgs(UnsecureServer, Secure));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Helper Method to call the Text Received Event
|
||||||
|
void onTextReceived(string text)
|
||||||
|
{
|
||||||
|
var handler = TextReceived;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new CopyCoreForSimplpGenericCommMethodReceiveTextArgs(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Helper Method to call the Server State Change Event
|
||||||
|
void onServerStateChange()
|
||||||
|
{
|
||||||
|
var handler = ServerStateChange;
|
||||||
|
if(handler != null)
|
||||||
|
{
|
||||||
|
if(Secure)
|
||||||
|
handler(this, new DynamicTCPServerStateChangedEventArgs(SecureServer, Secure));
|
||||||
|
else
|
||||||
|
handler(this, new DynamicTCPServerStateChangedEventArgs(UnsecureServer, Secure));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Event Handler method to handle the closing of connections when the program stops
|
||||||
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
|
{
|
||||||
|
if (programEventType == eProgramStatusEventType.Stopping)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Program stopping. Closing server");
|
||||||
|
DisconnectAllClients();
|
||||||
|
StopListening();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,14 @@
|
|||||||
using System;
|
/*PepperDash Technology Corp.
|
||||||
|
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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -24,6 +34,43 @@ namespace PepperDash.Core
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region DynamicTCPServerStateChangedEventArgs
|
||||||
|
public delegate void DynamicTCPServerStateChangedEventDelegate(object server);
|
||||||
|
|
||||||
|
public class DynamicTCPServerStateChangedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public bool Secure { get; private set; }
|
||||||
|
public object Server { get; private set; }
|
||||||
|
|
||||||
|
public DynamicTCPServerStateChangedEventArgs() { }
|
||||||
|
|
||||||
|
public DynamicTCPServerStateChangedEventArgs(object server, bool secure)
|
||||||
|
{
|
||||||
|
Secure = secure;
|
||||||
|
Server = server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region DynamicTCPSocketStatusChangeEventDelegate
|
||||||
|
public delegate void DynamicTCPSocketStatusChangeEventDelegate(object server);
|
||||||
|
|
||||||
|
public class DynamicTCPSocketStatusChangeEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public bool Secure { get; private set; }
|
||||||
|
public object Server { get; private set; }
|
||||||
|
|
||||||
|
public DynamicTCPSocketStatusChangeEventArgs() { }
|
||||||
|
|
||||||
|
public DynamicTCPSocketStatusChangeEventArgs(object server, bool secure)
|
||||||
|
{
|
||||||
|
Secure = secure;
|
||||||
|
Server = server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
371
Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient.cs
Normal file
371
Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient.cs
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
/*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
|
||||||
|
}
|
||||||
|
}
|
||||||
505
Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs
Normal file
505
Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
/*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 Crestron.SimplSharp;
|
||||||
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
public class GenericSecureTcpIpServer : Device
|
||||||
|
{
|
||||||
|
#region Events
|
||||||
|
/// <summary>
|
||||||
|
/// Event for Receiving text
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event for client connection socket status change
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DynamicTCPSocketStatusChangeEventArgs> ClientConnectionChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event for Server State Change
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DynamicTCPServerStateChangedEventArgs> ServerStateChange;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties/Variables
|
||||||
|
/// <summary>
|
||||||
|
/// Text representation of the Socket Status enum values for the server
|
||||||
|
/// </summary>
|
||||||
|
public string Status
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Server != null)
|
||||||
|
return Server.State.ToString();
|
||||||
|
else
|
||||||
|
return ServerState.SERVER_NOT_LISTENING.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get { return (Server != null) && (Server.State == ServerState.SERVER_CONNECTED); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsConnected
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsConnected
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsListening
|
||||||
|
{
|
||||||
|
get { return (Server != null) && (Server.State == ServerState.SERVER_LISTENING); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsConnected
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsListening
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsListening ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable
|
||||||
|
/// <summary>
|
||||||
|
/// Number of clients currently connected.
|
||||||
|
/// </summary>
|
||||||
|
public ushort NumberOfClientsConnected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Server != null)
|
||||||
|
return (ushort)Server.NumberOfClientsConnected;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Port Server should listen on
|
||||||
|
/// </summary>
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for Port
|
||||||
|
/// </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. Must be set the same in the client, and if true shared keys must be identical on server/client
|
||||||
|
/// </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.
|
||||||
|
/// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called
|
||||||
|
/// </summary>
|
||||||
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received
|
||||||
|
/// </summary>
|
||||||
|
public bool HeartbeatRequired { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ Helper for Heartbeat Required
|
||||||
|
/// </summary>
|
||||||
|
public ushort UHeartbeatRequired
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == 1)
|
||||||
|
HeartbeatRequired = true;
|
||||||
|
else
|
||||||
|
HeartbeatRequired = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
||||||
|
/// </summary>
|
||||||
|
public int HeartbeatRequiredIntervalMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simpl+ Heartbeat Analog value in seconds
|
||||||
|
/// </summary>
|
||||||
|
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// String to Match for heartbeat. If null or empty any string will reset heartbeat timer
|
||||||
|
/// </summary>
|
||||||
|
public string HeartbeatStringToMatch { get; set; }
|
||||||
|
|
||||||
|
//private timers for Heartbeats per client
|
||||||
|
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||||
|
|
||||||
|
//flags to show the server is waiting for client at index to send the shared key
|
||||||
|
List<uint> WaitingForSharedKey = new List<uint>();
|
||||||
|
|
||||||
|
//Store the connected client indexes
|
||||||
|
List<uint> ConnectedClientsIndexes = new List<uint>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to 2000
|
||||||
|
/// </summary>
|
||||||
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private flag to note that the server has stopped intentionally
|
||||||
|
/// </summary>
|
||||||
|
private bool ServerStopped { get; set; }
|
||||||
|
|
||||||
|
//Servers
|
||||||
|
SecureTCPServer Server;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
/// <summary>
|
||||||
|
/// constructor
|
||||||
|
/// </summary>
|
||||||
|
public GenericSecureTcpIpServer()
|
||||||
|
: base("Uninitialized Dynamic TCP Server")
|
||||||
|
{
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
BufferSize = 2000;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Server Actions
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
public void Initialize(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start listening on the specified port
|
||||||
|
/// </summary>
|
||||||
|
public void Listen()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Port < 1 || Port > 65535)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Server '{0}': Invalid port", Key);
|
||||||
|
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Server '{0}': No Shared Key set", Key);
|
||||||
|
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsListening)
|
||||||
|
return;
|
||||||
|
Server = new SecureTCPServer(Port, MaxClients);
|
||||||
|
Server.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SocketStatusChange);
|
||||||
|
ServerStopped = false;
|
||||||
|
Server.WaitForConnectionAsync(IPAddress.Any, ConnectCallback);
|
||||||
|
onServerStateChange();
|
||||||
|
Debug.Console(2, "Server Status: {0}, Socket Status: {1}\r\n", Server.State.ToString(), Server.ServerSocketStatus);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorLog.Error("Error with Dynamic Server: {0}", ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop Listeneing
|
||||||
|
/// </summary>
|
||||||
|
public void StopListening()
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Stopping Listener");
|
||||||
|
if (Server != null)
|
||||||
|
Server.Stop();
|
||||||
|
ServerStopped = true;
|
||||||
|
onServerStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect All Clients
|
||||||
|
/// </summary>
|
||||||
|
public void DisconnectAllClients()
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Disconnecting All Clients");
|
||||||
|
if (Server != null)
|
||||||
|
Server.DisconnectAll();
|
||||||
|
onConnectionChange();
|
||||||
|
onServerStateChange(); //State shows both listening and connected
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast text from server to all connected clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
public void BroadcastText(string text)
|
||||||
|
{
|
||||||
|
if (ConnectedClientsIndexes.Count > 0)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
foreach (uint i in ConnectedClientsIndexes)
|
||||||
|
Server.SendDataAsync(i, b, b.Length, SendDataAsyncCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not sure this is useful in library, maybe Pro??
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
public void SendTextToClient(string text, uint clientIndex)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
Server.SendDataAsync(clientIndex, b, b.Length, SendDataAsyncCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private method to check heartbeat requirements and start or reset timer
|
||||||
|
void checkHeartbeat(uint clientIndex, string received)
|
||||||
|
{
|
||||||
|
if (HeartbeatRequired)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(HeartbeatStringToMatch))
|
||||||
|
{
|
||||||
|
if (received == HeartbeatStringToMatch)
|
||||||
|
{
|
||||||
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Callbacks
|
||||||
|
/// <summary>
|
||||||
|
/// Callback to disconnect if heartbeat timer finishes without being reset
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="o"></param>
|
||||||
|
void HeartbeatTimer_CallbackFunction(object o)
|
||||||
|
{
|
||||||
|
uint clientIndex = (uint)o;
|
||||||
|
|
||||||
|
string address = Server.GetAddressServerAcceptedConnectionFromForSpecificClient(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);
|
||||||
|
|
||||||
|
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||||
|
Server.Disconnect(clientIndex);
|
||||||
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TCP Server Socket Status Change Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="serverSocketStatus"></param>
|
||||||
|
void SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Client at {0} ServerSocketStatus {1}",
|
||||||
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString());
|
||||||
|
if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex))
|
||||||
|
WaitingForSharedKey.Add(clientIndex);
|
||||||
|
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
ConnectedClientsIndexes.Add(clientIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
ConnectedClientsIndexes.Remove(clientIndex);
|
||||||
|
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
if (Server.ServerSocketStatus.ToString() != Status)
|
||||||
|
onConnectionChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TCP Client Connected to Server Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mySecureTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
void ConnectCallback(SecureTCPServer mySecureTCPServer, uint clientIndex)
|
||||||
|
{
|
||||||
|
if (mySecureTCPServer.ClientConnected(clientIndex))
|
||||||
|
{
|
||||||
|
if (SharedKeyRequired)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n");
|
||||||
|
mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, SendDataAsyncCallback);
|
||||||
|
Debug.Console(2, "Sent Shared Key to client at {0}", mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
|
}
|
||||||
|
if (HeartbeatRequired)
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Received Data Async Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mySecureTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="numberOfBytesReceived"></param>
|
||||||
|
void ReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived)
|
||||||
|
{
|
||||||
|
if (numberOfBytesReceived > 0)
|
||||||
|
{
|
||||||
|
string received = "Nothing";
|
||||||
|
byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
||||||
|
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
||||||
|
if (WaitingForSharedKey.Contains(clientIndex))
|
||||||
|
{
|
||||||
|
received = received.Replace("\r", "");
|
||||||
|
received = received.Replace("\n", "");
|
||||||
|
if (received != SharedKey)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
||||||
|
mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, null);
|
||||||
|
mySecureTCPServer.Disconnect(clientIndex);
|
||||||
|
}
|
||||||
|
if (mySecureTCPServer.NumberOfClientsConnected > 0)
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
||||||
|
WaitingForSharedKey.Remove(clientIndex);
|
||||||
|
byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication");
|
||||||
|
mySecureTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null);
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
||||||
|
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)
|
||||||
|
mySecureTCPServer.ReceiveDataAsync(clientIndex, ReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - EventHelpers/Callbacks
|
||||||
|
//Private Helper method to call the Connection Change Event
|
||||||
|
void onConnectionChange()
|
||||||
|
{
|
||||||
|
var handler = ClientConnectionChange;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new DynamicTCPSocketStatusChangeEventArgs(Server, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Helper Method to call the Text Received Event
|
||||||
|
void onTextReceived(string text)
|
||||||
|
{
|
||||||
|
var handler = TextReceived;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new GenericCommMethodReceiveTextArgs(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Helper Method to call the Server State Change Event
|
||||||
|
void onServerStateChange()
|
||||||
|
{
|
||||||
|
var handler = ServerStateChange;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new DynamicTCPServerStateChangedEventArgs(Server, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Event Handler method to handle the closing of connections when the program stops
|
||||||
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
|
{
|
||||||
|
if (programEventType == eProgramStatusEventType.Stopping)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Program stopping. Closing server");
|
||||||
|
DisconnectAllClients();
|
||||||
|
StopListening();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
505
Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpServer.cs
Normal file
505
Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpServer.cs
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
/*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 Crestron.SimplSharp;
|
||||||
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
public class GenericTcpIpServer : Device
|
||||||
|
{
|
||||||
|
#region Events
|
||||||
|
/// <summary>
|
||||||
|
/// Event for Receiving text
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event for client connection socket status change
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DynamicTCPSocketStatusChangeEventArgs> ClientConnectionChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event for Server State Change
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DynamicTCPServerStateChangedEventArgs> ServerStateChange;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties/Variables
|
||||||
|
/// <summary>
|
||||||
|
/// Text representation of the Socket Status enum values for the server
|
||||||
|
/// </summary>
|
||||||
|
public string Status
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Server != null)
|
||||||
|
return Server.State.ToString();
|
||||||
|
else
|
||||||
|
return ServerState.SERVER_NOT_LISTENING.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get { return (Server != null) && (Server.State == ServerState.SERVER_CONNECTED); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsConnected
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsConnected
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bool showing if socket is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsListening
|
||||||
|
{
|
||||||
|
get { return (Server != null) && (Server.State == ServerState.SERVER_LISTENING); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for IsConnected
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsListening
|
||||||
|
{
|
||||||
|
get { return (ushort)(IsListening ? 1 : 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable
|
||||||
|
/// <summary>
|
||||||
|
/// Number of clients currently connected.
|
||||||
|
/// </summary>
|
||||||
|
public ushort NumberOfClientsConnected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Server != null)
|
||||||
|
return (ushort)Server.NumberOfClientsConnected;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Port Server should listen on
|
||||||
|
/// </summary>
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ helper for Port
|
||||||
|
/// </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. Must be set the same in the client, and if true shared keys must be identical on server/client
|
||||||
|
/// </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.
|
||||||
|
/// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called
|
||||||
|
/// </summary>
|
||||||
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received
|
||||||
|
/// </summary>
|
||||||
|
public bool HeartbeatRequired { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ Helper for Heartbeat Required
|
||||||
|
/// </summary>
|
||||||
|
public ushort UHeartbeatRequired
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == 1)
|
||||||
|
HeartbeatRequired = true;
|
||||||
|
else
|
||||||
|
HeartbeatRequired = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
||||||
|
/// </summary>
|
||||||
|
public int HeartbeatRequiredIntervalMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simpl+ Heartbeat Analog value in seconds
|
||||||
|
/// </summary>
|
||||||
|
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// String to Match for heartbeat. If null or empty any string will reset heartbeat timer
|
||||||
|
/// </summary>
|
||||||
|
public string HeartbeatStringToMatch { get; set; }
|
||||||
|
|
||||||
|
//private timers for Heartbeats per client
|
||||||
|
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||||
|
|
||||||
|
//flags to show the server is waiting for client at index to send the shared key
|
||||||
|
List<uint> WaitingForSharedKey = new List<uint>();
|
||||||
|
|
||||||
|
//Store the connected client indexes
|
||||||
|
List<uint> ConnectedClientsIndexes = new List<uint>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to 2000
|
||||||
|
/// </summary>
|
||||||
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private flag to note that the server has stopped intentionally
|
||||||
|
/// </summary>
|
||||||
|
private bool ServerStopped { get; set; }
|
||||||
|
|
||||||
|
//Servers
|
||||||
|
TCPServer Server;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
/// <summary>
|
||||||
|
/// constructor
|
||||||
|
/// </summary>
|
||||||
|
public GenericTcpIpServer()
|
||||||
|
: base("Uninitialized Dynamic TCP Server")
|
||||||
|
{
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
BufferSize = 2000;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Server Actions
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
public void Initialize(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start listening on the specified port
|
||||||
|
/// </summary>
|
||||||
|
public void Listen()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Port < 1 || Port > 65535)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Server '{0}': Invalid port", Key);
|
||||||
|
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
|
{
|
||||||
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Server '{0}': No Shared Key set", Key);
|
||||||
|
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsListening)
|
||||||
|
return;
|
||||||
|
Server = new TCPServer(Port, MaxClients);
|
||||||
|
Server.SocketStatusChange += new TCPServerSocketStatusChangeEventHandler(SocketStatusChange);
|
||||||
|
ServerStopped = false;
|
||||||
|
Server.WaitForConnectionAsync(IPAddress.Any, ConnectCallback);
|
||||||
|
onServerStateChange();
|
||||||
|
Debug.Console(2, "Server Status: {0}, Socket Status: {1}\r\n", Server.State.ToString(), Server.ServerSocketStatus);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorLog.Error("Error with Dynamic Server: {0}", ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop Listeneing
|
||||||
|
/// </summary>
|
||||||
|
public void StopListening()
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Stopping Listener");
|
||||||
|
if (Server != null)
|
||||||
|
Server.Stop();
|
||||||
|
ServerStopped = true;
|
||||||
|
onServerStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect All Clients
|
||||||
|
/// </summary>
|
||||||
|
public void DisconnectAllClients()
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Disconnecting All Clients");
|
||||||
|
if (Server != null)
|
||||||
|
Server.DisconnectAll();
|
||||||
|
onConnectionChange();
|
||||||
|
onServerStateChange(); //State shows both listening and connected
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast text from server to all connected clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
public void BroadcastText(string text)
|
||||||
|
{
|
||||||
|
if (ConnectedClientsIndexes.Count > 0)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
foreach (uint i in ConnectedClientsIndexes)
|
||||||
|
Server.SendDataAsync(i, b, b.Length, SendDataAsyncCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not sure this is useful in library, maybe Pro??
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
public void SendTextToClient(string text, uint clientIndex)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
Server.SendDataAsync(clientIndex, b, b.Length, SendDataAsyncCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private method to check heartbeat requirements and start or reset timer
|
||||||
|
void checkHeartbeat(uint clientIndex, string received)
|
||||||
|
{
|
||||||
|
if (HeartbeatRequired)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(HeartbeatStringToMatch))
|
||||||
|
{
|
||||||
|
if (received == HeartbeatStringToMatch)
|
||||||
|
{
|
||||||
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - Callbacks
|
||||||
|
/// <summary>
|
||||||
|
/// Callback to disconnect if heartbeat timer finishes without being reset
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="o"></param>
|
||||||
|
void HeartbeatTimer_CallbackFunction(object o)
|
||||||
|
{
|
||||||
|
uint clientIndex = (uint)o;
|
||||||
|
|
||||||
|
string address = Server.GetAddressServerAcceptedConnectionFromForSpecificClient(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);
|
||||||
|
|
||||||
|
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||||
|
Server.Disconnect(clientIndex);
|
||||||
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TCP Server Socket Status Change Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="serverSocketStatus"></param>
|
||||||
|
void SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Client at {0} ServerSocketStatus {1}",
|
||||||
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString());
|
||||||
|
if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
{
|
||||||
|
if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex))
|
||||||
|
WaitingForSharedKey.Add(clientIndex);
|
||||||
|
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
ConnectedClientsIndexes.Add(clientIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
ConnectedClientsIndexes.Remove(clientIndex);
|
||||||
|
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
|
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
if (Server.ServerSocketStatus.ToString() != Status)
|
||||||
|
onConnectionChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TCP Client Connected to Server Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="myTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
void ConnectCallback(TCPServer myTCPServer, uint clientIndex)
|
||||||
|
{
|
||||||
|
if (myTCPServer.ClientConnected(clientIndex))
|
||||||
|
{
|
||||||
|
if (SharedKeyRequired)
|
||||||
|
{
|
||||||
|
byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n");
|
||||||
|
myTCPServer.SendDataAsync(clientIndex, b, b.Length, SendDataAsyncCallback);
|
||||||
|
Debug.Console(2, "Sent Shared Key to client at {0}", myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
|
}
|
||||||
|
if (HeartbeatRequired)
|
||||||
|
{
|
||||||
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Received Data Async Callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="myTCPServer"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="numberOfBytesReceived"></param>
|
||||||
|
void ReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived)
|
||||||
|
{
|
||||||
|
if (numberOfBytesReceived > 0)
|
||||||
|
{
|
||||||
|
string received = "Nothing";
|
||||||
|
byte[] bytes = myTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
||||||
|
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
||||||
|
if (WaitingForSharedKey.Contains(clientIndex))
|
||||||
|
{
|
||||||
|
received = received.Replace("\r", "");
|
||||||
|
received = received.Replace("\n", "");
|
||||||
|
if (received != SharedKey)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
||||||
|
myTCPServer.SendDataAsync(clientIndex, b, b.Length, null);
|
||||||
|
myTCPServer.Disconnect(clientIndex);
|
||||||
|
}
|
||||||
|
if (myTCPServer.NumberOfClientsConnected > 0)
|
||||||
|
myTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
||||||
|
WaitingForSharedKey.Remove(clientIndex);
|
||||||
|
byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication");
|
||||||
|
myTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null);
|
||||||
|
myTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
myTCPServer.ReceiveDataAsync(ReceivedDataAsyncCallback);
|
||||||
|
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)
|
||||||
|
myTCPServer.ReceiveDataAsync(clientIndex, ReceivedDataAsyncCallback);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods - EventHelpers/Callbacks
|
||||||
|
//Private Helper method to call the Connection Change Event
|
||||||
|
void onConnectionChange()
|
||||||
|
{
|
||||||
|
var handler = ClientConnectionChange;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new DynamicTCPSocketStatusChangeEventArgs(Server, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Helper Method to call the Text Received Event
|
||||||
|
void onTextReceived(string text)
|
||||||
|
{
|
||||||
|
var handler = TextReceived;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new GenericCommMethodReceiveTextArgs(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Helper Method to call the Server State Change Event
|
||||||
|
void onServerStateChange()
|
||||||
|
{
|
||||||
|
var handler = ServerStateChange;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new DynamicTCPServerStateChangedEventArgs(Server, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private Event Handler method to handle the closing of connections when the program stops
|
||||||
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
|
{
|
||||||
|
if (programEventType == eProgramStatusEventType.Stopping)
|
||||||
|
{
|
||||||
|
Debug.Console(1, this, "Program stopping. Closing server");
|
||||||
|
DisconnectAllClients();
|
||||||
|
StopListening();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,10 +64,13 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="CommunicationExtras.cs" />
|
<Compile Include="CommunicationExtras.cs" />
|
||||||
<Compile Include="Comm\ControlPropertiesConfig.cs" />
|
<Compile Include="Comm\ControlPropertiesConfig.cs" />
|
||||||
|
<Compile Include="Comm\GenericSecureTcpIpServer.cs" />
|
||||||
|
<Compile Include="Comm\GenericSecureTcpIpClient.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Comm\eControlMethods.cs" />
|
<Compile Include="Comm\eControlMethods.cs" />
|
||||||
<Compile Include="Comm\FINISH CommStatic.cs" />
|
<Compile Include="Comm\FINISH CommStatic.cs" />
|
||||||
<Compile Include="Comm\CommunicationGather.cs" />
|
<Compile Include="Comm\CommunicationGather.cs" />
|
||||||
<Compile Include="Comm\GenericSecureTcpClient.cs" />
|
|
||||||
<Compile Include="Comm\EventArgs.cs" />
|
<Compile Include="Comm\EventArgs.cs" />
|
||||||
<Compile Include="Comm\GenericSshClient.cs" />
|
<Compile Include="Comm\GenericSshClient.cs" />
|
||||||
<Compile Include="CoreInterfaces.cs" />
|
<Compile Include="CoreInterfaces.cs" />
|
||||||
@@ -76,6 +79,7 @@
|
|||||||
<Compile Include="Debug\DebugMemory.cs" />
|
<Compile Include="Debug\DebugMemory.cs" />
|
||||||
<Compile Include="Device.cs" />
|
<Compile Include="Device.cs" />
|
||||||
<Compile Include="DeviceConfig.cs" />
|
<Compile Include="DeviceConfig.cs" />
|
||||||
|
<Compile Include="Comm\GenericTcpIpServer.cs" />
|
||||||
<Compile Include="EthernetHelper.cs" />
|
<Compile Include="EthernetHelper.cs" />
|
||||||
<Compile Include="Comm\GenericTcpIpClient.cs" />
|
<Compile Include="Comm\GenericTcpIpClient.cs" />
|
||||||
<Compile Include="JsonToSimpl\EventArgs and Constants.cs" />
|
<Compile Include="JsonToSimpl\EventArgs and Constants.cs" />
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -10,8 +10,8 @@
|
|||||||
<ArchiveName />
|
<ArchiveName />
|
||||||
</RequiredInfo>
|
</RequiredInfo>
|
||||||
<OptionalInfo>
|
<OptionalInfo>
|
||||||
<CompiledOn>3/17/2017 10:28:42 AM</CompiledOn>
|
<CompiledOn>3/18/2017 5:00:59 PM</CompiledOn>
|
||||||
<CompilerRev>1.0.6285.17060</CompilerRev>
|
<CompilerRev>1.0.6286.28828</CompilerRev>
|
||||||
</OptionalInfo>
|
</OptionalInfo>
|
||||||
<Plugin>
|
<Plugin>
|
||||||
<Version>Crestron.SIMPLSharp, Version=2.0.52.0, Culture=neutral, PublicKeyToken=812d080f93e2de10</Version>
|
<Version>Crestron.SIMPLSharp, Version=2.0.52.0, Culture=neutral, PublicKeyToken=812d080f93e2de10</Version>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
MainAssembly=PepperDash_Core.dll:1cb57e7d4dc5c0e9b66ba4c496fb831f
|
MainAssembly=PepperDash_Core.dll:9f208dcbcc49e496f38e91289aee13c4
|
||||||
MainAssemblyMinFirmwareVersion=1.007.0017
|
MainAssemblyMinFirmwareVersion=1.007.0017
|
||||||
MainAssemblyResource=SimplSharpData.dat:315526abf906cded47fb0c7510266a7e
|
MainAssemblyResource=SimplSharpData.dat:315526abf906cded47fb0c7510266a7e
|
||||||
ü
|
ü
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user