1# -*- coding: utf-8 -*-
2"""
3h2/events
4~~~~~~~~~
5
6Defines Event types for HTTP/2.
7
8Events are returned by the H2 state machine to allow implementations to keep
9track of events triggered by receiving data. Each time data is provided to the
10H2 state machine it processes the data and returns a list of Event objects.
11"""
12import binascii
13
14from .settings import ChangedSetting, _setting_code_from_int
15
16
17class Event(object):
18    """
19    Base class for h2 events.
20    """
21    pass
22
23
24class RequestReceived(Event):
25    """
26    The RequestReceived event is fired whenever request headers are received.
27    This event carries the HTTP headers for the given request and the stream ID
28    of the new stream.
29
30    .. versionchanged:: 2.3.0
31       Changed the type of ``headers`` to :class:`HeaderTuple
32       <hpack:hpack.HeaderTuple>`. This has no effect on current users.
33
34    .. versionchanged:: 2.4.0
35       Added ``stream_ended`` and ``priority_updated`` properties.
36    """
37    def __init__(self):
38        #: The Stream ID for the stream this request was made on.
39        self.stream_id = None
40
41        #: The request headers.
42        self.headers = None
43
44        #: If this request also ended the stream, the associated
45        #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available
46        #: here.
47        #:
48        #: .. versionadded:: 2.4.0
49        self.stream_ended = None
50
51        #: If this request also had associated priority information, the
52        #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>`
53        #: event will be available here.
54        #:
55        #: .. versionadded:: 2.4.0
56        self.priority_updated = None
57
58    def __repr__(self):
59        return "<RequestReceived stream_id:%s, headers:%s>" % (
60            self.stream_id, self.headers
61        )
62
63
64class ResponseReceived(Event):
65    """
66    The ResponseReceived event is fired whenever response headers are received.
67    This event carries the HTTP headers for the given response and the stream
68    ID of the new stream.
69
70    .. versionchanged:: 2.3.0
71       Changed the type of ``headers`` to :class:`HeaderTuple
72       <hpack:hpack.HeaderTuple>`. This has no effect on current users.
73
74   .. versionchanged:: 2.4.0
75      Added ``stream_ended`` and ``priority_updated`` properties.
76    """
77    def __init__(self):
78        #: The Stream ID for the stream this response was made on.
79        self.stream_id = None
80
81        #: The response headers.
82        self.headers = None
83
84        #: If this response also ended the stream, the associated
85        #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available
86        #: here.
87        #:
88        #: .. versionadded:: 2.4.0
89        self.stream_ended = None
90
91        #: If this response also had associated priority information, the
92        #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>`
93        #: event will be available here.
94        #:
95        #: .. versionadded:: 2.4.0
96        self.priority_updated = None
97
98    def __repr__(self):
99        return "<ResponseReceived stream_id:%s, headers:%s>" % (
100            self.stream_id, self.headers
101        )
102
103
104class TrailersReceived(Event):
105    """
106    The TrailersReceived event is fired whenever trailers are received on a
107    stream. Trailers are a set of headers sent after the body of the
108    request/response, and are used to provide information that wasn't known
109    ahead of time (e.g. content-length). This event carries the HTTP header
110    fields that form the trailers and the stream ID of the stream on which they
111    were received.
112
113    .. versionchanged:: 2.3.0
114       Changed the type of ``headers`` to :class:`HeaderTuple
115       <hpack:hpack.HeaderTuple>`. This has no effect on current users.
116
117    .. versionchanged:: 2.4.0
118       Added ``stream_ended`` and ``priority_updated`` properties.
119    """
120    def __init__(self):
121        #: The Stream ID for the stream on which these trailers were received.
122        self.stream_id = None
123
124        #: The trailers themselves.
125        self.headers = None
126
127        #: Trailers always end streams. This property has the associated
128        #: :class:`StreamEnded <h2.events.StreamEnded>` in it.
129        #:
130        #: .. versionadded:: 2.4.0
131        self.stream_ended = None
132
133        #: If the trailers also set associated priority information, the
134        #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>`
135        #: event will be available here.
136        #:
137        #: .. versionadded:: 2.4.0
138        self.priority_updated = None
139
140    def __repr__(self):
141        return "<TrailersReceived stream_id:%s, headers:%s>" % (
142            self.stream_id, self.headers
143        )
144
145
146class _HeadersSent(Event):
147    """
148    The _HeadersSent event is fired whenever headers are sent.
149
150    This is an internal event, used to determine validation steps on
151    outgoing header blocks.
152    """
153    pass
154
155
156class _ResponseSent(_HeadersSent):
157    """
158    The _ResponseSent event is fired whenever response headers are sent
159    on a stream.
160
161    This is an internal event, used to determine validation steps on
162    outgoing header blocks.
163    """
164    pass
165
166
167class _RequestSent(_HeadersSent):
168    """
169    The _RequestSent event is fired whenever request headers are sent
170    on a stream.
171
172    This is an internal event, used to determine validation steps on
173    outgoing header blocks.
174    """
175    pass
176
177
178class _TrailersSent(_HeadersSent):
179    """
180    The _TrailersSent event is fired whenever trailers are sent on a
181    stream. Trailers are a set of headers sent after the body of the
182    request/response, and are used to provide information that wasn't known
183    ahead of time (e.g. content-length).
184
185    This is an internal event, used to determine validation steps on
186    outgoing header blocks.
187    """
188    pass
189
190
191class _PushedRequestSent(_HeadersSent):
192    """
193    The _PushedRequestSent event is fired whenever pushed request headers are
194    sent.
195
196    This is an internal event, used to determine validation steps on outgoing
197    header blocks.
198    """
199    pass
200
201
202class InformationalResponseReceived(Event):
203    """
204    The InformationalResponseReceived event is fired when an informational
205    response (that is, one whose status code is a 1XX code) is received from
206    the remote peer.
207
208    The remote peer may send any number of these, from zero upwards. These
209    responses are most commonly sent in response to requests that have the
210    ``expect: 100-continue`` header field present. Most users can safely
211    ignore this event unless you are intending to use the
212    ``expect: 100-continue`` flow, or are for any reason expecting a different
213    1XX status code.
214
215    .. versionadded:: 2.2.0
216
217    .. versionchanged:: 2.3.0
218       Changed the type of ``headers`` to :class:`HeaderTuple
219       <hpack:hpack.HeaderTuple>`. This has no effect on current users.
220
221    .. versionchanged:: 2.4.0
222       Added ``priority_updated`` property.
223    """
224    def __init__(self):
225        #: The Stream ID for the stream this informational response was made
226        #: on.
227        self.stream_id = None
228
229        #: The headers for this informational response.
230        self.headers = None
231
232        #: If this response also had associated priority information, the
233        #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>`
234        #: event will be available here.
235        #:
236        #: .. versionadded:: 2.4.0
237        self.priority_updated = None
238
239    def __repr__(self):
240        return "<InformationalResponseReceived stream_id:%s, headers:%s>" % (
241            self.stream_id, self.headers
242        )
243
244
245class DataReceived(Event):
246    """
247    The DataReceived event is fired whenever data is received on a stream from
248    the remote peer. The event carries the data itself, and the stream ID on
249    which the data was received.
250
251    .. versionchanged:: 2.4.0
252       Added ``stream_ended`` property.
253    """
254    def __init__(self):
255        #: The Stream ID for the stream this data was received on.
256        self.stream_id = None
257
258        #: The data itself.
259        self.data = None
260
261        #: The amount of data received that counts against the flow control
262        #: window. Note that padding counts against the flow control window, so
263        #: when adjusting flow control you should always use this field rather
264        #: than ``len(data)``.
265        self.flow_controlled_length = None
266
267        #: If this data chunk also completed the stream, the associated
268        #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available
269        #: here.
270        #:
271        #: .. versionadded:: 2.4.0
272        self.stream_ended = None
273
274    def __repr__(self):
275        return (
276            "<DataReceived stream_id:%s, "
277            "flow_controlled_length:%s, "
278            "data:%s>" % (
279                self.stream_id,
280                self.flow_controlled_length,
281                _bytes_representation(self.data[:20]),
282            )
283        )
284
285
286class WindowUpdated(Event):
287    """
288    The WindowUpdated event is fired whenever a flow control window changes
289    size. HTTP/2 defines flow control windows for connections and streams: this
290    event fires for both connections and streams. The event carries the ID of
291    the stream to which it applies (set to zero if the window update applies to
292    the connection), and the delta in the window size.
293    """
294    def __init__(self):
295        #: The Stream ID of the stream whose flow control window was changed.
296        #: May be ``0`` if the connection window was changed.
297        self.stream_id = None
298
299        #: The window delta.
300        self.delta = None
301
302    def __repr__(self):
303        return "<WindowUpdated stream_id:%s, delta:%s>" % (
304            self.stream_id, self.delta
305        )
306
307
308class RemoteSettingsChanged(Event):
309    """
310    The RemoteSettingsChanged event is fired whenever the remote peer changes
311    its settings. It contains a complete inventory of changed settings,
312    including their previous values.
313
314    In HTTP/2, settings changes need to be acknowledged. hyper-h2 automatically
315    acknowledges settings changes for efficiency. However, it is possible that
316    the caller may not be happy with the changed setting.
317
318    When this event is received, the caller should confirm that the new
319    settings are acceptable. If they are not acceptable, the user should close
320    the connection with the error code :data:`PROTOCOL_ERROR
321    <h2.errors.ErrorCodes.PROTOCOL_ERROR>`.
322
323    .. versionchanged:: 2.0.0
324       Prior to this version the user needed to acknowledge settings changes.
325       This is no longer the case: hyper-h2 now automatically acknowledges
326       them.
327    """
328    def __init__(self):
329        #: A dictionary of setting byte to
330        #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing
331        #: the changed settings.
332        self.changed_settings = {}
333
334    @classmethod
335    def from_settings(cls, old_settings, new_settings):
336        """
337        Build a RemoteSettingsChanged event from a set of changed settings.
338
339        :param old_settings: A complete collection of old settings, in the form
340                             of a dictionary of ``{setting: value}``.
341        :param new_settings: All the changed settings and their new values, in
342                             the form of a dictionary of ``{setting: value}``.
343        """
344        e = cls()
345        for setting, new_value in new_settings.items():
346            setting = _setting_code_from_int(setting)
347            original_value = old_settings.get(setting)
348            change = ChangedSetting(setting, original_value, new_value)
349            e.changed_settings[setting] = change
350
351        return e
352
353    def __repr__(self):
354        return "<RemoteSettingsChanged changed_settings:{%s}>" % (
355            ", ".join(repr(cs) for cs in self.changed_settings.values()),
356        )
357
358
359class PingAcknowledged(Event):
360    """
361    The PingAcknowledged event is fired whenever a user-emitted PING is
362    acknowledged. This contains the data in the ACK'ed PING, allowing the
363    user to correlate PINGs and calculate RTT.
364    """
365    def __init__(self):
366        #: The data included on the ping.
367        self.ping_data = None
368
369    def __repr__(self):
370        return "<PingAcknowledged ping_data:%s>" % (
371            _bytes_representation(self.ping_data),
372        )
373
374
375class StreamEnded(Event):
376    """
377    The StreamEnded event is fired whenever a stream is ended by a remote
378    party. The stream may not be fully closed if it has not been closed
379    locally, but no further data or headers should be expected on that stream.
380    """
381    def __init__(self):
382        #: The Stream ID of the stream that was closed.
383        self.stream_id = None
384
385    def __repr__(self):
386        return "<StreamEnded stream_id:%s>" % self.stream_id
387
388
389class StreamReset(Event):
390    """
391    The StreamReset event is fired in two situations. The first is when the
392    remote party forcefully resets the stream. The second is when the remote
393    party has made a protocol error which only affects a single stream. In this
394    case, Hyper-h2 will terminate the stream early and return this event.
395
396    .. versionchanged:: 2.0.0
397       This event is now fired when Hyper-h2 automatically resets a stream.
398    """
399    def __init__(self):
400        #: The Stream ID of the stream that was reset.
401        self.stream_id = None
402
403        #: The error code given. Either one of :class:`ErrorCodes
404        #: <h2.errors.ErrorCodes>` or ``int``
405        self.error_code = None
406
407        #: Whether the remote peer sent a RST_STREAM or we did.
408        self.remote_reset = True
409
410    def __repr__(self):
411        return "<StreamReset stream_id:%s, error_code:%s, remote_reset:%s>" % (
412            self.stream_id, self.error_code, self.remote_reset
413        )
414
415
416class PushedStreamReceived(Event):
417    """
418    The PushedStreamReceived event is fired whenever a pushed stream has been
419    received from a remote peer. The event carries on it the new stream ID, the
420    ID of the parent stream, and the request headers pushed by the remote peer.
421    """
422    def __init__(self):
423        #: The Stream ID of the stream created by the push.
424        self.pushed_stream_id = None
425
426        #: The Stream ID of the stream that the push is related to.
427        self.parent_stream_id = None
428
429        #: The request headers, sent by the remote party in the push.
430        self.headers = None
431
432    def __repr__(self):
433        return (
434            "<PushedStreamReceived pushed_stream_id:%s, parent_stream_id:%s, "
435            "headers:%s>" % (
436                self.pushed_stream_id,
437                self.parent_stream_id,
438                self.headers,
439            )
440        )
441
442
443class SettingsAcknowledged(Event):
444    """
445    The SettingsAcknowledged event is fired whenever a settings ACK is received
446    from the remote peer. The event carries on it the settings that were
447    acknowedged, in the same format as
448    :class:`h2.events.RemoteSettingsChanged`.
449    """
450    def __init__(self):
451        #: A dictionary of setting byte to
452        #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing
453        #: the changed settings.
454        self.changed_settings = {}
455
456    def __repr__(self):
457        return "<SettingsAcknowledged changed_settings:{%s}>" % (
458            ", ".join(repr(cs) for cs in self.changed_settings.values()),
459        )
460
461
462class PriorityUpdated(Event):
463    """
464    The PriorityUpdated event is fired whenever a stream sends updated priority
465    information. This can occur when the stream is opened, or at any time
466    during the stream lifetime.
467
468    This event is purely advisory, and does not need to be acted on.
469
470    .. versionadded:: 2.0.0
471    """
472    def __init__(self):
473        #: The ID of the stream whose priority information is being updated.
474        self.stream_id = None
475
476        #: The new stream weight. May be the same as the original stream
477        #: weight. An integer between 1 and 256.
478        self.weight = None
479
480        #: The stream ID this stream now depends on. May be ``0``.
481        self.depends_on = None
482
483        #: Whether the stream *exclusively* depends on the parent stream. If it
484        #: does, this stream should inherit the current children of its new
485        #: parent.
486        self.exclusive = None
487
488    def __repr__(self):
489        return (
490            "<PriorityUpdated stream_id:%s, weight:%s, depends_on:%s, "
491            "exclusive:%s>" % (
492                self.stream_id,
493                self.weight,
494                self.depends_on,
495                self.exclusive
496            )
497        )
498
499
500class ConnectionTerminated(Event):
501    """
502    The ConnectionTerminated event is fired when a connection is torn down by
503    the remote peer using a GOAWAY frame. Once received, no further action may
504    be taken on the connection: a new connection must be established.
505    """
506    def __init__(self):
507        #: The error code cited when tearing down the connection. Should be
508        #: one of :class:`ErrorCodes <h2.errors.ErrorCodes>`, but may not be if
509        #: unknown HTTP/2 extensions are being used.
510        self.error_code = None
511
512        #: The stream ID of the last stream the remote peer saw. This can
513        #: provide an indication of what data, if any, never reached the remote
514        #: peer and so can safely be resent.
515        self.last_stream_id = None
516
517        #: Additional debug data that can be appended to GOAWAY frame.
518        self.additional_data = None
519
520    def __repr__(self):
521        return (
522            "<ConnectionTerminated error_code:%s, last_stream_id:%s, "
523            "additional_data:%s>" % (
524                self.error_code,
525                self.last_stream_id,
526                _bytes_representation(
527                    self.additional_data[:20]
528                    if self.additional_data else None)
529            )
530        )
531
532
533class AlternativeServiceAvailable(Event):
534    """
535    The AlternativeServiceAvailable event is fired when the remote peer
536    advertises an `RFC 7838 <https://tools.ietf.org/html/rfc7838>`_ Alternative
537    Service using an ALTSVC frame.
538
539    This event always carries the origin to which the ALTSVC information
540    applies. That origin is either supplied by the server directly, or inferred
541    by hyper-h2 from the ``:authority`` pseudo-header field that was sent by
542    the user when initiating a given stream.
543
544    This event also carries what RFC 7838 calls the "Alternative Service Field
545    Value", which is formatted like a HTTP header field and contains the
546    relevant alternative service information. Hyper-h2 does not parse or in any
547    way modify that information: the user is required to do that.
548
549    This event can only be fired on the client end of a connection.
550
551    .. versionadded:: 2.3.0
552    """
553    def __init__(self):
554        #: The origin to which the alternative service field value applies.
555        #: This field is either supplied by the server directly, or inferred by
556        #: hyper-h2 from the ``:authority`` pseudo-header field that was sent
557        #: by the user when initiating the stream on which the frame was
558        #: received.
559        self.origin = None
560
561        #: The ALTSVC field value. This contains information about the HTTP
562        #: alternative service being advertised by the server. Hyper-h2 does
563        #: not parse this field: it is left exactly as sent by the server. The
564        #: structure of the data in this field is given by `RFC 7838 Section 3
565        #: <https://tools.ietf.org/html/rfc7838#section-3>`_.
566        self.field_value = None
567
568    def __repr__(self):
569        return (
570            "<AlternativeServiceAvailable origin:%s, field_value:%s>" % (
571                self.origin.decode('utf-8', 'ignore'),
572                self.field_value.decode('utf-8', 'ignore'),
573            )
574        )
575
576
577def _bytes_representation(data):
578    """
579    Converts a bytestring into something that is safe to print on all Python
580    platforms.
581
582    This function is relatively expensive, so it should not be called on the
583    mainline of the code. It's safe to use in things like object repr methods
584    though.
585    """
586    if data is None:
587        return None
588
589    hex = binascii.hexlify(data)
590
591    # This is moderately clever: on all Python versions hexlify returns a byte
592    # string. On Python 3 we want an actual string, so we just check whether
593    # that's what we have.
594    if not isinstance(hex, str):  # pragma: no cover
595        hex = hex.decode('ascii')
596
597    return hex
598