Skip to content

Commit 07c7dc9

Browse files
committed
wip.
1 parent 9a286b8 commit 07c7dc9

3 files changed

Lines changed: 121 additions & 8 deletions

File tree

lib/src/core/engine.dart

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'package:flutter/foundation.dart' hide internal;
2121
import 'package:collection/collection.dart';
2222
import 'package:connectivity_plus/connectivity_plus.dart';
2323
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
24+
import 'package:livekit_client/livekit_client.dart';
2425
import 'package:meta/meta.dart';
2526

2627
import '../events.dart';
@@ -149,6 +150,8 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
149150

150151
List<lk_models.Codec>? get enabledPublishCodecs => _enabledPublishCodecs;
151152

153+
E2EEManager? _e2eeManager;
154+
152155
void clearReconnectTimeout() {
153156
if (reconnectTimeout != null) {
154157
reconnectTimeout?.cancel();
@@ -313,6 +316,7 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
313316
completer.completeError('Engine disconnected');
314317
}
315318
}
319+
316320
events.once<EngineClosingEvent>((e) => onClosing());
317321

318322
while (!_dcBufferStatus[kind]!) {
@@ -333,8 +337,7 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
333337
bool? reliability = true,
334338
}) async {
335339
// construct the data channel message
336-
final message =
337-
rtc.RTCDataChannelMessage.fromBinary(packet.writeToBuffer());
340+
var message = rtc.RTCDataChannelMessage.fromBinary(packet.writeToBuffer());
338341

339342
final reliabilityType =
340343
reliability == true ? Reliability.reliable : Reliability.lossy;
@@ -364,6 +367,28 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
364367
'Data channel for ${packet.kind.toSDKType()} is null');
365368
}
366369

370+
if (_e2eeManager != null && _e2eeManager!.isDataChannelEncryptionEnabled) {
371+
final encryptablePacket = asEncryptablePacket(packet);
372+
if (encryptablePacket != null) {
373+
final encryptedData = await _e2eeManager?.encryptData(
374+
data: encryptablePacket.writeToBuffer());
375+
376+
if (encryptedData == null) {
377+
logger.warning('Failed to encrypt data packet');
378+
return;
379+
}
380+
381+
final packet = lk_models.EncryptedPacket(
382+
encryptionType: lk_models.Encryption_Type.GCM,
383+
encryptedValue: encryptedData.data,
384+
iv: encryptedData.iv,
385+
keyIndex: encryptedData.keyIndex,
386+
);
387+
388+
message = rtc.RTCDataChannelMessage.fromBinary(packet.writeToBuffer());
389+
}
390+
}
391+
367392
logger.fine('sendDataPacket(label:${channel.label})');
368393
await channel.send(message);
369394

@@ -390,6 +415,22 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
390415
}
391416
}
392417

418+
lk_models.EncryptedPacketPayload? asEncryptablePacket(
419+
lk_models.DataPacket packet) {
420+
if ([
421+
lk_models.DataPacket_Value.sipDtmf,
422+
lk_models.DataPacket_Value.metrics,
423+
lk_models.DataPacket_Value.speaker,
424+
lk_models.DataPacket_Value.transcription,
425+
lk_models.DataPacket_Value.encryptedPacket
426+
].contains(packet.whichValue()) ==
427+
false) {
428+
return lk_models.EncryptedPacketPayload.fromBuffer(
429+
packet.writeToBuffer());
430+
}
431+
return null;
432+
}
433+
393434
Future<RTCConfiguration> _buildRtcConfiguration(
394435
{required lk_models.ClientConfigSetting serverResponseForceRelay,
395436
required List<RTCIceServer> serverProvidedIceServers}) async {
@@ -636,7 +677,7 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
636677
}
637678
}
638679

639-
void _onDCMessage(rtc.RTCDataChannelMessage message) {
680+
void _onDCMessage(rtc.RTCDataChannelMessage message) async {
640681
// always expect binary
641682
if (!message.isBinary) {
642683
logger.warning('Data message is not binary');
@@ -711,6 +752,28 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
711752
identity: dp.participantIdentity,
712753
),
713754
);
755+
} else if (dp.whichValue() == lk_models.DataPacket_Value.encryptedPacket) {
756+
if (_e2eeManager != null) {
757+
logger.warning('Received encrypted packet but E2EE not set up');
758+
return;
759+
}
760+
final decryptedData = await _e2eeManager?.handleEncryptedData(
761+
data: Uint8List.fromList(dp.encryptedPacket.encryptedValue),
762+
iv: Uint8List.fromList(dp.encryptedPacket.iv),
763+
participantIdentity: dp.participantIdentity,
764+
keyIndex: dp.encryptedPacket.keyIndex,
765+
);
766+
if (decryptedData == null) {
767+
logger.warning('Failed to decrypt data packet');
768+
return;
769+
}
770+
final newDp = lk_models.DataPacket.fromBuffer(decryptedData);
771+
// User packet
772+
events.emit(EngineDataPacketReceivedEvent(
773+
packet: newDp.user,
774+
kind: newDp.kind,
775+
identity: newDp.participantIdentity,
776+
));
714777
} else {
715778
logger.warning('Unknown data packet type: ${dp.whichValue()}');
716779
}

