/proc/self/cwd/source/common/grpc/common.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/common/grpc/common.h" |
2 | | |
3 | | #include <atomic> |
4 | | #include <cstdint> |
5 | | #include <cstring> |
6 | | #include <string> |
7 | | |
8 | | #include "source/common/buffer/buffer_impl.h" |
9 | | #include "source/common/buffer/zero_copy_input_stream_impl.h" |
10 | | #include "source/common/common/assert.h" |
11 | | #include "source/common/common/base64.h" |
12 | | #include "source/common/common/empty_string.h" |
13 | | #include "source/common/common/enum_to_int.h" |
14 | | #include "source/common/common/fmt.h" |
15 | | #include "source/common/common/macros.h" |
16 | | #include "source/common/common/safe_memcpy.h" |
17 | | #include "source/common/common/utility.h" |
18 | | #include "source/common/http/header_utility.h" |
19 | | #include "source/common/http/headers.h" |
20 | | #include "source/common/http/message_impl.h" |
21 | | #include "source/common/http/utility.h" |
22 | | #include "source/common/protobuf/protobuf.h" |
23 | | |
24 | | #include "absl/container/fixed_array.h" |
25 | | #include "absl/strings/match.h" |
26 | | |
27 | | namespace Envoy { |
28 | | namespace Grpc { |
29 | | |
30 | 151k | bool Common::hasGrpcContentType(const Http::RequestOrResponseHeaderMap& headers) { |
31 | 151k | const absl::string_view content_type = headers.getContentTypeValue(); |
32 | | // Content type is gRPC if it is exactly "application/grpc" or starts with |
33 | | // "application/grpc+". Specifically, something like application/grpc-web is not gRPC. |
34 | 151k | return absl::StartsWith(content_type, Http::Headers::get().ContentTypeValues.Grpc) && |
35 | 151k | (content_type.size() == Http::Headers::get().ContentTypeValues.Grpc.size() || |
36 | 6.63k | content_type[Http::Headers::get().ContentTypeValues.Grpc.size()] == '+'); |
37 | 151k | } |
38 | | |
39 | 254 | bool Common::hasConnectProtocolVersionHeader(const Http::RequestOrResponseHeaderMap& headers) { |
40 | 254 | return !headers.get(Http::CustomHeaders::get().ConnectProtocolVersion).empty(); |
41 | 254 | } |
42 | | |
43 | 508 | bool Common::hasConnectStreamingContentType(const Http::RequestOrResponseHeaderMap& headers) { |
44 | | // Consider the request a connect request if the content type starts with "application/connect+". |
45 | 508 | static constexpr absl::string_view connect_prefix{"application/connect+"}; |
46 | 508 | const absl::string_view content_type = headers.getContentTypeValue(); |
47 | 508 | return absl::StartsWith(content_type, connect_prefix); |
48 | 508 | } |
49 | | |
50 | 5 | bool Common::hasProtobufContentType(const Http::RequestOrResponseHeaderMap& headers) { |
51 | 5 | return headers.getContentTypeValue() == Http::Headers::get().ContentTypeValues.Protobuf; |
52 | 5 | } |
53 | | |
54 | 252k | bool Common::isGrpcRequestHeaders(const Http::RequestHeaderMap& headers) { |
55 | 252k | if (!headers.Path()) { |
56 | 103k | return false; |
57 | 103k | } |
58 | 149k | return hasGrpcContentType(headers); |
59 | 252k | } |
60 | | |
61 | 254 | bool Common::isConnectRequestHeaders(const Http::RequestHeaderMap& headers) { |
62 | 254 | if (!headers.Path()) { |
63 | 0 | return false; |
64 | 0 | } |
65 | 254 | return hasConnectProtocolVersionHeader(headers); |
66 | 254 | } |
67 | | |
68 | 254 | bool Common::isConnectStreamingRequestHeaders(const Http::RequestHeaderMap& headers) { |
69 | 254 | if (!headers.Path()) { |
70 | 0 | return false; |
71 | 0 | } |
72 | 254 | return hasConnectStreamingContentType(headers); |
73 | 254 | } |
74 | | |
75 | 5 | bool Common::isProtobufRequestHeaders(const Http::RequestHeaderMap& headers) { |
76 | 5 | if (!headers.Path()) { |
77 | 0 | return false; |
78 | 0 | } |
79 | 5 | return hasProtobufContentType(headers); |
80 | 5 | } |
81 | | |
82 | 2.56k | bool Common::isGrpcResponseHeaders(const Http::ResponseHeaderMap& headers, bool end_stream) { |
83 | 2.56k | if (end_stream) { |
84 | | // Trailers-only response, only grpc-status is required. |
85 | 861 | return headers.GrpcStatus() != nullptr; |
86 | 861 | } |
87 | 1.70k | if (Http::Utility::getResponseStatus(headers) != enumToInt(Http::Code::OK)) { |
88 | 0 | return false; |
89 | 0 | } |
90 | 1.70k | return hasGrpcContentType(headers); |
91 | 1.70k | } |
92 | | |
93 | 254 | bool Common::isConnectStreamingResponseHeaders(const Http::ResponseHeaderMap& headers) { |
94 | 254 | if (Http::Utility::getResponseStatus(headers) != enumToInt(Http::Code::OK)) { |
95 | 0 | return false; |
96 | 0 | } |
97 | 254 | return hasConnectStreamingContentType(headers); |
98 | 254 | } |
99 | | |
100 | | absl::optional<Status::GrpcStatus> |
101 | 7.23k | Common::getGrpcStatus(const Http::ResponseHeaderOrTrailerMap& trailers, bool allow_user_defined) { |
102 | 7.23k | const absl::string_view grpc_status_header = trailers.getGrpcStatusValue(); |
103 | 7.23k | uint64_t grpc_status_code; |
104 | | |
105 | 7.23k | if (grpc_status_header.empty()) { |
106 | 5.76k | return absl::nullopt; |
107 | 5.76k | } |
108 | 1.46k | if (!absl::SimpleAtoi(grpc_status_header, &grpc_status_code) || |
109 | 1.46k | (grpc_status_code > Status::WellKnownGrpcStatus::MaximumKnown && !allow_user_defined)) { |
110 | 538 | return {Status::WellKnownGrpcStatus::InvalidCode}; |
111 | 538 | } |
112 | 929 | return {static_cast<Status::GrpcStatus>(grpc_status_code)}; |
113 | 1.46k | } |
114 | | |
115 | | absl::optional<Status::GrpcStatus> Common::getGrpcStatus(const Http::ResponseTrailerMap& trailers, |
116 | | const Http::ResponseHeaderMap& headers, |
117 | | const StreamInfo::StreamInfo& info, |
118 | 1.97k | bool allow_user_defined) { |
119 | | // The gRPC specification does not guarantee a gRPC status code will be returned from a gRPC |
120 | | // request. When it is returned, it will be in the response trailers. With that said, Envoy will |
121 | | // treat a trailers-only response as a headers-only response, so we have to check the following |
122 | | // in order: |
123 | | // 1. trailers gRPC status, if it exists. |
124 | | // 2. headers gRPC status, if it exists. |
125 | | // 3. Inferred from info HTTP status, if it exists. |
126 | 1.97k | absl::optional<Grpc::Status::GrpcStatus> optional_status; |
127 | 1.97k | optional_status = Grpc::Common::getGrpcStatus(trailers, allow_user_defined); |
128 | 1.97k | if (optional_status.has_value()) { |
129 | 394 | return optional_status; |
130 | 394 | } |
131 | 1.58k | optional_status = Grpc::Common::getGrpcStatus(headers, allow_user_defined); |
132 | 1.58k | if (optional_status.has_value()) { |
133 | 309 | return optional_status; |
134 | 309 | } |
135 | 1.27k | return info.responseCode() ? absl::optional<Grpc::Status::GrpcStatus>( |
136 | 1.08k | Grpc::Utility::httpToGrpcStatus(info.responseCode().value())) |
137 | 1.27k | : absl::nullopt; |
138 | 1.58k | } |
139 | | |
140 | 575 | std::string Common::getGrpcMessage(const Http::ResponseHeaderOrTrailerMap& trailers) { |
141 | 575 | const auto entry = trailers.GrpcMessage(); |
142 | 575 | return entry ? std::string(entry->value().getStringView()) : EMPTY_STRING; |
143 | 575 | } |
144 | | |
145 | | absl::optional<google::rpc::Status> |
146 | 0 | Common::getGrpcStatusDetailsBin(const Http::HeaderMap& trailers) { |
147 | 0 | const auto details_header = trailers.get(Http::Headers::get().GrpcStatusDetailsBin); |
148 | 0 | if (details_header.empty()) { |
149 | 0 | return absl::nullopt; |
150 | 0 | } |
151 | | |
152 | | // Some implementations use non-padded base64 encoding for grpc-status-details-bin. |
153 | | // This is effectively a trusted header so using the first value is fine. |
154 | 0 | auto decoded_value = Base64::decodeWithoutPadding(details_header[0]->value().getStringView()); |
155 | 0 | if (decoded_value.empty()) { |
156 | 0 | return absl::nullopt; |
157 | 0 | } |
158 | | |
159 | 0 | google::rpc::Status status; |
160 | 0 | if (!status.ParseFromString(decoded_value)) { |
161 | 0 | return absl::nullopt; |
162 | 0 | } |
163 | | |
164 | 0 | return {std::move(status)}; |
165 | 0 | } |
166 | | |
167 | 19.0k | Buffer::InstancePtr Common::serializeToGrpcFrame(const Protobuf::Message& message) { |
168 | | // http://www.grpc.io/docs/guides/wire.html |
169 | | // Reserve enough space for the entire message and the 5 byte header. |
170 | | // NB: we do not use prependGrpcFrameHeader because that would add another BufferFragment and this |
171 | | // (using a single BufferFragment) is more efficient. |
172 | 19.0k | Buffer::InstancePtr body(new Buffer::OwnedImpl()); |
173 | 19.0k | const uint32_t size = message.ByteSize(); |
174 | 19.0k | const uint32_t alloc_size = size + 5; |
175 | 19.0k | auto reservation = body->reserveSingleSlice(alloc_size); |
176 | 19.0k | ASSERT(reservation.slice().len_ >= alloc_size); |
177 | 19.0k | uint8_t* current = reinterpret_cast<uint8_t*>(reservation.slice().mem_); |
178 | 19.0k | *current++ = 0; // flags |
179 | 19.0k | const uint32_t nsize = htonl(size); |
180 | 19.0k | safeMemcpyUnsafeDst(current, &nsize); |
181 | 19.0k | current += sizeof(uint32_t); |
182 | 19.0k | Protobuf::io::ArrayOutputStream stream(current, size, -1); |
183 | 19.0k | Protobuf::io::CodedOutputStream codec_stream(&stream); |
184 | 19.0k | message.SerializeWithCachedSizes(&codec_stream); |
185 | 19.0k | reservation.commit(alloc_size); |
186 | 19.0k | return body; |
187 | 19.0k | } |
188 | | |
189 | 2.78k | Buffer::InstancePtr Common::serializeMessage(const Protobuf::Message& message) { |
190 | 2.78k | auto body = std::make_unique<Buffer::OwnedImpl>(); |
191 | 2.78k | const uint32_t size = message.ByteSize(); |
192 | 2.78k | auto reservation = body->reserveSingleSlice(size); |
193 | 2.78k | ASSERT(reservation.slice().len_ >= size); |
194 | 2.78k | uint8_t* current = reinterpret_cast<uint8_t*>(reservation.slice().mem_); |
195 | 2.78k | Protobuf::io::ArrayOutputStream stream(current, size, -1); |
196 | 2.78k | Protobuf::io::CodedOutputStream codec_stream(&stream); |
197 | 2.78k | message.SerializeWithCachedSizes(&codec_stream); |
198 | 2.78k | reservation.commit(size); |
199 | 2.78k | return body; |
200 | 2.78k | } |
201 | | |
202 | | absl::optional<std::chrono::milliseconds> |
203 | 171k | Common::getGrpcTimeout(const Http::RequestHeaderMap& request_headers) { |
204 | 171k | const Http::HeaderEntry* header_grpc_timeout_entry = request_headers.GrpcTimeout(); |
205 | 171k | std::chrono::milliseconds timeout; |
206 | 171k | if (header_grpc_timeout_entry) { |
207 | 451 | int64_t grpc_timeout; |
208 | 451 | absl::string_view timeout_entry = header_grpc_timeout_entry->value().getStringView(); |
209 | 451 | if (timeout_entry.empty()) { |
210 | | // Must be of the form TimeoutValue TimeoutUnit. See |
211 | | // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests. |
212 | 35 | return absl::nullopt; |
213 | 35 | } |
214 | | // TimeoutValue must be a positive integer of at most 8 digits. |
215 | 416 | if (absl::SimpleAtoi(timeout_entry.substr(0, timeout_entry.size() - 1), &grpc_timeout) && |
216 | 416 | grpc_timeout >= 0 && static_cast<uint64_t>(grpc_timeout) <= MAX_GRPC_TIMEOUT_VALUE) { |
217 | 262 | const char unit = timeout_entry[timeout_entry.size() - 1]; |
218 | 262 | switch (unit) { |
219 | 18 | case 'H': |
220 | 18 | return std::chrono::hours(grpc_timeout); |
221 | 18 | case 'M': |
222 | 18 | return std::chrono::minutes(grpc_timeout); |
223 | 18 | case 'S': |
224 | 18 | return std::chrono::seconds(grpc_timeout); |
225 | 34 | case 'm': |
226 | 34 | return std::chrono::milliseconds(grpc_timeout); |
227 | 0 | break; |
228 | 76 | case 'u': |
229 | 76 | timeout = std::chrono::duration_cast<std::chrono::milliseconds>( |
230 | 76 | std::chrono::microseconds(grpc_timeout)); |
231 | 76 | if (timeout < std::chrono::microseconds(grpc_timeout)) { |
232 | 42 | timeout++; |
233 | 42 | } |
234 | 76 | return timeout; |
235 | 64 | case 'n': |
236 | 64 | timeout = std::chrono::duration_cast<std::chrono::milliseconds>( |
237 | 64 | std::chrono::nanoseconds(grpc_timeout)); |
238 | 64 | if (timeout < std::chrono::nanoseconds(grpc_timeout)) { |
239 | 39 | timeout++; |
240 | 39 | } |
241 | 64 | return timeout; |
242 | 262 | } |
243 | 262 | } |
244 | 416 | } |
245 | 171k | return absl::nullopt; |
246 | 171k | } |
247 | | |
248 | | void Common::toGrpcTimeout(const std::chrono::milliseconds& timeout, |
249 | 18.4k | Http::RequestHeaderMap& headers) { |
250 | 18.4k | uint64_t time = timeout.count(); |
251 | 18.4k | static const char units[] = "mSMH"; |
252 | 18.4k | const char* unit = units; // start with milliseconds |
253 | 18.4k | if (time > MAX_GRPC_TIMEOUT_VALUE) { |
254 | 468 | time /= 1000; // Convert from milliseconds to seconds |
255 | 468 | unit++; |
256 | 468 | } |
257 | 19.0k | while (time > MAX_GRPC_TIMEOUT_VALUE) { |
258 | 598 | if (*unit == 'H') { |
259 | 0 | time = MAX_GRPC_TIMEOUT_VALUE; // No bigger unit available, clip to max 8 digit hours. |
260 | 598 | } else { |
261 | 598 | time /= 60; // Convert from seconds to minutes to hours |
262 | 598 | unit++; |
263 | 598 | } |
264 | 598 | } |
265 | 18.4k | headers.setGrpcTimeout(absl::StrCat(time, absl::string_view(unit, 1))); |
266 | 18.4k | } |
267 | | |
268 | | Http::RequestMessagePtr |
269 | | Common::prepareHeaders(const std::string& host_name, const std::string& service_full_name, |
270 | | const std::string& method_name, |
271 | 19.5k | const absl::optional<std::chrono::milliseconds>& timeout) { |
272 | 19.5k | Http::RequestMessagePtr message(new Http::RequestMessageImpl()); |
273 | 19.5k | message->headers().setReferenceMethod(Http::Headers::get().MethodValues.Post); |
274 | 19.5k | message->headers().setPath(absl::StrCat("/", service_full_name, "/", method_name)); |
275 | 19.5k | message->headers().setHost(host_name); |
276 | | // According to https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md TE should appear |
277 | | // before Timeout and ContentType. |
278 | 19.5k | message->headers().setReferenceTE(Http::Headers::get().TEValues.Trailers); |
279 | 19.5k | if (timeout) { |
280 | 0 | toGrpcTimeout(timeout.value(), message->headers()); |
281 | 0 | } |
282 | 19.5k | message->headers().setReferenceContentType(Http::Headers::get().ContentTypeValues.Grpc); |
283 | | |
284 | 19.5k | return message; |
285 | 19.5k | } |
286 | | |
287 | 29.2k | const std::string& Common::typeUrlPrefix() { |
288 | 29.2k | CONSTRUCT_ON_FIRST_USE(std::string, "type.googleapis.com"); |
289 | 29.2k | } |
290 | | |
291 | 28.8k | std::string Common::typeUrl(const std::string& qualified_name) { |
292 | 28.8k | return typeUrlPrefix() + "/" + qualified_name; |
293 | 28.8k | } |
294 | | |
295 | 2.34k | void Common::prependGrpcFrameHeader(Buffer::Instance& buffer) { |
296 | 2.34k | std::array<char, 5> header; |
297 | 2.34k | header[0] = 0; // flags |
298 | 2.34k | const uint32_t nsize = htonl(buffer.length()); |
299 | 2.34k | safeMemcpyUnsafeDst(&header[1], &nsize); |
300 | 2.34k | buffer.prepend(absl::string_view(&header[0], 5)); |
301 | 2.34k | } |
302 | | |
303 | 0 | bool Common::parseBufferInstance(Buffer::InstancePtr&& buffer, Protobuf::Message& proto) { |
304 | 0 | Buffer::ZeroCopyInputStreamImpl stream(std::move(buffer)); |
305 | 0 | return proto.ParseFromZeroCopyStream(&stream); |
306 | 0 | } |
307 | | |
308 | | absl::optional<Common::RequestNames> |
309 | 128 | Common::resolveServiceAndMethod(const Http::HeaderEntry* path) { |
310 | 128 | absl::optional<RequestNames> request_names; |
311 | 128 | if (path == nullptr) { |
312 | 0 | return request_names; |
313 | 0 | } |
314 | 128 | absl::string_view str = path->value().getStringView(); |
315 | 128 | str = str.substr(0, str.find('?')); |
316 | 128 | const auto parts = StringUtil::splitToken(str, "/"); |
317 | 128 | if (parts.size() != 2) { |
318 | 58 | return request_names; |
319 | 58 | } |
320 | 70 | request_names = RequestNames{parts[0], parts[1]}; |
321 | 70 | return request_names; |
322 | 128 | } |
323 | | |
324 | | } // namespace Grpc |
325 | | } // namespace Envoy |