Skip to content

Commit 8b4f110

Browse files
authored
Improvements for framecryptor (#363)
* update protocol. * improve for framecryptor. * dart format. * update. * no need to setup frame cryptor if encryptionType is None. * Skip frame encryption for AV1,VP9. * fix import sorter. * bump version for flutter-webrtc. * update default options for e2ee. * release: 1.5.0. * update. * Downgrade some pub versions for maximum compatibility.
1 parent badb77d commit 8b4f110

21 files changed

+3799
-4084
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# CHANGELOG
22

3+
## 1.5.0
4+
5+
* Update default bitrates according to VMAF guide
6+
* Support multi-codec simulcast.
7+
* Support SVC publishing with AV1/VP9.
8+
* More robustness for E2EE.
9+
* Configurable Audio Modes for Android.
10+
311
## 1.4.3
412

513
* Fix: remove js_bindings and use the built-in AudioContext for js interop to support flutter 3.13.0.

example/lib/pages/connect.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class _ConnectPageState extends State<ConnectPage> {
135135
final keyProvider = await BaseKeyProvider.create();
136136
e2eeOptions = E2EEOptions(keyProvider: keyProvider);
137137
var sharedKey = _sharedKeyCtrl.text;
138-
await keyProvider.setKey(sharedKey);
138+
await keyProvider.setSharedKey(sharedKey);
139139
}
140140

141141
String preferredCodec = 'VP8';

example/lib/widgets/participant.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,7 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget>
169169
firstAudioPublication?.subscribed == true,
170170
connectionQuality: widget.participant.connectionQuality,
171171
isScreenShare: widget.isScreenShare,
172-
enabledE2EE: widget.participant.firstTrackEncryptionType !=
173-
EncryptionType.kNone,
172+
enabledE2EE: widget.participant.isEncrypted,
174173
),
175174
],
176175
),

ios/livekit_client.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ Pod::Spec.new do |s|
1616
s.static_framework = true
1717

1818
s.dependency 'Flutter'
19-
s.dependency 'WebRTC-SDK', '114.5735.06'
19+
s.dependency 'WebRTC-SDK', '114.5735.07'
2020
end

