QUIC and HTTP/3 fixes (#40468)
authorCory Nelson <phrosty@gmail.com>
Thu, 6 Aug 2020 19:43:49 +0000 (12:43 -0700)
committerGitHub <noreply@github.com>
Thu, 6 Aug 2020 19:43:49 +0000 (12:43 -0700)
Update/fix MsQuic P/invoke layer.
Fix a race condition in MsQuicListener setting MsQuicConnection.Connected.
Some MsQuic cleanup.
Remove (un-reviewed) HttpVersion.Version30 and SslApplicationProtocol.Http3 APIs.
Fix Alt-Svc support.
Make Alt-Svc tests timeout properly.

29 files changed:
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicConnection.cs
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicListener.cs
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicStream.cs
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/Interop.MsQuic.cs
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicEnums.cs
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicNativeMethods.cs
src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs
src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpAuthority.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AltSvc.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/MsQuicTests.cs
src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs
src/libraries/System.Net.Primitives/src/System/Net/HttpVersion.cs
src/libraries/System.Net.Security/ref/System.Net.Security.cs
src/libraries/System.Net.Security/src/System/Net/Security/SslApplicationProtocol.cs
src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs

index 38eb74b..a06a08f 100644 (file)
@@ -11,7 +11,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
         internal const ushort IPv4 = 2;
         internal const ushort IPv6 = 23;
 
-        internal static unsafe IPEndPoint INetToIPEndPoint(SOCKADDR_INET inetAddress)
+        internal static unsafe IPEndPoint INetToIPEndPoint(ref SOCKADDR_INET inetAddress)
         {
             if (inetAddress.si_family == IPv4)
             {
index 73c3e55..5c62f1d 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
 
             try
             {
-                uint status = Interop.MsQuic.MsQuicOpen(version: 1, out registration);
+                uint status = Interop.MsQuic.MsQuicOpen(out registration);
                 if (!MsQuicStatusHelper.SuccessfulStatusCode(status))
                 {
                     throw new NotSupportedException(SR.net_quic_notsupported);
@@ -123,7 +123,13 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
                 Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.GetParamDelegate>(
                     nativeRegistration.GetParam);
 
-            RegistrationOpenDelegate(Encoding.UTF8.GetBytes("SystemNetQuic"), out IntPtr ctx);
+            var registrationConfig = new MsQuicNativeMethods.RegistrationConfig
+            {
+                AppName = "SystemNetQuic",
+                ExecutionProfile = QUIC_EXECUTION_PROFILE.QUIC_EXECUTION_PROFILE_LOW_LATENCY
+            };
+
+            RegistrationOpenDelegate(ref registrationConfig, out IntPtr ctx);
             _registrationContext = ctx;
         }
 
@@ -312,15 +318,26 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             return secConfig;
         }
 
-        public IntPtr SessionOpen(byte[] alpn)
+        public unsafe IntPtr SessionOpen(byte[] alpn)
         {
             IntPtr sessionPtr = IntPtr.Zero;
+            uint status;
 
-            uint status = SessionOpenDelegate(
-                _registrationContext,
-                alpn,
-                IntPtr.Zero,
-                ref sessionPtr);
+            fixed (byte* pAlpn = alpn)
+            {
+                var alpnBuffer = new MsQuicNativeMethods.QuicBuffer
+                {
+                    Length = (uint)alpn.Length,
+                    Buffer = pAlpn
+                };
+
+                status = SessionOpenDelegate(
+                    _registrationContext,
+                    &alpnBuffer,
+                    1,
+                    IntPtr.Zero,
+                    ref sessionPtr);
+            }
 
             QuicExceptionHelpers.ThrowIfFailed(status, "Could not open session.");
 
index e35493a..d5f1c11 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
 
             QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ConnectionOpenDelegate(
                 _nativeObjPtr,
-                MsQuicConnection.NativeCallbackHandler,
+                MsQuicConnection.s_connectionDelegate,
                 IntPtr.Zero,
                 out IntPtr connectionPtr),
                 "Could not open the connection.");
@@ -83,15 +83,15 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
 
         public void SetPeerBiDirectionalStreamCount(ushort count)
         {
-            SetUshortParamter(QUIC_PARAM_SESSION.PEER_BIDI_STREAM_COUNT, count);
+            SetUshortParameter(QUIC_PARAM_SESSION.PEER_BIDI_STREAM_COUNT, count);
         }
 
         public void SetPeerUnidirectionalStreamCount(ushort count)
         {
-            SetUshortParamter(QUIC_PARAM_SESSION.PEER_UNIDI_STREAM_COUNT, count);
+            SetUshortParameter(QUIC_PARAM_SESSION.PEER_UNIDI_STREAM_COUNT, count);
         }
 
-        private unsafe void SetUshortParamter(QUIC_PARAM_SESSION param, ushort count)
+        private unsafe void SetUshortParameter(QUIC_PARAM_SESSION param, ushort count)
         {
             var buffer = new MsQuicNativeMethods.QuicBuffer()
             {
index 0046c56..708e15e 100644 (file)
@@ -2,9 +2,11 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 #nullable enable
+using System.Diagnostics;
 using System.IO;
 using System.Net.Quic.Implementations.MsQuic.Internal;
 using System.Net.Security;
+using System.Runtime.ExceptionServices;
 using System.Runtime.InteropServices;
 using System.Security.Cryptography.X509Certificates;
 using System.Threading;
@@ -16,7 +18,7 @@ namespace System.Net.Quic.Implementations.MsQuic
 {
     internal sealed class MsQuicConnection : QuicConnectionProvider
     {
-        private MsQuicSession? _session;
+        private readonly MsQuicSession? _session;
 
         // Pointer to the underlying connection
         // TODO replace all IntPtr with SafeHandles
@@ -26,15 +28,17 @@ namespace System.Net.Quic.Implementations.MsQuic
         private GCHandle _handle;
 
         // Delegate that wraps the static function that will be called when receiving an event.
-        // TODO investigate if the delegate can be static instead.
-        private ConnectionCallbackDelegate? _connectionDelegate;
+        internal static readonly ConnectionCallbackDelegate s_connectionDelegate = new ConnectionCallbackDelegate(NativeCallbackHandler);
 
         // Endpoint to either connect to or the endpoint already accepted.
         private IPEndPoint? _localEndPoint;
         private readonly IPEndPoint _remoteEndPoint;
 
-        private readonly ResettableCompletionSource<uint> _connectTcs = new ResettableCompletionSource<uint>();
-        private readonly ResettableCompletionSource<uint> _shutdownTcs = new ResettableCompletionSource<uint>();
+        private SslApplicationProtocol _negotiatedAlpnProtocol;
+
+        // TODO: only allocate these when there is an outstanding connect/shutdown.
+        private readonly TaskCompletionSource<uint> _connectTcs = new TaskCompletionSource<uint>();
+        private readonly TaskCompletionSource<uint> _shutdownTcs = new TaskCompletionSource<uint>();
 
         private bool _disposed;
         private bool _connected;
@@ -54,6 +58,7 @@ namespace System.Net.Quic.Implementations.MsQuic
             _localEndPoint = localEndPoint;
             _remoteEndPoint = remoteEndPoint;
             _ptr = nativeObjPtr;
+            _connected = true;
 
             SetCallbackHandler();
             SetIdleTimeout(TimeSpan.FromSeconds(120));
@@ -89,125 +94,96 @@ namespace System.Net.Quic.Implementations.MsQuic
 
         internal override IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint.Address, _remoteEndPoint.Port);
 
-        internal override SslApplicationProtocol NegotiatedApplicationProtocol => throw new NotImplementedException();
+        internal override SslApplicationProtocol NegotiatedApplicationProtocol => _negotiatedAlpnProtocol;
 
         internal override bool Connected => _connected;
 
         internal uint HandleEvent(ref ConnectionEvent connectionEvent)
         {
-            uint status = MsQuicStatusCodes.Success;
             try
             {
                 switch (connectionEvent.Type)
                 {
-                    // Connection is connected, can start to create streams.
                     case QUIC_CONNECTION_EVENT.CONNECTED:
-                        {
-                            status = HandleEventConnected(
-                                connectionEvent);
-                        }
-                        break;
-
-                    // Connection is being closed by the transport
+                        return HandleEventConnected(ref connectionEvent);
                     case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_TRANSPORT:
-                        {
-                            status = HandleEventShutdownInitiatedByTransport(
-                                connectionEvent);
-                        }
-                        break;
-
-                    // Connection is being closed by the peer
+                        return HandleEventShutdownInitiatedByTransport(ref connectionEvent);
                     case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_PEER:
-                        {
-                            status = HandleEventShutdownInitiatedByPeer(
-                                connectionEvent);
-                        }
-                        break;
-
-                    // Connection has been shutdown
+                        return HandleEventShutdownInitiatedByPeer(ref connectionEvent);
                     case QUIC_CONNECTION_EVENT.SHUTDOWN_COMPLETE:
-                        {
-                            status = HandleEventShutdownComplete(
-                                connectionEvent);
-                        }
-                        break;
-
+                        return HandleEventShutdownComplete(ref connectionEvent);
                     case QUIC_CONNECTION_EVENT.PEER_STREAM_STARTED:
-                        {
-                            status = HandleEventNewStream(
-                                connectionEvent);
-                        }
-                        break;
-
+                        return HandleEventNewStream(ref connectionEvent);
                     case QUIC_CONNECTION_EVENT.STREAMS_AVAILABLE:
-                        {
-                            status = HandleEventStreamsAvailable(
-                                connectionEvent);
-                        }
-                        break;
-
+                        return HandleEventStreamsAvailable(ref connectionEvent);
                     default:
-                        break;
+                        return MsQuicStatusCodes.Success;
                 }
             }
-            catch (Exception)
+            catch (Exception ex)
             {
-                // TODO we may want to either add a debug assert here or return specific error codes
-                // based on the exception caught.
+                if (NetEventSource.Log.IsEnabled())
+                {
+                    NetEventSource.Error(this, $"Exception occurred during connection callback: {ex.Message}");
+                }
+
+                // TODO: trigger an exception on any outstanding async calls.
+
                 return MsQuicStatusCodes.InternalError;
             }
-
-            return status;
         }
 
-        private uint HandleEventConnected(ConnectionEvent connectionEvent)
+        private uint HandleEventConnected(ref ConnectionEvent connectionEvent)
         {
-            SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS);
-            _localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(inetAddress);
+            if (!_connected)
+            {
+                // _connected will already be true for connections accepted from a listener.
 
-            _connected = true;
-            // I don't believe we need to lock here because
-            // handle event connected will not be called at the same time as
-            // handle event shutdown initiated by transport
-            _connectTcs.Complete(MsQuicStatusCodes.Success);
+                SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS);
+                _localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref inetAddress);
+
+                SetNegotiatedAlpn(connectionEvent.Data.Connected.NegotiatedAlpn, connectionEvent.Data.Connected.NegotiatedAlpnLength);
+
+                _connected = true;
+                _connectTcs.SetResult(MsQuicStatusCodes.Success);
+            }
 
             return MsQuicStatusCodes.Success;
         }
 
-        private uint HandleEventShutdownInitiatedByTransport(ConnectionEvent connectionEvent)
+        private uint HandleEventShutdownInitiatedByTransport(ref ConnectionEvent connectionEvent)
         {
             if (!_connected)
             {
-                _connectTcs.CompleteException(new IOException("Connection has been shutdown."));
+                _connectTcs.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(new IOException("Connection has been shutdown.")));
             }
 
             _acceptQueue.Writer.Complete();
 
-
             return MsQuicStatusCodes.Success;
         }
 
-        private uint HandleEventShutdownInitiatedByPeer(ConnectionEvent connectionEvent)
+        private uint HandleEventShutdownInitiatedByPeer(ref ConnectionEvent connectionEvent)
         {
-            _abortErrorCode = connectionEvent.Data.ShutdownBeginPeer.ErrorCode;
+            _abortErrorCode = connectionEvent.Data.ShutdownInitiatedByPeer.ErrorCode;
             _acceptQueue.Writer.Complete();
             return MsQuicStatusCodes.Success;
         }
 
-        private uint HandleEventShutdownComplete(ConnectionEvent connectionEvent)
+        private uint HandleEventShutdownComplete(ref ConnectionEvent connectionEvent)
         {
-            _shutdownTcs.Complete(MsQuicStatusCodes.Success);
+            _shutdownTcs.SetResult(MsQuicStatusCodes.Success);
             return MsQuicStatusCodes.Success;
         }
 
-        private uint HandleEventNewStream(ConnectionEvent connectionEvent)
+        private uint HandleEventNewStream(ref ConnectionEvent connectionEvent)
         {
-            MsQuicStream msQuicStream = new MsQuicStream(this, connectionEvent.StreamFlags, connectionEvent.Data.NewStream.Stream, inbound: true);
+            MsQuicStream msQuicStream = new MsQuicStream(this, connectionEvent.StreamFlags, connectionEvent.Data.StreamStarted.Stream, inbound: true);
             _acceptQueue.Writer.TryWrite(msQuicStream);
             return MsQuicStatusCodes.Success;
         }
 
-        private uint HandleEventStreamsAvailable(ConnectionEvent connectionEvent)
+        private uint HandleEventStreamsAvailable(ref ConnectionEvent connectionEvent)
         {
             return MsQuicStatusCodes.Success;
         }
@@ -275,7 +251,7 @@ namespace System.Net.Quic.Implementations.MsQuic
                 (ushort)_remoteEndPoint.Port),
                 "Failed to connect to peer.");
 
