def __init__( self, private_key: PrivateKey, chain_id: ChainID, device_id: DeviceIDs, message_received_callback: Callable[[Message], None], servers: Optional[List[str]] = None, ) -> None: super().__init__() self.chain_id = chain_id self.device_id = device_id self.message_received_callback = message_received_callback self._displayname_cache = DisplayNameCache() self.startup_finished = AsyncResult() self._client_manager = ClientManager( available_servers=servers, device_id=self.device_id, chain_id=self.chain_id, private_key=private_key, handle_matrix_sync=self._handle_matrix_sync, ) self.base_url = self._client.api.base_url self.user_manager = MultiClientUserAddressManager( client=self._client, displayname_cache=self._displayname_cache, ) self._rate_limiter = RateLimiter( allowed_bytes=MATRIX_RATE_LIMIT_ALLOWED_BYTES, reset_interval=MATRIX_RATE_LIMIT_RESET_INTERVAL, )
def test_client_manager_start(get_accounts, get_private_key): server_urls = [f"https://example0{i}.com" for i in range(5)] (c1, ) = get_accounts(1) private_key = get_private_key(c1) client_mock = Mock() client_mock.api.base_url = "https://example00.com" client_mock.user_id = "1" client_mock.sync_worker = AsyncResult() start_client_counter = 0 def mock_start_client(server_url: str): # pylint: disable=unused-argument nonlocal start_client_counter client_mock.sync_worker = AsyncResult() start_client_counter += 1 return client_mock with patch.multiple( "raiden_libs.matrix", make_client=Mock(return_value=client_mock), get_matrix_servers=Mock(return_value=server_urls), login=Mock(), join_broadcast_room=Mock(), ): client_manager = ClientManager( available_servers=[f"https://example0{i}.com" for i in range(5)], broadcast_room_alias_prefix="_service", chain_id=ChainID(1), device_id=DeviceIDs.PFS, private_key=private_key, handle_matrix_sync=lambda s: True, ) client_manager._start_client = mock_start_client # pylint: disable=protected-access assert client_manager.known_servers == server_urls uam = MultiClientUserAddressManager( client=client_manager.main_client, displayname_cache=DisplayNameCache(), ) uam.start() client_manager.user_manager = uam client_manager.stop_event.clear() gevent.spawn(client_manager.connect_client_forever, client_mock.api.base_url) gevent.sleep(2) client_mock.sync_worker.set(True) gevent.sleep(2) client_manager.stop_event.set() client_mock.sync_worker.set(True) assert start_client_counter == 2
def test_filter_presences_by_client(presence_event, server_index): server_urls = [f"https://example0{i}.com" for i in range(5)] uuids_to_callbacks = {} server_url_to_clients = {} server_url_to_processed_presence = [] def _mock_add_presence_listener(listener): uuid = uuid1() uuids_to_callbacks[uuid] = listener return uuid def _mock_remove_presence_listener(listener_id): uuids_to_callbacks.pop(listener_id) def _mock_presence_listener( self, event: Dict[str, Any], presence_update_id: int # pylint: disable=unused-argument ) -> None: server_url_to_processed_presence.append(event) for server_url in server_urls: client = Mock() client.api.base_url = server_url client.add_presence_listener = _mock_add_presence_listener client.remove_presence_listener = _mock_remove_presence_listener server_url_to_clients[server_url] = client main_client = server_url_to_clients.pop(server_urls[0]) with mock.patch.object(MultiClientUserAddressManager, "_presence_listener", new=_mock_presence_listener): uam = MultiClientUserAddressManager( client=main_client, displayname_cache=DisplayNameCache(), ) uam.start() for client in server_url_to_clients.values(): uam.add_client(client) # call presence listener on all clients for listener in uuids_to_callbacks.values(): listener(presence_event, 0) # only the respective client should forward the presence to the uam expected_presences = 1 assert len(server_url_to_processed_presence) == expected_presences # drop the client of the last homeserver in the list # and remove listener client = server_url_to_clients[server_urls[-1]] uam.remove_client(client) # only call the listener of the main client # if the presence event comes from the server with the dropped client # it should also consume the presence, otherwise not uuids_to_callbacks[uam._listener_id](presence_event, 0) # pylint: disable=protected-access if server_index in [0, len(server_urls) - 1]: expected_presences += 1 assert len(server_url_to_processed_presence) == expected_presences
class MatrixListener(gevent.Greenlet): # pylint: disable=too-many-instance-attributes def __init__( self, private_key: PrivateKey, chain_id: ChainID, device_id: DeviceIDs, message_received_callback: Callable[[Message], None], servers: Optional[List[str]] = None, ) -> None: super().__init__() self.chain_id = chain_id self.device_id = device_id self.message_received_callback = message_received_callback self._displayname_cache = DisplayNameCache() self.startup_finished = AsyncResult() self._client_manager = ClientManager( available_servers=servers, device_id=self.device_id, chain_id=self.chain_id, private_key=private_key, handle_matrix_sync=self._handle_matrix_sync, ) self.base_url = self._client.api.base_url self.user_manager = MultiClientUserAddressManager( client=self._client, displayname_cache=self._displayname_cache, ) self._rate_limiter = RateLimiter( allowed_bytes=MATRIX_RATE_LIMIT_ALLOWED_BYTES, reset_interval=MATRIX_RATE_LIMIT_RESET_INTERVAL, ) @property def _client(self) -> GMatrixClient: return self._client_manager.main_client @property def server_url_to_other_clients(self) -> Dict[str, GMatrixClient]: return self._client_manager.server_url_to_other_clients def _run(self) -> None: # pylint: disable=method-hidden self.user_manager.start() self._client_manager.start(self.user_manager) def set_startup_finished() -> None: self._client.processed.wait() self.startup_finished.set() startup_finished_greenlet = gevent.spawn(set_startup_finished) try: assert self._client.sync_worker self._client.sync_worker.get() finally: self._client_manager.stop() gevent.joinall({startup_finished_greenlet}, raise_error=True, timeout=0) def _handle_matrix_sync(self, messages: MatrixSyncMessages) -> bool: all_messages: List[Message] = list() for room, room_messages in messages: if room is not None: # Ignore room messages # This will only handle to-device messages continue for text in room_messages: all_messages.extend(self._handle_message(room, text)) log.debug("Incoming messages", messages=all_messages) for message in all_messages: self.message_received_callback(message) return True def _handle_message(self, room: Optional[Room], message: MatrixMessage) -> List[SignedMessage]: """Handle a single Matrix message. The matrix message is expected to be a NDJSON, and each entry should be a valid JSON encoded Raiden message. If `room` is None this means we are processing a `to_device` message """ is_valid_type = (message["type"] == "m.room.message" and message["content"]["msgtype"] == MatrixMessageType.TEXT.value) if not is_valid_type: return [] sender_id = message["sender"] self._displayname_cache.warm_users([User(self._client.api, sender_id)]) # handles the "Could not get 'display_name' for user" case try: displayname = self._displayname_cache.userid_to_displayname[ sender_id] except KeyError: log.exception("Could not warm display cache", peer_user=sender_id) return [] peer_address = validate_user_id_signature(sender_id, displayname) if not peer_address: log.debug( "Message from invalid user displayName signature", peer_user=sender_id, room=room, ) return [] data = message["content"]["body"] if not isinstance(data, str): log.warning( "Received message body not a string", peer_user=sender_id, peer_address=to_checksum_address(peer_address), room=room, ) return [] messages = deserialize_messages(data=data, peer_address=peer_address, rate_limiter=self._rate_limiter) if not messages: return [] return messages