Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Send device list updates to application services (MSC3202) - part 1 (#…
Browse files Browse the repository at this point in the history
…11881)

Co-authored-by: Patrick Cloke <clokep@users.noreply.github.com>
  • Loading branch information
anoadragon453 and clokep committed Mar 30, 2022
1 parent 2fc15ac commit d8d0271
Show file tree
Hide file tree
Showing 15 changed files with 490 additions and 82 deletions.
1 change: 1 addition & 0 deletions changelog.d/11881.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Send device list changes to application services as specified by [MSC3202](https://github.com/matrix-org/matrix-spec-proposals/pull/3202), using unstable prefixes. The `msc3202_transaction_extensions` experimental homeserver config option must be enabled and `org.matrix.msc3202: true` must be present in the application service registration file for device list changes to be sent. The "left" field is currently always empty.
12 changes: 11 additions & 1 deletion synapse/appservice/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2015, 2016 OpenMarket Ltd
# Copyright 2022 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -22,7 +23,13 @@

from synapse.api.constants import EventTypes
from synapse.events import EventBase
from synapse.types import GroupID, JsonDict, UserID, get_domain_from_id
from synapse.types import (
DeviceListUpdates,
GroupID,
JsonDict,
UserID,
get_domain_from_id,
)
from synapse.util.caches.descriptors import _CacheContext, cached

if TYPE_CHECKING:
Expand Down Expand Up @@ -400,6 +407,7 @@ def __init__(
to_device_messages: List[JsonDict],
one_time_key_counts: TransactionOneTimeKeyCounts,
unused_fallback_keys: TransactionUnusedFallbackKeys,
device_list_summary: DeviceListUpdates,
):
self.service = service
self.id = id
Expand All @@ -408,6 +416,7 @@ def __init__(
self.to_device_messages = to_device_messages
self.one_time_key_counts = one_time_key_counts
self.unused_fallback_keys = unused_fallback_keys
self.device_list_summary = device_list_summary

async def send(self, as_api: "ApplicationServiceApi") -> bool:
"""Sends this transaction using the provided AS API interface.
Expand All @@ -424,6 +433,7 @@ async def send(self, as_api: "ApplicationServiceApi") -> bool:
to_device_messages=self.to_device_messages,
one_time_key_counts=self.one_time_key_counts,
unused_fallback_keys=self.unused_fallback_keys,
device_list_summary=self.device_list_summary,
txn_id=self.id,
)

Expand Down
10 changes: 9 additions & 1 deletion synapse/appservice/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2015, 2016 OpenMarket Ltd
# Copyright 2022 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,7 +28,7 @@
from synapse.events import EventBase
from synapse.events.utils import SerializeEventConfig, serialize_event
from synapse.http.client import SimpleHttpClient
from synapse.types import JsonDict, ThirdPartyInstanceID
from synapse.types import DeviceListUpdates, JsonDict, ThirdPartyInstanceID
from synapse.util.caches.response_cache import ResponseCache

if TYPE_CHECKING:
Expand Down Expand Up @@ -225,6 +226,7 @@ async def push_bulk(
to_device_messages: List[JsonDict],
one_time_key_counts: TransactionOneTimeKeyCounts,
unused_fallback_keys: TransactionUnusedFallbackKeys,
device_list_summary: DeviceListUpdates,
txn_id: Optional[int] = None,
) -> bool:
"""
Expand Down Expand Up @@ -268,6 +270,7 @@ async def push_bulk(
}
)

# TODO: Update to stable prefixes once MSC3202 completes FCP merge
if service.msc3202_transaction_extensions:
if one_time_key_counts:
body[
Expand All @@ -277,6 +280,11 @@ async def push_bulk(
body[
"org.matrix.msc3202.device_unused_fallback_keys"
] = unused_fallback_keys
if device_list_summary:
body["org.matrix.msc3202.device_lists"] = {
"changed": list(device_list_summary.changed),
"left": list(device_list_summary.left),
}

try:
await self.put_json(
Expand Down
53 changes: 50 additions & 3 deletions synapse/appservice/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
from synapse.logging.context import run_in_background
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.storage.databases.main import DataStore
from synapse.types import JsonDict
from synapse.types import DeviceListUpdates, JsonDict
from synapse.util import Clock

if TYPE_CHECKING:
Expand Down Expand Up @@ -122,6 +122,7 @@ def enqueue_for_appservice(
events: Optional[Collection[EventBase]] = None,
ephemeral: Optional[Collection[JsonDict]] = None,
to_device_messages: Optional[Collection[JsonDict]] = None,
device_list_summary: Optional[DeviceListUpdates] = None,
) -> None:
"""
Enqueue some data to be sent off to an application service.
Expand All @@ -133,10 +134,18 @@ def enqueue_for_appservice(
to_device_messages: The to-device messages to send. These differ from normal
to-device messages sent to clients, as they have 'to_device_id' and
'to_user_id' fields.
device_list_summary: A summary of users that the application service either needs
to refresh the device lists of, or those that the application service need no
longer track the device lists of.
"""
# We purposefully allow this method to run with empty events/ephemeral
# collections, so that callers do not need to check iterable size themselves.
if not events and not ephemeral and not to_device_messages:
if (
not events
and not ephemeral
and not to_device_messages
and not device_list_summary
):
return

if events:
Expand All @@ -147,6 +156,10 @@ def enqueue_for_appservice(
self.queuer.queued_to_device_messages.setdefault(appservice.id, []).extend(
to_device_messages
)
if device_list_summary:
self.queuer.queued_device_list_summaries.setdefault(
appservice.id, []
).append(device_list_summary)

# Kick off a new application service transaction
self.queuer.start_background_request(appservice)
Expand All @@ -169,6 +182,8 @@ def __init__(
self.queued_ephemeral: Dict[str, List[JsonDict]] = {}
# dict of {service_id: [to_device_message_json]}
self.queued_to_device_messages: Dict[str, List[JsonDict]] = {}
# dict of {service_id: [device_list_summary]}
self.queued_device_list_summaries: Dict[str, List[DeviceListUpdates]] = {}

# the appservices which currently have a transaction in flight
self.requests_in_flight: Set[str] = set()
Expand Down Expand Up @@ -212,7 +227,35 @@ async def _send_request(self, service: ApplicationService) -> None:
]
del all_to_device_messages[:MAX_TO_DEVICE_MESSAGES_PER_TRANSACTION]

if not events and not ephemeral and not to_device_messages_to_send:
# Consolidate any pending device list summaries into a single, up-to-date
# summary.
# Note: this code assumes that in a single DeviceListUpdates, a user will
# never be in both "changed" and "left" sets.
device_list_summary = DeviceListUpdates()
for summary in self.queued_device_list_summaries.get(service.id, []):
# For every user in the incoming "changed" set:
# * Remove them from the existing "left" set if necessary
# (as we need to start tracking them again)
# * Add them to the existing "changed" set if necessary.
device_list_summary.left.difference_update(summary.changed)
device_list_summary.changed.update(summary.changed)

# For every user in the incoming "left" set:
# * Remove them from the existing "changed" set if necessary
# (we no longer need to track them)
# * Add them to the existing "left" set if necessary.
device_list_summary.changed.difference_update(summary.left)
device_list_summary.left.update(summary.left)
self.queued_device_list_summaries.clear()

if (
not events
and not ephemeral
and not to_device_messages_to_send
# DeviceListUpdates is True if either the 'changed' or 'left' sets have
# at least one entry, otherwise False
and not device_list_summary
):
return

one_time_key_counts: Optional[TransactionOneTimeKeyCounts] = None
Expand Down Expand Up @@ -240,6 +283,7 @@ async def _send_request(self, service: ApplicationService) -> None:
to_device_messages_to_send,
one_time_key_counts,
unused_fallback_keys,
device_list_summary,
)
except Exception:
logger.exception("AS request failed")
Expand Down Expand Up @@ -322,6 +366,7 @@ async def send(
to_device_messages: Optional[List[JsonDict]] = None,
one_time_key_counts: Optional[TransactionOneTimeKeyCounts] = None,
unused_fallback_keys: Optional[TransactionUnusedFallbackKeys] = None,
device_list_summary: Optional[DeviceListUpdates] = None,
) -> None:
"""
Create a transaction with the given data and send to the provided
Expand All @@ -336,6 +381,7 @@ async def send(
appservice devices in the transaction.
unused_fallback_keys: Lists of unused fallback keys for relevant
appservice devices in the transaction.
device_list_summary: The device list summary to include in the transaction.
"""
try:
txn = await self.store.create_appservice_txn(
Expand All @@ -345,6 +391,7 @@ async def send(
to_device_messages=to_device_messages or [],
one_time_key_counts=one_time_key_counts or {},
unused_fallback_keys=unused_fallback_keys or {},
device_list_summary=device_list_summary or DeviceListUpdates(),
)
service_is_up = await self._is_service_up(service)
if service_is_up:
Expand Down
1 change: 1 addition & 0 deletions synapse/config/appservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ def _load_appservice(
# When enabled, appservice transactions contain the following information:
# - device One-Time Key counts
# - device unused fallback key usage states
# - device list changes
msc3202_transaction_extensions = as_info.get("org.matrix.msc3202", False)
if not isinstance(msc3202_transaction_extensions, bool):
raise ValueError(
Expand Down
5 changes: 3 additions & 2 deletions synapse/config/experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ def read_config(self, config: JsonDict, **kwargs):
"msc3202_device_masquerading", False
)

# Portion of MSC3202 related to transaction extensions:
# sending one-time key counts and fallback key usage to application services.
# The portion of MSC3202 related to transaction extensions:
# sending device list changes, one-time key counts and fallback key
# usage to application services.
self.msc3202_transaction_extensions: bool = experimental.get(
"msc3202_transaction_extensions", False
)
Expand Down
Loading

0 comments on commit d8d0271

Please sign in to comment.