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