setter) {
+ this.propagation = propagation;
+ this.setter = setter;
+ }
+
+ /**
+ * This version of propagation contains at least 74 characters corresponding to identifiers and
+ * the sampling bit. It will also include extra fields where present.
+ *
+ * Ex 74 characters: {@code Root=1-67891233-abcdef012345678912345678;Parent=463ac35c9f6413ad;Sampled=1}
+ *
+ *
{@inheritDoc}
+ */
+ @Override public void inject(TraceContext traceContext, C carrier) {
+ CharSequence extra = null;
+ for (int i = 0, length = traceContext.extra().size(); i < length; i++) {
+ Object next = traceContext.extra().get(i);
+ if (next instanceof Extra) {
+ extra = ((Extra) next).fields;
+ break;
+ }
+ }
+ int extraLength = extra == null ? 0 : extra.length();
+ //Root=1-67891233-abcdef012345678912345678;Parent=463ac35c9f6413ad;Sampled=1
+ char[] result = new char[74 + extraLength];
+ System.arraycopy(ROOT, 0, result, 0, 5);
+ writeTraceId(traceContext, result, 5);
+ System.arraycopy(PARENT, 0, result, 40, 8);
+ writeHexLong(result, 48, traceContext.spanId());
+ System.arraycopy(SAMPLED, 0, result, 64, 9);
+ Boolean sampled = traceContext.sampled();
+ // Sampled status is same as B3, but ? means downstream decides (like omitting X-B3-Sampled)
+ // https://github.com/aws/aws-xray-sdk-go/blob/391885218b556c43ed05a1e736a766d70fc416f1/header/header.go#L50
+ result[73] = sampled == null ? '?' : sampled ? '1' : '0';
+ for (int i = 0; i < extraLength; i++) {
+ result[i + 74] = extra.charAt(i);
+ }
+ setter.put(carrier, propagation.traceIdKey, new String(result));
+ }
+ }
+
+ /** Used for log correlation or {@link brave.Span#tag(String, String) tag values} */
+ public static String traceIdString(TraceContext context) {
+ char[] result = new char[35];
+ writeTraceId(context, result, 0);
+ return new String(result);
+ }
+
+ /** Writes 35 characters representing the input trace ID to the buffer at the given offset */
+ static void writeTraceId(TraceContext context, char[] result, int offset) {
+ result[offset] = '1'; // version
+ result[offset + 1] = '-'; // delimiter
+ long high = context.traceIdHigh();
+ writeHexByte(result, offset + 2, (byte) ((high >>> 56L) & 0xff));
+ writeHexByte(result, offset + 4, (byte) ((high >>> 48L) & 0xff));
+ writeHexByte(result, offset + 6, (byte) ((high >>> 40L) & 0xff));
+ writeHexByte(result, offset + 8, (byte) ((high >>> 32L) & 0xff));
+ result[offset + 10] = '-';
+ writeHexByte(result, offset + 11, (byte) ((high >>> 24L) & 0xff));
+ writeHexByte(result, offset + 13, (byte) ((high >>> 16L) & 0xff));
+ writeHexByte(result, offset + 15, (byte) ((high >>> 8L) & 0xff));
+ writeHexByte(result, offset + 17, (byte) (high & 0xff));
+ writeHexLong(result, offset + 19, context.traceId());
+ }
+
+ @Override public TraceContext.Extractor extractor(Getter getter) {
+ if (getter == null) throw new NullPointerException("getter == null");
+ return new AWSExtractor(this, getter);
+ }
+
+ static final AWSExtractor STRING_EXTRACTOR =
+ new AWSExtractor<>(new AWSPropagation<>(KeyFactory.STRING), (carrier, key) -> carrier);
+
+ /**
+ * Like {@link TraceContext.Extractor#extract(Object)} except reading from a single field.
+ *
+ * This is used for extracting from the AWS lambda environment variable {@code
+ * X_AMZN_TRACE_ID}.
+ */
+ public static TraceContextOrSamplingFlags extract(String amznTraceId) {
+ if (amznTraceId == null) throw new NullPointerException("amznTraceId == null");
+ return STRING_EXTRACTOR.extract(amznTraceId);
+ }
+
+ static final class AWSExtractor implements TraceContext.Extractor {
+ final AWSPropagation propagation;
+ final Getter getter;
+
+ AWSExtractor(AWSPropagation propagation, Getter getter) {
+ this.propagation = propagation;
+ this.getter = getter;
+ }
+
+ enum Op {
+ SKIP,
+ ROOT,
+ PARENT,
+ SAMPLED,
+ EXTRA
+ }
+
+ @Override public TraceContextOrSamplingFlags extract(C carrier) {
+ if (carrier == null) throw new NullPointerException("carrier == null");
+ String traceIdString = getter.get(carrier, propagation.traceIdKey);
+ if (traceIdString == null) return TraceContextOrSamplingFlags.create(SamplingFlags.EMPTY);
+
+ Boolean sampled = null;
+ long traceIdHigh = 0L, traceId = 0L;
+ Long parent = null;
+ StringBuilder currentString = new StringBuilder(7 /* Sampled.length */), currentExtra = null;
+ Op op = null;
+ OUTER:
+ for (int i = 0, length = traceIdString.length(); i < length; i++) {
+ char c = traceIdString.charAt(i);
+ if (c == ' ') continue; // trim whitespace
+ if (c == '=') { // we reached a field name
+ if (++i == length) break; // skip '=' character
+ if (currentString.indexOf("Root") == 0) {
+ op = Op.ROOT;
+ } else if (currentString.indexOf("Parent") == 0) {
+ op = Op.PARENT;
+ } else if (currentString.indexOf("Sampled") == 0) {
+ op = Op.SAMPLED;
+ } else if (currentString.indexOf("Self") == 0) {
+ // ALB implements Trace ID chaining using self so that customers not using X-Ray
+ // (I.e. request logs) can do the correlation themselves. We drop these
+ op = Op.SKIP;
+ } else {
+ op = Op.EXTRA;
+ if (currentExtra == null) currentExtra = new StringBuilder();
+ currentExtra.append(';').append(currentString);
+ }
+ currentString.setLength(0);
+ } else if (op == null) {
+ currentString.append(c);
+ continue;
+ }
+ // no longer whitespace
+ switch (op) {
+ case EXTRA:
+ currentExtra.append(c);
+ while (i < length && (c = traceIdString.charAt(i)) != ';') {
+ currentExtra.append(c);
+ i++;
+ }
+ break;
+ case SKIP:
+ while (++i < length && traceIdString.charAt(i) != ';') {
+ // skip until we hit a delimiter
+ }
+ break;
+ case ROOT:
+ if (i + 35 > length // 35 = length of 1-67891233-abcdef012345678912345678
+ || traceIdString.charAt(i++) != '1'
+ || traceIdString.charAt(i++) != '-') {
+ break OUTER; // invalid version or format
+ }
+ // Parse the epoch seconds and high 32 of the 96 bit trace ID into traceID high
+ for (int hyphenIndex = i + 8, endIndex = hyphenIndex + 1 + 8; i < endIndex; i++) {
+ c = traceIdString.charAt(i);
+ if (c == '-' && i == hyphenIndex) continue; // skip delimiter between epoch and random
+ traceIdHigh <<= 4;
+ if (c >= '0' && c <= '9') {
+ traceIdHigh |= c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ traceIdHigh |= c - 'a' + 10;
+ } else {
+ break OUTER; // invalid format
+ }
+ }
+ // Parse the low 64 of the 96 bit trace ID into traceId
+ for (int endIndex = i + 16; i < endIndex; i++) {
+ c = traceIdString.charAt(i);
+ traceId <<= 4;
+ if (c >= '0' && c <= '9') {
+ traceId |= c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ traceId |= c - 'a' + 10;
+ } else {
+ break OUTER; // invalid format
+ }
+ }
+ break;
+ case PARENT:
+ long parentId = 0L;
+ for (int endIndex = i + 16; i < endIndex; i++) {
+ c = traceIdString.charAt(i);
+ parentId <<= 4;
+ if (c >= '0' && c <= '9') {
+ parentId |= c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ parentId |= c - 'a' + 10;
+ } else {
+ break OUTER; // invalid format
+ }
+ }
+ parent = parentId;
+ break;
+ case SAMPLED:
+ c = traceIdString.charAt(i++);
+ if (c == '1') {
+ sampled = true;
+ } else if (c == '0') {
+ sampled = false;
+ }
+ break;
+ }
+ op = null;
+ }
+ TraceContextOrSamplingFlags result;
+
+ if (traceIdHigh == 0L) { // traceIdHigh cannot be null, so just return sampled
+ result = TraceContextOrSamplingFlags.create(
+ new SamplingFlags.Builder().sampled(sampled).build()
+ );
+ } else if (parent == null) {
+ result = TraceContextOrSamplingFlags.create(TraceIdContext.newBuilder()
+ .traceIdHigh(traceIdHigh)
+ .traceId(traceId)
+ .sampled(sampled)
+ .build()
+ );
+ } else {
+ result = TraceContextOrSamplingFlags.create(TraceContext.newBuilder()
+ .traceIdHigh(traceIdHigh)
+ .traceId(traceId)
+ .spanId(parent)
+ .sampled(sampled)
+ .build()
+ );
+ }
+ if (currentExtra == null) return result;
+ Extra extra = new Extra();
+ extra.fields = currentExtra;
+ return result.toBuilder().addExtra(extra).build();
+ }
+ }
+
+ static final class Extra { // hidden intentionally
+ CharSequence fields;
+ }
+}
diff --git a/propagation/aws/src/test/java/brave/propagation/aws/AWSPropagationTest.java b/propagation/aws/src/test/java/brave/propagation/aws/AWSPropagationTest.java
new file mode 100644
index 0000000000..c902fb7df9
--- /dev/null
+++ b/propagation/aws/src/test/java/brave/propagation/aws/AWSPropagationTest.java
@@ -0,0 +1,153 @@
+package brave.propagation.aws;
+
+import brave.internal.HexCodec;
+import brave.propagation.Propagation;
+import brave.propagation.SamplingFlags;
+import brave.propagation.TraceContext;
+import brave.propagation.TraceContextOrSamplingFlags;
+import brave.propagation.TraceIdContext;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AWSPropagationTest {
+ Map carrier = new LinkedHashMap<>();
+ TraceContext.Injector