Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,172 +17,24 @@
*/
package org.apache.skywalking.apm.agent.core.base64;

import java.io.UnsupportedEncodingException;
import org.apache.skywalking.apm.agent.core.logging.api.ILog;
import org.apache.skywalking.apm.agent.core.logging.api.LogManager;
import java.nio.charset.StandardCharsets;

/**
* Copied from {@code zipkin.internal.Base64}, adapted from {@code okio.Base64} as JRE 6 doesn't have a base64Url
* encoder.
*
* @author okio cited the original author as Alexander Y. Kleymenov
* A wrapper of {@link java.util.Base64} with convenient conversion methods between {@code byte[]} and {@code String}
*/
public final class Base64 {
private static final ILog logger = LogManager.getLogger(Base64.class);
private static final java.util.Base64.Decoder DECODER = java.util.Base64.getDecoder();
private static final java.util.Base64.Encoder ENCODER = java.util.Base64.getEncoder();

private Base64() {
}

public static String decode2UTFString(String in) {
try {
return new String(decode(in), "utf-8");
} catch (UnsupportedEncodingException e) {
logger.error(e, "Can't decode BASE64 text {}", in);
return "";
}
}

public static byte[] decode(String in) {
// Ignore trailing '=' padding and whitespace from the input.
int limit = in.length();
for (; limit > 0; limit--) {
char c = in.charAt(limit - 1);
if (c != '=' && c != '\n' && c != '\r' && c != ' ' && c != '\t') {
break;
}
}

// If the input includes whitespace, this output array will be longer than necessary.
byte[] out = new byte[(int)(limit * 6L / 8L)];
int outCount = 0;
int inCount = 0;

int word = 0;
for (int pos = 0; pos < limit; pos++) {
char c = in.charAt(pos);

int bits;
if (c >= 'A' && c <= 'Z') {
// char ASCII value
// A 65 0
// Z 90 25 (ASCII - 65)
bits = c - 65;
} else if (c >= 'a' && c <= 'z') {
// char ASCII value
// a 97 26
// z 122 51 (ASCII - 71)
bits = c - 71;
} else if (c >= '0' && c <= '9') {
// char ASCII value
// 0 48 52
// 9 57 61 (ASCII + 4)
bits = c + 4;
} else if (c == '+' || c == '-') {
bits = 62;
} else if (c == '/' || c == '_') {
bits = 63;
} else if (c == '\n' || c == '\r' || c == ' ' || c == '\t') {
continue;
} else {
return null;
}

// Append this char's 6 bits to the word.
word = (word << 6) | (byte)bits;

// For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes.
inCount++;
if (inCount % 4 == 0) {
out[outCount++] = (byte)(word >> 16);
out[outCount++] = (byte)(word >> 8);
out[outCount++] = (byte)word;
}
}

int lastWordChars = inCount % 4;
if (lastWordChars == 1) {
// We read 1 char followed by "===". But 6 bits is a truncated byte! Fail.
return null;
} else if (lastWordChars == 2) {
// We read 2 chars followed by "==". Emit 1 byte with 8 of those 12 bits.
word = word << 12;
out[outCount++] = (byte)(word >> 16);
} else if (lastWordChars == 3) {
// We read 3 chars, followed by "=". Emit 2 bytes for 16 of those 18 bits.
word = word << 6;
out[outCount++] = (byte)(word >> 16);
out[outCount++] = (byte)(word >> 8);
}

// If we sized our out array perfectly, we're done.
if (outCount == out.length)
return out;

// Copy the decoded bytes to a new, right-sized array.
byte[] prefix = new byte[outCount];
System.arraycopy(out, 0, prefix, 0, outCount);
return prefix;
return new String(DECODER.decode(in), StandardCharsets.UTF_8);
}

private static final byte[] MAP = new byte[] {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '+', '/'
};

private static final byte[] URL_MAP = new byte[] {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '-', '_'
};

public static String encode(String text) {
try {
return encode(text.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
logger.error(e, "Can't encode {} in BASE64", text);
return "";
}
return ENCODER.encodeToString(text.getBytes(StandardCharsets.UTF_8));
}

public static String encode(byte[] in) {
return encode(in, MAP);
}

public static String encodeUrl(byte[] in) {
return encode(in, URL_MAP);
}

private static String encode(byte[] in, byte[] map) {
int length = (in.length + 2) / 3 * 4;
byte[] out = new byte[length];
int index = 0, end = in.length - in.length % 3;
for (int i = 0; i < end; i += 3) {
out[index++] = map[(in[i] & 0xff) >> 2];
out[index++] = map[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)];
out[index++] = map[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)];
out[index++] = map[in[i + 2] & 0x3f];
}
switch (in.length % 3) {
case 1:
out[index++] = map[(in[end] & 0xff) >> 2];
out[index++] = map[(in[end] & 0x03) << 4];
out[index++] = '=';
out[index++] = '=';
break;
case 2:
out[index++] = map[(in[end] & 0xff) >> 2];
out[index++] = map[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)];
out[index++] = map[(in[end + 1] & 0x0f) << 2];
out[index++] = '=';
break;
}
try {
return new String(out, "US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.apache.skywalking.apm.agent.core.context.ids.DistributedTraceId;
import org.apache.skywalking.apm.agent.core.context.ids.ID;
import org.apache.skywalking.apm.agent.core.context.ids.PropagatedTraceId;
import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
import org.apache.skywalking.apm.agent.core.dictionary.DictionaryUtil;
import org.apache.skywalking.apm.util.StringUtil;

Expand All @@ -35,9 +34,6 @@
* Created by wusheng on 2017/2/17.
*/
public class ContextCarrier implements Serializable {
/**
* {@link TraceSegment#traceSegmentId}
*/
private ID traceSegmentId;

/**
Expand Down Expand Up @@ -97,9 +93,8 @@ String serialize(HeaderVersion version) {
Base64.encode(this.getPeerHost()),
Base64.encode(this.getEntryEndpointName()),
Base64.encode(this.getParentEndpointName()));
} else {
return "";
}
return "";
}

/**
Expand All @@ -108,53 +103,36 @@ String serialize(HeaderVersion version) {
* @param text carries {@link #traceSegmentId} and {@link #spanId}, with '|' split.
*/
ContextCarrier deserialize(String text, HeaderVersion version) {
if (text != null) {
// if this carrier is initialized by v1 or v2, don't do deserialize again for performance.
if (this.isValid(HeaderVersion.v1) || this.isValid(HeaderVersion.v2)) {
return this;
}
if (HeaderVersion.v1.equals(version)) {
String[] parts = text.split("\\|", 8);
if (parts.length == 8) {
try {
this.traceSegmentId = new ID(parts[0]);
this.spanId = Integer.parseInt(parts[1]);
this.parentServiceInstanceId = Integer.parseInt(parts[2]);
this.entryServiceInstanceId = Integer.parseInt(parts[3]);
this.peerHost = parts[4];
this.entryEndpointName = parts[5];
this.parentEndpointName = parts[6];
this.primaryDistributedTraceId = new PropagatedTraceId(parts[7]);
} catch (NumberFormatException e) {

}
}
} else if (HeaderVersion.v2.equals(version)) {
String[] parts = text.split("\\-", 9);
if (parts.length == 9) {
try {
// parts[0] is sample flag, always trace if header exists.
this.primaryDistributedTraceId = new PropagatedTraceId(Base64.decode2UTFString(parts[1]));
this.traceSegmentId = new ID(Base64.decode2UTFString(parts[2]));
this.spanId = Integer.parseInt(parts[3]);
this.parentServiceInstanceId = Integer.parseInt(parts[4]);
this.entryServiceInstanceId = Integer.parseInt(parts[5]);
this.peerHost = Base64.decode2UTFString(parts[6]);
this.entryEndpointName = Base64.decode2UTFString(parts[7]);
this.parentEndpointName = Base64.decode2UTFString(parts[8]);
} catch (NumberFormatException e) {

}
if (text == null) {
return this;
}
// if this carrier is initialized by v2, don't do deserialize again for performance.
if (this.isValid(HeaderVersion.v2)) {
return this;
}
if (HeaderVersion.v2 == version) {
String[] parts = text.split("-", 9);
if (parts.length == 9) {
try {
// parts[0] is sample flag, always trace if header exists.
this.primaryDistributedTraceId = new PropagatedTraceId(Base64.decode2UTFString(parts[1]));
this.traceSegmentId = new ID(Base64.decode2UTFString(parts[2]));
this.spanId = Integer.parseInt(parts[3]);
this.parentServiceInstanceId = Integer.parseInt(parts[4]);
this.entryServiceInstanceId = Integer.parseInt(parts[5]);
this.peerHost = Base64.decode2UTFString(parts[6]);
this.entryEndpointName = Base64.decode2UTFString(parts[7]);
this.parentEndpointName = Base64.decode2UTFString(parts[8]);
} catch (NumberFormatException ignored) {

Comment thread
kezhenxu94 marked this conversation as resolved.
Outdated
}
} else {
throw new IllegalArgumentException("Unimplemented header version." + version);
}
}
return this;
}

public boolean isValid() {
return isValid(HeaderVersion.v2) || isValid(HeaderVersion.v1);
return isValid(HeaderVersion.v2);
}

/**
Expand All @@ -163,27 +141,16 @@ public boolean isValid() {
* @return true for unbroken {@link ContextCarrier} or no-initialized. Otherwise, false;
*/
boolean isValid(HeaderVersion version) {
if (HeaderVersion.v1.equals(version)) {
return traceSegmentId != null
&& traceSegmentId.isValid()
&& getSpanId() > -1
&& parentServiceInstanceId != DictionaryUtil.nullValue()
&& entryServiceInstanceId != DictionaryUtil.nullValue()
&& !StringUtil.isEmpty(peerHost)
&& !StringUtil.isEmpty(entryEndpointName)
&& !StringUtil.isEmpty(parentEndpointName)
&& primaryDistributedTraceId != null;
} else if (HeaderVersion.v2.equals(version)) {
if (HeaderVersion.v2 == version) {
return traceSegmentId != null
&& traceSegmentId.isValid()
&& getSpanId() > -1
&& parentServiceInstanceId != DictionaryUtil.nullValue()
&& entryServiceInstanceId != DictionaryUtil.nullValue()
&& !StringUtil.isEmpty(peerHost)
&& primaryDistributedTraceId != null;
} else {
throw new IllegalArgumentException("Unimplemented header version." + version);
}
return false;
}

public String getEntryEndpointName() {
Expand Down Expand Up @@ -267,6 +234,6 @@ public void setEntryServiceInstanceId(int entryServiceInstanceId) {
}

public enum HeaderVersion {
v1, v2
v2
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.List;

import java.util.Objects;
import org.apache.skywalking.apm.agent.core.conf.RemoteDownstreamConfig;
import org.apache.skywalking.apm.agent.core.context.tag.Tags;
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
Expand Down Expand Up @@ -80,7 +81,7 @@ public static void afterClass() {

@Test
public void createSpanWithInvalidateContextCarrier() {
ContextCarrier contextCarrier = new ContextCarrier().deserialize("#AQA=#AQA=4WcWe0tQNQA=|1|#127.0.0.1:8080|#/testEntrySpan|#/testEntrySpan|#AQA=#AQA=Et0We0tQNQA=", ContextCarrier.HeaderVersion.v1);
ContextCarrier contextCarrier = new ContextCarrier();

AbstractSpan firstEntrySpan = ContextManager.createEntrySpan("/testEntrySpan", contextCarrier);
firstEntrySpan.setComponent(ComponentsDefine.TOMCAT);
Expand All @@ -94,7 +95,7 @@ public void createSpanWithInvalidateContextCarrier() {
assertNull(actualSegment.getRefs());

List<AbstractTracingSpan> spanList = SegmentHelper.getSpan(actualSegment);
assertThat(spanList.size(), is(1));
assertThat(Objects.requireNonNull(spanList).size(), is(1));

AbstractTracingSpan actualEntrySpan = spanList.get(0);
assertThat(actualEntrySpan.getOperationName(), is("/testEntrySpan"));
Expand All @@ -104,7 +105,7 @@ public void createSpanWithInvalidateContextCarrier() {

@Test
public void createMultipleEntrySpan() {
ContextCarrier contextCarrier = new ContextCarrier().deserialize("1.2343.234234234|1|1|1|#127.0.0.1:8080|#/portal/|#/testEntrySpan|1.2343.234234234", ContextCarrier.HeaderVersion.v1);
ContextCarrier contextCarrier = new ContextCarrier().deserialize("1-MS4yMzQzLjIzNDIzNDIzNA==-MS4yMzQzLjIzNDIzNDIzNA==-1-1-1-IzEyNy4wLjAuMTo4MDgw-Iy9wb3J0YWwv-Iy90ZXN0RW50cnlTcGFu", ContextCarrier.HeaderVersion.v2);
assertTrue(contextCarrier.isValid());

AbstractSpan firstEntrySpan = ContextManager.createEntrySpan("/testFirstEntry", contextCarrier);
Expand Down Expand Up @@ -195,7 +196,7 @@ public void createMultipleExitSpan() {
assertNull(actualSegment.getRefs());

List<AbstractTracingSpan> spanList = SegmentHelper.getSpan(actualSegment);
assertThat(spanList.size(), is(2));
assertThat(Objects.requireNonNull(spanList).size(), is(2));

AbstractTracingSpan actualFirstExitSpan = spanList.get(0);
assertThat(actualFirstExitSpan.getOperationName(), is("/testFirstExit"));
Expand Down Expand Up @@ -227,7 +228,7 @@ public void tearDown() throws Exception {

@Test
public void testTransform() throws InvalidProtocolBufferException {
ContextCarrier contextCarrier = new ContextCarrier().deserialize("1.234.1983829|3|1|1|#127.0.0.1:8080|#/portal/|#/testEntrySpan|1.2343.234234234", ContextCarrier.HeaderVersion.v1);
ContextCarrier contextCarrier = new ContextCarrier().deserialize("1-MS4yMzQzLjIzNDIzNDIzNA==-MS4yMzQuMTk4MzgyOQ==-3-1-1-IzEyNy4wLjAuMTo4MDgw-Iy9wb3J0YWwv-Iy90ZXN0RW50cnlTcGFu", ContextCarrier.HeaderVersion.v2);
assertTrue(contextCarrier.isValid());

AbstractSpan firstEntrySpan = ContextManager.createEntrySpan("/testFirstEntry", contextCarrier);
Expand Down