Coverage Report

Created: 2024-09-19 09:45

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