mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 01:05:42 +00:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+4
-4
@@ -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);
|
||||
|
||||
+4
-4
@@ -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);
|
||||
|
||||
+3
-3
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user