Home | History | Annotate | Download | only in util
      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  * &lt;TRACE_ID&gt;/&lt;SPAN_ID&gt;[;o=&lt;TRACE_OPTIONS&gt;]
     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