• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_PARSE_RESULT_H
16 #define GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_PARSE_RESULT_H
17 
18 #include <grpc/support/port_platform.h>
19 #include <stdint.h>
20 
21 #include <memory>
22 #include <string>
23 #include <utility>
24 
25 #include "absl/log/check.h"
26 #include "absl/status/status.h"
27 #include "absl/strings/str_cat.h"
28 #include "absl/strings/string_view.h"
29 #include "absl/types/optional.h"
30 #include "src/core/lib/surface/validate_metadata.h"
31 #include "src/core/lib/transport/metadata_batch.h"
32 #include "src/core/util/crash.h"
33 #include "src/core/util/ref_counted.h"
34 #include "src/core/util/ref_counted_ptr.h"
35 
36 namespace grpc_core {
37 
38 // Result of parsing
39 // Makes it trivial to identify stream vs connection errors (via a range check)
40 enum class HpackParseStatus : uint8_t {
41   ///////////////////////////////////////////////////////
42   // Non-Errors
43 
44   // Parsed OK
45   kOk,
46   // Parse reached end of the current frame
47   kEof,
48   // Moved from - used to denote a HpackParseResult that has been moved into a
49   // different object, and so the original should be deemed invalid.
50   kMovedFrom,
51 
52   ///////////////////////////////////////////////////////////////////
53   // Stream Errors - result in a stream cancellation
54 
55   // Sentinel value used to denote the first error that is a stream error.
56   // All stream errors are hence >= kFirstStreamError and <
57   // kFirstConnectionError.
58   // Should not be used in switch statements, instead the first error message
59   // after this one should be assigned the same value.
60   kFirstStreamError,
61   kInvalidMetadata = kFirstStreamError,
62   // Hard metadata limit exceeded by the total set of metadata
63   kHardMetadataLimitExceeded,
64   kSoftMetadataLimitExceeded,
65   // Hard metadata limit exceeded by a single key string
66   kHardMetadataLimitExceededByKey,
67   // Hard metadata limit exceeded by a single value string
68   kHardMetadataLimitExceededByValue,
69   kMetadataParseError,
70   // Parse failed due to a base64 decode error
71   kUnbase64Failed,
72 
73   ///////////////////////////////////////////////////////////////////
74   // Connection Errors - result in the tcp connection closing
75 
76   // Sentinel value used to denote the first error that is a connection error.
77   // All connection errors are hence >= kFirstConnectionError.
78   // Should not be used in switch statements, instead the first error message
79   // after this one should be assigned the same value.
80   kFirstConnectionError,
81   // Incomplete header at end of header boundary
82   kIncompleteHeaderAtBoundary = kFirstConnectionError,
83   // Varint out of range
84   kVarintOutOfRange,
85   // Invalid HPACK index
86   kInvalidHpackIndex,
87   // Illegal HPACK table size change
88   kIllegalTableSizeChange,
89   // Trying to add to the hpack table prior to reducing after a settings change
90   kAddBeforeTableSizeUpdated,
91   // Parse failed due to a huffman decode error
92   kParseHuffFailed,
93   // Too many dynamic table size changes in one frame
94   kTooManyDynamicTableSizeChanges,
95   // Maliciously long varint encoding
96   // We don't read past 16 repeated 0x80 prefixes on a varint (all zeros)
97   // because no reasonable varint encoder would emit that (16 is already quite
98   // generous!)
99   // Because we stop reading we don't parse the rest of the bytes and so we
100   // can't recover parsing and would end up with a hpack table desync if we
101   // tried, so this is a connection error.
102   kMaliciousVarintEncoding,
103   // Illegal hpack op code
104   kIllegalHpackOpCode,
105 };
106 
IsStreamError(HpackParseStatus status)107 inline bool IsStreamError(HpackParseStatus status) {
108   return status >= HpackParseStatus::kFirstStreamError &&
109          status < HpackParseStatus::kFirstConnectionError;
110 }
111 
IsConnectionError(HpackParseStatus status)112 inline bool IsConnectionError(HpackParseStatus status) {
113   return status >= HpackParseStatus::kFirstConnectionError;
114 }
115 
IsEphemeralError(HpackParseStatus status)116 inline bool IsEphemeralError(HpackParseStatus status) {
117   switch (status) {
118     case HpackParseStatus::kSoftMetadataLimitExceeded:
119     case HpackParseStatus::kHardMetadataLimitExceeded:
120       return true;
121     default:
122       return false;
123   }
124 }
125 
126 class HpackParseResult {
127  public:
HpackParseResult()128   HpackParseResult() : HpackParseResult{HpackParseStatus::kOk} {}
129 
ok()130   bool ok() const {
131     return state_ == nullptr || state_->status.get() == HpackParseStatus::kOk;
132   }
stream_error()133   bool stream_error() const {
134     return state_ != nullptr && IsStreamError(state_->status.get());
135   }
connection_error()136   bool connection_error() const {
137     return state_ != nullptr && IsConnectionError(state_->status.get());
138   }
ephemeral()139   bool ephemeral() const {
140     return state_ != nullptr && IsEphemeralError(state_->status.get());
141   }
142 
PersistentStreamErrorOrNullptr()143   std::unique_ptr<HpackParseResult> PersistentStreamErrorOrNullptr() const {
144     if (ok() || connection_error() || ephemeral()) return nullptr;
145     return std::make_unique<HpackParseResult>(*this);
146   }
147 
FromStatus(HpackParseStatus status)148   static HpackParseResult FromStatus(HpackParseStatus status) {
149     // Most statuses need some payloads, and we only need this functionality
150     // rarely - so allow list the statuses that we can include here.
151     switch (status) {
152       case HpackParseStatus::kUnbase64Failed:
153       case HpackParseStatus::kParseHuffFailed:
154         return HpackParseResult{status};
155       default:
156         Crash(
157             absl::StrCat("Invalid HpackParseStatus for FromStatus: ", status));
158     }
159   }
160 
FromStatusWithKey(HpackParseStatus status,absl::string_view key)161   static HpackParseResult FromStatusWithKey(HpackParseStatus status,
162                                             absl::string_view key) {
163     auto r = FromStatus(status);
164     if (r.state_ != nullptr) {
165       r.state_->key = std::string(key);
166     }
167     return r;
168   }
169 
MetadataParseError(absl::string_view key)170   static HpackParseResult MetadataParseError(absl::string_view key) {
171     HpackParseResult r{HpackParseStatus::kMetadataParseError};
172     r.state_->key = std::string(key);
173     return r;
174   }
175 
AddBeforeTableSizeUpdated(uint32_t current_size,uint32_t max_size)176   static HpackParseResult AddBeforeTableSizeUpdated(uint32_t current_size,
177                                                     uint32_t max_size) {
178     HpackParseResult p{HpackParseStatus::kAddBeforeTableSizeUpdated};
179     p.state_->illegal_table_size_change =
180         IllegalTableSizeChange{current_size, max_size};
181     return p;
182   }
183 
MaliciousVarintEncodingError()184   static HpackParseResult MaliciousVarintEncodingError() {
185     return HpackParseResult{HpackParseStatus::kMaliciousVarintEncoding};
186   }
187 
IllegalHpackOpCode()188   static HpackParseResult IllegalHpackOpCode() {
189     return HpackParseResult{HpackParseStatus::kIllegalHpackOpCode};
190   }
191 
InvalidMetadataError(ValidateMetadataResult result,absl::string_view key)192   static HpackParseResult InvalidMetadataError(ValidateMetadataResult result,
193                                                absl::string_view key) {
194     DCHECK(result != ValidateMetadataResult::kOk);
195     HpackParseResult p{HpackParseStatus::kInvalidMetadata};
196     p.state_->key = std::string(key);
197     p.state_->validate_metadata_result = result;
198     return p;
199   }
200 
IncompleteHeaderAtBoundaryError()201   static HpackParseResult IncompleteHeaderAtBoundaryError() {
202     return HpackParseResult{HpackParseStatus::kIncompleteHeaderAtBoundary};
203   }
204 
VarintOutOfRangeError(uint32_t value,uint8_t last_byte)205   static HpackParseResult VarintOutOfRangeError(uint32_t value,
206                                                 uint8_t last_byte) {
207     HpackParseResult p{HpackParseStatus::kVarintOutOfRange};
208     p.state_->varint_out_of_range = VarintOutOfRange{last_byte, value};
209     return p;
210   }
211 
InvalidHpackIndexError(uint32_t index)212   static HpackParseResult InvalidHpackIndexError(uint32_t index) {
213     HpackParseResult p{HpackParseStatus::kInvalidHpackIndex};
214     p.state_->invalid_hpack_index = index;
215     return p;
216   }
217 
IllegalTableSizeChangeError(uint32_t new_size,uint32_t max_size)218   static HpackParseResult IllegalTableSizeChangeError(uint32_t new_size,
219                                                       uint32_t max_size) {
220     HpackParseResult p{HpackParseStatus::kIllegalTableSizeChange};
221     p.state_->illegal_table_size_change =
222         IllegalTableSizeChange{new_size, max_size};
223     return p;
224   }
225 
TooManyDynamicTableSizeChangesError()226   static HpackParseResult TooManyDynamicTableSizeChangesError() {
227     return HpackParseResult{HpackParseStatus::kTooManyDynamicTableSizeChanges};
228   }
229 
SoftMetadataLimitExceededError(grpc_metadata_batch * metadata,uint32_t frame_length,uint32_t limit)230   static HpackParseResult SoftMetadataLimitExceededError(
231       grpc_metadata_batch* metadata, uint32_t frame_length, uint32_t limit) {
232     HpackParseResult p{HpackParseStatus::kSoftMetadataLimitExceeded};
233     p.state_->metadata_limit_exceeded =
234         MetadataLimitExceeded{frame_length, limit, metadata};
235     return p;
236   }
237 
HardMetadataLimitExceededError(grpc_metadata_batch * metadata,uint32_t frame_length,uint32_t limit)238   static HpackParseResult HardMetadataLimitExceededError(
239       grpc_metadata_batch* metadata, uint32_t frame_length, uint32_t limit) {
240     HpackParseResult p{HpackParseStatus::kHardMetadataLimitExceeded};
241     p.state_->metadata_limit_exceeded =
242         MetadataLimitExceeded{frame_length, limit, metadata};
243     return p;
244   }
245 
HardMetadataLimitExceededByKeyError(uint32_t key_length,uint32_t limit)246   static HpackParseResult HardMetadataLimitExceededByKeyError(
247       uint32_t key_length, uint32_t limit) {
248     HpackParseResult p{HpackParseStatus::kHardMetadataLimitExceededByKey};
249     p.state_->metadata_limit_exceeded_by_atom =
250         MetadataLimitExceededByAtom{key_length, limit};
251     return p;
252   }
253 
HardMetadataLimitExceededByValueError(absl::string_view key,uint32_t value_length,uint32_t limit)254   static HpackParseResult HardMetadataLimitExceededByValueError(
255       absl::string_view key, uint32_t value_length, uint32_t limit) {
256     HpackParseResult p{HpackParseStatus::kHardMetadataLimitExceededByValue};
257     p.state_->metadata_limit_exceeded_by_atom =
258         MetadataLimitExceededByAtom{value_length, limit};
259     p.state_->key = std::string(key);
260     return p;
261   }
262 
263   // Compute the absl::Status that goes along with this HpackParseResult.
264   // (may be cached, so this is not thread safe)
265   absl::Status Materialize() const;
266 
267  private:
HpackParseResult(HpackParseStatus status)268   explicit HpackParseResult(HpackParseStatus status) {
269     // Dynamically allocate state if status is not ok.
270     if (status != HpackParseStatus::kOk) {
271       state_ = MakeRefCounted<HpackParseResultState>(status);
272     }
273   }
274 
275   absl::Status BuildMaterialized() const;
276 
277   struct VarintOutOfRange {
278     uint8_t last_byte;
279     uint32_t value;
280   };
281 
282   struct MetadataLimitExceeded {
283     uint32_t frame_length;
284     uint32_t limit;
285     grpc_metadata_batch* prior;
286   };
287 
288   // atom here means one of either a key or a value - so this is used for when a
289   // metadata limit is consumed by either of these.
290   struct MetadataLimitExceededByAtom {
291     uint32_t atom_length;
292     uint32_t limit;
293   };
294 
295   struct IllegalTableSizeChange {
296     uint32_t new_size;
297     uint32_t max_size;
298   };
299 
300   class StatusWrapper {
301    public:
StatusWrapper(HpackParseStatus status)302     explicit StatusWrapper(HpackParseStatus status) : status_(status) {}
303 
304     StatusWrapper(const StatusWrapper&) = default;
305     StatusWrapper& operator=(const StatusWrapper&) = default;
StatusWrapper(StatusWrapper && other)306     StatusWrapper(StatusWrapper&& other) noexcept
307         : status_(std::exchange(other.status_, HpackParseStatus::kMovedFrom)) {}
308     StatusWrapper& operator=(StatusWrapper&& other) noexcept {
309       status_ = std::exchange(other.status_, HpackParseStatus::kMovedFrom);
310       return *this;
311     }
312 
get()313     HpackParseStatus get() const { return status_; }
314 
315    private:
316     HpackParseStatus status_;
317   };
318 
319   struct HpackParseResultState : public RefCounted<HpackParseResultState> {
HpackParseResultStateHpackParseResultState320     explicit HpackParseResultState(HpackParseStatus incoming_status)
321         : status(incoming_status) {}
322     StatusWrapper status;
323     union {
324       // Set if status == kInvalidMetadata
325       ValidateMetadataResult validate_metadata_result;
326       // Set if status == kVarintOutOfRange
327       VarintOutOfRange varint_out_of_range;
328       // Set if status == kInvalidHpackIndex
329       uint32_t invalid_hpack_index;
330       // Set if status == kHardMetadataLimitExceeded or
331       // kSoftMetadataLimitExceeded
332       MetadataLimitExceeded metadata_limit_exceeded;
333       // Set if status == kHardMetadataLimitExceededByKey or
334       // kHardMetadataLimitExceededByValue
335       MetadataLimitExceededByAtom metadata_limit_exceeded_by_atom;
336       // Set if status == kIllegalTableSizeChange
337       IllegalTableSizeChange illegal_table_size_change;
338     };
339     std::string key;
340     mutable absl::optional<absl::Status> materialized_status;
341   };
342 
343   RefCountedPtr<HpackParseResultState> state_ = nullptr;
344 };
345 
346 }  // namespace grpc_core
347 
348 #endif  // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_PARSE_RESULT_H
349