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

Add QuicConnection.IsQuicSupported for runtime feature detection #31689

Merged
merged 4 commits into from
Feb 7, 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
1 change: 1 addition & 0 deletions src/libraries/System.Net.Quic/ref/System.Net.Quic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public QuicConnection(IPEndPoint remoteEndPoint, System.Net.Security.SslClientAu
public System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol => throw null;
public ValueTask CloseAsync(long errorCode, System.Threading.CancellationToken cancellationToken = default) => throw null;
public void Dispose() => throw null;
public static bool IsQuicSupported => throw null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're still operating in a "none of this has been reviewed yet and we won't ship these APIs until we do and revise them accordingly" mode, right? Is there a must-do issue tracking that to make sure it doesn't slip through the cracks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're still operating in a "none of this has been reviewed yet and we won't ship these APIs until we do and revise them accordingly" mode, right?

Yes.

Is there a must-do issue tracking that to make sure it doesn't slip through the cracks?

It's been in active discussion during QUIC syncs; I'm aiming for us to have that ironed out in the next couple weeks. #31709

}
public sealed partial class QuicListener : IDisposable
{
Expand Down
68 changes: 65 additions & 3 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,64 @@
<root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema

Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
Expand Down Expand Up @@ -57,8 +117,10 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>

<data name="net_quic_notsupported" xml:space="preserve">
<value>QUIC is not supported on this platform. See http://aka.ms/dotnetquic</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link redirects to https://github.com/dotnet/runtime
Is this intentional or is the redirect not setup right now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional; Around release we'll have a docs page or blog post outlining how to play with HTTP/3 -- it'll get updated at that time.

</data>
<data name="net_quic_placeholdertext" xml:space="preserve">
<value>Placeholder text</value>
</data>
</root>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.IO;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
Expand All @@ -17,9 +19,20 @@ internal class MsQuicApi : IDisposable

private unsafe MsQuicApi()
{
QuicExceptionHelpers.ThrowIfFailed(
Interop.MsQuic.MsQuicOpen(version: 1, out MsQuicNativeMethods.NativeApi* registration),
"Could not open MsQuic.");
MsQuicNativeMethods.NativeApi* registration;

try
{
uint status = Interop.MsQuic.MsQuicOpen(version: 1, out registration);
if (!MsQuicStatusHelper.SuccessfulStatusCode(status))
{
throw new NotSupportedException(SR.net_quic_notsupported);
}
}
catch (DllNotFoundException)
{
throw new NotSupportedException(SR.net_quic_notsupported);
}

MsQuicNativeMethods.NativeApi nativeRegistration = *registration;

Expand Down Expand Up @@ -114,7 +127,40 @@ private unsafe MsQuicApi()
_registrationContext = ctx;
}

internal static MsQuicApi Api { get; } = new MsQuicApi();
internal static MsQuicApi Api { get; }

internal static bool IsQuicSupported { get; }

static MsQuicApi()
{
// MsQuicOpen will succeed even if the platform will not support it. It will then fail with unspecified
// platform-specific errors in subsequent callbacks. For now, check for the minimum build we've tested it on.

// TODO:
// - Hopefully, MsQuicOpen will perform this check for us and give us a consistent error code.
// - Otherwise, dial this in to reflect actual minimum requirements and add some sort of platform
// error code mapping when creating exceptions.

OperatingSystem ver = Environment.OSVersion;

if (ver.Platform == PlatformID.Win32NT && ver.Version < new Version(10, 0, 19041, 0))
{
IsQuicSupported = false;
return;
}

// TODO: try to initialize TLS 1.3 in SslStream.

try
{
Api = new MsQuicApi();
IsQuicSupported = true;
}
catch (NotSupportedException)
{
IsQuicSupported = false;
}
}

internal MsQuicNativeMethods.RegistrationOpenDelegate RegistrationOpenDelegate { get; }
internal MsQuicNativeMethods.RegistrationCloseDelegate RegistrationCloseDelegate { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ internal sealed class MsQuicSession : IDisposable

internal MsQuicSession()
{
if (!MsQuicApi.IsQuicSupported)
{
throw new NotSupportedException(SR.net_quic_notsupported);
}
}

public IntPtr ConnectionOpen(QuicClientConnectionOptions options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Net.Quic.Implementations;
using System.Net.Quic.Implementations.MsQuic.Internal;
using System.Net.Security;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -13,6 +14,8 @@ public sealed class QuicConnection : IDisposable
{
private readonly QuicConnectionProvider _provider;

public static bool IsQuicSupported => MsQuicApi.IsQuicSupported;

/// <summary>
/// Create an outbound QUIC connection.
/// </summary>
Expand Down
19 changes: 10 additions & 9 deletions src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@

namespace System.Net.Quic.Tests
{
[ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
public class MsQuicTests : MsQuicTestBase
{
private static ReadOnlyMemory<byte> s_data = Encoding.UTF8.GetBytes("Hello world!");

[Fact(Skip = "MsQuic not available")]
[Fact]
public async Task BasicTest()
{
for (int i = 0; i < 100; i++)
Expand Down Expand Up @@ -62,7 +63,7 @@ public async Task BasicTest()
}
}

[Fact(Skip = "MsQuic not available")]
[Fact]
public async Task MultipleReadsAndWrites()
{
for (int j = 0; j < 100; j++)
Expand Down Expand Up @@ -127,7 +128,7 @@ public async Task MultipleReadsAndWrites()
}
}

[Fact(Skip = "MsQuic not available")]
[Fact]
public async Task MultipleStreamsOnSingleConnection()
{
Task listenTask = Task.Run(async () =>
Expand Down Expand Up @@ -209,7 +210,7 @@ public async Task MultipleStreamsOnSingleConnection()
await (new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 60000);
}

[Fact(Skip = "MsQuic not available")]
[Fact]
public async Task AbortiveConnectionFromClient()
{
using QuicConnection clientConnection = CreateQuicConnection(DefaultListener.ListenEndPoint);
Expand All @@ -226,7 +227,7 @@ public async Task AbortiveConnectionFromClient()
Assert.Throws<NullReferenceException>(() => stream.CanRead);
}

[Fact(Skip = "MsQuic not available")]
[Fact]
public async Task TestStreams()
{
using (QuicListener listener = new QuicListener(
Expand Down Expand Up @@ -264,7 +265,7 @@ public async Task TestStreams()
}
}

[Fact(Skip = "MsQuic not available")]
[Fact]
public async Task UnidirectionalAndBidirectionalStreamCountsWork()
{
using QuicConnection clientConnection = CreateQuicConnection(DefaultListener.ListenEndPoint);
Expand All @@ -276,7 +277,7 @@ public async Task UnidirectionalAndBidirectionalStreamCountsWork()
Assert.Equal(100, serverConnection.GetRemoteAvailableUnidirectionalStreamCount());
}

[Fact(Skip = "MsQuic not available")]
[Fact]
public async Task UnidirectionalAndBidirectionalChangeValues()
{
QuicClientConnectionOptions options = new QuicClientConnectionOptions()
Expand All @@ -298,7 +299,7 @@ public async Task UnidirectionalAndBidirectionalChangeValues()
Assert.Equal(100, serverConnection.GetRemoteAvailableUnidirectionalStreamCount());
}

[Fact(Skip = "MsQuic not available")]
[Fact]
public async Task CallDifferentWriteMethodsWorks()
{
using QuicConnection clientConnection = CreateQuicConnection(DefaultListener.ListenEndPoint);
Expand Down Expand Up @@ -327,7 +328,7 @@ public async Task CallDifferentWriteMethodsWorks()
Assert.Equal(24, res);
}

[Theory(Skip = "MsQuic not available")]
[Theory]
[MemberData(nameof(QuicStream_ReadWrite_Random_Success_Data))]
public async Task QuicStream_ReadWrite_Random_Success(int readSize, int writeSize)
{
Expand Down