Skip to content

Commit 2674bb1

Browse files
Kriechipgjones
authored andcommitted
use new exception type for data protocol errors
1 parent e566cb4 commit 2674bb1

File tree

5 files changed

+76
-37
lines changed

5 files changed

+76
-37
lines changed

CHANGELOG.rst

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ Release History
44
6.0.0+dev
55
---------
66

7+
**Backwards Incompatible API Changes**
8+
9+
- Introduce ``HyperframeError`` base exception class for all errors raised within hyperframe.
10+
- Change exception base class of ``UnknownFrameError`` to ``HyperframeError``
11+
- Change exception base class of ``InvalidPaddingError`` to ``HyperframeError``
12+
- Change exception base class of ``InvalidFrameError`` to ``HyperframeError``
13+
- Invalid frames with wrong stream id (zero vs. non-zero) now raise ``InvalidDataError``.
14+
- Invalid SETTINGS frames (non-empty but ACK) now raise ``InvalidDataError``.
15+
- Invalid ALTSVC frames with non-bytestring field or origin now raise ``InvalidDataError``.
16+
717
**API Changes (Backward-compatible)**
818

919
- Deprecate ``total_padding`` - use `pad_length` instead.
@@ -12,16 +22,10 @@ Release History
1222

1323
- Fixed padding parsing for ``PushPromiseFrame``.
1424
- Fixed unchecked frame length for ``PriorityFrame``. It now correctly raises ``InvalidFrameError``.
15-
- Fixed promised stream id parsing for ``PushPromiseFrame``.
25+
- Fixed promised stream id validation for ``PushPromiseFrame``. It now raises ``InvalidDataError``.
1626
- Fixed unchecked frame length for ``WindowUpdateFrame``. It now correctly raises ``InvalidFrameError``.
17-
- Fixed window increment value range validation. It must be 1 <= increment <= 2^31-1.
27+
- Fixed window increment value range validation. It now raises ``InvalidDataError``.
1828
- Fixed parsing of ``SettingsFrame`` with mutual exclusion of ACK flag and payload.
19-
- Invalid frames with wrong stream id (zero vs. non-zero) now raise ``InvalidFrameError``, not
20-
``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass.
21-
- Invalid SETTINGS frames (non-empty but ACK) now raise ``InvalidFrameError``, not
22-
``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass.
23-
- Invalid ALTSVC frames with non-bytestring field or origin now raise ``InvalidFrameError``, not
24-
``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass.
2529

2630
**Other Changes**
2731

docs/source/api.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ you need is not present!
7272
Exceptions
7373
----------
7474

75+
.. autoclass:: hyperframe.exceptions.HyperframeError
76+
:members:
77+
7578
.. autoclass:: hyperframe.exceptions.UnknownFrameError
7679
:members:
7780

@@ -80,3 +83,6 @@ Exceptions
8083

8184
.. autoclass:: hyperframe.exceptions.InvalidFrameError
8285
:members:
86+
87+
.. autoclass:: hyperframe.exceptions.InvalidDataError
88+
:members:

