فهرست منبع

* Introduce ClientChannel and ServerChannel classes to distinguish between channel initiated by the client or the server.
* Increase maximum SSH packet size to 68536 bytes (64 KB + 3000 bytes).
* Increase local window size to 2 MB.
* Increase local maximum packet size from 32 KB to 64 KB.
* Modify Channel to save remote channel info (packet size, window size and channel number); distinguish between local and remote packet size.
* Expose channel in SubsystemSession to allow SftpSession to access local and remote maximum packet size for calculation of optimal read and write (buffer) length.
* Move verification of maximum data payload to send to Channel.SendMessage and verify data length against remote packet size.
* Limit size of payload to send to peer to RemotePacketSize in ChannelForwardedTcpip and ChannelDirectTcpip.
* Disable debug symbols when building in Release configuration.

Gert Driesen 11 سال پیش
والد
کامیت
2ddb3df0e0
27فایلهای تغییر یافته به همراه578 افزوده شده و 324 حذف شده
  1. 1 4
      Renci.SshClient/Renci.SshNet.Tests/Classes/ConnectionInfoTest.cs
  2. 7 7
      Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs
  3. 1 3
      Renci.SshClient/Renci.SshNet.Tests/Common/AsyncSocketListener.cs
  4. 2 4
      Renci.SshClient/Renci.SshNet.Tests/Common/HttpProxyStub.cs
  5. 135 137
      Renci.SshClient/Renci.SshNet/Channels/Channel.cs
  6. 3 3
      Renci.SshClient/Renci.SshNet/Channels/ChannelDirectTcpip.cs
  7. 1 1
      Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.NET40.cs
  8. 3 5
      Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.cs
  9. 2 2
      Renci.SshClient/Renci.SshNet/Channels/ChannelSession.cs
  10. 101 0
      Renci.SshClient/Renci.SshNet/Channels/ClientChannel.cs
  11. 60 0
      Renci.SshClient/Renci.SshNet/Channels/ServerChannel.cs
  12. 37 0
      Renci.SshClient/Renci.SshNet/Common/ChannelOpenConfirmedEventArgs.cs
  13. 1 1
      Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.NET.cs
  14. 1 1
      Renci.SshClient/Renci.SshNet/ForwardedPortLocal.NET.cs
  15. 6 3
      Renci.SshClient/Renci.SshNet/ForwardedPortRemote.cs
  16. 5 2
      Renci.SshClient/Renci.SshNet/Renci.SshNet.csproj
  17. 4 4
      Renci.SshClient/Renci.SshNet/ScpClient.NET.cs
  18. 2 2
      Renci.SshClient/Renci.SshNet/ScpClient.cs
  19. 2 1
      Renci.SshClient/Renci.SshNet/Session.NET.cs
  20. 28 15
      Renci.SshClient/Renci.SshNet/Session.cs
  21. 72 87
      Renci.SshClient/Renci.SshNet/Sftp/SftpFileStream.cs
  22. 49 0
      Renci.SshClient/Renci.SshNet/Sftp/SftpSession.cs
  23. 41 30
      Renci.SshClient/Renci.SshNet/SftpClient.cs
  24. 1 1
      Renci.SshClient/Renci.SshNet/Shell.cs
  25. 1 1
      Renci.SshClient/Renci.SshNet/ShellStream.cs
  26. 1 1
      Renci.SshClient/Renci.SshNet/SshCommand.cs
  27. 11 9
      Renci.SshClient/Renci.SshNet/SubsystemSession.cs

+ 1 - 4
Renci.SshClient/Renci.SshNet.Tests/Classes/ConnectionInfoTest.cs

@@ -174,10 +174,7 @@ namespace Renci.SshNet.Tests.Classes
             AuthenticationMethod[] authenticationMethods = null; // TODO: Initialize to an appropriate value
             ConnectionInfo target = new ConnectionInfo(host, username, authenticationMethods); // TODO: Initialize to an appropriate value
             Session session = null; // TODO: Initialize to an appropriate value
-            bool expected = false; // TODO: Initialize to an appropriate value
-            bool actual;
-            actual = target.Authenticate(session);
-            Assert.AreEqual(expected, actual);
+            target.Authenticate(session);
             Assert.Inconclusive("Verify the correctness of this test method.");
         }
 

+ 7 - 7
Renci.SshClient/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs

