瀏覽代碼

Merge pull request #272 from sshnet/sftpclient-setlength

SftpFileStream: Improve SetLength(long) compatibility with FileStream
Gert Driesen 8 年之前
父節點
當前提交
68c1e59920

+ 17 - 1
src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj

@@ -23,6 +23,7 @@
     <DefineConstants>TRACE;DEBUG</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <LangVersion>5</LangVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
@@ -1232,6 +1233,18 @@
     <Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_Closed.cs">
       <Link>Classes\Sftp\SftpFileStreamTest_SetLength_Closed.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs">
+      <Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs">
+      <Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs">
+      <Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs</Link>
+    </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs">
+      <Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_Disposed.cs">
       <Link>Classes\Sftp\SftpFileStreamTest_SetLength_Disposed.cs</Link>
     </Compile>
@@ -1424,6 +1437,9 @@
     <Compile Include="..\Renci.SshNet.Tests\Common\AsyncSocketListener.cs">
       <Link>Common\AsyncSocketListener.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet.Tests\Common\DictionaryAssert.cs">
+      <Link>Common\DictionaryAssert.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet.Tests\Common\Extensions.cs">
       <Link>Common\Extensions.cs</Link>
     </Compile>
@@ -1511,7 +1527,7 @@
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 10 - 3
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceived.cs

@@ -21,6 +21,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _remotePacketSize;
         private ChannelStub _channel;
         private Stopwatch _closeTimer;
+        private ManualResetEvent _channelClosedWaitHandle;
         private List<ChannelEventArgs> _channelClosedRegister;
         private IList<ExceptionEventArgs> _channelExceptionRegister;
 
@@ -42,6 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _closeTimer = new Stopwatch();
             _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelClosedWaitHandle = new ManualResetEvent(false);
             _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
@@ -61,8 +63,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
                                 // SSH_MSG_CHANNEL_CLOSE message from server which is waited on after
                                 // sending the SSH_MSG_CHANNEL_CLOSE message to the server
                                 _sessionMock.Raise(s => s.ChannelCloseReceived += null,
-                                    new MessageEventArgs<ChannelCloseMessage>(
-                                        new ChannelCloseMessage(_localChannelNumber)));
+                                                   new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(_localChannelNumber)));
                             }).Start();
                         _closeTimer.Start();
                         try
@@ -76,7 +77,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
                     });
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Closed += (sender, args) =>
+                {
+                    _channelClosedRegister.Add(args);
+                    _channelClosedWaitHandle.Set();
+                };
             _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
@@ -124,6 +129,8 @@ namespace Renci.SshNet.Tests.Classes.Channels
         [TestMethod]
         public void ClosedEventShouldHaveFiredOnce()
         {
+            _channelClosedWaitHandle.WaitOne(100);
+
             Assert.AreEqual(1, _channelClosedRegister.Count);
             Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
         }

+ 9 - 1
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceived_SendEofInvoked.cs

@@ -21,6 +21,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _remotePacketSize;
         private ChannelStub _channel;
         private Stopwatch _closeTimer;
+        private ManualResetEvent _channelClosedWaitHandle;
         private List<ChannelEventArgs> _channelClosedRegister;
         private IList<ExceptionEventArgs> _channelExceptionRegister;
 
@@ -41,6 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _closeTimer = new Stopwatch();
+            _channelClosedWaitHandle = new ManualResetEvent(false);
             _channelClosedRegister = new List<ChannelEventArgs>();
             _channelExceptionRegister = new List<ExceptionEventArgs>();
 
@@ -75,7 +77,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
                 });
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Closed += (sender, args) =>
+                {
+                    _channelClosedRegister.Add(args);
+                    _channelClosedWaitHandle.Set();
+                };
             _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
@@ -124,6 +130,8 @@ namespace Renci.SshNet.Tests.Classes.Channels
         [TestMethod]
         public void ClosedEventShouldHaveFiredOnce()
         {
+            _channelClosedWaitHandle.WaitOne(100);
+
             Assert.AreEqual(1, _channelClosedRegister.Count);
             Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
         }

+ 17 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTestBase.cs

@@ -38,11 +38,28 @@ namespace Renci.SshNet.Tests.Classes.Sftp
 
         protected abstract void Act();
 
