Skip to content

Commit fe115fe

Browse files
committed
Add baggage limits to JaegerPropagator, OtTracePropagator
1 parent 5989138 commit fe115fe

4 files changed

Lines changed: 233 additions & 12 deletions

File tree

extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ public final class JaegerPropagator implements TextMapPropagator {
7272
PARENT_SPAN_ID_OFFSET + PARENT_SPAN_ID_SIZE + PROPAGATION_HEADER_DELIMITER_SIZE;
7373
private static final int PROPAGATION_HEADER_SIZE = SAMPLED_FLAG_OFFSET + SAMPLED_FLAG_SIZE;
7474

75+
// No limits are defined by the Jaeger format; borrow the W3C Baggage spec limits as a
76+
// defense-in-depth measure (https://www.w3.org/TR/baggage/#limits).
77+
private static final int MAX_BAGGAGE_ENTRIES = 64;
78+
private static final int MAX_BAGGAGE_BYTES = 8192;
79+
7580
private static final Collection<String> FIELDS = Collections.singletonList(PROPAGATION_HEADER);
7681
private static final JaegerPropagator INSTANCE = new JaegerPropagator();
7782

@@ -126,8 +131,21 @@ private static <C> void injectSpan(
126131

127132
private static <C> void injectBaggage(
128133
Baggage baggage, @Nullable C carrier, TextMapSetter<C> setter) {
134+
int[] entriesEmitted = {0};
135+
int[] bytesEmitted = {0};
129136
baggage.forEach(
130-
(key, baggageEntry) -> setter.set(carrier, BAGGAGE_PREFIX + key, baggageEntry.getValue()));
137+
(key, baggageEntry) -> {
138+
if (entriesEmitted[0] >= MAX_BAGGAGE_ENTRIES) {
139+
return;
140+
}
141+
String value = baggageEntry.getValue();
142+
if (bytesEmitted[0] + key.length() + value.length() > MAX_BAGGAGE_BYTES) {
143+
return;
144+
}
145+
setter.set(carrier, BAGGAGE_PREFIX + key, value);
146+
entriesEmitted[0]++;
147+
bytesEmitted[0] += key.length() + value.length();
148+
});
131149
}
132150

133151
@Override
@@ -228,46 +246,70 @@ private static <C> SpanContext getSpanContextFromHeader(
228246
@Nullable
229247
private static <C> Baggage getBaggageFromHeader(@Nullable C carrier, TextMapGetter<C> getter) {
230248
BaggageBuilder builder = null;
249+
int entriesAdded = 0;
250+
int bytesAdded = 0;
231251

232252
Iterable<String> keys = carrier != null ? getter.keys(carrier) : Collections.emptyList();
233253

234254
for (String key : keys) {
255+
if (entriesAdded >= MAX_BAGGAGE_ENTRIES || bytesAdded > MAX_BAGGAGE_BYTES) {
256+
break;
257+
}
235258
if (key.startsWith(BAGGAGE_PREFIX)) {
236259
if (key.length() == BAGGAGE_PREFIX.length()) {
237260
continue;
238261
}
239-
240-
if (builder == null) {
241-
builder = Baggage.builder();
242-
}
243-
244262
String value = getter.get(carrier, key);
245263
if (value != null) {
246-
builder.put(key.substring(BAGGAGE_PREFIX.length()), value);
264+
String baggageKey = key.substring(BAGGAGE_PREFIX.length());
265+
if (bytesAdded + baggageKey.length() + value.length() > MAX_BAGGAGE_BYTES) {
266+
break;
267+
}
268+
if (builder == null) {
269+
builder = Baggage.builder();
270+
}
271+
builder.put(baggageKey, value);
272+
entriesAdded++;
273+
bytesAdded += baggageKey.length() + value.length();
247274
}
248275
} else if (key.equals(BAGGAGE_HEADER)) {
249276
String value = getter.get(carrier, key);
250277
if (value != null) {
251278
if (builder == null) {
252279
builder = Baggage.builder();
253280
}
254-
builder = parseBaggageHeader(value, builder);
281+
int[] counts =
282+
parseBaggageHeader(value, builder, MAX_BAGGAGE_ENTRIES - entriesAdded, MAX_BAGGAGE_BYTES - bytesAdded);
283+
entriesAdded += counts[0];
284+
bytesAdded += counts[1];
255285
}
256286
}
257287
}
258288
return builder == null ? null : builder.build();
259289
}
260290

261-
private static BaggageBuilder parseBaggageHeader(String header, BaggageBuilder builder) {
291+
/** Returns a two-element array of {@code [entriesAdded, bytesAdded]}. */
292+
private static int[] parseBaggageHeader(
293+
String header, BaggageBuilder builder, int maxEntries, int maxBytes) {
294+
int entriesAdded = 0;
295+
int bytesAdded = 0;
262296
for (String part : header.split("\\s*,\\s*")) {
297+
if (entriesAdded >= maxEntries || bytesAdded > maxBytes) {
298+
break;
299+
}
263300
String[] kv = part.split("\\s*=\\s*");
264301
if (kv.length == 2) {
302+
if (bytesAdded + kv[0].length() + kv[1].length() > maxBytes) {
303+
break;
304+
}
265305
builder.put(kv[0], kv[1]);
306+
entriesAdded++;
307+
bytesAdded += kv[0].length() + kv[1].length();
266308
} else {
267309
logger.fine("malformed token in " + BAGGAGE_HEADER + " header: " + part);
268310
}
269311
}
270-
return builder;
312+
return new int[] {entriesAdded, bytesAdded};
271313
}
272314

273315
private static SpanContext buildSpanContext(String traceId, String spanId, String flags) {

extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/OtTracePropagator.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ public final class OtTracePropagator implements TextMapPropagator {
5050
private static final Collection<String> FIELDS =
5151
Collections.unmodifiableList(Arrays.asList(TRACE_ID_HEADER, SPAN_ID_HEADER, SAMPLED_HEADER));
5252

53+
// No limits are defined by the OT trace format; borrow the W3C Baggage spec limits as a
54+
// defense-in-depth measure (https://www.w3.org/TR/baggage/#limits).
55+
private static final int MAX_BAGGAGE_ENTRIES = 64;
56+
private static final int MAX_BAGGAGE_BYTES = 8192;
57+
5358
private static final OtTracePropagator INSTANCE = new OtTracePropagator();
5459

5560
private OtTracePropagator() {
@@ -85,9 +90,21 @@ public <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> se
8590
Baggage baggage = Baggage.fromContext(context);
8691
if (!baggage.isEmpty()) {
8792
// Metadata is not supported by OpenTracing
93+
int[] entriesEmitted = {0};
94+
int[] bytesEmitted = {0};
8895
baggage.forEach(
89-
(key, baggageEntry) ->
90-
setter.set(carrier, PREFIX_BAGGAGE_HEADER + key, baggageEntry.getValue()));
96+
(key, baggageEntry) -> {
97+
if (entriesEmitted[0] >= MAX_BAGGAGE_ENTRIES) {
98+
return;
99+
}
100+
String value = baggageEntry.getValue();
101+
if (bytesEmitted[0] + key.length() + value.length() > MAX_BAGGAGE_BYTES) {
102+
return;
103+
}
104+
setter.set(carrier, PREFIX_BAGGAGE_HEADER + key, value);
105+
entriesEmitted[0]++;
106+
bytesEmitted[0] += key.length() + value.length();
107+
});
91108
}
92109
}
93110

@@ -122,7 +139,12 @@ public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C
122139
// Baggage is only extracted if there is a valid SpanContext
123140
if (carrier != null) {
124141
BaggageBuilder baggageBuilder = Baggage.builder();
142+
int entriesAdded = 0;
143+
int bytesAdded = 0;
125144
for (String key : getter.keys(carrier)) {
145+
if (entriesAdded >= MAX_BAGGAGE_ENTRIES || bytesAdded > MAX_BAGGAGE_BYTES) {
146+
break;
147+
}
126148
String lowercaseKey = key.toLowerCase(Locale.ROOT);
127149
if (!lowercaseKey.startsWith(PREFIX_BAGGAGE_HEADER)) {
128150
continue;
@@ -133,7 +155,12 @@ public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C
133155
}
134156
String baggageKey =
135157
lowercaseKey.substring(OtTracePropagator.PREFIX_BAGGAGE_HEADER.length());
158+
if (bytesAdded + baggageKey.length() + value.length() > MAX_BAGGAGE_BYTES) {
159+
break;
160+
}
136161
baggageBuilder.put(baggageKey, value);
162+
entriesAdded++;
163+
bytesAdded += baggageKey.length() + value.length();
137164
}
138165
Baggage baggage = baggageBuilder.build();
139166
if (!baggage.isEmpty()) {

extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/JaegerPropagatorTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.jaegertracing.internal.JaegerSpanContext;
1313
import io.jaegertracing.internal.propagation.TextMapCodec;
1414
import io.opentelemetry.api.baggage.Baggage;
15+
import io.opentelemetry.api.baggage.BaggageBuilder;
1516
import io.opentelemetry.api.baggage.BaggageEntryMetadata;
1617
import io.opentelemetry.api.trace.Span;
1718
import io.opentelemetry.api.trace.SpanContext;
@@ -25,11 +26,16 @@
2526
import java.io.UnsupportedEncodingException;
2627
import java.net.URLEncoder;
2728
import java.nio.charset.StandardCharsets;
29+
import java.util.Arrays;
2830
import java.util.Collections;
2931
import java.util.LinkedHashMap;
3032
import java.util.Map;
33+
import java.util.stream.Stream;
3134
import javax.annotation.Nullable;
3235
import org.junit.jupiter.api.Test;
36+
import org.junit.jupiter.params.ParameterizedTest;
37+
import org.junit.jupiter.params.provider.Arguments;
38+
import org.junit.jupiter.params.provider.MethodSource;
3339

3440
/** Unit tests for {@link JaegerPropagator}. */
3541
@SuppressWarnings("deprecation")
@@ -466,6 +472,70 @@ void extract_nullGetter() {
466472
assertThat(jaegerPropagator.extract(context, Collections.emptyMap(), null)).isSameAs(context);
467473
}
468474

475+
@Test
476+
void inject_baggageLimit_maxEntries() {
477+
Map<String, String> carrier = new LinkedHashMap<>();
478+
jaegerPropagator.inject(Context.root().with(baggageWithEntries(0, 65)), carrier, Map::put);
479+
long count = carrier.keySet().stream().filter(k -> k.startsWith(BAGGAGE_PREFIX)).count();
480+
assertThat(count).isEqualTo(64);
481+
}
482+
483+
@Test
484+
void inject_baggageLimit_maxBytes() {
485+
Baggage baggage = Baggage.builder().put("k", nChars('v', 8192)).build();
486+
Map<String, String> carrier = new LinkedHashMap<>();
487+
jaegerPropagator.inject(Context.root().with(baggage), carrier, Map::put);
488+
assertThat(carrier).doesNotContainKey(BAGGAGE_PREFIX + "k");
489+
}
490+
491+
@ParameterizedTest
492+
@MethodSource
493+
void extract_baggageLimit(Map<String, String> carrier, Baggage expectedBaggage) {
494+
assertThat(fromContext(jaegerPropagator.extract(Context.root(), carrier, getter)))
495+
.isEqualTo(expectedBaggage);
496+
}
497+
498+
static Stream<Arguments> extract_baggageLimit() {
499+
Map<String, String> prefixCarrier = new LinkedHashMap<>();
500+
for (int i = 0; i < 65; i++) {
501+
prefixCarrier.put(BAGGAGE_PREFIX + "k" + i, "v" + i);
502+
}
503+
StringBuilder jaegerHeader = new StringBuilder();
504+
for (int i = 0; i < 65; i++) {
505+
if (i > 0) {
506+
jaegerHeader.append(",");
507+
}
508+
jaegerHeader.append("k").append(i).append("=v").append(i);
509+
}
510+
Map<String, String> headerCarrier = new LinkedHashMap<>();
511+
headerCarrier.put(BAGGAGE_HEADER, jaegerHeader.toString());
512+
Map<String, String> bigValueCarrier = new LinkedHashMap<>();
513+
bigValueCarrier.put(BAGGAGE_PREFIX + "k", nChars('v', 8192));
514+
return Stream.of(
515+
// 65 uberctx- prefix keys — only first 64 extracted
516+
Arguments.of(prefixCarrier, baggageWithEntries(0, 64)),
517+
// 65 entries in jaeger-baggage header — only first 64 extracted
518+
Arguments.of(headerCarrier, baggageWithEntries(0, 64)),
519+
// single entry whose value exceeds the byte limit — not extracted
520+
Arguments.of(bigValueCarrier, Baggage.empty()));
521+
}
522+
523+
/** Builds a {@link Baggage} with entries {@code k{start}=v{start}} through {@code k{start+count-1}=v{start+count-1}}. */
524+
private static Baggage baggageWithEntries(int start, int count) {
525+
BaggageBuilder builder = Baggage.builder();
526+
for (int i = start; i < start + count; i++) {
527+
builder.put("k" + i, "v" + i);
528+
}
529+
return builder.build();
530+
}
531+
532+
/** Returns a string of {@code count} repetitions of {@code c}. */
533+
private static String nChars(char c, int count) {
534+
char[] chars = new char[count];
535+
Arrays.fill(chars, c);
536+
return new String(chars);
537+
}
538+
469539
@Test
470540
void toString_Valid() {
471541
assertThat(jaegerPropagator.toString()).isEqualTo("JaegerPropagator");

extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracePropagatorTest.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static org.assertj.core.api.Assertions.assertThat;
99

1010
import io.opentelemetry.api.baggage.Baggage;
11+
import io.opentelemetry.api.baggage.BaggageBuilder;
1112
import io.opentelemetry.api.trace.Span;
1213
import io.opentelemetry.api.trace.SpanContext;
1314
import io.opentelemetry.api.trace.SpanId;
@@ -17,13 +18,18 @@
1718
import io.opentelemetry.context.Context;
1819
import io.opentelemetry.context.propagation.TextMapGetter;
1920
import io.opentelemetry.context.propagation.TextMapSetter;
21+
import java.util.Arrays;
2022
import java.util.Collections;
2123
import java.util.HashMap;
2224
import java.util.LinkedHashMap;
2325
import java.util.Locale;
2426
import java.util.Map;
27+
import java.util.stream.Stream;
2528
import javax.annotation.Nullable;
2629
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.Arguments;
32+
import org.junit.jupiter.params.provider.MethodSource;
2733

2834
@SuppressWarnings("deprecation")
2935
class OtTracePropagatorTest {
@@ -412,6 +418,82 @@ void extract_nullGetter() {
412418
assertThat(propagator.extract(context, Collections.emptyMap(), null)).isSameAs(context);
413419
}
414420

421+
@Test
422+
void inject_baggageLimit_maxEntries() {
423+
Map<String, String> carrier = new LinkedHashMap<>();
424+
propagator.inject(
425+
withSpanContext(
426+
SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault()),
427+
Context.current().with(baggageWithEntries(0, 65))),
428+
carrier,
429+
setter);
430+
long count =
431+
carrier.keySet().stream()
432+
.filter(k -> k.startsWith(OtTracePropagator.PREFIX_BAGGAGE_HEADER))
433+
.count();
434+
assertThat(count).isEqualTo(64);
435+
}
436+
437+
@Test
438+
void inject_baggageLimit_maxBytes() {
439+
Baggage baggage = Baggage.builder().put("k", nChars('v', 8192)).build();
440+
Map<String, String> carrier = new LinkedHashMap<>();
441+
propagator.inject(
442+
withSpanContext(
443+
SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault()),
444+
Context.current().with(baggage)),
445+
carrier,
446+
setter);
447+
assertThat(carrier).doesNotContainKey(OtTracePropagator.PREFIX_BAGGAGE_HEADER + "k");
448+
}
449+
450+
@ParameterizedTest
451+
@MethodSource
452+
void extract_baggageLimit(Map<String, String> carrier, Baggage expectedBaggage) {
453+
assertThat(Baggage.fromContext(propagator.extract(Context.root(), carrier, getter)))
454+
.isEqualTo(expectedBaggage);
455+
}
456+
457+
static Stream<Arguments> extract_baggageLimit() {
458+
// Valid span context is required for baggage to be extracted
459+
Map<String, String> manyEntriesCarrier = carrierWithSpanContext();
460+
for (int i = 0; i < 65; i++) {
461+
manyEntriesCarrier.put(OtTracePropagator.PREFIX_BAGGAGE_HEADER + "k" + i, "v" + i);
462+
}
463+
Map<String, String> bigValueCarrier = carrierWithSpanContext();
464+
bigValueCarrier.put(OtTracePropagator.PREFIX_BAGGAGE_HEADER + "k", nChars('v', 8192));
465+
return Stream.of(
466+
// 65 ot-baggage- keys — only first 64 extracted
467+
Arguments.of(manyEntriesCarrier, baggageWithEntries(0, 64)),
468+
// single entry whose value exceeds the byte limit — not extracted
469+
Arguments.of(bigValueCarrier, Baggage.empty()));
470+
}
471+
472+
/** Returns a carrier pre-populated with a valid span context (required for baggage extraction). */
473+
private static Map<String, String> carrierWithSpanContext() {
474+
Map<String, String> carrier = new LinkedHashMap<>();
475+
carrier.put(OtTracePropagator.TRACE_ID_HEADER, TRACE_ID_RIGHT_PART);
476+
carrier.put(OtTracePropagator.SPAN_ID_HEADER, SPAN_ID);
477+
carrier.put(OtTracePropagator.SAMPLED_HEADER, "true");
478+
return carrier;
479+
}
480+
481+
/** Builds a {@link Baggage} with entries {@code k{start}=v{start}} through {@code k{start+count-1}=v{start+count-1}}. */
482+
private static Baggage baggageWithEntries(int start, int count) {
483+
BaggageBuilder builder = Baggage.builder();
484+
for (int i = start; i < start + count; i++) {
485+
builder.put("k" + i, "v" + i);
486+
}
487+
return builder.build();
488+
}
489+
490+
/** Returns a string of {@code count} repetitions of {@code c}. */
491+
private static String nChars(char c, int count) {
492+
char[] chars = new char[count];
493+
Arrays.fill(chars, c);
494+
return new String(chars);
495+
}
496+
415497
@Test
416498
void toString_Valid() {
417499
assertThat(propagator.toString()).isEqualTo("OtTracePropagator");

0 commit comments

Comments
 (0)