add System.Net.Quic assembly and use it in HttpClient (#43076)
authorGeoff Kizer <geoffrek@microsoft.com>
Wed, 7 Oct 2020 02:01:33 +0000 (19:01 -0700)
committerGitHub <noreply@github.com>
Wed, 7 Oct 2020 02:01:33 +0000 (19:01 -0700)
* add System.Net.Quic assembly and use it in HttpClient

* fix build issue

* remove old quic code from shared directory

* fix Common.Tests project

* fix WinHttp tests

* fix typo

Co-authored-by: Geoffrey Kizer <geoffrek@windows.microsoft.com>
77 files changed:
src/libraries/Common/src/System/Net/ArrayBuffer.cs
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockConnection.cs [deleted file]
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockListener.cs [deleted file]
src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockStream.cs [deleted file]
src/libraries/Common/tests/Common.Tests.csproj
src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs
src/libraries/Common/tests/System/Net/Http/TestHelper.cs
src/libraries/NetCoreAppLibrary.props
src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs
src/libraries/System.Net.Http/ref/System.Net.Http.cs
src/libraries/System.Net.Http/ref/System.Net.Http.csproj
src/libraries/System.Net.Http/src/System.Net.Http.csproj
src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/QuicConnectionTests.cs [deleted file]
src/libraries/System.Net.Http/tests/FunctionalTests/QuicStreamTests.cs [deleted file]
src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj
src/libraries/System.Net.Quic/Directory.Build.props [new file with mode: 0644]
src/libraries/System.Net.Quic/System.Net.Quic.sln [new file with mode: 0644]
src/libraries/System.Net.Quic/ref/System.Net.Quic.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/ref/System.Net.Quic.csproj [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Resources/Strings.resx [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System.Net.Quic.csproj [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockConnection.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockImplementationProvider.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockImplementationProvider.cs with 87% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockListener.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockStream.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/StreamBuffer.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicSecurityConfig.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/QuicExceptionHelpers.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/QuicExceptionHelpers.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/ResettableCompletionSource.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/Internal/ResettableCompletionSource.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicConnection.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicImplementationProvider.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicImplementationProvider.cs with 90% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicListener.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/MsQuic/MsQuicStream.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicConnectionProvider.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/QuicConnectionProvider.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicImplementationProvider.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/QuicImplementationProvider.cs with 81% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicListenerProvider.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/QuicListenerProvider.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicStreamProvider.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/QuicStreamProvider.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/Interop.MsQuic.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/Interop.MsQuic.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicEnums.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicEnums.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicNativeMethods.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicNativeMethods.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicStatusCodes.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicStatusCodes.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/MsQuicStatusHelper.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Interop/MsQuicStatusHelper.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/NetEventSource.Quic.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/NetEventSource.Quic.cs with 100% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicClientConnectionOptions.cs with 97% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicConnection.cs with 97% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnectionAbortedException.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicConnectionAbortedException.cs with 89% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicException.cs with 90% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicImplementationProviders.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicImplementationProviders.cs with 90% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicListener.cs with 97% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListenerOptions.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicListenerOptions.cs with 98% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicOperationAbortedException.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicOperationAbortedException.cs with 85% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicStream.cs with 99% similarity]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStreamAbortedException.cs [moved from src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/QuicStreamAbortedException.cs with 89% similarity]
src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTestBase.cs [moved from src/libraries/System.Net.Http/tests/FunctionalTests/MsQuicTestBase.cs with 100% similarity]
src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs [moved from src/libraries/System.Net.Http/tests/FunctionalTests/QuicListenerTests.cs with 65% similarity]
src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs [moved from src/libraries/System.Net.Http/tests/FunctionalTests/MsQuicTests.cs with 55% similarity]
src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj [new file with mode: 0644]
src/libraries/pkg/baseline/packageIndex.json

index 15ef10a..5e2eae0 100644 (file)
@@ -133,6 +133,61 @@ namespace System.Net
             Debug.Assert(byteCount <= AvailableLength);
         }
 
+        // Ensure at least [byteCount] bytes to write to, up to the specified limit
+        public void TryEnsureAvailableSpaceUpToLimit(int byteCount, int limit)
+        {
+            if (byteCount <= AvailableLength)
+            {
+                return;
+            }
+
+            int totalFree = _activeStart + AvailableLength;
+            if (byteCount <= totalFree)
+            {
+                // We can free up enough space by just shifting the bytes down, so do so.
+                Buffer.BlockCopy(_bytes, _activeStart, _bytes, 0, ActiveLength);
+                _availableStart = ActiveLength;
+                _activeStart = 0;
+                Debug.Assert(byteCount <= AvailableLength);
+                return;
+            }
+
+            if (_bytes.Length >= limit)
+            {
+                // Already at limit, can't grow further.
+                return;
+            }
+
+            // Double the size of the buffer until we have enough space, or we hit the limit
+            int desiredSize = Math.Min(ActiveLength + byteCount, limit);
+            int newSize = _bytes.Length;
+            do
+            {
+                newSize = Math.Min(newSize * 2, limit);
+            } while (newSize < desiredSize);
+
+            byte[] newBytes = _usePool ?
+                ArrayPool<byte>.Shared.Rent(newSize) :
+                new byte[newSize];
+            byte[] oldBytes = _bytes;
+
+            if (ActiveLength != 0)
+            {
+                Buffer.BlockCopy(oldBytes, _activeStart, newBytes, 0, ActiveLength);
+            }
+
+            _availableStart = ActiveLength;
+            _activeStart = 0;
+
+            _bytes = newBytes;
+            if (_usePool)
+            {
+                ArrayPool<byte>.Shared.Return(oldBytes);
+            }
+
+            Debug.Assert(byteCount <= AvailableLength);
+        }
+
         public void Grow()
         {
             EnsureAvailableSpace(AvailableLength + 1);
diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockConnection.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockConnection.cs
deleted file mode 100644 (file)
index 07995dd..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable enable
-using System.Buffers.Binary;
-using System.Net.Security;
-using System.Net.Sockets;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace System.Net.Quic.Implementations.Mock
-{
-    internal sealed class MockConnection : QuicConnectionProvider
-    {
-        private readonly bool _isClient;
-        private bool _disposed;
-        private EndPoint? _remoteEndPoint;
-        private IPEndPoint? _localEndPoint;
-        private object _syncObject = new object();
-        private Socket? _socket;
-        private IPEndPoint? _peerListenEndPoint;
-        private TcpListener? _inboundListener;
-        private long _nextOutboundBidirectionalStream;
-        private long _nextOutboundUnidirectionalStream;
-
-        // Constructor for outbound connections
-        internal MockConnection(EndPoint? remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null)
-        {
-            _remoteEndPoint = remoteEndPoint;
-            _localEndPoint = localEndPoint;
-
-            _isClient = true;
-            _nextOutboundBidirectionalStream = 0;
-            _nextOutboundUnidirectionalStream = 2;
-        }
-
-        // Constructor for accepted inbound connections
-        internal MockConnection(Socket socket, IPEndPoint peerListenEndPoint, TcpListener inboundListener)
-        {
-            _isClient = false;
-            _nextOutboundBidirectionalStream = 1;
-            _nextOutboundUnidirectionalStream = 3;
-            _socket = socket;
-            _peerListenEndPoint = peerListenEndPoint;
-            _inboundListener = inboundListener;
-            _localEndPoint = (IPEndPoint?)socket.LocalEndPoint;
-            _remoteEndPoint = (IPEndPoint?)socket.RemoteEndPoint;
-        }
-
-        internal override bool Connected
-        {
-            get
-            {
-                CheckDisposed();
-
-                return _socket != null;
-            }
-        }
-
-        internal override IPEndPoint LocalEndPoint => new IPEndPoint(_localEndPoint!.Address, _localEndPoint.Port);
-
-        internal override EndPoint RemoteEndPoint => _remoteEndPoint!;
-
-        internal override SslApplicationProtocol NegotiatedApplicationProtocol => throw new NotImplementedException();
-
-        internal override async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            if (Connected)
-            {
-                // TODO: Exception text
-                throw new InvalidOperationException("Already connected");
-            }
-
-            Socket socket = new Socket(_remoteEndPoint!.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
-            await socket.ConnectAsync(_remoteEndPoint, cancellationToken).ConfigureAwait(false);
-            socket.NoDelay = true;
-
-            _localEndPoint = (IPEndPoint?)socket.LocalEndPoint;
-
-            // Listen on a new local endpoint for inbound streams
-            TcpListener inboundListener = new TcpListener(_localEndPoint!.Address, 0);
-            inboundListener.Start();
-            int inboundListenPort = ((IPEndPoint)inboundListener.LocalEndpoint).Port;
-
-            // Write inbound listen port to socket so server can read it
-            byte[] buffer = new byte[4];
-            BinaryPrimitives.WriteInt32LittleEndian(buffer, inboundListenPort);
-            await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
-
-            // Read first 4 bytes to get server listen port
-            int bytesRead = 0;
-            do
-            {
-                bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None, cancellationToken).ConfigureAwait(false);
-            } while (bytesRead != buffer.Length);
-
-            int peerListenPort = BinaryPrimitives.ReadInt32LittleEndian(buffer);
-            IPEndPoint peerListenEndPoint = new IPEndPoint(((IPEndPoint)socket.RemoteEndPoint!).Address, peerListenPort);
-
-            _socket = socket;
-            _peerListenEndPoint = peerListenEndPoint;
-            _inboundListener = inboundListener;
-        }
-
-        internal override QuicStreamProvider OpenUnidirectionalStream()
-        {
-            long streamId;
-            lock (_syncObject)
-            {
-                streamId = _nextOutboundUnidirectionalStream;
-                _nextOutboundUnidirectionalStream += 4;
-            }
-
-            return new MockStream(this, streamId, bidirectional: false);
-        }
-
-        internal override QuicStreamProvider OpenBidirectionalStream()
-        {
-            long streamId;
-            lock (_syncObject)
-            {
-                streamId = _nextOutboundBidirectionalStream;
-                _nextOutboundBidirectionalStream += 4;
-            }
-
-            return new MockStream(this, streamId, bidirectional: true);
-        }
-
-        internal override long GetRemoteAvailableUnidirectionalStreamCount()
-        {
-            throw new NotImplementedException();
-        }
-
-        internal override long GetRemoteAvailableBidirectionalStreamCount()
-        {
-            throw new NotImplementedException();
-        }
-
-        internal async Task<Socket> CreateOutboundMockStreamAsync(long streamId)
-        {
-            Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
-            await socket.ConnectAsync(_peerListenEndPoint!).ConfigureAwait(false);
-            socket.NoDelay = true;
-
-            // Write stream ID to socket so server can read it
-            byte[] buffer = new byte[8];
-            BinaryPrimitives.WriteInt64LittleEndian(buffer, streamId);
-            await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
-
-            return socket;
-        }
-
-        internal override async ValueTask<QuicStreamProvider> AcceptStreamAsync(CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            Socket socket = await _inboundListener!.AcceptSocketAsync().ConfigureAwait(false);
-
-            // Read first bytes to get stream ID
-            byte[] buffer = new byte[8];
-            int bytesRead = 0;
-            do
-            {
-                bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None, cancellationToken).ConfigureAwait(false);
-            } while (bytesRead != buffer.Length);
-
-            long streamId = BinaryPrimitives.ReadInt64LittleEndian(buffer);
-
-            bool clientInitiated = ((streamId & 0b01) == 0);
-            if (clientInitiated == _isClient)
-            {
-                throw new Exception($"Wrong initiator on accepted stream??? streamId={streamId}, _isClient={_isClient}");
-            }
-
-            bool bidirectional = ((streamId & 0b10) == 0);
-            return new MockStream(socket, streamId, bidirectional: bidirectional);
-        }
-
-        internal override ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default)
-        {
-            Dispose();
-            return default;
-        }
-
-        private void CheckDisposed()
-        {
-            if (_disposed)
-            {
-                throw new ObjectDisposedException(nameof(QuicConnection));
-            }
-        }
-
-        private void Dispose(bool disposing)
-        {
-            if (!_disposed)
-            {
-                if (disposing)
-                {
-                    _socket?.Dispose();
-                    _socket = null;
-
-                    _inboundListener?.Stop();
-                    _inboundListener = null;
-                }
-
-                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
-                // TODO: set large fields to null.
-
-                _disposed = true;
-            }
-        }
-
-        ~MockConnection()
-        {
-            Dispose(false);
-        }
-
-        public override void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-    }
-}
diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockListener.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockListener.cs
deleted file mode 100644 (file)
index e7b2454..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable enable
-using System.Net.Sockets;
-using System.Net.Security;
-using System.Threading.Tasks;
-using System.Threading;
-using System.Buffers.Binary;
-
-namespace System.Net.Quic.Implementations.Mock
-{
-    internal sealed class MockListener : QuicListenerProvider
-    {
-        private bool _disposed;
-        private SslServerAuthenticationOptions? _sslOptions;
-        private IPEndPoint _listenEndPoint;
-        private TcpListener _tcpListener;
-
-        internal MockListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions? sslServerAuthenticationOptions)
-        {
-            if (listenEndPoint == null)
-            {
-                throw new ArgumentNullException(nameof(listenEndPoint));
-            }
-
-            _sslOptions = sslServerAuthenticationOptions;
-            _listenEndPoint = listenEndPoint;
-
-            _tcpListener = new TcpListener(listenEndPoint);
-        }
-
-        // IPEndPoint is mutable, so we must create a new instance every time this is retrieved.
-        internal override IPEndPoint ListenEndPoint => new IPEndPoint(_listenEndPoint.Address, _listenEndPoint.Port);
-
-        internal override async ValueTask<QuicConnectionProvider> AcceptConnectionAsync(CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            Socket socket = await _tcpListener.AcceptSocketAsync().ConfigureAwait(false);
-            socket.NoDelay = true;
-
-            // Read first 4 bytes to get client listen port
-            byte[] buffer = new byte[4];
-            int bytesRead = 0;
-            do
-            {
-                bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None, cancellationToken).ConfigureAwait(false);
-            } while (bytesRead != buffer.Length);
-
-            int peerListenPort = BinaryPrimitives.ReadInt32LittleEndian(buffer);
-            IPEndPoint peerListenEndPoint = new IPEndPoint(((IPEndPoint)socket.RemoteEndPoint!).Address, peerListenPort);
-
-            // Listen on a new local endpoint for inbound streams
-            TcpListener inboundListener = new TcpListener(_listenEndPoint.Address, 0);
-            inboundListener.Start();
-            int inboundListenPort = ((IPEndPoint)inboundListener.LocalEndpoint).Port;
-
-            // Write inbound listen port to socket so client can read it
-            BinaryPrimitives.WriteInt32LittleEndian(buffer, inboundListenPort);
-            await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
-
-            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();
-        }
-
-        private void CheckDisposed()
-        {
-            if (_disposed)
-            {
-                throw new ObjectDisposedException(nameof(QuicListener));
-            }
-        }
-
-        private void Dispose(bool disposing)
-        {
-            if (!_disposed)
-            {
-                if (disposing)
-                {
-                    _tcpListener?.Stop();
-                    _tcpListener = null!;
-                }
-
-                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
-                // TODO: set large fields to null.
-
-                _disposed = true;
-            }
-        }
-
-        ~MockListener()
-        {
-            Dispose(false);
-        }
-
-        public override void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-    }
-}
diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockStream.cs b/src/libraries/Common/src/System/Net/Http/aspnetcore/Quic/Implementations/Mock/MockStream.cs
deleted file mode 100644 (file)
index 32820ca..0000000
+++ /dev/null
@@ -1,259 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable enable
-using System.Buffers;
-using System.Diagnostics;
-using System.Net.Sockets;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace System.Net.Quic.Implementations.Mock
-{
-    internal sealed class MockStream : QuicStreamProvider
-    {
-        private bool _disposed;
-        private readonly long _streamId;
-        private bool _canRead;
-        private bool _canWrite;
-
-        private MockConnection? _connection;
-
-        private Socket? _socket;
-
-        // Constructor for outbound streams
-        internal MockStream(MockConnection connection, long streamId, bool bidirectional)
-        {
-            _connection = connection;
-            _streamId = streamId;
-            _canRead = bidirectional;
-            _canWrite = true;
-        }
-
-        // Constructor for inbound streams
-        internal MockStream(Socket socket, long streamId, bool bidirectional)
-        {
-            _socket = socket;
-            _streamId = streamId;
-            _canRead = true;
-            _canWrite = bidirectional;
-        }
-
-        private async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
-        {
-            Debug.Assert(_connection != null, "Stream not connected but no connection???");
-
-            _socket = await _connection.CreateOutboundMockStreamAsync(_streamId).ConfigureAwait(false);
-
-            // Don't need to hold on to the connection any longer.
-            _connection = null;
-        }
-
-        internal override long StreamId
-        {
-            get
-            {
-                CheckDisposed();
-                return _streamId;
-            }
-        }
-
-        internal override bool CanRead => _canRead;
-
-        internal override int Read(Span<byte> buffer)
-        {
-            CheckDisposed();
-
-            if (!_canRead)
-            {
-                throw new NotSupportedException();
-            }
-
-            return _socket!.Receive(buffer);
-        }
-
-        internal override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            if (!_canRead)
-            {
-                throw new NotSupportedException();
-            }
-
-            if (_socket == null)
-            {
-                await ConnectAsync(cancellationToken).ConfigureAwait(false);
-            }
-
-            return await _socket!.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
-        }
-
-        internal override bool CanWrite => _canWrite;
-
-        internal override void Write(ReadOnlySpan<byte> buffer)
-        {
-            CheckDisposed();
-
-            if (!_canWrite)
-            {
-                throw new NotSupportedException();
-            }
-
-            _socket!.Send(buffer);
-        }
-
-        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();
-
-            if (!_canWrite)
-            {
-                throw new NotSupportedException();
-            }
-
-            if (_socket == null)
-            {
-                await ConnectAsync(cancellationToken).ConfigureAwait(false);
-            }
-            await _socket!.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
-
-            if (endStream)
-            {
-                _socket!.Shutdown(SocketShutdown.Send);
-            }
-        }
-
-        internal override ValueTask WriteAsync(ReadOnlySequence<byte> buffers, CancellationToken cancellationToken = default)
-        {
-            return WriteAsync(buffers, endStream: false, cancellationToken);
-        }
-        internal override async ValueTask WriteAsync(ReadOnlySequence<byte> buffers, bool endStream, CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            if (!_canWrite)
-            {
-                throw new NotSupportedException();
-            }
-
-            if (_socket == null)
-            {
-                await ConnectAsync(cancellationToken).ConfigureAwait(false);
-            }
-
-            foreach (ReadOnlyMemory<byte> buffer in buffers)
-            {
-                await _socket!.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
-            }
-
-            if (endStream)
-            {
-                _socket!.Shutdown(SocketShutdown.Send);
-            }
-        }
-
-        internal override ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, CancellationToken cancellationToken = default)
-        {
-            return WriteAsync(buffers, endStream: false, cancellationToken);
-        }
-        internal override async ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, bool endStream, CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            if (!_canWrite)
-            {
-                throw new NotSupportedException();
-            }
-
-            if (_socket == null)
-            {
-                await ConnectAsync(cancellationToken).ConfigureAwait(false);
-            }
-
-            foreach (ReadOnlyMemory<byte> buffer in buffers.ToArray())
-            {
-                await _socket!.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
-            }
-
-            if (endStream)
-            {
-                _socket!.Shutdown(SocketShutdown.Send);
-            }
-        }
-
-        internal override void Flush()
-        {
-            CheckDisposed();
-        }
-
-        internal override Task FlushAsync(CancellationToken cancellationToken)
-        {
-            CheckDisposed();
-
-            return Task.CompletedTask;
-        }
-
-        internal override void AbortRead(long errorCode)
-        {
-            throw new NotImplementedException();
-        }
-
-        internal override void AbortWrite(long errorCode)
-        {
-            throw new NotImplementedException();
-        }
-
-
-        internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            return default;
-        }
-
-        internal override void Shutdown()
-        {
-            CheckDisposed();
-
-            _socket!.Shutdown(SocketShutdown.Send);
-        }
-
-        private void CheckDisposed()
-        {
-            if (_disposed)
-            {
-                throw new ObjectDisposedException(nameof(QuicStream));
-            }
-        }
-
-        public override void Dispose()
-        {
-            if (!_disposed)
-            {
-                _disposed = true;
-
-                _socket?.Dispose();
-                _socket = null;
-            }
-        }
-
-        public override ValueTask DisposeAsync()
-        {
-            if (!_disposed)
-            {
-                _disposed = true;
-
-                _socket?.Dispose();
-                _socket = null;
-            }
-
-            return default;
-        }
-    }
-}
index 4763858..ddc52ea 100644 (file)
              Link="Common\System\Net\Http\aspnetcore\Http2\Hpack\H2StaticTable.cs" />
     <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Http2\Hpack\StatusCodes.cs"
              Link="Common\System\Net\Http\aspnetcore\Http2\Hpack\StatusCodes.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicAddressHelpers.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicAddressHelpers.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicApi.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicApi.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicParameterHelpers.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicParameterHelpers.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSecurityConfig.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSecurityConfig.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSession.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSession.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\QuicExceptionHelpers.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\QuicExceptionHelpers.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\ResettableCompletionSource.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\ResettableCompletionSource.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicConnection.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicConnection.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicImplementationProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicImplementationProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicListener.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicListener.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicStream.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicStream.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicImplementationProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicImplementationProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicListenerProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicListenerProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicConnectionProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicConnectionProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicStreamProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicStreamProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockImplementationProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockImplementationProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockListener.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockListener.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockConnection.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockConnection.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockStream.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockStream.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\NetEventSource.Quic.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\NetEventSource.Quic.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicClientConnectionOptions.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicClientConnectionOptions.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicImplementationProviders.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicImplementationProviders.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicConnection.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicConnection.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicListener.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicListener.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicListenerOptions.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicListenerOptions.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicOperationAbortedException.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicOperationAbortedException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicStream.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicStream.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicException.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicConnectionAbortedException.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicConnectionAbortedException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicStreamAbortedException.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicStreamAbortedException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\Interop.MsQuic.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\Interop.MsQuic.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicEnums.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicEnums.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicNativeMethods.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicNativeMethods.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusCodes.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusCodes.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusHelper.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusHelper.cs" />
     <Compile Include="$(CommonPath)System\Text\ReusableTextReader.cs"
              Link="Common\System\Text\ReusableTextReader.cs" />
     <Compile Include="$(CommonPath)System\Text\SimpleRegex.cs"
