diff --git a/src/Renci.SshNet.NETCore/Renci.SshNet.NETCore.csproj b/src/Renci.SshNet.NETCore/Renci.SshNet.NETCore.csproj
index c06afc025..6f2178443 100644
--- a/src/Renci.SshNet.NETCore/Renci.SshNet.NETCore.csproj
+++ b/src/Renci.SshNet.NETCore/Renci.SshNet.NETCore.csproj
@@ -6,6 +6,7 @@
true
Renci.SshNet
../Renci.SshNet.snk
+ 5
true
true
false
diff --git a/src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj b/src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj
index 2c350023e..928789069 100644
--- a/src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj
+++ b/src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj
@@ -1202,8 +1202,29 @@
Classes\Sftp\SftpFileStreamTest_ReadByte_ReadMode_NoDataInWriteBufferAndNoDataInReadBuffer_LessDataThanReadBufferSizeAvailable.cs
-
- Classes\Sftp\SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesThanCount.cs
+
+ Classes\Sftp\SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndEqualToBufferSize.cs
+
+
+ Classes\Sftp\SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndLessThanBufferSize.cs
+
+
+ Classes\Sftp\SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesFromServerThanCount.cs
+
+
+ Classes\Sftp\SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetNegative.cs
+
+
+ Classes\Sftp\SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetPositive.cs
+
+
+ Classes\Sftp\SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetZero.cs
+
+
+ Classes\Sftp\SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_NoBuffering.cs
+
+
+ Classes\Sftp\SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_ReadBuffer.cs
Classes\Sftp\SftpFileStreamTest_SetLength_Closed.cs
@@ -1223,6 +1244,9 @@
Classes\Sftp\SftpFileStreamTest_SetLength_SessionOpen_FIleAccessWrite.cs
+
+ Classes\Sftp\SftpFileStreamTest_Write_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs
+
Classes\Sftp\SftpFileSystemInformationTest.cs
diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs
index ff6038744..262668d7e 100644
--- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs
+++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs
@@ -11,8 +11,10 @@ public class BaseClientTest_Connected_KeepAlivesNotSentConcurrently
{
private Mock _serviceFactoryMock;
private Mock _sessionMock;
+ private MockSequence _mockSequence;
private BaseClient _client;
private ConnectionInfo _connectionInfo;
+ private ManualResetEvent _keepAliveSent;
[TestInitialize]
public void Setup()
@@ -24,20 +26,33 @@ public void Setup()
[TestCleanup]
public void Cleanup()
{
+ if (_client != null)
+ {
+ _sessionMock.InSequence(_mockSequence).Setup(p => p.OnDisconnecting());
+ _sessionMock.InSequence(_mockSequence).Setup(p => p.Dispose());
+ _client.Dispose();
+ }
}
protected void Arrange()
{
_connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd"));
+ _keepAliveSent = new ManualResetEvent(false);
_serviceFactoryMock = new Mock(MockBehavior.Strict);
_sessionMock = new Mock(MockBehavior.Strict);
- _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object);
- _sessionMock.Setup(p => p.Connect());
- _sessionMock.Setup(p => p.TrySendMessage(It.IsAny()))
+ _mockSequence = new MockSequence();
+
+ _serviceFactoryMock.InSequence(_mockSequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object);
+ _sessionMock.InSequence(_mockSequence).Setup(p => p.Connect());
+ _sessionMock.InSequence(_mockSequence).Setup(p => p.TrySendMessage(It.IsAny()))
.Returns(true)
- .Callback(() => Thread.Sleep(300));
+ .Callback(() =>
+ {
+ Thread.Sleep(300);
+ _keepAliveSent.Set();
+ });
_client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object)
{
@@ -51,6 +66,12 @@ protected void Act()
// should keep-alive message be sent concurrently, then multiple keep-alive
// message would be sent during this sleep period
Thread.Sleep(200);
+
+ // disable further keep-alives
+ _client.KeepAliveInterval = Session.InfiniteTimeSpan;
+
+ // wait until keep-alive has been sent at least once
+ Assert.IsTrue(_keepAliveSent.WaitOne(500));
}
[TestMethod]
diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndEqualToBufferSize.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndEqualToBufferSize.cs
new file mode 100644
index 000000000..5f6f24be9
--- /dev/null
+++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndEqualToBufferSize.cs
@@ -0,0 +1,155 @@
+using System;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+ [TestClass]
+ public class SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndEqualToBufferSize : SftpFileStreamTestBase
+ {
+ private string _path;
+ private SftpFileStream _target;
+ private byte[] _handle;
+ private uint _bufferSize;
+ private uint _readBufferSize;
+ private uint _writeBufferSize;
+ private int _actual;
+ private byte[] _buffer;
+ private byte[] _serverData1;
+ private byte[] _serverData2;
+ private int _serverData1Length;
+ private int _serverData2Length;
+ private int _numberOfBytesToRead;
+
+ protected override void SetupData()
+ {
+ base.SetupData();
+
+ var random = new Random();
+ _path = random.Next().ToString();
+ _handle = GenerateRandom(5, random);
+ _bufferSize = (uint)random.Next(1, 1000);
+ _readBufferSize = 20;
+ _writeBufferSize = 500;
+
+ _numberOfBytesToRead = (int) _readBufferSize + 5; // greather than read buffer size
+ _buffer = new byte[_numberOfBytesToRead];
+ _serverData1Length = (int) _readBufferSize; // equal to read buffer size
+ _serverData1 = GenerateRandom(_serverData1Length, random);
+ _serverData2Length = (int) _readBufferSize; // equal to read buffer size
+ _serverData2 = GenerateRandom(_serverData2Length, random);
+
+ Assert.IsTrue(_serverData1Length < _numberOfBytesToRead && _serverData1Length == _readBufferSize);
+ }
+
+ protected override void SetupMocks()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestOpen(_path, Flags.Read, false))
+ .Returns(_handle);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+ .Returns(_readBufferSize);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+ .Returns(_writeBufferSize);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.IsOpen)
+ .Returns(true);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestRead(_handle, 0UL, _readBufferSize))
+ .Returns(_serverData1);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestRead(_handle, (ulong)_serverData1.Length, _readBufferSize))
+ .Returns(_serverData2);
+ }
+
+ [TestCleanup]
+ public void TearDown()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestClose(_handle));
+ }
+
+ protected override void Arrange()
+ {
+ base.Arrange();
+
+ _target = new SftpFileStream(SftpSessionMock.Object,
+ _path,
+ FileMode.Open,
+ FileAccess.Read,
+ (int)_bufferSize);
+ }
+
+ protected override void Act()
+ {
+ _actual = _target.Read(_buffer, 0, _numberOfBytesToRead);
+ }
+
+ [TestMethod]
+ public void ReadShouldHaveReturnedTheNumberOfBytesRequested()
+ {
+ Assert.AreEqual(_numberOfBytesToRead, _actual);
+ }
+
+ [TestMethod]
+ public void ReadShouldHaveWrittenBytesToTheCallerSuppliedBuffer()
+ {
+ Assert.IsTrue(_serverData1.IsEqualTo(_buffer.Take(_serverData1Length)));
+
+ var bytesWrittenFromSecondRead = _numberOfBytesToRead - _serverData1Length;
+ Assert.IsTrue(_serverData2.Take(bytesWrittenFromSecondRead).IsEqualTo(_buffer.Take(_serverData1Length, bytesWrittenFromSecondRead)));
+ }
+
+ [TestMethod]
+ public void PositionShouldReturnNumberOfBytesWrittenToCallerProvidedBuffer()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+ Assert.AreEqual(_actual, _target.Position);
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ }
+
+ [TestMethod]
+ public void ReadShouldReturnAllRemaningBytesFromReadBufferWhenCountIsEqualToNumberOfRemainingBytes()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+ var numberOfBytesRemainingInReadBuffer = _serverData1Length + _serverData2Length - _numberOfBytesToRead;
+
+ _buffer = new byte[numberOfBytesRemainingInReadBuffer];
+
+ var actual = _target.Read(_buffer, 0, _buffer.Length);
+
+ Assert.AreEqual(_buffer.Length, actual);
+ Assert.IsTrue(_serverData2.Take(_numberOfBytesToRead - _serverData1Length, _buffer.Length).IsEqualTo(_buffer));
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ }
+
+ [TestMethod]
+ public void ReadShouldReturnAllRemaningBytesFromReadBufferAndReadAgainWhenCountIsGreaterThanNumberOfRemainingBytesAndNewReadReturnsZeroBytes()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestRead(_handle, (ulong)(_serverData1Length + _serverData2Length), _readBufferSize)).Returns(Array.Empty);
+
+ var numberOfBytesRemainingInReadBuffer = _serverData1Length + _serverData2Length - _numberOfBytesToRead;
+
+ _buffer = new byte[numberOfBytesRemainingInReadBuffer + 1];
+
+ var actual = _target.Read(_buffer, 0, _buffer.Length);
+
+ Assert.AreEqual(numberOfBytesRemainingInReadBuffer, actual);
+ Assert.IsTrue(_serverData2.Take(_numberOfBytesToRead - _serverData1Length, numberOfBytesRemainingInReadBuffer).IsEqualTo(_buffer.Take(numberOfBytesRemainingInReadBuffer)));
+ Assert.AreEqual(0, _buffer[numberOfBytesRemainingInReadBuffer]);
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ SftpSessionMock.Verify(p => p.RequestRead(_handle, (ulong)(_serverData1Length + _serverData2Length), _readBufferSize));
+ }
+ }
+}
diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndLessThanBufferSize.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndLessThanBufferSize.cs
new file mode 100644
index 000000000..85a6679b8
--- /dev/null
+++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndLessThanBufferSize.cs
@@ -0,0 +1,148 @@
+using System;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+ [TestClass]
+ public class SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadLessBytesFromServerThanCountAndLessThanBufferSize : SftpFileStreamTestBase
+ {
+ private string _path;
+ private SftpFileStream _target;
+ private byte[] _handle;
+ private uint _bufferSize;
+ private uint _readBufferSize;
+ private uint _writeBufferSize;
+ private int _actual;
+ private byte[] _buffer;
+ private byte[] _serverData;
+ private int _serverDataLength;
+ private int _numberOfBytesToRead;
+ private byte[] _originalBuffer;
+
+ protected override void SetupData()
+ {
+ base.SetupData();
+
+ var random = new Random();
+ _path = random.Next().ToString();
+ _handle = GenerateRandom(5, random);
+ _bufferSize = (uint)random.Next(1, 1000);
+ _readBufferSize = 20;
+ _writeBufferSize = 500;
+
+ _numberOfBytesToRead = (int) _readBufferSize + 2; // greater than read buffer size
+ _originalBuffer = GenerateRandom(_numberOfBytesToRead, random);
+ _buffer = _originalBuffer.Copy();
+
+ _serverDataLength = (int) _readBufferSize - 1; // less than read buffer size
+ _serverData = GenerateRandom(_serverDataLength, random);
+
+ Assert.IsTrue(_serverDataLength < _numberOfBytesToRead && _serverDataLength < _readBufferSize);
+ }
+
+ protected override void SetupMocks()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestOpen(_path, Flags.Read, false))
+ .Returns(_handle);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+ .Returns(_readBufferSize);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+ .Returns(_writeBufferSize);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.IsOpen)
+ .Returns(true);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestRead(_handle, 0UL, _readBufferSize))
+ .Returns(_serverData);
+ }
+
+ [TestCleanup]
+ public void TearDown()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestClose(_handle));
+ }
+
+ protected override void Arrange()
+ {
+ base.Arrange();
+
+ _target = new SftpFileStream(SftpSessionMock.Object,
+ _path,
+ FileMode.Open,
+ FileAccess.Read,
+ (int)_bufferSize);
+ }
+
+ protected override void Act()
+ {
+ _actual = _target.Read(_buffer, 0, _numberOfBytesToRead);
+ }
+
+ [TestMethod]
+ public void ReadShouldHaveReturnedTheNumberOfBytesReturnedByTheReadFromTheServer()
+ {
+ Assert.AreEqual(_serverDataLength, _actual);
+ }
+
+ [TestMethod]
+ public void ReadShouldHaveWrittenBytesToTheCallerSuppliedBufferAndRemainingBytesShouldRemainUntouched()
+ {
+ Assert.IsTrue(_serverData.IsEqualTo(_buffer.Take(_serverDataLength)));
+ Assert.IsTrue(_originalBuffer.Take(_serverDataLength, _originalBuffer.Length - _serverDataLength).IsEqualTo(_buffer.Take(_serverDataLength, _buffer.Length - _serverDataLength)));
+ }
+
+ [TestMethod]
+ public void PositionShouldReturnNumberOfBytesWrittenToCallerProvidedBuffer()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+ Assert.AreEqual(_actual, _target.Position);
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ }
+
+ [TestMethod]
+ public void SubsequentReadShouldReadAgainFromCurrentPositionFromServerAndReturnZeroWhenServerReturnsZeroBytes()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestRead(_handle, (ulong) _actual, _readBufferSize))
+ .Returns(Array.Empty);
+
+ var buffer = _originalBuffer.Copy();
+ var actual = _target.Read(buffer, 0, buffer.Length);
+
+ Assert.AreEqual(0, actual);
+ Assert.IsTrue(_originalBuffer.IsEqualTo(buffer));
+
+ SftpSessionMock.Verify(p => p.RequestRead(_handle, (ulong)_actual, _readBufferSize), Times.Once);
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ }
+
+ [TestMethod]
+ public void SubsequentReadShouldReadAgainFromCurrentPositionFromServerAndNotUpdatePositionWhenServerReturnsZeroBytes()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestRead(_handle, (ulong)_actual, _readBufferSize))
+ .Returns(Array.Empty);
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+ _target.Read(new byte[10], 0, 10);
+
+ Assert.AreEqual(_actual, _target.Position);
+
+ SftpSessionMock.Verify(p => p.RequestRead(_handle, (ulong)_actual, _readBufferSize), Times.Once);
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
+ }
+ }
+}
diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesThanCount.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesFromServerThanCount.cs
similarity index 60%
rename from src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesThanCount.cs
rename to src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesFromServerThanCount.cs
index d1956e654..97c66422b 100644
--- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesThanCount.cs
+++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesFromServerThanCount.cs
@@ -3,11 +3,12 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Renci.SshNet.Sftp;
+using Renci.SshNet.Common;
namespace Renci.SshNet.Tests.Classes.Sftp
{
[TestClass]
- public class SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesThanCount : SftpFileStreamTestBase
+ public class SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreBytesFromServerThanCount : SftpFileStreamTestBase
{
private string _path;
private SftpFileStream _target;
@@ -18,7 +19,7 @@ public class SftpFileStreamTest_Read_ReadMode_NoDataInReaderBufferAndReadMoreByt
private int _actual;
private byte[] _buffer;
private byte[] _serverData;
- private int _numberOfBytesInReadBuffer;
+ private int _numberOfBytesToWriteToReadBuffer;
private int _numberOfBytesToRead;
protected override void SetupData()
@@ -34,8 +35,8 @@ protected override void SetupData()
_numberOfBytesToRead = 20;
_buffer = new byte[_numberOfBytesToRead];
- _numberOfBytesInReadBuffer = 10;
- _serverData = GenerateRandom(_buffer.Length + _numberOfBytesInReadBuffer, random);
+ _numberOfBytesToWriteToReadBuffer = 10; // should be less than _readBufferSize
+ _serverData = GenerateRandom(_numberOfBytesToRead + _numberOfBytesToWriteToReadBuffer, random);
}
protected override void SetupMocks()
@@ -81,40 +82,58 @@ protected override void Act()
}
[TestMethod]
- public void ReadShouldHaveReturnedTheNumberOfBytesWrittenToBuffer()
+ public void ReadShouldHaveReturnedTheNumberOfBytesWrittenToCallerSuppliedBuffer()
{
- Assert.AreEqual(_buffer.Length, _actual);
+ Assert.AreEqual(_numberOfBytesToRead, _actual);
}
[TestMethod]
public void ReadShouldHaveWrittenBytesToTheCallerSuppliedBuffer()
{
- Assert.IsTrue(_serverData.Take(_buffer.Length).IsEqualTo(_buffer));
+ Assert.IsTrue(_serverData.Take(_actual).IsEqualTo(_buffer));
}
[TestMethod]
- public void PositionShouldReturnNumberOfBytesWrittenToBuffer()
+ public void PositionShouldReturnNumberOfBytesWrittenToCallerProvidedBuffer()
{
SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
- Assert.AreEqual(_buffer.Length, _target.Position);
+ Assert.AreEqual(_actual, _target.Position);
SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
}
[TestMethod]
- public void ReadShouldReturnAllRemaningBytesFromReadBufferWhenCountIsEqualToNumberOfRemainingBytes()
+ public void SubsequentReadShouldReturnAllRemaningBytesFromReadBufferWhenCountIsEqualToNumberOfRemainingBytes()
{
SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
- _buffer = new byte[_numberOfBytesInReadBuffer];
+ var buffer = new byte[_numberOfBytesToWriteToReadBuffer];
- var actual = _target.Read(_buffer, 0, _numberOfBytesInReadBuffer);
+ var actual = _target.Read(buffer, 0, _numberOfBytesToWriteToReadBuffer);
- Assert.AreEqual(_numberOfBytesInReadBuffer, actual);
- Assert.IsTrue(_serverData.Take(_numberOfBytesToRead, _numberOfBytesInReadBuffer).IsEqualTo(_buffer));
+ Assert.AreEqual(_numberOfBytesToWriteToReadBuffer, actual);
+ Assert.IsTrue(_serverData.Take(_numberOfBytesToRead, _numberOfBytesToWriteToReadBuffer).IsEqualTo(buffer));
SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
}
+
+ [TestMethod]
+ public void SubsequentReadShouldReturnAllRemaningBytesFromReadBufferAndReadAgainWhenCountIsGreaterThanNumberOfRemainingBytesAndNewReadReturnsZeroBytes()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.RequestRead(_handle, (ulong)(_serverData.Length), _readBufferSize)).Returns(Array.Empty);
+
+ var buffer = new byte[_numberOfBytesToWriteToReadBuffer + 1];
+
+ var actual = _target.Read(buffer, 0, buffer.Length);
+
+ Assert.AreEqual(_numberOfBytesToWriteToReadBuffer, actual);
+ Assert.IsTrue(_serverData.Take(_numberOfBytesToRead, _numberOfBytesToWriteToReadBuffer).IsEqualTo(buffer.Take(_numberOfBytesToWriteToReadBuffer)));
+ Assert.AreEqual(0, buffer[_numberOfBytesToWriteToReadBuffer]);
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ SftpSessionMock.Verify(p => p.RequestRead(_handle, (ulong)(_serverData.Length), _readBufferSize));
+ }
}
}
diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetNegative.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetNegative.cs
new file mode 100644
index 000000000..712880d85
--- /dev/null
+++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetNegative.cs
@@ -0,0 +1,97 @@
+using System;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+ [TestClass]
+ public class SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetNegative : SftpFileStreamTestBase
+ {
+ private Random _random;
+ private string _path;
+ private FileMode _fileMode;
+ private FileAccess _fileAccess;
+ private int _bufferSize;
+ private uint _readBufferSize;
+ private uint _writeBufferSize;
+ private byte[] _handle;
+ private SftpFileStream _target;
+ private int _offset;
+ private EndOfStreamException _actualException;
+
+ protected override void SetupData()
+ {
+ base.SetupData();
+
+ _random = new Random();
+ _path = _random.Next().ToString();
+ _fileMode = FileMode.OpenOrCreate;
+ _fileAccess = FileAccess.Read;
+ _bufferSize = _random.Next(5, 1000);
+ _readBufferSize = (uint)_random.Next(5, 1000);
+ _writeBufferSize = (uint)_random.Next(5, 1000);
+ _handle = GenerateRandom(_random.Next(1, 10), _random);
+ _offset = _random.Next(int.MinValue, -1);
+ }
+
+ protected override void SetupMocks()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.CreateNewOrOpen, false))
+ .Returns(_handle);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalReadLength((uint)_bufferSize))
+ .Returns(_readBufferSize);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalWriteLength((uint)_bufferSize, _handle))
+ .Returns(_writeBufferSize);
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+ }
+
+ protected override void Arrange()
+ {
+ base.Arrange();
+
+ _target = new SftpFileStream(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize);
+ }
+
+ protected override void Act()
+ {
+ try
+ {
+ _target.Seek(_offset, SeekOrigin.Begin);
+ Assert.Fail();
+ }
+ catch (EndOfStreamException ex)
+ {
+ _actualException = ex;
+ }
+ }
+
+ [TestMethod]
+ public void SeekShouldHaveThrownEndOfStreamException()
+ {
+ Assert.IsNotNull(_actualException);
+ Assert.IsNull(_actualException.InnerException);
+ Assert.AreEqual("Attempted to read past the end of the stream.", _actualException.Message);
+ }
+
+ [TestMethod]
+ public void IsOpenOnSftpSessionShouldHaveBeenInvokedOnce()
+ {
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
+ }
+
+ [TestMethod]
+ public void PositionShouldReturnZero()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+ Assert.AreEqual(0L, _target.Position);
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ }
+ }
+}
diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetPositive.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetPositive.cs
new file mode 100644
index 000000000..c0f368b8c
--- /dev/null
+++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetPositive.cs
@@ -0,0 +1,88 @@
+using System;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+ [TestClass]
+ public class SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetPositive : SftpFileStreamTestBase
+ {
+ private Random _random;
+ private string _path;
+ private FileMode _fileMode;
+ private FileAccess _fileAccess;
+ private int _bufferSize;
+ private uint _readBufferSize;
+ private uint _writeBufferSize;
+ private byte[] _handle;
+ private SftpFileStream _target;
+ private int _offset;
+ private EndOfStreamException _actualException;
+ private long _actual;
+
+ protected override void SetupData()
+ {
+ base.SetupData();
+
+ _random = new Random();
+ _path = _random.Next().ToString();
+ _fileMode = FileMode.OpenOrCreate;
+ _fileAccess = FileAccess.Read;
+ _bufferSize = _random.Next(5, 1000);
+ _readBufferSize = (uint)_random.Next(5, 1000);
+ _writeBufferSize = (uint)_random.Next(5, 1000);
+ _handle = GenerateRandom(_random.Next(1, 10), _random);
+ _offset = _random.Next(1, int.MaxValue);
+ }
+
+ protected override void SetupMocks()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.CreateNewOrOpen, false))
+ .Returns(_handle);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalReadLength((uint)_bufferSize))
+ .Returns(_readBufferSize);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalWriteLength((uint)_bufferSize, _handle))
+ .Returns(_writeBufferSize);
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+ }
+
+ protected override void Arrange()
+ {
+ base.Arrange();
+
+ _target = new SftpFileStream(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize);
+ }
+
+ protected override void Act()
+ {
+ _actual = _target.Seek(_offset, SeekOrigin.Begin);
+ }
+
+ [TestMethod]
+ public void SeekShouldHaveReturnedOffset()
+ {
+ Assert.AreEqual(_offset, _actual);
+ }
+
+ [TestMethod]
+ public void IsOpenOnSftpSessionShouldHaveBeenInvokedOnce()
+ {
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
+ }
+
+ [TestMethod]
+ public void PositionShouldReturnOffset()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+ Assert.AreEqual(_offset, _target.Position);
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ }
+ }
+}
diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetZero.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetZero.cs
index 98ae83bf8..27e8d96fc 100644
--- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetZero.cs
+++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtBeginningOfStream_OriginBeginAndOffsetZero.cs
@@ -65,5 +65,21 @@ public void SeekShouldHaveReturnedZero()
{
Assert.AreEqual(0L, _actual);
}
+
+ [TestMethod]
+ public void IsOpenOnSftpSessionShouldHaveBeenInvokedOnce()
+ {
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
+ }
+
+ [TestMethod]
+ public void PositionShouldReturnZero()
+ {
+ SftpSessionMock.InSequence(MockSequence).Setup(p => p.IsOpen).Returns(true);
+
+ Assert.AreEqual(0L, _target.Position);
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ }
}
}
diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_WithinReadBuffer.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_NoBuffering.cs
similarity index 79%
rename from src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_WithinReadBuffer.cs
rename to src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_NoBuffering.cs
index 50b3172af..29b683375 100644
--- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_WithinReadBuffer.cs
+++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_NoBuffering.cs
@@ -7,8 +7,8 @@
namespace Renci.SshNet.Tests.Classes.Sftp
{
[TestClass]
- [Ignore]
- public class SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_WithinReadBuffer : SftpFileStreamTestBase
+ //[Ignore]
+ public class SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_NoBuffering : SftpFileStreamTestBase
{
private Random _random;
private string _path;
@@ -35,8 +35,8 @@ protected override void SetupData()
_readBufferSize = 20;
_writeBufferSize = (uint) _random.Next(5, 1000);
_handle = GenerateRandom(_random.Next(1, 10), _random);
- _buffer = new byte[_readBufferSize - 5];
- _serverData = GenerateRandom((int) _readBufferSize, _random);
+ _buffer = new byte[_readBufferSize];
+ _serverData = GenerateRandom(_buffer.Length, _random);
}
protected override void SetupMocks()
@@ -93,18 +93,30 @@ public void PositionShouldReturnZero()
}
[TestMethod]
- public void ReadUpToReadBufferSizeShouldReturnBytesFromReadBuffer()
+ public void IsOpenOnSftpSessionShouldHaveBeenInvokedTwice()
+ {
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ }
+
+ [TestMethod]
+ public void ReadShouldReturnReadBytesFromServer()
{
SftpSessionMock.InSequence(MockSequence)
.Setup(p => p.IsOpen)
.Returns(true);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestRead(_handle, 0UL, _readBufferSize))
+ .Returns(new byte[] { 0x05, 0x04 });
- var buffer = new byte[_readBufferSize];
+ var buffer = new byte[1];
var bytesRead = _target.Read(buffer, 0, buffer.Length);
Assert.AreEqual(buffer.Length, bytesRead);
- Assert.IsTrue(_serverData.IsEqualTo(buffer));
+ Assert.AreEqual(0x05, buffer[0]);
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
+ SftpSessionMock.Verify(p => p.RequestRead(_handle, 0UL, _readBufferSize), Times.Exactly(2));
}
}
}
diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_ReadBuffer.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_ReadBuffer.cs
new file mode 100644
index 000000000..624a9759f
--- /dev/null
+++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_ReadBuffer.cs
@@ -0,0 +1,139 @@
+using System;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+ [TestClass]
+ //[Ignore]
+ public class SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_ReadBuffer : SftpFileStreamTestBase
+ {
+ private Random _random;
+ private string _path;
+ private FileMode _fileMode;
+ private FileAccess _fileAccess;
+ private int _bufferSize;
+ private uint _readBufferSize;
+ private uint _writeBufferSize;
+ private byte[] _handle;
+ private SftpFileStream _target;
+ private long _actual;
+ private byte[] _buffer;
+ private byte[] _serverData1;
+ private byte[] _serverData2;
+
+ protected override void SetupData()
+ {
+ base.SetupData();
+
+ _random = new Random();
+ _path = _random.Next().ToString();
+ _fileMode = FileMode.OpenOrCreate;
+ _fileAccess = FileAccess.Read;
+ _bufferSize = _random.Next(5, 1000);
+ _readBufferSize = 20;
+ _writeBufferSize = (uint) _random.Next(5, 1000);
+ _handle = GenerateRandom(_random.Next(1, 10), _random);
+ _buffer = new byte[2]; // should be less than size of read buffer
+ _serverData1 = GenerateRandom((int) _readBufferSize, _random);
+ _serverData2 = GenerateRandom((int) _readBufferSize, _random);
+ }
+
+ protected override void SetupMocks()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.CreateNewOrOpen, false))
+ .Returns(_handle);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalReadLength((uint) _bufferSize))
+ .Returns(_readBufferSize);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.CalculateOptimalWriteLength((uint) _bufferSize, _handle))
+ .Returns(_writeBufferSize);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.IsOpen)
+ .Returns(true);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestRead(_handle, 0UL, _readBufferSize))
+ .Returns(_serverData1);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.IsOpen)
+ .Returns(true);
+ }
+
+ protected override void Arrange()
+ {
+ base.Arrange();
+
+ _target = new SftpFileStream(SftpSessionMock.Object, _path, _fileMode, _fileAccess, _bufferSize);
+ _target.Read(_buffer, 0, _buffer.Length);
+ }
+
+ protected override void Act()
+ {
+ _actual = _target.Seek(0L, SeekOrigin.Begin);
+ }
+
+ [TestMethod]
+ public void SeekShouldHaveReturnedZero()
+ {
+ Assert.AreEqual(0L, _actual);
+ }
+
+ [TestMethod]
+ public void PositionShouldReturnZero()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.IsOpen)
+ .Returns(true);
+
+ Assert.AreEqual(0L, _target.Position);
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
+ }
+
+ [TestMethod]
+ public void IsOpenOnSftpSessionShouldHaveBeenInvokedTwice()
+ {
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(2));
+ }
+
+ [TestMethod]
+ public void ReadBytesThatWereNotBufferedBeforeSeekShouldReadBytesFromServer()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.IsOpen)
+ .Returns(true);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestRead(_handle, 0UL, _readBufferSize))
+ .Returns(_serverData2);
+
+ var bytesRead = _target.Read(_buffer, 0, _buffer.Length);
+
+ Assert.AreEqual(_buffer.Length, bytesRead);
+ Assert.IsTrue(_serverData2.Take(_buffer.Length).IsEqualTo(_buffer));
+
+ SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
+ SftpSessionMock.Verify(p => p.RequestRead(_handle, 0UL, _readBufferSize), Times.Exactly(2));
+ }
+
+ [TestMethod]
+ public void ReadBytesThatWereBufferedBeforeSeekShouldReadBytesFromServer()
+ {
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.IsOpen)
+ .Returns(true);
+ SftpSessionMock.InSequence(MockSequence)
+ .Setup(p => p.RequestRead(_handle, 0UL, _readBufferSize))
+ .Returns(_serverData2);
+
+ var buffer = new byte[_buffer.Length + 1]; // we read one byte that was previously buffered
+ var bytesRead = _target.Read(buffer, 0, buffer.Length);
+
+ Assert.AreEqual(buffer.Length, bytesRead);
+ Assert.IsTrue(_serverData2.Take(buffer.Length).IsEqualTo(buffer));
+ }
+ }
+}
diff --git a/src/Renci.SshNet.Tests/Common/Extensions.cs b/src/Renci.SshNet.Tests/Common/Extensions.cs
index 68a2923ff..6bb1e45ea 100644
--- a/src/Renci.SshNet.Tests/Common/Extensions.cs
+++ b/src/Renci.SshNet.Tests/Common/Extensions.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Renci.SshNet.Common;
+using System;
namespace Renci.SshNet.Tests.Common
{
@@ -16,5 +17,12 @@ public static string AsString(this IList exceptionEvents)
return reportedExceptions;
}
+
+ public static byte[] Copy(this byte[] buffer)
+ {
+ var copy = new byte[buffer.Length];
+ Buffer.BlockCopy(buffer, 0, copy, 0, buffer.Length);
+ return copy;
+ }
}
}
diff --git a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
index fa976e89c..9bf0c4cd4 100644
--- a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
+++ b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
@@ -434,9 +434,14 @@
-
+
+
+
+
+
-
+
+
diff --git a/src/Renci.SshNet/Sftp/SftpFileStream.cs b/src/Renci.SshNet/Sftp/SftpFileStream.cs
index fbc4d4674..500e1ba9c 100644
--- a/src/Renci.SshNet/Sftp/SftpFileStream.cs
+++ b/src/Renci.SshNet/Sftp/SftpFileStream.cs
@@ -18,9 +18,9 @@ public class SftpFileStream : Stream
// Buffer information.
private readonly int _readBufferSize;
- private readonly byte[] _readBuffer;
+ private byte[] _readBuffer;
private readonly int _writeBufferSize;
- private readonly byte[] _writeBuffer;
+ private byte[] _writeBuffer;
private int _bufferPosition;
private int _bufferLen;
private long _position;
@@ -28,7 +28,6 @@ public class SftpFileStream : Stream
private bool _canRead;
private bool _canSeek;
private bool _canWrite;
- private ulong _serverFilePosition;
private readonly object _lock = new object();
@@ -254,15 +253,12 @@ internal SftpFileStream(ISftpSession session, string path, FileMode mode, FileAc
// or SSH_FXP_WRITE message
_readBufferSize = (int) session.CalculateOptimalReadLength((uint) bufferSize);
- _readBuffer = new byte[_readBufferSize];
_writeBufferSize = (int) session.CalculateOptimalWriteLength((uint) bufferSize, _handle);
- _writeBuffer = new byte[_writeBufferSize];
if (mode == FileMode.Append)
{
var attributes = _session.RequestFStat(_handle, false);
_position = attributes.Size;
- _serverFilePosition = (ulong) attributes.Size;
}
}
@@ -314,6 +310,22 @@ public override void Flush()
/// An I/O error occurs.
/// The stream does not support reading.
/// Methods were called after the stream was closed.
+ ///
+ ///
+ /// This method attempts to read up to bytes. This either from the buffer, from the
+ /// server (using one or more SSH_FXP_READ requests) or using a combination of both.
+ ///
+ ///
+ /// The read loop is interrupted when either bytes are read, the server returns zero
+ /// bytes (EOF) or less bytes than the read buffer size.
+ ///
+ ///
+ /// When a server returns less number of bytes than the read buffer size, this may indicate that EOF has
+ /// been reached. A subsequent (SSH_FXP_READ) server request is necessary to make sure EOF has effectively
+ /// been reached. Breaking out of the read loop avoids reading from the server twice to determine EOF: once in
+ /// the read loop, and once upon the next or invocation.
+ ///
+ ///
public override int Read(byte[] buffer, int offset, int count)
{
var readLen = 0;
@@ -342,60 +354,82 @@ public override int Read(byte[] buffer, int offset, int count)
var bytesAvailableInBuffer = _bufferLen - _bufferPosition;
if (bytesAvailableInBuffer <= 0)
{
- _bufferPosition = 0;
- _bufferLen = 0;
-
var data = _session.RequestRead(_handle, (ulong) _position, (uint) _readBufferSize);
- // TODO: don't we need to take into account the number of bytes read (data.Length) ?
- _serverFilePosition = (ulong) _position;
-
if (data.Length == 0)
{
+ _bufferPosition = 0;
+ _bufferLen = 0;
+
break;
}
- // determine number of bytes that we can read into caller-provided buffer
- var bytesToWriteToCallerBuffer = Math.Min(data.Length, count);
+ var bytesToWriteToCallerBuffer = count;
+ if (bytesToWriteToCallerBuffer >= data.Length)
+ {
+ // write all data read to caller-provided buffer
+ bytesToWriteToCallerBuffer = data.Length;
+ // reset buffer since we will skip buffering
+ _bufferPosition = 0;
+ _bufferLen = 0;
+ }
+ else
+ {
+ // determine number of bytes that we should write into read buffer
+ var bytesToWriteToReadBuffer = data.Length - bytesToWriteToCallerBuffer;
+ // write remaining bytes to read buffer
+ Buffer.BlockCopy(data, count, GetOrCreateReadBuffer(), 0, bytesToWriteToReadBuffer);
+ // update position in read buffer
+ _bufferPosition = 0;
+ // update number of bytes in read buffer
+ _bufferLen = bytesToWriteToReadBuffer;
+ }
+
// write bytes to caller-provided buffer
Buffer.BlockCopy(data, 0, buffer, offset, bytesToWriteToCallerBuffer);
- // advance offset to start writing bytes into caller-provided buffer
- offset += bytesToWriteToCallerBuffer;
- // update number of bytes left to read
- count -= bytesToWriteToCallerBuffer;
- // record total number of bytes read into caller-provided buffer
- readLen += bytesToWriteToCallerBuffer;
// update stream position
_position += bytesToWriteToCallerBuffer;
+ // record total number of bytes read into caller-provided buffer
+ readLen += bytesToWriteToCallerBuffer;
- if (data.Length > bytesToWriteToCallerBuffer)
+ // break out of the read loop when the server returned less than the request number of bytes
+ // as that *may* indicate that we've reached EOF
+ //
+ // doing this avoids reading from server twice to determine EOF: once in the read loop, and
+ // once upon the next Read or ReadByte invocation by the caller
+ if (data.Length < _readBufferSize)
{
- // copy remaining bytes to read buffer
- _bufferLen = data.Length - bytesToWriteToCallerBuffer;
- Buffer.BlockCopy(data, bytesToWriteToCallerBuffer, _readBuffer, 0, _bufferLen);
+ break;
}
+
+ // advance offset to start writing bytes into caller-provided buffer
+ offset += bytesToWriteToCallerBuffer;
+ // update number of bytes left to read into caller-provided buffer
+ count -= bytesToWriteToCallerBuffer;
}
else
{
- // determine number of bytes that we can write from read buffer to caller-provided buffer
- var bytesToWriteToCallerBuffer = Math.Min(bytesAvailableInBuffer, count);
+ // limit the number of bytes to use from read buffer to the caller-request number of bytes
+ if (bytesAvailableInBuffer > count)
+ bytesAvailableInBuffer = count;
+
// copy data from read buffer to the caller-provided buffer
- Buffer.BlockCopy(_readBuffer, _bufferPosition, buffer, offset, bytesToWriteToCallerBuffer);
+ Buffer.BlockCopy(GetOrCreateReadBuffer(), _bufferPosition, buffer, offset, bytesAvailableInBuffer);
// update position in read buffer
- _bufferPosition += bytesToWriteToCallerBuffer;
+ _bufferPosition += bytesAvailableInBuffer;
+ // update stream position
+ _position += bytesAvailableInBuffer;
+ // record total number of bytes read into caller-provided buffer
+ readLen += bytesAvailableInBuffer;
// advance offset to start writing bytes into caller-provided buffer
offset += bytesAvailableInBuffer;
// update number of bytes left to read
- count -= bytesToWriteToCallerBuffer;
- // record total number of bytes read into caller-provided buffer
- readLen += bytesToWriteToCallerBuffer;
- // update stream position
- _position += bytesToWriteToCallerBuffer;
+ count -= bytesAvailableInBuffer;
}
}
}
- // Return the number of bytes that were read to the caller.
+ // return the number of bytes that were read to the caller.
return readLen;
}
@@ -418,28 +452,32 @@ public override int ReadByte()
// Setup the object for reading.
SetupRead();
+ byte[] readBuffer;
+
// Read more data into the internal buffer if necessary.
if (_bufferPosition >= _bufferLen)
{
- _bufferPosition = 0;
-
var data = _session.RequestRead(_handle, (ulong) _position, (uint) _readBufferSize);
-
- _bufferLen = data.Length;
- _serverFilePosition = (ulong) _position;
-
- if (_bufferLen == 0)
+ if (data.Length == 0)
{
// We've reached EOF.
return -1;
}
- Buffer.BlockCopy(data, 0, _readBuffer, 0, _bufferLen);
+ readBuffer = GetOrCreateReadBuffer();
+ Buffer.BlockCopy(data, 0, readBuffer, 0, data.Length);
+
+ _bufferPosition = 0;
+ _bufferLen = data.Length;
+ }
+ else
+ {
+ readBuffer = GetOrCreateReadBuffer();
}
// Extract the next byte from the buffer.
++_position;
- return _readBuffer[_bufferPosition++];
+ return readBuffer[_bufferPosition++];
}
}
@@ -501,7 +539,6 @@ public override long Seek(long offset, SeekOrigin origin)
throw new EndOfStreamException("End of stream.");
}
_position = newPosn;
- _serverFilePosition = (ulong)newPosn;
}
else
{
@@ -510,8 +547,7 @@ public override long Seek(long offset, SeekOrigin origin)
if (origin == SeekOrigin.Begin)
{
newPosn = _position - _bufferPosition;
- if (offset >= newPosn && offset <
- (newPosn + _bufferLen))
+ if (offset >= newPosn && offset < (newPosn + _bufferLen))
{
_bufferPosition = (int)(offset - newPosn);
_position = offset;
@@ -524,8 +560,7 @@ public override long Seek(long offset, SeekOrigin origin)
if (newPosn >= (_position - _bufferPosition) &&
newPosn < (_position - _bufferPosition + _bufferLen))
{
- _bufferPosition =
- (int)(newPosn - (_position - _bufferPosition));
+ _bufferPosition = (int) (newPosn - (_position - _bufferPosition));
_position = newPosn;
return _position;
}
@@ -645,14 +680,13 @@ public override void Write(byte[] buffer, int offset, int count)
{
using (var wait = new AutoResetEvent(false))
{
- _session.RequestWrite(_handle, _serverFilePosition, buffer, offset, tempLen, wait);
- _serverFilePosition += (ulong) tempLen;
+ _session.RequestWrite(_handle, (ulong) _position, buffer, offset, tempLen, wait);
}
}
else
{
// No: copy the data to the write buffer first.
- Buffer.BlockCopy(buffer, offset, _writeBuffer, _bufferPosition, tempLen);
+ Buffer.BlockCopy(buffer, offset, GetOrCreateWriteBuffer(), _bufferPosition, tempLen);
_bufferPosition += tempLen;
}
@@ -668,8 +702,7 @@ public override void Write(byte[] buffer, int offset, int count)
{
using (var wait = new AutoResetEvent(false))
{
- _session.RequestWrite(_handle, _serverFilePosition, _writeBuffer, 0, _bufferPosition, wait);
- _serverFilePosition += (ulong) _bufferPosition;
+ _session.RequestWrite(_handle, (ulong) (_position - _bufferPosition), GetOrCreateWriteBuffer(), 0, _bufferPosition, wait);
}
_bufferPosition = 0;
@@ -694,20 +727,21 @@ public override void WriteByte(byte value)
// Setup the object for writing.
SetupWrite();
+ var writeBuffer = GetOrCreateWriteBuffer();
+
// Flush the current buffer if it is full.
if (_bufferPosition >= _writeBufferSize)
{
using (var wait = new AutoResetEvent(false))
{
- _session.RequestWrite(_handle, _serverFilePosition, _writeBuffer, 0, _bufferPosition, wait);
- _serverFilePosition += (ulong) _bufferPosition;
+ _session.RequestWrite(_handle, (ulong) (_position - _bufferPosition), writeBuffer, 0, _bufferPosition, wait);
}
_bufferPosition = 0;
}
// Write the byte into the buffer and advance the posn.
- _writeBuffer[_bufferPosition++] = value;
+ writeBuffer[_bufferPosition++] = value;
++_position;
}
}
@@ -754,6 +788,20 @@ protected override void Dispose(bool disposing)
}
}
+ private byte[] GetOrCreateReadBuffer()
+ {
+ if (_readBuffer == null)
+ _readBuffer = new byte[_readBufferSize];
+ return _readBuffer;
+ }
+
+ private byte[] GetOrCreateWriteBuffer()
+ {
+ if (_writeBuffer == null)
+ _writeBuffer = new byte[_writeBufferSize];
+ return _writeBuffer;
+ }
+
///
/// Flushes the read data from the buffer.
///
@@ -779,8 +827,7 @@ private void FlushWriteBuffer()
{
using (var wait = new AutoResetEvent(false))
{
- _session.RequestWrite(_handle, _serverFilePosition, _writeBuffer, 0, _bufferPosition, wait);
- _serverFilePosition += (ulong) _bufferPosition;
+ _session.RequestWrite(_handle, (ulong) (_position - _bufferPosition), _writeBuffer, 0, _bufferPosition, wait);
}
_bufferPosition = 0;
diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs
index 59d222bb9..3bde01ad3 100644
--- a/src/Renci.SshNet/SftpClient.cs
+++ b/src/Renci.SshNet/SftpClient.cs
@@ -1,5 +1,4 @@
using System;
-
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
@@ -1425,6 +1424,9 @@ public string[] ReadAllLines(string path)
/// The method was called after the client was disposed.
public string[] ReadAllLines(string path, Encoding encoding)
{
+ // we use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size
+ // for the SftpFileStream; may want to revisit this later
+
var lines = new List();
using (var stream = new StreamReader(OpenRead(path), encoding))
{
@@ -1464,6 +1466,9 @@ public string ReadAllText(string path)
/// The method was called after the client was disposed.
public string ReadAllText(string path, Encoding encoding)
{
+ // we use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size
+ // for the SftpFileStream; may want to revisit this later
+
using (var stream = new StreamReader(OpenRead(path), encoding))
{
return stream.ReadToEnd();