Skip to content

Commit d9717dc

Browse files
committed
Ignore the reserved bit in PUSH_PROMISE identifiers
1 parent 632e309 commit d9717dc

3 files changed

Lines changed: 37 additions & 2 deletions

File tree

CHANGELOG.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ dev
1919

2020
**Bugfixes**
2121

22-
-
22+
- Mask the reserved bit in PUSH_PROMISE stream identifiers when parsing and
23+
serializing.
2324

2425
6.1.0 (2025-01-22)
2526
------------------

src/hyperframe/frame.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ def _body_repr(self) -> str:
515515
def serialize_body(self) -> bytes:
516516
padding_data = self.serialize_padding_data()
517517
padding = b"\0" * self.pad_length
518-
data = _STRUCT_L.pack(self.promised_stream_id)
518+
data = _STRUCT_L.pack(self.promised_stream_id & 0x7FFFFFFF)
519519
return b"".join([padding_data, data, self.data, padding])
520520

521521
def parse_body(self, data: memoryview) -> None:
@@ -529,6 +529,9 @@ def parse_body(self, data: memoryview) -> None:
529529
msg = "Invalid PUSH_PROMISE body"
530530
raise InvalidFrameError(msg) from err
531531

532+
# Ignore the reserved bit, RFC 9113, Section 6.6.
533+
self.promised_stream_id &= 0x7FFFFFFF
534+
532535
self.data = (
533536
data[padding_data_length + 4:len(data)-self.pad_length].tobytes()
534537
)

tests/test_reserved_promise.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import struct
2+
3+
import pytest
4+
5+
from hyperframe.exceptions import InvalidDataError
6+
from hyperframe.frame import PushPromiseFrame
7+
8+
9+
@pytest.mark.parametrize("stream_id", [2, 0x7FFFFFFE])
10+
@pytest.mark.parametrize("padded", [False, True])
11+
def test_parse_promised_stream_reserved_bit(stream_id, padded):
12+
frame = PushPromiseFrame(1)
13+
body = struct.pack("!L", stream_id | 0x80000000) + b"headers"
14+
if padded:
15+
frame.flags.add("PADDED")
16+
body = b"\x01" + body + b"\x00"
17+
frame.parse_body(memoryview(body))
18+
assert frame.promised_stream_id == stream_id
19+
assert frame.data == b"headers"
20+
21+
22+
def test_reserved_bit_does_not_make_zero_stream_valid():
23+
frame = PushPromiseFrame(1)
24+
with pytest.raises(InvalidDataError):
25+
frame.parse_body(memoryview(b"\x80\x00\x00\x00"))
26+
27+
28+
@pytest.mark.parametrize("stream_id", [2, 0x7FFFFFFE])
29+
def test_serialize_promised_stream_reserved_bit(stream_id):
30+
frame = PushPromiseFrame(1, promised_stream_id=stream_id | 0x80000000)
31+
assert frame.serialize_body() == struct.pack("!L", stream_id)

0 commit comments

Comments
 (0)