Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get tests running for HTTP/3 #31898

Merged
merged 2 commits into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ private void OnByte(byte b, IHttpHeadersHandler handler)
}
break;
case State.CompressedHeaders:
switch (BitOperations.LeadingZeroCount(b) - 24)
switch (BitOperations.LeadingZeroCount(b) - 24) // byte 'b' is extended to uint, so will have 24 extra 0s.
{
case 0: // Indexed Header Field
prefixInt = IndexedHeaderFieldPrefixMask & b;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Xunit;
Expand All @@ -23,6 +25,7 @@ public static partial class Certificates
private static readonly X509Certificate2 s_noEKUCertificate;
private static readonly X509Certificate2 s_selfSignedServerCertificate;
private static readonly X509Certificate2 s_selfSignedClientCertificate;
private static X509Certificate2 s_selfSigned13ServerCertificate;

static Certificates()
{
Expand Down Expand Up @@ -69,6 +72,44 @@ static Certificates()
public static X509Certificate2 GetNoEKUCertificate() => new X509Certificate2(s_noEKUCertificate);
public static X509Certificate2 GetSelfSignedServerCertificate() => new X509Certificate2(s_selfSignedServerCertificate);
public static X509Certificate2 GetSelfSignedClientCertificate() => new X509Certificate2(s_selfSignedClientCertificate);

public static X509Certificate2 GetSelfSigned13ServerCertificate()
{
if (s_selfSigned13ServerCertificate == null)
{
X509Certificate2 cert;

using (ECDsa dsa = ECDsa.Create())
{
var certReq = new CertificateRequest("CN=testservereku.contoso.com", dsa, HashAlgorithmName.SHA256);
certReq.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
certReq.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
certReq.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));

X509Certificate2 innerCert = certReq.CreateSelfSigned(DateTimeOffset.UtcNow.AddMonths(-1), DateTimeOffset.UtcNow.AddMonths(1));

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
using (innerCert)
{
cert = new X509Certificate2(innerCert.Export(X509ContentType.Pfx));
}
}
else
{
cert = innerCert;
}
}

if (Interlocked.CompareExchange(ref s_selfSigned13ServerCertificate, cert, null) != null)
{
// Lost a race to create.
cert.Dispose();
}
}

return new X509Certificate2(s_selfSigned13ServerCertificate);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ public abstract class LoopbackServerFactory
public abstract GenericLoopbackServer CreateServer(GenericLoopbackOptions options = null);
public abstract Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60_000, GenericLoopbackOptions options = null);

// TODO: just expose a version property.
public abstract bool IsHttp11 { get; }
public abstract bool IsHttp2 { get; }
public abstract bool IsHttp3 { get; }
public abstract Version Version { get; }

// Common helper methods

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ public async Task<HeadersFrame> ReadRequestHeaderFrameAsync()

private static (int bytesConsumed, int value) DecodeInteger(ReadOnlySpan<byte> headerBlock, byte prefixMask)
{
return QPackDecoder.DecodeInteger(headerBlock, prefixMask);
return QPackTestDecoder.DecodeInteger(headerBlock, prefixMask);
}

private static (int bytesConsumed, string value) DecodeString(ReadOnlySpan<byte> headerBlock)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,7 @@ public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Ta
}
}

public override bool IsHttp11 => false;
public override bool IsHttp2 => true;
public override bool IsHttp3 => false;
public override Version Version => HttpVersion.Version20;
}