-            return _connectTcs.GetTypelessValueTask();
+            return new ValueTask(_connectTcs.Task);
         }
 
         private MsQuicStream StreamOpen(
@@ -286,7 +262,7 @@ namespace System.Net.Quic.Implementations.MsQuic
                 MsQuicApi.Api.StreamOpenDelegate(
                 _ptr,
                 (uint)flags,
-                MsQuicStream.NativeCallbackHandler,
+                MsQuicStream.s_streamDelegate,
                 IntPtr.Zero,
                 out streamPtr),
                 "Failed to open stream to peer.");
@@ -296,11 +272,12 @@ namespace System.Net.Quic.Implementations.MsQuic
 
         private void SetCallbackHandler()
         {
+            Debug.Assert(!_handle.IsAllocated);
             _handle = GCHandle.Alloc(this);
-            _connectionDelegate = new ConnectionCallbackDelegate(NativeCallbackHandler);
+
             MsQuicApi.Api.SetCallbackHandlerDelegate(
                 _ptr,
-                _connectionDelegate,
+                s_connectionDelegate,
                 GCHandle.ToIntPtr(_handle));
         }
 
@@ -314,10 +291,20 @@ namespace System.Net.Quic.Implementations.MsQuic
                 ErrorCode);
             QuicExceptionHelpers.ThrowIfFailed(status, "Failed to shutdown connection.");
 
-            return _shutdownTcs.GetTypelessValueTask();
+            return new ValueTask(_shutdownTcs.Task);
+        }
+
+        internal void SetNegotiatedAlpn(IntPtr alpn, int alpnLength)
+        {
+            if (alpn != IntPtr.Zero && alpnLength != 0)
+            {
+                var buffer = new byte[alpnLength];
+                Marshal.Copy(alpn, buffer, 0, alpnLength);
+                _negotiatedAlpnProtocol = new SslApplicationProtocol(buffer);
+            }
         }
 
-        internal static uint NativeCallbackHandler(
+        private static uint NativeCallbackHandler(
             IntPtr connection,
             IntPtr context,
             ref ConnectionEvent connectionEventStruct)
index c8d3388..721a8e5 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 #nullable enable
+using System.Diagnostics;
 using System.Net.Quic.Implementations.MsQuic.Internal;
 using System.Net.Security;
 using System.Runtime.InteropServices;
@@ -15,7 +16,7 @@ namespace System.Net.Quic.Implementations.MsQuic
     internal sealed class MsQuicListener : QuicListenerProvider, IDisposable
     {
         // Security configuration for MsQuic
-        private MsQuicSession _session;
+        private readonly MsQuicSession _session;
 
         // Pointer to the underlying listener
         // TODO replace all IntPtr with SafeHandles
@@ -25,10 +26,10 @@ namespace System.Net.Quic.Implementations.MsQuic
         private GCHandle _handle;
 
         // Delegate that wraps the static function that will be called when receiving an event.
-        private ListenerCallbackDelegate? _listenerDelegate;
+        private static readonly ListenerCallbackDelegate s_listenerDelegate = new ListenerCallbackDelegate(NativeCallbackHandler);
 
         // Ssl listening options (ALPN, cert, etc)
-        private SslServerAuthenticationOptions _sslOptions;
+        private readonly SslServerAuthenticationOptions _sslOptions;
 
         private QuicListenerOptions _options;
         private volatile bool _disposed;
@@ -142,11 +143,10 @@ namespace System.Net.Quic.Implementations.MsQuic
         {
             SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.LISTENER, (uint)QUIC_PARAM_LISTENER.LOCAL_ADDRESS);
 
-            _listenEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(inetAddress);
+            _listenEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref inetAddress);
         }
 
