|
|
@@ -1,4 +1,4 @@
|
|
|
-using System;
|
|
|
+ using System;
|
|
|
using System.Threading;
|
|
|
using Renci.SshNet.Common;
|
|
|
using Renci.SshNet.Messages;
|
|
|
@@ -360,7 +360,7 @@ namespace Renci.SshNet.Channels
|
|
|
/// <param name="message">The message.</param>
|
|
|
protected void SendMessage(Message message)
|
|
|
{
|
|
|
- // Send channel messages only while channel is open
|
|
|
+ // send channel messages only while channel is open
|
|
|
if (!this.IsOpen)
|
|
|
return;
|
|
|
|
|
|
@@ -373,99 +373,109 @@ namespace Renci.SshNet.Channels
|
|
|
/// <param name="message">The message to send.</param>
|
|
|
private void SendMessage(ChannelCloseMessage message)
|
|
|
{
|
|
|
- // Send channel messages only while channel is open
|
|
|
+ // send channel messages only while channel is open
|
|
|
if (!this.IsOpen)
|
|
|
return;
|
|
|
|
|
|
this._session.SendMessage(message);
|
|
|
|
|
|
- // When channel close message is sent channel considered to be closed
|
|
|
+ // when channel close message is sent channel considered to be closed
|
|
|
this.IsOpen = false;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 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>
|
|
|
- /// <exception cref="InvalidOperationException">The data of <paramref name="message"/> exceeds the maximum packet size of the channel.</exception>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// When the data of the message exceeds the maximum packet size or the remote window
|
|
|
+ /// size does not allow the full message to be sent, then this method will send the
|
|
|
+ /// data in multiple chunks and will only wait for the remote window size to be adjusted
|
|
|
+ /// when its zero.
|
|
|
+ /// </para>
|
|
|
+ /// <para>
|
|
|
+ /// This is done to support SSH servers will a small window size that do not agressively
|
|
|
+ /// increase their window size. We need to take into account that there may be SSH
|
|
|
+ /// servers that only increase their window size when it has reached zero.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
protected void SendMessage(ChannelDataMessage message)
|
|
|
{
|
|
|
- // Send channel messages only while channel is open
|
|
|
+ // send channel messages only while channel is open
|
|
|
if (!this.IsOpen)
|
|
|
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));
|
|
|
+ var totalDataLength = (uint) message.Data.Length;
|
|
|
+ var totalDataSent = 0u;
|
|
|
|
|
|
- do
|
|
|
+ var totalBytesToSend = totalDataLength;
|
|
|
+ while (totalBytesToSend > 0)
|
|
|
{
|
|
|
- lock (this._serverWindowSizeLock)
|
|
|
+ var dataThatCanBeSentInMessage = GetDataLengthThatCanBeSentInMessage(totalBytesToSend);
|
|
|
+ if (dataThatCanBeSentInMessage == totalDataLength)
|
|
|
{
|
|
|
- var serverWindowSize = this.RemoteWindowSize;
|
|
|
- if (serverWindowSize < messageLength)
|
|
|
- {
|
|
|
- // Wait for window to be big enough for this message
|
|
|
- this._channelServerWindowAdjustWaitHandle.Reset();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- this.RemoteWindowSize -= (uint)messageLength;
|
|
|
- break;
|
|
|
- }
|
|
|
+ // we can send the message in one chunk
|
|
|
+ this._session.SendMessage(message);
|
|
|
}
|
|
|
- // Wait for window to change
|
|
|
- this.WaitOnHandle(this._channelServerWindowAdjustWaitHandle);
|
|
|
- } while (true);
|
|
|
-
|
|
|
- this._session.SendMessage(message);
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // we need to send the message in multiple chunks
|
|
|
+ var dataToSend = new byte[dataThatCanBeSentInMessage];
|
|
|
+ Array.Copy(message.Data, totalDataSent, dataToSend, 0, dataThatCanBeSentInMessage);
|
|
|
+ this._session.SendMessage(new ChannelDataMessage(message.LocalChannelNumber, dataToSend));
|
|
|
+ }
|
|
|
+ totalDataSent += dataThatCanBeSentInMessage;
|
|
|
+ totalBytesToSend -= dataThatCanBeSentInMessage;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Sends channel extended data message to the servers.
|
|
|
/// </summary>
|
|
|
- /// <remarks>This method takes care of managing the window size.</remarks>
|
|
|
/// <param name="message">Channel data message.</param>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// When the data of the message exceeds the maximum packet size or the remote window
|
|
|
+ /// size does not allow the full message to be sent, then this method will send the
|
|
|
+ /// data in multiple chunks and will only wait for the remote window size to be adjusted
|
|
|
+ /// when its zero.
|
|
|
+ /// </para>
|
|
|
+ /// <para>
|
|
|
+ /// This is done to support SSH servers will a small window size that do not agressively
|
|
|
+ /// increase their window size. We need to take into account that there may be SSH
|
|
|
+ /// servers that only increase their window size when it has reached zero.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
protected void SendMessage(ChannelExtendedDataMessage message)
|
|
|
{
|
|
|
- // Send channel messages only while channel is open
|
|
|
+ // end channel messages only while channel is open
|
|
|
if (!this.IsOpen)
|
|
|
return;
|
|
|
|
|
|
- var messageLength = message.Data.Length;
|
|
|
- do
|
|
|
+ var totalDataLength = (uint) message.Data.Length;
|
|
|
+ var totalDataSent = 0u;
|
|
|
+
|
|
|
+ var totalBytesToSend = totalDataLength;
|
|
|
+ while (totalBytesToSend > 0)
|
|
|
{
|
|
|
- lock (this._serverWindowSizeLock)
|
|
|
+ var dataThatCanBeSentInMessage = GetDataLengthThatCanBeSentInMessage(totalBytesToSend);
|
|
|
+ if (dataThatCanBeSentInMessage == totalDataLength)
|
|
|
{
|
|
|
- var serverWindowSize = this.RemoteWindowSize;
|
|
|
- if (serverWindowSize < messageLength)
|
|
|
- {
|
|
|
- // Wait for window to be big enough for this message
|
|
|
- this._channelServerWindowAdjustWaitHandle.Reset();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- this.RemoteWindowSize -= (uint)messageLength;
|
|
|
- break;
|
|
|
- }
|
|
|
+ // we can send the message in one chunk
|
|
|
+ this._session.SendMessage(message);
|
|
|
}
|
|
|
- // Wait for window to change
|
|
|
- this.WaitOnHandle(this._channelServerWindowAdjustWaitHandle);
|
|
|
- } while (true);
|
|
|
-
|
|
|
- this._session.SendMessage(message);
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // we need to send the message in multiple chunks
|
|
|
+ var dataToSend = new byte[dataThatCanBeSentInMessage];
|
|
|
+ Array.Copy(message.Data, totalDataSent, dataToSend, 0, dataThatCanBeSentInMessage);
|
|
|
+ this._session.SendMessage(new ChannelExtendedDataMessage(message.LocalChannelNumber,
|
|
|
+ message.DataTypeCode, dataToSend));
|
|
|
+ }
|
|
|
+ totalDataSent += dataThatCanBeSentInMessage;
|
|
|
+ totalBytesToSend -= dataThatCanBeSentInMessage;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -479,8 +489,8 @@ namespace Renci.SshNet.Channels
|
|
|
|
|
|
protected virtual void Close(bool wait)
|
|
|
{
|
|
|
- // Send message to close the channel on the server
|
|
|
- // Ignore sending close message when client not connected
|
|
|
+ // send message to close the channel on the server
|
|
|
+ // ignore sending close message when client not connected
|
|
|
if (!_closeMessageSent && this.IsConnected)
|
|
|
{
|
|
|
lock (this)
|
|
|
@@ -498,7 +508,7 @@ namespace Renci.SshNet.Channels
|
|
|
IsOpen = false;
|
|
|
}
|
|
|
|
|
|
- // Wait for channel to be closed
|
|
|
+ // wait for channel to be closed
|
|
|
if (wait)
|
|
|
{
|
|
|
WaitOnHandle(this._channelClosedWaitHandle);
|
|
|
@@ -637,6 +647,38 @@ namespace Renci.SshNet.Channels
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Determines the length of data that currently can be sent in a single message.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="messageLength">The length of the message that must be sent.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// The actual data length that currently can be sent.
|
|
|
+ /// </returns>
|
|
|
+ private uint GetDataLengthThatCanBeSentInMessage(uint messageLength)
|
|
|
+ {
|
|
|
+ do
|
|
|
+ {
|
|
|
+ lock (this._serverWindowSizeLock)
|
|
|
+ {
|
|
|
+ var serverWindowSize = RemoteWindowSize;
|
|
|
+ if (serverWindowSize == 0)
|
|
|
+ {
|
|
|
+ // allow us to be signal when remote window size is adjusted
|
|
|
+ this._channelServerWindowAdjustWaitHandle.Reset();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var bytesThatCanBeSent = Math.Min(Math.Min(RemotePacketSize, messageLength),
|
|
|
+ serverWindowSize);
|
|
|
+ this.RemoteWindowSize -= bytesThatCanBeSent;
|
|
|
+ return bytesThatCanBeSent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // wait for remote window size to change
|
|
|
+ this.WaitOnHandle(this._channelServerWindowAdjustWaitHandle);
|
|
|
+ } while (true);
|
|
|
+ }
|
|
|
+
|
|
|
private InvalidOperationException CreateRemoteChannelInfoNotAvailableException()
|
|
|
{
|
|
|
throw new InvalidOperationException("The channel has not been opened, or the open has not yet been confirmed.");
|