1 /* 2 * Copyright 2018, 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.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 22 import com.google.common.annotations.VisibleForTesting; 23 import com.google.common.base.Splitter; 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.util.Arrays; 32 import java.util.Collections; 33 import java.util.List; 34 import java.util.regex.Pattern; 35 36 /*>>> 37 import org.checkerframework.checker.nullness.qual.NonNull; 38 */ 39 40 /** 41 * Implementation of the TraceContext propagation protocol. See <a 42 * href=https://github.com/w3c/distributed-tracing>w3c/distributed-tracing</a>. 43 */ 44 public class TraceContextFormat extends TextFormat { 45 private static final Tracestate TRACESTATE_DEFAULT = Tracestate.builder().build(); 46 @VisibleForTesting static final String TRACEPARENT = "traceparent"; 47 @VisibleForTesting static final String TRACESTATE = "tracestate"; 48 private static final List<String> FIELDS = 49 Collections.unmodifiableList(Arrays.asList(TRACEPARENT, TRACESTATE)); 50 51 private static final String VERSION = "00"; 52 private static final int VERSION_SIZE = 2; 53 private static final char TRACEPARENT_DELIMITER = '-'; 54 private static final int TRACEPARENT_DELIMITER_SIZE = 1; 55 private static final int TRACE_ID_HEX_SIZE = 2 * TraceId.SIZE; 56 private static final int SPAN_ID_HEX_SIZE = 2 * SpanId.SIZE; 57 private static final int TRACE_OPTION_HEX_SIZE = 2 * TraceOptions.SIZE; 58 private static final int TRACE_ID_OFFSET = VERSION_SIZE + TRACEPARENT_DELIMITER_SIZE; 59 private static final int SPAN_ID_OFFSET = 60 TRACE_ID_OFFSET + TRACE_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE; 61 private static final int TRACE_OPTION_OFFSET = 62 SPAN_ID_OFFSET + SPAN_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE; 63 private static final int TRACEPARENT_HEADER_SIZE = TRACE_OPTION_OFFSET + TRACE_OPTION_HEX_SIZE; 64 private static final int TRACESTATE_MAX_SIZE = 512; 65 private static final int TRACESTATE_MAX_MEMBERS = 32; 66 private static final char TRACESTATE_KEY_VALUE_DELIMITER = '='; 67 private static final char TRACESTATE_ENTRY_DELIMITER = ','; 68 private static final Splitter TRACESTATE_ENTRY_DELIMITER_SPLITTER = 69 Splitter.on(Pattern.compile("[ \t]*" + TRACESTATE_ENTRY_DELIMITER + "[ \t]*")); 70 71 @Override fields()72 public List<String> fields() { 73 return FIELDS; 74 } 75 76 @Override inject( SpanContext spanContext, C carrier, Setter<C> setter)77 public <C /*>>> extends @NonNull Object*/> void inject( 78 SpanContext spanContext, C carrier, Setter<C> setter) { 79 checkNotNull(spanContext, "spanContext"); 80 checkNotNull(setter, "setter"); 81 checkNotNull(carrier, "carrier"); 82 char[] chars = new char[TRACEPARENT_HEADER_SIZE]; 83 chars[0] = VERSION.charAt(0); 84 chars[1] = VERSION.charAt(1); 85 chars[2] = TRACEPARENT_DELIMITER; 86 spanContext.getTraceId().copyLowerBase16To(chars, TRACE_ID_OFFSET); 87 chars[SPAN_ID_OFFSET - 1] = TRACEPARENT_DELIMITER; 88 spanContext.getSpanId().copyLowerBase16To(chars, SPAN_ID_OFFSET); 89 chars[TRACE_OPTION_OFFSET - 1] = TRACEPARENT_DELIMITER; 90 spanContext.getTraceOptions().copyLowerBase16To(chars, TRACE_OPTION_OFFSET); 91 setter.put(carrier, TRACEPARENT, new String(chars)); 92 List<Tracestate.Entry> entries = spanContext.getTracestate().getEntries(); 93 if (entries.isEmpty()) { 94 // No need to add an empty "tracestate" header. 95 return; 96 } 97 StringBuilder stringBuilder = new StringBuilder(TRACESTATE_MAX_SIZE); 98 for (Tracestate.Entry entry : entries) { 99 if (stringBuilder.length() != 0) { 100 stringBuilder.append(TRACESTATE_ENTRY_DELIMITER); 101 } 102 stringBuilder 103 .append(entry.getKey()) 104 .append(TRACESTATE_KEY_VALUE_DELIMITER) 105 .append(entry.getValue()); 106 } 107 setter.put(carrier, TRACESTATE, stringBuilder.toString()); 108 } 109 110 @Override extract(C carrier, Getter<C> getter)111 public <C /*>>> extends @NonNull Object*/> SpanContext extract(C carrier, Getter<C> getter) 112 throws SpanContextParseException { 113 checkNotNull(carrier, "carrier"); 114 checkNotNull(getter, "getter"); 115 TraceId traceId; 116 SpanId spanId; 117 TraceOptions traceOptions; 118 String traceparent = getter.get(carrier, TRACEPARENT); 119 if (traceparent == null) { 120 throw new SpanContextParseException("Traceparent not present"); 121 } 122 try { 123 // TODO(bdrutu): Do we need to verify that version is hex and that for the version 124 // the length is the expected one? 125 checkArgument( 126 traceparent.charAt(TRACE_OPTION_OFFSET - 1) == TRACEPARENT_DELIMITER 127 && (traceparent.length() == TRACEPARENT_HEADER_SIZE 128 || (traceparent.length() > TRACEPARENT_HEADER_SIZE 129 && traceparent.charAt(TRACEPARENT_HEADER_SIZE) == TRACEPARENT_DELIMITER)) 130 && traceparent.charAt(SPAN_ID_OFFSET - 1) == TRACEPARENT_DELIMITER 131 && traceparent.charAt(TRACE_OPTION_OFFSET - 1) == TRACEPARENT_DELIMITER, 132 "Missing or malformed TRACEPARENT."); 133 134 traceId = TraceId.fromLowerBase16(traceparent, TRACE_ID_OFFSET); 135 spanId = SpanId.fromLowerBase16(traceparent, SPAN_ID_OFFSET); 136 traceOptions = TraceOptions.fromLowerBase16(traceparent, TRACE_OPTION_OFFSET); 137 } catch (IllegalArgumentException e) { 138 throw new SpanContextParseException("Invalid traceparent: " + traceparent, e); 139 } 140 141 String tracestate = getter.get(carrier, TRACESTATE); 142 try { 143 if (tracestate == null || tracestate.isEmpty()) { 144 return SpanContext.create(traceId, spanId, traceOptions, TRACESTATE_DEFAULT); 145 } 146 Tracestate.Builder tracestateBuilder = Tracestate.builder(); 147 List<String> listMembers = TRACESTATE_ENTRY_DELIMITER_SPLITTER.splitToList(tracestate); 148 checkArgument( 149 listMembers.size() <= TRACESTATE_MAX_MEMBERS, "Tracestate has too many elements."); 150 // Iterate in reverse order because when call builder set the elements is added in the 151 // front of the list. 152 for (int i = listMembers.size() - 1; i >= 0; i--) { 153 String listMember = listMembers.get(i); 154 int index = listMember.indexOf(TRACESTATE_KEY_VALUE_DELIMITER); 155 checkArgument(index != -1, "Invalid tracestate list-member format."); 156 tracestateBuilder.set( 157 listMember.substring(0, index), listMember.substring(index + 1, listMember.length())); 158 } 159 return SpanContext.create(traceId, spanId, traceOptions, tracestateBuilder.build()); 160 } catch (IllegalArgumentException e) { 161 throw new SpanContextParseException("Invalid tracestate: " + tracestate, e); 162 } 163 } 164 } 165