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