Commit 95492e9f authored by Luke Zielinski's avatar Luke Zielinski Committed by Chromium LUCI CQ

Roll wpt tooling.

This rolls up to SHA 0fb69997a8b5154bf39ba3ffa586c2aa25442da8

Change-Id: I04ceba8cf7238ff660f3aa3aad70041656ce26be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2585624
Commit-Queue: Robert Ma <robertma@chromium.org>
Auto-Submit: Luke Z <lpz@chromium.org>
Reviewed-by: default avatarRobert Ma <robertma@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835820}
parent 314cbf4c
......@@ -22,7 +22,7 @@ Local Modifications: None
Name: web-platform-tests - Test Suites for Web Platform specifications
Short Name: wpt
URL: https://github.com/web-platform-tests/wpt/
Version: da46f68d5b82371132b4f877e05b9b457de89af9
Version: 0fb69997a8b5154bf39ba3ffa586c2aa25442da8
License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
License File: wpt/wpt/LICENSE.md
Security Critical: no
......
......@@ -9,7 +9,7 @@ cd $DIR
TARGET_DIR=$DIR/wpt
REMOTE_REPO="https://github.com/web-platform-tests/wpt.git"
WPT_HEAD=da46f68d5b82371132b4f877e05b9b457de89af9
WPT_HEAD=0fb69997a8b5154bf39ba3ffa586c2aa25442da8
function clone {
# Remove existing repo if already exists.
......
......@@ -4,7 +4,7 @@ import sys as _sys
__all__ = ['Enum', 'IntEnum', 'unique']
version = 1, 1, 6
version = 1, 1, 10
pyver = float('%s.%s' % _sys.version_info[:2])
......@@ -183,7 +183,8 @@ class EnumMeta(type):
else:
del classdict['_order_']
if pyver < 3.0:
_order_ = _order_.replace(',', ' ').split()
if isinstance(_order_, basestring):
_order_ = _order_.replace(',', ' ').split()
aliases = [name for name in members if name not in _order_]
_order_ += aliases
......@@ -463,7 +464,7 @@ class EnumMeta(type):
_order_.append(member_name)
# only set _order_ in classdict if name/value was not from a mapping
if not isinstance(item, basestring):
classdict['_order_'] = ' '.join(_order_)
classdict['_order_'] = _order_
enum_class = metacls.__new__(metacls, class_name, bases, classdict)
# TODO: replace the frame hack if a blessed way to know the calling
......
......@@ -5,4 +5,4 @@ h2
A HTTP/2 implementation.
"""
__version__ = '3.0.1'
__version__ = '3.2.0'
......@@ -29,7 +29,7 @@ class DummyLogger(object):
"""
An Logger object that does not actual logging, hence a DummyLogger.
For the class the log operation is merely a no-op. The intent is to avoid
For the class the log operation is merely a no-op. The intent is to avoid
conditionals being sprinkled throughout the hyper-h2 code for calls to
logging functions when no logger is passed into the corresponding object.
"""
......@@ -42,6 +42,12 @@ class DummyLogger(object):
"""
pass
def trace(self, *vargs, **kwargs):
"""
No-op logging. Only level needed for now.
"""
pass
class H2Configuration(object):
"""
......
......@@ -21,7 +21,7 @@ from hpack.exceptions import HPACKError, OversizedHeaderListError
from .config import H2Configuration
from .errors import ErrorCodes, _error_code_from_int
from .events import (
WindowUpdated, RemoteSettingsChanged, PingAcknowledged,
WindowUpdated, RemoteSettingsChanged, PingReceived, PingAckReceived,
SettingsAcknowledged, ConnectionTerminated, PriorityUpdated,
AlternativeServiceAvailable, UnknownFrameReceived
)
......@@ -33,7 +33,7 @@ from .exceptions import (
from .frame_buffer import FrameBuffer
from .settings import Settings, SettingCodes
from .stream import H2Stream, StreamClosedBy
from .utilities import guard_increment_window
from .utilities import SizeLimitDict, guard_increment_window
from .windows import WindowManager
......@@ -281,6 +281,9 @@ class H2Connection(object):
# The initial default value of SETTINGS_MAX_HEADER_LIST_SIZE.
DEFAULT_MAX_HEADER_LIST_SIZE = 2**16
# Keep in memory limited amount of results for streams closes
MAX_CLOSED_STREAMS = 2**16
def __init__(self, config=None):
self.state_machine = H2ConnectionStateMachine()
self.streams = {}
......@@ -325,7 +328,7 @@ class H2Connection(object):
)
self.remote_settings = Settings(client=not self.config.client_side)
# The curent value of the connection flow control windows on the
# The current value of the connection flow control windows on the
# connection.
self.outbound_flow_control_window = (
self.remote_settings.initial_window_size
......@@ -347,7 +350,7 @@ class H2Connection(object):
self._header_frames = []
# Data that needs to be sent.
self._data_to_send = b''
self._data_to_send = bytearray()
# Keeps track of how streams are closed.
# Used to ensure that we don't blow up in the face of frames that were
......@@ -355,7 +358,9 @@ class H2Connection(object):
# Also used to determine whether we should consider a frame received
# while a stream is closed as either a stream error or a connection
# error.
self._closed_streams = {}
self._closed_streams = SizeLimitDict(
size_limit=self.MAX_CLOSED_STREAMS
)
# The flow control window manager for the connection.
self._inbound_flow_control_window_manager = WindowManager(
......@@ -909,16 +914,21 @@ class H2Connection(object):
frames = stream.increase_flow_control_window(
increment
)
self.config.logger.debug(
"Increase stream ID %d flow control window by %d",
stream_id, increment
)
else:
self._inbound_flow_control_window_manager.window_opened(increment)
f = WindowUpdateFrame(0)
f.window_increment = increment
frames = [f]
self.config.logger.debug(
"Increase stream ID %d flow control window by %d",
stream_id, increment
)
self.config.logger.debug(
"Increase connection flow control window by %d", increment
)
self._prepare_for_sending(frames)
def push_stream(self, stream_id, promised_stream_id, request_headers):
......@@ -1327,7 +1337,7 @@ class H2Connection(object):
self._prepare_for_sending(frames)
def data_to_send(self, amt=None):
def data_to_send(self, amount=None):
"""
Returns some data for sending out of the internal data buffer.
......@@ -1336,19 +1346,19 @@ class H2Connection(object):
or less if that much data is not available. It does not perform any
I/O, and so uses a different name.
:param amt: (optional) The maximum amount of data to return. If not
:param amount: (optional) The maximum amount of data to return. If not
set, or set to ``None``, will return as much data as possible.
:type amt: ``int``
:type amount: ``int``
:returns: A bytestring containing the data to send on the wire.
:rtype: ``bytes``
"""
if amt is None:
data = self._data_to_send
self._data_to_send = b''
if amount is None:
data = bytes(self._data_to_send)
self._data_to_send = bytearray()
return data
else:
data = self._data_to_send[:amt]
self._data_to_send = self._data_to_send[amt:]
data = bytes(self._data_to_send[:amount])
self._data_to_send = self._data_to_send[amount:]
return data
def clear_outbound_data_buffer(self):
......@@ -1361,7 +1371,7 @@ class H2Connection(object):
This method should not normally be used, but is made available to avoid
exposing implementation details.
"""
self._data_to_send = b''
self._data_to_send = bytearray()
def _acknowledge_settings(self):
"""
......@@ -1440,7 +1450,7 @@ class H2Connection(object):
:returns: A list of events that the remote peer triggered by sending
this data.
"""
self.config.logger.debug(
self.config.logger.trace(
"Process received data on connection. Received data: %r", data
)
......@@ -1624,6 +1634,35 @@ class H2Connection(object):
return frames, events + stream_events
def _handle_data_on_closed_stream(self, events, exc, frame):
# This stream is already closed - and yet we received a DATA frame.
# The received DATA frame counts towards the connection flow window.
# We need to manually to acknowledge the DATA frame to update the flow
# window of the connection. Otherwise the whole connection stalls due
# the inbound flow window being 0.
frames = []
conn_manager = self._inbound_flow_control_window_manager
conn_increment = conn_manager.process_bytes(
frame.flow_controlled_length
)
if conn_increment:
f = WindowUpdateFrame(0)
f.window_increment = conn_increment
frames.append(f)
self.config.logger.debug(
"Received DATA frame on closed stream %d - "
"auto-emitted a WINDOW_UPDATE by %d",
frame.stream_id, conn_increment
)
f = RstStreamFrame(exc.stream_id)
f.error_code = exc.error_code
frames.append(f)
self.config.logger.debug(
"Stream %d already CLOSED or cleaned up - "
"auto-emitted a RST_FRAME" % frame.stream_id
)
return frames, events + exc._events
def _receive_data_frame(self, frame):
"""
Receive a data frame on the connection.
......@@ -1636,12 +1675,19 @@ class H2Connection(object):
self._inbound_flow_control_window_manager.window_consumed(
flow_controlled_length
)
stream = self._get_stream_by_id(frame.stream_id)
frames, stream_events = stream.receive_data(
frame.data,
'END_STREAM' in frame.flags,
flow_controlled_length
)
try:
stream = self._get_stream_by_id(frame.stream_id)
frames, stream_events = stream.receive_data(
frame.data,
'END_STREAM' in frame.flags,
flow_controlled_length
)
except StreamClosedError as e:
# This stream is either marked as CLOSED or already gone from our
# internal state.
return self._handle_data_on_closed_stream(events, e, frame)
return frames, events + stream_events
def _receive_settings_frame(self, frame):
......@@ -1717,15 +1763,19 @@ class H2Connection(object):
flags = []
if 'ACK' in frame.flags:
evt = PingAcknowledged()
evt.ping_data = frame.opaque_data
events.append(evt)
evt = PingAckReceived()
else:
evt = PingReceived()
# automatically ACK the PING with the same 'opaque data'
f = PingFrame(0)
f.flags = {'ACK'}
f.opaque_data = frame.opaque_data
flags.append(f)
evt.ping_data = frame.opaque_data
events.append(evt)
return flags, events
def _receive_rst_stream_frame(self, frame):
......
......@@ -356,22 +356,51 @@ class RemoteSettingsChanged(Event):
)
class PingReceived(Event):
"""
The PingReceived event is fired whenever a PING is received. It contains
the 'opaque data' of the PING frame. A ping acknowledgment with the same
'opaque data' is automatically emitted after receiving a ping.
.. versionadded:: 3.1.0
"""
def __init__(self):
#: The data included on the ping.
self.ping_data = None
def __repr__(self):
return "<PingReceived ping_data:%s>" % (
_bytes_representation(self.ping_data),
)
class PingAcknowledged(Event):
"""
The PingAcknowledged event is fired whenever a user-emitted PING is
acknowledged. This contains the data in the ACK'ed PING, allowing the
user to correlate PINGs and calculate RTT.
Same as PingAckReceived.
.. deprecated:: 3.1.0
"""
def __init__(self):
#: The data included on the ping.
self.ping_data = None
def __repr__(self):
return "<PingAcknowledged ping_data:%s>" % (
return "<PingAckReceived ping_data:%s>" % (
_bytes_representation(self.ping_data),
)
class PingAckReceived(PingAcknowledged):
"""
The PingAckReceived event is fired whenever a PING acknowledgment is
received. It contains the 'opaque data' of the PING+ACK frame, allowing the
user to correlate PINGs and calculate RTT.
.. versionadded:: 3.1.0
"""
pass
class StreamEnded(Event):
"""
The StreamEnded event is fired whenever a stream is ended by a remote
......
......@@ -15,6 +15,12 @@ from hyperframe.frame import SettingsFrame
from h2.errors import ErrorCodes
from h2.exceptions import InvalidSettingsValueError
try:
from collections.abc import MutableMapping
except ImportError: # pragma: no cover
# Python 2.7 compatibility
from collections import MutableMapping
class SettingCodes(enum.IntEnum):
"""
......@@ -49,6 +55,10 @@ class SettingCodes(enum.IntEnum):
#: and value in octets plus an overhead of 32 octets for each header field.
MAX_HEADER_LIST_SIZE = SettingsFrame.MAX_HEADER_LIST_SIZE
#: This setting can be used to enable the connect protocol. To enable on a
#: client set this to 1.
ENABLE_CONNECT_PROTOCOL = SettingsFrame.ENABLE_CONNECT_PROTOCOL
def _setting_code_from_int(code):
"""
......@@ -88,7 +98,7 @@ class ChangedSetting:
)
class Settings(collections.MutableMapping):
class Settings(MutableMapping):
"""
An object that encapsulates HTTP/2 settings state.
......@@ -135,6 +145,7 @@ class Settings(collections.MutableMapping):
SettingCodes.ENABLE_PUSH: collections.deque([int(client)]),
SettingCodes.INITIAL_WINDOW_SIZE: collections.deque([65535]),
SettingCodes.MAX_FRAME_SIZE: collections.deque([16384]),
SettingCodes.ENABLE_CONNECT_PROTOCOL: collections.deque([0]),
}
if initial_values is not None:
for key, value in initial_values.items():
......@@ -243,6 +254,18 @@ class Settings(collections.MutableMapping):
def max_header_list_size(self, value):
self[SettingCodes.MAX_HEADER_LIST_SIZE] = value
@property
def enable_connect_protocol(self):
"""
The current value of the :data:`ENABLE_CONNECT_PROTOCOL
<h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL>` setting.
"""
return self[SettingCodes.ENABLE_CONNECT_PROTOCOL]
@enable_connect_protocol.setter
def enable_connect_protocol(self, value):
self[SettingCodes.ENABLE_CONNECT_PROTOCOL] = value
# Implement the MutableMapping API.
def __getitem__(self, key):
val = self._settings[key][0]
......@@ -292,7 +315,7 @@ class Settings(collections.MutableMapping):
return NotImplemented
def _validate_setting(setting, value):
def _validate_setting(setting, value): # noqa: C901
"""
Confirms that a specific setting has a well-formed value. If the setting is
invalid, returns an error code. Otherwise, returns 0 (NO_ERROR).
......@@ -309,5 +332,8 @@ def _validate_setting(setting, value):
elif setting == SettingCodes.MAX_HEADER_LIST_SIZE:
if value < 0:
return ErrorCodes.PROTOCOL_ERROR
elif setting == SettingCodes.ENABLE_CONNECT_PROTOCOL:
if value not in (0, 1):
return ErrorCodes.PROTOCOL_ERROR
return 0
......@@ -378,41 +378,6 @@ class H2StreamStateMachine(object):
"""
raise ProtocolError("Attempted to push on closed stream.")
def window_on_closed_stream(self, previous_state):
"""
Called when a WINDOW_UPDATE frame is received on an already-closed
stream.
If we sent an END_STREAM frame, we just ignore the frame, as instructed
in RFC 7540 Section 5.1. Technically we should eventually consider
WINDOW_UPDATE in this state an error, but we don't have access to a
clock so we just always allow it. If we closed the stream for any other
reason, we behave as we do for receiving any other frame on a closed
stream.
"""
assert self.stream_closed_by is not None
if self.stream_closed_by == StreamClosedBy.SEND_END_STREAM:
return []
return self.recv_on_closed_stream(previous_state)
def reset_on_closed_stream(self, previous_state):
"""
Called when a RST_STREAM frame is received on an already-closed stream.
If we sent an END_STREAM frame, we just ignore the frame, as instructed
in RFC 7540 Section 5.1. Technically we should eventually consider
RST_STREAM in this state an error, but we don't have access to a clock
so we just always allow it. If we closed the stream for any other
reason, we behave as we do for receiving any other frame on a closed
stream.
"""
assert self.stream_closed_by is not None
if self.stream_closed_by is StreamClosedBy.SEND_END_STREAM:
return []
return self.recv_on_closed_stream(previous_state)
def send_informational_response(self, previous_state):
"""
Called when an informational header block is sent (that is, a block
......@@ -740,11 +705,12 @@ _transitions = {
# > WINDOW_UPDATE or RST_STREAM frames can be received in this state
# > for a short period after a DATA or HEADERS frame containing a
# > END_STREAM flag is sent.
# > END_STREAM flag is sent, as instructed in RFC 7540 Section 5.1. But we
# > don't have access to a clock so we just always allow it.
(StreamState.CLOSED, StreamInputs.RECV_WINDOW_UPDATE):
(H2StreamStateMachine.window_on_closed_stream, StreamState.CLOSED),
(None, StreamState.CLOSED),
(StreamState.CLOSED, StreamInputs.RECV_RST_STREAM):
(H2StreamStateMachine.reset_on_closed_stream, StreamState.CLOSED),
(None, StreamState.CLOSED),
# > A receiver MUST treat the receipt of a PUSH_PROMISE on a stream that is
# > neither "open" nor "half-closed (local)" as a connection error of type
......@@ -788,7 +754,7 @@ class H2Stream(object):
self.max_outbound_frame_size = None
self.request_method = None
# The curent value of the outbound stream flow control window
# The current value of the outbound stream flow control window
self.outbound_flow_control_window = outbound_window_size
# The flow control manager.
......
......@@ -33,6 +33,7 @@ _ALLOWED_PSEUDO_HEADER_FIELDS = frozenset([
b':authority', u':authority',
b':path', u':path',
b':status', u':status',
b':protocol', u':protocol',
])
......@@ -47,13 +48,19 @@ _REQUEST_ONLY_HEADERS = frozenset([
b':scheme', u':scheme',
b':path', u':path',
b':authority', u':authority',
b':method', u':method'
b':method', u':method',
b':protocol', u':protocol',
])
_RESPONSE_ONLY_HEADERS = frozenset([b':status', u':status'])
# A Set of pseudo headers that are only valid if the method is
# CONNECT, see RFC 8441 § 5
_CONNECT_REQUEST_ONLY_HEADERS = frozenset([b':protocol', u':protocol'])
if sys.version_info[0] == 2: # Python 2.X
_WHITESPACE = frozenset(whitespace)
else: # Python 3.3+
......@@ -323,6 +330,7 @@ def _reject_pseudo_header_fields(headers, hdr_validation_flags):
"""
seen_pseudo_header_fields = set()
seen_regular_header = False
method = None
for header in headers:
if _custom_startswith(header[0], b':', u':'):
......@@ -344,6 +352,12 @@ def _reject_pseudo_header_fields(headers, hdr_validation_flags):
"Received custom pseudo-header field %s" % header[0]
)
if header[0] in (b':method', u':method'):
if not isinstance(header[1], bytes):
method = header[1].encode('utf-8')
else:
method = header[1]
else:
seen_regular_header = True
......@@ -351,11 +365,12 @@ def _reject_pseudo_header_fields(headers, hdr_validation_flags):
# Check the pseudo-headers we got to confirm they're acceptable.
_check_pseudo_header_field_acceptability(
seen_pseudo_header_fields, hdr_validation_flags
seen_pseudo_header_fields, method, hdr_validation_flags
)
def _check_pseudo_header_field_acceptability(pseudo_headers,
method,
hdr_validation_flags):
"""
Given the set of pseudo-headers present in a header block and the
......@@ -394,6 +409,13 @@ def _check_pseudo_header_field_acceptability(pseudo_headers,
"Encountered response-only headers %s" %
invalid_request_headers
)
if method != b'CONNECT':
invalid_headers = pseudo_headers & _CONNECT_REQUEST_ONLY_HEADERS
if invalid_headers:
raise ProtocolError(
"Encountered connect-request-only headers %s" %
invalid_headers
)
def _validate_host_authority_header(headers):
......@@ -617,3 +639,22 @@ def validate_outbound_headers(headers, hdr_validation_flags):
headers = _check_path_header(headers, hdr_validation_flags)
return headers
class SizeLimitDict(collections.OrderedDict):
def __init__(self, *args, **kwargs):
self._size_limit = kwargs.pop("size_limit", None)
super(SizeLimitDict, self).__init__(*args, **kwargs)
self._check_size_limit()
def __setitem__(self, key, value):
super(SizeLimitDict, self).__setitem__(key, value)
self._check_size_limit()
def _check_size_limit(self):
if self._size_limit is not None:
while len(self) > self._size_limit:
self.popitem(last=False)
......@@ -5,4 +5,4 @@ hyperframe
A module for providing a pure-Python HTTP/2 framing layer.
"""
__version__ = '5.1.0'
__version__ = '5.2.0'
......@@ -7,11 +7,16 @@ 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(collections.MutableSet):
class Flags(MutableSet):
"""
A simple MutableSet implementation that will only accept known flags as
elements.
......
......@@ -395,6 +395,8 @@ class SettingsFrame(Frame):
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)
......
......@@ -133,6 +133,8 @@ def is_bad_port(port):
2049, # nfs
3659, # apple-sasl
4045, # lockd
5060, # sip
5061, # sips
6000, # x11
6665, # irc (alternate)
6666, # irc (alternate)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment