Skip to content

Commit d802a6f

Browse files
authored
[API] fix the trace state regex to comply with the w3c trace context level 2 (#4194)
1 parent a56f0e2 commit d802a6f

3 files changed

Lines changed: 36 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ Increment the:
1515

1616
## [Unreleased]
1717

18+
* [API] Fix `TraceState::IsValidKey()` to comply with the W3C Trace Context
19+
Level 2, where keys containing `@` and keys with more than 241 characters
20+
before `@` or more than 14 characters after `@` are now accepted.
21+
Only the total 256-character key length limit is enforced.
22+
**Note**: this is a correctness fix to an inline API header; the observable
23+
behavior of `IsValidKey`, `IsValidKeyRegEx`, and `IsValidKeyNonRegEx`
24+
changes (see `docs/abi-policy.md`).
25+
[#4194](https://github.com/open-telemetry/opentelemetry-cpp/pull/4194)
26+
1827
* [SDK] Add `TracerProvider::UpdateTracerConfigurator()` and example
1928
[#4065](https://github.com/open-telemetry/opentelemetry-cpp/issues/4065)
2029

api/include/opentelemetry/trace/trace_state.h

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,9 @@ class OPENTELEMETRY_EXPORT TraceState
205205
/** Returns whether key is a valid key. See https://www.w3.org/TR/trace-context/#key
206206
* Identifiers MUST begin with a lowercase letter or a digit, and can only contain
207207
* lowercase letters (a-z), digits (0-9), underscores (_), dashes (-), asterisks (*),
208-
* and forward slashes (/).
209-
* For multi-tenant vendor scenarios, an at sign (@) can be used to prefix the vendor name.
210-
*
208+
* forward slashes (/), and at signs (@).
209+
* An at sign (@) is treated as a regular character (keychar) with no structural meaning.
210+
* Total key length must not exceed 256 characters.
211211
*/
212212
static bool IsValidKey(nostd::string_view key) noexcept
213213
{
@@ -255,15 +255,9 @@ class OPENTELEMETRY_EXPORT TraceState
255255
try
256256
{
257257
# endif
258-
static std::regex reg_key("^[a-z0-9][a-z0-9*_\\-/]{0,255}$");
259-
static std::regex reg_key_multitenant(
260-
"^[a-z0-9][a-z0-9*_\\-/]{0,240}(@)[a-z0-9][a-z0-9*_\\-/]{0,13}$");
258+
static std::regex reg_key("^[a-z0-9][a-z0-9*_\\-/@]{0,255}$");
261259
std::string key_s(key.data(), key.size());
262-
if (std::regex_match(key_s, reg_key) || std::regex_match(key_s, reg_key_multitenant))
263-
{
264-
return true;
265-
}
266-
return false;
260+
return std::regex_match(key_s, reg_key);
267261
# if OPENTELEMETRY_HAVE_EXCEPTIONS
268262
}
269263
catch (const std::regex_error &)
@@ -300,18 +294,12 @@ class OPENTELEMETRY_EXPORT TraceState
300294
return false;
301295
}
302296

303-
int ats = 0;
304-
305297
for (const char c : key)
306298
{
307299
if (!IsLowerCaseAlphaOrDigit(c) && c != '_' && c != '-' && c != '@' && c != '*' && c != '/')
308300
{
309301
return false;
310302
}
311-
if ((c == '@') && (++ats > 1))
312-
{
313-
return false;
314-
}
315303
}
316304
return true;
317305
}

api/test/trace/trace_state_test.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ TEST(TraceStateTest, ValidateHeaderParsing)
6262
{"k1=v1,k2=v2,invalidmember", ""},
6363
{"1a-2f@foo=bar1,a*/foo-_/bar=bar4", "1a-2f@foo=bar1,a*/foo-_/bar=bar4"},
6464
{"1a-2f@foo=bar1,*/foo-_/bar=bar4", ""},
65+
{"foo@@bar=1,baz=2", "foo@@bar=1,baz=2"},
66+
{"foo@bar@baz=1", "foo@bar@baz=1"},
67+
{"@foo=1", ""},
6568
{",k1=v1", "k1=v1"},
6669
{",", ""},
6770
{",=,", ""},
@@ -71,6 +74,12 @@ TEST(TraceStateTest, ValidateHeaderParsing)
7174
{
7275
EXPECT_EQ(create_ts_return_header(testcase.input), testcase.expected);
7376
}
77+
// Key length boundaries: 256-char key valid, 257-char invalid
78+
EXPECT_NE(create_ts_return_header(std::string(256, 'z') + "=1"), "");
79+
EXPECT_EQ(create_ts_return_header(std::string(257, 'z') + "=1"), "");
80+
// Old vendor-section limits (241 before @, 14 after) are gone; only total length matters
81+
EXPECT_NE(create_ts_return_header(std::string(242, 't') + "@v=1"), ""); // 244-char key with @
82+
EXPECT_NE(create_ts_return_header("t@" + std::string(15, 'v') + "=1"), ""); // 15 chars after @
7483
}
7584

7685
TEST(TraceStateTest, ExceedsMaxKeyValuePairs)
@@ -165,10 +174,23 @@ TEST(TraceStateTest, GetAllEntries)
165174
TEST(TraceStateTest, IsValidKey)
166175
{
167176
EXPECT_TRUE(TraceState::IsValidKey("valid-key23/*"));
177+
// @ is a regular keychar per W3C Trace Context Level 2
178+
EXPECT_TRUE(TraceState::IsValidKey("foo@"));
179+
EXPECT_TRUE(TraceState::IsValidKey("foo@@bar"));
180+
EXPECT_TRUE(TraceState::IsValidKey("foo@bar@baz"));
181+
EXPECT_FALSE(TraceState::IsValidKey("@foo")); // must start with lcalpha or DIGIT
168182
EXPECT_FALSE(TraceState::IsValidKey("Invalid_key"));
169183
EXPECT_FALSE(TraceState::IsValidKey("invalid$Key&"));
170184
EXPECT_FALSE(TraceState::IsValidKey(""));
171185
EXPECT_FALSE(TraceState::IsValidKey(kLongString));
186+
// Key length boundary: exactly 256 chars valid, 257 invalid
187+
EXPECT_TRUE(TraceState::IsValidKey(std::string(256, 'z')));
188+
EXPECT_FALSE(TraceState::IsValidKey(std::string(257, 'z')));
189+
// Old vendor-section limits (241 before @, 14 after) are gone; only total length matters
190+
EXPECT_TRUE(
191+
TraceState::IsValidKey(std::string(241, 't') + "@" + std::string(14, 'v'))); // 256 chars
192+
EXPECT_TRUE(TraceState::IsValidKey(std::string(242, 't') + "@v")); // 244 chars
193+
EXPECT_TRUE(TraceState::IsValidKey("t@" + std::string(15, 'v'))); // 17 chars
172194
}
173195

174196
TEST(TraceStateTest, IsValidValue)

0 commit comments

Comments
 (0)