W3CPropagator
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Text;
namespace System.Diagnostics
{
internal sealed class W3CPropagator : DistributedContextPropagator
{
private const int MaxBaggageEntriesToEmit = 64;
private const int MaxBaggageEncodedLength = 8192;
private const int MaxTraceStateEncodedLength = 256;
private const char Equal = '=';
private const char Percent = '%';
private const char Replacement = '�';
private static ulong[] s_invalidBaggageKeyCharsMask = new ulong[2] {
18158675351536926719,
2882303762456641537
};
private static ulong[] ValidTraceStateKeyCharsMask = new ulong[2] {
180319906955264,
576460745860972545
};
private static ulong[] s_traceStateValueMask = new ulong[2] {
16140883463719878656,
9223372036854775807
};
private static ulong[] s_baggageOctet = new ulong[2] {
17870265566011326464,
9223372036586340351
};
private static ulong[] s_traceParentMask = new ulong[2] {
287948901175001088,
541165879296
};
internal static DistributedContextPropagator Instance { get; } = new W3CPropagator();
public override IReadOnlyCollection<string> Fields { get; } = new ReadOnlyCollection<string>(new string[4] {
"traceparent",
"tracestate",
"baggage",
"Correlation-Context"
});
public override void Inject(Activity activity, object carrier, PropagatorSetterCallback setter)
{
if (activity != null && setter != null && activity.IdFormat == ActivityIdFormat.W3C) {
string id = activity.Id;
if (id != null) {
setter(carrier, "traceparent", id);
string traceStateString = activity.TraceStateString;
if (traceStateString != null && traceStateString.Length > 0)
InjectTraceState(traceStateString, carrier, setter);
InjectW3CBaggage(carrier, activity.Baggage, setter);
}
}
}
public override void ExtractTraceIdAndState(object carrier, PropagatorGetterCallback getter, out string traceId, out string traceState)
{
if (getter == null) {
traceId = null;
traceState = null;
} else {
getter(carrier, "traceparent", out traceId, out IEnumerable<string> fieldValues);
if (IsInvalidTraceParent(traceId))
traceId = null;
getter(carrier, "tracestate", out string fieldValue, out fieldValues);
traceState = ValidateTraceState(fieldValue);
}
}
public override IEnumerable<KeyValuePair<string, string>> ExtractBaggage(object carrier, PropagatorGetterCallback getter)
{
if (getter == null)
return null;
getter(carrier, "baggage", out string fieldValue, out IEnumerable<string> fieldValues);
if (fieldValue == null)
getter(carrier, "Correlation-Context", out fieldValue, out fieldValues);
TryExtractBaggage(fieldValue, out IEnumerable<KeyValuePair<string, string>> baggage);
return baggage;
}
internal static bool TryExtractBaggage(string baggageString, out IEnumerable<KeyValuePair<string, string>> baggage)
{
baggage = null;
List<KeyValuePair<string, string>> list = null;
if (string.IsNullOrEmpty(baggageString))
return true;
ReadOnlySpan<char> readOnlySpan = MemoryExtensions.AsSpan(baggageString);
do {
int num = readOnlySpan.IndexOf(',');
ReadOnlySpan<char> span = (num >= 0) ? readOnlySpan.Slice(0, num) : readOnlySpan;
int num2 = span.IndexOf('=');
if (num2 <= 0 || num2 >= span.Length - 1)
break;
ReadOnlySpan<char> keySpan = span.Slice(0, num2);
ReadOnlySpan<char> valueSpan = span.Slice(num2 + 1);
if (TryDecodeBaggageKey(keySpan, out string key) && TryDecodeBaggageValue(valueSpan, out string value)) {
if (list == null)
list = new List<KeyValuePair<string, string>>();
list.Add(new KeyValuePair<string, string>(key, value));
}
readOnlySpan = ((num >= 0) ? readOnlySpan.Slice(num + 1) : ReadOnlySpan<char>.Empty);
} while (readOnlySpan.Length > 0);
list?.Reverse();
baggage = list;
return list != null;
}
internal static string ValidateTraceState(string traceState)
{
if (string.IsNullOrEmpty(traceState))
return null;
int i;
int num2;
for (i = 0; i < traceState.Length; i += num2) {
ReadOnlySpan<char> readOnlySpan = MemoryExtensions.AsSpan(traceState, i);
int num = readOnlySpan.IndexOf(',');
ReadOnlySpan<char> span = (num >= 0) ? readOnlySpan.Slice(0, num) : readOnlySpan;
num2 = span.Length + ((num >= 0) ? 1 : 0);
if (i + num2 > 256)
break;
int num3 = span.IndexOf('=');
if (num3 <= 0 || num3 >= span.Length - 1 || IsInvalidTraceStateKey(Trim(span.Slice(0, num3))) || IsInvalidTraceStateValue(TrimSpaceOnly(span.Slice(num3 + 1))))
break;
}
if (i > 0) {
if (traceState[i - 1] == ',')
i--;
if (i > 0) {
if (i < traceState.Length)
return MemoryExtensions.AsSpan(traceState, 0, i).ToString();
return traceState;
}
}
return null;
}
internal static void InjectTraceState(string traceState, object carrier, PropagatorSetterCallback setter)
{
string text = ValidateTraceState(traceState);
if (text != null)
setter(carrier, "tracestate", text);
}
internal unsafe static void InjectW3CBaggage(object carrier, IEnumerable<KeyValuePair<string, string>> baggage, PropagatorSetterCallback setter)
{
using (IEnumerator<KeyValuePair<string, string>> enumerator = baggage.GetEnumerator()) {
if (enumerator.MoveNext()) {
Span<char> initialBuffer = new Span<char>(stackalloc byte[512], 256);
System.Text.ValueStringBuilder vsb = new System.Text.ValueStringBuilder(initialBuffer);
int num = 0;
int num2 = 0;
do {
KeyValuePair<string, string> current = enumerator.Current;
if (EncodeBaggageKey(MemoryExtensions.AsSpan(current.Key), ref vsb)) {
vsb.Append(' ');
vsb.Append('=');
vsb.Append(' ');
if (!string.IsNullOrEmpty(current.Value))
EncodeBaggageValue(MemoryExtensions.AsSpan(current.Value), ref vsb);
vsb.Append(", ");
num++;
if (vsb.Length < 8192)
num2 = vsb.Length;
}
} while (enumerator.MoveNext() && num < 64 && vsb.Length < 8192);
if (num2 - 2 > 0)
setter(carrier, "baggage", vsb.AsSpan(0, num2 - 2).ToString());
vsb.Dispose();
}
}
}
private static bool TryDecodeBaggageKey(ReadOnlySpan<char> keySpan, out string key)
{
key = null;
keySpan = Trim(keySpan);
if (keySpan.IsEmpty || IsInvalidBaggageKey(keySpan))
return false;
key = keySpan.ToString();
return true;
}
private unsafe static bool TryDecodeBaggageValue(ReadOnlySpan<char> valueSpan, out string value)
{
value = null;
valueSpan = Trim(valueSpan);
Span<char> initialBuffer = new Span<char>(stackalloc byte[256], 128);
System.Text.ValueStringBuilder valueStringBuilder = new System.Text.ValueStringBuilder(initialBuffer);
try {
for (int i = 0; i < valueSpan.Length; i++) {
char c = valueSpan[i];
if (c > '')
return false;
if (c != '%')
valueStringBuilder.Append(c);
else {
if (!TryDecodeEscapedByte(valueSpan.Slice(i), out byte value2))
return false;
if (value2 <= 127) {
valueStringBuilder.Append((char)value2);
i += 2;
} else if ((uint)(value2 - 194) <= 29) {
byte value3;
if (i + 5 >= valueSpan.Length || valueSpan[i + 3] != '%' || !TryDecodeEscapedByte(valueSpan.Slice(i + 3), out value3) || (value3 & 192) != 128) {
valueStringBuilder.Append('�');
i += 2;
} else {
valueStringBuilder.Append((char)(((value2 & 31) << 6) | (value3 & 63)));
i += 5;
}
} else if ((uint)(value2 - 224) <= 15) {
byte value4;
if (i + 8 >= valueSpan.Length || valueSpan[i + 3] != '%' || valueSpan[i + 6] != '%' || !TryDecodeEscapedByte(valueSpan.Slice(i + 3), out value4) || !TryDecodeEscapedByte(valueSpan.Slice(i + 6), out byte value5) || (value2 == 224 && value4 < 160) || (value2 == 237 && value4 >= 160)) {
valueStringBuilder.Append('�');
i += 2;
} else {
valueStringBuilder.Append((char)(((value2 & 15) << 12) | ((value4 & 63) << 6) | (value5 & 63)));
i += 8;
}
} else if ((uint)(value2 - 240) <= 4) {
byte value8;
byte value7;
byte value6;
if (i + 11 >= valueSpan.Length || valueSpan[i + 3] != '%' || valueSpan[i + 6] != '%' || valueSpan[i + 9] != '%' || !TryDecodeEscapedByte(valueSpan.Slice(i + 3), out value6) || !TryDecodeEscapedByte(valueSpan.Slice(i + 6), out value7) || !TryDecodeEscapedByte(valueSpan.Slice(i + 9), out value8) || (value6 & 192) != 128 || (value7 & 192) != 128 || (value8 & 192) != 128) {
valueStringBuilder.Append('�');
i += 2;
} else {
int num = ((value2 & 7) << 18) | ((value6 & 63) << 12) | ((value7 & 63) << 6) | (value8 & 63);
if (num < 65536 || num > 1114111) {
valueStringBuilder.Append('�');
i += 2;
} else {
num -= 65536;
valueStringBuilder.Append((char)((num >> 10) + 55296));
valueStringBuilder.Append((char)((num & 1023) + 56320));
i += 11;
}
}
} else {
valueStringBuilder.Append('�');
i += 2;
}
}
}
value = valueStringBuilder.ToString();
return true;
} finally {
valueStringBuilder.Dispose();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryDecodeEscapedByte(ReadOnlySpan<char> span, out byte value)
{
if (span.Length < 3 || !TryDecodeHexDigit(span[1], out byte value2) || !TryDecodeHexDigit(span[2], out byte value3)) {
value = 0;
return false;
}
value = (byte)((value2 << 4) + value3);
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryDecodeHexDigit(char c, out byte value)
{
value = (byte)System.HexConverter.FromChar(c);
return value != 255;
}
private static bool IsInvalidBaggageKey(ReadOnlySpan<char> span)
{
ReadOnlySpan<char> readOnlySpan = span;
foreach (char c in readOnlySpan) {
if (c >= '' || ((long)s_invalidBaggageKeyCharsMask[(int)c >> 6] & (1 << (int)c)) != 0)
return true;
}
return false;
}
private static bool IsInvalidTraceStateKey(ReadOnlySpan<char> key)
{
if (key.IsEmpty || key[0] < 'a' || key[0] > 'z')
return true;
ReadOnlySpan<char> readOnlySpan = key;
foreach (char c in readOnlySpan) {
if (c >= '' || ((long)ValidTraceStateKeyCharsMask[(int)c >> 6] & (1 << (int)c)) == 0)
return true;
}
return false;
}
private static bool IsInvalidTraceStateValue(ReadOnlySpan<char> value)
{
if (value.IsEmpty)
return true;
ReadOnlySpan<char> readOnlySpan = value;
foreach (char c in readOnlySpan) {
if (c >= '' || ((long)s_traceStateValueMask[(int)c >> 6] & (1 << (int)c)) == 0)
return true;
}
return false;
}
internal static bool EncodeBaggageKey(ReadOnlySpan<char> key, ref System.Text.ValueStringBuilder vsb)
{
key = Trim(key);
if (key.IsEmpty || IsInvalidBaggageKey(key))
return false;
vsb.Append(key);
return true;
}
internal static void EncodeBaggageValue(ReadOnlySpan<char> value, ref System.Text.ValueStringBuilder vsb)
{
value = Trim(value);
for (int i = 0; i < value.Length; i++) {
char c = value[i];
if (!NeedToEscapeBaggageValueCharacter(c))
vsb.Append(c);
else if ((uint)c <= 127) {
EmitEscapedByte((byte)c, ref vsb);
} else if ((uint)c <= 2047) {
EmitEscapedByte((byte)((uint)(c + 12288) >> 6), ref vsb);
EmitEscapedByte((byte)((c & 63) + 128), ref vsb);
} else if (char.IsSurrogate(c)) {
if (i < value.Length - 1 && char.IsSurrogatePair(c, value[i + 1])) {
int num = char.ConvertToUtf32(c, value[i + 1]);
EmitEscapedByte((byte)((uint)(num + 62914560) >> 18), ref vsb);
EmitEscapedByte((byte)(((uint)(num & 258048) >> 12) + 128), ref vsb);
EmitEscapedByte((byte)(((uint)(num & 4032) >> 6) + 128), ref vsb);
EmitEscapedByte((byte)((num & 63) + 128), ref vsb);
i++;
} else {
EmitEscapedByte(239, ref vsb);
EmitEscapedByte(191, ref vsb);
EmitEscapedByte(189, ref vsb);
}
} else {
EmitEscapedByte((byte)(c + 917504 >> 12), ref vsb);
EmitEscapedByte((byte)(((uint)(c & 4032) >> 6) + 128), ref vsb);
EmitEscapedByte((byte)((c & 63) + 128), ref vsb);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool NeedToEscapeBaggageValueCharacter(char c)
{
if (c >= '')
return true;
return ((long)s_baggageOctet[(int)c >> 6] & (1 << (int)c)) == 0;
}
private static bool IsInvalidTraceParent(string traceParent)
{
if (string.IsNullOrEmpty(traceParent) || traceParent.Length != 55)
return true;
if (traceParent[0] == 'f' || traceParent[1] == 'f' || IsInvalidTraceParentCharacter(traceParent[0]) || IsInvalidTraceParentCharacter(traceParent[1]))
return true;
if (traceParent[2] != '-' || traceParent[35] != '-' || traceParent[52] != '-')
return true;
bool flag = true;
for (int i = 3; i < 35; i++) {
if (IsInvalidTraceParentCharacter(traceParent[i]))
return true;
flag &= (traceParent[i] == '0');
}
if (flag)
return true;
flag = true;
for (int j = 36; j < 52; j++) {
if (IsInvalidTraceParentCharacter(traceParent[j]))
return true;
flag &= (traceParent[j] == '0');
}
if (flag)
return true;
if (IsInvalidTraceParentCharacter(traceParent[53]) || IsInvalidTraceParentCharacter(traceParent[54]))
return true;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsInvalidTraceParentCharacter(char c)
{
if (c >= '')
return true;
return ((long)s_traceParentMask[(int)c >> 6] & (1 << (int)c)) == 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EmitEscapedByte(byte b, ref System.Text.ValueStringBuilder vsb)
{
vsb.Append('%');
vsb.Append("0123456789ABCDEF"[(b >> 4) & 15]);
vsb.Append("0123456789ABCDEF"[b & 15]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<char> TrimSpaceOnly(ReadOnlySpan<char> span)
{
return span.Trim(' ');
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<char> Trim(ReadOnlySpan<char> span)
{
return span.Trim(MemoryExtensions.AsSpan(" \t"));
}
}
}