Philipp Hörist pushed to branch master at gajim / gajim
Commits: eeffaf9f by Philipp Hörist at 2023-05-14T21:21:48+02:00 new: Add lru cache decorate with ttl
- - - - - bb78fa8b by Philipp Hörist at 2023-05-14T21:28:02+02:00 refactor: OMEMO: Simplify pre condition checks
- Separate pre condition checks for group chats and single chats - Make more methods private - Use lru cache with ttl for device list and bundle queries
- - - - - 49156380 by Philipp Hörist at 2023-05-14T21:35:09+02:00 refactor: OMEMO: Improve logging
- - - - -
2 changed files:
- gajim/common/modules/omemo.py - + gajim/common/util/decorators.py
Changes:
===================================== gajim/common/modules/omemo.py ===================================== @@ -76,6 +76,7 @@ from gajim.common.modules.util import prepare_stanza from gajim.common.storage.omemo import OMEMOStorage from gajim.common.structs import OutgoingMessage +from gajim.common.util.decorators import lru_cache_with_ttl
ALLOWED_TAGS = [ ('request', Namespace.RECEIPTS), @@ -149,13 +150,9 @@ def __init__(self, client: types.Client) -> None:
self._omemo_groupchats: set[str] = set() self._muc_temp_store: dict[bytes, str] = {} - self._query_for_bundles: list[str] = [] - self._device_bundle_querys: list[int] = [] - self._query_for_devicelists: list[str] = []
def _on_signed_in(self, _event: SignedIn) -> None: - self._log.info('Announce Support after Sign In') - self._query_for_bundles = [] + self._log.info('Publish our bundle after sign in') self.set_bundle() self.request_devicelist()
@@ -180,8 +177,8 @@ def _on_room_joined(self,
jid = str(contact.jid) self._check_if_omemo_capable(jid) - if self.is_omemo_groupchat(jid): - self.get_affiliation_list(jid) + if self._is_omemo_groupchat(jid): + self._get_affiliation_list(jid)
def _on_republish_bundle(self, _session: OMEMOSessionManager, @@ -198,52 +195,79 @@ def backend(self) -> OMEMOSessionManager: def check_send_preconditions(self, contact: types.ChatContactT) -> bool: jid = str(contact.jid) if contact.is_groupchat: - if not self.is_omemo_groupchat(jid): - app.ged.raise_event(EncryptionInfo( - account=contact.account, - jid=contact.jid, - message=EncryptionInfoMsg.BAD_OMEMO_CONFIG)) + if not self._groupchat_pre_conditions_satisfied(contact): return False
- missing = True - for member_jid in self.backend.get_group_members(jid): - if not self.are_keys_missing(member_jid): - missing = False - if missing: - self._log.info('%s => No Trusted Fingerprints for %s', - contact.account, jid) - app.ged.raise_event(EncryptionInfo( - account=contact.account, - jid=contact.jid, - message=EncryptionInfoMsg.NO_FINGERPRINTS)) - return False else: - # check if we have devices for the contact - if not self.backend.get_devices(jid, without_self=True): - self.request_devicelist(jid) - app.ged.raise_event(EncryptionInfo( - account=contact.account, - jid=contact.jid, - message=EncryptionInfoMsg.QUERY_DEVICES)) + if not self._chat_pre_conditions_satisfied(contact): return False
if self.backend.get_identity_infos(jid, only_active=True, trust=OMEMOTrust.UNDECIDED): - self._log.info('%s => Undecided Fingerprints for %s', - contact.account, jid) + self._log.info('Undecided keys for %s', jid) app.ged.raise_event(EncryptionInfo( account=contact.account, jid=contact.jid, message=EncryptionInfoMsg.UNDECIDED_FINGERPRINTS)) return False
- self._log.debug('%s => Sending Message to %s', - contact.account, jid) + self._log.debug('Sending message to %s', jid)
return True
- def is_omemo_groupchat(self, room_jid: str) -> bool: + def _groupchat_pre_conditions_satisfied( + self, + contact: types.GroupchatContactT + ) -> bool: + + jid = str(contact.jid) + if not self._is_omemo_groupchat(jid): + app.ged.raise_event(EncryptionInfo( + account=contact.account, + jid=contact.jid, + message=EncryptionInfoMsg.BAD_OMEMO_CONFIG)) + return False + + has_trusted_keys = False + for member_jid in self.backend.get_group_members(jid): + self._request_bundles_for_new_devices(member_jid) + if self._has_trusted_keys(member_jid): + has_trusted_keys = True + + if not has_trusted_keys: + self._log.info('No trusted keys for %s', jid) + app.ged.raise_event(EncryptionInfo( + account=contact.account, + jid=contact.jid, + message=EncryptionInfoMsg.NO_FINGERPRINTS)) + return False + return True + + def _chat_pre_conditions_satisfied( + self, + contact: types.ChatContactT + ) -> bool: + + jid = str(contact.jid) + if not self.backend.get_devices(jid, without_self=True): + self.request_devicelist(jid) + app.ged.raise_event(EncryptionInfo( + account=contact.account, + jid=contact.jid, + message=EncryptionInfoMsg.QUERY_DEVICES)) + return False + + if not self._has_trusted_keys(jid): + self._log.info('No trusted keys for %s', jid) + app.ged.raise_event(EncryptionInfo( + account=contact.account, + jid=contact.jid, + message=EncryptionInfoMsg.NO_FINGERPRINTS)) + return False + return True + + def _is_omemo_groupchat(self, room_jid: str) -> bool: return room_jid in self._omemo_groupchats
def encrypt_message(self, event: OutgoingMessage) -> bool: @@ -311,7 +335,7 @@ def _send_key_transport_message(self, return
transport_message = get_key_transport_message(typ, jid, omemo_message) - self._log.info('Send key transport message %s (%s)', jid, devices) + self._log.info('Send key transport message to %s (%s)', jid, devices) self._client.send_stanza(transport_message)
def _message_received(self, @@ -352,7 +376,7 @@ def _message_received(self, raise NodeProcessed
if properties.omemo.payload not in self._muc_temp_store: - self._log.warning("Can't decrypt own GroupChat Message") + self._log.warning("Can't decrypt own group chat message") return
plaintext = self._muc_temp_store[properties.omemo.payload] @@ -397,7 +421,7 @@ def _process_muc_message(self, return contact.real_jid.bare
self._log.error( - 'Unable to find JID for group chat message from %s', resource) + 'Unable to find jid for group chat message from %s', resource) return None
def _process_mam_message(self, @@ -408,7 +432,7 @@ def _process_mam_message(self, if properties.from_muc: self._log.info('MUC MAM Message received') if properties.muc_user is None or properties.muc_user.jid is None: - self._log.warning('Received MAM Message which can ' + self._log.warning('Received MAM message which can ' 'not be mapped to a real jid') return None return properties.muc_user.jid.bare @@ -439,13 +463,13 @@ def _on_muc_user_presence(self, else: self.backend.add_group_member(room, jid)
- if self.is_omemo_groupchat(room): - if not self.is_contact_in_roster(jid): + if self._is_omemo_groupchat(room): + if not self._is_contact_in_roster(jid): # Query Devicelists from JIDs not in our Roster - self._log.info('%s not in Roster, query devicelist...', jid) - self.request_devicelist(jid) + self._log.info('%s not in roster, query devicelist...', jid) + self._request_device_list_ttl(jid)
- def get_affiliation_list(self, room_jid: str) -> None: + def _get_affiliation_list(self, room_jid: str) -> None: for affiliation in ('owner', 'admin', 'member'): self._nbxmpp('MUC').get_affiliation( room_jid, @@ -465,12 +489,11 @@ def _on_affiliations_received(self, task: Task) -> None: jid = str(user_jid) self.backend.add_group_member(room_jid, jid)
- if not self.is_contact_in_roster(jid): - # Query Devicelists from JIDs not in our Roster - self._log.info('%s not in Roster, query devicelist...', jid) - self.request_devicelist(jid) + if not self._is_contact_in_roster(jid): + self._log.info('%s not in roster, query devicelist...', jid) + self._request_device_list_ttl(jid)
- def is_contact_in_roster(self, jid: str) -> bool: + def _is_contact_in_roster(self, jid: str) -> bool: if jid == self._own_jid: return True
@@ -490,60 +513,19 @@ def _check_if_omemo_capable(self, jid: str) -> None: self._log.info('OMEMO room removed due to config change: %s', jid) self._omemo_groupchats.discard(jid)
- def _check_for_missing_sessions(self, jid: str) -> None: - devices_without_session = self.backend.get_devices_without_sessions(jid) - for device_id in devices_without_session: - if device_id in self._device_bundle_querys: - continue - self._device_bundle_querys.append(device_id) - self.request_bundle(jid, device_id) - - def are_keys_missing(self, contact_jid: str) -> bool: - ''' Checks if devicekeys are missing and queries the - bundles - - Parameters - ---------- - contact_jid : str - bare jid of the contact - - Returns - ------- - bool - Returns True if there are no trusted Fingerprints - ''' - - # Fetch Bundles of own other Devices - if self._own_jid not in self._query_for_bundles: - - devices_without_session = self.backend.get_devices_without_sessions( - self._own_jid) - - self._query_for_bundles.append(self._own_jid) - - if devices_without_session: - for device_id in devices_without_session: - self.request_bundle(self._own_jid, device_id) - - # Fetch Bundles of contacts devices - if contact_jid not in self._query_for_bundles: - - devices_without_session = self.backend.get_devices_without_sessions( - contact_jid) - - self._query_for_bundles.append(contact_jid) - - if devices_without_session: - for device_id in devices_without_session: - self.request_bundle(contact_jid, device_id) + def _request_bundles_for_new_devices(self, jid_: str) -> None: + for jid in [jid_, self._own_jid]: + device_ids = self.backend.get_devices_without_sessions(jid) + for device_id in device_ids: + self._request_bundle_ttl(jid, device_id)
+ def _has_trusted_keys(self, jid: str) -> bool: if self.backend.get_identity_infos( - contact_jid, + jid, only_active=True, trust=[OMEMOTrust.VERIFIED, OMEMOTrust.BLIND]): - return False - - return True + return True + return False
def set_bundle(self, bundle: OMEMOBundle | None = None) -> None: if bundle is None: @@ -551,11 +533,15 @@ def set_bundle(self, bundle: OMEMOBundle | None = None) -> None: self._nbxmpp('OMEMO').set_bundle(bundle, self.backend.get_our_device())
+ @lru_cache_with_ttl(maxsize=512, ttl=7200) + def _request_bundle_ttl(self, jid: str, device_id: int) -> None: + self.request_bundle(jid, device_id) + @as_task def request_bundle(self, jid: str, device_id: int): _task = yield # noqa: F841
- self._log.info('Fetch device bundle %s %s', device_id, jid) + self._log.info('Request device bundle %s %s', device_id, jid)
bundle = yield self._nbxmpp('OMEMO').request_bundle(jid, device_id)
@@ -588,6 +574,10 @@ def clear_devicelist(self) -> None: self._own_jid, [self.backend.get_our_device()]) self.set_devicelist()
+ @lru_cache_with_ttl(maxsize=512, ttl=7200) + def _request_device_list_ttl(self, jid: str) -> None: + self.request_devicelist(jid) + @as_task def request_devicelist(self, jid: Optional[str] = None): _task = yield # noqa: F841 @@ -595,10 +585,7 @@ def request_devicelist(self, jid: Optional[str] = None): if jid is None: jid = self._own_jid
- if jid in self._query_for_devicelists: - return - - self._query_for_devicelists.append(jid) + self._log.info('Request devicelist for %s', jid)
devicelist = yield self._nbxmpp('OMEMO').request_devicelist(jid=jid) if is_error(devicelist) or devicelist is None: @@ -634,16 +621,13 @@ def _process_devicelist_update(self, # Pass a copy, we need the full list for potential set_devicelist() self.backend.update_devicelist(jid, list(devicelist))
- if jid in self._query_for_devicelists: - self._query_for_devicelists.remove(jid) - if own_devices: if not self.backend.is_our_device_published(): # Our own device_id is not in the list, it could be # overwritten by some other client self.set_devicelist(devicelist)
- self._check_for_missing_sessions(jid) + self._request_bundles_for_new_devices(jid)
def _debug_print_stanza(self, stanza: Any) -> None: stanzastr = '\n' + stanza.__str__(fancy=True)
===================================== gajim/common/util/decorators.py ===================================== @@ -0,0 +1,52 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/. + +from typing import Any + +from functools import lru_cache +from functools import wraps +from time import monotonic + + +def lru_cache_with_ttl(maxsize: int, + typed: bool = False, + ttl: int = 2 + ) -> Any: + '''LRU-Cache with TTL''' + + class Result: + __slots__ = ('value', 'timeout') + + def __init__(self, value: Any, timeout: float) -> None: + self.value = value + self.timeout = timeout + + def decorator(func: Any) -> Any: + @lru_cache(maxsize=maxsize, typed=typed) + def cached_func(*args: Any, **kwargs: Any) -> Result: + value = func(*args, **kwargs) + timeout = monotonic() + ttl + return Result(value, timeout) + + @wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: + result = cached_func(*args, **kwargs) + if result.timeout < monotonic(): + result.value = func(*args, **kwargs) + result.timeout = monotonic() + ttl + return result.value + + return wrapper + + return decorator
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/3f8483c3b2a87a64610970fb74f2c747...