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 
20 #include <stdint.h>
21 
22 #include <string>
23 #include <utility>
24 
25 #include "absl/status/status.h"
26 #include "absl/strings/str_cat.h"
27 #include "absl/strings/string_view.h"
28 #include "absl/types/optional.h"
29 
30 #include <grpc/support/log.h>
31 
32 #include "src/core/lib/gprpp/crash.h"
33 #include "src/core/lib/surface/validate_metadata.h"
34 #include "src/core/lib/transport/metadata_batch.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 { return status_.get() == HpackParseStatus::kOk; }
stream_error()131   bool stream_error() const { return IsStreamError(status_.get()); }
connection_error()132   bool connection_error() const { return IsConnectionError(status_.get()); }
ephemeral()133   bool ephemeral() const { return IsEphemeralError(status_.get()); }
134 
PersistentStreamErrorOrOk()135   HpackParseResult PersistentStreamErrorOrOk() const {
136     if (connection_error() || ephemeral()) return HpackParseResult();
137     return *this;
138   }
139 
FromStatus(HpackParseStatus status)140   static HpackParseResult FromStatus(HpackParseStatus status) {
141     // Most statuses need some payloads, and we only need this functionality
142     // rarely - so allow list the statuses that we can include here.
143     switch (status) {
144       case HpackParseStatus::kUnbase64Failed:
145       case HpackParseStatus::kParseHuffFailed:
146         return HpackParseResult{status};
147       default:
148         Crash(
149             absl::StrCat("Invalid HpackParseStatus for FromStatus: ", status));
150     }
151   }
152 
FromStatusWithKey(HpackParseStatus status,absl::string_view key)153   static HpackParseResult FromStatusWithKey(HpackParseStatus status,
154                                             absl::string_view key) {
155     auto r = FromStatus(status);
156     r.key_ = std::string(key);
157     return r;
158   }
159 
MetadataParseError(absl::string_view key)160   static HpackParseResult MetadataParseError(absl::string_view key) {
161     HpackParseResult r{HpackParseStatus::kMetadataParseError};
162     r.key_ = std::string(key);
163     return r;
164   }
165 
AddBeforeTableSizeUpdated(uint32_t current_size,uint32_t max_size)166   static HpackParseResult AddBeforeTableSizeUpdated(uint32_t current_size,
167                                                     uint32_t max_size) {
168     HpackParseResult p{HpackParseStatus::kAddBeforeTableSizeUpdated};
169     p.illegal_table_size_change_ =
170         IllegalTableSizeChange{current_size, max_size};
171     return p;
172   }
173 
MaliciousVarintEncodingError()174   static HpackParseResult MaliciousVarintEncodingError() {
175     return HpackParseResult{HpackParseStatus::kMaliciousVarintEncoding};
176   }
177 
IllegalHpackOpCode()178   static HpackParseResult IllegalHpackOpCode() {
179     return HpackParseResult{HpackParseStatus::kIllegalHpackOpCode};
180   }
181 
InvalidMetadataError(ValidateMetadataResult result,absl::string_view key)182   static HpackParseResult InvalidMetadataError(ValidateMetadataResult result,
183                                                absl::string_view key) {
184     GPR_DEBUG_ASSERT(result != ValidateMetadataResult::kOk);
185     HpackParseResult p{HpackParseStatus::kInvalidMetadata};
186     p.key_ = std::string(key);
187     p.validate_metadata_result_ = result;
188     return p;
189   }
190 
IncompleteHeaderAtBoundaryError()191   static HpackParseResult IncompleteHeaderAtBoundaryError() {
192     return HpackParseResult{HpackParseStatus::kIncompleteHeaderAtBoundary};
193   }
194 
VarintOutOfRangeError(uint32_t value,uint8_t last_byte)195   static HpackParseResult VarintOutOfRangeError(uint32_t value,
196                                                 uint8_t last_byte) {
197     HpackParseResult p{HpackParseStatus::kVarintOutOfRange};
198     p.varint_out_of_range_ = VarintOutOfRange{last_byte, value};
199     return p;
200   }
201 
InvalidHpackIndexError(uint32_t index)202   static HpackParseResult InvalidHpackIndexError(uint32_t index) {
203     HpackParseResult p{HpackParseStatus::kInvalidHpackIndex};
204     p.invalid_hpack_index_ = index;
205     return p;
206   }
207 
IllegalTableSizeChangeError(uint32_t new_size,uint32_t max_size)208   static HpackParseResult IllegalTableSizeChangeError(uint32_t new_size,
209                                                       uint32_t max_size) {
210     HpackParseResult p{HpackParseStatus::kIllegalTableSizeChange};
211     p.illegal_table_size_change_ = IllegalTableSizeChange{new_size, max_size};
212     return p;
213   }
214 
TooManyDynamicTableSizeChangesError()215   static HpackParseResult TooManyDynamicTableSizeChangesError() {
216     return HpackParseResult{HpackParseStatus::kTooManyDynamicTableSizeChanges};
217   }
218 
SoftMetadataLimitExceededError(grpc_metadata_batch * metadata,uint32_t frame_length,uint32_t limit)219   static HpackParseResult SoftMetadataLimitExceededError(
220       grpc_metadata_batch* metadata, uint32_t frame_length, uint32_t limit) {
221     HpackParseResult p{HpackParseStatus::kSoftMetadataLimitExceeded};
222     p.metadata_limit_exceeded_ =
223         MetadataLimitExceeded{frame_length, limit, metadata};
224     return p;
225   }
226 
HardMetadataLimitExceededError(grpc_metadata_batch * metadata,uint32_t frame_length,uint32_t limit)227   static HpackParseResult HardMetadataLimitExceededError(
228       grpc_metadata_batch* metadata, uint32_t frame_length, uint32_t limit) {
229     HpackParseResult p{HpackParseStatus::kHardMetadataLimitExceeded};
230     p.metadata_limit_exceeded_ =
231         MetadataLimitExceeded{frame_length, limit, metadata};
232     return p;
233   }
234 
HardMetadataLimitExceededByKeyError(uint32_t key_length,uint32_t limit)235   static HpackParseResult HardMetadataLimitExceededByKeyError(
236       uint32_t key_length, uint32_t limit) {
237     HpackParseResult p{HpackParseStatus::kHardMetadataLimitExceededByKey};
238     p.metadata_limit_exceeded_by_atom_ =
239         MetadataLimitExceededByAtom{key_length, limit};
240     return p;
241   }
242 
HardMetadataLimitExceededByValueError(absl::string_view key,uint32_t value_length,uint32_t limit)243   static HpackParseResult HardMetadataLimitExceededByValueError(
244       absl::string_view key, uint32_t value_length, uint32_t limit) {
245     HpackParseResult p{HpackParseStatus::kHardMetadataLimitExceededByValue};
246     p.metadata_limit_exceeded_by_atom_ =
247         MetadataLimitExceededByAtom{value_length, limit};
248     p.key_ = std::string(key);
249     return p;
250   }
251 
252   // Compute the absl::Status that goes along with this HpackParseResult.
253   // (may be cached, so this is not thread safe)
254   absl::Status Materialize() const;
255 
256  private:
HpackParseResult(HpackParseStatus status)257   explicit HpackParseResult(HpackParseStatus status) : status_(status) {}
258   absl::Status BuildMaterialized() const;
259 
260   struct VarintOutOfRange {
261     uint8_t last_byte;
262     uint32_t value;
263   };
264 
265   struct MetadataLimitExceeded {
266     uint32_t frame_length;
267     uint32_t limit;
268     grpc_metadata_batch* prior;
269   };
270 
271   // atom here means one of either a key or a value - so this is used for when a
272   // metadata limit is consumed by either of these.
273   struct MetadataLimitExceededByAtom {
274     uint32_t atom_length;
275     uint32_t limit;
276   };
277 
278   struct IllegalTableSizeChange {
279     uint32_t new_size;
280     uint32_t max_size;
281   };
282 
283   class StatusWrapper {
284    public:
StatusWrapper(HpackParseStatus status)285     explicit StatusWrapper(HpackParseStatus status) : status_(status) {}
286 
287     StatusWrapper(const StatusWrapper&) = default;
288     StatusWrapper& operator=(const StatusWrapper&) = default;
StatusWrapper(StatusWrapper && other)289     StatusWrapper(StatusWrapper&& other) noexcept
290         : status_(std::exchange(other.status_, HpackParseStatus::kMovedFrom)) {}
291     StatusWrapper& operator=(StatusWrapper&& other) noexcept {
292       status_ = std::exchange(other.status_, HpackParseStatus::kMovedFrom);
293       return *this;
294     }
295 
get()296     HpackParseStatus get() const { return status_; }
297 
298    private:
299     HpackParseStatus status_;
300   };
301 
302   StatusWrapper status_;
303   union {
304     // Set if status == kInvalidMetadata
305     ValidateMetadataResult validate_metadata_result_;
306     // Set if status == kVarintOutOfRange
307     VarintOutOfRange varint_out_of_range_;
308     // Set if status == kInvalidHpackIndex
309     uint32_t invalid_hpack_index_;
310     // Set if status == kHardMetadataLimitExceeded or
311     // kSoftMetadataLimitExceeded
312     MetadataLimitExceeded metadata_limit_exceeded_;
313     // Set if status == kHardMetadataLimitExceededByKey or
314     // kHardMetadataLimitExceededByValue
315     MetadataLimitExceededByAtom metadata_limit_exceeded_by_atom_;
316     // Set if status == kIllegalTableSizeChange
317     IllegalTableSizeChange illegal_table_size_change_;
318   };
319   std::string key_;
320   mutable absl::optional<absl::Status> materialized_status_;
321 };
322 
323 }  // namespace grpc_core
324 
325 #endif  // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_PARSE_RESULT_H
326