This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add temporary QUIC internal implementation abstraction (#42432)
- Loading branch information
1 parent
3843042
commit 1ab8b61
Showing
17 changed files
with
732 additions
and
537 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
214 changes: 214 additions & 0 deletions
214
src/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockConnection.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
src/System.Net.Quic/src/System/Net/Quic/Implementations/Mock/MockImplementationProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
Oops, something went wrong.