Skip to content

fix(http2): ignore PRIORITY and unknown frames on streams and reject non-zero stream control frames - #2006

Open
mosuem wants to merge 1 commit into
mosum/http2-1-conn-window-replenishfrom
mosum/http2-2-priority-unknown-and-control-frames
Open

mosuem wants to merge 1 commit into
mosum/http2-1-conn-window-replenishfrom
mosum/http2-2-priority-unknown-and-control-frames

Conversation

@mosuem

@mosuem mosuem commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Note

This PR was generated by an AI coding agent (Jetski) on behalf of @mosuem.

Summary

Per RFC 9113:

  • Section 4.1 (Frame Format): "Implementations MUST ignore and discard frames of unknown types."
  • Section 6.3 (PRIORITY): "The PRIORITY frame can be sent on a stream in any state, including idle or closed streams."
  • Section 6.5 (SETTINGS), Section 6.7 (PING), Section 6.8 (GOAWAY): SETTINGS, PING, and GOAWAY frames apply to the entire connection and MUST be associated with stream 0x0; receiving one with a non-zero stream identifier MUST be treated as a connection error of type PROTOCOL_ERROR.

Previously:

  1. Connection._handleFrameImpl dispatched any frame with header.streamId != 0 to _streams.processStreamFrame, so a SETTINGS, PING, or GOAWAY frame with streamId != 0 was treated as a stream error (RST_STREAM(STREAM_CLOSED)) instead of a connection-level PROTOCOL_ERROR (GOAWAY(PROTOCOL_ERROR)).
  2. StreamHandler._processStreamFrameInternal threw ProtocolException('Unsupported frame type ...') (terminating the entire connection with GOAWAY(PROTOCOL_ERROR)) when a PriorityFrame or UnknownFrame arrived on an open stream, and threw StreamClosedException (sending RST_STREAM(STREAM_CLOSED)) when an UnknownFrame arrived on an idle or closed stream. Browsers and proxies that send PRIORITY frames on open streams therefore lost the whole connection.

Changes

  • lib/src/connection.dart: Reject SettingsFrame, PingFrame, and GoawayFrame with header.streamId != 0 via ProtocolException before stream dispatch.
  • lib/src/streams/stream_handler.dart: Ignore PriorityFrame and UnknownFrame on open streams, and on idle and closed streams (previously only PriorityFrame was ignored there).

Not changed (possible follow-up): an ignored PRIORITY/unknown frame on an idle peer-initiated stream still advances _highestStreamIdReceived, which feeds the last-stream-id of a later GOAWAY (RFC 9113 Section 6.8). That is pre-existing behavior.

Test Verification (Fails Before $\rightarrow$ Passes After)

Added two regression tests in test/server_test.dart:

  • ignores-priority-and-unknown-frames-on-open-and-idle-streams
  • rejects-control-frames-with-nonzero-stream-id

Before fix:

00:00 +0 -1: server-tests normal ignores-priority-and-unknown-frames-on-open-and-idle-streams [E]
  HTTP/2 error: Connection error: Connection is being forcefully terminated. (errorCode: 1)

00:00 +0 -2: server-tests client-errors rejects-control-frames-with-nonzero-stream-id [E]
  Expected: <Instance of 'GoawayFrame'> with `errorCode`: <1>
    Actual: <Instance of 'RstStreamFrame'>
     Which: is not an instance of 'GoawayFrame'

After fix:

00:00 +2: All tests passed!

@mosuem
mosuem added this pull request to stack #2011 October 8, 2026 08:32
@mosuem
mosuem force-pushed the mosum/http2-2-priority-unknown-and-control-frames branch from 3b76f21 to 10b0ced Compare October 8, 2026 10:36
@mosuem
mosuem removed this pull request from stack #2011 October 8, 2026 10:38
@mosuem
mosuem added this pull request to stack #2016 October 8, 2026 10:42
@mosuem
mosuem force-pushed the mosum/http2-2-priority-unknown-and-control-frames branch from 10b0ced to 12f71c3 Compare October 8, 2026 12:04
@mosuem
mosuem force-pushed the mosum/http2-2-priority-unknown-and-control-frames branch from 12f71c3 to 6c17792 Compare October 9, 2026 07:54
@mosuem
mosuem removed this pull request from stack #2016 October 9, 2026 07:55
@mosuem
mosuem added this pull request to stack #2020 October 9, 2026 07:55
@mosuem
mosuem force-pushed the mosum/http2-2-priority-unknown-and-control-frames branch from 6c17792 to 8428322 Compare October 9, 2026 08:18
Comment thread pkgs/http2/lib/src/streams/stream_handler.dart Outdated
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

PR Health

Coverage ✔️
File Coverage
pkgs/http2/lib/src/connection.dart 💚 87 % ⬆️ 0 %
pkgs/http2/lib/src/streams/stream_handler.dart 💚 91 % ⬆️ 0 %

This check for test coverage is informational (issues shown here will not fail the PR).

This check can be disabled by tagging the PR with skip-coverage-check.

License Headers ✔️
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
Files
no missing headers

All source files should start with a license header.

Unrelated files missing license headers
Files
pkgs/http_multi_server/test/cert.dart

This check can be disabled by tagging the PR with skip-license-check.

Breaking changes ✔️
Package Change Current Version New Version Needed Version Looking good?
http2 Non-Breaking 3.1.0 3.2.0-wip 3.2.0-wip ✔️

This check can be disabled by tagging the PR with skip-breaking-check.

Unused Dependencies ✔️
Package Status
http2 ✔️ All dependencies utilized correctly.

For details on how to fix these, see dependency_validator.

This check can be disabled by tagging the PR with skip-unused-dependencies-check.

API leaks ✔️

The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.

Package Leaked API symbol Leaking sources

This check can be disabled by tagging the PR with skip-leaking-check.

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

This check can be disabled by tagging the PR with skip-changelog-check.

…non-zero stream control frames

> [!NOTE]
> This PR was generated by an AI coding agent (Jetski) on behalf of @mosuem.

### Summary

Per **RFC 9113**:
- **Section 4.1 (Frame Format)**: *"Implementations MUST ignore and discard frames of unknown types."*
- **Section 6.3 (`PRIORITY`)**: *"The `PRIORITY` frame can be sent on a stream in any state, including idle or closed streams."*
- **Section 6.5 (`SETTINGS`), Section 6.7 (`PING`), Section 6.8 (`GOAWAY`)**: `SETTINGS`, `PING`, and `GOAWAY` frames apply to the entire connection and MUST be associated with stream `0x0`; receiving one with a non-zero stream identifier MUST be treated as a connection error of type `PROTOCOL_ERROR`.

Previously:
1. `Connection._handleFrameImpl` dispatched any frame with `header.streamId != 0` to `_streams.processStreamFrame`, so a `SETTINGS`, `PING`, or `GOAWAY` frame with `streamId != 0` was treated as a stream error (`RST_STREAM(STREAM_CLOSED)`) instead of a connection-level `PROTOCOL_ERROR` (`GOAWAY(PROTOCOL_ERROR)`).
2. `StreamHandler._processStreamFrameInternal` threw `ProtocolException('Unsupported frame type ...')` (terminating the entire connection with `GOAWAY(PROTOCOL_ERROR)`) when a `PriorityFrame` or `UnknownFrame` arrived on an open stream, and threw `StreamClosedException` (sending `RST_STREAM(STREAM_CLOSED)`) when an `UnknownFrame` arrived on an idle or closed stream. Browsers and proxies that send `PRIORITY` frames on open streams therefore lost the whole connection.

### Changes
- **`lib/src/connection.dart`**: Reject `SettingsFrame`, `PingFrame`, and `GoawayFrame` with `header.streamId != 0` via `ProtocolException` before stream dispatch.
- **`lib/src/streams/stream_handler.dart`**: Ignore `PriorityFrame` and `UnknownFrame` on open streams, and on idle and closed streams (previously only `PriorityFrame` was ignored there).

Not changed (possible follow-up): an ignored `PRIORITY`/unknown frame on an idle peer-initiated stream still advances `_highestStreamIdReceived`, which feeds the `last-stream-id` of a later `GOAWAY` (RFC 9113 Section 6.8). That is pre-existing behavior.

### Test Verification (Fails Before $\rightarrow$ Passes After)

Added two regression tests in `test/server_test.dart`:
- `ignores-priority-and-unknown-frames-on-open-and-idle-streams`
- `rejects-control-frames-with-nonzero-stream-id`

**Before fix:**
```text
00:00 +0 -1: server-tests normal ignores-priority-and-unknown-frames-on-open-and-idle-streams [E]
  HTTP/2 error: Connection error: Connection is being forcefully terminated. (errorCode: 1)

00:00 +0 -2: server-tests client-errors rejects-control-frames-with-nonzero-stream-id [E]
  Expected: <Instance of 'GoawayFrame'> with `errorCode`: <1>
    Actual: <Instance of 'RstStreamFrame'>
     Which: is not an instance of 'GoawayFrame'
```

**After fix:**
```text
00:00 +2: All tests passed!
```
@mosuem
mosuem force-pushed the mosum/http2-2-priority-unknown-and-control-frames branch from 8428322 to 2867462 Compare October 9, 2026 08:48
@mosuem
mosuem marked this pull request as ready for review October 9, 2026 08:54
@mosuem
mosuem requested a review from brianquinlan October 9, 2026 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant