Add temporary QUIC internal implementation abstraction (dotnet/corefx#42432)
authorGeoff Kizer <geoffrek@microsoft.com>
Fri, 8 Nov 2019 16:39:44 +0000 (08:39 -0800)
committerStephen Toub <stoub@microsoft.com>
Fri, 8 Nov 2019 16:39:44 +0000 (11:39 -0500)
Commit migrated from https://github.com/dotnet/corefx/commit/1ab8b616f0b622265c0911484e9794858047a748

17 files changed:
src/libraries/System.Net.Quic/ref/System.Net.Quic.Temporary.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/ref/System.Net.Quic.cs
src/libraries/System.Net.Quic/ref/System.Net.Quic.csproj
src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
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 [new file with mode: 0644]
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/QuicConnectionProvider.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicImplementationProvider.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicListenerProvider.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicStreamProvider.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicImplementationProviders.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs
src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs

diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.Temporary.cs b/src/libraries/System.Net.Quic/ref/System.Net.Quic.Temporary.cs
new file mode 100644 (file)
index 0000000..4cb50b8
--- /dev/null
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+// ------------------------------------------------------------------------------
+// Changes to this file must follow the http://aka.ms/api-review process.
+// ------------------------------------------------------------------------------
+
+using System.Threading;
+
+namespace System.Net.Quic
+{
+    public sealed partial class QuicConnection : System.IDisposable
+    {
+        public QuicConnection(IPEndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null, System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider = null) { }
+    }
+    public sealed partial class QuicListener : IDisposable
+    {
+        public QuicListener(IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions, System.Net.Quic.Implementations.QuicImplementationProvider implementationProvider = null) { }
+    }
+    public static class QuicImplementationProviders
+    {
+        public static System.Net.Quic.Implementations.QuicImplementationProvider Mock { get { throw null; } }
+    }
+}
+namespace System.Net.Quic.Implementations
+{ 
+    public abstract class QuicImplementationProvider
+    {
+        internal QuicImplementationProvider() { }
+    }
+}
index 6fe9208..8b55f7b 100644 (file)
@@ -9,9 +9,9 @@ using System.Threading;
 
 namespace System.Net.Quic
 {
-    public sealed class QuicConnection : System.IDisposable
+    public sealed partial class QuicConnection : System.IDisposable
     {
-        public QuicConnection(IPEndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null, bool mock = false) { }
+        public QuicConnection(IPEndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null) { }
         public System.Threading.Tasks.ValueTask ConnectAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
         public bool Connected => throw null;
         public IPEndPoint LocalEndPoint => throw null;
@@ -22,9 +22,9 @@ namespace System.Net.Quic
         public void Close() => throw null;
         public void Dispose() => throw null;
     }
-    public sealed class QuicListener : IDisposable
+    public sealed partial class QuicListener : IDisposable
     {
-        public QuicListener(IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions, bool mock = false) { }
+        public QuicListener(IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions) { }
         public IPEndPoint ListenEndPoint => throw null;
         public System.Threading.Tasks.ValueTask<QuicConnection> AcceptConnectionAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
         public void Close() => throw null;
index 6ba1da7..4bf7220 100644 (file)
@@ -3,6 +3,7 @@
     <Configurations>netcoreapp-Debug;netcoreapp-Release</Configurations>
   </PropertyGroup>
   <ItemGroup>
+    <Compile Include="System.Net.Quic.Temporary.cs" />
     <Compile Include="System.Net.Quic.cs" />
   </ItemGroup>
   <ItemGroup>
index ba83e1c..676826e 100644 (file)
@@ -6,6 +6,15 @@
   </PropertyGroup>
   <ItemGroup>
     <!-- All configurations -->
+    <Compile Include="System\Net\Quic\Implementations\QuicImplementationProvider.cs" />
+    <Compile Include="System\Net\Quic\Implementations\QuicListenerProvider.cs" />
+    <Compile Include="System\Net\Quic\Implementations\QuicConnectionProvider.cs" />
+    <Compile Include="System\Net\Quic\Implementations\QuicStreamProvider.cs" />
+    <Compile Include="System\Net\Quic\Implementations\Mock\MockImplementationProvider.cs" />
+    <Compile Include="System\Net\Quic\Implementations\Mock\MockListener.cs" />
+    <Compile Include="System\Net\Quic\Implementations\Mock\MockConnection.cs" />
+    <Compile Include="System\Net\Quic\Implementations\Mock\MockStream.cs" />
+    <Compile Include="System\Net\Quic\QuicImplementationProviders.cs" />
     <Compile Include="System\Net\Quic\QuicConnection.cs" />
     <Compile Include="System\Net\Quic\QuicListener.cs" />
     <Compile Include="System\Net\Quic\QuicStream.cs" />
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..f495336
--- /dev/null
@@ -0,0 +1,214 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers.Binary;
+using System.Diagnostics;
+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 = false;
+        private IPEndPoint _remoteEndPoint;
+        private IPEndPoint _localEndPoint;
+        private object _syncObject = new object();
+        private Socket _socket = null;
+        private IPEndPoint _peerListenEndPoint = null;
+        private TcpListener _inboundListener = null;
+        private long _nextOutboundBidirectionalStream;
+        private long _nextOutboundUnidirectionalStream;
+
+        // Constructor for outbound connections
+        internal MockConnection(IPEndPoint 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 IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint.Address, _remoteEndPoint.Port);
+
+        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).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).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 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).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 void Close()
+        {
+            Dispose();
+        }
+
+        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/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockImplementationProvider.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockImplementationProvider.cs
new file mode 100644 (file)
index 0000000..fd674c1
--- /dev/null
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Security;
+
+namespace System.Net.Quic.Implementations.Mock
+{
+    internal sealed class MockImplementationProvider : QuicImplementationProvider
+    {
+        internal override QuicListenerProvider CreateListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
+        {
+            return new MockListener(listenEndPoint, sslServerAuthenticationOptions);
+        }
+
+        internal override QuicConnectionProvider CreateConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint)
+        {
+            return new MockConnection(remoteEndPoint, sslClientAuthenticationOptions, localEndPoint);
+        }
+    }
+}
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..d224111
--- /dev/null
@@ -0,0 +1,114 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Sockets;
+using System.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 = false;
+        private SslServerAuthenticationOptions _sslOptions;
+        private IPEndPoint _listenEndPoint;
+        private TcpListener _tcpListener = null;
+
+        internal MockListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
+        {
+            if (listenEndPoint == null)
+            {
+                throw new ArgumentNullException(nameof(listenEndPoint));
+            }
+
+            _sslOptions = sslServerAuthenticationOptions;
+            _listenEndPoint = listenEndPoint;
+
+            _tcpListener = new TcpListener(listenEndPoint);
+            _tcpListener.Start();
+
+            if (listenEndPoint.Port == 0)
+            {
+                // Get auto-assigned port
+                _listenEndPoint = (IPEndPoint)_tcpListener.LocalEndpoint;
+            }
+        }
+
+        // IPEndPoint is mutable, so we must create a new instance every time this is retrieved.
+        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).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 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/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..30869a1
--- /dev/null
@@ -0,0 +1,165 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+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 = false;
+        private readonly long _streamId;
+        private bool _canRead;
+        private bool _canWrite;
+
+        private MockConnection _connection;
+
+        private Socket _socket = null;
+
+        // 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 async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, 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);
+        }
+
+        internal override void Flush()
+        {
+            CheckDisposed();
+        }
+
+        internal override Task FlushAsync(CancellationToken cancellationToken)
+        {
+            CheckDisposed();
+
+            return Task.CompletedTask;
+        }
+
+        internal override void ShutdownRead()
+        {
+            throw new NotImplementedException();
+        }
+
+        internal override void ShutdownWrite()
+        {
+            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;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicConnectionProvider.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicConnectionProvider.cs
new file mode 100644 (file)
index 0000000..7c0af3d
--- /dev/null
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations
+{
+    internal abstract class QuicConnectionProvider : IDisposable
+    {
+        internal abstract bool Connected { get; }
+
+        internal abstract IPEndPoint LocalEndPoint { get; }
+
+        internal abstract IPEndPoint RemoteEndPoint { get; }
+
+        internal abstract ValueTask ConnectAsync(CancellationToken cancellationToken = default);
+
+        internal abstract QuicStreamProvider OpenUnidirectionalStream();
+
+        internal abstract QuicStreamProvider OpenBidirectionalStream();
+
+        internal abstract ValueTask<QuicStreamProvider> AcceptStreamAsync(CancellationToken cancellationToken = default);
+
+        internal abstract void Close();
+
+        public abstract void Dispose();
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicImplementationProvider.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicImplementationProvider.cs
new file mode 100644 (file)
index 0000000..d250a2c
--- /dev/null
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Security;
+
+namespace System.Net.Quic.Implementations
+{
+    public abstract class QuicImplementationProvider
+    {
+        internal QuicImplementationProvider() { }
+
+        internal abstract QuicListenerProvider CreateListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions);
+
+        internal abstract QuicConnectionProvider CreateConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint);
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicListenerProvider.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicListenerProvider.cs
new file mode 100644 (file)
index 0000000..7d6819d
--- /dev/null
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations
+{
+    internal abstract class QuicListenerProvider : IDisposable
+    {
+        internal abstract IPEndPoint ListenEndPoint { get; }
+
+        internal abstract ValueTask<QuicConnectionProvider> AcceptConnectionAsync(CancellationToken cancellationToken = default);
+
+        internal abstract void Close();
+
+        public abstract void Dispose();
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicStreamProvider.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/QuicStreamProvider.cs
new file mode 100644 (file)
index 0000000..32956e2
--- /dev/null
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations
+{
+    internal abstract class QuicStreamProvider : IDisposable
+    {
+        internal abstract long StreamId { get; }
+
+        internal abstract bool CanRead { get; }
+
+        internal abstract int Read(Span<byte> buffer);
+
+        internal abstract ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);
+
+        internal abstract void ShutdownRead();
+
+        internal abstract bool CanWrite { get; }
+
+        internal abstract void Write(ReadOnlySpan<byte> buffer);
+
+        internal abstract ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default);
+
+        internal abstract void ShutdownWrite();
+
+        internal abstract void Flush();
+
+        internal abstract Task FlushAsync(CancellationToken cancellationToken);
+
+        public abstract void Dispose();
+    }
+}
index 0180b79..0832867 100644 (file)
@@ -2,10 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Buffers.Binary;
-using System.Diagnostics;
+using System.Net.Quic.Implementations;
 using System.Net.Security;
-using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -13,19 +11,7 @@ namespace System.Net.Quic
 {
     public sealed class QuicConnection : IDisposable
     {
-        private readonly bool _isClient;
-        private bool _disposed = false;
-        private IPEndPoint _remoteEndPoint;
-        private IPEndPoint _localEndPoint;
-        private object _syncObject = new object();
-
-        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
-        private readonly bool _mock = false;
-        private Socket _socket = null;
-        private IPEndPoint _peerListenEndPoint = null;
-        private TcpListener _inboundListener = null;
-        private long _nextOutboundBidirectionalStream;
-        private long _nextOutboundUnidirectionalStream;
+        private readonly QuicConnectionProvider _provider;
 
         /// <summary>
         /// Create an outbound QUIC connection.
@@ -33,260 +19,61 @@ namespace System.Net.Quic
         /// <param name="remoteEndPoint">The remote endpoint to connect to.</param>
         /// <param name="sslClientAuthenticationOptions">TLS options</param>
         /// <param name="localEndPoint">The local endpoint to connect from.</param>
-        /// <param name="mock">Use mock QUIC implementation.</param>
-        // !!! TEMPORARY FOR QUIC MOCK SUPPORT: Remove "mock" parameter before shipping
-        public QuicConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null, bool mock = false)
+        public QuicConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null)
+            : this(remoteEndPoint, sslClientAuthenticationOptions, localEndPoint, implementationProvider: null)
         {
-            // TODO: TLS handling
-
-            _mock = mock;
-            _remoteEndPoint = remoteEndPoint;
-            _localEndPoint = localEndPoint;
+        }
 
-            _isClient = true;
-            _nextOutboundBidirectionalStream = 0;
-            _nextOutboundUnidirectionalStream = 2;
+        // !!! TEMPORARY: Remove "implementationProvider" before shipping
+        public QuicConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null, QuicImplementationProvider implementationProvider = null)
+        {
+            _provider = implementationProvider.CreateConnection(remoteEndPoint, sslClientAuthenticationOptions, localEndPoint);
         }
 
-        // Constructor for accepted inbound QuicConnections
-        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
-        internal QuicConnection(Socket socket, IPEndPoint peerListenEndPoint, TcpListener inboundListener)
+        internal QuicConnection(QuicConnectionProvider provider)
         {
-            _mock = true;
-            _isClient = false;
-            _nextOutboundBidirectionalStream = 1;
-            _nextOutboundUnidirectionalStream = 3;
-            _socket = socket;
-            _peerListenEndPoint = peerListenEndPoint;
-            _inboundListener = inboundListener;
-            _localEndPoint = (IPEndPoint)socket.LocalEndPoint;
-            _remoteEndPoint = (IPEndPoint)socket.RemoteEndPoint;
+            _provider = provider;
         }
 
         /// <summary>
         /// Indicates whether the QuicConnection is connected.
         /// </summary>
-        public bool Connected
-        {
-            get
-            {
-                CheckDisposed();
-
-                if (_mock)
-                {
-                    return _socket != null;
-                }
-                else
-                {
-                    throw new NotImplementedException();
-                }
-            }
-        }
+        public bool Connected => _provider.Connected;
 
-        public IPEndPoint LocalEndPoint => new IPEndPoint(_localEndPoint.Address, _localEndPoint.Port);
+        public IPEndPoint LocalEndPoint => _provider.LocalEndPoint;
 
-        public IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint.Address, _remoteEndPoint.Port);
+        public IPEndPoint RemoteEndPoint => _provider.RemoteEndPoint;
 
         /// <summary>
         /// Connect to the remote endpoint.
         /// </summary>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            if (_mock)
-            {
-                if (Connected)
-                {
-                    // TODO: Exception text
-                    throw new InvalidOperationException("Already connected");
-                }
-
-                Socket socket = new Socket(_remoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
-                await socket.ConnectAsync(_remoteEndPoint).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).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;
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
+        public ValueTask ConnectAsync(CancellationToken cancellationToken = default) => _provider.ConnectAsync(cancellationToken);
 
         /// <summary>
         /// Create an outbound unidirectional stream.
         /// </summary>
         /// <returns></returns>
-        public QuicStream OpenUnidirectionalStream()
-        {
-            if (_mock)
-            {
-                long streamId;
-                lock (_syncObject)
-                {
-                    streamId = _nextOutboundUnidirectionalStream;
-                    _nextOutboundUnidirectionalStream += 4;
-                }
-
-                return new QuicStream(this, streamId, bidirectional: false);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
+        public QuicStream OpenUnidirectionalStream() => new QuicStream(_provider.OpenUnidirectionalStream());
 
         /// <summary>
         /// Create an outbound bidirectional stream.
         /// </summary>
         /// <returns></returns>
-        public QuicStream OpenBidirectionalStream()
-        {
-            if (_mock)
-            {
-                long streamId;
-                lock (_syncObject)
-                {
-                    streamId = _nextOutboundBidirectionalStream;
-                    _nextOutboundBidirectionalStream += 4;
-                }
-
-                return new QuicStream(this, streamId, bidirectional: true);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
-
-        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
-        internal async Task<Socket> CreateOutboundMockStreamAsync(long streamId)
-        {
-            Debug.Assert(_mock);
-            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;
-        }
+        public QuicStream OpenBidirectionalStream() => new QuicStream(_provider.OpenBidirectionalStream());
 
         /// <summary>
         /// Accept an incoming stream.
         /// </summary>
         /// <returns></returns>
-        public async ValueTask<QuicStream> AcceptStreamAsync(CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            if (_mock)
-            {
-                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).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 QuicStream(socket, streamId, bidirectional: bidirectional);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
+        public async ValueTask<QuicStream> AcceptStreamAsync(CancellationToken cancellationToken = default) => new QuicStream(await _provider.AcceptStreamAsync(cancellationToken).ConfigureAwait(false));
 
         /// <summary>
         /// Close the connection and terminate any active streams.
         /// </summary>
-        public void Close()
-        {
-            Dispose();
-        }
+        public void Close() => _provider.Close();
 
-        private void CheckDisposed()
-        {
-            if (_disposed)
-            {
-                throw new ObjectDisposedException(nameof(QuicConnection));
-            }
-        }
-
-        private void Dispose(bool disposing)
-        {
-            if (!_disposed)
-            {
-                if (disposing)
-                {
-                    if (_mock)
-                    {
-                        _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;
-            }
-        }
-
-        ~QuicConnection()
-        {
-            Dispose(false);
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
+        public void Dispose() => _provider.Dispose();
     }
 }
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicImplementationProviders.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicImplementationProviders.cs
new file mode 100644 (file)
index 0000000..7cc5ef2
--- /dev/null
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.Quic
+{
+    public static class QuicImplementationProviders
+    {
+        public static Implementations.QuicImplementationProvider Mock { get; } = new Implementations.Mock.MockImplementationProvider();
+    }
+}
index fc36baf..7a3348b 100644 (file)
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Net.Sockets;
+using System.Net.Quic.Implementations;
 using System.Net.Security;
-using System.Threading.Tasks;
 using System.Threading;
-using System.Buffers.Binary;
+using System.Threading.Tasks;
 
 namespace System.Net.Quic
 {
     public sealed class QuicListener : IDisposable
     {
-        private bool _disposed = false;
-        private SslServerAuthenticationOptions _sslOptions;
-        private IPEndPoint _listenEndPoint;
-
-        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
-        private bool _mock = false;
-        private TcpListener _tcpListener = null;
+        private readonly QuicListenerProvider _provider;
 
         /// <summary>
         /// Create a QUIC listener on the specified local endpoint and start listening.
         /// </summary>
         /// <param name="listenEndPoint">The local endpoint to listen on.</param>
         /// <param name="sslServerAuthenticationOptions">TLS options for the listener.</param>
-        /// <param name="mock">Use mock QUIC implementation.</param>
-        // !!! TEMPORARY FOR QUIC MOCK SUPPORT: Remove "mock" parameter before shipping
-        public QuicListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions, bool mock = false)
+        public QuicListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
+            : this(listenEndPoint, sslServerAuthenticationOptions, implementationProvider: null)
         {
-            if (sslServerAuthenticationOptions == null && !mock)
-            {
-                throw new ArgumentNullException(nameof(sslServerAuthenticationOptions));
-            }
-
-            if (listenEndPoint == null)
-            {
-                throw new ArgumentNullException(nameof(listenEndPoint));
-            }
-
-            _sslOptions = sslServerAuthenticationOptions;
-            _listenEndPoint = listenEndPoint;
-
-            _mock = mock;
-            if (mock)
-            {
-                _tcpListener = new TcpListener(listenEndPoint);
-                _tcpListener.Start();
+        }
 
-                if (listenEndPoint.Port == 0)
-                {
-                    // Get auto-assigned port
-                    _listenEndPoint = (IPEndPoint)_tcpListener.LocalEndpoint;
-                }
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
+        // !!! TEMPORARY: Remove "implementationProvider" before shipping
+        public QuicListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions, QuicImplementationProvider implementationProvider = null)
+        {
+            _provider = implementationProvider.CreateListener(listenEndPoint, sslServerAuthenticationOptions);
         }
 
-        // IPEndPoint is mutable, so we must create a new instance every time this is retrieved.
-        public IPEndPoint ListenEndPoint => new IPEndPoint(_listenEndPoint.Address, _listenEndPoint.Port);
+        public IPEndPoint ListenEndPoint => _provider.ListenEndPoint;
 
         /// <summary>
         /// Accept a connection.
         /// </summary>
         /// <returns></returns>
-        public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            if (_mock)
-            {
-                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).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 QuicConnection(socket, peerListenEndPoint, inboundListener);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
+        public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken cancellationToken = default) =>
+            new QuicConnection(await _provider.AcceptConnectionAsync(cancellationToken).ConfigureAwait(false));
 
         /// <summary>
         /// Stop listening and close the listener.
         /// </summary>
-        public 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.
+        public void Close() => _provider.Close();
 
-                _disposed = true;
-            }
-        }
-
-        ~QuicListener()
-        {
-            Dispose(false);
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
+        public void Dispose() => _provider.Dispose();
     }
 }
index 22df00c..ac1643f 100644 (file)
@@ -2,10 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System.Diagnostics;
 using System.IO;
-using System.Net.Security;
-using System.Net.Sockets;
+using System.Net.Quic.Implementations;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -13,37 +11,16 @@ namespace System.Net.Quic
 {
     public sealed class QuicStream : Stream
     {
-        private bool _disposed = false;
-        private readonly long _streamId;
-        private bool _canRead;
-        private bool _canWrite;
-        private QuicConnection _connection;
+        private readonly QuicStreamProvider _provider;
 
-        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
-        private readonly bool _mock = false;
-        private Socket _socket = null;
-
-        // Constructor for outbound streams
-        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
-        internal QuicStream(QuicConnection connection, long streamId, bool bidirectional)
+        internal QuicStream(QuicStreamProvider provider)
         {
-            _mock = true;
-            _connection = connection;
-            _streamId = streamId;
-            _canRead = bidirectional;
-            _canWrite = true;
+            _provider = provider;
         }
 
-        // Constructor for inbound streams
-        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
-        internal QuicStream(Socket socket, long streamId, bool bidirectional)
-        {
-            _mock = true;
-            _socket = socket;
-            _streamId = streamId;
-            _canRead = true;
-            _canWrite = bidirectional;
-        }
+        //
+        // Boilerplate implementation stuff
+        //
 
         public override bool CanSeek => false;
         public override long Length => throw new NotSupportedException();
@@ -105,172 +82,37 @@ namespace System.Net.Quic
             return WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
         }
 
-        private async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
-        {
-            Debug.Assert(_mock);
-            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;
-        }
-
         /// <summary>
         /// QUIC stream ID.
         /// </summary>
-        public long StreamId
-        {
-            get
-            {
-                CheckDisposed();
-                return _streamId;
-            }
-        }
+        public long StreamId => _provider.StreamId;
 
-        public override bool CanRead => _canRead;
+        public override bool CanRead => _provider.CanRead;
 
-        public override int Read(Span<byte> buffer)
-        {
-            CheckDisposed();
-
-            if (!_canRead)
-            {
-                throw new NotSupportedException();
-            }
-
-            if (_mock)
-            {
-                return _socket.Receive(buffer);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
+        public override int Read(Span<byte> buffer) => _provider.Read(buffer);
 
-        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
+        public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) => _provider.ReadAsync(buffer, cancellationToken);
 
-            if (!_canRead)
-            {
-                throw new NotSupportedException();
-            }
+        public override bool CanWrite => _provider.CanWrite;
 
-            if (_mock)
-            {
-                if (_socket == null)
-                {
-                    await ConnectAsync(cancellationToken).ConfigureAwait(false);
-                }
+        public override void Write(ReadOnlySpan<byte> buffer) => _provider.Write(buffer);
 
-                return await _socket.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
+        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffer, cancellationToken);
 
-        public override bool CanWrite => _canWrite;
+        public override void Flush() => _provider.Flush();
 
-        public override void Write(ReadOnlySpan<byte> buffer)
-        {
-            CheckDisposed();
+        public override Task FlushAsync(CancellationToken cancellationToken) => _provider.FlushAsync(cancellationToken);
 
-            if (!_canWrite)
-            {
-                throw new NotSupportedException();
-            }
+        public void ShutdownRead() => _provider.ShutdownRead();
 
-            if (_mock)
-            {
-                _socket.Send(buffer);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
-
-        public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
-        {
-            CheckDisposed();
-
-            if (!_canWrite)
-            {
-                throw new NotSupportedException();
-            }
-
-            if (_mock)
-            {
-                if (_socket == null)
-                {
-                    await ConnectAsync(cancellationToken).ConfigureAwait(false);
-                }
-
-                await _socket.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
-
-        public override void Flush()
-        {
-            CheckDisposed();
-        }
-
-        public override Task FlushAsync(CancellationToken cancellationToken)
-        {
-            CheckDisposed();
-
-            return Task.CompletedTask;
-        }
-
-        public void ShutdownRead()
-        {
-            throw new NotImplementedException();
-        }
-
-        public void ShutdownWrite()
-        {
-            CheckDisposed();
-
-            if (_mock)
-            {
-                _socket.Shutdown(SocketShutdown.Send);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
-
-        private void CheckDisposed()
-        {
-            if (_disposed)
-            {
-                throw new ObjectDisposedException(nameof(QuicStream));
-            }
-        }
+        public void ShutdownWrite() => _provider.ShutdownWrite();
 
         protected override void Dispose(bool disposing)
         {
-            if (!_disposed)
+            if (disposing)
             {
-                _disposed = true;
-
-                if (_mock)
-                {
-                    _socket?.Dispose();
-                    _socket = null;
-                }
+                _provider.Dispose();
             }
-
-            base.Dispose(disposing);
         }
     }
 }
index a13a353..c049fa8 100644 (file)
@@ -18,7 +18,7 @@ namespace System.Net.Quic.Tests
         [Fact]
         public async Task BasicTest()
         {
-            using (QuicListener listener = new QuicListener(new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null, mock: true))
+            using (QuicListener listener = new QuicListener(new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null, implementationProvider: QuicImplementationProviders.Mock))
             {
                 IPEndPoint listenEndPoint = listener.ListenEndPoint;
 
@@ -26,7 +26,7 @@ namespace System.Net.Quic.Tests
                     Task.Run(async () =>
                         {
                             // Client code
-                            using (QuicConnection connection = new QuicConnection(listenEndPoint, sslClientAuthenticationOptions: null, mock: true))
+                            using (QuicConnection connection = new QuicConnection(listenEndPoint, sslClientAuthenticationOptions: null, implementationProvider: QuicImplementationProviders.Mock))
                             {
                                 await connection.ConnectAsync();
                                 using (QuicStream stream = connection.OpenBidirectionalStream())
@@ -55,11 +55,11 @@ namespace System.Net.Quic.Tests
         [Fact]
         public async Task TestStreams()
         {
-            using (QuicListener listener = new QuicListener(new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null, mock: true))
+            using (QuicListener listener = new QuicListener(new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null, implementationProvider: QuicImplementationProviders.Mock))
             {
                 IPEndPoint listenEndPoint = listener.ListenEndPoint;
 
-                using (QuicConnection clientConnection = new QuicConnection(listenEndPoint, sslClientAuthenticationOptions: null, mock: true))
+                using (QuicConnection clientConnection = new QuicConnection(listenEndPoint, sslClientAuthenticationOptions: null, implementationProvider: QuicImplementationProviders.Mock))
                 {
                     Assert.False(clientConnection.Connected);
                     Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);