Add MsQuic implementation of System.Net.Quic (#427)
authorJustin Kotalik <jkotalik12@gmail.com>
Thu, 9 Jan 2020 17:55:04 +0000 (09:55 -0800)
committerGitHub <noreply@github.com>
Thu, 9 Jan 2020 17:55:04 +0000 (09:55 -0800)
47 files changed:
src/libraries/Common/src/Interop/Linux/Interop.Libraries.cs
src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs
src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs
src/libraries/System.Net.Quic/ref/System.Net.Quic.Temporary.cs
src/libraries/System.Net.Quic/ref/System.Net.Quic.cs
src/libraries/System.Net.Quic/src/Configurations.props
src/libraries/System.Net.Quic/src/Interop/Interop.MsQuic.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Interop/Linux/MsQuicStatusCodes.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Interop/Linux/MsQuicStatusHelper.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Interop/MsQuicEnums.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Interop/MsQuicNativeMethods.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Interop/OSX/MsQuicStatusCodes.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Interop/OSX/MsQuicStatusHelper.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Interop/Windows/MsQuicStatusCodes.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Interop/Windows/MsQuicStatusHelper.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/PInvokeAnalyzerExceptionList.analyzerdata [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockConnection.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockImplementationProvider.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockListener.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockStream.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicStatusException.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/ResettableCompletionSource.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicImplementationProvider.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicConnectionProvider.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicImplementationProvider.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicListenerProvider.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicStreamProvider.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/NetEventSource.Quic.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicImplementationProviders.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListenerOptions.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs
src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs
src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Tests.csproj

index 1cec655..25f0ce0 100644 (file)
@@ -7,5 +7,6 @@ internal static partial class Interop
     internal static partial class Libraries
     {
         internal const string Odbc32 = "libodbc.so.2";
+        internal const string MsQuic = "msquic";
     }
 }
index ffac0b2..84bf6d6 100644 (file)
@@ -15,5 +15,7 @@ internal static partial class Interop
         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";
+
     }
 }
index 8231632..8b4df46 100644 (file)
@@ -38,5 +38,6 @@ internal static partial class Interop
         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";
     }
 }
index f9735f4..8190934 100644 (file)
@@ -12,15 +12,17 @@ namespace System.Net.Quic
     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; } }
     }
 }
index 07454f6..d89a9e4 100644 (file)
@@ -5,7 +5,9 @@
 // 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
 {
@@ -20,7 +22,7 @@ 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
@@ -28,6 +30,7 @@ namespace System.Net.Quic
         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;
     }
@@ -45,7 +48,26 @@ namespace System.Net.Quic
         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; }
     }
 }
index f8d083e..056deb5 100644 (file)
@@ -1,7 +1,8 @@
 <Project>
   <PropertyGroup>
     <BuildConfigurations>
-      $(NetCoreAppCurrent)-Unix;
+      $(NetCoreAppCurrent)-Linux;
+      $(NetCoreAppCurrent)-OSX;
       $(NetCoreAppCurrent)-Windows_NT;
     </BuildConfigurations>
   </PropertyGroup>
