internal static partial class Libraries
{
internal const string Odbc32 = "libodbc.so.2";
+ internal const string MsQuic = "msquic";
}
}
internal const string Odbc32 = "libodbc.2.dylib";
internal const string SystemConfigurationLibrary = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration";
internal const string AppleCryptoNative = "System.Security.Cryptography.Native.Apple";
+ internal const string MsQuic = "msquic";
+
}
}
internal const string Wtsapi32 = "wtsapi32.dll";
internal const string CompressionNative = "clrcompression.dll";
internal const string CoreWinRT = "api-ms-win-core-winrt-l1-1-0.dll";
+ internal const string MsQuic = "msquic.dll";
}
}
public sealed partial class QuicConnection : System.IDisposable
{
public QuicConnection(System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider, IPEndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null) { }
+ public QuicConnection(System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider, QuicClientConnectionOptions options) { }
}
public sealed partial class QuicListener : IDisposable
{
public QuicListener(System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider, IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions) { }
+ public QuicListener(System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider, QuicListenerOptions option) { }
}
public static class QuicImplementationProviders
{
public static System.Net.Quic.Implementations.QuicImplementationProvider Mock { get { throw null; } }
-
+ public static System.Net.Quic.Implementations.QuicImplementationProvider MsQuic { get { throw null; } }
public static System.Net.Quic.Implementations.QuicImplementationProvider Default { get { throw null; } }
}
}
// Changes to this file must follow the http://aka.ms/api-review process.
// ------------------------------------------------------------------------------
+using System.Net.Security;
using System.Threading;
+using System.Threading.Tasks;
namespace System.Net.Quic
{
public QuicStream OpenBidirectionalStream() => throw null;
public System.Threading.Tasks.ValueTask<QuicStream> AcceptStreamAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
public System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol => throw null;
- public void Close() => throw null;
+ public ValueTask CloseAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
public void Dispose() => throw null;
}
public sealed partial class QuicListener : IDisposable
public QuicListener(IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions) { }
public IPEndPoint ListenEndPoint => throw null;
public System.Threading.Tasks.ValueTask<QuicConnection> AcceptConnectionAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
+ public void Start() => throw null;
public void Close() => throw null;
public void Dispose() => throw null;
}
public override int Read(byte[] buffer, int offset, int count) => throw null;
public override void Write(byte[] buffer, int offset, int count) => throw null;
public long StreamId => throw null;
- public void ShutdownRead() => throw null;
- public void ShutdownWrite() => throw null;
+ public void AbortRead() => throw null;
+ public ValueTask WriteAsync(ReadOnlyMemory<byte> data, bool endStream, System.Threading.CancellationToken cancellationToken = default) => throw null;
+ public ValueTask ShutdownWriteCompleted(System.Threading.CancellationToken cancellationToken = default) => throw null;
+ }
+ public class QuicClientConnectionOptions
+ {
+ public SslClientAuthenticationOptions ClientAuthenticationOptions { get => throw null; set => throw null; }
+ public IPEndPoint LocalEndPoint { get => throw null; set => throw null; }
+ public IPEndPoint RemoteEndPoint { get => throw null; set => throw null; }
+ public long MaxBidirectionalStreams { get => throw null; set => throw null; }
+ public long MaxUnidirectionalStreams { get => throw null; set => throw null; }
+ public TimeSpan IdleTimeout { get => throw null; set => throw null; }
+ }
+ public class QuicListenerOptions
+ {
+ public SslServerAuthenticationOptions ServerAuthenticationOptions { get => throw null; set => throw null; }
+ public IPEndPoint ListenEndPoint { get => throw null; set => throw null; }
+ public int ListenBacklog { get => throw null; set => throw null; }
+ public long MaxBidirectionalStreams { get => throw null; set => throw null; }
+ public long MaxUnidirectionalStreams { get => throw null; set => throw null; }
+ public TimeSpan IdleTimeout { get => throw null; set => throw null; }
}
}
<Project>
<PropertyGroup>
<BuildConfigurations>
- $(NetCoreAppCurrent)-Unix;
+ $(NetCoreAppCurrent)-Linux;
+ $(NetCoreAppCurrent)-OSX;
$(NetCoreAppCurrent)-Windows_NT;
</BuildConfigurations>
</PropertyGroup>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Net.Quic.Implementations.MsQuic.Internal;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static class MsQuic
+ {
+ [DllImport(Libraries.MsQuic)]
+ internal static unsafe extern uint MsQuicOpen(int version, out MsQuicNativeMethods.NativeApi* registration);
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal static class MsQuicStatusCodes
+ {
+ internal const uint Success = 0;
+ internal const uint Pending = unchecked((uint)-2);
+ internal const uint Continue = unchecked((uint)-1);
+ internal const uint OutOfMemory = 12;
+ internal const uint InvalidParameter = 22;
+ internal const uint InvalidState = 200000002;
+ internal const uint NotSupported = 95;
+ internal const uint NotFound = 2;
+ internal const uint BufferTooSmall = 75;
+ internal const uint HandshakeFailure = 200000009;
+ internal const uint Aborted = 200000008;
+ internal const uint AddressInUse = 98;
+ internal const uint ConnectionTimeout = 110;
+ internal const uint ConnectionIdle = 200000011;
+ internal const uint InternalError = 200000012;
+ internal const uint ServerBusy = 200000007;
+ internal const uint ProtocolError = 200000013;
+ internal const uint VerNegError = 200000014;
+
+ public static string GetError(uint status)
+ {
+ return status switch
+ {
+ Success => "SUCCESS",
+ Pending => "PENDING",
+ Continue => "CONTINUE",
+ OutOfMemory => "OUT_OF_MEMORY",
+ InvalidParameter => "INVALID_PARAMETER",
+ InvalidState => "INVALID_STATE",
+ NotSupported => "NOT_SUPPORTED",
+ NotFound => "NOT_FOUND",
+ BufferTooSmall => "BUFFER_TOO_SMALL",
+ HandshakeFailure => "HANDSHAKE_FAILURE",
+ Aborted => "ABORTED",
+ AddressInUse => "ADDRESS_IN_USE",
+ ConnectionTimeout => "CONNECTION_TIMEOUT",
+ ConnectionIdle => "CONNECTION_IDLE",
+ InternalError => "INTERNAL_ERROR",
+ ServerBusy => "SERVER_BUSY",
+ ProtocolError => "PROTOCOL_ERROR",
+ VerNegError => "VER_NEG_ERROR",
+ _ => status.ToString()
+ };
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal static class MsQuicStatusHelper
+ {
+ internal static bool SuccessfulStatusCode(uint status)
+ {
+ return (int)status <= 0;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ /// <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,
+ }
+
+ [Flags]
+ internal enum QUIC_CONNECTION_SHUTDOWN_FLAG : uint
+ {
+ NONE = 0x0,
+ SILENT = 0x1
+ }
+
+ [Flags]
+ internal enum QUIC_STREAM_OPEN_FLAG : uint
+ {
+ NONE = 0,
+ UNIDIRECTIONAL = 0x1,
+ ZERO_RTT = 0x2,
+ }
+
+ [Flags]
+ internal enum QUIC_STREAM_START_FLAG : uint
+ {
+ NONE = 0,
+ FAIL_BLOCKED = 0x1,
+ IMMEDIATE = 0x2,
+ ASYNC = 0x4,
+ }
+
+ [Flags]
+ internal enum QUIC_STREAM_SHUTDOWN_FLAG : uint
+ {
+ NONE = 0,
+ GRACEFUL = 0x1,
+ ABORT_SEND = 0x2,
+ ABORT_RECV = 0x4,
+ ABORT = ABORT_SEND | ABORT_RECV,
+ IMMEDIATE = 0x8
+ }
+
+ [Flags]
+ internal enum QUIC_RECEIVE_FLAG : byte
+ {
+ NONE = 0,
+ ZERO_RTT = 0x1,
+ FIN = 0x02
+ }
+
+ [Flags]
+ internal enum QUIC_SEND_FLAG : uint
+ {
+ NONE = 0,
+ ALLOW_0_RTT = 0x00000001,
+ FIN = 0x00000002,
+ }
+
+ internal enum QUIC_PARAM_LEVEL : uint
+ {
+ REGISTRATION = 0,
+ SESSION = 1,
+ LISTENER = 2,
+ CONNECTION = 3,
+ TLS = 4,
+ STREAM = 5,
+ }
+
+ internal enum QUIC_PARAM_REGISTRATION : uint
+ {
+ RETRY_MEMORY_PERCENT = 0,
+ CID_PREFIX = 1
+ }
+
+ internal enum QUIC_PARAM_SESSION : uint
+ {
+ TLS_TICKET_KEY = 0,
+ PEER_BIDI_STREAM_COUNT = 1,
+ PEER_UNIDI_STREAM_COUNT = 2,
+ IDLE_TIMEOUT = 3,
+ DISCONNECT_TIMEOUT = 4,
+ MAX_BYTES_PER_KEY = 5
+ }
+
+ internal enum QUIC_PARAM_LISTENER : uint
+ {
+ LOCAL_ADDRESS = 0,
+ STATS = 1
+ }
+
+ internal enum QUIC_PARAM_CONN : uint
+ {
+ QUIC_VERSION = 0,
+ LOCAL_ADDRESS = 1,
+ REMOTE_ADDRESS = 2,
+ IDLE_TIMEOUT = 3,
+ PEER_BIDI_STREAM_COUNT = 4,
+ PEER_UNIDI_STREAM_COUNT = 5,
+ LOCAL_BIDI_STREAM_COUNT = 6,
+ LOCAL_UNIDI_STREAM_COUNT = 7,
+ CLOSE_REASON_PHRASE = 8,
+ STATISTICS = 9,
+ STATISTICS_PLAT = 10,
+ CERT_VALIDATION_FLAGS = 11,
+ KEEP_ALIVE = 12,
+ DISCONNECT_TIMEOUT = 13,
+ SEC_CONFIG = 14,
+ SEND_BUFFERING = 15,
+ SEND_PACING = 16,
+ SHARE_UDP_BINDING = 17,
+ IDEAL_PROCESSOR = 18,
+ MAX_STREAM_IDS = 19
+ }
+
+ internal enum QUIC_PARAM_STREAM : uint
+ {
+ ID = 0,
+ ZERORTT_LENGTH = 1,
+ IDEAL_SEND_BUFFER = 2
+ }
+
+ internal enum QUIC_LISTENER_EVENT : byte
+ {
+ NEW_CONNECTION = 0
+ }
+
+ internal enum QUIC_CONNECTION_EVENT : byte
+ {
+ CONNECTED = 0,
+ SHUTDOWN_INITIATED_BY_TRANSPORT = 1,
+ SHUTDOWN_INITIATED_BY_PEER = 2,
+ SHUTDOWN_COMPLETE = 3,
+ LOCAL_ADDRESS_CHANGED = 4,
+ PEER_ADDRESS_CHANGED = 5,
+ PEER_STREAM_STARTED = 6,
+ STREAMS_AVAILABLE = 7,
+ PEER_NEEDS_STREAMS = 8,
+ IDEAL_PROCESSOR_CHANGED = 9,
+ }
+
+ internal enum QUIC_STREAM_EVENT : byte
+ {
+ START_COMPLETE = 0,
+ RECEIVE = 1,
+ SEND_COMPLETE = 2,
+ PEER_SEND_SHUTDOWN = 3,
+ PEER_SEND_ABORTED = 4,
+ PEER_RECEIVE_ABORTED = 5,
+ SEND_SHUTDOWN_COMPLETE = 6,
+ SHUTDOWN_COMPLETE = 7,
+ IDEAL_SEND_BUFFER_SIZE = 8,
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ /// <summary>
+ /// Contains all native delegates and structs that are used with MsQuic.
+ /// </summary>
+ internal static unsafe class MsQuicNativeMethods
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NativeApi
+ {
+ internal uint Version;
+
+ internal IntPtr SetContext;
+ internal IntPtr GetContext;
+ internal IntPtr SetCallbackHandler;
+
+ internal IntPtr SetParam;
+ internal IntPtr GetParam;
+
+ internal IntPtr RegistrationOpen;
+ internal IntPtr RegistrationClose;
+
+ internal IntPtr SecConfigCreate;
+ internal IntPtr SecConfigDelete;
+
+ internal IntPtr SessionOpen;
+ internal IntPtr SessionClose;
+ internal IntPtr SessionShutdown;
+
+ internal IntPtr ListenerOpen;
+ internal IntPtr ListenerClose;
+ internal IntPtr ListenerStart;
+ internal IntPtr ListenerStop;
+
+ internal IntPtr ConnectionOpen;
+ internal IntPtr ConnectionClose;
+ internal IntPtr ConnectionShutdown;
+ internal IntPtr ConnectionStart;
+
+ internal IntPtr StreamOpen;
+ internal IntPtr StreamClose;
+ internal IntPtr StreamStart;
+ internal IntPtr StreamShutdown;
+ internal IntPtr StreamSend;
+ internal IntPtr StreamReceiveComplete;
+ internal IntPtr StreamReceiveSetEnabled;
+ }
+
+ internal delegate uint SetContextDelegate(
+ IntPtr handle,
+ IntPtr context);
+
+ internal delegate IntPtr GetContextDelegate(
+ IntPtr handle);
+
+ internal delegate void SetCallbackHandlerDelegate(
+ IntPtr handle,
+ Delegate del,
+ IntPtr context);
+
+ internal delegate uint SetParamDelegate(
+ IntPtr handle,
+ uint level,
+ uint param,
+ uint bufferLength,
+ byte* buffer);
+
+ internal delegate uint GetParamDelegate(
+ IntPtr handle,
+ uint level,
+ uint param,
+ uint* bufferLength,
+ byte* buffer);
+
+ internal delegate uint RegistrationOpenDelegate(byte[] appName, out IntPtr registrationContext);
+
+ internal delegate void RegistrationCloseDelegate(IntPtr registrationContext);
+
+ internal delegate void SecConfigCreateCompleteDelegate(IntPtr context, uint status, IntPtr securityConfig);
+
+ internal delegate uint SecConfigCreateDelegate(
+ IntPtr registrationContext,
+ uint flags,
+ IntPtr certificate,
+ [MarshalAs(UnmanagedType.LPStr)]string principal,
+ IntPtr context,
+ SecConfigCreateCompleteDelegate completionHandler);
+
+ internal delegate void SecConfigDeleteDelegate(
+ IntPtr securityConfig);
+
+ internal delegate uint SessionOpenDelegate(
+ IntPtr registrationContext,
+ byte[] utf8String,
+ IntPtr context,
+ ref IntPtr session);
+
+ internal delegate void SessionCloseDelegate(
+ IntPtr session);
+
+ internal delegate void SessionShutdownDelegate(
+ IntPtr session,
+ uint flags,
+ ushort errorCode);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ListenerEvent
+ {
+ internal QUIC_LISTENER_EVENT Type;
+ internal ListenerEventDataUnion Data;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct ListenerEventDataUnion
+ {
+ [FieldOffset(0)]
+ internal ListenerEventDataNewConnection NewConnection;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ListenerEventDataNewConnection
+ {
+ internal IntPtr Info;
+ internal IntPtr Connection;
+ internal IntPtr SecurityConfig;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NewConnectionInfo
+ {
+ internal uint QuicVersion;
+ internal IntPtr LocalAddress;
+ internal IntPtr RemoteAddress;
+ internal ushort CryptoBufferLength;
+ internal ushort AlpnListLength;
+ internal ushort ServerNameLength;
+ internal IntPtr CryptoBuffer;
+ internal IntPtr AlpnList;
+ internal IntPtr ServerName;
+ }
+
+ internal delegate uint ListenerCallbackDelegate(
+ IntPtr listener,
+ IntPtr context,
+ ref ListenerEvent evt);
+
+ internal delegate uint ListenerOpenDelegate(
+ IntPtr session,
+ ListenerCallbackDelegate handler,
+ IntPtr context,
+ out IntPtr listener);
+
+ internal delegate uint ListenerCloseDelegate(
+ IntPtr listener);
+
+ internal delegate uint ListenerStartDelegate(
+ IntPtr listener,
+ ref SOCKADDR_INET localAddress);
+
+ internal delegate uint ListenerStopDelegate(
+ IntPtr listener);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEventDataConnected
+ {
+ internal bool EarlyDataAccepted;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEventDataShutdownBegin
+ {
+ internal uint Status;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEventDataShutdownBeginPeer
+ {
+ internal ushort ErrorCode;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEventDataShutdownComplete
+ {
+ internal bool TimedOut;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEventDataLocalAddrChanged
+ {
+ internal IntPtr Address;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEventDataPeerAddrChanged
+ {
+ internal IntPtr Address;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEventDataNewStream
+ {
+ internal IntPtr Stream;
+ internal QUIC_STREAM_OPEN_FLAG Flags;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEventDataStreamsAvailable
+ {
+ internal ushort BiDirectionalCount;
+ internal ushort UniDirectionalCount;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEventDataIdealSendBuffer
+ {
+ internal ulong NumBytes;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct ConnectionEventDataUnion
+ {
+ [FieldOffset(0)]
+ internal ConnectionEventDataConnected Connected;
+
+ [FieldOffset(0)]
+ internal ConnectionEventDataShutdownBegin ShutdownBegin;
+
+ [FieldOffset(0)]
+ internal ConnectionEventDataShutdownBeginPeer ShutdownBeginPeer;
+
+ [FieldOffset(0)]
+ internal ConnectionEventDataShutdownComplete ShutdownComplete;
+
+ [FieldOffset(0)]
+ internal ConnectionEventDataLocalAddrChanged LocalAddrChanged;
+
+ [FieldOffset(0)]
+ internal ConnectionEventDataPeerAddrChanged PeerAddrChanged;
+
+ [FieldOffset(0)]
+ internal ConnectionEventDataNewStream NewStream;
+
+ [FieldOffset(0)]
+ internal ConnectionEventDataStreamsAvailable StreamsAvailable;
+
+ [FieldOffset(0)]
+ internal ConnectionEventDataIdealSendBuffer IdealSendBuffer;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ConnectionEvent
+ {
+ 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 ushort ShutdownBeginPeerStatus => Data.ShutdownBeginPeer.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 delegate uint ConnectionCallbackDelegate(
+ IntPtr connection,
+ IntPtr context,
+ ref ConnectionEvent connectionEvent);
+
+ internal delegate uint ConnectionOpenDelegate(
+ IntPtr session,
+ ConnectionCallbackDelegate handler,
+ IntPtr context,
+ out IntPtr connection);
+
+ internal delegate uint ConnectionCloseDelegate(
+ IntPtr connection);
+
+ internal delegate uint ConnectionStartDelegate(
+ IntPtr connection,
+ ushort family,
+ [MarshalAs(UnmanagedType.LPStr)]
+ string serverName,
+ ushort serverPort);
+
+ internal delegate uint ConnectionShutdownDelegate(
+ IntPtr connection,
+ uint flags,
+ ushort errorCode);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct StreamEventDataRecv
+ {
+ internal ulong AbsoluteOffset;
+ internal ulong TotalBufferLength;
+ internal QuicBuffer* Buffers;
+ internal uint BufferCount;
+ internal byte Flags;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct StreamEventDataSendComplete
+ {
+ [FieldOffset(7)]
+ internal byte Canceled;
+ [FieldOffset(8)]
+ internal IntPtr ClientContext;
+
+ internal bool IsCanceled()
+ {
+ return Canceled != 0;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct StreamEventDataPeerSendAbort
+ {
+ internal ushort ErrorCode;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct StreamEventDataPeerRecvAbort
+ {
+ internal ushort ErrorCode;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct StreamEventDataSendShutdownComplete
+ {
+ internal bool Graceful;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct StreamEventDataUnion
+ {
+ [FieldOffset(0)]
+ internal StreamEventDataRecv Recv;
+
+ [FieldOffset(0)]
+ internal StreamEventDataSendComplete SendComplete;
+
+ [FieldOffset(0)]
+ internal StreamEventDataPeerSendAbort PeerSendAbort;
+
+ [FieldOffset(0)]
+ internal StreamEventDataPeerRecvAbort PeerRecvAbort;
+
+ [FieldOffset(0)]
+ internal StreamEventDataSendShutdownComplete SendShutdownComplete;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct StreamEvent
+ {
+ internal QUIC_STREAM_EVENT Type;
+ internal StreamEventDataUnion Data;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SOCKADDR_IN
+ {
+ internal ushort sin_family;
+ internal ushort sin_port;
+ internal byte sin_addr0;
+ internal byte sin_addr1;
+ internal byte sin_addr2;
+ internal byte sin_addr3;
+
+ internal byte[] Address
+ {
+ get
+ {
+ return new byte[] { sin_addr0, sin_addr1, sin_addr2, sin_addr3 };
+ }
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SOCKADDR_IN6
+ {
+ internal ushort _family;
+ internal ushort _port;
+ internal uint _flowinfo;
+ internal byte _addr0;
+ internal byte _addr1;
+ internal byte _addr2;
+ internal byte _addr3;
+ internal byte _addr4;
+ internal byte _addr5;
+ internal byte _addr6;
+ internal byte _addr7;
+ internal byte _addr8;
+ internal byte _addr9;
+ internal byte _addr10;
+ internal byte _addr11;
+ internal byte _addr12;
+ internal byte _addr13;
+ internal byte _addr14;
+ internal byte _addr15;
+ internal uint _scope_id;
+
+ internal byte[] Address
+ {
+ get
+ {
+ return new byte[] {
+ _addr0, _addr1, _addr2, _addr3,
+ _addr4, _addr5, _addr6, _addr7,
+ _addr8, _addr9, _addr10, _addr11,
+ _addr12, _addr13, _addr14, _addr15 };
+ }
+ }
+ }
+
+ [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
+ internal struct SOCKADDR_INET
+ {
+ [FieldOffset(0)]
+ internal SOCKADDR_IN Ipv4;
+ [FieldOffset(0)]
+ internal SOCKADDR_IN6 Ipv6;
+ [FieldOffset(0)]
+ internal ushort si_family;
+ }
+
+ internal delegate uint StreamCallbackDelegate(
+ IntPtr stream,
+ IntPtr context,
+ StreamEvent streamEvent);
+
+ internal delegate uint StreamOpenDelegate(
+ IntPtr connection,
+ uint flags,
+ StreamCallbackDelegate handler,
+ IntPtr context,
+ out IntPtr stream);
+
+ internal delegate uint StreamStartDelegate(
+ IntPtr stream,
+ uint flags);
+
+ internal delegate uint StreamCloseDelegate(
+ IntPtr stream);
+
+ internal delegate uint StreamShutdownDelegate(
+ IntPtr stream,
+ uint flags,
+ ushort errorCode);
+
+ internal delegate uint StreamSendDelegate(
+ IntPtr stream,
+ QuicBuffer* buffers,
+ uint bufferCount,
+ uint flags,
+ IntPtr clientSendContext);
+
+ internal delegate uint StreamReceiveCompleteDelegate(
+ IntPtr stream,
+ ulong bufferLength);
+
+ internal delegate uint StreamReceiveSetEnabledDelegate(
+ IntPtr stream,
+ bool enabled);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct QuicBuffer
+ {
+ internal uint Length;
+ internal byte* Buffer;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal static class MsQuicStatusCodes
+ {
+ internal const uint Success = 0;
+ internal const uint Pending = unchecked((uint)-2);
+ internal const uint Continue = unchecked((uint)-1);
+ internal const uint OutOfMemory = 12;
+ internal const uint InvalidParameter = 22;
+ internal const uint InvalidState = 200000002;
+ internal const uint NotSupported = 95;
+ internal const uint NotFound = 2;
+ internal const uint BufferTooSmall = 75;
+ internal const uint HandshakeFailure = 200000009;
+ internal const uint Aborted = 200000008;
+ internal const uint AddressInUse = 98;
+ internal const uint ConnectionTimeout = 110;
+ internal const uint ConnectionIdle = 200000011;
+ internal const uint InternalError = 200000012;
+ internal const uint ServerBusy = 200000007;
+ internal const uint ProtocolError = 200000013;
+ internal const uint VerNegError = 200000014;
+
+ public static string GetError(uint status)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal static class MsQuicStatusHelper
+ {
+ internal static bool SuccessfulStatusCode(uint status)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal static class MsQuicStatusCodes
+ {
+ internal const uint Success = 0;
+ internal const uint Pending = 0x703E5;
+ internal const uint Continue = 0x704DE;
+ internal const uint OutOfMemory = 0x8007000E;
+ internal const uint InvalidParameter = 0x80070057;
+ internal const uint InvalidState = 0x8007139F;
+ internal const uint NotSupported = 0x80004002;
+ internal const uint NotFound = 0x80070490;
+ internal const uint BufferTooSmall = 0x8007007A;
+ internal const uint HandshakeFailure = 0x80410000;
+ internal const uint Aborted = 0x80004004;
+ internal const uint AddressInUse = 0x80072740;
+ internal const uint ConnectionTimeout = 0x800704CF;
+ internal const uint ConnectionIdle = 0x800704D4;
+ internal const uint InternalError = 0x80004005;
+ internal const uint ServerBusy = 0x800704C9;
+ internal const uint ProtocolError = 0x800704CD;
+ internal const uint HostUnreachable = 0x800704D0;
+ internal const uint VerNegError = 0x80410001;
+
+ // TODO return better error messages here.
+ public static string GetError(uint status)
+ {
+ return status switch
+ {
+ Success => "SUCCESS",
+ Pending => "PENDING",
+ Continue => "CONTINUE",
+ OutOfMemory => "OUT_OF_MEMORY",
+ InvalidParameter => "INVALID_PARAMETER",
+ InvalidState => "INVALID_STATE",
+ NotSupported => "NOT_SUPPORTED",
+ NotFound => "NOT_FOUND",
+ BufferTooSmall => "BUFFER_TOO_SMALL",
+ HandshakeFailure => "HANDSHAKE_FAILURE",
+ Aborted => "ABORTED",
+ AddressInUse => "ADDRESS_IN_USE",
+ ConnectionTimeout => "CONNECTION_TIMEOUT",
+ ConnectionIdle => "CONNECTION_IDLE",
+ InternalError => "INTERNAL_ERROR",
+ ServerBusy => "SERVER_BUSY",
+ ProtocolError => "PROTOCOL_ERROR",
+ VerNegError => "VER_NEG_ERROR",
+ _ => status.ToString()
+ };
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal static class MsQuicStatusHelper
+ {
+ internal static bool SuccessfulStatusCode(uint status)
+ {
+ return status < 0x80000000;
+ }
+ }
+}
--- /dev/null
+<!-- MsQuic currently not available -->
+msquic.dll!MsQuicOpen
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>System.Net.Quic</AssemblyName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Configurations>$(NetCoreAppCurrent)-Unix-Debug;$(NetCoreAppCurrent)-Unix-Release;$(NetCoreAppCurrent)-Windows_NT-Debug;$(NetCoreAppCurrent)-Windows_NT-Release</Configurations>
+ <EnablePInvokeAnalyzer>false</EnablePInvokeAnalyzer>
</PropertyGroup>
<ItemGroup>
<!-- All configurations -->
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\Internal\MsQuicAddressHelpers.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\Internal\MsQuicApi.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\Internal\MsQuicParameterHelpers.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\Internal\MsQuicSecurityConfig.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\Internal\MsQuicSession.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\Internal\MsQuicStatusException.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\Internal\ResettableCompletionSource.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\MsQuicConnection.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\MsQuicImplementationProvider.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\MsQuicListener.cs" />
+ <Compile Include="System\Net\Quic\Implementations\MsQuic\MsQuicStream.cs" />
<Compile Include="System\Net\Quic\Implementations\QuicImplementationProvider.cs" />
<Compile Include="System\Net\Quic\Implementations\QuicListenerProvider.cs" />
<Compile Include="System\Net\Quic\Implementations\QuicConnectionProvider.cs" />
<Compile Include="System\Net\Quic\Implementations\Mock\MockListener.cs" />
<Compile Include="System\Net\Quic\Implementations\Mock\MockConnection.cs" />
<Compile Include="System\Net\Quic\Implementations\Mock\MockStream.cs" />
+ <Compile Include="System\Net\Quic\NetEventSource.Quic.cs" />
+ <Compile Include="System\Net\Quic\QuicClientConnectionOptions.cs" />
<Compile Include="System\Net\Quic\QuicImplementationProviders.cs" />
<Compile Include="System\Net\Quic\QuicConnection.cs" />
<Compile Include="System\Net\Quic\QuicListener.cs" />
+ <Compile Include="System\Net\Quic\QuicListenerOptions.cs" />
<Compile Include="System\Net\Quic\QuicStream.cs" />
+ <Compile Include="Interop\Interop.MsQuic.cs" />
+ <Compile Include="Interop\MsQuicEnums.cs" />
+ <Compile Include="Interop\MsQuicNativeMethods.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs">
<Link>Common\System\Threading\Tasks\TaskToApm.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)System\Net\Logging\NetEventSource.Common.cs">
+ <Link>Common\System\Net\Logging\NetEventSource.Common.cs</Link>
+ </Compile>
+ </ItemGroup>
+ <!-- Windows specific files -->
+ <ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
+ <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs">
+ <Link>Common\Interop\Windows\Interop.Libraries.cs</Link>
+ </Compile>
+ <Compile Include="Interop\Windows\MsQuicStatusCodes.cs" />
+ <Compile Include="Interop\Windows\MsQuicStatusHelper.cs" />
+ </ItemGroup>
+ <!-- Linux specific files -->
+ <ItemGroup Condition=" '$(TargetsLinux)' == 'true' ">
+ <Compile Include="$(CommonPath)Interop\Linux\Interop.Libraries.cs">
+ <Link>Common\Interop\Linux\Interop.Libraries.cs</Link>
+ </Compile>
+ <Compile Include="Interop\Linux\MsQuicStatusCodes.cs" />
+ <Compile Include="Interop\Linux\MsQuicStatusHelper.cs" />
+ </ItemGroup>
+ <!-- OSX specific files -->
+ <ItemGroup Condition=" '$(TargetsOSX)' == 'true' ">
+ <Compile Include="$(CommonPath)Interop\OSX\Interop.Libraries.cs">
+ <Link>Common\Interop\OSX\Interop.Libraries.cs</Link>
+ </Compile>
+ <Compile Include="Interop\OSX\MsQuicStatusCodes.cs" />
+ <Compile Include="Interop\Linux\MsQuicStatusHelper.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Buffers" />
<Reference Include="System.Console" />
+ <Reference Include="System.Collections" />
<Reference Include="System.Diagnostics.Debug" />
+ <Reference Include="System.Diagnostics.Tracing" />
<Reference Include="System.Memory" />
<Reference Include="System.Net.NameResolution" />
<Reference Include="System.Net.Primitives" />
<Reference Include="System.Net.Security" />
<Reference Include="System.Net.Sockets" />
+ <Reference Include="System.Net" />
<Reference Include="System.Resources.ResourceManager" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Extensions" />
<Reference Include="System.Runtime.InteropServices" />
+ <Reference Include="System.Runtime.InteropServices.RuntimeInformation" />
+ <Reference Include="System.Security.Cryptography.X509Certificates" />
<Reference Include="System.Threading" />
+ <Reference Include="System.Threading.Channels" />
<Reference Include="System.Threading.Tasks" />
+ <Reference Include="System.Threading.Tasks.Extensions" />
<Reference Include="System.Threading.ThreadPool" />
</ItemGroup>
+
+ <ItemGroup>
+ <Content Include="msquic.dll" Condition="Exists('msquic.dll')">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+ </Content>
+ <Content Include="libmsquic.so" Condition="Exists('libmsquic.so')">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+ </Content>
+ <Content Include="msquic.pdb" Condition="Exists('msquic.pdb')">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+ </Content>
+ </ItemGroup>
</Project>
// See the LICENSE file in the project root for more information.
using System.Buffers.Binary;
-using System.Diagnostics;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading;
return new MockStream(socket, streamId, bidirectional: bidirectional);
}
- internal override void Close()
+ internal override ValueTask CloseAsync(CancellationToken cancellationToken = default)
{
Dispose();
+ return default;
}
private void CheckDisposed()
{
internal sealed class MockImplementationProvider : QuicImplementationProvider
{
- internal override QuicListenerProvider CreateListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
+ internal override QuicListenerProvider CreateListener(QuicListenerOptions options)
{
- return new MockListener(listenEndPoint, sslServerAuthenticationOptions);
+ return new MockListener(options.ListenEndPoint, options.ServerAuthenticationOptions);
}
- internal override QuicConnectionProvider CreateConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint)
+ internal override QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options)
{
- return new MockConnection(remoteEndPoint, sslClientAuthenticationOptions, localEndPoint);
+ return new MockConnection(options.RemoteEndPoint, options.ClientAuthenticationOptions, options.LocalEndPoint);
}
}
}
_listenEndPoint = listenEndPoint;
_tcpListener = new TcpListener(listenEndPoint);
- _tcpListener.Start();
-
- if (listenEndPoint.Port == 0)
- {
- // Get auto-assigned port
- _listenEndPoint = (IPEndPoint)_tcpListener.LocalEndpoint;
- }
}
// IPEndPoint is mutable, so we must create a new instance every time this is retrieved.
return new MockConnection(socket, peerListenEndPoint, inboundListener);
}
+ internal override void Start()
+ {
+ CheckDisposed();
+
+ _tcpListener.Start();
+
+ if (_listenEndPoint.Port == 0)
+ {
+ // Get auto-assigned port
+ _listenEndPoint = (IPEndPoint)_tcpListener.LocalEndpoint;
+ }
+ }
+
internal override void Close()
{
Dispose();
_socket.Send(buffer);
}
- internal override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ internal override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ return WriteAsync(buffer, endStream: false, cancellationToken);
+ }
+
+ internal override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool endStream, CancellationToken cancellationToken = default)
{
CheckDisposed();
}
await _socket.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+
+ if (endStream)
+ {
+ _socket.Shutdown(SocketShutdown.Send);
+ }
}
internal override void Flush()
return Task.CompletedTask;
}
- internal override void ShutdownRead()
+ internal override void AbortRead()
{
throw new NotImplementedException();
}
- internal override void ShutdownWrite()
+ internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default)
{
CheckDisposed();
- _socket.Shutdown(SocketShutdown.Send);
+ return default;
}
private void CheckDisposed()
_socket = null;
}
}
+
+ public override ValueTask DisposeAsync()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ _socket?.Dispose();
+ _socket = null;
+ }
+
+ return default;
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Sockets;
+using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal static class MsQuicAddressHelpers
+ {
+ internal const ushort IPv4 = 2;
+ internal const ushort IPv6 = 23;
+
+ internal static unsafe IPEndPoint INetToIPEndPoint(SOCKADDR_INET inetAddress)
+ {
+ if (inetAddress.si_family == IPv4)
+ {
+ return new IPEndPoint(new IPAddress(inetAddress.Ipv4.Address), (ushort)IPAddress.NetworkToHostOrder((short)inetAddress.Ipv4.sin_port));
+ }
+ else
+ {
+ return new IPEndPoint(new IPAddress(inetAddress.Ipv6.Address), (ushort)IPAddress.NetworkToHostOrder((short)inetAddress.Ipv6._port));
+ }
+ }
+
+ internal static SOCKADDR_INET IPEndPointToINet(IPEndPoint endpoint)
+ {
+ SOCKADDR_INET socketAddress = default;
+ byte[] buffer = endpoint.Address.GetAddressBytes();
+ if (endpoint.Address != IPAddress.Any && endpoint.Address != IPAddress.IPv6Any)
+ {
+ switch (endpoint.Address.AddressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ socketAddress.Ipv4.sin_addr0 = buffer[0];
+ socketAddress.Ipv4.sin_addr1 = buffer[1];
+ socketAddress.Ipv4.sin_addr2 = buffer[2];
+ socketAddress.Ipv4.sin_addr3 = buffer[3];
+ socketAddress.Ipv4.sin_family = IPv4;
+ break;
+ case AddressFamily.InterNetworkV6:
+ socketAddress.Ipv6._addr0 = buffer[0];
+ socketAddress.Ipv6._addr1 = buffer[1];
+ socketAddress.Ipv6._addr2 = buffer[2];
+ socketAddress.Ipv6._addr3 = buffer[3];
+ socketAddress.Ipv6._addr4 = buffer[4];
+ socketAddress.Ipv6._addr5 = buffer[5];
+ socketAddress.Ipv6._addr6 = buffer[6];
+ socketAddress.Ipv6._addr7 = buffer[7];
+ socketAddress.Ipv6._addr8 = buffer[8];
+ socketAddress.Ipv6._addr9 = buffer[9];
+ socketAddress.Ipv6._addr10 = buffer[10];
+ socketAddress.Ipv6._addr11 = buffer[11];
+ socketAddress.Ipv6._addr12 = buffer[12];
+ socketAddress.Ipv6._addr13 = buffer[13];
+ socketAddress.Ipv6._addr14 = buffer[14];
+ socketAddress.Ipv6._addr15 = buffer[15];
+ socketAddress.Ipv6._family = IPv6;
+ break;
+ default:
+ throw new ArgumentException("Only IPv4 or IPv6 are supported");
+ }
+ }
+
+ SetPort(endpoint.Address.AddressFamily, ref socketAddress, endpoint.Port);
+ return socketAddress;
+ }
+
+ private static void SetPort(AddressFamily addressFamily, ref SOCKADDR_INET socketAddrInet, int originalPort)
+ {
+ ushort convertedPort = (ushort)IPAddress.HostToNetworkOrder((short)originalPort);
+ switch (addressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ socketAddrInet.Ipv4.sin_port = convertedPort;
+ break;
+ case AddressFamily.InterNetworkV6:
+ default:
+ socketAddrInet.Ipv6._port = convertedPort;
+ break;
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal class MsQuicApi : IDisposable
+ {
+ private bool _disposed;
+
+ private readonly IntPtr _registrationContext;
+
+ private unsafe MsQuicApi()
+ {
+ MsQuicStatusException.ThrowIfFailed(Interop.MsQuic.MsQuicOpen(version: 1, out MsQuicNativeMethods.NativeApi* registration));
+
+ MsQuicNativeMethods.NativeApi nativeRegistration = *registration;
+
+ RegistrationOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.RegistrationOpenDelegate>(
+ nativeRegistration.RegistrationOpen);
+ RegistrationCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.RegistrationCloseDelegate>(
+ nativeRegistration.RegistrationClose);
+
+ SecConfigCreateDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SecConfigCreateDelegate>(
+ nativeRegistration.SecConfigCreate);
+ SecConfigDeleteDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SecConfigDeleteDelegate>(
+ nativeRegistration.SecConfigDelete);
+ SessionOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SessionOpenDelegate>(
+ nativeRegistration.SessionOpen);
+ SessionCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SessionCloseDelegate>(
+ nativeRegistration.SessionClose);
+ SessionShutdownDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SessionShutdownDelegate>(
+ nativeRegistration.SessionShutdown);
+
+ ListenerOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerOpenDelegate>(
+ nativeRegistration.ListenerOpen);
+ ListenerCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerCloseDelegate>(
+ nativeRegistration.ListenerClose);
+ ListenerStartDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerStartDelegate>(
+ nativeRegistration.ListenerStart);
+ ListenerStopDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerStopDelegate>(
+ nativeRegistration.ListenerStop);
+
+ ConnectionOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionOpenDelegate>(
+ nativeRegistration.ConnectionOpen);
+ ConnectionCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionCloseDelegate>(
+ nativeRegistration.ConnectionClose);
+ ConnectionShutdownDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionShutdownDelegate>(
+ nativeRegistration.ConnectionShutdown);
+ ConnectionStartDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionStartDelegate>(
+ nativeRegistration.ConnectionStart);
+
+ StreamOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamOpenDelegate>(
+ nativeRegistration.StreamOpen);
+ StreamCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamCloseDelegate>(
+ nativeRegistration.StreamClose);
+ StreamStartDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamStartDelegate>(
+ nativeRegistration.StreamStart);
+ StreamShutdownDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamShutdownDelegate>(
+ nativeRegistration.StreamShutdown);
+ StreamSendDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamSendDelegate>(
+ nativeRegistration.StreamSend);
+ StreamReceiveCompleteDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamReceiveCompleteDelegate>(
+ nativeRegistration.StreamReceiveComplete);
+ StreamReceiveSetEnabledDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamReceiveSetEnabledDelegate>(
+ nativeRegistration.StreamReceiveSetEnabled);
+ SetContextDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SetContextDelegate>(
+ nativeRegistration.SetContext);
+ GetContextDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.GetContextDelegate>(
+ nativeRegistration.GetContext);
+ SetCallbackHandlerDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SetCallbackHandlerDelegate>(
+ nativeRegistration.SetCallbackHandler);
+
+ SetParamDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SetParamDelegate>(
+ nativeRegistration.SetParam);
+ GetParamDelegate =
+ Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.GetParamDelegate>(
+ nativeRegistration.GetParam);
+
+ RegistrationOpenDelegate(Encoding.UTF8.GetBytes("SystemNetQuic"), out IntPtr ctx);
+ _registrationContext = ctx;
+ }
+
+ internal static MsQuicApi Api { get; } = new MsQuicApi();
+
+ internal MsQuicNativeMethods.RegistrationOpenDelegate RegistrationOpenDelegate { get; }
+ internal MsQuicNativeMethods.RegistrationCloseDelegate RegistrationCloseDelegate { get; }
+
+ internal MsQuicNativeMethods.SecConfigCreateDelegate SecConfigCreateDelegate { get; }
+ internal MsQuicNativeMethods.SecConfigDeleteDelegate SecConfigDeleteDelegate { get; }
+
+ internal MsQuicNativeMethods.SessionOpenDelegate SessionOpenDelegate { get; }
+ internal MsQuicNativeMethods.SessionCloseDelegate SessionCloseDelegate { get; }
+ internal MsQuicNativeMethods.SessionShutdownDelegate SessionShutdownDelegate { get; }
+
+ internal MsQuicNativeMethods.ListenerOpenDelegate ListenerOpenDelegate { get; }
+ internal MsQuicNativeMethods.ListenerCloseDelegate ListenerCloseDelegate { get; }
+ internal MsQuicNativeMethods.ListenerStartDelegate ListenerStartDelegate { get; }
+ internal MsQuicNativeMethods.ListenerStopDelegate ListenerStopDelegate { get; }
+
+ internal MsQuicNativeMethods.ConnectionOpenDelegate ConnectionOpenDelegate { get; }
+ internal MsQuicNativeMethods.ConnectionCloseDelegate ConnectionCloseDelegate { get; }
+ internal MsQuicNativeMethods.ConnectionShutdownDelegate ConnectionShutdownDelegate { get; }
+ internal MsQuicNativeMethods.ConnectionStartDelegate ConnectionStartDelegate { get; }
+
+ internal MsQuicNativeMethods.StreamOpenDelegate StreamOpenDelegate { get; }
+ internal MsQuicNativeMethods.StreamCloseDelegate StreamCloseDelegate { get; }
+ internal MsQuicNativeMethods.StreamStartDelegate StreamStartDelegate { get; }
+ internal MsQuicNativeMethods.StreamShutdownDelegate StreamShutdownDelegate { get; }
+ internal MsQuicNativeMethods.StreamSendDelegate StreamSendDelegate { get; }
+ internal MsQuicNativeMethods.StreamReceiveCompleteDelegate StreamReceiveCompleteDelegate { get; }
+ internal MsQuicNativeMethods.StreamReceiveSetEnabledDelegate StreamReceiveSetEnabledDelegate { get; }
+
+ internal MsQuicNativeMethods.SetContextDelegate SetContextDelegate { get; }
+ internal MsQuicNativeMethods.GetContextDelegate GetContextDelegate { get; }
+ internal MsQuicNativeMethods.SetCallbackHandlerDelegate SetCallbackHandlerDelegate { get; }
+
+ internal MsQuicNativeMethods.SetParamDelegate SetParamDelegate { get; }
+ internal MsQuicNativeMethods.GetParamDelegate GetParamDelegate { get; }
+
+ internal unsafe uint UnsafeSetParam(
+ IntPtr Handle,
+ uint Level,
+ uint Param,
+ MsQuicNativeMethods.QuicBuffer Buffer)
+ {
+ return SetParamDelegate(
+ Handle,
+ Level,
+ Param,
+ Buffer.Length,
+ Buffer.Buffer);
+ }
+
+ internal unsafe uint UnsafeGetParam(
+ IntPtr Handle,
+ uint Level,
+ uint Param,
+ ref MsQuicNativeMethods.QuicBuffer Buffer)
+ {
+ uint bufferLength = Buffer.Length;
+ byte* buf = Buffer.Buffer;
+ return GetParamDelegate(
+ Handle,
+ Level,
+ Param,
+ &bufferLength,
+ buf);
+ }
+
+ public async ValueTask<MsQuicSecurityConfig> CreateSecurityConfig(X509Certificate certificate)
+ {
+ MsQuicSecurityConfig secConfig = null;
+ var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+ uint secConfigCreateStatus = MsQuicStatusCodes.InternalError;
+ uint status;
+ if (certificate != null)
+ {
+ status = SecConfigCreateDelegate(
+ _registrationContext,
+ (uint)QUIC_SEC_CONFIG_FLAG.CERT_CONTEXT,
+ certificate.Handle,
+ null,
+ IntPtr.Zero,
+ SecCfgCreateCallbackHandler);
+ }
+ else
+ {
+ status = SecConfigCreateDelegate(
+ _registrationContext,
+ (uint)QUIC_SEC_CONFIG_FLAG.CERT_NULL,
+ IntPtr.Zero,
+ null,
+ IntPtr.Zero,
+ SecCfgCreateCallbackHandler);
+ }
+
+ MsQuicStatusException.ThrowIfFailed(status);
+
+ void SecCfgCreateCallbackHandler(
+ IntPtr context,
+ uint status,
+ IntPtr securityConfig)
+ {
+ secConfig = new MsQuicSecurityConfig(this, securityConfig);
+ secConfigCreateStatus = status;
+ tcs.SetResult(null);
+ }
+
+ await tcs.Task.ConfigureAwait(false);
+
+ MsQuicStatusException.ThrowIfFailed(secConfigCreateStatus);
+
+ return secConfig;
+ }
+
+ public IntPtr SessionOpen(byte[] alpn)
+ {
+ IntPtr sessionPtr = IntPtr.Zero;
+
+ uint status = SessionOpenDelegate(
+ _registrationContext,
+ alpn,
+ IntPtr.Zero,
+ ref sessionPtr);
+ MsQuicStatusException.ThrowIfFailed(status);
+
+ return sessionPtr;
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~MsQuicApi()
+ {
+ Dispose(disposing: false);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ RegistrationCloseDelegate?.Invoke(_registrationContext);
+
+ _disposed = true;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal static class MsQuicParameterHelpers
+ {
+ internal static unsafe SOCKADDR_INET GetINetParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param)
+ {
+ byte* ptr = stackalloc byte[sizeof(SOCKADDR_INET)];
+ QuicBuffer buffer = new QuicBuffer
+ {
+ Length = (uint)sizeof(SOCKADDR_INET),
+ Buffer = ptr
+ };
+
+ MsQuicStatusException.ThrowIfFailed(api.UnsafeGetParam(nativeObject, level, param, ref buffer));
+
+ return *(SOCKADDR_INET*)ptr;
+ }
+
+ internal static unsafe void SetUshortParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param, ushort value)
+ {
+ QuicBuffer buffer = new QuicBuffer()
+ {
+ Length = sizeof(ushort),
+ Buffer = (byte*)&value
+ };
+ MsQuicStatusException.ThrowIfFailed(api.UnsafeSetParam(nativeObject, level, param, buffer));
+ }
+
+ internal static unsafe void SetULongParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param, ulong value)
+ {
+ QuicBuffer buffer = new QuicBuffer()
+ {
+ Length = sizeof(ulong),
+ Buffer = (byte*)&value
+ };
+ MsQuicStatusException.ThrowIfFailed(api.UnsafeGetParam(nativeObject, level, param, ref buffer));
+ }
+
+ internal static unsafe void SetSecurityConfig(MsQuicApi api, IntPtr nativeObject, uint level, uint param, IntPtr value)
+ {
+ QuicBuffer buffer = new QuicBuffer()
+ {
+ Length = (uint)sizeof(void*),
+ Buffer = (byte*)&value
+ };
+ MsQuicStatusException.ThrowIfFailed(api.UnsafeSetParam(nativeObject, level, param, buffer));
+ }
+
+ internal static unsafe ulong GetULongParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param)
+ {
+ byte* ptr = stackalloc byte[sizeof(ulong)];
+ QuicBuffer buffer = new QuicBuffer()
+ {
+ Length = sizeof(ulong),
+ Buffer = ptr
+ };
+ MsQuicStatusException.ThrowIfFailed(api.UnsafeGetParam(nativeObject, level, param, ref buffer));
+ return *(ulong*)ptr;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ // TODO this will eventually be abstracted to support both Client and Server
+ // certificates
+ internal class MsQuicSecurityConfig : IDisposable
+ {
+ private bool _disposed;
+ private MsQuicApi _registration;
+
+ public MsQuicSecurityConfig(MsQuicApi registration, IntPtr nativeObjPtr)
+ {
+ _registration = registration;
+ NativeObjPtr = nativeObjPtr;
+ }
+
+ public IntPtr NativeObjPtr { get; private set; }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _registration.SecConfigDeleteDelegate?.Invoke(NativeObjPtr);
+ NativeObjPtr = IntPtr.Zero;
+ _disposed = true;
+ }
+
+ ~MsQuicSecurityConfig()
+ {
+ Dispose(disposing: false);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal sealed class MsQuicSession : IDisposable
+ {
+ private bool _disposed = false;
+ private IntPtr _nativeObjPtr;
+ private bool _opened;
+
+ internal MsQuicSession()
+ {
+ }
+
+ public IntPtr ConnectionOpen(QuicClientConnectionOptions options)
+ {
+ if (!_opened)
+ {
+ OpenSession(options.ClientAuthenticationOptions.ApplicationProtocols[0].Protocol.ToArray(),
+ (ushort)options.MaxBidirectionalStreams,
+ (ushort)options.MaxUnidirectionalStreams);
+ }
+
+ MsQuicStatusException.ThrowIfFailed(MsQuicApi.Api.ConnectionOpenDelegate(
+ _nativeObjPtr,
+ MsQuicConnection.NativeCallbackHandler,
+ IntPtr.Zero,
+ out IntPtr connectionPtr));
+
+ return connectionPtr;
+ }
+
+ private void OpenSession(byte[] alpn, ushort bidirectionalStreamCount, ushort undirectionalStreamCount)
+ {
+ _opened = true;
+ _nativeObjPtr = MsQuicApi.Api.SessionOpen(alpn);
+ SetPeerBiDirectionalStreamCount(bidirectionalStreamCount);
+ SetPeerUnidirectionalStreamCount(undirectionalStreamCount);
+ }
+
+ // TODO allow for a callback to select the certificate (SNI).
+ public IntPtr ListenerOpen(QuicListenerOptions options)
+ {
+ if (!_opened)
+ {
+ OpenSession(options.ServerAuthenticationOptions.ApplicationProtocols[0].Protocol.ToArray(),
+ (ushort)options.MaxBidirectionalStreams,
+ (ushort)options.MaxUnidirectionalStreams);
+ }
+
+ MsQuicStatusException.ThrowIfFailed(MsQuicApi.Api.ListenerOpenDelegate(
+ _nativeObjPtr,
+ MsQuicListener.NativeCallbackHandler,
+ IntPtr.Zero,
+ out IntPtr listenerPointer));
+
+ return listenerPointer;
+ }
+
+ // TODO call this for graceful shutdown?
+ public void ShutDown(
+ QUIC_CONNECTION_SHUTDOWN_FLAG Flags,
+ ushort ErrorCode)
+ {
+ MsQuicApi.Api.SessionShutdownDelegate(
+ _nativeObjPtr,
+ (uint)Flags,
+ ErrorCode);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public void SetPeerBiDirectionalStreamCount(ushort count)
+ {
+ SetUshortParamter(QUIC_PARAM_SESSION.PEER_BIDI_STREAM_COUNT, count);
+ }
+
+ public void SetPeerUnidirectionalStreamCount(ushort count)
+ {
+ SetUshortParamter(QUIC_PARAM_SESSION.PEER_UNIDI_STREAM_COUNT, count);
+ }
+
+ private unsafe void SetUshortParamter(QUIC_PARAM_SESSION param, ushort count)
+ {
+ var buffer = new MsQuicNativeMethods.QuicBuffer()
+ {
+ Length = sizeof(ushort),
+ Buffer = (byte*)&count
+ };
+
+ SetParam(param, buffer);
+ }
+
+ public void SetDisconnectTimeout(TimeSpan timeout)
+ {
+ SetULongParamter(QUIC_PARAM_SESSION.DISCONNECT_TIMEOUT, (ulong)timeout.TotalMilliseconds);
+ }
+
+ public void SetIdleTimeout(TimeSpan timeout)
+ {
+ SetULongParamter(QUIC_PARAM_SESSION.IDLE_TIMEOUT, (ulong)timeout.TotalMilliseconds);
+
+ }
+ private unsafe void SetULongParamter(QUIC_PARAM_SESSION param, ulong count)
+ {
+ var buffer = new MsQuicNativeMethods.QuicBuffer()
+ {
+ Length = sizeof(ulong),
+ Buffer = (byte*)&count
+ };
+ SetParam(param, buffer);
+ }
+
+ private void SetParam(
+ QUIC_PARAM_SESSION param,
+ MsQuicNativeMethods.QuicBuffer buf)
+ {
+ MsQuicStatusException.ThrowIfFailed(MsQuicApi.Api.UnsafeSetParam(
+ _nativeObjPtr,
+ (uint)QUIC_PARAM_LEVEL.SESSION,
+ (uint)param,
+ buf));
+ }
+
+ ~MsQuicSession()
+ {
+ Dispose(false);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ MsQuicApi.Api.SessionCloseDelegate?.Invoke(_nativeObjPtr);
+ _nativeObjPtr = IntPtr.Zero;
+
+ _disposed = true;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal class MsQuicStatusException : Exception
+ {
+ internal MsQuicStatusException(uint status)
+ : this(status, null)
+ {
+ }
+
+ internal MsQuicStatusException(uint status, string message)
+ : this(status, message, null)
+ {
+ }
+
+ internal MsQuicStatusException(uint status, string message, Exception innerException)
+ : base(GetMessage(status, message), innerException)
+ {
+ Status = status;
+ }
+
+ internal uint Status { get; }
+
+ private static string GetMessage(uint status, string message)
+ {
+ string errorCode = MsQuicStatusCodes.GetError(status);
+ return $"Quic Error: {errorCode}. " + message;
+ }
+
+ internal static void ThrowIfFailed(uint status, string message = null, Exception innerException = null)
+ {
+ if (!MsQuicStatusHelper.SuccessfulStatusCode(status))
+ {
+ throw new MsQuicStatusException(status, message, innerException);
+ }
+ }
+ }
+}
--- /dev/null
+using System.Threading.Tasks;
+using System.Threading.Tasks.Sources;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ /// <summary>
+ /// A resettable completion source which can be completed multiple times.
+ /// Used to make methods async between completed events and their associated async method.
+ /// </summary>
+ internal class ResettableCompletionSource<T> : IValueTaskSource<T>, IValueTaskSource
+ {
+ protected ManualResetValueTaskSourceCore<T> _valueTaskSource;
+
+ public ResettableCompletionSource()
+ {
+ _valueTaskSource.RunContinuationsAsynchronously = true;
+ }
+
+ public ValueTask<T> GetValueTask()
+ {
+ return new ValueTask<T>(this, _valueTaskSource.Version);
+ }
+
+ public ValueTask GetTypelessValueTask()
+ {
+ return new ValueTask(this, _valueTaskSource.Version);
+ }
+
+ public ValueTaskSourceStatus GetStatus(short token)
+ {
+ return _valueTaskSource.GetStatus(token);
+ }
+
+ public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
+ {
+ _valueTaskSource.OnCompleted(continuation, state, token, flags);
+ }
+
+ public void Complete(T result)
+ {
+ _valueTaskSource.SetResult(result);
+ }
+
+ public void CompleteException(Exception ex)
+ {
+ _valueTaskSource.SetException(ex);
+ }
+
+ public T GetResult(short token)
+ {
+ bool isValid = token == _valueTaskSource.Version;
+ try
+ {
+ return _valueTaskSource.GetResult(token);
+ }
+ finally
+ {
+ if (isValid)
+ {
+ _valueTaskSource.Reset();
+ }
+ }
+ }
+
+ void IValueTaskSource.GetResult(short token)
+ {
+ bool isValid = token == _valueTaskSource.Version;
+ try
+ {
+ _valueTaskSource.GetResult(token);
+ }
+ finally
+ {
+ if (isValid)
+ {
+ _valueTaskSource.Reset();
+ }
+ }
+ }
+ }
+ }
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Net.Quic.Implementations.MsQuic.Internal;
+using System.Net.Security;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
+
+namespace System.Net.Quic.Implementations.MsQuic
+{
+ internal sealed class MsQuicConnection : QuicConnectionProvider
+ {
+ private MsQuicSession _session;
+
+ // Pointer to the underlying connection
+ // TODO replace all IntPtr with SafeHandles
+ private IntPtr _ptr;
+
+ // Handle to this object for native callbacks.
+ private GCHandle _handle;
+
+ // Delegate that wraps the static function that will be called when receiving an event.
+ // TODO investigate if the delegate can be static instead.
+ private ConnectionCallbackDelegate _connectionDelegate;
+
+ // 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 bool _disposed;
+ private bool _connected;
+ private MsQuicSecurityConfig _securityConfig;
+
+ // Queue for accepted streams
+ private readonly Channel<MsQuicStream> _acceptQueue = Channel.CreateUnbounded<MsQuicStream>(new UnboundedChannelOptions()
+ {
+ SingleReader = true,
+ SingleWriter = true,
+ });
+
+ // constructor for inbound connections
+ public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, IntPtr nativeObjPtr)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+ _localEndPoint = localEndPoint;
+ _remoteEndPoint = remoteEndPoint;
+ _ptr = nativeObjPtr;
+
+ SetCallbackHandler();
+ SetIdleTimeout(TimeSpan.FromSeconds(120));
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ }
+
+ // constructor for outbound connections
+ public MsQuicConnection(QuicClientConnectionOptions options)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ // TODO need to figure out if/how we want to expose sessions
+ // Creating a session per connection isn't ideal.
+ _session = new MsQuicSession();
+ _ptr = _session.ConnectionOpen(options);
+ _remoteEndPoint = options.RemoteEndPoint;
+
+ SetCallbackHandler();
+ SetIdleTimeout(options.IdleTimeout);
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ }
+
+ internal override IPEndPoint LocalEndPoint
+ {
+ get
+ {
+ return new IPEndPoint(_localEndPoint.Address, _localEndPoint.Port);
+ }
+ }
+
+ internal async ValueTask SetSecurityConfigForConnection(X509Certificate cert)
+ {
+ _securityConfig = await MsQuicApi.Api.CreateSecurityConfig(cert);
+ // TODO this isn't being set correctly
+ MsQuicParameterHelpers.SetSecurityConfig(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.SEC_CONFIG, _securityConfig.NativeObjPtr);
+ }
+
+ internal override IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint.Address, _remoteEndPoint.Port);
+
+ internal override SslApplicationProtocol NegotiatedApplicationProtocol => throw new NotImplementedException();
+
+ 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
+ case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_TRANSPORT:
+ {
+ status = HandleEventShutdownInitiatedByTransport(
+ connectionEvent);
+ }
+ break;
+
+ // Connection is being closed by the peer
+ case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_PEER:
+ {
+ status = HandleEventShutdownInitiatedByPeer(
+ connectionEvent);
+ }
+ break;
+
+ // Connection has been shutdown
+ case QUIC_CONNECTION_EVENT.SHUTDOWN_COMPLETE:
+ {
+ status = HandleEventShutdownComplete(
+ connectionEvent);
+ }
+ break;
+
+ case QUIC_CONNECTION_EVENT.PEER_STREAM_STARTED:
+ {
+ status = HandleEventNewStream(
+ connectionEvent);
+ }
+ break;
+
+ case QUIC_CONNECTION_EVENT.STREAMS_AVAILABLE:
+ {
+ status = HandleEventStreamsAvailable(
+ connectionEvent);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch (Exception)
+ {
+ // TODO we may want to either add a debug assert here or return specific error codes
+ // based on the exception caught.
+ return MsQuicStatusCodes.InternalError;
+ }
+
+ return status;
+ }
+
+ private uint HandleEventConnected(ConnectionEvent connectionEvent)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS);
+ _localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(inetAddress);
+
+ _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);
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventShutdownInitiatedByTransport(ConnectionEvent connectionEvent)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ if (!_connected)
+ {
+ _connectTcs.CompleteException(new IOException("Connection has been shutdown."));
+ }
+
+ _acceptQueue.Writer.Complete();
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventShutdownInitiatedByPeer(ConnectionEvent connectionEvent)
+ {
+ _acceptQueue.Writer.Complete();
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventShutdownComplete(ConnectionEvent connectionEvent)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ _shutdownTcs.Complete(MsQuicStatusCodes.Success);
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventNewStream(ConnectionEvent connectionEvent)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ MsQuicStream msQuicStream = new MsQuicStream(this, connectionEvent.StreamFlags, connectionEvent.Data.NewStream.Stream, inbound: true);
+
+ _acceptQueue.Writer.TryWrite(msQuicStream);
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventStreamsAvailable(ConnectionEvent connectionEvent)
+ {
+ return MsQuicStatusCodes.Success;
+ }
+
+ internal override async ValueTask<QuicStreamProvider> AcceptStreamAsync(CancellationToken cancellationToken = default)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ ThrowIfDisposed();
+
+ if (await _acceptQueue.Reader.WaitToReadAsync(cancellationToken))
+ {
+ if (_acceptQueue.Reader.TryRead(out MsQuicStream stream))
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ return stream;
+ }
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return null;
+ }
+
+ internal override QuicStreamProvider OpenUnidirectionalStream()
+ {
+ ThrowIfDisposed();
+
+ return StreamOpen(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL);
+ }
+
+ internal override QuicStreamProvider OpenBidirectionalStream()
+ {
+ ThrowIfDisposed();
+
+ return StreamOpen(QUIC_STREAM_OPEN_FLAG.NONE);
+ }
+
+ private unsafe void SetIdleTimeout(TimeSpan timeout)
+ {
+ MsQuicParameterHelpers.SetULongParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.IDLE_TIMEOUT, (ulong)timeout.TotalMilliseconds);
+ }
+
+ internal override ValueTask ConnectAsync(CancellationToken cancellationToken = default)
+ {
+ ThrowIfDisposed();
+
+ MsQuicStatusException.ThrowIfFailed(
+ MsQuicApi.Api.ConnectionStartDelegate(
+ _ptr,
+ (ushort)_remoteEndPoint.AddressFamily,
+ _remoteEndPoint.Address.ToString(),
+ (ushort)_remoteEndPoint.Port));
+
+ return _connectTcs.GetTypelessValueTask();
+ }
+
+ private MsQuicStream StreamOpen(
+ QUIC_STREAM_OPEN_FLAG flags)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ IntPtr streamPtr = IntPtr.Zero;
+ MsQuicStatusException.ThrowIfFailed(
+ MsQuicApi.Api.StreamOpenDelegate(
+ _ptr,
+ (uint)flags,
+ MsQuicStream.NativeCallbackHandler,
+ IntPtr.Zero,
+ out streamPtr));
+
+ MsQuicStream stream = new MsQuicStream(this, flags, streamPtr, inbound: false);
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ return stream;
+ }
+
+ private void SetCallbackHandler()
+ {
+ _handle = GCHandle.Alloc(this);
+ _connectionDelegate = new ConnectionCallbackDelegate(NativeCallbackHandler);
+ MsQuicApi.Api.SetCallbackHandlerDelegate(
+ _ptr,
+ _connectionDelegate,
+ GCHandle.ToIntPtr(_handle));
+ }
+
+ private ValueTask ShutdownAsync(
+ QUIC_CONNECTION_SHUTDOWN_FLAG Flags,
+ ushort ErrorCode)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ uint status = MsQuicApi.Api.ConnectionShutdownDelegate(
+ _ptr,
+ (uint)Flags,
+ ErrorCode);
+ MsQuicStatusException.ThrowIfFailed(status);
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ return _shutdownTcs.GetTypelessValueTask();
+ }
+
+ internal static uint NativeCallbackHandler(
+ IntPtr connection,
+ IntPtr context,
+ ref ConnectionEvent connectionEventStruct)
+ {
+ GCHandle handle = GCHandle.FromIntPtr(context);
+ MsQuicConnection quicConnection = (MsQuicConnection)handle.Target;
+ return quicConnection.HandleEvent(ref connectionEventStruct);
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~MsQuicConnection()
+ {
+ Dispose(false);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ if (_ptr != IntPtr.Zero)
+ {
+ MsQuicApi.Api.ConnectionCloseDelegate?.Invoke(_ptr);
+ }
+
+ _ptr = IntPtr.Zero;
+
+ if (disposing)
+ {
+ _handle.Free();
+ _session?.Dispose();
+ _securityConfig?.Dispose();
+ }
+
+ _disposed = true;
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ }
+
+ internal override ValueTask CloseAsync(CancellationToken cancellationToken = default)
+ {
+ ThrowIfDisposed();
+
+ return ShutdownAsync(QUIC_CONNECTION_SHUTDOWN_FLAG.NONE, 0);
+ }
+
+ private void ThrowIfDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(MsQuicStream));
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Quic.Implementations.MsQuic.Internal;
+using System.Net.Security;
+
+namespace System.Net.Quic.Implementations.MsQuic
+{
+ internal sealed class MsQuicImplementationProvider : QuicImplementationProvider
+ {
+ internal override QuicListenerProvider CreateListener(QuicListenerOptions options)
+ {
+ return new MsQuicListener(options);
+ }
+
+ internal override QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options)
+ {
+ return new MsQuicConnection(options);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Quic.Implementations.MsQuic.Internal;
+using System.Net.Security;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
+
+namespace System.Net.Quic.Implementations.MsQuic
+{
+ internal class MsQuicListener : QuicListenerProvider, IDisposable
+ {
+ // Security configuration for MsQuic
+ private MsQuicSession _session;
+
+ // Pointer to the underlying listener
+ // TODO replace all IntPtr with SafeHandles
+ private IntPtr _ptr;
+
+ // Handle to this object for native callbacks.
+ private GCHandle _handle;
+
+ // Delegate that wraps the static function that will be called when receiving an event.
+ private ListenerCallbackDelegate _listenerDelegate;
+
+ // Ssl listening options (ALPN, cert, etc)
+ private SslServerAuthenticationOptions _sslOptions;
+
+ private volatile bool _disposed;
+ private IPEndPoint _listenEndPoint;
+
+ private readonly Channel<MsQuicConnection> _acceptConnectionQueue;
+
+ internal MsQuicListener(QuicListenerOptions options)
+ {
+ _session = new MsQuicSession();
+ _acceptConnectionQueue = Channel.CreateBounded<MsQuicConnection>(new BoundedChannelOptions(options.ListenBacklog)
+ {
+ SingleReader = true,
+ SingleWriter = true
+ });
+
+ _sslOptions = options.ServerAuthenticationOptions;
+ _listenEndPoint = options.ListenEndPoint;
+
+ _ptr = _session.ListenerOpen(options);
+
+ }
+
+ internal override IPEndPoint ListenEndPoint
+ {
+ get
+ {
+ return new IPEndPoint(_listenEndPoint.Address, _listenEndPoint.Port);
+ }
+ }
+
+ internal override async ValueTask<QuicConnectionProvider> AcceptConnectionAsync(CancellationToken cancellationToken = default)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ ThrowIfDisposed();
+
+ if (await _acceptConnectionQueue.Reader.WaitToReadAsync())
+ {
+ if (_acceptConnectionQueue.Reader.TryRead(out MsQuicConnection connection))
+ {
+ // resolve security config here.
+ await connection.SetSecurityConfigForConnection(_sslOptions.ServerCertificate);
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return connection;
+ }
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return null;
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~MsQuicListener()
+ {
+ Dispose(false);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ StopAcceptingConnections();
+
+ if (_ptr != IntPtr.Zero)
+ {
+ MsQuicApi.Api.ListenerStopDelegate(_ptr);
+ MsQuicApi.Api.ListenerCloseDelegate(_ptr);
+ }
+
+ _ptr = IntPtr.Zero;
+
+ // TODO this call to session dispose hangs.
+ //_session.Dispose();
+ _disposed = true;
+ }
+
+ internal override void Start()
+ {
+ ThrowIfDisposed();
+
+ SetCallbackHandler();
+
+ SOCKADDR_INET address = MsQuicAddressHelpers.IPEndPointToINet(_listenEndPoint);
+
+ MsQuicStatusException.ThrowIfFailed(MsQuicApi.Api.ListenerStartDelegate(
+ _ptr,
+ ref address));
+
+ SetListenPort();
+ }
+
+ internal override void Close()
+ {
+ ThrowIfDisposed();
+
+ MsQuicApi.Api.ListenerStopDelegate(_ptr);
+ }
+
+ private unsafe void SetListenPort()
+ {
+ SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.LISTENER, (uint)QUIC_PARAM_LISTENER.LOCAL_ADDRESS);
+
+ _listenEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(inetAddress);
+ }
+
+ internal unsafe uint ListenerCallbackHandler(
+ ref ListenerEvent evt)
+ {
+ try
+ {
+ switch (evt.Type)
+ {
+ 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);
+ MsQuicConnection msQuicConnection = new MsQuicConnection(localEndPoint, remoteEndPoint, evt.Data.NewConnection.Connection);
+ _acceptConnectionQueue.Writer.TryWrite(msQuicConnection);
+ }
+ // Always pend the new connection to wait for the security config to be resolved
+ // TODO this doesn't need to be async always
+ return MsQuicStatusCodes.Pending;
+ default:
+ return MsQuicStatusCodes.InternalError;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ return MsQuicStatusCodes.InternalError;
+ }
+ }
+
+ protected void StopAcceptingConnections()
+ {
+ _acceptConnectionQueue.Writer.TryComplete();
+ }
+
+ internal static uint NativeCallbackHandler(
+ IntPtr listener,
+ IntPtr context,
+ ref ListenerEvent connectionEventStruct)
+ {
+ GCHandle handle = GCHandle.FromIntPtr(context);
+ MsQuicListener quicListener = (MsQuicListener)handle.Target;
+
+ return quicListener.ListenerCallbackHandler(ref connectionEventStruct);
+ }
+
+ internal void SetCallbackHandler()
+ {
+ _handle = GCHandle.Alloc(this);
+ _listenerDelegate = new ListenerCallbackDelegate(NativeCallbackHandler);
+ MsQuicApi.Api.SetCallbackHandlerDelegate(
+ _ptr,
+ _listenerDelegate,
+ GCHandle.ToIntPtr(_handle));
+ }
+
+ private void ThrowIfDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(MsQuicStream));
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Quic.Implementations.MsQuic.Internal;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
+
+namespace System.Net.Quic.Implementations.MsQuic
+{
+ internal sealed class MsQuicStream : QuicStreamProvider
+ {
+ // Pointer to the underlying stream
+ // TODO replace all IntPtr with SafeHandles
+ private readonly IntPtr _ptr;
+
+ // Handle to this object for native callbacks.
+ private GCHandle _handle;
+
+ // Delegate that wraps the static function that will be called when receiving an event.
+ private StreamCallbackDelegate _callback;
+
+ // Backing for StreamId
+ private long _streamId = -1;
+
+ // Resettable completions to be used for multiple calls to send, start, and shutdown.
+ private readonly ResettableCompletionSource<uint> _sendResettableCompletionSource;
+
+ // Resettable completions to be used for multiple calls to receive.
+ private readonly ResettableCompletionSource<uint> _receiveResettableCompletionSource;
+
+ private readonly ResettableCompletionSource<uint> _shutdownWriteResettableCompletionSource;
+
+ // Buffers to hold during a call to send.
+ private readonly MemoryHandle[] _bufferArrays = new MemoryHandle[1];
+ private readonly QuicBuffer[] _sendQuicBuffers = new QuicBuffer[1];
+
+ // Handle to hold when sending.
+ private GCHandle _sendHandle;
+
+ // Used to check if StartAsync has been called.
+ private StartState _started;
+
+ private ReadState _readState;
+
+ private ShutdownWriteState _shutdownState;
+
+ private SendState _sendState;
+
+ // Used by the class to indicate that the stream is m_Readable.
+ private readonly bool _canRead;
+
+ // Used by the class to indicate that the stream is writable.
+ private readonly bool _canWrite;
+
+ private volatile bool _disposed = false;
+
+ private 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();
+
+ // Creates a new MsQuicStream
+ internal MsQuicStream(MsQuicConnection connection, QUIC_STREAM_OPEN_FLAG flags, IntPtr nativeObjPtr, bool inbound)
+ {
+ Debug.Assert(connection != null);
+
+ _ptr = nativeObjPtr;
+
+ if (inbound)
+ {
+ _started = StartState.Finished;
+ _canWrite = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL);
+ _canRead = true;
+ }
+ else
+ {
+ _started = StartState.None;
+ _canWrite = true;
+ _canRead = !flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL);
+ }
+
+ _sendResettableCompletionSource = new ResettableCompletionSource<uint>();
+ _receiveResettableCompletionSource = new ResettableCompletionSource<uint>();
+ _shutdownWriteResettableCompletionSource = new ResettableCompletionSource<uint>();
+
+ SetCallbackHandler();
+ }
+
+ internal override bool CanRead => _canRead;
+
+ internal override bool CanWrite => _canWrite;
+
+ internal override long StreamId
+ {
+ get
+ {
+ if (_streamId == -1)
+ {
+ _streamId = GetStreamId();
+ }
+
+ return _streamId;
+ }
+ }
+
+ internal override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ return WriteAsync(buffer, endStream: false, cancellationToken);
+ }
+
+ internal override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool endStream, CancellationToken cancellationToken = default)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ ThrowIfDisposed();
+
+ if (!_canWrite)
+ {
+ throw new InvalidOperationException("Writing is not allowed on stream.");
+ }
+
+ lock (_sync)
+ {
+ if (_sendState == SendState.Aborted)
+ {
+ throw new OperationCanceledException("Sending has already been aborted on the stream");
+ }
+ }
+
+ using CancellationTokenRegistration registration = cancellationToken.Register(() =>
+ {
+ bool shouldComplete = false;
+ lock (_sync)
+ {
+ if (_sendState == SendState.None)
+ {
+ _sendState = SendState.Aborted;
+ shouldComplete = true;
+ }
+ }
+
+ if (shouldComplete)
+ {
+ _sendResettableCompletionSource.CompleteException(new OperationCanceledException("Write was canceled"));
+ }
+ });
+
+ // Implicit start on first write.
+ if (_started == StartState.None)
+ {
+ _started = StartState.Started;
+ await StartWritesAsync();
+ }
+
+ await SendAsync(buffer, endStream ? QUIC_SEND_FLAG.FIN : QUIC_SEND_FLAG.NONE);
+
+ lock (_sync)
+ {
+ if (_sendState == SendState.Finished)
+ {
+ _sendState = SendState.None;
+ }
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ }
+
+ internal override async ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ ThrowIfDisposed();
+
+ if (!_canRead)
+ {
+ throw new InvalidOperationException("Reading is not allowed on stream.");
+ }
+
+ lock (_sync)
+ {
+ if (_readState == ReadState.ReadsCompleted)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ return 0;
+ }
+ else if (_readState == ReadState.Aborted)
+ {
+ throw new IOException("Reading has been aborted by the peer.");
+ }
+ }
+
+ using CancellationTokenRegistration registration = cancellationToken.Register(() =>
+ {
+ bool shouldComplete = false;
+ lock (_sync)
+ {
+ if (_readState == ReadState.None)
+ {
+ shouldComplete = true;
+ }
+
+ _readState = ReadState.Aborted;
+ }
+
+ if (shouldComplete)
+ {
+ _receiveResettableCompletionSource.CompleteException(new OperationCanceledException("Read was canceled"));
+ }
+ });
+
+ // TODO there could potentially be a perf gain by storing the buffer from the inital read
+ // This reduces the amount of async calls, however it makes it so MsQuic holds onto the buffers
+ // longer than it needs to. We will need to benchmark this.
+ int length = (int)await _receiveResettableCompletionSource.GetValueTask();
+
+ int actual = Math.Min(length, destination.Length);
+
+ static unsafe void CopyToBuffer(Span<byte> destinationBuffer, List<QuicBuffer> sourceBuffers)
+ {
+ Span<byte> slicedBuffer = destinationBuffer;
+ for (int i = 0; i < sourceBuffers.Count; i++)
+ {
+ QuicBuffer nativeBuffer = sourceBuffers[i];
+ int length = Math.Min((int)nativeBuffer.Length, slicedBuffer.Length);
+ new Span<byte>(nativeBuffer.Buffer, length).CopyTo(slicedBuffer);
+ if (length < slicedBuffer.Length)
+ {
+ return;
+ }
+ slicedBuffer = slicedBuffer.Slice(length);
+ }
+ }
+
+ CopyToBuffer(destination.Span, _receiveQuicBuffers);
+
+ lock (_sync)
+ {
+ if (_readState == ReadState.IndividualReadComplete)
+ {
+ ReceiveComplete(actual);
+ EnableReceive();
+ _readState = ReadState.None;
+ }
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return actual;
+ }
+
+ // TODO do we want this to be a synchronization mechanism to cancel a pending read
+ // If so, we need to complete the read here as well.
+ internal override void AbortRead()
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ ThrowIfDisposed();
+
+ lock (_sync)
+ {
+ _readState = ReadState.Aborted;
+ }
+
+ MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT_RECV, errorCode: 0);
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+ }
+
+ internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ ThrowIfDisposed();
+
+ // TODO do anything to stop writes?
+ using CancellationTokenRegistration registration = cancellationToken.Register(() =>
+ {
+ bool shouldComplete = false;
+ lock (_sync)
+ {
+ if (_shutdownState == ShutdownWriteState.None)
+ {
+ _shutdownState = ShutdownWriteState.Canceled;
+ shouldComplete = true;
+ }
+ }
+
+ if (shouldComplete)
+ {
+ _shutdownWriteResettableCompletionSource.CompleteException(new OperationCanceledException("Shutdown was canceled"));
+ }
+ });
+
+ //var status = MsQuicApi.Api._streamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0);
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return _shutdownWriteResettableCompletionSource.GetTypelessValueTask();
+ }
+
+ // TODO consider removing sync-over-async with blocking calls.
+ internal override int Read(Span<byte> buffer)
+ {
+ ThrowIfDisposed();
+
+ return ReadAsync(buffer.ToArray()).GetAwaiter().GetResult();
+ }
+
+ internal override void Write(ReadOnlySpan<byte> buffer)
+ {
+ ThrowIfDisposed();
+
+ WriteAsync(buffer.ToArray()).GetAwaiter().GetResult();
+ }
+
+ // MsQuic doesn't support explicit flushing
+ internal override void Flush()
+ {
+ ThrowIfDisposed();
+ }
+
+ // MsQuic doesn't support explicit flushing
+ internal override Task FlushAsync(CancellationToken cancellationToken = default)
+ {
+ ThrowIfDisposed();
+
+ return default;
+ }
+
+ public override ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return default;
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ CleanupSendState();
+
+ if (_ptr != IntPtr.Zero)
+ {
+ // TODO resolve graceful vs abortive dispose here. Will file a separate issue.
+ //MsQuicApi.Api._streamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 1);
+ MsQuicApi.Api.StreamCloseDelegate?.Invoke(_ptr);
+ }
+
+ _handle.Free();
+
+ _disposed = true;
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return default;
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~MsQuicStream()
+ {
+ Dispose(false);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ CleanupSendState();
+
+ if (_ptr != IntPtr.Zero)
+ {
+ // TODO resolve graceful vs abortive dispose here. Will file a separate issue.
+ //MsQuicApi.Api._streamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 1);
+ MsQuicApi.Api.StreamCloseDelegate?.Invoke(_ptr);
+ }
+
+ _handle.Free();
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ _disposed = true;
+ }
+
+ private void EnableReceive()
+ {
+ MsQuicApi.Api.StreamReceiveSetEnabledDelegate(_ptr, enabled: true);
+ }
+
+ internal static uint NativeCallbackHandler(
+ IntPtr stream,
+ IntPtr context,
+ StreamEvent connectionEventStruct)
+ {
+ var handle = GCHandle.FromIntPtr(context);
+ var quicStream = (MsQuicStream)handle.Target;
+
+ return quicStream.HandleEvent(ref connectionEventStruct);
+ }
+
+ private uint HandleEvent(ref StreamEvent evt)
+ {
+ uint status = MsQuicStatusCodes.Success;
+
+ try
+ {
+ switch (evt.Type)
+ {
+ // Stream has started.
+ // Will only be done for outbound streams (inbound streams have already started)
+ case QUIC_STREAM_EVENT.START_COMPLETE:
+ status = HandleStartComplete();
+ break;
+ // Received data on the stream
+ case QUIC_STREAM_EVENT.RECEIVE:
+ {
+ status = HandleEventRecv(ref evt);
+ }
+ break;
+ // Send has completed.
+ // Contains a canceled bool to indicate if the send was canceled.
+ case QUIC_STREAM_EVENT.SEND_COMPLETE:
+ {
+ status = HandleEventSendComplete(ref evt);
+ }
+ break;
+ // Peer has told us to shutdown the reading side of the stream.
+ case QUIC_STREAM_EVENT.PEER_SEND_SHUTDOWN:
+ {
+ status = HandleEventPeerSendShutdown();
+ }
+ break;
+ // Peer has told us to abort the reading side of the stream.
+ case QUIC_STREAM_EVENT.PEER_SEND_ABORTED:
+ {
+ status = HandleEventPeerSendAborted();
+ }
+ break;
+ // Peer has stopped receiving data, don't send anymore.
+ // Potentially throw when WriteAsync/FlushAsync.
+ case QUIC_STREAM_EVENT.PEER_RECEIVE_ABORTED:
+ {
+ status = HandleEventPeerRecvAbort();
+ }
+ break;
+ // Occurs when shutdown is completed for the send side.
+ // This only happens for shutdown on sending, not receiving
+ // Receive shutdown can only be abortive.
+ case QUIC_STREAM_EVENT.SEND_SHUTDOWN_COMPLETE:
+ {
+ status = HandleEventSendShutdownComplete(ref evt);
+ }
+ break;
+ // Shutdown for both sending and receiving is completed.
+ case QUIC_STREAM_EVENT.SHUTDOWN_COMPLETE:
+ {
+ status = HandleEventShutdownComplete();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ catch (Exception)
+ {
+ return MsQuicStatusCodes.InternalError;
+ }
+
+ return status;
+ }
+
+ private unsafe uint HandleEventRecv(ref MsQuicNativeMethods.StreamEvent evt)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ StreamEventDataRecv receieveEvent = evt.Data.Recv;
+ for (int i = 0; i < receieveEvent.BufferCount; i++)
+ {
+ _receiveQuicBuffers.Add(receieveEvent.Buffers[i]);
+ }
+
+ bool shouldComplete = false;
+ lock (_sync)
+ {
+ if (_readState == ReadState.None)
+ {
+ shouldComplete = true;
+ }
+ _readState = ReadState.IndividualReadComplete;
+ }
+
+ if (shouldComplete)
+ {
+ _receiveResettableCompletionSource.Complete((uint)receieveEvent.TotalBufferLength);
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Pending;
+ }
+
+ private uint HandleEventPeerRecvAbort()
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleStartComplete()
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ bool shouldComplete = false;
+ lock (_sync)
+ {
+ _started = StartState.Finished;
+
+ // Check send state before completing as send cancellation is shared between start and send.
+ if (_sendState == SendState.None)
+ {
+ shouldComplete = true;
+ }
+ }
+
+ if (shouldComplete)
+ {
+ _sendResettableCompletionSource.Complete(MsQuicStatusCodes.Success);
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventSendShutdownComplete(ref MsQuicNativeMethods.StreamEvent evt)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+ bool shouldComplete = false;
+ lock (_sync)
+ {
+ if (_shutdownState == ShutdownWriteState.None)
+ {
+ _shutdownState = ShutdownWriteState.Finished;
+ shouldComplete = true;
+ }
+ }
+
+ if (shouldComplete)
+ {
+ _shutdownWriteResettableCompletionSource.Complete(MsQuicStatusCodes.Success);
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventShutdownComplete()
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ bool shouldReadComplete = false;
+ bool shouldShutdownWriteComplete = false;
+
+ lock (_sync)
+ {
+ // This event won't occur within the middle of a receive.
+ if (NetEventSource.IsEnabled) NetEventSource.Info("Completing resettable event source.");
+
+ if (_readState == ReadState.None)
+ {
+ shouldReadComplete = true;
+ }
+
+ _readState = ReadState.ReadsCompleted;
+
+ if (_shutdownState == ShutdownWriteState.None)
+ {
+ _shutdownState = ShutdownWriteState.Finished;
+ shouldShutdownWriteComplete = true;
+ }
+ }
+
+ if (shouldReadComplete)
+ {
+ _receiveResettableCompletionSource.Complete(0);
+ }
+
+ if (shouldShutdownWriteComplete)
+ {
+ _shutdownWriteResettableCompletionSource.Complete(MsQuicStatusCodes.Success);
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventPeerSendAborted()
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ bool shouldComplete = false;
+ lock (_sync)
+ {
+ if (_readState == ReadState.None)
+ {
+ shouldComplete = true;
+ }
+ _readState = ReadState.Aborted;
+ }
+
+ if (shouldComplete)
+ {
+ _receiveResettableCompletionSource.CompleteException(new IOException("Reading has been aborted by the peer."));
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventPeerSendShutdown()
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ bool shouldComplete = false;
+
+ lock (_sync)
+ {
+ // This event won't occur within the middle of a receive.
+ if (NetEventSource.IsEnabled) NetEventSource.Info("Completing resettable event source.");
+
+ if (_readState == ReadState.None)
+ {
+ shouldComplete = true;
+ }
+
+ _readState = ReadState.ReadsCompleted;
+ }
+
+ if (shouldComplete)
+ {
+ _receiveResettableCompletionSource.Complete(0);
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Success;
+ }
+
+ private uint HandleEventSendComplete(ref StreamEvent evt)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
+
+ CleanupSendState();
+
+ // TODO throw if a write was canceled.
+ uint errorCode = evt.Data.SendComplete.Canceled;
+
+ bool shouldComplete = false;
+ lock (_sync)
+ {
+ if (_sendState == SendState.None)
+ {
+ _sendState = SendState.Finished;
+ shouldComplete = true;
+ }
+ }
+
+ if (shouldComplete)
+ {
+ _sendResettableCompletionSource.Complete(MsQuicStatusCodes.Success);
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
+
+ return MsQuicStatusCodes.Success;
+ }
+
+ private void CleanupSendState()
+ {
+ if (_sendHandle.IsAllocated)
+ {
+ _sendHandle.Free();
+ }
+ // Callings dispose twice on a memory handle should be okay
+ _bufferArrays[0].Dispose();
+ }
+
+ private void SetCallbackHandler()
+ {
+ _handle = GCHandle.Alloc(this);
+
+ _callback = new StreamCallbackDelegate(NativeCallbackHandler);
+ MsQuicApi.Api.SetCallbackHandlerDelegate(
+ _ptr,
+ _callback,
+ GCHandle.ToIntPtr(_handle));
+ }
+
+ // TODO prevent overlapping sends or consider supporting it.
+ private unsafe ValueTask SendAsync(
+ ReadOnlyMemory<byte> buffer,
+ QUIC_SEND_FLAG flags)
+ {
+ if (buffer.IsEmpty)
+ {
+ if ((flags & QUIC_SEND_FLAG.FIN) == QUIC_SEND_FLAG.FIN)
+ {
+ // Start graceful shutdown sequence if passed in the fin flag and there is an empty buffer.
+ MsQuicApi.Api.StreamShutdownDelegate(_ptr, (uint)QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, errorCode: 0);
+ }
+ return default;
+ }
+
+ MemoryHandle handle = buffer.Pin();
+ _sendQuicBuffers[0].Length = (uint)buffer.Length;
+ _sendQuicBuffers[0].Buffer = (byte*)handle.Pointer;
+
+ _bufferArrays[0] = handle;
+
+ _sendHandle = GCHandle.Alloc(_sendQuicBuffers, GCHandleType.Pinned);
+
+ var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(_sendQuicBuffers, 0);
+
+ uint status = MsQuicApi.Api.StreamSendDelegate(
+ _ptr,
+ quicBufferPointer,
+ bufferCount: 1,
+ (uint)flags,
+ _ptr);
+
+ if (!MsQuicStatusHelper.SuccessfulStatusCode(status))
+ {
+ CleanupSendState();
+ MsQuicStatusException.ThrowIfFailed(status);
+ }
+
+ return _sendResettableCompletionSource.GetTypelessValueTask();
+ }
+
+ private ValueTask<uint> StartWritesAsync()
+ {
+ uint status = MsQuicApi.Api.StreamStartDelegate(
+ _ptr,
+ (uint)QUIC_STREAM_START_FLAG.ASYNC);
+
+ MsQuicStatusException.ThrowIfFailed(status);
+ return _sendResettableCompletionSource.GetValueTask();
+ }
+
+ private void ReceiveComplete(int bufferLength)
+ {
+ uint status = MsQuicApi.Api.StreamReceiveCompleteDelegate(_ptr, (ulong)bufferLength);
+ MsQuicStatusException.ThrowIfFailed(status);
+ }
+
+ // This can fail if the stream isn't started.
+ private unsafe long GetStreamId()
+ {
+ return (long)MsQuicParameterHelpers.GetULongParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.STREAM, (uint)QUIC_PARAM_STREAM.ID);
+ }
+
+ private void ThrowIfDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(MsQuicStream));
+ }
+ }
+
+ private enum StartState
+ {
+ None,
+ Started,
+ Finished
+ }
+
+ private enum ReadState
+ {
+ None,
+ IndividualReadComplete,
+ ReadsCompleted,
+ Aborted
+ }
+
+ private enum ShutdownWriteState
+ {
+ None,
+ Canceled,
+ Finished
+ }
+
+ private enum SendState
+ {
+ None,
+ Aborted,
+ Finished
+ }
+ }
+}
internal abstract System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol { get; }
- internal abstract void Close();
+ internal abstract ValueTask CloseAsync(CancellationToken cancellationToken = default);
public abstract void Dispose();
}
{
internal QuicImplementationProvider() { }
- internal abstract QuicListenerProvider CreateListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions);
+ internal abstract QuicListenerProvider CreateListener(QuicListenerOptions options);
- internal abstract QuicConnectionProvider CreateConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint);
+ internal abstract QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options);
}
}
internal abstract ValueTask<QuicConnectionProvider> AcceptConnectionAsync(CancellationToken cancellationToken = default);
+ internal abstract void Start();
+
internal abstract void Close();
public abstract void Dispose();
namespace System.Net.Quic.Implementations
{
- internal abstract class QuicStreamProvider : IDisposable
+ internal abstract class QuicStreamProvider : IDisposable, IAsyncDisposable
{
internal abstract long StreamId { get; }
internal abstract ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);
- internal abstract void ShutdownRead();
+ internal abstract void AbortRead();
internal abstract bool CanWrite { get; }
internal abstract ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default);
- internal abstract void ShutdownWrite();
+ internal abstract ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool endStream, CancellationToken cancellationToken = default);
+
+ internal abstract ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default);
internal abstract void Flush();
internal abstract Task FlushAsync(CancellationToken cancellationToken);
public abstract void Dispose();
+
+ public abstract ValueTask DisposeAsync();
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+
+namespace System.Net
+{
+ [EventSource(Name = "Microsoft-System-Net-Quic")]
+ internal sealed partial class NetEventSource : EventSource
+ {
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Security;
+
+namespace System.Net.Quic
+{
+ /// <summary>
+ /// Options to provide to the <see cref="QuicConnection"/> when connecting to a Listener.
+ /// </summary>
+ public class QuicClientConnectionOptions
+ {
+ /// <summary>
+ /// Client authentication options to use when establishing a <see cref="QuicConnection"/>.
+ /// </summary>
+ public SslClientAuthenticationOptions ClientAuthenticationOptions { get; set; }
+
+ /// <summary>
+ /// The local endpoint that will be bound to.
+ /// </summary>
+ public IPEndPoint LocalEndPoint { get; set; }
+
+ /// <summary>
+ /// The endpoint to connect to.
+ /// </summary>
+ public IPEndPoint RemoteEndPoint { get; set; }
+
+ /// <summary>
+ /// Limit on the number of bidirectional streams the peer connection can create
+ /// on an accepted connection.
+ /// Default is 100.
+ /// </summary>
+ // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using.
+ public long MaxBidirectionalStreams { get; set; } = 100;
+
+ /// <summary>
+ /// Limit on the number of unidirectional streams the peer connection can create
+ /// on an accepted connection.
+ /// Default is 100.
+ /// </summary>
+ // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using.
+ public long MaxUnidirectionalStreams { get; set; } = 100;
+
+ /// <summary>
+ /// Idle timeout for connections, afterwhich the connection will be closed.
+ /// </summary>
+ public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(2);
+ }
+}
// !!! TEMPORARY: Remove "implementationProvider" before shipping
public QuicConnection(QuicImplementationProvider implementationProvider, IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null)
+ : this(implementationProvider, new QuicClientConnectionOptions() { RemoteEndPoint = remoteEndPoint, ClientAuthenticationOptions = sslClientAuthenticationOptions, LocalEndPoint = localEndPoint })
{
- _provider = implementationProvider.CreateConnection(remoteEndPoint, sslClientAuthenticationOptions, localEndPoint);
+ }
+
+ public QuicConnection(QuicImplementationProvider implementationProvider, QuicClientConnectionOptions options)
+ {
+ _provider = implementationProvider.CreateConnection(options);
}
internal QuicConnection(QuicConnectionProvider provider)
/// <summary>
/// Close the connection and terminate any active streams.
/// </summary>
- public void Close() => _provider.Close();
+ public ValueTask CloseAsync(CancellationToken cancellationToken = default) => _provider.CloseAsync(cancellationToken);
public void Dispose() => _provider.Dispose();
}
public static class QuicImplementationProviders
{
public static Implementations.QuicImplementationProvider Mock { get; } = new Implementations.Mock.MockImplementationProvider();
-
- public static Implementations.QuicImplementationProvider Default => Mock;
+ public static Implementations.QuicImplementationProvider MsQuic { get; } = new Implementations.MsQuic.MsQuicImplementationProvider();
+ public static Implementations.QuicImplementationProvider Default => MsQuic;
}
}
// !!! TEMPORARY: Remove "implementationProvider" before shipping
public QuicListener(QuicImplementationProvider implementationProvider, IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
+ : this(implementationProvider, new QuicListenerOptions() { ListenEndPoint = listenEndPoint, ServerAuthenticationOptions = sslServerAuthenticationOptions })
{
- _provider = implementationProvider.CreateListener(listenEndPoint, sslServerAuthenticationOptions);
+ }
+
+ public QuicListener(QuicImplementationProvider implementationProvider, QuicListenerOptions options)
+ {
+ _provider = implementationProvider.CreateListener(options);
}
public IPEndPoint ListenEndPoint => _provider.ListenEndPoint;
public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken cancellationToken = default) =>
new QuicConnection(await _provider.AcceptConnectionAsync(cancellationToken).ConfigureAwait(false));
+ public void Start() => _provider.Start();
+
/// <summary>
/// Stop listening and close the listener.
/// </summary>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Security;
+
+namespace System.Net.Quic
+{
+ /// <summary>
+ /// Options to provide to the <see cref="QuicListener"/>.
+ /// </summary>
+ public class QuicListenerOptions
+ {
+ /// <summary>
+ /// Server Ssl options to use for ALPN, SNI, etc.
+ /// </summary>
+ public SslServerAuthenticationOptions ServerAuthenticationOptions { get; set; }
+
+ /// <summary>
+ /// The endpoint to listen on.
+ /// </summary>
+ public IPEndPoint ListenEndPoint { get; set; }
+
+ /// <summary>
+ /// Number of connections to be held without accepting the connection.
+ /// </summary>
+ public int ListenBacklog { get; set; } = 512;
+
+ /// <summary>
+ /// Limit on the number of bidirectional streams an accepted connection can create
+ /// back to the client.
+ /// Default is 100.
+ /// </summary>
+ // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using.
+ public long MaxBidirectionalStreams { get; set; } = 100;
+
+ /// <summary>
+ /// Limit on the number of unidirectional streams the peer connection can create.
+ /// Default is 100.
+ /// </summary>
+ // TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using.
+ public long MaxUnidirectionalStreams { get; set; } = 100;
+
+
+ /// <summary>
+ /// Idle timeout for connections, afterwhich the connection will be closed.
+ /// </summary>
+ public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(10);
+ }
+}
public override Task FlushAsync(CancellationToken cancellationToken) => _provider.FlushAsync(cancellationToken);
- public void ShutdownRead() => _provider.ShutdownRead();
+ public void AbortRead() => _provider.AbortRead();
- public void ShutdownWrite() => _provider.ShutdownWrite();
+ public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool endStream, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffer, endStream, cancellationToken);
+
+ public ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default) => _provider.ShutdownWriteCompleted(cancellationToken);
protected override void Dispose(bool disposing)
{
--- /dev/null
+using System.Collections.Generic;
+using System.Net.Security;
+
+namespace System.Net.Quic.Tests
+{
+ public class MsQuicTestBase : IDisposable
+ {
+ public MsQuicTestBase()
+ {
+ IPEndPoint endpoint = new IPEndPoint(IPAddress.Loopback, 8000);
+ DefaultListener = CreateQuicListener(endpoint);
+ }
+
+ public QuicListener DefaultListener { get; }
+
+ public SslServerAuthenticationOptions GetSslServerAuthenticationOptions()
+ {
+ return new SslServerAuthenticationOptions()
+ {
+ ApplicationProtocols = new List<SslApplicationProtocol>() { new SslApplicationProtocol("quictest") }
+ };
+ }
+
+ public SslClientAuthenticationOptions GetSslClientAuthenticationOptions()
+ {
+ return new SslClientAuthenticationOptions()
+ {
+ ApplicationProtocols = new List<SslApplicationProtocol>() { new SslApplicationProtocol("quictest") }
+ };
+ }
+
+ public QuicConnection CreateQuicConnection(IPEndPoint endpoint)
+ {
+ return new QuicConnection(QuicImplementationProviders.MsQuic, endpoint, GetSslClientAuthenticationOptions());
+ }
+
+ public QuicListener CreateQuicListener(IPEndPoint endpoint)
+ {
+ QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, endpoint, GetSslServerAuthenticationOptions());
+ listener.Start();
+ return listener;
+ }
+
+ public void Dispose()
+ {
+ DefaultListener.Dispose();
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+using System.Net.Security;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Net.Quic.Tests
+{
+ public class MsQuicTests : MsQuicTestBase
+ {
+ private static ReadOnlyMemory<byte> s_data = Encoding.UTF8.GetBytes("Hello world!");
+
+ [Fact(Skip = "MsQuic not available")]
+ public async Task BasicTest()
+ {
+ for (int i = 0; i < 100; i++)
+ {
+ Task listenTask = Task.Run(async () =>
+ {
+ using QuicConnection connection = await DefaultListener.AcceptConnectionAsync();
+ await using QuicStream stream = await connection.AcceptStreamAsync();
+
+ byte[] buffer = new byte[s_data.Length];
+ int bytesRead = await stream.ReadAsync(buffer);
+
+ Assert.Equal(s_data.Length, bytesRead);
+ Assert.True(s_data.Span.SequenceEqual(buffer));
+
+ await stream.WriteAsync(s_data, endStream: true);
+ await stream.ShutdownWriteCompleted();
+
+ await connection.CloseAsync();
+ });
+
+ Task clientTask = Task.Run(async () =>
+ {
+ using QuicConnection connection = CreateQuicConnection(DefaultListener.ListenEndPoint);
+ await connection.ConnectAsync();
+ await using QuicStream stream = connection.OpenBidirectionalStream();
+
+ await stream.WriteAsync(s_data, endStream: true);
+
+ byte[] memory = new byte[12];
+ int bytesRead = await stream.ReadAsync(memory);
+
+ Assert.Equal(s_data.Length, bytesRead);
+ // TODO this failed once...
+ Assert.True(s_data.Span.SequenceEqual(memory));
+ await stream.ShutdownWriteCompleted();
+
+ await connection.CloseAsync();
+ });
+
+ await (new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 10000);
+ }
+ }
+
+ [Fact(Skip = "MsQuic not available")]
+ public async Task MultipleReadsAndWrites()
+ {
+ for (int j = 0; j < 100; j++)
+ {
+ Task listenTask = Task.Run(async () =>
+ {
+ // Connection isn't being accepted, interesting.
+ using QuicConnection connection = await DefaultListener.AcceptConnectionAsync();
+ await using QuicStream stream = await connection.AcceptStreamAsync();
+ byte[] buffer = new byte[s_data.Length];
+
+ while (true)
+ {
+ int bytesRead = await stream.ReadAsync(buffer);
+ if (bytesRead == 0)
+ {
+ break;
+ }
+ Assert.Equal(s_data.Length, bytesRead);
+ Assert.True(s_data.Span.SequenceEqual(buffer));
+ }
+
+ for (int i = 0; i < 5; i++)
+ {
+ await stream.WriteAsync(s_data);
+ }
+ await stream.WriteAsync(Memory<byte>.Empty, endStream: true);
+ await stream.ShutdownWriteCompleted();
+ await connection.CloseAsync();
+ });
+
+ Task clientTask = Task.Run(async () =>
+ {
+ using QuicConnection connection = CreateQuicConnection(DefaultListener.ListenEndPoint);
+ await connection.ConnectAsync();
+ await using QuicStream stream = connection.OpenBidirectionalStream();
+
+ for (int i = 0; i < 5; i++)
+ {
+ await stream.WriteAsync(s_data);
+ }
+
+ await stream.WriteAsync(Memory<byte>.Empty, endStream: true);
+
+ byte[] memory = new byte[12];
+ while (true)
+ {
+ int res = await stream.ReadAsync(memory);
+ if (res == 0)
+ {
+ break;
+ }
+ Assert.True(s_data.Span.SequenceEqual(memory));
+ }
+
+ await stream.ShutdownWriteCompleted();
+ await connection.CloseAsync();
+ });
+
+ await (new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 1000000);
+
+ }
+ }
+
+ [Fact(Skip = "MsQuic not available")]
+ public async Task MultipleStreamsOnSingleConnection()
+ {
+ Task listenTask = Task.Run(async () =>
+ {
+ {
+ using QuicConnection connection = await DefaultListener.AcceptConnectionAsync();
+ await using QuicStream stream = await connection.AcceptStreamAsync();
+ await using QuicStream stream2 = await connection.AcceptStreamAsync();
+
+ byte[] buffer = new byte[s_data.Length];
+
+ while (true)
+ {
+ int bytesRead = await stream.ReadAsync(buffer);
+ if (bytesRead == 0)
+ {
+ break;
+ }
+ Assert.Equal(s_data.Length, bytesRead);
+ Assert.True(s_data.Span.SequenceEqual(buffer));
+ }
+
+ while (true)
+ {
+ int bytesRead = await stream2.ReadAsync(buffer);
+ if (bytesRead == 0)
+ {
+ break;
+ }
+ Assert.True(s_data.Span.SequenceEqual(buffer));
+ }
+
+ await stream.WriteAsync(s_data, endStream: true);
+ await stream.ShutdownWriteCompleted();
+
+ await stream2.WriteAsync(s_data, endStream: true);
+ await stream2.ShutdownWriteCompleted();
+
+ await connection.CloseAsync();
+ }
+ });
+
+ Task clientTask = Task.Run(async () =>
+ {
+ using QuicConnection connection = CreateQuicConnection(DefaultListener.ListenEndPoint);
+ await connection.ConnectAsync();
+ await using QuicStream stream = connection.OpenBidirectionalStream();
+ await using QuicStream stream2 = connection.OpenBidirectionalStream();
+
+ await stream.WriteAsync(s_data, endStream: true);
+ await stream.ShutdownWriteCompleted();
+ await stream2.WriteAsync(s_data, endStream: true);
+ await stream2.ShutdownWriteCompleted();
+
+ byte[] memory = new byte[12];
+ while (true)
+ {
+ int res = await stream.ReadAsync(memory);
+ if (res == 0)
+ {
+ break;
+ }
+ Assert.True(s_data.Span.SequenceEqual(memory));
+ }
+
+ while (true)
+ {
+ int res = await stream2.ReadAsync(memory);
+ if (res == 0)
+ {
+ break;
+ }
+ Assert.True(s_data.Span.SequenceEqual(memory));
+ }
+
+ await connection.CloseAsync();
+ });
+
+ await (new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 60000);
+ }
+
+ [Fact(Skip = "MsQuic not available")]
+ public async Task AbortiveConnectionFromClient()
+ {
+ using QuicConnection clientConnection = CreateQuicConnection(DefaultListener.ListenEndPoint);
+
+ ValueTask clientTask = clientConnection.ConnectAsync();
+ using QuicConnection serverConnection = await DefaultListener.AcceptConnectionAsync();
+ await clientTask;
+ // Close connection on client, verifying server connection is aborted.
+ await clientConnection.CloseAsync();
+ QuicStream stream = await serverConnection.AcceptStreamAsync();
+
+ // Providers are alaways wrapped right now by a QuicStream. All fields are null here.
+ // TODO make sure this returns null.
+ Assert.Throws<NullReferenceException>(() => stream.CanRead);
+ }
+
+ [Fact(Skip = "MsQuic not available")]
+ public async Task TestStreams()
+ {
+ using (QuicListener listener = new QuicListener(
+ QuicImplementationProviders.MsQuic,
+ new IPEndPoint(IPAddress.Loopback, 0),
+ GetSslServerAuthenticationOptions()))
+ {
+ listener.Start();
+ IPEndPoint listenEndPoint = listener.ListenEndPoint;
+
+ 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();
+ }
+ }
+ }
+
+ private static async Task CreateAndTestBidirectionalStream(QuicConnection c1, QuicConnection c2)
+ {
+ 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);
+ }
+ }
+ }
+
+ private static async Task CreateAndTestUnidirectionalStream(QuicConnection c1, QuicConnection c2)
+ {
+ using (QuicStream s1 = c1.OpenUnidirectionalStream())
+ {
+ 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)
+ {
+ Assert.True(s1.CanRead);
+ Assert.True(s1.CanWrite);
+ Assert.True(s2.CanRead);
+ Assert.True(s2.CanWrite);
+ Assert.Equal(s1.StreamId, s2.StreamId);
+
+ await SendAndReceiveDataAsync(s_data, s1, s2);
+ await SendAndReceiveDataAsync(s_data, s2, s1);
+ await SendAndReceiveDataAsync(s_data, s2, s1);
+ await SendAndReceiveDataAsync(s_data, s1, s2);
+
+ await SendAndReceiveEOFAsync(s1, s2);
+ await SendAndReceiveEOFAsync(s2, s1);
+ }
+
+ private static async Task TestUnidirectionalStream(QuicStream s1, QuicStream s2)
+ {
+ Assert.False(s1.CanRead);
+ Assert.True(s1.CanWrite);
+ Assert.True(s2.CanRead);
+ Assert.False(s2.CanWrite);
+ Assert.Equal(s1.StreamId, s2.StreamId);
+
+ await SendAndReceiveDataAsync(s_data, s1, s2);
+ await SendAndReceiveDataAsync(s_data, s1, s2);
+
+ await SendAndReceiveEOFAsync(s1, s2);
+ }
+
+ private static async Task SendAndReceiveDataAsync(ReadOnlyMemory<byte> data, QuicStream s1, QuicStream s2)
+ {
+ await s1.WriteAsync(data);
+ await ReceiveDataAsync(data, s2);
+ }
+
+ private static async Task ReceiveDataAsync(ReadOnlyMemory<byte> data, QuicStream s)
+ {
+ Memory<byte> readBuffer = new byte[data.Length];
+
+ int bytesRead = 0;
+ while (bytesRead < data.Length)
+ {
+ bytesRead += await s.ReadAsync(readBuffer.Slice(bytesRead));
+ }
+
+ Assert.True(data.Span.SequenceEqual(readBuffer.Span));
+ }
+
+ private static async Task SendAndReceiveEOFAsync(QuicStream s1, QuicStream s2)
+ {
+ byte[] readBuffer = new byte[1];
+
+ await s1.WriteAsync(Memory<byte>.Empty, endStream: true);
+ await s1.ShutdownWriteCompleted();
+
+ int bytesRead = await s2.ReadAsync(readBuffer);
+ Assert.Equal(0, bytesRead);
+
+ // Another read should still give EOF
+ bytesRead = await s2.ReadAsync(readBuffer);
+ Assert.Equal(0, bytesRead);
+ }
+ }
+}
{
using (QuicListener listener = new QuicListener(QuicImplementationProviders.Mock, new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null))
{
+ listener.Start();
IPEndPoint listenEndPoint = listener.ListenEndPoint;
await Task.WhenAll(
{
using (QuicListener listener = new QuicListener(QuicImplementationProviders.Mock, new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null))
{
+ listener.Start();
+
IPEndPoint listenEndPoint = listener.ListenEndPoint;
using (QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.Mock, listenEndPoint, sslClientAuthenticationOptions: null))
{
byte[] readBuffer = new byte[1];
- s1.ShutdownWrite();
+ await s1.WriteAsync(Memory<byte>.Empty, endStream: true);
+
+ await s1.ShutdownWriteCompleted();
int bytesRead = await s2.ReadAsync(readBuffer);
Assert.Equal(0, bytesRead);
<Configurations>$(NetCoreAppCurrent)-Unix-Debug;$(NetCoreAppCurrent)-Unix-Release;$(NetCoreAppCurrent)-Windows_NT-Debug;$(NetCoreAppCurrent)-Windows_NT-Release</Configurations>
</PropertyGroup>
<ItemGroup>
+ <Compile Include="MsQuicTestBase.cs" />
+ <Compile Include="MsQuicTests.cs" />
<Compile Include="QuicConnectionTests.cs" />
+ <Compile Include="$(CommonTestPath)System\Diagnostics\Tracing\ConsoleEventListener.cs">
+ <Link>Common\System\Diagnostics\Tracing\ConsoleEventListener.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)\System\Threading\Tasks\TaskTimeoutExtensions.cs">
+ <Link>Common\System\Threading\Tasks\TaskTimeoutExtensions.cs</Link>
+ </Compile>
</ItemGroup>
</Project>
\ No newline at end of file