private static async Task ServerConnectionTask(QuicConnection connection, CancellationToken cancellationToken) { try { while (true) { var stream = await connection.AcceptStreamAsync(cancellationToken).ConfigureAwait(false); if (!stream.CanRead || !stream.CanWrite) { await connection.CloseAsync(1, cancellationToken).ConfigureAwait(false); return; } _ = Helpers.Dispatch(() => ServerQuicStreamTask(connection, stream)); } } catch (QuicConnectionAbortedException e) when(e.ErrorCode == 0) { // ignore successful closing } catch (OperationCanceledException) { } finally { await connection.CloseAsync(1).ConfigureAwait(false); connection.Dispose(); } }
public override async ValueTask DisposeAsync() { try { _closeTask ??= _connection.CloseAsync(errorCode: 0).AsTask(); await _closeTask; } catch (Exception ex) { _log.LogWarning(ex, "Failed to gracefully shutdown connection."); } _connection.Dispose(); }
private async Task TestConnection(CipherSuitesPolicy serverPolicy, CipherSuitesPolicy clientPolicy) { var listenerOptions = new QuicListenerOptions() { ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), ApplicationProtocols = new List <SslApplicationProtocol>() { ApplicationProtocol }, ConnectionOptionsCallback = (_, _, _) => { var serverOptions = CreateQuicServerOptions(); serverOptions.ServerAuthenticationOptions.CipherSuitesPolicy = serverPolicy; return(ValueTask.FromResult(serverOptions)); } }; await using QuicListener listener = await CreateQuicListener(listenerOptions); var clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); clientOptions.ClientAuthenticationOptions.CipherSuitesPolicy = clientPolicy; await using QuicConnection clientConnection = await CreateQuicConnection(clientOptions); await clientConnection.CloseAsync(0); }
public async Task AcceptAsync_ClientClosesConnection_ServerNotified() { // Arrange var connectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory); // Act var acceptTask = connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); using var clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options); await clientConnection.ConnectAsync().DefaultTimeout(); await using var serverConnection = await acceptTask.DefaultTimeout(); serverConnection.ConnectionClosed.Register(() => connectionClosedTcs.SetResult()); var acceptStreamTask = serverConnection.AcceptAsync(); await clientConnection.CloseAsync(256); // Assert var ex = await Assert.ThrowsAsync <ConnectionResetException>(() => acceptStreamTask.AsTask()).DefaultTimeout(); var innerEx = Assert.IsType <QuicConnectionAbortedException>(ex.InnerException); Assert.Equal(256, innerEx.ErrorCode); await connectionClosedTcs.Task.DefaultTimeout(); }
public async Task TestStreams() { using QuicListener listener = CreateQuicListener(); 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); await CreateAndTestBidirectionalStream(clientConnection, serverConnection); await CreateAndTestBidirectionalStream(serverConnection, clientConnection); await CreateAndTestUnidirectionalStream(serverConnection, clientConnection); await CreateAndTestUnidirectionalStream(clientConnection, serverConnection); await clientConnection.CloseAsync(errorCode : 0); }
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); }
public async Task AcceptAsync_ClientClosesConnection_ExceptionThrown() { // Arrange await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); using var quicConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options); await quicConnection.ConnectAsync().DefaultTimeout(); var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout(); // Act var acceptTask = serverConnection.AcceptAsync().AsTask(); await quicConnection.CloseAsync((long)Http3ErrorCode.NoError).DefaultTimeout(); // Assert var ex = await Assert.ThrowsAsync <ConnectionResetException>(() => acceptTask).DefaultTimeout(); var innerEx = Assert.IsType <QuicConnectionAbortedException>(ex.InnerException); Assert.Equal((long)Http3ErrorCode.NoError, innerEx.ErrorCode); Assert.Equal((long)Http3ErrorCode.NoError, serverConnection.Features.Get <IProtocolErrorCodeFeature>().Error); }
/// <summary> /// Called when shutting down, this checks for when shutdown is complete (no more active requests) and does actual disposal. /// </summary> /// <remarks>Requires <see cref="SyncObj"/> to be locked.</remarks> private void CheckForShutdown() { Debug.Assert(Monitor.IsEntered(SyncObj)); Debug.Assert(ShuttingDown); if (_activeRequests.Count != 0) { return; } if (_clientControl != null) { _clientControl.Dispose(); _clientControl = null; } if (_connection != null) { // Close the QuicConnection in the background. if (_connectionClosedTask == null) { _connectionClosedTask = _connection.CloseAsync((long)Http3ErrorCode.NoError).AsTask(); } QuicConnection connection = _connection; _connection = null; _ = _connectionClosedTask.ContinueWith(closeTask => { if (closeTask.IsFaulted && NetEventSource.IsEnabled) { Trace($"{nameof(QuicConnection)} failed to close: {closeTask.Exception.InnerException}"); } try { connection.Dispose(); } catch (Exception ex) { Trace($"{nameof(QuicConnection)} failed to dispose: {ex}"); } }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } }
public override async ValueTask DisposeAsync() { try { lock (_shutdownLock) { _closeTask ??= _connection.CloseAsync(errorCode: _context.Options.DefaultCloseErrorCode).AsTask(); } await _closeTask; } catch (Exception ex) { _log.LogWarning(ex, "Failed to gracefully shutdown connection."); } await _connection.DisposeAsync(); }
public static async Task <int> RunClient(IPEndPoint serverEp, CancellationToken token) { using var client = new QuicConnection(new QuicClientConnectionOptions { RemoteEndPoint = serverEp, ClientAuthenticationOptions = new SslClientAuthenticationOptions { ApplicationProtocols = new List <SslApplicationProtocol> { new SslApplicationProtocol("echo") // same as used for server } } }); await client.ConnectAsync(token); try { await using QuicStream stream = client.OpenBidirectionalStream(); // spawn a reader task to not let server be flow-control blocked _ = Task.Run(async() => { byte[] arr = new byte[4 * 1024]; int read; while ((read = await stream.ReadAsync(arr, token)) > 0) { string s = Encoding.ASCII.GetString(arr, 0, read); Console.WriteLine($"Received: {s}"); } }); string line; while ((line = Console.ReadLine()) != null) { // convert into ASCII byte array before sending byte[] bytes = Encoding.ASCII.GetBytes(line); await stream.WriteAsync(bytes, token); // flush the stream to send the data immediately await stream.FlushAsync(token); } // once all stdin is written, close the stream stream.Shutdown(); // wait until the server receives all data await stream.ShutdownWriteCompleted(token); } finally { await client.CloseAsync(0, token); } return(0); }
public override async ValueTask DisposeAsync() { try { if (_closeTask != default) { _closeTask = _connection.CloseAsync(errorCode: 0); await _closeTask; } else { await _closeTask; } } catch (Exception ex) { _log.LogWarning(ex, "Failed to gracefully shutdown connection."); } _connection.Dispose(); }
private async Task TestConnection(CipherSuitesPolicy serverPolicy, CipherSuitesPolicy clientPolicy) { var listenerOptions = CreateQuicListenerOptions(); listenerOptions.ServerAuthenticationOptions.CipherSuitesPolicy = serverPolicy; using QuicListener listener = await CreateQuicListener(listenerOptions); var clientOptions = CreateQuicClientOptions(); clientOptions.ClientAuthenticationOptions.CipherSuitesPolicy = clientPolicy; clientOptions.RemoteEndPoint = listener.ListenEndPoint; using QuicConnection clientConnection = await CreateQuicConnection(clientOptions); await clientConnection.ConnectAsync(); await clientConnection.CloseAsync(0); }
public async Task ConnectWithClientCertificate() { bool clientCertificateOK = false; var serverOptions = new QuicListenerOptions(); serverOptions.ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0); serverOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions(); serverOptions.ServerAuthenticationOptions.ClientCertificateRequired = true; serverOptions.ServerAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => { _output.WriteLine("client certificate {0}", cert); Assert.NotNull(cert); Assert.Equal(ClientCertificate.Thumbprint, ((X509Certificate2)cert).Thumbprint); clientCertificateOK = true; return(true); }; using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, serverOptions); QuicClientConnectionOptions clientOptions = new QuicClientConnectionOptions() { RemoteEndPoint = listener.ListenEndPoint, ClientAuthenticationOptions = GetSslClientAuthenticationOptions(), }; clientOptions.ClientAuthenticationOptions.ClientCertificates = new X509CertificateCollection() { ClientCertificate }; using QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, clientOptions); Task <QuicConnection> serverTask = listener.AcceptConnectionAsync().AsTask(); await TaskTimeoutExtensions.WhenAllOrAnyFailed(clientConnection.ConnectAsync().AsTask(), serverTask, PassingTestTimeoutMilliseconds); using QuicConnection serverConnection = serverTask.Result; // Verify functionality of the connections. await PingPong(clientConnection, serverConnection); // check we completed the client certificate verification. Assert.True(clientCertificateOK); Assert.Equal(ClientCertificate, serverConnection.RemoteCertificate); await serverConnection.CloseAsync(0); }
public async Task BasicTest() { using QuicListener listener = CreateQuicListener(); for (int i = 0; i < 100; i++) { Task listenTask = Task.Run(async() => { using QuicConnection connection = await listener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); byte[] buffer = new byte[s_data.Length]; int bytesRead = await stream.ReadAsync(buffer); Assert.Equal(s_data.Length, bytesRead); Assert.True(s_data.Span.SequenceEqual(buffer)); await stream.WriteAsync(s_data, endStream: true); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(listener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); await stream.WriteAsync(s_data, endStream: true); byte[] memory = new byte[12]; int bytesRead = await stream.ReadAsync(memory); Assert.Equal(s_data.Length, bytesRead); // TODO this failed once... Assert.True(s_data.Span.SequenceEqual(memory)); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 10000); } }
public async Task AbortiveConnectionFromClient() { using QuicConnection clientConnection = CreateQuicConnection(DefaultListener.ListenEndPoint); ValueTask clientTask = clientConnection.ConnectAsync(); using QuicConnection serverConnection = await DefaultListener.AcceptConnectionAsync(); await clientTask; // Close connection on client, verifying server connection is aborted. await clientConnection.CloseAsync(); QuicStream stream = await serverConnection.AcceptStreamAsync(); // Providers are alaways wrapped right now by a QuicStream. All fields are null here. // TODO make sure this returns null. Assert.Throws <NullReferenceException>(() => stream.CanRead); }
public async Task TestStreams() { using (QuicListener listener = new QuicListener( QuicImplementationProviders.MsQuic, new IPEndPoint(IPAddress.Loopback, 0), GetSslServerAuthenticationOptions())) { listener.Start(); IPEndPoint listenEndPoint = listener.ListenEndPoint; using (QuicConnection clientConnection = new QuicConnection( QuicImplementationProviders.MsQuic, listenEndPoint, sslClientAuthenticationOptions: new SslClientAuthenticationOptions { ApplicationProtocols = new List <SslApplicationProtocol>() { new SslApplicationProtocol("quictest") } })) { 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); await CreateAndTestBidirectionalStream(clientConnection, serverConnection); await CreateAndTestBidirectionalStream(serverConnection, clientConnection); await CreateAndTestUnidirectionalStream(serverConnection, clientConnection); await CreateAndTestUnidirectionalStream(clientConnection, serverConnection); await clientConnection.CloseAsync(); } } }
/// <summary> /// Aborts the connection with an error. /// </summary> /// <remarks> /// Used for e.g. I/O or connection-level frame parsing errors. /// </remarks> internal Exception Abort(Exception abortException) { // Only observe the first exception we get. Exception firstException = Interlocked.CompareExchange(ref _abortException, abortException, null); if (firstException != null) { if (NetEventSource.IsEnabled && !ReferenceEquals(firstException, abortException)) { // Lost the race to set the field to another exception, so just trace this one. Trace($"{nameof(abortException)}=={abortException}"); } return(firstException); } // Stop sending requests to this connection. _pool.InvalidateHttp3Connection(this); Http3ErrorCode connectionResetErrorCode = (abortException as Http3ProtocolException)?.ErrorCode ?? Http3ErrorCode.InternalError; lock (SyncObj) { // Set _lastProcessedStreamId != -1 to make ShuttingDown = true. // It's possible GOAWAY is already being processed, in which case this would already be != -1. if (_lastProcessedStreamId == -1) { _lastProcessedStreamId = long.MaxValue; } // Abort the connection. This will cause all of our streams to abort on their next I/O. _connection?.CloseAsync((long)connectionResetErrorCode).GetAwaiter().GetResult(); // TODO: async... CancelWaiters(); CheckForShutdown(); } return(abortException); }
public async Task TestStreams() { using QuicListener listener = new QuicListener( QuicImplementationProviders.MsQuic, new IPEndPoint(IPAddress.Loopback, 0), GetSslServerAuthenticationOptions()); 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); 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); await CreateAndTestBidirectionalStream(clientConnection, serverConnection); await CreateAndTestBidirectionalStream(serverConnection, clientConnection); await CreateAndTestUnidirectionalStream(serverConnection, clientConnection); await CreateAndTestUnidirectionalStream(clientConnection, serverConnection); await clientConnection.CloseAsync(errorCode : 0); }
/// <summary> /// Called when shutting down, this checks for when shutdown is complete (no more active requests) and does actual disposal. /// </summary> /// <remarks>Requires <see cref="SyncObj"/> to be locked.</remarks> private void CheckForShutdown() { Debug.Assert(Monitor.IsEntered(SyncObj)); Debug.Assert(ShuttingDown); if (_activeRequests.Count != 0) { return; } if (_clientControl != null) { _clientControl.Dispose(); _clientControl = null; } if (_connection != null) { _connection.CloseAsync((long)Http3ErrorCode.NoError).GetAwaiter().GetResult(); // TODO: async... _connection.Dispose(); _connection = null; } }
private static async Task ServerQuicStreamTask(QuicConnection connection, QuicStream stream) { try { if (!await ServerStreamTask(stream).ConfigureAwait(false)) { await connection.CloseAsync(1).ConfigureAwait(false); } } catch (QuicStreamAbortedException e) when(e.ErrorCode == 0) { } catch (QuicConnectionAbortedException) { } catch (OperationCanceledException) { } finally { await stream.DisposeAsync().ConfigureAwait(false); } }
public static async Task HandleServerConnection(QuicConnection connection, CancellationToken token) { try { QuicStream stream = await connection.AcceptStreamAsync(token); byte[] buffer = new byte[4 * 1024]; int read; while ((read = await stream.ReadAsync(buffer, token)) > 0) { // echo the read data back await stream.WriteAsync(buffer, 0, read, token); await stream.FlushAsync(token); } } finally { // gracefully close the connection with 0 error code await connection.CloseAsync(0); } }
public async Task MultipleReadsAndWrites() { for (int j = 0; j < 100; j++) { Task listenTask = Task.Run(async() => { // Connection isn't being accepted, interesting. using QuicConnection connection = await DefaultListener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); byte[] buffer = new byte[s_data.Length]; while (true) { int bytesRead = await stream.ReadAsync(buffer); if (bytesRead == 0) { break; } Assert.Equal(s_data.Length, bytesRead); Assert.True(s_data.Span.SequenceEqual(buffer)); } for (int i = 0; i < 5; i++) { await stream.WriteAsync(s_data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(); }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(DefaultListener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); for (int i = 0; i < 5; i++) { await stream.WriteAsync(s_data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); byte[] memory = new byte[12]; while (true) { int res = await stream.ReadAsync(memory); if (res == 0) { break; } Assert.True(s_data.Span.SequenceEqual(memory)); } await stream.ShutdownWriteCompleted(); await connection.CloseAsync(); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 1000000); } }
public async Task MultipleStreamsOnSingleConnection() { Task listenTask = Task.Run(async() => { { using QuicConnection connection = await DefaultListener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); await using QuicStream stream2 = await connection.AcceptStreamAsync(); byte[] buffer = new byte[s_data.Length]; while (true) { int bytesRead = await stream.ReadAsync(buffer); if (bytesRead == 0) { break; } Assert.Equal(s_data.Length, bytesRead); Assert.True(s_data.Span.SequenceEqual(buffer)); } while (true) { int bytesRead = await stream2.ReadAsync(buffer); if (bytesRead == 0) { break; } Assert.True(s_data.Span.SequenceEqual(buffer)); } await stream.WriteAsync(s_data, endStream: true); await stream.ShutdownWriteCompleted(); await stream2.WriteAsync(s_data, endStream: true); await stream2.ShutdownWriteCompleted(); await connection.CloseAsync(); } }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(DefaultListener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); await using QuicStream stream2 = connection.OpenBidirectionalStream(); await stream.WriteAsync(s_data, endStream: true); await stream.ShutdownWriteCompleted(); await stream2.WriteAsync(s_data, endStream: true); await stream2.ShutdownWriteCompleted(); byte[] memory = new byte[12]; while (true) { int res = await stream.ReadAsync(memory); if (res == 0) { break; } Assert.True(s_data.Span.SequenceEqual(memory)); } while (true) { int res = await stream2.ReadAsync(memory); if (res == 0) { break; } Assert.True(s_data.Span.SequenceEqual(memory)); } await connection.CloseAsync(); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 60000); }
public async Task CloseAsync(long errorCode) { await _connection.CloseAsync(errorCode).ConfigureAwait(false); _closed = true; }
public Task CloseAsync(long errorCode) => _connection.CloseAsync(errorCode).AsTask();
public async Task LargeDataSentAndReceived() { byte[] data = Enumerable.Range(0, 64 * 1024).Select(x => (byte)x).ToArray(); const int NumberOfWrites = 256; // total sent = 16M using QuicListener listener = CreateQuicListener(); for (int j = 0; j < 100; j++) { Task listenTask = Task.Run(async() => { using QuicConnection connection = await listener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); byte[] buffer = new byte[data.Length]; for (int i = 0; i < NumberOfWrites; i++) { int totalBytesRead = 0; while (totalBytesRead < data.Length) { int bytesRead = await stream.ReadAsync(buffer.AsMemory(totalBytesRead)); Assert.NotEqual(0, bytesRead); totalBytesRead += bytesRead; } Assert.Equal(data.Length, totalBytesRead); Assert.True(data.AsSpan().SequenceEqual(buffer)); } for (int i = 0; i < NumberOfWrites; i++) { await stream.WriteAsync(data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(listener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); byte[] buffer = new byte[data.Length]; for (int i = 0; i < NumberOfWrites; i++) { await stream.WriteAsync(data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); for (int i = 0; i < NumberOfWrites; i++) { int totalBytesRead = 0; while (totalBytesRead < data.Length) { int bytesRead = await stream.ReadAsync(buffer.AsMemory(totalBytesRead)); Assert.NotEqual(0, bytesRead); totalBytesRead += bytesRead; } Assert.Equal(data.Length, totalBytesRead); Assert.True(data.AsSpan().SequenceEqual(buffer)); } await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 1000000); } }