src/hyperframe/exceptions.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,20 @@
77
"""
88

99

10-
class UnknownFrameError(ValueError):
10+
class HyperframeError(Exception):
11+
"""
12+
The base class for all exceptions for the hyperframe module.
13+
14+
.. versionadded:: 6.0.0
15+
"""
16+
17+
18+
class UnknownFrameError(HyperframeError):
1119
"""
1220
A frame of unknown type was received.
21+
22+
.. versionchanged:: 6.0.0
23+
Changed base class from `ValueError` to :class:`HyperframeError`
1324
"""
1425
def __init__(self, frame_type, length):
1526
#: The type byte of the unknown frame that was received.
@@ -25,17 +36,32 @@ def __str__(self):
2536
)
2637

2738

28-
class InvalidPaddingError(ValueError):
39+
class InvalidPaddingError(HyperframeError):
2940
"""
3041
A frame with invalid padding was received.
42+
43+
.. versionchanged:: 6.0.0
44+
Changed base class from `ValueError` to :class:`HyperframeError`
3145
"""
3246
pass
3347

3448

35-
class InvalidFrameError(ValueError):
49+
class InvalidFrameError(HyperframeError):
3650
"""
3751
Parsing a frame failed because the data was not laid out appropriately.
3852
3953
.. versionadded:: 3.0.2
54+
55+
.. versionchanged:: 6.0.0
56+
Changed base class from `ValueError` to :class:`HyperframeError`
57+
"""
58+
pass
59+
60+
61+
class InvalidDataError(HyperframeError):
62+
"""
63+
Content or data of a frame was is invalid or violates the specification.
64+
65+
.. versionadded:: 6.0.0
4066
"""
4167
pass

src/hyperframe/frame.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import binascii
1212

1313
from .exceptions import (
14-
UnknownFrameError, InvalidPaddingError, InvalidFrameError
14+
UnknownFrameError, InvalidPaddingError, InvalidFrameError, InvalidDataError
1515
)
1616
from .flags import Flag, Flags
1717

@@ -68,10 +68,10 @@ def __init__(self, stream_id, flags=()):
6868

6969
if (not self.stream_id and
7070
self.stream_association == _STREAM_ASSOC_HAS_STREAM):
71-
raise InvalidFrameError('Stream ID must be non-zero')
71+
raise InvalidDataError('Stream ID must be non-zero')
7272
if (self.stream_id and
7373
self.stream_association == _STREAM_ASSOC_NO_STREAM):
74-
raise InvalidFrameError('Stream ID must be zero')
74+
raise InvalidDataError('Stream ID must be zero')
7575

7676
def __repr__(self):
7777
flags = ", ".join(self.flags) or "None"
@@ -84,7 +84,7 @@ def __repr__(self):
8484
type=type(self).__name__,
8585
stream=self.stream_id,
8686
flags=flags,
87-
body=body
87+
body=body,
8888
)
8989

9090
@staticmethod
@@ -95,6 +95,9 @@ def parse_frame_header(header, strict=False):
9595
9696
This populates the flags field, and determines how long the body is.
9797
98+
:param header: A memoryview object containing the 9-byte frame header
99+
data of a frame. Must not contain more or less.
100+
98101
:param strict: Whether to raise an exception when encountering a frame
99102
not defined by spec and implemented by hyperframe.
100103
@@ -415,7 +418,7 @@ def __init__(self, stream_id=0, settings=None, **kwargs):
415418
super().__init__(stream_id, **kwargs)
416419

417420
if settings and "ACK" in kwargs.get("flags", ()):
418-
raise InvalidFrameError(
421+
raise InvalidDataError(
419422
"Settings must be empty if ACK flag is set."
420423
)
421424

@@ -428,7 +431,7 @@ def serialize_body(self):
428431

429432
def parse_body(self, data):
430433
if 'ACK' in self.flags and len(data) > 0:
431-
raise InvalidFrameError(
434+
raise InvalidDataError(
432435
"SETTINGS ack frame must not have payload: got %s bytes" %
433436
len(data)
434437
)
@@ -494,7 +497,7 @@ def parse_body(self, data):
494497
self.body_len = len(data)
495498

496499
if self.promised_stream_id == 0 or self.promised_stream_id % 2 != 0:
497-
raise InvalidFrameError(
500+
raise InvalidDataError(
498501
"Invalid PUSH_PROMISE promised stream id: %s" %
499502
self.promised_stream_id
500503
)
@@ -642,7 +645,7 @@ def parse_body(self, data):
642645
raise InvalidFrameError("Invalid WINDOW_UPDATE body")
643646

644647
if not 1 <= self.window_increment <= 2**31-1:
645-
raise InvalidFrameError(
648+
raise InvalidDataError(
646649
"WINDOW_UPDATE increment must be between 1 to 2^31-1"
647650
)
648651

@@ -764,9 +767,9 @@ def __init__(self, stream_id, origin=b'', field=b'', **kwargs):
764767
super().__init__(stream_id, **kwargs)
765768

766769
if not isinstance(origin, bytes):
767-
raise InvalidFrameError("AltSvc origin must be bytestring.")
770+
raise InvalidDataError("AltSvc origin must be bytestring.")
768771
if not isinstance(field, bytes):
769-
raise InvalidFrameError("AltSvc field must be a bytestring.")
772+
raise InvalidDataError("AltSvc field must be a bytestring.")
770773
self.origin = origin
771774
self.field = field
772775

test/test_frames.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
ContinuationFrame, AltSvcFrame, ExtensionFrame
66
)
77
from hyperframe.exceptions import (
8-
UnknownFrameError, InvalidPaddingError, InvalidFrameError
8+
UnknownFrameError, InvalidPaddingError, InvalidFrameError, InvalidDataError
99
)
1010
import pytest
1111

@@ -179,7 +179,7 @@ def test_data_frame_without_padding_calculates_flow_control_len(self):
179179
assert f.flow_controlled_length == 8
180180

181181
def test_data_frame_comes_on_a_stream(self):
182-
with pytest.raises(InvalidFrameError):
182+
with pytest.raises(InvalidDataError):
183183
DataFrame(0)
184184

185185
def test_long_data_frame(self):
@@ -269,7 +269,7 @@ def test_priority_frame_invalid(self):
269269
)
270270

271271
def test_priority_frame_comes_on_a_stream(self):
272-
with pytest.raises(InvalidFrameError):
272+
with pytest.raises(InvalidDataError):
273273
PriorityFrame(0)
274274

275275
def test_short_priority_frame_errors(self):
@@ -301,7 +301,7 @@ def test_rst_stream_frame_parses_properly(self):
301301
assert f.body_len == 4
302302

303303
def test_rst_stream_frame_comes_on_a_stream(self):
304-
with pytest.raises(InvalidFrameError):
304+
with pytest.raises(InvalidDataError):
305305
RstStreamFrame(0)
306306

307307
def test_rst_stream_frame_must_have_body_length_four(self):
@@ -358,10 +358,10 @@ def test_settings_frame_with_ack(self):
358358
assert 'ACK' in f.flags
359359

360360
def test_settings_frame_ack_and_settings(self):
361-
with pytest.raises(InvalidFrameError):
361+
with pytest.raises(InvalidDataError):
362362
SettingsFrame(settings=self.settings, flags=('ACK',))
363363

364-
with pytest.raises(InvalidFrameError):
364+
with pytest.raises(InvalidDataError):
365365
decode_frame(self.serialized)
366366

367367
def test_settings_frame_parses_properly(self):
@@ -382,11 +382,11 @@ def test_settings_frame_invalid_body_length(self):
382382
)
383383

384384
def test_settings_frames_never_have_streams(self):
385-
with pytest.raises(InvalidFrameError):
385+
with pytest.raises(InvalidDataError):
386386
SettingsFrame(stream_id=1)
387387

388388
def test_short_settings_frame_errors(self):
389-
with pytest.raises(InvalidFrameError):
389+
with pytest.raises(InvalidDataError):
390390
decode_frame(self.serialized[:-2])
391391

392392

@@ -458,11 +458,11 @@ def test_push_promise_frame_with_no_length_parses(self):
458458

459459
def test_push_promise_frame_invalid(self):
460460
data = PushPromiseFrame(1, 0).serialize()
461-
with pytest.raises(InvalidFrameError):
461+
with pytest.raises(InvalidDataError):
462462
decode_frame(data)
463463

464464
data = PushPromiseFrame(1, 3).serialize()
465-
with pytest.raises(InvalidFrameError):
465+
with pytest.raises(InvalidDataError):
466466
decode_frame(data)
467467

468468
def test_short_push_promise_errors(self):
@@ -513,7 +513,7 @@ def test_ping_frame_parses_properly(self):
513513
assert f.body_len == 8
514514

515515
def test_ping_frame_never_has_a_stream(self):
516-
with pytest.raises(InvalidFrameError):
516+
with pytest.raises(InvalidDataError):
517517
PingFrame(stream_id=1)
518518

519519
def test_ping_frame_has_no_more_than_body_length_8(self):
@@ -577,7 +577,7 @@ def test_goaway_frame_parses_properly(self):
577577
assert f.body_len == 8
578578

579579
def test_goaway_frame_never_has_a_stream(self):
580-
with pytest.raises(InvalidFrameError):
580+
with pytest.raises(InvalidDataError):
581581
GoAwayFrame(stream_id=1)
582582

583583
def test_short_goaway_frame_errors(self):
@@ -623,10 +623,10 @@ def test_short_windowupdate_frame_errors(self):
623623
with pytest.raises(InvalidFrameError):
624624
decode_frame(s)
625625

626-
with pytest.raises(InvalidFrameError):
626+
with pytest.raises(InvalidDataError):
627627
decode_frame(WindowUpdateFrame(0).serialize())
628628

629-
with pytest.raises(InvalidFrameError):
629+
with pytest.raises(InvalidDataError):
630630
decode_frame(WindowUpdateFrame(2**31).serialize())
631631

632632

@@ -829,14 +829,14 @@ def test_short_altsvc_frame_errors(self):
829829
decode_frame(self.payload_with_origin[:10])
830830

831831
def test_altsvc_with_unicode_origin_fails(self):
832-
with pytest.raises(InvalidFrameError):
832+
with pytest.raises(InvalidDataError):
833833
AltSvcFrame(
834834
stream_id=0, origin=u'hello', field=b'h2=":8000"; ma=60'
835835

836836
)
837837

838838
def test_altsvc_with_unicode_field_fails(self):
839-
with pytest.raises(InvalidFrameError):
839+
with pytest.raises(InvalidDataError):
840840
AltSvcFrame(
841841
stream_id=0, origin=b'hello', field=u'h2=":8000"; ma=60'
842842
)

0 commit comments

Comments
 (0)