diff --git a/src/libraries/System.Net.Quic/readme.md b/src/libraries/System.Net.Quic/readme.md new file mode 100644 index 00000000000000..16ac0d31a04909 --- /dev/null +++ b/src/libraries/System.Net.Quic/readme.md @@ -0,0 +1,45 @@ +# MsQuic + +`System.Net.Quic` depends on [MsQuic](https://github.com/microsoft/msquic), Microsoft, cross-platform, native implementation of the [QUIC](https://datatracker.ietf.org/wg/quic/about/) protocol. +Currently, `System.Net.Quic` depends on [**msquic@cc104e836a5d4a5e0d324bc08b42136d2acac997**](https://github.com/microsoft/msquic/commit/cc104e836a5d4a5e0d324bc08b42136d2acac997) revision. + +## Usage + +### Build MsQuic + +[MsQuic build docs](https://github.com/microsoft/msquic/blob/main/docs/BUILD.md) + +> **Note**: At the moment, we're using stub_tls option to bypass OpenSSL/SChannel, since work with certificates is not fully figured out. + +#### Linux +Prerequisites: +- build-essential +- cmake +- lttng-ust +- lttng-tools + +`dotnet tool install --global`: +- microsoft.logging.clog +- microsoft.logging.clog2text.lttng + + +Run inside the msquic directory (for **Debug** build with logging on): +```bash +# build msquic in debug with logging and stub tls +rm -rf build +mkdir build +cmake -B build -DCMAKE_BUILD_TYPE=Debug -DQUIC_ENABLE_LOGGING=on -DQUIC_TLS=stub +cd build +cmake --build . --config Debug + +# copy msquic into runtime +yes | cp -rf bin/Debug/libmsquic.* /src/libraries/System.Net.Quic/src/ +``` + +#### Windows +Prerequisites: +- Latest [Windows Insider Builds](https://insider.windows.com/en-us/), Insiders Fast build. This is required for SChannel support for QUIC. + - To confirm you have a new enough build, run winver on command line and confirm you version is greater than Version 2004 (OS Build 20145.1000). + +TODO + diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs index 12f167a2a66238..847773e8d501da 100644 --- a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs +++ b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs @@ -6,121 +6,123 @@ namespace System.Net.Quic { - public static class QuicImplementationProviders + public partial class QuicClientConnectionOptions : System.Net.Quic.QuicOptions { - public static Implementations.QuicImplementationProvider Mock => throw null; - public static Implementations.QuicImplementationProvider MsQuic => throw null; - public static Implementations.QuicImplementationProvider Default => throw null; + public QuicClientConnectionOptions() { } + public System.Net.Security.SslClientAuthenticationOptions? ClientAuthenticationOptions { get { throw null; } set { } } + public System.Net.IPEndPoint? LocalEndPoint { get { throw null; } set { } } + public System.Net.EndPoint? RemoteEndPoint { get { throw null; } set { } } } - public sealed class QuicListener : IDisposable + public sealed partial class QuicConnection : System.IDisposable { - public QuicListener(IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions) { throw null; } - public QuicListener(QuicListenerOptions options) { throw null; } - public QuicListener(Implementations.QuicImplementationProvider implementationProvider, IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions) { throw null; } - public QuicListener(Implementations.QuicImplementationProvider implementationProvider, QuicListenerOptions options) { throw null; } - public IPEndPoint ListenEndPoint => throw null; - public System.Threading.Tasks.ValueTask AcceptConnectionAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; - public void Start() => throw null; - public void Close() => throw null; - public void Dispose() => throw null; + public QuicConnection(System.Net.EndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions? sslClientAuthenticationOptions, System.Net.IPEndPoint? localEndPoint = null) { } + public QuicConnection(System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider, System.Net.EndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions? sslClientAuthenticationOptions, System.Net.IPEndPoint? localEndPoint = null) { } + public QuicConnection(System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider, System.Net.Quic.QuicClientConnectionOptions options) { } + public QuicConnection(System.Net.Quic.QuicClientConnectionOptions options) { } + public bool Connected { get { throw null; } } + public System.Net.IPEndPoint? LocalEndPoint { get { throw null; } } + public System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol { get { throw null; } } + public System.Net.EndPoint RemoteEndPoint { get { throw null; } } + public System.Threading.Tasks.ValueTask AcceptStreamAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask CloseAsync(long errorCode, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask ConnectAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public void Dispose() { } + public long GetRemoteAvailableBidirectionalStreamCount() { throw null; } + public long GetRemoteAvailableUnidirectionalStreamCount() { throw null; } + public System.Net.Quic.QuicStream OpenBidirectionalStream() { throw null; } + public System.Net.Quic.QuicStream OpenUnidirectionalStream() { throw null; } } - public class QuicListenerOptions + public partial class QuicConnectionAbortedException : System.Net.Quic.QuicException { - public System.Net.Security.SslServerAuthenticationOptions? ServerAuthenticationOptions { get => throw null; set => throw null; } - public string? CertificateFilePath { get => throw null; set => throw null; } - public string? PrivateKeyFilePath { get => throw null; set => throw null; } - public IPEndPoint? ListenEndPoint { get => throw null; set => throw null; } - public int ListenBacklog { get => throw null; set => throw null; } - public long MaxBidirectionalStreams { get => throw null; set => throw null; } - public long MaxUnidirectionalStreams { get => throw null; set => throw null; } - public TimeSpan IdleTimeout { get => throw null; set => throw null; } + public QuicConnectionAbortedException(string message, long errorCode) : base (default(string)) { } + public long ErrorCode { get { throw null; } } } - public sealed class QuicConnection : IDisposable + public partial class QuicException : System.Exception { - public QuicConnection(System.Net.EndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions? sslClientAuthenticationOptions, System.Net.IPEndPoint? localEndPoint = null) { throw null; } - public QuicConnection(QuicClientConnectionOptions options) { throw null; } - public QuicConnection(Implementations.QuicImplementationProvider implementationProvider, System.Net.EndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions? sslClientAuthenticationOptions, System.Net.IPEndPoint? localEndPoint = null) { throw null; } - public QuicConnection(Implementations.QuicImplementationProvider implementationProvider, QuicClientConnectionOptions options) { throw null; } - public bool Connected => throw null; - public System.Net.IPEndPoint LocalEndPoint => throw null; - public System.Net.EndPoint RemoteEndPoint => throw null; - public System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol => throw null; - public System.Threading.Tasks.ValueTask ConnectAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; - public QuicStream OpenUnidirectionalStream() => throw null; - public QuicStream OpenBidirectionalStream() => throw null; - public System.Threading.Tasks.ValueTask AcceptStreamAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; - public System.Threading.Tasks.ValueTask CloseAsync(long errorCode, System.Threading.CancellationToken cancellationToken = default) => throw null; - public void Dispose() => throw null; - public long GetRemoteAvailableUnidirectionalStreamCount() => throw null; - public long GetRemoteAvailableBidirectionalStreamCount() => throw null; + public QuicException(string? message) { } + public QuicException(string? message, System.Exception? innerException) { } } - public class QuicClientConnectionOptions + public static partial class QuicImplementationProviders { - public System.Net.Security.SslClientAuthenticationOptions? ClientAuthenticationOptions { get => throw null; set => throw null; } - public IPEndPoint? LocalEndPoint { get => throw null; set => throw null; } - public EndPoint? RemoteEndPoint { get => throw null; set => throw null; } - public long MaxBidirectionalStreams { get => throw null; set => throw null; } - public long MaxUnidirectionalStreams { get => throw null; set => throw null; } - public TimeSpan IdleTimeout { get => throw null; set => throw null; } + public static System.Net.Quic.Implementations.QuicImplementationProvider Default { get { throw null; } } + public static System.Net.Quic.Implementations.QuicImplementationProvider Mock { get { throw null; } } + public static System.Net.Quic.Implementations.QuicImplementationProvider MsQuic { get { throw null; } } } - public sealed class QuicStream : System.IO.Stream + public sealed partial class QuicListener : System.IDisposable { - internal QuicStream() { throw null; } - public override bool CanSeek => throw null; - public override long Length => throw null; - public override long Seek(long offset, System.IO.SeekOrigin origin) => throw null; - public override void SetLength(long value) => throw null; - public override long Position { get => throw null; set => throw null; } - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => throw null; - public override int EndRead(IAsyncResult asyncResult) => throw null; - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => throw null; - public override void EndWrite(IAsyncResult asyncResult) => throw null; - public override int Read(byte[] buffer, int offset, int count) => throw null; - public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) => throw null; - public override void Write(byte[] buffer, int offset, int count) => throw null; - public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) => throw null; - public long StreamId => throw null; - public override bool CanRead => throw null; - public override int Read(Span buffer) => throw null; - public override System.Threading.Tasks.ValueTask ReadAsync(Memory buffer, System.Threading.CancellationToken cancellationToken = default) => throw null; - public override bool CanWrite => throw null; - public override void Write(ReadOnlySpan buffer) => throw null; - public override System.Threading.Tasks.ValueTask WriteAsync(ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default) => throw null; - public override void Flush() => throw null; - public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) => throw null; - public void AbortRead(long errorCode) => throw null; - public void AbortWrite(long errorCode) => throw null; - public System.Threading.Tasks.ValueTask WriteAsync(ReadOnlyMemory buffer, bool endStream, System.Threading.CancellationToken cancellationToken = default) => throw null; - public System.Threading.Tasks.ValueTask WriteAsync(System.Buffers.ReadOnlySequence buffers, System.Threading.CancellationToken cancellationToken = default) => throw null; - public System.Threading.Tasks.ValueTask WriteAsync(System.Buffers.ReadOnlySequence buffers, bool endStream, System.Threading.CancellationToken cancellationToken = default) => throw null; - public System.Threading.Tasks.ValueTask WriteAsync(ReadOnlyMemory> buffers, System.Threading.CancellationToken cancellationToken = default) => throw null; - public System.Threading.Tasks.ValueTask WriteAsync(ReadOnlyMemory> buffers, bool endStream, System.Threading.CancellationToken cancellationToken = default) => throw null; - public System.Threading.Tasks.ValueTask ShutdownWriteCompleted(System.Threading.CancellationToken cancellationToken = default) => throw null; - public void Shutdown() => throw null; + public QuicListener(System.Net.IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions) { } + public QuicListener(System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider, System.Net.IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions) { } + public QuicListener(System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider, System.Net.Quic.QuicListenerOptions options) { } + public QuicListener(System.Net.Quic.QuicListenerOptions options) { } + public System.Net.IPEndPoint ListenEndPoint { get { throw null; } } + public System.Threading.Tasks.ValueTask AcceptConnectionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public void Close() { } + public void Dispose() { } + public void Start() { } } - public class QuicException : Exception + public partial class QuicListenerOptions : System.Net.Quic.QuicOptions { - public QuicException(string? message) { throw null; } - public QuicException(string? message, Exception? innerException) { throw null; } + public QuicListenerOptions() { } + public int ListenBacklog { get { throw null; } set { } } + public System.Net.IPEndPoint? ListenEndPoint { get { throw null; } set { } } + public System.Net.Security.SslServerAuthenticationOptions? ServerAuthenticationOptions { get { throw null; } set { } } } - public class QuicConnectionAbortedException : QuicException + public partial class QuicOperationAbortedException : System.Net.Quic.QuicException { - public QuicConnectionAbortedException(string message, long errorCode) : base(default) { throw null; } - public long ErrorCode { get { throw null; } } + public QuicOperationAbortedException(string message) : base (default(string)) { } + } + public partial class QuicOptions + { + public QuicOptions() { } + public System.TimeSpan IdleTimeout { get { throw null; } set { } } + public long MaxBidirectionalStreams { get { throw null; } set { } } + public long MaxUnidirectionalStreams { get { throw null; } set { } } } - public class QuicOperationAbortedException : QuicException + public sealed partial class QuicStream : System.IO.Stream { - public QuicOperationAbortedException(string message) : base(default) { throw null; } + internal QuicStream() { } + public override bool CanRead { get { throw null; } } + public override bool CanSeek { get { throw null; } } + public override bool CanWrite { get { throw null; } } + public override long Length { get { throw null; } } + public override long Position { get { throw null; } set { } } + public long StreamId { get { throw null; } } + public void AbortRead(long errorCode) { } + public void AbortWrite(long errorCode) { } + public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; } + public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; } + protected override void Dispose(bool disposing) { } + public override int EndRead(System.IAsyncResult asyncResult) { throw null; } + public override void EndWrite(System.IAsyncResult asyncResult) { } + public override void Flush() { } + public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } + public override int Read(byte[] buffer, int offset, int count) { throw null; } + public override int Read(System.Span buffer) { throw null; } + public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } + public override void SetLength(long value) { } + public void Shutdown() { } + public System.Threading.Tasks.ValueTask ShutdownWriteCompleted(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public override void Write(byte[] buffer, int offset, int count) { } + public override void Write(System.ReadOnlySpan buffer) { } + public System.Threading.Tasks.ValueTask WriteAsync(System.Buffers.ReadOnlySequence buffers, bool endStream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask WriteAsync(System.Buffers.ReadOnlySequence buffers, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } + public System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, bool endStream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory> buffers, bool endStream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory> buffers, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public class QuicStreamAbortedException : QuicException + public partial class QuicStreamAbortedException : System.Net.Quic.QuicException { - public QuicStreamAbortedException(string message, long errorCode) : base(default) { throw null; } + public QuicStreamAbortedException(string message, long errorCode) : base (default(string)) { } public long ErrorCode { get { throw null; } } } } namespace System.Net.Quic.Implementations { - public abstract class QuicImplementationProvider + public abstract partial class QuicImplementationProvider { internal QuicImplementationProvider() { } public abstract bool IsSupported { get; } diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj index f516d28789e30f..6ad41a39fdf03f 100644 --- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj +++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj @@ -15,7 +15,7 @@ - + @@ -62,11 +62,15 @@ PreserveNewest PreserveNewest + + PreserveNewest + PreserveNewest + PreserveNewest PreserveNewest - + PreserveNewest PreserveNewest diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockConnection.cs index 12f39ab1c00603..6e5d2719452de7 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockConnection.cs @@ -91,7 +91,9 @@ internal override bool Connected } // TODO: Should clone the endpoint since it is mutable - internal override IPEndPoint LocalEndPoint => _localEndPoint; + // TODO: could this be made back to non-nullable? + // For inbound we have it immediately, for outbound after connect. + internal override IPEndPoint? LocalEndPoint => _localEndPoint; // TODO: Should clone the endpoint since it is mutable internal override EndPoint RemoteEndPoint => _remoteEndPoint!; diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index b4dbc40bfa460c..d13438cc12d64e 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -1,154 +1,140 @@ // 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.Collections.Generic; -using System.IO; -using System.Net.Security; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; +using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods; namespace System.Net.Quic.Implementations.MsQuic.Internal { - internal sealed class MsQuicApi : IDisposable + internal unsafe sealed class MsQuicApi { - private bool _disposed; + public SafeMsQuicRegistrationHandle Registration { get; } + + // This is workaround for a bug in ILTrimmer. + // Without these DynamicDependency attributes, .ctor() will be removed from the safe handles. + // Remove once fixed: https://github.com/mono/linker/issues/1660 + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicRegistrationHandle))] + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicConfigurationHandle))] + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicListenerHandle))] + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicConnectionHandle))] + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(SafeMsQuicStreamHandle))] + private MsQuicApi(NativeApi* vtable) + { + uint status; - private readonly IntPtr _registrationContext; + SetParamDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->SetParam); - private unsafe MsQuicApi(MsQuicNativeMethods.NativeApi* registration) - { - MsQuicNativeMethods.NativeApi nativeRegistration = *registration; + GetParamDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->GetParam); + + SetCallbackHandlerDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->SetCallbackHandler); RegistrationOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.RegistrationOpen); + Marshal.GetDelegateForFunctionPointer( + vtable->RegistrationOpen); RegistrationCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.RegistrationClose); - - SecConfigCreateDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SecConfigCreate); - SecConfigDeleteDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SecConfigDelete); - SessionOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SessionOpen); - SessionCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SessionClose); - SessionShutdownDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SessionShutdown); + Marshal.GetDelegateForFunctionPointer( + vtable->RegistrationClose); + + ConfigurationOpenDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->ConfigurationOpen); + ConfigurationCloseDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->ConfigurationClose); + ConfigurationLoadCredentialDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->ConfigurationLoadCredential); ListenerOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ListenerOpen); + Marshal.GetDelegateForFunctionPointer( + vtable->ListenerOpen); ListenerCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ListenerClose); + Marshal.GetDelegateForFunctionPointer( + vtable->ListenerClose); ListenerStartDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ListenerStart); + Marshal.GetDelegateForFunctionPointer( + vtable->ListenerStart); ListenerStopDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ListenerStop); + Marshal.GetDelegateForFunctionPointer( + vtable->ListenerStop); ConnectionOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ConnectionOpen); + Marshal.GetDelegateForFunctionPointer( + vtable->ConnectionOpen); ConnectionCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ConnectionClose); + Marshal.GetDelegateForFunctionPointer( + vtable->ConnectionClose); + ConnectionSetConfigurationDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->ConnectionSetConfiguration); ConnectionShutdownDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ConnectionShutdown); + Marshal.GetDelegateForFunctionPointer( + vtable->ConnectionShutdown); ConnectionStartDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ConnectionStart); + Marshal.GetDelegateForFunctionPointer( + vtable->ConnectionStart); StreamOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamOpen); + Marshal.GetDelegateForFunctionPointer( + vtable->StreamOpen); StreamCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamClose); + Marshal.GetDelegateForFunctionPointer( + vtable->StreamClose); StreamStartDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamStart); + Marshal.GetDelegateForFunctionPointer( + vtable->StreamStart); StreamShutdownDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamShutdown); + Marshal.GetDelegateForFunctionPointer( + vtable->StreamShutdown); StreamSendDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamSend); + Marshal.GetDelegateForFunctionPointer( + vtable->StreamSend); StreamReceiveCompleteDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamReceiveComplete); + Marshal.GetDelegateForFunctionPointer( + vtable->StreamReceiveComplete); StreamReceiveSetEnabledDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamReceiveSetEnabled); - SetContextDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SetContext); - GetContextDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.GetContext); - SetCallbackHandlerDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SetCallbackHandler); - - SetParamDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SetParam); - GetParamDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.GetParam); + Marshal.GetDelegateForFunctionPointer( + vtable->StreamReceiveSetEnabled); - var registrationConfig = new MsQuicNativeMethods.RegistrationConfig + var cfg = new RegistrationConfig { - AppName = "SystemNetQuic", + AppName = ".NET", ExecutionProfile = QUIC_EXECUTION_PROFILE.QUIC_EXECUTION_PROFILE_LOW_LATENCY }; - RegistrationOpenDelegate(ref registrationConfig, out IntPtr ctx); - _registrationContext = ctx; + status = RegistrationOpenDelegate(ref cfg, out SafeMsQuicRegistrationHandle handle); + QuicExceptionHelpers.ThrowIfFailed(status, "RegistrationOpen failed."); + + Registration = handle; } internal static MsQuicApi Api { get; } = null!; internal static bool IsQuicSupported { get; } - static unsafe MsQuicApi() + 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. - - // TODO: try to initialize TLS 1.3 in SslStream. - // TODO: Consider updating all of these delegates to instead use function pointers. - if (NativeLibrary.TryLoad(Interop.Libraries.MsQuic, out IntPtr msQuicHandle)) { try { if (NativeLibrary.TryGetExport(msQuicHandle, "MsQuicOpen", out IntPtr msQuicOpenAddress)) { - MsQuicNativeMethods.MsQuicOpenDelegate msQuicOpen = Marshal.GetDelegateForFunctionPointer(msQuicOpenAddress); - uint status = msQuicOpen(out MsQuicNativeMethods.NativeApi* registration); + MsQuicOpenDelegate msQuicOpen = + Marshal.GetDelegateForFunctionPointer(msQuicOpenAddress); + uint status = msQuicOpen(out NativeApi* vtable); if (MsQuicStatusHelper.SuccessfulStatusCode(status)) { IsQuicSupported = true; - Api = new MsQuicApi(registration); + Api = new MsQuicApi(vtable); } } } @@ -162,257 +148,36 @@ static unsafe MsQuicApi() } } - internal MsQuicNativeMethods.RegistrationOpenDelegate RegistrationOpenDelegate { get; } - internal MsQuicNativeMethods.RegistrationCloseDelegate RegistrationCloseDelegate { get; } - - internal MsQuicNativeMethods.SecConfigCreateDelegate SecConfigCreateDelegate { get; } - internal MsQuicNativeMethods.SecConfigDeleteDelegate SecConfigDeleteDelegate { get; } - - internal MsQuicNativeMethods.SessionOpenDelegate SessionOpenDelegate { get; } - internal MsQuicNativeMethods.SessionCloseDelegate SessionCloseDelegate { get; } - internal MsQuicNativeMethods.SessionShutdownDelegate SessionShutdownDelegate { get; } - - internal MsQuicNativeMethods.ListenerOpenDelegate ListenerOpenDelegate { get; } - internal MsQuicNativeMethods.ListenerCloseDelegate ListenerCloseDelegate { get; } - internal MsQuicNativeMethods.ListenerStartDelegate ListenerStartDelegate { get; } - internal MsQuicNativeMethods.ListenerStopDelegate ListenerStopDelegate { get; } - - internal MsQuicNativeMethods.ConnectionOpenDelegate ConnectionOpenDelegate { get; } - internal MsQuicNativeMethods.ConnectionCloseDelegate ConnectionCloseDelegate { get; } - internal MsQuicNativeMethods.ConnectionShutdownDelegate ConnectionShutdownDelegate { get; } - internal MsQuicNativeMethods.ConnectionStartDelegate ConnectionStartDelegate { get; } - - internal MsQuicNativeMethods.StreamOpenDelegate StreamOpenDelegate { get; } - internal MsQuicNativeMethods.StreamCloseDelegate StreamCloseDelegate { get; } - internal MsQuicNativeMethods.StreamStartDelegate StreamStartDelegate { get; } - internal MsQuicNativeMethods.StreamShutdownDelegate StreamShutdownDelegate { get; } - internal MsQuicNativeMethods.StreamSendDelegate StreamSendDelegate { get; } - internal MsQuicNativeMethods.StreamReceiveCompleteDelegate StreamReceiveCompleteDelegate { get; } - internal MsQuicNativeMethods.StreamReceiveSetEnabledDelegate StreamReceiveSetEnabledDelegate { get; } - - internal MsQuicNativeMethods.SetContextDelegate SetContextDelegate { get; } - internal MsQuicNativeMethods.GetContextDelegate GetContextDelegate { get; } - internal MsQuicNativeMethods.SetCallbackHandlerDelegate SetCallbackHandlerDelegate { get; } - - internal MsQuicNativeMethods.SetParamDelegate SetParamDelegate { get; } - internal MsQuicNativeMethods.GetParamDelegate GetParamDelegate { get; } - - internal unsafe uint UnsafeSetParam( - IntPtr Handle, - uint Level, - uint Param, - MsQuicNativeMethods.QuicBuffer Buffer) - { - return SetParamDelegate( - Handle, - Level, - Param, - Buffer.Length, - Buffer.Buffer); - } - - internal unsafe uint UnsafeGetParam( - IntPtr Handle, - uint Level, - uint Param, - ref MsQuicNativeMethods.QuicBuffer Buffer) - { - uint bufferLength = Buffer.Length; - byte* buf = Buffer.Buffer; - return GetParamDelegate( - Handle, - Level, - Param, - &bufferLength, - buf); - } - - public async ValueTask CreateSecurityConfig(X509Certificate certificate, string? certFilePath, string? privateKeyFilePath) - { - MsQuicSecurityConfig? secConfig = null; - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - uint secConfigCreateStatus = MsQuicStatusCodes.InternalError; - uint createConfigStatus; - IntPtr unmanagedAddr = IntPtr.Zero; - MsQuicNativeMethods.CertFileParams fileParams = default; - - try - { - if (certFilePath != null && privateKeyFilePath != null) - { - fileParams = new MsQuicNativeMethods.CertFileParams - { - PrivateKeyFilePath = Marshal.StringToCoTaskMemUTF8(privateKeyFilePath), - CertificateFilePath = Marshal.StringToCoTaskMemUTF8(certFilePath) - }; - - unmanagedAddr = Marshal.AllocHGlobal(Marshal.SizeOf(fileParams)); - Marshal.StructureToPtr(fileParams, unmanagedAddr, fDeleteOld: false); - - createConfigStatus = SecConfigCreateDelegate( - _registrationContext, - (uint)QUIC_SEC_CONFIG_FLAG.CERT_FILE, - unmanagedAddr, - null, - IntPtr.Zero, - SecCfgCreateCallbackHandler); - } - else if (certificate != null) - { - createConfigStatus = SecConfigCreateDelegate( - _registrationContext, - (uint)QUIC_SEC_CONFIG_FLAG.CERT_CONTEXT, - certificate.Handle, - null, - IntPtr.Zero, - SecCfgCreateCallbackHandler); - } - else - { - // If no certificate is provided, provide a null one. - createConfigStatus = SecConfigCreateDelegate( - _registrationContext, - (uint)QUIC_SEC_CONFIG_FLAG.CERT_NULL, - IntPtr.Zero, - null, - IntPtr.Zero, - SecCfgCreateCallbackHandler); - } - - QuicExceptionHelpers.ThrowIfFailed( - createConfigStatus, - "Could not create security configuration."); - - void SecCfgCreateCallbackHandler( - IntPtr context, - uint status, - IntPtr securityConfig) - { - secConfig = new MsQuicSecurityConfig(this, securityConfig); - secConfigCreateStatus = status; - tcs.SetResult(); - } - - await tcs.Task.ConfigureAwait(false); - - QuicExceptionHelpers.ThrowIfFailed( - secConfigCreateStatus, - "Could not create security configuration."); - } - finally - { - if (fileParams.CertificateFilePath != IntPtr.Zero) - { - Marshal.FreeCoTaskMem(fileParams.CertificateFilePath); - } - - if (fileParams.PrivateKeyFilePath != IntPtr.Zero) - { - Marshal.FreeCoTaskMem(fileParams.PrivateKeyFilePath); - } - - if (unmanagedAddr != IntPtr.Zero) - { - Marshal.FreeHGlobal(unmanagedAddr); - } - } - - return secConfig; - } - - public unsafe IntPtr SessionOpen(List alpnProtocols) - { - if (alpnProtocols.Count == 1) - { - return SessionOpen(alpnProtocols[0]); - } - - var memoryHandles = ArrayPool.Shared.Rent(alpnProtocols.Count); - var quicBuffers = ArrayPool.Shared.Rent(alpnProtocols.Count); - - try - { - for (int i = 0; i < alpnProtocols.Count; ++i) - { - ReadOnlyMemory alpnProtocol = alpnProtocols[i].Protocol; - MemoryHandle h = alpnProtocol.Pin(); - - memoryHandles[i] = h; - quicBuffers[i].Buffer = (byte*)h.Pointer; - quicBuffers[i].Length = (uint)alpnProtocol.Length; - } - - IntPtr session; - - fixed (MsQuicNativeMethods.QuicBuffer* pQuicBuffers = quicBuffers) - { - session = SessionOpen(pQuicBuffers, (uint)alpnProtocols.Count); - } - - ArrayPool.Shared.Return(quicBuffers); - ArrayPool.Shared.Return(memoryHandles); - - return session; - } - finally - { - foreach (MemoryHandle handle in memoryHandles) - { - handle.Dispose(); - } - } - } - - private unsafe IntPtr SessionOpen(SslApplicationProtocol alpnProtocol) - { - ReadOnlyMemory memory = alpnProtocol.Protocol; - using MemoryHandle h = memory.Pin(); - - var quicBuffer = new MsQuicNativeMethods.QuicBuffer() - { - Buffer = (byte*)h.Pointer, - Length = (uint)memory.Length - }; - - return SessionOpen(&quicBuffer, 1); - } - - private unsafe IntPtr SessionOpen(MsQuicNativeMethods.QuicBuffer *alpnBuffers, uint bufferCount) - { - IntPtr sessionPtr = IntPtr.Zero; - uint status = SessionOpenDelegate( - _registrationContext, - alpnBuffers, - bufferCount, - IntPtr.Zero, - ref sessionPtr); - - QuicExceptionHelpers.ThrowIfFailed(status, "Could not open session."); - - return sessionPtr; - } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - ~MsQuicApi() - { - Dispose(disposing: false); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - RegistrationCloseDelegate?.Invoke(_registrationContext); - - _disposed = true; - } + internal RegistrationOpenDelegate RegistrationOpenDelegate { get; } + internal RegistrationCloseDelegate RegistrationCloseDelegate { get; } + + internal ConfigurationOpenDelegate ConfigurationOpenDelegate { get; } + internal ConfigurationCloseDelegate ConfigurationCloseDelegate { get; } + internal ConfigurationLoadCredentialDelegate ConfigurationLoadCredentialDelegate { get; } + + internal ListenerOpenDelegate ListenerOpenDelegate { get; } + internal ListenerCloseDelegate ListenerCloseDelegate { get; } + internal ListenerStartDelegate ListenerStartDelegate { get; } + internal ListenerStopDelegate ListenerStopDelegate { get; } + + // TODO: missing SendResumptionTicket + internal ConnectionOpenDelegate ConnectionOpenDelegate { get; } + internal ConnectionCloseDelegate ConnectionCloseDelegate { get; } + internal ConnectionShutdownDelegate ConnectionShutdownDelegate { get; } + internal ConnectionStartDelegate ConnectionStartDelegate { get; } + internal ConnectionSetConfigurationDelegate ConnectionSetConfigurationDelegate { get; } + + internal StreamOpenDelegate StreamOpenDelegate { get; } + internal StreamCloseDelegate StreamCloseDelegate { get; } + internal StreamStartDelegate StreamStartDelegate { get; } + internal StreamShutdownDelegate StreamShutdownDelegate { get; } + internal StreamSendDelegate StreamSendDelegate { get; } + internal StreamReceiveCompleteDelegate StreamReceiveCompleteDelegate { get; } + internal StreamReceiveSetEnabledDelegate StreamReceiveSetEnabledDelegate { get; } + + internal SetCallbackHandlerDelegate SetCallbackHandlerDelegate { get; } + + internal SetParamDelegate SetParamDelegate { get; } + internal GetParamDelegate GetParamDelegate { get; } } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs index afeb151a0436b0..63672d2a6e175a 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs @@ -1,97 +1,62 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System.Runtime.InteropServices; using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods; namespace System.Net.Quic.Implementations.MsQuic.Internal { internal static class MsQuicParameterHelpers { - internal static unsafe SOCKADDR_INET GetINetParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param) + internal static unsafe SOCKADDR_INET GetINetParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param) { - byte* ptr = stackalloc byte[sizeof(SOCKADDR_INET)]; - QuicBuffer buffer = new QuicBuffer - { - Length = (uint)sizeof(SOCKADDR_INET), - Buffer = ptr - }; + SOCKADDR_INET value; + uint valueLen = (uint)sizeof(SOCKADDR_INET); - QuicExceptionHelpers.ThrowIfFailed( - api.UnsafeGetParam(nativeObject, level, param, ref buffer), - "Could not get SOCKADDR_INET."); + uint status = api.GetParamDelegate(nativeObject, level, param, ref valueLen, (byte*)&value); + QuicExceptionHelpers.ThrowIfFailed(status, "GetINETParam failed."); + Debug.Assert(valueLen == sizeof(SOCKADDR_INET)); - return *(SOCKADDR_INET*)ptr; + return value; } - internal static unsafe ushort GetUShortParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param) + internal static unsafe ushort GetUShortParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param) { - byte* ptr = stackalloc byte[sizeof(ushort)]; - QuicBuffer buffer = new QuicBuffer() - { - Length = sizeof(ushort), - Buffer = ptr - }; + ushort value; + uint valueLen = (uint)sizeof(ushort); - QuicExceptionHelpers.ThrowIfFailed( - api.UnsafeGetParam(nativeObject, level, param, ref buffer), - "Could not get ushort."); + uint status = api.GetParamDelegate(nativeObject, level, param, ref valueLen, (byte*)&value); + QuicExceptionHelpers.ThrowIfFailed(status, "GetUShortParam failed."); + Debug.Assert(valueLen == sizeof(ushort)); - return *(ushort*)ptr; + return value; } - internal static unsafe void SetUshortParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param, ushort value) + internal static unsafe void SetUShortParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param, ushort value) { - QuicBuffer buffer = new QuicBuffer() - { - Length = sizeof(ushort), - Buffer = (byte*)&value - }; - QuicExceptionHelpers.ThrowIfFailed( - api.UnsafeSetParam(nativeObject, level, param, buffer), + api.SetParamDelegate(nativeObject, level, param, sizeof(ushort), (byte*)&value), "Could not set ushort."); } - internal static unsafe ulong GetULongParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param) + internal static unsafe ulong GetULongParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param) { - byte* ptr = stackalloc byte[sizeof(ulong)]; - QuicBuffer buffer = new QuicBuffer() - { - Length = sizeof(ulong), - Buffer = ptr - }; + ulong value; + uint valueLen = (uint)sizeof(ulong); - QuicExceptionHelpers.ThrowIfFailed( - api.UnsafeGetParam(nativeObject, level, param, ref buffer), - "Could not get ulong."); + uint status = api.GetParamDelegate(nativeObject, level, param, ref valueLen, (byte*)&value); + QuicExceptionHelpers.ThrowIfFailed(status, "GetULongParam failed."); + Debug.Assert(valueLen == sizeof(ulong)); - return *(ulong*)ptr; + return value; } - internal static unsafe void SetULongParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param, ulong value) + internal static unsafe void SetULongParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param, ulong value) { - QuicBuffer buffer = new QuicBuffer() - { - Length = sizeof(ulong), - Buffer = (byte*)&value - }; - QuicExceptionHelpers.ThrowIfFailed( - api.UnsafeGetParam(nativeObject, level, param, ref buffer), + api.SetParamDelegate(nativeObject, level, param, sizeof(ulong), (byte*)&value), "Could not set ulong."); } - - internal static unsafe void SetSecurityConfig(MsQuicApi api, IntPtr nativeObject, uint level, uint param, IntPtr value) - { - QuicBuffer buffer = new QuicBuffer() - { - Length = (uint)sizeof(void*), - Buffer = (byte*)&value - }; - - QuicExceptionHelpers.ThrowIfFailed( - api.UnsafeSetParam(nativeObject, level, param, buffer), - "Could not set security configuration."); - } } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs deleted file mode 100644 index 4c941c0dc1e713..00000000000000 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Net.Quic.Implementations.MsQuic.Internal -{ - // TODO this will eventually be abstracted to support both Client and Server - // certificates - internal sealed class MsQuicSecurityConfig : IDisposable - { - private bool _disposed; - private MsQuicApi _registration; - - public MsQuicSecurityConfig(MsQuicApi registration, IntPtr nativeObjPtr) - { - _registration = registration; - NativeObjPtr = nativeObjPtr; - } - - public IntPtr NativeObjPtr { get; private set; } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _registration.SecConfigDeleteDelegate?.Invoke(NativeObjPtr); - NativeObjPtr = IntPtr.Zero; - _disposed = true; - } - - ~MsQuicSecurityConfig() - { - Dispose(disposing: false); - } - } -} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs deleted file mode 100644 index 8b0e28e290ef7c..00000000000000 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; -using System.Net.Security; - -namespace System.Net.Quic.Implementations.MsQuic.Internal -{ - internal sealed class MsQuicSession : IDisposable - { - private bool _disposed; - private IntPtr _nativeObjPtr; - private bool _opened; - - internal MsQuicSession() - { - if (!MsQuicApi.IsQuicSupported) - { - throw new NotSupportedException(SR.net_quic_notsupported); - } - } - - public IntPtr ConnectionOpen(QuicClientConnectionOptions options) - { - if (!_opened) - { - OpenSession(options.ClientAuthenticationOptions!.ApplicationProtocols!, - (ushort)options.MaxBidirectionalStreams, - (ushort)options.MaxUnidirectionalStreams); - } - - QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ConnectionOpenDelegate( - _nativeObjPtr, - MsQuicConnection.s_connectionDelegate, - IntPtr.Zero, - out IntPtr connectionPtr), - "Could not open the connection."); - - return connectionPtr; - } - - private void OpenSession(List alpn, ushort bidirectionalStreamCount, ushort undirectionalStreamCount) - { - _opened = true; - _nativeObjPtr = MsQuicApi.Api.SessionOpen(alpn); - SetPeerBiDirectionalStreamCount(bidirectionalStreamCount); - SetPeerUnidirectionalStreamCount(undirectionalStreamCount); - } - - // TODO allow for a callback to select the certificate (SNI). - public IntPtr ListenerOpen(QuicListenerOptions options) - { - if (!_opened) - { - OpenSession(options.ServerAuthenticationOptions!.ApplicationProtocols!, - (ushort)options.MaxBidirectionalStreams, - (ushort)options.MaxUnidirectionalStreams); - } - - QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ListenerOpenDelegate( - _nativeObjPtr, - MsQuicListener.s_listenerDelegate, - IntPtr.Zero, - out IntPtr listenerPointer), - "Could not open listener."); - - return listenerPointer; - } - - // TODO call this for graceful shutdown? - public void ShutDown( - QUIC_CONNECTION_SHUTDOWN_FLAG Flags, - ushort ErrorCode) - { - MsQuicApi.Api.SessionShutdownDelegate( - _nativeObjPtr, - (uint)Flags, - ErrorCode); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public void SetPeerBiDirectionalStreamCount(ushort count) - { - SetUshortParameter(QUIC_PARAM_SESSION.PEER_BIDI_STREAM_COUNT, count); - } - - public void SetPeerUnidirectionalStreamCount(ushort count) - { - SetUshortParameter(QUIC_PARAM_SESSION.PEER_UNIDI_STREAM_COUNT, count); - } - - private unsafe void SetUshortParameter(QUIC_PARAM_SESSION param, ushort count) - { - var buffer = new MsQuicNativeMethods.QuicBuffer() - { - Length = sizeof(ushort), - Buffer = (byte*)&count - }; - - SetParam(param, buffer); - } - - public void SetDisconnectTimeout(TimeSpan timeout) - { - SetULongParamter(QUIC_PARAM_SESSION.DISCONNECT_TIMEOUT, (ulong)timeout.TotalMilliseconds); - } - - public void SetIdleTimeout(TimeSpan timeout) - { - SetULongParamter(QUIC_PARAM_SESSION.IDLE_TIMEOUT, (ulong)timeout.TotalMilliseconds); - - } - private unsafe void SetULongParamter(QUIC_PARAM_SESSION param, ulong count) - { - var buffer = new MsQuicNativeMethods.QuicBuffer() - { - Length = sizeof(ulong), - Buffer = (byte*)&count - }; - SetParam(param, buffer); - } - - private void SetParam( - QUIC_PARAM_SESSION param, - MsQuicNativeMethods.QuicBuffer buf) - { - QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.UnsafeSetParam( - _nativeObjPtr, - (uint)QUIC_PARAM_LEVEL.SESSION, - (uint)param, - buf), - "Could not set parameter on session."); - } - - ~MsQuicSession() - { - Dispose(false); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - MsQuicApi.Api.SessionCloseDelegate?.Invoke(_nativeObjPtr); - _nativeObjPtr = IntPtr.Zero; - - _disposed = true; - } - } -} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicAlpnHelper.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicAlpnHelper.cs new file mode 100644 index 00000000000000..916cfc4c550cdf --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicAlpnHelper.cs @@ -0,0 +1,58 @@ +// 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.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net.Security; +using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal static class MsQuicAlpnHelper + { + public static unsafe void Prepare(List alpnProtocols, [NotNull] out MemoryHandle[]? handles, [NotNull] out QuicBuffer[]? buffers) + { + handles = ArrayPool.Shared.Rent(alpnProtocols.Count); + buffers = ArrayPool.Shared.Rent(alpnProtocols.Count); + + try + { + for (int i = 0; i < alpnProtocols.Count; ++i) + { + ReadOnlyMemory alpnProtocol = alpnProtocols[i].Protocol; + MemoryHandle h = alpnProtocol.Pin(); + + handles[i] = h; + buffers[i].Buffer = (byte*)h.Pointer; + buffers[i].Length = (uint)alpnProtocol.Length; + } + } + catch + { + Return(ref handles, ref buffers); + throw; + } + } + + public static void Return(ref MemoryHandle[]? handles, ref QuicBuffer[]? buffers) + { + if (handles is MemoryHandle[] notNullHandles) + { + foreach (MemoryHandle h in notNullHandles) + { + h.Dispose(); + } + + handles = null; + ArrayPool.Shared.Return(notNullHandles); + } + + if (buffers is QuicBuffer[] notNullBuffers) + { + buffers = null; + ArrayPool.Shared.Return(notNullBuffers); + } + } + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicEnums.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicEnums.cs new file mode 100644 index 00000000000000..34dd31bf70ce83 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicEnums.cs @@ -0,0 +1,211 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal enum QUIC_EXECUTION_PROFILE : uint + { + QUIC_EXECUTION_PROFILE_LOW_LATENCY, // Default + QUIC_EXECUTION_PROFILE_TYPE_MAX_THROUGHPUT, + QUIC_EXECUTION_PROFILE_TYPE_SCAVENGER, + QUIC_EXECUTION_PROFILE_TYPE_REAL_TIME, + } + + internal enum QUIC_CREDENTIAL_TYPE : uint + { + NONE, + HASH, + HASH_STORE, + CONTEXT, + FILE, + FILE_PROTECTED, + STUB_NULL = 0xF0000000, // Pass as server cert to stubtls implementation. + } + + [Flags] + internal enum QUIC_CREDENTIAL_FLAGS : uint + { + NONE = 0x00000000, + CLIENT = 0x00000001, // Lack of client flag indicates server. + LOAD_ASYNCHRONOUS = 0x00000002, + NO_CERTIFICATE_VALIDATION = 0x00000004, + ENABLE_OCSP = 0x00000008, // Schannel only currently. + INDICATE_CERTIFICATE_RECEIVED = 0x00000010, + DEFER_CERTIFICATE_VALIDATION = 0x00000020, // Schannel only currently. + REQUIRE_CLIENT_AUTHENTICATION = 0x00000040, // Schannel only currently. + USE_TLS_BUILTIN_CERTIFICATE_VALIDATION = 0x00000080, + } + + internal enum QUIC_CERTIFICATE_HASH_STORE_FLAGS + { + QUIC_CERTIFICATE_HASH_STORE_FLAG_NONE = 0x0000, + QUIC_CERTIFICATE_HASH_STORE_FLAG_MACHINE_STORE = 0x0001, + } + + [Flags] + internal enum QUIC_CONNECTION_SHUTDOWN_FLAGS : uint + { + NONE = 0x0000, + SILENT = 0x0001, // Don't send the close frame over the network. + } + + internal enum QUIC_SERVER_RESUMPTION_LEVEL : uint + { + NO_RESUME, + RESUME_ONLY, + RESUME_AND_ZERORTT, + } + + [Flags] + internal enum QUIC_STREAM_OPEN_FLAGS : uint + { + NONE = 0x0000, + UNIDIRECTIONAL = 0x0001, // Indicates the stream is unidirectional. + ZERO_RTT = 0x0002, // The stream was opened via a 0-RTT packet. + } + + [Flags] + internal enum QUIC_STREAM_START_FLAGS : uint + { + NONE = 0x0000, + FAIL_BLOCKED = 0x0001, // Only opens the stream if flow control allows. + IMMEDIATE = 0x0002, // Immediately informs peer that stream is open. + ASYNC = 0x0004, // Don't block the API call to wait for completion. + SHUTDOWN_ON_FAIL = 0x0008, // Shutdown the stream immediately after start failure. + } + + [Flags] + internal enum QUIC_STREAM_SHUTDOWN_FLAGS : uint + { + NONE = 0x0000, + GRACEFUL = 0x0001, // Cleanly closes the send path. + ABORT_SEND = 0x0002, // Abruptly closes the send path. + ABORT_RECEIVE = 0x0004, // Abruptly closes the receive path. + ABORT = 0x0006, // Abruptly closes both send and receive paths. + IMMEDIATE = 0x0008, // Immediately sends completion events to app. + } + + [Flags] + internal enum QUIC_RECEIVE_FLAGS : uint + { + NONE = 0x0000, + ZERO_RTT = 0x0001, // Data was encrypted with 0-RTT key. + FIN = 0x0002, // FIN was included with this data. + } + + [Flags] + internal enum QUIC_SEND_FLAGS : uint + { + NONE = 0x0000, + ALLOW_0_RTT = 0x0001, // Allows the use of encrypting with 0-RTT key. + START = 0x0002, // Asynchronously starts the stream with the sent data. + FIN = 0x0004, // Indicates the request is the one last sent on the stream. + DGRAM_PRIORITY = 0x0008, // Indicates the datagram is higher priority than others. + DELAY_SEND = 0x0010, // Indicates the send should be delayed because more will be queued soon. + } + + internal enum QUIC_PARAM_LEVEL : uint + { + GLOBAL, + REGISTRATION, + CONFIGURATION, + LISTENER, + CONNECTION, + TLS, + STREAM, + } + + internal enum QUIC_PARAM_GLOBAL : uint + { + RETRY_MEMORY_PERCENT = 0, // uint16_t + SUPPORTED_VERSIONS = 1, // uint32_t[] - network byte order + LOAD_BALANCING_MODE = 2, // uint16_t - QUIC_LOAD_BALANCING_MODE + PERF_COUNTERS = 3, // uint64_t[] - Array size is QUIC_PERF_COUNTER_MAX + SETTINGS = 4, // QUIC_SETTINGS + } + + internal enum QUIC_PARAM_REGISTRATION : uint + { + CID_PREFIX = 0, // uint8_t[] + } + + internal enum QUIC_PARAM_LISTENER : uint + { + LOCAL_ADDRESS = 0, // QUIC_ADDR + STATS = 1, // QUIC_LISTENER_STATISTICS + } + + internal enum QUIC_PARAM_CONN : uint + { + QUIC_VERSION = 0, // uint32_t + LOCAL_ADDRESS = 1, // QUIC_ADDR + REMOTE_ADDRESS = 2, // QUIC_ADDR + IDEAL_PROCESSOR = 3, // uint16_t + SETTINGS = 4, // QUIC_SETTINGS + STATISTICS = 5, // QUIC_STATISTICS + STATISTICS_PLAT = 6, // QUIC_STATISTICS + SHARE_UDP_BINDING = 7, // uint8_t (BOOLEAN) + LOCAL_BIDI_STREAM_COUNT = 8, // uint16_t + LOCAL_UNIDI_STREAM_COUNT = 9, // uint16_t + MAX_STREAM_IDS = 10, // uint64_t[4] + CLOSE_REASON_PHRASE = 11, // char[] + STREAM_SCHEDULING_SCHEME = 12, // QUIC_STREAM_SCHEDULING_SCHEME + DATAGRAM_RECEIVE_ENABLED = 13, // uint8_t (BOOLEAN) + DATAGRAM_SEND_ENABLED = 14, // uint8_t (BOOLEAN) + DISABLE_1RTT_ENCRYPTION = 15, // uint8_t (BOOLEAN) + RESUMPTION_TICKET = 16, // uint8_t[] + PEER_CERTIFICATE_VALID = 17, // uint8_t (BOOLEAN) + } + + internal enum QUIC_PARAM_STREAM : uint + { + ID = 0, // QUIC_UINT62 + ZERRTT_LENGTH = 1, // uint64_t + IDEAL_SEND_BUFFER_SIZE = 2, // uint64_t - bytes + } + + internal enum QUIC_LISTENER_EVENT : uint + { + NEW_CONNECTION = 0, + } + + internal enum QUIC_CONNECTION_EVENT_TYPE : uint + { + CONNECTED = 0, + SHUTDOWN_INITIATED_BY_TRANSPORT = 1, // The transport started the shutdown process. + SHUTDOWN_INITIATED_BY_PEER = 2, // The peer application started the shutdown process. + SHUTDOWN_COMPLETE = 3, // Ready for the handle to be closed. + LOCAL_ADDRESS_CHANGED = 4, + PEER_ADDRESS_CHANGED = 5, + PEER_STREAM_STARTED = 6, + STREAMS_AVAILABLE = 7, + PEER_NEEDS_STREAMS = 8, + IDEAL_PROCESSOR_CHANGED = 9, + DATAGRAM_STATE_CHANGED = 10, + DATAGRAM_RECEIVED = 11, + DATAGRAM_SEND_STATE_CHANGED = 12, + RESUMED = 13, // Server-only; provides resumption data, if any. + RESUMPTION_TICKET_RECEIVED = 14, // Client-only; provides ticket to persist, if any. + PEER_CERTIFICATE_RECEIVED = 15, // Only with QUIC_CREDENTIAL_FLAG_INDICATE_CERTIFICATE_RECEIVED set. + } + + internal enum QUIC_STREAM_EVENT_TYPE : uint + { + START_COMPLETE = 0, + RECEIVE = 1, + SEND_COMPLETE = 2, + PEER_SEND_SHUTDOWN = 3, + PEER_SEND_ABORTED = 4, + PEER_RECEIVE_ABORTED = 5, + SEND_SHUTDOWN_COMPLETE = 6, + SHUTDOWN_COMPLETE = 7, + IDEAL_SEND_BUFFER_SIZE = 8, + } + + internal enum QUIC_ADDRESS_FAMILY : ushort + { + UNSPEC = 0, + INET = 2, + INET6 = 23, + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicNativeMethods.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs similarity index 54% rename from src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicNativeMethods.cs rename to src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs index 3027c402bb0ee2..131227746c3ad3 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicNativeMethods.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs @@ -1,9 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Text; namespace System.Net.Quic.Implementations.MsQuic.Internal { @@ -24,13 +22,11 @@ internal struct NativeApi internal IntPtr RegistrationOpen; internal IntPtr RegistrationClose; + internal IntPtr RegistrationShutdown; - internal IntPtr SecConfigCreate; - internal IntPtr SecConfigDelete; - - internal IntPtr SessionOpen; - internal IntPtr SessionClose; - internal IntPtr SessionShutdown; + internal IntPtr ConfigurationOpen; + internal IntPtr ConfigurationClose; + internal IntPtr ConfigurationLoadCredential; internal IntPtr ListenerOpen; internal IntPtr ListenerClose; @@ -41,6 +37,7 @@ internal struct NativeApi internal IntPtr ConnectionClose; internal IntPtr ConnectionShutdown; internal IntPtr ConnectionStart; + internal IntPtr ConnectionSetConfiguration; internal IntPtr ConnectionSendResumptionTicket; internal IntPtr StreamOpen; @@ -60,40 +57,43 @@ internal delegate uint MsQuicOpenDelegate( [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint SetContextDelegate( - IntPtr handle, + SafeHandle handle, IntPtr context); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr GetContextDelegate( - IntPtr handle); + SafeHandle handle); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void SetCallbackHandlerDelegate( - IntPtr handle, + SafeHandle handle, Delegate del, IntPtr context); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint SetParamDelegate( - IntPtr handle, - uint level, + SafeHandle handle, + QUIC_PARAM_LEVEL level, uint param, uint bufferLength, byte* buffer); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint GetParamDelegate( - IntPtr handle, - uint level, + SafeHandle handle, + QUIC_PARAM_LEVEL level, uint param, - uint* bufferLength, + ref uint bufferLength, byte* buffer); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint RegistrationOpenDelegate(ref RegistrationConfig config, out IntPtr registrationContext); + internal delegate uint RegistrationOpenDelegate( + ref RegistrationConfig config, + out SafeMsQuicRegistrationHandle registrationContext); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void RegistrationCloseDelegate(IntPtr registrationContext); + internal delegate void RegistrationCloseDelegate( + IntPtr registrationContext); [StructLayout(LayoutKind.Sequential)] internal struct RegistrationConfig @@ -104,38 +104,173 @@ internal struct RegistrationConfig } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void SecConfigCreateCompleteDelegate(IntPtr context, uint status, IntPtr securityConfig); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint SecConfigCreateDelegate( - IntPtr registrationContext, - uint flags, - IntPtr certificate, - [MarshalAs(UnmanagedType.LPUTF8Str)]string? principal, + internal delegate uint ConfigurationOpenDelegate( + SafeMsQuicRegistrationHandle registrationContext, + QuicBuffer* alpnBuffers, + uint alpnBufferCount, + ref QuicSettings settings, + uint settingsSize, IntPtr context, - SecConfigCreateCompleteDelegate completionHandler); + out SafeMsQuicConfigurationHandle configuration); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void SecConfigDeleteDelegate( - IntPtr securityConfig); + internal delegate void ConfigurationCloseDelegate( + IntPtr configuration); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint SessionOpenDelegate( - IntPtr registrationContext, - QuicBuffer *alpnBuffers, - uint alpnBufferCount, - IntPtr context, - ref IntPtr session); + internal delegate uint ConfigurationLoadCredentialDelegate( + SafeMsQuicConfigurationHandle configuration, + ref CredentialConfig credConfig); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void SessionCloseDelegate( - IntPtr session); + [StructLayout(LayoutKind.Sequential)] + internal struct QuicSettings + { + internal QuicSettingsIsSetFlags IsSetFlags; + internal ulong MaxBytesPerKey; + internal ulong HandshakeIdleTimeoutMs; + internal ulong IdleTimeoutMs; + internal uint TlsClientMaxSendBuffer; + internal uint TlsServerMaxSendBuffer; + internal uint StreamRecvWindowDefault; + internal uint StreamRecvBufferDefault; + internal uint ConnFlowControlWindow; + internal uint MaxWorkerQueueDelayUs; + internal uint MaxStatelessOperations; + internal uint InitialWindowPackets; + internal uint SendIdleTimeoutMs; + internal uint InitialRttMs; + internal uint MaxAckDelayMs; + internal uint DisconnectTimeoutMs; + internal uint KeepAliveIntervalMs; + internal ushort PeerBidiStreamCount; + internal ushort PeerUnidiStreamCount; + internal ushort RetryMemoryLimit; // Global only + internal ushort LoadBalancingMode; // Global only + internal byte MaxOperationsPerDrain; + internal QuicSettingsEnabledFlagsFlags EnabledFlags; + internal uint* DesiredVersionsList; + internal uint DesiredVersionsListLength; + } - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void SessionShutdownDelegate( - IntPtr session, - uint flags, - ulong errorCode); + [Flags] + internal enum QuicSettingsIsSetFlags : ulong + { + MaxBytesPerKey = 1 << 0, + HandshakeIdleTimeoutMs = 1 << 1, + IdleTimeoutMs = 1 << 2, + TlsClientMaxSendBuffer = 1 << 3, + TlsServerMaxSendBuffer = 1 << 4, + StreamRecvWindowDefault = 1 << 5, + StreamRecvBufferDefault = 1 << 6, + ConnFlowControlWindow = 1 << 7, + MaxWorkerQueueDelayUs = 1 << 8, + MaxStatelessOperations = 1 << 9, + InitialWindowPackets = 1 << 10, + SendIdleTimeoutMs = 1 << 11, + InitialRttMs = 1 << 12, + MaxAckDelayMs = 1 << 13, + DisconnectTimeoutMs = 1 << 14, + KeepAliveIntervalMs = 1 << 15, + PeerBidiStreamCount = 1 << 16, + PeerUnidiStreamCount = 1 << 17, + RetryMemoryLimit = 1 << 18, + LoadBalancingMode = 1 << 19, + MaxOperationsPerDrain = 1 << 20, + SendBufferingEnabled = 1 << 21, + PacingEnabled = 1 << 22, + MigrationEnabled = 1 << 23, + DatagramReceiveEnabled = 1 << 24, + ServerResumptionLevel = 1 << 25, + DesiredVersionsList = 1 << 26, + VersionNegotiationExtEnabled = 1 << 27, + } + + [Flags] + internal enum QuicSettingsEnabledFlagsFlags : byte + { + SendBufferingEnabled = 1 << 0, + PacingEnabled = 1 << 1, + MigrationEnabled = 1 << 2, + DatagramReceiveEnabled = 1 << 3, + // Contains values of QUIC_SERVER_RESUMPTION_LEVEL + ServerResumptionLevel = 1 << 4 | 1 << 5, + VersionNegotiationExtEnabled = 1 << 6, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CredentialConfig + { + internal QUIC_CREDENTIAL_TYPE Type; + internal QUIC_CREDENTIAL_FLAGS Flags; + // CredentialConfigCertificateUnion* + internal IntPtr Certificate; + [MarshalAs(UnmanagedType.LPUTF8Str)] + internal string Principal; + internal IntPtr Reserved; // Currently unused + // TODO: define delegate for AsyncHandler and make proper use of it. + internal IntPtr AsyncHandler; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct CredentialConfigCertificateUnion + { + [FieldOffset(0)] + internal CredentialConfigCertificateCertificateHash CertificateHash; + + [FieldOffset(0)] + internal CredentialConfigCertificateCertificateHashStore CertificateHashStore; + + [FieldOffset(0)] + internal IntPtr CertificateContext; + + [FieldOffset(0)] + internal CredentialConfigCertificateCertificateFile CertificateFile; + + [FieldOffset(0)] + internal CredentialConfigCertificateCertificateFileProtected CertificateFileProtected; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CredentialConfigCertificateCertificateHash + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + internal byte[] ShaHash; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct CredentialConfigCertificateCertificateHashStore + { + internal QUIC_CERTIFICATE_HASH_STORE_FLAGS Flags; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + internal byte[] ShaHash; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + internal char[] StoreName; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CredentialConfigCertificateCertificateFile + { + [MarshalAs(UnmanagedType.LPUTF8Str)] + internal string PrivateKeyFile; + + [MarshalAs(UnmanagedType.LPUTF8Str)] + internal string CertificateFile; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CredentialConfigCertificateCertificateFileProtected + { + [MarshalAs(UnmanagedType.LPUTF8Str)] + internal string PrivateKeyFile; + + [MarshalAs(UnmanagedType.LPUTF8Str)] + internal string CertificateFile; + + [MarshalAs(UnmanagedType.LPUTF8Str)] + internal string PrivateKeyPassword; + } [StructLayout(LayoutKind.Sequential)] internal struct ListenerEvent @@ -154,24 +289,29 @@ internal struct ListenerEventDataUnion [StructLayout(LayoutKind.Sequential)] internal struct ListenerEventDataNewConnection { - internal IntPtr Info; + internal NewConnectionInfo* Info; internal IntPtr Connection; - internal IntPtr SecurityConfig; } [StructLayout(LayoutKind.Sequential)] internal struct NewConnectionInfo { internal uint QuicVersion; + // QUIC_ADDR internal IntPtr LocalAddress; + // QUIC_ADDR internal IntPtr RemoteAddress; internal uint CryptoBufferLength; - internal ushort AlpnListLength; + internal ushort ClientAlpnListLength; internal ushort ServerNameLength; internal byte NegotiatedAlpnLength; + // byte[] internal IntPtr CryptoBuffer; + // byte[] internal IntPtr ClientAlpnList; + // byte[] internal IntPtr NegotiatedAlpn; + // string internal IntPtr ServerName; } @@ -183,29 +323,32 @@ internal delegate uint ListenerCallbackDelegate( [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint ListenerOpenDelegate( - IntPtr session, + SafeMsQuicRegistrationHandle registration, ListenerCallbackDelegate handler, IntPtr context, - out IntPtr listener); + out SafeMsQuicListenerHandle listener); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint ListenerCloseDelegate( + internal delegate void ListenerCloseDelegate( IntPtr listener); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint ListenerStartDelegate( - IntPtr listener, + SafeMsQuicListenerHandle listener, + QuicBuffer* alpnBuffers, + uint alpnBufferCount, ref SOCKADDR_INET localAddress); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint ListenerStopDelegate( - IntPtr listener); + internal delegate void ListenerStopDelegate( + SafeMsQuicListenerHandle listener); [StructLayout(LayoutKind.Sequential)] internal struct ConnectionEventDataConnected { - internal bool SessionResumed; + internal byte SessionResumed; internal byte NegotiatedAlpnLength; + // byte[] internal IntPtr NegotiatedAlpn; } @@ -224,26 +367,37 @@ internal struct ConnectionEventDataShutdownInitiatedByPeer [StructLayout(LayoutKind.Sequential)] internal struct ConnectionEventDataShutdownComplete { - internal bool TimedOut; + // The flags have fixed sized exactly 3 bits + internal ConnectionEventDataShutdownCompleteFlags Flags; + } + + [Flags] + internal enum ConnectionEventDataShutdownCompleteFlags : byte + { + HandshakeCompleted = 1 << 0, + PeerAcknowledgedShutdown = 1 << 1, + AppCloseInProgress = 1 << 2, } [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataLocalAddrChanged + internal struct ConnectionEventDataLocalAddressChanged { + // QUIC_ADDR internal IntPtr Address; } [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataPeerAddrChanged + internal struct ConnectionEventDataPeerAddressChanged { + // QUIC_ADDR internal IntPtr Address; } [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataStreamStarted + internal struct ConnectionEventDataPeerStreamStarted { internal IntPtr Stream; - internal QUIC_STREAM_OPEN_FLAG Flags; + internal QUIC_STREAM_OPEN_FLAGS Flags; } [StructLayout(LayoutKind.Sequential)] @@ -269,32 +423,25 @@ internal struct ConnectionEventDataUnion internal ConnectionEventDataShutdownComplete ShutdownComplete; [FieldOffset(0)] - internal ConnectionEventDataLocalAddrChanged LocalAddrChanged; + internal ConnectionEventDataLocalAddressChanged LocalAddressChanged; [FieldOffset(0)] - internal ConnectionEventDataPeerAddrChanged PeerAddrChanged; + internal ConnectionEventDataPeerAddressChanged PeerAddressChanged; [FieldOffset(0)] - internal ConnectionEventDataStreamStarted StreamStarted; + internal ConnectionEventDataPeerStreamStarted PeerStreamStarted; [FieldOffset(0)] internal ConnectionEventDataStreamsAvailable StreamsAvailable; + + // TODO: missing IDEAL_PROCESSOR_CHANGED, ..., PEER_CERTIFICATE_RECEIVED (7 total) } [StructLayout(LayoutKind.Sequential)] internal struct ConnectionEvent { - internal QUIC_CONNECTION_EVENT Type; + internal QUIC_CONNECTION_EVENT_TYPE Type; internal ConnectionEventDataUnion Data; - - internal bool EarlyDataAccepted => Data.Connected.SessionResumed; - //internal ulong NumBytes => Data.IdealSendBuffer.NumBytes; - internal uint ShutdownBeginStatus => Data.ShutdownInitiatedByTransport.Status; - internal long ShutdownBeginPeerStatus => Data.ShutdownInitiatedByPeer.ErrorCode; - internal bool ShutdownTimedOut => Data.ShutdownComplete.TimedOut; - internal ushort BiDirectionalCount => Data.StreamsAvailable.BiDirectionalCount; - internal ushort UniDirectionalCount => Data.StreamsAvailable.UniDirectionalCount; - internal QUIC_STREAM_OPEN_FLAG StreamFlags => Data.StreamStarted.Flags; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -303,56 +450,65 @@ internal delegate uint ConnectionCallbackDelegate( IntPtr context, ref ConnectionEvent connectionEvent); + // TODO: order is Open, Close, Shutdown, Start, SetConfiguration, SendResumptionTicket [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint ConnectionOpenDelegate( - IntPtr session, + SafeMsQuicRegistrationHandle registration, ConnectionCallbackDelegate handler, IntPtr context, - out IntPtr connection); + out SafeMsQuicConnectionHandle connection); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint ConnectionCloseDelegate( + internal delegate void ConnectionCloseDelegate( IntPtr connection); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate uint ConnectionSetConfigurationDelegate( + SafeMsQuicConnectionHandle connection, + SafeMsQuicConfigurationHandle configuration); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint ConnectionStartDelegate( - IntPtr connection, - ushort family, + SafeMsQuicConnectionHandle connection, + SafeMsQuicConfigurationHandle configuration, + QUIC_ADDRESS_FAMILY family, [MarshalAs(UnmanagedType.LPUTF8Str)] string serverName, ushort serverPort); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint ConnectionShutdownDelegate( - IntPtr connection, - uint flags, + internal delegate void ConnectionShutdownDelegate( + SafeMsQuicConnectionHandle connection, + QUIC_CONNECTION_SHUTDOWN_FLAGS flags, long errorCode); + // TODO: missing SendResumptionTicket + [StructLayout(LayoutKind.Sequential)] - internal struct StreamEventDataRecv + internal struct StreamEventDataReceive { internal ulong AbsoluteOffset; internal ulong TotalBufferLength; internal QuicBuffer* Buffers; internal uint BufferCount; - internal QUIC_RECEIVE_FLAG Flags; + internal QUIC_RECEIVE_FLAGS Flags; } [StructLayout(LayoutKind.Sequential)] internal struct StreamEventDataSendComplete { - internal bool Canceled; + internal byte Canceled; internal IntPtr ClientContext; } [StructLayout(LayoutKind.Sequential)] - internal struct StreamEventDataPeerSendAbort + internal struct StreamEventDataPeerSendAborted { internal long ErrorCode; } [StructLayout(LayoutKind.Sequential)] - internal struct StreamEventDataPeerRecvAbort + internal struct StreamEventDataPeerReceiveAborted { internal long ErrorCode; } @@ -366,29 +522,33 @@ internal struct StreamEventDataSendShutdownComplete [StructLayout(LayoutKind.Explicit)] internal struct StreamEventDataUnion { + // TODO: missing START_COMPLETE [FieldOffset(0)] - internal StreamEventDataRecv Recv; + internal StreamEventDataReceive Receive; [FieldOffset(0)] internal StreamEventDataSendComplete SendComplete; [FieldOffset(0)] - internal StreamEventDataPeerSendAbort PeerSendAbort; + internal StreamEventDataPeerSendAborted PeerSendAborted; [FieldOffset(0)] - internal StreamEventDataPeerRecvAbort PeerRecvAbort; + internal StreamEventDataPeerReceiveAborted PeerReceiveAborted; [FieldOffset(0)] internal StreamEventDataSendShutdownComplete SendShutdownComplete; + + // TODO: missing IDEAL_SEND_BUFFER_SIZE } [StructLayout(LayoutKind.Sequential)] internal struct StreamEvent { - internal QUIC_STREAM_EVENT Type; + internal QUIC_STREAM_EVENT_TYPE Type; internal StreamEventDataUnion Data; } + // TODO: rename to C#-like [StructLayout(LayoutKind.Sequential)] internal struct SOCKADDR_IN { @@ -408,6 +568,7 @@ internal byte[] Address } } + // TODO: rename to C#-like [StructLayout(LayoutKind.Sequential)] internal struct SOCKADDR_IN6 { @@ -445,7 +606,8 @@ internal byte[] Address } } - [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)] + // TODO: rename to C#-like + [StructLayout(LayoutKind.Explicit)] internal struct SOCKADDR_INET { [FieldOffset(0)] @@ -464,58 +626,53 @@ internal delegate uint StreamCallbackDelegate( [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamOpenDelegate( - IntPtr connection, - uint flags, + SafeMsQuicConnectionHandle connection, + QUIC_STREAM_OPEN_FLAGS flags, StreamCallbackDelegate handler, IntPtr context, - out IntPtr stream); + out SafeMsQuicStreamHandle stream); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamStartDelegate( - IntPtr stream, - uint flags); + SafeMsQuicStreamHandle stream, + QUIC_STREAM_START_FLAGS flags); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint StreamCloseDelegate( + internal delegate void StreamCloseDelegate( IntPtr stream); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamShutdownDelegate( - IntPtr stream, - uint flags, + SafeMsQuicStreamHandle stream, + QUIC_STREAM_SHUTDOWN_FLAGS flags, long errorCode); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamSendDelegate( - IntPtr stream, + SafeMsQuicStreamHandle stream, QuicBuffer* buffers, uint bufferCount, - uint flags, + QUIC_SEND_FLAGS flags, IntPtr clientSendContext); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamReceiveCompleteDelegate( - IntPtr stream, + SafeMsQuicStreamHandle stream, ulong bufferLength); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamReceiveSetEnabledDelegate( - IntPtr stream, + SafeMsQuicStreamHandle stream, [MarshalAs(UnmanagedType.U1)] bool enabled); [StructLayout(LayoutKind.Sequential)] - internal unsafe struct QuicBuffer + internal struct QuicBuffer { internal uint Length; internal byte* Buffer; } - [StructLayout(LayoutKind.Sequential)] - internal struct CertFileParams - { - internal IntPtr PrivateKeyFilePath; - internal IntPtr CertificateFilePath; - } + // TODO: DatagramSend missing } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicStatusCodes.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicStatusCodes.cs similarity index 100% rename from src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicStatusCodes.cs rename to src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicStatusCodes.cs diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicStatusHelper.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicStatusHelper.cs similarity index 100% rename from src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicStatusHelper.cs rename to src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicStatusHelper.cs diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs new file mode 100644 index 00000000000000..24a5b2fa5f9210 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs @@ -0,0 +1,138 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Net.Security; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal sealed class SafeMsQuicConfigurationHandle : SafeHandle + { + public override bool IsInvalid => handle == IntPtr.Zero; + + private SafeMsQuicConfigurationHandle() + : base(IntPtr.Zero, ownsHandle: true) + { } + + protected override bool ReleaseHandle() + { + MsQuicApi.Api.ConfigurationCloseDelegate(handle); + return true; + } + + // TODO: consider moving the static code from here to keep all the handle classes small and simple. + public static unsafe SafeMsQuicConfigurationHandle Create(QuicClientConnectionOptions options) + { + // TODO: lots of ClientAuthenticationOptions are not yet supported by MsQuic. + return Create(options, QUIC_CREDENTIAL_FLAGS.CLIENT, certificate: null, options.ClientAuthenticationOptions?.ApplicationProtocols); + } + + public static unsafe SafeMsQuicConfigurationHandle Create(QuicListenerOptions options) + { + // TODO: lots of ServerAuthenticationOptions are not yet supported by MsQuic. + return Create(options, QUIC_CREDENTIAL_FLAGS.NONE, options.ServerAuthenticationOptions?.ServerCertificate, options.ServerAuthenticationOptions?.ApplicationProtocols); + } + + // TODO: this is called from MsQuicListener and when it fails it wreaks havoc in MsQuicListener finalizer. + // Consider moving bigger logic like this outside of constructor call chains. + private static unsafe SafeMsQuicConfigurationHandle Create(QuicOptions options, QUIC_CREDENTIAL_FLAGS flags, X509Certificate? certificate, List? alpnProtocols) + { + // TODO: some of these checks should be done by the QuicOptions type. + if (alpnProtocols == null || alpnProtocols.Count == 0) + { + throw new Exception("At least one SslApplicationProtocol value must be present in SslClientAuthenticationOptions or SslServerAuthenticationOptions."); + } + + if (options.MaxBidirectionalStreams > ushort.MaxValue) + { + throw new Exception("MaxBidirectionalStreams overflow."); + } + + if (options.MaxBidirectionalStreams > ushort.MaxValue) + { + throw new Exception("MaxBidirectionalStreams overflow."); + } + + Debug.Assert(!MsQuicApi.Api.Registration.IsInvalid); + + var settings = new QuicSettings + { + IsSetFlags = QuicSettingsIsSetFlags.PeerBidiStreamCount | + QuicSettingsIsSetFlags.PeerUnidiStreamCount, + PeerBidiStreamCount = (ushort)options.MaxBidirectionalStreams, + PeerUnidiStreamCount = (ushort)options.MaxUnidirectionalStreams + }; + + if (options.IdleTimeout != Timeout.InfiniteTimeSpan) + { + if (options.IdleTimeout <= TimeSpan.Zero) throw new Exception("IdleTimeout must not be negative."); + + ulong ms = (ulong)options.IdleTimeout.Ticks / TimeSpan.TicksPerMillisecond; + if (ms > (1ul << 62) - 1) throw new Exception("IdleTimeout is too large (max 2^62-1 milliseconds)"); + + settings.IsSetFlags |= QuicSettingsIsSetFlags.IdleTimeoutMs; + settings.IdleTimeoutMs = (ulong)options.IdleTimeout.TotalMilliseconds; + } + + uint status; + SafeMsQuicConfigurationHandle? configurationHandle; + + MemoryHandle[]? handles = null; + QuicBuffer[]? buffers = null; + try + { + MsQuicAlpnHelper.Prepare(alpnProtocols, out handles, out buffers); + status = MsQuicApi.Api.ConfigurationOpenDelegate(MsQuicApi.Api.Registration, (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(buffers, 0), (uint)alpnProtocols.Count, ref settings, (uint)sizeof(QuicSettings), context: IntPtr.Zero, out configurationHandle); + } + finally + { + MsQuicAlpnHelper.Return(ref handles, ref buffers); + } + + QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationOpen failed."); + + try + { + // TODO: find out what to do for OpenSSL here -- passing handle won't work, because + // MsQuic has a private copy of OpenSSL so the SSL_CTX will be incompatible. + + CredentialConfig config = default; + + config.Flags = flags; // TODO: consider using LOAD_ASYNCHRONOUS with a callback. + + if (certificate != null) + { +#if true + // If using stub TLS. + config.Type = QUIC_CREDENTIAL_TYPE.STUB_NULL; +#else + // TODO: doesn't work on non-Windows + config.Type = QUIC_CREDENTIAL_TYPE.CONTEXT; + config.Certificate = certificate.Handle; +#endif + } + else + { + // TODO: not allowed for OpenSSL and server + config.Type = QUIC_CREDENTIAL_TYPE.NONE; + } + + status = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config); + QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationLoadCredential failed."); + } + catch + { + configurationHandle.Dispose(); + throw; + } + + return configurationHandle; + } + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConnectionHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConnectionHandle.cs new file mode 100644 index 00000000000000..38d908d3adfa9e --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConnectionHandle.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal sealed class SafeMsQuicConnectionHandle : SafeHandle + { + public override bool IsInvalid => handle == IntPtr.Zero; + + private SafeMsQuicConnectionHandle() + : base(IntPtr.Zero, ownsHandle: true) + { } + + public SafeMsQuicConnectionHandle(IntPtr connectionHandle) + : this() + { + SetHandle(connectionHandle); + } + + protected override bool ReleaseHandle() + { + MsQuicApi.Api.ConnectionCloseDelegate(handle); + return true; + } + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicListenerHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicListenerHandle.cs new file mode 100644 index 00000000000000..8a22ab84875424 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicListenerHandle.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal sealed class SafeMsQuicListenerHandle : SafeHandle + { + public override bool IsInvalid => handle == IntPtr.Zero; + + private SafeMsQuicListenerHandle() + : base(IntPtr.Zero, ownsHandle: true) + { } + + protected override bool ReleaseHandle() + { + MsQuicApi.Api.ListenerCloseDelegate(handle); + return true; + } + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicRegistrationHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicRegistrationHandle.cs new file mode 100644 index 00000000000000..47a0599c43f0a3 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicRegistrationHandle.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal sealed class SafeMsQuicRegistrationHandle : SafeHandle + { + public override bool IsInvalid => handle == IntPtr.Zero; + + private SafeMsQuicRegistrationHandle() + : base(IntPtr.Zero, ownsHandle: true) + { } + + protected override bool ReleaseHandle() + { + MsQuicApi.Api.RegistrationCloseDelegate(handle); + return true; + } + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicStreamHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicStreamHandle.cs new file mode 100644 index 00000000000000..c9e775b885e8ec --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicStreamHandle.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Net.Quic.Implementations.MsQuic.Internal +{ + internal sealed class SafeMsQuicStreamHandle : SafeHandle + { + public override bool IsInvalid => handle == IntPtr.Zero; + + private SafeMsQuicStreamHandle() + : base(IntPtr.Zero, ownsHandle: true) + { } + + public SafeMsQuicStreamHandle(IntPtr streamHandle) + : this() + { + SetHandle(streamHandle); + } + + protected override bool ReleaseHandle() + { + MsQuicApi.Api.StreamCloseDelegate(handle); + return true; + } + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs index 9a2063f89f087a..87897c19511594 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -2,13 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.IO; using System.Net.Quic.Implementations.MsQuic.Internal; using System.Net.Security; using System.Net.Sockets; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -18,175 +16,167 @@ namespace System.Net.Quic.Implementations.MsQuic { internal sealed class MsQuicConnection : QuicConnectionProvider { - private readonly MsQuicSession? _session; - - // Pointer to the underlying connection - // TODO replace all IntPtr with SafeHandles - private IntPtr _ptr; + // Delegate that wraps the static function that will be called when receiving an event. + private static readonly ConnectionCallbackDelegate s_connectionDelegate = new ConnectionCallbackDelegate(NativeCallbackHandler); - // Handle to this object for native callbacks. - private GCHandle _handle; + // TODO: remove this. + // This is only used for client-initiated connections, and isn't needed even then once Connect() has been called. + private readonly SafeMsQuicConfigurationHandle? _configuration; - // Delegate that wraps the static function that will be called when receiving an event. - internal static readonly ConnectionCallbackDelegate s_connectionDelegate = new ConnectionCallbackDelegate(NativeCallbackHandler); + private readonly State _state = new State(); + private GCHandle _stateHandle; + private bool _disposed; - // Endpoint to either connect to or the endpoint already accepted. private IPEndPoint? _localEndPoint; private readonly EndPoint _remoteEndPoint; - private SslApplicationProtocol _negotiatedAlpnProtocol; - // TODO: only allocate these when there is an outstanding connect/shutdown. - private readonly TaskCompletionSource _connectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - private readonly TaskCompletionSource _shutdownTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private sealed class State + { + public SafeMsQuicConnectionHandle Handle = null!; // set inside of MsQuicConnection ctor. - private bool _disposed; - private bool _connected; - private MsQuicSecurityConfig? _securityConfig; - private long _abortErrorCode = -1; + // These exists to prevent GC of the MsQuicConnection in the middle of an async op (Connect or Shutdown). + public MsQuicConnection? Connection; - // Queue for accepted streams - private readonly Channel _acceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions() - { - SingleReader = true, - SingleWriter = true, - }); + // TODO: only allocate these when there is an outstanding connect/shutdown. + public readonly TaskCompletionSource ConnectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + public readonly TaskCompletionSource ShutdownTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + public bool Connected; + public long AbortErrorCode = -1; + + // Queue for accepted streams. + // Backlog limit is managed by MsQuic so it can be unbounded here. + public readonly Channel AcceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions() + { + SingleReader = true, + SingleWriter = true, + }); + } // constructor for inbound connections - public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, IntPtr nativeObjPtr, TimeSpan idleTimeout) + public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SafeMsQuicConnectionHandle handle) { + _state.Handle = handle; + _state.Connected = true; _localEndPoint = localEndPoint; _remoteEndPoint = remoteEndPoint; - _ptr = nativeObjPtr; - _connected = true; - SetCallbackHandler(); + _stateHandle = GCHandle.Alloc(_state); - SetIdleTimeout(idleTimeout); + try + { + MsQuicApi.Api.SetCallbackHandlerDelegate( + _state.Handle, + s_connectionDelegate, + GCHandle.ToIntPtr(_stateHandle)); + } + catch + { + _stateHandle.Free(); + throw; + } } // constructor for outbound connections public MsQuicConnection(QuicClientConnectionOptions options) { - // TODO need to figure out if/how we want to expose sessions - // Creating a session per connection isn't ideal. - _session = new MsQuicSession(); - _ptr = _session.ConnectionOpen(options); _remoteEndPoint = options.RemoteEndPoint!; + _configuration = SafeMsQuicConfigurationHandle.Create(options); - SetCallbackHandler(); - SetIdleTimeout(options.IdleTimeout); - } + _stateHandle = GCHandle.Alloc(_state); + try + { + // this handle is ref counted by MsQuic, so safe to dispose here. + using SafeMsQuicConfigurationHandle config = SafeMsQuicConfigurationHandle.Create(options); - internal override IPEndPoint LocalEndPoint - { - get + uint status = MsQuicApi.Api.ConnectionOpenDelegate( + MsQuicApi.Api.Registration, + s_connectionDelegate, + GCHandle.ToIntPtr(_stateHandle), + out _state.Handle); + + QuicExceptionHelpers.ThrowIfFailed(status, "Could not open the connection."); + } + catch { - return new IPEndPoint(_localEndPoint!.Address, _localEndPoint.Port); + _stateHandle.Free(); + throw; } } - internal async ValueTask SetSecurityConfigForConnection(X509Certificate cert, string? certFilePath, string? privateKeyFilePath) - { - _securityConfig = await MsQuicApi.Api.CreateSecurityConfig(cert, certFilePath, privateKeyFilePath).ConfigureAwait(false); - // TODO this isn't being set correctly - MsQuicParameterHelpers.SetSecurityConfig(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.SEC_CONFIG, _securityConfig!.NativeObjPtr); - } + internal override IPEndPoint? LocalEndPoint => _localEndPoint; internal override EndPoint RemoteEndPoint => _remoteEndPoint; internal override SslApplicationProtocol NegotiatedApplicationProtocol => _negotiatedAlpnProtocol; - internal override bool Connected => _connected; + internal override bool Connected => _state.Connected; - internal uint HandleEvent(ref ConnectionEvent connectionEvent) + private static uint HandleEventConnected(State state, ref ConnectionEvent connectionEvent) { - try + if (!state.Connected) { - switch (connectionEvent.Type) - { - case QUIC_CONNECTION_EVENT.CONNECTED: - return HandleEventConnected(ref connectionEvent); - case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_TRANSPORT: - return HandleEventShutdownInitiatedByTransport(ref connectionEvent); - case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_PEER: - return HandleEventShutdownInitiatedByPeer(ref connectionEvent); - case QUIC_CONNECTION_EVENT.SHUTDOWN_COMPLETE: - return HandleEventShutdownComplete(ref connectionEvent); - case QUIC_CONNECTION_EVENT.PEER_STREAM_STARTED: - return HandleEventNewStream(ref connectionEvent); - case QUIC_CONNECTION_EVENT.STREAMS_AVAILABLE: - return HandleEventStreamsAvailable(ref connectionEvent); - default: - return MsQuicStatusCodes.Success; - } - } - catch (Exception ex) - { - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Error(this, $"Exception occurred during connection callback: {ex.Message}"); - } + // Connected will already be true for connections accepted from a listener. - // TODO: trigger an exception on any outstanding async calls. + SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, state.Handle, QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS); - return MsQuicStatusCodes.InternalError; - } - } - - private uint HandleEventConnected(ref ConnectionEvent connectionEvent) - { - if (!_connected) - { - // _connected will already be true for connections accepted from a listener. - - SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS); - _localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref inetAddress); + Debug.Assert(state.Connection != null); + state.Connection._localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref inetAddress); + state.Connection.SetNegotiatedAlpn(connectionEvent.Data.Connected.NegotiatedAlpn, connectionEvent.Data.Connected.NegotiatedAlpnLength); + state.Connection = null; - SetNegotiatedAlpn(connectionEvent.Data.Connected.NegotiatedAlpn, connectionEvent.Data.Connected.NegotiatedAlpnLength); - - _connected = true; - _connectTcs.SetResult(MsQuicStatusCodes.Success); + state.Connected = true; + state.ConnectTcs.SetResult(MsQuicStatusCodes.Success); } return MsQuicStatusCodes.Success; } - private uint HandleEventShutdownInitiatedByTransport(ref ConnectionEvent connectionEvent) + private static uint HandleEventShutdownInitiatedByTransport(State state, ref ConnectionEvent connectionEvent) { - if (!_connected) + if (!state.Connected) { + Debug.Assert(state.Connection != null); + state.Connection = null; + uint hresult = connectionEvent.Data.ShutdownInitiatedByTransport.Status; Exception ex = QuicExceptionHelpers.CreateExceptionForHResult(hresult, "Connection has been shutdown by transport."); - _connectTcs.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(ex)); + state.ConnectTcs.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(ex)); } + state.AcceptQueue.Writer.Complete(); return MsQuicStatusCodes.Success; } - private uint HandleEventShutdownInitiatedByPeer(ref ConnectionEvent connectionEvent) + private static uint HandleEventShutdownInitiatedByPeer(State state, ref ConnectionEvent connectionEvent) { - _abortErrorCode = connectionEvent.Data.ShutdownInitiatedByPeer.ErrorCode; + state.AbortErrorCode = (long)connectionEvent.Data.ShutdownInitiatedByPeer.ErrorCode; + state.AcceptQueue.Writer.Complete(); return MsQuicStatusCodes.Success; } - private uint HandleEventShutdownComplete(ref ConnectionEvent connectionEvent) + private static uint HandleEventShutdownComplete(State state, ref ConnectionEvent connectionEvent) { - _shutdownTcs.SetResult(MsQuicStatusCodes.Success); + state.Connection = null; + + state.ShutdownTcs.SetResult(MsQuicStatusCodes.Success); // Stop accepting new streams. - _acceptQueue?.Writer.Complete(); + state.AcceptQueue.Writer.Complete(); return MsQuicStatusCodes.Success; } - private uint HandleEventNewStream(ref ConnectionEvent connectionEvent) + private static uint HandleEventNewStream(State state, ref ConnectionEvent connectionEvent) { - MsQuicStream msQuicStream = new MsQuicStream(this, connectionEvent.StreamFlags, connectionEvent.Data.StreamStarted.Stream, inbound: true); - _acceptQueue.Writer.TryWrite(msQuicStream); + var streamHandle = new SafeMsQuicStreamHandle(connectionEvent.Data.PeerStreamStarted.Stream); + var stream = new MsQuicStream(streamHandle, connectionEvent.Data.PeerStreamStarted.Flags); + + state.AcceptQueue.Writer.TryWrite(stream); return MsQuicStatusCodes.Success; } - private uint HandleEventStreamsAvailable(ref ConnectionEvent connectionEvent) + private static uint HandleEventStreamsAvailable(State state, ref ConnectionEvent connectionEvent) { return MsQuicStatusCodes.Success; } @@ -199,11 +189,11 @@ internal override async ValueTask AcceptStreamAsync(Cancella try { - stream = await _acceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); + stream = await _state.AcceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); } catch (ChannelClosedException) { - throw _abortErrorCode switch + throw _state.AbortErrorCode switch { -1 => new QuicOperationAbortedException(), // Shutdown initiated by us. long err => new QuicConnectionAbortedException(err) // Shutdown initiated by peer. @@ -216,36 +206,34 @@ internal override async ValueTask AcceptStreamAsync(Cancella internal override QuicStreamProvider OpenUnidirectionalStream() { ThrowIfDisposed(); - - return StreamOpen(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL); + return new MsQuicStream(_state.Handle, QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL); } internal override QuicStreamProvider OpenBidirectionalStream() { ThrowIfDisposed(); - - return StreamOpen(QUIC_STREAM_OPEN_FLAG.NONE); + return new MsQuicStream(_state.Handle, QUIC_STREAM_OPEN_FLAGS.NONE); } internal override long GetRemoteAvailableUnidirectionalStreamCount() { - return MsQuicParameterHelpers.GetUShortParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.PEER_UNIDI_STREAM_COUNT); + return MsQuicParameterHelpers.GetUShortParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_UNIDI_STREAM_COUNT); } internal override long GetRemoteAvailableBidirectionalStreamCount() { - return MsQuicParameterHelpers.GetUShortParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.PEER_BIDI_STREAM_COUNT); - } - - private void SetIdleTimeout(TimeSpan timeout) - { - MsQuicParameterHelpers.SetULongParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.IDLE_TIMEOUT, (ulong)timeout.TotalMilliseconds); + return MsQuicParameterHelpers.GetUShortParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_BIDI_STREAM_COUNT); } internal override ValueTask ConnectAsync(CancellationToken cancellationToken = default) { ThrowIfDisposed(); + if (_configuration is null) + { + throw new Exception($"{nameof(ConnectAsync)} must not be called on a connection obtained from a listener."); + } + (string address, int port) = _remoteEndPoint switch { DnsEndPoint dnsEp => (dnsEp.Host, dnsEp.Port), @@ -253,64 +241,59 @@ internal override ValueTask ConnectAsync(CancellationToken cancellationToken = d _ => throw new Exception($"Unsupported remote endpoint type '{_remoteEndPoint.GetType()}'.") }; - // values taken from https://github.com/microsoft/msquic/blob/main/docs/api/ConnectionStart.md - int af = _remoteEndPoint.AddressFamily switch + QUIC_ADDRESS_FAMILY af = _remoteEndPoint.AddressFamily switch { - AddressFamily.Unspecified => 0, - AddressFamily.InterNetwork => 2, - AddressFamily.InterNetworkV6 => 23, + AddressFamily.Unspecified => QUIC_ADDRESS_FAMILY.UNSPEC, + AddressFamily.InterNetwork => QUIC_ADDRESS_FAMILY.INET, + AddressFamily.InterNetworkV6 => QUIC_ADDRESS_FAMILY.INET6, _ => throw new Exception(SR.Format(SR.net_quic_unsupported_address_family, _remoteEndPoint.AddressFamily)) }; - QuicExceptionHelpers.ThrowIfFailed( - MsQuicApi.Api.ConnectionStartDelegate( - _ptr, - (ushort)af, - address, - (ushort)port), - "Failed to connect to peer."); - - return new ValueTask(_connectTcs.Task); - } - - private MsQuicStream StreamOpen( - QUIC_STREAM_OPEN_FLAG flags) - { - IntPtr streamPtr = IntPtr.Zero; - QuicExceptionHelpers.ThrowIfFailed( - MsQuicApi.Api.StreamOpenDelegate( - _ptr, - (uint)flags, - MsQuicStream.s_streamDelegate, - IntPtr.Zero, - out streamPtr), - "Failed to open stream to peer."); - - return new MsQuicStream(this, flags, streamPtr, inbound: false); - } - - private void SetCallbackHandler() - { - Debug.Assert(!_handle.IsAllocated, "callback handler allocated already"); - _handle = GCHandle.Alloc(this); + _state.Connection = this; + try + { + uint status = MsQuicApi.Api.ConnectionStartDelegate( + _state.Handle, + _configuration, + af, + address, + (ushort)port); + + QuicExceptionHelpers.ThrowIfFailed(status, "Failed to connect to peer."); + } + catch + { + _state.Connection = null; + throw; + } - MsQuicApi.Api.SetCallbackHandlerDelegate( - _ptr, - s_connectionDelegate, - GCHandle.ToIntPtr(_handle)); + return new ValueTask(_state.ConnectTcs.Task); } private ValueTask ShutdownAsync( - QUIC_CONNECTION_SHUTDOWN_FLAG Flags, + QUIC_CONNECTION_SHUTDOWN_FLAGS Flags, long ErrorCode) { - uint status = MsQuicApi.Api.ConnectionShutdownDelegate( - _ptr, - (uint)Flags, - ErrorCode); - QuicExceptionHelpers.ThrowIfFailed(status, "Failed to shutdown connection."); + Debug.Assert(!_state.ShutdownTcs.Task.IsCompleted); + + // Store the connection into the GCHandle'd state to prevent GC if user calls ShutdownAsync and gets rid of all references to the MsQuicConnection. + Debug.Assert(_state.Connection == null); + _state.Connection = this; + + try + { + MsQuicApi.Api.ConnectionShutdownDelegate( + _state.Handle, + Flags, + ErrorCode); + } + catch + { + _state.Connection = null; + throw; + } - return new ValueTask(_shutdownTcs.Task); + return new ValueTask(_state.ShutdownTcs.Task); } internal void SetNegotiatedAlpn(IntPtr alpn, int alpnLength) @@ -326,11 +309,41 @@ internal void SetNegotiatedAlpn(IntPtr alpn, int alpnLength) private static uint NativeCallbackHandler( IntPtr connection, IntPtr context, - ref ConnectionEvent connectionEventStruct) + ref ConnectionEvent connectionEvent) { - GCHandle handle = GCHandle.FromIntPtr(context); - MsQuicConnection quicConnection = (MsQuicConnection)handle.Target!; - return quicConnection.HandleEvent(ref connectionEventStruct); + var state = (State)GCHandle.FromIntPtr(context).Target!; + + try + { + switch ((QUIC_CONNECTION_EVENT_TYPE)connectionEvent.Type) + { + case QUIC_CONNECTION_EVENT_TYPE.CONNECTED: + return HandleEventConnected(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT_TYPE.SHUTDOWN_INITIATED_BY_TRANSPORT: + return HandleEventShutdownInitiatedByTransport(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT_TYPE.SHUTDOWN_INITIATED_BY_PEER: + return HandleEventShutdownInitiatedByPeer(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT_TYPE.SHUTDOWN_COMPLETE: + return HandleEventShutdownComplete(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT_TYPE.PEER_STREAM_STARTED: + return HandleEventNewStream(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT_TYPE.STREAMS_AVAILABLE: + return HandleEventStreamsAvailable(state, ref connectionEvent); + default: + return MsQuicStatusCodes.Success; + } + } + catch (Exception ex) + { + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Error(state, $"Exception occurred during connection callback: {ex.Message}"); + } + + // TODO: trigger an exception on any outstanding async calls. + + return MsQuicStatusCodes.InternalError; + } } public override void Dispose() @@ -351,20 +364,8 @@ private void Dispose(bool disposing) return; } - if (_ptr != IntPtr.Zero) - { - MsQuicApi.Api.ConnectionCloseDelegate?.Invoke(_ptr); - } - - _ptr = IntPtr.Zero; - - if (disposing) - { - _handle.Free(); - _session?.Dispose(); - _securityConfig?.Dispose(); - } - + _state?.Handle?.Dispose(); + if (_stateHandle.IsAllocated) _stateHandle.Free(); _disposed = true; } @@ -374,7 +375,7 @@ internal override ValueTask CloseAsync(long errorCode, CancellationToken cancell { ThrowIfDisposed(); - return ShutdownAsync(QUIC_CONNECTION_SHUTDOWN_FLAG.NONE, errorCode); + return ShutdownAsync(QUIC_CONNECTION_SHUTDOWN_FLAGS.NONE, errorCode); } private void ThrowIfDisposed() diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs index 169ce5435507a8..0b55939205d1c8 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs @@ -1,7 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; +using System.Buffers; +using System.Collections.Generic; using System.Net.Quic.Implementations.MsQuic.Internal; using System.Net.Security; using System.Runtime.InteropServices; @@ -14,42 +15,58 @@ namespace System.Net.Quic.Implementations.MsQuic { internal sealed class MsQuicListener : QuicListenerProvider, IDisposable { - // Security configuration for MsQuic - private readonly MsQuicSession _session; + private static readonly ListenerCallbackDelegate s_listenerDelegate = new ListenerCallbackDelegate(NativeCallbackHandler); - // Pointer to the underlying listener - // TODO replace all IntPtr with SafeHandles - private IntPtr _ptr; + private readonly State _state; + private GCHandle _stateHandle; + private volatile bool _disposed; - // Handle to this object for native callbacks. - private GCHandle _handle; + private IPEndPoint _listenEndPoint; + private readonly List _applicationProtocols; - // Delegate that wraps the static function that will be called when receiving an event. - internal static readonly ListenerCallbackDelegate s_listenerDelegate = new ListenerCallbackDelegate(NativeCallbackHandler); + private sealed class State + { + // set immediately in ctor, but we need a GCHandle to State in order to create the handle. + public SafeMsQuicListenerHandle Handle = null!; - // Ssl listening options (ALPN, cert, etc) - private readonly SslServerAuthenticationOptions _sslOptions; + public readonly SafeMsQuicConfigurationHandle ConnectionConfiguration; + public readonly Channel AcceptConnectionQueue; - private QuicListenerOptions _options; - private volatile bool _disposed; - private IPEndPoint _listenEndPoint; - private bool _started; - private readonly Channel _acceptConnectionQueue; + public State(QuicListenerOptions options) + { + ConnectionConfiguration = SafeMsQuicConfigurationHandle.Create(options); + + AcceptConnectionQueue = Channel.CreateBounded(new BoundedChannelOptions(options.ListenBacklog) + { + SingleReader = true, + SingleWriter = true + }); + } + } internal MsQuicListener(QuicListenerOptions options) { - _session = new MsQuicSession(); - _acceptConnectionQueue = Channel.CreateBounded(new BoundedChannelOptions(options.ListenBacklog) - { - SingleReader = true, - SingleWriter = true - }); - - _options = options; - _sslOptions = options.ServerAuthenticationOptions!; + _applicationProtocols = options.ServerAuthenticationOptions!.ApplicationProtocols!; _listenEndPoint = options.ListenEndPoint!; - _ptr = _session.ListenerOpen(options); + _state = new State(options); + _stateHandle = GCHandle.Alloc(_state); + try + { + uint status = MsQuicApi.Api.ListenerOpenDelegate( + MsQuicApi.Api.Registration, + s_listenerDelegate, + GCHandle.ToIntPtr(_stateHandle), + out _state.Handle); + + QuicExceptionHelpers.ThrowIfFailed(status, "ListenerOpen failed."); + } + catch + { + _state.Handle?.Dispose(); + _stateHandle.Free(); + throw; + } } internal override IPEndPoint ListenEndPoint @@ -64,22 +81,14 @@ internal override async ValueTask AcceptConnectionAsync( { ThrowIfDisposed(); - MsQuicConnection connection; - try { - connection = await _acceptConnectionQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); + return await _state.AcceptConnectionQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); } catch (ChannelClosedException) { throw new QuicOperationAbortedException(); } - - await connection.SetSecurityConfigForConnection(_sslOptions.ServerCertificate!, - _options.CertificateFilePath, - _options.PrivateKeyFilePath).ConfigureAwait(false); - - return connection; } public override void Dispose() @@ -101,120 +110,98 @@ private void Dispose(bool disposing) } StopAcceptingConnections(); - - if (_ptr != IntPtr.Zero) - { - MsQuicApi.Api.ListenerStopDelegate(_ptr); - MsQuicApi.Api.ListenerCloseDelegate(_ptr); - } - - _ptr = IntPtr.Zero; - - // TODO this call to session dispose hangs. - //_session.Dispose(); + _state?.Handle?.Dispose(); + if (_stateHandle.IsAllocated) _stateHandle.Free(); + _state?.ConnectionConfiguration?.Dispose(); _disposed = true; } - internal override void Start() + internal override unsafe void Start() { ThrowIfDisposed(); - // protect against double starts. - if (_started) - { - throw new QuicException("Cannot start Listener multiple times"); - } + SOCKADDR_INET address = MsQuicAddressHelpers.IPEndPointToINet(_listenEndPoint); - _started = true; - SetCallbackHandler(); + uint status; - SOCKADDR_INET address = MsQuicAddressHelpers.IPEndPointToINet(_listenEndPoint); + MemoryHandle[]? handles = null; + QuicBuffer[]? buffers = null; + try + { + MsQuicAlpnHelper.Prepare(_applicationProtocols, out handles, out buffers); + status = MsQuicApi.Api.ListenerStartDelegate(_state.Handle, (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(buffers, 0), (uint)_applicationProtocols.Count, ref address); + } + finally + { + MsQuicAlpnHelper.Return(ref handles, ref buffers); + } - QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ListenerStartDelegate( - _ptr, - ref address), - "Failed to start listener."); + QuicExceptionHelpers.ThrowIfFailed(status, "ListenerStart failed."); - SetListenPort(); + SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LEVEL.LISTENER, (uint)QUIC_PARAM_LISTENER.LOCAL_ADDRESS); + _listenEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref inetAddress); } internal override void Close() { ThrowIfDisposed(); - - MsQuicApi.Api.ListenerStopDelegate(_ptr); + MsQuicApi.Api.ListenerStopDelegate(_state.Handle); } - private void SetListenPort() + private void StopAcceptingConnections() { - SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.LISTENER, (uint)QUIC_PARAM_LISTENER.LOCAL_ADDRESS); - - _listenEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref inetAddress); + // TODO finalizers are called even if the object construction fails. + if (_state != null) + { + _state.AcceptConnectionQueue.Writer.TryComplete(); + } } - internal unsafe uint ListenerCallbackHandler(ref ListenerEvent evt) + private static unsafe uint NativeCallbackHandler( + IntPtr listener, + IntPtr context, + ref ListenerEvent evt) { - try + if (evt.Type != QUIC_LISTENER_EVENT.NEW_CONNECTION) { - switch (evt.Type) - { - case QUIC_LISTENER_EVENT.NEW_CONNECTION: - { - ref NewConnectionInfo connectionInfo = ref *(NewConnectionInfo*)evt.Data.NewConnection.Info; - - IPEndPoint localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref *(SOCKADDR_INET*)connectionInfo.LocalAddress); - IPEndPoint remoteEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref *(SOCKADDR_INET*)connectionInfo.RemoteAddress); - - MsQuicConnection msQuicConnection = new MsQuicConnection(localEndPoint, remoteEndPoint, evt.Data.NewConnection.Connection, _options.IdleTimeout); - msQuicConnection.SetNegotiatedAlpn(connectionInfo.NegotiatedAlpn, connectionInfo.NegotiatedAlpnLength); - - _acceptConnectionQueue.Writer.TryWrite(msQuicConnection); - } - // Always pend the new connection to wait for the security config to be resolved - // TODO this doesn't need to be async always - return MsQuicStatusCodes.Pending; - default: - return MsQuicStatusCodes.InternalError; - } + return MsQuicStatusCodes.InternalError; } - catch (Exception ex) - { - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Error(this, $"Exception occurred during connection callback: {ex.Message}"); - } - // TODO: trigger an exception on any outstanding async calls. + State state = (State)GCHandle.FromIntPtr(context).Target!; + SafeMsQuicConnectionHandle? connectionHandle = null; - return MsQuicStatusCodes.InternalError; - } - } + try + { + ref NewConnectionInfo connectionInfo = ref *evt.Data.NewConnection.Info; - private void StopAcceptingConnections() - { - _acceptConnectionQueue.Writer.TryComplete(); - } + IPEndPoint localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref *(SOCKADDR_INET*)connectionInfo.LocalAddress); + IPEndPoint remoteEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref *(SOCKADDR_INET*)connectionInfo.RemoteAddress); - private static uint NativeCallbackHandler( - IntPtr listener, - IntPtr context, - ref ListenerEvent connectionEventStruct) - { - GCHandle handle = GCHandle.FromIntPtr(context); - MsQuicListener quicListener = (MsQuicListener)handle.Target!; + connectionHandle = new SafeMsQuicConnectionHandle(evt.Data.NewConnection.Connection); - return quicListener.ListenerCallbackHandler(ref connectionEventStruct); - } + uint status = MsQuicApi.Api.ConnectionSetConfigurationDelegate(connectionHandle, state.ConnectionConfiguration); + QuicExceptionHelpers.ThrowIfFailed(status, "ConnectionSetConfiguration failed."); - internal void SetCallbackHandler() - { - Debug.Assert(!_handle.IsAllocated, "listener allocated"); - _handle = GCHandle.Alloc(this); + var msQuicConnection = new MsQuicConnection(localEndPoint, remoteEndPoint, connectionHandle); + msQuicConnection.SetNegotiatedAlpn(connectionInfo.NegotiatedAlpn, connectionInfo.NegotiatedAlpnLength); - MsQuicApi.Api.SetCallbackHandlerDelegate( - _ptr, - s_listenerDelegate, - GCHandle.ToIntPtr(_handle)); + if (!state.AcceptConnectionQueue.Writer.TryWrite(msQuicConnection)) + { + // This handle will be cleaned up by MsQuic. + connectionHandle.SetHandleAsInvalid(); + msQuicConnection.Dispose(); + return MsQuicStatusCodes.InternalError; + } + + return MsQuicStatusCodes.Success; + } + catch (Exception ex) + { + // This handle will be cleaned up by MsQuic by returning InternalError. + connectionHandle?.SetHandleAsInvalid(); + state.AcceptConnectionQueue.Writer.TryComplete(ex); + return MsQuicStatusCodes.InternalError; + } } private void ThrowIfDisposed() diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs index d98110d15be4e8..45476d1a08de4c 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -14,46 +14,18 @@ namespace System.Net.Quic.Implementations.MsQuic { internal sealed class MsQuicStream : QuicStreamProvider { - // Pointer to the underlying stream - // TODO replace all IntPtr with SafeHandles - private readonly IntPtr _ptr; - - // Handle to this object for native callbacks. - private GCHandle _handle; - // Delegate that wraps the static function that will be called when receiving an event. internal static readonly StreamCallbackDelegate s_streamDelegate = new StreamCallbackDelegate(NativeCallbackHandler); + private readonly State _state = new State(); + private GCHandle _stateHandle; + // Backing for StreamId private long _streamId = -1; - // Resettable completions to be used for multiple calls to send, start, and shutdown. - private readonly ResettableCompletionSource _sendResettableCompletionSource; - - // Resettable completions to be used for multiple calls to receive. - private readonly ResettableCompletionSource _receiveResettableCompletionSource; - - // Set once writes have been shutdown. - private readonly TaskCompletionSource _shutdownWriteCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - // Buffers to hold during a call to send. - private MemoryHandle[] _bufferArrays = new MemoryHandle[1]; - private QuicBuffer[] _sendQuicBuffers = new QuicBuffer[1]; - - // Handle to hold when sending. - private GCHandle _sendHandle; - // Used to check if StartAsync has been called. private bool _started; - private ReadState _readState; - private long _readErrorCode = -1; - - private ShutdownWriteState _shutdownWriteState; - - private SendState _sendState; - private long _sendErrorCode = -1; - // Used by the class to indicate that the stream is m_Readable. private readonly bool _canRead; @@ -62,35 +34,87 @@ internal sealed class MsQuicStream : QuicStreamProvider private volatile bool _disposed; - private readonly List _receiveQuicBuffers = new List(); + private sealed class State + { + public SafeMsQuicStreamHandle Handle = null!; // set in ctor. + + public ReadState ReadState; + public long ReadErrorCode = -1; + public readonly List ReceiveQuicBuffers = new List(); + + // Resettable completions to be used for multiple calls to receive. + public readonly ResettableCompletionSource ReceiveResettableCompletionSource = new ResettableCompletionSource(); + + public SendState SendState; + public long SendErrorCode = -1; - // TODO consider using Interlocked.Exchange instead of a sync if we can avoid it. - private readonly object _sync = new object(); + // Buffers to hold during a call to send. + public MemoryHandle[] BufferArrays = new MemoryHandle[1]; + public QuicBuffer[] SendQuicBuffers = new QuicBuffer[1]; - // Creates a new MsQuicStream - internal MsQuicStream(MsQuicConnection connection, QUIC_STREAM_OPEN_FLAG flags, IntPtr nativeObjPtr, bool inbound) + // Handle to pinned SendQuicBuffers. + public GCHandle SendHandle; + + // Resettable completions to be used for multiple calls to send, start, and shutdown. + public readonly ResettableCompletionSource SendResettableCompletionSource = new ResettableCompletionSource(); + + public ShutdownWriteState ShutdownWriteState; + + // Set once writes have been shutdown. + public readonly TaskCompletionSource ShutdownWriteCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + // inbound. + internal MsQuicStream(SafeMsQuicStreamHandle streamHandle, QUIC_STREAM_OPEN_FLAGS flags) { - Debug.Assert(connection != null, "Connection null"); + _state.Handle = streamHandle; + _canRead = true; + _canWrite = !flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL); + _started = true; - _ptr = nativeObjPtr; + _stateHandle = GCHandle.Alloc(_state); + try + { + MsQuicApi.Api.SetCallbackHandlerDelegate( + _state.Handle, + s_streamDelegate, + GCHandle.ToIntPtr(_stateHandle)); + } + catch + { + _stateHandle.Free(); + throw; + } + } - _sendResettableCompletionSource = new ResettableCompletionSource(); - _receiveResettableCompletionSource = new ResettableCompletionSource(); - SetCallbackHandler(); + // outbound. + internal MsQuicStream(SafeMsQuicConnectionHandle connection, QUIC_STREAM_OPEN_FLAGS flags) + { + Debug.Assert(connection != null); - bool isBidirectional = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL); + _canRead = !flags.HasFlag(QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL); + _canWrite = true; - if (inbound) + _stateHandle = GCHandle.Alloc(_state); + try { - _canRead = true; - _canWrite = isBidirectional; - _started = true; + uint status = MsQuicApi.Api.StreamOpenDelegate( + connection, + flags, + s_streamDelegate, + GCHandle.ToIntPtr(_stateHandle), + out _state.Handle); + + QuicExceptionHelpers.ThrowIfFailed(status, "Failed to open stream to peer."); + + status = MsQuicApi.Api.StreamStartDelegate(_state.Handle, QUIC_STREAM_START_FLAGS.ASYNC); + QuicExceptionHelpers.ThrowIfFailed(status, "Could not start stream."); } - else + catch { - _canRead = isBidirectional; - _canWrite = true; - StartLocalStream(); + _state.Handle?.Dispose(); + _stateHandle.Free(); + throw; } } @@ -129,7 +153,7 @@ internal override async ValueTask WriteAsync(ReadOnlySequence buffers, boo using CancellationTokenRegistration registration = await HandleWriteStartState(cancellationToken).ConfigureAwait(false); - await SendReadOnlySequenceAsync(buffers, endStream ? QUIC_SEND_FLAG.FIN : QUIC_SEND_FLAG.NONE).ConfigureAwait(false); + await SendReadOnlySequenceAsync(buffers, endStream ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE).ConfigureAwait(false); HandleWriteCompletedState(); } @@ -144,8 +168,7 @@ internal override async ValueTask WriteAsync(ReadOnlyMemory ThrowIfDisposed(); using CancellationTokenRegistration registration = await HandleWriteStartState(cancellationToken).ConfigureAwait(false); - - await SendReadOnlyMemoryListAsync(buffers, endStream ? QUIC_SEND_FLAG.FIN : QUIC_SEND_FLAG.NONE).ConfigureAwait(false); + await SendReadOnlyMemoryListAsync(buffers, endStream ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE).ConfigureAwait(false); HandleWriteCompletedState(); } @@ -156,7 +179,7 @@ internal override async ValueTask WriteAsync(ReadOnlyMemory buffer, bool e using CancellationTokenRegistration registration = await HandleWriteStartState(cancellationToken).ConfigureAwait(false); - await SendReadOnlyMemoryAsync(buffer, endStream ? QUIC_SEND_FLAG.FIN : QUIC_SEND_FLAG.NONE).ConfigureAwait(false); + await SendReadOnlyMemoryAsync(buffer, endStream ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE).ConfigureAwait(false); HandleWriteCompletedState(); } @@ -168,9 +191,9 @@ private async ValueTask HandleWriteStartState(Can throw new InvalidOperationException(SR.net_quic_writing_notallowed); } - lock (_sync) + lock (_state) { - if (_sendState == SendState.Aborted) + if (_state.SendState == SendState.Aborted) { throw new OperationCanceledException(SR.net_quic_sending_aborted); } @@ -179,25 +202,25 @@ private async ValueTask HandleWriteStartState(Can CancellationTokenRegistration registration = cancellationToken.Register(() => { bool shouldComplete = false; - lock (_sync) + lock (_state) { - if (_sendState == SendState.None) + if (_state.SendState == SendState.None) { - _sendState = SendState.Aborted; + _state.SendState = SendState.Aborted; shouldComplete = true; } } if (shouldComplete) { - _sendResettableCompletionSource.CompleteException(new OperationCanceledException("Write was canceled", cancellationToken)); + _state.SendResettableCompletionSource.CompleteException(new OperationCanceledException("Write was canceled", cancellationToken)); } }); // Make sure start has completed if (!_started) { - await _sendResettableCompletionSource.GetTypelessValueTask().ConfigureAwait(false); + await _state.SendResettableCompletionSource.GetTypelessValueTask().ConfigureAwait(false); _started = true; } @@ -206,11 +229,11 @@ private async ValueTask HandleWriteStartState(Can private void HandleWriteCompletedState() { - lock (_sync) + lock (_state) { - if (_sendState == SendState.Finished) + if (_state.SendState == SendState.Finished) { - _sendState = SendState.None; + _state.SendState = SendState.None; } } } @@ -229,15 +252,15 @@ internal override async ValueTask ReadAsync(Memory destination, Cance NetEventSource.Info(this, $"[{GetHashCode()}] reading into Memory of '{destination.Length}' bytes."); } - lock (_sync) + lock (_state) { - if (_readState == ReadState.ReadsCompleted) + if (_state.ReadState == ReadState.ReadsCompleted) { return 0; } - else if (_readState == ReadState.Aborted) + else if (_state.ReadState == ReadState.Aborted) { - throw _readErrorCode switch + throw _state.ReadErrorCode switch { -1 => new QuicOperationAbortedException(), long err => new QuicStreamAbortedException(err) @@ -248,26 +271,26 @@ internal override async ValueTask ReadAsync(Memory destination, Cance using CancellationTokenRegistration registration = cancellationToken.Register(() => { bool shouldComplete = false; - lock (_sync) + lock (_state) { - if (_readState == ReadState.None) + if (_state.ReadState == ReadState.None) { shouldComplete = true; } - _readState = ReadState.Aborted; + _state.ReadState = ReadState.Aborted; } if (shouldComplete) { - _receiveResettableCompletionSource.CompleteException(new OperationCanceledException("Read was canceled", cancellationToken)); + _state.ReceiveResettableCompletionSource.CompleteException(new OperationCanceledException("Read was canceled", cancellationToken)); } }); // TODO there could potentially be a perf gain by storing the buffer from the inital read // This reduces the amount of async calls, however it makes it so MsQuic holds onto the buffers // longer than it needs to. We will need to benchmark this. - int length = (int)await _receiveResettableCompletionSource.GetValueTask().ConfigureAwait(false); + int length = (int)await _state.ReceiveResettableCompletionSource.GetValueTask().ConfigureAwait(false); int actual = Math.Min(length, destination.Length); @@ -288,16 +311,16 @@ static unsafe void CopyToBuffer(Span destinationBuffer, List s } } - CopyToBuffer(destination.Span, _receiveQuicBuffers); + CopyToBuffer(destination.Span, _state.ReceiveQuicBuffers); - lock (_sync) + lock (_state) { - if (_readState == ReadState.IndividualReadComplete) + if (_state.ReadState == ReadState.IndividualReadComplete) { - _receiveQuicBuffers.Clear(); + _state.ReceiveQuicBuffers.Clear(); ReceiveComplete(actual); EnableReceive(); - _readState = ReadState.None; + _state.ReadState = ReadState.None; } } @@ -310,13 +333,12 @@ internal override void AbortRead(long errorCode) { ThrowIfDisposed(); - lock (_sync) + lock (_state) { - _readState = ReadState.Aborted; + _state.ReadState = ReadState.Aborted; } - MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT_RECV, errorCode); - + StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.ABORT_RECEIVE, errorCode); } internal override void AbortWrite(long errorCode) @@ -325,24 +347,30 @@ internal override void AbortWrite(long errorCode) bool shouldComplete = false; - lock (_sync) + lock (_state) { - if (_shutdownWriteState == ShutdownWriteState.None) + if (_state.ShutdownWriteState == ShutdownWriteState.None) { - _shutdownWriteState = ShutdownWriteState.Canceled; + _state.ShutdownWriteState = ShutdownWriteState.Canceled; shouldComplete = true; } } if (shouldComplete) { - _shutdownWriteCompletionSource.SetException(new QuicStreamAbortedException("Shutdown was aborted.", errorCode)); + _state.ShutdownWriteCompletionSource.SetException(new QuicStreamAbortedException("Shutdown was aborted.", errorCode)); } - MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT_SEND, errorCode); + StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.ABORT_SEND, errorCode); } - internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default) + private void StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS flags, long errorCode) + { + uint status = MsQuicApi.Api.StreamShutdownDelegate(_state.Handle, flags, errorCode); + QuicExceptionHelpers.ThrowIfFailed(status, "StreamShutdown failed."); + } + + internal override async ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default) { ThrowIfDisposed(); @@ -350,29 +378,28 @@ internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellatio using CancellationTokenRegistration registration = cancellationToken.Register(() => { bool shouldComplete = false; - lock (_sync) + lock (_state) { - if (_shutdownWriteState == ShutdownWriteState.None) + if (_state.ShutdownWriteState == ShutdownWriteState.None) { - _shutdownWriteState = ShutdownWriteState.Canceled; + _state.ShutdownWriteState = ShutdownWriteState.Canceled; shouldComplete = true; } } if (shouldComplete) { - _shutdownWriteCompletionSource.SetException(new OperationCanceledException("Shutdown was canceled", cancellationToken)); + _state.ShutdownWriteCompletionSource.SetException(new OperationCanceledException("Shutdown was canceled", cancellationToken)); } }); - return new ValueTask(_shutdownWriteCompletionSource.Task); + await _state.ShutdownWriteCompletionSource.Task.ConfigureAwait(false); } internal override void Shutdown() { ThrowIfDisposed(); - - MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0); + StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.GRACEFUL, errorCode: 0); } // TODO consider removing sync-over-async with blocking calls. @@ -407,24 +434,9 @@ internal override Task FlushAsync(CancellationToken cancellationToken = default) public override ValueTask DisposeAsync() { - if (_disposed) - { - return default; - } - - if (_ptr != IntPtr.Zero) - { - // TODO resolve graceful vs abortive dispose here. Will file a separate issue. - //MsQuicApi.Api._streamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 1); - MsQuicApi.Api.StreamCloseDelegate?.Invoke(_ptr); - } - - _handle.Free(); - - CleanupSendState(); - - _disposed = true; + // TODO: perform a graceful shutdown and wait for completion? + Dispose(true); return default; } @@ -446,23 +458,16 @@ private void Dispose(bool disposing) return; } - if (_ptr != IntPtr.Zero) - { - // TODO resolve graceful vs abortive dispose here. Will file a separate issue. - //MsQuicApi.Api._streamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 1); - MsQuicApi.Api.StreamCloseDelegate?.Invoke(_ptr); - } - - _handle.Free(); - - CleanupSendState(); + _state.Handle.Dispose(); + if (_stateHandle.IsAllocated) _stateHandle.Free(); + CleanupSendState(_state); _disposed = true; } private void EnableReceive() { - MsQuicApi.Api.StreamReceiveSetEnabledDelegate(_ptr, enabled: true); + MsQuicApi.Api.StreamReceiveSetEnabledDelegate(_state.Handle, enabled: true); } private static uint NativeCallbackHandler( @@ -470,141 +475,108 @@ private static uint NativeCallbackHandler( IntPtr context, ref StreamEvent streamEvent) { - var handle = GCHandle.FromIntPtr(context); - var quicStream = (MsQuicStream)handle.Target!; - - return quicStream.HandleEvent(ref streamEvent); + var state = (State)GCHandle.FromIntPtr(context).Target!; + return HandleEvent(state, ref streamEvent); } - private uint HandleEvent(ref StreamEvent evt) + private static uint HandleEvent(State state, ref StreamEvent evt) { - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Info(this, $"[{GetHashCode()}] handling event '{evt.Type}'."); - } - - uint status = MsQuicStatusCodes.Success; - try { - switch (evt.Type) + switch ((QUIC_STREAM_EVENT_TYPE)evt.Type) { // Stream has started. // Will only be done for outbound streams (inbound streams have already started) - case QUIC_STREAM_EVENT.START_COMPLETE: - status = HandleStartComplete(); - break; + case QUIC_STREAM_EVENT_TYPE.START_COMPLETE: + return HandleStartComplete(state); // Received data on the stream - case QUIC_STREAM_EVENT.RECEIVE: - { - status = HandleEventRecv(ref evt); - } - break; + case QUIC_STREAM_EVENT_TYPE.RECEIVE: + return HandleEventRecv(state, ref evt); // Send has completed. // Contains a canceled bool to indicate if the send was canceled. - case QUIC_STREAM_EVENT.SEND_COMPLETE: - { - status = HandleEventSendComplete(ref evt); - } - break; + case QUIC_STREAM_EVENT_TYPE.SEND_COMPLETE: + return HandleEventSendComplete(state, ref evt); // Peer has told us to shutdown the reading side of the stream. - case QUIC_STREAM_EVENT.PEER_SEND_SHUTDOWN: - { - status = HandleEventPeerSendShutdown(); - } - break; + case QUIC_STREAM_EVENT_TYPE.PEER_SEND_SHUTDOWN: + return HandleEventPeerSendShutdown(state); // Peer has told us to abort the reading side of the stream. - case QUIC_STREAM_EVENT.PEER_SEND_ABORTED: - { - status = HandleEventPeerSendAborted(ref evt); - } - break; + case QUIC_STREAM_EVENT_TYPE.PEER_SEND_ABORTED: + return HandleEventPeerSendAborted(state, ref evt); // Peer has stopped receiving data, don't send anymore. - case QUIC_STREAM_EVENT.PEER_RECEIVE_ABORTED: - { - status = HandleEventPeerRecvAborted(ref evt); - } - break; + case QUIC_STREAM_EVENT_TYPE.PEER_RECEIVE_ABORTED: + return HandleEventPeerRecvAborted(state, ref evt); // Occurs when shutdown is completed for the send side. // This only happens for shutdown on sending, not receiving // Receive shutdown can only be abortive. - case QUIC_STREAM_EVENT.SEND_SHUTDOWN_COMPLETE: - { - status = HandleEventSendShutdownComplete(ref evt); - } - break; + case QUIC_STREAM_EVENT_TYPE.SEND_SHUTDOWN_COMPLETE: + return HandleEventSendShutdownComplete(state, ref evt); // Shutdown for both sending and receiving is completed. - case QUIC_STREAM_EVENT.SHUTDOWN_COMPLETE: - { - status = HandleEventShutdownComplete(); - } - break; + case QUIC_STREAM_EVENT_TYPE.SHUTDOWN_COMPLETE: + return HandleEventShutdownComplete(state); default: - break; + return MsQuicStatusCodes.Success; } } catch (Exception) { return MsQuicStatusCodes.InternalError; } - - return status; } - private unsafe uint HandleEventRecv(ref MsQuicNativeMethods.StreamEvent evt) + private static unsafe uint HandleEventRecv(State state, ref StreamEvent evt) { - StreamEventDataRecv receieveEvent = evt.Data.Recv; - for (int i = 0; i < receieveEvent.BufferCount; i++) + StreamEventDataReceive receiveEvent = evt.Data.Receive; + for (int i = 0; i < receiveEvent.BufferCount; i++) { - _receiveQuicBuffers.Add(receieveEvent.Buffers[i]); + state.ReceiveQuicBuffers.Add(receiveEvent.Buffers[i]); } bool shouldComplete = false; - lock (_sync) + lock (state) { - if (_readState == ReadState.None) + if (state.ReadState == ReadState.None) { shouldComplete = true; } - _readState = ReadState.IndividualReadComplete; + state.ReadState = ReadState.IndividualReadComplete; } if (shouldComplete) { - _receiveResettableCompletionSource.Complete((uint)receieveEvent.TotalBufferLength); + state.ReceiveResettableCompletionSource.Complete((uint)receiveEvent.TotalBufferLength); } return MsQuicStatusCodes.Pending; } - private uint HandleEventPeerRecvAborted(ref StreamEvent evt) + private static uint HandleEventPeerRecvAborted(State state, ref StreamEvent evt) { bool shouldComplete = false; - lock (_sync) + lock (state) { - if (_sendState == SendState.None) + if (state.SendState == SendState.None) { shouldComplete = true; } - _sendState = SendState.Aborted; - _sendErrorCode = evt.Data.PeerSendAbort.ErrorCode; + state.SendState = SendState.Aborted; + state.SendErrorCode = (long)evt.Data.PeerSendAborted.ErrorCode; } if (shouldComplete) { - _sendResettableCompletionSource.CompleteException(new QuicStreamAbortedException(_sendErrorCode)); + state.SendResettableCompletionSource.CompleteException(new QuicStreamAbortedException(state.SendErrorCode)); } return MsQuicStatusCodes.Success; } - private uint HandleStartComplete() + private static uint HandleStartComplete(State state) { bool shouldComplete = false; - lock (_sync) + lock (state) { // Check send state before completing as send cancellation is shared between start and send. - if (_sendState == SendState.None) + if (state.SendState == SendState.None) { shouldComplete = true; } @@ -612,217 +584,207 @@ private uint HandleStartComplete() if (shouldComplete) { - _sendResettableCompletionSource.Complete(MsQuicStatusCodes.Success); + state.SendResettableCompletionSource.Complete(MsQuicStatusCodes.Success); } return MsQuicStatusCodes.Success; } - private uint HandleEventSendShutdownComplete(ref MsQuicNativeMethods.StreamEvent evt) + private static uint HandleEventSendShutdownComplete(State state, ref StreamEvent evt) { bool shouldComplete = false; - lock (_sync) + lock (state) { - if (_shutdownWriteState == ShutdownWriteState.None) + if (state.ShutdownWriteState == ShutdownWriteState.None) { - _shutdownWriteState = ShutdownWriteState.Finished; + state.ShutdownWriteState = ShutdownWriteState.Finished; shouldComplete = true; } } if (shouldComplete) { - _shutdownWriteCompletionSource.TrySetResult(); + state.ShutdownWriteCompletionSource.TrySetResult(); } return MsQuicStatusCodes.Success; } - private uint HandleEventShutdownComplete() + private static uint HandleEventShutdownComplete(State state) { bool shouldReadComplete = false; bool shouldShutdownWriteComplete = false; - lock (_sync) + lock (state) { // This event won't occur within the middle of a receive. if (NetEventSource.Log.IsEnabled()) NetEventSource.Info("Completing resettable event source."); - if (_readState == ReadState.None) + if (state.ReadState == ReadState.None) { shouldReadComplete = true; } - _readState = ReadState.ReadsCompleted; + state.ReadState = ReadState.ReadsCompleted; - if (_shutdownWriteState == ShutdownWriteState.None) + if (state.ShutdownWriteState == ShutdownWriteState.None) { - _shutdownWriteState = ShutdownWriteState.Finished; + state.ShutdownWriteState = ShutdownWriteState.Finished; shouldShutdownWriteComplete = true; } } if (shouldReadComplete) { - _receiveResettableCompletionSource.Complete(0); + state.ReceiveResettableCompletionSource.Complete(0); } if (shouldShutdownWriteComplete) { - _shutdownWriteCompletionSource.TrySetResult(); + state.ShutdownWriteCompletionSource.TrySetResult(); } return MsQuicStatusCodes.Success; } - private uint HandleEventPeerSendAborted(ref StreamEvent evt) + private static uint HandleEventPeerSendAborted(State state, ref StreamEvent evt) { bool shouldComplete = false; - lock (_sync) + lock (state) { - if (_readState == ReadState.None) + if (state.ReadState == ReadState.None) { shouldComplete = true; } - _readState = ReadState.Aborted; - _readErrorCode = evt.Data.PeerSendAbort.ErrorCode; + state.ReadState = ReadState.Aborted; + state.ReadErrorCode = (long)evt.Data.PeerSendAborted.ErrorCode; } if (shouldComplete) { - _receiveResettableCompletionSource.CompleteException(new QuicStreamAbortedException(_readErrorCode)); + state.ReceiveResettableCompletionSource.CompleteException(new QuicStreamAbortedException(state.ReadErrorCode)); } return MsQuicStatusCodes.Success; } - private uint HandleEventPeerSendShutdown() + private static uint HandleEventPeerSendShutdown(State state) { bool shouldComplete = false; - lock (_sync) + lock (state) { // This event won't occur within the middle of a receive. if (NetEventSource.Log.IsEnabled()) NetEventSource.Info("Completing resettable event source."); - if (_readState == ReadState.None) + if (state.ReadState == ReadState.None) { shouldComplete = true; } - _readState = ReadState.ReadsCompleted; + state.ReadState = ReadState.ReadsCompleted; } if (shouldComplete) { - _receiveResettableCompletionSource.Complete(0); + state.ReceiveResettableCompletionSource.Complete(0); } return MsQuicStatusCodes.Success; } - private uint HandleEventSendComplete(ref StreamEvent evt) + private static uint HandleEventSendComplete(State state, ref StreamEvent evt) { - CleanupSendState(); + bool complete = false; - // TODO throw if a write was canceled. - - bool shouldComplete = false; - lock (_sync) + lock (state) { - if (_sendState == SendState.None) + if (state.SendState == SendState.None) { - _sendState = SendState.Finished; - shouldComplete = true; + state.SendState = SendState.Finished; + complete = true; } } - if (shouldComplete) + if (complete) { - _sendResettableCompletionSource.Complete(MsQuicStatusCodes.Success); + CleanupSendState(state); + + // TODO throw if a write was canceled. + state.SendResettableCompletionSource.Complete(MsQuicStatusCodes.Success); } return MsQuicStatusCodes.Success; } - private void CleanupSendState() + private static void CleanupSendState(State state) { - if (_sendHandle.IsAllocated) + if (state.SendHandle.IsAllocated) { - _sendHandle.Free(); + state.SendHandle.Free(); } // Callings dispose twice on a memory handle should be okay - foreach (MemoryHandle buffer in _bufferArrays) + foreach (MemoryHandle buffer in state.BufferArrays) { buffer.Dispose(); } } - private void SetCallbackHandler() - { - _handle = GCHandle.Alloc(this); - - MsQuicApi.Api.SetCallbackHandlerDelegate( - _ptr, - s_streamDelegate, - GCHandle.ToIntPtr(_handle)); - } - // TODO prevent overlapping sends or consider supporting it. private unsafe ValueTask SendReadOnlyMemoryAsync( ReadOnlyMemory buffer, - QUIC_SEND_FLAG flags) + QUIC_SEND_FLAGS flags) { if (buffer.IsEmpty) { - if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.FIN) + if ((flags & QUIC_SEND_FLAGS.FIN) == QUIC_SEND_FLAGS.FIN) { // Start graceful shutdown sequence if passed in the fin flag and there is an empty buffer. - MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0); + StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.GRACEFUL, errorCode: 0); } return default; } MemoryHandle handle = buffer.Pin(); - _sendQuicBuffers[0].Length = (uint)buffer.Length; - _sendQuicBuffers[0].Buffer = (byte*)handle.Pointer; + _state.SendQuicBuffers[0].Length = (uint)buffer.Length; + _state.SendQuicBuffers[0].Buffer = (byte*)handle.Pointer; - _bufferArrays[0] = handle; + _state.BufferArrays[0] = handle; - _sendHandle = GCHandle.Alloc(_sendQuicBuffers, GCHandleType.Pinned); + _state.SendHandle = GCHandle.Alloc(_state.SendQuicBuffers, GCHandleType.Pinned); - var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_sendQuicBuffers, 0); + var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_state.SendQuicBuffers, 0); uint status = MsQuicApi.Api.StreamSendDelegate( - _ptr, + _state.Handle, quicBufferPointer, bufferCount: 1, - (uint)flags, - _ptr); + flags, + IntPtr.Zero); if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) { - CleanupSendState(); + CleanupSendState(_state); // TODO this may need to be an aborted exception. QuicExceptionHelpers.ThrowIfFailed(status, "Could not send data to peer."); } - return _sendResettableCompletionSource.GetTypelessValueTask(); + return _state.SendResettableCompletionSource.GetTypelessValueTask(); } private unsafe ValueTask SendReadOnlySequenceAsync( ReadOnlySequence buffers, - QUIC_SEND_FLAG flags) + QUIC_SEND_FLAGS flags) { if (buffers.IsEmpty) { - if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.FIN) + if ((flags & QUIC_SEND_FLAGS.FIN) == QUIC_SEND_FLAGS.FIN) { // Start graceful shutdown sequence if passed in the fin flag and there is an empty buffer. - MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0); + StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.GRACEFUL, errorCode: 0); } return default; } @@ -834,10 +796,10 @@ private unsafe ValueTask SendReadOnlySequenceAsync( ++count; } - if (_sendQuicBuffers.Length < count) + if (_state.SendQuicBuffers.Length < count) { - _sendQuicBuffers = new QuicBuffer[count]; - _bufferArrays = new MemoryHandle[count]; + _state.SendQuicBuffers = new QuicBuffer[count]; + _state.BufferArrays = new MemoryHandle[count]; } count = 0; @@ -845,45 +807,45 @@ private unsafe ValueTask SendReadOnlySequenceAsync( foreach (ReadOnlyMemory buffer in buffers) { MemoryHandle handle = buffer.Pin(); - _sendQuicBuffers[count].Length = (uint)buffer.Length; - _sendQuicBuffers[count].Buffer = (byte*)handle.Pointer; - _bufferArrays[count] = handle; + _state.SendQuicBuffers[count].Length = (uint)buffer.Length; + _state.SendQuicBuffers[count].Buffer = (byte*)handle.Pointer; + _state.BufferArrays[count] = handle; ++count; } - _sendHandle = GCHandle.Alloc(_sendQuicBuffers, GCHandleType.Pinned); + _state.SendHandle = GCHandle.Alloc(_state.SendQuicBuffers, GCHandleType.Pinned); - var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_sendQuicBuffers, 0); + var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_state.SendQuicBuffers, 0); uint status = MsQuicApi.Api.StreamSendDelegate( - _ptr, + _state.Handle, quicBufferPointer, count, - (uint)flags, - _ptr); + flags, + IntPtr.Zero); if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) { - CleanupSendState(); + CleanupSendState(_state); // TODO this may need to be an aborted exception. QuicExceptionHelpers.ThrowIfFailed(status, "Could not send data to peer."); } - return _sendResettableCompletionSource.GetTypelessValueTask(); + return _state.SendResettableCompletionSource.GetTypelessValueTask(); } private unsafe ValueTask SendReadOnlyMemoryListAsync( ReadOnlyMemory> buffers, - QUIC_SEND_FLAG flags) + QUIC_SEND_FLAGS flags) { if (buffers.IsEmpty) { - if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.FIN) + if ((flags & QUIC_SEND_FLAGS.FIN) == QUIC_SEND_FLAGS.FIN) { // Start graceful shutdown sequence if passed in the fin flag and there is an empty buffer. - MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0); + StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.GRACEFUL, errorCode: 0); } return default; } @@ -892,67 +854,54 @@ private unsafe ValueTask SendReadOnlyMemoryListAsync( uint length = (uint)array.Length; - if (_sendQuicBuffers.Length < length) + if (_state.SendQuicBuffers.Length < length) { - _sendQuicBuffers = new QuicBuffer[length]; - _bufferArrays = new MemoryHandle[length]; + _state.SendQuicBuffers = new QuicBuffer[length]; + _state.BufferArrays = new MemoryHandle[length]; } for (int i = 0; i < length; i++) { ReadOnlyMemory buffer = array[i]; MemoryHandle handle = buffer.Pin(); - _sendQuicBuffers[i].Length = (uint)buffer.Length; - _sendQuicBuffers[i].Buffer = (byte*)handle.Pointer; - _bufferArrays[i] = handle; + _state.SendQuicBuffers[i].Length = (uint)buffer.Length; + _state.SendQuicBuffers[i].Buffer = (byte*)handle.Pointer; + _state.BufferArrays[i] = handle; } - _sendHandle = GCHandle.Alloc(_sendQuicBuffers, GCHandleType.Pinned); + _state.SendHandle = GCHandle.Alloc(_state.SendQuicBuffers, GCHandleType.Pinned); - var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_sendQuicBuffers, 0); + var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_state.SendQuicBuffers, 0); uint status = MsQuicApi.Api.StreamSendDelegate( - _ptr, + _state.Handle, quicBufferPointer, length, - (uint)flags, - _ptr); + flags, + IntPtr.Zero); if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) { - CleanupSendState(); + CleanupSendState(_state); // TODO this may need to be an aborted exception. QuicExceptionHelpers.ThrowIfFailed(status, "Could not send data to peer."); } - return _sendResettableCompletionSource.GetTypelessValueTask(); - } - - /// - /// Assigns a stream ID and begins process the stream. - /// - private void StartLocalStream() - { - Debug.Assert(!_started, "start local stream"); - uint status = MsQuicApi.Api.StreamStartDelegate( - _ptr, - (uint)QUIC_STREAM_START_FLAG.ASYNC); - - QuicExceptionHelpers.ThrowIfFailed(status, "Could not start stream."); + return _state.SendResettableCompletionSource.GetTypelessValueTask(); } private void ReceiveComplete(int bufferLength) { - uint status = MsQuicApi.Api.StreamReceiveCompleteDelegate(_ptr, (ulong)bufferLength); + uint status = MsQuicApi.Api.StreamReceiveCompleteDelegate(_state.Handle, (ulong)bufferLength); QuicExceptionHelpers.ThrowIfFailed(status, "Could not complete receive call."); } // This can fail if the stream isn't started. private long GetStreamId() { - return (long)MsQuicParameterHelpers.GetULongParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.STREAM, (uint)QUIC_PARAM_STREAM.ID); + return (long)MsQuicParameterHelpers.GetULongParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LEVEL.STREAM, (uint)QUIC_PARAM_STREAM.ID); } private void ThrowIfDisposed() @@ -971,7 +920,7 @@ private enum ReadState None, /// - /// Data is available in . + /// Data is available in . /// IndividualReadComplete, diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicConnectionProvider.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicConnectionProvider.cs index 7febd47be1ee17..94258334135896 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicConnectionProvider.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicConnectionProvider.cs @@ -10,7 +10,7 @@ internal abstract class QuicConnectionProvider : IDisposable { internal abstract bool Connected { get; } - internal abstract IPEndPoint LocalEndPoint { get; } + internal abstract IPEndPoint? LocalEndPoint { get; } internal abstract EndPoint RemoteEndPoint { get; } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicEnums.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicEnums.cs deleted file mode 100644 index 5fc281c8670d47..00000000000000 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicEnums.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Net.Quic.Implementations.MsQuic.Internal -{ - internal enum QUIC_EXECUTION_PROFILE : uint - { - QUIC_EXECUTION_PROFILE_LOW_LATENCY, // Default - QUIC_EXECUTION_PROFILE_TYPE_MAX_THROUGHPUT, - QUIC_EXECUTION_PROFILE_TYPE_SCAVENGER, - QUIC_EXECUTION_PROFILE_TYPE_REAL_TIME - } - - /// - /// Flags to pass when creating a security config. - /// - [Flags] - internal enum QUIC_SEC_CONFIG_FLAG : uint - { - CERT_HASH = 0x00000001, - CERT_HASH_STORE = 0x00000002, - CERT_CONTEXT = 0x00000004, - CERT_FILE = 0x00000008, - ENABL_OCSP = 0x00000010, - CERT_NULL = 0xF0000000 // TODO: only valid for stub TLS. - } - - [Flags] - internal enum QUIC_CONNECTION_SHUTDOWN_FLAG : uint - { - NONE = 0x0, - SILENT = 0x1 - } - - [Flags] - internal enum QUIC_STREAM_OPEN_FLAG : uint - { - NONE = 0, - UNIDIRECTIONAL = 0x1, - ZERO_RTT = 0x2, - } - - [Flags] - internal enum QUIC_STREAM_START_FLAG : uint - { - NONE = 0, - FAIL_BLOCKED = 0x1, - IMMEDIATE = 0x2, - ASYNC = 0x4, - } - - [Flags] - internal enum QUIC_STREAM_SHUTDOWN_FLAG : uint - { - NONE = 0, - GRACEFUL = 0x1, - ABORT_SEND = 0x2, - ABORT_RECV = 0x4, - ABORT = ABORT_SEND | ABORT_RECV, - IMMEDIATE = 0x8 - } - - [Flags] - internal enum QUIC_RECEIVE_FLAG : uint - { - NONE = 0, - ZERO_RTT = 0x1, - FIN = 0x02 - } - - [Flags] - internal enum QUIC_SEND_FLAG : uint - { - NONE = 0, - ALLOW_0_RTT = 0x00000001, - FIN = 0x00000002, - DGRAM_PRIORITY = 0x00000004 - } - - internal enum QUIC_PARAM_LEVEL : uint - { - GLOBAL, - REGISTRATION, - SESSION, - LISTENER, - CONNECTION, - TLS, - STREAM - } - - internal enum QUIC_PARAM_GLOBAL : uint - { - RETRY_MEMORY_PERCENT = 0, - SUPPORTED_VERSIONS = 1, - LOAD_BALANCING_MODE = 2, - } - - internal enum QUIC_PARAM_REGISTRATION : uint - { - CID_PREFIX = 0 - } - - internal enum QUIC_PARAM_SESSION : uint - { - TLS_TICKET_KEY = 0, - PEER_BIDI_STREAM_COUNT = 1, - PEER_UNIDI_STREAM_COUNT = 2, - IDLE_TIMEOUT = 3, - DISCONNECT_TIMEOUT = 4, - MAX_BYTES_PER_KEY = 5, - MIGRATION_ENABLED = 6, - DATAGRAM_RECEIVE_ENABLED = 7, - SERVER_RESUMPTION_LEVEL = 8 - } - - internal enum QUIC_PARAM_LISTENER : uint - { - LOCAL_ADDRESS = 0, - STATS = 1 - } - - internal enum QUIC_PARAM_CONN : uint - { - QUIC_VERSION = 0, - LOCAL_ADDRESS = 1, - REMOTE_ADDRESS = 2, - IDLE_TIMEOUT = 3, - PEER_BIDI_STREAM_COUNT = 4, - PEER_UNIDI_STREAM_COUNT = 5, - LOCAL_BIDI_STREAM_COUNT = 6, - LOCAL_UNIDI_STREAM_COUNT = 7, - CLOSE_REASON_PHRASE = 8, - STATISTICS = 9, - STATISTICS_PLAT = 10, - CERT_VALIDATION_FLAGS = 11, - KEEP_ALIVE = 12, - DISCONNECT_TIMEOUT = 13, - SEC_CONFIG = 14, - SEND_BUFFERING = 15, - SEND_PACING = 16, - SHARE_UDP_BINDING = 17, - IDEAL_PROCESSOR = 18, - MAX_STREAM_IDS = 19, - STREAM_SCHEDULING_SCHEME = 20, - DATAGRAM_RECEIVE_ENABLED = 21, - DATAGRAM_SEND_ENABLED = 22, - DISABLE_1RTT_ENCRYPTION = 23 - } - - internal enum QUIC_PARAM_STREAM : uint - { - ID = 0, - ZERORTT_LENGTH = 1, - IDEAL_SEND_BUFFER = 2 - } - - internal enum QUIC_LISTENER_EVENT : uint - { - NEW_CONNECTION = 0 - } - - internal enum QUIC_CONNECTION_EVENT : uint - { - CONNECTED = 0, - SHUTDOWN_INITIATED_BY_TRANSPORT = 1, - SHUTDOWN_INITIATED_BY_PEER = 2, - SHUTDOWN_COMPLETE = 3, - LOCAL_ADDRESS_CHANGED = 4, - PEER_ADDRESS_CHANGED = 5, - PEER_STREAM_STARTED = 6, - STREAMS_AVAILABLE = 7, - PEER_NEEDS_STREAMS = 8, - IDEAL_PROCESSOR_CHANGED = 9, - DATAGRAM_STATE_CHANGED = 10, - DATAGRAM_RECEIVED = 11, - DATAGRAM_SEND_STATE_CHANGED = 12, - RESUMED = 13, - RESUMPTION_TICKET_RECEIVED = 14 - } - - internal enum QUIC_STREAM_EVENT : uint - { - START_COMPLETE = 0, - RECEIVE = 1, - SEND_COMPLETE = 2, - PEER_SEND_SHUTDOWN = 3, - PEER_SEND_ABORTED = 4, - PEER_RECEIVE_ABORTED = 5, - SEND_SHUTDOWN_COMPLETE = 6, - SHUTDOWN_COMPLETE = 7, - IDEAL_SEND_BUFFER_SIZE = 8, - } -} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs index cc5691b1cc72b7..4b2ce8fcb84de9 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs @@ -8,7 +8,7 @@ namespace System.Net.Quic /// /// Options to provide to the when connecting to a Listener. /// - public class QuicClientConnectionOptions + public class QuicClientConnectionOptions : QuicOptions { /// /// Client authentication options to use when establishing a . @@ -25,25 +25,9 @@ public class QuicClientConnectionOptions /// public EndPoint? RemoteEndPoint { get; set; } - /// - /// Limit on the number of bidirectional streams the peer connection can create - /// on an accepted connection. - /// Default is 100. - /// - // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. - public long MaxBidirectionalStreams { get; set; } = 100; - - /// - /// Limit on the number of unidirectional streams the peer connection can create - /// on an accepted connection. - /// Default is 100. - /// - // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. - public long MaxUnidirectionalStreams { get; set; } = 100; - - /// - /// Idle timeout for connections, after which the connection will be closed. - /// - public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(2); + public QuicClientConnectionOptions() + { + IdleTimeout = TimeSpan.FromTicks(2 * TimeSpan.TicksPerMinute); + } } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs index fbd26f70002c43..ffe9196889a4c4 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs @@ -55,7 +55,7 @@ internal QuicConnection(QuicConnectionProvider provider) /// public bool Connected => _provider.Connected; - public IPEndPoint LocalEndPoint => _provider.LocalEndPoint; + public IPEndPoint? LocalEndPoint => _provider.LocalEndPoint; public EndPoint RemoteEndPoint => _provider.RemoteEndPoint; diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs index e465bc2b631a3b..991a6b5925b346 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs @@ -33,7 +33,7 @@ public QuicListener(QuicListenerOptions options) // !!! TEMPORARY: Remove or make internal before shipping public QuicListener(QuicImplementationProvider implementationProvider, IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions) - : this(implementationProvider, new QuicListenerOptions() { ListenEndPoint = listenEndPoint, ServerAuthenticationOptions = sslServerAuthenticationOptions }) + : this(implementationProvider, new QuicListenerOptions() { ListenEndPoint = listenEndPoint, ServerAuthenticationOptions = sslServerAuthenticationOptions }) { } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListenerOptions.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListenerOptions.cs index 6e861b39ca8156..b20a8b71285e21 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListenerOptions.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListenerOptions.cs @@ -8,23 +8,13 @@ namespace System.Net.Quic /// /// Options to provide to the . /// - public class QuicListenerOptions + public class QuicListenerOptions : QuicOptions { /// /// Server Ssl options to use for ALPN, SNI, etc. /// public SslServerAuthenticationOptions? ServerAuthenticationOptions { get; set; } - /// - /// Optional path to certificate file to configure the security configuration. - /// - public string? CertificateFilePath { get; set; } - - /// - /// Optional path to private key file to configure the security configuration. - /// - public string? PrivateKeyFilePath { get; set; } - /// /// The endpoint to listen on. /// @@ -35,24 +25,9 @@ public class QuicListenerOptions /// public int ListenBacklog { get; set; } = 512; - /// - /// Limit on the number of bidirectional streams an accepted connection can create - /// back to the client. - /// Default is 100. - /// - // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. - public long MaxBidirectionalStreams { get; set; } = 100; - - /// - /// Limit on the number of unidirectional streams the peer connection can create. - /// Default is 100. - /// - // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. - public long MaxUnidirectionalStreams { get; set; } = 100; - - /// - /// Idle timeout for connections, afterwhich the connection will be closed. - /// - public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(10); + public QuicListenerOptions() + { + IdleTimeout = TimeSpan.FromTicks(10 * TimeSpan.TicksPerMinute); + } } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicOptions.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicOptions.cs new file mode 100644 index 00000000000000..86dd644aaac1c7 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicOptions.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace System.Net.Quic +{ + /// + /// Options for QUIC + /// + public class QuicOptions + { + /// + /// Limit on the number of bidirectional streams the remote peer connection can create on an open connection. + /// Default is 100. + /// + // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. + public long MaxBidirectionalStreams { get; set; } = 100; + + /// + /// Limit on the number of unidirectional streams the remote peer connection can create on an open connection. + /// Default is 100. + /// + // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using. + public long MaxUnidirectionalStreams { get; set; } = 100; + + /// + /// Idle timeout for connections, after which the connection will be closed. + /// + public TimeSpan IdleTimeout { get; set; } + } +} diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs index 98e8438a7afda5..2178ec12c46fd2 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs @@ -7,13 +7,16 @@ namespace System.Net.Quic.Tests { + // TODO: why do we hawe 2 base clase with some duplicated methods? public class MsQuicTestBase { public SslServerAuthenticationOptions GetSslServerAuthenticationOptions() { return new SslServerAuthenticationOptions() { - ApplicationProtocols = new List() { new SslApplicationProtocol("quictest") } + ApplicationProtocols = new List() { new SslApplicationProtocol("quictest") }, + // TODO: use a cert. MsQuic currently only allows certs that are trusted. + ServerCertificate = System.Net.Test.Common.Configuration.Certificates.GetServerCertificate() }; } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index 8b86050c94a3ea..4f12d6c6830dc7 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -5,14 +5,13 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Net.Security; using System.Text; using System.Threading.Tasks; using Xunit; namespace System.Net.Quic.Tests { - [ConditionalClass(typeof(MsQuicTests), nameof(MsQuicTests.IsMsQuicSupported))] + [ConditionalClass(typeof(MsQuicTests), nameof(IsMsQuicSupported))] public class MsQuicTests : MsQuicTestBase { public static bool IsMsQuicSupported => QuicImplementationProviders.MsQuic.IsSupported; @@ -50,14 +49,14 @@ public async Task UnidirectionalAndBidirectionalChangeValues() ValueTask clientTask = clientConnection.ConnectAsync(); using QuicConnection serverConnection = await listener.AcceptConnectionAsync(); await clientTask; - Assert.Equal(20, clientConnection.GetRemoteAvailableUnidirectionalStreamCount()); - Assert.Equal(10, clientConnection.GetRemoteAvailableBidirectionalStreamCount()); - Assert.Equal(100, serverConnection.GetRemoteAvailableBidirectionalStreamCount()); - Assert.Equal(100, serverConnection.GetRemoteAvailableUnidirectionalStreamCount()); + Assert.Equal(100, clientConnection.GetRemoteAvailableBidirectionalStreamCount()); + Assert.Equal(100, clientConnection.GetRemoteAvailableUnidirectionalStreamCount()); + Assert.Equal(10, serverConnection.GetRemoteAvailableBidirectionalStreamCount()); + Assert.Equal(20, serverConnection.GetRemoteAvailableUnidirectionalStreamCount()); } [Fact] - [OuterLoop("May take serveral seconds")] + [OuterLoop("May take several seconds")] public async Task SetListenerTimeoutWorksWithSmallTimeout() { var quicOptions = new QuicListenerOptions(); diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs index 5eba6cc14089c7..94984496af6af8 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -83,6 +83,16 @@ public sealed class MsQuicQuicStreamConformanceTests : QuicStreamConformanceTest public abstract class QuicStreamConformanceTests : ConnectedStreamConformanceTests { + public SslServerAuthenticationOptions GetSslServerAuthenticationOptions() + { + return new SslServerAuthenticationOptions() + { + ApplicationProtocols = new List() { new SslApplicationProtocol("quictest") }, + // TODO: use a cert. MsQuic currently only allows certs that are trusted. + ServerCertificate = System.Net.Test.Common.Configuration.Certificates.GetServerCertificate() + }; + } + protected abstract QuicImplementationProvider Provider { get; } protected override async Task CreateConnectedStreamsAsync() @@ -93,7 +103,7 @@ protected override async Task CreateConnectedStreamsAsync() var listener = new QuicListener( provider, new IPEndPoint(IPAddress.Loopback, 0), - new SslServerAuthenticationOptions { ApplicationProtocols = new List { protocol } }); + GetSslServerAuthenticationOptions()); listener.Start(); QuicConnection connection1 = null, connection2 = null; diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs index 5cefeea94ceb1d..b849b8f919e89b 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs @@ -16,6 +16,7 @@ public abstract class QuicStreamTests : QuicTestBase { private static ReadOnlyMemory s_data = Encoding.UTF8.GetBytes("Hello world!"); + [ActiveIssue("https://github.com/dotnet/runtime/issues/49157")] [Fact] public async Task BasicTest() { @@ -470,7 +471,8 @@ await RunClientServer( totalBytesRead += bytesRead; } - Assert.True(receiveBuffer.AsSpan().SequenceEqual(testBuffer)); + Assert.Equal(testBuffer.Length, receiveBuffer.Length); + Assert.Equal(testBuffer, receiveBuffer); }); } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs index e5fc3779f3b9a8..6fbcc41bdc6d5f 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs @@ -22,7 +22,8 @@ public SslServerAuthenticationOptions GetSslServerAuthenticationOptions() { return new SslServerAuthenticationOptions() { - ApplicationProtocols = new List() { ApplicationProtocol } + ApplicationProtocols = new List() { ApplicationProtocol }, + ServerCertificate = System.Net.Test.Common.Configuration.Certificates.GetServerCertificate() }; } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj index fc7d6fb1986b16..504bc9d62f9f99 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj @@ -8,7 +8,8 @@ - + + @@ -18,4 +19,7 @@ + + +