File: System\Diagnostics\W3CPropagator.cs
Web Access
Project: src\src\libraries\System.Diagnostics.DiagnosticSource\src\System.Diagnostics.DiagnosticSource.csproj (System.Diagnostics.DiagnosticSource)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Text;
 
namespace System.Diagnostics
{
    internal sealed class W3CPropagator : DistributedContextPropagator
    {
        internal static DistributedContextPropagator Instance { get; } = new W3CPropagator();
 
        private const int MaxBaggageEntriesToEmit = 64;     // Suggested by W3C specs
        private const int MaxBaggageEncodedLength = 8192;   // Suggested by W3C specs
        private const int MaxTraceStateEncodedLength = 256; // Suggested by W3C specs
 
        private const char Equal = '=';
        private const char Percent = '%';
        private const char Replacement = '\uFFFD'; // �
 
        public override IReadOnlyCollection<string> Fields { get; } = new ReadOnlyCollection<string>(new[] { TraceParent, TraceState, Baggage, CorrelationContext });
 
        public override void Inject(Activity? activity, object? carrier, PropagatorSetterCallback? setter)
        {
            if (activity is null || setter is null || activity.IdFormat != ActivityIdFormat.W3C)
            {
                return;
            }
 
            string? id = activity.Id;
            if (id is null)
            {
                return;
            }
 
            setter(carrier, TraceParent, id);
            if (activity.TraceStateString is { Length: > 0 } traceState)
            {
                InjectTraceState(traceState, carrier, setter);
            }
 
            InjectW3CBaggage(carrier, activity.Baggage, setter);
        }
 
        public override void ExtractTraceIdAndState(object? carrier, PropagatorGetterCallback? getter, out string? traceId, out string? traceState)
        {
            if (getter is null)
            {
                traceId = null;
                traceState = null;
                return;
            }
 
            getter(carrier, TraceParent, out traceId, out _);
            if (IsInvalidTraceParent(traceId))
            {
                traceId = null;
            }
 
            getter(carrier, TraceState, out string? traceStateValue, out _);
            traceState = ValidateTraceState(traceStateValue);
        }
 
