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.contrib.http.util; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 22 import com.google.common.primitives.UnsignedInts; 23 import com.google.common.primitives.UnsignedLongs; 24 import io.opencensus.trace.SpanContext; 25 import io.opencensus.trace.SpanId; 26 import io.opencensus.trace.TraceId; 27 import io.opencensus.trace.TraceOptions; 28 import io.opencensus.trace.Tracestate; 29 import io.opencensus.trace.propagation.SpanContextParseException; 30 import io.opencensus.trace.propagation.TextFormat; 31 import java.nio.ByteBuffer; 32 import java.util.Collections; 33 import java.util.List; 34 35 /*>>> 36 import org.checkerframework.checker.nullness.qual.NonNull; 37 */ 38 39 /** 40 * Implementation of the "X-Cloud-Trace-Context" format, defined by the Google Cloud Trace. 41 * 42 * <p>The supported format is the following: 43 * 44 * <pre> 45 * <TRACE_ID>/<SPAN_ID>[;o=<TRACE_OPTIONS>] 46 * </pre> 47 * 48 * <ul> 49 * <li>TRACE_ID is a 32-character hex value; 50 * <li>SPAN_ID is a decimal representation of a 64-bit unsigned long; 51 * <li>TRACE_OPTIONS is a decimal representation of a 32-bit unsigned integer. The least 52 * significant bit defines whether a request is traced (1 - enabled, 0 - disabled). Behaviors 53 * of other bits are currently undefined. This value is optional. Although upstream service 54 * may leave this value unset to leave the sampling decision up to downstream client, this 55 * utility will always default it to 0 if absent. 56 * </ul> 57 * 58 * <p>Valid values: 59 * 60 * <ul> 61 * <li>"105445aa7843bc8bf206b120001000/123;o=1" 62 * <li>"105445aa7843bc8bf206b120001000/123" 63 * <li>"105445aa7843bc8bf206b120001000/123;o=0" 64 * </ul> 65 */ 66 final class CloudTraceFormat extends TextFormat { 67 static final String HEADER_NAME = "X-Cloud-Trace-Context"; 68 static final List<String> FIELDS = Collections.singletonList(HEADER_NAME); 69 static final char SPAN_ID_DELIMITER = '/'; 70 static final String TRACE_OPTION_DELIMITER = ";o="; 71 static final String SAMPLED = "1"; 72 static final String NOT_SAMPLED = "0"; 73 static final TraceOptions OPTIONS_SAMPLED = TraceOptions.builder().setIsSampled(true).build(); 74 static final TraceOptions OPTIONS_NOT_SAMPLED = TraceOptions.DEFAULT; 75 static final int TRACE_ID_SIZE = 2 * TraceId.SIZE; 76 static final int TRACE_OPTION_DELIMITER_SIZE = TRACE_OPTION_DELIMITER.length(); 77 static final int SPAN_ID_START_POS = TRACE_ID_SIZE + 1; 78 // 32-digit TRACE_ID + 1 digit SPAN_ID_DELIMITER + at least 1 digit SPAN_ID 79 static final int MIN_HEADER_SIZE = SPAN_ID_START_POS + 1; 80 static final int CLOUD_TRACE_IS_SAMPLED = 0x1; 81 private static final Tracestate TRACESTATE_DEFAULT = Tracestate.builder().build(); 82 83 @Override 84 public List<String> fields() { 85 return FIELDS; 86 } 87 88 @Override 89 public <C /*>>> extends @NonNull Object*/> void inject( 90 SpanContext spanContext, C carrier, Setter<C> setter) { 91 checkNotNull(spanContext, "spanContext"); 92 checkNotNull(setter, "setter"); 93 checkNotNull(carrier, "carrier"); 94 StringBuilder builder = 95 new StringBuilder() 96 .append(spanContext.getTraceId().toLowerBase16()) 97 .append(SPAN_ID_DELIMITER) 98 .append(UnsignedLongs.toString(spanIdToLong(spanContext.getSpanId()))) 99 .append(TRACE_OPTION_DELIMITER) 100 .append(spanContext.getTraceOptions().isSampled() ? SAMPLED : NOT_SAMPLED); 101 102 setter.put(carrier, HEADER_NAME, builder.toString()); 103 } 104 105 @Override 106 public <C /*>>> extends @NonNull Object*/> SpanContext extract(C carrier, Getter<C> getter) 107 throws SpanContextParseException { 108 checkNotNull(carrier, "carrier"); 109 checkNotNull(getter, "getter"); 110 try { 111 String headerStr = getter.get(carrier, HEADER_NAME); 112 if (headerStr == null || headerStr.length() < MIN_HEADER_SIZE) { 113 throw new SpanContextParseException("Missing or too short header: " + HEADER_NAME); 114 } 115 checkArgument(headerStr.charAt(TRACE_ID_SIZE) == SPAN_ID_DELIMITER, "Invalid TRACE_ID size"); 116 117 TraceId traceId = TraceId.fromLowerBase16(headerStr.subSequence(0, TRACE_ID_SIZE)); 118 int traceOptionsPos = headerStr.indexOf(TRACE_OPTION_DELIMITER, TRACE_ID_SIZE); 119 CharSequence spanIdStr = 120 headerStr.subSequence( 121 SPAN_ID_START_POS, traceOptionsPos < 0 ? headerStr.length() : traceOptionsPos); 122 SpanId spanId = longToSpanId(UnsignedLongs.parseUnsignedLong(spanIdStr.toString(), 10)); 123 TraceOptions traceOptions = OPTIONS_NOT_SAMPLED; 124 if (traceOptionsPos > 0) { 125 String traceOptionsStr = headerStr.substring(traceOptionsPos + TRACE_OPTION_DELIMITER_SIZE); 126 if ((UnsignedInts.parseUnsignedInt(traceOptionsStr, 10) & CLOUD_TRACE_IS_SAMPLED) != 0) { 127 traceOptions = OPTIONS_SAMPLED; 128 } 129 } 130 return SpanContext.create(traceId, spanId, traceOptions, TRACESTATE_DEFAULT); 131 } catch (IllegalArgumentException e) { 132 throw new SpanContextParseException("Invalid input", e); 133 } 134 } 135 136 // Using big-endian encoding. 137 private static SpanId longToSpanId(long x) { 138 ByteBuffer buffer = ByteBuffer.allocate(SpanId.SIZE); 139 buffer.putLong(x); 140 return SpanId.fromBytes(buffer.array()); 141 } 142 143 // Using big-endian encoding. 144 private static long spanIdToLong(SpanId spanId) { 145 ByteBuffer buffer = ByteBuffer.allocate(SpanId.SIZE); 146 buffer.put(spanId.getBytes()); 147 return buffer.getLong(0); 148 } 149 } 150