| # 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. |