--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+// ------------------------------------------------------------------------------
+// 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() { }
+ }
+}
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;
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;
<Configurations>netcoreapp-Debug;netcoreapp-Release</Configurations>
</PropertyGroup>
<ItemGroup>
+ <Compile Include="System.Net.Quic.Temporary.cs" />
<Compile Include="System.Net.Quic.cs" />
</ItemGroup>
<ItemGroup>
</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" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers.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);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Security;
+
+namespace System.Net.Quic.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);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Sockets;
+using System.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);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+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;
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.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();
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Security;
+
+namespace System.Net.Quic.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);
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.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();
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.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();
+ }
+}
// 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;
{
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.
/// <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();
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Net.Quic
+{
+ public static class QuicImplementationProviders
+ {
+ public static Implementations.QuicImplementationProvider Mock { get; } = new Implementations.Mock.MockImplementationProvider();
+ }
+}
// 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();
}
}
// 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;
{
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();
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);
}
}
}
[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;
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())
[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);