blob: 570142ee7d30500bbbd9af7fd3eb938f3a26da86 [file] [log] [blame] [view]
# HTTP/2 Frame Payload Decoders
This directory contains one decoder for each HTTP/2 frame type (i.e. a `.h`,
`.cc` and `_test.cc` file for each decoder, shared runtime infrastructure for
those decoders (`payload_decoder_base.*`), and shared test infrastructure
(`payload_decoder_base_test*`).
## `AbcXyzPayloadDecoder`
For frame type `ABC_XYZ`, there is a payload decoder called
`AbcXyzPayloadDecoder`. Each payload decoder class implements two methods which
will be called by `Http2FrameDecoder`:
* `DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer*
db)` is called after a frame header has been decoded and found to have frame
type `ABC_XYZ`. It must decode the entire contents of the decode buffer,
unless it detects an error.
* `DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, DecodeBuffer*
db)` is called only if the most recent call to `StartDecodingPayload()`
returned `DecodeStatus::kDecodeInProgress`, as did any and all subsequent
calls to `ResumeDecodingPayload()`. It must decode the entire contents of
the decode buffer, unless it detects an error.
These methods return:
* `DecodeStatus::kDecodeDone` if the frame payload has been fully decoded
without finding error (i.e. didn't call `OnFrameSizeError()` or
`OnPaddingTooLong()`).
* `DecodeStatus::kDecodeInProgress` if more payload remains to be read; in
this situation the payload decoder must appropriately update
`PayloadDecoderBase::remaining_payload_` and, if paddable,
`PaddedPayloadDecoderBase::remaining_padding_`. The caller is responsible
for calling `ResumeDecodingPayload()` when more of the payload is available.
* `DecodeStatus::kDecodeError` if the payload size wasn't valid, in which case
one of `OnFrameSizeError()` or `OnPaddingTooLong()` will have been called.
A payload decoder class has no virtual methods, no constructors, no destructor,
and no member initializers, nor any members with the same. This allows
`Http2FrameDecoder` to have a member that is a union of all the payload
decoders, which works because only one of these is needed at a time, and saves a
bit of memory.
### State machine
For those frame types (`DATA`, `PUSH_PROMISE`, etc.) that have optional fields,
or have both fixed length and variable length fields, the corresponding decoder
uses a state machine to track which field is being decoded (e.g. Pad Length,
Data, or Trailing Padding, in the case of `DATA` frames). The decoder has an
enum, `PayloadState`, whose values have names that either indicate the state or
the corresponding action to be performed (a bit confusing).
The decoder's `ResumeDecodingPayload()` method will have a switch statement,
with a case for each state. Often `StartDecodingPayload()` will choose the
initial state (e.g. for `DATA`, `kReadPadLength` if the `PADDED` flag is set,
else `kReadPayload`), and then call `ResumeDecodingPayload()` to execute the
state machine.
These frame types will typically have several associated methods in
`Http2FrameDecoderListener`. For example, when decoding a `CONTINUATION` frame,
the first callback is `OnContinuationStart()`, and the last is
`OnContinuationEnd()`; between these calls are as many `OnHpackFragment()` calls
as necessary to provide the entire payload to the listener; the number of
`OnHpackFragment()` calls depends on how many decode buffers the payload is
split across.
### Simpler decoders
The other frame types have quite simple payloads (e.g. `PING`, `PRIORITY`,
`RST_STREAM` and `WINDOW_UPDATE`), e.g. their payload is fixed size, has no
optional or variable length fields. The corresponding decoders do not have a
`PayloadState` enum, nor a switch statement using such an enum. Such decoders
use an `Http2StructureDecoder` to decode their payload into a type appropriate
structure (e.g. an `Http2RstStreamFields` instance for a `RST_STREAM` frame).
If the frame is contained entirely in a single decode buffer and is of the
correct size, `Http2StructureDecoder` will decode the structure into the output
structure, without any buffering.
However, if `StartDecodingPayload()` does not receive the entire payload, the
payload decoder uses two fields of `FrameDecoderState` to keep track of its
progress through the payload:
1. `remaining_payload_` records the number of bytes of the frame's payload that
have not yet been seen by the decoder.
1. `structure_decoder_` (an `Http2StructureDecoder` instance) stores the
buffered bytes of the structure; once all of the bytes of the encoded
structure have been buffered, then the structure is decoded. Note that the
fast path does not buffer the bytes, but decodes them straight out of the
decode buffer.
### Fast path
Some frame types (`DATA`, `HEADERS`, `WINDOW_UPDATE`, `PING`) occur fairly
often, and seem worth having a fast path to handle the case where:
* The entire payload in the decode buffer when `StartDecodingPayload` is
called.
* There are no optional fields present (e.g. the `PADDED` flag is not set on a
`DATA` frame).
* For fixed size payloads, the `payload_length` field of the frame's header
has the correct value (e.g. 8 for a `PING`, or 4 for a `WINDOW_UPDATE`).
Using the fast path avoids any buffering inside the decoder of fixed size
structures, avoids updating the `FrameDecoderState`'s members (i.e.
`remaining_payload_` and `remaining_padding_` are not touched), and reduces the
branches taken, though at the cost of making the decision at the start regarding
whether to enter the fast path.
### Preconditions
Prior to calling `StartDecodingPayload()` or `ResumeDecodingPayload()`, the
caller (`Http2FrameDecoder` or a test) is responsible for:
* Calling `FrameDecoderState::set_listener()` to provide the
`Http2FrameDecoderListener` that is to be notified as the payload is
decoded.
* Ensuring that the common frame header is available via
`FrameDecoderState::frame_header()`; in production this means decoding the
common frame header into `FrameDecoderState::frame_header_` using
`FrameDecoderState::StartDecodingFrameHeader`.
* Rejecting excessively long frames; for example, if `SETTINGS_MAX_FRAME_SIZE`
is 16KB, and a frame's payload length is greater than that, RFC 9113 calls
for treating that as a stream or connection level
[frame size error](https://www.rfc-editor.org/rfc/rfc9113.html#name-frame-size).
* Clearing any invalid flags from the flags field of the frame header (i.e. a
`SETTINGS` frame will either have no flags set, or the `ACK` flag, but
nothing else).
* Limiting the `DecodeBuffer` so that it does NOT extend beyond the end of the
payload; note that this requires that
`FrameDecoderState::remaining_payload_` and
`FrameDecoderState::remaining_padding_`, if appropriate, have been set by
the payload decoder when it returns `kDecodeInProgress` (see
`FrameDecoderState::InitializeRemainders`).
* Keeping track of whether to call `StartDecodingPayload()` or
`ResumeDecodingPayload()` next.
### Error Detection
The general approach in the decoders is to assume that all is well until it
reaches a stopping point (e.g. the end of the decode buffer or after decoding
the expected content of a fixed size payload), at which point it will check for
errors.
For example, a `WINDOW_UPDATE` payload is supposed to be 4 bytes long, but the
decoder doesn't check for that until one of these situations has occurred:
* It has decoded the window size increment (4 bytes). At this point, if it has
reached the end of the payload, then there is no error because the frame is
the correct size.
* It has reached the end of the decode buffer before it has finished decoding
the window size increment. At this point, if there is more payload remaining
to be decoded, then it can return `kDecodeInProgress`. But if the decoder
has also reached the end of the payload, then it must report a frame size
error.
In order to support this, the caller (`Http2FrameDecoder`) is required to pass
in a `DecodeBuffer` that does not extend past the end of the payload.
## `Listener` in payload decoder *tests*
Each payload decoder test file includes a `Listener` class, extending
`FramePartsCollector` and implementing exactly the set of
`Http2FrameDecoderListener` methods invoked by the corresponding payload
decoder. For example, the `WINDOW_UPDATE` decoder will handle `OnWindowUpdate()`
and `OnFrameSizeError()`, but no other methods of `Http2FrameDecoderListener`.
See the `README.md` in the parent directory for descriptions of
`FramePartsCollector`, etc.