+        protected byte[] GenerateRandom(int length)
+        {
+            return GenerateRandom(length, new Random());
+        }
+
         protected byte[] GenerateRandom(int length, Random random)
         {
             var buffer = new byte[length];
             random.NextBytes(buffer);
             return buffer;
         }
+
+        protected byte[] GenerateRandom(uint length)
+        {
+            return GenerateRandom(length, new Random());
+        }
+
+        protected byte[] GenerateRandom(uint length, Random random)
+        {
+            var buffer = new byte[length];
+            random.NextBytes(buffer);
+            return buffer;
+        }
     }
 }

+ 170 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs

@@ -0,0 +1,170 @@
+using System;
+using System.Globalization;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Tests.Common;
+using System.Threading;
+using Renci.SshNet.Sftp.Responses;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    /// <summary>
+    /// - In read mode
+    /// - Bytes in (read) buffer
+    /// - New length greater than client position and greater than server position
+    /// </summary>
+    [TestClass]
+    public class SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition : SftpFileStreamTestBase
+    {
+        private string _path;
+        private SftpFileStream _sftpFileStream;
+        private byte[] _handle;
+        private uint _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private MockSequence _sequence;
+        private long _length;
+
+        private SftpFileAttributes _fileAttributes;
+        private SftpFileAttributes _originalFileAttributes;
+        private SftpFileAttributes _newFileAttributes;
+        private byte[] _readBytes;
+        private byte[] _actualReadBytes;
+
+        protected override void SetupData()
+        {
+            var random = new Random();
+
+            _path = random.Next().ToString(CultureInfo.InvariantCulture);
+            _handle = GenerateRandom(random.Next(2, 6), random);
+            _bufferSize = (uint) random.Next(1, 1000);
+            _readBufferSize = (uint) random.Next(1, 1000);
+            _writeBufferSize = (uint) random.Next(100, 1000);
+            _readBytes = new byte[5];
+            _actualReadBytes = GenerateRandom(_readBytes.Length, random);
+            _length = _readBytes.Length + 2;
+
+            _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
+                                                             .WithExtension("V", "VValue")
+                                                             .WithGroupId(random.Next())
+                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithPermissions((uint)random.Next())
+                                                             .WithSize(_length + 100)
+                                                             .WithUserId(random.Next())
+                                                             .Build();
+            _originalFileAttributes = _fileAttributes.Clone();
+            _newFileAttributes = null;
+        }
+
+        protected override void SetupMocks()
+        {
+            _sequence = new MockSequence();
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write, false))
+                           .Returns(_handle);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestRead(_handle, 0, _readBufferSize))
+                           .Returns(_actualReadBytes);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestFStat(_handle, false))
+                           .Returns(_fileAttributes);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestFSetStat(_handle, _fileAttributes))
+                           .Callback<byte[], SftpFileAttributes>((bytes, attributes) => _newFileAttributes = attributes.Clone());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _sftpFileStream = new SftpFileStream(SftpSessionMock.Object, _path, FileMode.Open, FileAccess.ReadWrite, (int)_bufferSize);
+            _sftpFileStream.Read(_readBytes, 0, _readBytes.Length);
+        }
+
+        protected override void Act()
+        {
+            _sftpFileStream.SetLength(_length);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnSamePositionAsBeforeSetLength()
+        {
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+
+            Assert.AreEqual(_readBytes.Length, _sftpFileStream.Position);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
+        }
+
+        [TestMethod]
+        public void RequestFSetStatOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestFSetStat(_handle, _fileAttributes), Times.Once);
+        }
+
+        [TestMethod]
+        public void SizeOfSftpFileAttributesShouldBeModifiedToNewLengthBeforePassedToRequestFSetStat()
+        {
+            DictionaryAssert.AreEqual(_originalFileAttributes.Extensions, _newFileAttributes.Extensions);
+            Assert.AreEqual(_originalFileAttributes.GroupId, _newFileAttributes.GroupId);
+            Assert.AreEqual(_originalFileAttributes.LastAccessTime, _newFileAttributes.LastAccessTime);
+            Assert.AreEqual(_originalFileAttributes.LastWriteTime, _newFileAttributes.LastWriteTime);
+            Assert.AreEqual(_originalFileAttributes.Permissions, _newFileAttributes.Permissions);
+            Assert.AreEqual(_originalFileAttributes.UserId, _newFileAttributes.UserId);
+
+            Assert.AreEqual(_length, _newFileAttributes.Size);
+        }
+
+        [TestMethod]
+        public void ReadShouldReadStartFromSamePositionAsBeforeSetLength()
+        {
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestRead(_handle, (uint) _readBytes.Length, _readBufferSize))
+                           .Returns(new byte[] { 0x0f });
+
+            var byteRead = _sftpFileStream.ReadByte();
+
+            Assert.AreEqual(0x0f, byteRead);
+
+            SftpSessionMock.Verify(p => p.RequestRead(_handle, (uint) _readBytes.Length, _readBufferSize), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
+        }
+
+        [TestMethod]
+        public void WriteShouldStartFromSamePositionAsBeforeSetLength()
+        {
+            var bytesToWrite = GenerateRandom(5);
+
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null))
+                           .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverOffset, data, offset, length, wait, writeCompleted) =>
+                           {
+                               wait.Set();
+                           });
+
+            _sftpFileStream.Write(bytesToWrite, 0, bytesToWrite.Length);
+            _sftpFileStream.Flush();
+
+            SftpSessionMock.Verify(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
+        }
+    }
+}