lib/src/e2ee/e2ee_manager.dart

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class E2EEManager {
3131
final Map<Map<String, String>, FrameCryptor> _frameCryptors = {};
3232
final BaseKeyProvider _keyProvider;
3333
final Algorithm _algorithm = Algorithm.kAesGcm;
34+
DataPacketCryptor? _dataPacketCryptor;
3435
bool _enabled = true;
3536
EventsListener<RoomEvent>? _listener;
3637
E2EEManager(this._keyProvider);
@@ -117,6 +118,10 @@ class E2EEManager {
117118
}
118119
}
119120
});
121+
122+
_dataPacketCryptor ??=
123+
await dataPacketCryptorFactory.createDataPacketCryptor(
124+
algorithm: _algorithm, keyProvider: _keyProvider.keyProvider);
120125
}
121126
}
122127

@@ -144,17 +149,21 @@ class E2EEManager {
144149
await frameCryptor.dispose();
145150
}
146151
_frameCryptors.clear();
152+
153+
await _dataPacketCryptor?.dispose();
154+
_dataPacketCryptor = null;
147155
}
148156

149157
Future<FrameCryptor> _addRtpSender(
150158
{required RTCRtpSender sender,
151159
required String identity,
152160
required String sid}) async {
153-
final frameCryptor = await frameCryptorFactory.createFrameCryptorForRtpSender(
154-
participantId: identity,
155-
sender: sender,
156-
algorithm: _algorithm,
157-
keyProvider: _keyProvider.keyProvider);
161+
final frameCryptor =
162+
await frameCryptorFactory.createFrameCryptorForRtpSender(
163+
participantId: identity,
164+
sender: sender,
165+
algorithm: _algorithm,
166+
keyProvider: _keyProvider.keyProvider);
158167
_frameCryptors[{identity: sid}] = frameCryptor;
159168
await frameCryptor.setEnabled(_enabled);
160169
logger.info(
@@ -237,4 +246,40 @@ class E2EEManager {
237246
return E2EEState.kKeyRatcheted;
238247
}
239248
}
249+
250+
bool get isDataChannelEncryptionEnabled =>
251+
_room?.roomOptions.encryption != null;
252+
253+
Future<Uint8List?> handleEncryptedData({
254+
required Uint8List data,
255+
required Uint8List iv,
256+
required String participantIdentity,
257+
required int keyIndex,
258+
}) async {
259+
if (_dataPacketCryptor == null) {
260+
throw Exception('DataPacketCryptor is not initialized');
261+
}
262+
try {
263+
final decryptedData = await _dataPacketCryptor!.decrypt(
264+
participantId: participantIdentity,
265+
encryptedPacket:
266+
EncryptedPacket(data: data, keyIndex: keyIndex, iv: iv),
267+
);
268+
return decryptedData;
269+
} catch (e) {
270+
logger.warning('handleEncryptedData error: $e');
271+
return null;
272+
}
273+
}
274+
275+
Future<EncryptedPacket> encryptData({required Uint8List data}) async {
276+
final participantId = _room?.localParticipant?.identity;
277+
if (participantId == null || _dataPacketCryptor == null) {
278+
throw Exception('DataPacketCryptor is not initialized');
279+
}
280+
return await _dataPacketCryptor!.encrypt(
281+
participantId: participantId,
282+
keyIndex: _keyProvider.getLatestIndex(participantId),
283+
data: data);
284+
}
240285
}

lib/src/options.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,11 @@ class RoomOptions {
110110
final bool stopLocalTrackOnUnpublish;
111111

112112
/// Options for end-to-end encryption.
113+
@Deprecated('Use encryption instead')
113114
final E2EEOptions? e2eeOptions;
114115

116+
final E2EEOptions? encryption;
117+
115118
/// fast track publication
116119
final bool fastPublish;
117120

@@ -131,6 +134,7 @@ class RoomOptions {
131134
this.dynacast = false,
132135
this.stopLocalTrackOnUnpublish = true,
133136
this.e2eeOptions,
137+
this.encryption,
134138
this.enableVisualizer = false,
135139
this.fastPublish = true,
136140
});
@@ -166,6 +170,7 @@ class RoomOptions {
166170
stopLocalTrackOnUnpublish:
167171
stopLocalTrackOnUnpublish ?? this.stopLocalTrackOnUnpublish,
168172
e2eeOptions: e2eeOptions ?? this.e2eeOptions,
173+
encryption: encryption ?? this.encryption,
169174
fastPublish: fastPublish ?? this.fastPublish,
170175
);
171176
}

0 commit comments

Comments
 (0)