index e203bf6..63c13bd 100644 (file)
@@ -5,6 +5,7 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.Net.Quic;
+using System.Net.Quic.Implementations;
 using System.Net.Security;
 using System.Net.Sockets;
 using System.Security.Cryptography.X509Certificates;
@@ -19,7 +20,7 @@ namespace System.Net.Test.Common
 
         public override Uri Address => new Uri($"https://{_listener.ListenEndPoint}/");
 
-        public Http3LoopbackServer(GenericLoopbackOptions options = null)
+        public Http3LoopbackServer(QuicImplementationProvider quicImplementationProvider = null, GenericLoopbackOptions options = null)
         {
             options ??= new GenericLoopbackOptions();
 
@@ -33,7 +34,7 @@ namespace System.Net.Test.Common
                 ClientCertificateRequired = false
             };
 
-            _listener = new QuicListener(new IPEndPoint(options.Address, 0), sslOpts);
+            _listener = new QuicListener(quicImplementationProvider ?? QuicImplementationProviders.Default, new IPEndPoint(options.Address, 0), sslOpts);
             _listener.Start();
         }
 
@@ -64,13 +65,20 @@ namespace System.Net.Test.Common
 
     public sealed class Http3LoopbackServerFactory : LoopbackServerFactory
     {
-        public static Http3LoopbackServerFactory Singleton { get; } = new Http3LoopbackServerFactory();
+        private QuicImplementationProvider _quicImplementationProvider;
+
+        public Http3LoopbackServerFactory(QuicImplementationProvider quicImplementationProvider)
+        {
+            _quicImplementationProvider = quicImplementationProvider;
+        }
+
+        public static Http3LoopbackServerFactory Singleton { get; } = new Http3LoopbackServerFactory(null);
 
         public override Version Version { get; } = new Version(3, 0);
 
         public override GenericLoopbackServer CreateServer(GenericLoopbackOptions options = null)
         {
-            return new Http3LoopbackServer(options);
+            return new Http3LoopbackServer(_quicImplementationProvider, options);
         }
 
         public override async Task CreateServerAsync(Func<GenericLoopbackServer, Uri, Task> funcAsync, int millisecondsTimeout = 60000, GenericLoopbackOptions options = null)
index 94c7eb3..226a5ae 100644 (file)
@@ -164,6 +164,11 @@ namespace System.Net.Http.Functional.Tests
         [OuterLoop("Uses external servers")]
         public async Task UseDefaultCredentials_SetToFalseAndServerNeedsAuth_StatusCodeUnauthorized(bool useProxy)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             HttpClientHandler handler = CreateHttpClientHandler();
             handler.UseProxy = useProxy;
             handler.UseDefaultCredentials = false;
@@ -230,6 +235,10 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
 
             using HttpClientHandler handler = CreateHttpClientHandler();
             handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
@@ -260,6 +269,11 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             using HttpClientHandler handler = CreateHttpClientHandler();
             handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
 
@@ -326,6 +340,11 @@ namespace System.Net.Http.Functional.Tests
             string ipv6Address = "http://" + host;
             bool connectionAccepted = false;
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
             {
                 using (HttpClientHandler handler = CreateHttpClientHandler())
@@ -354,6 +373,11 @@ namespace System.Net.Http.Functional.Tests
             string uri = "http://" + host;
             bool connectionAccepted = false;
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
             {
                 using (HttpClientHandler handler = CreateHttpClientHandler())
@@ -388,6 +412,11 @@ namespace System.Net.Http.Functional.Tests
             string expectedAddressUri = $"http://{host}/";
             bool connectionAccepted = false;
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
             {
                 using (HttpClientHandler handler = CreateHttpClientHandler())
@@ -415,6 +444,11 @@ namespace System.Net.Http.Functional.Tests
             string addressUri = $"https://{requestTarget}/";
             bool connectionAccepted = false;
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
             {
                 using (HttpClientHandler handler = CreateHttpClientHandler())
@@ -445,6 +479,11 @@ namespace System.Net.Http.Functional.Tests
                 return; // Skip test since the fix is only in SocketsHttpHandler.
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             string addressUri = $"https://{Configuration.Http.SecureHost}/";
             bool connectionAccepted = false;
 
@@ -530,6 +569,11 @@ namespace System.Net.Http.Functional.Tests
         [Theory, MemberData(nameof(RemoteServersMemberData))]
         public async Task GetAsync_ServerNeedsBasicAuthAndSetDefaultCredentials_StatusCodeUnauthorized(Configuration.Http.RemoteServer remoteServer)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             HttpClientHandler handler = CreateHttpClientHandler();
             handler.Credentials = CredentialCache.DefaultCredentials;
             using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
@@ -546,6 +590,11 @@ namespace System.Net.Http.Functional.Tests
         [Theory, MemberData(nameof(RemoteServersMemberData))]
         public async Task GetAsync_ServerNeedsAuthAndSetCredential_StatusCodeOK(Configuration.Http.RemoteServer remoteServer)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             HttpClientHandler handler = CreateHttpClientHandler();
             handler.Credentials = _credential;
             using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
@@ -562,6 +611,11 @@ namespace System.Net.Http.Functional.Tests
         [Fact]
         public async Task GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized()
         {
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             using (HttpClient client = CreateHttpClient(UseVersion.ToString()))
             {
                 Uri uri = Configuration.Http.RemoteHttp11Server.BasicAuthUriForCreds(userName: Username, password: Password);
@@ -611,6 +665,11 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             string name = "X-Cust-Header-NoValue";
             string value = "";
             using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
@@ -631,6 +690,11 @@ namespace System.Net.Http.Functional.Tests
         [Theory, MemberData(nameof(RemoteServersHeaderValuesAndUris))]
         public async Task GetAsync_RequestHeadersAddCustomHeaders_HeaderAndValueSent(Configuration.Http.RemoteServer remoteServer, string name, string value, Uri uri)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
             {
                 _output.WriteLine($"name={name}, value={value}");
@@ -649,6 +713,11 @@ namespace System.Net.Http.Functional.Tests
         [Theory, MemberData(nameof(RemoteServersAndHeaderEchoUrisMemberData))]
         public async Task GetAsync_LargeRequestHeader_HeadersAndValuesSent(Configuration.Http.RemoteServer remoteServer, Uri uri)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             // Unfortunately, our remote servers seem to have pretty strict limits (around 16K?)
             // on the total size of the request header.
             // TODO: Figure out how to reconfigure remote endpoints to allow larger request headers,
@@ -720,6 +789,11 @@ namespace System.Net.Http.Functional.Tests
         [InlineData("Content-Length      ")]
         public async Task GetAsync_InvalidHeaderNameValue_ThrowsHttpRequestException(string invalidHeader)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateClientAndServerAsync(async uri =>
             {
                 using (HttpClient client = CreateHttpClient())
@@ -741,6 +815,11 @@ namespace System.Net.Http.Functional.Tests
                 return; // see https://github.com/dotnet/runtime/issues/30115#issuecomment-508330958
             }
 
+            if (UseVersion != HttpVersion.Version11)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateClientAndServerAsync(async uri =>
             {
                 using (HttpClient client = CreateHttpClient())
@@ -1115,6 +1194,11 @@ namespace System.Net.Http.Functional.Tests
         [InlineData("7\v\f")] // unacceptable whitespace
         public async Task GetAsync_InvalidChunkSize_ThrowsHttpRequestException(string chunkSize)
         {
+            if (UseVersion != HttpVersion.Version11)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateServerAsync(async (server, url) =>
             {
                 using (HttpClient client = CreateHttpClient())
@@ -1141,6 +1225,11 @@ namespace System.Net.Http.Functional.Tests
         [Fact]
         public async Task GetAsync_InvalidChunkTerminator_ThrowsHttpRequestException()
         {
+            if (UseVersion != HttpVersion.Version11)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateClientAndServerAsync(async url =>
             {
                 using (HttpClient client = CreateHttpClient())
@@ -1163,6 +1252,11 @@ namespace System.Net.Http.Functional.Tests
         [Fact]
         public async Task GetAsync_InfiniteChunkSize_ThrowsHttpRequestException()
         {
+            if (UseVersion != HttpVersion.Version11)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateServerAsync(async (server, url) =>
             {
                 using (HttpClient client = CreateHttpClient())
@@ -1260,6 +1354,11 @@ namespace System.Net.Http.Functional.Tests
         [Fact]
         public async Task SendAsync_ReadFromSlowStreamingServer_PartialDataReturned()
         {
+            if (UseVersion != HttpVersion.Version11)
+            {
+                return;
+            }
+
             await LoopbackServer.CreateServerAsync(async (server, url) =>
             {
                 using (HttpClient client = CreateHttpClient())
@@ -1551,6 +1650,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // TODO: Active issue
+                return;
+            }
+
             await LoopbackServerFactory.CreateServerAsync(async (server1, url1) =>
             {
                 await LoopbackServerFactory.CreateServerAsync(async (server2, url2) =>
@@ -1613,6 +1718,12 @@ namespace System.Net.Http.Functional.Tests
         [InlineData(1000)]
         public async Task GetAsync_StatusCodeOutOfRange_ExpectedException(int statusCode)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                // TODO: Try to make this test version-agnostic
+                return;
+            }
+
             await LoopbackServer.CreateServerAsync(async (server, url) =>
             {
                 using (HttpClient client = CreateHttpClient())
@@ -1928,6 +2039,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // TODO: ActiveIssue
+                return;
+            }
+
             const string ExpectedContent = "Hello, expecting and continuing world.";
             var clientCompleted = new TaskCompletionSource<bool>();
             await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
@@ -1973,6 +2090,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // TODO: ActiveIssue
+                return;
+            }
+
             var clientFinished = new TaskCompletionSource<bool>();
             const string TestString = "test";
             const string CookieHeaderExpected = "yummy_cookie=choco";
@@ -2042,6 +2165,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // TODO: ActiveIssue
+                return;
+            }
+
             var clientFinished = new TaskCompletionSource<bool>();
             const string TestString = "test";
 
@@ -2086,6 +2215,11 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             var clientFinished = new TaskCompletionSource<bool>();
             const string TestString = "test";
 
@@ -2134,6 +2268,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // TODO: ActiveIssue
+                return;
+            }
+
             var clientFinished = new TaskCompletionSource<bool>();
 
             await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
@@ -2169,6 +2309,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // TODO: ActiveIssue
+                return;
+            }
+
             var clientFinished = new TaskCompletionSource<bool>();
             const string RequestString = "request";
             const string ResponseString = "response";
@@ -2249,6 +2395,12 @@ namespace System.Net.Http.Functional.Tests
         [InlineData(true)]
         public async Task PostAsync_ThrowFromContentCopy_RequestFails(bool syncFailure)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                // TODO: Make this version-indepdendent
+                return;
+            }
+
             await LoopbackServer.CreateServerAsync(async (server, uri) =>
             {
                 Task responseTask = server.AcceptConnectionAsync(async connection =>
@@ -2417,6 +2569,12 @@ namespace System.Net.Http.Functional.Tests
             string method,
             Uri serverUri)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             using (HttpClient client = CreateHttpClient())
             {
                 var request = new HttpRequestMessage(
@@ -2437,6 +2595,12 @@ namespace System.Net.Http.Functional.Tests
             string method,
             Uri serverUri)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             using (HttpClient client = CreateHttpClient())
             {
                 var request = new HttpRequestMessage(
@@ -2466,6 +2630,12 @@ namespace System.Net.Http.Functional.Tests
         [InlineData("12345678910", 5)]
         public async Task SendAsync_SendSameRequestMultipleTimesDirectlyOnHandler_Success(string stringContent, int startingPosition)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             using (var handler = new HttpMessageInvoker(CreateHttpClientHandler()))
             {
                 byte[] byteContent = Encoding.ASCII.GetBytes(stringContent);
@@ -2500,6 +2670,12 @@ namespace System.Net.Http.Functional.Tests
             string method,
             Uri serverUri)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             using (HttpClient client = CreateHttpClient())
             {
                 var request = new HttpRequestMessage(
@@ -2542,6 +2718,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(1, 0));
             Assert.Equal(new Version(1, 0), receivedRequestVersion);
         }
@@ -2550,6 +2732,12 @@ namespace System.Net.Http.Functional.Tests
         [Fact]
         public async Task SendAsync_RequestVersion11_ServerReceivesVersion11Request()
         {
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(1, 1));
             Assert.Equal(new Version(1, 1), receivedRequestVersion);
         }
@@ -2564,6 +2752,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             // The default value for HttpRequestMessage.Version is Version(1,1).
             // So, we need to set something different (0,0), to test the "unknown" version.
             Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(0, 0));
@@ -2581,6 +2775,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             // We don't currently have a good way to test whether HTTP/2 is supported without
             // using the same mechanism we're trying to test, so for now we allow both 2.0 and 1.1 responses.
             var request = new HttpRequestMessage(HttpMethod.Get, server);
@@ -2616,6 +2816,11 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                return;
+            }
+
             await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
             {
                 using (HttpClient client = CreateHttpClient())
@@ -2639,6 +2844,12 @@ namespace System.Net.Http.Functional.Tests
                 return;
             }
 
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             _output.WriteLine(server.AbsoluteUri.ToString());
             var request = new HttpRequestMessage(HttpMethod.Get, server);
             request.Version = new Version(2, 0);
index 7aaeb06..c8ca1bc 100644 (file)
@@ -14,9 +14,6 @@ namespace System.Net.Http.Functional.Tests
 {
     using Configuration = System.Net.Test.Common.Configuration;
 
-#if WINHTTPHANDLER_TEST
-    using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
-#endif
 
     public abstract partial class HttpClientHandlerTestBase : FileCleanupTestBase
     {
@@ -52,27 +49,6 @@ namespace System.Net.Http.Functional.Tests
 #endif
             };
 
-        protected HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseVersion);
-
-        protected static HttpClientHandler CreateHttpClientHandler(string useVersionString) =>
-            CreateHttpClientHandler(Version.Parse(useVersionString));
-
-        protected LoopbackServerFactory LoopbackServerFactory => GetFactoryForVersion(UseVersion);
-
-        protected static LoopbackServerFactory GetFactoryForVersion(Version useVersion)
-        {
-            return useVersion.Major switch
-            {
-#if NETCOREAPP || WINHTTPHANDLER_TEST
-#if HTTP3
-                3 => Http3LoopbackServerFactory.Singleton,
-#endif
-                2 => Http2LoopbackServerFactory.Singleton,
-#endif
-                _ => Http11LoopbackServerFactory.Singleton
-            };
-        }
-
         public static readonly bool[] BoolValues = new[] { true, false };
 
         // For use by remote server tests
index 163fdea..d0f0d36 100644 (file)
@@ -5,7 +5,6 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Net.NetworkInformation;
 using System.Net.Security;
-using System.Net.Test.Common;
 using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Security.Cryptography;
index 0b19bb8..2070d46 100644 (file)
@@ -64,6 +64,7 @@
       System.Net.NetworkInformation;
       System.Net.Ping;
       System.Net.Primitives;
+      System.Net.Quic;
       System.Net.Requests;
       System.Net.Security;
       System.Net.ServicePoint;
index 0896bbd..5db2752 100644 (file)
@@ -26,10 +26,27 @@ namespace System.Net.Http.Functional.Tests
             return handler;
         }
 
+        protected WinHttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseVersion);
+
+        protected static WinHttpClientHandler CreateHttpClientHandler(string useVersionString) =>
+            CreateHttpClientHandler(Version.Parse(useVersionString));
+
         protected static HttpRequestMessage CreateRequest(HttpMethod method, Uri uri, Version version, bool exactVersion = false) =>
             new HttpRequestMessage(method, uri)
             {
                 Version = version
             };
+
+        protected LoopbackServerFactory LoopbackServerFactory => GetFactoryForVersion(UseVersion);
+
+        protected static LoopbackServerFactory GetFactoryForVersion(Version useVersion)
+        {
+            return useVersion.Major switch
+            {
+                2 => Http2LoopbackServerFactory.Singleton,
+                _ => Http11LoopbackServerFactory.Singleton
+            };
+        }
+
     }
 }
index c13f88d..95cb142 100644 (file)
@@ -380,6 +380,7 @@ namespace System.Net.Http
         public bool EnableMultipleHttp2Connections { get { throw null; } set { } }
         public Func<SocketsHttpConnectionContext, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<System.IO.Stream>>? ConnectCallback { get { throw null; } set { } }
         public Func<SocketsHttpPlaintextStreamFilterContext, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<System.IO.Stream>>? PlaintextStreamFilter { get { throw null; } set { } }
+        public System.Net.Quic.Implementations.QuicImplementationProvider? QuicImplementationProvider { get { throw null; } set { } }
     }
     public sealed class SocketsHttpConnectionContext
     {
index 0e6cf82..ae6a815 100644 (file)
@@ -9,6 +9,7 @@
   <ItemGroup>
     <ProjectReference Include="..\..\System.Runtime\ref\System.Runtime.csproj" />
     <ProjectReference Include="..\..\System.Net.Primitives\ref\System.Net.Primitives.csproj" />
+    <ProjectReference Include="..\..\System.Net.Quic\ref\System.Net.Quic.csproj" />
     <ProjectReference Include="..\..\System.Net.Sockets\ref\System.Net.Sockets.csproj" />
     <ProjectReference Include="..\..\System.Net.Security\ref\System.Net.Security.csproj" />
     <ProjectReference Include="..\..\System.Security.Cryptography.X509Certificates\ref\System.Security.Cryptography.X509Certificates.csproj" />
index 0e6aa74..bfc8a69 100644 (file)
              Link="Common\System\Net\NTAuthentication.Common.cs" />
     <Compile Include="$(CommonPath)System\Net\ContextFlagsPal.cs"
              Link="Common\System\Net\ContextFlagsPal.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicAddressHelpers.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicAddressHelpers.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicApi.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicApi.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicParameterHelpers.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicParameterHelpers.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSecurityConfig.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSecurityConfig.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSession.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSession.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\QuicExceptionHelpers.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\QuicExceptionHelpers.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\ResettableCompletionSource.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\ResettableCompletionSource.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicConnection.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicConnection.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicImplementationProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicImplementationProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicListener.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicListener.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicStream.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicStream.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicImplementationProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicImplementationProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicListenerProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicListenerProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicConnectionProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicConnectionProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicStreamProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicStreamProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockImplementationProvider.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockImplementationProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockListener.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockListener.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockConnection.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockConnection.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockStream.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockStream.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusCodes.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusCodes.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusHelper.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusHelper.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicClientConnectionOptions.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicClientConnectionOptions.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicImplementationProviders.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicImplementationProviders.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicConnection.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicConnection.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicListener.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicListener.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicListenerOptions.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicListenerOptions.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicOperationAbortedException.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicOperationAbortedException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicStream.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicStream.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicException.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicConnectionAbortedException.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicConnectionAbortedException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicStreamAbortedException.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicStreamAbortedException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\Interop.MsQuic.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\Interop.MsQuic.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicEnums.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicEnums.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicNativeMethods.cs"
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicNativeMethods.cs" />
     <Compile Include="$(CommonPath)System\Net\SecurityStatusPal.cs"
              Link="Common\System\Net\SecurityStatusPal.cs" />
     <Compile Include="$(CommonPath)System\Net\Security\SSPIHandleCache.cs"
     <Reference Include="System.Net.NameResolution" />
     <Reference Include="System.Net.NetworkInformation" />
     <Reference Include="System.Net.Primitives" />
+    <Reference Include="System.Net.Quic" />
     <Reference Include="System.Net.Security" />
     <Reference Include="System.Net.Sockets" />
     <Reference Include="System.Runtime" />
index 2c77415..0fb0ca5 100644 (file)
@@ -3,6 +3,8 @@
 
 using System.Collections.Generic;
 using System.IO;
+using System.Net.Quic;
+using System.Net.Quic.Implementations;
 using System.Net.Security;
 using System.Runtime.Versioning;
 using System.Threading;
@@ -185,5 +187,11 @@ namespace System.Net.Http
             get => throw new PlatformNotSupportedException();
             set => throw new PlatformNotSupportedException();
         }
+
+        public QuicImplementationProvider? QuicImplementationProvider
+        {
+            get => throw new PlatformNotSupportedException();
+            set => throw new PlatformNotSupportedException();
+        }
     }
 }
index 2e17fad..e3079e8 100644 (file)
@@ -4,6 +4,7 @@
 using System.Diagnostics;
 using System.IO;
 using System.Net.Quic;
+using System.Net.Quic.Implementations;
 using System.Net.Security;
 using System.Net.Sockets;
 using System.Security.Cryptography.X509Certificates;
@@ -145,9 +146,9 @@ namespace System.Net.Http
             return sslStream;
         }
 
-        public static async ValueTask<QuicConnection> ConnectQuicAsync(DnsEndPoint endPoint, SslClientAuthenticationOptions? clientAuthenticationOptions, CancellationToken cancellationToken)
+        public static async ValueTask<QuicConnection> ConnectQuicAsync(QuicImplementationProvider quicImplementationProvider, DnsEndPoint endPoint, SslClientAuthenticationOptions? clientAuthenticationOptions, CancellationToken cancellationToken)
         {
-            QuicConnection con = new QuicConnection(endPoint, clientAuthenticationOptions);
+            QuicConnection con = new QuicConnection(quicImplementationProvider, endPoint, clientAuthenticationOptions);
             try
             {
                 await con.ConnectAsync(cancellationToken).ConfigureAwait(false);
index 25013a1..cfd4252 100644 (file)
@@ -120,7 +120,7 @@ namespace System.Net.Http
             }
 
             _http2Enabled = _poolManager.Settings._maxHttpVersion >= HttpVersion.Version20;
-            _http3Enabled = _poolManager.Settings._maxHttpVersion >= Http3Connection.HttpVersion30;
+            _http3Enabled = _poolManager.Settings._maxHttpVersion >= Http3Connection.HttpVersion30 && (_poolManager.Settings._quicImplementationProvider ?? QuicImplementationProviders.Default).IsSupported;
 
             switch (kind)
             {
@@ -373,6 +373,7 @@ namespace System.Net.Http
                     return GetHttp3ConnectionAsync(request, authority, cancellationToken);
                 }
             }
+
             // If we got here, we cannot provide HTTP/3 connection. Do not continue if downgrade is not allowed.
             if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
             {
@@ -815,7 +816,7 @@ namespace System.Net.Http
                 QuicConnection quicConnection;
                 try
                 {
-                    quicConnection = await ConnectHelper.ConnectQuicAsync(new DnsEndPoint(authority.IdnHost, authority.Port), _sslOptionsHttp3, cancellationToken).ConfigureAwait(false);
+                    quicConnection = await ConnectHelper.ConnectQuicAsync(Settings._quicImplementationProvider ?? QuicImplementationProviders.Default, new DnsEndPoint(authority.IdnHost, authority.Port), _sslOptionsHttp3, cancellationToken).ConfigureAwait(false);
                 }
                 catch
                 {
index 2e3fd77..afa5b26 100644 (file)
@@ -4,6 +4,8 @@
 using System.Collections.Generic;
 using System.Net.Security;
 using System.IO;
+using System.Net.Quic;
+using System.Net.Quic.Implementations;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -59,6 +61,9 @@ namespace System.Net.Http
         internal Func<SocketsHttpConnectionContext, CancellationToken, ValueTask<Stream>>? _connectCallback;
         internal Func<SocketsHttpPlaintextStreamFilterContext, CancellationToken, ValueTask<Stream>>? _plaintextStreamFilter;
 
+        // !!! NOTE !!! This is temporary and will not ship.
+        internal QuicImplementationProvider? _quicImplementationProvider;
+
         internal IDictionary<string, object?>? _properties;
 
         public HttpConnectionSettings()
@@ -114,6 +119,7 @@ namespace System.Net.Http
                 _enableMultipleHttp2Connections = _enableMultipleHttp2Connections,
                 _connectCallback = _connectCallback,
                 _plaintextStreamFilter = _plaintextStreamFilter,
+                _quicImplementationProvider = _quicImplementationProvider
             };
         }
 
@@ -147,8 +153,8 @@ namespace System.Net.Http
         {
             get
             {
-                // Default to not allowing draft HTTP/3, but enable that to be overridden
-                // by an AppContext switch, or by an environment variable being to to true/1.
+                // Default to allowing draft HTTP/3, but enable that to be overridden
+                // by an AppContext switch, or by an environment variable being set to false/0.
 
                 // First check for the AppContext switch, giving it priority over the environment variable.
                 if (AppContext.TryGetSwitch(Http3DraftSupportAppCtxSettingName, out bool allowHttp3))
@@ -158,14 +164,14 @@ namespace System.Net.Http
 
                 // AppContext switch wasn't used. Check the environment variable.
                 string? envVar = Environment.GetEnvironmentVariable(Http3DraftSupportEnvironmentVariableSettingName);
-                if (envVar != null && (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1")))
+                if (envVar != null && (envVar.Equals("false", StringComparison.OrdinalIgnoreCase) || envVar.Equals("0")))
                 {
-                    // Allow HTTP/3 protocol for HTTP endpoints.
-                    return true;
+                    // Disallow HTTP/3 protocol for HTTP endpoints.
+                    return false;
                 }
 
-                // Default to disallow.
-                return false;
+                // Default to allow.
+                return true;
             }
         }
 
index 5c631e8..7e2631e 100644 (file)
@@ -4,6 +4,8 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
+using System.Net.Quic;
+using System.Net.Quic.Implementations;
 using System.Net.Security;
 using System.Runtime.Versioning;
 using System.Threading;
@@ -391,6 +393,21 @@ namespace System.Net.Http
             }
         }
 
+        /// <summary>
+        /// Gets or sets the QUIC implementation to be used for HTTP3 requests.
+        /// </summary>
+        public QuicImplementationProvider? QuicImplementationProvider
+        {
+            // !!! NOTE !!!
+            // This is temporary and will not ship.
+            get => _settings._quicImplementationProvider;
+            set
+            {
+                CheckDisposedOrStarted();
+                _settings._quicImplementationProvider = value;
+            }
+        }
+
         public IDictionary<string, object?> Properties =>
             _settings._properties ?? (_settings._properties = new Dictionary<string, object?>());
 
index 7ec1c22..0bf7745 100644 (file)
@@ -71,6 +71,12 @@ namespace System.Net.Http.Functional.Tests
         [Fact]
         public async Task SendAsync_LargeHeaders_CorrectlyWritten()
         {
+            if (UseVersion == HttpVersion30)
+            {
+                // TODO: ActiveIssue
+                return;
+            }
+
             // Intentionally larger than 16K in total because that's the limit that will trigger a CONTINUATION frame in HTTP2.
             string largeHeaderValue = new string('a', 1024);
             int count = 20;
@@ -332,6 +338,12 @@ namespace System.Net.Http.Functional.Tests
         [InlineData(true)]
         public async Task SendAsync_GetWithValidHostHeader_Success(bool withPort)
         {
+            if (UseVersion == HttpVersion30)
+            {
+                // External servers do not support HTTP3 currently.
+                return;
+            }
+
             var m = new HttpRequestMessage(HttpMethod.Get, Configuration.Http.SecureRemoteEchoServer) { Version = UseVersion };
             m.Headers.Host = withPort ? Configuration.Http.SecureHost + ":443" : Configuration.Http.SecureHost;
 
index d0f9d5f..dfbf00f 100644 (file)
@@ -2,6 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.IO;
+using System.Net.Quic;
+using System.Net.Quic.Implementations;
+using System.Net.Test.Common;
 using System.Reflection;
 using System.Threading.Tasks;
 
@@ -11,19 +14,36 @@ namespace System.Net.Http.Functional.Tests
     {
         protected static bool IsWinHttpHandler => false;
 
-        protected static HttpClientHandler CreateHttpClientHandler(Version useVersion = null)
+        protected virtual QuicImplementationProvider UseQuicImplementationProvider => null;
+
+        public static bool IsMsQuicSupported => QuicImplementationProviders.MsQuic.IsSupported;
+
+        protected static HttpClientHandler CreateHttpClientHandler(Version useVersion = null, QuicImplementationProvider quicImplementationProvider = null)
         {
             useVersion ??= HttpVersion.Version11;
 
-            HttpClientHandler handler = PlatformDetection.SupportsAlpn ? new HttpClientHandler() : new VersionHttpClientHandler(useVersion);
+            HttpClientHandler handler = (PlatformDetection.SupportsAlpn && useVersion != HttpVersion30) ? new HttpClientHandler() : new VersionHttpClientHandler(useVersion);
 
             if (useVersion >= HttpVersion.Version20)
             {
                 handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
             }
+
+            if (quicImplementationProvider != null)
+            {
+                SocketsHttpHandler socketsHttpHandler = (SocketsHttpHandler)GetUnderlyingSocketsHttpHandler(handler);
+                socketsHttpHandler.QuicImplementationProvider = quicImplementationProvider;
+            }
+
             return handler;
         }
 
+        protected HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseVersion, UseQuicImplementationProvider);
+
+        protected static HttpClientHandler CreateHttpClientHandler(string useVersionString) =>
+            CreateHttpClientHandler(Version.Parse(useVersionString));
+
+
         protected static object GetUnderlyingSocketsHttpHandler(HttpClientHandler handler)
         {
             FieldInfo field = typeof(HttpClientHandler).GetField("_underlyingHandler", BindingFlags.Instance | BindingFlags.NonPublic);
@@ -36,6 +56,23 @@ namespace System.Net.Http.Functional.Tests
                 Version = version,
                 VersionPolicy = exactVersion ? HttpVersionPolicy.RequestVersionExact : HttpVersionPolicy.RequestVersionOrLower
             };
+
+        protected LoopbackServerFactory LoopbackServerFactory => GetFactoryForVersion(UseVersion, UseQuicImplementationProvider);
+
+        protected static LoopbackServerFactory GetFactoryForVersion(Version useVersion, QuicImplementationProvider quicImplementationProvider = null)
+        {
+            return useVersion.Major switch
+            {
+#if NETCOREAPP
+#if HTTP3
+                3 => new Http3LoopbackServerFactory(quicImplementationProvider),
+#endif
+                2 => Http2LoopbackServerFactory.Singleton,
+#endif
+                _ => Http11LoopbackServerFactory.Singleton
+            };
+        }
+
     }
 
     internal class VersionHttpClientHandler : HttpClientHandler
index 5c89f69..2c59a25 100644 (file)
@@ -4,6 +4,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Net.Quic;
+using System.Net.Quic.Implementations;
 using System.Net.Security;
 using System.Net.Sockets;
 using System.Net.Test.Common;
@@ -31,11 +32,20 @@ namespace System.Net.Http.Functional.Tests
         }
     }
 
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public sealed class SocketsHttpHandler_HttpClientMiniStress_Http3 : HttpClientMiniStress
+    [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
+    public sealed class SocketsHttpHandler_HttpClientMiniStress_Http3_MsQuic : HttpClientMiniStress
     {
-        public SocketsHttpHandler_HttpClientMiniStress_Http3(ITestOutputHelper output) : base(output) { }
+        public SocketsHttpHandler_HttpClientMiniStress_Http3_MsQuic(ITestOutputHelper output) : base(output) { }
         protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic;
+    }
+
+    [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
+    public sealed class SocketsHttpHandler_HttpClientMiniStress_Http3_Mock : HttpClientMiniStress
+    {
+        public SocketsHttpHandler_HttpClientMiniStress_Http3_Mock(ITestOutputHelper output) : base(output) { }
+        protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock;
     }
 
     public sealed class SocketsHttpHandler_HttpClientMiniStress_Http2 : HttpClientMiniStress
index 2ff22e9..4cadad0 100644 (file)
@@ -1104,7 +1104,7 @@ namespace System.Net.Http.Functional.Tests
                 yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version20, useSsl, useSsl ? (object)HttpVersion.Version11 : typeof(HttpRequestException) };
                 yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version20, useSsl, useSsl ? (object)HttpVersion.Version11 : typeof(HttpRequestException) };
                 yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version20, useSsl, useSsl ? (object)HttpVersion.Version20 : typeof(HttpRequestException) };
-                if (QuicConnection.IsQuicSupported)
+                if (QuicImplementationProviders.Default.IsSupported)
                 {
                     yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionOrLower, HttpVersion30, useSsl, HttpVersion.Version11 };
                     yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionExact, HttpVersion30, useSsl, HttpVersion.Version11 };
@@ -1117,14 +1117,14 @@ namespace System.Net.Http.Functional.Tests
                 yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version20, useSsl, useSsl ? (object)HttpVersion.Version20 : typeof(HttpRequestException) };
                 yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version20, useSsl, HttpVersion.Version20 };
                 yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version20, useSsl, HttpVersion.Version20 };
-                if (QuicConnection.IsQuicSupported)
+                if (QuicImplementationProviders.Default.IsSupported)
                 {
                     yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrLower, HttpVersion30, useSsl, useSsl ? HttpVersion.Version20 : HttpVersion.Version11 };
                     yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionExact, HttpVersion30, useSsl, HttpVersion.Version20 };
                     yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion30, useSsl, useSsl ? (object)HttpVersion30 : typeof(HttpRequestException) };
                 }
 
-                if (QuicConnection.IsQuicSupported)
+                if (QuicImplementationProviders.Default.IsSupported)
                 {
                     yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version11, useSsl, useSsl ? HttpVersion30 : HttpVersion.Version11 };
                     yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version11, useSsl, typeof(HttpRequestException) };
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/QuicConnectionTests.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/QuicConnectionTests.cs
deleted file mode 100644 (file)
index efe82d7..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Net.Quic;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Quic.Tests
-{
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public class QuicConnectionTests : MsQuicTestBase
-    {
-        [Fact]
-        public async Task AcceptStream_ConnectionAborted_ByClient_Throws()
-        {
-            const int ExpectedErrorCode = 1234;
-
-            using var sync = new SemaphoreSlim(0);
-
-            await RunClientServer(
-                async clientConnection =>
-                {
-                    await clientConnection.CloseAsync(ExpectedErrorCode);
-                    sync.Release();
-                },
-                async serverConnection =>
-                {
-                    await sync.WaitAsync();
-                    QuicConnectionAbortedException ex = await Assert.ThrowsAsync<QuicConnectionAbortedException>(() => serverConnection.AcceptStreamAsync().AsTask());
-                    Assert.Equal(ExpectedErrorCode, ex.ErrorCode);
-                });
-        }
-    }
-}
diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/QuicStreamTests.cs
deleted file mode 100644 (file)
index 254887d..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Xunit;
-
-namespace System.Net.Quic.Tests
-{
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public class QuicStreamTests : MsQuicTestBase
-    {
-        [Theory]
-        [MemberData(nameof(ReadWrite_Random_Success_Data))]
-        public async Task ReadWrite_Random_Success(int readSize, int writeSize)
-        {
-            byte[] testBuffer = new byte[8192];
-            new Random().NextBytes(testBuffer);
-
-            await RunClientServer(
-                async clientConnection =>
-                {
-                    await using QuicStream clientStream = clientConnection.OpenUnidirectionalStream();
-
-                    ReadOnlyMemory<byte> sendBuffer = testBuffer;
-                    while (sendBuffer.Length != 0)
-                    {
-                        ReadOnlyMemory<byte> chunk = sendBuffer.Slice(0, Math.Min(sendBuffer.Length, writeSize));
-                        await clientStream.WriteAsync(chunk);
-                        sendBuffer = sendBuffer.Slice(chunk.Length);
-                    }
-
-                    clientStream.Shutdown();
-                    await clientStream.ShutdownWriteCompleted();
-                },
-                async serverConnection =>
-                {
-                    await using QuicStream serverStream = await serverConnection.AcceptStreamAsync();
-
-                    byte[] receiveBuffer = new byte[testBuffer.Length];
-                    int totalBytesRead = 0;
-
-                    while (totalBytesRead != receiveBuffer.Length)
-                    {
-                        int bytesRead = await serverStream.ReadAsync(receiveBuffer.AsMemory(totalBytesRead, Math.Min(receiveBuffer.Length - totalBytesRead, readSize)));
-
-                        if (bytesRead == 0)
-                        {
-                            break;
-                        }
-
-                        totalBytesRead += bytesRead;
-                    }
-
-                    Assert.True(receiveBuffer.AsSpan().SequenceEqual(testBuffer));
-                });
-        }
-
-        public static IEnumerable<object[]> ReadWrite_Random_Success_Data()
-        {
-            IEnumerable<int> sizes = Enumerable.Range(1, 8).Append(2048).Append(8192);
-
-            return
-                from readSize in sizes
-                from writeSize in sizes
-                select new object[] { readSize, writeSize };
-        }
-
-        [Fact]
-        public async Task Read_StreamAborted_Throws()
-        {
-            const int ExpectedErrorCode = 0xfffffff;
-
-            await Task.Run(async () =>
-            {
-                using QuicListener listener = CreateQuicListener();
-                ValueTask<QuicConnection> serverConnectionTask = listener.AcceptConnectionAsync();
-
-                using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
-                await clientConnection.ConnectAsync();
-
-                using QuicConnection serverConnection = await serverConnectionTask;
-
-                await using QuicStream clientStream = clientConnection.OpenBidirectionalStream();
-                await clientStream.WriteAsync(new byte[1]);
-
-                await using QuicStream serverStream = await serverConnection.AcceptStreamAsync();
-                await serverStream.ReadAsync(new byte[1]);
-
-                clientStream.AbortWrite(ExpectedErrorCode);
-
-                byte[] buffer = new byte[100];
-                QuicStreamAbortedException ex = await Assert.ThrowsAsync<QuicStreamAbortedException>(() => serverStream.ReadAsync(buffer).AsTask());
-                Assert.Equal(ExpectedErrorCode, ex.ErrorCode);
-            }).TimeoutAfter(millisecondsTimeout: 5_000);
-        }
-
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/32050")]
-        [Fact]
-        public async Task Read_ConnectionAborted_Throws()
-        {
-            const int ExpectedErrorCode = 1234;
-
-            await Task.Run(async () =>
-            {
-                using QuicListener listener = CreateQuicListener();
-                ValueTask<QuicConnection> serverConnectionTask = listener.AcceptConnectionAsync();
-
-                using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
-                await clientConnection.ConnectAsync();
-
-                using QuicConnection serverConnection = await serverConnectionTask;
-
-                await using QuicStream clientStream = clientConnection.OpenBidirectionalStream();
-                await clientStream.WriteAsync(new byte[1]);
-
-                await using QuicStream serverStream = await serverConnection.AcceptStreamAsync();
-                await serverStream.ReadAsync(new byte[1]);
-
-                await clientConnection.CloseAsync(ExpectedErrorCode);
-
-                byte[] buffer = new byte[100];
-                QuicConnectionAbortedException ex = await Assert.ThrowsAsync<QuicConnectionAbortedException>(() => serverStream.ReadAsync(buffer).AsTask());
-                Assert.Equal(ExpectedErrorCode, ex.ErrorCode);
-            }).TimeoutAfter(millisecondsTimeout: 5_000);
-        }
-    }
-}
index a3313a8..80432a5 100644 (file)
@@ -6,6 +6,7 @@ using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Net.Quic;
+using System.Net.Quic.Implementations;
 using System.Net.Security;
 using System.Net.Sockets;
 using System.Net.Test.Common;
@@ -2960,51 +2961,105 @@ namespace System.Net.Http.Functional.Tests
         protected override Version UseVersion => HttpVersion.Version20;
     }
 
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public sealed class SocketsHttpHandler_HttpClientHandler_Finalization_Http3_Test : HttpClientHandler_Finalization_Test
+    [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
+    public sealed class SocketsHttpHandlerTest_Http3 : HttpClientHandlerTest_Http3
+    {
+        public SocketsHttpHandlerTest_Http3(ITestOutputHelper output) : base(output) { }
+    }
+
+    [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
+    public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Http3_MsQuic : HttpClientHandlerTest
     {
-        public SocketsHttpHandler_HttpClientHandler_Finalization_Http3_Test(ITestOutputHelper output) : base(output) { }
+        public SocketsHttpHandlerTest_HttpClientHandlerTest_Http3_MsQuic(ITestOutputHelper output) : base(output) { }
         protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic;
     }
 
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public sealed class SocketsHttpHandlerTest_Http3 : HttpClientHandlerTest_Http3
+    public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Http3_Mock : HttpClientHandlerTest
     {
-        public SocketsHttpHandlerTest_Http3(ITestOutputHelper output) : base(output) { }
+        public SocketsHttpHandlerTest_HttpClientHandlerTest_Http3_Mock(ITestOutputHelper output) : base(output) { }
+        protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock;
     }
 
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public sealed class SocketsHttpHandlerTest_Cookies_Http3 : HttpClientHandlerTest_Cookies
+#if false // TODO: Many Cookie tests are failing for HTTP3.
+    [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
+    public sealed class SocketsHttpHandlerTest_Cookies_Http3_MsQuic : HttpClientHandlerTest_Cookies
     {
-        public SocketsHttpHandlerTest_Cookies_Http3(ITestOutputHelper output) : base(output) { }
+        public SocketsHttpHandlerTest_Cookies_Http3_MsQuic(ITestOutputHelper output) : base(output) { }
         protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic;
     }
 
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Http3 : HttpClientHandlerTest
+    public sealed class SocketsHttpHandlerTest_Cookies_Http3_Mock : HttpClientHandlerTest_Cookies
     {
-        public SocketsHttpHandlerTest_HttpClientHandlerTest_Http3(ITestOutputHelper output) : base(output) { }
+        public SocketsHttpHandlerTest_Cookies_Http3_Mock(ITestOutputHelper output) : base(output) { }
         protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock;
+    }
+#endif
+
+    [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
+    public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3_MsQuic : HttpClientHandlerTest_Headers
+    {
+        public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3_MsQuic(ITestOutputHelper output) : base(output) { }
+        protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic;
     }
 
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3 : HttpClientHandlerTest_Headers
+    public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3_Mock : HttpClientHandlerTest_Headers
     {
-        public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3(ITestOutputHelper output) : base(output) { }
+        public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3_Mock(ITestOutputHelper output) : base(output) { }
         protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock;
     }
 
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3 : HttpClientHandler_Cancellation_Test
+#if false   // TODO: Many cancellation tests are failing for HTTP3.
+    [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
+    public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_MsQuic : HttpClientHandler_Cancellation_Test
+    {
+        public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_MsQuic(ITestOutputHelper output) : base(output) { }
+        protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic;
+    }
+
+    public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_Mock : HttpClientHandler_Cancellation_Test
+    {
+        public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_Mock(ITestOutputHelper output) : base(output) { }
+        protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock;
+    }
+#endif
+
+#if false   // TODO: Many AltSvc tests are failing for HTTP3.
+    [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
+    public sealed class SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3_MsQuic : HttpClientHandler_AltSvc_Test
+    {
+        public SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3_MsQuic(ITestOutputHelper output) : base(output) { }
+        protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic;
+    }
+
+    public sealed class SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3_Mock : HttpClientHandler_AltSvc_Test
+    {
+        public SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3_Mock(ITestOutputHelper output) : base(output) { }
+        protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock;
+    }
+#endif
+
+    [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsMsQuicSupported))]
+    public sealed class SocketsHttpHandler_HttpClientHandler_Finalization_Http3_MsQuic : HttpClientHandler_Finalization_Test
     {
-        public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3(ITestOutputHelper output) : base(output) { }
+        public SocketsHttpHandler_HttpClientHandler_Finalization_Http3_MsQuic(ITestOutputHelper output) : base(output) { }
         protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic;
     }
 
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public sealed class SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3 : HttpClientHandler_AltSvc_Test
+    public sealed class SocketsHttpHandler_HttpClientHandler_Finalization_Http3_Mock : HttpClientHandler_Finalization_Test
     {
-        public SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3(ITestOutputHelper output) : base(output) { }
+        public SocketsHttpHandler_HttpClientHandler_Finalization_Http3_Mock(ITestOutputHelper output) : base(output) { }
         protected override Version UseVersion => HttpVersion30;
+        protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock;
     }
 }
index 3de49f7..05407cf 100644 (file)
              Link="Common\Interop\Unix\Interop.Libraries.cs" />
     <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\NetEventSource.Common.cs"
              Link="Common\System\Net\Http\aspnetcore\NetEventSource.Common.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicAddressHelpers.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicAddressHelpers.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicApi.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicApi.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicParameterHelpers.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicParameterHelpers.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSecurityConfig.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSecurityConfig.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSession.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\MsQuicSession.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\QuicExceptionHelpers.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\QuicExceptionHelpers.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\ResettableCompletionSource.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\Internal\ResettableCompletionSource.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicConnection.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicConnection.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicImplementationProvider.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicImplementationProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicListener.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicListener.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicStream.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\MsQuic\MsQuicStream.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicImplementationProvider.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicImplementationProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicListenerProvider.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicListenerProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicConnectionProvider.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicConnectionProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\QuicStreamProvider.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\QuicStreamProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockImplementationProvider.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockImplementationProvider.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockListener.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockListener.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockConnection.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockConnection.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockStream.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Implementations\Mock\MockStream.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusCodes.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusCodes.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusHelper.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicStatusHelper.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\NetEventSource.Quic.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\NetEventSource.Quic.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicClientConnectionOptions.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicClientConnectionOptions.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicImplementationProviders.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicImplementationProviders.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicConnection.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicConnection.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicListener.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicListener.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicListenerOptions.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicListenerOptions.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicOperationAbortedException.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicOperationAbortedException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicStream.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicStream.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicException.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicConnectionAbortedException.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicConnectionAbortedException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\QuicStreamAbortedException.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\QuicStreamAbortedException.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\Interop.MsQuic.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\Interop.MsQuic.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicEnums.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicEnums.cs" />
-    <Compile Include="$(CommonPath)System\Net\Http\aspnetcore\Quic\Interop\MsQuicNativeMethods.cs" 
-             Link="Common\System\Net\Http\aspnetcore\Quic\Interop\MsQuicNativeMethods.cs" />
     <Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
              Link="System\System\Threading\Tasks\TaskToApm.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true'"
              Link="Common\System\Net\Http\HttpProtocolTests.cs" />
     <Compile Include="HttpRequestMessageTest.cs" />
     <Compile Include="HttpResponseMessageTest.cs" />
-    <Compile Include="MsQuicTestBase.cs" />
-    <Compile Include="MsQuicTests.cs" />
     <Compile Include="MessageProcessingHandlerTest.cs" />
     <Compile Include="MultipartContentTest.cs" />
     <Compile Include="MultipartFormDataContentTest.cs" />
     <Compile Include="$(CommonTestPath)System\Net\Http\PostScenarioTest.cs"
              Link="Common\System\Net\Http\PostScenarioTest.cs" />
-    <Compile Include="QuicConnectionTests.cs" />
-    <Compile Include="QuicListenerTests.cs" />
-    <Compile Include="QuicStreamTests.cs" />
     <Compile Include="$(CommonTestPath)System\Net\Http\RepeatedFlushContent.cs"
              Link="Common\System\Net\Http\RepeatedFlushContent.cs" />
     <Compile Include="$(CommonTestPath)System\Net\Http\ResponseStreamTest.cs"
diff --git a/src/libraries/System.Net.Quic/Directory.Build.props b/src/libraries/System.Net.Quic/Directory.Build.props
new file mode 100644 (file)
index 0000000..d68d22c
--- /dev/null
@@ -0,0 +1,7 @@
+<Project>
+  <Import Project="..\Directory.Build.props" />
+  <PropertyGroup>
+    <StrongNameKeyId>Microsoft</StrongNameKeyId>
+    <IncludePlatformAttributes>true</IncludePlatformAttributes>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/src/libraries/System.Net.Quic/System.Net.Quic.sln b/src/libraries/System.Net.Quic/System.Net.Quic.sln
new file mode 100644 (file)
index 0000000..cf5fba5
--- /dev/null
@@ -0,0 +1,53 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27213.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.Quic.Functional.Tests", "tests\FunctionalTests\System.Net.Quic.Functional.Tests.csproj", "{C85CF035-7804-41FF-9557-48B7C948B58D}"
+       ProjectSection(ProjectDependencies) = postProject
+               {1D422B1D-D7C4-41B9-862D-EB3D98DF37DE} = {1D422B1D-D7C4-41B9-862D-EB3D98DF37DE}
+       EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.Quic", "src\System.Net.Quic.csproj", "{1D422B1D-D7C4-41B9-862D-EB3D98DF37DE}"
+       ProjectSection(ProjectDependencies) = postProject
+               {132BF813-FC40-4D39-8B6F-E55D7633F0ED} = {132BF813-FC40-4D39-8B6F-E55D7633F0ED}
+       EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.Quic", "ref\System.Net.Quic.csproj", "{132BF813-FC40-4D39-8B6F-E55D7633F0ED}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1A2F9F4A-A032-433E-B914-ADD5992BB178}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E107E9C1-E893-4E87-987E-04EF0DCEAEFD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{2E666815-2EDB-464B-9DF6-380BF4789AD4}"
+EndProject
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|Any CPU = Debug|Any CPU
+               Release|Any CPU = Release|Any CPU
+       EndGlobalSection
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+               {C85CF035-7804-41FF-9557-48B7C948B58D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {C85CF035-7804-41FF-9557-48B7C948B58D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {C85CF035-7804-41FF-9557-48B7C948B58D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {C85CF035-7804-41FF-9557-48B7C948B58D}.Release|Any CPU.Build.0 = Release|Any CPU
+               {1D422B1D-D7C4-41B9-862D-EB3D98DF37DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {1D422B1D-D7C4-41B9-862D-EB3D98DF37DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {1D422B1D-D7C4-41B9-862D-EB3D98DF37DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {1D422B1D-D7C4-41B9-862D-EB3D98DF37DE}.Release|Any CPU.Build.0 = Release|Any CPU
+               {132BF813-FC40-4D39-8B6F-E55D7633F0ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {132BF813-FC40-4D39-8B6F-E55D7633F0ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {132BF813-FC40-4D39-8B6F-E55D7633F0ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {132BF813-FC40-4D39-8B6F-E55D7633F0ED}.Release|Any CPU.Build.0 = Release|Any CPU
+       EndGlobalSection
+       GlobalSection(SolutionProperties) = preSolution
+               HideSolutionNode = FALSE
+       EndGlobalSection
+       GlobalSection(NestedProjects) = preSolution
+               {C85CF035-7804-41FF-9557-48B7C948B58D} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
+               {1D422B1D-D7C4-41B9-862D-EB3D98DF37DE} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD}
+               {132BF813-FC40-4D39-8B6F-E55D7633F0ED} = {2E666815-2EDB-464B-9DF6-380BF4789AD4}
+       EndGlobalSection
+       GlobalSection(ExtensibilityGlobals) = postSolution
+               SolutionGuid = {5100F629-0FAB-4C6F-9A54-95AE9565EE0D}
+       EndGlobalSection
+EndGlobal
diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs
new file mode 100644 (file)
index 0000000..bbbc1df
--- /dev/null
@@ -0,0 +1,126 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// ------------------------------------------------------------------------------
+// Changes to this file must follow the https://aka.ms/api-review process.
+// ------------------------------------------------------------------------------
+
+namespace System.Net.Quic
+{
+    public static class QuicImplementationProviders
+    {
+        public static Implementations.QuicImplementationProvider Mock => throw null;
+        public static Implementations.QuicImplementationProvider MsQuic => throw null;
+        public static Implementations.QuicImplementationProvider Default => throw null;
+    }
+    public sealed class QuicListener : IDisposable
+    {
+        public QuicListener(IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions) => throw null;
+        public QuicListener(Implementations.QuicImplementationProvider implementationProvider, IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions) => throw null;
+        public QuicListener(Implementations.QuicImplementationProvider implementationProvider, QuicListenerOptions options) => throw null;
+        public IPEndPoint ListenEndPoint => throw null;
+        public System.Threading.Tasks.ValueTask<QuicConnection> AcceptConnectionAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public void Start() => throw null;
+        public void Close() => throw null;
+        public void Dispose() => throw null;
+    }
+    public class QuicListenerOptions
+    {
+        public System.Net.Security.SslServerAuthenticationOptions? ServerAuthenticationOptions { get => throw null; set => throw null; }
+        public string? CertificateFilePath { get => throw null; set => throw null; }
+        public string? PrivateKeyFilePath { 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; }
+    }
+    public sealed class QuicConnection : IDisposable
+    {
+        public QuicConnection(System.Net.EndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions? sslClientAuthenticationOptions, System.Net.IPEndPoint? localEndPoint = null) => throw null;
+        public QuicConnection(Implementations.QuicImplementationProvider implementationProvider, System.Net.EndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions? sslClientAuthenticationOptions, System.Net.IPEndPoint? localEndPoint = null) => throw null;
+        public QuicConnection(Implementations.QuicImplementationProvider implementationProvider, QuicClientConnectionOptions options) => throw null;
+        public bool Connected => throw null;
+        public System.Net.IPEndPoint LocalEndPoint => throw null;
+        public System.Net.EndPoint RemoteEndPoint => throw null;
+        public System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol => throw null;
+        public System.Threading.Tasks.ValueTask ConnectAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public QuicStream OpenUnidirectionalStream() => throw null;
+        public QuicStream OpenBidirectionalStream() => throw null;
+        public System.Threading.Tasks.ValueTask<QuicStream> AcceptStreamAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public System.Threading.Tasks.ValueTask CloseAsync(long errorCode, System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public void Dispose() => throw null;
+        public long GetRemoteAvailableUnidirectionalStreamCount() => throw null;
+        public long GetRemoteAvailableBidirectionalStreamCount() => throw null;
+    }
+    public class QuicClientConnectionOptions
+    {
+        public System.Net.Security.SslClientAuthenticationOptions? ClientAuthenticationOptions { get => throw null; set => throw null; }
+        public IPEndPoint? LocalEndPoint { get => throw null; set => throw null; }
+        public EndPoint? 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 sealed class QuicStream : System.IO.Stream
+    {
+        internal QuicStream() => throw null;
+        public override bool CanSeek => throw null;
+        public override long Length => throw null;
+        public override long Seek(long offset, System.IO.SeekOrigin origin) => throw null;
+        public override void SetLength(long value) => throw null;
+        public override long Position { get => throw null; set => throw null; }
+        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => throw null;
+        public override int EndRead(IAsyncResult asyncResult) => throw null;
+        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => throw null;
+        public override void EndWrite(IAsyncResult asyncResult) => throw null;
+        public override int Read(byte[] buffer, int offset, int count) => throw null;
+        public override System.Threading.Tasks.Task<int> ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) => throw null;
+        public override void Write(byte[] buffer, int offset, int count) => throw null;
+        public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) => throw null;
+        public long StreamId => throw null;
+        public override bool CanRead => throw null;
+        public override int Read(Span<byte> buffer) => throw null;
+        public override System.Threading.Tasks.ValueTask<int> ReadAsync(Memory<byte> buffer, System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public override bool CanWrite => throw null;
+        public override void Write(ReadOnlySpan<byte> buffer) => throw null;
+        public override System.Threading.Tasks.ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public override void Flush() => throw null;
+        public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) => throw null;
+        public void AbortRead(long errorCode) => throw null;
+        public void AbortWrite(long errorCode) => throw null;
+        public System.Threading.Tasks.ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool endStream, System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public System.Threading.Tasks.ValueTask WriteAsync(System.Buffers.ReadOnlySequence<byte> buffers, System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public System.Threading.Tasks.ValueTask WriteAsync(System.Buffers.ReadOnlySequence<byte> buffers, bool endStream, System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public System.Threading.Tasks.ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public System.Threading.Tasks.ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, bool endStream, System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public System.Threading.Tasks.ValueTask ShutdownWriteCompleted(System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public void Shutdown() => throw null;
+    }
+    public class QuicException : Exception
+    {
+        public QuicException(string? message) { throw null; }
+        public QuicException(string? message, Exception? innerException) { throw null; }
+    }
+    public class QuicConnectionAbortedException : QuicException
+    {
+        public QuicConnectionAbortedException(string message, long errorCode) : base(default) { throw null; }
+        public long ErrorCode { get { throw null; } }
+    }
+    public class QuicOperationAbortedException : QuicException
+    {
+        public QuicOperationAbortedException(string message) : base(default) { throw null; }
+    }
+    public class QuicStreamAbortedException : QuicException
+    {
+        public QuicStreamAbortedException(string message, long errorCode) : base(default) { throw null; }
+        public long ErrorCode { get { throw null; } }
+    }
+}
+namespace System.Net.Quic.Implementations
+{
+    public abstract class QuicImplementationProvider
+    {
+        internal QuicImplementationProvider() { }
+        public abstract bool IsSupported { get; }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/ref/System.Net.Quic.csproj
new file mode 100644 (file)
index 0000000..d5dc6b1
--- /dev/null
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="System.Net.Quic.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\System.Runtime\ref\System.Runtime.csproj" />
+    <ProjectReference Include="..\..\System.Net.Primitives\ref\System.Net.Primitives.csproj" />
+    <ProjectReference Include="..\..\System.Net.Sockets\ref\System.Net.Sockets.csproj" />
+    <ProjectReference Include="..\..\System.Net.Security\ref\System.Net.Security.csproj" />
+    <ProjectReference Include="..\..\System.Security.Cryptography.X509Certificates\ref\System.Security.Cryptography.X509Certificates.csproj" />
+  </ItemGroup>
+</Project>
diff --git a/src/libraries/System.Net.Quic/src/Resources/Strings.resx b/src/libraries/System.Net.Quic/src/Resources/Strings.resx
new file mode 100644 (file)
index 0000000..d6548cd
--- /dev/null
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="net_quic_connectionaborted" xml:space="preserve">
+    <value>Connection aborted by peer ({0}).</value>
+  </data>
+  <data name="net_quic_notsupported" xml:space="preserve">
+    <value>QUIC is not supported on this platform. See http://aka.ms/dotnetquic</value>
+  </data>
+  <data name="net_quic_operationaborted" xml:space="preserve">
+    <value>Operation aborted.</value>
+  </data>
+  <data name="net_quic_streamaborted" xml:space="preserve">
+    <value>Stream aborted by peer ({0}).</value>
+  </data>
+</root>
diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
new file mode 100644 (file)
index 0000000..ea13913
--- /dev/null
@@ -0,0 +1,68 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <TargetFrameworks>$(NetCoreAppCurrent)-Windows_NT;$(NetCoreAppCurrent)-Linux;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-FreeBSD;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS</TargetFrameworks>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+  <!-- Source files -->
+  <ItemGroup>
+    <Compile Include="System\Net\Quic\*.cs" />
+    <Compile Include="System\Net\Quic\Implementations\*.cs" />
+    <Compile Include="System\Net\Quic\Implementations\Mock\*.cs" />
+    <Compile Include="System\Net\Quic\Implementations\MsQuic\*.cs" />
+    <Compile Include="System\Net\Quic\Implementations\MsQuic\Internal\*.cs" />
+    <Compile Include="System\Net\Quic\Interop\*.cs" />
+  </ItemGroup>
+  <!-- System.Net common -->
+  <ItemGroup>
+    <Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
+    <Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs" Link="Common\System\Net\ArrayBuffer.cs" />
+    <Compile Include="$(CommonPath)System\Net\Logging\NetEventSource.Common.cs" Link="Common\System\Net\Logging\NetEventSource.Common.cs" />
+  </ItemGroup>
+  <!-- Windows specific files -->
+  <ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
+  </ItemGroup>
+  <!-- Linux specific files -->
+  <ItemGroup Condition="'$(TargetsLinux)' == 'true' or '$(TargetsBrowser)' == 'true' ">
+    <Compile Include="$(CommonPath)Interop\Linux\Interop.Libraries.cs" Link="Common\Interop\Linux\Interop.Libraries.cs" />
+  </ItemGroup>
+  <!-- FreeBSD specific files -->
+  <ItemGroup Condition="'$(TargetsFreeBSD)' == 'true' ">
+    <Compile Include="$(CommonPath)Interop\FreeBSD\Interop.Libraries.cs" Link="Common\Interop\FreeBSD\Interop.Libraries.cs" />
+  </ItemGroup>
+  <!-- OSX specific files -->
+  <ItemGroup Condition=" '$(TargetsOSX)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">
+    <Compile Include="$(CommonPath)Interop\OSX\Interop.Libraries.cs" Link="Common\Interop\OSX\Interop.Libraries.cs" />
+  </ItemGroup>
+  <!-- Project references -->
+  <ItemGroup>
+    <Reference Include="System.Collections" />
+    <Reference Include="System.Collections.Concurrent" />
+    <Reference Include="System.Diagnostics.Tracing" />
+    <Reference Include="System.Memory" />
+    <Reference Include="System.Net.Primitives" />
+    <Reference Include="System.Net.Security" />
+    <Reference Include="System.Net.Sockets" />
+    <Reference Include="System.Runtime" />
+    <Reference Include="System.Runtime.InteropServices" />
+    <Reference Include="System.Security.Cryptography.X509Certificates" />
+    <Reference Include="System.Threading" />
+    <Reference Include="System.Threading.Channels" />
+  </ItemGroup>
+  <!-- Support for deploying msquic -->
+  <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>
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockConnection.cs
new file mode 100644 (file)
index 0000000..5fb4031
--- /dev/null
@@ -0,0 +1,283 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+using System.Diagnostics;
+using System.Net;
+using System.Net.Security;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations.Mock
+{
+    internal sealed class MockConnection : QuicConnectionProvider
+    {
+        private readonly bool _isClient;
+        private bool _disposed;
+        private SslClientAuthenticationOptions? _sslClientAuthenticationOptions;
+        private IPEndPoint _remoteEndPoint;
+        private IPEndPoint _localEndPoint;
+        private object _syncObject = new object();
+        private long _nextOutboundBidirectionalStream;
+        private long _nextOutboundUnidirectionalStream;
+
+        private ConnectionState? _state;
+
+        // Constructor for outbound connections
+        internal MockConnection(EndPoint? remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null)
+        {
+            if (remoteEndPoint is null)
+            {
+                throw new ArgumentNullException(nameof(remoteEndPoint));
+            }
+
+            IPEndPoint ipEndPoint = GetIPEndPoint(remoteEndPoint);
+            if (ipEndPoint.Address != IPAddress.Loopback)
+            {
+                throw new ArgumentException("Expected loopback address", nameof(remoteEndPoint));
+            }
+
+            _isClient = true;
+            _remoteEndPoint = ipEndPoint;
+            _localEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
+            _sslClientAuthenticationOptions = sslClientAuthenticationOptions;
+            _nextOutboundBidirectionalStream = 0;
+            _nextOutboundUnidirectionalStream = 2;
+
+            // _state is not initialized until ConnectAsync
+        }
+
+        // Constructor for accepted inbound connections
+        internal MockConnection(IPEndPoint localEndPoint, ConnectionState state)
+        {
+            _isClient = false;
+            _remoteEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
+            _localEndPoint = localEndPoint;
+
+            _nextOutboundBidirectionalStream = 1;
+            _nextOutboundUnidirectionalStream = 3;
+
+            _state = state;
+        }
+
+        private static IPEndPoint GetIPEndPoint(EndPoint endPoint)
+        {
+            if (endPoint is IPEndPoint ipEndPoint)
+            {
+                return ipEndPoint;
+            }
+
+            if (endPoint is DnsEndPoint dnsEndPoint)
+            {
+                if (dnsEndPoint.Host == "127.0.0.1")
+                {
+                    return new IPEndPoint(IPAddress.Loopback, dnsEndPoint.Port);
+                }
+
+                throw new InvalidOperationException($"invalid DNS name {dnsEndPoint.Host}");
+            }
+
+            throw new InvalidOperationException("unknown EndPoint type");
+        }
+
+        internal override bool Connected
+        {
+            get
+            {
+                CheckDisposed();
+
+                return _state != null;
+            }
+        }
+
+        // TODO: Should clone the endpoint since it is mutable
+        internal override IPEndPoint LocalEndPoint => _localEndPoint;
+
+        // TODO: Should clone the endpoint since it is mutable
+        internal override EndPoint RemoteEndPoint => _remoteEndPoint!;
+
+        internal override SslApplicationProtocol NegotiatedApplicationProtocol
+        {
+            get
+            {
+                if (_state is null)
+                {
+                    throw new InvalidOperationException("not connected");
+                }
+
+                return _state._applicationProtocol;
+            }
+        }
+
+        internal override ValueTask ConnectAsync(CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            if (Connected)
+            {
+                throw new InvalidOperationException("Already connected");
+            }
+
+            Debug.Assert(_isClient, "not connected but also not _isClient??");
+
+            MockListener? listener = MockListener.TryGetListener(_remoteEndPoint);
+            if (listener is null)
+            {
+                throw new InvalidOperationException("Could not find listener");
+            }
+
+            // TODO: deal with protocol negotiation
+            _state = new ConnectionState(_sslClientAuthenticationOptions!.ApplicationProtocols![0]);
+            if (!listener.TryConnect(_state))
+            {
+                throw new QuicException("Connection refused");
+            }
+
+            return ValueTask.CompletedTask;
+        }
+
+        internal override QuicStreamProvider OpenUnidirectionalStream()
+        {
+            long streamId;
+            lock (_syncObject)
+            {
+                streamId = _nextOutboundUnidirectionalStream;
+                _nextOutboundUnidirectionalStream += 4;
+            }
+
+            return OpenStream(streamId, false);
+        }
+
+        internal override QuicStreamProvider OpenBidirectionalStream()
+        {
+            long streamId;
+            lock (_syncObject)
+            {
+                streamId = _nextOutboundBidirectionalStream;
+                _nextOutboundBidirectionalStream += 4;
+            }
+
+            return OpenStream(streamId, true);
+        }
+
+        internal MockStream OpenStream(long streamId, bool bidirectional)
+        {
+            ConnectionState? state = _state;
+            if (state is null)
+            {
+                throw new InvalidOperationException("Not connected");
+            }
+
+            MockStream.StreamState streamState = new MockStream.StreamState(streamId, bidirectional);
+            Channel<MockStream.StreamState> streamChannel = _isClient ? state._clientInitiatedStreamChannel : state._serverInitiatedStreamChannel;
+            streamChannel.Writer.TryWrite(streamState);
+
+            return new MockStream(streamState, true);
+        }
+
+        internal override long GetRemoteAvailableUnidirectionalStreamCount() => long.MaxValue;
+
+        internal override long GetRemoteAvailableBidirectionalStreamCount() => long.MaxValue;
+
+        internal override async ValueTask<QuicStreamProvider> AcceptStreamAsync(CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            ConnectionState? state = _state;
+            if (state is null)
+            {
+                throw new InvalidOperationException("Not connected");
+            }
+
+            Channel<MockStream.StreamState> streamChannel = _isClient ? state._serverInitiatedStreamChannel : state._clientInitiatedStreamChannel;
+
+            try
+            {
+                MockStream.StreamState streamState = await streamChannel.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
+                return new MockStream(streamState, false);
+            }
+            catch (ChannelClosedException)
+            {
+                long errorCode = _isClient ? state._serverErrorCode : state._clientErrorCode;
+                throw new QuicConnectionAbortedException(errorCode);
+            }
+        }
+
+        internal override ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default)
+        {
+            ConnectionState? state = _state;
+            if (state is not null)
+            {
+                if (_isClient)
+                {
+                    state._clientErrorCode = errorCode;
+                }
+                else
+                {
+                    state._serverErrorCode = errorCode;
+                }
+            }
+
+            Dispose();
+
+            return default;
+        }
+
+        private void CheckDisposed()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(QuicConnection));
+            }
+        }
+
+        private void Dispose(bool disposing)
+        {
+            if (!_disposed)
+            {
+                if (disposing)
+                {
+                    ConnectionState? state = _state;
+                    if (state is not null)
+                    {
+                        Channel<MockStream.StreamState> streamChannel = _isClient ? state._clientInitiatedStreamChannel : state._serverInitiatedStreamChannel;
+                        streamChannel.Writer.Complete();
+                    }
+                }
+
+                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+                // TODO: set large fields to null.
+
+                _disposed = true;
+            }
+        }
+
+        ~MockConnection()
+        {
+            Dispose(false);
+        }
+
+        public override void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        internal sealed class ConnectionState
+        {
+            public readonly SslApplicationProtocol _applicationProtocol;
+            public Channel<MockStream.StreamState> _clientInitiatedStreamChannel;
+            public Channel<MockStream.StreamState> _serverInitiatedStreamChannel;
+            public long _clientErrorCode;
+            public long _serverErrorCode;
+
+            public ConnectionState(SslApplicationProtocol applicationProtocol)
+            {
+                _applicationProtocol = applicationProtocol;
+                _clientInitiatedStreamChannel = Channel.CreateUnbounded<MockStream.StreamState>();
+                _serverInitiatedStreamChannel = Channel.CreateUnbounded<MockStream.StreamState>();
+            }
+        }
+    }
+}
@@ -7,9 +7,11 @@ namespace System.Net.Quic.Implementations.Mock
 {
     internal sealed class MockImplementationProvider : QuicImplementationProvider
     {
+        public override bool IsSupported => true;
+
         internal override QuicListenerProvider CreateListener(QuicListenerOptions options)
         {
-            return new MockListener(options.ListenEndPoint!, options.ServerAuthenticationOptions);
+            return new MockListener(options);
         }
 
         internal override QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options)
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockListener.cs
new file mode 100644 (file)
index 0000000..8ce3061
--- /dev/null
@@ -0,0 +1,126 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations.Mock
+{
+    internal sealed class MockListener : QuicListenerProvider
+    {
+        private bool _disposed;
+        private readonly QuicListenerOptions _options;
+        private readonly IPEndPoint _listenEndPoint;
+        private Channel<MockConnection.ConnectionState> _listenQueue;
+
+        // We synthesize port numbers for the listener, starting with 1, and track these in a dictionary.
+        private static int s_mockPort;
+        private static ConcurrentDictionary<int, MockListener> s_listenerMap = new ConcurrentDictionary<int, MockListener>();
+
+        internal MockListener(QuicListenerOptions options)
+        {
+            if (options.ListenEndPoint is null || options.ListenEndPoint.Address != IPAddress.Loopback || options.ListenEndPoint.Port != 0)
+            {
+                throw new ArgumentException("Must pass loopback address and port 0");
+            }
+
+            _options = options;
+
+            int port = Interlocked.Increment(ref s_mockPort);
+
+            _listenEndPoint = new IPEndPoint(IPAddress.Loopback, port);
+            bool success = s_listenerMap.TryAdd(port, this);
+            Debug.Assert(success);
+
+            _listenQueue = Channel.CreateBounded<MockConnection.ConnectionState>(new BoundedChannelOptions(options.ListenBacklog));
+        }
+
+        // TODO: IPEndPoint is mutable, so we should create a copy here.
+        internal override IPEndPoint ListenEndPoint => _listenEndPoint;
+
+        internal static MockListener? TryGetListener(IPEndPoint endpoint)
+        {
+            if (endpoint.Address != IPAddress.Loopback || endpoint.Port == 0)
+            {
+                return null;
+            }
+
+            MockListener? listener;
+            if (!s_listenerMap.TryGetValue(endpoint.Port, out listener))
+            {
+                return null;
+            }
+
+            return listener;
+        }
+
+        internal override async ValueTask<QuicConnectionProvider> AcceptConnectionAsync(CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            MockConnection.ConnectionState state = await _listenQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
+
+            return new MockConnection(_listenEndPoint, state);
+        }
+
+        // Returns false if backlog queue is full.
+        internal bool TryConnect(MockConnection.ConnectionState state)
+        {
+            return _listenQueue.Writer.TryWrite(state);
+        }
+
+        internal override void Start()
+        {
+            CheckDisposed();
+
+            // TODO: Track start
+        }
+
+        internal override void Close()
+        {
+            Dispose();
+        }
+
+        private void CheckDisposed()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(QuicListener));
+            }
+        }
+
+        private void Dispose(bool disposing)
+        {
+            if (!_disposed)
+            {
+                if (disposing)
+                {
+                    MockListener? listener;
+                    bool success = s_listenerMap.TryRemove(_listenEndPoint.Port, out listener);
+                    Debug.Assert(success);
+                    Debug.Assert(listener == this);
+                }
+
+                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+                // TODO: set large fields to null.
+
+                _disposed = true;
+            }
+        }
+
+        ~MockListener()
+        {
+            Dispose(false);
+        }
+
+        public override void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockStream.cs
new file mode 100644 (file)
index 0000000..0d5c561
--- /dev/null
@@ -0,0 +1,233 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+using System.Buffers;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations.Mock
+{
+    internal sealed class MockStream : QuicStreamProvider
+    {
+        private bool _disposed;
+        private readonly bool _isInitiator;
+
+        private readonly StreamState _streamState;
+
+        internal MockStream(StreamState streamState, bool isInitiator)
+        {
+            _streamState = streamState;
+            _isInitiator = isInitiator;
+        }
+
+        private ValueTask ConnectAsync(CancellationToken cancellationToken = default)
+        {
+            return ValueTask.CompletedTask;
+        }
+
+        internal override long StreamId
+        {
+            get
+            {
+                CheckDisposed();
+                return _streamState._streamId;
+            }
+        }
+
+        private StreamBuffer? ReadStreamBuffer => _isInitiator ? _streamState._inboundStreamBuffer : _streamState._outboundStreamBuffer;
+
+        internal override bool CanRead => ReadStreamBuffer is not null;
+
+        internal override int Read(Span<byte> buffer)
+        {
+            CheckDisposed();
+
+            StreamBuffer? streamBuffer = ReadStreamBuffer;
+            if (streamBuffer is null)
+            {
+                throw new NotSupportedException();
+            }
+
+            return streamBuffer.Read(buffer);
+        }
+
+        internal override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            StreamBuffer? streamBuffer = ReadStreamBuffer;
+            if (streamBuffer is null)
+            {
+                throw new NotSupportedException();
+            }
+
+            int bytesRead = await streamBuffer.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+            if (bytesRead == 0)
+            {
+                long errorCode = _isInitiator ? _streamState._inboundErrorCode : _streamState._outboundErrorCode;
+                if (errorCode != 0)
+                {
+                    throw new QuicStreamAbortedException(errorCode);
+                }
+            }
+
+            return bytesRead;
+        }
+
+        private StreamBuffer? WriteStreamBuffer => _isInitiator ? _streamState._outboundStreamBuffer : _streamState._inboundStreamBuffer;
+
+        internal override bool CanWrite => WriteStreamBuffer is not null;
+
+        internal override void Write(ReadOnlySpan<byte> buffer)
+        {
+            CheckDisposed();
+
+            StreamBuffer? streamBuffer = WriteStreamBuffer;
+            if (streamBuffer is null)
+            {
+                throw new NotSupportedException();
+            }
+
+            streamBuffer.Write(buffer);
+        }
+
+        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();
+
+            StreamBuffer? streamBuffer = WriteStreamBuffer;
+            if (streamBuffer is null)
+            {
+                throw new NotSupportedException();
+            }
+
+            await streamBuffer.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
+
+            if (endStream)
+            {
+                streamBuffer.EndWrite();
+            }
+        }
+
+        internal override ValueTask WriteAsync(ReadOnlySequence<byte> buffers, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+        internal override ValueTask WriteAsync(ReadOnlySequence<byte> buffers, bool endStream, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+
+        internal override async ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, CancellationToken cancellationToken = default)
+        {
+            for (int i = 0; i < buffers.Length; i++)
+            {
+                await WriteAsync(buffers.Span[i], cancellationToken).ConfigureAwait(false);
+            }
+        }
+
+        internal override ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, bool endStream, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+
+        internal override void Flush()
+        {
+            CheckDisposed();
+        }
+
+        internal override Task FlushAsync(CancellationToken cancellationToken)
+        {
+            CheckDisposed();
+
+            return Task.CompletedTask;
+        }
+
+        internal override void AbortRead(long errorCode)
+        {
+            throw new NotImplementedException();
+        }
+
+        internal override void AbortWrite(long errorCode)
+        {
+            if (_isInitiator)
+            {
+                _streamState._outboundErrorCode = errorCode;
+            }
+            else
+            {
+                _streamState._inboundErrorCode = errorCode;
+            }
+
+            WriteStreamBuffer?.EndWrite();
+        }
+
+
+        internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            return default;
+        }
+
+        internal override void Shutdown()
+        {
+            CheckDisposed();
+
+            // This seems to mean shutdown send, in particular, not both.
+            WriteStreamBuffer?.EndWrite();
+        }
+
+        private void CheckDisposed()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(QuicStream));
+            }
+        }
+
+        public override void Dispose()
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+
+            }
+        }
+
+        public override ValueTask DisposeAsync()
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+            }
+
+            return default;
+        }
+
+        internal sealed class StreamState
+        {
+            public readonly long _streamId;
+            public StreamBuffer _outboundStreamBuffer;
+            public StreamBuffer? _inboundStreamBuffer;
+            public long _outboundErrorCode;
+            public long _inboundErrorCode;
+
+            public StreamState(long streamId, bool bidirectional)
+            {
+                _streamId = streamId;
+                _outboundStreamBuffer = new StreamBuffer();
+                _inboundStreamBuffer = (bidirectional ? new StreamBuffer() : null);
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/StreamBuffer.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/StreamBuffer.cs
new file mode 100644 (file)
index 0000000..a6b4994
--- /dev/null
@@ -0,0 +1,366 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Net;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Sources;
+
+namespace System.IO
+{
+    internal sealed class StreamBuffer : IDisposable
+    {
+        private ArrayBuffer _buffer; // mutable struct, do not make this readonly
+        private readonly int _maxSize;
+        private bool _writeEnded;
+        private bool _readAborted;
+        private readonly ResettableValueTaskSource _readTaskSource;
+        private readonly ResettableValueTaskSource _writeTaskSource;
+        private readonly object _syncObject = new object();
+
+        public const int DefaultInitialBufferSize = 4 * 1024;
+        public const int DefaultMaxBufferSize = 32 * 1024;
+
+        public StreamBuffer(int initialSize = DefaultInitialBufferSize, int maxSize = DefaultMaxBufferSize)
+        {
+            _buffer = new ArrayBuffer(initialSize, usePool: true);
+            _maxSize = maxSize;
+            _readTaskSource = new ResettableValueTaskSource();
+            _writeTaskSource = new ResettableValueTaskSource();
+        }
+
+        private object SyncObject => _syncObject;
+
+        public bool IsComplete
+        {
+            get
+            {
+                Debug.Assert(!Monitor.IsEntered(SyncObject));
+                lock (SyncObject)
+                {
+                    return (_writeEnded && _buffer.ActiveLength == 0);
+                }
+            }
+        }
+
+        public bool IsAborted
+        {
+            get
+            {
+                Debug.Assert(!Monitor.IsEntered(SyncObject));
+                lock (SyncObject)
+                {
+                    return _readAborted;
+                }
+            }
+        }
+
+        public int ReadBytesAvailable
+        {
+            get
+            {
+                Debug.Assert(!Monitor.IsEntered(SyncObject));
+                lock (SyncObject)
+                {
+                    if (_readAborted)
+                    {
+                        return 0;
+                    }
+
+                    return _buffer.ActiveLength;
+                }
+            }
+        }
+
+        public int WriteBytesAvailable
+        {
+            get
+            {
+                Debug.Assert(!Monitor.IsEntered(SyncObject));
+                lock (SyncObject)
+                {
+                    if (_writeEnded)
+                    {
+                        throw new InvalidOperationException();
+                    }
+
+                    return _maxSize - _buffer.ActiveLength;
+                }
+            }
+        }
+
+        private (bool wait, int bytesWritten) TryWriteToBuffer(ReadOnlySpan<byte> buffer)
+        {
+            Debug.Assert(buffer.Length > 0);
+
+            Debug.Assert(!Monitor.IsEntered(SyncObject));
+            lock (SyncObject)
+            {
+                if (_writeEnded)
+                {
+                    throw new InvalidOperationException();
+                }
+
+                if (_readAborted)
+                {
+                    return (false, buffer.Length);
+                }
+
+                _buffer.TryEnsureAvailableSpaceUpToLimit(buffer.Length, _maxSize);
+
+                int bytesWritten = Math.Min(buffer.Length, _buffer.AvailableLength);
+                if (bytesWritten > 0)
+                {
+                    buffer.Slice(0, bytesWritten).CopyTo(_buffer.AvailableSpan);
+                    _buffer.Commit(bytesWritten);
+
+                    _readTaskSource.SignalWaiter();
+                }
+
+                buffer = buffer.Slice(bytesWritten);
+                if (buffer.Length == 0)
+                {
+                    return (false, bytesWritten);
+                }
+
+                _writeTaskSource.Reset();
+
+                return (true, bytesWritten);
+            }
+        }
+
+        public void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (buffer.Length == 0)
+            {
+                return;
+            }
+
+            while (true)
+            {
+                (bool wait, int bytesWritten) = TryWriteToBuffer(buffer);
+                if (!wait)
+                {
+                    Debug.Assert(bytesWritten == buffer.Length);
+                    break;
+                }
+
+                buffer = buffer.Slice(bytesWritten);
+                _writeTaskSource.Wait();
+            }
+        }
+
+        public async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            if (buffer.Length == 0)
+            {
+                return;
+            }
+
+            while (true)
+            {
+                (bool wait, int bytesWritten) = TryWriteToBuffer(buffer.Span);
+                if (!wait)
+                {
+                    Debug.Assert(bytesWritten == buffer.Length);
+                    break;
+                }
+
+                buffer = buffer.Slice(bytesWritten);
+                await _writeTaskSource.WaitAsync(cancellationToken).ConfigureAwait(false);
+            }
+        }
+
+        public void EndWrite()
+        {
+            Debug.Assert(!Monitor.IsEntered(SyncObject));
+            lock (SyncObject)
+            {
+                if (_writeEnded)
+                {
+                    return;
+                }
+
+                _writeEnded = true;
+
+                _readTaskSource.SignalWaiter();
+            }
+        }
+
+        private (bool wait, int bytesRead) TryReadFromBuffer(Span<byte> buffer)
+        {
+            Debug.Assert(buffer.Length > 0);
+
+            Debug.Assert(!Monitor.IsEntered(SyncObject));
+            lock (SyncObject)
+            {
+                if (_readAborted)
+                {
+                    return (false, 0);
+                }
+
+                if (_buffer.ActiveLength > 0)
+                {
+                    int bytesRead = Math.Min(buffer.Length, _buffer.ActiveLength);
+                    _buffer.ActiveSpan.Slice(0, bytesRead).CopyTo(buffer);
+                    _buffer.Discard(bytesRead);
+
+                    _writeTaskSource.SignalWaiter();
+
+                    return (false, bytesRead);
+                }
+                else if (_writeEnded)
+                {
+                    return (false, 0);
+                }
+
+                _readTaskSource.Reset();
+
+                return (true, 0);
+            }
+        }
+
+        public int Read(Span<byte> buffer)
+        {
+            if (buffer.Length == 0)
+            {
+                return 0;
+            }
+
+            (bool wait, int bytesRead) = TryReadFromBuffer(buffer);
+            if (wait)
+            {
+                Debug.Assert(bytesRead == 0);
+                _readTaskSource.Wait();
+                (wait, bytesRead) = TryReadFromBuffer(buffer);
+                Debug.Assert(!wait);
+            }
+
+            return bytesRead;
+        }
+
+        public async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            if (buffer.Length == 0)
+            {
+                return 0;
+            }
+
+            (bool wait, int bytesRead) = TryReadFromBuffer(buffer.Span);
+            if (wait)
+            {
+                Debug.Assert(bytesRead == 0);
+                await _readTaskSource.WaitAsync(cancellationToken).ConfigureAwait(false);
+                (wait, bytesRead) = TryReadFromBuffer(buffer.Span);
+                Debug.Assert(!wait);
+            }
+
+            return bytesRead;
+        }
+
+        // Note, this can be called while a read is in progress, and will cause it to return 0 bytes.
+        // Caller can then check IsAborted if appropriate to distinguish between EOF and abort.
+        public void AbortRead()
+        {
+            Debug.Assert(!Monitor.IsEntered(SyncObject));
+            lock (SyncObject)
+            {
+                if (_readAborted)
+                {
+                    return;
+                }
+
+                _readAborted = true;
+                if (_buffer.ActiveLength != 0)
+                {
+                    _buffer.Discard(_buffer.ActiveLength);
+                }
+
+                _readTaskSource.SignalWaiter();
+                _writeTaskSource.SignalWaiter();
+            }
+        }
+
+        public void Dispose()
+        {
+            AbortRead();
+            EndWrite();
+
+            _buffer.Dispose();
+        }
+
+        private sealed class ResettableValueTaskSource : IValueTaskSource
+        {
+            // This object is used as the backing source for ValueTask.
+            // There should only ever be one awaiter at a time; users of this object must ensure this themselves.
+            // We use _hasWaiter to ensure mutual exclusion between successful completion and cancellation,
+            // and dispose/clear the cancellation registration in GetResult to guarantee it will not affect subsequent waiters.
+            // The rest of the logic is deferred to ManualResetValueTaskSourceCore.
+
+            private ManualResetValueTaskSourceCore<bool> _waitSource = new ManualResetValueTaskSourceCore<bool> { RunContinuationsAsynchronously = true }; // mutable struct, do not make this readonly
+            private CancellationToken _waitSourceCancellationToken;
+            private CancellationTokenRegistration _waitSourceCancellation;
+            private int _hasWaiter;
+
+            ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _waitSource.GetStatus(token);
+
+            void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _waitSource.OnCompleted(continuation, state, token, flags);
+
+            void IValueTaskSource.GetResult(short token)
+            {
+                Debug.Assert(_hasWaiter == 0);
+
+                // Clean up the registration.  This will wait for any in-flight cancellation to complete.
+                _waitSourceCancellation.Dispose();
+                _waitSourceCancellation = default;
+                _waitSourceCancellationToken = default;
+
+                // Propagate any exceptions if there were any.
+                _waitSource.GetResult(token);
+            }
+
+            public void SignalWaiter()
+            {
+                if (Interlocked.Exchange(ref _hasWaiter, 0) == 1)
+                {
+                    _waitSource.SetResult(true);
+                }
+            }
+
+            private void CancelWaiter()
+            {
+                if (Interlocked.Exchange(ref _hasWaiter, 0) == 1)
+                {
+                    Debug.Assert(_waitSourceCancellationToken != default);
+                    _waitSource.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(new OperationCanceledException(_waitSourceCancellationToken)));
+                }
+            }
+
+            public void Reset()
+            {
+                Debug.Assert(_hasWaiter == 0);
+
+                _waitSource.Reset();
+                Volatile.Write(ref _hasWaiter, 1);
+            }
+
+            public void Wait()
+            {
+                _waitSource.RunContinuationsAsynchronously = false;
+                new ValueTask(this, _waitSource.Version).AsTask().GetAwaiter().GetResult();
+            }
+
+            public ValueTask WaitAsync(CancellationToken cancellationToken)
+            {
+                _waitSource.RunContinuationsAsynchronously = true;
+
+                _waitSourceCancellationToken = cancellationToken;
+                _waitSourceCancellation = cancellationToken.UnsafeRegister(static s => ((ResettableValueTaskSource)s!).CancelWaiter(), this);
+
+                return new ValueTask(this, _waitSource.Version);
+            }
+        }
+    }
+}
@@ -2,12 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Net.Quic.Implementations.MsQuic.Internal;
-using System.Net.Security;
 
 namespace System.Net.Quic.Implementations.MsQuic
 {
     internal sealed class MsQuicImplementationProvider : QuicImplementationProvider
     {
+        public override bool IsSupported => MsQuicApi.IsQuicSupported;
+
         internal override QuicListenerProvider CreateListener(QuicListenerOptions options)
         {
             return new MsQuicListener(options);
@@ -5,10 +5,12 @@ using System.Net.Security;
 
 namespace System.Net.Quic.Implementations
 {
-    internal abstract class QuicImplementationProvider
+    public abstract class QuicImplementationProvider
     {
         internal QuicImplementationProvider() { }
 
+        public abstract bool IsSupported { get; }
+
         internal abstract QuicListenerProvider CreateListener(QuicListenerOptions options);
 
         internal abstract QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options);
@@ -9,7 +9,7 @@ namespace System.Net.Quic
     /// <summary>
     /// Options to provide to the <see cref="QuicConnection"/> when connecting to a Listener.
     /// </summary>
-    internal class QuicClientConnectionOptions
+    public class QuicClientConnectionOptions
     {
         /// <summary>
         /// Client authentication options to use when establishing a <see cref="QuicConnection"/>.
@@ -10,12 +10,10 @@ using System.Threading.Tasks;
 
 namespace System.Net.Quic
 {
-    internal sealed class QuicConnection : IDisposable
+    public sealed class QuicConnection : IDisposable
     {
         private readonly QuicConnectionProvider _provider;
 
-        public static bool IsQuicSupported => MsQuicApi.IsQuicSupported;
-
         /// <summary>
         /// Create an outbound QUIC connection.
         /// </summary>
@@ -3,7 +3,7 @@
 
 namespace System.Net.Quic
 {
-    internal class QuicConnectionAbortedException : QuicException
+    public class QuicConnectionAbortedException : QuicException
     {
         internal QuicConnectionAbortedException(long errorCode)
             : this(SR.Format(SR.net_quic_connectionaborted, errorCode), errorCode)
@@ -4,7 +4,7 @@
 #nullable enable
 namespace System.Net.Quic
 {
-    internal class QuicException : Exception
+    public class QuicException : Exception
     {
         public QuicException(string? message)
             : base(message)
@@ -3,7 +3,7 @@
 
 namespace System.Net.Quic
 {
-    internal static class QuicImplementationProviders
+    public static class QuicImplementationProviders
     {
         public static Implementations.QuicImplementationProvider Mock { get; } = new Implementations.Mock.MockImplementationProvider();
         public static Implementations.QuicImplementationProvider MsQuic { get; } = new Implementations.MsQuic.MsQuicImplementationProvider();
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
 
 namespace System.Net.Quic
 {
-    internal sealed class QuicListener : IDisposable
+    public sealed class QuicListener : IDisposable
     {
         private readonly QuicListenerProvider _provider;
 
@@ -9,7 +9,7 @@ namespace System.Net.Quic
     /// <summary>
     /// Options to provide to the <see cref="QuicListener"/>.
     /// </summary>
-    internal class QuicListenerOptions
+    public class QuicListenerOptions
     {
         /// <summary>
         /// Server Ssl options to use for ALPN, SNI, etc.
@@ -3,7 +3,7 @@
 
 namespace System.Net.Quic
 {
-    internal class QuicOperationAbortedException : QuicException
+    public class QuicOperationAbortedException : QuicException
     {
         internal QuicOperationAbortedException()
             : base(SR.net_quic_operationaborted)
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
 
 namespace System.Net.Quic
 {
-    internal sealed class QuicStream : Stream
+    public sealed class QuicStream : Stream
     {
         private readonly QuicStreamProvider _provider;
 
@@ -3,7 +3,7 @@
 
 namespace System.Net.Quic
 {
-    internal class QuicStreamAbortedException : QuicException
+    public class QuicStreamAbortedException : QuicException
     {
         internal QuicStreamAbortedException(long errorCode)
             : this(SR.Format(SR.net_quic_streamaborted, errorCode), errorCode)
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..21388ed
--- /dev/null
@@ -0,0 +1,256 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net.Security;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Net.Quic.Tests
+{
+    [ConditionalClass(typeof(MsQuicTests), nameof(MsQuicTests.IsMsQuicSupported))]
+    public class MsQuicTests : MsQuicTestBase
+    {
+        public static bool IsMsQuicSupported => QuicImplementationProviders.MsQuic.IsSupported;
+
+        private static ReadOnlyMemory<byte> s_data = Encoding.UTF8.GetBytes("Hello world!");
+
+        [Fact]
+        public async Task UnidirectionalAndBidirectionalStreamCountsWork()
+        {
+            using QuicListener listener = CreateQuicListener();
+            using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
+
+            ValueTask clientTask = clientConnection.ConnectAsync();
+            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+            await clientTask;
+            Assert.Equal(100, serverConnection.GetRemoteAvailableBidirectionalStreamCount());
+            Assert.Equal(100, serverConnection.GetRemoteAvailableUnidirectionalStreamCount());
+        }
+
+        [Fact]
+        public async Task UnidirectionalAndBidirectionalChangeValues()
+        {
+            using QuicListener listener = CreateQuicListener();
+
+            QuicClientConnectionOptions options = new QuicClientConnectionOptions()
+            {
+                MaxBidirectionalStreams = 10,
+                MaxUnidirectionalStreams = 20,
+                RemoteEndPoint = listener.ListenEndPoint,
+                ClientAuthenticationOptions = GetSslClientAuthenticationOptions()
+            };
+
+            using QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
+
+            ValueTask clientTask = clientConnection.ConnectAsync();
+            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+            await clientTask;
+            Assert.Equal(20, clientConnection.GetRemoteAvailableUnidirectionalStreamCount());
+            Assert.Equal(10, clientConnection.GetRemoteAvailableBidirectionalStreamCount());
+            Assert.Equal(100, serverConnection.GetRemoteAvailableBidirectionalStreamCount());
+            Assert.Equal(100, serverConnection.GetRemoteAvailableUnidirectionalStreamCount());
+        }
+
+        [Theory]
+        [MemberData(nameof(WriteData))]
+        public async Task WriteTests(int[][] writes, WriteType writeType)
+        {
+            await RunClientServer(
+                async clientConnection =>
+                {
+                    await using QuicStream stream = clientConnection.OpenUnidirectionalStream();
+
+                    foreach (int[] bufferLengths in writes)
+                    {
+                        switch (writeType)
+                        {
+                            case WriteType.SingleBuffer:
+                                foreach (int bufferLength in bufferLengths)
+                                {
+                                    await stream.WriteAsync(new byte[bufferLength]);
+                                }
+                                break;
+                            case WriteType.GatheredBuffers:
+                                var buffers = bufferLengths
+                                    .Select(bufferLength => new ReadOnlyMemory<byte>(new byte[bufferLength]))
+                                    .ToArray();
+                                await stream.WriteAsync(buffers);
+                                break;
+                            case WriteType.GatheredSequence:
+                                var firstSegment = new BufferSegment(new byte[bufferLengths[0]]);
+                                BufferSegment lastSegment = firstSegment;
+
+                                foreach (int bufferLength in bufferLengths.Skip(1))
+                                {
+                                    lastSegment = lastSegment.Append(new byte[bufferLength]);
+                                }
+
+                                var buffer = new ReadOnlySequence<byte>(firstSegment, 0, lastSegment, lastSegment.Memory.Length);
+                                await stream.WriteAsync(buffer);
+                                break;
+                            default:
+                                Debug.Fail("Unknown write type.");
+                                break;
+                        }
+                    }
+
+                    stream.Shutdown();
+                    await stream.ShutdownWriteCompleted();
+                },
+                async serverConnection =>
+                {
+                    await using QuicStream stream = await serverConnection.AcceptStreamAsync();
+
+                    var buffer = new byte[4096];
+                    int receivedBytes = 0, totalBytes = 0;
+
+                    while ((receivedBytes = await stream.ReadAsync(buffer)) != 0)
+                    {
+                        totalBytes += receivedBytes;
+                    }
+
+                    int expectedTotalBytes = writes.SelectMany(x => x).Sum();
+                    Assert.Equal(expectedTotalBytes, totalBytes);
+
+                    stream.Shutdown();
+                    await stream.ShutdownWriteCompleted();
+                });
+        }
+
+        public static IEnumerable<object[]> WriteData()
+        {
+            var bufferSizes = new[] { 1, 502, 15_003, 1_000_004 };
+            var r = new Random();
+
+            return
+                from bufferCount in new[] { 1, 2, 3, 10 }
+                from writeType in Enum.GetValues<WriteType>()
+                let writes =
+                    Enumerable.Range(0, 5)
+                    .Select(_ =>
+                        Enumerable.Range(0, bufferCount)
+                        .Select(_ => bufferSizes[r.Next(bufferSizes.Length)])
+                        .ToArray())
+                    .ToArray()
+                select new object[] { writes, writeType };
+        }
+
+        public enum WriteType
+        {
+            SingleBuffer,
+            GatheredBuffers,
+            GatheredSequence
+        }
+
+        [Fact]
+        public async Task CallDifferentWriteMethodsWorks()
+        {
+            using QuicListener listener = CreateQuicListener();
+            using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
+
+            ValueTask clientTask = clientConnection.ConnectAsync();
+            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+            await clientTask;
+
+            ReadOnlyMemory<byte> helloWorld = Encoding.ASCII.GetBytes("Hello world!");
+            ReadOnlySequence<byte> ros = CreateReadOnlySequenceFromBytes(helloWorld.ToArray());
+
+            Assert.False(ros.IsSingleSegment);
+            using QuicStream clientStream = clientConnection.OpenBidirectionalStream();
+            ValueTask writeTask = clientStream.WriteAsync(ros);
+            using QuicStream serverStream = await serverConnection.AcceptStreamAsync();
+
+            await writeTask;
+            byte[] memory = new byte[24];
+            int res = await serverStream.ReadAsync(memory);
+            Assert.Equal(12, res);
+            ReadOnlyMemory<ReadOnlyMemory<byte>> romrom = new ReadOnlyMemory<ReadOnlyMemory<byte>>(new ReadOnlyMemory<byte>[] { helloWorld, helloWorld });
+            
+            await clientStream.WriteAsync(romrom);
+
+            res = await serverStream.ReadAsync(memory);
+            Assert.Equal(24, res);
+        }
+
+        private static ReadOnlySequence<byte> CreateReadOnlySequenceFromBytes(byte[] data)
+        {
+            List<byte[]> segments = new List<byte[]>
+            {
+                Array.Empty<byte>()
+            };
+
+            foreach (var b in data)
+            {
+                segments.Add(new[] { b });
+                segments.Add(Array.Empty<byte>());
+            }
+
+            return CreateSegments(segments.ToArray());
+        }
+
+        private static ReadOnlySequence<byte> CreateSegments(params byte[][] inputs)
+        {
+            if (inputs == null || inputs.Length == 0)
+            {
+                throw new InvalidOperationException();
+            }
+
+            int i = 0;
+
+            BufferSegment last = null;
+            BufferSegment first = null;
+
+            do
+            {
+                byte[] s = inputs[i];
+                int length = s.Length;
+                int dataOffset = length;
+                var chars = new byte[length * 2];
+
+                for (int j = 0; j < length; j++)
+                {
+                    chars[dataOffset + j] = s[j];
+                }
+
+                // Create a segment that has offset relative to the OwnedMemory and OwnedMemory itself has offset relative to array
+                var memory = new Memory<byte>(chars).Slice(length, length);
+
+                if (first == null)
+                {
+                    first = new BufferSegment(memory);
+                    last = first;
+                }
+                else
+                {
+                    last = last.Append(memory);
+                }
+                i++;
+            } while (i < inputs.Length);
+
+            return new ReadOnlySequence<byte>(first, 0, last, last.Memory.Length);
+        }
+
+        internal class BufferSegment : ReadOnlySequenceSegment<byte>
+        {
+            public BufferSegment(ReadOnlyMemory<byte> memory)
+            {
+                Memory = memory;
+            }
+
+            public BufferSegment Append(ReadOnlyMemory<byte> memory)
+            {
+                var segment = new BufferSegment(memory)
+                {
+                    RunningIndex = RunningIndex + Memory.Length
+                };
+                Next = segment;
+                return segment;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs
new file mode 100644 (file)
index 0000000..ab4a961
--- /dev/null
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Net.Quic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Quic.Tests
+{
+    public abstract class QuicConnectionTests<T> : QuicTestBase<T>
+        where T : IQuicImplProviderFactory, new()
+    {
+        [Fact]
+        public async Task TestConnect()
+        {
+            using QuicListener listener = CreateQuicListener();
+
+            listener.Start();
+            IPEndPoint listenEndPoint = listener.ListenEndPoint;
+
+            using QuicConnection clientConnection = CreateQuicConnection(listenEndPoint);
+
+            Assert.False(clientConnection.Connected);
+            Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
+
+            ValueTask connectTask = clientConnection.ConnectAsync();
+            QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+            await connectTask;
+
+            Assert.True(clientConnection.Connected);
+            Assert.True(serverConnection.Connected);
+            Assert.Equal(listenEndPoint, serverConnection.LocalEndPoint);
+            Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
+            Assert.Equal(clientConnection.LocalEndPoint, serverConnection.RemoteEndPoint);
+            Assert.Equal(ApplicationProtocol.ToString(), clientConnection.NegotiatedApplicationProtocol.ToString());
+            Assert.Equal(ApplicationProtocol.ToString(), serverConnection.NegotiatedApplicationProtocol.ToString());
+        }
+
+        [Fact]
+        public async Task AcceptStream_ConnectionAborted_ByClient_Throws()
+        {
+            const int ExpectedErrorCode = 1234;
+
+            using var sync = new SemaphoreSlim(0);
+
+            await RunClientServer(
+                async clientConnection =>
+                {
+                    await clientConnection.CloseAsync(ExpectedErrorCode);
+                    sync.Release();
+                },
+                async serverConnection =>
+                {
+                    await sync.WaitAsync();
+                    QuicConnectionAbortedException ex = await Assert.ThrowsAsync<QuicConnectionAbortedException>(() => serverConnection.AcceptStreamAsync().AsTask());
+                    Assert.Equal(ExpectedErrorCode, ex.ErrorCode);
+                });
+        }
+    }
+
+    public sealed class QuicConnectionTests_MockProvider : QuicConnectionTests<MockProviderFactory> { }
+
+    [ConditionalClass(typeof(QuicTestBase<MsQuicProviderFactory>), nameof(QuicTestBase<MsQuicProviderFactory>.IsSupported))]
+    public sealed class QuicConnectionTests_MsQuicProvider : QuicConnectionTests<MsQuicProviderFactory> { }
+}
@@ -10,10 +10,9 @@ using Xunit;
 
 namespace System.Net.Quic.Tests
 {
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public class QuicListenerTests : MsQuicTestBase
+    public abstract class QuicListenerTests<T> : QuicTestBase<T>
+        where T : IQuicImplProviderFactory, new()
     {
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/32048")]
         [Fact]
         public async Task Listener_Backlog_Success()
         {
@@ -28,4 +27,9 @@ namespace System.Net.Quic.Tests
             }).TimeoutAfter(millisecondsTimeout: 5_000);
         }
     }
+
+    public sealed class QuicListenerTests_MockProvider : QuicListenerTests<MockProviderFactory> { }
+
+    [ConditionalClass(typeof(QuicTestBase<MsQuicProviderFactory>), nameof(QuicTestBase<MsQuicProviderFactory>.IsSupported))]
+    public sealed class QuicListenerTests_MsQuicProvider : QuicListenerTests<MsQuicProviderFactory> { }
 }
@@ -1,19 +1,18 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Buffers;
+using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Linq;
-using System.Net.Security;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using Xunit;
 
 namespace System.Net.Quic.Tests
 {
-    [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))]
-    public class MsQuicTests : MsQuicTestBase
+    public abstract class QuicStreamTests<T> : QuicTestBase<T>
+         where T : IQuicImplProviderFactory, new()
     {
         private static ReadOnlyMemory<byte> s_data = Encoding.UTF8.GetBytes("Hello world!");
 
@@ -216,56 +215,26 @@ namespace System.Net.Quic.Tests
         }
 
         [Fact]
-        public async Task TestConnect()
+        public async Task GetStreamIdWithoutStartWorks()
         {
-            SslServerAuthenticationOptions serverOpts = GetSslServerAuthenticationOptions();
-
-            using QuicListener listener = new QuicListener(
-                QuicImplementationProviders.MsQuic,
-                new IPEndPoint(IPAddress.Loopback, 0),
-                serverOpts);
-
-            listener.Start();
-            IPEndPoint listenEndPoint = listener.ListenEndPoint;
-            Assert.NotEqual(0, listenEndPoint.Port);
-
-            using QuicConnection clientConnection = new QuicConnection(
-                QuicImplementationProviders.MsQuic,
-                listenEndPoint,
-                GetSslClientAuthenticationOptions());
-
-            Assert.False(clientConnection.Connected);
-            Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
+            using QuicListener listener = CreateQuicListener();
+            using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
 
-            ValueTask connectTask = clientConnection.ConnectAsync();
-            QuicConnection serverConnection = await listener.AcceptConnectionAsync();
-            await connectTask;
+            ValueTask clientTask = clientConnection.ConnectAsync();
+            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+            await clientTask;
 
-            Assert.True(clientConnection.Connected);
-            Assert.True(serverConnection.Connected);
-            Assert.Equal(listenEndPoint, serverConnection.LocalEndPoint);
-            Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
-            Assert.Equal(clientConnection.LocalEndPoint, serverConnection.RemoteEndPoint);
-            Assert.Equal(serverOpts.ApplicationProtocols[0].ToString(), clientConnection.NegotiatedApplicationProtocol.ToString());
-            Assert.Equal(serverOpts.ApplicationProtocols[0].ToString(), serverConnection.NegotiatedApplicationProtocol.ToString());
+            using QuicStream clientStream = clientConnection.OpenBidirectionalStream();
+            Assert.Equal(0, clientStream.StreamId);
         }
 
         [Fact]
         public async Task TestStreams()
         {
-            using QuicListener listener = new QuicListener(
-                QuicImplementationProviders.MsQuic,
-                new IPEndPoint(IPAddress.Loopback, 0),
-                GetSslServerAuthenticationOptions());
-
-            listener.Start();
+            using QuicListener listener = CreateQuicListener();
             IPEndPoint listenEndPoint = listener.ListenEndPoint;
-            Assert.NotEqual(0, listenEndPoint.Port);
 
-            using QuicConnection clientConnection = new QuicConnection(
-                QuicImplementationProviders.MsQuic,
-                listenEndPoint,
-                GetSslClientAuthenticationOptions());
+            using QuicConnection clientConnection = CreateQuicConnection(listenEndPoint);
 
             Assert.False(clientConnection.Connected);
             Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
@@ -287,178 +256,6 @@ namespace System.Net.Quic.Tests
             await clientConnection.CloseAsync(errorCode: 0);
         }
 
-        [Fact]
-        public async Task UnidirectionalAndBidirectionalStreamCountsWork()
-        {
-            using QuicListener listener = CreateQuicListener();
-            using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
-
-            ValueTask clientTask = clientConnection.ConnectAsync();
-            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
-            await clientTask;
-            Assert.Equal(100, serverConnection.GetRemoteAvailableBidirectionalStreamCount());
-            Assert.Equal(100, serverConnection.GetRemoteAvailableUnidirectionalStreamCount());
-        }
-
-        [Fact]
-        public async Task UnidirectionalAndBidirectionalChangeValues()
-        {
-            using QuicListener listener = CreateQuicListener();
-
-            QuicClientConnectionOptions options = new QuicClientConnectionOptions()
-            {
-                MaxBidirectionalStreams = 10,
-                MaxUnidirectionalStreams = 20,
-                RemoteEndPoint = listener.ListenEndPoint,
-                ClientAuthenticationOptions = GetSslClientAuthenticationOptions()
-            };
-
-            using QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
-
-            ValueTask clientTask = clientConnection.ConnectAsync();
-            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
-            await clientTask;
-            Assert.Equal(20, clientConnection.GetRemoteAvailableUnidirectionalStreamCount());
-            Assert.Equal(10, clientConnection.GetRemoteAvailableBidirectionalStreamCount());
-            Assert.Equal(100, serverConnection.GetRemoteAvailableBidirectionalStreamCount());
-            Assert.Equal(100, serverConnection.GetRemoteAvailableUnidirectionalStreamCount());
-        }
-
-        [Theory]
-        [MemberData(nameof(WriteData))]
-        public async Task WriteTests(int[][] writes, WriteType writeType)
-        {
-            await RunClientServer(
-                async clientConnection =>
-                {
-                    await using QuicStream stream = clientConnection.OpenUnidirectionalStream();
-
-                    foreach (int[] bufferLengths in writes)
-                    {
-                        switch (writeType)
-                        {
-                            case WriteType.SingleBuffer:
-                                foreach (int bufferLength in bufferLengths)
-                                {
-                                    await stream.WriteAsync(new byte[bufferLength]);
-                                }
-                                break;
-                            case WriteType.GatheredBuffers:
-                                var buffers = bufferLengths
-                                    .Select(bufferLength => new ReadOnlyMemory<byte>(new byte[bufferLength]))
-                                    .ToArray();
-                                await stream.WriteAsync(buffers);
-                                break;
-                            case WriteType.GatheredSequence:
-                                var firstSegment = new BufferSegment(new byte[bufferLengths[0]]);
-                                BufferSegment lastSegment = firstSegment;
-
-                                foreach (int bufferLength in bufferLengths.Skip(1))
-                                {
-                                    lastSegment = lastSegment.Append(new byte[bufferLength]);
-                                }
-
-                                var buffer = new ReadOnlySequence<byte>(firstSegment, 0, lastSegment, lastSegment.Memory.Length);
-                                await stream.WriteAsync(buffer);
-                                break;
-                            default:
-                                Debug.Fail("Unknown write type.");
-                                break;
-                        }
-                    }
-
-                    stream.Shutdown();
-                    await stream.ShutdownWriteCompleted();
-                },
-                async serverConnection =>
-                {
-                    await using QuicStream stream = await serverConnection.AcceptStreamAsync();
-
-                    var buffer = new byte[4096];
-                    int receivedBytes = 0, totalBytes = 0;
-
-                    while ((receivedBytes = await stream.ReadAsync(buffer)) != 0)
-                    {
-                        totalBytes += receivedBytes;
-                    }
-
-                    int expectedTotalBytes = writes.SelectMany(x => x).Sum();
-                    Assert.Equal(expectedTotalBytes, totalBytes);
-
-                    stream.Shutdown();
-                    await stream.ShutdownWriteCompleted();
-                });
-        }
-
-        public static IEnumerable<object[]> WriteData()
-        {
-            var bufferSizes = new[] { 1, 502, 15_003, 1_000_004 };
-            var r = new Random();
-
-            return
-                from bufferCount in new[] { 1, 2, 3, 10 }
-                from writeType in Enum.GetValues<WriteType>()
-                let writes =
-                    Enumerable.Range(0, 5)
-                    .Select(_ =>
-                        Enumerable.Range(0, bufferCount)
-                        .Select(_ => bufferSizes[r.Next(bufferSizes.Length)])
-                        .ToArray())
-                    .ToArray()
-                select new object[] { writes, writeType };
-        }
-
-        public enum WriteType
-        {
-            SingleBuffer,
-            GatheredBuffers,
-            GatheredSequence
-        }
-
-        [Fact]
-        public async Task CallDifferentWriteMethodsWorks()
-        {
-            using QuicListener listener = CreateQuicListener();
-            using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
-
-            ValueTask clientTask = clientConnection.ConnectAsync();
-            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
-            await clientTask;
-
-            ReadOnlyMemory<byte> helloWorld = Encoding.ASCII.GetBytes("Hello world!");
-            ReadOnlySequence<byte> ros = CreateReadOnlySequenceFromBytes(helloWorld.ToArray());
-
-            Assert.False(ros.IsSingleSegment);
-            using QuicStream clientStream = clientConnection.OpenBidirectionalStream();
-            ValueTask writeTask = clientStream.WriteAsync(ros);
-            using QuicStream serverStream = await serverConnection.AcceptStreamAsync();
-
-            await writeTask;
-            byte[] memory = new byte[24];
-            int res = await serverStream.ReadAsync(memory);
-            Assert.Equal(12, res);
-            ReadOnlyMemory<ReadOnlyMemory<byte>> romrom = new ReadOnlyMemory<ReadOnlyMemory<byte>>(new ReadOnlyMemory<byte>[] { helloWorld, helloWorld });
-            
-            await clientStream.WriteAsync(romrom);
-
-            res = await serverStream.ReadAsync(memory);
-            Assert.Equal(24, res);
-        }
-
-        [Fact]
-        public async Task GetStreamIdWithoutStartWorks()
-        {
-            using QuicListener listener = CreateQuicListener();
-            using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
-
-            ValueTask clientTask = clientConnection.ConnectAsync();
-            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
-            await clientTask;
-
-            using QuicStream clientStream = clientConnection.OpenBidirectionalStream();
-            Assert.Equal(0, clientStream.StreamId);
-        }
-
         private static async Task CreateAndTestBidirectionalStream(QuicConnection c1, QuicConnection c2)
         {
             using QuicStream s1 = c1.OpenBidirectionalStream();
@@ -553,80 +350,123 @@ namespace System.Net.Quic.Tests
             Assert.Equal(0, bytesRead);
         }
 
-        private static ReadOnlySequence<byte> CreateReadOnlySequenceFromBytes(byte[] data)
+        [Theory]
+        [MemberData(nameof(ReadWrite_Random_Success_Data))]
+        public async Task ReadWrite_Random_Success(int readSize, int writeSize)
         {
-            List<byte[]> segments = new List<byte[]>
-            {
-                Array.Empty<byte>()
-            };
+            byte[] testBuffer = new byte[8192];
+            new Random().NextBytes(testBuffer);
 
-            foreach (var b in data)
-            {
-                segments.Add(new[] { b });
-                segments.Add(Array.Empty<byte>());
-            }
+            await RunClientServer(
+                async clientConnection =>
+                {
+                    await using QuicStream clientStream = clientConnection.OpenUnidirectionalStream();
+
+                    ReadOnlyMemory<byte> sendBuffer = testBuffer;
+                    while (sendBuffer.Length != 0)
+                    {
+                        ReadOnlyMemory<byte> chunk = sendBuffer.Slice(0, Math.Min(sendBuffer.Length, writeSize));
+                        await clientStream.WriteAsync(chunk);
+                        sendBuffer = sendBuffer.Slice(chunk.Length);
+                    }
+
+                    clientStream.Shutdown();
+                    await clientStream.ShutdownWriteCompleted();
+                },
+                async serverConnection =>
+                {
+                    await using QuicStream serverStream = await serverConnection.AcceptStreamAsync();
+
+                    byte[] receiveBuffer = new byte[testBuffer.Length];
+                    int totalBytesRead = 0;
+
+                    while (totalBytesRead != receiveBuffer.Length)
+                    {
+                        int bytesRead = await serverStream.ReadAsync(receiveBuffer.AsMemory(totalBytesRead, Math.Min(receiveBuffer.Length - totalBytesRead, readSize)));
+                        if (bytesRead == 0)
+                        {
+                            break;
+                        }
 
-            return CreateSegments(segments.ToArray());
+                        totalBytesRead += bytesRead;
+                    }
+
+                    Assert.True(receiveBuffer.AsSpan().SequenceEqual(testBuffer));
+                });
         }
 
-        private static ReadOnlySequence<byte> CreateSegments(params byte[][] inputs)
+        public static IEnumerable<object[]> ReadWrite_Random_Success_Data()
         {
-            if (inputs == null || inputs.Length == 0)
-            {
-                throw new InvalidOperationException();
-            }
+            IEnumerable<int> sizes = Enumerable.Range(1, 8).Append(2048).Append(8192);
 
-            int i = 0;
+            return
+                from readSize in sizes
+                from writeSize in sizes
+                select new object[] { readSize, writeSize };
+        }
 
-            BufferSegment last = null;
-            BufferSegment first = null;
+        [Fact]
+        public async Task Read_StreamAborted_Throws()
+        {
+            const int ExpectedErrorCode = 0xfffffff;
 
-            do
+            await Task.Run(async () =>
             {
-                byte[] s = inputs[i];
-                int length = s.Length;
-                int dataOffset = length;
-                var chars = new byte[length * 2];
+                using QuicListener listener = CreateQuicListener();
+                ValueTask<QuicConnection> serverConnectionTask = listener.AcceptConnectionAsync();
 
-                for (int j = 0; j < length; j++)
-                {
-                    chars[dataOffset + j] = s[j];
-                }
+                using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
+                await clientConnection.ConnectAsync();
 
-                // Create a segment that has offset relative to the OwnedMemory and OwnedMemory itself has offset relative to array
-                var memory = new Memory<byte>(chars).Slice(length, length);
+                using QuicConnection serverConnection = await serverConnectionTask;
 
-                if (first == null)
-                {
-                    first = new BufferSegment(memory);
-                    last = first;
-                }
-                else
-                {
-                    last = last.Append(memory);
-                }
-                i++;
-            } while (i < inputs.Length);
+                await using QuicStream clientStream = clientConnection.OpenBidirectionalStream();
+                await clientStream.WriteAsync(new byte[1]);
 
-            return new ReadOnlySequence<byte>(first, 0, last, last.Memory.Length);
+                await using QuicStream serverStream = await serverConnection.AcceptStreamAsync();
+                await serverStream.ReadAsync(new byte[1]);
+
+                clientStream.AbortWrite(ExpectedErrorCode);
+
+                byte[] buffer = new byte[100];
+                QuicStreamAbortedException ex = await Assert.ThrowsAsync<QuicStreamAbortedException>(() => serverStream.ReadAsync(buffer).AsTask());
+                Assert.Equal(ExpectedErrorCode, ex.ErrorCode);
+            }).TimeoutAfter(millisecondsTimeout: 5_000);
         }
 
-        internal class BufferSegment : ReadOnlySequenceSegment<byte>
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/32050")]
+        [Fact]
+        public async Task Read_ConnectionAborted_Throws()
         {
-            public BufferSegment(ReadOnlyMemory<byte> memory)
-            {
-                Memory = memory;
-            }
+            const int ExpectedErrorCode = 1234;
 
-            public BufferSegment Append(ReadOnlyMemory<byte> memory)
+            await Task.Run(async () =>
             {
-                var segment = new BufferSegment(memory)
-                {
-                    RunningIndex = RunningIndex + Memory.Length
-                };
-                Next = segment;
-                return segment;
-            }
+                using QuicListener listener = CreateQuicListener();
+                ValueTask<QuicConnection> serverConnectionTask = listener.AcceptConnectionAsync();
+
+                using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
+                await clientConnection.ConnectAsync();
+
+                using QuicConnection serverConnection = await serverConnectionTask;
+
+                await using QuicStream clientStream = clientConnection.OpenBidirectionalStream();
+                await clientStream.WriteAsync(new byte[1]);
+
+                await using QuicStream serverStream = await serverConnection.AcceptStreamAsync();
+                await serverStream.ReadAsync(new byte[1]);
+
+                await clientConnection.CloseAsync(ExpectedErrorCode);
+
+                byte[] buffer = new byte[100];
+                QuicConnectionAbortedException ex = await Assert.ThrowsAsync<QuicConnectionAbortedException>(() => serverStream.ReadAsync(buffer).AsTask());
+                Assert.Equal(ExpectedErrorCode, ex.ErrorCode);
+            }).TimeoutAfter(millisecondsTimeout: 5_000);
         }
     }
+
+    public sealed class QuicStreamTests_MockProvider : QuicStreamTests<MockProviderFactory> { }
+
+    [ConditionalClass(typeof(QuicTestBase<MsQuicProviderFactory>), nameof(QuicTestBase<MsQuicProviderFactory>.IsSupported))]
+    public sealed class QuicStreamTests_MsQuicProvider : QuicStreamTests<MsQuicProviderFactory> { }
 }
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs
new file mode 100644 (file)
index 0000000..e5fc377
--- /dev/null
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Net.Security;
+using System.Threading.Tasks;
+using System.Net.Quic.Implementations;
+
+namespace System.Net.Quic.Tests
+{
+    public abstract class QuicTestBase<T>
+        where T : IQuicImplProviderFactory, new()
+    {
+        private static readonly IQuicImplProviderFactory s_factory = new T();
+
+        public static QuicImplementationProvider ImplementationProvider { get; } = s_factory.GetProvider();
+        public static bool IsSupported => ImplementationProvider.IsSupported;
+
+        public static SslApplicationProtocol ApplicationProtocol { get; } = new SslApplicationProtocol("quictest");
+
+        public SslServerAuthenticationOptions GetSslServerAuthenticationOptions()
+        {
+            return new SslServerAuthenticationOptions()
+            {
+                ApplicationProtocols = new List<SslApplicationProtocol>() { ApplicationProtocol }
+            };
+        }
+
+        public SslClientAuthenticationOptions GetSslClientAuthenticationOptions()
+        {
+            return new SslClientAuthenticationOptions()
+            {
+                ApplicationProtocols = new List<SslApplicationProtocol>() { ApplicationProtocol }
+            };
+        }
+
+        internal QuicConnection CreateQuicConnection(IPEndPoint endpoint)
+        {
+            return new QuicConnection(ImplementationProvider, endpoint, GetSslClientAuthenticationOptions());
+        }
+
+        internal QuicListener CreateQuicListener()
+        {
+            return CreateQuicListener(new IPEndPoint(IPAddress.Loopback, 0));
+        }
+
+        internal QuicListener CreateQuicListener(IPEndPoint endpoint)
+        {
+            QuicListener listener = new QuicListener(ImplementationProvider, endpoint, GetSslServerAuthenticationOptions());
+            listener.Start();
+            return listener;
+        }
+
+        internal async Task RunClientServer(Func<QuicConnection, Task> clientFunction, Func<QuicConnection, Task> serverFunction, int millisecondsTimeout = 10_000)
+        {
+            using QuicListener listener = CreateQuicListener();
+
+            await new[]
+            {
+                Task.Run(async () =>
+                {
+                    using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+                    await serverFunction(serverConnection);
+                }),
+                Task.Run(async () =>
+                {
+                    using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
+                    await clientConnection.ConnectAsync();
+                    await clientFunction(clientConnection);
+                })
+            }.WhenAllOrAnyFailed(millisecondsTimeout);
+        }
+    }
+
+    public interface IQuicImplProviderFactory
+    {
+        QuicImplementationProvider GetProvider();
+    }
+
+    public sealed class MsQuicProviderFactory : IQuicImplProviderFactory
+    {
+        public QuicImplementationProvider GetProvider() => QuicImplementationProviders.MsQuic;
+    }
+
+    public sealed class MockProviderFactory : IQuicImplProviderFactory
+    {
+        public QuicImplementationProvider GetProvider() => QuicImplementationProviders.Mock;
+    }
+}
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj
new file mode 100644 (file)
index 0000000..ab524c2
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IncludeRemoteExecutor>true</IncludeRemoteExecutor>
+    <TargetFrameworks>$(NetCoreAppCurrent)-Windows_NT;$(NetCoreAppCurrent)-Unix</TargetFrameworks>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="*.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="$(CommonTestPath)System\Threading\Tasks\TaskTimeoutExtensions.cs" Link="TestCommon\System\Threading\Tasks\TaskTimeoutExtensions.cs" />
+  </ItemGroup>
+</Project>
index 49b7804..fdbacc7 100644 (file)
       }
     },
     "System.Net.Quic": {
+      "StableVersions": [],
       "InboxOn": {
-        "netcoreapp3.0": "4.2.1.0"
+        "net5.0": "5.0.0.0"
+      },
+      "AssemblyVersionInPackageVersion": {
+        "5.0.0.0": "6.0.0"
       }
     },
     "System.Net.Requests": {