From 76a92cfb0acae1b52f2b8525bccebcbd5a69f87a Mon Sep 17 00:00:00 2001 From: drieseng Date: Thu, 1 Sep 2016 21:54:57 +0200 Subject: [PATCH] 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. --- src/Renci.SshNet/BaseClient.cs | 5 + src/Renci.SshNet/Common/SshData.cs | 77 +++------ src/Renci.SshNet/Common/SshDataStream.cs | 12 ++ src/Renci.SshNet/Messages/Message.cs | 14 -- .../Transport/KeyExchangeDhInitMessage.cs | 1 - .../Transport/KeyExchangeDhReplyMessage.cs | 1 - .../Transport/KeyExchangeEcdhInitMessage.cs | 1 - .../Transport/KeyExchangeEcdhReplyMessage.cs | 1 - .../Transport/KeyExchangeInitMessage.cs | 2 - src/Renci.SshNet/PrivateKeyFile.cs | 2 +- src/Renci.SshNet/Session.NET.cs | 10 +- src/Renci.SshNet/Session.cs | 62 ++++--- src/Renci.SshNet/Sftp/SftpMessage.cs | 27 ++- src/Renci.SshNet/Sftp/SftpSession.cs | 162 ++++++++++++++---- 14 files changed, 233 insertions(+), 144 deletions(-) diff --git a/src/Renci.SshNet/BaseClient.cs b/src/Renci.SshNet/BaseClient.cs index 2ed1e265..b89e2a71 100644 --- a/src/Renci.SshNet/BaseClient.cs +++ b/src/Renci.SshNet/BaseClient.cs @@ -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 /// The method was called after the client was disposed. public void Disconnect() { + DiagnosticAbstraction.Log(string.Format("{0} Disconnecting client", DateTime.Now.Ticks)); + CheckDisposed(); OnDisconnecting(); @@ -317,6 +320,8 @@ namespace Renci.SshNet /// public void Dispose() { + DiagnosticAbstraction.Log(string.Format("{0} Disposing client", DateTime.Now.Ticks)); + Dispose(true); GC.SuppressFinalize(this); } diff --git a/src/Renci.SshNet/Common/SshData.cs b/src/Renci.SshNet/Common/SshData.cs index f1383282..3016e0ee 100644 --- a/src/Renci.SshNet/Common/SshData.cs +++ b/src/Renci.SshNet/Common/SshData.cs @@ -47,20 +47,7 @@ namespace Renci.SshNet.Common private byte[] _loadedData; private int _offset; - - /// - /// Gets the index that represents zero in current data type. - /// - /// - /// The index of the zero reader. - /// - protected virtual int ZeroReaderIndex - { - get - { - return 0; - } - } + private int _count; /// /// Gets the size of the message in bytes. @@ -99,30 +86,41 @@ namespace Renci.SshNet.Common internal T OfType() where T : SshData, new() { var result = new T(); - result.LoadBytes(_loadedData, _offset); - result.LoadData(); + result.Load(_loadedData, _offset, _count); return result; } /// /// Loads data from specified bytes. /// - /// Bytes array. - /// is null. - public void Load(byte[] value) + /// Bytes array. + /// is null. + public void Load(byte[] data) { - Load(value, 0); + if (data == null) + throw new ArgumentNullException("data"); + + LoadInternal(data, 0, data.Length); } /// /// Loads data from the specified buffer. /// - /// Bytes array. - /// The zero-based offset in at which to begin reading SSH data. - /// is null. - public void Load(byte[] value, int offset) + /// Bytes array. + /// The zero-based offset in at which to begin reading SSH data. + /// The number of bytes to load. + /// is null. + 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 /// protected abstract void SaveData(); - /// - /// Loads data bytes into internal buffer. - /// - /// The bytes. - /// is null. - protected void LoadBytes(byte[] bytes) - { - LoadBytes(bytes, 0); - } - /// /// Loads data bytes into internal buffer. /// /// The bytes. /// The zero-based offset in at which to begin reading SSH data. - /// is null. - protected void LoadBytes(byte[] bytes, int offset) + /// The number of bytes to load. + 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(); - } - - /// - /// Resets internal data reader index. - /// - protected void ResetReader() - { - _stream.Position = ZeroReaderIndex + _offset; + _stream = new SshDataStream(bytes, _offset, count); } /// diff --git a/src/Renci.SshNet/Common/SshDataStream.cs b/src/Renci.SshNet/Common/SshDataStream.cs index 36ee3f8f..408d8704 100644 --- a/src/Renci.SshNet/Common/SshDataStream.cs +++ b/src/Renci.SshNet/Common/SshDataStream.cs @@ -30,6 +30,18 @@ namespace Renci.SshNet.Common { } + /// + /// Initializes a new non-resizable instance of the class based on the specified byte array. + /// + /// The array of unsigned bytes from which to create the current stream. + /// The zero-based offset in at which to begin reading SSH data. + /// The number of bytes to load. + /// is null. + public SshDataStream(byte[] buffer, int offset, int count) + : base(buffer, offset, count) + { + } + /// /// Gets a value indicating whether all data from the SSH data stream has been read. /// diff --git a/src/Renci.SshNet/Messages/Message.cs b/src/Renci.SshNet/Messages/Message.cs index b438d0e9..1107ba1a 100644 --- a/src/Renci.SshNet/Messages/Message.cs +++ b/src/Renci.SshNet/Messages/Message.cs @@ -12,20 +12,6 @@ namespace Renci.SshNet.Messages /// public abstract class Message : SshData { - /// - /// Gets the index that represents zero in current data type. - /// - /// - /// The index of the zero reader. - /// - protected override int ZeroReaderIndex - { - get - { - return 1; - } - } - /// /// Gets the size of the message in bytes. /// diff --git a/src/Renci.SshNet/Messages/Transport/KeyExchangeDhInitMessage.cs b/src/Renci.SshNet/Messages/Transport/KeyExchangeDhInitMessage.cs index bf6dd21d..f15c1d30 100644 --- a/src/Renci.SshNet/Messages/Transport/KeyExchangeDhInitMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/KeyExchangeDhInitMessage.cs @@ -49,7 +49,6 @@ namespace Renci.SshNet.Messages.Transport /// protected override void LoadData() { - ResetReader(); _eBytes = ReadBinary(); } diff --git a/src/Renci.SshNet/Messages/Transport/KeyExchangeDhReplyMessage.cs b/src/Renci.SshNet/Messages/Transport/KeyExchangeDhReplyMessage.cs index 76532e35..5979484c 100644 --- a/src/Renci.SshNet/Messages/Transport/KeyExchangeDhReplyMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/KeyExchangeDhReplyMessage.cs @@ -56,7 +56,6 @@ namespace Renci.SshNet.Messages.Transport /// protected override void LoadData() { - ResetReader(); HostKey = ReadBinary(); _fBytes = ReadBinary(); Signature = ReadBinary(); diff --git a/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhInitMessage.cs b/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhInitMessage.cs index d7b31981..d0222fb8 100644 --- a/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhInitMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhInitMessage.cs @@ -51,7 +51,6 @@ namespace Renci.SshNet.Messages.Transport /// protected override void LoadData() { - ResetReader(); QC = ReadBinary(); } diff --git a/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs b/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs index b8af8875..4815ad8a 100644 --- a/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs @@ -49,7 +49,6 @@ /// protected override void LoadData() { - ResetReader(); KS = ReadBinary(); QS = ReadBinary(); Signature = ReadBinary(); diff --git a/src/Renci.SshNet/Messages/Transport/KeyExchangeInitMessage.cs b/src/Renci.SshNet/Messages/Transport/KeyExchangeInitMessage.cs index 00e6f6d4..7c8e9338 100644 --- a/src/Renci.SshNet/Messages/Transport/KeyExchangeInitMessage.cs +++ b/src/Renci.SshNet/Messages/Transport/KeyExchangeInitMessage.cs @@ -140,8 +140,6 @@ namespace Renci.SshNet.Messages.Transport /// protected override void LoadData() { - ResetReader(); - Cookie = ReadBytes(16); KeyExchangeAlgorithms = ReadNamesList(); ServerHostKeyAlgorithms = ReadNamesList(); diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs index 5df9d927..f4467234 100644 --- a/src/Renci.SshNet/PrivateKeyFile.cs +++ b/src/Renci.SshNet/PrivateKeyFile.cs @@ -391,7 +391,7 @@ namespace Renci.SshNet { public SshDataReader(byte[] data) { - LoadBytes(data); + Load(data); } public new uint ReadUInt32() diff --git a/src/Renci.SshNet/Session.NET.cs b/src/Renci.SshNet/Session.NET.cs index 4c0c951d..93a9b334 100644 --- a/src/Renci.SshNet/Session.NET.cs +++ b/src/Renci.SshNet/Session.NET.cs @@ -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 diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 44296f0d..0e5657a6 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -657,7 +657,7 @@ namespace Renci.SshNet /// 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 /// /// Invoked via reflection. @@ -1076,9 +1089,9 @@ namespace Renci.SshNet OnNewKeysReceived(message); } - #endregion +#endregion - #region Handle User Authentication messages +#region Handle User Authentication messages /// /// Invoked via reflection. @@ -1112,9 +1125,9 @@ namespace Renci.SshNet OnUserAuthenticationBannerReceived(message); } - #endregion +#endregion - #region Handle connection messages +#region Handle connection messages /// /// Invoked via reflection. @@ -1228,9 +1241,9 @@ namespace Renci.SshNet OnChannelFailureReceived(message); } - #endregion +#endregion - #region Handle received message events +#region Handle received message events /// /// Called when received. @@ -1238,7 +1251,7 @@ namespace Renci.SshNet /// message. 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)); } - #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 /// /// Registers SSH message with the session. @@ -1656,19 +1669,22 @@ namespace Renci.SshNet /// /// An array of bytes from which to construct the message. /// The zero-based byte offset in at which to begin reading. + /// The number of bytes to load. /// /// A message constructed from . /// /// The type of the message is not supported. - 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 /// /// 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 /// /// Gets or sets the connection info. @@ -2400,6 +2420,6 @@ namespace Renci.SshNet return TrySendMessage(message); } - #endregion ISession implementation +#endregion ISession implementation } } \ No newline at end of file diff --git a/src/Renci.SshNet/Sftp/SftpMessage.cs b/src/Renci.SshNet/Sftp/SftpMessage.cs index 13e27213..90fbd0e4 100644 --- a/src/Renci.SshNet/Sftp/SftpMessage.cs +++ b/src/Renci.SshNet/Sftp/SftpMessage.cs @@ -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); } /// @@ -34,7 +24,12 @@ namespace Renci.SshNet.Sftp /// 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; } diff --git a/src/Renci.SshNet/Sftp/SftpSession.cs b/src/Renci.SshNet/Sftp/SftpSession.cs index c66ff71c..0fb775c6 100644 --- a/src/Renci.SshNet/Sftp/SftpSession.cs +++ b/src/Renci.SshNet/Sftp/SftpSession.cs @@ -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; } }