diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3f6600e..5834785 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,10 @@ Release History dev --- +**API Changes (Backward Compatible)** + +- Setting Identifier are now correctly serialized as 16-bit values, instead of 8-bit. + **API Changes (Backward Incompatible)** - diff --git a/src/hyperframe/frame.py b/src/hyperframe/frame.py index a67487e..b5ca2e5 100644 --- a/src/hyperframe/frame.py +++ b/src/hyperframe/frame.py @@ -457,8 +457,11 @@ def _body_repr(self) -> str: return f"settings={self.settings}" def serialize_body(self) -> bytes: - return b"".join([_STRUCT_HL.pack(setting & 0xFF, value) - for setting, value in self.settings.items()]) + return b"".join([_STRUCT_HL.pack( + setting_identifier & 0xFFFF, # Settings identifiers are 16 bits, mask them appropriately, RFC 9113, Section 6.5.1 + setting_value & 0xFFFFFFFF, # Settings values are 32 bits, mask them appropriately, RFC 9113, Section 6.5.1 + ) + for setting_identifier, setting_value in self.settings.items()]) def parse_body(self, data: memoryview) -> None: if "ACK" in self.flags and len(data) > 0: diff --git a/tests/test_frames.py b/tests/test_frames.py index 10f2300..9b7de9d 100644 --- a/tests/test_frames.py +++ b/tests/test_frames.py @@ -426,6 +426,29 @@ def test_short_settings_frame_errors(self): with pytest.raises(InvalidDataError): decode_frame(self.serialized[:-2]) + def test_settings_frame_with_large_setting_ids(self): + f = SettingsFrame() + f.settings[0xFFFE] = 12345 + f.settings[0xFFFF] = 54321 + + s = f.serialize() + new_f = decode_frame(s) + + assert new_f.settings[0xFFFE] == 12345 + assert new_f.settings[0xFFFF] == 54321 + + def test_settings_frame_serializes_with_large_setting_ids(self): + f = SettingsFrame() + f.settings[0xFFFE] = 12345 + f.settings[0xFFFF] = 54321 + + s = f.serialize() + assert s == ( + b'\x00\x00\x0C\x04\x00\x00\x00\x00\x00' + # Frame header + b'\xFF\xFE\x00\x00\x30\x39' + # Setting ID 0xFFFE + b'\xFF\xFF\x00\x00\xd4\x31' # Setting ID 0xFFFF + ) + class TestPushPromiseFrame: def test_repr(self):