Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Add temporary QUIC internal implementation abstraction (#42432)
Browse files Browse the repository at this point in the history
  • Loading branch information
geoffkizer authored and stephentoub committed Nov 8, 2019
1 parent 3843042 commit 1ab8b61
Show file tree
Hide file tree
Showing 17 changed files with 732 additions and 537 deletions.
31 changes: 31 additions & 0 deletions src/System.Net.Quic/ref/System.Net.Quic.Temporary.cs
Original file line number Diff line number Diff line change
@@ -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() { }
}
}
8 changes: 4 additions & 4 deletions src/System.Net.Quic/ref/System.Net.Quic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

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;
Expand All @@ -22,9 +22,9 @@ public QuicConnection(IPEndPoint remoteEndPoint, System.Net.Security.SslClientAu
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;
Expand Down
1 change: 1 addition & 0 deletions src/System.Net.Quic/ref/System.Net.Quic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
9 changes: 9 additions & 0 deletions src/System.Net.Quic/src/System.Net.Quic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading

0 comments on commit 1ab8b61

Please sign in to comment.