Use the read buffer in UploadFile for the SFTP write packets (#1798)

* Use the read buffer in UploadFile for the SFTP write packets

In SftpClient.UploadFile, a buffer is allocated to read from the given stream, and for
each read, another array is allocated for the SFTP write packet (which consists of that
data prepended with headers). This change effectively leaves space at the start of the
buffer for the headers such that it can be used to assemble the packets without that
per-packet array allocation.

There are cleaner/more general ways to do this (e.g. for all packet types, leave space
for the SSH headers as well), but this gets the most impact for about as much effort as
I can be bothered with.

* Rent from pool
This commit is contained in:
Rob Hague
2026-06-26 18:20:53 +02:00
committed by GitHub
parent 41053cb80f
commit fa98d59384
14 changed files with 322 additions and 123 deletions
+9 -3
View File
@@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Renci.SshNet.Common;
using Renci.SshNet.Sftp.Requests;
using Renci.SshNet.Sftp.Responses;
namespace Renci.SshNet.Sftp
@@ -325,14 +326,19 @@ namespace Renci.SshNet.Sftp
/// <param name="offset">the zero-based offset in <paramref name="data" /> at which to begin taking bytes to write.</param>
/// <param name="length">The length (in bytes) of the data to write.</param>
/// <param name="wait">The wait event handle if needed.</param>
/// <param name="writeCompleted">The callback to invoke when the write has completed.</param>
void RequestWrite(byte[] handle,
ulong serverOffset,
byte[] data,
int offset,
int length,
AutoResetEvent wait,
Action<SftpStatusResponse> writeCompleted = null);
AutoResetEvent wait);
/// <summary>
/// Performs SSH_FXP_WRITE request.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="writeCompleted">The callback to invoke when the write has completed.</param>
void RequestWrite(SftpWriteRequestBuffer buffer, Action<SftpStatusResponse> writeCompleted);
/// <summary>
/// Asynchronouly performs a <c>SSH_FXP_WRITE</c> request.
@@ -1,17 +1,27 @@
using System;
using System.Buffers.Binary;
using Renci.SshNet.Common;
using Renci.SshNet.Sftp.Responses;
namespace Renci.SshNet.Sftp.Requests
{
internal sealed class SftpWriteRequest : SftpRequest
{
private readonly SftpWriteRequestBuffer _buffer;
public override SftpMessageTypes SftpMessageType
{
get { return SftpMessageTypes.Write; }
}
public byte[] Handle { get; private set; }
public ReadOnlySpan<byte> Handle
{
get
{
return _buffer.Handle;
}
}
/// <summary>
/// Gets the zero-based offset (in bytes) relative to the beginning of the file that the write
@@ -21,7 +31,13 @@ namespace Renci.SshNet.Sftp.Requests
/// The zero-based offset (in bytes) relative to the beginning of the file that the write must
/// start at.
/// </value>
public ulong ServerFileOffset { get; private set; }
public ulong ServerFileOffset
{
get
{
return _buffer.ServerFileOffset;
}
}
/// <summary>
/// Gets the buffer holding the data to write.
@@ -29,74 +45,55 @@ namespace Renci.SshNet.Sftp.Requests
/// <value>
/// The buffer holding the data to write.
/// </value>
public byte[] Data { get; private set; }
/// <summary>
/// Gets the zero-based offset in <see cref="Data" /> at which to begin taking bytes to
/// write.
/// </summary>
/// <value>
/// The zero-based offset in <see cref="Data" /> at which to begin taking bytes to write.
/// </value>
public int Offset { get; private set; }
/// <summary>
/// Gets the length (in bytes) of the data to write.
/// </summary>
/// <value>
/// The length (in bytes) of the data to write.
/// </value>
public int Length { get; private set; }
public ReadOnlySpan<byte> Data
{
get
{
return _buffer.Data.AsSpan(0, _buffer.DataLength);
}
}
protected override int BufferCapacity
{
get
{
var capacity = base.BufferCapacity;
capacity += 4; // Handle length
capacity += Handle.Length; // Handle
capacity += 8; // ServerFileOffset length
capacity += 4; // Data length
capacity += Length; // Data
return capacity;
return _buffer.ActiveBytes.Count;
}
}
public SftpWriteRequest(uint protocolVersion,
uint requestId,
byte[] handle,
ulong serverFileOffset,
byte[] data,
int offset,
int length,
SftpWriteRequestBuffer buffer,
Action<SftpStatusResponse> statusAction)
: base(protocolVersion, requestId, statusAction)
: base(protocolVersion, buffer.RequestId, statusAction)
{
Handle = handle;
ServerFileOffset = serverFileOffset;
Data = data;
Offset = offset;
Length = length;
_buffer = buffer;
}
protected override void LoadData()
{
base.LoadData();
Handle = ReadBinary();
ServerFileOffset = ReadUInt64();
Data = ReadBinary();
Offset = 0;
Length = Data.Length;
throw new NotImplementedException();
}
protected override void SaveData()
{
base.SaveData();
throw new NotImplementedException();
}
WriteBinaryString(Handle);
Write(ServerFileOffset);
WriteBinary(Data, Offset, Length);
protected override void WriteBytes(SshDataStream stream)
{
var activeBuffer = GetBytes();
stream.Write(activeBuffer.Array, activeBuffer.Offset, activeBuffer.Count);
}
public new ArraySegment<byte> GetBytes()
{
var activeBuffer = _buffer.ActiveBytes;
// Write SFTP packet length.
BinaryPrimitives.WriteInt32BigEndian(activeBuffer.AsSpan(), activeBuffer.Count - 4);
return activeBuffer;
}
}
}
@@ -0,0 +1,149 @@
#nullable enable
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
namespace Renci.SshNet.Sftp.Requests
{
/// <summary>
/// A helper type that wraps a buffer for SFTP write requests.
/// </summary>
/// <remarks>
/// [Sftp packet length, SftpMessageType, RequestId, Handle length, Handle, Server offset, data length, data].
/// [ 4, 1, 4, 4, ?, 8, 4, ?].
/// </remarks>
internal sealed class SftpWriteRequestBuffer : IDisposable
{
private const int MessageTypeOffset = 4;
private const int RequestIdOffset = MessageTypeOffset + 1;
private const int HandleLengthOffset = RequestIdOffset + 4;
private const int HandleOffset = HandleLengthOffset + 4;
private readonly bool _usePool;
private byte[] _buffer;
public ArraySegment<byte> ActiveBytes
{
get
{
return new(_buffer, 0, HandleOffset + HandleLength + 8 + 4 + DataLength);
}
}
public SftpWriteRequestBuffer(ReadOnlySpan<byte> handle, int dataCapacity, bool usePool = false)
{
Debug.Assert(dataCapacity >= 0);
var totalCapacity = HandleOffset + handle.Length + 8 + 4 + dataCapacity;
_usePool = usePool;
_buffer = usePool
? ArrayPool<byte>.Shared.Rent(totalCapacity)
: new byte[totalCapacity];
_buffer[MessageTypeOffset] = (byte)SftpMessageTypes.Write;
HandleLength = handle.Length;
handle.CopyTo(_buffer.AsSpan(HandleOffset));
}
public SftpWriteRequestBuffer(ReadOnlySpan<byte> handle, ulong serverFileOffset, ReadOnlySpan<byte> data, bool usePool = false)
: this(handle, data.Length, usePool)
{
ServerFileOffset = serverFileOffset;
DataLength = data.Length;
data.CopyTo(Data);
}
public uint RequestId
{
get
{
return BinaryPrimitives.ReadUInt32BigEndian(_buffer.AsSpan(RequestIdOffset));
}
set
{
BinaryPrimitives.WriteUInt32BigEndian(_buffer.AsSpan(RequestIdOffset), value);
}
}
public int HandleLength
{
get
{
return BinaryPrimitives.ReadInt32BigEndian(_buffer.AsSpan(HandleLengthOffset));
}
private init
{
Debug.Assert(value >= 0);
BinaryPrimitives.WriteInt32BigEndian(_buffer.AsSpan(HandleLengthOffset), value);
}
}
public ReadOnlySpan<byte> Handle
{
get
{
return _buffer.AsSpan(HandleOffset, HandleLength);
}
}
public ulong ServerFileOffset
{
get
{
return BinaryPrimitives.ReadUInt64BigEndian(_buffer.AsSpan(HandleOffset + HandleLength));
}
set
{
BinaryPrimitives.WriteUInt64BigEndian(_buffer.AsSpan(HandleOffset + HandleLength), value);
}
}
public int DataLength
{
get
{
return BinaryPrimitives.ReadInt32BigEndian(_buffer.AsSpan(HandleOffset + HandleLength + 8));
}
set
{
Debug.Assert(value >= 0);
Debug.Assert(value <= _buffer.Length - (HandleOffset + HandleLength + 8 + 4));
BinaryPrimitives.WriteInt32BigEndian(_buffer.AsSpan(HandleOffset + HandleLength + 8), value);
}
}
/// <summary>
/// Gets the space available to write as file data. Does not consider <see cref="DataLength"/>.
/// </summary>
public ArraySegment<byte> Data
{
get
{
var offset = HandleOffset + HandleLength + 8 + 4;
return new ArraySegment<byte>(_buffer, offset, _buffer.Length - offset);
}
}
public void Dispose()
{
if (_usePool)
{
var buffer = _buffer;
_buffer = null!;
if (buffer is not null)
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
}
}
}
+46 -27
View File
@@ -87,8 +87,16 @@ namespace Renci.SshNet.Sftp
internal void SendMessage(SftpMessage sftpMessage)
{
var data = sftpMessage.GetBytes();
SendData(data);
if (sftpMessage is SftpWriteRequest writeRequest)
{
var data = writeRequest.GetBytes();
SendData(data.Array, data.Offset, data.Count);
}
else
{
var data = sftpMessage.GetBytes();
SendData(data);
}
}
/// <inheritdoc/>
@@ -579,20 +587,31 @@ namespace Renci.SshNet.Sftp
byte[] data,
int offset,
int length,
AutoResetEvent wait)
{
using var buffer = new SftpWriteRequestBuffer(handle, serverOffset, data.AsSpan(offset, length), usePool: true);
RequestWrite(buffer, wait, writeCompleted: null);
}
/// <inheritdoc/>
public void RequestWrite(SftpWriteRequestBuffer buffer, Action<SftpStatusResponse> writeCompleted)
{
RequestWrite(buffer, wait: null, writeCompleted);
}
private void RequestWrite(SftpWriteRequestBuffer buffer,
AutoResetEvent wait,
Action<SftpStatusResponse> writeCompleted = null)
Action<SftpStatusResponse> writeCompleted)
{
Debug.Assert((wait is null) != (writeCompleted is null), "Should have one parameter or the other.");
SftpException exception = null;
buffer.RequestId = NextRequestId;
var request = new SftpWriteRequest(ProtocolVersion,
NextRequestId,
handle,
serverOffset,
data,
offset,
length,
buffer,
response =>
{
if (writeCompleted is not null)
@@ -629,24 +648,24 @@ namespace Renci.SshNet.Sftp
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
SendRequest(new SftpWriteRequest(ProtocolVersion,
NextRequestId,
handle,
serverOffset,
data,
offset,
length,
response =>
{
if (response.StatusCode == StatusCode.Ok)
{
_ = tcs.TrySetResult(true);
}
else
{
_ = tcs.TrySetException(GetSftpException(response));
}
}));
using (var buffer = new SftpWriteRequestBuffer(handle, serverOffset, data.AsSpan(offset, length), usePool: true))
{
buffer.RequestId = NextRequestId;
SendRequest(new SftpWriteRequest(ProtocolVersion,
buffer,
response =>
{
if (response.StatusCode == StatusCode.Ok)
{
_ = tcs.TrySetResult(true);
}
else
{
_ = tcs.TrySetException(GetSftpException(response));
}
}));
}
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
}
+15 -5
View File
@@ -16,6 +16,7 @@ using System.Threading.Tasks;
using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;
using Renci.SshNet.Sftp;
using Renci.SshNet.Sftp.Requests;
namespace Renci.SshNet
{
@@ -2477,7 +2478,13 @@ namespace Renci.SshNet
ulong offset = 0;
// create buffer of optimal length
var buffer = new byte[_sftpSession.CalculateOptimalWriteLength(_bufferSize, handle)];
var dataCapacity = (int)_sftpSession.CalculateOptimalWriteLength(_bufferSize, handle);
using var buffer = new SftpWriteRequestBuffer(handle, dataCapacity, usePool: true);
var dataBuffer = buffer.Data;
Debug.Assert(dataBuffer.Count >= dataCapacity);
var expectedResponses = 0;
@@ -2492,11 +2499,11 @@ namespace Renci.SshNet
{
var bytesRead = isAsync
#if NET
? await input.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)
? await input.ReadAsync(dataBuffer.AsMemory(0, dataCapacity), cancellationToken).ConfigureAwait(false)
#else
? await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)
? await input.ReadAsync(dataBuffer.Array, dataBuffer.Offset, dataCapacity, cancellationToken).ConfigureAwait(false)
#endif
: input.Read(buffer, 0, buffer.Length);
: input.Read(dataBuffer.Array!, dataBuffer.Offset, dataCapacity);
if (bytesRead == 0)
{
@@ -2510,12 +2517,15 @@ namespace Renci.SshNet
exception?.Throw();
buffer.ServerFileOffset = offset;
buffer.DataLength = bytesRead;
var writtenBytes = offset + (ulong)bytesRead;
_ = Interlocked.Increment(ref expectedResponses);
mres.Reset();
_sftpSession.RequestWrite(handle, offset, buffer, offset: 0, bytesRead, wait: null, s =>
_sftpSession.RequestWrite(buffer, s =>
{
var setHandle = false;
+12 -1
View File
@@ -165,11 +165,22 @@ namespace Renci.SshNet
/// </summary>
/// <param name="data">The data to be sent.</param>
public void SendData(byte[] data)
{
SendData(data, 0, data.Length);
}
/// <summary>
/// Sends data to the subsystem.
/// </summary>
/// <param name="data">The data to be sent.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="data"/> at which to begin sending bytes.</param>
/// <param name="count">The number of bytes to send.</param>
public void SendData(byte[] data, int offset, int count)
{
ObjectDisposedException.ThrowIf(_isDisposed, this);
EnsureSessionIsOpen();
_channel.SendData(data);
_channel.SendData(data, offset, count);
}
/// <summary>
@@ -41,12 +41,16 @@ namespace Renci.SshNet.Tests.Classes.Sftp.Requests
[TestMethod]
public void Constructor()
{
var request = new SftpWriteRequest(_protocolVersion, _requestId, _handle, _serverFileOffset, _data, _offset, _length, null);
var request = new SftpWriteRequest(
_protocolVersion,
new SftpWriteRequestBuffer(_handle, _serverFileOffset, _data.AsSpan(_offset, _length))
{
RequestId = _requestId
},
statusAction: null);
Assert.AreSame(_data, request.Data);
Assert.AreSame(_handle, request.Handle);
Assert.AreEqual(_length, request.Length);
Assert.AreEqual(_offset, request.Offset);
CollectionAssert.AreEqual(_data.Take(_offset, _length), request.Data.ToArray());
CollectionAssert.AreEqual(_handle, request.Handle.ToArray());
Assert.AreEqual(_protocolVersion, request.ProtocolVersion);
Assert.AreEqual(_requestId, request.RequestId);
Assert.AreEqual(_serverFileOffset, request.ServerFileOffset);
@@ -62,12 +66,10 @@ namespace Renci.SshNet.Tests.Classes.Sftp.Requests
var request = new SftpWriteRequest(
_protocolVersion,
_requestId,
_handle,
_serverFileOffset,
_data,
_offset,
_length,
new SftpWriteRequestBuffer(_handle, _serverFileOffset, _data.AsSpan(_offset, _length))
{
RequestId = _requestId
},
statusAction);
request.Complete(statusResponse);
@@ -79,9 +81,15 @@ namespace Renci.SshNet.Tests.Classes.Sftp.Requests
[TestMethod]
public void GetBytes()
{
var request = new SftpWriteRequest(_protocolVersion, _requestId, _handle, _serverFileOffset, _data, _offset, _length, null);
var request = new SftpWriteRequest(
_protocolVersion,
new SftpWriteRequestBuffer(_handle, _serverFileOffset, _data.AsSpan(_offset, _length))
{
RequestId = _requestId
},
statusAction: null);
var bytes = request.GetBytes();
var bytes = ((SftpRequest)request).GetBytes();
var expectedBytesLength = 0;
expectedBytesLength += 4; // Length
@@ -114,6 +122,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp.Requests
CollectionAssert.AreEqual(_data.Take(_offset, _length), actualData);
Assert.IsTrue(sshDataStream.IsEndOfData);
CollectionAssert.AreEqual(bytes, request.GetBytes().ToArray());
}
}
}
@@ -11,7 +11,6 @@ using Moq;
using Renci.SshNet.Common;
using Renci.SshNet.Sftp;
using Renci.SshNet.Sftp.Responses;
namespace Renci.SshNet.Tests.Classes.Sftp
{
@@ -240,8 +239,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
It.IsAny<byte[]>(),
It.IsAny<int>(),
It.IsAny<int>(),
It.IsAny<AutoResetEvent>(),
It.IsAny<Action<SftpStatusResponse>>()),
It.IsAny<AutoResetEvent>()),
Times.Never);
// Whatever is called here should trigger the bytes to be sent
@@ -336,8 +334,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
/* data: */ It.Is<byte[]>(x => IndexOf(x, newData) >= 0),
/* offset: */ It.IsAny<int>(),
/* length: */ newData.Length,
/* wait: */ It.IsAny<AutoResetEvent>(),
/* writeCompleted: */ It.IsAny<Action<SftpStatusResponse>>()),
/* wait: */ It.IsAny<AutoResetEvent>()),
Times.Once);
}
@@ -114,14 +114,14 @@ namespace Renci.SshNet.Tests.Classes.Sftp
_channelSessionMock.InSequence(sequence).Setup(p => p.Open());
_channelSessionMock.InSequence(sequence).Setup(p => p.SendSubsystemRequest("sftp")).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpInitRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpInitRequestBytes, 0, _sftpInitRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
new ChannelDataEventArgs(0, _sftpVersionResponse.GetBytes()));
});
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpRealPathRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpRealPathRequestBytes, 0, _sftpRealPathRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -131,7 +131,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
#endregion SftpSession.Connect()
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpReadRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpReadRequestBytes, 0, _sftpReadRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(
@@ -115,7 +115,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
.Setup(p => p.IsOpen)
.Returns(true);
_ = _channelSessionMock.InSequence(sequence)
.Setup(p => p.SendData(_sftpInitRequestBytes))
.Setup(p => p.SendData(_sftpInitRequestBytes, 0, _sftpInitRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -125,7 +125,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
.Setup(p => p.IsOpen)
.Returns(true);
_ = _channelSessionMock.InSequence(sequence)
.Setup(p => p.SendData(_sftpRealPathRequestBytes))
.Setup(p => p.SendData(_sftpRealPathRequestBytes, 0, _sftpRealPathRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -138,7 +138,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
.Setup(p => p.IsOpen)
.Returns(true);
_ = _channelSessionMock.InSequence(sequence)
.Setup(p => p.SendData(_sftpStatVfsRequestBytes))
.Setup(p => p.SendData(_sftpStatVfsRequestBytes, 0, _sftpStatVfsRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -132,7 +132,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
_channelSessionMock.InSequence(sequence).Setup(p => p.Open());
_channelSessionMock.InSequence(sequence).Setup(p => p.SendSubsystemRequest("sftp")).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpInitRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpInitRequestBytes, 0, _sftpInitRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -142,7 +142,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
.Setup(p => p.Create(0U, (byte)SftpMessageTypes.Version, _encoding))
.Returns(_sftpVersionResponse);
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpRealPathRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpRealPathRequestBytes, 0, _sftpRealPathRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -155,9 +155,9 @@ namespace Renci.SshNet.Tests.Classes.Sftp
#endregion SftpSession.Connect()
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpOpenRequestBytes));
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpOpenRequestBytes, 0, _sftpOpenRequestBytes.Length));
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpReadRequestBytes)).Callback(() =>
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpReadRequestBytes, 0, _sftpReadRequestBytes.Length)).Callback(() =>
{
var sshMessagePayload = new byte[_sftpHandleResponseBytes.Length + _sftpDataResponseBytes.Length];
Buffer.BlockCopy(_sftpHandleResponseBytes, 0, sshMessagePayload, 0, _sftpHandleResponseBytes.Length);
@@ -130,7 +130,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
_channelSessionMock.InSequence(sequence).Setup(p => p.Open());
_channelSessionMock.InSequence(sequence).Setup(p => p.SendSubsystemRequest("sftp")).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpInitRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpInitRequestBytes, 0, _sftpInitRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -140,7 +140,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
.Setup(p => p.Create(0U, (byte)SftpMessageTypes.Version, _encoding))
.Returns(_sftpVersionResponse);
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpRealPathRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpRealPathRequestBytes, 0, _sftpRealPathRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -153,7 +153,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
#endregion SftpSession.Connect()
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpOpenRequestBytes)).Callback(() =>
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpOpenRequestBytes, 0, _sftpOpenRequestBytes.Length)).Callback(() =>
{
var sshMessagePayload = new byte[_sftpHandleResponseBytes.Length + 40];
Buffer.BlockCopy(_sftpHandleResponseBytes, 0, sshMessagePayload, 0, _sftpHandleResponseBytes.Length);
@@ -166,7 +166,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
.Setup(p => p.Create(_protocolVersion, (byte)SftpMessageTypes.Handle, _encoding))
.Returns(new SftpHandleResponse(_protocolVersion));
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpReadRequestBytes)).Callback(() =>
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpReadRequestBytes, 0, _sftpReadRequestBytes.Length)).Callback(() =>
{
var sshMessagePayload = new byte[_sftpDataResponseBytes.Length - 40];
Buffer.BlockCopy(_sftpDataResponseBytes, 40, sshMessagePayload, 0, _sftpDataResponseBytes.Length - 40);
@@ -113,7 +113,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
_channelSessionMock.InSequence(sequence).Setup(p => p.Open());
_channelSessionMock.InSequence(sequence).Setup(p => p.SendSubsystemRequest("sftp")).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpInitRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpInitRequestBytes, 0, _sftpInitRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -123,7 +123,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
.Setup(p => p.Create(0U, (byte)SftpMessageTypes.Version, _encoding))
.Returns(_sftpVersionResponse);
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpRealPathRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpRealPathRequestBytes, 0, _sftpRealPathRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -136,7 +136,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
#endregion SftpSession.Connect()
_channelSessionMock.InSequence(sequence).Setup(p => p.IsOpen).Returns(true);
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpReadRequestBytes))
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(_sftpReadRequestBytes, 0, _sftpReadRequestBytes.Length))
.Callback(() =>
{
_channelSessionMock.Raise(c => c.DataReceived += null,
@@ -50,7 +50,7 @@ namespace Renci.SshNet.Tests.Classes
_channelMock.InSequence(_sequence).Setup(p => p.Open());
_channelMock.InSequence(_sequence).Setup(p => p.SendSubsystemRequest(_subsystemName)).Returns(true);
_channelMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
_channelMock.InSequence(_sequence).Setup(p => p.SendData(_data));
_channelMock.InSequence(_sequence).Setup(p => p.SendData(_data, 0, _data.Length));
_subsystemSession = new SubsystemSessionStub(
_sessionMock.Object,
@@ -81,7 +81,7 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
public void SendDataOnChannelShouldBeInvokedOnce()
{
_channelMock.Verify(p => p.SendData(_data), Times.Once);
_channelMock.Verify(p => p.SendData(_data, 0, _data.Length), Times.Once);
}
[TestMethod]