< Summary

Information
Class: Renci.SshNet.Sftp.SftpFileStream
Assembly: Renci.SshNet
File(s): \home\appveyor\projects\ssh-net\src\Renci.SshNet\Sftp\SftpFileStream.cs
Line coverage
83%
Covered lines: 547
Uncovered lines: 112
Coverable lines: 659
Total lines: 1349
Line coverage: 83%
Branch coverage
81%
Covered branches: 191
Total branches: 234
Branch coverage: 81.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.ctor(...)100%1100%
get_CanRead()100%1100%
get_CanSeek()100%1100%
get_CanWrite()100%1100%
get_CanTimeout()100%1100%
get_Length()83.33%688.23%
get_Position()50%271.42%
set_Position(...)100%1100%
get_Name()100%1100%
get_Handle()100%10%
get_Timeout()100%1100%
.ctor(...)91.42%3591.89%
OpenAsync()90.32%3178.57%
Finalize()100%1100%
Flush()100%2100%
FlushAsync(...)50%275%
Read(...)80%2088.05%
ReadAsync()75%2082.81%
ReadByte()100%4100%
Seek(...)60%3061.81%
SetLength(...)75%884.61%
Write(...)70%2064.81%
WriteAsync()75%2073.33%
WriteByte(...)50%261.11%
Dispose(...)100%12100%
GetOrCreateReadBuffer()100%2100%
GetOrCreateWriteBuffer()100%2100%
FlushReadBuffer()100%1100%
FlushWriteBuffer()100%2100%
FlushWriteBufferAsync()100%2100%
SetupRead()100%4100%
SetupWrite()100%4100%
CheckSessionIsOpen()100%2100%

File(s)

\home\appveyor\projects\ssh-net\src\Renci.SshNet\Sftp\SftpFileStream.cs