+ 171 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs

@@ -0,0 +1,171 @@
+using System;
+using System.Globalization;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Tests.Common;
+using System.Threading;
+using Renci.SshNet.Sftp.Responses;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    /// <summary>
+    /// - In read mode
+    /// - Bytes in (read) buffer
+    /// - New length less than client position and greater than server position
+    /// </summary>
+    [TestClass]
+    public class SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition : SftpFileStreamTestBase
+    {
+        private string _path;
+        private SftpFileStream _sftpFileStream;
+        private byte[] _handle;
+        private uint _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private MockSequence _sequence;
+        private long _length;
+
+        private SftpFileAttributes _fileAttributes;
+        private SftpFileAttributes _originalFileAttributes;
+        private SftpFileAttributes _newFileAttributes;
+        private byte[] _readBytes;
+        private byte[] _actualReadBytes;
+
+        protected override void SetupData()
+        {
+            var random = new Random();
+
+            _path = random.Next().ToString(CultureInfo.InvariantCulture);
+            _handle = GenerateRandom(random.Next(2, 6), random);
+            _bufferSize = (uint)random.Next(1, 1000);
+            _readBufferSize = (uint)random.Next(1, 1000);
+            _writeBufferSize = (uint)random.Next(100, 1000);
+            _readBytes = new byte[5];
+            _actualReadBytes = GenerateRandom(_readBytes.Length, random);
+            _length = _readBytes.Length - 2;
+
+            _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
+                                                             .WithExtension("V", "VValue")
+                                                             .WithGroupId(random.Next())
+                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithPermissions((uint)random.Next())
+                                                             .WithSize(_length + 100)
+                                                             .WithUserId(random.Next())
+                                                             .Build();
+            _originalFileAttributes = _fileAttributes.Clone();
+            _newFileAttributes = null;
+        }
+
+        protected override void SetupMocks()
+        {
+            _sequence = new MockSequence();
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write, false))
+                           .Returns(_handle);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestRead(_handle, 0, _readBufferSize))
+                           .Returns(_actualReadBytes);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestFStat(_handle, false))
+                           .Returns(_fileAttributes);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestFSetStat(_handle, _fileAttributes))
+                           .Callback<byte[], SftpFileAttributes>((bytes, attributes) => _newFileAttributes = attributes.Clone());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _sftpFileStream = new SftpFileStream(SftpSessionMock.Object, _path, FileMode.Open, FileAccess.ReadWrite, (int)_bufferSize);
+            _sftpFileStream.Read(_readBytes, 0, _readBytes.Length);
+        }
+
+        protected override void Act()
+        {
+            _sftpFileStream.SetLength(_length);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnLengthOfStream()
+        {
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+
+            Assert.AreEqual(_length, _sftpFileStream.Position);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
+        }
+
+        [TestMethod]
+        public void RequestFSetStatOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestFSetStat(_handle, _fileAttributes), Times.Once);
+        }
+
+        [TestMethod]
+        public void SizeOfSftpFileAttributesShouldBeModifiedToNewLengthBeforePassedToRequestFSetStat()
+        {
+            DictionaryAssert.AreEqual(_originalFileAttributes.Extensions, _newFileAttributes.Extensions);
+            Assert.AreEqual(_originalFileAttributes.GroupId, _newFileAttributes.GroupId);
+            Assert.AreEqual(_originalFileAttributes.LastAccessTime, _newFileAttributes.LastAccessTime);
+            Assert.AreEqual(_originalFileAttributes.LastWriteTime, _newFileAttributes.LastWriteTime);
+            Assert.AreEqual(_originalFileAttributes.Permissions, _newFileAttributes.Permissions);
+            Assert.AreEqual(_originalFileAttributes.UserId, _newFileAttributes.UserId);
+
+            Assert.AreEqual(_length, _newFileAttributes.Size);
+        }
+
+        [TestMethod]
+        public void ReadShouldStartFromEndOfStream()
+        {
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestRead(_handle, (uint) _length, _readBufferSize))
+                           .Returns(Array<byte>.Empty);
+
+            var byteRead = _sftpFileStream.ReadByte();
+
+            Assert.AreEqual(-1, byteRead);
+
+            SftpSessionMock.Verify(p => p.RequestRead(_handle, (uint) _length, _readBufferSize), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
+        }
+
+        [TestMethod]
+        public void WriteShouldStartFromEndOfStream()
+        {
+            var bytesToWrite = GenerateRandom(5);
+
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestWrite(_handle, (uint) _length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null))
+                           .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverOffset, data, offset, length, wait, writeCompleted) =>
+                           {
+                               wait.Set();
+                           });
+
+            _sftpFileStream.Write(bytesToWrite, 0, bytesToWrite.Length);
+            _sftpFileStream.Flush();
+
+            SftpSessionMock.Verify(p => p.RequestWrite(_handle, (uint)_length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
+        }
+    }
+}

