Skip to content

[API Proposal]: Base64(Url).EncodeToXXX(ReadOnlySequence<byte>) #115449

Open
@RamType0

Description

@RamType0

Background and motivation

I want to implement ZLib -> Base64Url encode to bytes.

  1. Use ZLibStream for compression
  2. Use RecyclableMemoryStream as the stream to which compressed data is written because I wanna reduce allocations.
  3. Get compressed data by RecyclableMemoryStream.GetReadOnlySequence().

Then, how could we encode it to Base64 URL?

You may think that this would be work.

Span<char> encodedChars = stackalloc char[Base64Url.GetEncodedLength((int)sequence.Length)];
var destination = encodedChars;
var remainingLength = sequence.Length;
foreach (var memory in sequence)
{
    var span = memory.Span;
    remainingLength -= span.Length;
    Base64Url.EncodeToChars(span, destination, out var bytesConsumed, out var charsWritten, remainingLength == 0);
    destination = destination[charsWritten..];
}

Actually, we need to process border of segments.

API Proposal

namespace System.Buffers.Text

public static class Base64
{
    public static int EncodeToUtf8(ReadOnlySequence<byte> source, Span<byte> destination); 
    public static int EncodeToChars(ReadOnlySequence<byte> source, Span<char> destination); 

    public static void EncodeToUtf8(ReadOnlySequence<byte> source, IBufferWriter<byte> destination); 

    // This one could be not needed as IBufferWriter<char> is not commonly used
    public static void EncodeToChars(ReadOnlySequence<byte> source, IBufferWriter<char> destination); 

    public static string EncodeToString(ReadOnlySequence<byte> source); 
}
public static class Base64Url
{
    public static int EncodeToUtf8(ReadOnlySequence<byte> source, Span<byte> destination); 
    public static int EncodeToChars(ReadOnlySequence<byte> source, Span<char> destination); 

    public static void EncodeToUtf8(ReadOnlySequence<byte> source, IBufferWriter<byte> destination); 

    // This one could be not needed as IBufferWriter<char> is not commonly used
    public static void EncodeToChars(ReadOnlySequence<byte> source, IBufferWriter<char> destination); 

    public static string EncodeToString(ReadOnlySequence<byte> source); 
}

API Usage

using RecyclableMemoryStream memoryStream = MemoryStreamManager.GetStream();


using (ZLibStream zlibStream = new(memoryStream, compressionLevel, leaveOpen: true))
{
    zlibStream.Write(utf8DiagramSource);
}

var compressedSequence = memoryStream.GetReadOnlySequence();

return Base64Url.EncodeToString(compressedSequence);

Alternative Designs

I have created draft implementation.

public static int EncodeToChars(ReadOnlySequence<byte> source, Span<char> destination)
{
    const int BlockSize = 3;

    Span<byte> processBorderBuffer = stackalloc byte[BlockSize];

    var reader = new SequenceReader<byte>(source);
    var totalCharsWritten = 0;
    while (true)
    {
        var operationStatus = Base64Url.EncodeToChars(reader.UnreadSpan, destination, out var bytesConsumed, out var charsWritten, reader.UnreadSequence.IsSingleSegment);
        Debug.Assert(operationStatus is OperationStatus.NeedMoreData or OperationStatus.Done);

        reader.Advance(bytesConsumed);
        destination = destination[charsWritten..];
        totalCharsWritten += charsWritten;

        if (reader.End)
        {
            break;
        }

        if (reader.UnreadSpan.Length < BlockSize)
        {
            // Operation has been interrupted without completely consumed current span.
            // Need to process border.

            if (reader.TryCopyTo(processBorderBuffer))
            {
                reader.Advance(BlockSize);
            }
            else
            {
                // Remaining data is less than 3
                // UnreadSpan has at least 1 length and there is next segment with at least 1 length
                // So remaining data must be 2 bytes

                // No longer to maintain buffer, operation is almost over
                processBorderBuffer = processBorderBuffer[..2];

                var result = reader.TryCopyTo(processBorderBuffer);

                Debug.Assert(result);

                reader.AdvanceToEnd();
            }

            operationStatus = Base64Url.EncodeToChars(processBorderBuffer, destination, out bytesConsumed, out charsWritten, reader.End);

            Debug.Assert(operationStatus is OperationStatus.NeedMoreData or OperationStatus.Done);

            destination = destination[charsWritten..];
            totalCharsWritten += charsWritten;

            if (reader.End)
            {
                break;
            }

        }

    }
    return totalCharsWritten;
}

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.RuntimeuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions