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;
namespace Renci.SshNet
{
public partial class Session
{
private const byte Null = 0x00;
private const byte CarriageReturn = 0x0d;
private const byte LineFeed = 0x0a;
private static readonly TimeSpan Infinite = new TimeSpan(0, 0, 0, 0, -1);
private readonly AutoResetEvent _connectEvent = new AutoResetEvent(false);
private readonly AutoResetEvent _sendEvent = new AutoResetEvent(false);
private readonly AutoResetEvent _receiveEvent = new AutoResetEvent(false);
///
/// Gets a value indicating whether the socket is connected.
///
/// true if the socket is connected; otherwise, false
partial void IsSocketConnected(ref bool 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 timeout = ConnectionInfo.Timeout;
var ipAddress = host.GetIPAddress();
var ep = new IPEndPoint(ipAddress, port);
_socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
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);
}
///
/// Closes the socket.
///
///
/// This method will wait up to 10 seconds to send any remaining data.
///
partial void SocketDisconnect()
{
_socket.Close(10);
}
///
/// 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();
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 = 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 (args.SocketError != SocketError.Success)
throw new SocketException((int) args.SocketError);
if (args.BytesTransferred == 0)
// the remote server shut down the socket
break;
buffer.Add(data[0]);
}
while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == LineFeed || buffer[buffer.Count - 1] == Null)));
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] == CarriageReturn)
// strip trailing CRLF
response = encoding.GetString(buffer.ToArray(), 0, buffer.Count - 2);
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);
}
///
/// 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.
/// The read failed.
partial void SocketRead(int length, ref byte[] buffer)
{
var timeout = Infinite;
var totalBytesReceived = 0; // how many bytes are already received
do
{
var args = CreateSocketAsyncEventArgs(_receiveEvent, buffer, totalBytesReceived,
length - totalBytesReceived);
if (_socket.ReceiveAsync(args))
{
if (!_receiveEvent.WaitOne(timeout))
// currently we wait indefinitely, so this exception will never be thrown
// but let's leave this here anyway as we may revisit this later
throw new SshOperationTimeoutException(string.Format(CultureInfo.InvariantCulture,
"Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds));
}
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:
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);
}
} 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)
{
var timeout = ConnectionInfo.Timeout;
var totalBytesSent = 0; // how many bytes are already sent
var totalBytesToSend = data.Length;
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));
}
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)
{
ThreadPool.QueueUserWorkItem(o => action());
}
partial void InternalRegisterMessage(string messageName)
{
lock (_messagesMetadata)
{
foreach (var item in from m in _messagesMetadata where m.Name == messageName select m)
{
item.Enabled = true;
item.Activated = true;
}
}
}
partial void InternalUnRegisterMessage(string messageName)
{
lock (_messagesMetadata)
{
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;
}
}
}