diff --git a/src/libraries/System.Net.Quic/src/Interop/Interop.MsQuic.cs b/src/libraries/System.Net.Quic/src/Interop/Interop.MsQuic.cs
new file mode 100644 (file)
index 0000000..25a5e57
--- /dev/null
@@ -0,0 +1,16 @@
+// 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);
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/Interop/Linux/MsQuicStatusCodes.cs b/src/libraries/System.Net.Quic/src/Interop/Linux/MsQuicStatusCodes.cs
new file mode 100644 (file)
index 0000000..4152fde
--- /dev/null
@@ -0,0 +1,56 @@
+// 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()
+            };
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/Interop/Linux/MsQuicStatusHelper.cs b/src/libraries/System.Net.Quic/src/Interop/Linux/MsQuicStatusHelper.cs
new file mode 100644 (file)
index 0000000..7c49a8f
--- /dev/null
@@ -0,0 +1,16 @@
+// 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;
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/Interop/MsQuicEnums.cs b/src/libraries/System.Net.Quic/src/Interop/MsQuicEnums.cs
new file mode 100644 (file)
index 0000000..b36fd27
--- /dev/null
@@ -0,0 +1,167 @@
+// 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,
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/Interop/MsQuicNativeMethods.cs b/src/libraries/System.Net.Quic/src/Interop/MsQuicNativeMethods.cs
new file mode 100644 (file)
index 0000000..f14ec85
--- /dev/null
@@ -0,0 +1,481 @@
+// 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;
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/Interop/OSX/MsQuicStatusCodes.cs b/src/libraries/System.Net.Quic/src/Interop/OSX/MsQuicStatusCodes.cs
new file mode 100644 (file)
index 0000000..60c1bec
--- /dev/null
@@ -0,0 +1,35 @@
+// 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();
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/Interop/OSX/MsQuicStatusHelper.cs b/src/libraries/System.Net.Quic/src/Interop/OSX/MsQuicStatusHelper.cs
new file mode 100644 (file)
index 0000000..cae5db4
--- /dev/null
@@ -0,0 +1,16 @@
+// 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();
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/Interop/Windows/MsQuicStatusCodes.cs b/src/libraries/System.Net.Quic/src/Interop/Windows/MsQuicStatusCodes.cs
new file mode 100644 (file)
index 0000000..1d0513e
--- /dev/null
@@ -0,0 +1,56 @@
+// 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()
+            };
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/Interop/Windows/MsQuicStatusHelper.cs b/src/libraries/System.Net.Quic/src/Interop/Windows/MsQuicStatusHelper.cs
new file mode 100644 (file)
index 0000000..8491873
--- /dev/null
@@ -0,0 +1,16 @@
+// 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;
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/PInvokeAnalyzerExceptionList.analyzerdata b/src/libraries/System.Net.Quic/src/PInvokeAnalyzerExceptionList.analyzerdata
new file mode 100644 (file)
index 0000000..3720311
--- /dev/null
@@ -0,0 +1,2 @@
+<!-- MsQuic currently not available -->
+msquic.dll!MsQuicOpen
index fed9eb4..cf9ee3b 100644 (file)
@@ -1,11 +1,23 @@
-<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>
index a0c1ff4..353016a 100644 (file)
@@ -3,7 +3,6 @@
 // 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;
@@ -169,9 +168,10 @@ namespace System.Net.Quic.Implementations.Mock
             return new MockStream(socket, streamId, bidirectional: bidirectional);
         }
 
-        internal override void Close()
+        internal override ValueTask CloseAsync(CancellationToken cancellationToken = default)
         {
             Dispose();
+            return default;
         }
 
         private void CheckDisposed()
index fd674c1..b70a113 100644 (file)
@@ -8,14 +8,14 @@ namespace System.Net.Quic.Implementations.Mock
 {
     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);
         }
     }
 }
index d224111..911f189 100644 (file)
@@ -28,13 +28,6 @@ namespace System.Net.Quic.Implementations.Mock
             _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.
@@ -70,6 +63,19 @@ namespace System.Net.Quic.Implementations.Mock
             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();
index 30869a1..bb42f48 100644 (file)
@@ -102,7 +102,12 @@ namespace System.Net.Quic.Implementations.Mock
             _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();
 
@@ -117,6 +122,11 @@ namespace System.Net.Quic.Implementations.Mock
             }
 
             await _socket.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+
+            if (endStream)
+            {
+                _socket.Shutdown(SocketShutdown.Send);
+            }
         }
 
         internal override void Flush()
@@ -131,16 +141,16 @@ namespace System.Net.Quic.Implementations.Mock
             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()
@@ -161,5 +171,18 @@ namespace System.Net.Quic.Implementations.Mock
                 _socket = null;
             }
         }
+
+        public override ValueTask DisposeAsync()
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+
+                _socket?.Dispose();
+                _socket = null;
+            }
+
+            return default;
+        }
     }
 }
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs
new file mode 100644 (file)
index 0000000..2ecf0eb
--- /dev/null
@@ -0,0 +1,85 @@
+// 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;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
new file mode 100644 (file)
index 0000000..3f853ab
--- /dev/null
@@ -0,0 +1,265 @@
+// 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;
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs
new file mode 100644 (file)
index 0000000..21e960c
--- /dev/null
@@ -0,0 +1,67 @@
+// 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;
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs
new file mode 100644 (file)
index 0000000..58fc811
--- /dev/null
@@ -0,0 +1,45 @@
+// 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);
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs
new file mode 100644 (file)
index 0000000..8a43387
--- /dev/null
@@ -0,0 +1,149 @@
+// 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;
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicStatusException.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicStatusException.cs
new file mode 100644 (file)
index 0000000..e84c8be
--- /dev/null
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// 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);
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/ResettableCompletionSource.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/ResettableCompletionSource.cs
new file mode 100644 (file)
index 0000000..1db5dc6
--- /dev/null
@@ -0,0 +1,81 @@
+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();
+                }
+            }
+        }
+    }
+ }
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs
new file mode 100644 (file)
index 0000000..cf7575c
--- /dev/null
@@ -0,0 +1,397 @@
+// 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));
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicImplementationProvider.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicImplementationProvider.cs
new file mode 100644 (file)
index 0000000..55c5e52
--- /dev/null
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// 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);
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs
new file mode 100644 (file)
index 0000000..68e65c2
--- /dev/null
@@ -0,0 +1,210 @@
+// 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));
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs
new file mode 100644 (file)
index 0000000..4c44399
--- /dev/null
@@ -0,0 +1,817 @@
+// 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
+        }
+    }
+}
index 596841d..25fa092 100644 (file)
@@ -25,7 +25,7 @@ namespace System.Net.Quic.Implementations
 
         internal abstract System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol { get; }
 