@@ -16,7 +16,7 @@ namespace Renci.SshNet.Tests.Classes
             var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
             var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
 
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
             {
                 proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
                 proxyStub.Start();
@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes
             var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
             var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
 
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "B"))
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
             {
                 proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
                 proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n"));
@@ -73,7 +73,7 @@ namespace Renci.SshNet.Tests.Classes
             var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
             var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
 
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "B"))
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
             {
                 proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n"));
                 proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Length: 13\r\n"));
@@ -105,7 +105,7 @@ namespace Renci.SshNet.Tests.Classes
             var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
             var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
 
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
             {
                 proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
                 proxyStub.Start();
@@ -132,7 +132,7 @@ namespace Renci.SshNet.Tests.Classes
             var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
             var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
 
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
             {
                 proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
                 proxyStub.Start();
@@ -161,7 +161,7 @@ namespace Renci.SshNet.Tests.Classes
             var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
             var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
 
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
             {
                 proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
                 proxyStub.Start();
@@ -189,7 +189,7 @@ namespace Renci.SshNet.Tests.Classes
             var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123);
             var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
 
-            using (var proxyStub = new HttpProxyStub(proxyEndPoint, "A"))
+            using (var proxyStub = new HttpProxyStub(proxyEndPoint))
             {
                 proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n"));
                 proxyStub.Start();

+ 1 - 3
Renci.SshClient/Renci.SshNet.Tests/Common/AsyncSocketListener.cs

@@ -8,7 +8,6 @@ namespace Renci.SshNet.Tests.Common
     public class AsyncSocketListener : IDisposable
     {
         private readonly IPEndPoint _endPoint;
-        private readonly string _id;
         private readonly ManualResetEvent _acceptCallbackDone;
         private Socket _listener;
         private Thread _receiveThread;
@@ -18,10 +17,9 @@ namespace Renci.SshNet.Tests.Common
 
         public event BytesReceivedHandler BytesReceived;
 
-        public AsyncSocketListener(IPEndPoint endPoint, string id)
+        public AsyncSocketListener(IPEndPoint endPoint)
         {
             _endPoint = endPoint;
-            _id = id;
             _acceptCallbackDone = new ManualResetEvent(false);
         }
 

+ 2 - 4
Renci.SshClient/Renci.SshNet.Tests/Common/HttpProxyStub.cs

@@ -9,15 +9,13 @@ namespace Renci.SshNet.Tests.Common
     public class HttpProxyStub : IDisposable
     {
         private readonly IPEndPoint _endPoint;
-        private readonly string _id;
         private AsyncSocketListener _listener;
         private HttpRequestParser _httpRequestParser;
         private readonly IList<byte[]> _responses;
 
-        public HttpProxyStub(IPEndPoint endPoint, string id)
+        public HttpProxyStub(IPEndPoint endPoint)
         {
             _endPoint = endPoint;
-            _id = id;
             _responses = new List<byte[]>();
         }
 
@@ -40,7 +38,7 @@ namespace Renci.SshNet.Tests.Common
         {
             _httpRequestParser = new HttpRequestParser();
 
-            _listener = new AsyncSocketListener(_endPoint, _id);
+            _listener = new AsyncSocketListener(_endPoint);
             _listener.BytesReceived += OnBytesReceived;
             _listener.Start();
         }

+ 135 - 137
Renci.SshClient/Renci.SshNet/Channels/Channel.cs

@@ -18,10 +18,23 @@ namespace Renci.SshNet.Channels
         private EventWaitHandle _disconnectedWaitHandle = new ManualResetEvent(false);
         private readonly object _serverWindowSizeLock = new object();
         private bool _closeMessageSent;
-        private uint _initialWindowSize = 0x100000;
-        private uint _maximumPacketSize = 0x8000;
+        private uint _initialWindowSize;
+        private uint? _remoteWindowSize;
+        private uint? _remoteChannelNumber;
+        private uint? _remotePacketSize;
         private Session _session;
 
+        /// <summary>
+        /// Gets the session.
+        /// </summary>
+        /// <value>
+        ///  Thhe session.
+        /// </value>
+        protected Session Session
+        {
+            get { return _session; }
+        }
+
         /// <summary>
         /// Gets the type of the channel.
         /// </summary>
@@ -36,9 +49,12 @@ namespace Renci.SshNet.Channels
         public uint LocalChannelNumber { get; private set; }
 
         /// <summary>
-        /// Gets the remote channel number assigned by the server.
+        /// Gets the maximum size of a packet.
         /// </summary>
-        public uint RemoteChannelNumber { get; private set; }
+        /// <value>
+        /// The maximum size of a packet.
+        /// </value>
+        public uint LocalPacketSize { get; private set; }
 
         /// <summary>
         /// Gets the size of the local window.
@@ -49,35 +65,76 @@ namespace Renci.SshNet.Channels
         public uint LocalWindowSize { get; private set; }
 
         /// <summary>
-        /// Gets or sets the size of the server window.
+        /// Gets the remote channel number.
         /// </summary>
         /// <value>
-        /// The size of the server window.
+        /// The remote channel number.
         /// </value>
-        public uint ServerWindowSize { get; protected set; }
+        public uint RemoteChannelNumber
+        {
+            get
+            {
+                if (!_remoteChannelNumber.HasValue)
+                    throw CreateRemoteChannelInfoNotAvailableException();
+                return _remoteChannelNumber.Value;
+            }
+            private set
+            {
+                _remoteChannelNumber = value;
+            }
+        }
 
         /// <summary>
-        /// Gets the size of the packet.
+        /// Gets the maximum size of a data packet that we can send using the channel.
         /// </summary>
         /// <value>
-        /// The size of the packet.
+        /// The maximum size of data that can be sent using a <see cref="ChannelDataMessage"/>
+        /// on the current channel.
         /// </value>
-        public uint PacketSize { get; private set; }
+        /// <exception cref="InvalidOperationException">The channel has not been opened, or the open has not yet been confirmed.</exception>
+        public uint RemotePacketSize
+        {
+            get
+            {
+                if (!_remotePacketSize.HasValue)
+                    throw CreateRemoteChannelInfoNotAvailableException();
+                return _remotePacketSize.Value;
+            }
+            private set
+            {
+                _remotePacketSize = value;
+            }
+        }
 
         /// <summary>
-        /// Gets a value indicating whether this channel is open.
+        /// Gets the window size of the remote server.
         /// </summary>
         /// <value>
-        ///   <c>true</c> if this channel is open; otherwise, <c>false</c>.
+        /// The size of the server window.
         /// </value>
-        public bool IsOpen { get; private set; }
-
-        #region Message events
+        public uint RemoteWindowSize
+        {
+            get
+            {
+                if (!_remoteWindowSize.HasValue)
+                    throw CreateRemoteChannelInfoNotAvailableException();
+                return _remoteWindowSize.Value;
+            }
+            private set
+            {
+                _remoteWindowSize = value;
+            }
+        }
 
         /// <summary>
-        /// Occurs when <see cref="ChannelOpenFailureMessage"/> message received
+        /// Gets a value indicating whether this channel is open.
         /// </summary>
-        public event EventHandler<ChannelOpenFailedEventArgs> OpenFailed;
+        /// <value>
+        /// <c>true</c> if this channel is open; otherwise, <c>false</c>.
+        /// </value>
+        public bool IsOpen { get; protected set; }
+
+        #region Message events
 
         /// <summary>
         /// Occurs when <see cref="ChannelDataMessage"/> message received
@@ -149,34 +206,33 @@ namespace Renci.SshNet.Channels
         /// Initializes the channel.
         /// </summary>
         /// <param name="session">The session.</param>
-        /// <param name="serverChannelNumber">The server channel number.</param>
-        /// <param name="windowSize">Size of the window.</param>
-        /// <param name="packetSize">Size of the packet.</param>
-        internal virtual void Initialize(Session session, uint serverChannelNumber, uint windowSize, uint packetSize)
+        /// <param name="localWindowSize">Size of the window.</param>
+        /// <param name="localPacketSize">Size of the packet.</param>
+        internal virtual void Initialize(Session session, uint localWindowSize, uint localPacketSize)
         {
-            this._initialWindowSize = windowSize;
-            this._maximumPacketSize = Math.Max(packetSize, 0x8000); //  Ensure minimum maximum packet size of 0x8000 bytes
-
-            this._session = session;
-            this.LocalWindowSize = this._initialWindowSize;  // Initial window size
-            this.PacketSize = this._maximumPacketSize;     // Maximum packet size
+            _session = session;
+            _initialWindowSize = localWindowSize;
+            LocalPacketSize = localPacketSize;
+            LocalWindowSize = localWindowSize;  // Initial window size
+            LocalChannelNumber = session.NextChannelNumber;
 
-            this.LocalChannelNumber = session.NextChannelNumber;
-            this.RemoteChannelNumber = serverChannelNumber;
+            _session.ChannelWindowAdjustReceived += OnChannelWindowAdjust;
+            _session.ChannelDataReceived += OnChannelData;
+            _session.ChannelExtendedDataReceived += OnChannelExtendedData;
+            _session.ChannelEofReceived += OnChannelEof;
+            _session.ChannelCloseReceived += OnChannelClose;
+            _session.ChannelRequestReceived += OnChannelRequest;
+            _session.ChannelSuccessReceived += OnChannelSuccess;
+            _session.ChannelFailureReceived += OnChannelFailure;
+            _session.ErrorOccured += Session_ErrorOccured;
+            _session.Disconnected += Session_Disconnected;
+        }
 
-            this._session.ChannelOpenReceived += OnChannelOpen;
-            this._session.ChannelOpenConfirmationReceived += OnChannelOpenConfirmation;
-            this._session.ChannelOpenFailureReceived += OnChannelOpenFailure;
-            this._session.ChannelWindowAdjustReceived += OnChannelWindowAdjust;
-            this._session.ChannelDataReceived += OnChannelData;
-            this._session.ChannelExtendedDataReceived += OnChannelExtendedData;
-            this._session.ChannelEofReceived += OnChannelEof;
-            this._session.ChannelCloseReceived += OnChannelClose;
-            this._session.ChannelRequestReceived += OnChannelRequest;
-            this._session.ChannelSuccessReceived += OnChannelSuccess;
-            this._session.ChannelFailureReceived += OnChannelFailure;
-            this._session.ErrorOccured += Session_ErrorOccured;
-            this._session.Disconnected += Session_Disconnected;
+        protected void InitializeRemoteInfo(uint remoteChannelNumber, uint remoteWindowSize, uint remotePacketSize)
+        {
+            RemoteChannelNumber = remoteChannelNumber;
+            RemoteWindowSize = remoteWindowSize;
+            RemotePacketSize = remotePacketSize;
         }
 
         /// <summary>
@@ -203,43 +259,6 @@ namespace Renci.SshNet.Channels
 
         #region Channel virtual methods
 
-        /// <summary>
-        /// Called when channel need to be open on the client.
-        /// </summary>
-        /// <param name="info">Channel open information.</param>
-        protected virtual void OnOpen(ChannelOpenInfo info)
-        {
-        }
-
-        /// <summary>
-        /// Called when channel is opened by the server.
-        /// </summary>
-        /// <param name="remoteChannelNumber">The remote channel number.</param>
-        /// <param name="initialWindowSize">Initial size of the window.</param>
-        /// <param name="maximumPacketSize">Maximum size of the packet.</param>
-        protected virtual void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize)
-        {
-            this.RemoteChannelNumber = remoteChannelNumber;
-            this.ServerWindowSize = initialWindowSize;
-            this.PacketSize = maximumPacketSize;
-
-            //  Chanel consider to be open when confirmation message was received
-            this.IsOpen = true;
-        }
-
-        /// <summary>
-        /// Called when channel failed to open.
-        /// </summary>
-        /// <param name="reasonCode">The reason code.</param>
-        /// <param name="description">The description.</param>
-        /// <param name="language">The language.</param>
-        protected virtual void OnOpenFailure(uint reasonCode, string description, string language)
-        {
-            var openFailed = OpenFailed;
-            if (openFailed != null)
-                openFailed(this, new ChannelOpenFailedEventArgs(LocalChannelNumber, reasonCode, description, language));
-        }
-
         /// <summary>
         /// Called when channel window need to be adjust.
         /// </summary>
@@ -248,7 +267,7 @@ namespace Renci.SshNet.Channels
         {
             lock (this._serverWindowSizeLock)
             {
-                this.ServerWindowSize += bytesToAdd;
+                this.RemoteWindowSize += bytesToAdd;
             }
             this._channelServerWindowAdjustWaitHandle.Set();
         }
@@ -348,30 +367,11 @@ namespace Renci.SshNet.Channels
             this._session.SendMessage(message);
         }
 
-        protected void SendMessage(ChannelOpenConfirmationMessage message)
-        {
-            //  No need to check whether channel is open when trying to open a channel
-            this._session.SendMessage(message);
-
-            //  Chanel consider to be open when confirmation message is sent
-            this.IsOpen = true;
-        }
-
-        /// <summary>
-        /// Send message to open a channel.
-        /// </summary>
-        /// <param name="message">Message to send</param>
-        protected void SendMessage(ChannelOpenMessage message)
-        {
-            //  No need to check whether channel is open when trying to open a channel
-            this._session.SendMessage(message);
-        }
-
         /// <summary>
-        /// Sends close channel message to the server
+        /// Sends close channel message to the server, and marks the channel closed.
         /// </summary>
-        /// <param name="message">Message to send.</param>
-        protected void SendMessage(ChannelCloseMessage message)
+        /// <param name="message">The message to send.</param>
+        private void SendMessage(ChannelCloseMessage message)
         {
             //  Send channel messages only while channel is open
             if (!this.IsOpen)
@@ -379,7 +379,7 @@ namespace Renci.SshNet.Channels
 
             this._session.SendMessage(message);
 
-            //  When channel close message is sent channel considred to be closed
+            //  When channel close message is sent channel considered to be closed
             this.IsOpen = false;
         }
 
@@ -387,7 +387,8 @@ namespace Renci.SshNet.Channels
         /// Sends channel data message to the servers.
         /// </summary>
         /// <remarks>This method takes care of managing the window size.</remarks>
-        /// <param name="message">Channel data message.</param>        
+        /// <param name="message">Channel data message.</param>
+        /// <exception cref="InvalidOperationException">The data of <paramref name="message"/> exceeds the maximum packet size of the channel.</exception>
         protected void SendMessage(ChannelDataMessage message)
         {
             //  Send channel messages only while channel is open
@@ -395,11 +396,25 @@ namespace Renci.SshNet.Channels
                 return;
 
             var messageLength = message.Data.Length;
+
+            // RFC4254:
+            // The maximum amount of data allowed is determined by the maximum packet size
+            // for the channel
+            //
+            // there's some ambiguity in the RFC, but most ssh implementations take only the
+            // data into account for determining the size of a packet; the 4 bytes for the
+            // packet length and the 9 bytes of the header are not considered part of the
+            // data
+            if (messageLength > RemotePacketSize)
+                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
+                    "The payload of the data message is {0} bytes while the maximum packet size of the channel is {1} bytes.",
+                    messageLength, RemotePacketSize));
+
             do
             {
                 lock (this._serverWindowSizeLock)
                 {
-                    var serverWindowSize = this.ServerWindowSize;
+                    var serverWindowSize = this.RemoteWindowSize;
                     if (serverWindowSize < messageLength)
                     {
                         //  Wait for window to be big enough for this message
@@ -407,7 +422,7 @@ namespace Renci.SshNet.Channels
                     }
                     else
                     {
-                        this.ServerWindowSize -= (uint)messageLength;
+                        this.RemoteWindowSize -= (uint)messageLength;
                         break;
                     }
                 }
@@ -434,7 +449,7 @@ namespace Renci.SshNet.Channels
             {
                 lock (this._serverWindowSizeLock)
                 {
-                    var serverWindowSize = this.ServerWindowSize;
+                    var serverWindowSize = this.RemoteWindowSize;
                     if (serverWindowSize < messageLength)
                     {
                         //  Wait for window to be big enough for this message
@@ -442,7 +457,7 @@ namespace Renci.SshNet.Channels
                     }
                     else
                     {
-                        this.ServerWindowSize -= (uint)messageLength;
+                        this.RemoteWindowSize -= (uint)messageLength;
                         break;
                     }
                 }
@@ -477,6 +492,11 @@ namespace Renci.SshNet.Channels
                     }
                 }
             }
+            else
+            {
+                // also mark the channel closed if the session is no longer connected
+                IsOpen = false;
+            }
 
             //  Wait for channel to be closed
             if (wait)
@@ -521,30 +541,6 @@ namespace Renci.SshNet.Channels
 
         #region Channel message event handlers
 
-        private void OnChannelOpen(object sender, MessageEventArgs<ChannelOpenMessage> e)
-        {
-            if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
-            {
-                this.OnOpen(e.Message.Info);
-            }
-        }
-
-        private void OnChannelOpenConfirmation(object sender, MessageEventArgs<ChannelOpenConfirmationMessage> e)
-        {
-            if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
-            {
-                this.OnOpenConfirmation(e.Message.RemoteChannelNumber, e.Message.InitialWindowSize, e.Message.MaximumPacketSize);
-            }
-        }
-
-        private void OnChannelOpenFailure(object sender, MessageEventArgs<ChannelOpenFailureMessage> e)
-        {
-            if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
-            {
-                this.OnOpenFailure(e.Message.ReasonCode, e.Message.Description, e.Message.Language);
-            }
-        }
-
         private void OnChannelWindowAdjust(object sender, MessageEventArgs<ChannelWindowAdjustMessage> e)
         {
             if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
@@ -634,13 +630,18 @@ namespace Renci.SshNet.Channels
             this.LocalWindowSize -= (uint)messageData.Length;
 
             //  Adjust window if window size is too low
-            if (this.LocalWindowSize < this.PacketSize)
+            if (this.LocalWindowSize < this.LocalPacketSize)
             {
                 this.SendMessage(new ChannelWindowAdjustMessage(this.RemoteChannelNumber, this._initialWindowSize - this.LocalWindowSize));
                 this.LocalWindowSize = this._initialWindowSize;
             }
         }
 
+        private InvalidOperationException CreateRemoteChannelInfoNotAvailableException()
+        {
+            throw new InvalidOperationException("The channel has not been opened, or the open has not yet been confirmed.");
+        }
+
         #region IDisposable Members
 
         private bool _isDisposed;
@@ -659,7 +660,7 @@ namespace Renci.SshNet.Channels
         /// </summary>
         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
         protected virtual void Dispose(bool disposing)
-        {            
+        {
             // Check to see if Dispose has already been called.
             if (!this._isDisposed)
             {
@@ -693,9 +694,6 @@ namespace Renci.SshNet.Channels
                 }
 
                 //  Ensure that all events are detached from current instance
-                this._session.ChannelOpenReceived -= OnChannelOpen;
-                this._session.ChannelOpenConfirmationReceived -= OnChannelOpenConfirmation;
-                this._session.ChannelOpenFailureReceived -= OnChannelOpenFailure;
                 this._session.ChannelWindowAdjustReceived -= OnChannelWindowAdjust;
                 this._session.ChannelDataReceived -= OnChannelData;
                 this._session.ChannelExtendedDataReceived -= OnChannelExtendedData;

+ 3 - 3
Renci.SshClient/Renci.SshNet/Channels/ChannelDirectTcpip.cs

@@ -11,7 +11,7 @@ namespace Renci.SshNet.Channels
     /// <summary>
     /// Implements "direct-tcpip" SSH channel.
     /// </summary>
-    internal partial class ChannelDirectTcpip : Channel
+    internal partial class ChannelDirectTcpip : ClientChannel
     {
         private EventWaitHandle _channelEof = new AutoResetEvent(false);
         private EventWaitHandle _channelOpen = new AutoResetEvent(false);
@@ -48,7 +48,7 @@ namespace Renci.SshNet.Channels
             }
 
             //  Open channel
-            this.SendMessage(new ChannelOpenMessage(this.LocalChannelNumber, this.LocalWindowSize, this.PacketSize,
+            this.SendMessage(new ChannelOpenMessage(this.LocalChannelNumber, this.LocalWindowSize, this.LocalPacketSize,
                                                         new DirectTcpipChannelInfo(remoteHost, port, ep.Address.ToString(), (uint)ep.Port)));
 
             //  Wait for channel to open
@@ -69,7 +69,7 @@ namespace Renci.SshNet.Channels
 
             try
             {
-                var buffer = new byte[this.PacketSize - 9];
+                var buffer = new byte[this.RemotePacketSize];
 
                 while (this._socket != null && this._socket.CanRead())
                 {

+ 1 - 1
Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.NET40.cs

@@ -6,7 +6,7 @@ namespace Renci.SshNet.Channels
     /// <summary>
     /// Implements "forwarded-tcpip" SSH channel.
     /// </summary>
-    internal partial class ChannelForwardedTcpip : Channel
+    internal partial class ChannelForwardedTcpip : ServerChannel
     {
         partial void OpenSocket(IPAddress connectedHost, uint connectedPort)
         {

+ 3 - 5
Renci.SshClient/Renci.SshNet/Channels/ChannelForwardedTcpip.cs

@@ -11,7 +11,7 @@ namespace Renci.SshNet.Channels
     /// <summary>
     /// Implements "forwarded-tcpip" SSH channel.
     /// </summary>
-    internal partial class ChannelForwardedTcpip : Channel
+    internal partial class ChannelForwardedTcpip : ServerChannel
     {
         private Socket _socket;
 
@@ -42,8 +42,6 @@ namespace Renci.SshNet.Channels
         {
             byte[] buffer;
 
-            this.ServerWindowSize = this.LocalWindowSize;
-
             if (!this.IsConnected)
             {
                 throw new SshException("Session is not connected.");
@@ -53,12 +51,12 @@ namespace Renci.SshNet.Channels
             try
             {
                 //  Get buffer in memory for data exchange
-                buffer = new byte[this.PacketSize - 9];
+                buffer = new byte[this.RemotePacketSize];
 
                 this.OpenSocket(connectedHost, connectedPort);
 
                 //  Send channel open confirmation message
-                this.SendMessage(new ChannelOpenConfirmationMessage(this.RemoteChannelNumber, this.LocalWindowSize, this.PacketSize, this.LocalChannelNumber));
+                this.SendMessage(new ChannelOpenConfirmationMessage(this.RemoteChannelNumber, this.LocalWindowSize, this.LocalPacketSize, this.LocalChannelNumber));
             }
             catch (Exception exp)
             {

+ 2 - 2
Renci.SshClient/Renci.SshNet/Channels/ChannelSession.cs

@@ -9,7 +9,7 @@ namespace Renci.SshNet.Channels
     /// <summary>
     /// Implements Session SSH channel.
     /// </summary>
-    internal class ChannelSession : Channel
+    internal class ChannelSession : ClientChannel
     {
         /// <summary>
         /// Counts faile channel open attempts
@@ -323,7 +323,7 @@ namespace Renci.SshNet.Channels
             {
                 //  Ensure that channels are available
                 this.SessionSemaphore.Wait();
-                this.SendMessage(new ChannelOpenMessage(this.LocalChannelNumber, this.LocalWindowSize, this.PacketSize, new SessionChannelOpenInfo()));
+                this.SendMessage(new ChannelOpenMessage(this.LocalChannelNumber, this.LocalWindowSize, this.LocalPacketSize, new SessionChannelOpenInfo()));
             }
         }
 

+ 101 - 0
Renci.SshClient/Renci.SshNet/Channels/ClientChannel.cs

@@ -0,0 +1,101 @@
+using System;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Channels
+{
+    internal abstract class ClientChannel : Channel
+    {
+        /// <summary>
+        /// Occurs when <see cref="ChannelOpenConfirmationMessage"/> message is received.
+        /// </summary>
+        public event EventHandler<ChannelOpenConfirmedEventArgs> OpenConfirmed;
+
+        /// <summary>
+        /// Occurs when <see cref="ChannelOpenFailureMessage"/> message received
+        /// </summary>
+        public event EventHandler<ChannelOpenFailedEventArgs> OpenFailed;
+
+        /// <summary>
+        /// Initializes the channel.
+        /// </summary>
+        /// <param name="session">The session.</param>
+        /// <param name="localWindowSize">Size of the window.</param>
+        /// <param name="localPacketSize">Size of the packet.</param>
+        internal override void Initialize(Session session, uint localWindowSize, uint localPacketSize)
+        {
+            base.Initialize(session, localWindowSize, localPacketSize);
+            Session.ChannelOpenConfirmationReceived += OnChannelOpenConfirmation;
+            Session.ChannelOpenFailureReceived += OnChannelOpenFailure;
+        }
+
+        /// <summary>
+        /// Called when channel is opened by the server.
+        /// </summary>
+        /// <param name="remoteChannelNumber">The remote channel number.</param>
+        /// <param name="initialWindowSize">Initial size of the window.</param>
+        /// <param name="maximumPacketSize">Maximum size of the packet.</param>
+        protected virtual void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize)
+        {
+            InitializeRemoteInfo(remoteChannelNumber, initialWindowSize, maximumPacketSize);
+
+            //  Channel is consider to be open when confirmation message was received
+            this.IsOpen = true;
+
+            var openConfirmed = OpenConfirmed;
+            if (openConfirmed != null)
+                openConfirmed(this, new ChannelOpenConfirmedEventArgs(remoteChannelNumber, initialWindowSize, maximumPacketSize));
+        }
+
+        /// <summary>
+        /// Send message to open a channel.
+        /// </summary>
+        /// <param name="message">Message to send</param>
+        protected void SendMessage(ChannelOpenMessage message)
+        {
+            Session.SendMessage(message);
+        }
+
+        /// <summary>
+        /// Called when channel failed to open.
+        /// </summary>
+        /// <param name="reasonCode">The reason code.</param>
+        /// <param name="description">The description.</param>
+        /// <param name="language">The language.</param>
+        protected virtual void OnOpenFailure(uint reasonCode, string description, string language)
+        {
+            var openFailed = OpenFailed;
+            if (openFailed != null)
+                openFailed(this, new ChannelOpenFailedEventArgs(LocalChannelNumber, reasonCode, description, language));
+        }
+
+        private void OnChannelOpenConfirmation(object sender, MessageEventArgs<ChannelOpenConfirmationMessage> e)
+        {
+            if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
+            {
+                this.OnOpenConfirmation(e.Message.RemoteChannelNumber, e.Message.InitialWindowSize, e.Message.MaximumPacketSize);
+            }
+        }
+
+        private void OnChannelOpenFailure(object sender, MessageEventArgs<ChannelOpenFailureMessage> e)
+        {
+            if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
+            {
+                this.OnOpenFailure(e.Message.ReasonCode, e.Message.Description, e.Message.Language);
+            }
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                var session = Session;
+                if (session != null)
+                {
+                    session.ChannelOpenConfirmationReceived -= OnChannelOpenConfirmation;
+                    session.ChannelOpenFailureReceived -= OnChannelOpenFailure;
+                }
+            }
+        }
+    }
+}

+ 60 - 0
Renci.SshClient/Renci.SshNet/Channels/ServerChannel.cs

@@ -0,0 +1,60 @@
+using System;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Channels
+{
+    internal abstract class ServerChannel : Channel
+    {
+        internal void Initialize(Session session, uint localWindowSize, uint localPacketSize, uint remoteChannelNumber, uint remoteWindowSize, uint remotePacketSize)
+        {
+            Initialize(session, localWindowSize, localPacketSize);
+            InitializeRemoteInfo(remoteChannelNumber, remoteWindowSize, remotePacketSize);
+            //Session.ChannelOpenReceived += OnChannelOpen;
+        }
+
+        //private void OnChannelOpen(object sender, MessageEventArgs<ChannelOpenMessage> e)
+        //{
+        //    var channelOpenMessage = e.Message;
+
+        //    if (channelOpenMessage.LocalChannelNumber == LocalChannelNumber)
+        //    {
+        //        _remoteChannelNumber = channelOpenMessage.LocalChannelNumber;
+        //        RemoteWindowSize = channelOpenMessage.InitialWindowSize;
+        //        _remotePacketSize = channelOpenMessage.MaximumPacketSize;
+        //        OnOpen(e.Message.Info);
+        //    }
+        //}
+
+        protected void SendMessage(ChannelOpenConfirmationMessage message)
+        {
+            //  No need to check whether channel is open when trying to open a channel
+            Session.SendMessage(message);
+
+            //  When we act as server, consider the channel open when we've sent the
+            // confirmation message to the peer
+            IsOpen = true;
+        }
+
+        ///// <summary>
+        ///// Called when channel need to be open on the client.
+        ///// </summary>
+        ///// <param name="info">Channel open information.</param>
+        //protected virtual void OnOpen(ChannelOpenInfo info)
+        //{
+        //}
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                var session = Session;
+                if (session != null)
+                {
+                    //session.ChannelOpenReceived -= OnChannelOpen;
+                }
+            }
+
+            base.Dispose(disposing);
+        }
+    }
+}

+ 37 - 0
Renci.SshClient/Renci.SshNet/Common/ChannelOpenConfirmedEventArgs.cs

@@ -0,0 +1,37 @@
+namespace Renci.SshNet.Common
+{
+    /// <summary>
+    /// Provides data for <see cref="Renci.SshNet.Channels.Channel.OpenConfirmed"/> event.
+    /// </summary>
+    internal class ChannelOpenConfirmedEventArgs : ChannelEventArgs
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ChannelOpenConfirmedEventArgs"/> class.
+        /// </summary>
+        /// <param name="remoteChannelNumber">The remote channel number.</param>
+        /// <param name="initialWindowSize">The initial window size.</param>
+        /// <param name="maximumPacketSize">The maximum packet size.</param>
+        public ChannelOpenConfirmedEventArgs(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize)
+            : base(remoteChannelNumber)
+        {
+            InitialWindowSize = initialWindowSize;
+            MaximumPacketSize = maximumPacketSize;
+        }
+
+        /// <summary>
+        /// Gets the initial size of the window.
+        /// </summary>
+        /// <value>
+        /// The initial size of the window.
+        /// </value>
+        public uint InitialWindowSize { get; private set; }
+
+        /// <summary>
+        /// Gets the maximum size of the packet.
+        /// </summary>
+        /// <value>
+        /// The maximum size of the packet.
+        /// </value>
+        public uint MaximumPacketSize { get; private set; }
+    }
+}

+ 1 - 1
Renci.SshClient/Renci.SshNet/ForwardedPortDynamic.NET.cs

@@ -50,7 +50,7 @@ namespace Renci.SshNet
                         {
                             try
                             {
-                                using (var channel = this.Session.CreateChannel<ChannelDirectTcpip>())
+                                using (var channel = this.Session.CreateClientChannel<ChannelDirectTcpip>())
                                 {
                                     var version = new byte[1];
 

+ 1 - 1
Renci.SshClient/Renci.SshNet/ForwardedPortLocal.NET.cs

@@ -54,7 +54,7 @@ namespace Renci.SshNet
 
                                 this.RaiseRequestReceived(originatorEndPoint.Address.ToString(), (uint)originatorEndPoint.Port);
 
-                                using (var channel = this.Session.CreateChannel<ChannelDirectTcpip>())
+                                using (var channel = this.Session.CreateClientChannel<ChannelDirectTcpip>())
                                 {
                                     channel.Open(this.Host, this.Port, socket);
 

+ 6 - 3
Renci.SshClient/Renci.SshNet/ForwardedPortRemote.cs

@@ -146,10 +146,11 @@ namespace Renci.SshNet
 
         private void Session_ChannelOpening(object sender, MessageEventArgs<ChannelOpenMessage> e)
         {
-            //  Ensure that this is corresponding request
-            var info = e.Message.Info as ForwardedTcpipChannelInfo;
+            var channelOpenMessage = e.Message;
+            var info = channelOpenMessage.Info as ForwardedTcpipChannelInfo;
             if (info != null)
             {
+                //  Ensure this is the corresponding request
                 if (info.ConnectedAddress == this.BoundHost && info.ConnectedPort == this.BoundPort)
                 {
                     this.ExecuteThread(() =>
@@ -158,7 +159,9 @@ namespace Renci.SshNet
                         {
                             this.RaiseRequestReceived(info.OriginatorAddress, info.OriginatorPort);
 
-                            var channel = this.Session.CreateChannel<ChannelForwardedTcpip>(e.Message.LocalChannelNumber, e.Message.InitialWindowSize, e.Message.MaximumPacketSize);
+                            var channel = this.Session.CreateServerChannel<ChannelForwardedTcpip>(
+                                channelOpenMessage.LocalChannelNumber, channelOpenMessage.InitialWindowSize,
+                                channelOpenMessage.MaximumPacketSize);
                             channel.Bind(this.HostAddress, this.Port);
                         }
                         catch (Exception exp)

+ 5 - 2
Renci.SshClient/Renci.SshNet/Renci.SshNet.csproj

@@ -22,13 +22,13 @@
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
     <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DefineConstants>TRACE;DEBUG</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <DocumentationFile>bin\Debug\Renci.SshNet.xml</DocumentationFile>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>pdbonly</DebugType>
+    <DebugType>none</DebugType>
     <Optimize>true</Optimize>
     <OutputPath>bin\Release\</OutputPath>
     <DefineConstants>TRACE</DefineConstants>
@@ -65,6 +65,8 @@
     <Compile Include="Channels\ChannelForwardedTcpip.NET40.cs" />
     <Compile Include="Channels\ChannelSession.cs" />
     <Compile Include="Channels\ChannelTypes.cs" />
+    <Compile Include="Channels\ClientChannel.cs" />
+    <Compile Include="Channels\ServerChannel.cs" />
     <Compile Include="CipherInfo.cs" />
     <Compile Include="Common\ASCIIEncoding.cs" />
     <Compile Include="Common\AsyncResult.cs" />
@@ -78,6 +80,7 @@
     </Compile>
     <Compile Include="Common\ChannelDataEventArgs.cs" />
     <Compile Include="Common\ChannelEventArgs.cs" />
+    <Compile Include="Common\ChannelOpenConfirmedEventArgs.cs" />
     <Compile Include="Common\ChannelOpenFailedEventArgs.cs" />
     <Compile Include="Common\ChannelRequestEventArgs.cs" />
     <Compile Include="Common\Extensions.NET.cs" />

+ 4 - 4
Renci.SshClient/Renci.SshNet/ScpClient.NET.cs

@@ -29,7 +29,7 @@ namespace Renci.SshNet
                 throw new ArgumentException("path");
 
             using (var input = new PipeStream())
-            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            using (var channel = this.Session.CreateClientChannel<ChannelSession>())
             {
                 channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
                 {
@@ -64,7 +64,7 @@ namespace Renci.SshNet
                 throw new ArgumentException("path");
 
             using (var input = new PipeStream())
-            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            using (var channel = this.Session.CreateClientChannel<ChannelSession>())
             {
                 channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
                 {
@@ -106,7 +106,7 @@ namespace Renci.SshNet
                 throw new ArgumentNullException("fileInfo");
 
             using (var input = new PipeStream())
-            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            using (var channel = this.Session.CreateClientChannel<ChannelSession>())
             {
                 channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
                 {
@@ -141,7 +141,7 @@ namespace Renci.SshNet
                 throw new ArgumentNullException("directoryInfo");
 
             using (var input = new PipeStream())
-            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            using (var channel = this.Session.CreateClientChannel<ChannelSession>())
             {
                 channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
                 {

+ 2 - 2
Renci.SshClient/Renci.SshNet/ScpClient.cs

@@ -152,7 +152,7 @@ namespace Renci.SshNet
         public void Upload(Stream source, string path)
         {
             using (var input = new PipeStream())
-            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            using (var channel = this.Session.CreateClientChannel<ChannelSession>())
             {
                 channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
                 {
@@ -198,7 +198,7 @@ namespace Renci.SshNet
                 throw new ArgumentNullException("destination");
 
             using (var input = new PipeStream())
-            using (var channel = this.Session.CreateChannel<ChannelSession>())
+            using (var channel = this.Session.CreateClientChannel<ChannelSession>())
             {
                 channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
                 {

+ 2 - 1
Renci.SshClient/Renci.SshNet/Session.NET.cs

@@ -37,7 +37,7 @@ namespace Renci.SshNet
 
         partial void SocketConnect(string host, int port)
         {
-            const int socketBufferSize = 2 * MaximumPacketSize;
+            const int socketBufferSize = 2 * MaximumSshPacketSize;
 
             var addr = host.GetIPAddress();
 
@@ -187,6 +187,7 @@ namespace Renci.SshNet
             } while (sent < length);
         }
 
+        [Conditional("DEBUG")]
         partial void Log(string text)
         {
             this._log.TraceEvent(TraceEventType.Verbose, 1, text);

+ 28 - 15
Renci.SshClient/Renci.SshNet/Session.cs

@@ -28,12 +28,23 @@ namespace Renci.SshNet
         /// <summary>
         /// Specifies maximum packet size defined by the protocol.
         /// </summary>
-        protected const int MaximumPacketSize = 35000;
+        private const int MaximumSshPacketSize = LocalChannelDataPacketSize + 3000;
 
         /// <summary>
-        /// Specifies maximum payload size defined by the protocol.
+        /// Holds the initial local window size for the channels.
         /// </summary>
-        protected const int MaximumPayloadSize = 1024 * 32;
+        /// <value>
+        /// 2 MB.
+        /// </value>
+        private const int InitialLocalWindowSize = 0x200000;
+
+        /// <summary>
+        /// Holds the maximum size of channel data packets that we receive.
+        /// </summary>
+        /// <value>
+        /// 64 KB.
+        /// </value>
+        private const int LocalChannelDataPacketSize = 1024*64;
 
         private static readonly RNGCryptoServiceProvider Randomizer = new RNGCryptoServiceProvider();
 
@@ -224,6 +235,7 @@ namespace Renci.SshNet
         public byte[] SessionId { get; private set; }
 
         private Message _clientInitMessage;
+
         /// <summary>
         /// Gets the client init message.
         /// </summary>
@@ -643,17 +655,23 @@ namespace Renci.SshNet
             }
         }
 
-        internal T CreateChannel<T>() where T : Channel, new()
+        internal T CreateClientChannel<T>() where T : ClientChannel, new()
         {
-            return CreateChannel<T>(0, 0x100000, 0x8000);
+            var channel = new T();
+            lock (this)
+            {
+                channel.Initialize(this, InitialLocalWindowSize, LocalChannelDataPacketSize);
+            }
+            return channel;
         }
 
-        internal T CreateChannel<T>(uint serverChannelNumber, uint windowSize, uint packetSize) where T : Channel, new()
+        internal T CreateServerChannel<T>(uint remoteChannelNumber, uint remoteWindowSize, uint remoteChannelDataPacketSize) where T : ServerChannel, new()
         {
             var channel = new T();
             lock (this)
             {
-                channel.Initialize(this, serverChannelNumber, windowSize, packetSize);
+                channel.Initialize(this, InitialLocalWindowSize, LocalChannelDataPacketSize, remoteChannelNumber, remoteWindowSize,
+                    remoteChannelDataPacketSize);
             }
             return channel;
         }
@@ -748,11 +766,6 @@ namespace Renci.SshNet
 
             var messageData = message.GetBytes();
 
-            if (messageData.Length > MaximumPayloadSize)
-            {
-                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Payload cannot be more than {0} bytes.", MaximumPayloadSize));
-            }
-
             if (this._clientCompression != null)
             {
                 messageData = this._clientCompression.Compress(messageData);
@@ -799,9 +812,9 @@ namespace Renci.SshNet
                     packetData = this._clientCipher.Encrypt(packetData);
                 }
 
-                if (packetData.Length > MaximumPacketSize)
+                if (packetData.Length > MaximumSshPacketSize)
                 {
-                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Packet is too big. Maximum packet size is {0} bytes.", MaximumPacketSize));
+                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Packet is too big. Maximum packet size is {0} bytes.", MaximumSshPacketSize));
                 }
 
                 if (this._clientMac == null)
@@ -889,7 +902,7 @@ namespace Renci.SshNet
             var packetLength = (uint)(firstBlock[0] << 24 | firstBlock[1] << 16 | firstBlock[2] << 8 | firstBlock[3]);
 
             //  Test packet minimum and maximum boundaries
-            if (packetLength < Math.Max((byte)16, blockSize) - 4 || packetLength > MaximumPacketSize - 4)
+            if (packetLength < Math.Max((byte)16, blockSize) - 4 || packetLength > MaximumSshPacketSize - 4)
                 throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Bad packet length {0}", packetLength), DisconnectReason.ProtocolError);
 
             //  Read rest of the packet data

+ 72 - 87
Renci.SshClient/Renci.SshNet/Sftp/SftpFileStream.cs

@@ -17,13 +17,14 @@ namespace Renci.SshNet.Sftp
         private readonly FileAccess _access;
         private readonly bool _ownsHandle;
         private readonly bool _isAsync;
-        private string _path;
         private SftpSession _session;
 
         // Buffer information.
-        private readonly int _bufferSize;
-        private readonly byte[] _buffer;
-        private int _bufferPosn;
+        private readonly int _readBufferSize;
+        private readonly byte[] _readBuffer;
+        private readonly int _writeBufferSize;
+        private readonly byte[] _writeBuffer;
+        private int _bufferPosition;
         private int _bufferLen;
         private long _position;
         private bool _bufferOwnedByWrite;
@@ -182,22 +183,23 @@ namespace Renci.SshNet.Sftp
         /// </value>
         public TimeSpan Timeout { get; set; }
 
+        /// <summary>
+        /// Initializes a new <see cref="SftpFileStream"/> instance with a read and write buffer
+        /// of 4 KB.
+        /// </summary>
         internal SftpFileStream(SftpSession session, string path, FileMode mode)
-            : this(session, path, mode, FileAccess.ReadWrite, 4096, false)
+            : this(session, path, mode, FileAccess.ReadWrite)
         {
-            // Nothing to do here.
         }
 
         internal SftpFileStream(SftpSession session, string path, FileMode mode, FileAccess access)
-            : this(session, path, mode, access, 4096, false)
+            : this(session, path, mode, access, 4096)
         {
-            // Nothing to do here.
         }
 
         internal SftpFileStream(SftpSession session, string path, FileMode mode, FileAccess access, int bufferSize)
             : this(session, path, mode, access, bufferSize, false)
         {
-            // Nothing to do here.
         }
 
         internal SftpFileStream(SftpSession session, string path, FileMode mode, FileAccess access, int bufferSize, bool useAsync)
@@ -210,7 +212,7 @@ namespace Renci.SshNet.Sftp
             {
                 throw new ArgumentNullException("path");
             }
-            if (bufferSize <= 0 || bufferSize > 16 * 1024)
+            if (bufferSize <= 0)
             {
                 throw new ArgumentOutOfRangeException("bufferSize");
             }
@@ -231,10 +233,7 @@ namespace Renci.SshNet.Sftp
             this._access = access;
             this._ownsHandle = true;
             this._isAsync = useAsync;
-            this._path = path;
-            this._bufferSize = bufferSize;
-            this._buffer = new byte[bufferSize];
-            this._bufferPosn = 0;
+            this._bufferPosition = 0;
             this._bufferLen = 0;
             this._bufferOwnedByWrite = false;
             this._canSeek = true;
@@ -288,10 +287,15 @@ namespace Renci.SshNet.Sftp
             }
 
             if (this._handle == null)
-                this._handle = this._session.RequestOpen(this._path, flags);
+                this._handle = this._session.RequestOpen(path, flags);
 
             this._attributes = this._session.RequestFStat(this._handle);
 
+            this._readBufferSize = (int)session.CalculateOptimalReadLength((uint)bufferSize);
+            this._readBuffer = new byte[_readBufferSize];
+            this._writeBufferSize = (int)session.CalculateOptimalWriteLength((uint)bufferSize, _handle);
+            this._writeBuffer = new byte[_writeBufferSize];
+
             if (mode == FileMode.Append)
             {
                 this._position = this._attributes.Size;
@@ -354,17 +358,10 @@ namespace Renci.SshNet.Sftp
         /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
         /// </returns>
         /// <exception cref="T:System.ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is larger than the buffer length. </exception>
-        ///   
-        /// <exception cref="T:System.ArgumentNullException">
-        ///   <paramref name="buffer"/> is null. </exception>
-        ///   
-        /// <exception cref="T:System.ArgumentOutOfRangeException">
-        ///   <paramref name="offset"/> or <paramref name="count"/> is negative. </exception>
-        ///   
+        /// <exception cref="T:System.ArgumentNullException"><paramref name="buffer"/> is null. </exception>
+        /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="count"/> is negative.</exception>
         /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
-        ///   
         /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception>
-        ///   
         /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
         public override int Read(byte[] buffer, int offset, int count)
         {
@@ -389,16 +386,16 @@ namespace Renci.SshNet.Sftp
                 while (count > 0)
                 {
                     // How much data do we have available in the buffer?
-                    var tempLen = this._bufferLen - this._bufferPosn;
+                    var tempLen = this._bufferLen - this._bufferPosition;
                     if (tempLen <= 0)
                     {
-                        this._bufferPosn = 0;
+                        this._bufferPosition = 0;
 
-                        var data = this._session.RequestRead(this._handle, (ulong)this._position, (uint)this._bufferSize);
+                        var data = this._session.RequestRead(this._handle, (ulong)this._position, (uint)this._readBufferSize);
 
                         this._bufferLen = data.Length;
 
-                        Buffer.BlockCopy(data, 0, this._buffer, 0, this._bufferLen);
+                        Buffer.BlockCopy(data, 0, this._readBuffer, 0, this._bufferLen);
                         this._serverFilePosition = (ulong)this._position;
 
                         if (this._bufferLen < 0)
@@ -421,13 +418,13 @@ namespace Renci.SshNet.Sftp
                     }
 
                     // Copy stream data to the caller's buffer.
-                    Buffer.BlockCopy(this._buffer, this._bufferPosn, buffer, offset, tempLen);
+                    Buffer.BlockCopy(this._readBuffer, this._bufferPosition, buffer, offset, tempLen);
 
                     // Advance to the next buffer positions.
                     readLen += tempLen;
                     offset += tempLen;
                     count -= tempLen;
-                    this._bufferPosn += tempLen;
+                    this._bufferPosition += tempLen;
                     this._position += tempLen;
                 }
             }
@@ -443,7 +440,6 @@ namespace Renci.SshNet.Sftp
         /// The unsigned byte cast to an Int32, or -1 if at the end of the stream.
         /// </returns>
         /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception>
-        ///   
         /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
         /// <exception cref="System.IO.IOException">Read operation failed.</exception>
         public override int ReadByte()
@@ -455,14 +451,14 @@ namespace Renci.SshNet.Sftp
                 this.SetupRead();
 
                 // Read more data into the internal buffer if necessary.
-                if (this._bufferPosn >= this._bufferLen)
+                if (this._bufferPosition >= this._bufferLen)
                 {
-                    this._bufferPosn = 0;
+                    this._bufferPosition = 0;
 
-                    var data = this._session.RequestRead(this._handle, (ulong)this._position, (uint)this._bufferSize);
+                    var data = this._session.RequestRead(this._handle, (ulong)this._position, (uint)this._readBufferSize);
 
                     this._bufferLen = data.Length;
-                    Buffer.BlockCopy(data, 0, this._buffer, 0, this._bufferSize);
+                    Buffer.BlockCopy(data, 0, this._readBuffer, 0, this._readBufferSize);
                     this._serverFilePosition = (ulong)this._position;
 
                     if (this._bufferLen < 0)
@@ -480,7 +476,7 @@ namespace Renci.SshNet.Sftp
 
                 // Extract the next byte from the buffer.
                 ++this._position;
-                return this._buffer[this._bufferPosn++];
+                return this._readBuffer[this._bufferPosition++];
             }
         }
 
@@ -493,9 +489,7 @@ namespace Renci.SshNet.Sftp
         /// The new position within the current stream.
         /// </returns>
         /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
-        ///   
         /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output. </exception>
-        ///   
         /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
         public override long Seek(long offset, SeekOrigin origin)
         {
@@ -560,11 +554,11 @@ namespace Renci.SshNet.Sftp
                     // the current read buffer bounds.
                     if (origin == SeekOrigin.Begin)
                     {
-                        newPosn = this._position - this._bufferPosn;
+                        newPosn = this._position - this._bufferPosition;
                         if (offset >= newPosn && offset <
                                 (newPosn + this._bufferLen))
                         {
-                            this._bufferPosn = (int)(offset - newPosn);
+                            this._bufferPosition = (int)(offset - newPosn);
                             this._position = offset;
                             return this._position;
                         }
@@ -572,18 +566,18 @@ namespace Renci.SshNet.Sftp
                     else if (origin == SeekOrigin.Current)
                     {
                         newPosn = this._position + offset;
-                        if (newPosn >= (this._position - this._bufferPosn) &&
-                           newPosn < (this._position - this._bufferPosn + this._bufferLen))
+                        if (newPosn >= (this._position - this._bufferPosition) &&
+                           newPosn < (this._position - this._bufferPosition + this._bufferLen))
                         {
-                            this._bufferPosn =
-                                (int)(newPosn - (this._position - this._bufferPosn));
+                            this._bufferPosition =
+                                (int)(newPosn - (this._position - this._bufferPosition));
                             this._position = newPosn;
                             return this._position;
                         }
                     }
 
                     // Abandon the read buffer.
-                    this._bufferPosn = 0;
+                    this._bufferPosition = 0;
                     this._bufferLen = 0;
 
                     // Seek to the new position.
@@ -615,11 +609,9 @@ namespace Renci.SshNet.Sftp
         /// When overridden in a derived class, sets the length of the current stream.
         /// </summary>
         /// <param name="value">The desired length of the current stream in bytes.</param>
-        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
-        ///   
-        /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. </exception>
-        ///   
-        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
+        /// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
+        /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.</exception>
+        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> must be greater than zero.</exception>
         public override void SetLength(long value)
         {
@@ -651,19 +643,12 @@ namespace Renci.SshNet.Sftp
         /// <param name="buffer">An array of bytes. This method copies <paramref name="count"/> bytes from <paramref name="buffer"/> to the current stream.</param>
         /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes to the current stream.</param>
         /// <param name="count">The number of bytes to be written to the current stream.</param>
-        /// <exception cref="T:System.ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is greater than the buffer length. </exception>
-        ///   
-        /// <exception cref="T:System.ArgumentNullException">
-        ///   <paramref name="buffer"/> is null. </exception>
-        ///   
-        /// <exception cref="T:System.ArgumentOutOfRangeException">
-        ///   <paramref name="offset"/> or <paramref name="count"/> is negative. </exception>
-        ///   
-        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
-        ///   
-        /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception>
-        ///   
-        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
+        /// <exception cref="T:System.ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is greater than the buffer length.</exception>
+        /// <exception cref="T:System.ArgumentNullException"><paramref name="buffer"/> is null.</exception>
+        /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="count"/> is negative.</exception>
+        /// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
+        /// <exception cref="T:System.NotSupportedException">The stream does not support writing.</exception>
+        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
         public override void Write(byte[] buffer, int offset, int count)
         {
             if (buffer == null)
@@ -685,12 +670,12 @@ namespace Renci.SshNet.Sftp
                 while (count > 0)
                 {
                     // Determine how many bytes we can write to the buffer.
-                    var tempLen = this._bufferSize - this._bufferPosn;
+                    var tempLen = this._writeBufferSize - this._bufferPosition;
                     if (tempLen <= 0)
                     {
-                        var data = new byte[this._bufferPosn];
+                        var data = new byte[this._bufferPosition];
 
-                        Buffer.BlockCopy(this._buffer, 0, data, 0, this._bufferPosn);
+                        Buffer.BlockCopy(this._writeBuffer, 0, data, 0, this._bufferPosition);
 
                         using (var wait = new AutoResetEvent(false))
                         {
@@ -698,8 +683,8 @@ namespace Renci.SshNet.Sftp
                             this._serverFilePosition += (ulong)data.Length;
                         }
 
-                        this._bufferPosn = 0;
-                        tempLen = this._bufferSize;
+                        this._bufferPosition = 0;
+                        tempLen = this._writeBufferSize;
                     }
                     if (tempLen > count)
                     {
@@ -707,7 +692,7 @@ namespace Renci.SshNet.Sftp
                     }
 
                     // Can we short-cut the internal buffer?
-                    if (this._bufferPosn == 0 && tempLen == this._bufferSize)
+                    if (this._bufferPosition == 0 && tempLen == this._writeBufferSize)
                     {
                         // Yes: write the data directly to the file.
                         var data = new byte[tempLen];
@@ -723,8 +708,8 @@ namespace Renci.SshNet.Sftp
                     else
                     {
                         // No: copy the data to the write buffer first.
-                        Buffer.BlockCopy(buffer, offset, this._buffer, this._bufferPosn, tempLen);
-                        this._bufferPosn += tempLen;
+                        Buffer.BlockCopy(buffer, offset, _writeBuffer, this._bufferPosition, tempLen);
+                        this._bufferPosition += tempLen;
                     }
 
                     // Advance the buffer and stream positions.
@@ -735,11 +720,11 @@ namespace Renci.SshNet.Sftp
 
                 // If the buffer is full, then do a speculative flush now,
                 // rather than waiting for the next call to this method.
-                if (this._bufferPosn >= this._bufferSize)
+                if (this._bufferPosition >= _writeBufferSize)
                 {
-                    var data = new byte[this._bufferPosn];
+                    var data = new byte[this._bufferPosition];
 
-                    Buffer.BlockCopy(this._buffer, 0, data, 0, this._bufferPosn);
+                    Buffer.BlockCopy(this._writeBuffer, 0, data, 0, this._bufferPosition);
 
                     using (var wait = new AutoResetEvent(false))
                     {
@@ -747,7 +732,7 @@ namespace Renci.SshNet.Sftp
                         this._serverFilePosition += (ulong)data.Length;
                     }
 
-                    this._bufferPosn = 0;
+                    this._bufferPosition = 0;
                 }
             }
         }
@@ -768,11 +753,11 @@ namespace Renci.SshNet.Sftp
                 this.SetupWrite();
 
                 // Flush the current buffer if it is full.
-                if (this._bufferPosn >= this._bufferSize)
+                if (this._bufferPosition >= this._writeBufferSize)
                 {
-                    var data = new byte[this._bufferPosn];
+                    var data = new byte[this._bufferPosition];
 
-                    Buffer.BlockCopy(this._buffer, 0, data, 0, this._bufferPosn);
+                    Buffer.BlockCopy(this._writeBuffer, 0, data, 0, this._bufferPosition);
 
                     using (var wait = new AutoResetEvent(false))
                     {
@@ -780,11 +765,11 @@ namespace Renci.SshNet.Sftp
                         this._serverFilePosition += (ulong)data.Length;
                     }
 
-                    this._bufferPosn = 0;
+                    this._bufferPosition = 0;
                 }
 
                 // Write the byte into the buffer and advance the posn.
-                this._buffer[this._bufferPosn++] = value;
+                _writeBuffer[this._bufferPosition++] = value;
                 ++this._position;
             }
         }
@@ -832,11 +817,11 @@ namespace Renci.SshNet.Sftp
         {
             if (this._canSeek)
             {
-                if (this._bufferPosn < this._bufferLen)
+                if (this._bufferPosition < this._bufferLen)
                 {
-                    this._position -= this._bufferPosn;
+                    this._position -= this._bufferPosition;
                 }
-                this._bufferPosn = 0;
+                this._bufferPosition = 0;
                 this._bufferLen = 0;
             }
         }
@@ -846,11 +831,11 @@ namespace Renci.SshNet.Sftp
         /// </summary>
         private void FlushWriteBuffer()
         {
-            if (this._bufferPosn > 0)
+            if (this._bufferPosition > 0)
             {
-                var data = new byte[this._bufferPosn];
+                var data = new byte[this._bufferPosition];
 
-                Buffer.BlockCopy(this._buffer, 0, data, 0, this._bufferPosn);
+                Buffer.BlockCopy(this._writeBuffer, 0, data, 0, this._bufferPosition);
 
                 using (var wait = new AutoResetEvent(false))
                 {
@@ -858,7 +843,7 @@ namespace Renci.SshNet.Sftp
                     this._serverFilePosition += (ulong)data.Length;
                 }
 
-                this._bufferPosn = 0;
+                this._bufferPosition = 0;
             }
         }
 

+ 49 - 0
Renci.SshClient/Renci.SshNet/Sftp/SftpSession.cs

@@ -2,6 +2,7 @@
 using System.Linq;
 using System.Text;
 using System.Threading;
+using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 using System.Collections.Generic;
 using System.Globalization;
@@ -1066,6 +1067,54 @@ namespace Renci.SshNet.Sftp
 
         #endregion
 
+        /// <summary>
+        /// Calculates the optimal size of the buffer to read data from the channel.
+        /// </summary>
+        /// <param name="bufferSize">The buffer size configured on the client.</param>
+        /// <returns>
+        /// The optimal size of the buffer to read data from the channel.
+        /// </returns>
+        internal uint CalculateOptimalReadLength(uint bufferSize)
+        {
+            // a SSH_FXP_DATA message has 13 bytes of protocol fields:
+            // bytes 1 to 4: packet length
+            // byte 5: message type
+            // bytes 6 to 9: response id
+            // bytes 10 to 13: length of payload‏
+            //
+            // most ssh servers limit the size of the payload of a SSH_MSG_CHANNEL_DATA
+            // response to 16 KB; if we requested 16 KB of data, then the SSH_FXP_DATA
+            // payload of the SSH_MSG_CHANNEL_DATA message would be too big (16 KB + 13 bytes), and
+            // as a result, the ssh server would split this into two responses:
+            // one containing 16384 bytes (13 bytes header, and 16371 bytes file data)
+            // and one with the remaining 13 bytes of file data
+            const uint lengthOfNonDataProtocolFields = 13u;
+            var maximumPacketSize = Channel.LocalPacketSize;
+            return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
+        }
+
+        /// <summary>
+        /// Calculates the optimal size of the buffer to write data on the channel.
+        /// </summary>
+        /// <param name="bufferSize">The buffer size configured on the client.</param>
+        /// <param name="handle">The file handle.</param>
+        /// <returns>
+        /// The optimal size of the buffer to write data on the channel.
+        /// </returns>
+        internal uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle)
+        {
+            // 1-4: package length of SSH_FXP_WRITE message
+            // 5: message type
+            // 6-9: request id
+            // 10-13: handle length
+            // <handle>
+            // 14-21: offset
+            // 22-25: data length
+            var lengthOfNonDataProtocolFields = 25u + (uint)handle.Length;
+            var maximumPacketSize = Channel.RemotePacketSize;
+            return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
+        }
+
         private SshException GetSftpException(SftpStatusResponse response)
         {
             if (response.StatusCode == StatusCodes.Ok)

+ 41 - 30
Renci.SshClient/Renci.SshNet/SftpClient.cs

@@ -55,11 +55,32 @@ namespace Renci.SshNet
         }
 
         /// <summary>
-        /// Gets or sets the size of the buffer.
+        /// Gets or sets the maximum size of the buffer in bytes.
         /// </summary>
         /// <value>
-        /// The size of the buffer. The default buffer size is 16384 bytes.
+        /// The size of the buffer. The default buffer size is 65536 bytes (64 KB).
         /// </value>
+        /// <remarks>
+        /// <para>
+        /// For write operations, this limits the size of the payload for
+        /// individual SSH_FXP_WRITE messages. The actual size is always
+        /// capped at the maximum packet size supported by the peer
+        /// (minus the size of protocol fields).
+        /// </para>
+        /// <para>
+        /// For read operations, this controls the size of the payload which
+        /// is requested from the peer in each SSH_FXP_READ message. The peer
+        /// will send the requested number of bytes in one or more SSH_FXP_DATA
+        /// messages. To optimize the size of the SSH packets sent by the peer,
+        /// the actual requested size will take into account the size of the
+        /// SSH_FXP_DATA protocol fields.
+        /// </para>
+        /// <para>
+        /// The size of the each indivual SSH_FXP_DATA message is limited to the
+        /// local maximum packet size of the channel, which is set to <c>64 KB</c>
+        /// for SSH.NET. However, the peer can limit this even further.
+        /// </para>
+        /// </remarks>
         /// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
         public uint BufferSize
         {
@@ -191,7 +212,7 @@ namespace Renci.SshNet
             : base(connectionInfo, ownsConnectionInfo)
         {
             this.OperationTimeout = new TimeSpan(0, 0, 0, 0, -1);
-            this.BufferSize = 1024 * 16;
+            this.BufferSize = 1024 * 64;
         }
 
         #endregion
@@ -432,7 +453,7 @@ namespace Renci.SshNet
             {
                 try
                 {
-                    var result = this.InternalListDirectory(path, (count) =>
+                    var result = this.InternalListDirectory(path, count =>
                     {
                         asyncResult.Update(count);
 
@@ -629,7 +650,7 @@ namespace Renci.SshNet
             {
                 try
                 {
-                    this.InternalDownloadFile(path, output, asyncResult, (offset) =>
+                    this.InternalDownloadFile(path, output, asyncResult, offset =>
                     {
                         asyncResult.Update(offset);
 
@@ -830,7 +851,7 @@ namespace Renci.SshNet
             {
                 try
                 {
-                    this.InternalUploadFile(input, path, flags, asyncResult, (offset) =>
+                    this.InternalUploadFile(input, path, flags, asyncResult, offset =>
                     {
                         asyncResult.Update(offset);
 
@@ -1157,7 +1178,7 @@ namespace Renci.SshNet
         {
             CheckDisposed();
 
-            return new SftpFileStream(this._sftpSession, path, mode, access);
+            return new SftpFileStream(this._sftpSession, path, mode, access, (int) _bufferSize);
         }
 
         /// <summary>
@@ -1195,7 +1216,8 @@ namespace Renci.SshNet
         {
             CheckDisposed();
 
-            return new SftpFileStream(this._sftpSession, path, FileMode.OpenOrCreate, FileAccess.Write);
+            return new SftpFileStream(this._sftpSession, path, FileMode.OpenOrCreate, FileAccess.Write,
+                (int) _bufferSize);
         }
 
         /// <summary>
@@ -1540,8 +1562,8 @@ namespace Renci.SshNet
                 //  Call callback to report number of files read
                 if (listCallback != null)
                 {
-                    //  Execute callback on different thread                
-                    this.ExecuteThread(() => { listCallback(result.Count); });
+                    //  Execute callback on different thread
+                    this.ExecuteThread(() => listCallback(result.Count));
                 }
 
                 files = this._sftpSession.RequestReadDir(handle);
@@ -1579,21 +1601,9 @@ namespace Renci.SshNet
 
             ulong offset = 0;
 
-            // a SSH_FXP_DATA message has 13 bytes of header info:
-            // bytes 1 to 4: packet length
-            // byte 5: message type
-            // bytes 6 to 9: response id
-            // bytes 10 to 13: length of payload‏
-            //
-            // some ssh servers limit the size of the payload of a SSH_MSG_CHANNEL_DATA
-            // response to 16 KB; if we requested 16 KB of data, then the SSH_FXP_DATA
-            // payload of the SSH_MSG_CHANNEL_DATA message would be too big (16 KB + 13 bytes), and
-            // as a result, the ssh server would split this into two responses:
-            // one containing 16384 bytes (13 bytes header, and 16371 bytes file data)
-            // and one with the remaining 13 bytes
-            var readLength = _bufferSize - 13;
-
-            var data = this._sftpSession.RequestRead(handle, offset, readLength);
+            var optimalReadLength = _sftpSession.CalculateOptimalReadLength(_bufferSize);
+
+            var data = this._sftpSession.RequestRead(handle, offset, optimalReadLength);
 
             //  Read data while available
             while (data.Length > 0)
@@ -1615,7 +1625,7 @@ namespace Renci.SshNet
                     this.ExecuteThread(() => { downloadCallback(offset); });
                 }
 
-                data = this._sftpSession.RequestRead(handle, offset, readLength);
+                data = this._sftpSession.RequestRead(handle, offset, optimalReadLength);
             }
 
             this._sftpSession.RequestClose(handle);
@@ -1649,7 +1659,8 @@ namespace Renci.SshNet
 
             ulong offset = 0;
 
-            var buffer = new byte[_bufferSize];
+            // create buffer of optimal length
+            var buffer = new byte[_sftpSession.CalculateOptimalWriteLength(_bufferSize, handle)];
 
             var bytesRead = input.Read(buffer, 0, buffer.Length);
             var expectedResponses = 0;
@@ -1663,7 +1674,7 @@ namespace Renci.SshNet
 
                 if (bytesRead > 0)
                 {
-                    if (bytesRead < _bufferSize)
+                    if (bytesRead < buffer.Length)
                     {
                         //  Replace buffer for last chunk of data
                         var data = new byte[bytesRead];
@@ -1672,7 +1683,7 @@ namespace Renci.SshNet
                     }
 
                     var writtenBytes = offset + (ulong)buffer.Length;
-                    this._sftpSession.RequestWrite(handle, offset, buffer, null, (s) =>
+                    this._sftpSession.RequestWrite(handle, offset, buffer, null, s =>
                     {
                         if (s.StatusCode == StatusCodes.Ok)
                         {
@@ -1683,7 +1694,7 @@ namespace Renci.SshNet
                             if (uploadCallback != null)
                             {
                                 //  Execute callback on different thread
-                                this.ExecuteThread(() => { uploadCallback(writtenBytes); });
+                                this.ExecuteThread(() => uploadCallback(writtenBytes));
                             }
                         }
                     });

+ 1 - 1
Renci.SshClient/Renci.SshNet/Shell.cs

@@ -119,7 +119,7 @@ namespace Renci.SshNet
                 this.Starting(this, new EventArgs());
             }
 
-            this._channel = this._session.CreateChannel<ChannelSession>();
+            this._channel = this._session.CreateClientChannel<ChannelSession>();
             this._channel.DataReceived += Channel_DataReceived;
             this._channel.ExtendedDataReceived += Channel_ExtendedDataReceived;
             this._channel.Closed += Channel_Closed;

+ 1 - 1
Renci.SshClient/Renci.SshNet/ShellStream.cs

@@ -63,7 +63,7 @@ namespace Renci.SshNet
             this._incoming = new Queue<byte>();
             this._outgoing = new Queue<byte>();
 
-            this._channel = this._session.CreateChannel<ChannelSession>();
+            this._channel = this._session.CreateClientChannel<ChannelSession>();
             this._channel.DataReceived += Channel_DataReceived;
             this._channel.Closed += Channel_Closed;
             this._session.Disconnected += Session_Disconnected;

+ 1 - 1
Renci.SshClient/Renci.SshNet/SshCommand.cs

@@ -346,7 +346,7 @@ namespace Renci.SshNet
 
         private void CreateChannel()
         {
-            this._channel = this._session.CreateChannel<ChannelSession>();
+            this._channel = this._session.CreateClientChannel<ChannelSession>();
             this._channel.DataReceived += Channel_DataReceived;
             this._channel.ExtendedDataReceived += Channel_ExtendedDataReceived;
             this._channel.RequestReceived += Channel_RequestReceived;

+ 11 - 9
Renci.SshClient/Renci.SshNet/SubsystemSession.cs

@@ -4,6 +4,7 @@ using System.Text;
 using System.Threading;
 using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
 
 namespace Renci.SshNet.Sftp
 {
@@ -35,9 +36,15 @@ namespace Renci.SshNet.Sftp
         public event EventHandler<EventArgs> Disconnected;
 
         /// <summary>
-        /// Gets the channel number.
+        /// Gets the channel associated with this session.
         /// </summary>
-        protected uint ChannelNumber { get; private set; }
+        /// <value>
+        /// The channel associated with this session.
+        /// </value>
+        internal ChannelSession Channel
+        {
+            get { return _channel; }
+        }
 
         /// <summary>
         /// Gets the character encoding to use.
@@ -52,7 +59,7 @@ namespace Renci.SshNet.Sftp
         /// <param name="operationTimeout">The operation timeout.</param>
         /// <param name="encoding">The character encoding to use.</param>
         /// <exception cref="ArgumentNullException"><paramref name="session" /> or <paramref name="subsystemName" /> or <paramref name="encoding"/>is null.</exception>
-        public SubsystemSession(Session session, string subsystemName, TimeSpan operationTimeout, Encoding encoding)
+        protected SubsystemSession(Session session, string subsystemName, TimeSpan operationTimeout, Encoding encoding)
         {
             if (session == null)
                 throw new ArgumentNullException("session");
@@ -72,19 +79,14 @@ namespace Renci.SshNet.Sftp
         /// </summary>
         public void Connect()
         {
-            this._channel = this._session.CreateChannel<ChannelSession>();
+            this._channel = this._session.CreateClientChannel<ChannelSession>();
 
             this._session.ErrorOccured += Session_ErrorOccured;
             this._session.Disconnected += Session_Disconnected;
             this._channel.DataReceived += Channel_DataReceived;
             this._channel.Closed += Channel_Closed;
-
             this._channel.Open();
-
-            this.ChannelNumber = this._channel.RemoteChannelNumber;
-
             this._channel.SendSubsystemRequest(_subsystemName);
-
             this.OnChannelOpen();
         }