public async override ValueTask <ReadResult> ReadAsync(CancellationToken cancellationToken = default) { // ReadAsync needs to handle some common situations: // 1. Base64 requires are least 4 bytes to decode content. If less than 4 bytes are returned // from the inner reader then repeatedly call the inner reader until 4 bytes are available. // 2. It is possible that ReadAsync is called many times without consuming the data. We don't // want to decode the same base64 content over and over. ReadAsync only decodes new content // and appends it to a sequence. var innerResult = await _inner.ReadAsync(cancellationToken); if (innerResult.Buffer.IsEmpty) { _currentDecodedBuffer = innerResult.Buffer; _currentInnerBuffer = innerResult.Buffer; return(innerResult); } // Minimum valid base64 length is 4. Read until we have at least that much content while (innerResult.Buffer.Length - _currentInnerBufferRead < 4) { if (innerResult.IsCompleted) { // If the reader completes with less than 4 bytes then the base64 isn't valid.. throw new InvalidOperationException("Unexpected end of data when reading base64 content."); } if (innerResult.IsCanceled) { // Cancelled before we have enough data to decode. Return a cancelled result with no data. _currentDecodedBuffer = ReadOnlySequence <byte> .Empty; _currentInnerBuffer = innerResult.Buffer; return(new ReadResult( ReadOnlySequence <byte> .Empty, innerResult.IsCanceled, innerResult.IsCompleted)); } // Attempt to get more data _inner.AdvanceTo(innerResult.Buffer.Start, innerResult.Buffer.End); innerResult = await _inner.ReadAsync(cancellationToken); } // Limit result to complete base64 segments (multiples of 4) var newResultLength = innerResult.Buffer.Length - _currentInnerBufferRead; var newResultValidLength = (newResultLength / 4) * 4; var buffer = innerResult.Buffer.Slice(_currentInnerBufferRead, newResultValidLength); // The content can contain multiple fragments of base64 content // Check for padding, and limit returned data to one fragment at a time var paddingIndex = PositionOf(buffer, (byte)'='); if (paddingIndex != null) { buffer = buffer.Slice(0, ((paddingIndex.Value / 4) + 1) * 4); } // Copy the buffer data to a new array. // Need a copy that we own because it will be decoded in place. var decodedBuffer = buffer.ToArray(); var status = Base64.DecodeFromUtf8InPlace(decodedBuffer, out var bytesWritten); if (status == OperationStatus.Done || status == OperationStatus.NeedMoreData) { _currentInnerBuffer = innerResult.Buffer.Slice(0, _currentInnerBufferRead + decodedBuffer.Length); _currentInnerBufferRead = _currentInnerBuffer.Length; // Update decoded buffer. If there have been multiple reads with the same content then // newly decoded content will be appended to the sequence. if (_currentDecodedBuffer.IsEmpty) { // Avoid creating segments for single segment sequence. _currentDecodedBuffer = new ReadOnlySequence <byte>(decodedBuffer, 0, bytesWritten); } else if (_currentDecodedBuffer.IsSingleSegment) { var start = new MemorySegment <byte>(_currentDecodedBuffer.First); // Append new content to end. var end = start.Append(decodedBuffer.AsMemory(0, bytesWritten)); _currentDecodedBuffer = new ReadOnlySequence <byte>(start, 0, end, end.Memory.Length); } else { var start = (MemorySegment <byte>)_currentDecodedBuffer.Start.GetObject() !; var end = (MemorySegment <byte>)_currentDecodedBuffer.End.GetObject() !; // Append new content to end. end = end.Append(decodedBuffer.AsMemory(0, bytesWritten)); _currentDecodedBuffer = new ReadOnlySequence <byte>(start, 0, end, end.Memory.Length); } return(new ReadResult( _currentDecodedBuffer, innerResult.IsCanceled, innerResult.IsCompleted)); } throw new InvalidOperationException("Unexpected status: " + status); }