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)); }
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."); } }