• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017, OpenCensus Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.opencensus.implcore.trace.propagation;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.common.annotations.VisibleForTesting;
22 import io.opencensus.trace.SpanContext;
23 import io.opencensus.trace.SpanId;
24 import io.opencensus.trace.TraceId;
25 import io.opencensus.trace.TraceOptions;
26 import io.opencensus.trace.Tracestate;
27 import io.opencensus.trace.propagation.BinaryFormat;
28 import io.opencensus.trace.propagation.SpanContextParseException;
29 
30 /**
31  * Implementation of the {@link BinaryFormat}.
32  *
33  * <p>BinaryFormat format:
34  *
35  * <ul>
36  *   <li>Binary value: &lt;version_id&gt;&lt;version_format&gt;
37  *   <li>version_id: 1-byte representing the version id.
38  *   <li>For version_id = 0:
39  *       <ul>
40  *         <li>version_format: &lt;field&gt;&lt;field&gt;
41  *         <li>field_format: &lt;field_id&gt;&lt;field_format&gt;
42  *         <li>Fields:
43  *             <ul>
44  *               <li>TraceId: (field_id = 0, len = 16, default = &#34;0000000000000000&#34;) -
45  *                   16-byte array representing the trace_id.
46  *               <li>SpanId: (field_id = 1, len = 8, default = &#34;00000000&#34;) - 8-byte array
47  *                   representing the span_id.
48  *               <li>TraceOptions: (field_id = 2, len = 1, default = &#34;0&#34;) - 1-byte array
49  *                   representing the trace_options.
50  *             </ul>
51  *         <li>Fields MUST be encoded using the field id order (smaller to higher).
52  *         <li>Valid value example:
53  *             <ul>
54  *               <li>{0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97,
55  *                   98, 99, 100, 101, 102, 103, 104, 2, 1}
56  *               <li>version_id = 0;
57  *               <li>trace_id = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}
58  *               <li>span_id = {97, 98, 99, 100, 101, 102, 103, 104};
59  *               <li>trace_options = {1};
60  *             </ul>
61  *       </ul>
62  * </ul>
63  */
64 final class BinaryFormatImpl extends BinaryFormat {
65   private static final Tracestate TRACESTATE_DEFAULT = Tracestate.builder().build();
66   private static final byte VERSION_ID = 0;
67   private static final int VERSION_ID_OFFSET = 0;
68   // The version_id/field_id size in bytes.
69   private static final byte ID_SIZE = 1;
70   private static final byte TRACE_ID_FIELD_ID = 0;
71 
72   // TODO: clarify if offsets are correct here. While the specification suggests you should stop
73   // parsing when you hit an unknown field, it does not suggest that fields must be declared in
74   // ID order. Rather it only groups by data type order, in this case Trace Context
75   // https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md#deserialization-rules
76   @VisibleForTesting static final int TRACE_ID_FIELD_ID_OFFSET = VERSION_ID_OFFSET + ID_SIZE;
77 
78   private static final int TRACE_ID_OFFSET = TRACE_ID_FIELD_ID_OFFSET + ID_SIZE;
79   private static final byte SPAN_ID_FIELD_ID = 1;
80 
81   @VisibleForTesting static final int SPAN_ID_FIELD_ID_OFFSET = TRACE_ID_OFFSET + TraceId.SIZE;
82 
83   private static final int SPAN_ID_OFFSET = SPAN_ID_FIELD_ID_OFFSET + ID_SIZE;
84   private static final byte TRACE_OPTION_FIELD_ID = 2;
85 
86   @VisibleForTesting static final int TRACE_OPTION_FIELD_ID_OFFSET = SPAN_ID_OFFSET + SpanId.SIZE;
87 
88   private static final int TRACE_OPTIONS_OFFSET = TRACE_OPTION_FIELD_ID_OFFSET + ID_SIZE;
89   /** Version, Trace and Span IDs are required fields. */
90   private static final int REQUIRED_FORMAT_LENGTH = 3 * ID_SIZE + TraceId.SIZE + SpanId.SIZE;
91   /** Use {@link TraceOptions#DEFAULT} unless its optional field is present. */
92   private static final int ALL_FORMAT_LENGTH = REQUIRED_FORMAT_LENGTH + ID_SIZE + TraceOptions.SIZE;
93 
94   @Override
toByteArray(SpanContext spanContext)95   public byte[] toByteArray(SpanContext spanContext) {
96     checkNotNull(spanContext, "spanContext");
97     byte[] bytes = new byte[ALL_FORMAT_LENGTH];
98     bytes[VERSION_ID_OFFSET] = VERSION_ID;
99     bytes[TRACE_ID_FIELD_ID_OFFSET] = TRACE_ID_FIELD_ID;
100     spanContext.getTraceId().copyBytesTo(bytes, TRACE_ID_OFFSET);
101     bytes[SPAN_ID_FIELD_ID_OFFSET] = SPAN_ID_FIELD_ID;
102     spanContext.getSpanId().copyBytesTo(bytes, SPAN_ID_OFFSET);
103     bytes[TRACE_OPTION_FIELD_ID_OFFSET] = TRACE_OPTION_FIELD_ID;
104     spanContext.getTraceOptions().copyBytesTo(bytes, TRACE_OPTIONS_OFFSET);
105     return bytes;
106   }
107 
108   @Override
fromByteArray(byte[] bytes)109   public SpanContext fromByteArray(byte[] bytes) throws SpanContextParseException {
110     checkNotNull(bytes, "bytes");
111     if (bytes.length == 0 || bytes[0] != VERSION_ID) {
112       throw new SpanContextParseException("Unsupported version.");
113     }
114     if (bytes.length < REQUIRED_FORMAT_LENGTH) {
115       throw new SpanContextParseException("Invalid input: truncated");
116     }
117     // TODO: the following logic assumes that fields are written in ID order. The spec does not say
118     // that. If it decides not to, this logic would need to be more like a loop
119     TraceId traceId;
120     SpanId spanId;
121     TraceOptions traceOptions = TraceOptions.DEFAULT;
122     int pos = 1;
123     if (bytes[pos] == TRACE_ID_FIELD_ID) {
124       traceId = TraceId.fromBytes(bytes, pos + ID_SIZE);
125       pos += ID_SIZE + TraceId.SIZE;
126     } else {
127       // TODO: update the spec to suggest that the trace ID is not actually optional
128       throw new SpanContextParseException("Invalid input: expected trace ID at offset " + pos);
129     }
130     if (bytes[pos] == SPAN_ID_FIELD_ID) {
131       spanId = SpanId.fromBytes(bytes, pos + ID_SIZE);
132       pos += ID_SIZE + SpanId.SIZE;
133     } else {
134       // TODO: update the spec to suggest that the span ID is not actually optional.
135       throw new SpanContextParseException("Invalid input: expected span ID at offset " + pos);
136     }
137     // Check to see if we are long enough to include an options field, and also that the next field
138     // is an options field. Per spec we simply stop parsing at first unknown field instead of
139     // failing.
140     if (bytes.length > pos && bytes[pos] == TRACE_OPTION_FIELD_ID) {
141       if (bytes.length < ALL_FORMAT_LENGTH) {
142         throw new SpanContextParseException("Invalid input: truncated");
143       }
144       traceOptions = TraceOptions.fromByte(bytes[pos + ID_SIZE]);
145     }
146     return SpanContext.create(traceId, spanId, traceOptions, TRACESTATE_DEFAULT);
147   }
148 }
149