diff --git a/.gitignore b/.gitignore index feea0a3d..1756b987 100644 --- a/.gitignore +++ b/.gitignore @@ -160,4 +160,6 @@ cython_debug/ #.idea/ # vscode project settings -.vscode \ No newline at end of file +.vscode + +.DS_Store \ No newline at end of file diff --git a/examples/e2ee.py b/examples/e2ee.py new file mode 100644 index 00000000..10549850 --- /dev/null +++ b/examples/e2ee.py @@ -0,0 +1,100 @@ +import asyncio +import colorsys +import logging +from signal import SIGINT, SIGTERM + +import numpy as np + +import livekit + +URL = 'ws://localhost:7880' +TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY' # noqa + + +async def publish_frames(source: livekit.VideoSource): + argb_frame = livekit.ArgbFrame( + livekit.VideoFormatType.FORMAT_ARGB, 1280, 720) + + arr = np.ctypeslib.as_array(argb_frame.data) + + framerate = 1 / 30 + hue = 0.0 + + while True: + frame = livekit.VideoFrame( + 0, livekit.VideoRotation.VIDEO_ROTATION_0, argb_frame.to_i420()) + + rgb = colorsys.hsv_to_rgb(hue, 1.0, 1.0) + rgb = [(x * 255) for x in rgb] # type: ignore + + argb_color = np.array(rgb + [255], dtype=np.uint8) + arr.flat[::4] = argb_color[0] + arr.flat[1::4] = argb_color[1] + arr.flat[2::4] = argb_color[2] + arr.flat[3::4] = argb_color[3] + + source.capture_frame(frame) + + hue += framerate/3 # 3s for a full cycle + if hue >= 1.0: + hue = 0.0 + + try: + await asyncio.sleep(framerate) + except asyncio.CancelledError: + break + + +async def main(): + room = livekit.Room() + + @room.listens_to("e2ee_state_changed") + def on_e2ee_state_changed(participant: livekit.Participant, + state: livekit.E2eeState) -> None: + logging.info("e2ee state changed: %s %s", participant.identity, state) + + logging.info("connecting to %s", URL) + try: + e2ee_options = livekit.E2EEOptions() + e2ee_options.key_provider_options.shared_key = b"abcdef" # this is our e2ee key + + await room.connect(URL, TOKEN, options=livekit.RoomOptions( + auto_subscribe=True, + e2ee=e2ee_options + )) + + logging.info("connected to room %s", room.name) + except livekit.ConnectError as e: + logging.error("failed to connect to the room: %s", e) + return False + + source = livekit.VideoSource() + source_task = asyncio.create_task(publish_frames(source)) + + track = livekit.LocalVideoTrack.create_video_track("hue", source) + options = livekit.TrackPublishOptions() + options.source = livekit.TrackSource.SOURCE_CAMERA + publication = await room.local_participant.publish_track(track, options) + logging.info("published track %s", publication.sid) + + try: + await room.run() + except asyncio.CancelledError: + logging.info("closing the room") + source_task.cancel() + await source_task + await room.disconnect() + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, handlers=[ + logging.FileHandler("publish_hue.log"), logging.StreamHandler()]) + + loop = asyncio.get_event_loop() + main_task = asyncio.ensure_future(main()) + for signal in [SIGINT, SIGTERM]: + loop.add_signal_handler(signal, main_task.cancel) + try: + loop.run_until_complete(main_task) + finally: + loop.close() diff --git a/livekit/__init__.py b/livekit/__init__.py index 19b5fc5a..3467d70c 100644 --- a/livekit/__init__.py +++ b/livekit/__init__.py @@ -22,6 +22,7 @@ DataPacketKind, TrackPublishOptions, ) +from ._proto.e2ee_pb2 import (EncryptionType, E2eeState) from ._proto.track_pb2 import StreamState, TrackKind, TrackSource from ._proto.video_frame_pb2 import VideoFormatType, VideoFrameBufferType, VideoRotation from .audio_frame import AudioFrame @@ -36,6 +37,13 @@ RemoteVideoTrack, Track, ) +from .e2ee import ( + E2EEManager, + E2EEOptions, + KeyProviderOptions, + KeyProvider, + FrameCryptor +) from .track_publication import ( LocalTrackPublication, RemoteTrackPublication, diff --git a/livekit/_proto/e2ee_pb2.py b/livekit/_proto/e2ee_pb2.py index b3cfaf70..192301a2 100644 --- a/livekit/_proto/e2ee_pb2.py +++ b/livekit/_proto/e2ee_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ne2ee.proto\x12\rlivekit.proto\"c\n\x0c\x46rameCryptor\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\ttrack_sid\x18\x02 \x01(\t\x12\x11\n\tkey_index\x18\x03 \x01(\x05\x12\x0f\n\x07\x65nabled\x18\x04 \x01(\x08\"\x8e\x01\n\x12KeyProviderOptions\x12\x17\n\nshared_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x1b\n\x13ratchet_window_size\x18\x02 \x01(\x05\x12\x14\n\x0cratchet_salt\x18\x03 \x01(\x0c\x12\x1d\n\x15uncrypted_magic_bytes\x18\x04 \x01(\x0c\x42\r\n\x0b_shared_key\"\x86\x01\n\x0b\x45\x32\x65\x65Options\x12\x36\n\x0f\x65ncryption_type\x18\x01 \x01(\x0e\x32\x1d.livekit.proto.EncryptionType\x12?\n\x14key_provider_options\x18\x02 \x01(\x0b\x32!.livekit.proto.KeyProviderOptions\"/\n\x1c\x45\x32\x65\x65ManagerSetEnabledRequest\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\"\x1f\n\x1d\x45\x32\x65\x65ManagerSetEnabledResponse\"$\n\"E2eeManagerGetFrameCryptorsRequest\"Z\n#E2eeManagerGetFrameCryptorsResponse\x12\x33\n\x0e\x66rame_cryptors\x18\x01 \x03(\x0b\x32\x1b.livekit.proto.FrameCryptor\"a\n\x1d\x46rameCryptorSetEnabledRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\ttrack_sid\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\" \n\x1e\x46rameCryptorSetEnabledResponse\"d\n\x1e\x46rameCryptorSetKeyIndexRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\ttrack_sid\x18\x02 \x01(\t\x12\x11\n\tkey_index\x18\x03 \x01(\x05\"!\n\x1f\x46rameCryptorSetKeyIndexResponse\"<\n\x13SetSharedKeyRequest\x12\x12\n\nshared_key\x18\x01 \x01(\x0c\x12\x11\n\tkey_index\x18\x02 \x01(\x05\"\x16\n\x14SetSharedKeyResponse\"+\n\x16RachetSharedKeyRequest\x12\x11\n\tkey_index\x18\x01 \x01(\x05\";\n\x17RachetSharedKeyResponse\x12\x14\n\x07new_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_new_key\"(\n\x13GetSharedKeyRequest\x12\x11\n\tkey_index\x18\x01 \x01(\x05\"0\n\x14GetSharedKeyResponse\x12\x10\n\x03key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\x06\n\x04_key\"M\n\rSetKeyRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x11\n\tkey_index\x18\x03 \x01(\x05\"\x10\n\x0eSetKeyResponse\"C\n\x10RachetKeyRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\tkey_index\x18\x02 \x01(\x05\"5\n\x11RachetKeyResponse\x12\x14\n\x07new_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_new_key\"@\n\rGetKeyRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\tkey_index\x18\x02 \x01(\x05\"*\n\x0eGetKeyResponse\x12\x10\n\x03key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\x06\n\x04_key\"\xc8\x05\n\x0b\x45\x32\x65\x65Request\x12\x13\n\x0broom_handle\x18\x01 \x01(\x04\x12J\n\x13manager_set_enabled\x18\x02 \x01(\x0b\x32+.livekit.proto.E2eeManagerSetEnabledRequestH\x00\x12W\n\x1amanager_get_frame_cryptors\x18\x03 \x01(\x0b\x32\x31.livekit.proto.E2eeManagerGetFrameCryptorsRequestH\x00\x12K\n\x13\x63ryptor_set_enabled\x18\x04 \x01(\x0b\x32,.livekit.proto.FrameCryptorSetEnabledRequestH\x00\x12N\n\x15\x63ryptor_set_key_index\x18\x05 \x01(\x0b\x32-.livekit.proto.FrameCryptorSetKeyIndexRequestH\x00\x12<\n\x0eset_shared_key\x18\x06 \x01(\x0b\x32\".livekit.proto.SetSharedKeyRequestH\x00\x12\x42\n\x11rachet_shared_key\x18\x07 \x01(\x0b\x32%.livekit.proto.RachetSharedKeyRequestH\x00\x12<\n\x0eget_shared_key\x18\x08 \x01(\x0b\x32\".livekit.proto.GetSharedKeyRequestH\x00\x12/\n\x07set_key\x18\t \x01(\x0b\x32\x1c.livekit.proto.SetKeyRequestH\x00\x12\x35\n\nrachet_key\x18\n \x01(\x0b\x32\x1f.livekit.proto.RachetKeyRequestH\x00\x12/\n\x07get_key\x18\x0b \x01(\x0b\x32\x1c.livekit.proto.GetKeyRequestH\x00\x42\t\n\x07message\"\xbe\x05\n\x0c\x45\x32\x65\x65Response\x12K\n\x13manager_set_enabled\x18\x01 \x01(\x0b\x32,.livekit.proto.E2eeManagerSetEnabledResponseH\x00\x12X\n\x1amanager_get_frame_cryptors\x18\x02 \x01(\x0b\x32\x32.livekit.proto.E2eeManagerGetFrameCryptorsResponseH\x00\x12L\n\x13\x63ryptor_set_enabled\x18\x03 \x01(\x0b\x32-.livekit.proto.FrameCryptorSetEnabledResponseH\x00\x12O\n\x15\x63ryptor_set_key_index\x18\x04 \x01(\x0b\x32..livekit.proto.FrameCryptorSetKeyIndexResponseH\x00\x12=\n\x0eset_shared_key\x18\x05 \x01(\x0b\x32#.livekit.proto.SetSharedKeyResponseH\x00\x12\x43\n\x11rachet_shared_key\x18\x06 \x01(\x0b\x32&.livekit.proto.RachetSharedKeyResponseH\x00\x12=\n\x0eget_shared_key\x18\x07 \x01(\x0b\x32#.livekit.proto.GetSharedKeyResponseH\x00\x12\x30\n\x07set_key\x18\x08 \x01(\x0b\x32\x1d.livekit.proto.SetKeyResponseH\x00\x12\x36\n\nrachet_key\x18\t \x01(\x0b\x32 .livekit.proto.RachetKeyResponseH\x00\x12\x30\n\x07get_key\x18\n \x01(\x0b\x32\x1d.livekit.proto.GetKeyResponseH\x00\x42\t\n\x07message*/\n\x0e\x45ncryptionType\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03GCM\x10\x01\x12\n\n\x06\x43USTOM\x10\x02*\x82\x01\n\tE2eeState\x12\x07\n\x03NEW\x10\x00\x12\x06\n\x02OK\x10\x01\x12\x15\n\x11\x45NCRYPTION_FAILED\x10\x02\x12\x15\n\x11\x44\x45\x43RYPTION_FAILED\x10\x03\x12\x0f\n\x0bMISSING_KEY\x10\x04\x12\x11\n\rKEY_RATCHETED\x10\x05\x12\x12\n\x0eINTERNAL_ERROR\x10\x06\x42\x10\xaa\x02\rLiveKit.Protob\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ne2ee.proto\x12\rlivekit.proto\"c\n\x0c\x46rameCryptor\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\ttrack_sid\x18\x02 \x01(\t\x12\x11\n\tkey_index\x18\x03 \x01(\x05\x12\x0f\n\x07\x65nabled\x18\x04 \x01(\x08\"\x8e\x01\n\x12KeyProviderOptions\x12\x17\n\nshared_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x1b\n\x13ratchet_window_size\x18\x02 \x01(\x05\x12\x14\n\x0cratchet_salt\x18\x03 \x01(\x0c\x12\x1d\n\x15uncrypted_magic_bytes\x18\x04 \x01(\x0c\x42\r\n\x0b_shared_key\"\x86\x01\n\x0b\x45\x32\x65\x65Options\x12\x36\n\x0f\x65ncryption_type\x18\x01 \x01(\x0e\x32\x1d.livekit.proto.EncryptionType\x12?\n\x14key_provider_options\x18\x02 \x01(\x0b\x32!.livekit.proto.KeyProviderOptions\"/\n\x1c\x45\x32\x65\x65ManagerSetEnabledRequest\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\"\x1f\n\x1d\x45\x32\x65\x65ManagerSetEnabledResponse\"$\n\"E2eeManagerGetFrameCryptorsRequest\"Z\n#E2eeManagerGetFrameCryptorsResponse\x12\x33\n\x0e\x66rame_cryptors\x18\x01 \x03(\x0b\x32\x1b.livekit.proto.FrameCryptor\"a\n\x1d\x46rameCryptorSetEnabledRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\ttrack_sid\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\" \n\x1e\x46rameCryptorSetEnabledResponse\"d\n\x1e\x46rameCryptorSetKeyIndexRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\ttrack_sid\x18\x02 \x01(\t\x12\x11\n\tkey_index\x18\x03 \x01(\x05\"!\n\x1f\x46rameCryptorSetKeyIndexResponse\"<\n\x13SetSharedKeyRequest\x12\x12\n\nshared_key\x18\x01 \x01(\x0c\x12\x11\n\tkey_index\x18\x02 \x01(\x05\"\x16\n\x14SetSharedKeyResponse\",\n\x17RatchetSharedKeyRequest\x12\x11\n\tkey_index\x18\x01 \x01(\x05\"<\n\x18RatchetSharedKeyResponse\x12\x14\n\x07new_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_new_key\"(\n\x13GetSharedKeyRequest\x12\x11\n\tkey_index\x18\x01 \x01(\x05\"0\n\x14GetSharedKeyResponse\x12\x10\n\x03key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\x06\n\x04_key\"M\n\rSetKeyRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x11\n\tkey_index\x18\x03 \x01(\x05\"\x10\n\x0eSetKeyResponse\"D\n\x11RatchetKeyRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\tkey_index\x18\x02 \x01(\x05\"6\n\x12RatchetKeyResponse\x12\x14\n\x07new_key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\n\n\x08_new_key\"@\n\rGetKeyRequest\x12\x1c\n\x14participant_identity\x18\x01 \x01(\t\x12\x11\n\tkey_index\x18\x02 \x01(\x05\"*\n\x0eGetKeyResponse\x12\x10\n\x03key\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\x06\n\x04_key\"\xcc\x05\n\x0b\x45\x32\x65\x65Request\x12\x13\n\x0broom_handle\x18\x01 \x01(\x04\x12J\n\x13manager_set_enabled\x18\x02 \x01(\x0b\x32+.livekit.proto.E2eeManagerSetEnabledRequestH\x00\x12W\n\x1amanager_get_frame_cryptors\x18\x03 \x01(\x0b\x32\x31.livekit.proto.E2eeManagerGetFrameCryptorsRequestH\x00\x12K\n\x13\x63ryptor_set_enabled\x18\x04 \x01(\x0b\x32,.livekit.proto.FrameCryptorSetEnabledRequestH\x00\x12N\n\x15\x63ryptor_set_key_index\x18\x05 \x01(\x0b\x32-.livekit.proto.FrameCryptorSetKeyIndexRequestH\x00\x12<\n\x0eset_shared_key\x18\x06 \x01(\x0b\x32\".livekit.proto.SetSharedKeyRequestH\x00\x12\x44\n\x12ratchet_shared_key\x18\x07 \x01(\x0b\x32&.livekit.proto.RatchetSharedKeyRequestH\x00\x12<\n\x0eget_shared_key\x18\x08 \x01(\x0b\x32\".livekit.proto.GetSharedKeyRequestH\x00\x12/\n\x07set_key\x18\t \x01(\x0b\x32\x1c.livekit.proto.SetKeyRequestH\x00\x12\x37\n\x0bratchet_key\x18\n \x01(\x0b\x32 .livekit.proto.RatchetKeyRequestH\x00\x12/\n\x07get_key\x18\x0b \x01(\x0b\x32\x1c.livekit.proto.GetKeyRequestH\x00\x42\t\n\x07message\"\xc2\x05\n\x0c\x45\x32\x65\x65Response\x12K\n\x13manager_set_enabled\x18\x01 \x01(\x0b\x32,.livekit.proto.E2eeManagerSetEnabledResponseH\x00\x12X\n\x1amanager_get_frame_cryptors\x18\x02 \x01(\x0b\x32\x32.livekit.proto.E2eeManagerGetFrameCryptorsResponseH\x00\x12L\n\x13\x63ryptor_set_enabled\x18\x03 \x01(\x0b\x32-.livekit.proto.FrameCryptorSetEnabledResponseH\x00\x12O\n\x15\x63ryptor_set_key_index\x18\x04 \x01(\x0b\x32..livekit.proto.FrameCryptorSetKeyIndexResponseH\x00\x12=\n\x0eset_shared_key\x18\x05 \x01(\x0b\x32#.livekit.proto.SetSharedKeyResponseH\x00\x12\x45\n\x12ratchet_shared_key\x18\x06 \x01(\x0b\x32\'.livekit.proto.RatchetSharedKeyResponseH\x00\x12=\n\x0eget_shared_key\x18\x07 \x01(\x0b\x32#.livekit.proto.GetSharedKeyResponseH\x00\x12\x30\n\x07set_key\x18\x08 \x01(\x0b\x32\x1d.livekit.proto.SetKeyResponseH\x00\x12\x38\n\x0bratchet_key\x18\t \x01(\x0b\x32!.livekit.proto.RatchetKeyResponseH\x00\x12\x30\n\x07get_key\x18\n \x01(\x0b\x32\x1d.livekit.proto.GetKeyResponseH\x00\x42\t\n\x07message*/\n\x0e\x45ncryptionType\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03GCM\x10\x01\x12\n\n\x06\x43USTOM\x10\x02*\x82\x01\n\tE2eeState\x12\x07\n\x03NEW\x10\x00\x12\x06\n\x02OK\x10\x01\x12\x15\n\x11\x45NCRYPTION_FAILED\x10\x02\x12\x15\n\x11\x44\x45\x43RYPTION_FAILED\x10\x03\x12\x0f\n\x0bMISSING_KEY\x10\x04\x12\x11\n\rKEY_RATCHETED\x10\x05\x12\x12\n\x0eINTERNAL_ERROR\x10\x06\x42\x10\xaa\x02\rLiveKit.Protob\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -22,10 +22,10 @@ DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\252\002\rLiveKit.Proto' - _globals['_ENCRYPTIONTYPE']._serialized_start=2929 - _globals['_ENCRYPTIONTYPE']._serialized_end=2976 - _globals['_E2EESTATE']._serialized_start=2979 - _globals['_E2EESTATE']._serialized_end=3109 + _globals['_ENCRYPTIONTYPE']._serialized_start=2941 + _globals['_ENCRYPTIONTYPE']._serialized_end=2988 + _globals['_E2EESTATE']._serialized_start=2991 + _globals['_E2EESTATE']._serialized_end=3121 _globals['_FRAMECRYPTOR']._serialized_start=29 _globals['_FRAMECRYPTOR']._serialized_end=128 _globals['_KEYPROVIDEROPTIONS']._serialized_start=131 @@ -52,28 +52,28 @@ _globals['_SETSHAREDKEYREQUEST']._serialized_end=954 _globals['_SETSHAREDKEYRESPONSE']._serialized_start=956 _globals['_SETSHAREDKEYRESPONSE']._serialized_end=978 - _globals['_RACHETSHAREDKEYREQUEST']._serialized_start=980 - _globals['_RACHETSHAREDKEYREQUEST']._serialized_end=1023 - _globals['_RACHETSHAREDKEYRESPONSE']._serialized_start=1025 - _globals['_RACHETSHAREDKEYRESPONSE']._serialized_end=1084 - _globals['_GETSHAREDKEYREQUEST']._serialized_start=1086 - _globals['_GETSHAREDKEYREQUEST']._serialized_end=1126 - _globals['_GETSHAREDKEYRESPONSE']._serialized_start=1128 - _globals['_GETSHAREDKEYRESPONSE']._serialized_end=1176 - _globals['_SETKEYREQUEST']._serialized_start=1178 - _globals['_SETKEYREQUEST']._serialized_end=1255 - _globals['_SETKEYRESPONSE']._serialized_start=1257 - _globals['_SETKEYRESPONSE']._serialized_end=1273 - _globals['_RACHETKEYREQUEST']._serialized_start=1275 - _globals['_RACHETKEYREQUEST']._serialized_end=1342 - _globals['_RACHETKEYRESPONSE']._serialized_start=1344 - _globals['_RACHETKEYRESPONSE']._serialized_end=1397 - _globals['_GETKEYREQUEST']._serialized_start=1399 - _globals['_GETKEYREQUEST']._serialized_end=1463 - _globals['_GETKEYRESPONSE']._serialized_start=1465 - _globals['_GETKEYRESPONSE']._serialized_end=1507 - _globals['_E2EEREQUEST']._serialized_start=1510 - _globals['_E2EEREQUEST']._serialized_end=2222 - _globals['_E2EERESPONSE']._serialized_start=2225 - _globals['_E2EERESPONSE']._serialized_end=2927 + _globals['_RATCHETSHAREDKEYREQUEST']._serialized_start=980 + _globals['_RATCHETSHAREDKEYREQUEST']._serialized_end=1024 + _globals['_RATCHETSHAREDKEYRESPONSE']._serialized_start=1026 + _globals['_RATCHETSHAREDKEYRESPONSE']._serialized_end=1086 + _globals['_GETSHAREDKEYREQUEST']._serialized_start=1088 + _globals['_GETSHAREDKEYREQUEST']._serialized_end=1128 + _globals['_GETSHAREDKEYRESPONSE']._serialized_start=1130 + _globals['_GETSHAREDKEYRESPONSE']._serialized_end=1178 + _globals['_SETKEYREQUEST']._serialized_start=1180 + _globals['_SETKEYREQUEST']._serialized_end=1257 + _globals['_SETKEYRESPONSE']._serialized_start=1259 + _globals['_SETKEYRESPONSE']._serialized_end=1275 + _globals['_RATCHETKEYREQUEST']._serialized_start=1277 + _globals['_RATCHETKEYREQUEST']._serialized_end=1345 + _globals['_RATCHETKEYRESPONSE']._serialized_start=1347 + _globals['_RATCHETKEYRESPONSE']._serialized_end=1401 + _globals['_GETKEYREQUEST']._serialized_start=1403 + _globals['_GETKEYREQUEST']._serialized_end=1467 + _globals['_GETKEYRESPONSE']._serialized_start=1469 + _globals['_GETKEYRESPONSE']._serialized_end=1511 + _globals['_E2EEREQUEST']._serialized_start=1514 + _globals['_E2EEREQUEST']._serialized_end=2230 + _globals['_E2EERESPONSE']._serialized_start=2233 + _globals['_E2EERESPONSE']._serialized_end=2939 # @@protoc_insertion_point(module_scope) diff --git a/livekit/_proto/e2ee_pb2.pyi b/livekit/_proto/e2ee_pb2.pyi index ecdb5c7f..01e434f2 100644 --- a/livekit/_proto/e2ee_pb2.pyi +++ b/livekit/_proto/e2ee_pb2.pyi @@ -287,7 +287,7 @@ class SetSharedKeyResponse(google.protobuf.message.Message): global___SetSharedKeyResponse = SetSharedKeyResponse @typing_extensions.final -class RachetSharedKeyRequest(google.protobuf.message.Message): +class RatchetSharedKeyRequest(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor KEY_INDEX_FIELD_NUMBER: builtins.int @@ -299,10 +299,10 @@ class RachetSharedKeyRequest(google.protobuf.message.Message): ) -> None: ... def ClearField(self, field_name: typing_extensions.Literal["key_index", b"key_index"]) -> None: ... -global___RachetSharedKeyRequest = RachetSharedKeyRequest +global___RatchetSharedKeyRequest = RatchetSharedKeyRequest @typing_extensions.final -class RachetSharedKeyResponse(google.protobuf.message.Message): +class RatchetSharedKeyResponse(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor NEW_KEY_FIELD_NUMBER: builtins.int @@ -316,7 +316,7 @@ class RachetSharedKeyResponse(google.protobuf.message.Message): def ClearField(self, field_name: typing_extensions.Literal["_new_key", b"_new_key", "new_key", b"new_key"]) -> None: ... def WhichOneof(self, oneof_group: typing_extensions.Literal["_new_key", b"_new_key"]) -> typing_extensions.Literal["new_key"] | None: ... -global___RachetSharedKeyResponse = RachetSharedKeyResponse +global___RatchetSharedKeyResponse = RatchetSharedKeyResponse @typing_extensions.final class GetSharedKeyRequest(google.protobuf.message.Message): @@ -382,7 +382,7 @@ class SetKeyResponse(google.protobuf.message.Message): global___SetKeyResponse = SetKeyResponse @typing_extensions.final -class RachetKeyRequest(google.protobuf.message.Message): +class RatchetKeyRequest(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor PARTICIPANT_IDENTITY_FIELD_NUMBER: builtins.int @@ -397,10 +397,10 @@ class RachetKeyRequest(google.protobuf.message.Message): ) -> None: ... def ClearField(self, field_name: typing_extensions.Literal["key_index", b"key_index", "participant_identity", b"participant_identity"]) -> None: ... -global___RachetKeyRequest = RachetKeyRequest +global___RatchetKeyRequest = RatchetKeyRequest @typing_extensions.final -class RachetKeyResponse(google.protobuf.message.Message): +class RatchetKeyResponse(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor NEW_KEY_FIELD_NUMBER: builtins.int @@ -414,7 +414,7 @@ class RachetKeyResponse(google.protobuf.message.Message): def ClearField(self, field_name: typing_extensions.Literal["_new_key", b"_new_key", "new_key", b"new_key"]) -> None: ... def WhichOneof(self, oneof_group: typing_extensions.Literal["_new_key", b"_new_key"]) -> typing_extensions.Literal["new_key"] | None: ... -global___RachetKeyResponse = RachetKeyResponse +global___RatchetKeyResponse = RatchetKeyResponse @typing_extensions.final class GetKeyRequest(google.protobuf.message.Message): @@ -461,10 +461,10 @@ class E2eeRequest(google.protobuf.message.Message): CRYPTOR_SET_ENABLED_FIELD_NUMBER: builtins.int CRYPTOR_SET_KEY_INDEX_FIELD_NUMBER: builtins.int SET_SHARED_KEY_FIELD_NUMBER: builtins.int - RACHET_SHARED_KEY_FIELD_NUMBER: builtins.int + RATCHET_SHARED_KEY_FIELD_NUMBER: builtins.int GET_SHARED_KEY_FIELD_NUMBER: builtins.int SET_KEY_FIELD_NUMBER: builtins.int - RACHET_KEY_FIELD_NUMBER: builtins.int + RATCHET_KEY_FIELD_NUMBER: builtins.int GET_KEY_FIELD_NUMBER: builtins.int room_handle: builtins.int @property @@ -478,13 +478,13 @@ class E2eeRequest(google.protobuf.message.Message): @property def set_shared_key(self) -> global___SetSharedKeyRequest: ... @property - def rachet_shared_key(self) -> global___RachetSharedKeyRequest: ... + def ratchet_shared_key(self) -> global___RatchetSharedKeyRequest: ... @property def get_shared_key(self) -> global___GetSharedKeyRequest: ... @property def set_key(self) -> global___SetKeyRequest: ... @property - def rachet_key(self) -> global___RachetKeyRequest: ... + def ratchet_key(self) -> global___RatchetKeyRequest: ... @property def get_key(self) -> global___GetKeyRequest: ... def __init__( @@ -496,15 +496,15 @@ class E2eeRequest(google.protobuf.message.Message): cryptor_set_enabled: global___FrameCryptorSetEnabledRequest | None = ..., cryptor_set_key_index: global___FrameCryptorSetKeyIndexRequest | None = ..., set_shared_key: global___SetSharedKeyRequest | None = ..., - rachet_shared_key: global___RachetSharedKeyRequest | None = ..., + ratchet_shared_key: global___RatchetSharedKeyRequest | None = ..., get_shared_key: global___GetSharedKeyRequest | None = ..., set_key: global___SetKeyRequest | None = ..., - rachet_key: global___RachetKeyRequest | None = ..., + ratchet_key: global___RatchetKeyRequest | None = ..., get_key: global___GetKeyRequest | None = ..., ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["cryptor_set_enabled", b"cryptor_set_enabled", "cryptor_set_key_index", b"cryptor_set_key_index", "get_key", b"get_key", "get_shared_key", b"get_shared_key", "manager_get_frame_cryptors", b"manager_get_frame_cryptors", "manager_set_enabled", b"manager_set_enabled", "message", b"message", "rachet_key", b"rachet_key", "rachet_shared_key", b"rachet_shared_key", "set_key", b"set_key", "set_shared_key", b"set_shared_key"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["cryptor_set_enabled", b"cryptor_set_enabled", "cryptor_set_key_index", b"cryptor_set_key_index", "get_key", b"get_key", "get_shared_key", b"get_shared_key", "manager_get_frame_cryptors", b"manager_get_frame_cryptors", "manager_set_enabled", b"manager_set_enabled", "message", b"message", "rachet_key", b"rachet_key", "rachet_shared_key", b"rachet_shared_key", "room_handle", b"room_handle", "set_key", b"set_key", "set_shared_key", b"set_shared_key"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["message", b"message"]) -> typing_extensions.Literal["manager_set_enabled", "manager_get_frame_cryptors", "cryptor_set_enabled", "cryptor_set_key_index", "set_shared_key", "rachet_shared_key", "get_shared_key", "set_key", "rachet_key", "get_key"] | None: ... + def HasField(self, field_name: typing_extensions.Literal["cryptor_set_enabled", b"cryptor_set_enabled", "cryptor_set_key_index", b"cryptor_set_key_index", "get_key", b"get_key", "get_shared_key", b"get_shared_key", "manager_get_frame_cryptors", b"manager_get_frame_cryptors", "manager_set_enabled", b"manager_set_enabled", "message", b"message", "ratchet_key", b"ratchet_key", "ratchet_shared_key", b"ratchet_shared_key", "set_key", b"set_key", "set_shared_key", b"set_shared_key"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["cryptor_set_enabled", b"cryptor_set_enabled", "cryptor_set_key_index", b"cryptor_set_key_index", "get_key", b"get_key", "get_shared_key", b"get_shared_key", "manager_get_frame_cryptors", b"manager_get_frame_cryptors", "manager_set_enabled", b"manager_set_enabled", "message", b"message", "ratchet_key", b"ratchet_key", "ratchet_shared_key", b"ratchet_shared_key", "room_handle", b"room_handle", "set_key", b"set_key", "set_shared_key", b"set_shared_key"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["message", b"message"]) -> typing_extensions.Literal["manager_set_enabled", "manager_get_frame_cryptors", "cryptor_set_enabled", "cryptor_set_key_index", "set_shared_key", "ratchet_shared_key", "get_shared_key", "set_key", "ratchet_key", "get_key"] | None: ... global___E2eeRequest = E2eeRequest @@ -517,10 +517,10 @@ class E2eeResponse(google.protobuf.message.Message): CRYPTOR_SET_ENABLED_FIELD_NUMBER: builtins.int CRYPTOR_SET_KEY_INDEX_FIELD_NUMBER: builtins.int SET_SHARED_KEY_FIELD_NUMBER: builtins.int - RACHET_SHARED_KEY_FIELD_NUMBER: builtins.int + RATCHET_SHARED_KEY_FIELD_NUMBER: builtins.int GET_SHARED_KEY_FIELD_NUMBER: builtins.int SET_KEY_FIELD_NUMBER: builtins.int - RACHET_KEY_FIELD_NUMBER: builtins.int + RATCHET_KEY_FIELD_NUMBER: builtins.int GET_KEY_FIELD_NUMBER: builtins.int @property def manager_set_enabled(self) -> global___E2eeManagerSetEnabledResponse: ... @@ -533,13 +533,13 @@ class E2eeResponse(google.protobuf.message.Message): @property def set_shared_key(self) -> global___SetSharedKeyResponse: ... @property - def rachet_shared_key(self) -> global___RachetSharedKeyResponse: ... + def ratchet_shared_key(self) -> global___RatchetSharedKeyResponse: ... @property def get_shared_key(self) -> global___GetSharedKeyResponse: ... @property def set_key(self) -> global___SetKeyResponse: ... @property - def rachet_key(self) -> global___RachetKeyResponse: ... + def ratchet_key(self) -> global___RatchetKeyResponse: ... @property def get_key(self) -> global___GetKeyResponse: ... def __init__( @@ -550,14 +550,14 @@ class E2eeResponse(google.protobuf.message.Message): cryptor_set_enabled: global___FrameCryptorSetEnabledResponse | None = ..., cryptor_set_key_index: global___FrameCryptorSetKeyIndexResponse | None = ..., set_shared_key: global___SetSharedKeyResponse | None = ..., - rachet_shared_key: global___RachetSharedKeyResponse | None = ..., + ratchet_shared_key: global___RatchetSharedKeyResponse | None = ..., get_shared_key: global___GetSharedKeyResponse | None = ..., set_key: global___SetKeyResponse | None = ..., - rachet_key: global___RachetKeyResponse | None = ..., + ratchet_key: global___RatchetKeyResponse | None = ..., get_key: global___GetKeyResponse | None = ..., ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["cryptor_set_enabled", b"cryptor_set_enabled", "cryptor_set_key_index", b"cryptor_set_key_index", "get_key", b"get_key", "get_shared_key", b"get_shared_key", "manager_get_frame_cryptors", b"manager_get_frame_cryptors", "manager_set_enabled", b"manager_set_enabled", "message", b"message", "rachet_key", b"rachet_key", "rachet_shared_key", b"rachet_shared_key", "set_key", b"set_key", "set_shared_key", b"set_shared_key"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["cryptor_set_enabled", b"cryptor_set_enabled", "cryptor_set_key_index", b"cryptor_set_key_index", "get_key", b"get_key", "get_shared_key", b"get_shared_key", "manager_get_frame_cryptors", b"manager_get_frame_cryptors", "manager_set_enabled", b"manager_set_enabled", "message", b"message", "rachet_key", b"rachet_key", "rachet_shared_key", b"rachet_shared_key", "set_key", b"set_key", "set_shared_key", b"set_shared_key"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["message", b"message"]) -> typing_extensions.Literal["manager_set_enabled", "manager_get_frame_cryptors", "cryptor_set_enabled", "cryptor_set_key_index", "set_shared_key", "rachet_shared_key", "get_shared_key", "set_key", "rachet_key", "get_key"] | None: ... + def HasField(self, field_name: typing_extensions.Literal["cryptor_set_enabled", b"cryptor_set_enabled", "cryptor_set_key_index", b"cryptor_set_key_index", "get_key", b"get_key", "get_shared_key", b"get_shared_key", "manager_get_frame_cryptors", b"manager_get_frame_cryptors", "manager_set_enabled", b"manager_set_enabled", "message", b"message", "ratchet_key", b"ratchet_key", "ratchet_shared_key", b"ratchet_shared_key", "set_key", b"set_key", "set_shared_key", b"set_shared_key"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["cryptor_set_enabled", b"cryptor_set_enabled", "cryptor_set_key_index", b"cryptor_set_key_index", "get_key", b"get_key", "get_shared_key", b"get_shared_key", "manager_get_frame_cryptors", b"manager_get_frame_cryptors", "manager_set_enabled", b"manager_set_enabled", "message", b"message", "ratchet_key", b"ratchet_key", "ratchet_shared_key", b"ratchet_shared_key", "set_key", b"set_key", "set_shared_key", b"set_shared_key"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["message", b"message"]) -> typing_extensions.Literal["manager_set_enabled", "manager_get_frame_cryptors", "cryptor_set_enabled", "cryptor_set_key_index", "set_shared_key", "ratchet_shared_key", "get_shared_key", "set_key", "ratchet_key", "get_key"] | None: ... global___E2eeResponse = E2eeResponse diff --git a/livekit/e2ee.py b/livekit/e2ee.py new file mode 100644 index 00000000..7b35683e --- /dev/null +++ b/livekit/e2ee.py @@ -0,0 +1,183 @@ +# Copyright 2023 LiveKit, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass, field +from typing import List, Optional + +from ._ffi_client import ffi_client +from ._proto import e2ee_pb2 as proto_e2ee +from ._proto import ffi_pb2 as proto_ffi + +DEFAULT_RATCHET_SALT = b"LKFrameEncryptionKey" +DEFAULT_MAGIC_BYTES = b"LK-ROCKS" +DEFAULT_RATCHET_WINDOW_SIZE = 0 + + +@dataclass +class KeyProviderOptions: + shared_key: Optional[bytes] = None + ratchet_salt: bytes = DEFAULT_RATCHET_SALT + uncrypted_magic_bytes: bytes = DEFAULT_MAGIC_BYTES + ratchet_window_size: int = DEFAULT_RATCHET_WINDOW_SIZE + + +@dataclass +class E2EEOptions: + key_provider_options: KeyProviderOptions = field( + default_factory=KeyProviderOptions) + encryption_type: proto_e2ee.EncryptionType.ValueType = proto_e2ee.EncryptionType.GCM + + +class KeyProvider: + def __init__(self, room_handle: int, options: KeyProviderOptions): + self._options = options + self._room_handle = room_handle + + @property + def options(self) -> KeyProviderOptions: + return self._options + + def set_shared_key(self, key: bytes, key_index: int) -> None: + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + req.e2ee.set_shared_key.key_index = key_index + req.e2ee.set_shared_key.shared_key = key + ffi_client.request(req) + + def export_shared_key(self, key_index: int) -> bytes: + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + req.e2ee.get_shared_key.key_index = key_index + resp = ffi_client.request(req) + key = resp.e2ee.get_shared_key.key + return key + + def rachet_shared_key(self, key_index: int) -> bytes: + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + req.e2ee.ratchet_shared_key.key_index = key_index + + resp = ffi_client.request(req) + + new_key = resp.e2ee.ratchet_shared_key.new_key + return new_key + + def set_key(self, participant_identity: str, key: bytes, key_index: int) -> None: + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + req.e2ee.set_key.participant_identity = participant_identity + req.e2ee.set_key.key_index = key_index + req.e2ee.set_key.key = key + + self.key_index = key_index + ffi_client.request(req) + + def export_key(self, participant_identity: str, key_index: int) -> bytes: + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + req.e2ee.get_key.participant_identity = participant_identity + req.e2ee.get_key.key_index = key_index + resp = ffi_client.request(req) + key = resp.e2ee.get_key.key + return key + + def ratchet_key(self, participant_identity: str, key_index: int) -> bytes: + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + req.e2ee.ratchet_key.participant_identity = participant_identity + req.e2ee.ratchet_key.key_index = key_index + + resp = ffi_client.request(req) + new_key = resp.e2ee.ratchet_key.new_key + return new_key + + +class FrameCryptor: + def __init__(self, room_handle: int, + participant_identity: str, + key_index: int, + enabled: bool): + self._room_handle = room_handle + self._enabled = enabled + self._participant_identity = participant_identity + self._key_index = key_index + + @property + def participant_identity(self) -> str: + return self._participant_identity + + @property + def key_index(self) -> int: + return self._key_index + + @property + def enabled(self) -> bool: + return self._enabled + + def set_enabled(self, enabled: bool) -> None: + self._enabled = enabled + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + req.e2ee.cryptor_set_enabled.participant_identity = self._participant_identity + req.e2ee.cryptor_set_enabled.enabled = enabled + ffi_client.request(req) + + def set_key_index(self, key_index: int) -> None: + self._key_index = key_index + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + req.e2ee.cryptor_set_key_index.participant_identity = self._participant_identity + req.e2ee.cryptor_set_key_index.key_index = key_index + ffi_client.request(req) + + +class E2EEManager: + def __init__(self, room_handle: int, options: Optional[E2EEOptions]): + self.options = options + self._room_handle = room_handle + self._enabled = options is not None + + if options is not None: + self._key_provider = KeyProvider( + self._room_handle, options.key_provider_options) + + @property + def key_provider(self) -> Optional[KeyProvider]: + return self._key_provider + + @property + def enabled(self) -> bool: + return self._enabled + + def set_enabled(self, enabled: bool) -> None: + self._enabled = enabled + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + req.e2ee.manager_set_enabled.enabled = enabled + ffi_client.request(req) + + def frame_cryptors(self) -> List[FrameCryptor]: + req = proto_ffi.FfiRequest() + req.e2ee.room_handle = self._room_handle + + resp = ffi_client.request(req) + frame_cryptors = [] + for frame_cryptor in resp.e2ee.manager_get_frame_cryptors.frame_cryptors: + frame_cryptors.append(FrameCryptor( + self._room_handle, + frame_cryptor.participant_identity, + frame_cryptor.key_index, + frame_cryptor.enabled + )) + return frame_cryptors diff --git a/livekit/participant.py b/livekit/participant.py index 1e0dc6de..9fa98308 100644 --- a/livekit/participant.py +++ b/livekit/participant.py @@ -75,7 +75,8 @@ def __init__(self, owned_info: proto_participant.OwnedParticipant) -> None: async def publish_data(self, payload: Union[bytes, str], kind: DataPacketKind.ValueType = DataPacketKind.KIND_RELIABLE, - destination_sids: Optional[Union[List[str], List['RemoteParticipant']]] = None) -> None: + destination_sids: Optional[Union[List[str], + List['RemoteParticipant']]] = None) -> None: if isinstance(payload, str): payload = payload.encode('utf-8') diff --git a/livekit/room.py b/livekit/room.py index 05db341d..cfdd82c5 100644 --- a/livekit/room.py +++ b/livekit/room.py @@ -25,6 +25,7 @@ from ._proto import room_pb2 as proto_room from ._proto.room_pb2 import ConnectionState from ._proto.track_pb2 import TrackKind +from .e2ee import E2EEManager, E2EEOptions from .participant import LocalParticipant, Participant, RemoteParticipant from .track import RemoteAudioTrack, RemoteVideoTrack from .track_publication import RemoteTrackPublication @@ -34,6 +35,7 @@ class RoomOptions: auto_subscribe: bool = True dynacast: bool = False + e2ee: Optional[E2EEOptions] = None class ConnectError(Exception): @@ -64,6 +66,9 @@ def name(self) -> str: def metadata(self) -> str: return self._info.metadata + def e2ee_manager(self) -> E2EEManager: + return self._e2ee_manager + def isconnected(self) -> bool: return self._ffi_handle is not None and \ self.connection_state != ConnectionState.CONN_DISCONNECTED @@ -80,6 +85,18 @@ async def connect(self, req.connect.options.auto_subscribe = options.auto_subscribe req.connect.options.dynacast = options.dynacast + if options.e2ee: + req.connect.options.e2ee.encryption_type = \ + options.e2ee.encryption_type + req.connect.options.e2ee.key_provider_options.shared_key = \ + options.e2ee.key_provider_options.shared_key # type: ignore + req.connect.options.e2ee.key_provider_options.ratchet_salt = \ + options.e2ee.key_provider_options.ratchet_salt + req.connect.options.e2ee.key_provider_options.uncrypted_magic_bytes = \ + options.e2ee.key_provider_options.uncrypted_magic_bytes + req.connect.options.e2ee.key_provider_options.ratchet_window_size = \ + options.e2ee.key_provider_options.ratchet_window_size + resp = ffi_client.request(req) future: asyncio.Future[proto_room.ConnectCallback] = asyncio.Future() @@ -95,6 +112,10 @@ def on_connect_callback(cb: proto_room.ConnectCallback): self._close_future: asyncio.Future[None] = asyncio.Future() self._ffi_handle = FfiHandle(cb.room.handle.id) + + self._e2ee_manager = E2EEManager( + self._ffi_handle.handle, options.e2ee) + self._info = cb.room.info self.connection_state = ConnectionState.CONN_CONNECTED @@ -243,9 +264,9 @@ def _on_room_event(self, event: proto_room.RoomEvent): self.emit('data_received', data, event.data_received.kind, rparticipant) elif which == 'connection_state_changed': - state = event.connection_state_changed.state - self.connection_state = state - self.emit('connection_state_changed', state) + connection_state = event.connection_state_changed.state + self.connection_state = connection_state + self.emit('connection_state_changed', connection_state) elif which == 'connected': self.emit('connected') elif which == 'disconnected': @@ -254,6 +275,11 @@ def _on_room_event(self, event: proto_room.RoomEvent): self.emit('reconnecting') elif which == 'reconnected': self.emit('reconnected') + elif which == 'e2ee_state_changed': + sid = event.e2ee_state_changed.participant_sid + e2ee_state = event.e2ee_state_changed.state + self.emit('e2ee_state_changed', + self._retrieve_participant(sid), e2ee_state) def _retrieve_participant(self, sid: str) -> Participant: """ Retrieve a participant by sid, returns the LocalParticipant diff --git a/livekit/track_publication.py b/livekit/track_publication.py index b3019a5e..0ff344f5 100644 --- a/livekit/track_publication.py +++ b/livekit/track_publication.py @@ -17,6 +17,7 @@ from livekit._proto import track_pb2 as proto_track from ._ffi_client import FfiHandle, ffi_client +from ._proto import e2ee_pb2 as proto_e2ee from ._proto import ffi_pb2 as proto_ffi from .track import Track @@ -63,6 +64,10 @@ def mime_type(self) -> str: def muted(self) -> bool: return self._info.muted + @property + def encryption_type(self) -> proto_e2ee.EncryptionType.ValueType: + return self._info.encryption_type + class LocalTrackPublication(TrackPublication): def __init__(self, owned_info: proto_track.OwnedTrackPublication):