Unset reserved bit when setting 31 bit uint values (#7339)
* Unset reserved bit when setting 31 bit uint values * Add FrameWriter tests * Add overload
This commit is contained in:
parent
3de5cde536
commit
92f21dbda0
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -36,11 +36,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
// Does not overwrite the highest order bit
|
// Does not overwrite the highest order bit
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void WriteUInt31BigEndian(Span<byte> destination, uint value)
|
public static void WriteUInt31BigEndian(Span<byte> destination, uint value)
|
||||||
|
=> WriteUInt31BigEndian(destination, value, true);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void WriteUInt31BigEndian(Span<byte> destination, uint value, bool preserveHighestBit)
|
||||||
{
|
{
|
||||||
Debug.Assert(value <= 0x7F_FF_FF_FF, value.ToString());
|
Debug.Assert(value <= 0x7F_FF_FF_FF, value.ToString());
|
||||||
// Keep the highest bit
|
|
||||||
var reserved = (destination[0] & 0x80u) << 24;
|
if (preserveHighestBit)
|
||||||
BinaryPrimitives.WriteUInt32BigEndian(destination, value | reserved);
|
{
|
||||||
|
// Keep the highest bit
|
||||||
|
value |= (destination[0] & 0x80u) << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryPrimitives.WriteUInt32BigEndian(destination, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -402,7 +402,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
_outgoingFrame.PrepareWindowUpdate(streamId, sizeIncrement);
|
_outgoingFrame.PrepareWindowUpdate(streamId, sizeIncrement);
|
||||||
WriteHeaderUnsynchronized();
|
WriteHeaderUnsynchronized();
|
||||||
var buffer = _outputWriter.GetSpan(4);
|
var buffer = _outputWriter.GetSpan(4);
|
||||||
Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement);
|
Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement, preserveHighestBit: false);
|
||||||
_outputWriter.Advance(4);
|
_outputWriter.Advance(4);
|
||||||
return TimeFlushUnsynchronizedAsync();
|
return TimeFlushUnsynchronizedAsync();
|
||||||
}
|
}
|
||||||
|
|
@ -538,7 +538,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
WriteHeaderUnsynchronized();
|
WriteHeaderUnsynchronized();
|
||||||
|
|
||||||
var buffer = _outputWriter.GetSpan(8);
|
var buffer = _outputWriter.GetSpan(8);
|
||||||
Bitshifter.WriteUInt31BigEndian(buffer, (uint)lastStreamId);
|
Bitshifter.WriteUInt31BigEndian(buffer, (uint)lastStreamId, preserveHighestBit: false);
|
||||||
buffer = buffer.Slice(4);
|
buffer = buffer.Slice(4);
|
||||||
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)errorCode);
|
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)errorCode);
|
||||||
_outputWriter.Advance(8);
|
_outputWriter.Advance(8);
|
||||||
|
|
@ -578,7 +578,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
buffer[1] = frame.Flags;
|
buffer[1] = frame.Flags;
|
||||||
buffer = buffer.Slice(2);
|
buffer = buffer.Slice(2);
|
||||||
|
|
||||||
Bitshifter.WriteUInt31BigEndian(buffer, (uint)frame.StreamId);
|
Bitshifter.WriteUInt31BigEndian(buffer, (uint)frame.StreamId, preserveHighestBit: false);
|
||||||
|
|
||||||
output.Advance(Http2FrameReader.HeaderLength);
|
output.Advance(Http2FrameReader.HeaderLength);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
{
|
||||||
|
public class BitShifterTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void WriteUInt31BigEndian_PreservesHighestBit()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Span<byte> dirtySpan = new byte[] { 0xff, 0xff, 0xff, 0xff };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Bitshifter.WriteUInt31BigEndian(dirtySpan, 1);
|
||||||
|
|
||||||
|
Assert.Equal(new byte[] { 0x80, 0x00, 0x00, 0x01 }, dirtySpan.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WriteUInt31BigEndian_True_PreservesHighestBit()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Span<byte> dirtySpan = new byte[] { 0xff, 0xff, 0xff, 0xff };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Bitshifter.WriteUInt31BigEndian(dirtySpan, 1, true);
|
||||||
|
|
||||||
|
Assert.Equal(new byte[] { 0x80, 0x00, 0x00, 0x01 }, dirtySpan.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WriteUInt31BigEndian_False_OverwritesHighestBit()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Span<byte> dirtySpan = new byte[] { 0xff, 0xff, 0xff, 0xff };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Bitshifter.WriteUInt31BigEndian(dirtySpan, 1, false);
|
||||||
|
|
||||||
|
Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x01 }, dirtySpan.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
{
|
||||||
|
public class Http2FrameWriterTests
|
||||||
|
{
|
||||||
|
private MemoryPool<byte> _dirtyMemoryPool;
|
||||||
|
|
||||||
|
public Http2FrameWriterTests()
|
||||||
|
{
|
||||||
|
var memoryBlock = new Mock<IMemoryOwner<byte>>();
|
||||||
|
memoryBlock.Setup(block => block.Memory).Returns(() =>
|
||||||
|
{
|
||||||
|
var blockArray = new byte[4096];
|
||||||
|
for (int i = 0; i < 4096; i++)
|
||||||
|
{
|
||||||
|
blockArray[i] = 0xff;
|
||||||
|
}
|
||||||
|
return new Memory<byte>(blockArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
var dirtyMemoryPool = new Mock<MemoryPool<byte>>();
|
||||||
|
dirtyMemoryPool.Setup(pool => pool.Rent(It.IsAny<int>())).Returns(memoryBlock.Object);
|
||||||
|
_dirtyMemoryPool = dirtyMemoryPool.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteWindowUpdate_UnsetsReservedBit()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline));
|
||||||
|
var frameWriter = new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, new Mock<IKestrelTrace>().Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await frameWriter.WriteWindowUpdateAsync(1, 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var payload = await pipe.Reader.ReadForLengthAsync(Http2FrameReader.HeaderLength + 4);
|
||||||
|
|
||||||
|
Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x01 }, payload.Skip(Http2FrameReader.HeaderLength).Take(4).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteGoAway_UnsetsReservedBit()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline));
|
||||||
|
var frameWriter = new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, new Mock<IKestrelTrace>().Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await frameWriter.WriteGoAwayAsync(1, Http2ErrorCode.NO_ERROR);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var payload = await pipe.Reader.ReadForLengthAsync(Http2FrameReader.HeaderLength + 4);
|
||||||
|
|
||||||
|
Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x01 }, payload.Skip(Http2FrameReader.HeaderLength).Take(4).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteHeader_UnsetsReservedBit()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline));
|
||||||
|
var frame = new Http2Frame();
|
||||||
|
frame.PreparePing(Http2PingFrameFlags.NONE);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Http2FrameWriter.WriteHeader(frame, pipe.Writer);
|
||||||
|
await pipe.Writer.FlushAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var payload = await pipe.Reader.ReadForLengthAsync(Http2FrameReader.HeaderLength);
|
||||||
|
|
||||||
|
Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x00 }, payload.Skip(5).Take(4).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PipeReaderExtensions
|
||||||
|
{
|
||||||
|
public static async Task<byte[]> ReadForLengthAsync(this PipeReader pipeReader, int length)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var result = await pipeReader.ReadAsync();
|
||||||
|
var buffer = result.Buffer;
|
||||||
|
|
||||||
|
if (!buffer.IsEmpty && buffer.Length >= length)
|
||||||
|
{
|
||||||
|
return buffer.Slice(0, length).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeReader.AdvanceTo(buffer.Start, buffer.End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1055,7 +1055,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
frame.PrepareWindowUpdate(streamId, sizeIncrement);
|
frame.PrepareWindowUpdate(streamId, sizeIncrement);
|
||||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||||
var buffer = outputWriter.GetSpan(4);
|
var buffer = outputWriter.GetSpan(4);
|
||||||
Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement);
|
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)sizeIncrement);
|
||||||
outputWriter.Advance(4);
|
outputWriter.Advance(4);
|
||||||
return FlushAsync(outputWriter);
|
return FlushAsync(outputWriter);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue