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