+ 193 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs

@@ -0,0 +1,193 @@
+using System;
+using System.Globalization;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Tests.Common;
+using System.Threading;
+using Renci.SshNet.Sftp.Responses;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    /// <summary>
+    /// - In write mode
+    /// - Bytes in (write) buffer
+    /// - New length greater than client position and greater than server position
+    /// </summary>
+    [TestClass]
+    public class SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreaterThanPosition : SftpFileStreamTestBase
+    {
+        private string _path;
+        private SftpFileStream _sftpFileStream;
+        private byte[] _handle;
+        private uint _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private MockSequence _sequence;
+        private long _length;
+
+        private SftpFileAttributes _fileAttributes;
+        private SftpFileAttributes _originalFileAttributes;
+        private SftpFileAttributes _newFileAttributes;
+        private byte[] _readBytes;
+        private byte[] _actualReadBytes;
+        private byte[] _writeBytes;
+        private byte[] _actualWrittenBytes;
+
+        protected override void SetupData()
+        {
+            var random = new Random();
+
+            _path = random.Next().ToString(CultureInfo.InvariantCulture);
+            _handle = GenerateRandom(random.Next(2, 6), random);
+            _bufferSize = (uint) random.Next(1, 1000);
+            _readBufferSize = (uint) random.Next(1, 1000);
+            _writeBufferSize = (uint) random.Next(100, 1000);
+            _readBytes = new byte[5];
+            _actualReadBytes = GenerateRandom(_readBytes.Length, random);
+            _writeBytes = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+            _length = _readBytes.Length + _writeBytes.Length + 2;
+            _actualWrittenBytes = null;
+
+            _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
+                                                             .WithExtension("V", "VValue")
+                                                             .WithGroupId(random.Next())
+                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithPermissions((uint)random.Next())
+                                                             .WithSize(_length + 100)
+                                                             .WithUserId(random.Next())
+                                                             .Build();
+            _originalFileAttributes = _fileAttributes.Clone();
+            _newFileAttributes = null;
+        }
+
+        protected override void SetupMocks()
+        {
+            _sequence = new MockSequence();
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write, false))
+                           .Returns(_handle);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestRead(_handle, 0, _readBufferSize))
+                           .Returns(_actualReadBytes);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny<byte[]>(), 0, _writeBytes.Length, It.IsAny<AutoResetEvent>(), null))
+                           .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverOffset, data, offset, length, wait, writeCompleted)
+                               =>
+                           {
+                               _actualWrittenBytes = data.Take(0, _writeBytes.Length);
+                               wait.Set();
+                           });
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestFStat(_handle, false))
+                           .Returns(_fileAttributes);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestFSetStat(_handle, _fileAttributes))
+                           .Callback<byte[], SftpFileAttributes>((bytes, attributes) => _newFileAttributes = attributes.Clone());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _sftpFileStream = new SftpFileStream(SftpSessionMock.Object, _path, FileMode.Open, FileAccess.ReadWrite, (int)_bufferSize);
+            _sftpFileStream.Read(_readBytes, 0, _readBytes.Length);
+            _sftpFileStream.Write(new byte[] { 0x01, 0x02, 0x03, 0x04 }, 0, 4);
+        }
+
+        protected override void Act()
+        {
+            _sftpFileStream.SetLength(_length);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnSamePositionAsBeforeSetLength()
+        {
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+
+            Assert.AreEqual(_readBytes.Length + _writeBytes.Length, _sftpFileStream.Position);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
+        }
+
+        [TestMethod]
+        public void RequestFSetStatOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestFSetStat(_handle, _fileAttributes), Times.Once);
+        }
+
+        [TestMethod]
+        public void SizeOfSftpFileAttributesShouldBeModifiedToNewLengthBeforePassedToRequestFSetStat()
+        {
+            DictionaryAssert.AreEqual(_originalFileAttributes.Extensions, _newFileAttributes.Extensions);
+            Assert.AreEqual(_originalFileAttributes.GroupId, _newFileAttributes.GroupId);
+            Assert.AreEqual(_originalFileAttributes.LastAccessTime, _newFileAttributes.LastAccessTime);
+            Assert.AreEqual(_originalFileAttributes.LastWriteTime, _newFileAttributes.LastWriteTime);
+            Assert.AreEqual(_originalFileAttributes.Permissions, _newFileAttributes.Permissions);
+            Assert.AreEqual(_originalFileAttributes.UserId, _newFileAttributes.UserId);
+
+            Assert.AreEqual(_length, _newFileAttributes.Size);
+        }
+
+        [TestMethod]
+        public void WrittenBytesShouldByFlushedToServer()
+        {
+            Assert.IsNotNull(_actualWrittenBytes);
+            CollectionAssert.AreEqual(_writeBytes, _actualWrittenBytes);
+        }
+
+        [TestMethod]
+        public void ReadShouldReadStartFromSamePositionAsBeforeSetLength()
+        {
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestRead(_handle, (uint) (_readBytes.Length + _writeBytes.Length), _readBufferSize))
+                           .Returns(new byte[] { 0x0f });
+
+            var byteRead = _sftpFileStream.ReadByte();
+
+            Assert.AreEqual(0x0f, byteRead);
+
+            SftpSessionMock.Verify(p => p.RequestRead(_handle, (uint) (_readBytes.Length + _writeBytes.Length), _readBufferSize), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
+        }
+
+        [TestMethod]
+        public void WriteShouldStartFromSamePositionAsBeforeSetLength()
+        {
+            var bytesToWrite = GenerateRandom(5);
+
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestWrite(_handle, (uint)(_readBytes.Length + _writeBytes.Length), It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null))
+                           .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverOffset, data, offset, length, wait, writeCompleted) =>
+                                {
+                                    wait.Set();
+                                });
+
+            _sftpFileStream.Write(bytesToWrite, 0, bytesToWrite.Length);
+            _sftpFileStream.Flush();
+
+            SftpSessionMock.Verify(p => p.RequestWrite(_handle, (uint) (_readBytes.Length + _writeBytes.Length), It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(5));
+        }
+    }
+}

