|
|
@@ -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;
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets a value indicating whether the socket is connected.
|
|
|
/// </summary>
|
|
|
- /// <value>
|
|
|
- /// <c>true</c> if the socket is connected; otherwise, <c>false</c>.
|
|
|
- /// </value>
|
|
|
+ /// <param name="isConnected"><c>true</c> if the socket is connected; otherwise, <c>false</c></param>
|
|
|
partial void IsSocketConnected(ref bool isConnected)
|
|
|
{
|
|
|
- isConnected = (this._socket != null && this._socket.Connected && this._isConnected);
|
|
|
+ isConnected = (_socket != null && _socket.Connected);
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Establishes a socket connection to the specified host and port.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="host">The host name of the server to connect to.</param>
|
|
|
+ /// <param name="port">The port to connect to.</param>
|
|
|
+ /// <exception cref="SshOperationTimeoutException">The connection failed to establish within the configured <see cref="Renci.SshNet.ConnectionInfo.Timeout"/>.</exception>
|
|
|
+ /// <exception cref="SocketException">An error occurred trying to establish the connection.</exception>
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Closes the socket.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// This method will wait up to <c>10</c> seconds to send any remaining data.
|
|
|
+ /// </remarks>
|
|
|
partial void SocketDisconnect()
|
|
|
{
|
|
|
- this._socket.Close(10000);
|
|
|
+ _socket.Close(10);
|
|
|
}
|
|
|
|
|
|
- partial void SocketReadLine(ref string response)
|
|
|
+ /// <summary>
|
|
|
+ /// Performs a blocking read on the socket until a line is read.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="response">The line read from the socket, or <c>null</c> when the remote server has shutdown and all data has been received.</param>
|
|
|
+ /// <param name="timeout">A <see cref="TimeSpan"/> that represents the time to wait until a line is read.</param>
|
|
|
+ /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
|
|
|
+ /// <exception cref="SocketException">An error occurred when trying to access the socket.</exception>
|
|
|
+ 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<byte>();
|
|
|
-
|
|
|
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)
|
|
|
- {
|
|
|
- response = string.Empty;
|
|
|
- }
|
|
|
- else if (buffer.Count == 0)
|
|
|
+ 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.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)
|
|
|
+ /// <summary>
|
|
|
+ /// Performs a blocking read on the socket until <paramref name="length"/> bytes are received.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="length">The number of bytes to read.</param>
|
|
|
+ /// <param name="buffer">The buffer to read to.</param>
|
|
|
+ /// <param name="timeout">A <see cref="TimeSpan"/> that represents the time to wait until <paramref name="length"/> bytes a read.</param>
|
|
|
+ /// <exception cref="SshConnectionException">The socket is closed.</exception>
|
|
|
+ /// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
|
|
|
+ /// <exception cref="SocketException">The read failed.</exception>
|
|
|
+ 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)
|
|
|
- {
|
|
|
- // socket buffer is probably empty, wait and try again
|
|
|
- Thread.Sleep(30);
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (args.SocketError != SocketError.Success)
|
|
|
+ var args = CreateSocketAsyncEventArgs(_receiveEvent, buffer, totalBytesReceived,
|
|
|
+ length - totalBytesReceived);
|
|
|
+ if (_socket.ReceiveAsync(args))
|
|
|
{
|
|
|
- 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);
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Writes the specified data to the server.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="data">The data to write to the server.</param>
|
|
|
+ /// <exception cref="SshOperationTimeoutException">The write has timed-out.</exception>
|
|
|
+ /// <exception cref="SocketException">The write failed.</exception>
|
|
|
partial void SocketWrite(byte[] data)
|
|
|
{
|
|
|
- if (this._isConnected)
|
|
|
- {
|
|
|
- var args = new SocketAsyncEventArgs();
|
|
|
- args.SetBuffer(data, 0, data.Length);
|
|
|
- args.UserToken = this._socket;
|
|
|
- args.RemoteEndPoint = this._socket.RemoteEndPoint;
|
|
|
- args.Completed += OnSend;
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
+ var timeout = ConnectionInfo.Timeout;
|
|
|
+ var totalBytesSent = 0; // how many bytes are already sent
|
|
|
+ var totalBytesToSend = data.Length;
|
|
|
|
|
|
- private void OnSend(object sender, SocketAsyncEventArgs e)
|
|
|
- {
|
|
|
- this._sendEvent.Set();
|
|
|
- }
|
|
|
+ do
|
|
|
+ {
|
|
|
+ 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));
|
|
|
+ }
|
|
|
|
|
|
- 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;
|
|
|
+ }
|
|
|
}
|
|
|
}
|