        public override IEnumerable<KeyValuePair<string, string?>>? ExtractBaggage(object? carrier, PropagatorGetterCallback? getter)
        {
            if (getter is null)
            {
                return null;
            }
 
            getter(carrier, Baggage, out string? theBaggage, out _);
            if (theBaggage is null)
            {
                getter(carrier, CorrelationContext, out theBaggage, out _);
            }
 
            TryExtractBaggage(theBaggage, 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?>>? baggageList = null;
 
            if (string.IsNullOrEmpty(baggageString))
            {
                return true;
            }
 
            ReadOnlySpan<char> baggageSpan = baggageString;
 
            do
            {
                int entrySeparator = baggageSpan.IndexOf(Comma);
                ReadOnlySpan<char> currentEntry = entrySeparator >= 0 ? baggageSpan.Slice(0, entrySeparator) : baggageSpan;
 
                int keyValueSeparator = currentEntry.IndexOf(Equal);
                if (keyValueSeparator <= 0 || keyValueSeparator >= currentEntry.Length - 1)
                {
                    break; // invalid format
                }
 
                ReadOnlySpan<char> keySpan = currentEntry.Slice(0, keyValueSeparator);
                ReadOnlySpan<char> valueSpan = currentEntry.Slice(keyValueSeparator + 1);
 
                if (TryDecodeBaggageKey(keySpan, out string? key) && TryDecodeBaggageValue(valueSpan, out string value))
                {
                    baggageList ??= new List<KeyValuePair<string, string?>>();
                    baggageList.Add(new KeyValuePair<string, string?>(key, value));
                }
 
                baggageSpan = entrySeparator >= 0 ? baggageSpan.Slice(entrySeparator + 1) : ReadOnlySpan<char>.Empty;
            } while (baggageSpan.Length > 0);
 
            // reverse order for asp.net compatibility.
            baggageList?.Reverse();
 
            baggage = baggageList;
            return baggageList != null;
        }
 
        // list  = list-member 0*31( OWS "," OWS list-member )
        // list-member = (key "=" value) / OWS
        //
        // key = ( lcalpha / DIGIT ) 0*255 ( keychar )
        // keychar    = lcalpha / DIGIT / "_" / "-"/ "*" / "/" / "@"
        // lcalpha    = %x61-7A ; a-z
        //
        // value    = 0*255(chr) nblk-chr
        // nblk-chr = %x21-2B / %x2D-3C / %x3E-7E
        // chr      = %x20 / nblk-chr
 
        internal static string? ValidateTraceState(string? traceState)
        {
            if (string.IsNullOrEmpty(traceState))
            {
                return null; // invalid format
            }
 
            int processed = 0;
 
            while (processed < traceState.Length)
            {
                ReadOnlySpan<char> traceStateSpan = traceState.AsSpan(processed);
                int commaIndex = traceStateSpan.IndexOf(Comma);
                ReadOnlySpan<char> entry = commaIndex >= 0 ? traceStateSpan.Slice(0, commaIndex) : traceStateSpan;
                int delta = entry.Length + (commaIndex >= 0 ? 1 : 0); // +1 for the comma
 
                if (processed + delta > MaxTraceStateEncodedLength)
                {
                    break; // entry exceeds max length
                }
 
                int equalIndex = entry.IndexOf(Equal);
                if (equalIndex <= 0 || equalIndex >= entry.Length - 1)
                {
                    break; // invalid format
                }
 
                if (IsInvalidTraceStateKey(Trim(entry.Slice(0, equalIndex))) || IsInvalidTraceStateValue(TrimSpaceOnly(entry.Slice(equalIndex + 1))))
                {
                    break; // entry exceeds max length or invalid key/value, skip the whole trace state entries.
                }
 
                processed += delta;
            }
 
            if (processed > 0)
            {
                if (traceState[processed - 1] == Comma)
                {
                    processed--; // remove the last comma
                }
 
                if (processed > 0)
                {
                    return processed >= traceState.Length ? traceState : traceState.AsSpan(0, processed).ToString();
                }
            }
 
            return null;
        }
 
        internal static void InjectTraceState(string traceState, object? carrier, PropagatorSetterCallback setter)
        {
            Debug.Assert(traceState != null, "traceState cannot be null");
            Debug.Assert(setter != null, "setter cannot be null");
 
            string? traceStateValue = ValidateTraceState(traceState);
            if (traceStateValue is not null)
            {
                setter(carrier, TraceState, traceStateValue);
            }
        }
 
        internal static void InjectW3CBaggage(object? carrier, IEnumerable<KeyValuePair<string, string?>> baggage, PropagatorSetterCallback setter)
        {
            using (IEnumerator<KeyValuePair<string, string?>> e = baggage.GetEnumerator())
            {
                if (e.MoveNext())
                {
                    ValueStringBuilder encodedBaggage = new ValueStringBuilder(stackalloc char[256]);
 
                    int entriesCount = 0;
                    int lastGoodLength = 0;
 
                    do
                    {
                        KeyValuePair<string, string?> item = e.Current;
 
                        if (EncodeBaggageKey(item.Key, ref encodedBaggage))
                        {
                            encodedBaggage.Append(Space);
                            encodedBaggage.Append(Equal);
                            encodedBaggage.Append(Space);
                            if (!string.IsNullOrEmpty(item.Value))
                            {
                                EncodeBaggageValue(item.Value, ref encodedBaggage);
                            }
                            encodedBaggage.Append(CommaWithSpace);
 
                            entriesCount++;
 
                            if (encodedBaggage.Length < MaxBaggageEncodedLength)
                            {
                                lastGoodLength = encodedBaggage.Length;
                            }
                        }
                    } while (e.MoveNext() && entriesCount < MaxBaggageEntriesToEmit && encodedBaggage.Length < MaxBaggageEncodedLength);
 
                    if (lastGoodLength - 2 > 0)
                    {
                        setter(carrier, Baggage, encodedBaggage.AsSpan(0, lastGoodLength - 2).ToString());
                    }
 
                    encodedBaggage.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 static bool TryDecodeBaggageValue(ReadOnlySpan<char> valueSpan, out string value)
        {
            value = null!;
            valueSpan = Trim(valueSpan);
 
            using ValueStringBuilder vsb = new ValueStringBuilder(stackalloc char[128]);
 
            for (int i = 0; i < valueSpan.Length; i++)
            {
                char c = valueSpan[i];
                if (c > 0x7F)
                {
                    return false; // we expect only ascii characters
                }
 
                if (c != Percent) // none escaped
                {
                    vsb.Append(c);
                    continue;
                }
 
                if (!TryDecodeEscapedByte(valueSpan.Slice(i), out byte b0))
                {
                    return false;
                }
 
                if (b0 <= 0x7F)
                {
                    vsb.Append((char)b0);
                    i += 2;
                    continue;
                }
 
                // 2-byte sequence: 110xxxxx 10xxxxxx
                if ((uint)(b0 - 0xC2) <=  (0xDF - 0xC2))
                {
                    if (i + 5 >= valueSpan.Length || valueSpan[i + 3] != Percent || !TryDecodeEscapedByte(valueSpan.Slice(i + 3), out byte b1) || (b1 & 0xC0) != 0x80)
                    {
                        // Malformed utf-8 sequence. emit U+FFFD and continue
                        vsb.Append(Replacement);
                        i += 2;
                        continue;
                    }
 
                    vsb.Append((char)(((b0 & 0x1F) << 6) | (b1 & 0x3F)));
                    i += 5;
                    continue;
                }
 
                // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx
                if ((uint)(b0 - 0xE0) <= (0xEF - 0xE0))
                {
                    if (i + 8 >= valueSpan.Length ||
                        valueSpan[i + 3] != Percent ||
                        valueSpan[i + 6] != Percent ||
                        !TryDecodeEscapedByte(valueSpan.Slice(i + 3), out byte b1) ||
                        !TryDecodeEscapedByte(valueSpan.Slice(i + 6), out byte b2) ||
                        (b0 == 0xE0 && b1 < 0xA0) || (b0 == 0xED && b1 >= 0xA0))
                    {
                        // Malformed utf-8 sequence. emit U+FFFD and continue
                        vsb.Append(Replacement);
                        i += 2;
                        continue;
                    }
 
                    vsb.Append((char)(((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F)));
                    i += 8;
                    continue;
                }
 
                // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                if ((uint)(b0 - 0xF0) <= (0xF4 - 0xF0))
                {
                    if (i + 11 >= valueSpan.Length ||
                        valueSpan[i + 3] != Percent ||
                        valueSpan[i + 6] != Percent ||
                        valueSpan[i + 9] != Percent ||
                        !TryDecodeEscapedByte(valueSpan.Slice(i + 3), out byte b1) ||
                        !TryDecodeEscapedByte(valueSpan.Slice(i + 6), out byte b2) ||
                        !TryDecodeEscapedByte(valueSpan.Slice(i + 9), out byte b3) ||
                        (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80)
                    {
                        // Malformed utf-8 sequence. emit U+FFFD and continue
                        vsb.Append(Replacement);
                        i += 2;
                        continue;
                    }
 
                    int cp = (((b0 & 0x07) << 18) | ((b1 & 0x3F) << 12) | ((b2 & 0x3F) << 6) | ((b3 & 0x3F)));
 
                    if (cp < 0x10000 || cp > 0x10FFFF)
                    {
                        // Malformed utf-8 sequence. emit U+FFFD and continue
                        vsb.Append(Replacement);
                        i += 2;
                        continue;
                    }
 
                    cp -= 0x10000;
                    vsb.Append((char)((cp >> 10) + 0xD800));
                    vsb.Append((char)((cp & 0x3FF) + 0xDC00));
 
                    i += 11;
                    continue;
                }
 
                // invalid byte sequence. emit U+FFFD and continue
                vsb.Append(Replacement);
                i += 2;
            }
 
            value = vsb.ToString();
            return true;
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static bool TryDecodeEscapedByte(ReadOnlySpan<char> span, out byte value)
        {
            Debug.Assert(span.Length > 0 && span[0] == Percent);
 
            if (span.Length < 3 || !TryDecodeHexDigit(span[1], out byte byte1) || !TryDecodeHexDigit(span[2], out byte byte2))
            {
                value = 0;
                return false;
            }
 
            value = (byte)(((uint)byte1 << 4) + byte2);
            return true;
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static bool TryDecodeHexDigit(char c, out byte value)
        {
            value = (byte)HexConverter.FromChar((int)c);
            return value != 0xFF; // invalid hex digit
        }
 
        // Allowed baggage key characters:
        //  tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
        //  DIGIT = 0-9
        //  ALPHA = A-Z / a-z
#if NET
        private const string BaggageKeyValidCharacters = "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~";
        private static readonly SearchValues<char> s_validBaggageKeyChars = SearchValues.Create(BaggageKeyValidCharacters);
 
        private static bool IsInvalidBaggageKey(ReadOnlySpan<char> span) => span.ContainsAnyExcept(s_validBaggageKeyChars);
#else
        private static ulong[] s_invalidBaggageKeyCharsMask = [0xFC009305FFFFFFFF, 0x2800000038000001];
 
        private static bool IsInvalidBaggageKey(ReadOnlySpan<char> span)
        {
            foreach (char c in span)
            {
                // key support only ascii characters according to the W3C specs
                if (c >= 0x07F || ((s_invalidBaggageKeyCharsMask[c >> 6] & (ulong)((ulong)1 << ((int)c & 63))) != 0))
                {
                    return true;
                }
            }
 
            return false; // valid key
        }
#endif
 
        // key = ( lcalpha / DIGIT ) 0*255 ( keychar )
        // keychar    = lcalpha / DIGIT / "_" / "-"/ "*" / "/" / "@"
        // lcalpha    = %x61-7A ; a-z
 
#if NET
        private const string TraceStateKeyValidChars = "*-/@_abcdefghijklmnopqrstuvwxyz";
        private static readonly SearchValues<char> s_validTraceStateChars = SearchValues.Create(TraceStateKeyValidChars);
 
        private static bool IsInvalidTraceStateKey(ReadOnlySpan<char> key) => key.IsEmpty || (key[0] < 'a' || key[0] > 'z') || key.ContainsAnyExcept(s_validTraceStateChars);
 
        private const string TraceStateValueValidChars = "!\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
        private static readonly SearchValues<char> s_validTraceStateValueChars = SearchValues.Create(TraceStateValueValidChars);
 
        private static bool IsInvalidTraceStateValue(ReadOnlySpan<char> value) => value.IsEmpty || value.ContainsAnyExcept(s_validTraceStateValueChars);
#else
        private static ulong[] ValidTraceStateKeyCharsMask = [0x0000A40000000000, 0x07FFFFFE80000001];
 
        private static bool IsInvalidTraceStateKey(ReadOnlySpan<char> key)
        {
            if (key.IsEmpty || (key[0] < 'a' || key[0] > 'z')) // Key has to start with a lowercase letter
            {
                return true; // invalid key character, skip current entry
            }
 
            foreach (char c in key)
            {
                if (c >= 0x07F || (ValidTraceStateKeyCharsMask[c >> 6] & (ulong)((ulong)1 << ((int)c & 63))) == 0)
                {
                    return true; // invalid key character
                }
            }
 
            return false; // valid key
        }
 
        // value = 0 * 255(chr) nblk-chr
        // nblk-chr = % x21 - 2B / % x2D - 3C / % x3E - 7E
        // chr = % x20 / nblk - chr
        private static ulong[] s_traceStateValueMask = { 0xDFFFEFFE00000000, 0x7FFFFFFFFFFFFFFF };
 
        private static bool IsInvalidTraceStateValue(ReadOnlySpan<char> value)
        {
            if (value.IsEmpty)
            {
                return true;
            }
 
            foreach (char c in value)
            {
                if (c >= 0x07F || (s_traceStateValueMask[c >> 6] & (ulong)((ulong)1 << ((int)c & 63))) == 0)
                {
                    return true; // invalid key character, skip current entry
                }
            }
 
            return false; // valid key
        }
#endif
 
        // baggage-string         =  list-member 0*179( OWS "," OWS list-member )
        // list-member            =  key OWS "=" OWS value *( OWS ";" OWS property )
        // property               =  key OWS "=" OWS value
        // property               =/ key OWS
        // key                    =  token ; as defined in RFC 7230, Section 3.2.6
        // value                  =  *baggage-octet
        // baggage-octet          =  %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
        //                         ; US-ASCII characters excluding CTLs,
        //                         ; whitespace, DQUOTE, comma, semicolon,
        //                         ; and backslash
        // OWS                    =  *( SP / HTAB ) ; optional white space, as defined in RFC 7230, Section 3.2.3
 
        /// <summary>
        /// Encode the baggage entry key according to the W3C Specification https://www.w3.org/TR/baggage/#key and https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6.
        /// </summary>
        /// <param name="key">The baggage entry key to encode.</param>
        /// <param name="vsb">The string builder to store the encoded key.</param>
        /// <returns>True if the key encoded correctly, False other wise.</returns>
        /// <remarks>
        /// Though the baggage header is a [UTF-8] encoded string, key is limited to the ASCII code points (code point in the range U+0000 NULL to U+007F DELETE, inclusive) allowed by the definition of token in [RFC7230].
        /// This is due to the implementation details of stable implementations prior to the writing of this specification.
        /// Allowed characters: HTAB / SP /%x21 / %x23-5B / %x5D-7E
        /// </remarks>
        internal static bool EncodeBaggageKey(ReadOnlySpan<char> key, ref 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 ValueStringBuilder vsb)
        {
            value = Trim(value);
 
            for (int i = 0; i < value.Length; i++)
            {
                char c = value[i];
 
                if (!NeedToEscapeBaggageValueCharacter(c))
                {
                    vsb.Append(c);
                    continue;
                }
 
                if (c <= 0x7Fu) // still in ascii range
                {
                    EmitEscapedByte((byte)c, ref vsb);
                    continue;
                }
 
                if (c <= 0x7FFu)
                {
                    // Scalar 00000yyy yyxxxxxx -> bytes [ 110yyyyy 10xxxxxx ]
                    EmitEscapedByte((byte)((c + (0b110u << 11)) >> 6), ref vsb);
                    EmitEscapedByte((byte)((c & 0x3Fu) + 0x80u), ref vsb);
                    continue;
                }
 
                if (char.IsSurrogate(c))
                {
                    if (i < value.Length - 1 && char.IsSurrogatePair((char)c, value[i + 1]))
                    {
                        // Scalar 000uuuuu zzzzyyyy yyxxxxxx -> bytes [ 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx ]
                        uint v = (uint)char.ConvertToUtf32((char)c, value[i + 1]);
 
                        EmitEscapedByte((byte)((v + (0b11110 << 21)) >> 18), ref vsb);
                        EmitEscapedByte((byte)(((v & (0x3Fu << 12)) >> 12) + 0x80u), ref vsb);
                        EmitEscapedByte((byte)(((v & (0x3Fu << 6)) >> 6) + 0x80u), ref vsb);
                        EmitEscapedByte((byte)((v & 0x3Fu) + 0x80u), ref vsb);
                        i++;
                    }
                    else
                    {
                        // Wrong surrogate: emit 0xFFFD which has the UTF-8 encoding bytes 0xEF, 0xBF, and 0xBD.
                        EmitEscapedByte(0xEF, ref vsb);
                        EmitEscapedByte(0xBF, ref vsb);
                        EmitEscapedByte(0xBD, ref vsb);
                    }
                    continue;
                }
 
                // Scalar zzzzyyyy yyxxxxxx -> bytes [ 1110zzzz 10yyyyyy 10xxxxxx ]
                EmitEscapedByte((byte)((c + (0b1110 << 16)) >> 12), ref vsb);
                EmitEscapedByte((byte)(((c & (0x3Fu << 6)) >> 6) + 0x80u), ref vsb);
                EmitEscapedByte((byte)((c & 0x3Fu) + 0x80u), ref vsb);
            }
        }
 
        // baggage-octet =  %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E (Exclude the `%` %x25)
        // are the characters don't need to get escaped
        private static ulong[] s_baggageOctet = [0xF7FFEFDA00000000, 0x7FFFFFFFEFFFFFFF];
 
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static bool NeedToEscapeBaggageValueCharacter(char c)
        {
            if (c >= 0x07F) // non-escapable characters are in ASCII range
            {
                return true;
            }
 
            return (s_baggageOctet[c >> 6] & (ulong)((ulong)1 << ((int)c & 63))) == 0;
        }
 
        // HEXDIGLC = DIGIT / "a" / "b" / "c" / "d" / "e" / "f"; lowercase hex character
        // value           = version "-" version-format
        // version = 2HEXDIGLC; this document assumes version 00. Version ff is forbidden
        // version - format = trace - id "-" parent - id "-" trace - flags
        // trace - id = 32HEXDIGLC; 16 bytes array identifier. All zeroes forbidden
        // parent-id        = 16HEXDIGLC  ; 8 bytes array identifier. All zeroes forbidden
        // trace-flags      = 2HEXDIGLC   ; 8 bit flags.
        //         .         .         .         .         .         .
        // Example 00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01
        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 isAllZeroes = true;
            for (int i = 3; i < 35; i++)
            {
                if (IsInvalidTraceParentCharacter(traceParent[i]))
                {
                    return true;
                }
 
                isAllZeroes &= traceParent[i] == '0';
            }
 
            if (isAllZeroes)
            {
                return true; // all zeroes forbidden
            }
 
            isAllZeroes = true;
            for (int i = 36; i < 52; i++)
            {
                if (IsInvalidTraceParentCharacter(traceParent[i]))
                {
                    return true;
                }
 
                isAllZeroes &= traceParent[i] == '0';
            }
 
            if (isAllZeroes)
            {
                return true; // all zeroes forbidden
            }
 
            if (IsInvalidTraceParentCharacter(traceParent[53]) || IsInvalidTraceParentCharacter(traceParent[54]))
            {
                return true;
            }
 
            return false;
        }
 
        // '0' .. '9' and 'a' .. 'f' are valid characters in the traceparent header.
        private static ulong[] s_traceParentMask = [0x03FF000000000000, 0x0000007E00000000];
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static bool IsInvalidTraceParentCharacter(char c)
        {
            if (c >= 0x07F)
            {
                return true;
            }
 
            return (s_traceParentMask[c >> 6] & (ulong)((ulong)1 << ((int)c & 63))) == 0;
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void EmitEscapedByte(byte b, ref ValueStringBuilder vsb)
        {
            const string hexChars = "0123456789ABCDEF";
 
            vsb.Append(Percent);
            vsb.Append(hexChars[(b >> 4) & 0x0F]);
            vsb.Append(hexChars[b & 0x0F]);
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static ReadOnlySpan<char> TrimSpaceOnly(ReadOnlySpan<char> span) => span.Trim(Space);
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static ReadOnlySpan<char> Trim(ReadOnlySpan<char> span) => span.Trim(" \t");
    }
}