diff --git a/Renci.SshClient/Renci.SshNet.Silverlight/Session.SilverlightShared.cs b/Renci.SshClient/Renci.SshNet.Silverlight/Session.SilverlightShared.cs
index 1ce15c54..598e66e6 100644
--- a/Renci.SshClient/Renci.SshNet.Silverlight/Session.SilverlightShared.cs
+++ b/Renci.SshClient/Renci.SshNet.Silverlight/Session.SilverlightShared.cs
@@ -1,164 +1,212 @@
using System;
+using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Transport;
-using System.Collections.Generic;
namespace Renci.SshNet
{
public partial class Session
{
- private readonly AutoResetEvent _autoEvent = new AutoResetEvent(false);
+ private const byte Null = 0x00;
+ private const byte CarriageReturn = 0x0d;
+ private const byte LineFeed = 0x0a;
+
+ private readonly AutoResetEvent _connectEvent = new AutoResetEvent(false);
private readonly AutoResetEvent _sendEvent = new AutoResetEvent(false);
private readonly AutoResetEvent _receiveEvent = new AutoResetEvent(false);
- private bool _isConnected;
///
/// Gets a value indicating whether the socket is connected.
///
- ///
- /// true if the socket is connected; otherwise, false.
- ///
+ /// true if the socket is connected; otherwise, false
partial void IsSocketConnected(ref bool isConnected)
{
- isConnected = (this._socket != null && this._socket.Connected && this._isConnected);
+ isConnected = (_socket != null && _socket.Connected);
}
+ ///
+ /// Establishes a socket connection to the specified host and port.
+ ///
+ /// The host name of the server to connect to.
+ /// The port to connect to.
+ /// The connection failed to establish within the configured .
+ /// An error occurred trying to establish the connection.
partial void SocketConnect(string host, int port)
{
- var ep = new DnsEndPoint(host, port);
- this._socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ var timeout = ConnectionInfo.Timeout;
+ var ipAddress = host.GetIPAddress();
+ var ep = new IPEndPoint(ipAddress, port);
- var args = new SocketAsyncEventArgs();
- args.UserToken = this._socket;
- args.RemoteEndPoint = ep;
- args.Completed += OnConnect;
+ _socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
- this._socket.ConnectAsync(args);
- this._autoEvent.WaitOne(this.ConnectionInfo.Timeout);
+ var args = CreateSocketAsyncEventArgs(_connectEvent);
+ if (_socket.ConnectAsync(args))
+ {
+ if (!_connectEvent.WaitOne(timeout))
+ throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
+ "Connection failed to establish within {0:F0} milliseconds.", timeout.TotalMilliseconds));
+ }
if (args.SocketError != SocketError.Success)
- throw new SocketException((int)args.SocketError);
+ throw new SocketException((int) args.SocketError);
}
+ ///
+ /// Closes the socket.
+ ///
+ ///
+ /// This method will wait up to 10 seconds to send any remaining data.
+ ///
partial void SocketDisconnect()
{
- this._socket.Close(10000);
+ _socket.Close(10);
}
- partial void SocketReadLine(ref string response)
+ ///
+ /// Performs a blocking read on the socket until a line is read.
+ ///
+ /// The line read from the socket, or null when the remote server has shutdown and all data has been received.
+ /// A that represents the time to wait until a line is read.
+ /// The read has timed-out.
+ /// An error occurred when trying to access the socket.
+ partial void SocketReadLine(ref string response, TimeSpan timeout)
{
var encoding = new ASCIIEncoding();
-
- // Read data one byte at a time to find end of line and leave any unhandled information in the buffer to be processed later
var buffer = new List();
-
var data = new byte[1];
+
+ // read data one byte at a time to find end of line and leave any unhandled information in the buffer
+ // to be processed by subsequent invocations
do
{
- var args = new SocketAsyncEventArgs();
- args.SetBuffer(data, 0, data.Length);
- args.UserToken = this._socket;
- args.RemoteEndPoint = this._socket.RemoteEndPoint;
- args.Completed += OnReceive;
- this._socket.ReceiveAsync(args);
+ var args = CreateSocketAsyncEventArgs(_receiveEvent, data, 0, data.Length);
+ if (_socket.ReceiveAsync(args))
+ {
+ if (!_receiveEvent.WaitOne(timeout))
+ throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
+ "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
+ }
- if (!this._receiveEvent.WaitOne(this.ConnectionInfo.Timeout))
- throw new SshOperationTimeoutException("Socket read operation has timed out");
+ if (args.SocketError != SocketError.Success)
+ throw new SocketException((int) args.SocketError);
- // If zero bytes received then exit
if (args.BytesTransferred == 0)
+ // the remote server shut down the socket
break;
buffer.Add(data[0]);
}
- while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == 0x0A || buffer[buffer.Count - 1] == 0x00)));
+ while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == LineFeed || buffer[buffer.Count - 1] == Null)));
- // Return an empty version string if the buffer consists of a 0x00 character.
- if (buffer.Count > 0 && buffer[buffer.Count - 1] == 0x00)
- {
+ if (buffer.Count == 0)
+ response = null;
+ else if (buffer.Count == 1 && buffer[buffer.Count - 1] == 0x00)
+ // return an empty version string if the buffer consists of only a 0x00 character
response = string.Empty;
- }
- else if (buffer.Count == 0)
- response = string.Empty;
- else if (buffer.Count > 1 && buffer[buffer.Count - 2] == 0x0D)
+ else if (buffer.Count > 1 && buffer[buffer.Count - 2] == CarriageReturn)
+ // strip trailing CRLF
response = encoding.GetString(buffer.ToArray(), 0, buffer.Count - 2);
- else
+ else if (buffer.Count > 1 && buffer[buffer.Count - 1] == LineFeed)
+ // strip trailing LF
response = encoding.GetString(buffer.ToArray(), 0, buffer.Count - 1);
+ else
+ response = encoding.GetString(buffer.ToArray(), 0, buffer.Count);
}
- partial void SocketRead(int length, ref byte[] buffer)
+ ///
+ /// Performs a blocking read on the socket until bytes are received.
+ ///
+ /// The number of bytes to read.
+ /// The buffer to read to.
+ /// A that represents the time to wait until bytes a read.
+ /// The socket is closed.
+ /// The read has timed-out.
+ /// The read failed.
+ partial void SocketRead(int length, ref byte[] buffer, TimeSpan timeout)
{
- var receivedTotal = 0; // how many bytes is already received
+ var totalBytesReceived = 0; // how many bytes are already received
do
{
- var args = new SocketAsyncEventArgs();
- args.SetBuffer(buffer, receivedTotal, length - receivedTotal);
- args.UserToken = this._socket;
- args.RemoteEndPoint = this._socket.RemoteEndPoint;
- args.Completed += OnReceive;
- this._socket.ReceiveAsync(args);
-
- this._receiveEvent.WaitOne(this.ConnectionInfo.Timeout);
-
- if (args.SocketError == SocketError.WouldBlock ||
- args.SocketError == SocketError.IOPending ||
- args.SocketError == SocketError.NoBufferSpaceAvailable)
+ var args = CreateSocketAsyncEventArgs(_receiveEvent, buffer, totalBytesReceived,
+ length - totalBytesReceived);
+ if (_socket.ReceiveAsync(args))
{
- // socket buffer is probably empty, wait and try again
- Thread.Sleep(30);
- continue;
- }
- if (args.SocketError != SocketError.Success)
- {
- throw new SocketException((int)args.SocketError);
+ if (!_receiveEvent.WaitOne(timeout))
+ throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
+ "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
}
- var receivedBytes = args.BytesTransferred;
- if (receivedBytes > 0)
+ switch (args.SocketError)
{
- receivedTotal += receivedBytes;
- continue;
+ case SocketError.WouldBlock:
+ case SocketError.IOPending:
+ case SocketError.NoBufferSpaceAvailable:
+ // socket buffer is probably full, wait and try again
+ Thread.Sleep(30);
+ break;
+ case SocketError.Success:
+ var bytesReceived = args.BytesTransferred;
+ if (bytesReceived > 0)
+ {
+ totalBytesReceived += bytesReceived;
+ continue;
+ }
+
+ if (_isDisconnecting)
+ throw new SshConnectionException(
+ "An established connection was aborted by the software in your host machine.",
+ DisconnectReason.ConnectionLost);
+ throw new SshConnectionException("An established connection was aborted by the server.",
+ DisconnectReason.ConnectionLost);
+ default:
+ throw new SocketException((int) args.SocketError);
}
- throw new SshConnectionException("An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost);
- } while (receivedTotal < length);
+ } while (totalBytesReceived < length);
}
+ ///
+ /// Writes the specified data to the server.
+ ///
+ /// The data to write to the server.
+ /// The write has timed-out.
+ /// The write failed.
partial void SocketWrite(byte[] data)
{
- if (this._isConnected)
+ var timeout = ConnectionInfo.Timeout;
+ var totalBytesSent = 0; // how many bytes are already sent
+ var totalBytesToSend = data.Length;
+
+ do
{
- var args = new SocketAsyncEventArgs();
- args.SetBuffer(data, 0, data.Length);
- args.UserToken = this._socket;
- args.RemoteEndPoint = this._socket.RemoteEndPoint;
- args.Completed += OnSend;
+ var args = CreateSocketAsyncEventArgs(_sendEvent, data, 0, totalBytesToSend - totalBytesSent);
+ if (_socket.SendAsync(args))
+ {
+ if (!_sendEvent.WaitOne(timeout))
+ throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
+ "Socket write operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
+ }
- this._socket.SendAsync(args);
- }
- else
- throw new SocketException((int)SocketError.NotConnected);
-
- }
-
- private void OnConnect(object sender, SocketAsyncEventArgs e)
- {
- this._autoEvent.Set();
- this._isConnected = (e.SocketError == SocketError.Success);
- }
-
- private void OnSend(object sender, SocketAsyncEventArgs e)
- {
- this._sendEvent.Set();
- }
-
- private void OnReceive(object sender, SocketAsyncEventArgs e)
- {
- this._receiveEvent.Set();
+ switch (args.SocketError)
+ {
+ case SocketError.WouldBlock:
+ case SocketError.IOPending:
+ case SocketError.NoBufferSpaceAvailable:
+ // socket buffer is probably full, wait and try again
+ Thread.Sleep(30);
+ break;
+ case SocketError.Success:
+ totalBytesSent += args.BytesTransferred;
+ break;
+ default:
+ throw new SocketException((int) args.SocketError);
+}
+ } while (totalBytesSent < totalBytesToSend);
}
partial void ExecuteThread(Action action)
@@ -168,9 +216,9 @@ namespace Renci.SshNet
partial void InternalRegisterMessage(string messageName)
{
- lock (this._messagesMetadata)
+ lock (_messagesMetadata)
{
- foreach (var item in from m in this._messagesMetadata where m.Name == messageName select m)
+ foreach (var item in from m in _messagesMetadata where m.Name == messageName select m)
{
item.Enabled = true;
item.Activated = true;
@@ -180,14 +228,30 @@ namespace Renci.SshNet
partial void InternalUnRegisterMessage(string messageName)
{
- lock (this._messagesMetadata)
+ lock (_messagesMetadata)
{
- foreach (var item in from m in this._messagesMetadata where m.Name == messageName select m)
+ foreach (var item in from m in _messagesMetadata where m.Name == messageName select m)
{
item.Enabled = false;
item.Activated = false;
}
}
}
+
+ private SocketAsyncEventArgs CreateSocketAsyncEventArgs(EventWaitHandle waitHandle)
+ {
+ var args = new SocketAsyncEventArgs();
+ args.UserToken = _socket;
+ args.RemoteEndPoint = _socket.RemoteEndPoint;
+ args.Completed += (sender, eventArgs) => waitHandle.Set();
+ return args;
+ }
+
+ private SocketAsyncEventArgs CreateSocketAsyncEventArgs(EventWaitHandle waitHandle, byte[] data, int offset, int count)
+ {
+ var args = CreateSocketAsyncEventArgs(waitHandle);
+ args.SetBuffer(data, offset, count);
+ return args;
+ }
}
}
diff --git a/Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs b/Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs
index ad13e3a9..ba8207b9 100644
--- a/Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs
+++ b/Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs
@@ -10,6 +10,33 @@ namespace Renci.SshNet.Tests.Classes
{
public partial class SessionTest
{
+ [TestMethod]
+ public void ConnectShouldThrowProxyExceptionWhenHttpProxyResponseDoesNotContainStatusLine()
+ {
+ var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
+ var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+ using (var proxyStub = new HttpProxyStub(proxyEndPoint))
+ {
+ proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Whatever\r\n"));
+ proxyStub.Start();
+
+ using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon")))
+ {
+ try
+ {
+ session.Connect();
+ Assert.Fail();
+ }
+ catch (ProxyException ex)
+ {
+ Assert.IsNull(ex.InnerException);
+ Assert.AreEqual("HTTP response does not contain status line.", ex.Message);
+ }
+ }
+ }
+ }
+
[TestMethod]
public void ConnectShouldThrowProxyExceptionWhenHttpProxyReturnsHttpStatusOtherThan200()
{
diff --git a/Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.cs b/Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.cs
index 62d38455..679424f8 100644
--- a/Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.cs
+++ b/Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.cs
@@ -1,4 +1,9 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Common;
using Renci.SshNet.Messages;
using Renci.SshNet.Tests.Common;
@@ -11,10 +16,206 @@ namespace Renci.SshNet.Tests.Classes
[TestClass]
public partial class SessionTest : TestBase
{
+ [TestMethod]
+ public void ConnectShouldSkipLinesBeforeProtocolIdentificationString()
+ {
+ var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+ var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5));
+
+ using (var serverStub = new AsyncSocketListener(serverEndPoint))
+ {
+ serverStub.Connected += (socket) =>
+ {
+ socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+ socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
+ socket.Send(Encoding.ASCII.GetBytes("SSH-666-SshStub\r\n"));
+ socket.Shutdown(SocketShutdown.Send);
+ };
+ serverStub.Start();
+
+ using (var session = new Session(connectionInfo))
+ {
+ try
+ {
+ session.Connect();
+ Assert.Fail();
+ }
+ catch (SshConnectionException ex)
+ {
+ Assert.IsNull(ex.InnerException);
+ Assert.AreEqual("Server version '666' is not supported.", ex.Message);
+
+ Assert.AreEqual("SSH-666-SshStub", connectionInfo.ServerVersion);
+ }
+ }
+ }
+ }
+
+ [TestMethod]
+ public void ConnectShouldSupportProtocolIdentificationStringThatDoesNotEndWithCrlf()
+ {
+ var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+ var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5));
+
+ using (var serverStub = new AsyncSocketListener(serverEndPoint))
+ {
+ serverStub.Connected += (socket) =>
+ {
+ socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+ socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
+ socket.Send(Encoding.ASCII.GetBytes("SSH-666-SshStub"));
+ socket.Shutdown(SocketShutdown.Send);
+ };
+ serverStub.Start();
+
+ using (var session = new Session(connectionInfo))
+ {
+ try
+ {
+ session.Connect();
+ Assert.Fail();
+ }
+ catch (SshConnectionException ex)
+ {
+ Assert.IsNull(ex.InnerException);
+ Assert.AreEqual("Server version '666' is not supported.", ex.Message);
+
+ Assert.AreEqual("SSH-666-SshStub", connectionInfo.ServerVersion);
+ }
+ }
+ }
+ }
+
+ [TestMethod]
+ public void ConnectShouldThrowSshOperationExceptionWhenServerDoesNotRespondWithinConnectionTimeout()
+ {
+ var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+ var timeout = TimeSpan.FromMilliseconds(500);
+ Socket clientSocket = null;
+
+ using (var serverStub = new AsyncSocketListener(serverEndPoint))
+ {
+ serverStub.Connected += (socket) =>
+ {
+ socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+ socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
+ clientSocket = socket;
+ };
+ serverStub.Start();
+
+ using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromMilliseconds(500))))
+ {
+ try
+ {
+ session.Connect();
+ Assert.Fail();
+ }
+ catch (SshOperationTimeoutException ex)
+ {
+ Assert.IsNull(ex.InnerException);
+ Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds), ex.Message);
+
+ Assert.IsNotNull(clientSocket);
+ Assert.IsTrue(clientSocket.Connected);
+
+ // shut down socket
+ clientSocket.Shutdown(SocketShutdown.Send);
+ }
+ }
+ }
+ }
+
+ [TestMethod]
+ public void ConnectShouldSshConnectionExceptionWhenServerResponseDoesNotContainProtocolIdentificationString()
+ {
+ var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
+
+ // response ends with CRLF
+ using (var serverStub = new AsyncSocketListener(serverEndPoint))
+ {
+ serverStub.Connected += (socket) =>
+ {
+ socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+ socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
+ socket.Shutdown(SocketShutdown.Send);
+ };
+ serverStub.Start();
+
+ using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5))))
+ {
+ try
+ {
+ session.Connect();
+ Assert.Fail();
+ }
+ catch (SshConnectionException ex)
+ {
+ Assert.IsNull(ex.InnerException);
+ Assert.AreEqual("Server response does not contain SSH protocol identification.", ex.Message);
+ }
+ }
+ }
+
+ // response does not end with CRLF
+ using (var serverStub = new AsyncSocketListener(serverEndPoint))
+ {
+ serverStub.Connected += (socket) =>
+ {
+ socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+ socket.Send(Encoding.ASCII.GetBytes("WELCOME banner"));
+ socket.Shutdown(SocketShutdown.Send);
+ };
+ serverStub.Start();
+
+ using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5))))
+ {
+ try
+ {
+ session.Connect();
+ Assert.Fail();
+ }
+ catch (SshConnectionException ex)
+ {
+ Assert.IsNull(ex.InnerException);
+ Assert.AreEqual("Server response does not contain SSH protocol identification.", ex.Message);
+ }
+ }
+ }
+
+ // last line is empty
+ using (var serverStub = new AsyncSocketListener(serverEndPoint))
+ {
+ serverStub.Connected += (socket) =>
+ {
+ socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+ socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n"));
+ socket.Send(Encoding.ASCII.GetBytes("\r\n"));
+ socket.Shutdown(SocketShutdown.Send);
+ };
+ serverStub.Start();
+
+ using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5))))
+ {
+ try
+ {
+ session.Connect();
+ Assert.Fail();
+ }
+ catch (SshConnectionException ex)
+ {
+ Assert.IsNull(ex.InnerException);
+ Assert.AreEqual("Server response does not contain SSH protocol identification.", ex.Message);
+ }
+ }
+ }
+ }
+
+
///
///A test for SessionSemaphore
///
[TestMethod()]
+ [Ignore]
public void SessionSemaphoreTest()
{
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
@@ -28,6 +229,7 @@ namespace Renci.SshNet.Tests.Classes
///A test for IsConnected
///
[TestMethod()]
+ [Ignore]
public void IsConnectedTest()
{
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
@@ -41,6 +243,7 @@ namespace Renci.SshNet.Tests.Classes
///A test for ClientInitMessage
///
[TestMethod()]
+ [Ignore]
public void ClientInitMessageTest()
{
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
@@ -54,6 +257,7 @@ namespace Renci.SshNet.Tests.Classes
///A test for UnRegisterMessage
///
[TestMethod()]
+ [Ignore]
public void UnRegisterMessageTest()
{
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
@@ -67,6 +271,7 @@ namespace Renci.SshNet.Tests.Classes
///A test for RegisterMessage
///
[TestMethod()]
+ [Ignore]
public void RegisterMessageTest()
{
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
@@ -80,6 +285,7 @@ namespace Renci.SshNet.Tests.Classes
///A test for Dispose
///
[TestMethod()]
+ [Ignore]
public void DisposeTest()
{
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
@@ -92,6 +298,7 @@ namespace Renci.SshNet.Tests.Classes
///A test for Disconnect
///
[TestMethod()]
+ [Ignore]
public void DisconnectTest()
{
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
@@ -104,6 +311,7 @@ namespace Renci.SshNet.Tests.Classes
///A test for Connect
///
[TestMethod()]
+ [Ignore]
public void ConnectTest()
{
ConnectionInfo connectionInfo = null; // TODO: Initialize to an appropriate value
@@ -112,5 +320,15 @@ namespace Renci.SshNet.Tests.Classes
Assert.Inconclusive("A method that does not return a value cannot be verified.");
}
+ private static ConnectionInfo CreateConnectionInfo(IPEndPoint serverEndPoint, TimeSpan timeout)
+ {
+ var connectionInfo = new ConnectionInfo(
+ serverEndPoint.Address.ToString(),
+ serverEndPoint.Port,
+ "eric",
+ new NoneAuthenticationMethod("eric"));
+ connectionInfo.Timeout = timeout;
+ return connectionInfo;
+ }
}
}
\ No newline at end of file
diff --git a/Renci.SshClient/Renci.SshNet/Session.NET.cs b/Renci.SshClient/Renci.SshNet/Session.NET.cs
index 09e2cc3f..46bf0d5d 100644
--- a/Renci.SshClient/Renci.SshNet/Session.NET.cs
+++ b/Renci.SshClient/Renci.SshNet/Session.NET.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System.Globalization;
+using System.Linq;
using System;
using System.Net.Sockets;
using System.Net;
@@ -12,6 +13,10 @@ namespace Renci.SshNet
{
public partial class Session
{
+ private const byte Null = 0x00;
+ private const byte CarriageReturn = 0x0d;
+ private const byte LineFeed = 0x0a;
+
private readonly TraceSource _log =
#if DEBUG
new TraceSource("SshNet.Logging", SourceLevels.All);
@@ -27,9 +32,7 @@ namespace Renci.SshNet
///
/// Gets a value indicating whether the socket is connected.
///
- ///
- /// true if the socket is connected; otherwise, false.
- ///
+ /// true if the socket is connected; otherwise, false
///
///
/// As a first check we verify whether is
@@ -52,7 +55,7 @@ namespace Renci.SshNet
///
///
/// Conclusion: when the return value is true - but no data is available for reading - then
- /// the socket is no longer connected.
+ /// the socket is no longer connected.
///
///
/// When a is used from multiple threads, there's a race condition
@@ -86,81 +89,102 @@ namespace Renci.SshNet
}
}
+ ///
+ /// Establishes a socket connection to the specified host and port.
+ ///
+ /// The host name of the server to connect to.
+ /// The port to connect to.
+ /// The connection failed to establish within the configured .
+ /// An error occurred trying to establish the connection.
partial void SocketConnect(string host, int port)
{
const int socketBufferSize = 2 * MaximumSshPacketSize;
- var addr = host.GetIPAddress();
+ var ipAddress = host.GetIPAddress();
+ var timeout = ConnectionInfo.Timeout;
+ var ep = new IPEndPoint(ipAddress, port);
- var ep = new IPEndPoint(addr, port);
- this._socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ _socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
+ _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, socketBufferSize);
+ _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, socketBufferSize);
- this._socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
- this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, socketBufferSize);
- this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, socketBufferSize);
+ Log(string.Format("Initiating connect to '{0}:{1}'.", ConnectionInfo.Host, ConnectionInfo.Port));
- this.Log(string.Format("Initiating connect to '{0}:{1}'.", this.ConnectionInfo.Host, this.ConnectionInfo.Port));
+ var connectResult = _socket.BeginConnect(ep, null, null);
+ if (!connectResult.AsyncWaitHandle.WaitOne(timeout, false))
+ throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
+ "Connection failed to establish within {0:F0} milliseconds.", timeout.TotalMilliseconds));
- // Connect socket with specified timeout
- var connectResult = this._socket.BeginConnect(ep, null, null);
-
- if (!connectResult.AsyncWaitHandle.WaitOne(this.ConnectionInfo.Timeout, false))
- {
- throw new SshOperationTimeoutException("Connection Could Not Be Established");
- }
-
- this._socket.EndConnect(connectResult);
+ _socket.EndConnect(connectResult);
}
+ ///
+ /// Closes the socket and allows the socket to be reused after the current connection is closed.
+ ///
+ /// An error occurred when trying to access the socket.
partial void SocketDisconnect()
{
_socket.Disconnect(true);
}
- partial void SocketReadLine(ref string response)
+ ///
+ /// Performs a blocking read on the socket until a line is read.
+ ///
+ /// The line read from the socket, or null when the remote server has shutdown and all data has been received.
+ /// A that represents the time to wait until a line is read.
+ /// The read has timed-out.
+ /// An error occurred when trying to access the socket.
+ partial void SocketReadLine(ref string response, TimeSpan timeout)
{
var encoding = new ASCIIEncoding();
-
- // Read data one byte at a time to find end of line and leave any unhandled information in the buffer to be processed later
var buffer = new List();
-
var data = new byte[1];
+
+ // read data one byte at a time to find end of line and leave any unhandled information in the buffer
+ // to be processed by subsequent invocations
do
{
- var asyncResult = this._socket.BeginReceive(data, 0, data.Length, SocketFlags.None, null, null);
+ var asyncResult = _socket.BeginReceive(data, 0, data.Length, SocketFlags.None, null, null);
+ if (!asyncResult.AsyncWaitHandle.WaitOne(timeout))
+ throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
+ "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
- if (!asyncResult.AsyncWaitHandle.WaitOne(this.ConnectionInfo.Timeout))
- throw new SshOperationTimeoutException("Socket read operation has timed out");
+ var received = _socket.EndReceive(asyncResult);
- var received = this._socket.EndReceive(asyncResult);
-
- // If zero bytes received then exit
if (received == 0)
+ // the remote server shut down the socket
break;
buffer.Add(data[0]);
}
- while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == 0x0A || buffer[buffer.Count - 1] == 0x00)));
+ while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == LineFeed || buffer[buffer.Count - 1] == Null)));
- // Return an empty version string if the buffer consists of a 0x00 character.
- if (buffer.Count > 0 && buffer[buffer.Count - 1] == 0x00)
- {
+ if (buffer.Count == 0)
+ response = null;
+ else if (buffer.Count == 1 && buffer[buffer.Count - 1] == 0x00)
+ // return an empty version string if the buffer consists of only a 0x00 character
response = string.Empty;
- }
- else if (buffer.Count > 1 && buffer[buffer.Count - 2] == 0x0D)
+ else if (buffer.Count > 1 && buffer[buffer.Count - 2] == CarriageReturn)
+ // strip trailing CRLF
response = encoding.GetString(buffer.Take(buffer.Count - 2).ToArray());
- else
+ else if (buffer.Count > 1 && buffer[buffer.Count - 1] == LineFeed)
+ // strip trailing LF
response = encoding.GetString(buffer.Take(buffer.Count - 1).ToArray());
+ else
+ response = encoding.GetString(buffer.ToArray());
}
///
- /// Function to read amount of data before returning, or throwing an exception.
+ /// Performs a blocking read on the socket until bytes are received.
///
- /// The amount wanted.
+ /// The number of bytes to read.
/// The buffer to read to.
- /// Happens when the socket is closed.
- /// Unhandled exception.
- partial void SocketRead(int length, ref byte[] buffer)
+ /// A that represents the time to wait until bytes a read.
+ /// The socket is closed.
+ /// The read has timed-out.
+ /// The read failed.
+ partial void SocketRead(int length, ref byte[] buffer, TimeSpan timeout)
{
var receivedTotal = 0; // how many bytes is already received
@@ -168,7 +192,7 @@ namespace Renci.SshNet
{
try
{
- var receivedBytes = this._socket.Receive(buffer, receivedTotal, length - receivedTotal, SocketFlags.None);
+ var receivedBytes = _socket.Receive(buffer, receivedTotal, length - receivedTotal, SocketFlags.None);
if (receivedBytes > 0)
{
// signal that bytes have been read from the socket
@@ -186,7 +210,7 @@ namespace Renci.SshNet
// 3 - MessageListener then again calls RaiseError().
// There the exception is checked for the exception thrown here (ConnectionLost), and if it matches it will not call Session.SendDisconnect().
//
- // Adding a check for this._isDisconnecting causes ReceiveMessage() to throw SshConnectionException: "Bad packet length {0}".
+ // Adding a check for _isDisconnecting causes ReceiveMessage() to throw SshConnectionException: "Bad packet length {0}".
//
if (_isDisconnecting)
@@ -198,7 +222,7 @@ namespace Renci.SshNet
if (exp.SocketErrorCode == SocketError.ConnectionAborted)
{
buffer = new byte[length];
- this.Disconnect();
+ Disconnect();
return;
}
@@ -215,16 +239,23 @@ namespace Renci.SshNet
} while (receivedTotal < length);
}
+ ///
+ /// Writes the specified data to the server.
+ ///
+ /// The data to write to the server.
+ /// The write has timed-out.
+ /// The write failed.
partial void SocketWrite(byte[] data)
{
- var sent = 0; // how many bytes is already sent
- var length = data.Length;
+ var totalBytesSent = 0; // how many bytes are already sent
+ var totalBytesToSend = data.Length;
do
{
try
{
- sent += this._socket.Send(data, sent, length - sent, SocketFlags.None);
+ totalBytesSent += _socket.Send(data, totalBytesSent, totalBytesToSend - totalBytesSent,
+ SocketFlags.None);
}
catch (SocketException ex)
{
@@ -238,13 +269,13 @@ namespace Renci.SshNet
else
throw; // any serious error occurr
}
- } while (sent < length);
+ } while (totalBytesSent < totalBytesToSend);
}
[Conditional("DEBUG")]
partial void Log(string text)
{
- this._log.TraceEvent(TraceEventType.Verbose, 1, text);
+ _log.TraceEvent(TraceEventType.Verbose, 1, text);
}
#if ASYNC_SOCKET_READ
@@ -273,7 +304,7 @@ namespace Renci.SshNet
if (socketException.SocketErrorCode == SocketError.ConnectionAborted)
{
buffer = new byte[length];
- this.Disconnect();
+ Disconnect();
return;
}
}
diff --git a/Renci.SshClient/Renci.SshNet/Session.cs b/Renci.SshClient/Renci.SshNet/Session.cs
index bcab9492..e1e51e68 100644
--- a/Renci.SshClient/Renci.SshNet/Session.cs
+++ b/Renci.SshClient/Renci.SshNet/Session.cs
@@ -510,18 +510,13 @@ namespace Renci.SshNet
while (true)
{
string serverVersion = string.Empty;
- this.SocketReadLine(ref serverVersion);
-
- this.ServerVersion = serverVersion;
- if (string.IsNullOrEmpty(this.ServerVersion))
- {
- throw new InvalidOperationException("Server string is null or empty.");
- }
-
- versionMatch = ServerVersionRe.Match(this.ServerVersion);
-
+ this.SocketReadLine(ref serverVersion, ConnectionInfo.Timeout);
+ if (serverVersion == null)
+ throw new SshConnectionException("Server response does not contain SSH protocol identification.");
+ versionMatch = ServerVersionRe.Match(serverVersion);
if (versionMatch.Success)
{
+ this.ServerVersion = serverVersion;
break;
}
}
@@ -1672,20 +1667,58 @@ namespace Renci.SshNet
///
partial void IsSocketConnected(ref bool isConnected);
+ ///
+ /// Establishes a socket connection to the specified host and port.
+ ///
+ /// The host name of the server to connect to.
+ /// The port to connect to.
+ /// The connection failed to establish within the configured .
+ /// An error occurred trying to establish the connection.
partial void SocketConnect(string host, int port);
+ ///
+ /// Closes the socket.
+ ///
+ /// An error occurred when trying to access the socket.
partial void SocketDisconnect();
- partial void SocketRead(int length, ref byte[] buffer);
+ ///
+ /// Performs a blocking read on the socket until bytes are received.
+ ///
+ /// The number of bytes to read.
+ /// The buffer to read to.
+ /// The socket is closed.
+ /// The read has timed-out.
+ private void SocketRead(int length, ref byte[] buffer)
+ {
+ SocketRead(length, ref buffer, ConnectionInfo.Timeout);
+ }
- partial void SocketReadLine(ref string response);
+ ///
+ /// Performs a blocking read on the socket until bytes are received.
+ ///
+ /// The number of bytes to read.
+ /// The buffer to read to.
+ /// A that represents the time to wait until bytes a read.
+ /// The socket is closed.
+ /// The read has timed-out.
+ partial void SocketRead(int length, ref byte[] buffer, TimeSpan timeout);
+
+ ///
+ /// Performs a blocking read on the socket until a line is read.
+ ///
+ /// The line read from the socket, or null when the remote server has shutdown and all data has been received.
+ /// A that represents the time to wait until a line is read.
+ /// The read has timed-out.
+ partial void SocketReadLine(ref string response, TimeSpan timeout);
partial void Log(string text);
///
/// Writes the specified data to the server.
///
- /// The data.
+ /// The data to write to the server.
+ /// The write has timed-out.
partial void SocketWrite(byte[] data);
///
@@ -1976,7 +2009,10 @@ namespace Renci.SshNet
while (true)
{
- this.SocketReadLine(ref response);
+ this.SocketReadLine(ref response, ConnectionInfo.Timeout);
+ if (response == null)
+ // server shut down socket
+ break;
if (statusCode == null)
{
@@ -1991,8 +2027,9 @@ namespace Renci.SshNet
throw new ProxyException(string.Format("HTTP: Status code {0}, \"{1}\"", httpStatusCode,
reasonPhrase));
}
- continue;
}
+
+ continue;
}
// continue on parsing message headers coming from the server
@@ -2019,6 +2056,9 @@ namespace Renci.SshNet
break;
}
}
+
+ if (statusCode == null)
+ throw new ProxyException("HTTP response does not contain status line.");
}
///