1 /* 2 * Copyright 2019, 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.tags.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.implcore.internal.CurrentState; 25 import io.opencensus.implcore.internal.CurrentState.State; 26 import io.opencensus.implcore.tags.TagMapImpl; 27 import io.opencensus.implcore.tags.TagValueWithMetadata; 28 import io.opencensus.tags.InternalUtils; 29 import io.opencensus.tags.Tag; 30 import io.opencensus.tags.TagContext; 31 import io.opencensus.tags.TagKey; 32 import io.opencensus.tags.TagMetadata; 33 import io.opencensus.tags.TagMetadata.TagTtl; 34 import io.opencensus.tags.TagValue; 35 import io.opencensus.tags.propagation.TagContextDeserializationException; 36 import io.opencensus.tags.propagation.TagContextSerializationException; 37 import io.opencensus.tags.propagation.TagContextTextFormat; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.Iterator; 41 import java.util.List; 42 import java.util.Map; 43 import javax.annotation.Nullable; 44 45 /*>>> 46 import org.checkerframework.checker.nullness.qual.NonNull; 47 */ 48 49 /** 50 * Implementation of the W3C correlation context propagation protocol. See <a 51 * href=https://github.com/w3c/correlation-context>w3c/correlation-context</a>. 52 */ 53 final class CorrelationContextFormat extends TagContextTextFormat { 54 55 @VisibleForTesting static final String CORRELATION_CONTEXT = "Correlation-Context"; 56 private static final List<String> FIELDS = Collections.singletonList(CORRELATION_CONTEXT); 57 58 @VisibleForTesting static final int MAX_NUMBER_OF_TAGS = 180; 59 private static final int TAG_SERIALIZED_SIZE_LIMIT = 4096; 60 private static final int TAGCONTEXT_SERIALIZED_SIZE_LIMIT = 8192; 61 private static final char TAG_KEY_VALUE_DELIMITER = '='; 62 private static final char TAG_DELIMITER = ','; 63 private static final Splitter TAG_KEY_VALUE_SPLITTER = Splitter.on(TAG_KEY_VALUE_DELIMITER); 64 private static final Splitter TAG_SPLITTER = Splitter.on(TAG_DELIMITER); 65 66 // TODO(songya): These constants are for tag metadata. Uncomment them when we decided to support 67 // encoding tag metadata. 68 private static final char TAG_PROPERTIES_DELIMITER = ';'; 69 // private static final char TAG_PROPERTIES_KEY_VALUE_DELIMITER = '='; 70 71 @VisibleForTesting 72 static final TagMetadata METADATA_UNLIMITED_PROPAGATION = 73 TagMetadata.create(TagTtl.UNLIMITED_PROPAGATION); 74 75 private final CurrentState state; 76 CorrelationContextFormat(CurrentState state)77 CorrelationContextFormat(CurrentState state) { 78 this.state = state; 79 } 80 81 @Override fields()82 public List<String> fields() { 83 return FIELDS; 84 } 85 86 @Override inject( TagContext tagContext, C carrier, Setter<C> setter)87 public <C /*>>> extends @NonNull Object*/> void inject( 88 TagContext tagContext, C carrier, Setter<C> setter) throws TagContextSerializationException { 89 checkNotNull(tagContext, "tagContext"); 90 checkNotNull(carrier, "carrier"); 91 checkNotNull(setter, "setter"); 92 if (State.DISABLED.equals(state.getInternal())) { 93 return; 94 } 95 96 try { 97 StringBuilder stringBuilder = new StringBuilder(TAGCONTEXT_SERIALIZED_SIZE_LIMIT); 98 int totalChars = 0; // Here chars are equivalent to bytes, since we're using ascii chars. 99 int totalTags = 0; 100 for (Iterator<Tag> i = InternalUtils.getTags(tagContext); i.hasNext(); ) { 101 Tag tag = i.next(); 102 if (TagTtl.NO_PROPAGATION.equals(tag.getTagMetadata().getTagTtl())) { 103 continue; 104 } 105 if (stringBuilder.length() > 0) { 106 stringBuilder.append(TAG_DELIMITER); 107 } 108 totalTags++; 109 totalChars += encodeTag(tag, stringBuilder); 110 } 111 checkArgument( 112 totalTags <= MAX_NUMBER_OF_TAGS, 113 "Number of tags in the TagContext exceeds limit " + MAX_NUMBER_OF_TAGS); 114 // Note per W3C spec, only the length of tag key and value counts towards the total length. 115 // Length of properties (a.k.a TagMetadata) does not count. 116 checkArgument( 117 totalChars <= TAGCONTEXT_SERIALIZED_SIZE_LIMIT, 118 "Size of TagContext exceeds the maximum serialized size " 119 + TAGCONTEXT_SERIALIZED_SIZE_LIMIT); 120 setter.put(carrier, CORRELATION_CONTEXT, stringBuilder.toString()); 121 } catch (IllegalArgumentException e) { 122 throw new TagContextSerializationException("Failed to serialize TagContext", e); 123 } 124 } 125 126 // Encodes the tag to the given string builder, and returns the length of encoded key-value pair. encodeTag(Tag tag, StringBuilder stringBuilder)127 private static int encodeTag(Tag tag, StringBuilder stringBuilder) { 128 String key = tag.getKey().getName(); 129 String value = tag.getValue().asString(); 130 int charsOfTag = key.length() + value.length(); 131 // This should never happen with our current constraints (<= 255 chars) on tags. 132 checkArgument( 133 charsOfTag <= TAG_SERIALIZED_SIZE_LIMIT, 134 "Serialized size of tag " + tag + " exceeds limit " + TAG_SERIALIZED_SIZE_LIMIT); 135 136 // TODO(songy23): do we want to encode TagMetadata? 137 stringBuilder.append(key).append(TAG_KEY_VALUE_DELIMITER).append(value); 138 return charsOfTag; 139 } 140 141 @Override extract(C carrier, Getter<C> getter)142 public <C /*>>> extends @NonNull Object*/> TagContext extract(C carrier, Getter<C> getter) 143 throws TagContextDeserializationException { 144 checkNotNull(carrier, "carrier"); 145 checkNotNull(getter, "getter"); 146 if (State.DISABLED.equals(state.getInternal())) { 147 return TagMapImpl.EMPTY; 148 } 149 150 @Nullable String correlationContext = getter.get(carrier, CORRELATION_CONTEXT); 151 if (correlationContext == null) { 152 throw new TagContextDeserializationException(CORRELATION_CONTEXT + " not present."); 153 } 154 try { 155 if (correlationContext.isEmpty()) { 156 return TagMapImpl.EMPTY; 157 } 158 Map<TagKey, TagValueWithMetadata> tags = new HashMap<>(); 159 List<String> stringTags = TAG_SPLITTER.splitToList(correlationContext); 160 for (String stringTag : stringTags) { 161 decodeTag(stringTag, tags); 162 } 163 return new TagMapImpl(tags); 164 } catch (IllegalArgumentException e) { 165 throw new TagContextDeserializationException("Invalid TagContext: " + correlationContext, e); 166 } 167 } 168 169 // Decodes tag key, value and metadata from the encoded string tag, then puts it into the tag map. 170 // The format of encoded string tag is name1=value1;properties1=p1;properties2=p2. decodeTag(String stringTag, Map<TagKey, TagValueWithMetadata> tags)171 private static void decodeTag(String stringTag, Map<TagKey, TagValueWithMetadata> tags) { 172 String keyWithValue; 173 int firstPropertyIndex = stringTag.indexOf(TAG_PROPERTIES_DELIMITER); 174 if (firstPropertyIndex != -1) { // Tag with properties. 175 keyWithValue = stringTag.substring(0, firstPropertyIndex); 176 // TODO(songya): support decoding tag properties. 177 } else { // Tag without properties. 178 keyWithValue = stringTag; 179 } 180 List<String> keyValuePair = TAG_KEY_VALUE_SPLITTER.splitToList(keyWithValue); 181 checkArgument(keyValuePair.size() == 2, "Malformed tag " + stringTag); 182 TagKey key = TagKey.create(keyValuePair.get(0).trim()); 183 TagValue value = TagValue.create(keyValuePair.get(1).trim()); 184 TagValueWithMetadata valueWithMetadata = 185 TagValueWithMetadata.create(value, METADATA_UNLIMITED_PROPAGATION); 186 tags.put(key, valueWithMetadata); 187 } 188 } 189