-        internal unsafe uint ListenerCallbackHandler(
-            ref ListenerEvent evt)
+        internal unsafe uint ListenerCallbackHandler(ref ListenerEvent evt)
         {
             try
             {
@@ -154,10 +154,14 @@ namespace System.Net.Quic.Implementations.MsQuic
                 {
                     case QUIC_LISTENER_EVENT.NEW_CONNECTION:
                         {
-                            NewConnectionInfo connectionInfo = *(NewConnectionInfo*)evt.Data.NewConnection.Info;
-                            IPEndPoint localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(*(SOCKADDR_INET*)connectionInfo.LocalAddress);
-                            IPEndPoint remoteEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(*(SOCKADDR_INET*)connectionInfo.RemoteAddress);
+                            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);
+                            msQuicConnection.SetNegotiatedAlpn(connectionInfo.NegotiatedAlpn, connectionInfo.NegotiatedAlpnLength);
+
                             _acceptConnectionQueue.Writer.TryWrite(msQuicConnection);
                         }
                         // Always pend the new connection to wait for the security config to be resolved
@@ -167,8 +171,15 @@ namespace System.Net.Quic.Implementations.MsQuic
                         return MsQuicStatusCodes.InternalError;
                 }
             }
-            catch (Exception)
+            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.
+
                 return MsQuicStatusCodes.InternalError;
             }
         }
@@ -191,11 +202,12 @@ namespace System.Net.Quic.Implementations.MsQuic
 
         internal void SetCallbackHandler()
         {
+            Debug.Assert(!_handle.IsAllocated);
             _handle = GCHandle.Alloc(this);
-            _listenerDelegate = new ListenerCallbackDelegate(NativeCallbackHandler);
+
             MsQuicApi.Api.SetCallbackHandlerDelegate(
                 _ptr,
-                _listenerDelegate,
+                s_listenerDelegate,
                 GCHandle.ToIntPtr(_handle));
         }
 
index 6b8e9ce..fc451a7 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Net.Quic.Implementations.MsQuic
         private GCHandle _handle;
 
         // Delegate that wraps the static function that will be called when receiving an event.
-        private StreamCallbackDelegate? _callback;
+        internal static readonly StreamCallbackDelegate s_streamDelegate = new StreamCallbackDelegate(NativeCallbackHandler);
 
         // Backing for StreamId
         private long _streamId = -1;
@@ -62,10 +62,10 @@ namespace System.Net.Quic.Implementations.MsQuic
 
         private volatile bool _disposed;
 
-        private List<QuicBuffer> _receiveQuicBuffers = new List<QuicBuffer>();
+        private readonly List<QuicBuffer> _receiveQuicBuffers = new List<QuicBuffer>();
 
         // TODO consider using Interlocked.Exchange instead of a sync if we can avoid it.
-        private object _sync = new object();
+        private readonly object _sync = new object();
 
         // Creates a new MsQuicStream
         internal MsQuicStream(MsQuicConnection connection, QUIC_STREAM_OPEN_FLAG flags, IntPtr nativeObjPtr, bool inbound)
@@ -79,17 +79,19 @@ namespace System.Net.Quic.Implementations.MsQuic
             _shutdownWriteResettableCompletionSource = new ResettableCompletionSource<uint>();
             SetCallbackHandler();
 
+            bool isBidirectional = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL);
+
             if (inbound)
             {
-                _started = true;
-                _canWrite = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL);
                 _canRead = true;
+                _canWrite = isBidirectional;
+                _started = true;
             }
             else
             {
+                _canRead = isBidirectional;
                 _canWrite = true;
-                _canRead = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL);
-                StartWrites();
+                StartLocalStream();
             }
         }
 
@@ -715,7 +717,6 @@ namespace System.Net.Quic.Implementations.MsQuic
             CleanupSendState();
 
             // TODO throw if a write was canceled.
-            uint errorCode = evt.Data.SendComplete.Canceled;
 
             bool shouldComplete = false;
             lock (_sync)
@@ -753,10 +754,9 @@ namespace System.Net.Quic.Implementations.MsQuic
         {
             _handle = GCHandle.Alloc(this);
 
-            _callback = new StreamCallbackDelegate(NativeCallbackHandler);
             MsQuicApi.Api.SetCallbackHandlerDelegate(
                 _ptr,
-                _callback,
+                s_streamDelegate,
                 GCHandle.ToIntPtr(_handle));
         }
 
@@ -921,7 +921,10 @@ namespace System.Net.Quic.Implementations.MsQuic
             return _sendResettableCompletionSource.GetTypelessValueTask();
         }
 
-        private void StartWrites()
+        /// <summary>
+        /// Assigns a stream ID and begins process the stream.
+        /// </summary>
+        private void StartLocalStream()
         {
             Debug.Assert(!_started);
             uint status = MsQuicApi.Api.StreamStartDelegate(
index ab68320..b873b78 100644 (file)
@@ -9,7 +9,7 @@ internal static partial class Interop
 {
     internal static class MsQuic
     {
-        [DllImport(Libraries.MsQuic)]
-        internal static unsafe extern uint MsQuicOpen(int version, out MsQuicNativeMethods.NativeApi* registration);
+        [DllImport(Libraries.MsQuic, CallingConvention = CallingConvention.Cdecl)]
+        internal static unsafe extern uint MsQuicOpen(out MsQuicNativeMethods.NativeApi* registration);
     }
 }
index 62f8fab..5fc281c 100644 (file)
@@ -3,19 +3,26 @@
 
 namespace System.Net.Quic.Implementations.MsQuic.Internal
 {
+    internal enum QUIC_EXECUTION_PROFILE : uint
+    {
+        QUIC_EXECUTION_PROFILE_LOW_LATENCY,         // Default
+        QUIC_EXECUTION_PROFILE_TYPE_MAX_THROUGHPUT,
+        QUIC_EXECUTION_PROFILE_TYPE_SCAVENGER,
+        QUIC_EXECUTION_PROFILE_TYPE_REAL_TIME
+    }
+
     /// <summary>
     /// Flags to pass when creating a security config.
     /// </summary>
     [Flags]
     internal enum QUIC_SEC_CONFIG_FLAG : uint
     {
-        NONE = 0,
         CERT_HASH = 0x00000001,
         CERT_HASH_STORE = 0x00000002,
         CERT_CONTEXT = 0x00000004,
         CERT_FILE = 0x00000008,
         ENABL_OCSP = 0x00000010,
-        CERT_NULL = 0xF0000000,
+        CERT_NULL = 0xF0000000 // TODO: only valid for stub TLS.
     }
 
     [Flags]
@@ -67,22 +74,30 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
         NONE = 0,
         ALLOW_0_RTT = 0x00000001,
         FIN = 0x00000002,
+        DGRAM_PRIORITY = 0x00000004
     }
 
     internal enum QUIC_PARAM_LEVEL : uint
     {
-        REGISTRATION = 0,
-        SESSION = 1,
-        LISTENER = 2,
-        CONNECTION = 3,
-        TLS = 4,
-        STREAM = 5,
+        GLOBAL,
+        REGISTRATION,
+        SESSION,
+        LISTENER,
+        CONNECTION,
+        TLS,
+        STREAM
     }
 
-    internal enum QUIC_PARAM_REGISTRATION : uint
+    internal enum QUIC_PARAM_GLOBAL : uint
     {
         RETRY_MEMORY_PERCENT = 0,
-        CID_PREFIX = 1
+        SUPPORTED_VERSIONS = 1,
+        LOAD_BALANCING_MODE = 2,
+    }
+
+    internal enum QUIC_PARAM_REGISTRATION : uint
+    {
+        CID_PREFIX = 0
     }
 
     internal enum QUIC_PARAM_SESSION : uint
@@ -92,7 +107,10 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
         PEER_UNIDI_STREAM_COUNT = 2,
         IDLE_TIMEOUT = 3,
         DISCONNECT_TIMEOUT = 4,
-        MAX_BYTES_PER_KEY = 5
+        MAX_BYTES_PER_KEY = 5,
+        MIGRATION_ENABLED = 6,
+        DATAGRAM_RECEIVE_ENABLED = 7,
+        SERVER_RESUMPTION_LEVEL = 8
     }
 
     internal enum QUIC_PARAM_LISTENER : uint
@@ -122,7 +140,11 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
         SEND_PACING = 16,
         SHARE_UDP_BINDING = 17,
         IDEAL_PROCESSOR = 18,
-        MAX_STREAM_IDS = 19
+        MAX_STREAM_IDS = 19,
+        STREAM_SCHEDULING_SCHEME = 20,
+        DATAGRAM_RECEIVE_ENABLED = 21,
+        DATAGRAM_SEND_ENABLED = 22,
+        DISABLE_1RTT_ENCRYPTION = 23
     }
 
     internal enum QUIC_PARAM_STREAM : uint
@@ -149,6 +171,11 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
         STREAMS_AVAILABLE = 7,
         PEER_NEEDS_STREAMS = 8,
         IDEAL_PROCESSOR_CHANGED = 9,
+        DATAGRAM_STATE_CHANGED = 10,
+        DATAGRAM_RECEIVED = 11,
+        DATAGRAM_SEND_STATE_CHANGED = 12,
+        RESUMED = 13,
+        RESUMPTION_TICKET_RECEIVED = 14
     }
 
     internal enum QUIC_STREAM_EVENT : uint
index 644787e..49903de 100644 (file)
@@ -16,8 +16,6 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
         [StructLayout(LayoutKind.Sequential)]
         internal struct NativeApi
         {
-            internal uint Version;
-
             internal IntPtr SetContext;
             internal IntPtr GetContext;
             internal IntPtr SetCallbackHandler;
@@ -44,6 +42,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             internal IntPtr ConnectionClose;
             internal IntPtr ConnectionShutdown;
             internal IntPtr ConnectionStart;
+            internal IntPtr ConnectionSendResumptionTicket;
 
             internal IntPtr StreamOpen;
             internal IntPtr StreamClose;
@@ -52,20 +51,26 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             internal IntPtr StreamSend;
             internal IntPtr StreamReceiveComplete;
             internal IntPtr StreamReceiveSetEnabled;
+
+            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,
             Delegate del,
             IntPtr context);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint SetParamDelegate(
             IntPtr handle,
             uint level,
@@ -73,6 +78,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             uint bufferLength,
             byte* buffer);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint GetParamDelegate(
             IntPtr handle,
             uint level,
@@ -80,36 +86,53 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             uint* bufferLength,
             byte* buffer);
 
-        internal delegate uint RegistrationOpenDelegate(byte[] appName, out IntPtr registrationContext);
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        internal delegate uint RegistrationOpenDelegate(ref RegistrationConfig config, out IntPtr registrationContext);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate void RegistrationCloseDelegate(IntPtr registrationContext);
 
+        [StructLayout(LayoutKind.Sequential)]
+        internal struct RegistrationConfig
+        {
+            [MarshalAs(UnmanagedType.LPUTF8Str)]
+            internal string AppName;
+            internal QUIC_EXECUTION_PROFILE 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.LPStr)]string? principal,
+            [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,
-            byte[] utf8String,
+            QuicBuffer *alpnBuffers,
+            uint alpnBufferCount,
             IntPtr context,
             ref IntPtr session);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate void SessionCloseDelegate(
             IntPtr session);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate void SessionShutdownDelegate(
             IntPtr session,
             uint flags,
-            ushort errorCode);
+            ulong errorCode);
 
         [StructLayout(LayoutKind.Sequential)]
         internal struct ListenerEvent
@@ -139,49 +162,58 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             internal uint QuicVersion;
             internal IntPtr LocalAddress;
             internal IntPtr RemoteAddress;
-            internal ushort CryptoBufferLength;
+            internal uint CryptoBufferLength;
             internal ushort AlpnListLength;
             internal ushort ServerNameLength;
+            internal byte NegotiatedAlpnLength;
             internal IntPtr CryptoBuffer;
-            internal IntPtr AlpnList;
+            internal IntPtr ClientAlpnList;
+            internal IntPtr NegotiatedAlpn;
             internal IntPtr ServerName;
         }
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ListenerCallbackDelegate(
             IntPtr listener,
             IntPtr context,
             ref ListenerEvent evt);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ListenerOpenDelegate(
            IntPtr session,
            ListenerCallbackDelegate handler,
            IntPtr context,
            out IntPtr listener);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ListenerCloseDelegate(
             IntPtr listener);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ListenerStartDelegate(
             IntPtr listener,
             ref SOCKADDR_INET localAddress);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ListenerStopDelegate(
             IntPtr listener);
 
         [StructLayout(LayoutKind.Sequential)]
         internal struct ConnectionEventDataConnected
         {
-            internal bool EarlyDataAccepted;
+            internal bool SessionResumed;
+            internal byte NegotiatedAlpnLength;
+            internal IntPtr NegotiatedAlpn;
         }
 
         [StructLayout(LayoutKind.Sequential)]
-        internal struct ConnectionEventDataShutdownBegin
+        internal struct ConnectionEventDataShutdownInitiatedByTransport
         {
             internal uint Status;
         }
 
         [StructLayout(LayoutKind.Sequential)]
-        internal struct ConnectionEventDataShutdownBeginPeer
+        internal struct ConnectionEventDataShutdownInitiatedByPeer
         {
             internal long ErrorCode;
         }
@@ -205,7 +237,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
         }
 
         [StructLayout(LayoutKind.Sequential)]
-        internal struct ConnectionEventDataNewStream
+        internal struct ConnectionEventDataStreamStarted
         {
             internal IntPtr Stream;
             internal QUIC_STREAM_OPEN_FLAG Flags;
@@ -218,12 +250,6 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             internal ushort UniDirectionalCount;
         }
 
-        [StructLayout(LayoutKind.Sequential)]
-        internal struct ConnectionEventDataIdealSendBuffer
-        {
-            internal ulong NumBytes;
-        }
-
         [StructLayout(LayoutKind.Explicit)]
         internal struct ConnectionEventDataUnion
         {
@@ -231,10 +257,10 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             internal ConnectionEventDataConnected Connected;
 
             [FieldOffset(0)]
-            internal ConnectionEventDataShutdownBegin ShutdownBegin;
+            internal ConnectionEventDataShutdownInitiatedByTransport ShutdownInitiatedByTransport;
 
             [FieldOffset(0)]
-            internal ConnectionEventDataShutdownBeginPeer ShutdownBeginPeer;
+            internal ConnectionEventDataShutdownInitiatedByPeer ShutdownInitiatedByPeer;
 
             [FieldOffset(0)]
             internal ConnectionEventDataShutdownComplete ShutdownComplete;
@@ -246,13 +272,10 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             internal ConnectionEventDataPeerAddrChanged PeerAddrChanged;
 
             [FieldOffset(0)]
-            internal ConnectionEventDataNewStream NewStream;
+            internal ConnectionEventDataStreamStarted StreamStarted;
 
             [FieldOffset(0)]
             internal ConnectionEventDataStreamsAvailable StreamsAvailable;
-
-            [FieldOffset(0)]
-            internal ConnectionEventDataIdealSendBuffer IdealSendBuffer;
         }
 
         [StructLayout(LayoutKind.Sequential)]
@@ -261,37 +284,42 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             internal QUIC_CONNECTION_EVENT Type;
             internal ConnectionEventDataUnion Data;
 
