Skip to content

Commit 960aa52

Browse files
More migrated tests
1 parent 6237ece commit 960aa52

4 files changed

Lines changed: 46 additions & 132 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ include(GNUInstallDirs)
661661
include(CMakePackageConfigHelpers)
662662
include(uniffi)
663663
add_dependencies(livekit livekit_uniffi_cpp)
664+
target_link_libraries(livekit PRIVATE livekit_uniffi_cpp livekit_uniffi)
664665

665666
set(LIVEKIT_PACKAGE_NAME "LiveKit")
666667
set(LIVEKIT_EXPORT_NAMESPACE "LiveKit::")

src/tests/unit/test_token_source.cpp

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,19 @@ namespace livekit::test {
3131

3232
namespace {
3333

34-
// A non-expired unsigned JWT (alg=none, exp far in the future) used for stubbed
35-
// token-endpoint responses.
36-
constexpr const char* kValidToken = "eyJhbGciOiJub25lIn0.eyJleHAiOjk5OTk5OTk5OTk5fQ.";
34+
// Non-production ES256 fixtures shared with the JS SDK's token-source tests.
35+
// Their signatures have no corresponding private key in this repository.
36+
constexpr const char* kValidToken =
37+
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9."
38+
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjo5ODc2NTQzMjEwLCJuYmYiOjEyMzQ1Njc4OTAsImlhdCI6MTIzNDU2Nzg5MCwicm9vbUNvbmZpZyI6ey"
39+
"JuYW1lIjoidGVzdCByb29tIG5hbWUiLCJlbXB0eVRpbWVvdXQiOjAsImRlcGFydHVyZVRpbWVvdXQiOjAsIm1heFBhcnRpY2lwYW50cyI6MCwibWlu"
40+
"UGxheW91dERlbGF5IjowLCJtYXhQbGF5b3V0RGVsYXkiOjAsInN5bmNTdHJlYW1zIjpmYWxzZSwiYWdlbnRzIjpbeyJhZ2VudE5hbWUiOiJ0ZXN0IG"
41+
"FnZW50IG5hbWUiLCJtZXRhZGF0YSI6InRlc3QgYWdlbnQgbWV0YWRhdGEifV0sIm1ldGFkYXRhIjoiIn19."
42+
"EDetpHG8cSubaApzgWJaQrpCiSy9KDBlfCfVdIydbQ-_CHiNnXOK_f_mCJbTf9A-duT1jmvPOkLrkkWFT60XPQ";
43+
constexpr const char* kExpiredToken =
44+
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9."
45+
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxMjM0NTY3ODkxLCJuYmYiOjEyMzQ1Njc4OTAsImlhdCI6MTIzNDU2Nzg5MH0."
46+
"OYP1NITayotBYt0mioInLJmaIM0bHyyR-yG6iwKyQDzhoGha15qbsc7dOJlzz4za1iW5EzCgjc2_xGxqaSu5XA";
3747
constexpr const char* kServerUrl = "wss://localhost:7000";
3848

3949
// Captures the arguments the token source passed to the HTTP transport so tests
@@ -299,11 +309,8 @@ TEST(TokenSourceJsonTest, ParseResponseMissingParticipantTokenFails) {
299309
}
300310

301311
TEST(TokenSourceJwtTest, ValidAndExpiredTokens) {
302-
const std::string valid_token = "eyJhbGciOiJub25lIn0.eyJleHAiOjk5OTk5OTk5OTk5fQ.";
303-
const std::string expired_token = "eyJhbGciOiJub25lIn0.eyJleHAiOjF9.";
304-
305-
EXPECT_TRUE(isParticipantTokenValid(valid_token));
306-
EXPECT_FALSE(isParticipantTokenValid(expired_token));
312+
EXPECT_TRUE(isParticipantTokenValid(kValidToken));
313+
EXPECT_FALSE(isParticipantTokenValid(kExpiredToken));
307314
}
308315

309316
TEST(TokenSourceJwtTest, UnparseableTokenIsInvalid) { EXPECT_FALSE(isParticipantTokenValid("not-a-jwt")); }
@@ -347,7 +354,7 @@ TEST(TokenSourceFactoryTest, CachingTokenSourceReusesValidToken) {
347354
++fetch_count;
348355
TokenSourceResponse details;
349356
details.server_url = "wss://example.livekit.io";
350-
details.participant_token = "eyJhbGciOiJub25lIn0.eyJleHAiOjk5OTk5OTk5OTk5fQ.";
357+
details.participant_token = kValidToken;
351358
std::promise<Result<TokenSourceResponse, TokenSourceError>> promise;
352359
promise.set_value(Result<TokenSourceResponse, TokenSourceError>::success(details));
353360
return promise.get_future();
@@ -371,7 +378,7 @@ TEST(TokenSourceFactoryTest, CachingTokenSourceRefetchesAfterInvalidate) {
371378
++fetch_count;
372379
TokenSourceResponse details;
373380
details.server_url = "wss://example.livekit.io";
374-
details.participant_token = "eyJhbGciOiJub25lIn0.eyJleHAiOjk5OTk5OTk5OTk5fQ.";
381+
details.participant_token = kValidToken;
375382
std::promise<Result<TokenSourceResponse, TokenSourceError>> promise;
376383
promise.set_value(Result<TokenSourceResponse, TokenSourceError>::success(details));
377384
return promise.get_future();
@@ -391,7 +398,7 @@ TEST(TokenSourceFactoryTest, CachingTokenSourceExposesCachedResponse) {
391398
[](const TokenRequestOptions&) -> std::future<Result<TokenSourceResponse, TokenSourceError>> {
392399
TokenSourceResponse details;
393400
details.server_url = "wss://example.livekit.io";
394-
details.participant_token = "eyJhbGciOiJub25lIn0.eyJleHAiOjk5OTk5OTk5OTk5fQ.";
401+
details.participant_token = kValidToken;
395402
std::promise<Result<TokenSourceResponse, TokenSourceError>> promise;
396403
promise.set_value(Result<TokenSourceResponse, TokenSourceError>::success(details));
397404
return promise.get_future();
@@ -418,7 +425,7 @@ TEST(TokenSourceFactoryTest, CachingTokenSourceRefetchesWhenOptionsChange) {
418425
++fetch_count;
419426
TokenSourceResponse details;
420427
details.server_url = "wss://example.livekit.io";
421-
details.participant_token = "eyJhbGciOiJub25lIn0.eyJleHAiOjk5OTk5OTk5OTk5fQ.";
428+
details.participant_token = kValidToken;
422429
std::promise<Result<TokenSourceResponse, TokenSourceError>> promise;
423430
promise.set_value(Result<TokenSourceResponse, TokenSourceError>::success(details));
424431
return promise.get_future();
@@ -443,8 +450,7 @@ TEST(TokenSourceFactoryTest, CachingTokenSourceRefetchesWhenTokenExpired) {
443450
const int count = ++fetch_count;
444451
TokenSourceResponse details;
445452
details.server_url = "wss://example.livekit.io";
446-
details.participant_token =
447-
(count == 1) ? "eyJhbGciOiJub25lIn0.eyJleHAiOjF9." : "eyJhbGciOiJub25lIn0.eyJleHAiOjk5OTk5OTk5OTk5fQ.";
453+
details.participant_token = (count == 1) ? kExpiredToken : kValidToken;
448454
std::promise<Result<TokenSourceResponse, TokenSourceError>> promise;
449455
promise.set_value(Result<TokenSourceResponse, TokenSourceError>::success(details));
450456
return promise.get_future();
@@ -470,7 +476,7 @@ TEST(TokenSourceFactoryTest, CachingTokenSourceRefetchesWhenTokenUnparseable) {
470476
const int count = ++fetch_count;
471477
TokenSourceResponse details;
472478
details.server_url = "wss://example.livekit.io";
473-
details.participant_token = (count == 1) ? "not-a-jwt" : "eyJhbGciOiJub25lIn0.eyJleHAiOjk5OTk5OTk5OTk5fQ.";
479+
details.participant_token = (count == 1) ? "not-a-jwt" : kValidToken;
474480
std::promise<Result<TokenSourceResponse, TokenSourceError>> promise;
475481
promise.set_value(Result<TokenSourceResponse, TokenSourceError>::success(details));
476482
return promise.get_future();
@@ -506,7 +512,7 @@ TEST(TokenSourceFactoryTest, CachingTokenSourceSerializesConcurrentFetches) {
506512

507513
TokenSourceResponse details;
508514
details.server_url = "wss://example.livekit.io";
509-
details.participant_token = "eyJhbGciOiJub25lIn0.eyJleHAiOjk5OTk5OTk5OTk5fQ.";
515+
details.participant_token = kValidToken;
510516
std::promise<Result<TokenSourceResponse, TokenSourceError>> promise;
511517
promise.set_value(Result<TokenSourceResponse, TokenSourceError>::success(details));
512518
--concurrent_calls;

src/tests/unit/test_uniffi.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@ TEST(UniFfiBuildInfoTest, ReturnsCrateVersion) {
3030
EXPECT_NE(version, "unknown");
3131
}
3232

33-
TEST(UniFfiLogForwardTest, BootstrapsIdempotently) {
34-
EXPECT_NO_THROW({
35-
livekit_uniffi::log_forward_bootstrap(livekit_uniffi::LogForwardFilter::kInfo);
36-
livekit_uniffi::log_forward_bootstrap(livekit_uniffi::LogForwardFilter::kWarn);
37-
});
33+
TEST(UniFfiLogForwardTest, ForwardsRustLogEntriesWithoutSdkInitialization) {
34+
livekit_uniffi::log_forward_bootstrap(livekit_uniffi::LogForwardFilter::kDebug);
35+
36+
const livekit_uniffi::ApiCredentials credentials{"devkey", "secret"};
37+
const livekit_uniffi::TokenOptions options{};
38+
(void)livekit_uniffi::token_generate(options, credentials);
39+
40+
const auto entry = livekit_uniffi::log_forward_receive().get();
41+
std::cout << "Entry: " << entry->message << std::endl;
42+
ASSERT_TRUE(entry.has_value());
43+
EXPECT_EQ(entry->level, livekit_uniffi::LogForwardLevel::kDebug);
44+
EXPECT_EQ(entry->message, "Generating access token");
3845
}
3946

4047
TEST(UniFfiGeneratedBindingsTest, ExposesAllGeneratedComponents) {

src/token_source_jwt.cpp

Lines changed: 11 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -15,127 +15,27 @@
1515

1616
#include <chrono>
1717
#include <cstdint>
18-
#include <nlohmann/json.hpp>
19-
#include <optional>
18+
#include <exception>
19+
#include <livekit_uniffi.hpp>
2020
#include <string>
21-
#include <vector>
2221

2322
#include "token_source_internal.h"
2423

2524
namespace livekit {
26-
namespace {
27-
28-
std::optional<std::vector<std::uint8_t>> base64UrlDecode(const std::string& input) {
29-
std::string normalized;
30-
normalized.reserve(input.size());
31-
for (const char ch : input) {
32-
if (ch == '-') {
33-
normalized += '+';
34-
} else if (ch == '_') {
35-
normalized += '/';
36-
} else {
37-
normalized += ch;
38-
}
39-
}
40-
41-
while (normalized.size() % 4 != 0) {
42-
normalized += '=';
43-
}
44-
45-
static const int kDecodeTable[256] = {
46-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
47-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57,
48-
58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
49-
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
50-
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
51-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
52-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
53-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
54-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
55-
56-
std::vector<std::uint8_t> output;
57-
output.reserve(normalized.size() * 3 / 4);
58-
59-
std::uint32_t buffer = 0;
60-
int bits = 0;
61-
for (const unsigned char ch : normalized) {
62-
if (ch == '=') {
63-
break;
64-
}
65-
const int value = kDecodeTable[ch];
66-
if (value < 0) {
67-
return std::nullopt;
68-
}
69-
buffer = (buffer << 6) | static_cast<std::uint32_t>(value);
70-
bits += 6;
71-
if (bits >= 8) {
72-
bits -= 8;
73-
output.push_back(static_cast<std::uint8_t>((buffer >> bits) & 0xFF));
74-
}
75-
}
76-
77-
return output;
78-
}
79-
80-
std::optional<std::string> extractJwtPayloadJson(const std::string& token) {
81-
const std::size_t first_dot = token.find('.');
82-
if (first_dot == std::string::npos) {
83-
return std::nullopt;
84-
}
85-
const std::size_t second_dot = token.find('.', first_dot + 1);
86-
if (second_dot == std::string::npos) {
87-
return std::nullopt;
88-
}
89-
90-
const std::string payload_segment = token.substr(first_dot + 1, second_dot - first_dot - 1);
91-
const auto decoded = base64UrlDecode(payload_segment);
92-
if (!decoded.has_value() || decoded->empty()) {
93-
return std::nullopt;
94-
}
95-
96-
return std::string(decoded->begin(), decoded->end());
97-
}
98-
99-
// Read an integer-valued JWT claim (e.g. "nbf"/"exp"). JWT numeric date claims
100-
// are seconds since the epoch; non-integer or absent claims return nullopt.
101-
std::optional<std::int64_t> readNumericClaim(const nlohmann::json& payload, const char* key) {
102-
const auto it = payload.find(key);
103-
if (it == payload.end() || !it->is_number()) {
104-
return std::nullopt;
105-
}
106-
return it->get<std::int64_t>();
107-
}
108-
109-
} // namespace
11025

11126
bool isParticipantTokenValid(const std::string& participant_token) {
112-
const auto payload_json = extractJwtPayloadJson(participant_token);
113-
if (!payload_json.has_value()) {
114-
return false;
115-
}
116-
117-
const nlohmann::json payload = nlohmann::json::parse(*payload_json, nullptr, /*allow_exceptions=*/false);
118-
if (!payload.is_object()) {
119-
return false;
120-
}
121-
122-
const auto now_seconds =
123-
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
124-
125-
const auto nbf = readNumericClaim(payload, "nbf");
126-
if (nbf.has_value() && *nbf > now_seconds) {
127-
return false;
128-
}
129-
130-
const auto exp = readNumericClaim(payload, "exp");
131-
if (exp.has_value()) {
132-
constexpr std::int64_t kExpiryBufferSeconds = 60;
133-
if (*exp <= now_seconds + kExpiryBufferSeconds) {
27+
try {
28+
const auto claims = livekit_uniffi::token_claims_from_unverified(participant_token);
29+
constexpr std::uint64_t kExpiryBufferSeconds = 60;
30+
const auto now_seconds =
31+
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
32+
if (now_seconds < 0) {
13433
return false;
13534
}
35+
return claims.exp > static_cast<std::uint64_t>(now_seconds) + kExpiryBufferSeconds;
36+
} catch (const std::exception&) {
37+
return false;
13638
}
137-
138-
return true;
13939
}
14040

14141
} // namespace livekit

0 commit comments

Comments
 (0)