Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ dev

**Bugfixes**

-
- Reject nonempty SETTINGS with ACK even when flags are supplied as an iterator
or generator.

6.1.0 (2025-01-22)
------------------
Expand Down
2 changes: 1 addition & 1 deletion src/hyperframe/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ class SettingsFrame(Frame):
def __init__(self, stream_id: int = 0, settings: dict[int, int] | None = None, **kwargs: Any) -> None:
super().__init__(stream_id, **kwargs)

if settings and "ACK" in kwargs.get("flags", ()):
if settings and "ACK" in self.flags:
msg = "Settings must be empty if ACK flag is set."
raise InvalidDataError(msg)

Expand Down
40 changes: 32 additions & 8 deletions tests/test_frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,22 +382,46 @@ def test_settings_frame_serializes_properly(self):
s = f.serialize()
assert s == self.serialized

def test_settings_frame_with_settings(self):
f = SettingsFrame(settings=self.settings)
@pytest.mark.parametrize(
'make_flags', [list, tuple, iter, lambda flags: (flag for flag in flags)],
ids=['list', 'tuple', 'iterator', 'generator'],
)
def test_settings_frame_with_settings(self, make_flags):
f = SettingsFrame(settings=self.settings, flags=make_flags([]))
assert f.settings == self.settings
parsed = decode_frame(f.serialize())
assert parsed.settings == self.settings
assert parsed.flags == set()

def test_settings_frame_without_settings(self):
f = SettingsFrame()
assert f.settings == {}

def test_settings_frame_with_ack(self):
f = SettingsFrame(flags=('ACK',))
@pytest.mark.parametrize(
'make_flags', [list, tuple, iter, lambda flags: (flag for flag in flags)],
ids=['list', 'tuple', 'iterator', 'generator'],
)
@pytest.mark.parametrize('settings', [None, {}])
def test_settings_frame_with_ack(self, make_flags, settings):
f = SettingsFrame(settings=settings, flags=make_flags(['ACK']))
assert 'ACK' in f.flags
assert f.serialize() == b'\x00\x00\x00\x04\x01\x00\x00\x00\x00'
parsed = decode_frame(f.serialize())
assert parsed.flags == set(['ACK'])
assert parsed.settings == {}
assert parsed.body_len == 0

@pytest.mark.parametrize(
'make_flags', [list, tuple, iter, lambda flags: (flag for flag in flags)],
ids=['list', 'tuple', 'iterator', 'generator'],
)
def test_settings_frame_ack_and_settings(self, make_flags):
with pytest.raises(
InvalidDataError, match='Settings must be empty if ACK flag is set.',
):
SettingsFrame(settings=self.settings, flags=make_flags(['ACK']))

def test_settings_frame_ack_and_settings(self):
with pytest.raises(InvalidDataError):
SettingsFrame(settings=self.settings, flags=('ACK',))

def test_settings_frame_rejects_ack_payload(self):
with pytest.raises(InvalidDataError):
decode_frame(self.serialized)

Expand Down