-            internal bool EarlyDataAccepted => Data.Connected.EarlyDataAccepted;
-            internal ulong NumBytes => Data.IdealSendBuffer.NumBytes;
-            internal uint ShutdownBeginStatus => Data.ShutdownBegin.Status;
-            internal long ShutdownBeginPeerStatus => Data.ShutdownBeginPeer.ErrorCode;
+            internal bool EarlyDataAccepted => Data.Connected.SessionResumed;
+            //internal ulong NumBytes => Data.IdealSendBuffer.NumBytes;
+            internal uint ShutdownBeginStatus => Data.ShutdownInitiatedByTransport.Status;
+            internal long ShutdownBeginPeerStatus => Data.ShutdownInitiatedByPeer.ErrorCode;
             internal bool ShutdownTimedOut => Data.ShutdownComplete.TimedOut;
             internal ushort BiDirectionalCount => Data.StreamsAvailable.BiDirectionalCount;
             internal ushort UniDirectionalCount => Data.StreamsAvailable.UniDirectionalCount;
-            internal QUIC_STREAM_OPEN_FLAG StreamFlags => Data.NewStream.Flags;
+            internal QUIC_STREAM_OPEN_FLAG StreamFlags => Data.StreamStarted.Flags;
         }
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ConnectionCallbackDelegate(
             IntPtr connection,
             IntPtr context,
             ref ConnectionEvent connectionEvent);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ConnectionOpenDelegate(
             IntPtr session,
             ConnectionCallbackDelegate handler,
             IntPtr context,
             out IntPtr connection);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ConnectionCloseDelegate(
             IntPtr connection);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ConnectionStartDelegate(
             IntPtr connection,
             ushort family,
-            [MarshalAs(UnmanagedType.LPStr)]
+            [MarshalAs(UnmanagedType.LPUTF8Str)]
             string serverName,
             ushort serverPort);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint ConnectionShutdownDelegate(
             IntPtr connection,
             uint flags,
@@ -304,21 +332,14 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             internal ulong TotalBufferLength;
             internal QuicBuffer* Buffers;
             internal uint BufferCount;
-            internal uint Flags;
+            internal QUIC_RECEIVE_FLAG Flags;
         }
 
-        [StructLayout(LayoutKind.Explicit)]
+        [StructLayout(LayoutKind.Sequential)]
         internal struct StreamEventDataSendComplete
         {
-            [FieldOffset(0)]
-            internal byte Canceled;
-            [FieldOffset(1)]
+            internal bool Canceled;
             internal IntPtr ClientContext;
-
-            internal bool IsCanceled()
-            {
-                return Canceled != 0;
-            }
         }
 
         [StructLayout(LayoutKind.Sequential)]
@@ -432,11 +453,13 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             internal ushort si_family;
         }
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint StreamCallbackDelegate(
             IntPtr stream,
             IntPtr context,
             ref StreamEvent streamEvent);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint StreamOpenDelegate(
             IntPtr connection,
             uint flags,
@@ -444,18 +467,22 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             IntPtr context,
             out IntPtr stream);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint StreamStartDelegate(
             IntPtr stream,
             uint flags);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint StreamCloseDelegate(
             IntPtr stream);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint StreamShutdownDelegate(
             IntPtr stream,
             uint flags,
             long errorCode);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint StreamSendDelegate(
             IntPtr stream,
             QuicBuffer* buffers,
@@ -463,12 +490,15 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
             uint flags,
             IntPtr clientSendContext);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint StreamReceiveCompleteDelegate(
             IntPtr stream,
             ulong bufferLength);
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
         internal delegate uint StreamReceiveSetEnabledDelegate(
             IntPtr stream,
+            [MarshalAs(UnmanagedType.U1)]
             bool enabled);
 
         [StructLayout(LayoutKind.Sequential)]
index 6c033a8..b290c5a 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Net.Test.Common
             var sslOpts = new SslServerAuthenticationOptions
             {
                 EnabledSslProtocols = options.SslProtocols,
-                ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
+                ApplicationProtocols = new List<SslApplicationProtocol> { new SslApplicationProtocol("h3") },
                 //ServerCertificate = _cert,
                 ClientCertificateRequired = false
             };
