Skip to content

Commit 77b3733

Browse files
Get E2EE functional
1 parent ae11d64 commit 77b3733

File tree

17 files changed

+503
-109
lines changed

17 files changed

+503
-109
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ add_library(livekit
160160
include/livekit/audio_source.h
161161
include/livekit/audio_stream.h
162162
include/livekit/data_stream.h
163+
include/livekit/e2ee.h
163164
include/livekit/room.h
164165
include/livekit/room_event_types.h
165166
include/livekit/room_delegate.h
@@ -186,6 +187,7 @@ add_library(livekit
186187
src/audio_source.cpp
187188
src/audio_stream.cpp
188189
src/data_stream.cpp
190+
src/e2ee.cpp
189191
src/ffi_handle.cpp
190192
src/ffi_client.cpp
191193
src/local_audio_track.cpp

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,19 @@ export LIVEKIT_TOKEN=<jwt-token>
6060
./build/examples/SimpleRoom
6161
```
6262

63+
**End-to-End Encryption (E2EE)**
64+
You can enable E2E encryption for the streams via --enable_e2ee and --e2ee_key flags,
65+
by running the following cmds in two terminals or computers. **Note, jwt_token needs to be different identity**
66+
```bash
67+
./build/examples/SimpleRoom --url $URL --token <jwt-token> --enable_e2ee --e2ee_key="your_key"
68+
```
69+
**Note**, **all participants must use the exact same E2EE configuration and shared key.**
70+
If the E2EE keys do not match between participants:
71+
- Media cannot be decrypted
72+
- Video tracks will appear as a black screen
73+
- Audio will be silent
74+
- No explicit error may be shown at the UI level
75+
6376
Press Ctrl-C to exit the example.
6477

6578
### SimpleRpc

examples/simple_data_stream/main.cpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ std::string randomHexId(std::size_t nbytes = 16) {
4646
}
4747

4848
// Greeting: send text + image
49-
void greetParticipant(Room &room, const std::string &identity) {
49+
void greetParticipant(Room *room, const std::string &identity) {
5050
std::cout << "[DataStream] Greeting participant: " << identity << "\n";
5151

52-
LocalParticipant *lp = room.localParticipant();
52+
LocalParticipant *lp = room->localParticipant();
5353
if (!lp) {
5454
std::cerr << "[DataStream] No local participant, cannot greet.\n";
5555
return;
@@ -209,33 +209,33 @@ int main(int argc, char *argv[]) {
209209
std::signal(SIGTERM, handleSignal);
210210
#endif
211211

212-
Room room{};
212+
auto room = std::make_unique<Room>();
213213
RoomOptions options;
214214
options.auto_subscribe = true;
215215
options.dynacast = false;
216216

217-
bool ok = room.Connect(url, token, options);
217+
bool ok = room->Connect(url, token, options);
218218
std::cout << "[DataStream] Connect result: " << std::boolalpha << ok << "\n";
219219
if (!ok) {
220220
std::cerr << "[DataStream] Failed to connect to room\n";
221221
FfiClient::instance().shutdown();
222222
return 1;
223223
}
224224

225-
auto info = room.room_info();
225+
auto info = room->room_info();
226226
std::cout << "[DataStream] Connected to room '" << info.name
227227
<< "', participants: " << info.num_participants << "\n";
228228

229229
// Register stream handlers
230-
room.registerTextStreamHandler(
230+
room->registerTextStreamHandler(
231231
"chat", [](std::shared_ptr<TextStreamReader> reader,
232232
const std::string &participant_identity) {
233233
std::thread t(handleChatMessage, std::move(reader),
234234
participant_identity);
235235
t.detach();
236236
});
237237

238-
room.registerByteStreamHandler(
238+
room->registerByteStreamHandler(
239239
"files", [](std::shared_ptr<ByteStreamReader> reader,
240240
const std::string &participant_identity) {
241241
std::thread t(handleWelcomeImage, std::move(reader),
@@ -245,25 +245,25 @@ int main(int argc, char *argv[]) {
245245

246246
// Greet existing participants
247247
{
248-
auto remotes = room.remoteParticipants();
248+
auto remotes = room->remoteParticipants();
249249
for (const auto &rp : remotes) {
250250
if (!rp)
251251
continue;
252252
std::cout << "Remote: " << rp->identity() << "\n";
253-
greetParticipant(room, rp->identity());
253+
greetParticipant(room.get(), rp->identity());
254254
}
255255
}
256256

257257
// Optionally: greet on join
258258
//
259259
// If Room API exposes a participant-connected callback, you could do:
260260
//
261-
// room.onParticipantConnected(
261+
// room->onParticipantConnected(
262262
// [&](RemoteParticipant& participant) {
263263
// std::cout << "[DataStream] participant connected: "
264264
// << participant.sid() << " " << participant.identity()
265265
// << "\n";
266-
// greetParticipant(room, participant.identity());
266+
// greetParticipant(room.get(), participant.identity());
267267
// });
268268
//
269269
// Adjust to your actual event API.
@@ -274,6 +274,9 @@ int main(int argc, char *argv[]) {
274274
}
275275

276276
std::cout << "[DataStream] Shutting down...\n";
277+
// It is important to clean up the delegate and room in order.
278+
room->setDelegate(nullptr);
279+
room.reset();
277280
FfiClient::instance().shutdown();
278281
return 0;
279282
}

examples/simple_room/main.cpp

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,40 @@ namespace {
4343
std::atomic<bool> g_running{true};
4444

4545
void printUsage(const char *prog) {
46-
std::cerr << "Usage:\n"
47-
<< " " << prog << " <ws-url> <token>\n"
48-
<< "or:\n"
49-
<< " " << prog << " --url=<ws-url> --token=<token>\n"
50-
<< " " << prog << " --url <ws-url> --token <token>\n\n"
51-
<< "Env fallbacks:\n"
52-
<< " LIVEKIT_URL, LIVEKIT_TOKEN\n";
46+
std::cerr
47+
<< "Usage:\n"
48+
<< " " << prog
49+
<< " <ws-url> <token> [--enable_e2ee] [--e2ee_key <key>]\n"
50+
<< "or:\n"
51+
<< " " << prog
52+
<< " --url=<ws-url> --token=<token> [--enable_e2ee] [--e2ee_key=<key>]\n"
53+
<< " " << prog
54+
<< " --url <ws-url> --token <token> [--enable_e2ee] [--e2ee_key "
55+
"<key>]\n\n"
56+
<< "E2EE:\n"
57+
<< " --enable_e2ee Enable end-to-end encryption (E2EE)\n"
58+
<< " --e2ee_key <key> Optional shared key (UTF-8). If omitted, "
59+
"E2EE is enabled\n"
60+
<< " but no shared key is set (advanced "
61+
"usage).\n\n"
62+
<< "Env fallbacks:\n"
63+
<< " LIVEKIT_URL, LIVEKIT_TOKEN, LIVEKIT_E2EE_KEY\n";
5364
}
5465

5566
void handleSignal(int) { g_running.store(false); }
5667

57-
bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
58-
// 1) --help
68+
bool parseArgs(int argc, char *argv[], std::string &url, std::string &token,
69+
bool &enable_e2ee, std::string &e2ee_key) {
70+
enable_e2ee = false;
71+
// --help
5972
for (int i = 1; i < argc; ++i) {
6073
std::string a = argv[i];
6174
if (a == "-h" || a == "--help") {
6275
return false;
6376
}
6477
}
6578

66-
// 2) flags: --url= / --token= or split form
79+
// flags: --url= / --token= or split form
6780
auto get_flag_value = [&](const std::string &name, int &i) -> std::string {
6881
std::string arg = argv[i];
6982
const std::string eq = name + "=";
@@ -79,18 +92,24 @@ bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
7992

8093
for (int i = 1; i < argc; ++i) {
8194
const std::string a = argv[i];
82-
if (a.rfind("--url", 0) == 0) {
95+
if (a == "--enable_e2ee") {
96+
enable_e2ee = true;
97+
} else if (a.rfind("--url", 0) == 0) {
8398
auto v = get_flag_value("--url", i);
8499
if (!v.empty())
85100
url = v;
86101
} else if (a.rfind("--token", 0) == 0) {
87102
auto v = get_flag_value("--token", i);
88103
if (!v.empty())
89104
token = v;
105+
} else if (a.rfind("--e2ee_key", 0) == 0) {
106+
auto v = get_flag_value("--e2ee_key", i);
107+
if (!v.empty())
108+
e2ee_key = v;
90109
}
91110
}
92111

93-
// 3) positional if still empty
112+
// positional if still empty
94113
if (url.empty() || token.empty()) {
95114
std::vector<std::string> pos;
96115
for (int i = 1; i < argc; ++i) {
@@ -118,6 +137,11 @@ bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
118137
if (e)
119138
token = e;
120139
}
140+
if (e2ee_key.empty()) {
141+
const char *e = std::getenv("LIVEKIT_E2EE_KEY");
142+
if (e)
143+
e2ee_key = e;
144+
}
121145

122146
return !(url.empty() || token.empty());
123147
}
@@ -211,11 +235,17 @@ class SimpleRoomDelegate : public livekit::RoomDelegate {
211235
SDLMediaManager &media_;
212236
};
213237

238+
static std::vector<std::uint8_t> toBytes(const std::string &s) {
239+
return std::vector<std::uint8_t>(s.begin(), s.end());
240+
}
241+
214242
} // namespace
215243

216244
int main(int argc, char *argv[]) {
217245
std::string url, token;
218-
if (!parseArgs(argc, argv, url, token)) {
246+
bool enable_e2ee = false;
247+
std::string e2ee_key;
248+
if (!parseArgs(argc, argv, url, token, enable_e2ee, e2ee_key)) {
219249
printUsage(argv[0]);
220250
return 1;
221251
}
@@ -240,22 +270,41 @@ int main(int argc, char *argv[]) {
240270
// Handle Ctrl-C to exit the idle loop
241271
std::signal(SIGINT, handleSignal);
242272

243-
livekit::Room room{};
273+
auto room = std::make_unique<livekit::Room>();
244274
SimpleRoomDelegate delegate(media);
245-
room.setDelegate(&delegate);
275+
room->setDelegate(&delegate);
246276

247277
RoomOptions options;
248278
options.auto_subscribe = true;
249279
options.dynacast = false;
250-
bool res = room.Connect(url, token, options);
280+
281+
if (enable_e2ee) {
282+
livekit::E2EEOptions e2ee;
283+
e2ee.encryption_type = livekit::EncryptionType::GCM;
284+
// Optional shared key: if empty, we enable E2EE without setting a shared
285+
// key. (Advanced use: keys can be set/ratcheted later via
286+
// E2EEManager/KeyProvider.)
287+
if (!e2ee_key.empty()) {
288+
e2ee.shared_key = toBytes(e2ee_key);
289+
}
290+
options.e2ee = e2ee;
291+
if (!e2ee_key.empty()) {
292+
std::cout << "[E2EE] enabled : (shared key length=" << e2ee_key.size()
293+
<< ")\n";
294+
} else {
295+
std::cout << "[E2EE] enabled: (no shared key set)\n";
296+
}
297+
}
298+
299+
bool res = room->Connect(url, token, options);
251300
std::cout << "Connect result is " << std::boolalpha << res << std::endl;
252301
if (!res) {
253302
std::cerr << "Failed to connect to room\n";
254303
FfiClient::instance().shutdown();
255304
return 1;
256305
}
257306

258-
auto info = room.room_info();
307+
auto info = room->room_info();
259308
std::cout << "Connected to room:\n"
260309
<< " SID: " << (info.sid ? *info.sid : "(none)") << "\n"
261310
<< " Name: " << info.name << "\n"
@@ -286,7 +335,7 @@ int main(int argc, char *argv[]) {
286335
try {
287336
// publishTrack takes std::shared_ptr<Track>, LocalAudioTrack derives from
288337
// Track
289-
audioPub = room.localParticipant()->publishTrack(audioTrack, audioOpts);
338+
audioPub = room->localParticipant()->publishTrack(audioTrack, audioOpts);
290339

291340
std::cout << "Published track:\n"
292341
<< " SID: " << audioPub->sid() << "\n"
@@ -314,7 +363,7 @@ int main(int argc, char *argv[]) {
314363
try {
315364
// publishTrack takes std::shared_ptr<Track>, LocalAudioTrack derives from
316365
// Track
317-
videoPub = room.localParticipant()->publishTrack(videoTrack, videoOpts);
366+
videoPub = room->localParticipant()->publishTrack(videoTrack, videoOpts);
318367

319368
std::cout << "Published track:\n"
320369
<< " SID: " << videoPub->sid() << "\n"
@@ -341,12 +390,16 @@ int main(int argc, char *argv[]) {
341390
media.stopMic();
342391

343392
// Clean up the audio track publishment
344-
room.localParticipant()->unpublishTrack(audioPub->sid());
393+
room->localParticipant()->unpublishTrack(audioPub->sid());
345394

346395
media.stopCamera();
347396

348397
// Clean up the video track publishment
349-
room.localParticipant()->unpublishTrack(videoPub->sid());
398+
room->localParticipant()->unpublishTrack(videoPub->sid());
399+
400+
// Must be cleaned up before FfiClient::instance().shutdown();
401+
room->setDelegate(nullptr);
402+
room.reset();
350403

351404
FfiClient::instance().shutdown();
352405
std::cout << "Exiting.\n";

0 commit comments

Comments
 (0)