diff --git a/src/Pepperdash Core/Comm/GenericSshClient.cs b/src/Pepperdash Core/Comm/GenericSshClient.cs
index 51ba8f4..277b34c 100644
--- a/src/Pepperdash Core/Comm/GenericSshClient.cs
+++ b/src/Pepperdash Core/Comm/GenericSshClient.cs
@@ -8,167 +8,149 @@ using Crestron.SimplSharp.Ssh.Common;
namespace PepperDash.Core
{
- ///
- ///
- ///
+ ///
+ ///
+ ///
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
- {
- private const string SPlusKey = "Uninitialized SshClient";
+ {
+ private const string SPlusKey = "Uninitialized SshClient";
///
/// Object to enable stream debugging
///
public CommunicationStreamDebugging StreamDebugging { get; private set; }
- ///
- /// Event that fires when data is received. Delivers args with byte array
- ///
- public event EventHandler BytesReceived;
+ ///
+ /// Event that fires when data is received. Delivers args with byte array
+ ///
+ public event EventHandler BytesReceived;
- ///
- /// Event that fires when data is received. Delivered as text.
- ///
- public event EventHandler TextReceived;
+ ///
+ /// Event that fires when data is received. Delivered as text.
+ ///
+ public event EventHandler TextReceived;
- ///
- /// Event when the connection status changes.
- ///
- public event EventHandler ConnectionChange;
+ ///
+ /// Event when the connection status changes.
+ ///
+ public event EventHandler ConnectionChange;
/////
/////
/////
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
- ///
- /// Address of server
- ///
- public string Hostname { get; set; }
+ ///
+ /// Address of server
+ ///
+ public string Hostname { get; set; }
- ///
- /// Port on server
- ///
- public int Port { get; set; }
+ ///
+ /// Port on server
+ ///
+ public int Port { get; set; }
- ///
- /// Username for server
- ///
- public string Username { get; set; }
+ ///
+ /// Username for server
+ ///
+ public string Username { get; set; }
- ///
- /// And... Password for server. That was worth documenting!
- ///
- public string Password { get; set; }
+ ///
+ /// And... Password for server. That was worth documenting!
+ ///
+ public string Password { get; set; }
- ///
- /// True when the server is connected - when status == 2.
- ///
- public bool IsConnected
- {
- // returns false if no client or not connected
+ ///
+ /// True when the server is connected - when status == 2.
+ ///
+ public bool IsConnected
+ {
+ // returns false if no client or not connected
get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
- }
+ }
- ///
- /// S+ helper for IsConnected
- ///
- public ushort UIsConnected
- {
- get { return (ushort)(IsConnected ? 1 : 0); }
- }
+ ///
+ /// S+ helper for IsConnected
+ ///
+ public ushort UIsConnected
+ {
+ get { return (ushort)(IsConnected ? 1 : 0); }
+ }
- ///
- ///
- ///
- public SocketStatus ClientStatus
- {
- get { return _ClientStatus; }
- private set
- {
- if (_ClientStatus == value)
- return;
- _ClientStatus = value;
- OnConnectionChange();
- }
- }
- SocketStatus _ClientStatus;
+ ///
+ ///
+ ///
+ public SocketStatus ClientStatus
+ {
+ get { return _ClientStatus; }
+ private set
+ {
+ if (_ClientStatus == value)
+ return;
+ _ClientStatus = value;
+ OnConnectionChange();
+ }
+ }
+ SocketStatus _ClientStatus;
- ///
- /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
- /// and IsConnected with be true when this == 2.
- ///
- public ushort UStatus
- {
- get { return (ushort)_ClientStatus; }
- }
+ ///
+ /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
+ /// and IsConnected with be true when this == 2.
+ ///
+ public ushort UStatus
+ {
+ get { return (ushort)_ClientStatus; }
+ }
- ///
- /// Determines whether client will attempt reconnection on failure. Default is true
- ///
- public bool AutoReconnect { get; set; }
+ ///
+ /// Determines whether client will attempt reconnection on failure. Default is true
+ ///
+ public bool AutoReconnect { get; set; }
- ///
- /// Will be set and unset by connect and disconnect only
- ///
- public bool ConnectEnabled { get; private set; }
+ ///
+ /// Will be set and unset by connect and disconnect only
+ ///
+ public bool ConnectEnabled { get; private set; }
- ///
- /// S+ helper for AutoReconnect
- ///
- public ushort UAutoReconnect
- {
- get { return (ushort)(AutoReconnect ? 1 : 0); }
- set { AutoReconnect = value == 1; }
- }
+ ///
+ /// S+ helper for AutoReconnect
+ ///
+ public ushort UAutoReconnect
+ {
+ get { return (ushort)(AutoReconnect ? 1 : 0); }
+ set { AutoReconnect = value == 1; }
+ }
- ///
- /// Millisecond value, determines the timeout period in between reconnect attempts.
- /// Set to 5000 by default
- ///
- public int AutoReconnectIntervalMs { get; set; }
+ ///
+ /// Millisecond value, determines the timeout period in between reconnect attempts.
+ /// Set to 5000 by default
+ ///
+ public int AutoReconnectIntervalMs { get; set; }
- SshClient Client;
+ SshClient Client;
- ShellStream TheStream;
+ ShellStream TheStream;
- CTimer ReconnectTimer;
+ CTimer ReconnectTimer;
//Lock object to prevent simulatneous connect/disconnect operations
private CCriticalSection connectLock = new CCriticalSection();
private bool DisconnectLogged = false;
- ///
- /// Typical constructor.
- ///
- public GenericSshClient(string key, string hostname, int port, string username, string password) :
- base(key)
- {
+ ///
+ /// Typical constructor.
+ ///
+ public GenericSshClient(string key, string hostname, int port, string username, string password) :
+ base(key)
+ {
StreamDebugging = new CommunicationStreamDebugging(key);
- CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
- Key = key;
- Hostname = hostname;
- Port = port;
- Username = username;
- Password = password;
- AutoReconnectIntervalMs = 5000;
-
- ReconnectTimer = new CTimer(o =>
- {
- if (ConnectEnabled)
- {
- Connect();
- }
- }, Timeout.Infinite);
- }
-
- ///
- /// S+ Constructor - Must set all properties before calling Connect
- ///
- public GenericSshClient()
- : base(SPlusKey)
- {
- CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
- AutoReconnectIntervalMs = 5000;
+ CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
+ Key = key;
+ Hostname = hostname;
+ Port = port;
+ Username = username;
+ Password = password;
+ AutoReconnectIntervalMs = 5000;
ReconnectTimer = new CTimer(o =>
{
@@ -177,35 +159,53 @@ namespace PepperDash.Core
Connect();
}
}, Timeout.Infinite);
- }
+ }
- ///
- /// Just to help S+ set the key
- ///
- public void Initialize(string key)
- {
- Key = key;
- }
+ ///
+ /// S+ Constructor - Must set all properties before calling Connect
+ ///
+ public GenericSshClient()
+ : base(SPlusKey)
+ {
+ CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
+ AutoReconnectIntervalMs = 5000;
- ///
- /// Handles closing this up when the program shuts down
- ///
- void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
- {
- if (programEventType == eProgramStatusEventType.Stopping)
- {
- if (Client != null)
- {
- Debug.Console(1, this, "Program stopping. Closing connection");
+ ReconnectTimer = new CTimer(o =>
+ {
+ if (ConnectEnabled)
+ {
+ Connect();
+ }
+ }, Timeout.Infinite);
+ }
+
+ ///
+ /// Just to help S+ set the key
+ ///
+ public void Initialize(string key)
+ {
+ Key = key;
+ }
+
+ ///
+ /// Handles closing this up when the program shuts down
+ ///
+ void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
+ {
+ if (programEventType == eProgramStatusEventType.Stopping)
+ {
+ if (Client != null)
+ {
+ Debug.Console(1, this, "Program stopping. Closing connection");
Disconnect();
- }
- }
- }
+ }
+ }
+ }
- ///
- /// Connect to the server, using the provided properties.
- ///
- public void Connect()
+ ///
+ /// Connect to the server, using the provided properties.
+ ///
+ public void Connect()
{
// Don't go unless everything is here
if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535
@@ -229,7 +229,10 @@ namespace PepperDash.Core
Debug.Console(1, this, "Attempting connect");
// Cancel reconnect if running.
- ReconnectTimer.Stop();
+ if (ReconnectTimer != null)
+ {
+ ReconnectTimer.Stop();
+ }
// Cleanup the old client if it already exists
if (Client != null)
@@ -246,8 +249,6 @@ namespace PepperDash.Core
Debug.Console(1, this, "Creating new SshClient");
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
Client = new SshClient(connectionInfo);
-
- Client.ErrorOccurred -= Client_ErrorOccurred;
Client.ErrorOccurred += Client_ErrorOccurred;
//Attempt to connect
@@ -256,6 +257,11 @@ namespace PepperDash.Core
{
Client.Connect();
TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534);
+ if (TheStream.DataAvailable)
+ {
+ // empty the buffer if there is data
+ string str = TheStream.Read();
+ }
TheStream.DataReceived += Stream_DataReceived;
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Connected");
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
@@ -307,21 +313,21 @@ namespace PepperDash.Core
}
}
- ///
- /// Disconnect the clients and put away it's resources.
- ///
- public void Disconnect()
- {
- ConnectEnabled = false;
- // Stop trying reconnects, if we are
- if (ReconnectTimer != null)
- {
- ReconnectTimer.Stop();
- ReconnectTimer = null;
- }
+ ///
+ /// Disconnect the clients and put away it's resources.
+ ///
+ public void Disconnect()
+ {
+ ConnectEnabled = false;
+ // Stop trying reconnects, if we are
+ if (ReconnectTimer != null)
+ {
+ ReconnectTimer.Stop();
+ // ReconnectTimer = null;
+ }
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
- }
+ }
///
/// Kills the stream, cleans up the client and sets it to null
@@ -330,105 +336,125 @@ namespace PepperDash.Core
{
KillStream();
- if (Client != null)
- {
- Client.Disconnect();
- Client = null;
- ClientStatus = status;
- Debug.Console(1, this, "Disconnected");
+ try
+ {
+ if (Client != null)
+ {
+ Client.ErrorOccurred -= Client_ErrorOccurred;
+ Client.Disconnect();
+ Client.Dispose();
+ Client = null;
+ ClientStatus = status;
+ Debug.Console(1, this, "Disconnected");
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception in Kill Client:{0}", ex);
}
}
- ///
- /// Anything to do with reestablishing connection on failures
- ///
- void HandleConnectionFailure()
- {
+ ///
+ /// Anything to do with reestablishing connection on failures
+ ///
+ void HandleConnectionFailure()
+ {
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
Debug.Console(1, this, "Client nulled due to connection failure. AutoReconnect: {0}, ConnectEnabled: {1}", AutoReconnect, ConnectEnabled);
- if (AutoReconnect && ConnectEnabled)
- {
- Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
- if (ReconnectTimer == null)
- {
- ReconnectTimer = new CTimer(o =>
- {
- Connect();
- }, AutoReconnectIntervalMs);
- Debug.Console(1, this, "Attempting connection in {0} seconds",
- (float) (AutoReconnectIntervalMs/1000));
- }
- else
- {
- Debug.Console(1, this, "{0} second reconnect cycle running",
- (float) (AutoReconnectIntervalMs/1000));
- }
- }
- }
+ if (AutoReconnect && ConnectEnabled)
+ {
+ Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
+ if (ReconnectTimer == null)
+ {
+ ReconnectTimer = new CTimer(o =>
+ {
+ Connect();
+ }, AutoReconnectIntervalMs);
+ Debug.Console(1, this, "Attempting connection in {0} seconds",
+ (float)(AutoReconnectIntervalMs / 1000));
+ }
+ else
+ {
+ Debug.Console(1, this, "{0} second reconnect cycle running",
+ (float)(AutoReconnectIntervalMs / 1000));
+ }
+ }
+ }
///
/// Kills the stream
///
void KillStream()
- {
- if (TheStream != null)
- {
- TheStream.DataReceived -= Stream_DataReceived;
- TheStream.Close();
- TheStream.Dispose();
- TheStream = null;
- Debug.Console(1, this, "Disconnected stream");
- }
- }
-
- ///
- /// Handles the keyboard interactive authentication, should it be required.
- ///
- void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
- {
- foreach (AuthenticationPrompt prompt in e.Prompts)
- if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
- prompt.Response = Password;
- }
-
- ///
- /// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
- ///
- void Stream_DataReceived(object sender, Crestron.SimplSharp.Ssh.Common.ShellDataEventArgs e)
- {
- var bytes = e.Data;
- if (bytes.Length > 0)
- {
- var bytesHandler = BytesReceived;
- if (bytesHandler != null)
- {
- if (StreamDebugging.RxStreamDebuggingIsEnabled)
- {
- Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
- }
- bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
- }
-
- var textHandler = TextReceived;
- if (textHandler != null)
- {
- var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
- if (StreamDebugging.RxStreamDebuggingIsEnabled)
- Debug.Console(0, this, "Received: '{0}'", ComTextHelper.GetDebugText(str));
-
- textHandler(this, new GenericCommMethodReceiveTextArgs(str));
+ {
+ try
+ {
+ if (TheStream != null)
+ {
+ TheStream.DataReceived -= Stream_DataReceived;
+ TheStream.Close();
+ TheStream.Dispose();
+ TheStream = null;
+ Debug.Console(1, this, "Disconnected stream");
}
- }
- }
+ }
+ catch (Exception ex)
+ {
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception in Kill Stream:{0}", ex);
+ }
+ }
+
+ ///
+ /// Handles the keyboard interactive authentication, should it be required.
+ ///
+ void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
+ {
+ foreach (AuthenticationPrompt prompt in e.Prompts)
+ if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
+ prompt.Response = Password;
+ }
+
+ ///
+ /// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
+ ///
+ void Stream_DataReceived(object sender, Crestron.SimplSharp.Ssh.Common.ShellDataEventArgs e)
+ {
+ if (((ShellStream)sender).Length <= 0L)
+ {
+ return;
+ }
+ var response = ((ShellStream)sender).Read();
+
+ var bytesHandler = BytesReceived;
+
+ if (bytesHandler != null)
+ {
+ var bytes = Encoding.UTF8.GetBytes(response);
+ if (StreamDebugging.RxStreamDebuggingIsEnabled)
+ {
+ Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
+ }
+ bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
+ }
+
+ var textHandler = TextReceived;
+ if (textHandler != null)
+ {
+ if (StreamDebugging.RxStreamDebuggingIsEnabled)
+ Debug.Console(0, this, "Received: '{0}'", ComTextHelper.GetDebugText(response));
+
+ textHandler(this, new GenericCommMethodReceiveTextArgs(response));
+ }
+
+ }
- ///
- /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
- /// event
- ///
- void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e)
- {
+ ///
+ /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
+ /// event
+ ///
+ void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e)
+ {
CrestronInvoke.BeginInvoke(o =>
{
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
@@ -451,58 +477,69 @@ namespace PepperDash.Core
ReconnectTimer.Reset(AutoReconnectIntervalMs);
}
});
- }
+ }
- ///
- /// Helper for ConnectionChange event
- ///
- void OnConnectionChange()
- {
- if (ConnectionChange != null)
- ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
- }
+ ///
+ /// Helper for ConnectionChange event
+ ///
+ void OnConnectionChange()
+ {
+ if (ConnectionChange != null)
+ ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
+ }
- #region IBasicCommunication Members
+ #region IBasicCommunication Members
- ///
- /// Sends text to the server
- ///
- ///
- public void SendText(string text)
- {
- try
- {
+ ///
+ /// Sends text to the server
+ ///
+ ///
+ public void SendText(string text)
+ {
+ try
+ {
if (Client != null && TheStream != null && IsConnected)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
- Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
+ Debug.Console(0,
+ this,
+ "Sending {0} characters of text: '{1}'",
+ text.Length,
+ ComTextHelper.GetDebugText(text));
TheStream.Write(text);
TheStream.Flush();
-
}
else
{
Debug.Console(1, this, "Client is null or disconnected. Cannot Send Text");
}
- }
- catch (Exception ex)
- {
- Debug.Console(0, "Exception: {0}", ex.Message);
- Debug.Console(0, "Stack Trace: {0}", ex.StackTrace);
+ }
+ catch (ObjectDisposedException ex)
+ {
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message);
+ Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace);
- Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed. Disconnected, closing");
- }
- }
+ KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
+ ReconnectTimer.Reset();
+ }
+ catch (Exception ex)
+ {
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message);
+ Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace);
+
+ Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed");
+ }
+ }
///
/// Sends Bytes to the server
///
///
public void SendBytes(byte[] bytes)
- {
- try
- {
+ {
+ try
+ {
if (Client != null && TheStream != null && IsConnected)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
@@ -515,23 +552,34 @@ namespace PepperDash.Core
{
Debug.Console(1, this, "Client is null or disconnected. Cannot Send Bytes");
}
- }
- catch
- {
- Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed. Disconnected, closing");
- }
- }
+ }
+ catch (ObjectDisposedException ex)
+ {
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message);
+ Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace);
- #endregion
- }
+ KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
+ ReconnectTimer.Reset();
+ }
+ catch (Exception ex)
+ {
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message);
+ Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace);
- //*****************************************************************************************************
- //*****************************************************************************************************
- ///
- /// Fired when connection changes
- ///
- public class SshConnectionChangeEventArgs : EventArgs
- {
+ Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed");
+ }
+ }
+
+ #endregion
+ }
+
+ //*****************************************************************************************************
+ //*****************************************************************************************************
+ ///
+ /// Fired when connection changes
+ ///
+ public class SshConnectionChangeEventArgs : EventArgs
+ {
///
/// Connection State
///
@@ -552,10 +600,10 @@ namespace PepperDash.Core
///
public ushort Status { get { return Client.UStatus; } }
- ///
+ ///
/// S+ Constructor
- ///
- public SshConnectionChangeEventArgs() { }
+ ///
+ public SshConnectionChangeEventArgs() { }
///
/// EventArgs class
@@ -563,9 +611,9 @@ namespace PepperDash.Core
/// Connection State
/// The Client
public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
- {
- IsConnected = isConnected;
- Client = client;
- }
- }
-}
+ {
+ IsConnected = isConnected;
+ Client = client;
+ }
+ }
+}
\ No newline at end of file