diff --git a/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/Parser.java b/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/Parser.java index a616e8e24d2..dfd6977a42d 100644 --- a/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/Parser.java +++ b/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/Parser.java @@ -27,6 +27,7 @@ private enum State { } private final String baggageHeader; + private final int maxEntries; private final Element key = Element.createKeyElement(); private final Element value = Element.createValueElement(); @@ -36,14 +37,19 @@ private enum State { private int metaStart; private boolean skipToNext; + private int entriesAdded; - Parser(String baggageHeader) { + Parser(String baggageHeader, int maxEntries) { this.baggageHeader = baggageHeader; + this.maxEntries = maxEntries; reset(0); } - void parseInto(BaggageBuilder baggageBuilder) { + int parseInto(BaggageBuilder baggageBuilder) { for (int i = 0, n = baggageHeader.length(); i < n; i++) { + if (entriesAdded >= maxEntries) { + break; + } char current = baggageHeader.charAt(i); if (skipToNext) { @@ -123,13 +129,17 @@ void parseInto(BaggageBuilder baggageBuilder) { } } } + return entriesAdded; } - private static void putBaggage( + private void putBaggage( BaggageBuilder baggage, @Nullable String key, @Nullable String value, @Nullable String metadataValue) { + if (entriesAdded >= maxEntries) { + return; + } String decodedValue = decodeValue(value); metadataValue = decodeValue(metadataValue); BaggageEntryMetadata baggageEntryMetadata = @@ -138,6 +148,7 @@ private static void putBaggage( : BaggageEntryMetadata.empty(); if (key != null && decodedValue != null) { baggage.put(key, decodedValue, baggageEntryMetadata); + entriesAdded++; } } diff --git a/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagator.java b/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagator.java index d5589f7fd51..2b589315435 100644 --- a/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagator.java +++ b/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagator.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.logging.Logger; import javax.annotation.Nullable; /** @@ -26,10 +27,15 @@ */ public final class W3CBaggagePropagator implements TextMapPropagator { + // Limits from https://www.w3.org/TR/baggage/#limits + private static final int MAX_BAGGAGE_ENTRIES = 64; + private static final int MAX_BAGGAGE_BYTES = 8192; + private static final String FIELD = "baggage"; private static final List FIELDS = singletonList(FIELD); private static final W3CBaggagePropagator INSTANCE = new W3CBaggagePropagator(); private static final PercentEscaper URL_ESCAPER = PercentEscaper.create(); + private static final Logger LOGGER = Logger.getLogger(W3CBaggagePropagator.class.getName()); /** Singleton instance of the W3C Baggage Propagator. */ public static W3CBaggagePropagator getInstance() { @@ -61,17 +67,34 @@ public void inject(Context context, @Nullable C carrier, TextMapSetter se private static String baggageToString(Baggage baggage) { StringBuilder headerContent = new StringBuilder(); + int[] entryCount = {0}; baggage.forEach( (key, baggageEntry) -> { if (baggageIsInvalid(key, baggageEntry)) { return; } - headerContent.append(key).append("=").append(encodeValue(baggageEntry.getValue())); + if (entryCount[0] >= MAX_BAGGAGE_ENTRIES) { + return; + } + String encodedValue = encodeValue(baggageEntry.getValue()); String metadataValue = baggageEntry.getMetadata().getValue(); - if (metadataValue != null && !metadataValue.isEmpty()) { - headerContent.append(";").append(encodeValue(metadataValue)); + String encodedMetadata = + (metadataValue != null && !metadataValue.isEmpty()) + ? encodeValue(metadataValue) + : null; + // Exit early if adding this entry causes the total length to exceed the limit + // encodedEntryLength includes a trailing comma; the final string trims exactly one, + // so the net contribution to the final length is entryLength - 1. + if (headerContent.length() + encodedEntryLength(key, encodedValue, encodedMetadata) - 1 + > MAX_BAGGAGE_BYTES) { + return; + } + headerContent.append(key).append("=").append(encodedValue); + if (encodedMetadata != null) { + headerContent.append(";").append(encodedMetadata); } headerContent.append(","); + entryCount[0]++; }); if (headerContent.length() == 0) { @@ -87,6 +110,21 @@ private static String encodeValue(String value) { return URL_ESCAPER.escape(value); } + /** + * Returns the length of the serialized entry as it would appear in the baggage header, including + * the trailing comma used by the trailing-comma pattern in {@link #baggageToString}. The length + * accounts for {@code "key=encodedValue,"} plus {@code ";encodedMetadata"} when metadata is + * present. + */ + private static int encodedEntryLength( + String key, String encodedValue, @Nullable String encodedMetadata) { + int length = key.length() + 1 + encodedValue.length() + 1; // "key=value," + if (encodedMetadata != null) { + length += 1 + encodedMetadata.length(); // ";metadata" + } + return length; + } + @Override public Context extract(Context context, @Nullable C carrier, TextMapGetter getter) { if (context == null) { @@ -108,6 +146,8 @@ private static Context extractMulti( boolean extracted = false; BaggageBuilder baggageBuilder = Baggage.builder(); + int totalBytes = 0; + int totalEntries = 0; while (baggageHeaders.hasNext()) { String header = baggageHeaders.next(); @@ -115,9 +155,16 @@ private static Context extractMulti( continue; } + totalBytes += header.length(); + if (totalBytes > MAX_BAGGAGE_BYTES || totalEntries >= MAX_BAGGAGE_ENTRIES) { + LOGGER.fine("Baggage header exceeded W3C limits, dropping remaining entries"); + break; + } + try { - extractEntries(header, baggageBuilder); + int added = extractEntries(header, baggageBuilder, MAX_BAGGAGE_ENTRIES - totalEntries); extracted = true; + totalEntries += added; } catch (RuntimeException expected) { // invalid baggage header, continue } @@ -126,8 +173,9 @@ private static Context extractMulti( return extracted ? context.with(baggageBuilder.build()) : context; } - private static void extractEntries(String baggageHeader, BaggageBuilder baggageBuilder) { - new Parser(baggageHeader).parseInto(baggageBuilder); + private static int extractEntries( + String baggageHeader, BaggageBuilder baggageBuilder, int maxEntries) { + return new Parser(baggageHeader, maxEntries).parseInto(baggageBuilder); } private static boolean baggageIsInvalid(String key, BaggageEntry baggageEntry) { diff --git a/api/all/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorTest.java b/api/all/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorTest.java index 2d1803d7a60..93f4e25f1a6 100644 --- a/api/all/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorTest.java @@ -11,17 +11,23 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; import io.opentelemetry.api.baggage.BaggageEntryMetadata; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class W3CBaggagePropagatorTest { @@ -595,6 +601,114 @@ void inject_nullSetter() { assertThat(carrier).isEmpty(); } + @ParameterizedTest + @MethodSource + void extract_limit_maxEntries(List headers, Baggage expectedBaggage) { + Context result = + W3CBaggagePropagator.getInstance() + .extract(Context.root(), ImmutableMap.of("baggage", headers), multiGetter); + assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage); + } + + static Stream extract_limit_maxEntries() { + return Stream.of( + // Exactly at the limit — all 64 entries extracted + Arguments.of(ImmutableList.of(baggageHeader(0, 64)), baggageWithEntries(0, 64)), + // One over the limit — only the first 64 extracted + Arguments.of(ImmutableList.of(baggageHeader(0, 65)), baggageWithEntries(0, 64)), + // Split across two headers — only the first 64 total extracted + Arguments.of( + ImmutableList.of(baggageHeader(0, 32), baggageHeader(32, 33)), + baggageWithEntries(0, 64))); + } + + /** + * Builds a {@link Baggage} with entries {@code k{start}=v{start}} through {@code + * k{start+count-1}=v{start+count-1}}. + */ + private static Baggage baggageWithEntries(int start, int count) { + BaggageBuilder builder = Baggage.builder(); + for (int i = start; i < start + count; i++) { + builder.put("k" + i, "v" + i); + } + return builder.build(); + } + + /** Builds {@code "k{start}=v{start},...,k{start+count-1}=v{start+count-1}"}. */ + private static String baggageHeader(int start, int count) { + StringBuilder sb = new StringBuilder(); + for (int i = start; i < start + count; i++) { + if (i > start) { + sb.append(","); + } + sb.append("k").append(i).append("=v").append(i); + } + return sb.toString(); + } + + @Test + void extract_limit_maxBytes_exceedsLimit() { + W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); + // Single header over 8192 bytes — dropped entirely; partial values must not be extracted + String header = "k=" + fillChars('v', 8192); // 8194 bytes + Context result = propagator.extract(Context.root(), ImmutableMap.of("baggage", header), getter); + assertThat(Baggage.fromContext(result)).isEqualTo(Baggage.empty()); + } + + @Test + void extract_limit_maxBytes_acrossMultipleHeaders() { + W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); + // First header just under 8192 bytes is extracted; second header pushes total over the limit + String almostMax = "k=" + fillChars('v', 8189); // "k=vvv..." + String second = "k2=v2"; + Context result = + propagator.extract( + Context.root(), + ImmutableMap.of("baggage", ImmutableList.of(almostMax, second)), + multiGetter); + // Only the first header should have been extracted + assertThat(Baggage.fromContext(result).size()).isEqualTo(1); + assertThat(Baggage.fromContext(result).getEntryValue("k2")).isNull(); + } + + @Test + void inject_limit_maxEntries() { + Map carrier = new HashMap<>(); + W3CBaggagePropagator.getInstance() + .inject(Context.root().with(baggageWithEntries(0, 74)), carrier, Map::put); + String header = carrier.get("baggage"); + assertThat(header).isNotNull(); + long count = header.chars().filter(c -> c == '=').count(); + assertThat(count).isEqualTo(64); + } + + @Test + void inject_limit_maxBytes() { + W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); + // One entry whose encoded form alone exceeds the byte limit — should produce empty header + Baggage baggage = Baggage.builder().put("k", fillChars('v', 8192)).build(); + Map carrier = new HashMap<>(); + propagator.inject(Context.root().with(baggage), carrier, Map::put); + assertThat(carrier).doesNotContainKey("baggage"); + } + + @Test + void inject_limit_maxBytes_metadata() { + // Value alone fits easily (k=v is 3 bytes), but k=v;{metadata} exceeds 8192 bytes. + // Verifies that metadata length is included in the byte limit check. + Baggage baggage = + Baggage.builder().put("k", "v", BaggageEntryMetadata.create(fillChars('x', 8190))).build(); + Map carrier = new HashMap<>(); + W3CBaggagePropagator.getInstance().inject(Context.root().with(baggage), carrier, Map::put); + assertThat(carrier).doesNotContainKey("baggage"); + } + + private static String fillChars(char c, int count) { + char[] chars = new char[count]; + Arrays.fill(chars, c); + return new String(chars); + } + @Test void toString_Valid() { assertThat(W3CBaggagePropagator.getInstance().toString()).isEqualTo("W3CBaggagePropagator"); diff --git a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java index 8db82ff2b8c..c19a2793fcf 100644 --- a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java +++ b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java @@ -72,6 +72,11 @@ public final class JaegerPropagator implements TextMapPropagator { PARENT_SPAN_ID_OFFSET + PARENT_SPAN_ID_SIZE + PROPAGATION_HEADER_DELIMITER_SIZE; private static final int PROPAGATION_HEADER_SIZE = SAMPLED_FLAG_OFFSET + SAMPLED_FLAG_SIZE; + // No limits are defined by the Jaeger format; borrow the W3C Baggage spec limits as a + // defense-in-depth measure (https://www.w3.org/TR/baggage/#limits). + private static final int MAX_BAGGAGE_ENTRIES = 64; + private static final int MAX_BAGGAGE_BYTES = 8192; + private static final Collection FIELDS = Collections.singletonList(PROPAGATION_HEADER); private static final JaegerPropagator INSTANCE = new JaegerPropagator(); @@ -126,8 +131,21 @@ private static void injectSpan( private static void injectBaggage( Baggage baggage, @Nullable C carrier, TextMapSetter setter) { + int[] entriesEmitted = {0}; + int[] bytesEmitted = {0}; baggage.forEach( - (key, baggageEntry) -> setter.set(carrier, BAGGAGE_PREFIX + key, baggageEntry.getValue())); + (key, baggageEntry) -> { + if (entriesEmitted[0] >= MAX_BAGGAGE_ENTRIES) { + return; + } + String value = baggageEntry.getValue(); + if (bytesEmitted[0] + key.length() + value.length() > MAX_BAGGAGE_BYTES) { + return; + } + setter.set(carrier, BAGGAGE_PREFIX + key, value); + entriesEmitted[0]++; + bytesEmitted[0] += key.length() + value.length(); + }); } @Override @@ -228,22 +246,31 @@ private static SpanContext getSpanContextFromHeader( @Nullable private static Baggage getBaggageFromHeader(@Nullable C carrier, TextMapGetter getter) { BaggageBuilder builder = null; + int entriesAdded = 0; + int bytesAdded = 0; Iterable keys = carrier != null ? getter.keys(carrier) : Collections.emptyList(); for (String key : keys) { + if (entriesAdded >= MAX_BAGGAGE_ENTRIES || bytesAdded > MAX_BAGGAGE_BYTES) { + break; + } if (key.startsWith(BAGGAGE_PREFIX)) { if (key.length() == BAGGAGE_PREFIX.length()) { continue; } - - if (builder == null) { - builder = Baggage.builder(); - } - String value = getter.get(carrier, key); if (value != null) { - builder.put(key.substring(BAGGAGE_PREFIX.length()), value); + String baggageKey = key.substring(BAGGAGE_PREFIX.length()); + if (bytesAdded + baggageKey.length() + value.length() > MAX_BAGGAGE_BYTES) { + break; + } + if (builder == null) { + builder = Baggage.builder(); + } + builder.put(baggageKey, value); + entriesAdded++; + bytesAdded += baggageKey.length() + value.length(); } } else if (key.equals(BAGGAGE_HEADER)) { String value = getter.get(carrier, key); @@ -251,23 +278,42 @@ private static Baggage getBaggageFromHeader(@Nullable C carrier, TextMapGett if (builder == null) { builder = Baggage.builder(); } - builder = parseBaggageHeader(value, builder); + int[] counts = + parseBaggageHeader( + value, + builder, + MAX_BAGGAGE_ENTRIES - entriesAdded, + MAX_BAGGAGE_BYTES - bytesAdded); + entriesAdded += counts[0]; + bytesAdded += counts[1]; } } } return builder == null ? null : builder.build(); } - private static BaggageBuilder parseBaggageHeader(String header, BaggageBuilder builder) { + /** Returns a two-element array of {@code [entriesAdded, bytesAdded]}. */ + private static int[] parseBaggageHeader( + String header, BaggageBuilder builder, int maxEntries, int maxBytes) { + int entriesAdded = 0; + int bytesAdded = 0; for (String part : header.split("\\s*,\\s*")) { + if (entriesAdded >= maxEntries || bytesAdded > maxBytes) { + break; + } String[] kv = part.split("\\s*=\\s*"); if (kv.length == 2) { + if (bytesAdded + kv[0].length() + kv[1].length() > maxBytes) { + break; + } builder.put(kv[0], kv[1]); + entriesAdded++; + bytesAdded += kv[0].length() + kv[1].length(); } else { logger.fine("malformed token in " + BAGGAGE_HEADER + " header: " + part); } } - return builder; + return new int[] {entriesAdded, bytesAdded}; } private static SpanContext buildSpanContext(String traceId, String spanId, String flags) { diff --git a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/OtTracePropagator.java b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/OtTracePropagator.java index b1df98877de..10f3c8d8e8e 100644 --- a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/OtTracePropagator.java +++ b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/OtTracePropagator.java @@ -50,6 +50,11 @@ public final class OtTracePropagator implements TextMapPropagator { private static final Collection FIELDS = Collections.unmodifiableList(Arrays.asList(TRACE_ID_HEADER, SPAN_ID_HEADER, SAMPLED_HEADER)); + // No limits are defined by the OT trace format; borrow the W3C Baggage spec limits as a + // defense-in-depth measure (https://www.w3.org/TR/baggage/#limits). + private static final int MAX_BAGGAGE_ENTRIES = 64; + private static final int MAX_BAGGAGE_BYTES = 8192; + private static final OtTracePropagator INSTANCE = new OtTracePropagator(); private OtTracePropagator() { @@ -85,9 +90,21 @@ public void inject(Context context, @Nullable C carrier, TextMapSetter se Baggage baggage = Baggage.fromContext(context); if (!baggage.isEmpty()) { // Metadata is not supported by OpenTracing + int[] entriesEmitted = {0}; + int[] bytesEmitted = {0}; baggage.forEach( - (key, baggageEntry) -> - setter.set(carrier, PREFIX_BAGGAGE_HEADER + key, baggageEntry.getValue())); + (key, baggageEntry) -> { + if (entriesEmitted[0] >= MAX_BAGGAGE_ENTRIES) { + return; + } + String value = baggageEntry.getValue(); + if (bytesEmitted[0] + key.length() + value.length() > MAX_BAGGAGE_BYTES) { + return; + } + setter.set(carrier, PREFIX_BAGGAGE_HEADER + key, value); + entriesEmitted[0]++; + bytesEmitted[0] += key.length() + value.length(); + }); } } @@ -122,7 +139,12 @@ public Context extract(Context context, @Nullable C carrier, TextMapGetter= MAX_BAGGAGE_ENTRIES || bytesAdded > MAX_BAGGAGE_BYTES) { + break; + } String lowercaseKey = key.toLowerCase(Locale.ROOT); if (!lowercaseKey.startsWith(PREFIX_BAGGAGE_HEADER)) { continue; @@ -133,7 +155,12 @@ public Context extract(Context context, @Nullable C carrier, TextMapGetter MAX_BAGGAGE_BYTES) { + break; + } baggageBuilder.put(baggageKey, value); + entriesAdded++; + bytesAdded += baggageKey.length() + value.length(); } Baggage baggage = baggageBuilder.build(); if (!baggage.isEmpty()) { diff --git a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/JaegerPropagatorTest.java b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/JaegerPropagatorTest.java index a4d63735c24..b0b79195e70 100644 --- a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/JaegerPropagatorTest.java +++ b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/JaegerPropagatorTest.java @@ -12,6 +12,7 @@ import io.jaegertracing.internal.JaegerSpanContext; import io.jaegertracing.internal.propagation.TextMapCodec; import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; import io.opentelemetry.api.baggage.BaggageEntryMetadata; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; @@ -25,11 +26,16 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.stream.Stream; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** Unit tests for {@link JaegerPropagator}. */ @SuppressWarnings("deprecation") @@ -466,6 +472,73 @@ void extract_nullGetter() { assertThat(jaegerPropagator.extract(context, Collections.emptyMap(), null)).isSameAs(context); } + @Test + void inject_baggageLimit_maxEntries() { + Map carrier = new LinkedHashMap<>(); + jaegerPropagator.inject(Context.root().with(baggageWithEntries(0, 65)), carrier, Map::put); + long count = carrier.keySet().stream().filter(k -> k.startsWith(BAGGAGE_PREFIX)).count(); + assertThat(count).isEqualTo(64); + } + + @Test + void inject_baggageLimit_maxBytes() { + Baggage baggage = Baggage.builder().put("k", fillChars('v', 8192)).build(); + Map carrier = new LinkedHashMap<>(); + jaegerPropagator.inject(Context.root().with(baggage), carrier, Map::put); + assertThat(carrier).doesNotContainKey(BAGGAGE_PREFIX + "k"); + } + + @ParameterizedTest + @MethodSource + void extract_baggageLimit(Map carrier, Baggage expectedBaggage) { + assertThat(fromContext(jaegerPropagator.extract(Context.root(), carrier, getter))) + .isEqualTo(expectedBaggage); + } + + static Stream extract_baggageLimit() { + Map prefixCarrier = new LinkedHashMap<>(); + for (int i = 0; i < 65; i++) { + prefixCarrier.put(BAGGAGE_PREFIX + "k" + i, "v" + i); + } + StringBuilder jaegerHeader = new StringBuilder(); + for (int i = 0; i < 65; i++) { + if (i > 0) { + jaegerHeader.append(","); + } + jaegerHeader.append("k").append(i).append("=v").append(i); + } + Map headerCarrier = new LinkedHashMap<>(); + headerCarrier.put(BAGGAGE_HEADER, jaegerHeader.toString()); + Map bigValueCarrier = new LinkedHashMap<>(); + bigValueCarrier.put(BAGGAGE_PREFIX + "k", fillChars('v', 8192)); + return Stream.of( + // 65 uberctx- prefix keys — only first 64 extracted + Arguments.of(prefixCarrier, baggageWithEntries(0, 64)), + // 65 entries in jaeger-baggage header — only first 64 extracted + Arguments.of(headerCarrier, baggageWithEntries(0, 64)), + // single entry whose value exceeds the byte limit — not extracted + Arguments.of(bigValueCarrier, Baggage.empty())); + } + + /** + * Builds a {@link Baggage} with entries {@code k{start}=v{start}} through {@code + * k{start+count-1}=v{start+count-1}}. + */ + private static Baggage baggageWithEntries(int start, int count) { + BaggageBuilder builder = Baggage.builder(); + for (int i = start; i < start + count; i++) { + builder.put("k" + i, "v" + i); + } + return builder.build(); + } + + /** Returns a string of {@code count} repetitions of {@code c}. */ + private static String fillChars(char c, int count) { + char[] chars = new char[count]; + Arrays.fill(chars, c); + return new String(chars); + } + @Test void toString_Valid() { assertThat(jaegerPropagator.toString()).isEqualTo("JaegerPropagator"); diff --git a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracePropagatorTest.java b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracePropagatorTest.java index daae9816959..3e3b211a502 100644 --- a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracePropagatorTest.java +++ b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracePropagatorTest.java @@ -8,6 +8,7 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.SpanId; @@ -17,13 +18,18 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapSetter; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; +import java.util.stream.Stream; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; @SuppressWarnings("deprecation") class OtTracePropagatorTest { @@ -412,6 +418,87 @@ void extract_nullGetter() { assertThat(propagator.extract(context, Collections.emptyMap(), null)).isSameAs(context); } + @Test + void inject_baggageLimit_maxEntries() { + Map carrier = new LinkedHashMap<>(); + propagator.inject( + withSpanContext( + SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault()), + Context.current().with(baggageWithEntries(0, 65))), + carrier, + setter); + long count = + carrier.keySet().stream() + .filter(k -> k.startsWith(OtTracePropagator.PREFIX_BAGGAGE_HEADER)) + .count(); + assertThat(count).isEqualTo(64); + } + + @Test + void inject_baggageLimit_maxBytes() { + Baggage baggage = Baggage.builder().put("k", fillChars('v', 8192)).build(); + Map carrier = new LinkedHashMap<>(); + propagator.inject( + withSpanContext( + SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault()), + Context.current().with(baggage)), + carrier, + setter); + assertThat(carrier).doesNotContainKey(OtTracePropagator.PREFIX_BAGGAGE_HEADER + "k"); + } + + @ParameterizedTest + @MethodSource + void extract_baggageLimit(Map carrier, Baggage expectedBaggage) { + assertThat(Baggage.fromContext(propagator.extract(Context.root(), carrier, getter))) + .isEqualTo(expectedBaggage); + } + + static Stream extract_baggageLimit() { + // Valid span context is required for baggage to be extracted + Map manyEntriesCarrier = carrierWithSpanContext(); + for (int i = 0; i < 65; i++) { + manyEntriesCarrier.put(OtTracePropagator.PREFIX_BAGGAGE_HEADER + "k" + i, "v" + i); + } + Map bigValueCarrier = carrierWithSpanContext(); + bigValueCarrier.put(OtTracePropagator.PREFIX_BAGGAGE_HEADER + "k", fillChars('v', 8192)); + return Stream.of( + // 65 ot-baggage- keys — only first 64 extracted + Arguments.of(manyEntriesCarrier, baggageWithEntries(0, 64)), + // single entry whose value exceeds the byte limit — not extracted + Arguments.of(bigValueCarrier, Baggage.empty())); + } + + /** + * Returns a carrier pre-populated with a valid span context (required for baggage extraction). + */ + private static Map carrierWithSpanContext() { + Map carrier = new LinkedHashMap<>(); + carrier.put(OtTracePropagator.TRACE_ID_HEADER, TRACE_ID_RIGHT_PART); + carrier.put(OtTracePropagator.SPAN_ID_HEADER, SPAN_ID); + carrier.put(OtTracePropagator.SAMPLED_HEADER, "true"); + return carrier; + } + + /** + * Builds a {@link Baggage} with entries {@code k{start}=v{start}} through {@code + * k{start+count-1}=v{start+count-1}}. + */ + private static Baggage baggageWithEntries(int start, int count) { + BaggageBuilder builder = Baggage.builder(); + for (int i = start; i < start + count; i++) { + builder.put("k" + i, "v" + i); + } + return builder.build(); + } + + /** Returns a string of {@code count} repetitions of {@code c}. */ + private static String fillChars(char c, int count) { + char[] chars = new char[count]; + Arrays.fill(chars, c); + return new String(chars); + } + @Test void toString_Valid() { assertThat(propagator.toString()).isEqualTo("OtTracePropagator");