• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/hpack/decoder/hpack_decoder_state.h"
6 
7 #include <utility>
8 
9 #include "quiche/http2/http2_constants.h"
10 #include "quiche/common/platform/api/quiche_logging.h"
11 
12 namespace http2 {
13 namespace {
14 
ExtractString(HpackDecoderStringBuffer * string_buffer)15 std::string ExtractString(HpackDecoderStringBuffer* string_buffer) {
16   if (string_buffer->IsBuffered()) {
17     return string_buffer->ReleaseString();
18   } else {
19     auto result = std::string(string_buffer->str());
20     string_buffer->Reset();
21     return result;
22   }
23 }
24 
25 }  // namespace
26 
HpackDecoderState(HpackDecoderListener * listener)27 HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener)
28     : listener_(listener),
29       final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()),
30       lowest_header_table_size_(final_header_table_size_),
31       require_dynamic_table_size_update_(false),
32       allow_dynamic_table_size_update_(true),
33       saw_dynamic_table_size_update_(false),
34       error_(HpackDecodingError::kOk) {
35   QUICHE_CHECK(listener_);
36 }
37 
38 HpackDecoderState::~HpackDecoderState() = default;
39 
ApplyHeaderTableSizeSetting(uint32_t header_table_size)40 void HpackDecoderState::ApplyHeaderTableSizeSetting(
41     uint32_t header_table_size) {
42   QUICHE_DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting("
43                   << header_table_size << ")";
44   QUICHE_DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
45   if (header_table_size < lowest_header_table_size_) {
46     lowest_header_table_size_ = header_table_size;
47   }
48   final_header_table_size_ = header_table_size;
49   QUICHE_DVLOG(2) << "low water mark: " << lowest_header_table_size_;
50   QUICHE_DVLOG(2) << "final limit: " << final_header_table_size_;
51 }
52 
53 // Called to notify this object that we're starting to decode an HPACK block
54 // (e.g. a HEADERS or PUSH_PROMISE frame's header has been decoded).
OnHeaderBlockStart()55 void HpackDecoderState::OnHeaderBlockStart() {
56   QUICHE_DVLOG(2) << "HpackDecoderState::OnHeaderBlockStart";
57   // This instance can't be reused after an error has been detected, as we must
58   // assume that the encoder and decoder compression states are no longer
59   // synchronized.
60   QUICHE_DCHECK(error_ == HpackDecodingError::kOk)
61       << HpackDecodingErrorToString(error_);
62   QUICHE_DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
63   allow_dynamic_table_size_update_ = true;
64   saw_dynamic_table_size_update_ = false;
65   // If the peer has acknowledged a HEADER_TABLE_SIZE smaller than that which
66   // its HPACK encoder has been using, then the next HPACK block it sends MUST
67   // start with a Dynamic Table Size Update entry that is at least as low as
68   // lowest_header_table_size_. That may be followed by another as great as
69   // final_header_table_size_, if those are different.
70   require_dynamic_table_size_update_ =
71       (lowest_header_table_size_ <
72            decoder_tables_.current_header_table_size() ||
73        final_header_table_size_ < decoder_tables_.header_table_size_limit());
74   QUICHE_DVLOG(2) << "HpackDecoderState::OnHeaderListStart "
75                   << "require_dynamic_table_size_update_="
76                   << require_dynamic_table_size_update_;
77   listener_->OnHeaderListStart();
78 }
79 
OnIndexedHeader(size_t index)80 void HpackDecoderState::OnIndexedHeader(size_t index) {
81   QUICHE_DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index;
82   if (error_ != HpackDecodingError::kOk) {
83     return;
84   }
85   if (require_dynamic_table_size_update_) {
86     ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate, "");
87     return;
88   }
89   allow_dynamic_table_size_update_ = false;
90   const HpackStringPair* entry = decoder_tables_.Lookup(index);
91   if (entry != nullptr) {
92     listener_->OnHeader(entry->name, entry->value);
93   } else {
94     ReportError(HpackDecodingError::kInvalidIndex, "");
95   }
96 }
97 
OnNameIndexAndLiteralValue(HpackEntryType entry_type,size_t name_index,HpackDecoderStringBuffer * value_buffer)98 void HpackDecoderState::OnNameIndexAndLiteralValue(
99     HpackEntryType entry_type, size_t name_index,
100     HpackDecoderStringBuffer* value_buffer) {
101   QUICHE_DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue "
102                   << entry_type << ", " << name_index << ", "
103                   << value_buffer->str();
104   if (error_ != HpackDecodingError::kOk) {
105     return;
106   }
107   if (require_dynamic_table_size_update_) {
108     ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate, "");
109     return;
110   }
111   allow_dynamic_table_size_update_ = false;
112   const HpackStringPair* entry = decoder_tables_.Lookup(name_index);
113   if (entry != nullptr) {
114     std::string value(ExtractString(value_buffer));
115     listener_->OnHeader(entry->name, value);
116     if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
117       decoder_tables_.Insert(entry->name, std::move(value));
118     }
119   } else {
120     ReportError(HpackDecodingError::kInvalidNameIndex, "");
121   }
122 }
123 
OnLiteralNameAndValue(HpackEntryType entry_type,HpackDecoderStringBuffer * name_buffer,HpackDecoderStringBuffer * value_buffer)124 void HpackDecoderState::OnLiteralNameAndValue(
125     HpackEntryType entry_type, HpackDecoderStringBuffer* name_buffer,
126     HpackDecoderStringBuffer* value_buffer) {
127   QUICHE_DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type
128                   << ", " << name_buffer->str() << ", " << value_buffer->str();
129   if (error_ != HpackDecodingError::kOk) {
130     return;
131   }
132   if (require_dynamic_table_size_update_) {
133     ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate, "");
134     return;
135   }
136   allow_dynamic_table_size_update_ = false;
137   std::string name(ExtractString(name_buffer));
138   std::string value(ExtractString(value_buffer));
139   listener_->OnHeader(name, value);
140   if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
141     decoder_tables_.Insert(std::move(name), std::move(value));
142   }
143 }
144 
OnDynamicTableSizeUpdate(size_t size_limit)145 void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) {
146   QUICHE_DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate "
147                   << size_limit << ", required="
148                   << (require_dynamic_table_size_update_ ? "true" : "false")
149                   << ", allowed="
150                   << (allow_dynamic_table_size_update_ ? "true" : "false");
151   if (error_ != HpackDecodingError::kOk) {
152     return;
153   }
154   QUICHE_DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
155   if (!allow_dynamic_table_size_update_) {
156     // At most two dynamic table size updates allowed at the start, and not
157     // after a header.
158     ReportError(HpackDecodingError::kDynamicTableSizeUpdateNotAllowed, "");
159     return;
160   }
161   if (require_dynamic_table_size_update_) {
162     // The new size must not be greater than the low water mark.
163     if (size_limit > lowest_header_table_size_) {
164       ReportError(
165           HpackDecodingError::kInitialDynamicTableSizeUpdateIsAboveLowWaterMark,
166           "");
167       return;
168     }
169     require_dynamic_table_size_update_ = false;
170   } else if (size_limit > final_header_table_size_) {
171     // The new size must not be greater than the final max header table size
172     // that the peer acknowledged.
173     ReportError(
174         HpackDecodingError::kDynamicTableSizeUpdateIsAboveAcknowledgedSetting,
175         "");
176     return;
177   }
178   decoder_tables_.DynamicTableSizeUpdate(size_limit);
179   if (saw_dynamic_table_size_update_) {
180     allow_dynamic_table_size_update_ = false;
181   } else {
182     saw_dynamic_table_size_update_ = true;
183   }
184   // We no longer need to keep an eye out for a lower header table size.
185   lowest_header_table_size_ = final_header_table_size_;
186 }
187 
OnHpackDecodeError(HpackDecodingError error,std::string detailed_error)188 void HpackDecoderState::OnHpackDecodeError(HpackDecodingError error,
189                                            std::string detailed_error) {
190   QUICHE_DVLOG(2) << "HpackDecoderState::OnHpackDecodeError "
191                   << HpackDecodingErrorToString(error);
192   if (error_ == HpackDecodingError::kOk) {
193     ReportError(error, detailed_error);
194   }
195 }
196 
OnHeaderBlockEnd()197 void HpackDecoderState::OnHeaderBlockEnd() {
198   QUICHE_DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd";
199   if (error_ != HpackDecodingError::kOk) {
200     return;
201   }
202   if (require_dynamic_table_size_update_) {
203     // Apparently the HPACK block was empty, but we needed it to contain at
204     // least 1 dynamic table size update.
205     ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate, "");
206   } else {
207     listener_->OnHeaderListEnd();
208   }
209 }
210 
ReportError(HpackDecodingError error,std::string detailed_error)211 void HpackDecoderState::ReportError(HpackDecodingError error,
212                                     std::string detailed_error) {
213   QUICHE_DVLOG(2) << "HpackDecoderState::ReportError is new="
214                   << (error_ == HpackDecodingError::kOk ? "true" : "false")
215                   << ", error: " << HpackDecodingErrorToString(error);
216   if (error_ == HpackDecodingError::kOk) {
217     listener_->OnHeaderErrorDetected(HpackDecodingErrorToString(error));
218     error_ = error;
219     detailed_error_ = detailed_error;
220   }
221 }
222 
223 }  // namespace http2
224