Beispiel #1
0
    public override ValueTask <ConnectionContext> ConnectAsync(IFeatureCollection?features = null, CancellationToken cancellationToken = default)
    {
        QuicStream quicStream;

        var streamDirectionFeature = features?.Get <IStreamDirectionFeature>();

        if (streamDirectionFeature != null)
        {
            if (streamDirectionFeature.CanRead)
            {
                quicStream = _connection.OpenBidirectionalStream();
            }
            else
            {
                quicStream = _connection.OpenUnidirectionalStream();
            }
        }
        else
        {
            quicStream = _connection.OpenBidirectionalStream();
        }

        // Only a handful of control streams are created by the server and they last for the
        // lifetime of the connection. No value in pooling them.
        QuicStreamContext?context = new QuicStreamContext(this, _context);

        context.Initialize(quicStream);
        context.Start();

        QuicLog.ConnectedStream(_log, context);

        return(new ValueTask <ConnectionContext>(context));
    }
Beispiel #2
0
 public static void StreamPause(ILogger logger, QuicStreamContext streamContext)
 {
     if (logger.IsEnabled(LogLevel.Debug))
     {
         StreamPauseCore(logger, streamContext.ConnectionId);
     }
 }
Beispiel #3
0
 public static void ConnectedStream(ILogger logger, QuicStreamContext streamContext)
 {
     if (logger.IsEnabled(LogLevel.Debug))
     {
         ConnectedStreamCore(logger, streamContext.ConnectionId, GetStreamType(streamContext));
     }
 }
Beispiel #4
0
 public static void StreamError(ILogger logger, QuicStreamContext streamContext, Exception ex)
 {
     if (logger.IsEnabled(LogLevel.Debug))
     {
         StreamErrorCore(logger, streamContext.ConnectionId, ex);
     }
 }
Beispiel #5
0
 public static void StreamAbortWrite(ILogger logger, QuicStreamContext streamContext, long errorCode, string reason)
 {
     if (logger.IsEnabled(LogLevel.Debug))
     {
         StreamAbortWriteCore(logger, streamContext.ConnectionId, errorCode, reason);
     }
 }
Beispiel #6
0
 public static void StreamAbortedRead(ILogger logger, QuicStreamContext streamContext, long errorCode)
 {
     if (logger.IsEnabled(LogLevel.Debug))
     {
         StreamAbortedReadCore(logger, streamContext.ConnectionId, errorCode);
     }
 }
Beispiel #7
0
 public static void StreamShutdownWrite(ILogger logger, QuicStreamContext streamContext, string reason)
 {
     if (logger.IsEnabled(LogLevel.Debug))
     {
         StreamShutdownWriteCore(logger, streamContext.ConnectionId, reason);
     }
 }
Beispiel #8
0
 public static void StreamReused(ILogger logger, QuicStreamContext streamContext)
 {
     if (logger.IsEnabled(LogLevel.Trace))
     {
         StreamReusedCore(logger, streamContext.ConnectionId);
     }
 }
Beispiel #9
0
        public QuicStream(QuicConnection connection, StreamId streamId)
        {
            StreamId = streamId;
            Type     = streamId.Type;

            _connection = connection;
            Context     = new QuicStreamContext(this, _connection.Context);
        }
Beispiel #10
0
        static void Main(string[] args)
        {
            QuicClient        client  = new QuicClient();
            QuicContext       context = client.Connect("127.0.0.1", 11000); // Connect to peer (Server)
            QuicStreamContext sc      = client.CreateStream();              // Create a data stream

            sc.Send(Encoding.UTF8.GetBytes("Hello from Client!"));          // Send Data

            sc.Close();                                                     // Close the stream after processing
        }
Beispiel #11
0
        //private static void Listener_OnClientConnected(QuicContext obj)
        //{
        //    System.Console.WriteLine("Client connected.");
        //    obj.OnDataReceived += Obj_OnDataReceived;
        //}

        private static void Obj_OnDataReceived(QuicStreamContext obj)
        {
            System.Console.WriteLine("Data received");
            foreach (byte b in obj.Data)
            {
                System.Console.Write(string.Format("{0},", b));
            }

            // Echo back to the client
            obj.Send(Encoding.UTF8.GetBytes("Echo!"));
        }