+ 194 - 0
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs

@@ -0,0 +1,194 @@
+using System;
+using System.Globalization;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Sftp;
+using Renci.SshNet.Tests.Common;
+using System.Threading;
+using Renci.SshNet.Sftp.Responses;
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet.Tests.Classes.Sftp
+{
+    /// <summary>
+    /// - In write mode
+    /// - Bytes in (write) buffer
+    /// - New length less than client position and less than server position
+    /// </summary>
+    [TestClass]
+    public class SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition : SftpFileStreamTestBase
+    {
+        private string _path;
+        private SftpFileStream _sftpFileStream;
+        private byte[] _handle;
+        private uint _bufferSize;
+        private uint _readBufferSize;
+        private uint _writeBufferSize;
+        private MockSequence _sequence;
+        private long _length;
+
+        private SftpFileAttributes _fileAttributes;
+        private SftpFileAttributes _originalFileAttributes;
+        private SftpFileAttributes _newFileAttributes;
+        private byte[] _readBytes;
+        private byte[] _actualReadBytes;
+        private byte[] _writeBytes;
+        private byte[] _actualWrittenBytes;
+
+        protected override void SetupData()
+        {
+            var random = new Random();
+
+            _path = random.Next().ToString(CultureInfo.InvariantCulture);
+            _handle = GenerateRandom(random.Next(2, 6), random);
+            _bufferSize = (uint) random.Next(1, 1000);
+            _readBufferSize = (uint) random.Next(1, 1000);
+            _writeBufferSize = (uint) random.Next(100, 1000);
+            _readBytes = new byte[5];
+            _actualReadBytes = GenerateRandom(_readBytes.Length, random);
+            _writeBytes = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+            _length = _readBytes.Length - 2;
+            _actualWrittenBytes = null;
+
+            _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
+                                                             .WithExtension("V", "VValue")
+                                                             .WithGroupId(random.Next())
+                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithPermissions((uint) random.Next())
+                                                             .WithSize(_length + 100)
+                                                             .WithUserId(random.Next())
+                                                             .Build();
+            _originalFileAttributes = _fileAttributes.Clone();
+            _newFileAttributes = null;
+        }
+
+        protected override void SetupMocks()
+        {
+            _sequence = new MockSequence();
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write, false))
+                           .Returns(_handle);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
+                           .Returns(_readBufferSize);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
+                           .Returns(_writeBufferSize);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestRead(_handle, 0, _readBufferSize))
+                           .Returns(_actualReadBytes);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.IsOpen)
+                           .Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny<byte[]>(), 0, _writeBytes.Length, It.IsAny<AutoResetEvent>(), null))
+                           .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverOffset, data, offset, length, wait, writeCompleted)
+                               =>
+                                   {
+                                       _actualWrittenBytes = data.Take(0, _writeBytes.Length);
+                                       wait.Set();
+                                   });
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestFStat(_handle, false))
+                           .Returns(_fileAttributes);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestFSetStat(_handle, _fileAttributes))
+                           .Callback<byte[], SftpFileAttributes>((bytes, attributes) => _newFileAttributes = attributes.Clone());
+        }
+
+        protected override void Arrange()
+        {
+            base.Arrange();
+
+            _sftpFileStream = new SftpFileStream(SftpSessionMock.Object, _path, FileMode.Open, FileAccess.ReadWrite, (int) _bufferSize);
+            _sftpFileStream.Read(_readBytes, 0, _readBytes.Length);
+            _sftpFileStream.Write(new byte[] { 0x01, 0x02, 0x03, 0x04 }, 0, 4);
+        }
+
+        protected override void Act()
+        {
+            _sftpFileStream.SetLength(_length);
+        }
+
+        [TestMethod]
+        public void PositionShouldReturnLengthOfStream()
+        {
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+
+            Assert.AreEqual(_length, _sftpFileStream.Position);
+
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
+        }
+
+        [TestMethod]
+        public void RequestFSetStatOnSftpSessionShouldBeInvokedOnce()
+        {
+            SftpSessionMock.Verify(p => p.RequestFSetStat(_handle, _fileAttributes), Times.Once);
+        }
+
+        [TestMethod]
+        public void SizeOfSftpFileAttributesShouldBeModifiedToNewLengthBeforePassedToRequestFSetStat()
+        {
+            DictionaryAssert.AreEqual(_originalFileAttributes.Extensions, _newFileAttributes.Extensions);
+            Assert.AreEqual(_originalFileAttributes.GroupId, _newFileAttributes.GroupId);
+            Assert.AreEqual(_originalFileAttributes.LastAccessTime, _newFileAttributes.LastAccessTime);
+            Assert.AreEqual(_originalFileAttributes.LastWriteTime, _newFileAttributes.LastWriteTime);
+            Assert.AreEqual(_originalFileAttributes.Permissions, _newFileAttributes.Permissions);
+            Assert.AreEqual(_originalFileAttributes.UserId, _newFileAttributes.UserId);
+
+            Assert.AreEqual(_length, _newFileAttributes.Size);
+        }
+
+        [TestMethod]
+        public void WrittenBytesShouldByFlushedToServer()
+        {
+            Assert.IsNotNull(_actualWrittenBytes);
+            CollectionAssert.AreEqual(_writeBytes, _actualWrittenBytes);
+        }
+
+        [TestMethod]
+        public void ReadShouldStartFromEndOfStream()
+        {
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestRead(_handle, (uint) _length, _readBufferSize))
+                           .Returns(Array<byte>.Empty);
+
+            var byteRead = _sftpFileStream.ReadByte();
+
+            Assert.AreEqual(-1, byteRead);
+
+            SftpSessionMock.Verify(p => p.RequestRead(_handle, (uint)_length, _readBufferSize), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
+        }
+
+        [TestMethod]
+        public void WriteShouldStartFromEndOfStream()
+        {
+            var bytesToWrite = GenerateRandom(5);
+
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
+            SftpSessionMock.InSequence(_sequence)
+                           .Setup(p => p.RequestWrite(_handle, (uint) _length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null))
+                           .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverOffset, data, offset, length, wait, writeCompleted) =>
+                           {
+                               wait.Set();
+                           });
+
+            _sftpFileStream.Write(bytesToWrite, 0, bytesToWrite.Length);
+            _sftpFileStream.Flush();
+
+            SftpSessionMock.Verify(p => p.RequestWrite(_handle, (uint) _length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null), Times.Once);
+            SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(5));
+        }
+    }
+}