lib/src/core/engine.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,12 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
204204
Iterable<lk_models.VideoLayer>? videoLayers,
205205
Iterable<lk_rtc.SimulcastCodec>? simulcastCodecs,
206206
String? sid,
207+
String? videoCodec,
207208
}) async {
208209
// TODO: Check if cid already published
209210

210211
lk_models.Encryption_Type encryptionType = lk_models.Encryption_Type.NONE;
211-
if (roomOptions.e2eeOptions != null) {
212+
if (roomOptions.e2eeOptions != null && !isSVCCodec(videoCodec ?? '')) {
212213
switch (roomOptions.e2eeOptions!.encryptionType) {
213214
case EncryptionType.kNone:
214215
encryptionType = lk_models.Encryption_Type.NONE;

lib/src/core/room.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
255255
_getOrCreateRemoteParticipant(info.sid, info);
256256
}
257257

258+
if (e2eeManager != null && event.response.sifTrailer.isNotEmpty) {
259+
e2eeManager!.keyProvider
260+
.setSifTrailer(Uint8List.fromList(event.response.sifTrailer));
261+
}
262+
258263
logger.fine('Room Connect completed');
259264
})
260265
..on<SignalParticipantUpdateEvent>(

lib/src/e2ee/e2ee_manager.dart

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,16 @@ import 'package:flutter_webrtc/flutter_webrtc.dart';
1818

1919
import '../core/room.dart';
2020
import '../e2ee/events.dart';
21+
import '../e2ee/options.dart';
2122
import '../events.dart';
2223
import '../extensions.dart';
2324
import '../managers/event.dart';
25+
import '../utils.dart';
2426
import 'key_provider.dart';
2527

2628
class E2EEManager {
2729
Room? _room;
28-
final Map<String, FrameCryptor> _frameCryptors = {};
29-
final List<FrameCryptor> _senderFrameCryptors = [];
30+
final Map<Map<String, String>, FrameCryptor> _frameCryptors = {};
3031
final BaseKeyProvider _keyProvider;
3132
final Algorithm _algorithm = Algorithm.kAesGcm;
3233
bool _enabled = true;
@@ -40,13 +41,15 @@ class E2EEManager {
4041
_listener = _room!.createListener();
4142
_listener!
4243
..on<LocalTrackPublishedEvent>((event) async {
43-
var trackId = event.publication.sid;
44-
var participantId = event.participant.sid;
44+
if (event.publication.encryptionType == EncryptionType.kNone ||
45+
isSVCCodec(event.publication.track?.codec ?? '')) {
46+
// no need to setup frame cryptor
47+
return;
48+
}
4549
var frameCryptor = await _addRtpSender(
46-
event.publication.track!.sender!,
47-
participantId,
48-
trackId,
49-
event.publication.track!.kind.name.toLowerCase());
50+
sender: event.publication.track!.sender!,
51+
identity: event.participant.identity,
52+
sid: event.publication.sid);
5053
if (kIsWeb && event.publication.track!.codec != null) {
5154
await frameCryptor.updateCodec(event.publication.track!.codec!);
5255
}
@@ -63,21 +66,30 @@ class E2EEManager {
6366
state: _e2eeStateFromFrameCryptoState(state),
6467
));
6568
};
66-
_senderFrameCryptors.add(frameCryptor);
6769
})
6870
..on<LocalTrackUnpublishedEvent>((event) async {
69-
var trackId = event.publication.sid;
70-
var frameCryptor = _frameCryptors.remove(trackId);
71-
_senderFrameCryptors.remove(frameCryptor);
72-
await frameCryptor?.dispose();
71+
for (var key in _frameCryptors.keys.toList()) {
72+
if (key.keys.first == event.participant.identity &&
73+
key.values.first == event.publication.sid) {
74+
var frameCryptor = _frameCryptors.remove(key);
75+
await frameCryptor?.setEnabled(false);
76+
await frameCryptor?.dispose();
77+
}
78+
}
7379
})
7480
..on<TrackSubscribedEvent>((event) async {
75-
var trackId = event.publication.sid;
76-
var participantId = event.participant.sid;
77-
var frameCryptor = await _addRtpReceiver(event.track.receiver!,
78-
participantId, trackId, event.track.kind.name.toLowerCase());
81+
var codec = event.publication.mimeType.split('/')[1];
82+
if (event.publication.encryptionType == EncryptionType.kNone ||
83+
isSVCCodec(codec)) {
84+
// no need to setup frame cryptor
85+
return;
86+
}
87+
var frameCryptor = await _addRtpReceiver(
88+
receiver: event.track.receiver!,
89+
identity: event.participant.identity,
90+
sid: event.publication.sid,
91+
);
7992
if (kIsWeb) {
80-
var codec = event.publication.mimeType.split('/')[1];
8193
await frameCryptor.updateCodec(codec.toLowerCase());
8294
}
8395
frameCryptor.onFrameCryptorStateChanged = (trackId, state) {
@@ -95,16 +107,28 @@ class E2EEManager {
95107
};
96108
})
97109
..on<TrackUnsubscribedEvent>((event) async {
98-
var trackId = event.publication.sid;
99-
var frameCryptor = _frameCryptors.remove(trackId);
100-
await frameCryptor?.dispose();
110+
for (var key in _frameCryptors.keys.toList()) {
111+
if (key.keys.first == event.participant.identity &&
112+
key.values.first == event.publication.sid) {
113+
var frameCryptor = _frameCryptors.remove(key);
114+
await frameCryptor?.setEnabled(false);
115+
await frameCryptor?.dispose();
116+
}
117+
}
101118
});
102119
}
103120
}
104121

