diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index 13192ed5..2277a21c 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text; using System.Threading; using Crestron.SimplSharp; @@ -11,11 +12,12 @@ using Renci.SshNet.Common; namespace PepperDash.Core { /// - /// + /// SSH Client /// public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect { private const string SPlusKey = "Uninitialized SshClient"; + /// /// Object to enable stream debugging /// @@ -36,11 +38,6 @@ namespace PepperDash.Core /// public event EventHandler ConnectionChange; - /// - ///// - ///// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - /// /// Gets or sets the Hostname /// @@ -67,7 +64,7 @@ namespace PepperDash.Core public bool IsConnected { // returns false if no client or not connected - get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + get { return client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } /// @@ -83,16 +80,26 @@ namespace PepperDash.Core /// public SocketStatus ClientStatus { - get { return _ClientStatus; } + get { lock (_stateLock) { return _ClientStatus; } } private set { - if (_ClientStatus == value) - return; - _ClientStatus = value; - OnConnectionChange(); + bool shouldFireEvent = false; + lock (_stateLock) + { + if (_ClientStatus != value) + { + _ClientStatus = value; + shouldFireEvent = true; + } + } + // Fire event outside lock to avoid deadlock + if (shouldFireEvent) + OnConnectionChange(); } } - SocketStatus _ClientStatus; + + private SocketStatus _ClientStatus; + private bool _ConnectEnabled; /// /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event @@ -100,7 +107,7 @@ namespace PepperDash.Core /// public ushort UStatus { - get { return (ushort)_ClientStatus; } + get { lock (_stateLock) { return (ushort)_ClientStatus; } } } /// @@ -111,7 +118,11 @@ namespace PepperDash.Core /// /// Will be set and unset by connect and disconnect only /// - public bool ConnectEnabled { get; private set; } + public bool ConnectEnabled + { + get { lock (_stateLock) { return _ConnectEnabled; } } + private set { lock (_stateLock) { _ConnectEnabled = value; } } + } /// /// S+ helper for AutoReconnect @@ -127,17 +138,25 @@ namespace PepperDash.Core /// public int AutoReconnectIntervalMs { get; set; } - SshClient Client; + private SshClient client; - ShellStream TheStream; + private ShellStream shellStream; - CTimer ReconnectTimer; + private readonly Timer reconnectTimer; //Lock object to prevent simulatneous connect/disconnect operations //private CCriticalSection connectLock = new CCriticalSection(); - private SemaphoreSlim connectLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim connectLock = new SemaphoreSlim(1); - private bool DisconnectLogged = false; + // Thread-safety lock for state changes + private readonly object _stateLock = new object(); + + private bool disconnectLogged = false; + + /// + /// When true, turns off echo for the SSH session + /// + public bool DisableEcho { get; set; } /// /// Typical constructor. @@ -154,13 +173,13 @@ namespace PepperDash.Core Password = password; AutoReconnectIntervalMs = 5000; - ReconnectTimer = new CTimer(o => + reconnectTimer = new Timer(o => { - if (ConnectEnabled) + if (ConnectEnabled) // Now thread-safe property access { Connect(); } - }, System.Threading.Timeout.Infinite); + }, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); } /// @@ -172,23 +191,23 @@ namespace PepperDash.Core CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; - ReconnectTimer = new CTimer(o => + reconnectTimer = new Timer(o => { - if (ConnectEnabled) + if (ConnectEnabled) // Now thread-safe property access { Connect(); } - }, System.Threading.Timeout.Infinite); + }, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); } /// /// Handles closing this up when the program shuts down /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + private void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) { if (programEventType == eProgramStatusEventType.Stopping) { - if (Client != null) + if (client != null) { this.LogDebug("Program stopping. Closing connection"); Disconnect(); @@ -223,10 +242,10 @@ namespace PepperDash.Core this.LogDebug("Attempting connect"); // Cancel reconnect if running. - ReconnectTimer?.Stop(); + StopReconnectTimer(); // Cleanup the old client if it already exists - if (Client != null) + if (client != null) { this.LogDebug("Cleaning up disconnected client"); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); @@ -239,29 +258,37 @@ namespace PepperDash.Core this.LogDebug("Creating new SshClient"); ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); - Client = new SshClient(connectionInfo); - Client.ErrorOccurred += Client_ErrorOccurred; + client = new SshClient(connectionInfo); + client.ErrorOccurred += Client_ErrorOccurred; //Attempt to connect ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; try { - Client.Connect(); - TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534); - if (TheStream.DataAvailable) + client.Connect(); + + var modes = new Dictionary(); + + if (DisableEcho) + { + modes.Add(TerminalModes.ECHO, 0); + } + + shellStream = client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534, modes); + if (shellStream.DataAvailable) { // empty the buffer if there is data - string str = TheStream.Read(); + string str = shellStream.Read(); } - TheStream.DataReceived += Stream_DataReceived; + shellStream.DataReceived += Stream_DataReceived; this.LogInformation("Connected"); ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; - DisconnectLogged = false; + disconnectLogged = false; } catch (SshConnectionException e) { var ie = e.InnerException; // The details are inside!! - var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; + var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; if (ie is SocketException) { @@ -286,37 +313,37 @@ namespace PepperDash.Core this.LogVerbose(ie, "Exception details: "); } - DisconnectLogged = true; + disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); if (AutoReconnect) { this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); + StartReconnectTimer(); } } catch (SshOperationTimeoutException ex) { this.LogWarning("Connection attempt timed out: {message}", ex.Message); - DisconnectLogged = true; + disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); if (AutoReconnect) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); + StartReconnectTimer(); } } catch (Exception e) { - var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; + var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; this.LogError("Unhandled exception on connect: {error}", e.Message); this.LogVerbose(e, "Exception details: "); - DisconnectLogged = true; + disconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); if (AutoReconnect) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); + StartReconnectTimer(); } } } @@ -334,11 +361,7 @@ namespace PepperDash.Core { ConnectEnabled = false; // Stop trying reconnects, if we are - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - // ReconnectTimer = null; - } + StopReconnectTimer(); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); } @@ -352,12 +375,12 @@ namespace PepperDash.Core try { - if (Client != null) + if (client != null) { - Client.ErrorOccurred -= Client_ErrorOccurred; - Client.Disconnect(); - Client.Dispose(); - Client = null; + client.ErrorOccurred -= Client_ErrorOccurred; + client.Disconnect(); + client.Dispose(); + client = null; ClientStatus = status; this.LogDebug("Disconnected"); } @@ -371,16 +394,16 @@ namespace PepperDash.Core /// /// Kills the stream /// - void KillStream() + private void KillStream() { try { - if (TheStream != null) + if (shellStream != null) { - TheStream.DataReceived -= Stream_DataReceived; - TheStream.Close(); - TheStream.Dispose(); - TheStream = null; + shellStream.DataReceived -= Stream_DataReceived; + shellStream.Close(); + shellStream.Dispose(); + shellStream = null; this.LogDebug("Disconnected stream"); } } @@ -393,7 +416,7 @@ namespace PepperDash.Core /// /// Handles the keyboard interactive authentication, should it be required. /// - void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) + private void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) { foreach (AuthenticationPrompt prompt in e.Prompts) if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) @@ -403,7 +426,7 @@ namespace PepperDash.Core /// /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. /// - void Stream_DataReceived(object sender, ShellDataEventArgs e) + private void Stream_DataReceived(object sender, ShellDataEventArgs e) { if (((ShellStream)sender).Length <= 0L) { @@ -439,7 +462,7 @@ namespace PepperDash.Core /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange /// event /// - void Client_ErrorOccurred(object sender, ExceptionEventArgs e) + private void Client_ErrorOccurred(object sender, ExceptionEventArgs e) { CrestronInvoke.BeginInvoke(o => { @@ -459,7 +482,7 @@ namespace PepperDash.Core if (AutoReconnect && ConnectEnabled) { this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); + StartReconnectTimer(); } }); } @@ -467,7 +490,7 @@ namespace PepperDash.Core /// /// Helper for ConnectionChange event /// - void OnConnectionChange() + private void OnConnectionChange() { ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this)); } @@ -482,7 +505,7 @@ namespace PepperDash.Core { try { - if (Client != null && TheStream != null && IsConnected) + if (client != null && shellStream != null && IsConnected) { if (StreamDebugging.TxStreamDebuggingIsEnabled) this.LogInformation( @@ -490,8 +513,8 @@ namespace PepperDash.Core text.Length, ComTextHelper.GetDebugText(text)); - TheStream.Write(text); - TheStream.Flush(); + shellStream.Write(text); + shellStream.Flush(); } else { @@ -503,7 +526,7 @@ namespace PepperDash.Core this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - ReconnectTimer.Reset(); + StopReconnectTimer(); } catch (Exception ex) { @@ -519,13 +542,13 @@ namespace PepperDash.Core { try { - if (Client != null && TheStream != null && IsConnected) + if (client != null && shellStream != null && IsConnected) { if (StreamDebugging.TxStreamDebuggingIsEnabled) this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - TheStream.Write(bytes, 0, bytes.Length); - TheStream.Flush(); + shellStream.Write(bytes, 0, bytes.Length); + shellStream.Flush(); } else { @@ -537,7 +560,7 @@ namespace PepperDash.Core this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - ReconnectTimer.Reset(); + StopReconnectTimer(); } catch (Exception ex) { @@ -546,6 +569,83 @@ namespace PepperDash.Core } #endregion + /// + /// Safely starts the reconnect timer with exception handling + /// + private void StartReconnectTimer() + { + try + { + reconnectTimer?.Change(AutoReconnectIntervalMs, System.Threading.Timeout.Infinite); + } + catch (ObjectDisposedException) + { + // Timer was disposed, ignore + this.LogDebug("Attempted to start timer but it was already disposed"); + } + } + + /// + /// Safely stops the reconnect timer with exception handling + /// + private void StopReconnectTimer() + { + try + { + reconnectTimer?.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); + } + catch (ObjectDisposedException) + { + // Timer was disposed, ignore + this.LogDebug("Attempted to stop timer but it was already disposed"); + } + } + + /// + /// Deactivate method - properly dispose of resources + /// + public override bool Deactivate() + { + try + { + this.LogDebug("Deactivating SSH client - disposing resources"); + + // Stop trying reconnects + ConnectEnabled = false; + StopReconnectTimer(); + + // Disconnect and cleanup client + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + + // Dispose timer + try + { + reconnectTimer?.Dispose(); + } + catch (ObjectDisposedException) + { + // Already disposed, ignore + } + + // Dispose semaphore + try + { + connectLock?.Dispose(); + } + catch (ObjectDisposedException) + { + // Already disposed, ignore + } + + return base.Deactivate(); + } + catch (Exception ex) + { + this.LogException(ex, "Error during SSH client deactivation"); + return false; + } + } + } //***************************************************************************************************** diff --git a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs index c9235411..51b5871f 100644 --- a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs +++ b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs @@ -19,44 +19,44 @@ namespace PepperDash.Core /// public CommunicationStreamDebugging StreamDebugging { get; private set; } - /// - /// Fires when data is received from the server and returns it as a Byte array - /// - public event EventHandler BytesReceived; + /// + /// Fires when data is received from the server and returns it as a Byte array + /// + public event EventHandler BytesReceived; - /// - /// Fires when data is received from the server and returns it as text - /// - public event EventHandler TextReceived; + /// + /// Fires when data is received from the server and returns it as text + /// + public event EventHandler TextReceived; - /// - /// - /// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - public event EventHandler ConnectionChange; + /// + /// + /// + //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; + public event EventHandler ConnectionChange; - private string _hostname; + private string _hostname; /// /// Address of server /// public string Hostname { - get - { - return _hostname; - } + get + { + return _hostname; + } - set - { - _hostname = value; - if (_client != null) - { - _client.AddressClientConnectedTo = _hostname; - } - } - } + set + { + _hostname = value; + if (_client != null) + { + _client.AddressClientConnectedTo = _hostname; + } + } + } /// /// Gets or sets the Port @@ -78,19 +78,19 @@ namespace PepperDash.Core /// public int BufferSize { get; set; } - /// - /// The actual client class - /// - private TCPClient _client; + /// + /// The actual client class + /// + private TCPClient _client; - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } - + /// /// S+ helper for IsConnected /// @@ -99,15 +99,15 @@ namespace PepperDash.Core get { return (ushort)(IsConnected ? 1 : 0); } } - /// - /// _client socket status Read only - /// - public SocketStatus ClientStatus - { - get + /// + /// _client socket status Read only + /// + public SocketStatus ClientStatus + { + get { - return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; - } + return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; + } } /// @@ -119,26 +119,26 @@ namespace PepperDash.Core get { return (ushort)ClientStatus; } } - /// + /// /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } - /// - /// Ushort representation of client status - /// + /// + /// Ushort representation of client status + /// [Obsolete] - public ushort UClientStatus { get { return (ushort)ClientStatus; } } + public ushort UClientStatus { get { return (ushort)ClientStatus; } } - /// - /// Connection failure reason - /// - public string ConnectionFailure { get { return ClientStatus.ToString(); } } + /// + /// Connection failure reason + /// + public string ConnectionFailure { get { return ClientStatus.ToString(); } } - /// - /// Gets or sets the AutoReconnect - /// - public bool AutoReconnect { get; set; } + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } /// /// S+ helper for AutoReconnect @@ -149,29 +149,29 @@ namespace PepperDash.Core set { AutoReconnect = value == 1; } } - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } - /// - /// Set only when the disconnect method is called - /// - bool DisconnectCalledByUser; + /// + /// Set only when the disconnect method is called + /// + bool DisconnectCalledByUser; - /// - /// - /// - public bool Connected - { - get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } + /// + /// + /// + public bool Connected + { + get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } //Lock object to prevent simulatneous connect/disconnect operations private CCriticalSection connectLock = new CCriticalSection(); // private Timer for auto reconnect - private CTimer RetryTimer; + private CTimer RetryTimer; /// /// Constructor @@ -181,8 +181,8 @@ namespace PepperDash.Core /// /// public GenericTcpIpClient(string key, string address, int port, int bufferSize) - : base(key) - { + : base(key) + { StreamDebugging = new CommunicationStreamDebugging(key); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; @@ -218,18 +218,18 @@ namespace PepperDash.Core /// Default constructor for S+ /// public GenericTcpIpClient() - : base(SplusKey) + : base(SplusKey) { StreamDebugging = new CommunicationStreamDebugging(SplusKey); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; BufferSize = 2000; RetryTimer = new CTimer(o => { Reconnect(); }, Timeout.Infinite); - } + } /// /// Initialize method @@ -255,26 +255,26 @@ namespace PepperDash.Core /// /// /// - /// - /// Deactivate method - /// - public override bool Deactivate() - { + /// + /// Deactivate method + /// + public override bool Deactivate() + { RetryTimer.Stop(); RetryTimer.Dispose(); if (_client != null) { - _client.SocketStatusChange -= this.Client_SocketStatusChange; + _client.SocketStatusChange -= this.Client_SocketStatusChange; DisconnectClient(); } - return true; - } + return true; + } - /// - /// Connect method - /// - public void Connect() - { + /// + /// Connect method + /// + public void Connect() + { if (string.IsNullOrEmpty(Hostname)) { Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key); @@ -310,7 +310,7 @@ namespace PepperDash.Core { connectLock.Leave(); } - } + } private void Reconnect() { @@ -337,11 +337,11 @@ namespace PepperDash.Core } } - /// - /// Disconnect method - /// - public void Disconnect() - { + /// + /// Disconnect method + /// + public void Disconnect() + { try { connectLock.Enter(); @@ -355,7 +355,7 @@ namespace PepperDash.Core { connectLock.Leave(); } - } + } /// /// DisconnectClient method @@ -375,7 +375,7 @@ namespace PepperDash.Core /// /// void ConnectToServerCallback(TCPClient c) - { + { if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) { Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus); @@ -385,13 +385,13 @@ namespace PepperDash.Core { Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); } - } + } /// /// Disconnects, waits and attemtps to connect again /// void WaitAndTryReconnect() - { + { CrestronInvoke.BeginInvoke(o => { try @@ -409,7 +409,7 @@ namespace PepperDash.Core connectLock.Leave(); } }); - } + } /// /// Recieves incoming data @@ -417,7 +417,7 @@ namespace PepperDash.Core /// /// void Receive(TCPClient client, int numBytes) - { + { if (client != null) { if (numBytes > 0) @@ -443,52 +443,52 @@ namespace PepperDash.Core } textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } + } } client.ReceiveDataAsync(Receive); } - } + } - /// - /// SendText method - /// - public void SendText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - // Check debug level before processing byte array + /// + /// SendText method + /// + public void SendText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + // Check debug level before processing byte array if (StreamDebugging.TxStreamDebuggingIsEnabled) Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); if (_client != null) - _client.SendData(bytes, bytes.Length); - } + _client.SendData(bytes, bytes.Length); + } - /// - /// SendEscapedText method - /// - public void SendEscapedText(string text) - { - var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => - { - var hex = s.Groups[1].Value; - return ((char)Convert.ToByte(hex, 16)).ToString(); - }); - SendText(unescapedText); - } + /// + /// SendEscapedText method + /// + public void SendEscapedText(string text) + { + var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => + { + var hex = s.Groups[1].Value; + return ((char)Convert.ToByte(hex, 16)).ToString(); + }); + SendText(unescapedText); + } /// /// Sends Bytes to the server /// /// - /// - /// SendBytes method - /// - public void SendBytes(byte[] bytes) - { + /// + /// SendBytes method + /// + public void SendBytes(byte[] bytes) + { if (StreamDebugging.TxStreamDebuggingIsEnabled) Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); if (_client != null) - _client.SendData(bytes, bytes.Length); - } + _client.SendData(bytes, bytes.Length); + } /// /// Socket Status Change Handler @@ -496,7 +496,7 @@ namespace PepperDash.Core /// /// void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) - { + { if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) { Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); @@ -505,68 +505,73 @@ namespace PepperDash.Core else { Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - _client.ReceiveDataAsync(Receive); + _client.ReceiveDataAsync(Receive); } - var handler = ConnectionChange; - if (handler != null) - ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); - } - } + var handler = ConnectionChange; + if (handler != null) + ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); + } + } - /// - /// Represents a TcpSshPropertiesConfig - /// - public class TcpSshPropertiesConfig - { + /// + /// Represents a TcpSshPropertiesConfig + /// + public class TcpSshPropertiesConfig + { /// /// Address to connect to /// [JsonProperty(Required = Required.Always)] - public string Address { get; set; } - + public string Address { get; set; } + /// /// Port to connect to /// - [JsonProperty(Required = Required.Always)] - public int Port { get; set; } - + [JsonProperty(Required = Required.Always)] + public int Port { get; set; } + /// /// Username credential /// - public string Username { get; set; } - /// - /// Gets or sets the Password - /// - public string Password { get; set; } + public string Username { get; set; } + /// + /// Gets or sets the Password + /// + public string Password { get; set; } - /// - /// Defaults to 32768 - /// - public int BufferSize { get; set; } + /// + /// Defaults to 32768 + /// + public int BufferSize { get; set; } - /// - /// Gets or sets the AutoReconnect - /// - public bool AutoReconnect { get; set; } + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } - /// - /// Gets or sets the AutoReconnectIntervalMs - /// - public int AutoReconnectIntervalMs { get; set; } + /// + /// Gets or sets the AutoReconnectIntervalMs + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// When true, turns off echo for the SSH session + /// + [JsonProperty("disableSshEcho")] + public bool DisableSshEcho { get; set; } /// /// Default constructor /// public TcpSshPropertiesConfig() - { - BufferSize = 32768; - AutoReconnect = true; - AutoReconnectIntervalMs = 5000; + { + BufferSize = 32768; + AutoReconnect = true; + AutoReconnectIntervalMs = 5000; Username = ""; Password = ""; - } - - } - + DisableSshEcho = false; + } + } } diff --git a/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs b/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs index a0723c4b..fbc46579 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs @@ -64,8 +64,11 @@ namespace PepperDash.Essentials.Core break; case eControlMethod.Ssh: { - var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password); - ssh.AutoReconnect = c.AutoReconnect; + var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password) + { + AutoReconnect = c.AutoReconnect, + DisableEcho = c.DisableSshEcho + }; if (ssh.AutoReconnect) ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; comm = ssh; @@ -73,8 +76,10 @@ namespace PepperDash.Essentials.Core } case eControlMethod.Tcpip: { - var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize); - tcp.AutoReconnect = c.AutoReconnect; + var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize) + { + AutoReconnect = c.AutoReconnect + }; if (tcp.AutoReconnect) tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; comm = tcp; @@ -90,8 +95,10 @@ namespace PepperDash.Essentials.Core break; case eControlMethod.SecureTcpIp: { - var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize); - secureTcp.AutoReconnect = c.AutoReconnect; + var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize) + { + AutoReconnect = c.AutoReconnect + }; if (secureTcp.AutoReconnect) secureTcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; comm = secureTcp;