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: <version_id><version_format> 37 * <li>version_id: 1-byte representing the version id. 38 * <li>For version_id = 0: 39 * <ul> 40 * <li>version_format: <field><field> 41 * <li>field_format: <field_id><field_format> 42 * <li>Fields: 43 * <ul> 44 * <li>TraceId: (field_id = 0, len = 16, default = "0000000000000000") - 45 * 16-byte array representing the trace_id. 46 * <li>SpanId: (field_id = 1, len = 8, default = "00000000") - 8-byte array 47 * representing the span_id. 48 * <li>TraceOptions: (field_id = 2, len = 1, default = "0") - 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