otel tracing: add binary format, grpcTraceBinContextPropagator (#11409) · grpc/grpc-java@043ba55
1+/*
2+ * Copyright 2024 The gRPC Authors
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+17+package io.grpc.opentelemetry;
18+19+20+import static com.google.common.base.Preconditions.checkNotNull;
21+22+import io.grpc.Metadata;
23+import io.opentelemetry.api.trace.SpanContext;
24+import io.opentelemetry.api.trace.SpanId;
25+import io.opentelemetry.api.trace.TraceFlags;
26+import io.opentelemetry.api.trace.TraceId;
27+import io.opentelemetry.api.trace.TraceState;
28+import java.util.Arrays;
29+30+/**
31+ * Binary encoded {@link SpanContext} for context propagation. This is adapted from OpenCensus
32+ * binary format.
33+ *
34+ * <p>BinaryFormat format:
35+ *
36+ * <ul>
37+ * <li>Binary value: <version_id><version_format>
38+ * <li>version_id: 1-byte representing the version id.
39+ * <li>For version_id = 0:
40+ * <ul>
41+ * <li>version_format: <field><field>
42+ * <li>field_format: <field_id><field_format>
43+ * <li>Fields:
44+ * <ul>
45+ * <li>TraceId: (field_id = 0, len = 16, default = "0000000000000000") -
46+ * 16-byte array representing the trace_id.
47+ * <li>SpanId: (field_id = 1, len = 8, default = "00000000") - 8-byte array
48+ * representing the span_id.
49+ * <li>TraceFlags: (field_id = 2, len = 1, default = "0") - 1-byte array
50+ * representing the trace_flags.
51+ * </ul>
52+ * <li>Fields MUST be encoded using the field id order (smaller to higher).
53+ * <li>Valid value example:
54+ * <ul>
55+ * <li>{0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97,
56+ * 98, 99, 100, 101, 102, 103, 104, 2, 1}
57+ * <li>version_id = 0;
58+ * <li>trace_id = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}
59+ * <li>span_id = {97, 98, 99, 100, 101, 102, 103, 104};
60+ * <li>trace_flags = {1};
61+ * </ul>
62+ * </ul>
63+ * </ul>
64+ */
65+final class BinaryFormat implements Metadata.BinaryMarshaller<SpanContext> {
66+private static final byte VERSION_ID = 0;
67+private static final int VERSION_ID_OFFSET = 0;
68+private static final byte ID_SIZE = 1;
69+private static final byte TRACE_ID_FIELD_ID = 0;
70+71+private static final int TRACE_ID_FIELD_ID_OFFSET = VERSION_ID_OFFSET + ID_SIZE;
72+private static final int TRACE_ID_OFFSET = TRACE_ID_FIELD_ID_OFFSET + ID_SIZE;
73+private static final int TRACE_ID_SIZE = TraceId.getLength() / 2;
74+75+private static final byte SPAN_ID_FIELD_ID = 1;
76+private static final int SPAN_ID_FIELD_ID_OFFSET = TRACE_ID_OFFSET + TRACE_ID_SIZE;
77+private static final int SPAN_ID_OFFSET = SPAN_ID_FIELD_ID_OFFSET + ID_SIZE;
78+private static final int SPAN_ID_SIZE = SpanId.getLength() / 2;
79+80+private static final byte TRACE_FLAG_FIELD_ID = 2;
81+private static final int TRACE_FLAG_FIELD_ID_OFFSET = SPAN_ID_OFFSET + SPAN_ID_SIZE;
82+private static final int TRACE_FLAG_OFFSET = TRACE_FLAG_FIELD_ID_OFFSET + ID_SIZE;
83+private static final int REQUIRED_FORMAT_LENGTH = 3 * ID_SIZE + TRACE_ID_SIZE + SPAN_ID_SIZE;
84+private static final int TRACE_FLAG_SIZE = TraceFlags.getLength() / 2;
85+private static final int ALL_FORMAT_LENGTH = REQUIRED_FORMAT_LENGTH + ID_SIZE + TRACE_FLAG_SIZE;
86+87+private static final BinaryFormat INSTANCE = new BinaryFormat();
88+89+public static BinaryFormat getInstance() {
90+return INSTANCE;
91+ }
92+93+@Override
94+public byte[] toBytes(SpanContext spanContext) {
95+checkNotNull(spanContext, "spanContext");
96+byte[] bytes = new byte[ALL_FORMAT_LENGTH];
97+bytes[VERSION_ID_OFFSET] = VERSION_ID;
98+bytes[TRACE_ID_FIELD_ID_OFFSET] = TRACE_ID_FIELD_ID;
99+System.arraycopy(spanContext.getTraceIdBytes(), 0, bytes, TRACE_ID_OFFSET, TRACE_ID_SIZE);
100+bytes[SPAN_ID_FIELD_ID_OFFSET] = SPAN_ID_FIELD_ID;
101+System.arraycopy(spanContext.getSpanIdBytes(), 0, bytes, SPAN_ID_OFFSET, SPAN_ID_SIZE);
102+bytes[TRACE_FLAG_FIELD_ID_OFFSET] = TRACE_FLAG_FIELD_ID;
103+bytes[TRACE_FLAG_OFFSET] = spanContext.getTraceFlags().asByte();
104+return bytes;
105+ }
106+107+108+@Override
109+public SpanContext parseBytes(byte[] serialized) {
110+checkNotNull(serialized, "bytes");
111+if (serialized.length == 0 || serialized[0] != VERSION_ID) {
112+throw new IllegalArgumentException("Unsupported version.");
113+ }
114+if (serialized.length < REQUIRED_FORMAT_LENGTH) {
115+throw new IllegalArgumentException("Invalid input: truncated");
116+ }
117+String traceId;
118+String spanId;
119+TraceFlags traceFlags = TraceFlags.getDefault();
120+int pos = 1;
121+if (serialized[pos] == TRACE_ID_FIELD_ID) {
122+traceId = TraceId.fromBytes(
123+Arrays.copyOfRange(serialized, pos + ID_SIZE, pos + ID_SIZE + TRACE_ID_SIZE));
124+pos += ID_SIZE + TRACE_ID_SIZE;
125+ } else {
126+throw new IllegalArgumentException("Invalid input: expected trace ID at offset " + pos);
127+ }
128+if (serialized[pos] == SPAN_ID_FIELD_ID) {
129+spanId = SpanId.fromBytes(
130+Arrays.copyOfRange(serialized, pos + ID_SIZE, pos + ID_SIZE + SPAN_ID_SIZE));
131+pos += ID_SIZE + SPAN_ID_SIZE;
132+ } else {
133+throw new IllegalArgumentException("Invalid input: expected span ID at offset " + pos);
134+ }
135+if (serialized.length > pos && serialized[pos] == TRACE_FLAG_FIELD_ID) {
136+if (serialized.length < ALL_FORMAT_LENGTH) {
137+throw new IllegalArgumentException("Invalid input: truncated");
138+ }
139+traceFlags = TraceFlags.fromByte(serialized[pos + ID_SIZE]);
140+ }
141+return SpanContext.create(traceId, spanId, traceFlags, TraceState.getDefault());
142+ }
143+}