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 b67ac671e6245d..a603394e04af02 100644 --- a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs +++ b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs @@ -6,129 +6,126 @@ 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 bool DatagramReceiveEnabled { get => throw null; set => throw null; } } - public sealed class QuicListener : IDisposable + public delegate void QuicDatagramReceivedEventHandler(object sender, ReadOnlySpan buffer); + 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 bool DatagramReceiveEnabled { get => throw null; set => throw null; } + public QuicConnectionAbortedException(string message, long errorCode) : base (default(string)) { } + public long ErrorCode { get { throw null; } } } - public delegate void QuicDatagramReceivedEventHandler(object sender, ReadOnlySpan buffer); - 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 bool DatagramReceiveEnabled { get => throw null; set => throw null; } - public bool DatagramSendEnabled { get => throw null; set => throw null; } - public ushort DatagramMaxSendLength { get => throw null; } - public event QuicDatagramReceivedEventHandler DatagramReceived { add => throw null; remove => throw null; } - public System.Threading.Tasks.ValueTask SendDatagramAsync(ReadOnlyMemory buffer, bool priority = false) => 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 bool DatagramReceiveEnabled { 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 bool DatagramReceiveEnabled { get => throw null; set => throw null; } } - 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..1e1e84e05bff4f 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 @@ - + 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 49d7e564a57d9c..69d1c522c8030f 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,30 +1,34 @@ // 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 class MsQuicApi : IDisposable + internal unsafe sealed class MsQuicApi { - private bool _disposed; - - private readonly IntPtr _registrationContext; - + private static MsQuicApi? _api; + + public SafeMsQuicRegistrationHandle Registration { get; } + + // This is workaround for a bug in ILTrimmer. + // Withough 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 unsafe MsQuicApi() { - MsQuicNativeMethods.NativeApi* registration; + MsQuicNativeMethods.NativeApi* vtable; + uint status; try { - uint status = Interop.MsQuic.MsQuicOpen(out registration); + status = Interop.MsQuic.MsQuicOpen(out vtable); if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) { throw new NotSupportedException(SR.net_quic_notsupported); @@ -35,144 +39,119 @@ private unsafe MsQuicApi() throw new NotSupportedException(SR.net_quic_notsupported); } - MsQuicNativeMethods.NativeApi nativeRegistration = *registration; + SetParamDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->SetParam); + + GetParamDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->GetParam); + + SetCallbackHandlerDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->SetCallbackHandler); RegistrationOpenDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.RegistrationOpen); + 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); + vtable->RegistrationClose); + + ConfigurationOpenDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->ConfigurationOpen); + ConfigurationCloseDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->ConfigurationClose); + ConfigurationLoadCredentialDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->ConfigurationLoadCredential); ListenerOpenDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ListenerOpen); + vtable->ListenerOpen); ListenerCloseDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ListenerClose); + vtable->ListenerClose); ListenerStartDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ListenerStart); + vtable->ListenerStart); ListenerStopDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ListenerStop); + vtable->ListenerStop); ConnectionOpenDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ConnectionOpen); + vtable->ConnectionOpen); ConnectionCloseDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ConnectionClose); + vtable->ConnectionClose); + ConnectionSetConfigurationDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->ConnectionSetConfiguration); ConnectionShutdownDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ConnectionShutdown); + vtable->ConnectionShutdown); ConnectionStartDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.ConnectionStart); - DatagramSendDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.DatagramSend); + vtable->ConnectionStart); StreamOpenDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamOpen); + vtable->StreamOpen); StreamCloseDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamClose); + vtable->StreamClose); StreamStartDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamStart); + vtable->StreamStart); StreamShutdownDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamShutdown); + vtable->StreamShutdown); StreamSendDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamSend); + vtable->StreamSend); StreamReceiveCompleteDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamReceiveComplete); + vtable->StreamReceiveComplete); StreamReceiveSetEnabledDelegate = Marshal.GetDelegateForFunctionPointer( - nativeRegistration.StreamReceiveSetEnabled); - SetContextDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SetContext); - GetContextDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.GetContext); - SetCallbackHandlerDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SetCallbackHandler); + vtable->StreamReceiveSetEnabled); - SetParamDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.SetParam); - GetParamDelegate = - Marshal.GetDelegateForFunctionPointer( - nativeRegistration.GetParam); + DatagramSendDelegate = + Marshal.GetDelegateForFunctionPointer( + vtable->DatagramSend); + + byte* appName = stackalloc byte[5]; + appName[0] = (byte)'.'; + appName[1] = (byte)'N'; + appName[2] = (byte)'E'; + appName[3] = (byte)'T'; + appName[4] = 0; - var registrationConfig = new MsQuicNativeMethods.RegistrationConfig + var cfg = new RegistrationConfig { - AppName = "SystemNetQuic", - ExecutionProfile = QUIC_EXECUTION_PROFILE.QUIC_EXECUTION_PROFILE_LOW_LATENCY + AppName = appName, + ExecutionProfile = (uint)QUIC_EXECUTION_PROFILE.QUIC_EXECUTION_PROFILE_LOW_LATENCY }; - RegistrationOpenDelegate(ref registrationConfig, out IntPtr ctx); - _registrationContext = ctx; - } - - internal static MsQuicApi Api { get; } = null!; + status = RegistrationOpenDelegate(ref cfg, out SafeMsQuicRegistrationHandle handle); + QuicExceptionHelpers.ThrowIfFailed(status, "RegistrationOpen failed."); - internal static bool IsQuicSupported { get; } - - static MsQuicApi() - { - // MsQuicOpen will succeed even if the platform will not support it. It will then fail with unspecified - // platform-specific errors in subsequent callbacks. For now, check for the minimum build we've tested it on. + Registration = handle; + } - // 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. + internal static MsQuicApi Api => _api ??= new MsQuicApi(); - // TODO: try to initialize TLS 1.3 in SslStream. - - try - { - Api = new MsQuicApi(); - IsQuicSupported = true; - } - catch (NotSupportedException) - { - IsQuicSupported = false; - } - } + internal static bool IsQuicSupported => true; 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.ConfigurationOpenDelegate ConfigurationOpenDelegate { get; } + internal MsQuicNativeMethods.ConfigurationCloseDelegate ConfigurationCloseDelegate { get; } + internal MsQuicNativeMethods.ConfigurationLoadCredentialDelegate ConfigurationLoadCredentialDelegate { get; } internal MsQuicNativeMethods.ListenerOpenDelegate ListenerOpenDelegate { get; } internal MsQuicNativeMethods.ListenerCloseDelegate ListenerCloseDelegate { get; } @@ -181,9 +160,9 @@ static MsQuicApi() internal MsQuicNativeMethods.ConnectionOpenDelegate ConnectionOpenDelegate { get; } internal MsQuicNativeMethods.ConnectionCloseDelegate ConnectionCloseDelegate { get; } + internal MsQuicNativeMethods.ConnectionSetConfigurationDelegate ConnectionSetConfigurationDelegate { get; } internal MsQuicNativeMethods.ConnectionShutdownDelegate ConnectionShutdownDelegate { get; } internal MsQuicNativeMethods.ConnectionStartDelegate ConnectionStartDelegate { get; } - internal MsQuicNativeMethods.DatagramSendDelegate DatagramSendDelegate { get; } internal MsQuicNativeMethods.StreamOpenDelegate StreamOpenDelegate { get; } internal MsQuicNativeMethods.StreamCloseDelegate StreamCloseDelegate { get; } @@ -193,229 +172,11 @@ static MsQuicApi() 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 MsQuicNativeMethods.DatagramSendDelegate DatagramSendDelegate { 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 8c0c3c40ce57b6..bf993d7f16c290 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,126 +1,81 @@ // 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, uint 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, uint 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, uint 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, uint 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, uint level, uint param, ulong value) { - QuicBuffer buffer = new QuicBuffer() - { - Length = sizeof(ulong), - Buffer = (byte*)&value - }; - QuicExceptionHelpers.ThrowIfFailed( - api.UnsafeSetParam(nativeObject, level, param, buffer), + api.SetParamDelegate(nativeObject, level, param, sizeof(ulong), (byte*)&value), "Could not set ulong."); } - internal static unsafe byte GetByteParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param) + internal static unsafe byte GetByteParam(MsQuicApi api, SafeHandle nativeObject, uint level, uint param) { - byte* ptr = stackalloc byte[sizeof(byte)]; - QuicBuffer buffer = new QuicBuffer() - { - Length = sizeof(byte), - Buffer = ptr - }; + byte value; + uint valueLen = (uint)sizeof(byte); - QuicExceptionHelpers.ThrowIfFailed( - api.UnsafeGetParam(nativeObject, level, param, ref buffer), - "Could not get byte."); + uint status = api.GetParamDelegate(nativeObject, level, param, ref valueLen, &value); + QuicExceptionHelpers.ThrowIfFailed(status, "GetByteParam failed."); + Debug.Assert(valueLen == sizeof(byte)); - return *ptr; + return value; } - internal static unsafe void SetByteParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param, byte value) + internal static unsafe void SetByteParam(MsQuicApi api, SafeHandle nativeObject, uint level, uint param, byte value) { - QuicBuffer buffer = new QuicBuffer() - { - Length = sizeof(byte), - Buffer = &value - }; - QuicExceptionHelpers.ThrowIfFailed( - api.UnsafeSetParam(nativeObject, level, param, buffer), - "Could not set byte."); - } - - 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."); + api.SetParamDelegate(nativeObject, level, param, sizeof(byte), &value), + "Could not set ulong."); } } } 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 68b1432028b66d..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 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 6bb55f02242785..00000000000000 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs +++ /dev/null @@ -1,175 +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, - (byte)(options.DatagramReceiveEnabled ? 1 : 0)); - } - - 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, byte datagramReceiveEnabled) - { - _opened = true; - _nativeObjPtr = MsQuicApi.Api.SessionOpen(alpn); - SetPeerBiDirectionalStreamCount(bidirectionalStreamCount); - SetPeerUnidirectionalStreamCount(undirectionalStreamCount); - SetDatagramReceiveEnabled(datagramReceiveEnabled); - } - - // 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, - (byte)(options.DatagramReceiveEnabled ? 1 : 0)); - } - - 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); - } - - public void SetDatagramReceiveEnabled(byte enabled) - { - SetByteParameter(QUIC_PARAM_SESSION.DATAGRAM_RECEIVE_ENABLED, enabled); - } - - private unsafe void SetByteParameter(QUIC_PARAM_SESSION param, byte value) - { - var buffer = new MsQuicNativeMethods.QuicBuffer() - { - Length = sizeof(byte), - Buffer = &value - }; - 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/Interop/Interop.MsQuic.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/Interop.MsQuic.cs similarity index 100% rename from src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/Interop.MsQuic.cs rename to src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/Interop.MsQuic.cs 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..c5d9cce43d4a07 --- /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 + { + Destroy(ref handles, ref buffers); + throw; + } + } + + public static void Destroy(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/Interop/MsQuicEnums.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicEnums.cs similarity index 73% rename from src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicEnums.cs rename to src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicEnums.cs index f05293dcbf3783..8ead1f2c043f64 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicEnums.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicEnums.cs @@ -11,18 +11,24 @@ internal enum QUIC_EXECUTION_PROFILE : uint QUIC_EXECUTION_PROFILE_TYPE_REAL_TIME } - /// - /// Flags to pass when creating a security config. - /// + internal enum QUIC_CREDENTIAL_TYPE : uint + { + NONE, + HASH, + HASH_STORE, + CONTEXT, + FILE, + STUB_NULL = 0xF0000000 // pass as server cert to stubtls implementation + } + [Flags] - internal enum QUIC_SEC_CONFIG_FLAG : uint + internal enum QUIC_CREDENTIAL_FLAGS : 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. + NONE = 0, + CLIENT = 1, // lack of client flag indicates server. + LOAD_ASYNCHRONOUS = 2, + NO_CERTIFICATE_VALIDATION = 4, + ENABLE_OCSP = 8 } [Flags] @@ -73,15 +79,16 @@ internal enum QUIC_SEND_FLAG : uint { NONE = 0, ALLOW_0_RTT = 0x00000001, - FIN = 0x00000002, - DGRAM_PRIORITY = 0x00000004 + START = 0x00000002, + FIN = 0x00000004, + DGRAM_PRIORITY = 0x00000008 } internal enum QUIC_PARAM_LEVEL : uint { GLOBAL, REGISTRATION, - SESSION, + CONFIGURATION, LISTENER, CONNECTION, TLS, @@ -90,9 +97,11 @@ internal enum QUIC_PARAM_LEVEL : uint internal enum QUIC_PARAM_GLOBAL : uint { - RETRY_MEMORY_PERCENT = 0, - SUPPORTED_VERSIONS = 1, - LOAD_BALANCING_MODE = 2, + RETRY_MEMORY_PERCENT, + SUPPORTED_VERSIONS, + LOAD_BALANCING_MODE, + PERF_COUNTERS, + SETTINGS } internal enum QUIC_PARAM_REGISTRATION : uint @@ -121,30 +130,23 @@ internal enum QUIC_PARAM_LISTENER : uint 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 + QUIC_VERSION, + LOCAL_ADDRESS, + REMOTE_ADDRESS, + IDEAL_PROCESSOR, + SETTINGS, + STATISTICS, + STATISTICS_PLAT, + SHARE_UDP_BINDING, + LOCAL_BIDI_STREAM_COUNT, + LOCAL_UNIDI_STREAM_COUNT, + MAX_STREAM_IDS, + CLOSE_REASON_PHRASE, + STREAM_SCHEDULING_SCHEME, + DATAGRAM_RECEIVE_ENABLED, + DATAGRAM_SEND_ENABLED, + DISABLE_1RTT_ENCRYPTION, + RESUMPTION_TICKET } internal enum QUIC_PARAM_STREAM : uint @@ -200,4 +202,11 @@ internal enum QUIC_STREAM_EVENT : uint SHUTDOWN_COMPLETE = 7, IDEAL_SEND_BUFFER_SIZE = 8, } + + internal enum QUIC_SERVER_RESUMPTION_LEVEL : byte + { + NO_RESUME, + RESUME_ONLY, + RESUME_AND_ZERORTT + } } 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 71% 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 b4782712fc01ac..98c4cf9a9ead61 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; @@ -54,24 +51,15 @@ internal struct NativeApi internal IntPtr DatagramSend; } - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint SetContextDelegate( - IntPtr handle, - IntPtr context); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate IntPtr GetContextDelegate( - IntPtr 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, + SafeHandle handle, uint level, uint param, uint bufferLength, @@ -79,14 +67,16 @@ internal delegate uint SetParamDelegate( [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint GetParamDelegate( - IntPtr handle, + SafeHandle handle, uint 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); @@ -94,49 +84,33 @@ internal delegate uint GetParamDelegate( [StructLayout(LayoutKind.Sequential)] internal struct RegistrationConfig { - [MarshalAs(UnmanagedType.LPUTF8Str)] - internal string AppName; - internal QUIC_EXECUTION_PROFILE ExecutionProfile; + internal byte* AppName; + internal uint ExecutionProfile; } [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, - IntPtr context, - SecConfigCreateCompleteDelegate completionHandler); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void SecConfigDeleteDelegate( - IntPtr securityConfig); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint SessionOpenDelegate( - IntPtr registrationContext, - QuicBuffer *alpnBuffers, + internal delegate uint ConfigurationOpenDelegate( + SafeMsQuicRegistrationHandle registrationContext, + ref QuicBuffer alpnBuffers, uint alpnBufferCount, + ref Settings settings, + uint settingsSize, IntPtr context, - ref IntPtr session); + out SafeMsQuicConfigurationHandle configuration); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void SessionCloseDelegate( - IntPtr session); + internal delegate void ConfigurationCloseDelegate( + IntPtr configuration); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void SessionShutdownDelegate( - IntPtr session, - uint flags, - ulong errorCode); + internal delegate uint ConfigurationLoadCredentialDelegate( + SafeMsQuicConfigurationHandle configuration, + CredentialConfig *credConfig); [StructLayout(LayoutKind.Sequential)] internal struct ListenerEvent { - internal QUIC_LISTENER_EVENT Type; + internal uint Type; internal ListenerEventDataUnion Data; } @@ -179,23 +153,25 @@ 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, + ref 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 @@ -239,7 +215,7 @@ internal struct ConnectionEventDataPeerAddrChanged internal struct ConnectionEventDataStreamStarted { internal IntPtr Stream; - internal QUIC_STREAM_OPEN_FLAG Flags; + internal uint Flags; } [StructLayout(LayoutKind.Sequential)] @@ -310,7 +286,7 @@ internal struct ConnectionEventDataUnion [StructLayout(LayoutKind.Sequential)] internal struct ConnectionEvent { - internal QUIC_CONNECTION_EVENT Type; + internal uint Type; internal ConnectionEventDataUnion Data; internal bool EarlyDataAccepted => Data.Connected.SessionResumed; @@ -320,8 +296,8 @@ internal struct ConnectionEvent internal bool ShutdownTimedOut => Data.ShutdownComplete.TimedOut; internal ushort BiDirectionalCount => Data.StreamsAvailable.BiDirectionalCount; internal ushort UniDirectionalCount => Data.StreamsAvailable.UniDirectionalCount; + internal QUIC_STREAM_OPEN_FLAG StreamFlags => (QUIC_STREAM_OPEN_FLAG)Data.StreamStarted.Flags; internal ReadOnlySpan DatagramReceivedBuffer => new ReadOnlySpan(Data.DatagramReceived.Buffer->Buffer, (int)Data.DatagramReceived.Buffer->Length); - internal QUIC_STREAM_OPEN_FLAG StreamFlags => Data.StreamStarted.Flags; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -332,26 +308,32 @@ internal delegate uint ConnectionCallbackDelegate( [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, + SafeMsQuicConnectionHandle connection, + SafeMsQuicConfigurationHandle configuration, ushort family, [MarshalAs(UnmanagedType.LPUTF8Str)] string serverName, ushort serverPort); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint ConnectionShutdownDelegate( - IntPtr connection, + internal delegate void ConnectionShutdownDelegate( + SafeMsQuicConnectionHandle connection, uint flags, long errorCode); @@ -362,7 +344,7 @@ internal struct StreamEventDataRecv internal ulong TotalBufferLength; internal QuicBuffer* Buffers; internal uint BufferCount; - internal QUIC_RECEIVE_FLAG Flags; + internal uint Flags; } [StructLayout(LayoutKind.Sequential)] @@ -412,7 +394,7 @@ internal struct StreamEventDataUnion [StructLayout(LayoutKind.Sequential)] internal struct StreamEvent { - internal QUIC_STREAM_EVENT Type; + internal uint Type; internal StreamEventDataUnion Data; } @@ -491,49 +473,49 @@ internal delegate uint StreamCallbackDelegate( [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamOpenDelegate( - IntPtr connection, + SafeMsQuicConnectionHandle connection, uint flags, StreamCallbackDelegate handler, IntPtr context, - out IntPtr stream); + out SafeMsQuicStreamHandle stream); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamStartDelegate( - IntPtr stream, + SafeMsQuicStreamHandle stream, uint flags); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate uint StreamCloseDelegate( + internal delegate void StreamCloseDelegate( IntPtr stream); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamShutdownDelegate( - IntPtr stream, + SafeMsQuicStreamHandle stream, uint flags, long errorCode); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint StreamSendDelegate( - IntPtr stream, - QuicBuffer* buffers, + SafeMsQuicStreamHandle stream, + QuicBuffer *buffers, uint bufferCount, uint 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); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint DatagramSendDelegate( - IntPtr connection, + SafeMsQuicConnectionHandle connection, QuicBuffer* buffers, uint bufferCount, uint flags, @@ -547,10 +529,93 @@ internal unsafe struct QuicBuffer } [StructLayout(LayoutKind.Sequential)] - internal struct CertFileParams + internal struct CredentialConfig + { + internal uint Type; + internal uint Flags; + internal IntPtr Certificate; // PCERT_CONTEXT (SCHANNEL) + internal IntPtr Principal; + internal IntPtr TicketKey; // optional. + internal delegate* unmanaged AsyncHandler; // optional, must set LOAD_ASYNCHRONOUS flag. + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CredentialConfigCertificateFile + { + internal IntPtr PrivateKeyFile; + internal IntPtr CertificateFile; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct Settings + { + internal ulong 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 byte BitField; + } + + internal enum SettingsBitField : byte + { + SendBufferingEnabled = 1, + PacingEnabled = 1 << 1, + MigrationEnabled = 1 << 2, + DatagramReceiveEnabled = 1 << 3, + + // single enum field, 2 bits. + NoResume = QUIC_SERVER_RESUMPTION_LEVEL.NO_RESUME << 4, + ResumeOnly = QUIC_SERVER_RESUMPTION_LEVEL.RESUME_ONLY << 4, + ResumeAndZeroRtt = QUIC_SERVER_RESUMPTION_LEVEL.RESUME_AND_ZERORTT << 4 + } + + [Flags] + internal enum SettingsFlags : ulong { - internal IntPtr PrivateKeyFilePath; - internal IntPtr CertificateFilePath; + MaxBytesPerKey = 1, + 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 } } } 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..cd6a341b8ec542 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs @@ -0,0 +1,151 @@ +// 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; + } + + 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); + } + + 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 Settings + { + IsSetFlags = + (ulong)SettingsFlags.PeerBidiStreamCount + | (ulong)SettingsFlags.PeerUnidiStreamCount + | (ulong)SettingsFlags.DatagramReceiveEnabled, + PeerBidiStreamCount = (ushort)options.MaxBidirectionalStreams, + PeerUnidiStreamCount = (ushort)options.MaxUnidirectionalStreams, + BitField = options.DatagramReceiveEnabled ? (byte)SettingsBitField.DatagramReceiveEnabled : (byte)0 + }; + + 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 |= (ulong)SettingsFlags.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, ref MemoryMarshal.GetReference(buffers.AsSpan()), (uint)alpnProtocols.Count, ref settings, (uint)sizeof(Settings), context: IntPtr.Zero, out configurationHandle); + } + finally + { + MsQuicAlpnHelper.Destroy(ref handles, ref buffers); + } + + QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationOpen failed."); + + CredentialConfigCertificateFile file = default; + 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 = (uint)flags; // TODO: consider using LOAD_ASYNCHRONOUS with a callback. + + if (certFilePath != null && privateKeyFilePath != null) + { + config.Type = (uint)QUIC_CREDENTIAL_TYPE.FILE; + file.PrivateKeyFile = Marshal.StringToCoTaskMemUTF8(privateKeyFilePath); + file.CertificateFile = Marshal.StringToCoTaskMemUTF8(certFilePath); + config.Certificate = new IntPtr(&file); + } + else if (certificate != null) + { +#if true + // If using stub TLS. + config.Type = (uint)QUIC_CREDENTIAL_TYPE.STUB_NULL; +#else + config.Type = (uint)QUIC_CREDENTIAL_TYPE.CONTEXT; + config.Certificate = certificate.Handle; +#endif + } + else + { + config.Type = (uint)QUIC_CREDENTIAL_TYPE.NONE; + } + + status = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, &config); + QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationLoadCredential failed."); + } + catch + { + configurationHandle.Dispose(); + throw; + } + finally + { + if (file.PrivateKeyFile != default) + Marshal.ZeroFreeCoTaskMemUTF8(file.PrivateKeyFile); + if (file.CertificateFile != default) + Marshal.ZeroFreeCoTaskMemUTF8(file.CertificateFile); + } + + 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..fa7f42c7130364 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConnectionHandle.cs @@ -0,0 +1,27 @@ +// 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..ffbfddb50757a6 --- /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..69336aaadb9e2b --- /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..4769174e46a09c --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicStreamHandle.cs @@ -0,0 +1,27 @@ +// 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 4ecbf822257635..aee40e6a00784c 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; @@ -19,210 +17,194 @@ 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; - private ushort _datagramMaxSendLength; + // 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 IPEndPoint? LocalEndPoint; + public bool Connected; + public long AbortErrorCode = -1; + public ushort DatagramMaxSendLength; + + // 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(); - - SetIdleTimeout(idleTimeout); + _stateHandle = GCHandle.Alloc(_state); + 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 => + new IPEndPoint(_localEndPoint!.Address, _localEndPoint!.Port); internal override EndPoint RemoteEndPoint => _remoteEndPoint; internal override SslApplicationProtocol NegotiatedApplicationProtocol => _negotiatedAlpnProtocol; - internal override bool Connected => _connected; - - internal uint HandleEvent(ref ConnectionEvent connectionEvent) - { - try - { - 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); - case QUIC_CONNECTION_EVENT.DATAGRAM_STATE_CHANGED: - return HandleEventDatagramStateChanged(ref connectionEvent); - case QUIC_CONNECTION_EVENT.DATAGRAM_RECEIVED: - return HandleEventDatagramReceived(ref connectionEvent); - case QUIC_CONNECTION_EVENT.DATAGRAM_SEND_STATE_CHANGED: - return HandleEventDatagramSendStateChanged(ref connectionEvent); - default: - return MsQuicStatusCodes.Success; - } - } - 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. + internal override bool Connected => _state.Connected; - return MsQuicStatusCodes.InternalError; - } - } - - private uint HandleEventConnected(ref ConnectionEvent connectionEvent) + private static uint HandleEventConnected(State state, ref ConnectionEvent connectionEvent) { - if (!_connected) + if (!state.Connected) { - // _connected will already be true for connections accepted from a listener. + // 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); + SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, state.Handle, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS); + state.LocalEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref inetAddress); - SetNegotiatedAlpn(connectionEvent.Data.Connected.NegotiatedAlpn, connectionEvent.Data.Connected.NegotiatedAlpnLength); + Debug.Assert(state.Connection != null); + state.Connection.SetNegotiatedAlpn(connectionEvent.Data.Connected.NegotiatedAlpn, connectionEvent.Data.Connected.NegotiatedAlpnLength); + state.Connection = null; - _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 = 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; - // Stop accepting new streams. - _acceptQueue?.Writer.Complete(); + state.ShutdownTcs.SetResult(MsQuicStatusCodes.Success); 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.StreamStarted.Stream); + var stream = new MsQuicStream(streamHandle, connectionEvent.StreamFlags); + + 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; } - private uint HandleEventDatagramStateChanged(ref ConnectionEvent connectionEvent) + private static uint HandleEventDatagramStateChanged(State state, ref ConnectionEvent connectionEvent) { - _datagramMaxSendLength = connectionEvent.Data.DatagramStateChanged.MaxSendLength; + state.DatagramMaxSendLength = connectionEvent.Data.DatagramStateChanged.MaxSendLength; return MsQuicStatusCodes.Success; } - private uint HandleEventDatagramReceived(ref ConnectionEvent connectionEvent) + private static uint HandleEventDatagramReceived(State state, ref ConnectionEvent connectionEvent) { - DatagramReceived?.Invoke(this, connectionEvent.DatagramReceivedBuffer); + state.Connection!.DatagramReceived?.Invoke(state.Connection, connectionEvent.DatagramReceivedBuffer); return MsQuicStatusCodes.Success; } - private uint HandleEventDatagramSendStateChanged(ref ConnectionEvent connectionEvent) + private static uint HandleEventDatagramSendStateChanged(State state, ref ConnectionEvent connectionEvent) { - var state = connectionEvent.Data.DatagramSendStateChanged.State; + var datagramState = connectionEvent.Data.DatagramSendStateChanged.State; GCHandle handle = GCHandle.FromIntPtr(connectionEvent.Data.DatagramSendStateChanged.ClientContext); var source = (SendDatagramValueTaskSource)handle.Target!; - switch (state) + switch (datagramState) { case QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_LOST_DISCARDED: case QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_ACKNOWLEDGED: case QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_ACKNOWLEDGED_SPURIOUS: case QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_CANCELED: - source.SetResult(state); + source.SetResult(datagramState); break; default: break; @@ -238,11 +220,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. @@ -255,36 +237,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_FLAG.UNIDIRECTIONAL); } internal override QuicStreamProvider OpenBidirectionalStream() { ThrowIfDisposed(); - - return StreamOpen(QUIC_STREAM_OPEN_FLAG.NONE); + return new MsQuicStream(_state.Handle, QUIC_STREAM_OPEN_FLAG.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, (uint)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, (uint)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), @@ -292,7 +272,7 @@ 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 + // TODO: MsQuic will use system constants, so we should use the Socket PAL to translate these. int af = _remoteEndPoint.AddressFamily switch { AddressFamily.Unspecified => 0, @@ -301,55 +281,51 @@ internal override ValueTask ConnectAsync(CancellationToken cancellationToken = d _ => 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, + (ushort)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, long ErrorCode) { - uint status = MsQuicApi.Api.ConnectionShutdownDelegate( - _ptr, - (uint)Flags, - ErrorCode); - QuicExceptionHelpers.ThrowIfFailed(status, "Failed to shutdown connection."); + Debug.Assert(!_state.ShutdownTcs.Task.IsCompleted); - return new ValueTask(_shutdownTcs.Task); + // 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, + (uint)Flags, + ErrorCode); + } + catch + { + _state.Connection = null; + throw; + } + + return new ValueTask(_state.ShutdownTcs.Task); } internal void SetNegotiatedAlpn(IntPtr alpn, int alpnLength) @@ -365,11 +341,47 @@ 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)connectionEvent.Type) + { + case QUIC_CONNECTION_EVENT.CONNECTED: + return HandleEventConnected(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_TRANSPORT: + return HandleEventShutdownInitiatedByTransport(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_PEER: + return HandleEventShutdownInitiatedByPeer(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT.SHUTDOWN_COMPLETE: + return HandleEventShutdownComplete(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT.PEER_STREAM_STARTED: + return HandleEventNewStream(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT.STREAMS_AVAILABLE: + return HandleEventStreamsAvailable(state, ref connectionEvent); + case QUIC_CONNECTION_EVENT.DATAGRAM_STATE_CHANGED: + return HandleEventDatagramStateChanged(ref connectionEvent); + case QUIC_CONNECTION_EVENT.DATAGRAM_RECEIVED: + return HandleEventDatagramReceived(ref connectionEvent); + case QUIC_CONNECTION_EVENT.DATAGRAM_SEND_STATE_CHANGED: + return HandleEventDatagramSendStateChanged(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() @@ -390,20 +402,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; } @@ -418,17 +418,17 @@ internal override ValueTask CloseAsync(long errorCode, CancellationToken cancell internal override bool DatagramReceiveEnabled { - get => MsQuicParameterHelpers.GetByteParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.DATAGRAM_RECEIVE_ENABLED) != 0; - set => MsQuicParameterHelpers.SetByteParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.DATAGRAM_RECEIVE_ENABLED, (byte)(value ? 1 : 0)); + get => MsQuicParameterHelpers.GetByteParam(MsQuicApi.Api, _state.Handle, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.DATAGRAM_RECEIVE_ENABLED) != 0; + set => MsQuicParameterHelpers.SetByteParam(MsQuicApi.Api, _state.Handle, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.DATAGRAM_RECEIVE_ENABLED, (byte)(value ? 1 : 0)); } internal override bool DatagramSendEnabled { - get => MsQuicParameterHelpers.GetByteParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.DATAGRAM_SEND_ENABLED) != 0; - set => MsQuicParameterHelpers.SetByteParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.DATAGRAM_SEND_ENABLED, (byte)(value ? 1 : 0)); + get => MsQuicParameterHelpers.GetByteParam(MsQuicApi.Api, _state.Handle, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.DATAGRAM_SEND_ENABLED) != 0; + set => MsQuicParameterHelpers.SetByteParam(MsQuicApi.Api, _state.Handle, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.DATAGRAM_SEND_ENABLED, (byte)(value ? 1 : 0)); } - internal override ushort DatagramMaxSendLength => _datagramMaxSendLength; + internal override ushort DatagramMaxSendLength => _state.DatagramMaxSendLength; internal override event QuicDatagramReceivedEventHandler? DatagramReceived; @@ -463,7 +463,7 @@ internal override async ValueTask SendDatagramAsync(ReadOnlyMemory b unsafe { var status = MsQuicApi.Api.DatagramSendDelegate( - _ptr, + _state.Handle, (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(quicBuffer, 0), 1, (uint)(priority ? QUIC_SEND_FLAG.DGRAM_PRIORITY : QUIC_SEND_FLAG.NONE), 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..c169c39cdd9f51 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,94 @@ 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, ref MemoryMarshal.GetReference(buffers.AsSpan()), (uint)_applicationProtocols.Count, ref address); + } + finally + { + MsQuicAlpnHelper.Destroy(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, (uint)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); + _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 ((QUIC_LISTENER_EVENT)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 *(NewConnectionInfo*)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..c491312fd157eb 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_FLAG flags) { - Debug.Assert(connection != null, "Connection null"); + _state.Handle = streamHandle; + _canRead = true; + _canWrite = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.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_FLAG flags) + { + Debug.Assert(connection != null); - bool isBidirectional = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL); + _canRead = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL); + _canWrite = true; - if (inbound) + _stateHandle = GCHandle.Alloc(_state); + try { - _canRead = true; - _canWrite = isBidirectional; - _started = true; + uint status = MsQuicApi.Api.StreamOpenDelegate( + connection, + (uint)flags, + s_streamDelegate, + GCHandle.ToIntPtr(_stateHandle), + out _state.Handle); + + QuicExceptionHelpers.ThrowIfFailed(status, "Failed to open stream to peer."); + + status = MsQuicApi.Api.StreamStartDelegate(_state.Handle, (uint)QUIC_STREAM_START_FLAG.ASYNC); + QuicExceptionHelpers.ThrowIfFailed(status, "Could not start stream."); } - else + catch { - _canRead = isBidirectional; - _canWrite = true; - StartLocalStream(); + _state.Handle?.Dispose(); + _stateHandle.Free(); + throw; } } @@ -144,7 +168,6 @@ 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); 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_FLAG.ABORT_RECV, 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_FLAG.ABORT_SEND, errorCode); } - internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default) + private void StartShutdown(QUIC_STREAM_SHUTDOWN_FLAG flags, long errorCode) + { + uint status = MsQuicApi.Api.StreamShutdownDelegate(_state.Handle, (uint)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_FLAG.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)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; + return HandleStartComplete(state); // Received data on the stream case QUIC_STREAM_EVENT.RECEIVE: - { - status = HandleEventRecv(ref evt); - } - break; + 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; + 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; + 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; + 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; + 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; + return HandleEventSendShutdownComplete(state, ref evt); // Shutdown for both sending and receiving is completed. case QUIC_STREAM_EVENT.SHUTDOWN_COMPLETE: - { - status = HandleEventShutdownComplete(); - } - break; + 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++) { - _receiveQuicBuffers.Add(receieveEvent.Buffers[i]); + state.ReceiveQuicBuffers.Add(receieveEvent.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)receieveEvent.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 = evt.Data.PeerSendAbort.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,163 +584,153 @@ 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 MsQuicNativeMethods.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 = evt.Data.PeerSendAbort.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, @@ -779,38 +741,38 @@ private unsafe ValueTask SendReadOnlyMemoryAsync( if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.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_FLAG.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); + 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( @@ -822,7 +784,7 @@ private unsafe ValueTask SendReadOnlySequenceAsync( if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.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_FLAG.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,33 +807,33 @@ 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); + 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( @@ -883,7 +845,7 @@ private unsafe ValueTask SendReadOnlyMemoryListAsync( if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.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_FLAG.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); + 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, (uint)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/QuicClientConnectionOptions.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs index cc91a7e7914910..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,30 +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); - - /// - /// Whether to receiving datagrams is enabled. - /// - public bool DatagramReceiveEnabled { get; set; } + public QuicClientConnectionOptions() + { + IdleTimeout = TimeSpan.FromTicks(2 * TimeSpan.TicksPerMinute); + } } } 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 7c60cd390fe5ed..7594789ff38746 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,7 +8,7 @@ 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. @@ -35,29 +35,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); - - /// - /// Whether to receiving datagrams is enabled. - /// - public bool DatagramReceiveEnabled { get; set; } + 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..66e425ed5f69a2 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicOptions.cs @@ -0,0 +1,41 @@ +// 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; } + + /// + /// Whether to receiving datagrams is enabled. + /// + public bool DatagramReceiveEnabled { 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..8f49d8b6113830 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs @@ -13,7 +13,8 @@ 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. }; } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index 39d2eebc668b88..a035e243600f3e 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;