+ 38 - 0
src/Renci.SshNet.Tests/Common/DictionaryAssert.cs

@@ -0,0 +1,38 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections.Generic;
+
+namespace Renci.SshNet.Tests.Common
+{
+    public static class DictionaryAssert
+    {
+        public static void AreEqual<TKey, TValue>(IDictionary<TKey, TValue> expected, IDictionary<TKey, TValue> actual)
+        {
+            if (ReferenceEquals(expected, actual))
+                return;
+
+            if (expected == null)
+                throw new AssertFailedException("Expected dictionary to be null, but was not null.");
+
+            if (actual == null)
+                throw new AssertFailedException("Expected dictionary not to be null, but was null.");
+
+            if (expected.Count != actual.Count)
+                throw new AssertFailedException(string.Format("Expected dictionary to contain {0} entries, but was {1}.",
+                                                              expected.Count, actual.Count));
+
+            foreach (var expectedEntry in expected)
+            {
+                TValue actualValue;
+                if (!actual.TryGetValue(expectedEntry.Key, out actualValue))
+                {
+                    throw new AssertFailedException(string.Format("Dictionary contains no entry with key '{0}'.", expectedEntry.Key));
+                }
+
+                if (!Equals(expectedEntry.Value, actualValue))
+                {
+                    throw new AssertFailedException(string.Format("Value for key '{0}' does not match.", expectedEntry.Key));
+                }
+            }
+        }
+    }
+}

