28
28
Dict ,
29
29
FrozenSet ,
30
30
List ,
31
+ Literal ,
31
32
Mapping ,
32
33
Optional ,
33
34
Sequence ,
34
35
Set ,
35
36
Tuple ,
37
+ Union ,
38
+ overload ,
36
39
)
37
40
38
41
import attr
@@ -128,6 +131,8 @@ class SyncVersion(Enum):
128
131
129
132
# Traditional `/sync` endpoint
130
133
SYNC_V2 = "sync_v2"
134
+ # Part of MSC3575 Sliding Sync
135
+ E2EE_SYNC = "e2ee_sync"
131
136
132
137
133
138
@attr .s (slots = True , frozen = True , auto_attribs = True )
@@ -280,6 +285,26 @@ def __bool__(self) -> bool:
280
285
)
281
286
282
287
288
+ @attr .s (slots = True , frozen = True , auto_attribs = True )
289
+ class E2eeSyncResult :
290
+ """
291
+ Attributes:
292
+ next_batch: Token for the next sync
293
+ to_device: List of direct messages for the device.
294
+ device_lists: List of user_ids whose devices have changed
295
+ device_one_time_keys_count: Dict of algorithm to count for one time keys
296
+ for this device
297
+ device_unused_fallback_key_types: List of key types that have an unused fallback
298
+ key
299
+ """
300
+
301
+ next_batch : StreamToken
302
+ to_device : List [JsonDict ]
303
+ device_lists : DeviceListUpdates
304
+ device_one_time_keys_count : JsonMapping
305
+ device_unused_fallback_key_types : List [str ]
306
+
307
+
283
308
class SyncHandler :
284
309
def __init__ (self , hs : "HomeServer" ):
285
310
self .hs_config = hs .config
@@ -322,6 +347,31 @@ def __init__(self, hs: "HomeServer"):
322
347
323
348
self .rooms_to_exclude_globally = hs .config .server .rooms_to_exclude_from_sync
324
349
350
+ @overload
351
+ async def wait_for_sync_for_user (
352
+ self ,
353
+ requester : Requester ,
354
+ sync_config : SyncConfig ,
355
+ sync_version : Literal [SyncVersion .SYNC_V2 ],
356
+ request_key : SyncRequestKey ,
357
+ since_token : Optional [StreamToken ] = None ,
358
+ timeout : int = 0 ,
359
+ full_state : bool = False ,
360
+ ) -> SyncResult : ...
361
+
362
+ @overload
363
+ async def wait_for_sync_for_user (
364
+ self ,
365
+ requester : Requester ,
366
+ sync_config : SyncConfig ,
367
+ sync_version : Literal [SyncVersion .E2EE_SYNC ],
368
+ request_key : SyncRequestKey ,
369
+ since_token : Optional [StreamToken ] = None ,
370
+ timeout : int = 0 ,
371
+ full_state : bool = False ,
372
+ ) -> E2eeSyncResult : ...
373
+
374
+ @overload
325
375
async def wait_for_sync_for_user (
326
376
self ,
327
377
requester : Requester ,
@@ -331,7 +381,18 @@ async def wait_for_sync_for_user(
331
381
since_token : Optional [StreamToken ] = None ,
332
382
timeout : int = 0 ,
333
383
full_state : bool = False ,
334
- ) -> SyncResult :
384
+ ) -> Union [SyncResult , E2eeSyncResult ]: ...
385
+
386
+ async def wait_for_sync_for_user (
387
+ self ,
388
+ requester : Requester ,
389
+ sync_config : SyncConfig ,
390
+ sync_version : SyncVersion ,
391
+ request_key : SyncRequestKey ,
392
+ since_token : Optional [StreamToken ] = None ,
393
+ timeout : int = 0 ,
394
+ full_state : bool = False ,
395
+ ) -> Union [SyncResult , E2eeSyncResult ]:
335
396
"""Get the sync for a client if we have new data for it now. Otherwise
336
397
wait for new data to arrive on the server. If the timeout expires, then
337
398
return an empty sync result.
@@ -344,8 +405,10 @@ async def wait_for_sync_for_user(
344
405
since_token: The point in the stream to sync from.
345
406
timeout: How long to wait for new data to arrive before giving up.
346
407
full_state: Whether to return the full state for each room.
408
+
347
409
Returns:
348
410
When `SyncVersion.SYNC_V2`, returns a full `SyncResult`.
411
+ When `SyncVersion.E2EE_SYNC`, returns a `E2eeSyncResult`.
349
412
"""
350
413
# If the user is not part of the mau group, then check that limits have
351
414
# not been exceeded (if not part of the group by this point, almost certain
@@ -366,6 +429,29 @@ async def wait_for_sync_for_user(
366
429
logger .debug ("Returning sync response for %s" , user_id )
367
430
return res
368
431
432
+ @overload
433
+ async def _wait_for_sync_for_user (
434
+ self ,
435
+ sync_config : SyncConfig ,
436
+ sync_version : Literal [SyncVersion .SYNC_V2 ],
437
+ since_token : Optional [StreamToken ],
438
+ timeout : int ,
439
+ full_state : bool ,
440
+ cache_context : ResponseCacheContext [SyncRequestKey ],
441
+ ) -> SyncResult : ...
442
+
443
+ @overload
444
+ async def _wait_for_sync_for_user (
445
+ self ,
446
+ sync_config : SyncConfig ,
447
+ sync_version : Literal [SyncVersion .E2EE_SYNC ],
448
+ since_token : Optional [StreamToken ],
449
+ timeout : int ,
450
+ full_state : bool ,
451
+ cache_context : ResponseCacheContext [SyncRequestKey ],
452
+ ) -> E2eeSyncResult : ...
453
+
454
+ @overload
369
455
async def _wait_for_sync_for_user (
370
456
self ,
371
457
sync_config : SyncConfig ,
@@ -374,7 +460,17 @@ async def _wait_for_sync_for_user(
374
460
timeout : int ,
375
461
full_state : bool ,
376
462
cache_context : ResponseCacheContext [SyncRequestKey ],
377
- ) -> SyncResult :
463
+ ) -> Union [SyncResult , E2eeSyncResult ]: ...
464
+
465
+ async def _wait_for_sync_for_user (
466
+ self ,
467
+ sync_config : SyncConfig ,
468
+ sync_version : SyncVersion ,
469
+ since_token : Optional [StreamToken ],
470
+ timeout : int ,
471
+ full_state : bool ,
472
+ cache_context : ResponseCacheContext [SyncRequestKey ],
473
+ ) -> Union [SyncResult , E2eeSyncResult ]:
378
474
"""The start of the machinery that produces a /sync response.
379
475
380
476
See https://spec.matrix.org/v1.1/client-server-api/#syncing for full details.
@@ -417,14 +513,16 @@ async def _wait_for_sync_for_user(
417
513
if timeout == 0 or since_token is None or full_state :
418
514
# we are going to return immediately, so don't bother calling
419
515
# notifier.wait_for_events.
420
- result : SyncResult = await self .current_sync_for_user (
421
- sync_config , sync_version , since_token , full_state = full_state
516
+ result : Union [SyncResult , E2eeSyncResult ] = (
517
+ await self .current_sync_for_user (
518
+ sync_config , sync_version , since_token , full_state = full_state
519
+ )
422
520
)
423
521
else :
424
522
# Otherwise, we wait for something to happen and report it to the user.
425
523
async def current_sync_callback (
426
524
before_token : StreamToken , after_token : StreamToken
427
- ) -> SyncResult :
525
+ ) -> Union [ SyncResult , E2eeSyncResult ] :
428
526
return await self .current_sync_for_user (
429
527
sync_config , sync_version , since_token
430
528
)
@@ -456,14 +554,43 @@ async def current_sync_callback(
456
554
457
555
return result
458
556
557
+ @overload
558
+ async def current_sync_for_user (
559
+ self ,
560
+ sync_config : SyncConfig ,
561
+ sync_version : Literal [SyncVersion .SYNC_V2 ],
562
+ since_token : Optional [StreamToken ] = None ,
563
+ full_state : bool = False ,
564
+ ) -> SyncResult : ...
565
+
566
+ @overload
567
+ async def current_sync_for_user (
568
+ self ,
569
+ sync_config : SyncConfig ,
570
+ sync_version : Literal [SyncVersion .E2EE_SYNC ],
571
+ since_token : Optional [StreamToken ] = None ,
572
+ full_state : bool = False ,
573
+ ) -> E2eeSyncResult : ...
574
+
575
+ @overload
459
576
async def current_sync_for_user (
460
577
self ,
461
578
sync_config : SyncConfig ,
462
579
sync_version : SyncVersion ,
463
580
since_token : Optional [StreamToken ] = None ,
464
581
full_state : bool = False ,
465
- ) -> SyncResult :
466
- """Generates the response body of a sync result, represented as a SyncResult.
582
+ ) -> Union [SyncResult , E2eeSyncResult ]: ...
583
+
584
+ async def current_sync_for_user (
585
+ self ,
586
+ sync_config : SyncConfig ,
587
+ sync_version : SyncVersion ,
588
+ since_token : Optional [StreamToken ] = None ,
589
+ full_state : bool = False ,
590
+ ) -> Union [SyncResult , E2eeSyncResult ]:
591
+ """
592
+ Generates the response body of a sync result, represented as a
593
+ `SyncResult`/`E2eeSyncResult`.
467
594
468
595
This is a wrapper around `generate_sync_result` which starts an open tracing
469
596
span to track the sync. See `generate_sync_result` for the next part of your
@@ -474,15 +601,25 @@ async def current_sync_for_user(
474
601
sync_version: Determines what kind of sync response to generate.
475
602
since_token: The point in the stream to sync from.p.
476
603
full_state: Whether to return the full state for each room.
604
+
477
605
Returns:
478
606
When `SyncVersion.SYNC_V2`, returns a full `SyncResult`.
607
+ When `SyncVersion.E2EE_SYNC`, returns a `E2eeSyncResult`.
479
608
"""
480
609
with start_active_span ("sync.current_sync_for_user" ):
481
610
log_kv ({"since_token" : since_token })
611
+
482
612
# Go through the `/sync` v2 path
483
613
if sync_version == SyncVersion .SYNC_V2 :
484
- sync_result : SyncResult = await self .generate_sync_result (
485
- sync_config , since_token , full_state
614
+ sync_result : Union [SyncResult , E2eeSyncResult ] = (
615
+ await self .generate_sync_result (
616
+ sync_config , since_token , full_state
617
+ )
618
+ )
619
+ # Go through the MSC3575 Sliding Sync `/sync/e2ee` path
620
+ elif sync_version == SyncVersion .E2EE_SYNC :
621
+ sync_result = await self .generate_e2ee_sync_result (
622
+ sync_config , since_token
486
623
)
487
624
else :
488
625
raise Exception (
@@ -1691,6 +1828,96 @@ async def generate_sync_result(
1691
1828
next_batch = sync_result_builder .now_token ,
1692
1829
)
1693
1830
1831
+ async def generate_e2ee_sync_result (
1832
+ self ,
1833
+ sync_config : SyncConfig ,
1834
+ since_token : Optional [StreamToken ] = None ,
1835
+ ) -> E2eeSyncResult :
1836
+ """
1837
+ Generates the response body of a MSC3575 Sliding Sync `/sync/e2ee` result.
1838
+
1839
+ This is represented by a `E2eeSyncResult` struct, which is built from small
1840
+ pieces using a `SyncResultBuilder`. The `sync_result_builder` is passed as a
1841
+ mutable ("inout") parameter to various helper functions. These retrieve and
1842
+ process the data which forms the sync body, often writing to the
1843
+ `sync_result_builder` to store their output.
1844
+
1845
+ At the end, we transfer data from the `sync_result_builder` to a new `E2eeSyncResult`
1846
+ instance to signify that the sync calculation is complete.
1847
+ """
1848
+ user_id = sync_config .user .to_string ()
1849
+ app_service = self .store .get_app_service_by_user_id (user_id )
1850
+ if app_service :
1851
+ # We no longer support AS users using /sync directly.
1852
+ # See https://github.com/matrix-org/matrix-doc/issues/1144
1853
+ raise NotImplementedError ()
1854
+
1855
+ sync_result_builder = await self .get_sync_result_builder (
1856
+ sync_config ,
1857
+ since_token ,
1858
+ full_state = False ,
1859
+ )
1860
+
1861
+ # 1. Calculate `to_device` events
1862
+ await self ._generate_sync_entry_for_to_device (sync_result_builder )
1863
+
1864
+ # 2. Calculate `device_lists`
1865
+ # Device list updates are sent if a since token is provided.
1866
+ device_lists = DeviceListUpdates ()
1867
+ include_device_list_updates = bool (since_token and since_token .device_list_key )
1868
+ if include_device_list_updates :
1869
+ # Note that _generate_sync_entry_for_rooms sets sync_result_builder.joined, which
1870
+ # is used in calculate_user_changes below.
1871
+ #
1872
+ # TODO: Running `_generate_sync_entry_for_rooms()` is a lot of work just to
1873
+ # figure out the membership changes/derived info needed for
1874
+ # `_generate_sync_entry_for_device_list()`. In the future, we should try to
1875
+ # refactor this away.
1876
+ (
1877
+ newly_joined_rooms ,
1878
+ newly_left_rooms ,
1879
+ ) = await self ._generate_sync_entry_for_rooms (sync_result_builder )
1880
+
1881
+ # This uses the sync_result_builder.joined which is set in
1882
+ # `_generate_sync_entry_for_rooms`, if that didn't find any joined
1883
+ # rooms for some reason it is a no-op.
1884
+ (
1885
+ newly_joined_or_invited_or_knocked_users ,
1886
+ newly_left_users ,
1887
+ ) = sync_result_builder .calculate_user_changes ()
1888
+
1889
+ device_lists = await self ._generate_sync_entry_for_device_list (
1890
+ sync_result_builder ,
1891
+ newly_joined_rooms = newly_joined_rooms ,
1892
+ newly_joined_or_invited_or_knocked_users = newly_joined_or_invited_or_knocked_users ,
1893
+ newly_left_rooms = newly_left_rooms ,
1894
+ newly_left_users = newly_left_users ,
1895
+ )
1896
+
1897
+ # 3. Calculate `device_one_time_keys_count` and `device_unused_fallback_key_types`
1898
+ device_id = sync_config .device_id
1899
+ one_time_keys_count : JsonMapping = {}
1900
+ unused_fallback_key_types : List [str ] = []
1901
+ if device_id :
1902
+ # TODO: We should have a way to let clients differentiate between the states of:
1903
+ # * no change in OTK count since the provided since token
1904
+ # * the server has zero OTKs left for this device
1905
+ # Spec issue: https://github.com/matrix-org/matrix-doc/issues/3298
1906
+ one_time_keys_count = await self .store .count_e2e_one_time_keys (
1907
+ user_id , device_id
1908
+ )
1909
+ unused_fallback_key_types = list (
1910
+ await self .store .get_e2e_unused_fallback_key_types (user_id , device_id )
1911
+ )
1912
+
1913
+ return E2eeSyncResult (
1914
+ to_device = sync_result_builder .to_device ,
1915
+ device_lists = device_lists ,
1916
+ device_one_time_keys_count = one_time_keys_count ,
1917
+ device_unused_fallback_key_types = unused_fallback_key_types ,
1918
+ next_batch = sync_result_builder .now_token ,
1919
+ )
1920
+
1694
1921
async def get_sync_result_builder (
1695
1922
self ,
1696
1923
sync_config : SyncConfig ,
@@ -1889,7 +2116,7 @@ async def _generate_sync_entry_for_device_list(
1889
2116
users_that_have_changed = (
1890
2117
await self ._device_handler .get_device_changes_in_shared_rooms (
1891
2118
user_id ,
1892
- sync_result_builder . joined_room_ids ,
2119
+ joined_room_ids ,
1893
2120
from_token = since_token ,
1894
2121
now_token = sync_result_builder .now_token ,
1895
2122
)
0 commit comments