From 59891388651534c505113e63e6b9b87195c8d09c Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:01:24 -0500 Subject: [PATCH 1/5] Apply limits to baggage extraction and injection --- .../api/baggage/propagation/Parser.java | 14 ++- .../propagation/W3CBaggagePropagator.java | 60 ++++++++- .../propagation/W3CBaggagePropagatorTest.java | 117 ++++++++++++++++++ 3 files changed, 182 insertions(+), 9 deletions(-) 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..05319c63770 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,13 +37,15 @@ 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++) { char current = baggageHeader.charAt(i); @@ -123,13 +126,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 +145,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..f73fa43b102 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,117 @@ 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 — should not be extracted + char[] chars = new char[8192]; + Arrays.fill(chars, 'v'); + String header = "k=" + new String(chars); + 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 + char[] almostMaxChars = new char[8189]; + Arrays.fill(almostMaxChars, 'v'); + String almostMax = "k=" + new String(almostMaxChars); // "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 + char[] longValueChars = new char[8192]; + Arrays.fill(longValueChars, 'v'); + String longValue = new String(longValueChars); + Baggage baggage = Baggage.builder().put("k", longValue).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. + char[] metaChars = new char[8190]; + Arrays.fill(metaChars, 'x'); + Baggage baggage = + Baggage.builder().put("k", "v", BaggageEntryMetadata.create(new String(metaChars))).build(); + Map carrier = new HashMap<>(); + W3CBaggagePropagator.getInstance().inject(Context.root().with(baggage), carrier, Map::put); + assertThat(carrier).doesNotContainKey("baggage"); + } + @Test void toString_Valid() { assertThat(W3CBaggagePropagator.getInstance().toString()).isEqualTo("W3CBaggagePropagator"); From fe115fe942bebc447c19da778274fcfe8db0193c Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:22:22 -0500 Subject: [PATCH 2/5] Add baggage limits to JaegerPropagator, OtTracePropagator --- .../trace/propagation/JaegerPropagator.java | 62 +++++++++++--- .../trace/propagation/OtTracePropagator.java | 31 ++++++- .../propagation/JaegerPropagatorTest.java | 70 ++++++++++++++++ .../propagation/OtTracePropagatorTest.java | 82 +++++++++++++++++++ 4 files changed, 233 insertions(+), 12 deletions(-) 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..938ba55148f 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,38 @@ 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..3ff75fd03c8 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,70 @@ 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", nChars('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", nChars('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 nChars(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..15b71f24546 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,82 @@ 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", nChars('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", nChars('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 nChars(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"); From 6844a040633e18234a09ff81522e65f588b83bc8 Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Thu, 7 May 2026 15:35:00 -0500 Subject: [PATCH 3/5] Fix build --- .../trace/propagation/JaegerPropagator.java | 6 +++++- .../trace/propagation/JaegerPropagatorTest.java | 11 +++++++---- .../trace/propagation/OtTracePropagatorTest.java | 15 ++++++++++----- 3 files changed, 22 insertions(+), 10 deletions(-) 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 938ba55148f..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 @@ -279,7 +279,11 @@ private static Baggage getBaggageFromHeader(@Nullable C carrier, TextMapGett builder = Baggage.builder(); } int[] counts = - parseBaggageHeader(value, builder, MAX_BAGGAGE_ENTRIES - entriesAdded, MAX_BAGGAGE_BYTES - bytesAdded); + parseBaggageHeader( + value, + builder, + MAX_BAGGAGE_ENTRIES - entriesAdded, + MAX_BAGGAGE_BYTES - bytesAdded); entriesAdded += counts[0]; bytesAdded += counts[1]; } 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 3ff75fd03c8..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 @@ -482,7 +482,7 @@ void inject_baggageLimit_maxEntries() { @Test void inject_baggageLimit_maxBytes() { - Baggage baggage = Baggage.builder().put("k", nChars('v', 8192)).build(); + 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"); @@ -510,7 +510,7 @@ static Stream extract_baggageLimit() { Map headerCarrier = new LinkedHashMap<>(); headerCarrier.put(BAGGAGE_HEADER, jaegerHeader.toString()); Map bigValueCarrier = new LinkedHashMap<>(); - bigValueCarrier.put(BAGGAGE_PREFIX + "k", nChars('v', 8192)); + 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)), @@ -520,7 +520,10 @@ static Stream extract_baggageLimit() { 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}}. */ + /** + * 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++) { @@ -530,7 +533,7 @@ private static Baggage baggageWithEntries(int start, int count) { } /** Returns a string of {@code count} repetitions of {@code c}. */ - private static String nChars(char c, int count) { + private static String fillChars(char c, int count) { char[] chars = new char[count]; Arrays.fill(chars, c); return new String(chars); 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 15b71f24546..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 @@ -436,7 +436,7 @@ void inject_baggageLimit_maxEntries() { @Test void inject_baggageLimit_maxBytes() { - Baggage baggage = Baggage.builder().put("k", nChars('v', 8192)).build(); + Baggage baggage = Baggage.builder().put("k", fillChars('v', 8192)).build(); Map carrier = new LinkedHashMap<>(); propagator.inject( withSpanContext( @@ -461,7 +461,7 @@ static Stream extract_baggageLimit() { manyEntriesCarrier.put(OtTracePropagator.PREFIX_BAGGAGE_HEADER + "k" + i, "v" + i); } Map bigValueCarrier = carrierWithSpanContext(); - bigValueCarrier.put(OtTracePropagator.PREFIX_BAGGAGE_HEADER + "k", nChars('v', 8192)); + 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)), @@ -469,7 +469,9 @@ static Stream extract_baggageLimit() { Arguments.of(bigValueCarrier, Baggage.empty())); } - /** Returns a carrier pre-populated with a valid span context (required for baggage extraction). */ + /** + * 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); @@ -478,7 +480,10 @@ private static Map carrierWithSpanContext() { return carrier; } - /** Builds a {@link Baggage} with entries {@code k{start}=v{start}} through {@code k{start+count-1}=v{start+count-1}}. */ + /** + * 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++) { @@ -488,7 +493,7 @@ private static Baggage baggageWithEntries(int start, int count) { } /** Returns a string of {@code count} repetitions of {@code c}. */ - private static String nChars(char c, int count) { + private static String fillChars(char c, int count) { char[] chars = new char[count]; Arrays.fill(chars, c); return new String(chars); From 38486fe28b4aa27949aab8289039d0c63e3f4750 Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Fri, 8 May 2026 09:30:48 -0500 Subject: [PATCH 4/5] PR feedback --- .../api/baggage/propagation/Parser.java | 3 ++ .../propagation/W3CBaggagePropagator.java | 15 ++++++- .../propagation/W3CBaggagePropagatorTest.java | 42 ++++++++++++------- 3 files changed, 43 insertions(+), 17 deletions(-) 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 05319c63770..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 @@ -47,6 +47,9 @@ private enum State { 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) { 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 2b589315435..72b38a80e4e 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 @@ -155,19 +155,30 @@ private static Context extractMulti( continue; } + int remainingBytes = MAX_BAGGAGE_BYTES - totalBytes; totalBytes += header.length(); - if (totalBytes > MAX_BAGGAGE_BYTES || totalEntries >= MAX_BAGGAGE_ENTRIES) { + + if (totalEntries >= MAX_BAGGAGE_ENTRIES) { LOGGER.fine("Baggage header exceeded W3C limits, dropping remaining entries"); break; } + String headerToParse = + totalBytes > MAX_BAGGAGE_BYTES ? header.substring(0, remainingBytes) : header; + try { - int added = extractEntries(header, baggageBuilder, MAX_BAGGAGE_ENTRIES - totalEntries); + int added = + extractEntries(headerToParse, baggageBuilder, MAX_BAGGAGE_ENTRIES - totalEntries); extracted = true; totalEntries += added; } catch (RuntimeException expected) { // invalid baggage header, continue } + + if (totalBytes > MAX_BAGGAGE_BYTES) { + LOGGER.fine("Baggage header exceeded W3C limits, dropping remaining entries"); + break; + } } return extracted ? context.with(baggageBuilder.build()) : context; 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 f73fa43b102..96e8368f813 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 @@ -649,21 +649,32 @@ private static String baggageHeader(int start, int count) { @Test void extract_limit_maxBytes_exceedsLimit() { W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); - // Single header over 8192 bytes — should not be extracted - char[] chars = new char[8192]; - Arrays.fill(chars, 'v'); - String header = "k=" + new String(chars); + // Single header over 8192 bytes — truncated to the byte limit; the entry within budget is + // extracted with a truncated value + String header = "k=" + fillChars('v', 8192); // 8194 bytes; truncated to 8192 → k=<8190 v's> Context result = propagator.extract(Context.root(), ImmutableMap.of("baggage", header), getter); - assertThat(Baggage.fromContext(result)).isEqualTo(Baggage.empty()); + assertThat(Baggage.fromContext(result).getEntryValue("k")).isEqualTo(fillChars('v', 8190)); + } + + @Test + void extract_limit_maxBytes_partialHeader() { + W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); + // A header where the first entry is complete within the byte budget but the second entry's + // key is cut off by the truncation — only the first entry is extracted. + // "k1=" (3) + 8186 'v's + "," (1) + "k2=v2" (5) = 8195 bytes; + // truncated to 8192 → "k1=<8186 v's>,k2" (k2's "=" is beyond the budget) + String header = "k1=" + fillChars('v', 8186) + ",k2=v2"; + Context result = propagator.extract(Context.root(), ImmutableMap.of("baggage", header), getter); + Baggage baggage = Baggage.fromContext(result); + assertThat(baggage.getEntryValue("k1")).isEqualTo(fillChars('v', 8186)); + assertThat(baggage.getEntryValue("k2")).isNull(); } @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 - char[] almostMaxChars = new char[8189]; - Arrays.fill(almostMaxChars, 'v'); - String almostMax = "k=" + new String(almostMaxChars); // "k=vvv..." + String almostMax = "k=" + fillChars('v', 8189); // "k=vvv..." String second = "k2=v2"; Context result = propagator.extract( @@ -690,10 +701,7 @@ void inject_limit_maxEntries() { void inject_limit_maxBytes() { W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); // One entry whose encoded form alone exceeds the byte limit — should produce empty header - char[] longValueChars = new char[8192]; - Arrays.fill(longValueChars, 'v'); - String longValue = new String(longValueChars); - Baggage baggage = Baggage.builder().put("k", longValue).build(); + 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"); @@ -703,15 +711,19 @@ void inject_limit_maxBytes() { 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. - char[] metaChars = new char[8190]; - Arrays.fill(metaChars, 'x'); Baggage baggage = - Baggage.builder().put("k", "v", BaggageEntryMetadata.create(new String(metaChars))).build(); + 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"); From cbadf0e7eebf92af665f0db2cf6159085e12dcb3 Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Fri, 8 May 2026 09:40:13 -0500 Subject: [PATCH 5/5] Revert partial parsing --- .../propagation/W3CBaggagePropagator.java | 15 ++----------- .../propagation/W3CBaggagePropagatorTest.java | 21 +++---------------- 2 files changed, 5 insertions(+), 31 deletions(-) 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 72b38a80e4e..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 @@ -155,30 +155,19 @@ private static Context extractMulti( continue; } - int remainingBytes = MAX_BAGGAGE_BYTES - totalBytes; totalBytes += header.length(); - - if (totalEntries >= MAX_BAGGAGE_ENTRIES) { + if (totalBytes > MAX_BAGGAGE_BYTES || totalEntries >= MAX_BAGGAGE_ENTRIES) { LOGGER.fine("Baggage header exceeded W3C limits, dropping remaining entries"); break; } - String headerToParse = - totalBytes > MAX_BAGGAGE_BYTES ? header.substring(0, remainingBytes) : header; - try { - int added = - extractEntries(headerToParse, baggageBuilder, MAX_BAGGAGE_ENTRIES - totalEntries); + int added = extractEntries(header, baggageBuilder, MAX_BAGGAGE_ENTRIES - totalEntries); extracted = true; totalEntries += added; } catch (RuntimeException expected) { // invalid baggage header, continue } - - if (totalBytes > MAX_BAGGAGE_BYTES) { - LOGGER.fine("Baggage header exceeded W3C limits, dropping remaining entries"); - break; - } } return extracted ? context.with(baggageBuilder.build()) : context; 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 96e8368f813..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 @@ -649,25 +649,10 @@ private static String baggageHeader(int start, int count) { @Test void extract_limit_maxBytes_exceedsLimit() { W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); - // Single header over 8192 bytes — truncated to the byte limit; the entry within budget is - // extracted with a truncated value - String header = "k=" + fillChars('v', 8192); // 8194 bytes; truncated to 8192 → k=<8190 v's> + // 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).getEntryValue("k")).isEqualTo(fillChars('v', 8190)); - } - - @Test - void extract_limit_maxBytes_partialHeader() { - W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance(); - // A header where the first entry is complete within the byte budget but the second entry's - // key is cut off by the truncation — only the first entry is extracted. - // "k1=" (3) + 8186 'v's + "," (1) + "k2=v2" (5) = 8195 bytes; - // truncated to 8192 → "k1=<8186 v's>,k2" (k2's "=" is beyond the budget) - String header = "k1=" + fillChars('v', 8186) + ",k2=v2"; - Context result = propagator.extract(Context.root(), ImmutableMap.of("baggage", header), getter); - Baggage baggage = Baggage.fromContext(result); - assertThat(baggage.getEntryValue("k1")).isEqualTo(fillChars('v', 8186)); - assertThat(baggage.getEntryValue("k2")).isNull(); + assertThat(Baggage.fromContext(result)).isEqualTo(Baggage.empty()); } @Test