Coverage Report

Created: 2023-11-12 09:30

/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