Пример #1
0
        public override async ValueTask <int> ReadAsync(Memory <byte> data, CancellationToken cancellationToken = default)
#endif
        {
#if NETSTANDARD2_0
            var data = buffer.AsMemory(offset, count);
#endif

            // There is enough remaining data to fill passed in data
            if (data.Length <= _remainder)
            {
                return(CopyRemainderToData(data));
            }

            var underlyingReadData    = data;
            var copyFromMinimumBuffer = false;
            if (data.Length < 6)
            {
                // If the requested data is very small, increase it to 6.
                // 4 bytes for base64, and 2 bytes for potential remaining content.
                if (_minimumBuffer == null)
                {
                    _minimumBuffer = new byte[4 + 2];
                }

                underlyingReadData    = _minimumBuffer;
                copyFromMinimumBuffer = true;
            }
            underlyingReadData = SetRemainder(underlyingReadData);

            // We want to read base64 data in multiples of 4
            underlyingReadData = underlyingReadData.Slice(0, (underlyingReadData.Length / 4) * 4);

            var availableReadData = underlyingReadData;

            var totalRead = 0;
            // Minimum valid base64 length is 4. Read until we have at least that much content
            do
            {
                var read = await StreamHelpers.ReadAsync(_inner, availableReadData, cancellationToken).ConfigureAwait(false);

                if (read == 0)
                {
                    if (_remainder > 0)
                    {
                        return(ReturnData(data, copyFromMinimumBuffer, bytesWritten: 0));
                    }
                    else if (totalRead == 0)
                    {
                        return(0);
                    }

                    throw new InvalidOperationException("Invalid base64 data.");
                }

                availableReadData = availableReadData.Slice(read);
                totalRead        += read;

                // The underlying stream may not have a complete 4 byte segment yet
                // so read again until we have the right data length.
            } while (totalRead % 4 != 0);

            var base64Data   = underlyingReadData.Slice(0, totalRead);
            int bytesWritten = DecodeBase64DataFragments(base64Data.Span);

            return(ReturnData(data, copyFromMinimumBuffer, bytesWritten));
        }
Пример #2
0
        public override async ValueTask WriteAsync(ReadOnlyMemory <byte> data, CancellationToken cancellationToken = default)
#endif
        {
#if NETSTANDARD2_0
            var data = buffer.AsMemory(offset, count);
#endif

            if (_buffer == null)
            {
                _buffer = ArrayPool <byte> .Shared.Rent(minimumLength : 4096);
            }

            // Data has to be encoded to base64 in increments of 3
            // 1. First handle any remaining data from the last call to WriteAsync
            //    - There may still not be enough data (e.g. 1 byte + 1 byte). Add to remaining data and exit
            //    - Use remaining data to write to buffer
            // 2. Write data to buffer and then write buffer until there is not enough data remaining
            // 3. Save remainder to buffer

            Memory <byte> localBuffer;
            if (_remainder > 0)
            {
                var required = 3 - _remainder;
                if (data.Length < required)
                {
                    // There is remainder and the new buffer doesn't have enough content for the
                    // remainder to be written as base64
                    data.CopyTo(_buffer.AsMemory(_remainder));
                    _remainder += data.Length;
                    return;
                }

                // Use data to complete remainder and write to buffer
                data.Slice(0, required).CopyTo(_buffer.AsMemory(_remainder));
                EnsureSuccess(Base64.EncodeToUtf8InPlace(_buffer, 3, out var bytesWritten));

                // Trim used data
                data        = data.Slice(required);
                localBuffer = _buffer.AsMemory(bytesWritten);
            }
            else
            {
                localBuffer = _buffer;
            }

            while (data.Length >= 3)
            {
                // Final encoded data length could exceed buffer length
                // When this happens the data will be encoded and WriteAsync in a loop
                var encodeLength = Math.Min(data.Length, localBuffer.Length / 4 * 3);

                EnsureSuccess(
                    Base64.EncodeToUtf8(data.Span.Slice(0, encodeLength), localBuffer.Span, out var bytesConsumed, out var bytesWritten, isFinalBlock: false),
#if NETSTANDARD2_1 || NETSTANDARD2_0
                    OperationStatus.NeedMoreData
#else
                    // React to fix https://github.com/dotnet/runtime/pull/281
                    encodeLength == bytesConsumed ? OperationStatus.Done : OperationStatus.NeedMoreData
#endif
                    );

                var base64Remainder = _buffer.Length - localBuffer.Length;
                await StreamHelpers.WriteAsync(_inner, _buffer, 0, bytesWritten + base64Remainder, cancellationToken).ConfigureAwait(false);

                data        = data.Slice(bytesConsumed);
                localBuffer = _buffer;
            }

            // Remainder content will usually be written with other data
            // If there was not enough data to write along with remainder then write it here
            if (localBuffer.Length < _buffer.Length)
            {
                await StreamHelpers.WriteAsync(_inner, _buffer, 0, 4, cancellationToken).ConfigureAwait(false);
            }

            if (data.Length > 0)
            {
                data.CopyTo(_buffer);
            }

            // Remainder can be 0-2 bytes
            _remainder = data.Length;
        }
        public override async ValueTask <int> ReadAsync(Memory <byte> data, CancellationToken cancellationToken = default)
#endif
        {
#if NETSTANDARD2_0
            var data = buffer.AsMemory(offset, count);
#endif

            switch (_state)
            {
            case ResponseState.Ready:
                // Read the header first
                // - 1 byte flag for compression
                // - 4 bytes for the content length
                Memory <byte> headerBuffer;

                if (data.Length >= 5)
                {
                    headerBuffer = data.Slice(0, 5);
                }
                else
                {
                    // Should never get here. Client always passes 5 to read the header.
                    throw new InvalidOperationException("Buffer is not large enough for header");
                }

                var success = await TryReadDataAsync(_inner, headerBuffer, cancellationToken).ConfigureAwait(false);

                if (!success)
                {
                    return(0);
                }

                var compressed = headerBuffer.Span[0];
                var length     = (int)BinaryPrimitives.ReadUInt32BigEndian(headerBuffer.Span.Slice(1));

                var isTrailer = IsBitSet(compressed, pos: 7);
                if (isTrailer)
                {
                    await ReadTrailersAsync(length, data, cancellationToken).ConfigureAwait(false);

                    return(0);
                }

                _contentRemaining = length;
                // If there is no content then state is still ready
                _state = _contentRemaining > 0 ? ResponseState.Content : ResponseState.Ready;
                return(5);

            case ResponseState.Content:
                if (data.Length >= _contentRemaining)
                {
                    data = data.Slice(0, _contentRemaining);
                }

                var read = await StreamHelpers.ReadAsync(_inner, data, cancellationToken).ConfigureAwait(false);

                _contentRemaining -= read;
                if (_contentRemaining == 0)
                {
                    _state = ResponseState.Ready;
                }

                return(read);

            default:
                throw new InvalidOperationException("Unexpected state.");
            }
        }