using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Timers; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; using PepperDash.Core.Logging; namespace PepperDash.Core; /// /// Generic secure TCP/IP server /// public class GenericSecureTcpIpServer : Device { #region Events /// /// Event for Receiving text /// public event EventHandler TextReceived; /// /// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread. /// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event. /// public event EventHandler TextReceivedQueueInvoke; /// /// Event for client connection socket status change /// public event EventHandler ClientConnectionChange; /// /// Event for Server State Change /// public event EventHandler ServerStateChange; /// /// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire /// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key. /// public event EventHandler ServerClientReadyForCommunications; /// /// A band aid event to notify user that the server has choked. /// public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; } /// /// /// public delegate void ServerHasChokedCallbackDelegate(); #endregion #region Properties/Variables /// /// Server listen lock /// private readonly object _serverLock = new(); /// /// Queue lock /// private readonly object _dequeueLock = new(); /// /// Broadcast lock /// private readonly object _broadcastLock = new(); /// /// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before /// calling initialize. /// public int ReceiveQueueSize { get; set; } /// /// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before /// calling initialize. /// private CrestronQueue MessageQueue; /// /// A bandaid client that monitors whether the server is reachable /// GenericSecureTcpIpClient_ForServer MonitorClient; /// /// Timer to operate the bandaid monitor client in a loop. /// Timer MonitorClientTimer; /// /// /// int MonitorClientFailureCount; /// /// 3 by default /// public int MonitorClientMaxFailureCount { get; set; } /// /// Text representation of the Socket Status enum values for the server /// public string Status { get { if (SecureServer != null) return SecureServer.State.ToString(); return ServerState.SERVER_NOT_LISTENING.ToString(); } } /// /// Bool showing if socket is connected /// public bool IsConnected { get { if (SecureServer != null) return (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED; return false; //return (Secure ? SecureServer != null : UnsecureServer != null) && //(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED : // (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED); } } /// /// S+ helper for IsConnected /// public ushort UIsConnected { get { return (ushort)(IsConnected ? 1 : 0); } } /// /// Bool showing if socket is connected /// public bool IsListening { get { if (SecureServer != null) return (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING; else return false; //return (Secure ? SecureServer != null : UnsecureServer != null) && //(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING : // (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING); } } /// /// S+ helper for IsConnected /// public ushort UIsListening { get { return (ushort)(IsListening ? 1 : 0); } } /// /// Max number of clients this server will allow for connection. Crestron max is 64. This number should be less than 65 /// public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable /// /// Number of clients currently connected. /// public ushort NumberOfClientsConnected { get { if (SecureServer != null) return (ushort)SecureServer.NumberOfClientsConnected; return 0; } } /// /// Port Server should listen on /// public int Port { get; set; } /// /// S+ helper for Port /// public ushort UPort { get { return Convert.ToUInt16(Port); } set { Port = Convert.ToInt32(value); } } /// /// 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 /// public bool SharedKeyRequired { get; set; } /// /// S+ helper for requires shared key bool /// public ushort USharedKeyRequired { set { if (value == 1) SharedKeyRequired = true; else SharedKeyRequired = false; } } /// /// 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 /// public string SharedKey { get; set; } /// /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received /// public bool HeartbeatRequired { get; set; } /// /// S+ Helper for Heartbeat Required /// public ushort UHeartbeatRequired { set { if (value == 1) HeartbeatRequired = true; else HeartbeatRequired = false; } } /// /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ /// public int HeartbeatRequiredIntervalMs { get; set; } /// /// Simpl+ Heartbeat Analog value in seconds /// public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } /// /// String to Match for heartbeat. If null or empty any string will reset heartbeat timer /// public string HeartbeatStringToMatch { get; set; } //private timers for Heartbeats per client Dictionary HeartbeatTimerDictionary = new Dictionary(); //flags to show the secure server is waiting for client at index to send the shared key List WaitingForSharedKey = new List(); List ClientReadyAfterKeyExchange = new List(); /// /// The connected client indexes /// public List ConnectedClientsIndexes = new List(); /// /// Defaults to 2000 /// public int BufferSize { get; set; } /// /// Private flag to note that the server has stopped intentionally /// private bool ServerStopped { get; set; } //Servers SecureTCPServer SecureServer; /// /// /// bool ProgramIsStopping; #endregion #region Constructors /// /// constructor S+ Does not accept a key. Use initialze with key to set the debug key on this device. If using with + make sure to set all properties manually. /// public GenericSecureTcpIpServer() : base("Uninitialized Secure TCP Server") { HeartbeatRequiredIntervalInSeconds = 15; CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); BufferSize = 2000; MonitorClientMaxFailureCount = 3; } /// /// constructor with debug key set at instantiation. Make sure to set all properties before listening. /// /// public GenericSecureTcpIpServer(string key) : base("Uninitialized Secure TCP Server") { HeartbeatRequiredIntervalInSeconds = 15; CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); BufferSize = 2000; MonitorClientMaxFailureCount = 3; Key = key; } /// /// Contstructor that sets all properties by calling the initialize method with a config object. This does set Queue size. /// /// public GenericSecureTcpIpServer(TcpServerConfigObject serverConfigObject) : base("Uninitialized Secure TCP Server") { HeartbeatRequiredIntervalInSeconds = 15; CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); BufferSize = 2000; MonitorClientMaxFailureCount = 3; Initialize(serverConfigObject); } #endregion #region Methods - Server Actions /// /// Disconnects all clients and stops the server /// public void KillServer() { ServerStopped = true; if (MonitorClient != null) { MonitorClient.Disconnect(); } DisconnectAllClientsForShutdown(); StopListening(); } /// /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ /// /// public void Initialize(string key) { Key = key; } /// /// Initialze the server /// /// public void Initialize(TcpServerConfigObject serverConfigObject) { try { if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key)) { Key = serverConfigObject.Key; MaxClients = serverConfigObject.MaxClients; Port = serverConfigObject.Port; SharedKeyRequired = serverConfigObject.SharedKeyRequired; SharedKey = serverConfigObject.SharedKey; HeartbeatRequired = serverConfigObject.HeartbeatRequired; HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; BufferSize = serverConfigObject.BufferSize; ReceiveQueueSize = serverConfigObject.ReceiveQueueSize > 20 ? serverConfigObject.ReceiveQueueSize : 20; MessageQueue = new CrestronQueue(ReceiveQueueSize); } else { ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); } } catch { ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); } } /// /// Start listening on the specified port /// public void Listen() { lock (_serverLock) { try { if (Port < 1 || Port > 65535) { this.LogError("Server '{0}': Invalid port", Key); ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key)); return; } if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) { this.LogError("Server '{0}': No Shared Key set", Key); ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key)); return; } if (SecureServer == null) { SecureServer = new SecureTCPServer(Port, MaxClients); if (HeartbeatRequired) SecureServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5); SecureServer.HandshakeTimeout = 30; SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange); } else { SecureServer.PortNumber = Port; } ServerStopped = false; // Start the listner SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING) { this.LogError("Error starting WaitForConnectionAsync {0}", status); } else { ServerStopped = false; } OnServerStateChange(SecureServer.State); this.LogInformation("Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus); } catch (Exception ex) { this.LogException(ex, "{1} Error with Dynamic Server: {0}", ex.ToString(), Key); } } // end lock } /// /// Stop Listeneing /// public void StopListening() { try { this.LogVerbose("Stopping Listener"); if (SecureServer != null) { SecureServer.Stop(); this.LogVerbose("Server State: {0}", SecureServer.State); OnServerStateChange(SecureServer.State); } ServerStopped = true; } catch (Exception ex) { this.LogException(ex, "Error stopping server. Error: {0}", ex.Message); } } /// /// Disconnects Client /// /// public void DisconnectClient(uint client) { try { SecureServer.Disconnect(client); this.LogVerbose("Disconnected client index: {0}", client); } catch (Exception ex) { this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message); } } /// /// Disconnect All Clients /// public void DisconnectAllClientsForShutdown() { this.LogInformation("Disconnecting All Clients"); if (SecureServer != null) { SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange; foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly { var i = index; if (!SecureServer.ClientConnected(index)) continue; try { SecureServer.Disconnect(i); this.LogInformation("Disconnected client index: {0}", i); } catch (Exception ex) { this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message); } } this.LogInformation("Server Status: {0}", SecureServer.ServerSocketStatus); } this.LogInformation("Disconnected All Clients"); ConnectedClientsIndexes.Clear(); if (!ProgramIsStopping) { OnConnectionChange(); OnServerStateChange(SecureServer.State); //State shows both listening and connected } // var o = new { }; } /// /// Broadcast text from server to all connected clients /// /// public void BroadcastText(string text) { lock (_broadcastLock) { try { if (ConnectedClientsIndexes.Count > 0) { byte[] b = Encoding.GetEncoding(28591).GetBytes(text); foreach (uint i in ConnectedClientsIndexes) { if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i))) { SocketErrorCodes error = SecureServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) this.LogVerbose("{error}", error); } } } } catch (Exception ex) { this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message); } } // end lock } /// /// Not sure this is useful in library, maybe Pro?? /// /// /// public void SendTextToClient(string text, uint clientIndex) { try { byte[] b = Encoding.GetEncoding(28591).GetBytes(text); if (SecureServer != null && SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) { if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) SecureServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); } } catch (Exception ex) { this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); } } //private method to check heartbeat requirements and start or reset timer string checkHeartbeat(uint clientIndex, string received) { try { if (HeartbeatRequired) { if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) { var remainingText = received.Replace(HeartbeatStringToMatch, ""); var noDelimiter = received.Trim(new char[] { '\r', '\n' }); if (noDelimiter.Contains(HeartbeatStringToMatch)) { if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) { HeartbeatTimerDictionary[clientIndex].Stop(); HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs; HeartbeatTimerDictionary[clientIndex].Start(); } else { var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false }; heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex); heartbeatTimer.Start(); HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer); } this.LogDebug("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); // Return Heartbeat SendTextToClient(HeartbeatStringToMatch, clientIndex); return remainingText; } } else { if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) { HeartbeatTimerDictionary[clientIndex].Stop(); HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs; HeartbeatTimerDictionary[clientIndex].Start(); } else { var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false }; heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex); heartbeatTimer.Start(); HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer); } this.LogInformation("Heartbeat Received: {0}, from client index: {1}", received, clientIndex); } } } catch (Exception ex) { this.LogException(ex, "Error checking heartbeat: {0}", ex.Message); } return received; } /// /// Get the IP Address for the client at the specifed index /// /// /// public string GetClientIPAddress(uint clientIndex) { this.LogInformation("GetClientIPAddress Index: {0}", clientIndex); if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) { var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); this.LogInformation("GetClientIPAddress IPAddreess: {0}", ipa); return ipa; } else { return ""; } } #endregion #region Methods - HeartbeatTimer Callback void HeartbeatTimer_CallbackFunction(object o) { uint clientIndex = 99999; string address = string.Empty; try { clientIndex = (uint)o; address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); this.LogInformation("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}", address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex); if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); var discoResult = SecureServer.Disconnect(clientIndex); if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) { HeartbeatTimerDictionary[clientIndex].Stop(); HeartbeatTimerDictionary[clientIndex].Dispose(); HeartbeatTimerDictionary.Remove(clientIndex); } } catch (Exception ex) { ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key); } } #endregion #region Methods - Socket Status Changed Callbacks /// /// Secure Server Socket Status Changed Callback /// /// /// /// void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus) { try { if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) { this.LogInformation("SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber); if (ConnectedClientsIndexes.Contains(clientIndex)) ConnectedClientsIndexes.Remove(clientIndex); if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) { HeartbeatTimerDictionary[clientIndex].Stop(); HeartbeatTimerDictionary[clientIndex].Dispose(); HeartbeatTimerDictionary.Remove(clientIndex); } if (ClientReadyAfterKeyExchange.Contains(clientIndex)) ClientReadyAfterKeyExchange.Remove(clientIndex); if (WaitingForSharedKey.Contains(clientIndex)) WaitingForSharedKey.Remove(clientIndex); if (SecureServer.MaxNumberOfClientSupported > SecureServer.NumberOfClientsConnected) { Listen(); } } } catch (Exception ex) { this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex.Message); } //Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state //after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag //is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event. System.Threading.Tasks.Task.Run(() => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex))); } #endregion #region Methods Connected Callbacks /// /// Secure TCP Client Connected to Secure Server Callback /// /// /// void SecureConnectCallback(SecureTCPServer server, uint clientIndex) { try { this.LogInformation("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); if (clientIndex != 0) { if (server.ClientConnected(clientIndex)) { if (!ConnectedClientsIndexes.Contains(clientIndex)) { ConnectedClientsIndexes.Add(clientIndex); } if (SharedKeyRequired) { if (!WaitingForSharedKey.Contains(clientIndex)) { WaitingForSharedKey.Add(clientIndex); } byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:"); server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); this.LogInformation("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); } else { OnServerClientReadyForCommunications(clientIndex); } if (HeartbeatRequired) { if (!HeartbeatTimerDictionary.ContainsKey(clientIndex)) { var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false }; heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex); heartbeatTimer.Start(); HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer); } } server.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); } } else { this.LogError("Client attempt faulty."); } } catch (Exception ex) { this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message); } // Rearm the listner SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING) { this.LogError("Socket status connect callback status {0}", status); if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS) { // There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact. server.Stop(); Listen(); } } } #endregion #region Methods - Send/Receive Callbacks /// /// Secure Received Data Async Callback /// /// /// /// void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived) { if (numberOfBytesReceived > 0) { string received = "Nothing"; var handler = TextReceivedQueueInvoke; try { 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"); this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received); mySecureTCPServer.SendData(clientIndex, b, b.Length); mySecureTCPServer.Disconnect(clientIndex); return; } WaitingForSharedKey.Remove(clientIndex); byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match"); mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null); OnServerClientReadyForCommunications(clientIndex); this.LogInformation("Client with index {0} provided the shared key and successfully connected to the server", clientIndex); } else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received))) { onTextReceived(received, clientIndex); if (handler != null) { MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(received, clientIndex)); } } } catch (Exception ex) { this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex.Message); } if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); //Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread. if (handler != null) { if (System.Threading.Monitor.TryEnter(_dequeueLock)) System.Threading.Tasks.Task.Run(() => DequeueEvent()); } } else { mySecureTCPServer.Disconnect(clientIndex); } } /// /// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently. /// It will dequeue items as they are enqueued automatically. /// void DequeueEvent() { try { while (true) { // Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather. var message = MessageQueue.Dequeue(); var handler = TextReceivedQueueInvoke; if (handler != null) { handler(this, message); } } } catch (Exception e) { this.LogError(e, "DequeueEvent error"); } // Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it. System.Threading.Monitor.Exit(_dequeueLock); } #endregion #region Methods - EventHelpers/Callbacks //Private Helper method to call the Connection Change Event void onConnectionChange(uint clientIndex, SocketStatus clientStatus) { if (clientIndex != 0) //0 is error not valid client change { var handler = ClientConnectionChange; if (handler != null) { handler(this, new GenericTcpServerSocketStatusChangeEventArgs(SecureServer, clientIndex, clientStatus)); } } } //Private Helper method to call the Connection Change Event void OnConnectionChange() { if (ProgramIsStopping) { return; } var handler = ClientConnectionChange; if (handler != null) { handler(this, new GenericTcpServerSocketStatusChangeEventArgs()); } } //Private Helper Method to call the Text Received Event void onTextReceived(string text, uint clientIndex) { var handler = TextReceived; if (handler != null) handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex)); } //Private Helper Method to call the Server State Change Event void OnServerStateChange(ServerState state) { if (ProgramIsStopping) { return; } var handler = ServerStateChange; if (handler != null) { handler(this, new GenericTcpServerStateChangedEventArgs(state)); } } /// /// Private Event Handler method to handle the closing of connections when the program stops /// /// void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) { if (programEventType == eProgramStatusEventType.Stopping) { ProgramIsStopping = true; // kill bandaid things if (MonitorClientTimer != null) MonitorClientTimer.Stop(); if (MonitorClient != null) MonitorClient.Disconnect(); this.LogInformation("Program stopping. Closing server"); KillServer(); } } //Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation void OnServerClientReadyForCommunications(uint clientIndex) { ClientReadyAfterKeyExchange.Add(clientIndex); var handler = ServerClientReadyForCommunications; if (handler != null) handler(this, new GenericTcpServerSocketStatusChangeEventArgs( this, clientIndex, SecureServer.GetServerSocketStatusForSpecificClient(clientIndex))); } #endregion #region Monitor Client /// /// Starts the monitor client cycle. Timed wait, then call RunMonitorClient /// void StartMonitorClient() { if (MonitorClientTimer != null) { return; } MonitorClientTimer = new Timer(60000) { AutoReset = false }; MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient(); MonitorClientTimer.Start(); } /// /// /// void RunMonitorClient() { MonitorClient = new GenericSecureTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000); MonitorClient.SharedKeyRequired = this.SharedKeyRequired; MonitorClient.SharedKey = this.SharedKey; MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback; //MonitorClient.ConnectionChange += MonitorClient_ConnectionChange; MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm; this.LogInformation("Starting monitor check"); MonitorClient.Connect(); // From here MonitorCLient either connects or hangs, MonitorClient will call back } /// /// /// void StopMonitorClient() { if (MonitorClient == null) return; MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm; MonitorClient.Disconnect(); MonitorClient = null; } /// /// On monitor connect, restart the operation /// void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args) { if (args.IsReady) { this.LogInformation("Monitor client connection success. Disconnecting in 2s"); MonitorClientTimer.Stop(); MonitorClientTimer = null; MonitorClientFailureCount = 0; CrestronEnvironment.Sleep(2000); StopMonitorClient(); StartMonitorClient(); } } /// /// If the client hangs, add to counter and maybe fire the choke event /// void MonitorClientHasHungCallback() { MonitorClientFailureCount++; MonitorClientTimer.Stop(); MonitorClientTimer = null; StopMonitorClient(); if (MonitorClientFailureCount < MonitorClientMaxFailureCount) { this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}", MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount); StartMonitorClient(); } else { this.LogError( "\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************", MonitorClientMaxFailureCount); var handler = ServerHasChoked; if (handler != null) handler(); // Some external thing is in charge here. Expected reset of program } } #endregion }