Skip to content

Commit 29fedcc

Browse files
committed
product trace source should generate two-character case-insensitive hexadecimal string and be able to parse masks of at least 32 bits to ensure forward compatibility
1 parent f789660 commit 29fedcc

File tree

4 files changed

+61
-21
lines changed

4 files changed

+61
-21
lines changed

dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsCodec.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,17 @@ private static boolean validateTraceId(TagValue value) {
230230
}
231231

232232
private static boolean validateTraceSourceTagValue(TagValue value) {
233-
// Ensure the string is not null, has a length of 2, and matches the hex pattern
234-
return value != null
235-
&& value.length() == 2
236-
&& isHexDigit(value.charAt(0))
237-
&& isHexDigit(value.charAt(1));
233+
// Ensure the string is not null and has a length between 2 and 8
234+
if (value == null || value.length() < 2 || value.length() > 8) {
235+
return false;
236+
}
237+
for (int i = 0; i < value.length(); i++) {
238+
// Ensure each character is a valid hex digit
239+
if (!isHexDigitCaseInsensitive(value.charAt(i))) {
240+
return false;
241+
}
242+
}
243+
return true;
238244
}
239245

240246
protected static boolean isDigit(char c) {
@@ -244,4 +250,8 @@ protected static boolean isDigit(char c) {
244250
protected static boolean isHexDigit(char c) {
245251
return c >= 'a' && c <= 'f' || isDigit(c);
246252
}
253+
254+
protected static boolean isHexDigitCaseInsensitive(char c) {
255+
return isHexDigit(c) || c >= 'A' && c <= 'F';
256+
}
247257
}

dd-trace-core/src/test/groovy/datadog/trace/core/propagation/DatadogPropagationTagsTest.groovy

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,19 @@ class DatadogPropagationTagsTest extends DDCoreSpecification {
154154
propagationTags.createTagMap() == tags
155155

156156
where:
157-
originalTagSet | product | expectedHeaderValue | tags
157+
originalTagSet | product | expectedHeaderValue | tags
158158
// keep the existing dm tag as is
159-
"" | ProductTraceSource.ASM | "_dd.p.ts=02" | ["_dd.p.ts": "02"]
160-
"_dd.p.ts=00" | ProductTraceSource.ASM | "_dd.p.ts=02" | ["_dd.p.ts": "02"]
161-
"_dd.p.ts=02" | ProductTraceSource.DBM | "_dd.p.ts=12" | ["_dd.p.ts": "12"]
159+
"" | ProductTraceSource.ASM | "_dd.p.ts=02" | ["_dd.p.ts": "02"]
160+
"_dd.p.ts=00" | ProductTraceSource.ASM | "_dd.p.ts=02" | ["_dd.p.ts": "02"]
161+
"_dd.p.ts=FFC00000" | ProductTraceSource.ASM | "_dd.p.ts=02" | ["_dd.p.ts": "02"]
162+
"_dd.p.ts=02" | ProductTraceSource.DBM | "_dd.p.ts=12" | ["_dd.p.ts": "12"]
162163
//Invalid input
163-
"_dd.p.ts=" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
164-
"_dd.p.ts=0" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
165-
"_dd.p.ts=GG" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
166-
"_dd.p.ts=foo" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
164+
"_dd.p.ts=" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
165+
"_dd.p.ts=0" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
166+
"_dd.p.ts=0G" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
167+
"_dd.p.ts=GG" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
168+
"_dd.p.ts=foo" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
169+
"_dd.p.ts=000000002" | ProductTraceSource.UNSET | null | ["_dd.propagation_error": "decoding_error"]
167170
}
168171

169172
def extractionLimitExceeded() {

dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CPropagationTagsTest.groovy

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ class W3CPropagationTagsTest extends DDCoreSpecification {
311311
propagationTags.createTagMap() == tags
312312

313313
where:
314-
headerValue | priority | mechanism | origin | expectedHeaderValue | tags
314+
headerValue | priority | mechanism | origin | expectedHeaderValue | tags
315315
'dd=s:0;o:some;t.dm:934086a686-4' | PrioritySampling.SAMPLER_KEEP | SamplingMechanism.DEFAULT | "other" | 'dd=s:0;o:other;t.dm:934086a686-4' | ['_dd.p.dm': '934086a686-4']
316316
'dd=s:0;o:some;x:unknown' | PrioritySampling.USER_KEEP | SamplingMechanism.LOCAL_USER_RULE | "same" | 'dd=s:2;o:same;t.dm:-3;x:unknown' | ['_dd.p.dm': '-3']
317317
'dd=s:0;o:some;x:unknown' | PrioritySampling.USER_DROP | SamplingMechanism.MANUAL | null | 'dd=s:-1;x:unknown' | [:]
@@ -339,9 +339,11 @@ class W3CPropagationTagsTest extends DDCoreSpecification {
339339
propagationTags.createTagMap() == tags
340340

341341
where:
342-
headerValue | product | expectedHeaderValue | tags
342+
headerValue | product | expectedHeaderValue | tags
343343
'dd=x:unknown' | ProductTraceSource.ASM | 'dd=t.ts:02;x:unknown' | ['_dd.p.ts': '02']
344344
'dd=t.ts:02;x:unknown' | ProductTraceSource.DBM | 'dd=t.ts:12;x:unknown' | ['_dd.p.ts': '12']
345+
"dd=t.ts:00" | ProductTraceSource.ASM | 'dd=t.ts:02' | ["_dd.p.ts": "02"]
346+
"dd=t.ts:FFC00000" | ProductTraceSource.ASM | 'dd=t.ts:02' | ["_dd.p.ts": "02"]
345347
}
346348

347349
static private String toLcAlpha(String cs) {
Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
package datadog.trace.api;
22

3+
/**
4+
* Represents a bitfield-based trace source for tracking product propagation tags.
5+
*
6+
* <p>The bitfield is encoded as an **8-bit hexadecimal mask** (ranging from `00` to `FF`), where
7+
* each bit corresponds to a specific product. This class provides utility methods for updating,
8+
* checking, and parsing these product bitfields.
9+
*
10+
* <ul>
11+
* <li>MUST use a two-character, case-insensitive hexadecimal string (e.g., "00" to "FF").
12+
* <li>MUST support parsing masks of at least 32 bits to ensure forward compatibility.
13+
* <li>Each bit corresponds to a specific product:
14+
* </ul>
15+
*/
316
public class ProductTraceSource {
417

518
public static final int UNSET = 0;
@@ -10,26 +23,38 @@ public class ProductTraceSource {
1023
public static final int DJM = 0x08;
1124
public static final int DBM = 0x10;
1225

13-
// Update (set) the bitfield for a specific product
26+
/** Updates the bitfield by setting the bit corresponding to a specific product. */
1427
public static int updateProduct(int bitfield, int product) {
1528
return bitfield |= product; // Set the bit for the given product
1629
}
1730

18-
// Check if the bitfield is marked for a specific product
31+
/** Checks if the bitfield is marked for a specific product. */
1932
public static boolean isProductMarked(final int bitfield, int product) {
2033
return (bitfield & product) != 0; // Check if the bit is set
2134
}
2235

23-
// Get the current bitfield as a hexadecimal string
36+
/**
37+
* Converts the current bitfield to a two-character hexadecimal string.
38+
*
39+
* <p>This method ensures the output follows the **00 to FF** format, padding with leading zeros
40+
* if necessary.
41+
*/
2442
public static String getBitfieldHex(final int bitfield) {
25-
return String.format("%02x", bitfield); // Convert to 2-character hex
43+
String hex = Integer.toHexString(bitfield & 0xFF);
44+
return hex.length() == 1 ? "0" + hex : hex; // Ensure two characters
2645
}
2746

28-
// Parse a hexadecimal string back to an integer
47+
/**
48+
* Parses a hexadecimal string back into an integer bitfield.
49+
*
50+
* <p>This method allows for **at least 32-bit parsing**, ensuring forward compatibility with
51+
* potential future expansions.
52+
*/
2953
public static int parseBitfieldHex(final String hexString) {
3054
if (hexString == null || hexString.isEmpty()) {
3155
return 0; // Return 0 if the string is empty
3256
}
33-
return Integer.parseInt(hexString, 16); // Parse the string as a base-16 number
57+
// Need to support unsigned parsing
58+
return (int) Long.parseUnsignedLong(hexString, 16); // Parse the string as a base-16 number
3459
}
3560
}

0 commit comments

Comments
 (0)