diff options
| author | Bogdan Drutu <bdrutu@google.com> | 2017-03-15 14:48:11 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-03-15 14:48:11 -0700 |
| commit | 8c1d4d23d5488b0fa80117f9355b2f9a3f3082da (patch) | |
| tree | 37813c18d9c8f87c5c7858949898220c5f447444 /core | |
| parent | 544d349f4c3bdbf7fc86444c3c36fba5801710b3 (diff) | |
| download | platform_external_opencensus-java-8c1d4d23d5488b0fa80117f9355b2f9a3f3082da.tar.gz platform_external_opencensus-java-8c1d4d23d5488b0fa80117f9355b2f9a3f3082da.tar.bz2 platform_external_opencensus-java-8c1d4d23d5488b0fa80117f9355b2f9a3f3082da.zip | |
Add an util class to be used for HTTP propagation of the SpanContext. (#139)
* Add an util class to be used for HTTP propagation of the SpanContext.
* First round of comments. Upper-case characters for encoding. Use offset instead of position.
Second round of comments.
Diffstat (limited to 'core')
5 files changed, 506 insertions, 79 deletions
diff --git a/core/src/main/java/com/google/instrumentation/trace/HttpPropagationUtil.java b/core/src/main/java/com/google/instrumentation/trace/HttpPropagationUtil.java new file mode 100644 index 00000000..cc676e0a --- /dev/null +++ b/core/src/main/java/com/google/instrumentation/trace/HttpPropagationUtil.java @@ -0,0 +1,140 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.instrumentation.trace; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.io.BaseEncoding; + +/** + * This is a helper class for {@link SpanContext} propagation on the wire when using the HTTP + * transport protocol. + * + * <p>HTTP header format: + * + * <ul> + * <li>Header name: Trace-Context + * <li>Header value: base16(<version_id>)<version_format> + * <li>version_id: 1-byte representing the version id. + * <li>For version_id = 0: + * <ul> + * <li>version_format: base16(<trace-id><span-id><trace-options>) + * <li>trace-id: 16-byte array representing the trace_id. + * <li>span-id: 8-byte array representing the span_id. + * <li>trace-options: 4-byte array representing the trace_options. + * <li>Valid value example: + * <ul> + * <li>"00404142434445464748494A4B4C4D4E4F616263646566676800000001" + * <li>version_id = 0; + * <li>trace_id = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79} + * <li>span_id = {97, 98, 99, 100, 101, 102, 103, 104}; + * <li>trace_options = {0, 0, 0, 1}; + * </ul> + * </ul> + * + * </ul> + * + * <p>All characters in the header value must be upper case and US-ASCII encoded. + * + * <p>It is strongly encouraged to use this format when using HTTP as a RPC transport. + * + * <p>Example of usage on the client: + * + * <pre>{@code + * private static final Tracer tracer = Tracer.getTracer(); + * void onSendRequest() { + * try (NonThrowingCloseable ss = tracer.spanBuilder("Sent.MyRequest")) { + * String headerName = HttpPropagationUtil.HTTP_HEADER_NAME; + * String headerValue = HttpPropagationUtil.toHttpHeaderValue(span.context()); + * headers.add(headerName, headerValue); + * // Send the HTTP request and wait for the response. + * } + * } + * }</pre> + * + * <p>Example of usage on the server: + * + * <pre>{@code + * private static final Tracer tracer = Tracer.getTracer(); + * void onRequestReceived() { + * String headerName = HttpPropagationUtil.HTTP_HEADER_NAME; + * SpanContext spanContext = HttpPropagationUtil.fromHttpHeaderValue(headers.find(headerName)); + * try (NonThrowingCloseable ss = + * tracer.spanBuilderWithRemoteParent(spanContext, "Recv.MyRequest").startScopedSpan() { + * // Handle request and send response back. + * } + * } + * }</pre> + */ +public final class HttpPropagationUtil { + private static final byte VERSION_ID = 0; + // The version_id size in bytes. + private static final byte VERSION_ID_SIZE = 1; + private static final int BINARY_VERSION_TRACE_ID_OFFSET = VERSION_ID_SIZE; + private static final int BINARY_VERSION_SPAN_ID_OFFSET = VERSION_ID_SIZE + TraceId.SIZE; + private static final int BINARY_VERSION_TRACE_OPTIONS_OFFSET = + VERSION_ID_SIZE + TraceId.SIZE + SpanId.SIZE; + private static final int BINARY_VERSION_FORMAT_LENGTH = + VERSION_ID_SIZE + TraceId.SIZE + SpanId.SIZE + TraceOptions.SIZE; + private static final int HEX_VERSION_FORMAT_LENGTH = 2 * BINARY_VERSION_FORMAT_LENGTH; + + /** The header name that must be used in the HTTP request for the tracing context. */ + public static final String HTTP_HEADER_NAME = "Trace-Context"; + + // Disallow instances of this class. + private HttpPropagationUtil() {} + + + /** + * Serializes a {@link SpanContext} using the HTTP standard format. + * + * @param spanContext the {@code SpanContext} to serialize. + * @return the serialized US-ASCII encoded HTTP header value. + * @throws NullPointerException if the {@code spanContext} is null. + */ + public static String toHttpHeaderValue(SpanContext spanContext) { + checkNotNull(spanContext, "spanContext"); + byte[] bytes = new byte[BINARY_VERSION_FORMAT_LENGTH]; + bytes[0] = VERSION_ID; + spanContext.getTraceId().copyBytesTo(bytes, BINARY_VERSION_TRACE_ID_OFFSET); + spanContext.getSpanId().copyBytesTo(bytes, BINARY_VERSION_SPAN_ID_OFFSET); + spanContext.getTraceOptions().copyBytesTo(bytes, BINARY_VERSION_TRACE_OPTIONS_OFFSET); + return BaseEncoding.base16().encode(bytes); + } + + /** + * Parses the {@link SpanContext} from the HTTP standard format. + * + * @param input a US-ASCII encoded buffer of characters from which the {@code SpanContext} will be + * parsed. + * @return the parsed {@code SpanContext}. + * @throws NullPointerException if the {@code input} is null. + * @throws IllegalArgumentException if the {@code input} is invalid. + */ + public static SpanContext fromHttpHeaderValue(CharSequence input) { + checkNotNull(input, "input"); + checkArgument( + input.length() == HEX_VERSION_FORMAT_LENGTH, + "Invalid input size: expected %s, got %s", + HEX_VERSION_FORMAT_LENGTH, + input.length()); + byte[] bytes = BaseEncoding.base16().decode(input); + checkArgument(bytes[0] == VERSION_ID, "Unsupported version."); + return new SpanContext( + TraceId.fromBytes(bytes, BINARY_VERSION_TRACE_ID_OFFSET), + SpanId.fromBytes(bytes, BINARY_VERSION_SPAN_ID_OFFSET), + TraceOptions.fromBytes(bytes, BINARY_VERSION_TRACE_OPTIONS_OFFSET)); + } +} diff --git a/core/src/main/java/com/google/instrumentation/trace/SpanId.java b/core/src/main/java/com/google/instrumentation/trace/SpanId.java index 70ed6a14..c6e99a89 100644 --- a/core/src/main/java/com/google/instrumentation/trace/SpanId.java +++ b/core/src/main/java/com/google/instrumentation/trace/SpanId.java @@ -14,22 +14,27 @@ package com.google.instrumentation.trace; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.MoreObjects; import com.google.common.io.BaseEncoding; - import java.util.Arrays; import java.util.Random; import javax.annotation.concurrent.Immutable; /** - * A class that represents a span identifier. A valid span identifier is an 8-byte array with - * at least one non-zero byte. + * A class that represents a span identifier. A valid span identifier is an 8-byte array with at + * least one non-zero byte. */ @Immutable public final class SpanId implements Comparable<SpanId> { - // The size in bytes of the span id. - private static final int SPAN_ID_SIZE = 8; + /** The size in bytes of the {@code SpanId}. */ + public static final int SIZE = 8; + + /** The invalid {@code SpanId}. All bytes are 0. */ + public static final SpanId INVALID = new SpanId(new byte[SIZE]); + + // The internal representation of the SpanId. private final byte[] bytes; private SpanId(byte[] bytes) { @@ -37,22 +42,42 @@ public final class SpanId implements Comparable<SpanId> { } /** - * The invalid {@code SpanId}. All bytes are 0. + * Returns a {@code SpanId} built from a byte representation. + * + * <p>Equivalent with: + * + * <pre>{@code + * SpanId.fromBytes(buffer, 0); + * }</pre> + * + * @param buffer the representation of the {@code SpanId}. + * @return a {@code SpanId} whose representation is given by the {@code buffer} parameter. + * @throws NullPointerException if {@code buffer} is null. + * @throws IllegalArgumentException if {@code buffer.length} is not {@link SpanId#SIZE}. */ - public static final SpanId INVALID = new SpanId(new byte[SPAN_ID_SIZE]); + public static SpanId fromBytes(byte[] buffer) { + checkNotNull(buffer, "buffer"); + checkArgument(buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length); + byte[] bytesCopied = Arrays.copyOf(buffer, SIZE); + return new SpanId(bytesCopied); + } /** - * Returns a {@code SpanId} whose representation is given param. + * Returns a {@code SpanId} whose representation is copied from the {@code src} beginning at the + * {@code srcOffset} offset. * - * @param bytes the representation of the {@code SpanId}. - * @return a {@code SpanId} whose representation is given param. - * @throws NullPointerException if bytes is null. - * @throws IllegalArgumentException if bytes length is not 8. + * @param src the buffer where the representation of the {@code SpanId} is copied. + * @param srcOffset the offset in the buffer where the representation of the {@code SpanId} + * begins. + * @return a {@code SpanId} whose representation is copied from the buffer. + * @throws NullPointerException if {@code src} is null. + * @throws IllegalArgumentException if {@code srcOffset+SpanId.SIZE} is greater than {@code + * src.length}. */ - public static SpanId fromBytes(byte[] bytes) { - checkArgument(bytes.length == SPAN_ID_SIZE, "bytes"); - byte[] bytesCopy = Arrays.copyOf(bytes, SPAN_ID_SIZE); - return Arrays.equals(bytesCopy, INVALID.bytes) ? INVALID : new SpanId(bytes); + public static SpanId fromBytes(byte[] src, int srcOffset) { + byte[] bytes = new byte[SIZE]; + System.arraycopy(src, srcOffset, bytes, 0, SIZE); + return new SpanId(bytes); } /** @@ -62,7 +87,7 @@ public final class SpanId implements Comparable<SpanId> { * @return a valid new {@code SpanId}. */ public static SpanId generateRandomId(Random random) { - byte[] bytes = new byte[SPAN_ID_SIZE]; + byte[] bytes = new byte[SIZE]; do { random.nextBytes(bytes); } while (Arrays.equals(bytes, INVALID.bytes)); @@ -70,14 +95,33 @@ public final class SpanId implements Comparable<SpanId> { } /** - * Returns the 8-bytes array representation of the {@code SpanId}. + * Returns the byte representation of the {@code SpanId}. * - * @return the 8-bytes array representation of the {@code SpanId}. + * @return the byte representation of the {@code SpanId}. */ public byte[] getBytes() { - return Arrays.copyOf(bytes, SPAN_ID_SIZE); + return Arrays.copyOf(bytes, SIZE); } + /** + * Copies the byte array representations of the {@code SpanId} into the {@code dest} beginning at + * the {@code destOffset} offset. + * + * <p>Equivalent with (but faster because it avoids any new allocations): + * + * <pre>{@code + * System.arraycopy(getBytes(), 0, dest, destOffset, SpanId.SIZE); + * }</pre> + * + * @param dest the destination buffer. + * @param destOffset the starting offset in the destination buffer. + * @throws NullPointerException if {@code dest} is null. + * @throws IndexOutOfBoundsException if {@code destOffset+SpanId.SIZE} is greater than {@code + * dest.length}. + */ + public void copyBytesTo(byte[] dest, int destOffset) { + System.arraycopy(bytes, 0, dest, destOffset, SIZE); + } /** * Returns whether the span identifier is valid. A valid span identifier is an 8-byte array with @@ -111,15 +155,13 @@ public final class SpanId implements Comparable<SpanId> { @Override public String toString() { return MoreObjects.toStringHelper(this) - .add( - "spanId", - BaseEncoding.base16().lowerCase().encode(bytes)) + .add("spanId", BaseEncoding.base16().lowerCase().encode(bytes)) .toString(); } @Override public int compareTo(SpanId that) { - for (int i = 0; i < SPAN_ID_SIZE; i++) { + for (int i = 0; i < SIZE; i++) { if (bytes[i] != that.bytes[i]) { return bytes[i] < that.bytes[i] ? -1 : 1; } diff --git a/core/src/main/java/com/google/instrumentation/trace/TraceId.java b/core/src/main/java/com/google/instrumentation/trace/TraceId.java index 20d2e108..389007ee 100644 --- a/core/src/main/java/com/google/instrumentation/trace/TraceId.java +++ b/core/src/main/java/com/google/instrumentation/trace/TraceId.java @@ -14,45 +14,70 @@ package com.google.instrumentation.trace; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.MoreObjects; import com.google.common.io.BaseEncoding; - import java.util.Arrays; import java.util.Random; import javax.annotation.concurrent.Immutable; /** - * A class that represents a trace identifier. A valid trace identifier is a 16-byte array with - * at least one non-zero byte. + * A class that represents a trace identifier. A valid trace identifier is a 16-byte array with at + * least one non-zero byte. */ @Immutable public final class TraceId implements Comparable<TraceId> { - // The size in bytes of the trace id. - private static final int TRACE_ID_SIZE = 16; - private final byte[] bytes; + /** The size in bytes of the {@code TraceId}. */ + public static final int SIZE = 16; - /** - * The invalid {@code TraceId}. All bytes are '\0'. - */ - public static final TraceId INVALID = new TraceId(new byte[TRACE_ID_SIZE]); + /** The invalid {@code TraceId}. All bytes are '\0'. */ + public static final TraceId INVALID = new TraceId(new byte[SIZE]); + + // The internal representation of the TraceId. + private final byte[] bytes; private TraceId(byte[] bytes) { this.bytes = bytes; } /** - * Returns a {@code TraceId} whose representation is given param. + * Returns a {@code TraceId} built from a byte representation. * - * @param bytes the representation of the {@code TraceId}. - * @return a {@code TraceId} whose representation is given param. - * @throws NullPointerException if bytes is null. - * @throws IllegalArgumentException if bytes length is not 16. + * <p>Equivalent with: + * + * <pre>{@code + * TraceId.fromBytes(buffer, 0); + * }</pre> + * + * @param buffer the representation of the {@code TraceId}. + * @return a {@code TraceId} whose representation is given by the {@code buffer} parameter. + * @throws NullPointerException if {@code buffer} is null. + * @throws IllegalArgumentException if {@code buffer.length} is not {@link TraceId#SIZE}. */ - public static TraceId fromBytes(byte[] bytes) { - checkArgument(bytes.length == TRACE_ID_SIZE, "bytes"); - byte[] bytesCopy = Arrays.copyOf(bytes, TRACE_ID_SIZE); - return Arrays.equals(bytesCopy, INVALID.bytes) ? INVALID : new TraceId(bytes); + public static TraceId fromBytes(byte[] buffer) { + checkNotNull(buffer, "buffer"); + checkArgument(buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length); + byte[] bytesCopied = Arrays.copyOf(buffer, SIZE); + return new TraceId(bytesCopied); + } + + /** + * Returns a {@code TraceId} whose representation is copied from the {@code src} beginning at the + * {@code srcOffset} offset. + * + * @param src the buffer where the representation of the {@code TraceId} is copied. + * @param srcOffset the offset in the buffer where the representation of the {@code TraceId} + * begins. + * @return a {@code TraceId} whose representation is copied from the buffer. + * @throws NullPointerException if {@code src} is null. + * @throws IllegalArgumentException if {@code srcOffset+TraceId.SIZE} is greater than {@code + * src.length}. + */ + public static TraceId fromBytes(byte[] src, int srcOffset) { + byte[] bytes = new byte[SIZE]; + System.arraycopy(src, srcOffset, bytes, 0, SIZE); + return new TraceId(bytes); } /** @@ -62,7 +87,7 @@ public final class TraceId implements Comparable<TraceId> { * @return a new valid {@code TraceId}. */ public static TraceId generateRandomId(Random random) { - byte[] bytes = new byte[TRACE_ID_SIZE]; + byte[] bytes = new byte[SIZE]; do { random.nextBytes(bytes); } while (Arrays.equals(bytes, INVALID.bytes)); @@ -75,7 +100,27 @@ public final class TraceId implements Comparable<TraceId> { * @return the 16-bytes array representation of the {@code TraceId}. */ public byte[] getBytes() { - return Arrays.copyOf(bytes, TRACE_ID_SIZE); + return Arrays.copyOf(bytes, SIZE); + } + + /** + * Copies the byte array representations of the {@code TraceId} into the {@code dest} beginning at + * the {@code destOffset} offset. + * + * <p>Equivalent with (but faster because it avoids any new allocations): + * + * <pre>{@code + * System.arraycopy(getBytes(), 0, dest, destOffset, TraceId.SIZE); + * }</pre> + * + * @param dest the destination buffer. + * @param destOffset the starting offset in the destination buffer. + * @throws NullPointerException if {@code dest} is null. + * @throws IndexOutOfBoundsException if {@code destOffset+TraceId.SIZE} is greater than {@code + * dest.length}. + */ + public void copyBytesTo(byte[] dest, int destOffset) { + System.arraycopy(bytes, 0, dest, destOffset, SIZE); } /** @@ -110,15 +155,13 @@ public final class TraceId implements Comparable<TraceId> { @Override public String toString() { return MoreObjects.toStringHelper(this) - .add( - "traceId", - BaseEncoding.base16().lowerCase().encode(bytes)) + .add("traceId", BaseEncoding.base16().lowerCase().encode(bytes)) .toString(); } @Override public int compareTo(TraceId that) { - for (int i = 0; i < TRACE_ID_SIZE; i++) { + for (int i = 0; i < SIZE; i++) { if (bytes[i] != that.bytes[i]) { return bytes[i] < that.bytes[i] ? -1 : 1; } diff --git a/core/src/main/java/com/google/instrumentation/trace/TraceOptions.java b/core/src/main/java/com/google/instrumentation/trace/TraceOptions.java index a1198da0..03436b89 100644 --- a/core/src/main/java/com/google/instrumentation/trace/TraceOptions.java +++ b/core/src/main/java/com/google/instrumentation/trace/TraceOptions.java @@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; - import javax.annotation.concurrent.Immutable; /** @@ -36,14 +35,10 @@ public final class TraceOptions { // Bit to represent whether trace is sampled or not. private static final int IS_SAMPLED = 0x1; - /** - * The size in bytes of the {@code TraceOptions}. - */ + /** The size in bytes of the {@code TraceOptions}. */ public static final int SIZE = 4; - /** - * The default {@code TraceOptions}. - */ + /** The default {@code TraceOptions}. */ public static final TraceOptions DEFAULT = new TraceOptions(DEFAULT_OPTIONS); // The set of enabled features is determined by all the enabled bits. @@ -57,15 +52,37 @@ public final class TraceOptions { /** * Returns a {@code TraceOptions} built from a byte representation. * - * @param bytes the representation of the {@code TraceOptions}. - * @return a {@code TraceOptions} whose representation is given by the {@code bytes} parameter. - * @throws NullPointerException if {@code bytes} is null. - * @throws IllegalArgumentException if {@code bytes.length} is not 4. + * <p>Equivalent with: + * + * <pre>{@code + * TraceOptions.fromBytes(buffer, 0); + * }</pre> + * + * @param buffer the representation of the {@code TraceOptions}. + * @return a {@code TraceOptions} whose representation is given by the {@code buffer} parameter. + * @throws NullPointerException if {@code buffer} is null. + * @throws IllegalArgumentException if {@code buffer.length} is not {@link TraceOptions#SIZE}. */ - public static TraceOptions fromBytes(byte[] bytes) { - checkNotNull(bytes); - checkArgument(bytes.length == SIZE); - return new TraceOptions(intFromBytes(bytes, 0)); + public static TraceOptions fromBytes(byte[] buffer) { + checkNotNull(buffer, "buffer"); + checkArgument(buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length); + return new TraceOptions(intFromBytes(buffer, 0)); + } + + /** + * Returns a {@code TraceOptions} whose representation is copied from the {@code src} beginning at + * the {@code srcOffset} offset. + * + * @param src the buffer where the representation of the {@code TraceOptions} is copied. + * @param srcOffset the offset in the buffer where the representation of the {@code TraceOptions} + * begins. + * @return a {@code TraceOptions} whose representation is copied from the buffer. + * @throws NullPointerException if {@code src} is null. + * @throws IllegalArgumentException if {@code srcOffset+TraceOptions.SIZE} is greater than {@code + * src.length}. + */ + public static TraceOptions fromBytes(byte[] src, int srcOffset) { + return new TraceOptions(intFromBytes(src, srcOffset)); } /** @@ -80,6 +97,26 @@ public final class TraceOptions { } /** + * Copies the byte representations of the {@code TraceOptions} into the {@code dest} beginning at + * the {@code destOffset} offset. + * + * <p>Equivalent with (but faster because it avoids any new allocations): + * + * <pre>{@code + * System.arraycopy(getBytes(), 0, dest, destOffset, TraceOptions.SIZE); + * }</pre> + * + * @param dest the destination buffer. + * @param destOffset the starting offset in the destination buffer. + * @throws NullPointerException if {@code dest} is null. + * @throws IndexOutOfBoundsException if {@code destOffset+TraceOptions.SIZE} is greater than + * {@code dest.length}. + */ + public void copyBytesTo(byte[] dest, int destOffset) { + intToBytes(options, dest, destOffset); + } + + /** * Returns a new {@link Builder} with default options. * * @return a new {@code Builder} with default options. @@ -132,9 +169,7 @@ public final class TraceOptions { return MoreObjects.toStringHelper(this).add("sampled", isSampled()).toString(); } - /** - * Builder class for {@link TraceOptions}. - */ + /** Builder class for {@link TraceOptions}. */ public static final class Builder { private int options; @@ -172,19 +207,21 @@ public final class TraceOptions { return (this.options & mask) != 0; } - // Returns the int value whose big-endian representation is stored in the first 4 bytes of src. - private static int intFromBytes(byte[] src, int srcPos) { - return src[srcPos] << (3 * Byte.SIZE) - | (src[srcPos + 1] & BYTE_MASK) << (2 * Byte.SIZE) - | (src[srcPos + 2] & BYTE_MASK) << Byte.SIZE - | (src[srcPos + 3] & BYTE_MASK); + // Returns the int value whose big-endian representation is stored in the first 4 bytes of src + // starting from the given offset. + private static int intFromBytes(byte[] src, int srcOffset) { + return src[srcOffset] << (3 * Byte.SIZE) + | (src[srcOffset + 1] & BYTE_MASK) << (2 * Byte.SIZE) + | (src[srcOffset + 2] & BYTE_MASK) << Byte.SIZE + | (src[srcOffset + 3] & BYTE_MASK); } - // Appends the big-endian representation of value as a 4-element byte array to the destination. - private static void intToBytes(int value, byte[] dest, int destPos) { - dest[destPos] = (byte) (value >> (3 * Byte.SIZE)); - dest[destPos + 1] = (byte) (value >> (2 * Byte.SIZE)); - dest[destPos + 2] = (byte) (value >> Byte.SIZE); - dest[destPos + 3] = (byte) value; + // Appends the big-endian representation of value as a 4-element byte array to the destination + // starting at the given offset. + private static void intToBytes(int value, byte[] dest, int destOffset) { + dest[destOffset] = (byte) (value >> (3 * Byte.SIZE)); + dest[destOffset + 1] = (byte) (value >> (2 * Byte.SIZE)); + dest[destOffset + 2] = (byte) (value >> Byte.SIZE); + dest[destOffset + 3] = (byte) value; } } diff --git a/core/src/test/java/com/google/instrumentation/trace/HttpPropagationUtilTest.java b/core/src/test/java/com/google/instrumentation/trace/HttpPropagationUtilTest.java new file mode 100644 index 00000000..508119a0 --- /dev/null +++ b/core/src/test/java/com/google/instrumentation/trace/HttpPropagationUtilTest.java @@ -0,0 +1,165 @@ +/* + * Copyright 2017, Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.instrumentation.trace; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link HttpPropagationUtil}. */ +@RunWith(JUnit4.class) +public class HttpPropagationUtilTest { + private static final byte[] traceIdBytes = + new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'a'}; + private static final TraceId traceId = TraceId.fromBytes(traceIdBytes); + private static final byte[] spanIdBytes = new byte[] {(byte) 0xFF, 0, 0, 0, 0, 0, 0, 0}; + private static final SpanId spanId = SpanId.fromBytes(spanIdBytes); + private static final byte[] traceOptionsBytes = new byte[] {(byte) 0x12, (byte) 0x34, 0, 1}; + private static final TraceOptions traceOptions = TraceOptions.fromBytes(traceOptionsBytes); + + private static void testSpanContextConversion(SpanContext tc) { + SpanContext propagatedTc = + HttpPropagationUtil.fromHttpHeaderValue(HttpPropagationUtil.toHttpHeaderValue(tc)); + assertWithMessage("Propagated Context is not null.").that(propagatedTc).isNotNull(); + assertWithMessage("Propagated Context is valid.") + .that(propagatedTc.isValid()) + .isEqualTo(tc.isValid()); + assertWithMessage("Trace ids match.") + .that(propagatedTc.getTraceId()) + .isEqualTo(tc.getTraceId()); + assertWithMessage("Span ids match.").that(propagatedTc.getSpanId()).isEqualTo(tc.getSpanId()); + assertWithMessage("TraceOptions should match.") + .that(propagatedTc.getTraceOptions()) + .isEqualTo(tc.getTraceOptions()); + } + + @Test + public void getHttpHeaderName() { + assertThat(HttpPropagationUtil.HTTP_HEADER_NAME).isEqualTo("Trace-Context"); + } + + @Test + public void propagate_SpanContextTracingEnabled() { + testSpanContextConversion( + new SpanContext(traceId, spanId, TraceOptions.builder().setIsSampled().build())); + } + + @Test + public void propagate_SpanContextNoTracing() { + testSpanContextConversion(new SpanContext(traceId, spanId, TraceOptions.DEFAULT)); + } + + @Test + public void format_SpanContext() { + assertThat( + HttpPropagationUtil.toHttpHeaderValue(new SpanContext(traceId, spanId, traceOptions))) + .isEqualTo("0000000000000000000000000000000061FF0000000000000012340001"); + } + + @Test(expected = NullPointerException.class) + public void toHttpHeaderValue_NullSpanContext() { + HttpPropagationUtil.toHttpHeaderValue(null); + } + + @Test + public void toHttpHeaderValue_InvalidSpanContext() { + assertThat(HttpPropagationUtil.toHttpHeaderValue(SpanContext.INVALID)).isEqualTo + ("0000000000000000000000000000000000000000000000000000000000"); + } + + @Test + public void parseHeaderValue_ValidValues() { + assertThat( + HttpPropagationUtil.fromHttpHeaderValue( + "0000000000000000000000000000000061FF0000000000000012340001")) + .isEqualTo(new SpanContext(traceId, spanId, traceOptions)); + + assertThat( + HttpPropagationUtil.fromHttpHeaderValue( + "0000000000000000000000000000000000FF0000000000000012340001")) + .isEqualTo(new SpanContext(TraceId.INVALID, spanId, traceOptions)); + + assertThat( + HttpPropagationUtil.fromHttpHeaderValue( + "0000000000000000000000000000000061000000000000000012340001")) + .isEqualTo(new SpanContext(traceId, SpanId.INVALID, traceOptions)); + + assertThat( + HttpPropagationUtil.fromHttpHeaderValue( + "0000000000000000000000000000000061FF0000000000000000000000")) + .isEqualTo(new SpanContext(traceId, spanId, TraceOptions.DEFAULT)); + } + + @Test + public void parseHeaderValue_ExampleValue() { + assertThat( + HttpPropagationUtil.fromHttpHeaderValue( + "00404142434445464748494A4B4C4D4E4F616263646566676800000001")) + .isEqualTo( + new SpanContext( + TraceId.fromBytes( + new byte[] {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}), + SpanId.fromBytes(new byte[] {97, 98, 99, 100, 101, 102, 103, 104}), + TraceOptions.fromBytes(new byte[] {0, 0, 0, 1}))); + } + + @Test(expected = NullPointerException.class) + public void parseHeaderValue_NullInput() { + HttpPropagationUtil.fromHttpHeaderValue(null); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHeaderValue_EmptyInput() { + HttpPropagationUtil.fromHttpHeaderValue(""); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHeaderValue_InvalidVersionId() { + HttpPropagationUtil.fromHttpHeaderValue( + "0100000000000000000000000000000061FF0000000000000000000001"); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHeaderValue_LongerVersionFormat() { + HttpPropagationUtil.fromHttpHeaderValue( + "0000000000000000000000000000000061FF000000000000000000000100"); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHeaderValue_ShorterVersionFormat() { + HttpPropagationUtil.fromHttpHeaderValue( + "0000000000000000000000000000000000ff00000000000000000001"); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHeaderValue_InvalidCharactersInTraceId() { + HttpPropagationUtil.fromHttpHeaderValue( + "00000000000000007b0000SPAN000001c8ff0000000000000000000001"); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHeaderValue_InvalidCharactersInSpanId() { + HttpPropagationUtil.fromHttpHeaderValue( + "0000000000000000000000000000000061000000ROOT00031500000001"); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHeaderValue_InvalidCharactersInTraceOptions() { + HttpPropagationUtil.fromHttpHeaderValue( + "0000000000000000000000000000000061FF0000000000000000BAR001"); + } +} |