public enum ProtocolErrors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,27 @@ namespace System.Net.Test.Common
{
public sealed class Http3LoopbackConnection : GenericLoopbackConnection
{
public const long H3_NO_ERROR = 0x100;
public const long H3_GENERAL_PROTOCOL_ERROR = 0x101;
public const long H3_INTERNAL_ERROR = 0x102;
public const long H3_STREAM_CREATION_ERROR = 0x103;
public const long H3_CLOSED_CRITICAL_STREAM = 0x104;
public const long H3_FRAME_UNEXPECTED = 0x105;
public const long H3_FRAME_ERROR = 0x106;
public const long H3_EXCESSIVE_LOAD = 0x107;
public const long H3_ID_ERROR = 0x108;
public const long H3_SETTINGS_ERROR = 0x109;
public const long H3_MISSING_SETTINGS = 0x10a;
public const long H3_REQUEST_REJECTED = 0x10b;
public const long H3_REQUEST_CANCELLED = 0x10c;
public const long H3_REQUEST_INCOMPLETE = 0x10d;
public const long H3_CONNECT_ERROR = 0x10f;
public const long H3_VERSION_FALLBACK = 0x110;

private readonly QuicConnection _connection;
private readonly Dictionary<int, Http3LoopbackStream> _openStreams = new Dictionary<int, Http3LoopbackStream>();
private Http3LoopbackStream _currentStream;
private bool _closed;

public Http3LoopbackConnection(QuicConnection connection)
{
Expand All @@ -29,9 +47,20 @@ public override void Dispose()
stream.Dispose();
}

if (!_closed)
{
CloseAsync(H3_INTERNAL_ERROR).GetAwaiter().GetResult();
}

_connection.Dispose();
}

public async Task CloseAsync(long errorCode)
{
await _connection.CloseAsync(errorCode).ConfigureAwait(false);
_closed = true;
}

public Http3LoopbackStream OpenUnidirectionalStream()
{
return new Http3LoopbackStream(_connection.OpenUnidirectionalStream());
Expand Down Expand Up @@ -73,7 +102,14 @@ public override async Task<byte[]> ReadRequestBodyAsync()

public override async Task<HttpRequestData> ReadRequestDataAsync(bool readBody = true)
{
Http3LoopbackStream stream = await AcceptStreamAsync().ConfigureAwait(false);
Http3LoopbackStream stream;

do
{
stream = await AcceptStreamAsync().ConfigureAwait(false);
}
while (!stream.CanWrite); // skip control stream.

return await stream.ReadRequestDataAsync(readBody).ConfigureAwait(false);
}

Expand All @@ -94,7 +130,7 @@ public override async Task SendResponseBodyAsync(byte[] content, bool isFinal =

if (isFinal)
{
stream.ShutdownSend();
await stream.ShutdownSendAsync().ConfigureAwait(false);
stream.Dispose();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,18 @@ public Http3LoopbackServer(GenericLoopbackOptions options = null)
{
options ??= new GenericLoopbackOptions();

_cert = Configuration.Certificates.GetServerCertificate();
_cert = Configuration.Certificates.GetSelfSigned13ServerCertificate();

var sslOpts = new SslServerAuthenticationOptions
{
EnabledSslProtocols = options.SslProtocols,
ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
ServerCertificate = _cert,
//ServerCertificate = _cert,
ClientCertificateRequired = false
};

_listener = new QuicListener(new IPEndPoint(options.Address, 0), sslOpts);
_listener.Start();
}

public override void Dispose()
Expand All @@ -55,10 +56,11 @@ public override async Task AcceptConnectionAsync(Func<GenericLoopbackConnection,

public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = "")
{
using GenericLoopbackConnection con = await EstablishGenericConnectionAsync().ConfigureAwait(false);
using var con = (Http3LoopbackConnection)await EstablishGenericConnectionAsync().ConfigureAwait(false);

HttpRequestData request = await con.ReadRequestDataAsync().ConfigureAwait(false);
await con.SendResponseAsync(statusCode, headers, content).ConfigureAwait(false);
await con.CloseAsync(Http3LoopbackConnection.H3_NO_ERROR);
return request;
}
}
Expand All @@ -67,11 +69,7 @@ public sealed class Http3LoopbackServerFactory : LoopbackServerFactory
{
public static Http3LoopbackServerFactory Singleton { get; } = new Http3LoopbackServerFactory();

public override bool IsHttp11 => false;

public override bool IsHttp2 => false;

public override bool IsHttp3 => true;
public override Version Version => HttpVersion.Version30;

public override GenericLoopbackServer CreateServer(GenericLoopbackOptions options = null)
{
Expand Down
22 changes: 11 additions & 11 deletions src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,22 @@ public async Task SendSettingsFrameAsync(ICollection<(long settingId, long setti

public async Task SendHeadersFrameAsync(ICollection<HttpHeaderData> headers)
{
int bufferLength = QPackEncoder.MaxPrefixLength;
int bufferLength = QPackTestEncoder.MaxPrefixLength;

foreach (HttpHeaderData header in headers)
{
// Two varints for length, and double the name/value lengths to account for expanding Huffman coding.
bufferLength += QPackEncoder.MaxVarIntLength * 2 + header.Name.Length * 2 + header.Value.Length * 2;
bufferLength += QPackTestEncoder.MaxVarIntLength * 2 + header.Name.Length * 2 + header.Value.Length * 2;
}

var buffer = new byte[bufferLength];
int bytesWritten = 0;

bytesWritten += QPackEncoder.EncodePrefix(buffer.AsSpan(bytesWritten), 0, 0);
bytesWritten += QPackTestEncoder.EncodePrefix(buffer.AsSpan(bytesWritten), 0, 0);

foreach (HttpHeaderData header in headers)
{
bytesWritten += QPackEncoder.EncodeHeader(buffer.AsSpan(bytesWritten), header.Name, header.Value, header.HuffmanEncoded ? QPackFlags.HuffmanEncode : QPackFlags.None);
bytesWritten += QPackTestEncoder.EncodeHeader(buffer.AsSpan(bytesWritten), header.Name, header.Value, header.HuffmanEncoded ? QPackFlags.HuffmanEncode : QPackFlags.None);
}

await SendFrameAsync(HeadersFrame, buffer.AsMemory(0, bytesWritten)).ConfigureAwait(false);
Expand All @@ -100,9 +100,10 @@ public async Task SendFrameAsync(long frameType, ReadOnlyMemory<byte> framePaylo
await _stream.WriteAsync(framePayload).ConfigureAwait(false);
}

public void ShutdownSend()
public async Task ShutdownSendAsync()
{
_stream.Shutdown();
await _stream.ShutdownWriteCompleted().ConfigureAwait(false);
}

static int EncodeHttpInteger(long longToEncode, Span<byte> buffer)
Expand Down Expand Up @@ -176,14 +177,14 @@ private HttpRequestData ParseHeaders(ReadOnlySpan<byte> buffer)
{
HttpRequestData request = new HttpRequestData { RequestId = Http3LoopbackConnection.GetRequestId(_stream) };

(int prefixLength, int requiredInsertCount, int deltaBase) = QPackDecoder.DecodePrefix(buffer);
(int prefixLength, int requiredInsertCount, int deltaBase) = QPackTestDecoder.DecodePrefix(buffer);
if (requiredInsertCount != 0 || deltaBase != 0) throw new Exception("QPack dynamic table not yet supported.");

buffer = buffer.Slice(prefixLength);

while (!buffer.IsEmpty)
{
(int headerLength, HttpHeaderData header) = QPackDecoder.DecodeHeader(buffer);
(int headerLength, HttpHeaderData header) = QPackTestDecoder.DecodeHeader(buffer);

request.Headers.Add(header);
buffer = buffer.Slice(headerLength);
Expand Down Expand Up @@ -247,22 +248,21 @@ public async Task WaitForCancellationAsync(bool ignoreIncomingData = true)
public async Task<long?> ReadInteger()
{
byte[] buffer = new byte[MaximumVarIntBytes];
int bufferAvailableOffset = -1;
int bufferActiveLength = 0;

long integerValue;
int bytesRead;

do
{
bytesRead = await _stream.ReadAsync(buffer.AsMemory(++bufferAvailableOffset, 1)).ConfigureAwait(false);
bytesRead = await _stream.ReadAsync(buffer.AsMemory(bufferActiveLength++, 1)).ConfigureAwait(false);
if (bytesRead == 0)
{
return bufferActiveLength == 0 ? (long?)null : throw new Exception("Unable to read varint; unexpected end of stream.");
return bufferActiveLength == 1 ? (long?)null : throw new Exception("Unable to read varint; unexpected end of stream.");
}
Debug.Assert(bytesRead == 1);
}
while (!TryDecodeHttpInteger(buffer.AsSpan(0, ++bufferActiveLength), out integerValue, out bytesRead));
while (!TryDecodeHttpInteger(buffer.AsSpan(0, bufferActiveLength), out integerValue, out bytesRead));

Debug.Assert(bytesRead == bufferActiveLength);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ public HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(outp
[InlineData(true, CancellationMode.Token)]
public async Task PostAsync_CancelDuringRequestContentSend_TaskCanceledQuickly(bool chunkedTransfer, CancellationMode mode)
{
if (LoopbackServerFactory.IsHttp2 && chunkedTransfer)
if (LoopbackServerFactory.Version >= HttpVersion.Version20 && chunkedTransfer)
{
// There is no chunked encoding in HTTP/2
// There is no chunked encoding in HTTP/2 and later
return;
}

Expand Down Expand Up @@ -80,9 +80,9 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
[MemberData(nameof(OneBoolAndCancellationMode))]
public async Task GetAsync_CancelDuringResponseHeadersReceived_TaskCanceledQuickly(bool connectionClose, CancellationMode mode)
{
if (LoopbackServerFactory.IsHttp2 && connectionClose)
if (LoopbackServerFactory.Version >= HttpVersion.Version20 && connectionClose)
{
// There is no Connection header in HTTP/2
// There is no Connection header in HTTP/2 and later
return;
}

Expand Down Expand Up @@ -130,9 +130,9 @@ await ValidateClientCancellationAsync(async () =>
[MemberData(nameof(TwoBoolsAndCancellationMode))]
public async Task GetAsync_CancelDuringResponseBodyReceived_Buffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, CancellationMode mode)
{
if (LoopbackServerFactory.IsHttp2 && (chunkedTransfer || connectionClose))
if (LoopbackServerFactory.Version >= HttpVersion.Version20 && (chunkedTransfer || connectionClose))
{
// There is no chunked encoding or connection header in HTTP/2
// There is no chunked encoding or connection header in HTTP/2 and later
return;
}

Expand Down Expand Up @@ -186,9 +186,9 @@ await ValidateClientCancellationAsync(async () =>
[MemberData(nameof(ThreeBools))]
public async Task GetAsync_CancelDuringResponseBodyReceived_Unbuffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, bool readOrCopyToAsync)
{
if (LoopbackServerFactory.IsHttp2 && (chunkedTransfer || connectionClose))
if (LoopbackServerFactory.Version >= HttpVersion.Version20 && (chunkedTransfer || connectionClose))
{
// There is no chunked encoding or connection header in HTTP/2
// There is no chunked encoding or connection header in HTTP/2 and later
return;
}

Expand Down Expand Up @@ -312,10 +312,10 @@ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
[ConditionalFact]
public async Task MaxConnectionsPerServer_WaitingConnectionsAreCancelable()
{
if (LoopbackServerFactory.IsHttp2)
if (LoopbackServerFactory.Version >= HttpVersion.Version20)
{
// HTTP/2 does not use connection limits.
throw new SkipTestException("Not supported on HTTP/2");
throw new SkipTestException("Not supported on HTTP/2 and later");
}

using (HttpClientHandler handler = CreateHttpClientHandler())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(

private string GetCookieValue(HttpRequestData request)
{
if (!LoopbackServerFactory.IsHttp2)
if (LoopbackServerFactory.Version < HttpVersion.Version20)
{
// HTTP/1.x must have only one value.
return request.GetSingleHeaderValue("Cookie");
Expand Down
Loading