@@ -66,7 +66,7 @@ namespace System.Net.Test.Common
     {
         public static Http3LoopbackServerFactory Singleton { get; } = new Http3LoopbackServerFactory();
 
-        public override Version Version => HttpVersion.Version30;
+        public override Version Version { get; } = new Version(3, 0);
 
         public override GenericLoopbackServer CreateServer(GenericLoopbackOptions options = null)
         {
index 9d0e62e..7aaeb06 100644 (file)
@@ -20,6 +20,8 @@ namespace System.Net.Http.Functional.Tests
 
     public abstract partial class HttpClientHandlerTestBase : FileCleanupTestBase
     {
+        public static readonly Version HttpVersion30 = new Version(3, 0);
+
         public readonly ITestOutputHelper _output;
 
         protected virtual Version UseVersion => HttpVersion.Version11;
index c4400b6..ce534b0 100644 (file)
@@ -1,6 +1,9 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Globalization;
+using System.Text;
+
 namespace System.Net.Http.Headers
 {
     /// <remarks>
@@ -44,5 +47,30 @@ namespace System.Net.Http.Headers
         /// </summary>
         /// <remarks>TODO: if made public, this should be made internal as Persist is left open-ended and can be non-boolean in the future.</remarks>
         public bool Persist { get; }
+
+        public override string ToString()
+        {
+            StringBuilder sb = StringBuilderCache.Acquire(capacity: AlpnProtocolName.Length + (Host?.Length ?? 0) + 64);
+
+            sb.Append(AlpnProtocolName);
+            sb.Append("=\"");
+            if (Host != null) sb.Append(Host);
+            sb.Append(':');
+            sb.Append(Port.ToString(CultureInfo.InvariantCulture));
+            sb.Append('"');
+
+            if (MaxAge != TimeSpan.FromTicks(AltSvcHeaderParser.DefaultMaxAgeTicks))
+            {
+                sb.Append("; ma=");
+                sb.Append((MaxAge.Ticks / TimeSpan.TicksPerSecond).ToString(CultureInfo.InvariantCulture));
+            }
+
+            if (Persist)
+            {
+                sb.Append("; persist=1");
+            }
+
+            return StringBuilderCache.GetStringAndRelease(sb);
+        }
     }
 }
index bb59149..289fd7a 100644 (file)
@@ -490,7 +490,7 @@ namespace System.Net.Http
                 //  - On stream 0, the origin will be specified. HTTP/2 can service multiple origins per connection, and so the server can report origins other than what our pool is using.
                 //  - Otherwise, the origin is implicitly defined by the request stream and must be of length 0.
 
-                if ((frameHeader.StreamId != 0 && originLength == 0) || (frameHeader.StreamId == 0 && span.Length >= originLength && span.Slice(originLength).SequenceEqual(_pool.Http2AltSvcOriginUri)))
+                if ((frameHeader.StreamId != 0 && originLength == 0) || (frameHeader.StreamId == 0 && span.Length >= originLength && span.Slice(0, originLength).SequenceEqual(_pool.Http2AltSvcOriginUri)))
                 {
                     span = span.Slice(originLength);
 
index 54fe0e9..9ee2bdf 100644 (file)
@@ -9,11 +9,16 @@ using System.IO;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Net.Http.Headers;
+using System.Net.Security;
 
 namespace System.Net.Http
 {
     internal sealed class Http3Connection : HttpConnectionBase, IDisposable
     {
+        // TODO: once HTTP/3 is standardized, create APIs for these.
+        public static readonly Version HttpVersion30 = new Version(3, 0);
+        public static readonly SslApplicationProtocol Http3ApplicationProtocol = new SslApplicationProtocol("h3");
+
         /// <summary>
         /// If we receive a settings frame larger than this, tear down the connection with an error.
         /// </summary>
index 72e8090..b5afcb5 100644 (file)
@@ -12,8 +12,6 @@ using System.Threading.Tasks;
 using System.Runtime.CompilerServices;
 using System.Net.Http.QPack;
 using System.Runtime.ExceptionServices;
-using System.Buffers;
-using System.Diagnostics.CodeAnalysis;
 
 namespace System.Net.Http
 {
@@ -292,11 +290,9 @@ namespace System.Net.Http
         /// <summary>
         /// Waits for the initial response headers to be completed, including e.g. Expect 100 Continue.
         /// </summary>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         private async Task ReadResponseAsync(CancellationToken cancellationToken)
         {
-            Debug.Assert(_response != null);
+            Debug.Assert(_response == null);
             do
             {
                 _headerState = HeaderState.StatusHeader;
@@ -313,6 +309,7 @@ namespace System.Net.Http
                 }
 
                 await ReadHeadersAsync(payloadLength, cancellationToken).ConfigureAwait(false);
+                Debug.Assert(_response != null);
             }
             while ((int)_response.StatusCode < 200);
 
@@ -886,7 +883,7 @@ namespace System.Net.Http
 
                 _response = new HttpResponseMessage()
                 {
-                    Version = HttpVersion.Version30,
+                    Version = Http3Connection.HttpVersion30,
                     RequestMessage = _request,
                     Content = new HttpConnectionResponseContent(),
                     StatusCode = (HttpStatusCode)statusCode
index de4a6a5..43a4181 100644 (file)
@@ -31,8 +31,7 @@ namespace System.Net.Http
 
         public bool Equals(HttpAuthority? other)
         {
-            Debug.Assert(other != null);
-            return string.Equals(IdnHost, other.IdnHost) && Port == other.Port;
+            return other != null && string.Equals(IdnHost, other.IdnHost) && Port == other.Port;
         }
 
         public override bool Equals(object? obj)
index af9e2cd..e2c2e36 100644 (file)
@@ -124,7 +124,7 @@ namespace System.Net.Http
             }
 
             _http2Enabled = _poolManager.Settings._maxHttpVersion >= HttpVersion.Version20;
-            _http3Enabled = _poolManager.Settings._maxHttpVersion >= HttpVersion.Version30;
+            _http3Enabled = _poolManager.Settings._maxHttpVersion >= Http3Connection.HttpVersion30;
 
             switch (kind)
             {
@@ -259,7 +259,7 @@ namespace System.Net.Http
             if (NetEventSource.Log.IsEnabled()) Trace($"{this}");
         }
 
-        private static readonly List<SslApplicationProtocol> s_http3ApplicationProtocols = new List<SslApplicationProtocol>() { SslApplicationProtocol.Http3 };
+        private static readonly List<SslApplicationProtocol> s_http3ApplicationProtocols = new List<SslApplicationProtocol>() { Http3Connection.Http3ApplicationProtocol };
         private static readonly List<SslApplicationProtocol> s_http2ApplicationProtocols = new List<SslApplicationProtocol>() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11 };
 
         private static SslClientAuthenticationOptions ConstructSslOptions(HttpConnectionPoolManager poolManager, string sslHostName)
@@ -883,7 +883,7 @@ namespace System.Net.Http
             {
                 int parseIdx = 0;
 
-                while (AltSvcHeaderParser.Parser.TryParseValue(altSvcHeaderValue, null, ref parseIdx, out object? parsedValue))
+                if (AltSvcHeaderParser.Parser.TryParseValue(altSvcHeaderValue, null, ref parseIdx, out object? parsedValue))
                 {
                     var value = (AltSvcHeaderValue?)parsedValue;
 
index 507af88..42b67aa 100644 (file)
@@ -69,7 +69,7 @@ namespace System.Net.Http
         {
             bool allowHttp2 = AllowHttp2;
             _maxHttpVersion =
-                AllowDraftHttp3 && allowHttp2 ? HttpVersion.Version30 :
+                AllowDraftHttp3 && allowHttp2 ? Http3Connection.HttpVersion30 :
                 allowHttp2 ? HttpVersion.Version20 :
                 HttpVersion.Version11;
             _allowUnencryptedHttp2 = allowHttp2 && AllowUnencryptedHttp2;
index b09815b..7fcd937 100644 (file)
@@ -20,43 +20,59 @@ namespace System.Net.Http.Functional.Tests
         /// </summary>
         protected override HttpClient CreateHttpClient()
         {
-            HttpClientHandler handler = CreateHttpClientHandler(HttpVersion.Version30);
+            bool http3Enabled = (bool)typeof(SocketsHttpHandler)
+                .Assembly
+                .GetType("System.Net.Http.HttpConnectionSettings", throwOnError: true)
+                .GetProperty("AllowDraftHttp3", Reflection.BindingFlags.Static | Reflection.BindingFlags.NonPublic)
+                .GetValue(null);
+
+            Assert.True(http3Enabled, "HTTP/3 draft support must be enabled for this test.");
+
+            HttpClientHandler handler = CreateHttpClientHandler(HttpVersion30);
             SetUsePrenegotiatedHttp3(handler, usePrenegotiatedHttp3: false);
 
             return CreateHttpClient(handler);
         }
 
-        [Fact]
-        public async Task AltSvc_Header_UpgradeFrom11_Success()
+        [Theory]
+        [MemberData(nameof(AltSvcHeaderUpgradeVersions))]
+        public async Task AltSvc_Header_Upgrade_Success(Version fromVersion)
         {
-            await AltSvc_Header_Upgrade_Success(HttpVersion.Version11).ConfigureAwait(false);
-        }
+            // The test makes a request to a HTTP/1 or HTTP/2 server first, which supplies an Alt-Svc header pointing to the second server.
+            using GenericLoopbackServer firstServer =
+                fromVersion.Major switch
+                {
+                    1 => new LoopbackServer(new LoopbackServer.Options { UseSsl = true }),
+                    2 => Http2LoopbackServer.CreateServer(),
+                    _ => throw new Exception("Unknown HTTP version.")
+                };
+
+            // The second request is expected to come in on this HTTP/3 server.
+            using var secondServer = new Http3LoopbackServer();
 
-        [Fact]
-        public async Task AltSvc_Header_UpgradeFrom20_Success()
-        {
-            await AltSvc_Header_Upgrade_Success(HttpVersion.Version20).ConfigureAwait(false);
-        }
-
-        private async Task AltSvc_Header_Upgrade_Success(Version fromVersion)
-        {
-            using GenericLoopbackServer firstServer = GetFactoryForVersion(fromVersion).CreateServer();
-            using Http3LoopbackServer secondServer = new Http3LoopbackServer();
             using HttpClient client = CreateHttpClient();
 
             Task<HttpResponseMessage> firstResponseTask = client.GetAsync(firstServer.Address);
-
-            await firstServer.AcceptConnectionSendResponseAndCloseAsync(additionalHeaders: new[]
+            Task serverTask = firstServer.AcceptConnectionSendResponseAndCloseAsync(additionalHeaders: new[]
             {
-                new HttpHeaderData("Alt-Svc", $"h3={secondServer.Address.IdnHost}:{secondServer.Address.Port}")
+                new HttpHeaderData("Alt-Svc", $"h3=\"{secondServer.Address.IdnHost}:{secondServer.Address.Port}\"")
             });
 
-            HttpResponseMessage firstResponse = await firstResponseTask;
+            await new[] { firstResponseTask, serverTask }.WhenAllOrAnyFailed(30_000);
+
+            using HttpResponseMessage firstResponse = firstResponseTask.Result;
             Assert.True(firstResponse.IsSuccessStatusCode);
 
             await AltSvc_Upgrade_Success(firstServer, secondServer, client);
         }
 
+        public static TheoryData<Version> AltSvcHeaderUpgradeVersions =>
+            new TheoryData<Version>
+            {
+                { HttpVersion.Version11 },
+                { HttpVersion.Version20 }
+            };
+
         [Fact]
         public async Task AltSvc_ConnectionFrame_UpgradeFrom20_Success()
         {
@@ -65,15 +81,18 @@ namespace System.Net.Http.Functional.Tests
             using HttpClient client = CreateHttpClient();
 
             Task<HttpResponseMessage> firstResponseTask = client.GetAsync(firstServer.Address);
-
-            using (Http2LoopbackConnection connection = await firstServer.EstablishConnectionAsync())
+            Task serverTask = Task.Run(async () =>
             {
+                using Http2LoopbackConnection connection = await firstServer.EstablishConnectionAsync();
+
                 int streamId = await connection.ReadRequestHeaderAsync();
+                await connection.WriteFrameAsync(new AltSvcFrame($"https://{firstServer.Address.IdnHost}:{firstServer.Address.Port}", $"h3=\"{secondServer.Address.IdnHost}:{secondServer.Address.Port}\"", streamId: 0));
                 await connection.SendDefaultResponseAsync(streamId);
-                await connection.WriteFrameAsync(new AltSvcFrame($"https://{firstServer.Address.IdnHost}:{firstServer.Address.Port}", $"h3={secondServer.Address.IdnHost}:{secondServer.Address.Port}", 0));
-            }
+            });
+
+            await new[] { firstResponseTask, serverTask }.WhenAllOrAnyFailed(30_000);
 
-            HttpResponseMessage firstResponse = await firstResponseTask;
+            HttpResponseMessage firstResponse = firstResponseTask.Result;
             Assert.True(firstResponse.IsSuccessStatusCode);
 
             await AltSvc_Upgrade_Success(firstServer, secondServer, client);
@@ -87,16 +106,19 @@ namespace System.Net.Http.Functional.Tests
             using HttpClient client = CreateHttpClient();
 
             Task<HttpResponseMessage> firstResponseTask = client.GetAsync(firstServer.Address);
-
-            using (Http2LoopbackConnection connection = await firstServer.EstablishConnectionAsync())
+            Task serverTask = Task.Run(async () =>
             {
+                using Http2LoopbackConnection connection = await firstServer.EstablishConnectionAsync();
+
                 int streamId = await connection.ReadRequestHeaderAsync();
                 await connection.SendDefaultResponseHeadersAsync(streamId);
-                await connection.WriteFrameAsync(new AltSvcFrame("", $"h3={secondServer.Address.IdnHost}:{secondServer.Address.Port}", streamId));
+                await connection.WriteFrameAsync(new AltSvcFrame("", $"h3=\"{secondServer.Address.IdnHost}:{secondServer.Address.Port}\"", streamId));
                 await connection.SendResponseDataAsync(streamId, Array.Empty<byte>(), true);
-            }
+            });
+
+            await new[] { firstResponseTask, serverTask }.WhenAllOrAnyFailed(30_000);
 
-            HttpResponseMessage firstResponse = await firstResponseTask;
+            HttpResponseMessage firstResponse = firstResponseTask.Result;
             Assert.True(firstResponse.IsSuccessStatusCode);
 
             await AltSvc_Upgrade_Success(firstServer, secondServer, client);
@@ -105,12 +127,15 @@ namespace System.Net.Http.Functional.Tests
         private async Task AltSvc_Upgrade_Success(GenericLoopbackServer firstServer, Http3LoopbackServer secondServer, HttpClient client)
         {
             Task<HttpResponseMessage> secondResponseTask = client.GetAsync(firstServer.Address);
+            Task<HttpRequestData> secondRequestTask = secondServer.AcceptConnectionSendResponseAndCloseAsync();
+
+            await new[] { (Task)secondResponseTask, secondRequestTask }.WhenAllOrAnyFailed(30_000);
+
+            HttpRequestData secondRequest = secondRequestTask.Result;
+            using HttpResponseMessage secondResponse = secondResponseTask.Result;
 
-            HttpRequestData secondRequest = await secondServer.AcceptConnectionSendResponseAndCloseAsync();
             string altUsed = secondRequest.GetSingleHeaderValue("Alt-Used");
             Assert.Equal($"{secondServer.Address.IdnHost}:{secondServer.Address.Port}", altUsed);
-
-            HttpResponseMessage secondResponse = await secondResponseTask;
             Assert.True(secondResponse.IsSuccessStatusCode);
         }
     }
index 145ecd0..27d4b77 100644 (file)
@@ -19,7 +19,7 @@ namespace System.Net.Http.Functional.Tests
 {
     public abstract class HttpClientHandlerTest_Http3 : HttpClientHandlerTestBase
     {
-        protected override Version UseVersion => HttpVersion.Version30;
+        protected override Version UseVersion => HttpVersion30;
 
         public static bool SupportsAlpn => PlatformDetection.SupportsAlpn;
 
index 90fddb4..bc718b2 100644 (file)
@@ -22,7 +22,7 @@ namespace System.Net.Http.Functional.Tests
                 handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
             }
 
-            if (useVersion == HttpVersion.Version30)
+            if (useVersion == HttpVersion30)
             {
                 SetUsePrenegotiatedHttp3(handler, usePrenegotiatedHttp3: true);
             }
index 8a316d0..5c89f69 100644 (file)
@@ -35,7 +35,7 @@ namespace System.Net.Http.Functional.Tests
     public sealed class SocketsHttpHandler_HttpClientMiniStress_Http3 : HttpClientMiniStress
     {
         public SocketsHttpHandler_HttpClientMiniStress_Http3(ITestOutputHelper output) : base(output) { }
-        protected override Version UseVersion => HttpVersion.Version30;
+        protected override Version UseVersion => HttpVersion30;
     }
 
     public sealed class SocketsHttpHandler_HttpClientMiniStress_Http2 : HttpClientMiniStress
index a8a5b93..76fe3c5 100644 (file)
@@ -216,41 +216,75 @@ namespace System.Net.Quic.Tests
         }
 
         [Fact]
+        public async Task TestConnect()
+        {
+            SslServerAuthenticationOptions serverOpts = GetSslServerAuthenticationOptions();
+
+            using QuicListener listener = new QuicListener(
+                QuicImplementationProviders.MsQuic,
+                new IPEndPoint(IPAddress.Loopback, 0),
+                serverOpts);
+
+            listener.Start();
+            IPEndPoint listenEndPoint = listener.ListenEndPoint;
+            Assert.NotEqual(0, listenEndPoint.Port);
+
+            using QuicConnection clientConnection = new QuicConnection(
+                QuicImplementationProviders.MsQuic,
+                listenEndPoint,
+                GetSslClientAuthenticationOptions());
+
+            Assert.False(clientConnection.Connected);
+            Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
+
+            ValueTask connectTask = clientConnection.ConnectAsync();
+            QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+            await connectTask;
+
+            Assert.True(clientConnection.Connected);
+            Assert.True(serverConnection.Connected);
+            Assert.Equal(listenEndPoint, serverConnection.LocalEndPoint);
+            Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
+            Assert.Equal(clientConnection.LocalEndPoint, serverConnection.RemoteEndPoint);
+            Assert.Equal(serverOpts.ApplicationProtocols[0].ToString(), clientConnection.NegotiatedApplicationProtocol.ToString());
+            Assert.Equal(serverOpts.ApplicationProtocols[0].ToString(), serverConnection.NegotiatedApplicationProtocol.ToString());
+        }
+
+        [Fact]
         public async Task TestStreams()
         {
-            using (QuicListener listener = new QuicListener(
+            using QuicListener listener = new QuicListener(
                 QuicImplementationProviders.MsQuic,
                 new IPEndPoint(IPAddress.Loopback, 0),
-                GetSslServerAuthenticationOptions()))
-            {
-                listener.Start();
-                IPEndPoint listenEndPoint = listener.ListenEndPoint;
+                GetSslServerAuthenticationOptions());
 
-                using (QuicConnection clientConnection = new QuicConnection(
-                    QuicImplementationProviders.MsQuic,
-                    listenEndPoint,
-                    sslClientAuthenticationOptions: new SslClientAuthenticationOptions { ApplicationProtocols = new List<SslApplicationProtocol>() { new SslApplicationProtocol("quictest") } }))
-                {
-                    Assert.False(clientConnection.Connected);
-                    Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
-
-                    ValueTask connectTask = clientConnection.ConnectAsync();
-                    QuicConnection serverConnection = await listener.AcceptConnectionAsync();
-                    await connectTask;
-
-                    Assert.True(clientConnection.Connected);
-                    Assert.True(serverConnection.Connected);
-                    Assert.Equal(listenEndPoint, serverConnection.LocalEndPoint);
-                    Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
-                    Assert.Equal(clientConnection.LocalEndPoint, serverConnection.RemoteEndPoint);
-
-                    await CreateAndTestBidirectionalStream(clientConnection, serverConnection);
-                    await CreateAndTestBidirectionalStream(serverConnection, clientConnection);
-                    await CreateAndTestUnidirectionalStream(serverConnection, clientConnection);
-                    await CreateAndTestUnidirectionalStream(clientConnection, serverConnection);
-                    await clientConnection.CloseAsync(errorCode: 0);
-                }
-            }
+            listener.Start();
+            IPEndPoint listenEndPoint = listener.ListenEndPoint;
+            Assert.NotEqual(0, listenEndPoint.Port);
+
+            using QuicConnection clientConnection = new QuicConnection(
+                QuicImplementationProviders.MsQuic,
+                listenEndPoint,
+                GetSslClientAuthenticationOptions());
+
+            Assert.False(clientConnection.Connected);
+            Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
+
+            ValueTask connectTask = clientConnection.ConnectAsync();
+            QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+            await connectTask;
+
+            Assert.True(clientConnection.Connected);
+            Assert.True(serverConnection.Connected);
+            Assert.Equal(listenEndPoint, serverConnection.LocalEndPoint);
+            Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
+            Assert.Equal(clientConnection.LocalEndPoint, serverConnection.RemoteEndPoint);
+
+            await CreateAndTestBidirectionalStream(clientConnection, serverConnection);
+            await CreateAndTestBidirectionalStream(serverConnection, clientConnection);
+            await CreateAndTestUnidirectionalStream(serverConnection, clientConnection);
+            await CreateAndTestUnidirectionalStream(clientConnection, serverConnection);
+            await clientConnection.CloseAsync(errorCode: 0);
         }
 
         [Fact]
@@ -336,36 +370,31 @@ namespace System.Net.Quic.Tests
 
         private static async Task CreateAndTestBidirectionalStream(QuicConnection c1, QuicConnection c2)
         {
-            using (QuicStream s1 = c1.OpenBidirectionalStream())
-            {
-                Assert.True(s1.CanRead);
-                Assert.True(s1.CanWrite);
+            using QuicStream s1 = c1.OpenBidirectionalStream();
+            Assert.True(s1.CanRead);
+            Assert.True(s1.CanWrite);
 
-                ValueTask writeTask = s1.WriteAsync(s_data);
-                using (QuicStream s2 = await c2.AcceptStreamAsync())
-                {
-                    await ReceiveDataAsync(s_data, s2);
-                    await writeTask;
-                    await TestBidirectionalStream(s1, s2);
-                }
-            }
+            ValueTask writeTask = s1.WriteAsync(s_data);
+
+            using QuicStream s2 = await c2.AcceptStreamAsync();
+            await ReceiveDataAsync(s_data, s2);
+            await writeTask;
+            await TestBidirectionalStream(s1, s2);
         }
 
         private static async Task CreateAndTestUnidirectionalStream(QuicConnection c1, QuicConnection c2)
         {
-            using (QuicStream s1 = c1.OpenUnidirectionalStream())
-            {
-                Assert.False(s1.CanRead);
-                Assert.True(s1.CanWrite);
+            using QuicStream s1 = c1.OpenUnidirectionalStream();
 
-                ValueTask writeTask = s1.WriteAsync(s_data);
-                using (QuicStream s2 = await c2.AcceptStreamAsync())
-                {
-                    await ReceiveDataAsync(s_data, s2);
-                    await writeTask;
-                    await TestUnidirectionalStream(s1, s2);
-                }
-            }
+            Assert.False(s1.CanRead);
+            Assert.True(s1.CanWrite);
+
+            ValueTask writeTask = s1.WriteAsync(s_data);
+
+            using QuicStream s2 = await c2.AcceptStreamAsync();
+            await ReceiveDataAsync(s_data, s2);
+            await writeTask;
+            await TestUnidirectionalStream(s1, s2);
         }
 
         private static async Task TestBidirectionalStream(QuicStream s1, QuicStream s2)
index 18b6036..f096461 100644 (file)
@@ -2323,7 +2323,7 @@ namespace System.Net.Http.Functional.Tests
     public sealed class SocketsHttpHandler_HttpClientHandler_Finalization_Http3_Test : HttpClientHandler_Finalization_Test
     {
         public SocketsHttpHandler_HttpClientHandler_Finalization_Http3_Test(ITestOutputHelper output) : base(output) { }
-        protected override Version UseVersion => HttpVersion.Version30;
+        protected override Version UseVersion => HttpVersion30;
     }
 
     [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
@@ -2336,34 +2336,34 @@ namespace System.Net.Http.Functional.Tests
     public sealed class SocketsHttpHandlerTest_Cookies_Http3 : HttpClientHandlerTest_Cookies
     {
         public SocketsHttpHandlerTest_Cookies_Http3(ITestOutputHelper output) : base(output) { }
-        protected override Version UseVersion => HttpVersion.Version30;
+        protected override Version UseVersion => HttpVersion30;
     }
 
     [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
     public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Http3 : HttpClientHandlerTest
     {
         public SocketsHttpHandlerTest_HttpClientHandlerTest_Http3(ITestOutputHelper output) : base(output) { }
-        protected override Version UseVersion => HttpVersion.Version30;
+        protected override Version UseVersion => HttpVersion30;
     }
 
     [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
     public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3 : HttpClientHandlerTest_Headers
     {
         public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3(ITestOutputHelper output) : base(output) { }
-        protected override Version UseVersion => HttpVersion.Version30;
+        protected override Version UseVersion => HttpVersion30;
     }
 
     [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
     public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3 : HttpClientHandler_Cancellation_Test
     {
         public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3(ITestOutputHelper output) : base(output) { }
-        protected override Version UseVersion => HttpVersion.Version30;
+        protected override Version UseVersion => HttpVersion30;
     }
 
     [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
     public sealed class SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3 : HttpClientHandler_AltSvc_Test
     {
         public SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3(ITestOutputHelper output) : base(output) { }
-        protected override Version UseVersion => HttpVersion.Version30;
+        protected override Version UseVersion => HttpVersion30;
     }
 }
index 979baf5..899fa7d 100644 (file)
@@ -208,7 +208,6 @@ namespace System.Net
         public static readonly System.Version Version10;
         public static readonly System.Version Version11;
         public static readonly System.Version Version20;
-        public static readonly System.Version Version30;
     }
     public partial interface ICredentials
     {
index f293df8..55a2b50 100644 (file)
@@ -9,6 +9,5 @@ namespace System.Net
         public static readonly Version Version10 = new Version(1, 0);
         public static readonly Version Version11 = new Version(1, 1);
         public static readonly Version Version20 = new Version(2, 0);
-        public static readonly Version Version30 = new Version(3, 0);
     }
 }
index 3ef7ad8..9092eb2 100644 (file)
@@ -119,7 +119,6 @@ namespace System.Net.Security
         private readonly int _dummyPrimitive;
         public static readonly System.Net.Security.SslApplicationProtocol Http11;
         public static readonly System.Net.Security.SslApplicationProtocol Http2;
-        public static readonly System.Net.Security.SslApplicationProtocol Http3;
         public SslApplicationProtocol(byte[] protocol) { throw null; }
         public SslApplicationProtocol(string protocol) { throw null; }
         public System.ReadOnlyMemory<byte> Protocol { get { throw null; } }
index 0c1e323..6b1a912 100644 (file)
@@ -10,13 +10,10 @@ namespace System.Net.Security
     public readonly struct SslApplicationProtocol : IEquatable<SslApplicationProtocol>
     {
         private static readonly Encoding s_utf8 = Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
-        private static readonly byte[] s_http3Utf8 = new byte[] { 0x68, 0x33 }; // "h3"
         private static readonly byte[] s_http2Utf8 = new byte[] { 0x68, 0x32 }; // "h2"
         private static readonly byte[] s_http11Utf8 = new byte[] { 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31 }; // "http/1.1"
 
         // Refer to IANA on ApplicationProtocols: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
-        // h3
-        public static readonly SslApplicationProtocol Http3 = new SslApplicationProtocol(s_http3Utf8, copy: false);
         // h2
         public static readonly SslApplicationProtocol Http2 = new SslApplicationProtocol(s_http2Utf8, copy: false);
         // http/1.1
index e365703..7140fea 100644 (file)
@@ -111,7 +111,6 @@ namespace System.Net.Security
             None = 0,
             Http11 = 1,
             Http2 = 2,
-            Http3 = 4,
             Other = 128
         }
 
@@ -673,10 +672,6 @@ namespace System.Net.Security
                     {
                         alpn |= ApplicationProtocolInfo.Http2;
                     }
-                    else if (protocol.SequenceEqual(SslApplicationProtocol.Http3.Protocol.Span))
-                    {
-                        alpn |= ApplicationProtocolInfo.Http3;
-                    }
                     else
                     {
                         alpn |= ApplicationProtocolInfo.Other;