فهرست منبع

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.

drieseng 9 سال پیش
والد
کامیت
76a92cfb0a

+ 5 - 0
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
         /// <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
src/Renci.SshNet/Common/SshData.cs

@@ -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)
+        {
+            if (data == null)
+                throw new ArgumentNullException("data");
+
+            LoadInternal(data, offset, count);
+        }
+
+        private void LoadInternal(byte[] value, int offset, int count)
         {
-            LoadBytes(value, offset);
+            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 - 0
src/Renci.SshNet/Common/SshDataStream.cs

@@ -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>

+ 0 - 14
src/Renci.SshNet/Messages/Message.cs

@@ -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>

+ 0 - 1
src/Renci.SshNet/Messages/Transport/KeyExchangeDhInitMessage.cs

@@ -49,7 +49,6 @@ namespace Renci.SshNet.Messages.Transport
         /// </summary>
         protected override void LoadData()
         {
-            ResetReader();
             _eBytes = ReadBinary();
         }
 

+ 0 - 1
src/Renci.SshNet/Messages/Transport/KeyExchangeDhReplyMessage.cs

@@ -56,7 +56,6 @@ namespace Renci.SshNet.Messages.Transport
         /// </summary>
         protected override void LoadData()
         {
-            ResetReader();
             HostKey = ReadBinary();
             _fBytes = ReadBinary();
             Signature = ReadBinary();

+ 0 - 1
src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhInitMessage.cs

@@ -51,7 +51,6 @@ namespace Renci.SshNet.Messages.Transport
         /// </summary>
         protected override void LoadData()
         {
-            ResetReader();
             QC = ReadBinary();
         }
 

+ 0 - 1
src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs

@@ -49,7 +49,6 @@
         /// </summary>
         protected override void LoadData()
         {
-            ResetReader();
             KS = ReadBinary();
             QS = ReadBinary();
             Signature = ReadBinary();

+ 0 - 2
src/Renci.SshNet/Messages/Transport/KeyExchangeInitMessage.cs

@@ -140,8 +140,6 @@ namespace Renci.SshNet.Messages.Transport
         /// </summary>
         protected override void LoadData()
         {
-            ResetReader();
-
             Cookie = ReadBytes(16);
             KeyExchangeAlgorithms = ReadNamesList();
             ServerHostKeyAlgorithms = ReadNamesList();

+ 1 - 1
src/Renci.SshNet/PrivateKeyFile.cs

@@ -391,7 +391,7 @@ namespace Renci.SshNet
         {
             public SshDataReader(byte[] data)
             {
-                LoadBytes(data);
+                Load(data);
             }
 
             public new uint ReadUInt32()

+ 8 - 2
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

+ 41 - 21
src/Renci.SshNet/Session.cs

@@ -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
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);
         }
 
         /// <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;
         }

+ 125 - 31
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
+            const int packetLengthByteCount = 4;
+            const int sftpMessageTypeByteCount = 1;
+            const int minimumChannelDataLength = packetLengthByteCount + sftpMessageTypeByteCount;
+
+            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)
+            {
+                while (count >= minimumChannelDataLength)
+                {
+                    // extract packet length
+                    var packetDataLength = data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 |
+                                           data[offset + 3];
+
+                    var packetTotalLength = packetDataLength + packetLengthByteCount;
+
+                    // check if complete packet data (or more) is available
+                    if (count >= packetTotalLength)
+                    {
+                        // load and process SFTP message
+                        if (!TryLoadSftpMessage(data, offset + packetLengthByteCount, packetDataLength))
+                        {
+                            return;
+                        }
+
+                        // 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
+                    {
+                        // we don't have a complete message
+                        break;
+                    }
+                }
+
+                // check if there is channel data left to process or buffer
+                if (count == 0)
+                {
+                    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 > 4 + 1)
+            while (_data.Count >= minimumChannelDataLength)
             {
-                //  Extract packet length
-                var packetLength = (_data[0] << 24 | _data[1] << 16 | _data[2] << 8 | _data[3]);
+                // 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
 
-                //  Check if complete packet data is available
-                if (_data.Count < packetLength + 4)
+                var packetTotalLength = packetDataLength + packetLengthByteCount;
+
+                // check if complete packet data is available
+                if (_data.Count < packetTotalLength)
                 {
-                    //  Wait for complete message to arrive first
+                    // wait for complete message to arrive first
                     break;
                 }
 
-                var packetLengthIncludingBytesForLength = packetLength + 4;
-
-                //  Create buffer to hold packet data and 4 bytes for packet length
-                var packetData = new byte[packetLengthIncludingBytesForLength];
+                // create buffer to hold packet data
+                var packetData = new byte[packetDataLength];
 
                 // copy packet data and bytes for length to array
-                _data.CopyTo(0, packetData, 0, packetLengthIncludingBytesForLength);
+                _data.CopyTo(packetLengthByteCount, packetData, 0, packetDataLength);
 
-                //  Remove loaded data and bytes for length from _data holder
-                _data.RemoveRange(0, packetLengthIncludingBytesForLength);
+                // 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 SFTP Message and handle it
-                var response = SftpMessage.Load(ProtocolVersion, packetData, Encoding);
+                // load and process SFTP message
+                if (!TryLoadSftpMessage(packetData, 0, packetDataLength))
+                {
+                    break;
+                }
+            }
+        }
 
-                try
+        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)
                 {
-                    var versionResponse = response as SftpVersionResponse;
-                    if (versionResponse != null)
-                    {
-                        ProtocolVersion = versionResponse.Version;
-                        _supportedExtensions = versionResponse.Extentions;
+                    ProtocolVersion = versionResponse.Version;
+                    _supportedExtensions = versionResponse.Extentions;
 
-                        _sftpVersionConfirmed.Set();
-                    }
-                    else
-                    {
-                        HandleResponse(response as SftpResponse);
-                    }
+                    _sftpVersionConfirmed.Set();
                 }
-                catch (Exception exp)
+                else
                 {
-                    RaiseError(exp);
-                    break;
+                    HandleResponse(response as SftpResponse);
                 }
+
+                return true;
+            }
+            catch (Exception exp)
+            {
+                RaiseError(exp);
+                return false;
             }
         }