11// Copyright The OpenTelemetry Authors
22// SPDX-License-Identifier: Apache-2.0
33
4- using System . Buffers ;
54using System . Diagnostics ;
65using System . Runtime . CompilerServices ;
6+ using System . Text ;
77using OpenTelemetry . Internal ;
88
99namespace OpenTelemetry . Context . Propagation ;
@@ -76,7 +76,7 @@ public override PropagationContext Extract<T>(PropagationContext context, T carr
7676 var tracestateCollection = getter ( carrier , TraceState ) ;
7777 if ( tracestateCollection ? . Any ( ) ?? false )
7878 {
79- TryExtractTracestate ( tracestateCollection , out tracestate ) ;
79+ TryExtractTracestate ( tracestateCollection . ToArray ( ) , out tracestate ) ;
8080 }
8181
8282 return new PropagationContext (
@@ -220,37 +220,31 @@ internal static bool TryExtractTraceparent(string traceparent, out ActivityTrace
220220 return true ;
221221 }
222222
223- internal static bool TryExtractTracestate ( IEnumerable < string > tracestateCollection , out string tracestateResult )
223+ internal static bool TryExtractTracestate ( string [ ] tracestateCollection , out string tracestateResult )
224224 {
225225 tracestateResult = string . Empty ;
226226
227- char [ ] ? rentedArray = null ;
228- Span < char > traceStateBuffer = stackalloc char [ 128 ] ; // 256B
229- Span < char > keyLookupBuffer = stackalloc char [ 96 ] ; // 192B (3x32 keys)
230- int keys = 0 ;
231- int charsWritten = 0 ;
232-
233- try
227+ if ( tracestateCollection != null )
234228 {
235- foreach ( var tracestateItem in tracestateCollection )
229+ var keySet = new HashSet < string > ( ) ;
230+ var result = new StringBuilder ( ) ;
231+ for ( int i = 0 ; i < tracestateCollection . Length ; ++ i )
236232 {
237- var tracestate = tracestateItem . AsSpan ( ) ;
238- int position = 0 ;
239-
240- while ( position < tracestate . Length )
233+ var tracestate = tracestateCollection [ i ] . AsSpan ( ) ;
234+ int begin = 0 ;
235+ while ( begin < tracestate . Length )
241236 {
242- int length = tracestate . Slice ( position ) . IndexOf ( ',' ) ;
237+ int length = tracestate . Slice ( begin ) . IndexOf ( ',' ) ;
243238 ReadOnlySpan < char > listMember ;
244-
245239 if ( length != - 1 )
246240 {
247- listMember = tracestate . Slice ( position , length ) . Trim ( ) ;
248- position += length + 1 ;
241+ listMember = tracestate . Slice ( begin , length ) . Trim ( ) ;
242+ begin += length + 1 ;
249243 }
250244 else
251245 {
252- listMember = tracestate . Slice ( position ) . Trim ( ) ;
253- position = tracestate . Length ;
246+ listMember = tracestate . Slice ( begin ) . Trim ( ) ;
247+ begin = tracestate . Length ;
254248 }
255249
256250 // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#tracestate-header-field-values
@@ -261,7 +255,7 @@ internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollecti
261255 continue ;
262256 }
263257
264- if ( keys >= 32 )
258+ if ( keySet . Count >= 32 )
265259 {
266260 // https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#list
267261 // test_tracestate_member_count_limit
@@ -292,107 +286,25 @@ internal static bool TryExtractTracestate(IEnumerable<string> tracestateCollecti
292286 }
293287
294288 // ValidateKey() call above has ensured the key does not contain upper case letters.
295-
296- var duplicationCheckLength = Math . Min ( key . Length , 3 ) ;
297-
298- if ( keys > 0 )
299- {
300- // Fast path check of first three chars for potential duplicated keys
301- var potentialMatchingKeyPosition = 1 ;
302- var found = false ;
303- for ( int i = 0 ; i < keys * 3 ; i += 3 )
304- {
305- if ( keyLookupBuffer . Slice ( i , duplicationCheckLength ) . SequenceEqual ( key . Slice ( 0 , duplicationCheckLength ) ) )
306- {
307- found = true ;
308- break ;
309- }
310-
311- potentialMatchingKeyPosition ++ ;
312- }
313-
314- // If the fast check has found a possible duplicate, we need to do a full check
315- if ( found )
316- {
317- var bufferToCompare = traceStateBuffer . Slice ( 0 , charsWritten ) ;
318-
319- // We know which key is the first possible duplicate, so skip to that key
320- // by slicing to the position after the appropriate comma.
321- for ( int i = 1 ; i < potentialMatchingKeyPosition ; i ++ )
322- {
323- var commaIndex = bufferToCompare . IndexOf ( ',' ) ;
324-
325- if ( commaIndex > - 1 )
326- {
327- bufferToCompare . Slice ( commaIndex ) ;
328- }
329- }
330-
331- int existingIndex = - 1 ;
332- while ( ( existingIndex = bufferToCompare . IndexOf ( key ) ) > - 1 )
333- {
334- if ( ( existingIndex > 0 && bufferToCompare [ existingIndex - 1 ] != ',' ) || bufferToCompare [ existingIndex + key . Length ] != '=' )
335- {
336- continue ; // this is not a key
337- }
338-
339- return false ; // test_tracestate_duplicated_keys
340- }
341- }
342- }
343-
344- // Store up to the first three characters of the key for use in the duplicate lookup fast path
345- var startKeyLookupIndex = keys > 0 ? keys * 3 : 0 ;
346- key . Slice ( 0 , duplicationCheckLength ) . CopyTo ( keyLookupBuffer . Slice ( startKeyLookupIndex ) ) ;
347-
348- // Check we have capacity to write the key and value
349- var requiredCapacity = charsWritten > 0 ? listMember . Length + 1 : listMember . Length ;
350-
351- while ( charsWritten + requiredCapacity > traceStateBuffer . Length )
289+ if ( ! keySet . Add ( key . ToString ( ) ) )
352290 {
353- GrowBuffer ( ref rentedArray , ref traceStateBuffer ) ;
291+ // test_tracestate_duplicated_keys
292+ return false ;
354293 }
355294
356- if ( charsWritten > 0 )
295+ if ( result . Length > 0 )
357296 {
358- traceStateBuffer [ charsWritten ++ ] = ',' ;
297+ result . Append ( ',' ) ;
359298 }
360299
361- listMember . CopyTo ( traceStateBuffer . Slice ( charsWritten ) ) ;
362- charsWritten += listMember . Length ;
363-
364- keys ++ ;
300+ result . Append ( listMember . ToString ( ) ) ;
365301 }
366302 }
367303
368- tracestateResult = traceStateBuffer . Slice ( 0 , charsWritten ) . ToString ( ) ;
369-
370- return true ;
304+ tracestateResult = result . ToString ( ) ;
371305 }
372- finally
373- {
374- if ( rentedArray is not null )
375- {
376- ArrayPool < char > . Shared . Return ( rentedArray ) ;
377- rentedArray = null ;
378- }
379- }
380-
381- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
382- static void GrowBuffer ( ref char [ ] ? array , ref Span < char > buffer )
383- {
384- var newBuffer = ArrayPool < char > . Shared . Rent ( buffer . Length * 2 ) ;
385306
386- buffer . CopyTo ( newBuffer . AsSpan ( ) ) ;
387-
388- if ( array is not null )
389- {
390- ArrayPool < char > . Shared . Return ( array ) ;
391- }
392-
393- array = newBuffer ;
394- buffer = array . AsSpan ( ) ;
395- }
307+ return true ;
396308 }
397309
398310 private static byte HexCharToByte ( char c )
0 commit comments