+ 33 - 0
src/Renci.SshNet.Tests/Common/Extensions.cs

@@ -1,6 +1,7 @@
 using System.Collections.Generic;
 using Renci.SshNet.Common;
 using System;
+using Renci.SshNet.Sftp;
 
 namespace Renci.SshNet.Tests.Common
 {
@@ -24,5 +25,37 @@ namespace Renci.SshNet.Tests.Common
             Buffer.BlockCopy(buffer, 0, copy, 0, buffer.Length);
             return copy;
         }
+
+        /// <summary>
+        /// Creates a deep clone of the current instance.
+        /// </summary>
+        /// <returns>
+        /// A deep clone of the current instance.
+        /// </returns>
+        internal static SftpFileAttributes Clone(this SftpFileAttributes value)
+        {
+            Dictionary<string, string> clonedExtensions;
+
+            if (value.Extensions != null)
+            {
+                clonedExtensions = new Dictionary<string, string>(value.Extensions.Count);
+                foreach (var entry in value.Extensions)
+                {
+                    clonedExtensions.Add(entry.Key, entry.Value);
+                }
+            }
+            else
+            {
+                clonedExtensions = null;
+            }
+
+            return new SftpFileAttributes(value.LastAccessTime,
+                                          value.LastWriteTime,
+                                          value.Size,
+                                          value.UserId,
+                                          value.GroupId,
+                                          value.Permissions,
+                                          clonedExtensions);
+        }
     }
 }

