1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "quiche/http2/decoder/payload_decoders/push_promise_payload_decoder.h"
6 
7 #include <stddef.h>
8 
9 #include "absl/base/macros.h"
10 #include "quiche/http2/decoder/decode_buffer.h"
11 #include "quiche/http2/decoder/http2_frame_decoder_listener.h"
12 #include "quiche/http2/http2_constants.h"
13 #include "quiche/http2/http2_structures.h"
14 #include "quiche/common/platform/api/quiche_bug_tracker.h"
15 #include "quiche/common/platform/api/quiche_logging.h"
16 
17 namespace http2 {
18 
operator <<(std::ostream & out,PushPromisePayloadDecoder::PayloadState v)19 std::ostream& operator<<(std::ostream& out,
20                          PushPromisePayloadDecoder::PayloadState v) {
21   switch (v) {
22     case PushPromisePayloadDecoder::PayloadState::kReadPadLength:
23       return out << "kReadPadLength";
24     case PushPromisePayloadDecoder::PayloadState::
25         kStartDecodingPushPromiseFields:
26       return out << "kStartDecodingPushPromiseFields";
27     case PushPromisePayloadDecoder::PayloadState::kReadPayload:
28       return out << "kReadPayload";
29     case PushPromisePayloadDecoder::PayloadState::kSkipPadding:
30       return out << "kSkipPadding";
31     case PushPromisePayloadDecoder::PayloadState::
32         kResumeDecodingPushPromiseFields:
33       return out << "kResumeDecodingPushPromiseFields";
34   }
35   return out << static_cast<int>(v);
36 }
37 
StartDecodingPayload(FrameDecoderState * state,DecodeBuffer * db)38 DecodeStatus PushPromisePayloadDecoder::StartDecodingPayload(
39     FrameDecoderState* state, DecodeBuffer* db) {
40   const Http2FrameHeader& frame_header = state->frame_header();
41   const uint32_t total_length = frame_header.payload_length;
42 
43   QUICHE_DVLOG(2) << "PushPromisePayloadDecoder::StartDecodingPayload: "
44                   << frame_header;
45 
46   QUICHE_DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type);
47   QUICHE_DCHECK_LE(db->Remaining(), total_length);
48   QUICHE_DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::END_HEADERS |
49                                              Http2FrameFlag::PADDED));
50 
51   if (!frame_header.IsPadded()) {
52     // If it turns out that PUSH_PROMISE frames without padding are sufficiently
53     // common, and that they are usually short enough that they fit entirely
54     // into one DecodeBuffer, we can detect that here and implement a special
55     // case, avoiding the state machine in ResumeDecodingPayload.
56     payload_state_ = PayloadState::kStartDecodingPushPromiseFields;
57   } else {
58     payload_state_ = PayloadState::kReadPadLength;
59   }
60   state->InitializeRemainders();
61   return ResumeDecodingPayload(state, db);
62 }
63 
ResumeDecodingPayload(FrameDecoderState * state,DecodeBuffer * db)64 DecodeStatus PushPromisePayloadDecoder::ResumeDecodingPayload(
65     FrameDecoderState* state, DecodeBuffer* db) {
66   QUICHE_DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload"
67                   << "  remaining_payload=" << state->remaining_payload()
68                   << "  db->Remaining=" << db->Remaining();
69 
70   const Http2FrameHeader& frame_header = state->frame_header();
71   QUICHE_DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type);
72   QUICHE_DCHECK_LE(state->remaining_payload(), frame_header.payload_length);
73   QUICHE_DCHECK_LE(db->Remaining(), frame_header.payload_length);
74 
75   DecodeStatus status;
76   while (true) {
77     QUICHE_DVLOG(2)
78         << "PushPromisePayloadDecoder::ResumeDecodingPayload payload_state_="
79         << payload_state_;
80     switch (payload_state_) {
81       case PayloadState::kReadPadLength:
82         QUICHE_DCHECK_EQ(state->remaining_payload(),
83                          frame_header.payload_length);
84         // ReadPadLength handles the OnPadLength callback, and updating the
85         // remaining_payload and remaining_padding fields. If the amount of
86         // padding is too large to fit in the frame's payload, ReadPadLength
87         // instead calls OnPaddingTooLong and returns kDecodeError.
88         // Suppress the call to OnPadLength because we haven't yet called
89         // OnPushPromiseStart, which needs to wait until we've decoded the
90         // Promised Stream ID.
91         status = state->ReadPadLength(db, /*report_pad_length*/ false);
92         if (status != DecodeStatus::kDecodeDone) {
93           payload_state_ = PayloadState::kReadPadLength;
94           return status;
95         }
96         ABSL_FALLTHROUGH_INTENDED;
97 
98       case PayloadState::kStartDecodingPushPromiseFields:
99         status =
100             state->StartDecodingStructureInPayload(&push_promise_fields_, db);
101         if (status != DecodeStatus::kDecodeDone) {
102           payload_state_ = PayloadState::kResumeDecodingPushPromiseFields;
103           return status;
104         }
105         // Finished decoding the Promised Stream ID. Can now tell the listener
106         // that we're starting to decode a PUSH_PROMISE frame.
107         ReportPushPromise(state);
108         ABSL_FALLTHROUGH_INTENDED;
109 
110       case PayloadState::kReadPayload:
111         QUICHE_DCHECK_LT(state->remaining_payload(),
112                          frame_header.payload_length);
113         QUICHE_DCHECK_LE(state->remaining_payload(),
114                          frame_header.payload_length -
115                              Http2PushPromiseFields::EncodedSize());
116         QUICHE_DCHECK_LE(
117             state->remaining_payload(),
118             frame_header.payload_length -
119                 Http2PushPromiseFields::EncodedSize() -
120                 (frame_header.IsPadded() ? (1 + state->remaining_padding())
121                                          : 0));
122         {
123           size_t avail = state->AvailablePayload(db);
124           state->listener()->OnHpackFragment(db->cursor(), avail);
125           db->AdvanceCursor(avail);
126           state->ConsumePayload(avail);
127         }
128         if (state->remaining_payload() > 0) {
129           payload_state_ = PayloadState::kReadPayload;
130           return DecodeStatus::kDecodeInProgress;
131         }
132         ABSL_FALLTHROUGH_INTENDED;
133 
134       case PayloadState::kSkipPadding:
135         // SkipPadding handles the OnPadding callback.
136         if (state->SkipPadding(db)) {
137           state->listener()->OnPushPromiseEnd();
138           return DecodeStatus::kDecodeDone;
139         }
140         payload_state_ = PayloadState::kSkipPadding;
141         return DecodeStatus::kDecodeInProgress;
142 
143       case PayloadState::kResumeDecodingPushPromiseFields:
144         status =
145             state->ResumeDecodingStructureInPayload(&push_promise_fields_, db);
146         if (status == DecodeStatus::kDecodeDone) {
147           // Finished decoding the Promised Stream ID. Can now tell the listener
148           // that we're starting to decode a PUSH_PROMISE frame.
149           ReportPushPromise(state);
150           payload_state_ = PayloadState::kReadPayload;
151           continue;
152         }
153         payload_state_ = PayloadState::kResumeDecodingPushPromiseFields;
154         return status;
155     }
156     QUICHE_BUG(http2_bug_183_1) << "PayloadState: " << payload_state_;
157   }
158 }
159 
ReportPushPromise(FrameDecoderState * state)160 void PushPromisePayloadDecoder::ReportPushPromise(FrameDecoderState* state) {
161   const Http2FrameHeader& frame_header = state->frame_header();
162   if (frame_header.IsPadded()) {
163     state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_,
164                                           1 + state->remaining_padding());
165   } else {
166     state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_,
167                                           0);
168   }
169 }
170 
171 }  // namespace http2
172