|
7 | 7 | using System.Runtime.Versioning;
|
8 | 8 | using System.Net.Quic;
|
9 | 9 | using System.IO;
|
| 10 | +using System.Linq; |
10 | 11 | using System.Collections.Generic;
|
11 | 12 | using System.Diagnostics;
|
12 | 13 | using System.Globalization;
|
@@ -368,6 +369,16 @@ private async Task SendSettingsAsync()
|
368 | 369 | try
|
369 | 370 | {
|
370 | 371 | _clientControl = await _connection!.OpenOutboundStreamAsync(QuicStreamType.Unidirectional).ConfigureAwait(false);
|
| 372 | + |
| 373 | + // Server MUST NOT abort our control stream, setup a continuation which will react accordingly |
| 374 | + _ = _clientControl.WritesClosed.ContinueWith(t => |
| 375 | + { |
| 376 | + if (t.Exception?.InnerException is QuicException ex && ex.QuicError == QuicError.StreamAborted) |
| 377 | + { |
| 378 | + Abort(HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCriticalStream)); |
| 379 | + } |
| 380 | + }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Current); |
| 381 | + |
371 | 382 | await _clientControl.WriteAsync(_pool.Settings.Http3SettingsFrame, CancellationToken.None).ConfigureAwait(false);
|
372 | 383 | }
|
373 | 384 | catch (Exception ex)
|
@@ -571,70 +582,78 @@ private async Task ProcessServerStreamAsync(QuicStream stream)
|
571 | 582 | /// </summary>
|
572 | 583 | private async Task ProcessServerControlStreamAsync(QuicStream stream, ArrayBuffer buffer)
|
573 | 584 | {
|
574 |
| - using (buffer) |
| 585 | + try |
575 | 586 | {
|
576 |
| - // Read the first frame of the control stream. Per spec: |
577 |
| - // A SETTINGS frame MUST be sent as the first frame of each control stream. |
578 |
| - |
579 |
| - (Http3FrameType? frameType, long payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); |
580 |
| - |
581 |
| - if (frameType == null) |
| 587 | + using (buffer) |
582 | 588 | {
|
583 |
| - // Connection closed prematurely, expected SETTINGS frame. |
584 |
| - throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCriticalStream); |
585 |
| - } |
| 589 | + // Read the first frame of the control stream. Per spec: |
| 590 | + // A SETTINGS frame MUST be sent as the first frame of each control stream. |
586 | 591 |
|
587 |
| - if (frameType != Http3FrameType.Settings) |
588 |
| - { |
589 |
| - throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.MissingSettings); |
590 |
| - } |
| 592 | + (Http3FrameType? frameType, long payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); |
591 | 593 |
|
592 |
| - await ProcessSettingsFrameAsync(payloadLength).ConfigureAwait(false); |
| 594 | + if (frameType == null) |
| 595 | + { |
| 596 | + // Connection closed prematurely, expected SETTINGS frame. |
| 597 | + throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCriticalStream); |
| 598 | + } |
593 | 599 |
|
594 |
| - // Read subsequent frames. |
| 600 | + if (frameType != Http3FrameType.Settings) |
| 601 | + { |
| 602 | + throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.MissingSettings); |
| 603 | + } |
595 | 604 |
|
596 |
| - while (true) |
597 |
| - { |
598 |
| - (frameType, payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); |
| 605 | + await ProcessSettingsFrameAsync(payloadLength).ConfigureAwait(false); |
| 606 | + |
| 607 | + // Read subsequent frames. |
599 | 608 |
|
600 |
| - switch (frameType) |
| 609 | + while (true) |
601 | 610 | {
|
602 |
| - case Http3FrameType.GoAway: |
603 |
| - await ProcessGoAwayFrameAsync(payloadLength).ConfigureAwait(false); |
604 |
| - break; |
605 |
| - case Http3FrameType.Settings: |
606 |
| - // If an endpoint receives a second SETTINGS frame on the control stream, the endpoint MUST respond with a connection error of type H3_FRAME_UNEXPECTED. |
607 |
| - throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.UnexpectedFrame); |
608 |
| - case Http3FrameType.Headers: // Servers should not send these frames to a control stream. |
609 |
| - case Http3FrameType.Data: |
610 |
| - case Http3FrameType.MaxPushId: |
611 |
| - case Http3FrameType.ReservedHttp2Priority: // These frames are explicitly reserved and must never be sent. |
612 |
| - case Http3FrameType.ReservedHttp2Ping: |
613 |
| - case Http3FrameType.ReservedHttp2WindowUpdate: |
614 |
| - case Http3FrameType.ReservedHttp2Continuation: |
615 |
| - throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.UnexpectedFrame); |
616 |
| - case Http3FrameType.PushPromise: |
617 |
| - case Http3FrameType.CancelPush: |
618 |
| - // Because we haven't sent any MAX_PUSH_ID frame, it is invalid to receive any push-related frames as they will all reference a too-large ID. |
619 |
| - throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.IdError); |
620 |
| - case null: |
621 |
| - // End of stream reached. If we're shutting down, stop looping. Otherwise, this is an error (this stream should not be closed for life of connection). |
622 |
| - bool shuttingDown; |
623 |
| - lock (SyncObj) |
624 |
| - { |
625 |
| - shuttingDown = ShuttingDown; |
626 |
| - } |
627 |
| - if (!shuttingDown) |
628 |
| - { |
629 |
| - throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCriticalStream); |
630 |
| - } |
631 |
| - return; |
632 |
| - default: |
633 |
| - await SkipUnknownPayloadAsync(frameType.GetValueOrDefault(), payloadLength).ConfigureAwait(false); |
634 |
| - break; |
| 611 | + (frameType, payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); |
| 612 | + |
| 613 | + switch (frameType) |
| 614 | + { |
| 615 | + case Http3FrameType.GoAway: |
| 616 | + await ProcessGoAwayFrameAsync(payloadLength).ConfigureAwait(false); |
| 617 | + break; |
| 618 | + case Http3FrameType.Settings: |
| 619 | + // If an endpoint receives a second SETTINGS frame on the control stream, the endpoint MUST respond with a connection error of type H3_FRAME_UNEXPECTED. |
| 620 | + throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.UnexpectedFrame); |
| 621 | + case Http3FrameType.Headers: // Servers should not send these frames to a control stream. |
| 622 | + case Http3FrameType.Data: |
| 623 | + case Http3FrameType.MaxPushId: |
| 624 | + case Http3FrameType.ReservedHttp2Priority: // These frames are explicitly reserved and must never be sent. |
| 625 | + case Http3FrameType.ReservedHttp2Ping: |
| 626 | + case Http3FrameType.ReservedHttp2WindowUpdate: |
| 627 | + case Http3FrameType.ReservedHttp2Continuation: |
| 628 | + throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.UnexpectedFrame); |
| 629 | + case Http3FrameType.PushPromise: |
| 630 | + case Http3FrameType.CancelPush: |
| 631 | + // Because we haven't sent any MAX_PUSH_ID frame, it is invalid to receive any push-related frames as they will all reference a too-large ID. |
| 632 | + throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.IdError); |
| 633 | + case null: |
| 634 | + // End of stream reached. If we're shutting down, stop looping. Otherwise, this is an error (this stream should not be closed for life of connection). |
| 635 | + bool shuttingDown; |
| 636 | + lock (SyncObj) |
| 637 | + { |
| 638 | + shuttingDown = ShuttingDown; |
| 639 | + } |
| 640 | + if (!shuttingDown) |
| 641 | + { |
| 642 | + throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCriticalStream); |
| 643 | + } |
| 644 | + return; |
| 645 | + default: |
| 646 | + await SkipUnknownPayloadAsync(frameType.GetValueOrDefault(), payloadLength).ConfigureAwait(false); |
| 647 | + break; |
| 648 | + } |
635 | 649 | }
|
636 | 650 | }
|
637 | 651 | }
|
| 652 | + catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted) |
| 653 | + { |
| 654 | + // Peers MUST NOT close the control stream |
| 655 | + throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.ClosedCriticalStream); |
| 656 | + } |
638 | 657 |
|
639 | 658 | async ValueTask<(Http3FrameType? frameType, long payloadLength)> ReadFrameEnvelopeAsync()
|
640 | 659 | {
|
|
0 commit comments