-        internal abstract void Close();
+        internal abstract ValueTask CloseAsync(CancellationToken cancellationToken = default);
 
         public abstract void Dispose();
     }
index d250a2c..683a752 100644 (file)
@@ -10,8 +10,8 @@ namespace System.Net.Quic.Implementations
     {
         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);
     }
 }
index 7d6819d..f533a8b 100644 (file)
@@ -13,6 +13,8 @@ namespace System.Net.Quic.Implementations
 
         internal abstract ValueTask<QuicConnectionProvider> AcceptConnectionAsync(CancellationToken cancellationToken = default);
 
+        internal abstract void Start();
+
         internal abstract void Close();
 
         public abstract void Dispose();
index 32956e2..270bc9c 100644 (file)
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 
 namespace System.Net.Quic.Implementations
 {
-    internal abstract class QuicStreamProvider : IDisposable
+    internal abstract class QuicStreamProvider : IDisposable, IAsyncDisposable
     {
         internal abstract long StreamId { get; }
 
@@ -17,7 +17,7 @@ namespace System.Net.Quic.Implementations
 
         internal abstract ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);
 
-        internal abstract void ShutdownRead();
+        internal abstract void AbortRead();
 
         internal abstract bool CanWrite { get; }
 
@@ -25,12 +25,16 @@ namespace System.Net.Quic.Implementations
 
         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();
     }
 }
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/NetEventSource.Quic.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/NetEventSource.Quic.cs
new file mode 100644 (file)
index 0000000..9218088
--- /dev/null
@@ -0,0 +1,13 @@
+// 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
+    {
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs
new file mode 100644 (file)
index 0000000..17270a4
--- /dev/null
@@ -0,0 +1,50 @@
+// 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);
+    }
+}
index 99a9c47..9d6ed09 100644 (file)
@@ -26,8 +26,13 @@ namespace System.Net.Quic
 
         // !!! 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)
@@ -74,7 +79,7 @@ namespace System.Net.Quic
         /// <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();
     }
index adb7cd2..e3490d2 100644 (file)
@@ -7,7 +7,7 @@ namespace System.Net.Quic
     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;
     }
 }
index 809bf01..b642b9d 100644 (file)
@@ -25,8 +25,13 @@ namespace System.Net.Quic
 
         // !!! 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;
@@ -38,6 +43,8 @@ namespace System.Net.Quic
         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>
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListenerOptions.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListenerOptions.cs
new file mode 100644 (file)
index 0000000..4da94f0
--- /dev/null
@@ -0,0 +1,50 @@
+// 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);
+    }
+}
index ac1643f..e2033aa 100644 (file)
@@ -103,9 +103,11 @@ namespace System.Net.Quic
 
         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)
         {
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs
new file mode 100644 (file)
index 0000000..da6d6d2
--- /dev/null
@@ -0,0 +1,49 @@
+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();
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
new file mode 100644 (file)
index 0000000..03a5744
--- /dev/null
@@ -0,0 +1,364 @@
+// 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);
+        }
+    }
+}
index d05324e..3b046be 100644 (file)
@@ -20,6 +20,7 @@ namespace System.Net.Quic.Tests
         {
             using (QuicListener listener = new QuicListener(QuicImplementationProviders.Mock, new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null))
             {
+                listener.Start();
                 IPEndPoint listenEndPoint = listener.ListenEndPoint;
 
                 await Task.WhenAll(
@@ -57,6 +58,8 @@ namespace System.Net.Quic.Tests
         {
             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))
@@ -170,7 +173,9 @@ namespace System.Net.Quic.Tests
         {
             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);
index dc6f0c7..942a801 100644 (file)
@@ -4,6 +4,14 @@
     <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