#LineLine coverage
 1using System;
 2using System.Diagnostics.CodeAnalysis;
 3using System.Globalization;
 4using System.IO;
 5using System.Threading;
 6using System.Threading.Tasks;
 7
 8using Renci.SshNet.Common;
 9
 10namespace Renci.SshNet.Sftp
 11{
 12    /// <summary>
 13    /// Exposes a <see cref="Stream"/> around a remote SFTP file, supporting both synchronous and asynchronous read and 
 14    /// </summary>
 15    /// <threadsafety static="true" instance="false"/>
 16#pragma warning disable CA1844 // Provide memory-based overrides of async methods when subclassing 'Stream'
 17    public class SftpFileStream : Stream
 18#pragma warning restore CA1844 // Provide memory-based overrides of async methods when subclassing 'Stream'
 19    {
 152820        private readonly object _lock = new object();
 21        private readonly int _readBufferSize;
 22        private readonly int _writeBufferSize;
 23
 24        // Internal state.
 25        private byte[] _handle;
 26        private ISftpSession _session;
 27
 28        // Buffer information.
 29        private byte[] _readBuffer;
 30        private byte[] _writeBuffer;
 31        private int _bufferPosition;
 32        private int _bufferLen;
 33        private long _position;
 34        private bool _bufferOwnedByWrite;
 35        private bool _canRead;
 36        private bool _canSeek;
 37        private bool _canWrite;
 38
 39        /// <summary>
 40        /// Gets a value indicating whether the current stream supports reading.
 41        /// </summary>
 42        /// <returns>
 43        /// <see langword="true"/> if the stream supports reading; otherwise, <see langword="false"/>.
 44        /// </returns>
 45        public override bool CanRead
 46        {
 243347            get { return _canRead; }
 48        }
 49
 50        /// <summary>
 51        /// Gets a value indicating whether the current stream supports seeking.
 52        /// </summary>
 53        /// <returns>
 54        /// <see langword="true"/> if the stream supports seeking; otherwise, <see langword="false"/>.
 55        /// </returns>
 56        public override bool CanSeek
 57        {
 224758            get { return _canSeek; }
 59        }
 60
 61        /// <summary>
 62        /// Gets a value indicating whether the current stream supports writing.
 63        /// </summary>
 64        /// <returns>
 65        /// <see langword="true"/> if the stream supports writing; otherwise, <see langword="false"/>.
 66        /// </returns>
 67        public override bool CanWrite
 68        {
 193269            get { return _canWrite; }
 70        }
 71
 72        /// <summary>
 73        /// Gets a value indicating whether timeout properties are usable for <see cref="SftpFileStream"/>.
 74        /// </summary>
 75        /// <value>
 76        /// <see langword="true"/> in all cases.
 77        /// </value>
 78        public override bool CanTimeout
 79        {
 27080            get { return true; }
 81        }
 82
 83        /// <summary>
 84        /// Gets the length in bytes of the stream.
 85        /// </summary>
 86        /// <returns>A long value representing the length of the stream in bytes.</returns>
 87        /// <exception cref="NotSupportedException">A class derived from Stream does not support seeking. </exception>
 88        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed. </exception>
 89        /// <exception cref="IOException">IO operation failed. </exception>
 90        [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification = "Be des
 91        public override long Length
 92        {
 93            get
 8294            {
 95                // Lock down the file stream while we do this.
 8296                lock (_lock)
 8297                {
 8298                    CheckSessionIsOpen();
 99
 82100                    if (!CanSeek)
 0101                    {
 0102                        throw new NotSupportedException("Seek operation is not supported.");
 103                    }
 104
 105                    // Flush the write buffer, because it may
 106                    // affect the length of the stream.
 82107                    if (_bufferOwnedByWrite)
 6108                    {
 6109                        FlushWriteBuffer();
 6110                    }
 111
 112                    // obtain file attributes
 82113                    var attributes = _session.RequestFStat(_handle, nullOnError: true);
 82114                    if (attributes != null)
 79115                    {
 79116                        return attributes.Size;
 117                    }
 118
 3119                    throw new IOException("Seek operation failed.");
 120                }
 79121            }
 122        }
 123
 124        /// <summary>
 125        /// Gets or sets the position within the current stream.
 126        /// </summary>
 127        /// <returns>The current position within the stream.</returns>
 128        /// <exception cref="IOException">An I/O error occurs. </exception>
 129        /// <exception cref="NotSupportedException">The stream does not support seeking. </exception>
 130        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed. </exception>
 131        public override long Position
 132        {
 133            get
 280134            {
 280135                CheckSessionIsOpen();
 136
 280137                if (!CanSeek)
 0138                {
 0139                    throw new NotSupportedException("Seek operation not supported.");
 140                }
 141
 280142                return _position;
 280143            }
 144            set
 3145            {
 3146                _ = Seek(value, SeekOrigin.Begin);
 3147            }
 148        }
 149
 150        /// <summary>
 151        /// Gets the name of the path that was used to construct the current <see cref="SftpFileStream"/>.
 152        /// </summary>
 153        /// <value>
 154        /// The name of the path that was used to construct the current <see cref="SftpFileStream"/>.
 155        /// </value>
 1528156        public string Name { get; private set; }
 157
 158        /// <summary>
 159        /// Gets the operating system file handle for the file that the current <see cref="SftpFileStream"/> encapsulate
 160        /// </summary>
 161        /// <value>
 162        /// The operating system file handle for the file that the current <see cref="SftpFileStream"/> encapsulates.
 163        /// </value>
 164        public virtual byte[] Handle
 165        {
 166            get
 0167            {
 0168                Flush();
 0169                return _handle;
 0170            }
 171        }
 172
 173        /// <summary>
 174        /// Gets or sets the operation timeout.
 175        /// </summary>
 176        /// <value>
 177        /// The timeout.
 178        /// </value>
 1528179        public TimeSpan Timeout { get; set; }
 180
 418181        private SftpFileStream(ISftpSession session, string path, FileAccess access, int bufferSize, byte[] handle, long
 418182        {
 418183            Timeout = TimeSpan.FromSeconds(30);
 418184            Name = path;
 185
 418186            _session = session;
 418187            _canRead = (access & FileAccess.Read) == FileAccess.Read;
 418188            _canSeek = true;
 418189            _canWrite = (access & FileAccess.Write) == FileAccess.Write;
 190
 418191            _handle = handle;
 192
 193            /*
 194             * Instead of using the specified buffer size as is, we use it to calculate a buffer size
 195             * that ensures we always receive or send the max. number of bytes in a single SSH_FXP_READ
 196             * or SSH_FXP_WRITE message.
 197             */
 198
 418199            _readBufferSize = (int) session.CalculateOptimalReadLength((uint) bufferSize);
 418200            _writeBufferSize = (int) session.CalculateOptimalWriteLength((uint) bufferSize, _handle);
 201
 418202            _position = position;
 418203        }
 204
 1110205        internal SftpFileStream(ISftpSession session, string path, FileMode mode, FileAccess access, int bufferSize)
 1110206        {
 1110207            if (session is null)
 0208            {
 0209                throw new SshConnectionException("Client not connected.");
 210            }
 211
 1110212            if (path is null)
 0213            {
 0214                throw new ArgumentNullException(nameof(path));
 215            }
 216
 1110217            if (bufferSize <= 0)
 0218            {
 0219                throw new ArgumentOutOfRangeException(nameof(bufferSize), "Cannot be less than or equal to zero.");
 220            }
 221
 1110222            Timeout = TimeSpan.FromSeconds(30);
 1110223            Name = path;
 224
 225            // Initialize the object state.
 1110226            _session = session;
 1110227            _canRead = (access & FileAccess.Read) == FileAccess.Read;
 1110228            _canSeek = true;
 1110229            _canWrite = (access & FileAccess.Write) == FileAccess.Write;
 230
 1110231            var flags = Flags.None;
 232
 1110233            switch (access)
 234            {
 235                case FileAccess.Read:
 377236                    flags |= Flags.Read;
 377237                    break;
 238                case FileAccess.Write:
 435239                    flags |= Flags.Write;
 435240                    break;
 241                case FileAccess.ReadWrite:
 295242                    flags |= Flags.Read;
 295243                    flags |= Flags.Write;
 295244                    break;
 245                default:
 3246                    throw new ArgumentOutOfRangeException(nameof(access));
 247            }
 248
 1107249            if ((access & FileAccess.Read) == FileAccess.Read && mode == FileMode.Append)
 6250            {
 6251                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
 6252                                                          "{0} mode can be requested only when combined with write-only 
 6253                                                          mode.ToString("G")),
 6254                                            nameof(mode));
 255            }
 256
 1101257            if ((access & FileAccess.Write) != FileAccess.Write)
 374258            {
 374259                if (mode is FileMode.Create or FileMode.CreateNew or FileMode.Truncate or FileMode.Append)
 9260                {
 9261                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
 9262                                                              "Combining {0}: {1} with {2}: {3} is invalid.",
 9263                                                              nameof(FileMode),
 9264                                                              mode,
 9265                                                              nameof(FileAccess),
 9266                                                              access),
 9267                                                nameof(mode));
 268                }
 365269            }
 270
 1092271            switch (mode)
 272            {
 273                case FileMode.Append:
 59274                    flags |= Flags.Append | Flags.CreateNewOrOpen;
 59275                    break;
 276                case FileMode.Create:
 170277                    _handle = _session.RequestOpen(path, flags | Flags.Truncate, nullOnError: true);
 170278                    if (_handle is null)
 63279                    {
 63280                        flags |= Flags.CreateNew;
 63281                    }
 282                    else
 107283                    {
 107284                        flags |= Flags.Truncate;
 107285                    }
 286
 170287                    break;
 288                case FileMode.CreateNew:
 65289                    flags |= Flags.CreateNew;
 65290                    break;
 291                case FileMode.Open:
 445292                    break;
 293                case FileMode.OpenOrCreate:
 288294                    flags |= Flags.CreateNewOrOpen;
 288295                    break;
 296                case FileMode.Truncate:
 62297                    flags |= Flags.Truncate;
 62298                    break;
 299                default:
 3300                    throw new ArgumentOutOfRangeException(nameof(mode));
 301            }
 302
 1089303            _handle ??= _session.RequestOpen(path, flags);
 304
 305            /*
 306             * Instead of using the specified buffer size as is, we use it to calculate a buffer size
 307             * that ensures we always receive or send the max. number of bytes in a single SSH_FXP_READ
 308             * or SSH_FXP_WRITE message.
 309             */
 310
 1063311            _readBufferSize = (int) session.CalculateOptimalReadLength((uint) bufferSize);
 1063312            _writeBufferSize = (int) session.CalculateOptimalWriteLength((uint) bufferSize, _handle);
 313
 1063314            if (mode == FileMode.Append)
 53315            {
 53316                var attributes = _session.RequestFStat(_handle, nullOnError: false);
 53317                _position = attributes.Size;
 53318            }
 1063319        }
 320
 321        internal static async Task<SftpFileStream> OpenAsync(ISftpSession session, string path, FileMode mode, FileAcces
 439322        {
 439323            if (session is null)
 0324            {
 0325                throw new SshConnectionException("Client not connected.");
 326            }
 327
 439328            if (path is null)
 0329            {
 0330                throw new ArgumentNullException(nameof(path));
 331            }
 332
 439333            if (bufferSize <= 0)
 0334            {
 0335                throw new ArgumentOutOfRangeException(nameof(bufferSize), "Cannot be less than or equal to zero.");
 336            }
 337
 439338            var flags = Flags.None;
 339
 439340            switch (access)
 341            {
 342                case FileAccess.Read:
 108343                    flags |= Flags.Read;
 108344                    break;
 345                case FileAccess.Write:
 181346                    flags |= Flags.Write;
 181347                    break;
 348                case FileAccess.ReadWrite:
 147349                    flags |= Flags.Read;
 147350                    flags |= Flags.Write;
 147351                    break;
 352                default:
 3353                    throw new ArgumentOutOfRangeException(nameof(access));
 354            }
 355
 436356            if ((access & FileAccess.Read) == FileAccess.Read && mode == FileMode.Append)
 6357            {
 6358                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
 6359                                                          "{0} mode can be requested only when combined with write-only 
 6360                                                          mode.ToString("G")),
 6361                                            nameof(mode));
 362            }
 363
 430364            if ((access & FileAccess.Write) != FileAccess.Write)
 105365            {
 105366                if (mode is FileMode.Create or FileMode.CreateNew or FileMode.Truncate or FileMode.Append)
 9367                {
 9368                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
 9369                                                              "Combining {0}: {1} with {2}: {3} is invalid.",
 9370                                                              nameof(FileMode),
 9371                                                              mode,
 9372                                                              nameof(FileAccess),
 9373                                                              access),
 9374                                                nameof(mode));
 375                }
 96376            }
 377
 421378            switch (mode)
 379            {
 380                case FileMode.Append:
 27381                    flags |= Flags.Append | Flags.CreateNewOrOpen;
 27382                    break;
 383                case FileMode.Create:
 105384                    flags |= Flags.CreateNewOrOpen | Flags.Truncate;
 105385                    break;
 386                case FileMode.CreateNew:
 49387                    flags |= Flags.CreateNew;
 49388                    break;
 389                case FileMode.Open:
 117390                    break;
 391                case FileMode.OpenOrCreate:
 72392                    flags |= Flags.CreateNewOrOpen;
 72393                    break;
 394                case FileMode.Truncate:
 48395                    flags |= Flags.Truncate;
 48396                    break;
 397                default:
 3398                    throw new ArgumentOutOfRangeException(nameof(mode));
 399            }
 400
 418401            var handle = await session.RequestOpenAsync(path, flags, cancellationToken).ConfigureAwait(false);
 402
 418403            long position = 0;
 418404            if (mode == FileMode.Append)
 27405            {
 406                try
 27407                {
 27408                    var attributes = await session.RequestFStatAsync(handle, cancellationToken).ConfigureAwait(false);
 27409                    position = attributes.Size;
 27410                }
 0411                catch
 0412                {
 413                    try
 0414                    {
 0415                        await session.RequestCloseAsync(handle, cancellationToken).ConfigureAwait(false);
 0416                    }
 0417                    catch
 0418                    {
 419                        // The original exception is presumably more informative, so we just ignore this one.
 0420                    }
 421
 0422                    throw;
 423                }
 424            }
 425
 418426            return new SftpFileStream(session, path, access, bufferSize, handle, position);
 418427        }
 428
 429        /// <summary>
 430        /// Finalizes an instance of the <see cref="SftpFileStream"/> class.
 431        /// </summary>
 432        ~SftpFileStream()
 2344433        {
 1172434            Dispose(disposing: false);
 2344435        }
 436
 437        /// <summary>
 438        /// Clears all buffers for this stream and causes any buffered data to be written to the file.
 439        /// </summary>
 440        /// <exception cref="IOException">An I/O error occurs. </exception>
 441        /// <exception cref="ObjectDisposedException">Stream is closed.</exception>
 442        public override void Flush()
 105443        {
 105444            lock (_lock)
 105445            {
 105446                CheckSessionIsOpen();
 447
 102448                if (_bufferOwnedByWrite)
 81449                {
 81450                    FlushWriteBuffer();
 81451                }
 452                else
 21453                {
 21454                    FlushReadBuffer();
 21455                }
 102456            }
 102457        }
 458
 459        /// <summary>
 460        /// Asynchronously clears all buffers for this stream and causes any buffered data to be written to the file.
 461        /// </summary>
 462        /// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
 463        /// <returns>A <see cref="Task"/> that represents the asynchronous flush operation.</returns>
 464        /// <exception cref="IOException">An I/O error occurs. </exception>
 465        /// <exception cref="ObjectDisposedException">Stream is closed.</exception>
 466        public override Task FlushAsync(CancellationToken cancellationToken)
 3467        {
 3468            CheckSessionIsOpen();
 469
 3470            if (_bufferOwnedByWrite)
 3471            {
 3472                return FlushWriteBufferAsync(cancellationToken);
 473            }
 474
 0475            FlushReadBuffer();
 476
 0477            return Task.CompletedTask;
 3478        }
 479
 480        /// <summary>
 481        /// Reads a sequence of bytes from the current stream and advances the position within the stream by the
 482        /// number of bytes read.
 483        /// </summary>
 484        /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte arr
 485        /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the d
 486        /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
 487        /// <returns>
 488        /// The total number of bytes read into the buffer. This can be less than the number of bytes requested
 489        /// if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
 490        /// </returns>
 491        /// <exception cref="ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is lar
 492        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>. </exception>
 493        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="count"/> is negat
 494        /// <exception cref="IOException">An I/O error occurs. </exception>
 495        /// <exception cref="NotSupportedException">The stream does not support reading. </exception>
 496        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed. </exception>
 497        /// <remarks>
 498        /// <para>
 499        /// This method attempts to read up to <paramref name="count"/> bytes. This either from the buffer, from the
 500        /// server (using one or more <c>SSH_FXP_READ</c> requests) or using a combination of both.
 501        /// </para>
 502        /// <para>
 503        /// The read loop is interrupted when either <paramref name="count"/> bytes are read, the server returns zero
 504        /// bytes (EOF) or less bytes than the read buffer size.
 505        /// </para>
 506        /// <para>
 507        /// When a server returns less number of bytes than the read buffer size, this <c>may</c> indicate that EOF has
 508        /// been reached. A subsequent (<c>SSH_FXP_READ</c>) server request is necessary to make sure EOF has effectivel
 509        /// been reached.  Breaking out of the read loop avoids reading from the server twice to determine EOF: once in
 510        /// the read loop, and once upon the next <see cref="Read"/> or <see cref="ReadByte"/> invocation.
 511        /// </para>
 512        /// </remarks>
 513        public override int Read(byte[] buffer, int offset, int count)
 414514        {
 414515            var readLen = 0;
 516
 414517            if (buffer is null)
 0518            {
 0519                throw new ArgumentNullException(nameof(buffer));
 520            }
 521
 414522            if (offset < 0)
 0523            {
 0524                throw new ArgumentOutOfRangeException(nameof(offset));
 525            }
 526
 414527            if (count < 0)
 0528            {
 0529                throw new ArgumentOutOfRangeException(nameof(count));
 530            }
 531
 414532            if ((buffer.Length - offset) < count)
 0533            {
 0534                throw new ArgumentException("Invalid array range.");
 535            }
 536
 537            // Lock down the file stream while we do this.
 414538            lock (_lock)
 414539            {
 414540                CheckSessionIsOpen();
 541
 542                // Set up for the read operation.
 414543                SetupRead();
 544
 545                // Read data into the caller's buffer.
 699546                while (count > 0)
 592547                {
 548                    // How much data do we have available in the buffer?
 592549                    var bytesAvailableInBuffer = _bufferLen - _bufferPosition;
 592550                    if (bytesAvailableInBuffer <= 0)
 536551                    {
 536552                        var data = _session.RequestRead(_handle, (ulong) _position, (uint) _readBufferSize);
 553
 536554                        if (data.Length == 0)
 48555                        {
 48556                            _bufferPosition = 0;
 48557                            _bufferLen = 0;
 558
 48559                            break;
 560                        }
 561
 488562                        var bytesToWriteToCallerBuffer = count;
 488563                        if (bytesToWriteToCallerBuffer >= data.Length)
 367564                        {
 565                            // write all data read to caller-provided buffer
 367566                            bytesToWriteToCallerBuffer = data.Length;
 567
 568                            // reset buffer since we will skip buffering
 367569                            _bufferPosition = 0;
 367570                            _bufferLen = 0;
 367571                        }
 572                        else
 121573                        {
 574                            // determine number of bytes that we should write into read buffer
 121575                            var bytesToWriteToReadBuffer = data.Length - bytesToWriteToCallerBuffer;
 576
 577                            // write remaining bytes to read buffer
 121578                            Buffer.BlockCopy(data, count, GetOrCreateReadBuffer(), 0, bytesToWriteToReadBuffer);
 579
 580                            // update position in read buffer
 121581                            _bufferPosition = 0;
 582
 583                            // update number of bytes in read buffer
 121584                            _bufferLen = bytesToWriteToReadBuffer;
 121585                        }
 586
 587                        // write bytes to caller-provided buffer
 488588                        Buffer.BlockCopy(data, 0, buffer, offset, bytesToWriteToCallerBuffer);
 589
 590                        // update stream position
 488591                        _position += bytesToWriteToCallerBuffer;
 592
 593                        // record total number of bytes read into caller-provided buffer
 488594                        readLen += bytesToWriteToCallerBuffer;
 595
 596                        // break out of the read loop when the server returned less than the request number of bytes
 597                        // as that *may* indicate that we've reached EOF
 598                        //
 599                        // doing this avoids reading from server twice to determine EOF: once in the read loop, and
 600                        // once upon the next Read or ReadByte invocation by the caller
 488601                        if (data.Length < _readBufferSize)
 238602                        {
 238603                            break;
 604                        }
 605
 606                        // advance offset to start writing bytes into caller-provided buffer
 250607                        offset += bytesToWriteToCallerBuffer;
 608
 609                        // update number of bytes left to read into caller-provided buffer
 250610                        count -= bytesToWriteToCallerBuffer;
 250611                    }
 612                    else
 56613                    {
 614                        // limit the number of bytes to use from read buffer to the caller-request number of bytes
 56615                        if (bytesAvailableInBuffer > count)
 25616                        {
 25617                            bytesAvailableInBuffer = count;
 25618                        }
 619
 620                        // copy data from read buffer to the caller-provided buffer
 56621                        Buffer.BlockCopy(GetOrCreateReadBuffer(), _bufferPosition, buffer, offset, bytesAvailableInBuffe
 622
 623                        // update position in read buffer
 56624                        _bufferPosition += bytesAvailableInBuffer;
 625
 626                        // update stream position
 56627                        _position += bytesAvailableInBuffer;
 628
 629                        // record total number of bytes read into caller-provided buffer
 56630                        readLen += bytesAvailableInBuffer;
 631
 632                        // advance offset to start writing bytes into caller-provided buffer
 56633                        offset += bytesAvailableInBuffer;
 634
 635                        // update number of bytes left to read
 56636                        count -= bytesAvailableInBuffer;
 56637                    }
 306638                }
 393639            }
 640
 641            // return the number of bytes that were read to the caller.
 393642            return readLen;
 393643        }
 644
 645        /// <summary>
 646        /// Asynchronously reads a sequence of bytes from the current stream and advances the position within the stream
 647        /// number of bytes read.
 648        /// </summary>
 649        /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte arr
 650        /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the d
 651        /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
 652        /// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
 653        /// <returns>A <see cref="Task" /> that represents the asynchronous read operation.</returns>
 654        public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationTo
 108655        {
 108656            var readLen = 0;
 657
 108658            if (buffer is null)
 0659            {
 0660                throw new ArgumentNullException(nameof(buffer));
 661            }
 662
 108663            if (offset < 0)
 0664            {
 0665                throw new ArgumentOutOfRangeException(nameof(offset));
 666            }
 667
 108668            if (count < 0)
 0669            {
 0670                throw new ArgumentOutOfRangeException(nameof(count));
 671            }
 672
 108673            if ((buffer.Length - offset) < count)
 0674            {
 0675                throw new ArgumentException("Invalid array range.");
 676            }
 677
 108678            CheckSessionIsOpen();
 679
 680            // Set up for the read operation.
 108681            SetupRead();
 682
 683            // Read data into the caller's buffer.
 144684            while (count > 0)
 108685            {
 686                // How much data do we have available in the buffer?
 108687                var bytesAvailableInBuffer = _bufferLen - _bufferPosition;
 108688                if (bytesAvailableInBuffer <= 0)
 96689                {
 96690                    var data = await _session.RequestReadAsync(_handle, (ulong)_position, (uint)_readBufferSize, cancell
 691
 96692                    if (data.Length == 0)
 12693                    {
 12694                        _bufferPosition = 0;
 12695                        _bufferLen = 0;
 696
 12697                        break;
 698                    }
 699
 84700                    var bytesToWriteToCallerBuffer = count;
 84701                    if (bytesToWriteToCallerBuffer >= data.Length)
 54702                    {
 703                        // write all data read to caller-provided buffer
 54704                        bytesToWriteToCallerBuffer = data.Length;
 705
 706                        // reset buffer since we will skip buffering
 54707                        _bufferPosition = 0;
 54708                        _bufferLen = 0;
 54709                    }
 710                    else
 30711                    {
 712                        // determine number of bytes that we should write into read buffer
 30713                        var bytesToWriteToReadBuffer = data.Length - bytesToWriteToCallerBuffer;
 714
 715                        // write remaining bytes to read buffer
 30716                        Buffer.BlockCopy(data, count, GetOrCreateReadBuffer(), 0, bytesToWriteToReadBuffer);
 717
 718                        // update position in read buffer
 30719                        _bufferPosition = 0;
 720
 721                        // update number of bytes in read buffer
 30722                        _bufferLen = bytesToWriteToReadBuffer;
 30723                    }
 724
 725                    // write bytes to caller-provided buffer
 84726                    Buffer.BlockCopy(data, 0, buffer, offset, bytesToWriteToCallerBuffer);
 727
 728                    // update stream position
 84729                    _position += bytesToWriteToCallerBuffer;
 730
 731                    // record total number of bytes read into caller-provided buffer
 84732                    readLen += bytesToWriteToCallerBuffer;
 733
 734                    // break out of the read loop when the server returned less than the request number of bytes
 735                    // as that *may* indicate that we've reached EOF
 736                    //
 737                    // doing this avoids reading from server twice to determine EOF: once in the read loop, and
 738                    // once upon the next Read or ReadByte invocation by the caller
 84739                    if (data.Length < _readBufferSize)
 39740                    {
 39741                        break;
 742                    }
 743
 744                    // advance offset to start writing bytes into caller-provided buffer
 45745                    offset += bytesToWriteToCallerBuffer;
 746
 747                    // update number of bytes left to read into caller-provided buffer
 45748                    count -= bytesToWriteToCallerBuffer;
 45749                }
 750                else
 12751                {
 752                    // limit the number of bytes to use from read buffer to the caller-request number of bytes
 12753                    if (bytesAvailableInBuffer > count)
 0754                    {
 0755                        bytesAvailableInBuffer = count;
 0756                    }
 757
 758                    // copy data from read buffer to the caller-provided buffer
 12759                    Buffer.BlockCopy(GetOrCreateReadBuffer(), _bufferPosition, buffer, offset, bytesAvailableInBuffer);
 760
 761                    // update position in read buffer
 12762                    _bufferPosition += bytesAvailableInBuffer;
 763
 764                    // update stream position
 12765                    _position += bytesAvailableInBuffer;
 766
 767                    // record total number of bytes read into caller-provided buffer
 12768                    readLen += bytesAvailableInBuffer;
 769
 770                    // advance offset to start writing bytes into caller-provided buffer
 12771                    offset += bytesAvailableInBuffer;
 772
 773                    // update number of bytes left to read
 12774                    count -= bytesAvailableInBuffer;
 12775                }
 57776            }
 777
 778            // return the number of bytes that were read to the caller.
 87779            return readLen;
 87780        }
 781
 782        /// <summary>
 783        /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at th
 784        /// </summary>
 785        /// <returns>
 786        /// The unsigned byte cast to an <see cref="int"/>, or -1 if at the end of the stream.
 787        /// </returns>
 788        /// <exception cref="NotSupportedException">The stream does not support reading. </exception>
 789        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed. </exception>
 790        /// <exception cref="IOException">Read operation failed.</exception>
 791        public override int ReadByte()
 160792        {
 793            // Lock down the file stream while we do this.
 160794            lock (_lock)
 160795            {
 160796                CheckSessionIsOpen();
 797
 798                // Setup the object for reading.
 160799                SetupRead();
 800
 801                byte[] readBuffer;
 802
 803                // Read more data into the internal buffer if necessary.
 139804                if (_bufferPosition >= _bufferLen)
 112805                {
 112806                    var data = _session.RequestRead(_handle, (ulong) _position, (uint) _readBufferSize);
 112807                    if (data.Length == 0)
 43808                    {
 809                        // We've reached EOF.
 43810                        return -1;
 811                    }
 812
 69813                    readBuffer = GetOrCreateReadBuffer();
 69814                    Buffer.BlockCopy(data, 0, readBuffer, 0, data.Length);
 815
 69816                    _bufferPosition = 0;
 69817                    _bufferLen = data.Length;
 69818                }
 819                else
 27820                {
 27821                    readBuffer = GetOrCreateReadBuffer();
 27822                }
 823
 824                // Extract the next byte from the buffer.
 96825                ++_position;
 826
 96827                return readBuffer[_bufferPosition++];
 828            }
 139829        }
 830
 831        /// <summary>
 832        /// Sets the position within the current stream.
 833        /// </summary>
 834        /// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param>
 835        /// <param name="origin">A value of type <see cref="SeekOrigin"/> indicating the reference point used to obtain 
 836        /// <returns>
 837        /// The new position within the current stream.
 838        /// </returns>
 839        /// <exception cref="IOException">An I/O error occurs. </exception>
 840        /// <exception cref="NotSupportedException">The stream does not support seeking, such as if the stream is constr
 841        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed. </exception>
 842        public override long Seek(long offset, SeekOrigin origin)
 107843        {
 844            long newPosn;
 845
 846            // Lock down the file stream while we do this.
 107847            lock (_lock)
 107848            {
 107849                CheckSessionIsOpen();
 850
 107851                if (!CanSeek)
 0852                {
 0853                    throw new NotSupportedException("Seek is not supported.");
 854                }
 855
 856                // Don't do anything if the position won't be moving.
 107857                if (origin == SeekOrigin.Begin && offset == _position)
 10858                {
 10859                    return offset;
 860                }
 861
 97862                if (origin == SeekOrigin.Current && offset == 0)
 0863                {
 0864                    return _position;
 865                }
 866
 867                // The behaviour depends upon the read/write mode.
 97868                if (_bufferOwnedByWrite)
 31869                {
 870                    // Flush the write buffer and then seek.
 31871                    FlushWriteBuffer();
 31872                }
 873                else
 66874                {
 875                    // Determine if the seek is to somewhere inside
 876                    // the current read buffer bounds.
 66877                    if (origin == SeekOrigin.Begin)
 58878                    {
 58879                        newPosn = _position - _bufferPosition;
 58880                        if (offset >= newPosn && offset < (newPosn + _bufferLen))
 0881                        {
 0882                            _bufferPosition = (int)(offset - newPosn);
 0883                            _position = offset;
 0884                            return _position;
 885                        }
 58886                    }
 8887                    else if (origin == SeekOrigin.Current)
 0888                    {
 0889                        newPosn = _position + offset;
 0890                        if (newPosn >= (_position - _bufferPosition) &&
 0891                           newPosn < (_position - _bufferPosition + _bufferLen))
 0892                        {
 0893                            _bufferPosition = (int) (newPosn - (_position - _bufferPosition));
 0894                            _position = newPosn;
 0895                            return _position;
 896                        }
 0897                    }
 898
 899                    // Abandon the read buffer.
 66900                    _bufferPosition = 0;
 66901                    _bufferLen = 0;
 66902                }
 903
 904                // Seek to the new position.
 97905                switch (origin)
 906                {
 907                    case SeekOrigin.Begin:
 60908                        newPosn = offset;
 60909                        break;
 910                    case SeekOrigin.Current:
 0911                        newPosn = _position + offset;
 0912                        break;
 913                    case SeekOrigin.End:
 37914                        var attributes = _session.RequestFStat(_handle, nullOnError: false);
 37915                        newPosn = attributes.Size + offset;
 37916                        break;
 917                    default:
 0918                        throw new ArgumentException("Invalid seek origin.", nameof(origin));
 919                }
 920
 97921                if (newPosn < 0)
 9922                {
 9923                    throw new EndOfStreamException();
 924                }
 925
 88926                _position = newPosn;
 88927                return _position;
 928            }
 98929        }
 930
 931        /// <summary>
 932        /// Sets the length of the current stream.
 933        /// </summary>
 934        /// <param name="value">The desired length of the current stream in bytes.</param>
 935        /// <exception cref="IOException">An I/O error occurs.</exception>
 936        /// <exception cref="NotSupportedException">The stream does not support both writing and seeking.</exception>
 937        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
 938        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> must be greater than zero.</exception
 939        /// <remarks>
 940        /// <para>
 941        /// Buffers are first flushed.
 942        /// </para>
 943        /// <para>
 944        /// If the specified value is less than the current length of the stream, the stream is truncated and - if the
 945        /// current position is greater than the new length - the current position is moved to the last byte of the stre
 946        /// </para>
 947        /// <para>
 948        /// If the given value is greater than the current length of the stream, the stream is expanded and the current
 949        /// position remains the same.
 950        /// </para>
 951        /// </remarks>
 952        public override void SetLength(long value)
 133953        {
 133954            if (value < 0)
 0955            {
 0956                throw new ArgumentOutOfRangeException(nameof(value));
 957            }
 958
 959            // Lock down the file stream while we do this.
 133960            lock (_lock)
 133961            {
 133962                CheckSessionIsOpen();
 963
 124964                if (!CanSeek)
 0965                {
 0966                    throw new NotSupportedException("Seek is not supported.");
 967                }
 968
 124969                if (_bufferOwnedByWrite)
 37970                {
 37971                    FlushWriteBuffer();
 37972                }
 973                else
 87974                {
 87975                    SetupWrite();
 84976                }
 977
 121978                var attributes = _session.RequestFStat(_handle, nullOnError: false);
 121979                attributes.Size = value;
 121980                _session.RequestFSetStat(_handle, attributes);
 981
 121982                if (_position > value)
 35983                {
 35984                    _position = value;
 35985                }
 121986            }
 121987        }
 988
 989        /// <summary>
 990        /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the
 991        /// </summary>
 992        /// <param name="buffer">An array of bytes. This method copies <paramref name="count"/> bytes from <paramref nam
 993        /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes
 994        /// <param name="count">The number of bytes to be written to the current stream.</param>
 995        /// <exception cref="ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is gre
 996        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
 997        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="count"/> is negat
 998        /// <exception cref="IOException">An I/O error occurs.</exception>
 999        /// <exception cref="NotSupportedException">The stream does not support writing.</exception>
 1000        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
 1001        public override void Write(byte[] buffer, int offset, int count)
 2801002        {
 2801003            if (buffer is null)
 01004            {
 01005                throw new ArgumentNullException(nameof(buffer));
 1006            }
 1007
 2801008            if (offset < 0)
 01009            {
 01010                throw new ArgumentOutOfRangeException(nameof(offset));
 1011            }
 1012
 2801013            if (count < 0)
 01014            {
 01015                throw new ArgumentOutOfRangeException(nameof(count));
 1016            }
 1017
 2801018            if ((buffer.Length - offset) < count)
 01019            {
 01020                throw new ArgumentException("Invalid array range.");
 1021            }
 1022
 1023            // Lock down the file stream while we do this.
 2801024            lock (_lock)
 2801025            {
 2801026                CheckSessionIsOpen();
 1027
 1028                // Setup this object for writing.
 2801029                SetupWrite();
 1030
 1031                // Write data to the file stream.
 6071032                while (count > 0)
 3331033                {
 1034                    // Determine how many bytes we can write to the buffer.
 3331035                    var tempLen = _writeBufferSize - _bufferPosition;
 3331036                    if (tempLen <= 0)
 01037                    {
 1038                        // flush write buffer, and mark it empty
 01039                        FlushWriteBuffer();
 1040
 1041                        // we can now write or buffer the full buffer size
 01042                        tempLen = _writeBufferSize;
 01043                    }
 1044
 1045                    // limit the number of bytes to write to the actual number of bytes requested
 3331046                    if (tempLen > count)
 2081047                    {
 2081048                        tempLen = count;
 2081049                    }
 1050
 1051                    // Can we short-cut the internal buffer?
 3331052                    if (_bufferPosition == 0 && tempLen == _writeBufferSize)
 1251053                    {
 1251054                        using (var wait = new AutoResetEvent(initialState: false))
 1251055                        {
 1251056                            _session.RequestWrite(_handle, (ulong) _position, buffer, offset, tempLen, wait);
 1251057                        }
 1251058                    }
 1059                    else
 2081060                    {
 1061                        // No: copy the data to the write buffer first.
 2081062                        Buffer.BlockCopy(buffer, offset, GetOrCreateWriteBuffer(), _bufferPosition, tempLen);
 2081063                        _bufferPosition += tempLen;
 2081064                    }
 1065
 1066                    // Advance the buffer and stream positions.
 3331067                    _position += tempLen;
 3331068                    offset += tempLen;
 3331069                    count -= tempLen;
 3331070                }
 1071
 1072                // If the buffer is full, then do a speculative flush now,
 1073                // rather than waiting for the next call to this method.
 2741074                if (_bufferPosition >= _writeBufferSize)
 01075                {
 01076                    using (var wait = new AutoResetEvent(initialState: false))
 01077                    {
 01078                        _session.RequestWrite(_handle, (ulong) (_position - _bufferPosition), GetOrCreateWriteBuffer(), 
 01079                    }
 1080
 01081                    _bufferPosition = 0;
 01082                }
 2741083            }
 2741084        }
 1085
 1086        /// <summary>
 1087        /// Asynchronously writes a sequence of bytes to the current stream and advances the current position within thi
 1088        /// </summary>
 1089        /// <param name="buffer">An array of bytes. This method copies <paramref name="count"/> bytes from <paramref nam
 1090        /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes
 1091        /// <param name="count">The number of bytes to be written to the current stream.</param>
 1092        /// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
 1093        /// <returns>A <see cref="Task"/> that represents the asynchronous write operation.</returns>
 1094        /// <exception cref="ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is gre
 1095        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
 1096        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="count"/> is negat
 1097        /// <exception cref="IOException">An I/O error occurs.</exception>
 1098        /// <exception cref="NotSupportedException">The stream does not support writing.</exception>
 1099        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
 1100        public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 621101        {
 621102            if (buffer is null)
 01103            {
 01104                throw new ArgumentNullException(nameof(buffer));
 1105            }
 1106
 621107            if (offset < 0)
 01108            {
 01109                throw new ArgumentOutOfRangeException(nameof(offset));
 1110            }
 1111
 621112            if (count < 0)
 01113            {
 01114                throw new ArgumentOutOfRangeException(nameof(count));
 1115            }
 1116
 621117            if ((buffer.Length - offset) < count)
 01118            {
 01119                throw new ArgumentException("Invalid array range.");
 1120            }
 1121
 621122            CheckSessionIsOpen();
 1123
 1124            // Setup this object for writing.
 621125            SetupWrite();
 1126
 1127            // Write data to the file stream.
 1621128            while (count > 0)
 1061129            {
 1130                // Determine how many bytes we can write to the buffer.
 1061131                var tempLen = _writeBufferSize - _bufferPosition;
 1061132                if (tempLen <= 0)
 71133                {
 1134                    // flush write buffer, and mark it empty
 71135                    await FlushWriteBufferAsync(cancellationToken).ConfigureAwait(false);
 1136
 1137                    // we can now write or buffer the full buffer size
 71138                    tempLen = _writeBufferSize;
 71139                }
 1140
 1141                // limit the number of bytes to write to the actual number of bytes requested
 1061142                if (tempLen > count)
 171143                {
 171144                    tempLen = count;
 171145                }
 1146
 1147                // Can we short-cut the internal buffer?
 1061148                if (_bufferPosition == 0 && tempLen == _writeBufferSize)
 821149                {
 821150                    await _session.RequestWriteAsync(_handle, (ulong)_position, buffer, offset, tempLen, cancellationTok
 821151                }
 1152                else
 241153                {
 1154                    // No: copy the data to the write buffer first.
 241155                    Buffer.BlockCopy(buffer, offset, GetOrCreateWriteBuffer(), _bufferPosition, tempLen);
 241156                    _bufferPosition += tempLen;
 241157                }
 1158
 1159                // Advance the buffer and stream positions.
 1061160                _position += tempLen;
 1061161                offset += tempLen;
 1061162                count -= tempLen;
 1061163            }
 1164
 1165            // If the buffer is full, then do a speculative flush now,
 1166            // rather than waiting for the next call to this method.
 561167            if (_bufferPosition >= _writeBufferSize)
 01168            {
 01169                await _session.RequestWriteAsync(_handle, (ulong)(_position - _bufferPosition), GetOrCreateWriteBuffer()
 01170                _bufferPosition = 0;
 01171            }
 561172        }
 1173
 1174        /// <summary>
 1175        /// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
 1176        /// </summary>
 1177        /// <param name="value">The byte to write to the stream.</param>
 1178        /// <exception cref="IOException">An I/O error occurs. </exception>
 1179        /// <exception cref="NotSupportedException">The stream does not support writing, or the stream is already closed
 1180        /// <exception cref="ObjectDisposedException">Methods were called after the stream was closed. </exception>
 1181        public override void WriteByte(byte value)
 391182        {
 1183            // Lock down the file stream while we do this.
 391184            lock (_lock)
 391185            {
 391186                CheckSessionIsOpen();
 1187
 1188                // Setup the object for writing.
 391189                SetupWrite();
 1190
 331191                var writeBuffer = GetOrCreateWriteBuffer();
 1192
 1193                // Flush the current buffer if it is full.
 331194                if (_bufferPosition >= _writeBufferSize)
 01195                {
 01196                    using (var wait = new AutoResetEvent(initialState: false))
 01197                    {
 01198                        _session.RequestWrite(_handle, (ulong) (_position - _bufferPosition), writeBuffer, 0, _bufferPos
 01199                    }
 1200
 01201                    _bufferPosition = 0;
 01202                }
 1203
 1204                // Write the byte into the buffer and advance the posn.
 331205                writeBuffer[_bufferPosition++] = value;
 331206                ++_position;
 331207            }
 331208        }
 1209
 1210        /// <summary>
 1211        /// Releases the unmanaged resources used by the <see cref="Stream"/> and optionally releases the managed resour
 1212        /// </summary>
 1213        /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langwor
 1214        protected override void Dispose(bool disposing)
 15541215        {
 15541216            base.Dispose(disposing);
 1217
 15541218            if (_session != null)
 15281219            {
 15281220                if (disposing)
 3561221                {
 3561222                    lock (_lock)
 3561223                    {
 3561224                        if (_session != null)
 3561225                        {
 3561226                            _canRead = false;
 3561227                            _canSeek = false;
 3561228                            _canWrite = false;
 1229
 3561230                            if (_handle != null)
 3561231                            {
 3561232                                if (_session.IsOpen)
 3441233                                {
 3441234                                    if (_bufferOwnedByWrite)
 1311235                                    {
 1311236                                        FlushWriteBuffer();
 1311237                                    }
 1238
 3441239                                    _session.RequestClose(_handle);
 3441240                                }
 1241
 3561242                                _handle = null;
 3561243                            }
 1244
 3561245                            _session = null;
 3561246                        }
 3561247                    }
 3561248                }
 15281249            }
 15541250        }
 1251
 1252        private byte[] GetOrCreateReadBuffer()
 3151253        {
 3151254            _readBuffer ??= new byte[_readBufferSize];
 3151255            return _readBuffer;
 3151256        }
 1257
 1258        private byte[] GetOrCreateWriteBuffer()
 2651259        {
 2651260            _writeBuffer ??= new byte[_writeBufferSize];
 2651261            return _writeBuffer;
 2651262        }
 1263
 1264        /// <summary>
 1265        /// Flushes the read data from the buffer.
 1266        /// </summary>
 1267        private void FlushReadBuffer()
 3921268        {
 3921269            _bufferPosition = 0;
 3921270            _bufferLen = 0;
 3921271        }
 1272
 1273        /// <summary>
 1274        /// Flush any buffered write data to the file.
 1275        /// </summary>
 1276        private void FlushWriteBuffer()
 3101277        {
 3101278            if (_bufferPosition > 0)
 1911279            {
 1911280                using (var wait = new AutoResetEvent(initialState: false))
 1911281                {
 1911282                    _session.RequestWrite(_handle, (ulong) (_position - _bufferPosition), _writeBuffer, 0, _bufferPositi
 1911283                }
 1284
 1911285                _bufferPosition = 0;
 1911286            }
 3101287        }
 1288
 1289        private async Task FlushWriteBufferAsync(CancellationToken cancellationToken)
 101290        {
 101291            if (_bufferPosition > 0)
 101292            {
 101293                await _session.RequestWriteAsync(_handle, (ulong)(_position - _bufferPosition), _writeBuffer, 0, _buffer
 101294                _bufferPosition = 0;
 101295            }
 101296        }
 1297
 1298        /// <summary>
 1299        /// Setups the read.
 1300        /// </summary>
 1301        private void SetupRead()
 6821302        {
 6821303            if (!CanRead)
 631304            {
 631305                throw new NotSupportedException("Read not supported.");
 1306            }
 1307
 6191308            if (_bufferOwnedByWrite)
 241309            {
 241310                FlushWriteBuffer();
 241311                _bufferOwnedByWrite = false;
 241312            }
 6191313        }
 1314
 1315        /// <summary>
 1316        /// Setups the write.
 1317        /// </summary>
 1318        private void SetupWrite()
 4681319        {
 4681320            if (!CanWrite)
 211321            {
 211322                throw new NotSupportedException("Write not supported.");
 1323            }
 1324
 4471325            if (!_bufferOwnedByWrite)
 3711326            {
 3711327                FlushReadBuffer();
 3711328                _bufferOwnedByWrite = true;
 3711329            }
 4471330        }
 1331
 1332        private void CheckSessionIsOpen()
 17731333        {
 1334#if NET7_0_OR_GREATER
 14111335            ObjectDisposedException.ThrowIf(_session is null, this);
 1336#else
 3621337            if (_session is null)
 21338            {
 21339                throw new ObjectDisposedException(GetType().FullName);
 1340            }
 1341#endif // NET7_0_OR_GREATER
 1342
 17671343            if (!_session.IsOpen)
 61344            {
 61345                throw new ObjectDisposedException(GetType().FullName, "Cannot access a closed SFTP session.");
 1346            }
 17611347        }
 1348    }
 1349}