Skip to content

Commit 1dfb135

Browse files
Fix E2EE connection failure caused by missing required protobuf fields key_ring_size and key_derivation_function in KeyProviderOptions. (#628)
1 parent a143eb3 commit 1dfb135

5 files changed

Lines changed: 234 additions & 1 deletion

File tree

livekit-rtc/livekit/rtc/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"""
2020

2121
from ._proto import stats_pb2 as stats
22-
from ._proto.e2ee_pb2 import EncryptionState, EncryptionType
22+
from ._proto.e2ee_pb2 import EncryptionState, EncryptionType, KeyDerivationFunction
2323
from ._proto.participant_pb2 import ParticipantKind, ParticipantState, DisconnectReason
2424
from ._proto.room_pb2 import (
2525
ConnectionQuality,
@@ -129,6 +129,7 @@
129129
"IceServer",
130130
"EncryptionType",
131131
"EncryptionState",
132+
"KeyDerivationFunction",
132133
"StreamState",
133134
"TrackKind",
134135
"TrackSource",

livekit-rtc/livekit/rtc/e2ee.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
DEFAULT_RATCHET_SALT = b"LKFrameEncryptionKey"
2323
DEFAULT_RATCHET_WINDOW_SIZE = 16
2424
DEFAULT_FAILURE_TOLERANCE = -1
25+
DEFAULT_KEY_RING_SIZE = 16
2526

2627

2728
@dataclass
@@ -30,6 +31,10 @@ class KeyProviderOptions:
3031
ratchet_salt: bytes = DEFAULT_RATCHET_SALT
3132
ratchet_window_size: int = DEFAULT_RATCHET_WINDOW_SIZE
3233
failure_tolerance: int = DEFAULT_FAILURE_TOLERANCE
34+
key_ring_size: int = DEFAULT_KEY_RING_SIZE
35+
key_derivation_function: proto_e2ee.KeyDerivationFunction.ValueType = (
36+
proto_e2ee.KeyDerivationFunction.PBKDF2
37+
)
3338

3439

3540
@dataclass

livekit-rtc/livekit/rtc/room.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,12 @@ def on_participant_connected(participant):
472472
req.connect.options.e2ee.key_provider_options.ratchet_window_size = (
473473
options.e2ee.key_provider_options.ratchet_window_size
474474
)
475+
req.connect.options.e2ee.key_provider_options.key_ring_size = (
476+
options.e2ee.key_provider_options.key_ring_size
477+
)
478+
req.connect.options.e2ee.key_provider_options.key_derivation_function = (
479+
options.e2ee.key_provider_options.key_derivation_function
480+
)
475481

476482
if options.encryption:
477483
req.connect.options.encryption.encryption_type = options.encryption.encryption_type
@@ -487,6 +493,12 @@ def on_participant_connected(participant):
487493
req.connect.options.encryption.key_provider_options.ratchet_window_size = (
488494
options.encryption.key_provider_options.ratchet_window_size
489495
)
496+
req.connect.options.encryption.key_provider_options.key_ring_size = (
497+
options.encryption.key_provider_options.key_ring_size
498+
)
499+
req.connect.options.encryption.key_provider_options.key_derivation_function = (
500+
options.encryption.key_provider_options.key_derivation_function
501+
)
490502

491503
if options.rtc_config:
492504
req.connect.options.rtc_config.ice_transport_type = (

livekit-rtc/tests/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2026 LiveKit, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.

livekit-rtc/tests/test_e2ee.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Copyright 2026 LiveKit, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Unit tests for E2EE functionality."""
16+
17+
18+
class TestKeyProviderOptions:
19+
"""Tests for KeyProviderOptions dataclass."""
20+
21+
def test_default_values(self):
22+
"""Test that KeyProviderOptions has correct default values."""
23+
from livekit.rtc.e2ee import (
24+
KeyProviderOptions,
25+
DEFAULT_RATCHET_SALT,
26+
DEFAULT_RATCHET_WINDOW_SIZE,
27+
DEFAULT_FAILURE_TOLERANCE,
28+
DEFAULT_KEY_RING_SIZE,
29+
)
30+
from livekit.rtc._proto import e2ee_pb2 as proto_e2ee
31+
32+
options = KeyProviderOptions()
33+
34+
assert options.shared_key is None
35+
assert options.ratchet_salt == DEFAULT_RATCHET_SALT
36+
assert options.ratchet_window_size == DEFAULT_RATCHET_WINDOW_SIZE
37+
assert options.failure_tolerance == DEFAULT_FAILURE_TOLERANCE
38+
assert options.key_ring_size == DEFAULT_KEY_RING_SIZE
39+
assert options.key_derivation_function == proto_e2ee.KeyDerivationFunction.PBKDF2
40+
41+
def test_custom_values(self):
42+
"""Test KeyProviderOptions with custom values."""
43+
from livekit.rtc.e2ee import KeyProviderOptions
44+
from livekit.rtc._proto import e2ee_pb2 as proto_e2ee
45+
46+
options = KeyProviderOptions(
47+
shared_key=b"my-secret-key",
48+
ratchet_salt=b"custom-salt",
49+
ratchet_window_size=32,
50+
failure_tolerance=5,
51+
key_ring_size=8,
52+
key_derivation_function=proto_e2ee.KeyDerivationFunction.HKDF,
53+
)
54+
55+
assert options.shared_key == b"my-secret-key"
56+
assert options.ratchet_salt == b"custom-salt"
57+
assert options.ratchet_window_size == 32
58+
assert options.failure_tolerance == 5
59+
assert options.key_ring_size == 8
60+
assert options.key_derivation_function == proto_e2ee.KeyDerivationFunction.HKDF
61+
62+
def test_various_key_lengths(self):
63+
"""Test that shared_key accepts various lengths."""
64+
from livekit.rtc.e2ee import KeyProviderOptions
65+
66+
# Short key
67+
options_short = KeyProviderOptions(shared_key=b"short")
68+
assert options_short.shared_key == b"short"
69+
70+
# Medium key
71+
options_medium = KeyProviderOptions(shared_key=b"medium-length-key-here")
72+
assert options_medium.shared_key == b"medium-length-key-here"
73+
74+
# Long key
75+
long_key = b"a" * 256
76+
options_long = KeyProviderOptions(shared_key=long_key)
77+
assert options_long.shared_key == long_key
78+
79+
# Binary key
80+
binary_key = bytes(range(256))
81+
options_binary = KeyProviderOptions(shared_key=binary_key)
82+
assert options_binary.shared_key == binary_key
83+
84+
85+
class TestE2EEOptions:
86+
"""Tests for E2EEOptions dataclass."""
87+
88+
def test_default_values(self):
89+
"""Test E2EEOptions default values."""
90+
from livekit.rtc.e2ee import E2EEOptions, KeyProviderOptions
91+
from livekit.rtc._proto import e2ee_pb2 as proto_e2ee
92+
93+
options = E2EEOptions()
94+
95+
assert isinstance(options.key_provider_options, KeyProviderOptions)
96+
assert options.encryption_type == proto_e2ee.EncryptionType.GCM
97+
98+
def test_with_shared_key(self):
99+
"""Test E2EEOptions with a shared key."""
100+
from livekit.rtc.e2ee import E2EEOptions, KeyProviderOptions
101+
102+
key_options = KeyProviderOptions(shared_key=b"test-key")
103+
options = E2EEOptions(key_provider_options=key_options)
104+
105+
assert options.key_provider_options.shared_key == b"test-key"
106+
107+
108+
class TestProtoMessageBuilding:
109+
"""Tests for proto message building with E2EE options."""
110+
111+
def test_proto_key_provider_options_fields(self):
112+
"""Test that proto KeyProviderOptions has all required fields."""
113+
from livekit.rtc._proto import e2ee_pb2 as proto_e2ee
114+
115+
proto_options = proto_e2ee.KeyProviderOptions()
116+
117+
# Set all fields that should be present
118+
proto_options.shared_key = b"test-key"
119+
proto_options.ratchet_window_size = 16
120+
proto_options.ratchet_salt = b"LKFrameEncryptionKey"
121+
proto_options.failure_tolerance = -1
122+
proto_options.key_ring_size = 16
123+
proto_options.key_derivation_function = proto_e2ee.KeyDerivationFunction.PBKDF2
124+
125+
# Verify fields are set correctly
126+
assert proto_options.shared_key == b"test-key"
127+
assert proto_options.ratchet_window_size == 16
128+
assert proto_options.ratchet_salt == b"LKFrameEncryptionKey"
129+
assert proto_options.failure_tolerance == -1
130+
assert proto_options.key_ring_size == 16
131+
assert proto_options.key_derivation_function == proto_e2ee.KeyDerivationFunction.PBKDF2
132+
133+
def test_proto_serialization(self):
134+
"""Test that proto message can be serialized without errors."""
135+
from livekit.rtc._proto import e2ee_pb2 as proto_e2ee
136+
137+
proto_options = proto_e2ee.KeyProviderOptions()
138+
proto_options.ratchet_window_size = 16
139+
proto_options.ratchet_salt = b"LKFrameEncryptionKey"
140+
proto_options.failure_tolerance = -1
141+
proto_options.key_ring_size = 16
142+
proto_options.key_derivation_function = proto_e2ee.KeyDerivationFunction.PBKDF2
143+
144+
# This should not raise an EncodeError
145+
serialized = proto_options.SerializeToString()
146+
assert len(serialized) > 0
147+
148+
# Verify we can deserialize it back
149+
parsed = proto_e2ee.KeyProviderOptions()
150+
parsed.ParseFromString(serialized)
151+
assert parsed.key_ring_size == 16
152+
assert parsed.key_derivation_function == proto_e2ee.KeyDerivationFunction.PBKDF2
153+
154+
def test_e2ee_options_proto_serialization(self):
155+
"""Test full E2eeOptions proto serialization."""
156+
from livekit.rtc._proto import e2ee_pb2 as proto_e2ee
157+
158+
e2ee_opts = proto_e2ee.E2eeOptions()
159+
e2ee_opts.encryption_type = proto_e2ee.EncryptionType.GCM
160+
e2ee_opts.key_provider_options.shared_key = b"my-shared-key"
161+
e2ee_opts.key_provider_options.ratchet_window_size = 16
162+
e2ee_opts.key_provider_options.ratchet_salt = b"LKFrameEncryptionKey"
163+
e2ee_opts.key_provider_options.failure_tolerance = -1
164+
e2ee_opts.key_provider_options.key_ring_size = 16
165+
e2ee_opts.key_provider_options.key_derivation_function = (
166+
proto_e2ee.KeyDerivationFunction.PBKDF2
167+
)
168+
169+
# This should not raise an EncodeError
170+
serialized = e2ee_opts.SerializeToString()
171+
assert len(serialized) > 0
172+
173+
174+
class TestPublicExports:
175+
"""Tests for public API exports."""
176+
177+
def test_key_derivation_function_exported(self):
178+
"""Test that KeyDerivationFunction is exported from the package."""
179+
from livekit.rtc import KeyDerivationFunction
180+
181+
# Verify enum values are accessible
182+
assert KeyDerivationFunction.PBKDF2 == 0
183+
assert KeyDerivationFunction.HKDF == 1
184+
185+
def test_encryption_type_exported(self):
186+
"""Test that EncryptionType is exported from the package."""
187+
from livekit.rtc import EncryptionType
188+
189+
assert EncryptionType.NONE == 0
190+
assert EncryptionType.GCM == 1
191+
assert EncryptionType.CUSTOM == 2
192+
193+
def test_e2ee_classes_exported(self):
194+
"""Test that E2EE classes are exported from the package."""
195+
from livekit.rtc import E2EEOptions, KeyProviderOptions
196+
197+
# Should be able to instantiate without errors
198+
key_opts = KeyProviderOptions()
199+
e2ee_opts = E2EEOptions()
200+
201+
assert key_opts is not None
202+
assert e2ee_opts is not None

0 commit comments

Comments
 (0)