summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/tools/third_party/hyperframe
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/tools/third_party/hyperframe
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/tools/third_party/hyperframe')
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/CONTRIBUTORS.rst56
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/HISTORY.rst179
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/LICENSE21
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/MANIFEST.in2
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/PKG-INFO242
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/README.rst39
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/__init__.py8
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/exceptions.py41
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/flags.py50
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/frame.py822
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/setup.cfg10
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/setup.py59
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/test/test_flags.py35
-rw-r--r--testing/web-platform/tests/tools/third_party/hyperframe/test/test_frames.py791
14 files changed, 2355 insertions, 0 deletions
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/CONTRIBUTORS.rst b/testing/web-platform/tests/tools/third_party/hyperframe/CONTRIBUTORS.rst
new file mode 100644
index 0000000000..aa7ab8b637
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/CONTRIBUTORS.rst
@@ -0,0 +1,56 @@
+Hyper is written and maintained by Cory Benfield and various contributors:
+
+Development Lead
+````````````````
+
+- Cory Benfield <cory@lukasa.co.uk>
+
+Contributors
+````````````
+
+In chronological order:
+
+- Sriram Ganesan (@elricL)
+
+ - Implemented the Huffman encoding/decoding logic.
+
+- Alek Storm (@alekstorm)
+
+ - Implemented Python 2.7 support.
+ - Implemented HTTP/2 draft 10 support.
+ - Implemented server push.
+
+- Tetsuya Morimoto (@t2y)
+
+ - Fixed a bug where large or incomplete frames were not handled correctly.
+ - Added hyper command-line tool.
+ - General code cleanups.
+
+- Jerome De Cuyper (@jdecuyper)
+
+ - Updated documentation and tests.
+
+- Maximilian Hils (@mhils)
+
+ - Added repr for frames.
+ - Improved frame initialization code.
+ - Added flag validation.
+
+- Thomas Kriechbaumer (@Kriechi)
+
+ - Improved initialization code.
+ - Fixed bugs in frame initialization code.
+ - Improved frame repr for frames with non-printable bodies.
+
+- Davey Shafik (@dshafik)
+
+ - Fixed Alt Svc frame stream association.
+
+- Seth Michael Larson (@SethMichaelLarson)
+
+ - Performance improvements to serialization and parsing.
+
+- Fred Thomsen (@fredthomsen)
+
+ - Support for memoryview in DataFrames.
+
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/HISTORY.rst b/testing/web-platform/tests/tools/third_party/hyperframe/HISTORY.rst
new file mode 100644
index 0000000000..172b2b91ba
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/HISTORY.rst
@@ -0,0 +1,179 @@
+Release History
+===============
+
+6.0.0dev0
+---------
+
+5.2.0 (2019-01-18)
+------------------
+
+**API Changes (Backward-compatible)**
+
+- Add a new ENABLE_CONNECT_PROTOCOL settings paramter.
+
+**Other Changes**
+
+- Fix collections.abc deprecation.
+- Drop support for Python 3.3 and support 3.7.
+
+5.1.0 (2017-04-24)
+------------------
+
+**API Changes (Backward-compatible)**
+
+- Added support for ``DataFrame.data`` being a ``memoryview`` object.
+
+5.0.0 (2017-03-07)
+------------------
+
+**Backwards Incompatible API Changes**
+
+- Added support for unknown extension frames. These will be returned in the new
+ ``ExtensionFrame`` object. The flag information for these frames is persisted
+ in ``flag_byte`` if needed.
+
+4.0.2 (2017-02-20)
+------------------
+
+**Bugfixes**
+
+- Fixed AltSvc stream association, which was incorrectly set to ``'both'``:
+ should have been ``'either'``.
+- Fixed a bug where stream IDs on received frames were allowed to be 32-bit,
+ instead of 31-bit.
+- Fixed a bug with frames that had the ``PADDING`` flag set but zero-length
+ padding, whose flow-controlled length was calculated wrongly.
+- Miscellaneous performance improvements to serialization and parsing logic.
+
+4.0.1 (2016-03-13)
+------------------
+
+**Bugfixes**
+
+- Fixed bug with the repr of ``AltSvcFrame``, where building it could throw
+ exceptions if the frame had been received from the network.
+
+4.0.0 (2016-03-13)
+------------------
+
+**Backwards Incompatible API Changes**
+
+- Updated old ALTSVC frame definition to match the newly specified RFC 7838.
+- Remove BLOCKED frame, which was never actually specified.
+- Removed previously deprecated ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and
+ ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``.
+
+3.2.0 (2016-02-02)
+------------------
+
+**API Changes (Backward-compatible)**
+
+- Invalid PING frame bodies now raise ``InvalidFrameError``, not
+ ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass.
+- Invalid RST_STREAM frame bodies now raise ``InvalidFramError``, not
+ ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass.
+- Canonicalized the names of ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and
+ ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE`` to match their peers, by
+ adding new properties ``SettingsFrame.MAX_FRAME_SIZE`` and
+ ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. The old names are still
+ present, but will be deprecated in 4.0.0.
+
+**Bugfixes**
+
+- The change in ``3.1.0`` that ensured that ``InvalidFrameError`` would be
+ thrown did not affect certain invalid values in ALT_SVC frames. This has been
+ fixed: ``ValueError`` will no longer be thrown from invalid ALT_SVC bodies.
+
+3.1.1 (2016-01-18)
+------------------
+
+**Bugfixes**
+
+- Correctly error when receiving Ping frames that have insufficient data.
+
+3.1.0 (2016-01-13)
+------------------
+
+**API Changes**
+
+- Added new ``InvalidFrameError`` that is thrown instead of ``struct.error``
+ when parsing a frame.
+
+**Bugfixes**
+
+- Fixed error when trying to serialize frames that use Priority information
+ with the defaults for that information.
+- Fixed errors when displaying the repr of frames with non-printable bodies.
+
+3.0.1 (2016-01-08)
+------------------
+
+**Bugfixes**
+
+- Fix issue where unpadded DATA, PUSH_PROMISE and HEADERS frames that had empty
+ bodies would raise ``InvalidPaddingError`` exceptions when parsed.
+
+3.0.0 (2016-01-08)
+------------------
+
+**Backwards Incompatible API Changes**
+
+- Parsing padded frames that have invalid padding sizes now throws an
+ ``InvalidPaddingError``.
+
+2.2.0 (2015-10-15)
+------------------
+
+**API Changes**
+
+- When an unknown frame is encountered, ``parse_frame_header`` now throws a
+ ``ValueError`` subclass: ``UnknownFrameError``. This subclass contains the
+ frame type and the length of the frame body.
+
+2.1.0 (2015-10-06)
+------------------
+
+**API Changes**
+
+- Frames parsed from binary data now carry a ``body_len`` attribute that
+ matches the frame length (minus the frame header).
+
+2.0.0 (2015-09-21)
+------------------
+
+**API Changes**
+
+- Attempting to parse unrecognised frames now throws ``ValueError`` instead of
+ ``KeyError``. Thanks to @Kriechi!
+- Flags are now validated for correctness, preventing setting flags that
+ ``hyperframe`` does not recognise and that would not serialize. Thanks to
+ @mhils!
+- Frame properties can now be initialized in the constructors. Thanks to @mhils
+ and @Kriechi!
+- Frames that cannot be sent on a stream now have their stream ID defaulted
+ to ``0``. Thanks to @Kriechi!
+
+**Other Changes**
+
+- Frames have a more useful repr. Thanks to @mhils!
+
+1.1.1 (2015-07-20)
+------------------
+
+- Fix a bug where ``FRAME_MAX_LEN`` was one byte too small.
+
+1.1.0 (2015-06-28)
+------------------
+
+- Add ``body_len`` property to frames to enable introspection of the actual
+ frame length. Thanks to @jdecuyper!
+
+1.0.1 (2015-06-27)
+------------------
+
+- Fix bug where the frame header would have an incorrect length added to it.
+
+1.0.0 (2015-04-12)
+------------------
+
+- Initial extraction from hyper.
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/LICENSE b/testing/web-platform/tests/tools/third_party/hyperframe/LICENSE
new file mode 100644
index 0000000000..d24c351e18
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Cory Benfield
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/MANIFEST.in b/testing/web-platform/tests/tools/third_party/hyperframe/MANIFEST.in
new file mode 100644
index 0000000000..2f464676cb
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/MANIFEST.in
@@ -0,0 +1,2 @@
+include README.rst LICENSE CONTRIBUTORS.rst HISTORY.rst
+
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/PKG-INFO b/testing/web-platform/tests/tools/third_party/hyperframe/PKG-INFO
new file mode 100644
index 0000000000..cfd53f7e93
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/PKG-INFO
@@ -0,0 +1,242 @@
+Metadata-Version: 1.1
+Name: hyperframe
+Version: 5.2.0
+Summary: HTTP/2 framing layer for Python
+Home-page: https://python-hyper.org/hyperframe/en/latest/
+Author: Cory Benfield
+Author-email: cory@lukasa.co.uk
+License: MIT License
+Description: ======================================
+ hyperframe: Pure-Python HTTP/2 framing
+ ======================================
+
+ .. image:: https://travis-ci.org/python-hyper/hyperframe.png?branch=master
+ :target: https://travis-ci.org/python-hyper/hyperframe
+
+ This library contains the HTTP/2 framing code used in the `hyper`_ project. It
+ provides a pure-Python codebase that is capable of decoding a binary stream
+ into HTTP/2 frames.
+
+ This library is used directly by `hyper`_ and a number of other projects to
+ provide HTTP/2 frame decoding logic.
+
+ Contributing
+ ============
+
+ hyperframe welcomes contributions from anyone! Unlike many other projects we
+ are happy to accept cosmetic contributions and small contributions, in addition
+ to large feature requests and changes.
+
+ Before you contribute (either by opening an issue or filing a pull request),
+ please `read the contribution guidelines`_.
+
+ .. _read the contribution guidelines: http://hyper.readthedocs.org/en/development/contributing.html
+
+ License
+ =======
+
+ hyperframe is made available under the MIT License. For more details, see the
+ ``LICENSE`` file in the repository.
+
+ Authors
+ =======
+
+ hyperframe is maintained by Cory Benfield, with contributions from others. For
+ more details about the contributors, please see ``CONTRIBUTORS.rst``.
+
+ .. _hyper: http://python-hyper.org/
+
+
+ Release History
+ ===============
+
+ 6.0.0dev0
+ ---------
+
+ 5.2.0 (2019-01-18)
+ ------------------
+
+ **API Changes (Backward-compatible)**
+
+ - Add a new ENABLE_CONNECT_PROTOCOL settings paramter.
+
+ **Other Changes**
+
+ - Fix collections.abc deprecation.
+ - Drop support for Python 3.3 and support 3.7.
+
+ 5.1.0 (2017-04-24)
+ ------------------
+
+ **API Changes (Backward-compatible)**
+
+ - Added support for ``DataFrame.data`` being a ``memoryview`` object.
+
+ 5.0.0 (2017-03-07)
+ ------------------
+
+ **Backwards Incompatible API Changes**
+
+ - Added support for unknown extension frames. These will be returned in the new
+ ``ExtensionFrame`` object. The flag information for these frames is persisted
+ in ``flag_byte`` if needed.
+
+ 4.0.2 (2017-02-20)
+ ------------------
+
+ **Bugfixes**
+
+ - Fixed AltSvc stream association, which was incorrectly set to ``'both'``:
+ should have been ``'either'``.
+ - Fixed a bug where stream IDs on received frames were allowed to be 32-bit,
+ instead of 31-bit.
+ - Fixed a bug with frames that had the ``PADDING`` flag set but zero-length
+ padding, whose flow-controlled length was calculated wrongly.
+ - Miscellaneous performance improvements to serialization and parsing logic.
+
+ 4.0.1 (2016-03-13)
+ ------------------
+
+ **Bugfixes**
+
+ - Fixed bug with the repr of ``AltSvcFrame``, where building it could throw
+ exceptions if the frame had been received from the network.
+
+ 4.0.0 (2016-03-13)
+ ------------------
+
+ **Backwards Incompatible API Changes**
+
+ - Updated old ALTSVC frame definition to match the newly specified RFC 7838.
+ - Remove BLOCKED frame, which was never actually specified.
+ - Removed previously deprecated ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and
+ ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``.
+
+ 3.2.0 (2016-02-02)
+ ------------------
+
+ **API Changes (Backward-compatible)**
+
+ - Invalid PING frame bodies now raise ``InvalidFrameError``, not
+ ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass.
+ - Invalid RST_STREAM frame bodies now raise ``InvalidFramError``, not
+ ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass.
+ - Canonicalized the names of ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and
+ ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE`` to match their peers, by
+ adding new properties ``SettingsFrame.MAX_FRAME_SIZE`` and
+ ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. The old names are still
+ present, but will be deprecated in 4.0.0.
+
+ **Bugfixes**
+
+ - The change in ``3.1.0`` that ensured that ``InvalidFrameError`` would be
+ thrown did not affect certain invalid values in ALT_SVC frames. This has been
+ fixed: ``ValueError`` will no longer be thrown from invalid ALT_SVC bodies.
+
+ 3.1.1 (2016-01-18)
+ ------------------
+
+ **Bugfixes**
+
+ - Correctly error when receiving Ping frames that have insufficient data.
+
+ 3.1.0 (2016-01-13)
+ ------------------
+
+ **API Changes**
+
+ - Added new ``InvalidFrameError`` that is thrown instead of ``struct.error``
+ when parsing a frame.
+
+ **Bugfixes**
+
+ - Fixed error when trying to serialize frames that use Priority information
+ with the defaults for that information.
+ - Fixed errors when displaying the repr of frames with non-printable bodies.
+
+ 3.0.1 (2016-01-08)
+ ------------------
+
+ **Bugfixes**
+
+ - Fix issue where unpadded DATA, PUSH_PROMISE and HEADERS frames that had empty
+ bodies would raise ``InvalidPaddingError`` exceptions when parsed.
+
+ 3.0.0 (2016-01-08)
+ ------------------
+
+ **Backwards Incompatible API Changes**
+
+ - Parsing padded frames that have invalid padding sizes now throws an
+ ``InvalidPaddingError``.
+
+ 2.2.0 (2015-10-15)
+ ------------------
+
+ **API Changes**
+
+ - When an unknown frame is encountered, ``parse_frame_header`` now throws a
+ ``ValueError`` subclass: ``UnknownFrameError``. This subclass contains the
+ frame type and the length of the frame body.
+
+ 2.1.0 (2015-10-06)
+ ------------------
+
+ **API Changes**
+
+ - Frames parsed from binary data now carry a ``body_len`` attribute that
+ matches the frame length (minus the frame header).
+
+ 2.0.0 (2015-09-21)
+ ------------------
+
+ **API Changes**
+
+ - Attempting to parse unrecognised frames now throws ``ValueError`` instead of
+ ``KeyError``. Thanks to @Kriechi!
+ - Flags are now validated for correctness, preventing setting flags that
+ ``hyperframe`` does not recognise and that would not serialize. Thanks to
+ @mhils!
+ - Frame properties can now be initialized in the constructors. Thanks to @mhils
+ and @Kriechi!
+ - Frames that cannot be sent on a stream now have their stream ID defaulted
+ to ``0``. Thanks to @Kriechi!
+
+ **Other Changes**
+
+ - Frames have a more useful repr. Thanks to @mhils!
+
+ 1.1.1 (2015-07-20)
+ ------------------
+
+ - Fix a bug where ``FRAME_MAX_LEN`` was one byte too small.
+
+ 1.1.0 (2015-06-28)
+ ------------------
+
+ - Add ``body_len`` property to frames to enable introspection of the actual
+ frame length. Thanks to @jdecuyper!
+
+ 1.0.1 (2015-06-27)
+ ------------------
+
+ - Fix bug where the frame header would have an incorrect length added to it.
+
+ 1.0.0 (2015-04-12)
+ ------------------
+
+ - Initial extraction from hyper.
+
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: Implementation :: CPython
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/README.rst b/testing/web-platform/tests/tools/third_party/hyperframe/README.rst
new file mode 100644
index 0000000000..385b39af9b
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/README.rst
@@ -0,0 +1,39 @@
+======================================
+hyperframe: Pure-Python HTTP/2 framing
+======================================
+
+.. image:: https://travis-ci.org/python-hyper/hyperframe.png?branch=master
+ :target: https://travis-ci.org/python-hyper/hyperframe
+
+This library contains the HTTP/2 framing code used in the `hyper`_ project. It
+provides a pure-Python codebase that is capable of decoding a binary stream
+into HTTP/2 frames.
+
+This library is used directly by `hyper`_ and a number of other projects to
+provide HTTP/2 frame decoding logic.
+
+Contributing
+============
+
+hyperframe welcomes contributions from anyone! Unlike many other projects we
+are happy to accept cosmetic contributions and small contributions, in addition
+to large feature requests and changes.
+
+Before you contribute (either by opening an issue or filing a pull request),
+please `read the contribution guidelines`_.
+
+.. _read the contribution guidelines: http://hyper.readthedocs.org/en/development/contributing.html
+
+License
+=======
+
+hyperframe is made available under the MIT License. For more details, see the
+``LICENSE`` file in the repository.
+
+Authors
+=======
+
+hyperframe is maintained by Cory Benfield, with contributions from others. For
+more details about the contributors, please see ``CONTRIBUTORS.rst``.
+
+.. _hyper: http://python-hyper.org/
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/__init__.py b/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/__init__.py
new file mode 100644
index 0000000000..7620b4bdf7
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+"""
+hyperframe
+~~~~~~~~~~
+
+A module for providing a pure-Python HTTP/2 framing layer.
+"""
+__version__ = '5.2.0'
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/exceptions.py b/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/exceptions.py
new file mode 100644
index 0000000000..dd30369c70
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/exceptions.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+"""
+hyperframe/exceptions
+~~~~~~~~~~~~~~~~~~~~~
+
+Defines the exceptions that can be thrown by hyperframe.
+"""
+
+
+class UnknownFrameError(ValueError):
+ """
+ An frame of unknown type was received.
+ """
+ def __init__(self, frame_type, length):
+ #: The type byte of the unknown frame that was received.
+ self.frame_type = frame_type
+
+ #: The length of the data portion of the unknown frame.
+ self.length = length
+
+ def __str__(self):
+ return (
+ "UnknownFrameError: Unknown frame type 0x%X received, "
+ "length %d bytes" % (self.frame_type, self.length)
+ )
+
+
+class InvalidPaddingError(ValueError):
+ """
+ A frame with invalid padding was received.
+ """
+ pass
+
+
+class InvalidFrameError(ValueError):
+ """
+ Parsing a frame failed because the data was not laid out appropriately.
+
+ .. versionadded:: 3.0.2
+ """
+ pass
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/flags.py b/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/flags.py
new file mode 100644
index 0000000000..1660bd1800
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/flags.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+"""
+hyperframe/flags
+~~~~~~~~~~~~~~~~
+
+Defines basic Flag and Flags data structures.
+"""
+import collections
+
+try:
+ from collections.abc import MutableSet
+except ImportError: # pragma: no cover
+ # Python 2.7 compatibility
+ from collections import MutableSet
+
+Flag = collections.namedtuple("Flag", ["name", "bit"])
+
+
+class Flags(MutableSet):
+ """
+ A simple MutableSet implementation that will only accept known flags as
+ elements.
+
+ Will behave like a regular set(), except that a ValueError will be thrown
+ when .add()ing unexpected flags.
+ """
+ def __init__(self, defined_flags):
+ self._valid_flags = set(flag.name for flag in defined_flags)
+ self._flags = set()
+
+ def __contains__(self, x):
+ return self._flags.__contains__(x)
+
+ def __iter__(self):
+ return self._flags.__iter__()
+
+ def __len__(self):
+ return self._flags.__len__()
+
+ def discard(self, value):
+ return self._flags.discard(value)
+
+ def add(self, value):
+ if value not in self._valid_flags:
+ raise ValueError(
+ "Unexpected flag: {}. Valid flags are: {}".format(
+ value, self._valid_flags
+ )
+ )
+ return self._flags.add(value)
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/frame.py b/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/frame.py
new file mode 100644
index 0000000000..795057279b
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/frame.py
@@ -0,0 +1,822 @@
+# -*- coding: utf-8 -*-
+"""
+hyperframe/frame
+~~~~~~~~~~~~~~~~
+
+Defines framing logic for HTTP/2. Provides both classes to represent framed
+data and logic for aiding the connection when it comes to reading from the
+socket.
+"""
+import struct
+import binascii
+
+from .exceptions import (
+ UnknownFrameError, InvalidPaddingError, InvalidFrameError
+)
+from .flags import Flag, Flags
+
+
+# The maximum initial length of a frame. Some frames have shorter maximum
+# lengths.
+FRAME_MAX_LEN = (2 ** 14)
+
+# The maximum allowed length of a frame.
+FRAME_MAX_ALLOWED_LEN = (2 ** 24) - 1
+
+# Stream association enumerations.
+_STREAM_ASSOC_HAS_STREAM = "has-stream"
+_STREAM_ASSOC_NO_STREAM = "no-stream"
+_STREAM_ASSOC_EITHER = "either"
+
+# Structs for packing and unpacking
+_STRUCT_HBBBL = struct.Struct(">HBBBL")
+_STRUCT_LL = struct.Struct(">LL")
+_STRUCT_HL = struct.Struct(">HL")
+_STRUCT_LB = struct.Struct(">LB")
+_STRUCT_L = struct.Struct(">L")
+_STRUCT_H = struct.Struct(">H")
+_STRUCT_B = struct.Struct(">B")
+
+
+class Frame(object):
+ """
+ The base class for all HTTP/2 frames.
+ """
+ #: The flags defined on this type of frame.
+ defined_flags = []
+
+ #: The byte used to define the type of the frame.
+ type = None
+
+ # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream',
+ # it must be zero. If 'either', it's not checked.
+ stream_association = None
+
+ def __init__(self, stream_id, flags=()):
+ #: The stream identifier for the stream this frame was received on.
+ #: Set to 0 for frames sent on the connection (stream-id 0).
+ self.stream_id = stream_id
+
+ #: The flags set for this frame.
+ self.flags = Flags(self.defined_flags)
+
+ #: The frame length, excluding the nine-byte header.
+ self.body_len = 0
+
+ for flag in flags:
+ self.flags.add(flag)
+
+ if (not self.stream_id and
+ self.stream_association == _STREAM_ASSOC_HAS_STREAM):
+ raise ValueError('Stream ID must be non-zero')
+ if (self.stream_id and
+ self.stream_association == _STREAM_ASSOC_NO_STREAM):
+ raise ValueError('Stream ID must be zero')
+
+ def __repr__(self):
+ flags = ", ".join(self.flags) or "None"
+ body = binascii.hexlify(self.serialize_body()).decode('ascii')
+ if len(body) > 20:
+ body = body[:20] + "..."
+ return (
+ "{type}(Stream: {stream}; Flags: {flags}): {body}"
+ ).format(
+ type=type(self).__name__,
+ stream=self.stream_id,
+ flags=flags,
+ body=body
+ )
+
+ @staticmethod
+ def parse_frame_header(header, strict=False):
+ """
+ Takes a 9-byte frame header and returns a tuple of the appropriate
+ Frame object and the length that needs to be read from the socket.
+
+ This populates the flags field, and determines how long the body is.
+
+ :param strict: Whether to raise an exception when encountering a frame
+ not defined by spec and implemented by hyperframe.
+
+ :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown
+ type is received.
+
+ .. versionchanged:: 5.0.0
+ Added :param:`strict` to accommodate :class:`ExtensionFrame`
+ """
+ try:
+ fields = _STRUCT_HBBBL.unpack(header)
+ except struct.error:
+ raise InvalidFrameError("Invalid frame header")
+
+ # First 24 bits are frame length.
+ length = (fields[0] << 8) + fields[1]
+ type = fields[2]
+ flags = fields[3]
+ stream_id = fields[4] & 0x7FFFFFFF
+
+ try:
+ frame = FRAMES[type](stream_id)
+ except KeyError:
+ if strict:
+ raise UnknownFrameError(type, length)
+ frame = ExtensionFrame(type=type, stream_id=stream_id)
+
+ frame.parse_flags(flags)
+ return (frame, length)
+
+ def parse_flags(self, flag_byte):
+ for flag, flag_bit in self.defined_flags:
+ if flag_byte & flag_bit:
+ self.flags.add(flag)
+
+ return self.flags
+
+ def serialize(self):
+ """
+ Convert a frame into a bytestring, representing the serialized form of
+ the frame.
+ """
+ body = self.serialize_body()
+ self.body_len = len(body)
+
+ # Build the common frame header.
+ # First, get the flags.
+ flags = 0
+
+ for flag, flag_bit in self.defined_flags:
+ if flag in self.flags:
+ flags |= flag_bit
+
+ header = _STRUCT_HBBBL.pack(
+ (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits
+ self.body_len & 0xFF,
+ self.type,
+ flags,
+ self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits.
+ )
+
+ return header + body
+
+ def serialize_body(self):
+ raise NotImplementedError()
+
+ def parse_body(self, data):
+ """
+ Given the body of a frame, parses it into frame data. This populates
+ the non-header parts of the frame: that is, it does not populate the
+ stream ID or flags.
+
+ :param data: A memoryview object containing the body data of the frame.
+ Must not contain *more* data than the length returned by
+ :meth:`parse_frame_header
+ <hyperframe.frame.Frame.parse_frame_header>`.
+ """
+ raise NotImplementedError()
+
+
+class Padding(object):
+ """
+ Mixin for frames that contain padding. Defines extra fields that can be
+ used and set by frames that can be padded.
+ """
+ def __init__(self, stream_id, pad_length=0, **kwargs):
+ super(Padding, self).__init__(stream_id, **kwargs)
+
+ #: The length of the padding to use.
+ self.pad_length = pad_length
+
+ def serialize_padding_data(self):
+ if 'PADDED' in self.flags:
+ return _STRUCT_B.pack(self.pad_length)
+ return b''
+
+ def parse_padding_data(self, data):
+ if 'PADDED' in self.flags:
+ try:
+ self.pad_length = struct.unpack('!B', data[:1])[0]
+ except struct.error:
+ raise InvalidFrameError("Invalid Padding data")
+ return 1
+ return 0
+
+ @property
+ def total_padding(self):
+ return self.pad_length
+
+
+class Priority(object):
+ """
+ Mixin for frames that contain priority data. Defines extra fields that can
+ be used and set by frames that contain priority data.
+ """
+ def __init__(self,
+ stream_id,
+ depends_on=0x0,
+ stream_weight=0x0,
+ exclusive=False,
+ **kwargs):
+ super(Priority, self).__init__(stream_id, **kwargs)
+
+ #: The stream ID of the stream on which this stream depends.
+ self.depends_on = depends_on
+
+ #: The weight of the stream. This is an integer between 0 and 256.
+ self.stream_weight = stream_weight
+
+ #: Whether the exclusive bit was set.
+ self.exclusive = exclusive
+
+ def serialize_priority_data(self):
+ return _STRUCT_LB.pack(
+ self.depends_on + (0x80000000 if self.exclusive else 0),
+ self.stream_weight
+ )
+
+ def parse_priority_data(self, data):
+ try:
+ self.depends_on, self.stream_weight = _STRUCT_LB.unpack(data[:5])
+ except struct.error:
+ raise InvalidFrameError("Invalid Priority data")
+
+ self.exclusive = True if self.depends_on >> 31 else False
+ self.depends_on &= 0x7FFFFFFF
+ return 5
+
+
+class DataFrame(Padding, Frame):
+ """
+ DATA frames convey arbitrary, variable-length sequences of octets
+ associated with a stream. One or more DATA frames are used, for instance,
+ to carry HTTP request or response payloads.
+ """
+ #: The flags defined for DATA frames.
+ defined_flags = [
+ Flag('END_STREAM', 0x01),
+ Flag('PADDED', 0x08),
+ ]
+
+ #: The type byte for data frames.
+ type = 0x0
+
+ stream_association = _STREAM_ASSOC_HAS_STREAM
+
+ def __init__(self, stream_id, data=b'', **kwargs):
+ super(DataFrame, self).__init__(stream_id, **kwargs)
+
+ #: The data contained on this frame.
+ self.data = data
+
+ def serialize_body(self):
+ padding_data = self.serialize_padding_data()
+ padding = b'\0' * self.total_padding
+ if isinstance(self.data, memoryview):
+ self.data = self.data.tobytes()
+ return b''.join([padding_data, self.data, padding])
+
+ def parse_body(self, data):
+ padding_data_length = self.parse_padding_data(data)
+ self.data = (
+ data[padding_data_length:len(data)-self.total_padding].tobytes()
+ )
+ self.body_len = len(data)
+
+ if self.total_padding and self.total_padding >= self.body_len:
+ raise InvalidPaddingError("Padding is too long.")
+
+ @property
+ def flow_controlled_length(self):
+ """
+ The length of the frame that needs to be accounted for when considering
+ flow control.
+ """
+ padding_len = 0
+ if 'PADDED' in self.flags:
+ # Account for extra 1-byte padding length field, which is still
+ # present if possibly zero-valued.
+ padding_len = self.total_padding + 1
+ return len(self.data) + padding_len
+
+
+class PriorityFrame(Priority, Frame):
+ """
+ The PRIORITY frame specifies the sender-advised priority of a stream. It
+ can be sent at any time for an existing stream. This enables
+ reprioritisation of existing streams.
+ """
+ #: The flags defined for PRIORITY frames.
+ defined_flags = []
+
+ #: The type byte defined for PRIORITY frames.
+ type = 0x02
+
+ stream_association = _STREAM_ASSOC_HAS_STREAM
+
+ def serialize_body(self):
+ return self.serialize_priority_data()
+
+ def parse_body(self, data):
+ self.parse_priority_data(data)
+ self.body_len = len(data)
+
+
+class RstStreamFrame(Frame):
+ """
+ The RST_STREAM frame allows for abnormal termination of a stream. When sent
+ by the initiator of a stream, it indicates that they wish to cancel the
+ stream or that an error condition has occurred. When sent by the receiver
+ of a stream, it indicates that either the receiver is rejecting the stream,
+ requesting that the stream be cancelled or that an error condition has
+ occurred.
+ """
+ #: The flags defined for RST_STREAM frames.
+ defined_flags = []
+
+ #: The type byte defined for RST_STREAM frames.
+ type = 0x03
+
+ stream_association = _STREAM_ASSOC_HAS_STREAM
+
+ def __init__(self, stream_id, error_code=0, **kwargs):
+ super(RstStreamFrame, self).__init__(stream_id, **kwargs)
+
+ #: The error code used when resetting the stream.
+ self.error_code = error_code
+
+ def serialize_body(self):
+ return _STRUCT_L.pack(self.error_code)
+
+ def parse_body(self, data):
+ if len(data) != 4:
+ raise InvalidFrameError(
+ "RST_STREAM must have 4 byte body: actual length %s." %
+ len(data)
+ )
+
+ try:
+ self.error_code = _STRUCT_L.unpack(data)[0]
+ except struct.error: # pragma: no cover
+ raise InvalidFrameError("Invalid RST_STREAM body")
+
+ self.body_len = 4
+
+
+class SettingsFrame(Frame):
+ """
+ The SETTINGS frame conveys configuration parameters that affect how
+ endpoints communicate. The parameters are either constraints on peer
+ behavior or preferences.
+
+ Settings are not negotiated. Settings describe characteristics of the
+ sending peer, which are used by the receiving peer. Different values for
+ the same setting can be advertised by each peer. For example, a client
+ might set a high initial flow control window, whereas a server might set a
+ lower value to conserve resources.
+ """
+ #: The flags defined for SETTINGS frames.
+ defined_flags = [Flag('ACK', 0x01)]
+
+ #: The type byte defined for SETTINGS frames.
+ type = 0x04
+
+ stream_association = _STREAM_ASSOC_NO_STREAM
+
+ # We need to define the known settings, they may as well be class
+ # attributes.
+ #: The byte that signals the SETTINGS_HEADER_TABLE_SIZE setting.
+ HEADER_TABLE_SIZE = 0x01
+ #: The byte that signals the SETTINGS_ENABLE_PUSH setting.
+ ENABLE_PUSH = 0x02
+ #: The byte that signals the SETTINGS_MAX_CONCURRENT_STREAMS setting.
+ MAX_CONCURRENT_STREAMS = 0x03
+ #: The byte that signals the SETTINGS_INITIAL_WINDOW_SIZE setting.
+ INITIAL_WINDOW_SIZE = 0x04
+ #: The byte that signals the SETTINGS_MAX_FRAME_SIZE setting.
+ MAX_FRAME_SIZE = 0x05
+ #: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting.
+ MAX_HEADER_LIST_SIZE = 0x06
+ #: The byte that signals SETTINGS_ENABLE_CONNECT_PROTOCOL setting.
+ ENABLE_CONNECT_PROTOCOL = 0x08
+
+ def __init__(self, stream_id=0, settings=None, **kwargs):
+ super(SettingsFrame, self).__init__(stream_id, **kwargs)
+
+ if settings and "ACK" in kwargs.get("flags", ()):
+ raise ValueError("Settings must be empty if ACK flag is set.")
+
+ #: A dictionary of the setting type byte to the value of the setting.
+ self.settings = settings or {}
+
+ def serialize_body(self):
+ return b''.join([_STRUCT_HL.pack(setting & 0xFF, value)
+ for setting, value in self.settings.items()])
+
+ def parse_body(self, data):
+ body_len = 0
+ for i in range(0, len(data), 6):
+ try:
+ name, value = _STRUCT_HL.unpack(data[i:i+6])
+ except struct.error:
+ raise InvalidFrameError("Invalid SETTINGS body")
+
+ self.settings[name] = value
+ body_len += 6
+
+ self.body_len = body_len
+
+
+class PushPromiseFrame(Padding, Frame):
+ """
+ The PUSH_PROMISE frame is used to notify the peer endpoint in advance of
+ streams the sender intends to initiate.
+ """
+ #: The flags defined for PUSH_PROMISE frames.
+ defined_flags = [
+ Flag('END_HEADERS', 0x04),
+ Flag('PADDED', 0x08)
+ ]
+
+ #: The type byte defined for PUSH_PROMISE frames.
+ type = 0x05
+
+ stream_association = _STREAM_ASSOC_HAS_STREAM
+
+ def __init__(self, stream_id, promised_stream_id=0, data=b'', **kwargs):
+ super(PushPromiseFrame, self).__init__(stream_id, **kwargs)
+
+ #: The stream ID that is promised by this frame.
+ self.promised_stream_id = promised_stream_id
+
+ #: The HPACK-encoded header block for the simulated request on the new
+ #: stream.
+ self.data = data
+
+ def serialize_body(self):
+ padding_data = self.serialize_padding_data()
+ padding = b'\0' * self.total_padding
+ data = _STRUCT_L.pack(self.promised_stream_id)
+ return b''.join([padding_data, data, self.data, padding])
+
+ def parse_body(self, data):
+ padding_data_length = self.parse_padding_data(data)
+
+ try:
+ self.promised_stream_id = _STRUCT_L.unpack(
+ data[padding_data_length:padding_data_length + 4]
+ )[0]
+ except struct.error:
+ raise InvalidFrameError("Invalid PUSH_PROMISE body")
+
+ self.data = data[padding_data_length + 4:].tobytes()
+ self.body_len = len(data)
+
+ if self.total_padding and self.total_padding >= self.body_len:
+ raise InvalidPaddingError("Padding is too long.")
+
+
+class PingFrame(Frame):
+ """
+ The PING frame is a mechanism for measuring a minimal round-trip time from
+ the sender, as well as determining whether an idle connection is still
+ functional. PING frames can be sent from any endpoint.
+ """
+ #: The flags defined for PING frames.
+ defined_flags = [Flag('ACK', 0x01)]
+
+ #: The type byte defined for PING frames.
+ type = 0x06
+
+ stream_association = _STREAM_ASSOC_NO_STREAM
+
+ def __init__(self, stream_id=0, opaque_data=b'', **kwargs):
+ super(PingFrame, self).__init__(stream_id, **kwargs)
+
+ #: The opaque data sent in this PING frame, as a bytestring.
+ self.opaque_data = opaque_data
+
+ def serialize_body(self):
+ if len(self.opaque_data) > 8:
+ raise InvalidFrameError(
+ "PING frame may not have more than 8 bytes of data, got %s" %
+ self.opaque_data
+ )
+
+ data = self.opaque_data
+ data += b'\x00' * (8 - len(self.opaque_data))
+ return data
+
+ def parse_body(self, data):
+ if len(data) != 8:
+ raise InvalidFrameError(
+ "PING frame must have 8 byte length: got %s" % len(data)
+ )
+
+ self.opaque_data = data.tobytes()
+ self.body_len = 8
+
+
+class GoAwayFrame(Frame):
+ """
+ The GOAWAY frame informs the remote peer to stop creating streams on this
+ connection. It can be sent from the client or the server. Once sent, the
+ sender will ignore frames sent on new streams for the remainder of the
+ connection.
+ """
+ #: The flags defined for GOAWAY frames.
+ defined_flags = []
+
+ #: The type byte defined for GOAWAY frames.
+ type = 0x07
+
+ stream_association = _STREAM_ASSOC_NO_STREAM
+
+ def __init__(self,
+ stream_id=0,
+ last_stream_id=0,
+ error_code=0,
+ additional_data=b'',
+ **kwargs):
+ super(GoAwayFrame, self).__init__(stream_id, **kwargs)
+
+ #: The last stream ID definitely seen by the remote peer.
+ self.last_stream_id = last_stream_id
+
+ #: The error code for connection teardown.
+ self.error_code = error_code
+
+ #: Any additional data sent in the GOAWAY.
+ self.additional_data = additional_data
+
+ def serialize_body(self):
+ data = _STRUCT_LL.pack(
+ self.last_stream_id & 0x7FFFFFFF,
+ self.error_code
+ )
+ data += self.additional_data
+
+ return data
+
+ def parse_body(self, data):
+ try:
+ self.last_stream_id, self.error_code = _STRUCT_LL.unpack(
+ data[:8]
+ )
+ except struct.error:
+ raise InvalidFrameError("Invalid GOAWAY body.")
+
+ self.body_len = len(data)
+
+ if len(data) > 8:
+ self.additional_data = data[8:].tobytes()
+
+
+class WindowUpdateFrame(Frame):
+ """
+ The WINDOW_UPDATE frame is used to implement flow control.
+
+ Flow control operates at two levels: on each individual stream and on the
+ entire connection.
+
+ Both types of flow control are hop by hop; that is, only between the two
+ endpoints. Intermediaries do not forward WINDOW_UPDATE frames between
+ dependent connections. However, throttling of data transfer by any receiver
+ can indirectly cause the propagation of flow control information toward the
+ original sender.
+ """
+ #: The flags defined for WINDOW_UPDATE frames.
+ defined_flags = []
+
+ #: The type byte defined for WINDOW_UPDATE frames.
+ type = 0x08
+
+ stream_association = _STREAM_ASSOC_EITHER
+
+ def __init__(self, stream_id, window_increment=0, **kwargs):
+ super(WindowUpdateFrame, self).__init__(stream_id, **kwargs)
+
+ #: The amount the flow control window is to be incremented.
+ self.window_increment = window_increment
+
+ def serialize_body(self):
+ return _STRUCT_L.pack(self.window_increment & 0x7FFFFFFF)
+
+ def parse_body(self, data):
+ try:
+ self.window_increment = _STRUCT_L.unpack(data)[0]
+ except struct.error:
+ raise InvalidFrameError("Invalid WINDOW_UPDATE body")
+
+ self.body_len = 4
+
+
+class HeadersFrame(Padding, Priority, Frame):
+ """
+ The HEADERS frame carries name-value pairs. It is used to open a stream.
+ HEADERS frames can be sent on a stream in the "open" or "half closed
+ (remote)" states.
+
+ The HeadersFrame class is actually basically a data frame in this
+ implementation, because of the requirement to control the sizes of frames.
+ A header block fragment that doesn't fit in an entire HEADERS frame needs
+ to be followed with CONTINUATION frames. From the perspective of the frame
+ building code the header block is an opaque data segment.
+ """
+ #: The flags defined for HEADERS frames.
+ defined_flags = [
+ Flag('END_STREAM', 0x01),
+ Flag('END_HEADERS', 0x04),
+ Flag('PADDED', 0x08),
+ Flag('PRIORITY', 0x20),
+ ]
+
+ #: The type byte defined for HEADERS frames.
+ type = 0x01
+
+ stream_association = _STREAM_ASSOC_HAS_STREAM
+
+ def __init__(self, stream_id, data=b'', **kwargs):
+ super(HeadersFrame, self).__init__(stream_id, **kwargs)
+
+ #: The HPACK-encoded header block.
+ self.data = data
+
+ def serialize_body(self):
+ padding_data = self.serialize_padding_data()
+ padding = b'\0' * self.total_padding
+
+ if 'PRIORITY' in self.flags:
+ priority_data = self.serialize_priority_data()
+ else:
+ priority_data = b''
+
+ return b''.join([padding_data, priority_data, self.data, padding])
+
+ def parse_body(self, data):
+ padding_data_length = self.parse_padding_data(data)
+ data = data[padding_data_length:]
+
+ if 'PRIORITY' in self.flags:
+ priority_data_length = self.parse_priority_data(data)
+ else:
+ priority_data_length = 0
+
+ self.body_len = len(data)
+ self.data = (
+ data[priority_data_length:len(data)-self.total_padding].tobytes()
+ )
+
+ if self.total_padding and self.total_padding >= self.body_len:
+ raise InvalidPaddingError("Padding is too long.")
+
+
+class ContinuationFrame(Frame):
+ """
+ The CONTINUATION frame is used to continue a sequence of header block
+ fragments. Any number of CONTINUATION frames can be sent on an existing
+ stream, as long as the preceding frame on the same stream is one of
+ HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set.
+
+ Much like the HEADERS frame, hyper treats this as an opaque data frame with
+ different flags and a different type.
+ """
+ #: The flags defined for CONTINUATION frames.
+ defined_flags = [Flag('END_HEADERS', 0x04)]
+
+ #: The type byte defined for CONTINUATION frames.
+ type = 0x09
+
+ stream_association = _STREAM_ASSOC_HAS_STREAM
+
+ def __init__(self, stream_id, data=b'', **kwargs):
+ super(ContinuationFrame, self).__init__(stream_id, **kwargs)
+
+ #: The HPACK-encoded header block.
+ self.data = data
+
+ def serialize_body(self):
+ return self.data
+
+ def parse_body(self, data):
+ self.data = data.tobytes()
+ self.body_len = len(data)
+
+
+class AltSvcFrame(Frame):
+ """
+ The ALTSVC frame is used to advertise alternate services that the current
+ host, or a different one, can understand. This frame is standardised as
+ part of RFC 7838.
+
+ This frame does no work to validate that the ALTSVC field parameter is
+ acceptable per the rules of RFC 7838.
+
+ .. note:: If the ``stream_id`` of this frame is nonzero, the origin field
+ must have zero length. Conversely, if the ``stream_id`` of this
+ frame is zero, the origin field must have nonzero length. Put
+ another way, a valid ALTSVC frame has ``stream_id != 0`` XOR
+ ``len(origin) != 0``.
+ """
+ type = 0xA
+
+ stream_association = _STREAM_ASSOC_EITHER
+
+ def __init__(self, stream_id, origin=b'', field=b'', **kwargs):
+ super(AltSvcFrame, self).__init__(stream_id, **kwargs)
+
+ if not isinstance(origin, bytes):
+ raise ValueError("AltSvc origin must be bytestring.")
+ if not isinstance(field, bytes):
+ raise ValueError("AltSvc field must be a bytestring.")
+ self.origin = origin
+ self.field = field
+
+ def serialize_body(self):
+ origin_len = _STRUCT_H.pack(len(self.origin))
+ return b''.join([origin_len, self.origin, self.field])
+
+ def parse_body(self, data):
+ try:
+ origin_len = _STRUCT_H.unpack(data[0:2])[0]
+ self.origin = data[2:2+origin_len].tobytes()
+
+ if len(self.origin) != origin_len:
+ raise InvalidFrameError("Invalid ALTSVC frame body.")
+
+ self.field = data[2+origin_len:].tobytes()
+ except (struct.error, ValueError):
+ raise InvalidFrameError("Invalid ALTSVC frame body.")
+
+ self.body_len = len(data)
+
+
+class ExtensionFrame(Frame):
+ """
+ ExtensionFrame is used to wrap frames which are not natively interpretable
+ by hyperframe.
+
+ Although certain byte prefixes are ordained by specification to have
+ certain contextual meanings, frames with other prefixes are not prohibited,
+ and may be used to communicate arbitrary meaning between HTTP/2 peers.
+
+ Thus, hyperframe, rather than raising an exception when such a frame is
+ encountered, wraps it in a generic frame to be properly acted upon by
+ upstream consumers which might have additional context on how to use it.
+
+ .. versionadded:: 5.0.0
+ """
+
+ stream_association = _STREAM_ASSOC_EITHER
+
+ def __init__(self, type, stream_id, **kwargs):
+ super(ExtensionFrame, self).__init__(stream_id, **kwargs)
+ self.type = type
+ self.flag_byte = None
+
+ def parse_flags(self, flag_byte):
+ """
+ For extension frames, we parse the flags by just storing a flag byte.
+ """
+ self.flag_byte = flag_byte
+
+ def parse_body(self, data):
+ self.body = data.tobytes()
+ self.body_len = len(data)
+
+ def serialize(self):
+ """
+ A broad override of the serialize method that ensures that the data
+ comes back out exactly as it came in. This should not be used in most
+ user code: it exists only as a helper method if frames need to be
+ reconstituted.
+ """
+ # Build the frame header.
+ # First, get the flags.
+ flags = self.flag_byte
+
+ header = _STRUCT_HBBBL.pack(
+ (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits
+ self.body_len & 0xFF,
+ self.type,
+ flags,
+ self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits.
+ )
+
+ return header + self.body
+
+
+_FRAME_CLASSES = [
+ DataFrame,
+ HeadersFrame,
+ PriorityFrame,
+ RstStreamFrame,
+ SettingsFrame,
+ PushPromiseFrame,
+ PingFrame,
+ GoAwayFrame,
+ WindowUpdateFrame,
+ ContinuationFrame,
+ AltSvcFrame,
+]
+#: FRAMES maps the type byte for each frame to the class used to represent that
+#: frame.
+FRAMES = {cls.type: cls for cls in _FRAME_CLASSES}
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/setup.cfg b/testing/web-platform/tests/tools/third_party/hyperframe/setup.cfg
new file mode 100644
index 0000000000..50220e135b
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/setup.cfg
@@ -0,0 +1,10 @@
+[wheel]
+universal = 1
+
+[tool:pytest]
+testpaths = test
+
+[egg_info]
+tag_build =
+tag_date = 0
+
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/setup.py b/testing/web-platform/tests/tools/third_party/hyperframe/setup.py
new file mode 100644
index 0000000000..1ab7212141
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/setup.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import itertools
+import os
+import re
+import sys
+
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+# Get the version
+version_regex = r'__version__ = ["\']([^"\']*)["\']'
+with open('hyperframe/__init__.py', 'r') as f:
+ text = f.read()
+ match = re.search(version_regex, text)
+
+ if match:
+ version = match.group(1)
+ else:
+ raise RuntimeError("No version number found!")
+
+# Stealing this from Kenneth Reitz
+if sys.argv[-1] == 'publish':
+ os.system('python setup.py sdist upload')
+ sys.exit()
+
+
+packages = ['hyperframe']
+
+setup(
+ name='hyperframe',
+ version=version,
+ description='HTTP/2 framing layer for Python',
+ long_description=open('README.rst').read() + '\n\n' + open('HISTORY.rst').read(),
+ author='Cory Benfield',
+ author_email='cory@lukasa.co.uk',
+ url='https://python-hyper.org/hyperframe/en/latest/',
+ packages=packages,
+ package_data={'': ['LICENSE', 'README.rst', 'CONTRIBUTORS.rst', 'HISTORY.rst']},
+ package_dir={'hyperframe': 'hyperframe'},
+ include_package_data=True,
+ license='MIT License',
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ ],
+)
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/test/test_flags.py b/testing/web-platform/tests/tools/third_party/hyperframe/test/test_flags.py
new file mode 100644
index 0000000000..62a6a30f67
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/test/test_flags.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+from hyperframe.frame import (
+ Flags, Flag,
+)
+import pytest
+
+
+class TestFlags(object):
+ def test_add(self):
+ flags = Flags([Flag("VALID_FLAG", 0x00)])
+ assert not flags
+
+ flags.add("VALID_FLAG")
+ flags.add("VALID_FLAG")
+ assert "VALID_FLAG" in flags
+ assert list(flags) == ["VALID_FLAG"]
+ assert len(flags) == 1
+
+ def test_remove(self):
+ flags = Flags([Flag("VALID_FLAG", 0x00)])
+ flags.add("VALID_FLAG")
+
+ flags.discard("VALID_FLAG")
+ assert "VALID_FLAG" not in flags
+ assert list(flags) == []
+ assert len(flags) == 0
+
+ # discarding elements not in the set should not throw an exception
+ flags.discard("END_STREAM")
+
+ def test_validation(self):
+ flags = Flags([Flag("VALID_FLAG", 0x00)])
+ flags.add("VALID_FLAG")
+ with pytest.raises(ValueError):
+ flags.add("INVALID_FLAG")
diff --git a/testing/web-platform/tests/tools/third_party/hyperframe/test/test_frames.py b/testing/web-platform/tests/tools/third_party/hyperframe/test/test_frames.py
new file mode 100644
index 0000000000..abfecb692d
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/hyperframe/test/test_frames.py
@@ -0,0 +1,791 @@
+# -*- coding: utf-8 -*-
+from hyperframe.frame import (
+ Frame, Flags, DataFrame, PriorityFrame, RstStreamFrame, SettingsFrame,
+ PushPromiseFrame, PingFrame, GoAwayFrame, WindowUpdateFrame, HeadersFrame,
+ ContinuationFrame, AltSvcFrame, ExtensionFrame
+)
+from hyperframe.exceptions import (
+ UnknownFrameError, InvalidPaddingError, InvalidFrameError
+)
+import pytest
+
+
+def decode_frame(frame_data):
+ f, length = Frame.parse_frame_header(frame_data[:9])
+ f.parse_body(memoryview(frame_data[9:9 + length]))
+ assert 9 + length == len(frame_data)
+ return f
+
+
+class TestGeneralFrameBehaviour(object):
+ def test_base_frame_ignores_flags(self):
+ f = Frame(stream_id=0)
+ flags = f.parse_flags(0xFF)
+ assert not flags
+ assert isinstance(flags, Flags)
+
+ def test_base_frame_cant_serialize(self):
+ f = Frame(stream_id=0)
+ with pytest.raises(NotImplementedError):
+ f.serialize()
+
+ def test_base_frame_cant_parse_body(self):
+ data = b''
+ f = Frame(stream_id=0)
+ with pytest.raises(NotImplementedError):
+ f.parse_body(data)
+
+ def test_parse_frame_header_unknown_type_strict(self):
+ with pytest.raises(UnknownFrameError) as excinfo:
+ Frame.parse_frame_header(
+ b'\x00\x00\x59\xFF\x00\x00\x00\x00\x01',
+ strict=True
+ )
+ exception = excinfo.value
+ assert exception.frame_type == 0xFF
+ assert exception.length == 0x59
+ assert str(exception) == (
+ "UnknownFrameError: Unknown frame type 0xFF received, "
+ "length 89 bytes"
+ )
+
+ def test_parse_frame_header_ignore_first_bit_of_stream_id(self):
+ s = b'\x00\x00\x00\x06\x01\x80\x00\x00\x00'
+ f, _ = Frame.parse_frame_header(s)
+
+ assert f.stream_id == 0
+
+ def test_parse_frame_header_unknown_type(self):
+ frame, length = Frame.parse_frame_header(
+ b'\x00\x00\x59\xFF\x00\x00\x00\x00\x01'
+ )
+ assert frame.type == 0xFF
+ assert length == 0x59
+ assert isinstance(frame, ExtensionFrame)
+ assert frame.stream_id == 1
+
+ def test_flags_are_persisted(self):
+ frame, length = Frame.parse_frame_header(
+ b'\x00\x00\x59\xFF\x09\x00\x00\x00\x01'
+ )
+ assert frame.type == 0xFF
+ assert length == 0x59
+ assert frame.flag_byte == 0x09
+
+ def test_parse_body_unknown_type(self):
+ frame = decode_frame(
+ b'\x00\x00\x0C\xFF\x00\x00\x00\x00\x01hello world!'
+ )
+ assert frame.body == b'hello world!'
+ assert frame.body_len == 12
+ assert frame.stream_id == 1
+
+ def test_can_round_trip_unknown_frames(self):
+ frame_data = b'\x00\x00\x0C\xFF\x00\x00\x00\x00\x01hello world!'
+ f = decode_frame(frame_data)
+ assert f.serialize() == frame_data
+
+ def test_repr(self, monkeypatch):
+ f = Frame(stream_id=0)
+ monkeypatch.setattr(Frame, "serialize_body", lambda _: b"body")
+ assert repr(f) == "Frame(Stream: 0; Flags: None): 626f6479"
+
+ monkeypatch.setattr(Frame, "serialize_body", lambda _: b"A"*25)
+ assert repr(f) == (
+ "Frame(Stream: 0; Flags: None): {}...".format("41"*10)
+ )
+
+ def test_cannot_parse_invalid_frame_header(self):
+ with pytest.raises(InvalidFrameError):
+ Frame.parse_frame_header(b'\x00\x00\x08\x00\x01\x00\x00\x00')
+
+
+class TestDataFrame(object):
+ payload = b'\x00\x00\x08\x00\x01\x00\x00\x00\x01testdata'
+ payload_with_padding = (
+ b'\x00\x00\x13\x00\x09\x00\x00\x00\x01\x0Atestdata' + b'\0' * 10
+ )
+
+ def test_data_frame_has_correct_flags(self):
+ f = DataFrame(1)
+ flags = f.parse_flags(0xFF)
+ assert flags == set([
+ 'END_STREAM', 'PADDED'
+ ])
+
+ @pytest.mark.parametrize('data', [
+ b'testdata',
+ memoryview(b'testdata')
+ ])
+ def test_data_frame_serializes_properly(self, data):
+ f = DataFrame(1)
+ f.flags = set(['END_STREAM'])
+ f.data = data
+
+ s = f.serialize()
+ assert s == self.payload
+
+ def test_data_frame_with_padding_serializes_properly(self):
+ f = DataFrame(1)
+ f.flags = set(['END_STREAM', 'PADDED'])
+ f.data = b'testdata'
+ f.pad_length = 10
+
+ s = f.serialize()
+ assert s == self.payload_with_padding
+
+ def test_data_frame_parses_properly(self):
+ f = decode_frame(self.payload)
+
+ assert isinstance(f, DataFrame)
+ assert f.flags == set(['END_STREAM'])
+ assert f.pad_length == 0
+ assert f.data == b'testdata'
+ assert f.body_len == 8
+
+ def test_data_frame_with_padding_parses_properly(self):
+ f = decode_frame(self.payload_with_padding)
+
+ assert isinstance(f, DataFrame)
+ assert f.flags == set(['END_STREAM', 'PADDED'])
+ assert f.pad_length == 10
+ assert f.data == b'testdata'
+ assert f.body_len == 19
+
+ def test_data_frame_with_invalid_padding_errors(self):
+ with pytest.raises(InvalidFrameError):
+ decode_frame(self.payload_with_padding[:9])
+
+ def test_data_frame_with_padding_calculates_flow_control_len(self):
+ f = DataFrame(1)
+ f.flags = set(['PADDED'])
+ f.data = b'testdata'
+ f.pad_length = 10
+
+ assert f.flow_controlled_length == 19
+
+ def test_data_frame_zero_length_padding_calculates_flow_control_len(self):
+ f = DataFrame(1)
+ f.flags = set(['PADDED'])
+ f.data = b'testdata'
+ f.pad_length = 0
+
+ assert f.flow_controlled_length == len(b'testdata') + 1
+
+ def test_data_frame_without_padding_calculates_flow_control_len(self):
+ f = DataFrame(1)
+ f.data = b'testdata'
+
+ assert f.flow_controlled_length == 8
+
+ def test_data_frame_comes_on_a_stream(self):
+ with pytest.raises(ValueError):
+ DataFrame(0)
+
+ def test_long_data_frame(self):
+ f = DataFrame(1)
+
+ # Use more than 256 bytes of data to force setting higher bits.
+ f.data = b'\x01' * 300
+ data = f.serialize()
+
+ # The top three bytes should be numerically equal to 300. That means
+ # they should read 00 01 2C.
+ # The weird double index trick is to ensure this test behaves equally
+ # on Python 2 and Python 3.
+ assert data[0] == b'\x00'[0]
+ assert data[1] == b'\x01'[0]
+ assert data[2] == b'\x2C'[0]
+
+ def test_body_length_behaves_correctly(self):
+ f = DataFrame(1)
+
+ f.data = b'\x01' * 300
+
+ # Initially the body length is zero. For now this is incidental, but
+ # I'm going to test it to ensure that the behaviour is codified. We
+ # should change this test if we change that.
+ assert f.body_len == 0
+
+ f.serialize()
+ assert f.body_len == 300
+
+ def test_data_frame_with_invalid_padding_fails_to_parse(self):
+ # This frame has a padding length of 6 bytes, but a total length of
+ # only 5.
+ data = b'\x00\x00\x05\x00\x0b\x00\x00\x00\x01\x06\x54\x65\x73\x74'
+
+ with pytest.raises(InvalidPaddingError):
+ decode_frame(data)
+
+ def test_data_frame_with_no_length_parses(self):
+ # Fixes issue with empty data frames raising InvalidPaddingError.
+ f = DataFrame(1)
+ f.data = b''
+ data = f.serialize()
+
+ new_frame = decode_frame(data)
+ assert new_frame.data == b''
+
+
+class TestPriorityFrame(object):
+ payload = b'\x00\x00\x05\x02\x00\x00\x00\x00\x01\x80\x00\x00\x04\x40'
+
+ def test_priority_frame_has_no_flags(self):
+ f = PriorityFrame(1)
+ flags = f.parse_flags(0xFF)
+ assert flags == set()
+ assert isinstance(flags, Flags)
+
+ def test_priority_frame_default_serializes_properly(self):
+ f = PriorityFrame(1)
+
+ assert f.serialize() == (
+ b'\x00\x00\x05\x02\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00'
+ )
+
+ def test_priority_frame_with_all_data_serializes_properly(self):
+ f = PriorityFrame(1)
+ f.depends_on = 0x04
+ f.stream_weight = 64
+ f.exclusive = True
+
+ assert f.serialize() == self.payload
+
+ def test_priority_frame_with_all_data_parses_properly(self):
+ f = decode_frame(self.payload)
+
+ assert isinstance(f, PriorityFrame)
+ assert f.flags == set()
+ assert f.depends_on == 4
+ assert f.stream_weight == 64
+ assert f.exclusive is True
+ assert f.body_len == 5
+
+ def test_priority_frame_comes_on_a_stream(self):
+ with pytest.raises(ValueError):
+ PriorityFrame(0)
+
+ def test_short_priority_frame_errors(self):
+ with pytest.raises(InvalidFrameError):
+ decode_frame(self.payload[:-2])
+
+
+class TestRstStreamFrame(object):
+ def test_rst_stream_frame_has_no_flags(self):
+ f = RstStreamFrame(1)
+ flags = f.parse_flags(0xFF)
+ assert not flags
+ assert isinstance(flags, Flags)
+
+ def test_rst_stream_frame_serializes_properly(self):
+ f = RstStreamFrame(1)
+ f.error_code = 420
+
+ s = f.serialize()
+ assert s == b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x01\xa4'
+
+ def test_rst_stream_frame_parses_properly(self):
+ s = b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x01\xa4'
+ f = decode_frame(s)
+
+ assert isinstance(f, RstStreamFrame)
+ assert f.flags == set()
+ assert f.error_code == 420
+ assert f.body_len == 4
+
+ def test_rst_stream_frame_comes_on_a_stream(self):
+ with pytest.raises(ValueError):
+ RstStreamFrame(0)
+
+ def test_rst_stream_frame_must_have_body_length_four(self):
+ f = RstStreamFrame(1)
+ with pytest.raises(ValueError):
+ f.parse_body(b'\x01')
+
+
+class TestSettingsFrame(object):
+ serialized = (
+ b'\x00\x00\x2A\x04\x01\x00\x00\x00\x00' + # Frame header
+ b'\x00\x01\x00\x00\x10\x00' + # HEADER_TABLE_SIZE
+ b'\x00\x02\x00\x00\x00\x00' + # ENABLE_PUSH
+ b'\x00\x03\x00\x00\x00\x64' + # MAX_CONCURRENT_STREAMS
+ b'\x00\x04\x00\x00\xFF\xFF' + # INITIAL_WINDOW_SIZE
+ b'\x00\x05\x00\x00\x40\x00' + # MAX_FRAME_SIZE
+ b'\x00\x06\x00\x00\xFF\xFF' + # MAX_HEADER_LIST_SIZE
+ b'\x00\x08\x00\x00\x00\x01' # ENABLE_CONNECT_PROTOCOL
+ )
+
+ settings = {
+ SettingsFrame.HEADER_TABLE_SIZE: 4096,
+ SettingsFrame.ENABLE_PUSH: 0,
+ SettingsFrame.MAX_CONCURRENT_STREAMS: 100,
+ SettingsFrame.INITIAL_WINDOW_SIZE: 65535,
+ SettingsFrame.MAX_FRAME_SIZE: 16384,
+ SettingsFrame.MAX_HEADER_LIST_SIZE: 65535,
+ SettingsFrame.ENABLE_CONNECT_PROTOCOL: 1,
+ }
+
+ def test_settings_frame_has_only_one_flag(self):
+ f = SettingsFrame()
+ flags = f.parse_flags(0xFF)
+ assert flags == set(['ACK'])
+
+ def test_settings_frame_serializes_properly(self):
+ f = SettingsFrame()
+ f.parse_flags(0xFF)
+ f.settings = self.settings
+
+ s = f.serialize()
+ assert s == self.serialized
+
+ def test_settings_frame_with_settings(self):
+ f = SettingsFrame(settings=self.settings)
+ assert f.settings == self.settings
+
+ def test_settings_frame_without_settings(self):
+ f = SettingsFrame()
+ assert f.settings == {}
+
+ def test_settings_frame_with_ack(self):
+ f = SettingsFrame(flags=('ACK',))
+ assert 'ACK' in f.flags
+
+ def test_settings_frame_ack_and_settings(self):
+ with pytest.raises(ValueError):
+ SettingsFrame(settings=self.settings, flags=('ACK',))
+
+ def test_settings_frame_parses_properly(self):
+ f = decode_frame(self.serialized)
+
+ assert isinstance(f, SettingsFrame)
+ assert f.flags == set(['ACK'])
+ assert f.settings == self.settings
+ assert f.body_len == 42
+
+ def test_settings_frames_never_have_streams(self):
+ with pytest.raises(ValueError):
+ SettingsFrame(stream_id=1)
+
+ def test_short_settings_frame_errors(self):
+ with pytest.raises(InvalidFrameError):
+ decode_frame(self.serialized[:-2])
+
+
+class TestPushPromiseFrame(object):
+ def test_push_promise_frame_flags(self):
+ f = PushPromiseFrame(1)
+ flags = f.parse_flags(0xFF)
+
+ assert flags == set(['END_HEADERS', 'PADDED'])
+
+ def test_push_promise_frame_serializes_properly(self):
+ f = PushPromiseFrame(1)
+ f.flags = set(['END_HEADERS'])
+ f.promised_stream_id = 4
+ f.data = b'hello world'
+
+ s = f.serialize()
+ assert s == (
+ b'\x00\x00\x0F\x05\x04\x00\x00\x00\x01' +
+ b'\x00\x00\x00\x04' +
+ b'hello world'
+ )
+
+ def test_push_promise_frame_parses_properly(self):
+ s = (
+ b'\x00\x00\x0F\x05\x04\x00\x00\x00\x01' +
+ b'\x00\x00\x00\x04' +
+ b'hello world'
+ )
+ f = decode_frame(s)
+
+ assert isinstance(f, PushPromiseFrame)
+ assert f.flags == set(['END_HEADERS'])
+ assert f.promised_stream_id == 4
+ assert f.data == b'hello world'
+ assert f.body_len == 15
+
+ def test_push_promise_frame_with_invalid_padding_fails_to_parse(self):
+ # This frame has a padding length of 6 bytes, but a total length of
+ # only 5.
+ data = b'\x00\x00\x05\x05\x08\x00\x00\x00\x01\x06\x54\x65\x73\x74'
+
+ with pytest.raises(InvalidPaddingError):
+ decode_frame(data)
+
+ def test_push_promise_frame_with_no_length_parses(self):
+ # Fixes issue with empty data frames raising InvalidPaddingError.
+ f = PushPromiseFrame(1)
+ f.data = b''
+ data = f.serialize()
+
+ new_frame = decode_frame(data)
+ assert new_frame.data == b''
+
+ def test_short_push_promise_errors(self):
+ s = (
+ b'\x00\x00\x0F\x05\x04\x00\x00\x00\x01' +
+ b'\x00\x00\x00' # One byte short
+ )
+
+ with pytest.raises(InvalidFrameError):
+ decode_frame(s)
+
+
+class TestPingFrame(object):
+ def test_ping_frame_has_only_one_flag(self):
+ f = PingFrame()
+ flags = f.parse_flags(0xFF)
+
+ assert flags == set(['ACK'])
+
+ def test_ping_frame_serializes_properly(self):
+ f = PingFrame()
+ f.parse_flags(0xFF)
+ f.opaque_data = b'\x01\x02'
+
+ s = f.serialize()
+ assert s == (
+ b'\x00\x00\x08\x06\x01\x00\x00\x00\x00\x01\x02\x00\x00\x00\x00\x00'
+ b'\x00'
+ )
+
+ def test_no_more_than_8_octets(self):
+ f = PingFrame()
+ f.opaque_data = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09'
+
+ with pytest.raises(ValueError):
+ f.serialize()
+
+ def test_ping_frame_parses_properly(self):
+ s = (
+ b'\x00\x00\x08\x06\x01\x00\x00\x00\x00\x01\x02\x00\x00\x00\x00\x00'
+ b'\x00'
+ )
+ f = decode_frame(s)
+
+ assert isinstance(f, PingFrame)
+ assert f.flags == set(['ACK'])
+ assert f.opaque_data == b'\x01\x02\x00\x00\x00\x00\x00\x00'
+ assert f.body_len == 8
+
+ def test_ping_frame_never_has_a_stream(self):
+ with pytest.raises(ValueError):
+ PingFrame(stream_id=1)
+
+ def test_ping_frame_has_no_more_than_body_length_8(self):
+ f = PingFrame()
+ with pytest.raises(ValueError):
+ f.parse_body(b'\x01\x02\x03\x04\x05\x06\x07\x08\x09')
+
+ def test_ping_frame_has_no_less_than_body_length_8(self):
+ f = PingFrame()
+ with pytest.raises(ValueError):
+ f.parse_body(b'\x01\x02\x03\x04\x05\x06\x07')
+
+
+class TestGoAwayFrame(object):
+ def test_go_away_has_no_flags(self):
+ f = GoAwayFrame()
+ flags = f.parse_flags(0xFF)
+
+ assert not flags
+ assert isinstance(flags, Flags)
+
+ def test_goaway_serializes_properly(self):
+ f = GoAwayFrame()
+ f.last_stream_id = 64
+ f.error_code = 32
+ f.additional_data = b'hello'
+
+ s = f.serialize()
+ assert s == (
+ b'\x00\x00\x0D\x07\x00\x00\x00\x00\x00' + # Frame header
+ b'\x00\x00\x00\x40' + # Last Stream ID
+ b'\x00\x00\x00\x20' + # Error Code
+ b'hello' # Additional data
+ )
+
+ def test_goaway_frame_parses_properly(self):
+ s = (
+ b'\x00\x00\x0D\x07\x00\x00\x00\x00\x00' + # Frame header
+ b'\x00\x00\x00\x40' + # Last Stream ID
+ b'\x00\x00\x00\x20' + # Error Code
+ b'hello' # Additional data
+ )
+ f = decode_frame(s)
+
+ assert isinstance(f, GoAwayFrame)
+ assert f.flags == set()
+ assert f.additional_data == b'hello'
+ assert f.body_len == 13
+
+ s = (
+ b'\x00\x00\x08\x07\x00\x00\x00\x00\x00' + # Frame header
+ b'\x00\x00\x00\x40' + # Last Stream ID
+ b'\x00\x00\x00\x20' + # Error Code
+ b'' # Additional data
+ )
+ f = decode_frame(s)
+
+ assert isinstance(f, GoAwayFrame)
+ assert f.flags == set()
+ assert f.additional_data == b''
+ assert f.body_len == 8
+
+ def test_goaway_frame_never_has_a_stream(self):
+ with pytest.raises(ValueError):
+ GoAwayFrame(stream_id=1)
+
+ def test_short_goaway_frame_errors(self):
+ s = (
+ b'\x00\x00\x0D\x07\x00\x00\x00\x00\x00' + # Frame header
+ b'\x00\x00\x00\x40' + # Last Stream ID
+ b'\x00\x00\x00' # short Error Code
+ )
+ with pytest.raises(InvalidFrameError):
+ decode_frame(s)
+
+
+class TestWindowUpdateFrame(object):
+ def test_window_update_has_no_flags(self):
+ f = WindowUpdateFrame(0)
+ flags = f.parse_flags(0xFF)
+
+ assert not flags
+ assert isinstance(flags, Flags)
+
+ def test_window_update_serializes_properly(self):
+ f = WindowUpdateFrame(0)
+ f.window_increment = 512
+
+ s = f.serialize()
+ assert s == b'\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x00\x02\x00'
+
+ def test_windowupdate_frame_parses_properly(self):
+ s = b'\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x00\x02\x00'
+ f = decode_frame(s)
+
+ assert isinstance(f, WindowUpdateFrame)
+ assert f.flags == set()
+ assert f.window_increment == 512
+ assert f.body_len == 4
+
+ def test_short_windowupdate_frame_errors(self):
+ s = b'\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x00\x02' # -1 byte
+
+ with pytest.raises(InvalidFrameError):
+ decode_frame(s)
+
+
+class TestHeadersFrame(object):
+ def test_headers_frame_flags(self):
+ f = HeadersFrame(1)
+ flags = f.parse_flags(0xFF)
+
+ assert flags == set(['END_STREAM', 'END_HEADERS',
+ 'PADDED', 'PRIORITY'])
+
+ def test_headers_frame_serializes_properly(self):
+ f = HeadersFrame(1)
+ f.flags = set(['END_STREAM', 'END_HEADERS'])
+ f.data = b'hello world'
+
+ s = f.serialize()
+ assert s == (
+ b'\x00\x00\x0B\x01\x05\x00\x00\x00\x01' +
+ b'hello world'
+ )
+
+ def test_headers_frame_parses_properly(self):
+ s = (
+ b'\x00\x00\x0B\x01\x05\x00\x00\x00\x01' +
+ b'hello world'
+ )
+ f = decode_frame(s)
+
+ assert isinstance(f, HeadersFrame)
+ assert f.flags == set(['END_STREAM', 'END_HEADERS'])
+ assert f.data == b'hello world'
+ assert f.body_len == 11
+
+ def test_headers_frame_with_priority_parses_properly(self):
+ # This test also tests that we can receive a HEADERS frame with no
+ # actual headers on it. This is technically possible.
+ s = (
+ b'\x00\x00\x05\x01\x20\x00\x00\x00\x01' +
+ b'\x80\x00\x00\x04\x40'
+ )
+ f = decode_frame(s)
+
+ assert isinstance(f, HeadersFrame)
+ assert f.flags == set(['PRIORITY'])
+ assert f.data == b''
+ assert f.depends_on == 4
+ assert f.stream_weight == 64
+ assert f.exclusive is True
+ assert f.body_len == 5
+
+ def test_headers_frame_with_priority_serializes_properly(self):
+ # This test also tests that we can receive a HEADERS frame with no
+ # actual headers on it. This is technically possible.
+ s = (
+ b'\x00\x00\x05\x01\x20\x00\x00\x00\x01' +
+ b'\x80\x00\x00\x04\x40'
+ )
+ f = HeadersFrame(1)
+ f.flags = set(['PRIORITY'])
+ f.data = b''
+ f.depends_on = 4
+ f.stream_weight = 64
+ f.exclusive = True
+
+ assert f.serialize() == s
+
+ def test_headers_frame_with_invalid_padding_fails_to_parse(self):
+ # This frame has a padding length of 6 bytes, but a total length of
+ # only 5.
+ data = b'\x00\x00\x05\x01\x08\x00\x00\x00\x01\x06\x54\x65\x73\x74'
+
+ with pytest.raises(InvalidPaddingError):
+ decode_frame(data)
+
+ def test_headers_frame_with_no_length_parses(self):
+ # Fixes issue with empty data frames raising InvalidPaddingError.
+ f = HeadersFrame(1)
+ f.data = b''
+ data = f.serialize()
+
+ new_frame = decode_frame(data)
+ assert new_frame.data == b''
+
+
+class TestContinuationFrame(object):
+ def test_continuation_frame_flags(self):
+ f = ContinuationFrame(1)
+ flags = f.parse_flags(0xFF)
+
+ assert flags == set(['END_HEADERS'])
+
+ def test_continuation_frame_serializes(self):
+ f = ContinuationFrame(1)
+ f.parse_flags(0x04)
+ f.data = b'hello world'
+
+ s = f.serialize()
+ assert s == (
+ b'\x00\x00\x0B\x09\x04\x00\x00\x00\x01' +
+ b'hello world'
+ )
+
+ def test_continuation_frame_parses_properly(self):
+ s = b'\x00\x00\x0B\x09\x04\x00\x00\x00\x01hello world'
+ f = decode_frame(s)
+
+ assert isinstance(f, ContinuationFrame)
+ assert f.flags == set(['END_HEADERS'])
+ assert f.data == b'hello world'
+ assert f.body_len == 11
+
+
+class TestAltSvcFrame(object):
+ payload_with_origin = (
+ b'\x00\x00\x31' # Length
+ b'\x0A' # Type
+ b'\x00' # Flags
+ b'\x00\x00\x00\x00' # Stream ID
+ b'\x00\x0B' # Origin len
+ b'example.com' # Origin
+ b'h2="alt.example.com:8000", h2=":443"' # Field Value
+ )
+ payload_without_origin = (
+ b'\x00\x00\x13' # Length
+ b'\x0A' # Type
+ b'\x00' # Flags
+ b'\x00\x00\x00\x01' # Stream ID
+ b'\x00\x00' # Origin len
+ b'' # Origin
+ b'h2=":8000"; ma=60' # Field Value
+ )
+ payload_with_origin_and_stream = (
+ b'\x00\x00\x36' # Length
+ b'\x0A' # Type
+ b'\x00' # Flags
+ b'\x00\x00\x00\x01' # Stream ID
+ b'\x00\x0B' # Origin len
+ b'example.com' # Origin
+ b'Alt-Svc: h2=":443"; ma=2592000; persist=1' # Field Value
+ )
+
+ def test_altsvc_frame_flags(self):
+ f = AltSvcFrame(stream_id=0)
+ flags = f.parse_flags(0xFF)
+
+ assert flags == set()
+
+ def test_altsvc_frame_with_origin_serializes_properly(self):
+ f = AltSvcFrame(stream_id=0)
+ f.origin = b'example.com'
+ f.field = b'h2="alt.example.com:8000", h2=":443"'
+
+ s = f.serialize()
+ assert s == self.payload_with_origin
+
+ def test_altsvc_frame_with_origin_parses_properly(self):
+ f = decode_frame(self.payload_with_origin)
+
+ assert isinstance(f, AltSvcFrame)
+ assert f.origin == b'example.com'
+ assert f.field == b'h2="alt.example.com:8000", h2=":443"'
+ assert f.body_len == 49
+ assert f.stream_id == 0
+
+ def test_altsvc_frame_without_origin_serializes_properly(self):
+ f = AltSvcFrame(stream_id=1, origin=b'', field=b'h2=":8000"; ma=60')
+ s = f.serialize()
+ assert s == self.payload_without_origin
+
+ def test_altsvc_frame_without_origin_parses_properly(self):
+ f = decode_frame(self.payload_without_origin)
+
+ assert isinstance(f, AltSvcFrame)
+ assert f.origin == b''
+ assert f.field == b'h2=":8000"; ma=60'
+ assert f.body_len == 19
+ assert f.stream_id == 1
+
+ def test_altsvc_frame_without_origin_parses_with_good_repr(self):
+ f = decode_frame(self.payload_without_origin)
+
+ assert repr(f) == (
+ "AltSvcFrame(Stream: 1; Flags: None): 000068323d223a383030..."
+ )
+
+ def test_altsvc_frame_with_origin_and_stream_serializes_properly(self):
+ # This frame is not valid, but we allow it to be serialized anyway.
+ f = AltSvcFrame(stream_id=1)
+ f.origin = b'example.com'
+ f.field = b'Alt-Svc: h2=":443"; ma=2592000; persist=1'
+
+ assert f.serialize() == self.payload_with_origin_and_stream
+
+ def test_short_altsvc_frame_errors(self):
+ with pytest.raises(InvalidFrameError):
+ decode_frame(self.payload_with_origin[:12])
+
+ with pytest.raises(InvalidFrameError):
+ decode_frame(self.payload_with_origin[:10])
+
+ def test_altsvc_with_unicode_origin_fails(self):
+ with pytest.raises(ValueError):
+ AltSvcFrame(
+ stream_id=0, origin=u'hello', field=b'h2=":8000"; ma=60'
+
+ )
+
+ def test_altsvc_with_unicode_field_fails(self):
+ with pytest.raises(ValueError):
+ AltSvcFrame(
+ stream_id=0, origin=b'hello', field=u'h2=":8000"; ma=60'
+ )