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)); }
public async ValueTask <MultiplexedConnectionContext?> AcceptAsync(IFeatureCollection?features = null, CancellationToken cancellationToken = default) { if (_listener == null) { throw new InvalidOperationException($"The listener needs to be initialized by calling {nameof(CreateListenerAsync)}."); } try { var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken); if (!_pendingConnections.TryGetValue(quicConnection, out var connectionContext)) { throw new InvalidOperationException("Couldn't find ConnectionContext for QuicConnection."); } else { _pendingConnections.Remove(quicConnection); } // Verify the connection context was created and set correctly. Debug.Assert(connectionContext != null); Debug.Assert(connectionContext.GetInnerConnection() == quicConnection); QuicLog.AcceptedConnection(_log, connectionContext); return(connectionContext); } catch (QuicException ex) when(ex.QuicError == QuicError.OperationAborted) { QuicLog.ConnectionListenerAborted(_log, ex); } return(null); }
private void ValidateServerAuthenticationOptions(SslServerAuthenticationOptions serverAuthenticationOptions) { if (serverAuthenticationOptions.ServerCertificate == null && serverAuthenticationOptions.ServerCertificateContext == null && serverAuthenticationOptions.ServerCertificateSelectionCallback == null) { QuicLog.ConnectionListenerCertificateNotSpecified(_log); } if (serverAuthenticationOptions.ApplicationProtocols == null || serverAuthenticationOptions.ApplicationProtocols.Count == 0) { QuicLog.ConnectionListenerApplicationProtocolsNotSpecified(_log); } }
public override void Abort(ConnectionAbortedException abortReason) { lock (_shutdownLock) { // Check if connection has already been already aborted. if (_abortReason != null) { return; } var resolvedErrorCode = _error ?? 0; _abortReason = ExceptionDispatchInfo.Capture(abortReason); QuicLog.ConnectionAbort(_log, this, resolvedErrorCode, abortReason.Message); _closeTask = _connection.CloseAsync(errorCode: resolvedErrorCode).AsTask(); } }
public async ValueTask <MultiplexedConnectionContext?> AcceptAsync(IFeatureCollection?features = null, CancellationToken cancellationToken = default) { try { var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken); var connectionContext = new QuicConnectionContext(quicConnection, _context); QuicLog.AcceptedConnection(_log, connectionContext); return(connectionContext); } catch (QuicOperationAbortedException ex) { _log.LogDebug($"Listener has aborted with exception: {ex.Message}"); } return(null); }
public async ValueTask CreateListenerAsync() { QuicLog.ConnectionListenerStarting(_log, _quicListenerOptions.ListenEndPoint); try { _listener = await QuicListener.ListenAsync(_quicListenerOptions); } catch (QuicException ex) when(ex.QuicError == QuicError.AddressInUse) { throw new AddressInUseException(ex.Message, ex); } // EndPoint could be configured with an ephemeral port of 0. // Listener endpoint will resolve an ephemeral port, e.g. 127.0.0.1:0, into the actual port // so we need to update the public listener endpoint property. EndPoint = _listener.LocalEndPoint; }
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); }