+ 7 - 4
src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs

@@ -14,6 +14,11 @@ namespace Renci.SshNet.Tests.Common
         private uint? _permissions;
         private IDictionary<string, string> _extensions;
 
+        public SftpFileAttributesBuilder()
+        {
+            _extensions = new Dictionary<string, string>();
+        }
+
         public SftpFileAttributesBuilder WithLastAccessTime(DateTime lastAccessTime)
         {
             _lastAccessTime = lastAccessTime;
@@ -50,9 +55,9 @@ namespace Renci.SshNet.Tests.Common
             return this;
         }
 
-        public SftpFileAttributesBuilder WithExtensions(IDictionary<string, string> extensions)
+        public SftpFileAttributesBuilder WithExtension(string name, string value)
         {
-            _extensions = extensions;
+            _extensions.Add(name, value);
             return this;
         }
 
@@ -79,7 +84,5 @@ namespace Renci.SshNet.Tests.Common
                                           _permissions.Value,
                                           _extensions);
         }
-
-
     }
 }

+ 5 - 0
src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -444,6 +444,10 @@
     <Compile Include="Classes\Sftp\SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_NoBuffering.cs" />
     <Compile Include="Classes\Sftp\SftpFileStreamTest_Seek_PositionedAtMiddleOfStream_OriginBeginAndOffsetZero_ReadBuffer.cs" />
     <Compile Include="Classes\Sftp\SftpFileStreamTest_SetLength_Closed.cs" />
+    <Compile Include="Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs" />
+    <Compile Include="Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs" />
+    <Compile Include="Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs" />
+    <Compile Include="Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs" />
     <Compile Include="Classes\Sftp\SftpFileStreamTest_SetLength_Disposed.cs" />
     <Compile Include="Classes\Sftp\SftpFileStreamTest_SetLength_SessionNotOpen.cs" />
     <Compile Include="Classes\Sftp\SftpFileStreamTest_SetLength_SessionOpen_FIleAccessRead.cs" />
@@ -513,6 +517,7 @@
     <Compile Include="Classes\SubsystemSession_SendData_Disposed.cs" />
     <Compile Include="Classes\SubsystemSession_SendData_NeverConnected.cs" />
     <Compile Include="Common\AsyncSocketListener.cs" />
+    <Compile Include="Common\DictionaryAssert.cs" />
     <Compile Include="Common\Extensions.cs" />
     <Compile Include="Common\HttpProxyStub.cs" />
     <Compile Include="Common\HttpRequest.cs" />

+ 28 - 3
src/Renci.SshNet/Sftp/SftpFileStream.cs

@@ -602,13 +602,26 @@ namespace Renci.SshNet.Sftp
         }
 
         /// <summary>
-        /// When overridden in a derived class, sets the length of the current stream.
+        /// Sets the length of the current stream.
         /// </summary>
         /// <param name="value">The desired length of the current stream in bytes.</param>
         /// <exception cref="IOException">An I/O error occurs.</exception>
-        /// <exception cref="NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.</exception>
+        /// <exception cref="NotSupportedException">The stream does not support both writing and seeking.</exception>
         /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
         /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> must be greater than zero.</exception>
+        /// <remarks>
+        /// <para>
+        /// Buffers are first flushed.
+        /// </para>
+        /// <para>
+        /// If the specified value is less than the current length of the stream, the stream is truncated and - if the
+        /// current position is greater than the new length - the current position is moved to the last byte of the stream.
+        /// </para>
+        /// <para>
+        /// If the given value is greater than the current length of the stream, the stream is expanded and the current
+        /// position remains the same.
+        /// </para>
+        /// </remarks>
         public override void SetLength(long value)
         {
             if (value < 0)
@@ -622,11 +635,23 @@ namespace Renci.SshNet.Sftp
                 if (!CanSeek)
                     throw new NotSupportedException("Seek is not supported.");
 
-                SetupWrite();
+                if (_bufferOwnedByWrite)
+                {
+                    FlushWriteBuffer();
+                }
+                else
+                {
+                    SetupWrite();
+                }
 
                 var attributes = _session.RequestFStat(_handle, false);
                 attributes.Size = value;
                 _session.RequestFSetStat(_handle, attributes);
+
+                if (_position > value)
+                {
+                    _position = value;
+                }
             }
         }