1
#include "source/extensions/tracers/opentelemetry/span_context_extractor.h"
2

            
3
#include "envoy/tracing/tracer.h"
4

            
5
#include "source/common/http/header_map_impl.h"
6
#include "source/common/tracing/trace_context_impl.h"
7
#include "source/extensions/tracers/opentelemetry/span_context.h"
8

            
9
#include "absl/strings/escaping.h"
10

            
11
namespace Envoy {
12
namespace Extensions {
13
namespace Tracers {
14
namespace OpenTelemetry {
15
namespace {
16

            
17
// See https://www.w3.org/TR/trace-context/#traceparent-header
18
constexpr int kTraceparentHeaderSize = 55; // 2 + 1 + 32 + 1 + 16 + 1 + 2
19
constexpr int kVersionHexSize = 2;
20
constexpr int kTraceIdHexSize = 32;
21
constexpr int kParentIdHexSize = 16;
22
constexpr int kTraceFlagsHexSize = 2;
23

            
24
116
bool isValidHex(const absl::string_view& input) {
25
116
  return std::all_of(input.begin(), input.end(),
26
1501
                     [](const char& c) { return absl::ascii_isxdigit(c); });
27
116
}
28

            
29
47
bool isAllZeros(const absl::string_view& input) {
30
447
  return std::all_of(input.begin(), input.end(), [](const char& c) { return c == '0'; });
31
47
}
32

            
33
} // namespace
34

            
35
SpanContextExtractor::SpanContextExtractor(Tracing::TraceContext& trace_context)
36
103
    : trace_context_(trace_context) {}
37

            
38
103
SpanContextExtractor::~SpanContextExtractor() = default;
39

            
40
89
bool SpanContextExtractor::propagationHeaderPresent() {
41
89
  auto propagation_header = OpenTelemetryConstants::get().TRACE_PARENT.get(trace_context_);
42
89
  return propagation_header.has_value();
43
89
}
44

            
45
55
absl::StatusOr<SpanContext> SpanContextExtractor::extractSpanContext() {
46
55
  auto propagation_header = OpenTelemetryConstants::get().TRACE_PARENT.get(trace_context_);
47
55
  if (!propagation_header.has_value()) {
48
    // We should have already caught this, but just in case.
49
2
    return absl::InvalidArgumentError("No propagation header found");
50
2
  }
51
53
  auto header_value_string = propagation_header.value();
52

            
53
53
  if (header_value_string.size() != kTraceparentHeaderSize) {
54
19
    return absl::InvalidArgumentError("Invalid traceparent header length");
55
19
  }
56
  // Try to split it into its component parts:
57
34
  std::vector<absl::string_view> propagation_header_components =
58
34
      absl::StrSplit(header_value_string, '-', absl::SkipEmpty());
59
34
  if (propagation_header_components.size() != 4) {
60
    return absl::InvalidArgumentError("Invalid traceparent hyphenation");
61
  }
62
34
  absl::string_view version = propagation_header_components[0];
63
34
  absl::string_view trace_id = propagation_header_components[1];
64
34
  absl::string_view span_id = propagation_header_components[2];
65
34
  absl::string_view trace_flags = propagation_header_components[3];
66
34
  if (version.size() != kVersionHexSize || trace_id.size() != kTraceIdHexSize ||
67
34
      span_id.size() != kParentIdHexSize || trace_flags.size() != kTraceFlagsHexSize) {
68
1
    return absl::InvalidArgumentError("Invalid traceparent field sizes");
69
1
  }
70
33
  if (!isValidHex(version) || !isValidHex(trace_id) || !isValidHex(span_id) ||
71
33
      !isValidHex(trace_flags)) {
72
9
    return absl::InvalidArgumentError("Invalid header hex");
73
9
  }
74
  // As per the traceparent header definition, if the trace-id or parent-id are all zeros, they are
75
  // invalid and must be ignored.
76
24
  if (isAllZeros(trace_id)) {
77
1
    return absl::InvalidArgumentError("Invalid trace id");
78
1
  }
79
23
  if (isAllZeros(span_id)) {
80
1
    return absl::InvalidArgumentError("Invalid parent id");
81
1
  }
82

            
83
  // Set whether or not the span is sampled from the trace flags.
84
  // See https://w3c.github.io/trace-context/#trace-flags.
85
22
  char decoded_trace_flags = absl::HexStringToBytes(trace_flags).front();
86
22
  bool sampled = (decoded_trace_flags & 1);
87

            
88
  // If a tracestate header is received without an accompanying traceparent header,
89
  // it is invalid and MUST be discarded. Because we're already checking for the
90
  // traceparent header above, we don't need to check here.
91
  // See https://www.w3.org/TR/trace-context/#processing-model-for-working-with-trace-context
92
22
  const auto tracestate_values = OpenTelemetryConstants::get().TRACE_STATE.getAll(trace_context_);
93

            
94
22
  SpanContext parent_context(version, trace_id, span_id, sampled,
95
22
                             absl::StrJoin(tracestate_values, ","));
96
22
  return parent_context;
97
23
}
98

            
99
} // namespace OpenTelemetry
100
} // namespace Tracers
101
} // namespace Extensions
102
} // namespace Envoy