105-
Future<void> ratchetKey() async {
106-
for (var frameCryptor in _senderFrameCryptors) {
107-
var newKey = await _keyProvider.ratchetKey(frameCryptor.participantId, 0);
122+
BaseKeyProvider get keyProvider => _keyProvider;
123+
124+
Future<void> ratchetKey({String? participantId, int? keyIndex}) async {
125+
if (participantId != null) {
126+
var newKey = await _keyProvider.ratchetKey(participantId, keyIndex);
127+
if (kDebugMode) {
128+
print('newKey: $newKey');
129+
}
130+
} else {
131+
var newKey = await _keyProvider.ratchetSharedKey(keyIndex: keyIndex);
108132
if (kDebugMode) {
109133
print('newKey: $newKey');
110134
}
@@ -121,54 +145,41 @@ class E2EEManager {
121145
_frameCryptors.clear();
122146
}
123147

124-
Future<FrameCryptor> _addRtpSender(RTCRtpSender sender, String participantId,
125-
String trackId, String kind) async {
126-
var pid = '$kind-sender-$participantId-$trackId';
148+
Future<FrameCryptor> _addRtpSender(
149+
{required RTCRtpSender sender,
150+
required String identity,
151+
required String sid}) async {
127152
var frameCryptor = await frameCryptorFactory.createFrameCryptorForRtpSender(
128-
participantId: pid,
153+
participantId: identity,
129154
sender: sender,
130155
algorithm: _algorithm,
131156
keyProvider: _keyProvider.keyProvider);
132-
_frameCryptors[trackId] = frameCryptor;
157+
_frameCryptors[{identity: sid}] = frameCryptor;
133158
await frameCryptor.setEnabled(_enabled);
134-
if (_keyProvider.options.sharedKey) {
135-
await _keyProvider.keyProvider
136-
.setKey(participantId: pid, index: 0, key: _keyProvider.sharedKey!);
137-
await frameCryptor.setKeyIndex(0);
138-
}
159+
await frameCryptor.setKeyIndex(0);
139160
return frameCryptor;
140161
}
141162

142-
Future<FrameCryptor> _addRtpReceiver(RTCRtpReceiver receiver,
143-
String participantId, String trackId, String kind) async {
144-
var pid = '$kind-receiver-$participantId-$trackId';
163+
Future<FrameCryptor> _addRtpReceiver(
164+
{required RTCRtpReceiver receiver,
165+
required String identity,
166+
required String sid}) async {
145167
var frameCryptor =
146168
await frameCryptorFactory.createFrameCryptorForRtpReceiver(
147-
participantId: pid,
169+
participantId: identity,
148170
receiver: receiver,
149171
algorithm: _algorithm,
150172
keyProvider: _keyProvider.keyProvider);
151-
_frameCryptors[trackId] = frameCryptor;
173+
_frameCryptors[{identity: sid}] = frameCryptor;
152174
await frameCryptor.setEnabled(_enabled);
153-
if (_keyProvider.options.sharedKey) {
154-
await _keyProvider.keyProvider
155-
.setKey(participantId: pid, index: 0, key: _keyProvider.sharedKey!);
156-
await frameCryptor.setKeyIndex(0);
157-
}
175+
await frameCryptor.setKeyIndex(0);
158176
return frameCryptor;
159177
}
160178

161179
Future<void> setEnabled(bool enabled) async {
162180
_enabled = enabled;
163181
for (var frameCryptor in _frameCryptors.entries) {
164182
await frameCryptor.value.setEnabled(enabled);
165-
if (_keyProvider.options.sharedKey) {
166-
await _keyProvider.keyProvider.setKey(
167-
participantId: frameCryptor.key,
168-
index: 0,
169-
key: _keyProvider.sharedKey!);
170-
await frameCryptor.value.setKeyIndex(0);
171-
}
172183
}
173184
}
174185

lib/src/e2ee/key_provider.dart

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
1818

1919
const defaultRatchetSalt = 'LKFrameEncryptionKey';
2020
const defaultMagicBytes = 'LK-ROCKS';
21-
const defaultRatchetWindowSize = 16;
21+
const defaultRatchetWindowSize = 0;
22+
const defaultFailureTolerance = -1;
2223

2324
class KeyInfo {
2425
final String participantId;
@@ -32,8 +33,13 @@ class KeyInfo {
3233
}
3334

3435
abstract class KeyProvider {
35-
Future<void> setKey(String key, {String? participantId, int keyIndex = 0});
36-
Future<Uint8List> ratchetKey(String participantId, int index);
36+
Future<void> setSharedKey(String key, {int? keyIndex});
37+
Future<Uint8List> ratchetSharedKey({int? keyIndex});
38+
Future<Uint8List> exportSharedKey({int? keyIndex});
39+
Future<void> setKey(String key, {String? participantId, int? keyIndex});
40+
Future<Uint8List> ratchetKey(String participantId, int? keyIndex);
41+
Future<Uint8List> exportKey(String participantId, int? keyIndex);
42+
Future<void> setSifTrailer(Uint8List trailer);
3743
rtc.KeyProvider get keyProvider;
3844
}
3945

@@ -54,6 +60,7 @@ class BaseKeyProvider implements KeyProvider {
5460
String? ratchetSalt,
5561
String? uncryptedMagicBytes,
5662
int? ratchetWindowSize,
63+
int? failureTolerance,
5764
}) async {
5865
rtc.KeyProviderOptions options = rtc.KeyProviderOptions(
5966
sharedKey: sharedKey,
@@ -62,26 +69,56 @@ class BaseKeyProvider implements KeyProvider {
6269
ratchetWindowSize: ratchetWindowSize ?? defaultRatchetWindowSize,
6370
uncryptedMagicBytes: Uint8List.fromList(
6471
(uncryptedMagicBytes ?? defaultMagicBytes).codeUnits),
72+
failureTolerance: failureTolerance ?? defaultFailureTolerance,
6573
);
6674
final keyProvider =
6775
await rtc.frameCryptorFactory.createDefaultKeyProvider(options);
6876
return BaseKeyProvider(keyProvider, options);
6977
}
7078

7179
@override
72-
Future<Uint8List> ratchetKey(String participantId, int index) =>
73-
_keyProvider.ratchetKey(participantId: participantId, index: index);
80+
Future<void> setSharedKey(String key, {int? keyIndex}) async {
81+
_sharedKey = Uint8List.fromList(key.codeUnits);
82+
return _keyProvider.setSharedKey(key: _sharedKey!, index: keyIndex ?? 0);
83+
}
84+
85+
@override
86+
Future<Uint8List> ratchetSharedKey({int? keyIndex}) async {
87+
if (_sharedKey == null) {
88+
throw Exception('shared key not set');
89+
}
90+
_sharedKey = await _keyProvider.ratchetSharedKey(index: keyIndex ?? 0);
91+
return _sharedKey!;
92+
}
93+
94+
@override
95+
Future<Uint8List> exportSharedKey({int? keyIndex}) async {
96+
if (_sharedKey == null) {
97+
throw Exception('shared key not set');
98+
}
99+
return _keyProvider.exportSharedKey(index: keyIndex ?? 0);
100+
}
101+
102+
@override
103+
Future<Uint8List> ratchetKey(String participantId, int? keyIndex) =>
104+
_keyProvider.ratchetKey(
105+
participantId: participantId, index: keyIndex ?? 0);
106+
107+
@override
108+
Future<Uint8List> exportKey(String participantId, int? keyIndex) =>
109+
_keyProvider.exportKey(
110+
participantId: participantId, index: keyIndex ?? 0);
74111

75112
@override
76113
Future<void> setKey(String key,
77-
{String? participantId, int keyIndex = 0}) async {
114+
{String? participantId, int? keyIndex}) async {
78115
if (options.sharedKey) {
79116
_sharedKey = Uint8List.fromList(key.codeUnits);
80117
return;
81118
}
82119
final keyInfo = KeyInfo(
83120
participantId: participantId ?? '',
84-
keyIndex: keyIndex,
121+
keyIndex: keyIndex ?? 0,
85122
key: Uint8List.fromList(key.codeUnits),
86123
);
87124
return _setKey(keyInfo);
@@ -98,4 +135,9 @@ class BaseKeyProvider implements KeyProvider {
98135
key: keyInfo.key,
99136
);
100137
}
138+
139+
@override
140+
Future<void> setSifTrailer(Uint8List trailer) async {
141+
return _keyProvider.setSifTrailer(trailer: trailer);
142+
}
101143
}

0 commit comments

Comments
 (0)