SshData.cs:

- Remove ZeroReaderIndex from SshData; offset should instead be specified when instance is created/loaded.
- LoadBytes is now private, and is always invoked when a message is loaded.
- Eliminate ResetReader().
SshDataStream.cs:
- Added overload taking buffer, offset and count.
KeyExchange*Message.cs:
- Remove ResetReader() calls.
Message.cs:
- Remove ZeroReaderIndex override.
SftpMessage.cs:
- Pass offset and count to Load overload to allow skipping byte representing the message type.
- Remove ZeroReaderIndex override.
SftpSession.cs:
- Avoid buffering when packet contains full SFTP response message.
Session.cs:
- Pass offset and count to Load overload to allow skipping offset bytes.
General:
- Temporarily add (too) verbose tracing.
This commit is contained in:
drieseng
2016-09-01 21:54:57 +02:00
parent bd2fa5d16f
commit 76a92cfb0a
14 changed files with 233 additions and 144 deletions
+5
View File
@@ -1,6 +1,7 @@
using System;
using System.Net.Sockets;
using System.Threading;
using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Transport;
@@ -221,6 +222,8 @@ namespace Renci.SshNet
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
public void Disconnect()
{
DiagnosticAbstraction.Log(string.Format("{0} Disconnecting client", DateTime.Now.Ticks));
CheckDisposed();
OnDisconnecting();
@@ -317,6 +320,8 @@ namespace Renci.SshNet
/// </summary>
public void Dispose()
{
DiagnosticAbstraction.Log(string.Format("{0} Disposing client", DateTime.Now.Ticks));
Dispose(true);
GC.SuppressFinalize(this);
}
+27 -50
View File
@@ -47,20 +47,7 @@ namespace Renci.SshNet.Common
private byte[] _loadedData;
private int _offset;
/// <summary>
/// Gets the index that represents zero in current data type.
/// </summary>
/// <value>
/// The index of the zero reader.
/// </value>
protected virtual int ZeroReaderIndex
{
get
{
return 0;
}
}
private int _count;
/// <summary>
/// Gets the size of the message in bytes.
@@ -99,30 +86,41 @@ namespace Renci.SshNet.Common
internal T OfType<T>() where T : SshData, new()
{
var result = new T();
result.LoadBytes(_loadedData, _offset);
result.LoadData();
result.Load(_loadedData, _offset, _count);
return result;
}
/// <summary>
/// Loads data from specified bytes.
/// </summary>
/// <param name="value">Bytes array.</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <c>null</c>.</exception>
public void Load(byte[] value)
/// <param name="data">Bytes array.</param>
/// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception>
public void Load(byte[] data)
{
Load(value, 0);
if (data == null)
throw new ArgumentNullException("data");
LoadInternal(data, 0, data.Length);
}
/// <summary>
/// Loads data from the specified buffer.
/// </summary>
/// <param name="value">Bytes array.</param>
/// <param name="offset">The zero-based offset in <paramref name="value"/> at which to begin reading SSH data.</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <c>null</c>.</exception>
public void Load(byte[] value, int offset)
/// <param name="data">Bytes array.</param>
/// <param name="offset">The zero-based offset in <paramref name="data"/> at which to begin reading SSH data.</param>
/// <param name="count">The number of bytes to load.</param>
/// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception>
public void Load(byte[] data, int offset, int count)
{
LoadBytes(value, offset);
if (data == null)
throw new ArgumentNullException("data");
LoadInternal(data, offset, count);
}
private void LoadInternal(byte[] value, int offset, int count)
{
LoadBytes(value, offset, count);
LoadData();
}
@@ -136,40 +134,19 @@ namespace Renci.SshNet.Common
/// </summary>
protected abstract void SaveData();
/// <summary>
/// Loads data bytes into internal buffer.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <exception cref="ArgumentNullException"><paramref name="bytes"/> is <c>null</c>.</exception>
protected void LoadBytes(byte[] bytes)
{
LoadBytes(bytes, 0);
}
/// <summary>
/// Loads data bytes into internal buffer.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The zero-based offset in <paramref name="bytes"/> at which to begin reading SSH data.</param>
/// <exception cref="ArgumentNullException"><paramref name="bytes"/> is <c>null</c>.</exception>
protected void LoadBytes(byte[] bytes, int offset)
/// <param name="count">The number of bytes to load.</param>
private void LoadBytes(byte[] bytes, int offset, int count)
{
if (bytes == null)
throw new ArgumentNullException("bytes");
_loadedData = bytes;
_offset = offset;
_count = count;
_stream = new SshDataStream(bytes);
ResetReader();
}
/// <summary>
/// Resets internal data reader index.
/// </summary>
protected void ResetReader()
{
_stream.Position = ZeroReaderIndex + _offset;
_stream = new SshDataStream(bytes, _offset, count);
}
/// <summary>
+12
View File
@@ -30,6 +30,18 @@ namespace Renci.SshNet.Common
{
}
/// <summary>
/// Initializes a new non-resizable instance of the <see cref="SshDataStream"/> class based on the specified byte array.
/// </summary>
/// <param name="buffer">The array of unsigned bytes from which to create the current stream.</param>
/// <param name="offset">The zero-based offset in <paramref name="buffer"/> at which to begin reading SSH data.</param>
/// <param name="count">The number of bytes to load.</param>
/// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <c>null</c>.</exception>
public SshDataStream(byte[] buffer, int offset, int count)
: base(buffer, offset, count)
{
}
/// <summary>
/// Gets a value indicating whether all data from the SSH data stream has been read.
/// </summary>
-14
View File
@@ -12,20 +12,6 @@ namespace Renci.SshNet.Messages
/// </summary>
public abstract class Message : SshData
{
/// <summary>
/// Gets the index that represents zero in current data type.
/// </summary>
/// <value>
/// The index of the zero reader.
/// </value>
protected override int ZeroReaderIndex
{
get
{
return 1;
}
}
/// <summary>
/// Gets the size of the message in bytes.
/// </summary>
@@ -49,7 +49,6 @@ namespace Renci.SshNet.Messages.Transport
/// </summary>
protected override void LoadData()
{
ResetReader();
_eBytes = ReadBinary();
}
@@ -56,7 +56,6 @@ namespace Renci.SshNet.Messages.Transport
/// </summary>
protected override void LoadData()
{
ResetReader();
HostKey = ReadBinary();
_fBytes = ReadBinary();
Signature = ReadBinary();
@@ -51,7 +51,6 @@ namespace Renci.SshNet.Messages.Transport
/// </summary>
protected override void LoadData()
{
ResetReader();
QC = ReadBinary();
}
@@ -49,7 +49,6 @@
/// </summary>
protected override void LoadData()
{
ResetReader();
KS = ReadBinary();
QS = ReadBinary();
Signature = ReadBinary();
@@ -140,8 +140,6 @@ namespace Renci.SshNet.Messages.Transport
/// </summary>
protected override void LoadData()
{
ResetReader();
Cookie = ReadBytes(16);
KeyExchangeAlgorithms = ReadNamesList();
ServerHostKeyAlgorithms = ReadNamesList();
+1 -1
View File
@@ -391,7 +391,7 @@ namespace Renci.SshNet
{
public SshDataReader(byte[] data)
{
LoadBytes(data);
Load(data);
}
public new uint ReadUInt32()
+8 -2
View File
@@ -1,4 +1,6 @@
using System.Net.Sockets;
using System;
using System.Net.Sockets;
using Renci.SshNet.Abstractions;
namespace Renci.SshNet
{
@@ -70,10 +72,12 @@ namespace Renci.SshNet
// actually received
lock (_socketReadLock)
{
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Checking socket", ToHex(SessionId), DateTime.Now.Ticks));
// reset waithandle, as we're only interested in reads that take
// place between Poll and the Available check
_bytesReadFromSocket.Reset();
var connectionClosedOrDataAvailable = _socket.Poll(1000, SelectMode.SelectRead);
var connectionClosedOrDataAvailable = _socket.Poll(100, SelectMode.SelectRead);
isConnected = !(connectionClosedOrDataAvailable && _socket.Available == 0);
if (!isConnected)
{
@@ -83,6 +87,8 @@ namespace Renci.SshNet
// shortly after
isConnected = _bytesReadFromSocket.WaitOne(500);
}
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Checked socket", ToHex(SessionId), DateTime.Now.Ticks));
}
}
#endif // FEATURE_SOCKET_POLL
+41 -21
View File
@@ -657,7 +657,7 @@ namespace Renci.SshNet
/// </remarks>
public void Disconnect()
{
DiagnosticAbstraction.Log(string.Format("[{0}] Disconnecting session", ToHex(SessionId)));
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Disconnecting session", ToHex(SessionId), DateTime.Now.Ticks));
Disconnect(DisconnectReason.ByApplication, "Connection terminated by the client.");
@@ -892,12 +892,16 @@ namespace Renci.SshNet
// Read first block - which starts with the packet length
var firstBlock = Read(blockSize);
#if DEBUG_GERT
DiagnosticAbstraction.Log(string.Format("[{0}] FirstBlock [{1}]: {2}", ToHex(SessionId), blockSize, ToHex(firstBlock)));
#endif // DEBUG_GERT
if (_serverCipher != null)
{
firstBlock = _serverCipher.Decrypt(firstBlock);
#if DEBUG_GERT
DiagnosticAbstraction.Log(string.Format("[{0}] FirstBlock decrypted [{1}]: {2}", ToHex(SessionId), firstBlock.Length, ToHex(firstBlock)));
#endif // DEBUG_GERT
}
var packetLength = (uint)(firstBlock[0] << 24 | firstBlock[1] << 16 | firstBlock[2] << 8 | firstBlock[3]);
@@ -927,14 +931,17 @@ namespace Renci.SshNet
{
var nextBlocks = Read(bytesToRead);
#if DEBUG_GERT
DiagnosticAbstraction.Log(string.Format("[{0}] NextBlocks [{1}]: {2}", ToHex(SessionId), bytesToRead, ToHex(nextBlocks)));
#endif // DEBUG_GERT
if (serverHash != null)
{
Buffer.BlockCopy(nextBlocks, nextBlocks.Length - serverHash.Length, serverHash, 0, serverHash.Length);
nextBlocks = nextBlocks.Take(nextBlocks.Length - serverHash.Length);
#if DEBUG_GERT
DiagnosticAbstraction.Log(string.Format("[{0}] ServerHash [{1}]: {2}", ToHex(SessionId), serverHash.Length, ToHex(serverHash)));
#endif // DEBUG_GERT
}
if (nextBlocks.Length > 0)
@@ -942,7 +949,9 @@ namespace Renci.SshNet
if (_serverCipher != null)
{
nextBlocks = _serverCipher.Decrypt(nextBlocks);
#if DEBUG_GERT
DiagnosticAbstraction.Log(string.Format("[{0}] NextBlocks decrypted [{1}]: {2}", ToHex(SessionId), nextBlocks.Length, ToHex(nextBlocks)));
#endif // DEBUG_GERT
}
nextBlocks.CopyTo(data, blockSize + inboundPacketSequenceLength);
@@ -970,13 +979,17 @@ namespace Renci.SshNet
// data now only contains the decompressed payload, and as such the offset is reset to zero
messagePayloadOffset = 0;
// the length of the payload is now the complete decompressed content
messagePayloadLength = data.Length;
}
#if DEBUG_GERT
DiagnosticAbstraction.Log(string.Format("[{0}] Message info (Sequence:{1},MessagePayloadLength:{2})", ToHex(SessionId), _inboundPacketSequence, messagePayloadLength));
#endif // DEBUG_GERT
_inboundPacketSequence++;
return LoadMessage(data, messagePayloadOffset);
return LoadMessage(data, messagePayloadOffset, messagePayloadLength);
}
private void SendDisconnect(DisconnectReason reasonCode, string message)
@@ -1006,7 +1019,7 @@ namespace Renci.SshNet
OnMessageReceived(message);
}
#region Handle transport messages
#region Handle transport messages
/// <summary>
/// Invoked via reflection.
@@ -1076,9 +1089,9 @@ namespace Renci.SshNet
OnNewKeysReceived(message);
}
#endregion
#endregion
#region Handle User Authentication messages
#region Handle User Authentication messages
/// <summary>
/// Invoked via reflection.
@@ -1112,9 +1125,9 @@ namespace Renci.SshNet
OnUserAuthenticationBannerReceived(message);
}
#endregion
#endregion
#region Handle connection messages
#region Handle connection messages
/// <summary>
/// Invoked via reflection.
@@ -1228,9 +1241,9 @@ namespace Renci.SshNet
OnChannelFailureReceived(message);
}
#endregion
#endregion
#region Handle received message events
#region Handle received message events
/// <summary>
/// Called when <see cref="DisconnectMessage"/> received.
@@ -1238,7 +1251,7 @@ namespace Renci.SshNet
/// <param name="message"><see cref="DisconnectMessage"/> message.</param>
protected virtual void OnDisconnectReceived(DisconnectMessage message)
{
DiagnosticAbstraction.Log(string.Format("[{0}] Disconnect received: {1} {2}", ToHex(SessionId), message.ReasonCode, message.Description));
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Disconnect received: {2} {3}", ToHex(SessionId), DateTime.Now.Ticks, message.ReasonCode, message.Description));
_exception = new SshConnectionException(string.Format(CultureInfo.InvariantCulture, "The connection was closed by the server: {0} ({1}).", message.Description, message.ReasonCode), message.ReasonCode);
_exceptionWaitHandle.Set();
@@ -1606,7 +1619,7 @@ namespace Renci.SshNet
handlers(this, new MessageEventArgs<Message>(message));
}
#endregion
#endregion
private void KeyExchange_HostKeyReceived(object sender, HostKeyEventArgs e)
{
@@ -1631,7 +1644,7 @@ namespace Renci.SshNet
return buffer;
}
#region Message loading functions
#region Message loading functions
/// <summary>
/// Registers SSH message with the session.
@@ -1656,19 +1669,22 @@ namespace Renci.SshNet
/// </summary>
/// <param name="data">An array of bytes from which to construct the message.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="data"/> at which to begin reading.</param>
/// <param name="count">The number of bytes to load.</param>
/// <returns>
/// A message constructed from <paramref name="data"/>.
/// </returns>
/// <exception cref="SshException">The type of the message is not supported.</exception>
private Message LoadMessage(byte[] data, int offset)
private Message LoadMessage(byte[] data, int offset, int count)
{
var messageType = data[offset];
var message = _sshMessageFactory.Create(messageType);
#if DEBUG_GERT
DiagnosticAbstraction.Log(string.Format("[{0}] Loading message with offset '{1}': {2}", ToHex(SessionId), offset, ToHex(data, 0)));
#endif // DEBUG_GERT
message.Load(data, offset);
message.Load(data, offset + 1, count - 1);
DiagnosticAbstraction.Log(string.Format("[{0}] ReceiveMessage from server: '{1}': '{2}'.", ToHex(SessionId), message.GetType().Name, message));
@@ -1698,7 +1714,7 @@ namespace Renci.SshNet
return ToHex(bytes, 0);
}
#endregion
#endregion
/// <summary>
/// Gets a value indicating whether the socket is connected.
@@ -1824,8 +1840,11 @@ namespace Renci.SshNet
_socket.Shutdown(SocketShutdown.Send);
SocketAbstraction.ClearReadBuffer(_socket);
}
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Disposing socket", ToHex(SessionId), DateTime.Now.Ticks));
_socket.Dispose();
_socket = null;
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Disposed socket", ToHex(SessionId), DateTime.Now.Ticks));
}
}
}
@@ -2178,7 +2197,7 @@ namespace Renci.SshNet
return;
}
DiagnosticAbstraction.Log(string.Format("[{0}] Raised exception: {1}", ToHex(SessionId), exp));
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Raised exception: {2}", ToHex(SessionId), DateTime.Now.Ticks, exp));
_exception = exp;
@@ -2190,6 +2209,7 @@ namespace Renci.SshNet
if (connectionException != null)
{
DiagnosticAbstraction.Log(string.Format("[{0}] {1} Disconnecting after exception: {2}", ToHex(SessionId), DateTime.Now.Ticks, exp));
Disconnect(connectionException.DisconnectReason, exp.ToString());
}
}
@@ -2215,7 +2235,7 @@ namespace Renci.SshNet
_keyExchangeInProgress = false;
}
#region IDisposable implementation
#region IDisposable implementation
private bool _disposed;
@@ -2313,9 +2333,9 @@ namespace Renci.SshNet
Dispose(false);
}
#endregion IDisposable implementation
#endregion IDisposable implementation
#region ISession implementation
#region ISession implementation
/// <summary>
/// Gets or sets the connection info.
@@ -2400,6 +2420,6 @@ namespace Renci.SshNet
return TrySendMessage(message);
}
#endregion ISession implementation
#endregion ISession implementation
}
}
+11 -16
View File
@@ -9,21 +9,11 @@ namespace Renci.SshNet.Sftp
{
internal abstract class SftpMessage : SshData
{
public static SftpMessage Load(uint protocolVersion, byte[] data, Encoding encoding)
public static SftpMessage Load(uint protocolVersion, byte[] data, int offset, int count, Encoding encoding)
{
var messageType = (SftpMessageTypes) data[4]; // skip packet length bytes
var messageType = (SftpMessageTypes) data[offset];
return Load(protocolVersion, data, messageType, encoding);
}
protected override int ZeroReaderIndex
{
get
{
// 4 bytes for the length of the SFTP data
// 1 byte for the SFTP message type
return 5;
}
return Load(protocolVersion, messageType, data, offset + 1, count - 1, encoding);
}
/// <summary>
@@ -34,7 +24,12 @@ namespace Renci.SshNet.Sftp
/// </value>
protected override int BufferCapacity
{
get { return ZeroReaderIndex; }
get
{
// 4 bytes for the length of the SFTP data
// 1 byte for the SFTP message type
return 5;
}
}
public abstract SftpMessageTypes SftpMessageType { get; }
@@ -84,7 +79,7 @@ namespace Renci.SshNet.Sftp
return SftpFileAttributes.FromBytes(DataStream);
}
private static SftpMessage Load(uint protocolVersion, byte[] data, SftpMessageTypes messageType, Encoding encoding)
private static SftpMessage Load(uint protocolVersion, SftpMessageTypes messageType, byte[] data, int offset, int count, Encoding encoding)
{
SftpMessage message;
@@ -115,7 +110,7 @@ namespace Renci.SshNet.Sftp
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Message type '{0}' is not supported.", messageType));
}
message.Load(data);
message.Load(data, offset, data.Length - offset);
return message;
}
+128 -34
View File
@@ -163,55 +163,149 @@ namespace Renci.SshNet.Sftp
protected override void OnDataReceived(byte[] data)
{
// Add channel data to internal data holder
_data.AddRange(data);
const int packetLengthByteCount = 4;
const int sftpMessageTypeByteCount = 1;
const int minimumChannelDataLength = packetLengthByteCount + sftpMessageTypeByteCount;
while (_data.Count > 4 + 1)
var offset = 0;
var count = data.Length;
// improve performance and reduce GC pressure by not buffering channel data if the received
// chunk contains the complete packet data.
//
// for this, the buffer should be empty and the chunk should contain at least the packet length
// and the type of the SFTP message
if (_data.Count == 0)
{
// Extract packet length
var packetLength = (_data[0] << 24 | _data[1] << 16 | _data[2] << 8 | _data[3]);
// Check if complete packet data is available
if (_data.Count < packetLength + 4)
while (count >= minimumChannelDataLength)
{
// Wait for complete message to arrive first
break;
}
// extract packet length
var packetDataLength = data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 |
data[offset + 3];
var packetLengthIncludingBytesForLength = packetLength + 4;
var packetTotalLength = packetDataLength + packetLengthByteCount;
// Create buffer to hold packet data and 4 bytes for packet length
var packetData = new byte[packetLengthIncludingBytesForLength];
// copy packet data and bytes for length to array
_data.CopyTo(0, packetData, 0, packetLengthIncludingBytesForLength);
// Remove loaded data and bytes for length from _data holder
_data.RemoveRange(0, packetLengthIncludingBytesForLength);
// Load SFTP Message and handle it
var response = SftpMessage.Load(ProtocolVersion, packetData, Encoding);
try
{
var versionResponse = response as SftpVersionResponse;
if (versionResponse != null)
// check if complete packet data (or more) is available
if (count >= packetTotalLength)
{
ProtocolVersion = versionResponse.Version;
_supportedExtensions = versionResponse.Extentions;
// load and process SFTP message
if (!TryLoadSftpMessage(data, offset + packetLengthByteCount, packetDataLength))
{
return;
}
_sftpVersionConfirmed.Set();
// remove processed bytes from the number of bytes to process as the channel
// data we received may contain (part of) another message
count -= packetTotalLength;
// move offset beyond bytes we just processed
offset += packetTotalLength;
}
else
{
HandleResponse(response as SftpResponse);
// we don't have a complete message
break;
}
}
catch (Exception exp)
// check if there is channel data left to process or buffer
if (count == 0)
{
RaiseError(exp);
return;
}
// check if we processed part of the channel data we received
if (offset > 0)
{
// add (remaining) channel data to internal data holder
var remainingChannelData = new byte[count];
Buffer.BlockCopy(data, offset, remainingChannelData, 0, count);
_data.AddRange(remainingChannelData);
}
else
{
// add (remaining) channel data to internal data holder
_data.AddRange(data);
}
// skip further processing as we'll need a new chunk to complete the message
return;
}
// add (remaining) channel data to internal data holder
_data.AddRange(data);
while (_data.Count >= minimumChannelDataLength)
{
// extract packet length
var packetDataLength = _data[0] << 24 | _data[1] << 16 | _data[2] << 8 | _data[3];
#if DEBUG_GERT
if (packetDataLength > 32 * 1024)
Console.WriteLine("BIG PACKAGE: " + packetDataLength + " | " + data.Length);
#endif // DEBUG_GERT
var packetTotalLength = packetDataLength + packetLengthByteCount;
// check if complete packet data is available
if (_data.Count < packetTotalLength)
{
// wait for complete message to arrive first
break;
}
// create buffer to hold packet data
var packetData = new byte[packetDataLength];
// copy packet data and bytes for length to array
_data.CopyTo(packetLengthByteCount, packetData, 0, packetDataLength);
// remove loaded data and bytes for length from _data holder
if (_data.Count == packetTotalLength)
{
// the only buffered data is the data we're processing
_data.Clear();
}
else
{
// remove only the data we're processing
_data.RemoveRange(0, packetTotalLength);
}
// load and process SFTP message
if (!TryLoadSftpMessage(packetData, 0, packetDataLength))
{
break;
}
}
}
private bool TryLoadSftpMessage(byte[] packetData, int offset, int count)
{
// Load SFTP Message and handle it
var response = SftpMessage.Load(ProtocolVersion, packetData, offset, count, Encoding);
try
{
var versionResponse = response as SftpVersionResponse;
if (versionResponse != null)
{
ProtocolVersion = versionResponse.Version;
_supportedExtensions = versionResponse.Extentions;
_sftpVersionConfirmed.Set();
}
else
{
HandleResponse(response as SftpResponse);
}
return true;
}
catch (Exception exp)
{
RaiseError(exp);
return false;
}
}