Open
Description
Background and motivation
I want to implement ZLib -> Base64Url encode to bytes.
- Use
ZLibStream
for compression - Use
RecyclableMemoryStream
as the stream to which compressed data is written because I wanna reduce allocations. - 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