File: System\Net\BufferedReadStream.cs
Web Access
Project: src\src\libraries\System.Net.Mail\src\System.Net.Mail.csproj (System.Net.Mail)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.IO;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Net
{
    internal sealed class BufferedReadStream : DelegatedStream
    {
        private byte[]? _storedBuffer;
        private int _storedLength;
        private int _storedOffset;
        private readonly bool _readMore;
 
        internal BufferedReadStream(Stream stream) : this(stream, false)
        {
        }
 
        internal BufferedReadStream(Stream stream, bool readMore) : base(stream)
        {
            _readMore = readMore;
        }
 
        public override bool CanWrite
        {
            get
            {
                return false;
            }
        }
 
        public override bool CanSeek
        {
            get
            {
                return false;
            }
        }
 
        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) =>
            TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), callback, state);
 
        public override int EndRead(IAsyncResult asyncResult) =>
            TaskToAsyncResult.End<int>(asyncResult);
 
        public override int Read(byte[] buffer, int offset, int count)
        {
            int read = 0;
            if (_storedOffset < _storedLength)
            {
                read = Math.Min(count, _storedLength - _storedOffset);
                Buffer.BlockCopy(_storedBuffer!, _storedOffset, buffer, offset, read);
                _storedOffset += read;
                if (read == count || !_readMore)
                {
                    return read;
                }
 
                offset += read;
                count -= read;
            }
            return read + base.Read(buffer, offset, count);
        }
 
        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            int read;
            if (_storedOffset >= _storedLength)
            {
                return base.ReadAsync(buffer, offset, count, cancellationToken);
            }
 
            read = Math.Min(count, _storedLength - _storedOffset);
            Buffer.BlockCopy(_storedBuffer!, _storedOffset, buffer, offset, read);
            _storedOffset += read;
            if (read == count || !_readMore)
            {
                return Task.FromResult<int>(read);
            }
 
            offset += read;
            count -= read;
 
            return ReadMoreAsync(read, buffer, offset, count, cancellationToken);
        }
 
        private async Task<int> ReadMoreAsync(int bytesAlreadyRead, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            int returnValue = await base.ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
            return bytesAlreadyRead + returnValue;
        }
 
        public override int ReadByte()
        {
            if (_storedOffset < _storedLength)
            {
                return _storedBuffer![_storedOffset++];
            }
            else
            {
                return base.ReadByte();
            }
        }
 
        // adds additional content to the beginning of the buffer
        // so the layout of the storedBuffer will be
        // <buffer><existingBuffer>
        // after calling push
        internal void Push(byte[] buffer, int offset, int count)
        {
            if (count == 0)
                return;
 
            if (_storedOffset == _storedLength)
            {
                if (_storedBuffer == null || _storedBuffer.Length < count)
                {
                    _storedBuffer = new byte[count];
                }
                _storedOffset = 0;
                _storedLength = count;
            }
            else
            {
                // if there's room to just insert before existing data
                if (count <= _storedOffset)
                {
                    _storedOffset -= count;
                }
                // if there's room in the buffer but need to shift things over
                else if (count <= _storedBuffer!.Length - _storedLength + _storedOffset)
                {
                    Buffer.BlockCopy(_storedBuffer, _storedOffset, _storedBuffer, count, _storedLength - _storedOffset);
                    _storedLength += count - _storedOffset;
                    _storedOffset = 0;
                }
                else
                {
                    byte[] newBuffer = new byte[count + _storedLength - _storedOffset];
                    Buffer.BlockCopy(_storedBuffer, _storedOffset, newBuffer, count, _storedLength - _storedOffset);
                    _storedLength += count - _storedOffset;
                    _storedOffset = 0;
                    _storedBuffer = newBuffer;
                }
            }
 
            Buffer.BlockCopy(buffer, offset, _storedBuffer!, _storedOffset, count);
        }
    }
}