|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.IO.Pipelines;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
internal static class ChunkWriter
{
public static int BeginChunkBytes(int dataCount, Span<byte> span)
{
// Determine the most-significant non-zero nibble
int total, shift;
var count = dataCount;
total = (count > 0xffff) ? 0x10 : 0x00;
count >>= total;
shift = (count > 0x00ff) ? 0x08 : 0x00;
count >>= shift;
total |= shift;
total |= (count > 0x000f) ? 0x04 : 0x00;
count = (total >> 2) + 3;
// This must be explicity typed as ReadOnlySpan<byte>
// It then becomes a non-allocating mapping to the data section of the assembly.
// For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
ReadOnlySpan<byte> hex = "0123456789abcdef"u8;
var offset = 0;
for (shift = total; shift >= 0; shift -= 4)
{
// Uses dotnet/runtime#1644 to elide the bounds check on hex as the & 0x0f definitely
// constrains it to the range 0x0 - 0xf, matching the bounds of the array.
span[offset] = hex[(dataCount >> shift) & 0x0f];
offset++;
}
span[count - 2] = (byte)'\r';
span[count - 1] = (byte)'\n';
return count;
}
internal static int GetPrefixBytesForChunk(int length, out bool sliceOneByte)
{
sliceOneByte = false;
// If GetMemory returns one of the following values, there is no way to set the prefix/body lengths
// such that we either wouldn't have an invalid chunk or would need to copy if the entire memory chunk is used.
// For example, if GetMemory returned 21, we would guess that the chunked prefix is 4 bytes initially
// and the suffix is 2 bytes, meaning there is 15 bytes remaining to write into. However, 15 bytes only need 3
// bytes for the chunked prefix, so we would have to copy once we call advance. Therefore, to avoid this scenario,
// we slice the memory by one byte.
// See https://gist.github.com/halter73/af2b9f78978f83813b19e187c4e5309e if you would like to tweak the algorithm at all.
if (length <= 65544)
{
if (length <= 262)
{
if (length <= 21)
{
if (length == 21)
{
sliceOneByte = true;
}
return 3;
}
else
{
if (length == 262)
{
sliceOneByte = true;
}
return 4;
}
}
else
{
if (length <= 4103)
{
if (length == 4103)
{
sliceOneByte = true;
}
return 5;
}
else
{
if (length == 65544)
{
sliceOneByte = true;
}
return 6;
}
}
}
else
{
if (length <= 16777226)
{
if (length <= 1048585)
{
if (length == 1048585)
{
sliceOneByte = true;
}
return 7;
}
else
{
if (length == 16777226)
{
sliceOneByte = true;
}
return 8;
}
}
else
{
if (length <= 268435467)
{
if (length == 268435467)
{
sliceOneByte = true;
}
return 9;
}
else
{
return 10;
}
}
}
}
internal static int WriteBeginChunkBytes(this ref BufferWriter<PipeWriter> start, int dataCount)
{
// 10 bytes is max length + \r\n
start.Ensure(10);
var count = BeginChunkBytes(dataCount, start.Span);
start.Advance(count);
return count;
}
internal static void WriteEndChunkBytes(this ref BufferWriter<PipeWriter> start)
{
start.Ensure(2);
var span = start.Span;
// CRLF done in reverse order so the 1st index will elide the bounds check for the 0th index
span[1] = (byte)'\n';
span[0] = (byte)'\r';
start.Advance(2);
}
}
|