updated the secure and non secure servers and added clients that are meant to be used with our server classes. The clients add the shared key and other stability features when we manage the server.

This commit is contained in:
Joshua Gutenplan
2019-02-18 15:49:45 -08:00
parent 79b9059f1b
commit b3203cf961
9 changed files with 2858 additions and 743 deletions

View File

@@ -1,371 +0,0 @@
/*PepperDash Technology Corp.
JAG
Copyright: 2017
------------------------------------
***Notice of Ownership and Copyright***
The material in which this notice appears is the property of PepperDash Technology Corporation,
which claims copyright under the laws of the United States of America in the entire body of material
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
PepperDash Technology Corporation reserves all rights under applicable laws.
------------------------------------ */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
namespace PepperDash.Core
{
public class GenericSecureTcpIpClient : Device, ISocketStatus, IAutoReconnect
{
#region Events
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
#endregion
#region Properties & Variables
/// <summary>
/// Address of server
/// </summary>
public string Hostname { get; set; }
/// <summary>
/// Port on server
/// </summary>
public int Port { get; set; }
/// <summary>
/// S+ helper
/// </summary>
public ushort UPort
{
get { return Convert.ToUInt16(Port); }
set { Port = Convert.ToInt32(value); }
}
/// <summary>
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
/// </summary>
public bool SharedKeyRequired { get; set; }
/// <summary>
/// S+ helper for requires shared key bool
/// </summary>
public ushort USharedKeyRequired
{
set
{
if (value == 1)
SharedKeyRequired = true;
else
SharedKeyRequired = false;
}
}
/// <summary>
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
/// </summary>
public string SharedKey { get; set; }
/// <summary>
/// flag to show the client is waiting for the server to send the shared key
/// </summary>
private bool WaitingForSharedKeyResponse { get; set; }
/// <summary>
/// Defaults to 2000
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Bool showing if socket is connected
/// </summary>
public bool IsConnected
{
get
{
return (Client != null) && (Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED);
}
}
/// <summary>
/// S+ helper for IsConnected
/// </summary>
public ushort UIsConnected
{
get { return (ushort)(IsConnected ? 1 : 0); }
}
/// <summary>
/// Client socket status Read only
/// </summary>
public SocketStatus ClientStatus
{
get
{
if (Client == null)
return SocketStatus.SOCKET_STATUS_NO_CONNECT;
return Client.ClientStatus;
}
}
/// <summary>
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
/// and IsConnected would be true when this == 2.
/// </summary>
public ushort UStatus
{
get { return (ushort)ClientStatus; }
}
/// <summary>
/// Status text shows the message associated with socket status
/// </summary>
public string ClientStatusText { get { return ClientStatus.ToString(); } }
/// <summary>
/// bool to track if auto reconnect should be set on the socket
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// S+ helper for AutoReconnect
/// </summary>
public ushort UAutoReconnect
{
get { return (ushort)(AutoReconnect ? 1 : 0); }
set { AutoReconnect = value == 1; }
}
/// <summary>
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
/// </summary>
public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// Flag Set only when the disconnect method is called.
/// </summary>
bool DisconnectCalledByUser;
/// <summary>
/// Connected bool
/// </summary>
public bool Connected
{
get { return (Client != null) && (Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED); }
}
CTimer RetryTimer; //private Timer for auto reconnect
SecureTCPClient Client; //Secure Client Class
#endregion
#region Constructors
//Base class constructor
public GenericSecureTcpIpClient(string key, string address, int port, int bufferSize)
: base(key)
{
Hostname = address;
Port = port;
BufferSize = bufferSize;
AutoReconnectIntervalMs = 5000;
}
//base class constructor
public GenericSecureTcpIpClient()
: base("Uninitialized DynamicTcpClient")
{
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
BufferSize = 2000;
}
#endregion
#region Methods
/// <summary>
/// Just to help S+ set the key
/// </summary>
public void Initialize(string key)
{
Key = key;
}
/// <summary>
/// Handles closing this up when the program shuts down
/// </summary>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType == eProgramStatusEventType.Stopping)
{
if (Client != null)
{
Debug.Console(1, this, "Program stopping. Closing connection");
Client.DisconnectFromServer();
Client.Dispose();
}
}
}
/// <summary>
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
/// </summary>
public void Connect()
{
if (IsConnected)
return;
if (string.IsNullOrEmpty(Hostname))
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "DynamicTcpClient '{0}': No address set", Key);
ErrorLog.Warn(string.Format("DynamicTcpClient '{0}': No address set", Key));
return;
}
if (Port < 1 || Port > 65535)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "DynamicTcpClient '{0}': Invalid port", Key);
ErrorLog.Warn(string.Format("DynamicTcpClient '{0}': Invalid port", Key));
return;
}
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "DynamicTcpClient '{0}': No Shared Key set", Key);
ErrorLog.Warn(string.Format("DynamicTcpClient '{0}': No Shared Key set", Key));
return;
}
Client = new SecureTCPClient(Hostname, Port, BufferSize);
Client.SocketStatusChange += SecureClient_SocketStatusChange;
DisconnectCalledByUser = false;
if (SharedKeyRequired)
WaitingForSharedKeyResponse = true;
Client.ConnectToServer();
}
/// <summary>
/// Disconnect client. Does not dispose.
/// </summary>
public void Disconnect()
{
DisconnectCalledByUser = true;
if(Client != null)
Client.DisconnectFromServer();
}
/// <summary>
/// callback after connection made
/// </summary>
/// <param name="o"></param>
void ConnectToServerCallback(object o)
{
Client.ConnectToServer();
if (Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
WaitAndTryReconnect();
}
/// <summary>
/// Called from Socket Status change if auto reconnect and socket disconnected (Not disconnected by user)
/// </summary>
void WaitAndTryReconnect()
{
Client.DisconnectFromServer();
Debug.Console(2, "Attempting reconnect, status={0}", Client.ClientStatus);
if (!DisconnectCalledByUser)
RetryTimer = new CTimer(ConnectToServerCallback, AutoReconnectIntervalMs);
}
/// <summary>
/// Receive callback
/// </summary>
/// <param name="client"></param>
/// <param name="numBytes"></param>
void SecureReceive(SecureTCPClient client, int numBytes)
{
if (numBytes > 0)
{
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
if (WaitingForSharedKeyResponse && SharedKeyRequired)
{
if (str != (SharedKey + "\n"))
{
WaitingForSharedKeyResponse = false;
client.DisconnectFromServer();
CrestronConsole.PrintLine("Client {0} was disconnected from server because the server did not respond with a matching shared key after connection", Key);
ErrorLog.Error("Client {0} was disconnected from server because the server did not respond with a matching shared key after connection", Key);
return;
}
else
{
WaitingForSharedKeyResponse = false;
CrestronConsole.PrintLine("Client {0} successfully connected to the server and received the Shared Key. Ready for communication", Key);
}
}
else
{
var bytesHandler = BytesReceived;
if (bytesHandler != null)
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
var textHandler = TextReceived;
if (textHandler != null)
{
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
}
}
client.ReceiveDataAsync(SecureReceive);
}
/// <summary>
/// General send method
/// </summary>
public void SendText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
Client.SendData(bytes, bytes.Length);
}
public void SendBytes(byte[] bytes)
{
Client.SendData(bytes, bytes.Length);
}
/// <summary>
/// SocketStatusChange Callback
/// </summary>
/// <param name="client"></param>
/// <param name="clientSocketStatus"></param>
void SecureClient_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus)
{
Debug.Console(2, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED && !DisconnectCalledByUser && AutoReconnect)
WaitAndTryReconnect();
// Probably doesn't need to be a switch since all other cases were eliminated
switch (clientSocketStatus)
{
case SocketStatus.SOCKET_STATUS_CONNECTED:
client.ReceiveDataAsync(SecureReceive);
if(SharedKeyRequired)
SendText(SharedKey + "\n");
DisconnectCalledByUser = false;
break;
}
var handler = ConnectionChange;
if (handler != null)
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
}
#endregion
}
}