// 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.Text; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { public class PipelineExtensionTests : IDisposable { // ulong.MaxValue.ToString().Length private const int _ulongMaxValueLength = 20; private readonly Pipe _pipe; private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); public PipelineExtensionTests() { _pipe = new Pipe(new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false)); } public void Dispose() { _pipe.Reader.Complete(); _pipe.Writer.Complete(); _memoryPool.Dispose(); } [Theory] [InlineData(ulong.MinValue)] [InlineData(ulong.MaxValue)] [InlineData(4_8_15_16_23_42)] public void WritesNumericToAscii(ulong number) { var writerBuffer = _pipe.Writer; var writer = new BufferWriter(writerBuffer); writer.WriteNumeric(number); writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); var numAsStr = number.ToString(); var expected = Encoding.ASCII.GetBytes(numAsStr); AssertExtensions.Equal(expected, reader.Buffer.Slice(0, numAsStr.Length).ToArray()); } [Theory] [InlineData(1)] [InlineData(_ulongMaxValueLength / 2)] [InlineData(_ulongMaxValueLength - 1)] public void WritesNumericAcrossSpanBoundaries(int gapSize) { var writerBuffer = _pipe.Writer; var writer = new BufferWriter(writerBuffer); // almost fill up the first block var spacer = new byte[writer.Span.Length - gapSize]; writer.Write(spacer); var bufferLength = writer.Span.Length; writer.WriteNumeric(ulong.MaxValue); Assert.NotEqual(bufferLength, writer.Span.Length); writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); var numAsString = ulong.MaxValue.ToString(); var written = reader.Buffer.Slice(spacer.Length, numAsString.Length); Assert.False(written.IsSingleSegment, "The buffer should cross spans"); AssertExtensions.Equal(Encoding.ASCII.GetBytes(numAsString), written.ToArray()); } [Theory] [InlineData("\0abcxyz", new byte[] { 0, 97, 98, 99, 120, 121, 122 })] [InlineData("!#$%i", new byte[] { 33, 35, 36, 37, 105 })] [InlineData("!#$%", new byte[] { 33, 35, 36, 37 })] [InlineData("!#$", new byte[] { 33, 35, 36 })] [InlineData("!#", new byte[] { 33, 35 })] [InlineData("!", new byte[] { 33 })] // null or empty [InlineData("", new byte[0])] [InlineData(null, new byte[0])] public void EncodesAsAscii(string input, byte[] expected) { var pipeWriter = _pipe.Writer; var writer = new BufferWriter(pipeWriter); writer.WriteAsciiNoValidation(input); writer.Commit(); pipeWriter.FlushAsync().GetAwaiter().GetResult(); pipeWriter.Complete(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); if (expected.Length > 0) { AssertExtensions.Equal( expected, reader.Buffer.ToArray()); } else { Assert.Equal(0, reader.Buffer.Length); } } [Theory] // non-ascii characters stored in 32 bits [InlineData("𤭢𐐝")] // non-ascii characters stored in 16 bits [InlineData("ñ٢⛄⛵")] public void WriteAsciiNoValidationWritesOnlyOneBytePerChar(string input) { // WriteAscii doesn't validate if characters are in the ASCII range // but it shouldn't produce more than one byte per character var writerBuffer = _pipe.Writer; var writer = new BufferWriter(writerBuffer); writer.WriteAsciiNoValidation(input); writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); Assert.Equal(input.Length, reader.Buffer.Length); } [Fact] public void WriteAsciiNoValidation() { const byte maxAscii = 0x7f; var writerBuffer = _pipe.Writer; var writer = new BufferWriter(writerBuffer); for (var i = 0; i < maxAscii; i++) { writer.WriteAsciiNoValidation(new string((char)i, 1)); } writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); var data = reader.Buffer.Slice(0, maxAscii).ToArray(); for (var i = 0; i < maxAscii; i++) { Assert.Equal(i, data[i]); } } [Theory] [InlineData(2, 1)] [InlineData(3, 1)] [InlineData(4, 2)] [InlineData(5, 3)] [InlineData(7, 4)] [InlineData(8, 3)] [InlineData(8, 4)] [InlineData(8, 5)] [InlineData(100, 48)] public void WritesAsciiAcrossBlockBoundaries(int stringLength, int gapSize) { var testString = new string(' ', stringLength); var writerBuffer = _pipe.Writer; var writer = new BufferWriter(writerBuffer); // almost fill up the first block var spacer = new byte[writer.Span.Length - gapSize]; writer.Write(spacer); Assert.Equal(gapSize, writer.Span.Length); var bufferLength = writer.Span.Length; writer.WriteAsciiNoValidation(testString); Assert.NotEqual(bufferLength, writer.Span.Length); writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); var written = reader.Buffer.Slice(spacer.Length, stringLength); Assert.False(written.IsSingleSegment, "The buffer should cross spans"); AssertExtensions.Equal(Encoding.ASCII.GetBytes(testString), written.ToArray()); } } }