From: Cory Nelson Date: Thu, 6 Aug 2020 19:43:49 +0000 (-0700) Subject: QUIC and HTTP/3 fixes (#40468) X-Git-Tag: submit/tizen/20210909.063632~6175 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a34177e1ec1f32720daa07e4e88d955cd23c397c;p=platform%2Fupstream%2Fdotnet%2Fruntime.git QUIC and HTTP/3 fixes (#40468) 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. --- diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs index 38eb74b..a06a08f 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs @@ -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) { diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index 73c3e55..5c62f1d 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -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( 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."); diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs index e35493a..d5f1c11 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs @@ -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() { diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicConnection.cs index 0046c56..708e15e 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicConnection.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -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 _connectTcs = new ResettableCompletionSource(); - private readonly ResettableCompletionSource _shutdownTcs = new ResettableCompletionSource(); + private SslApplicationProtocol _negotiatedAlpnProtocol; + + // TODO: only allocate these when there is an outstanding connect/shutdown. + private readonly TaskCompletionSource _connectTcs = new TaskCompletionSource(); + private readonly TaskCompletionSource _shutdownTcs = new TaskCompletionSource(); 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) diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicListener.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicListener.cs index c8d3388..721a8e5 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicListener.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicListener.cs @@ -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)); } diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicStream.cs index 6b8e9ce..fc451a7 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -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 _receiveQuicBuffers = new List(); + private readonly List _receiveQuicBuffers = new List(); // 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(); 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() + /// + /// Assigns a stream ID and begins process the stream. + /// + private void StartLocalStream() { Debug.Assert(!_started); uint status = MsQuicApi.Api.StreamStartDelegate( diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/Interop.MsQuic.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/Interop.MsQuic.cs index ab68320..b873b78 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/Interop.MsQuic.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/Interop.MsQuic.cs @@ -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); } } diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicEnums.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicEnums.cs index 62f8fab..5fc281c 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicEnums.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicEnums.cs @@ -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 + } + /// /// Flags to pass when creating a security config. /// [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 diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicNativeMethods.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicNativeMethods.cs index 644787e..49903de 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicNativeMethods.cs +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicNativeMethods.cs @@ -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)] diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs index 6c033a8..b290c5a 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs @@ -28,7 +28,7 @@ namespace System.Net.Test.Common var sslOpts = new SslServerAuthenticationOptions { EnabledSslProtocols = options.SslProtocols, - ApplicationProtocols = new List { SslApplicationProtocol.Http3 }, + ApplicationProtocols = new List { 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) { diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs index 9d0e62e..7aaeb06 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs @@ -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; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs index c4400b6..ce534b0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderValue.cs @@ -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 { /// @@ -44,5 +47,30 @@ namespace System.Net.Http.Headers /// /// TODO: if made public, this should be made internal as Persist is left open-ended and can be non-boolean in the future. 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); + } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index bb59149..289fd7a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -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); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs index 54fe0e9..9ee2bdf 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs @@ -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"); + /// /// If we receive a settings frame larger than this, tear down the connection with an error. /// diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs index 72e8090..b5afcb5 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs @@ -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 /// /// Waits for the initial response headers to be completed, including e.g. Expect 100 Continue. /// - /// - /// 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 diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpAuthority.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpAuthority.cs index de4a6a5..43a4181 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpAuthority.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpAuthority.cs @@ -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) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index af9e2cd..e2c2e36 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -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 s_http3ApplicationProtocols = new List() { SslApplicationProtocol.Http3 }; + private static readonly List s_http3ApplicationProtocols = new List() { Http3Connection.Http3ApplicationProtocol }; private static readonly List s_http2ApplicationProtocols = new List() { 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; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 507af88..42b67aa 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -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; diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AltSvc.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AltSvc.cs index b09815b..7fcd937 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AltSvc.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AltSvc.cs @@ -20,43 +20,59 @@ namespace System.Net.Http.Functional.Tests /// 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 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 AltSvcHeaderUpgradeVersions => + new TheoryData + { + { 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 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 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(), 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 secondResponseTask = client.GetAsync(firstServer.Address); + Task 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); } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs index 145ecd0..27d4b77 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs @@ -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; diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs index 90fddb4..bc718b2 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs @@ -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); } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs index 8a316d0..5c89f69 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs @@ -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 diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/MsQuicTests.cs index a8a5b93..76fe3c5 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/MsQuicTests.cs @@ -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() { 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) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index 18b6036..f096461 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -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; } } diff --git a/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs b/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs index 979baf5..899fa7d 100644 --- a/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs +++ b/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs @@ -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 { diff --git a/src/libraries/System.Net.Primitives/src/System/Net/HttpVersion.cs b/src/libraries/System.Net.Primitives/src/System/Net/HttpVersion.cs index f293df8..55a2b50 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/HttpVersion.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/HttpVersion.cs @@ -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); } } diff --git a/src/libraries/System.Net.Security/ref/System.Net.Security.cs b/src/libraries/System.Net.Security/ref/System.Net.Security.cs index 3ef7ad8..9092eb2 100644 --- a/src/libraries/System.Net.Security/ref/System.Net.Security.cs +++ b/src/libraries/System.Net.Security/ref/System.Net.Security.cs @@ -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 Protocol { get { throw null; } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslApplicationProtocol.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslApplicationProtocol.cs index 0c1e323..6b1a912 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslApplicationProtocol.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslApplicationProtocol.cs @@ -10,13 +10,10 @@ namespace System.Net.Security public readonly struct SslApplicationProtocol : IEquatable { 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 diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs b/src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs index e365703..7140fea 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs @@ -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;