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.
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)
{
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);
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;
}
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.");
QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ConnectionOpenDelegate(
_nativeObjPtr,
- MsQuicConnection.NativeCallbackHandler,
+ MsQuicConnection.s_connectionDelegate,
IntPtr.Zero,
out IntPtr connectionPtr),
"Could not open the connection.");
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()
{
// 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;
{
internal sealed class MsQuicConnection : QuicConnectionProvider
{
- private MsQuicSession? _session;
+ private readonly MsQuicSession? _session;
// Pointer to the underlying connection
// TODO replace all IntPtr with SafeHandles
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;
_localEndPoint = localEndPoint;
_remoteEndPoint = remoteEndPoint;
_ptr = nativeObjPtr;
+ _connected = true;
SetCallbackHandler();
SetIdleTimeout(TimeSpan.FromSeconds(120));
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;
}
(ushort)_remoteEndPoint.Port),
"Failed to connect to peer.");
- return _connectTcs.GetTypelessValueTask();
+ return new ValueTask(_connectTcs.Task);
}
private MsQuicStream StreamOpen(
MsQuicApi.Api.StreamOpenDelegate(
_ptr,
(uint)flags,
- MsQuicStream.NativeCallbackHandler,
+ MsQuicStream.s_streamDelegate,
IntPtr.Zero,
out streamPtr),
"Failed to open stream to peer.");
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));
}
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)
// 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;
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
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;
{
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
{
{
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
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;
}
}
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));
}
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;
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)
_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();
}
}
CleanupSendState();
// TODO throw if a write was canceled.
- uint errorCode = evt.Data.SendComplete.Canceled;
bool shouldComplete = false;
lock (_sync)
{
_handle = GCHandle.Alloc(this);
- _callback = new StreamCallbackDelegate(NativeCallbackHandler);
MsQuicApi.Api.SetCallbackHandlerDelegate(
_ptr,
- _callback,
+ s_streamDelegate,
GCHandle.ToIntPtr(_handle));
}
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(
{
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);
}
}
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]
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
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
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
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
[StructLayout(LayoutKind.Sequential)]
internal struct NativeApi
{
- internal uint Version;
-
internal IntPtr SetContext;
internal IntPtr GetContext;
internal IntPtr SetCallbackHandler;
internal IntPtr ConnectionClose;
internal IntPtr ConnectionShutdown;
internal IntPtr ConnectionStart;
+ internal IntPtr ConnectionSendResumptionTicket;
internal IntPtr StreamOpen;
internal IntPtr StreamClose;
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,
uint bufferLength,
byte* buffer);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint GetParamDelegate(
IntPtr handle,
uint level,
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
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;
}
}
[StructLayout(LayoutKind.Sequential)]
- internal struct ConnectionEventDataNewStream
+ internal struct ConnectionEventDataStreamStarted
{
internal IntPtr Stream;
internal QUIC_STREAM_OPEN_FLAG Flags;
internal ushort UniDirectionalCount;
}
- [StructLayout(LayoutKind.Sequential)]
- internal struct ConnectionEventDataIdealSendBuffer
- {
- internal ulong NumBytes;
- }
-
[StructLayout(LayoutKind.Explicit)]
internal struct ConnectionEventDataUnion
{
internal ConnectionEventDataConnected Connected;
[FieldOffset(0)]
- internal ConnectionEventDataShutdownBegin ShutdownBegin;
+ internal ConnectionEventDataShutdownInitiatedByTransport ShutdownInitiatedByTransport;
[FieldOffset(0)]
- internal ConnectionEventDataShutdownBeginPeer ShutdownBeginPeer;
+ internal ConnectionEventDataShutdownInitiatedByPeer ShutdownInitiatedByPeer;
[FieldOffset(0)]
internal ConnectionEventDataShutdownComplete ShutdownComplete;
internal ConnectionEventDataPeerAddrChanged PeerAddrChanged;
[FieldOffset(0)]
- internal ConnectionEventDataNewStream NewStream;
+ internal ConnectionEventDataStreamStarted StreamStarted;
[FieldOffset(0)]
internal ConnectionEventDataStreamsAvailable StreamsAvailable;
-
- [FieldOffset(0)]
- internal ConnectionEventDataIdealSendBuffer IdealSendBuffer;
}
[StructLayout(LayoutKind.Sequential)]
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,
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)]
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,
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,
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)]
var sslOpts = new SslServerAuthenticationOptions
{
EnabledSslProtocols = options.SslProtocols,
- ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
+ ApplicationProtocols = new List<SslApplicationProtocol> { new SslApplicationProtocol("h3") },
//ServerCertificate = _cert,
ClientCertificateRequired = false
};
{
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)
{
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;
// 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>
/// </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);
+ }
}
}
// - 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);
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>
using System.Runtime.CompilerServices;
using System.Net.Http.QPack;
using System.Runtime.ExceptionServices;
-using System.Buffers;
-using System.Diagnostics.CodeAnalysis;
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;
}
await ReadHeadersAsync(payloadLength, cancellationToken).ConfigureAwait(false);
+ Debug.Assert(_response != null);
}
while ((int)_response.StatusCode < 200);
_response = new HttpResponseMessage()
{
- Version = HttpVersion.Version30,
+ Version = Http3Connection.HttpVersion30,
RequestMessage = _request,
Content = new HttpConnectionResponseContent(),
StatusCode = (HttpStatusCode)statusCode
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)
}
_http2Enabled = _poolManager.Settings._maxHttpVersion >= HttpVersion.Version20;
- _http3Enabled = _poolManager.Settings._maxHttpVersion >= HttpVersion.Version30;
+ _http3Enabled = _poolManager.Settings._maxHttpVersion >= Http3Connection.HttpVersion30;
switch (kind)
{
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)
{
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;
{
bool allowHttp2 = AllowHttp2;
_maxHttpVersion =
- AllowDraftHttp3 && allowHttp2 ? HttpVersion.Version30 :
+ AllowDraftHttp3 && allowHttp2 ? Http3Connection.HttpVersion30 :
allowHttp2 ? HttpVersion.Version20 :
HttpVersion.Version11;
_allowUnencryptedHttp2 = allowHttp2 && AllowUnencryptedHttp2;
/// </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()
{
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);
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);
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);
}
}
{
public abstract class HttpClientHandlerTest_Http3 : HttpClientHandlerTestBase
{
- protected override Version UseVersion => HttpVersion.Version30;
+ protected override Version UseVersion => HttpVersion30;
public static bool SupportsAlpn => PlatformDetection.SupportsAlpn;
handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
}
- if (useVersion == HttpVersion.Version30)
+ if (useVersion == HttpVersion30)
{
SetUsePrenegotiatedHttp3(handler, usePrenegotiatedHttp3: true);
}
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
await (new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 60000);
}
+ [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]
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)
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))]
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;
}
}
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
{
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);
}
}
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; } }
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
None = 0,
Http11 = 1,
Http2 = 2,
- Http3 = 4,
Other = 128
}
{
alpn |= ApplicationProtocolInfo.Http2;
}
- else if (protocol.SequenceEqual(SslApplicationProtocol.Http3.Protocol.Span))
- {
- alpn |= ApplicationProtocolInfo.Http3;
- }
else
{
alpn |= ApplicationProtocolInfo.Other;