Beispiel #12
0
    internal bool TryReturnStream(QuicStreamContext stream)
    {
        lock (_poolLock)
        {
            if (!_streamPoolHeartbeatInitialized)
            {
                // Heartbeat feature is added to connection features by Kestrel.
                // No event is on the context is raised between feature being added and serving
                // connections so initialize heartbeat the first time a stream is added to
                // the connection's stream pool.
                var heartbeatFeature = Features.Get <IConnectionHeartbeatFeature>();
                if (heartbeatFeature == null)
                {
                    throw new InvalidOperationException($"Required {nameof(IConnectionHeartbeatFeature)} not found in connection features.");
                }

                heartbeatFeature.OnHeartbeat(static state => ((QuicConnectionContext)state).RemoveExpiredStreams(), this);
Beispiel #13
0
    public override async ValueTask <ConnectionContext?> AcceptAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            var stream = await _connection.AcceptStreamAsync(cancellationToken);

            QuicStreamContext?context = null;

            // Only use pool for bidirectional streams. Just a handful of unidirecitonal
            // streams are created for a connection and they live for the lifetime of the connection.
            if (stream.CanRead && stream.CanWrite)
            {
                lock (_poolLock)
                {
                    StreamPool.TryPop(out context);
                }
            }

            if (context == null)
            {
                context = new QuicStreamContext(this, _context);
            }
            else
            {
                context.ResetFeatureCollection();
                context.ResetItems();
            }

            context.Initialize(stream);
            context.Start();

            QuicLog.AcceptedStream(_log, context);

            return(context);
        }
        catch (QuicConnectionAbortedException ex)
        {
            // Shutdown initiated by peer, abortive.
            _error = ex.ErrorCode;
            QuicLog.ConnectionAborted(_log, this, ex.ErrorCode, ex);

            ThreadPool.UnsafeQueueUserWorkItem(state =>
            {
                state.CancelConnectionClosedToken();
            },
                                               this,
                                               preferLocal: false);

            // Throw error so consumer sees the connection is aborted by peer.
            throw new ConnectionResetException(ex.Message, ex);
        }
        catch (QuicOperationAbortedException ex)
        {
            lock (_shutdownLock)
            {
                // This error should only happen when shutdown has been initiated by the server.
                // If there is no abort reason and we have this error then the connection is in an
                // unexpected state. Abort connection and throw reason error.
                if (_abortReason == null)
                {
                    Abort(new ConnectionAbortedException("Unexpected error when accepting stream.", ex));
                }

                _abortReason !.Throw();
            }
        }
        catch (OperationCanceledException)
        {
            Debug.Assert(cancellationToken.IsCancellationRequested, "Error requires cancellation is requested.");

            lock (_shutdownLock)
            {
                // Connection has been aborted. Throw reason exception.
                _abortReason?.Throw();
            }
        }
        catch (Exception ex)
        {
            Debug.Fail($"Unexpected exception in {nameof(QuicConnectionContext)}.{nameof(AcceptAsync)}: {ex}");
            throw;
        }

        // Return null for graceful closure or cancellation.
        return(null);
    }
Beispiel #14
0
        public async Task StreamPool_Heartbeat_ExpiredStreamRemoved()
        {
            // Arrange
            var now             = new DateTimeOffset(2021, 7, 6, 12, 0, 0, TimeSpan.Zero);
            var testSystemClock = new TestSystemClock {
                UtcNow = now
            };

            await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, testSystemClock);

            var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint);

            using var clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
            await clientConnection.ConnectAsync().DefaultTimeout();

            await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout();

            var testHeartbeatFeature = new TestHeartbeatFeature();

            serverConnection.Features.Set <IConnectionHeartbeatFeature>(testHeartbeatFeature);

            // Act & Assert
            var quicConnectionContext = Assert.IsType <QuicConnectionContext>(serverConnection);

            Assert.Equal(0, quicConnectionContext.StreamPool.Count);

            var stream1 = await QuicTestHelpers.CreateAndCompleteBidirectionalStreamGracefully(clientConnection, serverConnection);

            Assert.Equal(1, quicConnectionContext.StreamPool.Count);
            QuicStreamContext pooledStream = quicConnectionContext.StreamPool._array[0];

            Assert.Same(stream1, pooledStream);
            Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks);

            now = now.AddMilliseconds(100);
            testSystemClock.UtcNow = now;
            testHeartbeatFeature.RaiseHeartbeat();
            // Not removed.
            Assert.Equal(1, quicConnectionContext.StreamPool.Count);

            var stream2 = await QuicTestHelpers.CreateAndCompleteBidirectionalStreamGracefully(clientConnection, serverConnection);

            Assert.Equal(1, quicConnectionContext.StreamPool.Count);
            pooledStream = quicConnectionContext.StreamPool._array[0];
            Assert.Same(stream1, pooledStream);
            Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks);

            Assert.Same(stream1, stream2);

            now = now.AddTicks(QuicConnectionContext.StreamPoolExpiryTicks);
            testSystemClock.UtcNow = now;
            testHeartbeatFeature.RaiseHeartbeat();
            // Not removed.
            Assert.Equal(1, quicConnectionContext.StreamPool.Count);

            now = now.AddTicks(1);
            testSystemClock.UtcNow = now;
            testHeartbeatFeature.RaiseHeartbeat();
            // Removed.
            Assert.Equal(0, quicConnectionContext.StreamPool.Count);
        }
Beispiel #15
0
 private static StreamType GetStreamType(QuicStreamContext streamContext) =>
 streamContext.CanRead && streamContext.CanWrite
         ? StreamType.Bidirectional
         : StreamType.Unidirectional;