Skip to content

Commit 6d593e5

Browse files
Get E2EE functional (#18)
* Get E2EE functional * update the code to adapt the style / apis like other sdk
1 parent 0484c7c commit 6d593e5

File tree

17 files changed

+656
-129
lines changed

17 files changed

+656
-129
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
@@ -185,6 +186,7 @@ add_library(livekit
185186
src/audio_source.cpp
186187
src/audio_stream.cpp
187188
src/data_stream.cpp
189+
src/e2ee.cpp
188190
src/ffi_handle.cpp
189191
src/ffi_client.cpp
190192
src/ffi_client.h

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: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
#include <vector>
1313

1414
#include "livekit/livekit.h"
15-
#include "livekit_ffi.h"
15+
// TODO, remove the ffi_client from the public usage.
16+
#include "ffi_client.h"
1617

1718
using namespace livekit;
1819

@@ -46,10 +47,10 @@ std::string randomHexId(std::size_t nbytes = 16) {
4647
}
4748

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

52-
LocalParticipant *lp = room.localParticipant();
53+
LocalParticipant *lp = room->localParticipant();
5354
if (!lp) {
5455
std::cerr << "[DataStream] No local participant, cannot greet.\n";
5556
return;
@@ -209,33 +210,33 @@ int main(int argc, char *argv[]) {
209210
std::signal(SIGTERM, handleSignal);
210211
#endif
211212

212-
Room room{};
213+
auto room = std::make_unique<Room>();
213214
RoomOptions options;
214215
options.auto_subscribe = true;
215216
options.dynacast = false;
216217

217-
bool ok = room.Connect(url, token, options);
218+
bool ok = room->Connect(url, token, options);
218219
std::cout << "[DataStream] Connect result: " << std::boolalpha << ok << "\n";
219220
if (!ok) {
220221
std::cerr << "[DataStream] Failed to connect to room\n";
221222
FfiClient::instance().shutdown();
222223
return 1;
223224
}
224225

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

229230
// Register stream handlers
230-
room.registerTextStreamHandler(
231+
room->registerTextStreamHandler(
231232
"chat", [](std::shared_ptr<TextStreamReader> reader,
232233
const std::string &participant_identity) {
233234
std::thread t(handleChatMessage, std::move(reader),
234235
participant_identity);
235236
t.detach();
236237
});
237238

238-
room.registerByteStreamHandler(
239+
room->registerByteStreamHandler(
239240
"files", [](std::shared_ptr<ByteStreamReader> reader,
240241
const std::string &participant_identity) {
241242
std::thread t(handleWelcomeImage, std::move(reader),
@@ -245,25 +246,25 @@ int main(int argc, char *argv[]) {
245246

246247
// Greet existing participants
247248
{
248-
auto remotes = room.remoteParticipants();
249+
auto remotes = room->remoteParticipants();
249250
for (const auto &rp : remotes) {
250251
if (!rp)
251252
continue;
252253
std::cout << "Remote: " << rp->identity() << "\n";
253-
greetParticipant(room, rp->identity());
254+
greetParticipant(room.get(), rp->identity());
254255
}
255256
}
256257

257258
// Optionally: greet on join
258259
//
259260
// If Room API exposes a participant-connected callback, you could do:
260261
//
261-
// room.onParticipantConnected(
262+
// room->onParticipantConnected(
262263
// [&](RemoteParticipant& participant) {
263264
// std::cout << "[DataStream] participant connected: "
264265
// << participant.sid() << " " << participant.identity()
265266
// << "\n";
266-
// greetParticipant(room, participant.identity());
267+
// greetParticipant(room.get(), participant.identity());
267268
// });
268269
//
269270
// Adjust to your actual event API.
@@ -274,6 +275,9 @@ int main(int argc, char *argv[]) {
274275
}
275276

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

examples/simple_room/main.cpp

Lines changed: 83 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@
2929
#include "livekit/livekit.h"
3030
#include "sdl_media_manager.h"
3131
#include "wav_audio_source.h"
32-
33-
// TODO(shijing), remove this livekit_ffi.h as it should be internal only.
34-
#include "livekit_ffi.h"
32+
// TODO, remove the ffi_client from the public usage.
33+
#include "ffi_client.h"
3534

3635
// Consider expose this video_utils.h to public ?
3736
#include "video_utils.h"
@@ -43,27 +42,40 @@ namespace {
4342
std::atomic<bool> g_running{true};
4443

4544
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";
45+
std::cerr
46+
<< "Usage:\n"
47+
<< " " << prog
48+
<< " <ws-url> <token> [--enable_e2ee] [--e2ee_key <key>]\n"
49+
<< "or:\n"
50+
<< " " << prog
51+
<< " --url=<ws-url> --token=<token> [--enable_e2ee] [--e2ee_key=<key>]\n"
52+
<< " " << prog
53+
<< " --url <ws-url> --token <token> [--enable_e2ee] [--e2ee_key "
54+
"<key>]\n\n"
55+
<< "E2EE:\n"
56+
<< " --enable_e2ee Enable end-to-end encryption (E2EE)\n"
57+
<< " --e2ee_key <key> Optional shared key (UTF-8). If omitted, "
58+
"E2EE is enabled\n"
59+
<< " but no shared key is set (advanced "
60+
"usage).\n\n"
61+
<< "Env fallbacks:\n"
62+
<< " LIVEKIT_URL, LIVEKIT_TOKEN, LIVEKIT_E2EE_KEY\n";
5363
}
5464

5565
void handleSignal(int) { g_running.store(false); }
5666

57-
bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
58-
// 1) --help
67+
bool parseArgs(int argc, char *argv[], std::string &url, std::string &token,
68+
bool &enable_e2ee, std::string &e2ee_key) {
69+
enable_e2ee = false;
70+
// --help
5971
for (int i = 1; i < argc; ++i) {
6072
std::string a = argv[i];
6173
if (a == "-h" || a == "--help") {
6274
return false;
6375
}
6476
}
6577

66-
// 2) flags: --url= / --token= or split form
78+
// flags: --url= / --token= or split form
6779
auto get_flag_value = [&](const std::string &name, int &i) -> std::string {
6880
std::string arg = argv[i];
6981
const std::string eq = name + "=";
@@ -79,18 +91,24 @@ bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
7991

8092
for (int i = 1; i < argc; ++i) {
8193
const std::string a = argv[i];
82-
if (a.rfind("--url", 0) == 0) {
94+
if (a == "--enable_e2ee") {
95+
enable_e2ee = true;
96+
} else if (a.rfind("--url", 0) == 0) {
8397
auto v = get_flag_value("--url", i);
8498
if (!v.empty())
8599
url = v;
86100
} else if (a.rfind("--token", 0) == 0) {
87101
auto v = get_flag_value("--token", i);
88102
if (!v.empty())
89103
token = v;
104+
} else if (a.rfind("--e2ee_key", 0) == 0) {
105+
auto v = get_flag_value("--e2ee_key", i);
106+
if (!v.empty())
107+
e2ee_key = v;
90108
}
91109
}
92110

93-
// 3) positional if still empty
111+
// positional if still empty
94112
if (url.empty() || token.empty()) {
95113
std::vector<std::string> pos;
96114
for (int i = 1; i < argc; ++i) {
@@ -118,6 +136,11 @@ bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
118136
if (e)
119137
token = e;
120138
}
139+
if (e2ee_key.empty()) {
140+
const char *e = std::getenv("LIVEKIT_E2EE_KEY");
141+
if (e)
142+
e2ee_key = e;
143+
}
121144

122145
return !(url.empty() || token.empty());
123146
}
@@ -211,11 +234,17 @@ class SimpleRoomDelegate : public livekit::RoomDelegate {
211234
SDLMediaManager &media_;
212235
};
213236

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

216243
int main(int argc, char *argv[]) {
217244
std::string url, token;
218-
if (!parseArgs(argc, argv, url, token)) {
245+
bool enable_e2ee = false;
246+
std::string e2ee_key;
247+
if (!parseArgs(argc, argv, url, token, enable_e2ee, e2ee_key)) {
219248
printUsage(argv[0]);
220249
return 1;
221250
}
@@ -240,22 +269,41 @@ int main(int argc, char *argv[]) {
240269
// Handle Ctrl-C to exit the idle loop
241270
std::signal(SIGINT, handleSignal);
242271

243-
livekit::Room room{};
272+
auto room = std::make_unique<livekit::Room>();
244273
SimpleRoomDelegate delegate(media);
245-
room.setDelegate(&delegate);
274+
room->setDelegate(&delegate);
246275

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

258-
auto info = room.room_info();
306+
auto info = room->room_info();
259307
std::cout << "Connected to room:\n"
260308
<< " SID: " << (info.sid ? *info.sid : "(none)") << "\n"
261309
<< " Name: " << info.name << "\n"
@@ -286,7 +334,7 @@ int main(int argc, char *argv[]) {
286334
try {
287335
// publishTrack takes std::shared_ptr<Track>, LocalAudioTrack derives from
288336
// Track
289-
audioPub = room.localParticipant()->publishTrack(audioTrack, audioOpts);
337+
audioPub = room->localParticipant()->publishTrack(audioTrack, audioOpts);
290338

291339
std::cout << "Published track:\n"
292340
<< " SID: " << audioPub->sid() << "\n"
@@ -314,7 +362,7 @@ int main(int argc, char *argv[]) {
314362
try {
315363
// publishTrack takes std::shared_ptr<Track>, LocalAudioTrack derives from
316364
// Track
317-
videoPub = room.localParticipant()->publishTrack(videoTrack, videoOpts);
365+
videoPub = room->localParticipant()->publishTrack(videoTrack, videoOpts);
318366

319367
std::cout << "Published track:\n"
320368
<< " SID: " << videoPub->sid() << "\n"
@@ -337,16 +385,24 @@ int main(int argc, char *argv[]) {
337385
std::this_thread::sleep_for(std::chrono::milliseconds(10));
338386
}
339387

340-
// Shutdown the audio thread.
388+
// Shutdown the audio / video capture threads.
341389
media.stopMic();
390+
media.stopCamera();
342391

343-
// Clean up the audio track publishment
344-
room.localParticipant()->unpublishTrack(audioPub->sid());
392+
// Drain any queued tasks that might still try to update the renderer /
393+
// speaker
394+
MainThreadDispatcher::update();
345395

346-
media.stopCamera();
396+
// Must be cleaned up before FfiClient::instance().shutdown();
397+
room->setDelegate(nullptr);
398+
399+
// Clean up the audio track publishment
400+
room->localParticipant()->unpublishTrack(audioPub->sid());
347401

348402
// Clean up the video track publishment
349-
room.localParticipant()->unpublishTrack(videoPub->sid());
403+
room->localParticipant()->unpublishTrack(videoPub->sid());
404+
405+
room.reset();
350406

351407
FfiClient::instance().shutdown();
352408
std::cout << "Exiting.\n";

0 commit comments

Comments
 (0)