def add_device_change_to_streams(self, user_id, device_ids, hosts): """Persist that a user's devices have been updated, and which hosts (if any) should be poked. """ if not device_ids: return with self._device_list_id_gen.get_next_mult(len(device_ids)) as stream_ids: yield self.db.runInteraction( "add_device_change_to_stream", self._add_device_change_to_stream_txn, user_id, device_ids, stream_ids, ) if not hosts: return stream_ids[-1] context = get_active_span_text_map() with self._device_list_id_gen.get_next_mult( len(hosts) * len(device_ids) ) as stream_ids: yield self.db.runInteraction( "add_device_outbound_poke_to_stream", self._add_device_outbound_poke_to_stream_txn, user_id, device_ids, hosts, stream_ids, context, ) return stream_ids[-1]
async def send_device_message( self, sender_user_id: str, message_type: str, messages: Dict[str, Dict[str, JsonDict]], ) -> None: set_tag("number_of_messages", len(messages)) set_tag("sender", sender_user_id) local_messages = {} remote_messages = {} # type: Dict[str, Dict[str, Dict[str, JsonDict]]] for user_id, by_device in messages.items(): # we use UserID.from_string to catch invalid user ids if self.is_mine(UserID.from_string(user_id)): messages_by_device = { device_id: { "content": message_content, "type": message_type, "sender": sender_user_id, } for device_id, message_content in by_device.items() } if messages_by_device: local_messages[user_id] = messages_by_device else: destination = get_domain_from_id(user_id) remote_messages.setdefault(destination, {})[user_id] = by_device message_id = random_string(16) context = get_active_span_text_map() remote_edu_contents = {} for destination, messages in remote_messages.items(): with start_active_span("to_device_for_user"): set_tag("destination", destination) remote_edu_contents[destination] = { "messages": messages, "sender": sender_user_id, "type": message_type, "message_id": message_id, "org.matrix.opentracing_context": json_encoder.encode(context), } log_kv({"local_messages": local_messages}) stream_id = await self.store.add_messages_to_device_inbox( local_messages, remote_edu_contents) self.notifier.on_new_event("to_device_key", stream_id, users=local_messages.keys()) log_kv({"remote_messages": remote_messages}) if self.federation_sender: for destination in remote_messages.keys(): # Enqueue a new federation transaction to send the new # device messages to each remote destination. self.federation_sender.send_device_messages(destination)
def _add_device_change_txn(self, txn, user_id, device_ids, hosts, stream_id): now = self._clock.time_msec() txn.call_after(self._device_list_stream_cache.entity_has_changed, user_id, stream_id) for host in hosts: txn.call_after( self._device_list_federation_stream_cache.entity_has_changed, host, stream_id, ) # Delete older entries in the table, as we really only care about # when the latest change happened. txn.executemany( """ DELETE FROM device_lists_stream WHERE user_id = ? AND device_id = ? AND stream_id < ? """, [(user_id, device_id, stream_id) for device_id in device_ids], ) self._simple_insert_many_txn( txn, table="device_lists_stream", values=[{ "stream_id": stream_id, "user_id": user_id, "device_id": device_id } for device_id in device_ids], ) context = get_active_span_text_map() self._simple_insert_many_txn( txn, table="device_lists_outbound_pokes", values=[{ "destination": destination, "stream_id": stream_id, "user_id": user_id, "device_id": device_id, "sent": False, "ts": now, "opentracing_context": json.dumps(context) if whitelisted_homeserver(destination) else "{}", } for destination in hosts for device_id in device_ids], )
async def send_device_message( self, requester: Requester, message_type: str, messages: Dict[str, Dict[str, JsonDict]], ) -> None: """ Handle a request from a user to send to-device message(s). Args: requester: The user that is sending the to-device messages. message_type: The type of to-device messages that are being sent. messages: A dictionary containing recipients mapped to messages intended for them. """ sender_user_id = requester.user.to_string() message_id = random_string(16) set_tag(SynapseTags.TO_DEVICE_MESSAGE_ID, message_id) log_kv({"number_of_to_device_messages": len(messages)}) set_tag("sender", sender_user_id) local_messages = {} remote_messages: Dict[str, Dict[str, Dict[str, JsonDict]]] = {} for user_id, by_device in messages.items(): # Ratelimit local cross-user key requests by the sending device. if ( message_type == ToDeviceEventTypes.RoomKeyRequest and user_id != sender_user_id ): allowed, _ = await self._ratelimiter.can_do_action( requester, (sender_user_id, requester.device_id) ) if not allowed: logger.info( "Dropping room_key_request from %s to %s due to rate limit", sender_user_id, user_id, ) continue # we use UserID.from_string to catch invalid user ids if self.is_mine(UserID.from_string(user_id)): messages_by_device = { device_id: { "content": message_content, "type": message_type, "sender": sender_user_id, "message_id": message_id, } for device_id, message_content in by_device.items() } if messages_by_device: local_messages[user_id] = messages_by_device log_kv( { "user_id": user_id, "device_id": list(messages_by_device), } ) else: destination = get_domain_from_id(user_id) remote_messages.setdefault(destination, {})[user_id] = by_device context = get_active_span_text_map() remote_edu_contents = {} for destination, messages in remote_messages.items(): log_kv({"destination": destination}) remote_edu_contents[destination] = { "messages": messages, "sender": sender_user_id, "type": message_type, "message_id": message_id, "org.matrix.opentracing_context": json_encoder.encode(context), } # Add messages to the database. # Retrieve the stream id of the last-processed to-device message. last_stream_id = await self.store.add_messages_to_device_inbox( local_messages, remote_edu_contents ) # Notify listeners that there are new to-device messages to process, # handing them the latest stream id. self.notifier.on_new_event( "to_device_key", last_stream_id, users=local_messages.keys() ) if self.federation_sender: for destination in remote_messages.keys(): # Enqueue a new federation transaction to send the new # device messages to each remote destination. self.federation_sender.send_device_messages(destination)
async def send_device_message( self, requester: Requester, message_type: str, messages: Dict[str, Dict[str, JsonDict]], ) -> None: sender_user_id = requester.user.to_string() message_id = random_string(16) set_tag(SynapseTags.TO_DEVICE_MESSAGE_ID, message_id) log_kv({"number_of_to_device_messages": len(messages)}) set_tag("sender", sender_user_id) local_messages = {} remote_messages = {} # type: Dict[str, Dict[str, Dict[str, JsonDict]]] for user_id, by_device in messages.items(): # Ratelimit local cross-user key requests by the sending device. if (message_type == EduTypes.RoomKeyRequest and user_id != sender_user_id and await self._ratelimiter.can_do_action( requester, (sender_user_id, requester.device_id))): continue # we use UserID.from_string to catch invalid user ids if self.is_mine(UserID.from_string(user_id)): messages_by_device = { device_id: { "content": message_content, "type": message_type, "sender": sender_user_id, "message_id": message_id, } for device_id, message_content in by_device.items() } if messages_by_device: local_messages[user_id] = messages_by_device log_kv({ "user_id": user_id, "device_id": list(messages_by_device), }) else: destination = get_domain_from_id(user_id) remote_messages.setdefault(destination, {})[user_id] = by_device context = get_active_span_text_map() remote_edu_contents = {} for destination, messages in remote_messages.items(): log_kv({"destination": destination}) remote_edu_contents[destination] = { "messages": messages, "sender": sender_user_id, "type": message_type, "message_id": message_id, "org.matrix.opentracing_context": json_encoder.encode(context), } stream_id = await self.store.add_messages_to_device_inbox( local_messages, remote_edu_contents) self.notifier.on_new_event("to_device_key", stream_id, users=local_messages.keys()) if self.federation_sender: for destination in remote_messages.keys(): # Enqueue a new federation transaction to send the new # device messages to each remote destination. self.federation_sender.send_device_messages(destination)