summary refs log tree commit diff
diff options
context:
space:
mode:
authorErik Johnston <erik@matrix.org>2021-05-05 16:37:37 +0100
committerErik Johnston <erik@matrix.org>2021-05-05 16:37:37 +0100
commit015fdfe5bb090d30f25fe97fc3fc41d8863418dc (patch)
tree9475cd81546630a79d31457857374a21a42f0156
parentFix log contexts (diff)
parentMore ensmalling (diff)
downloadsynapse-015fdfe5bb090d30f25fe97fc3fc41d8863418dc.tar.xz
Merge branch 'erikj/smaller_events' into erikj/test_send
-rw-r--r--synapse/api/filtering.py8
-rw-r--r--synapse/crypto/event_signing.py2
-rw-r--r--synapse/event_auth.py4
-rw-r--r--synapse/events/__init__.py245
-rw-r--r--synapse/events/validator.py4
-rw-r--r--synapse/federation/federation_base.py4
-rw-r--r--synapse/handlers/message.py6
-rw-r--r--synapse/handlers/room.py2
-rw-r--r--synapse/notifier.py2
-rw-r--r--synapse/push/push_rule_evaluator.py4
10 files changed, 189 insertions, 92 deletions
diff --git a/synapse/api/filtering.py b/synapse/api/filtering.py

index ce49a0ad58..3e7eb47001 100644 --- a/synapse/api/filtering.py +++ b/synapse/api/filtering.py
@@ -23,6 +23,7 @@ from jsonschema import FormatChecker from synapse.api.constants import EventContentFields from synapse.api.errors import SynapseError from synapse.api.presence import UserPresenceState +from synapse.events import EventBase from synapse.types import RoomID, UserID FILTER_SCHEMA = { @@ -290,6 +291,13 @@ class Filter: ev_type = "m.presence" contains_url = False labels = [] # type: List[str] + elif isinstance(event, EventBase): + sender = event.sender + room_id = event.room_id + ev_type = event.type + content = event.content + contains_url = isinstance(content.get("url"), str) + labels = content.get(EventContentFields.LABELS, []) else: sender = event.get("sender", None) if not sender: diff --git a/synapse/crypto/event_signing.py b/synapse/crypto/event_signing.py
index 0f2b632e47..c2a67704d6 100644 --- a/synapse/crypto/event_signing.py +++ b/synapse/crypto/event_signing.py
@@ -48,7 +48,7 @@ def check_event_content_hash( # some malformed events lack a 'hashes'. Protect against it being missing # or a weird type by basically treating it the same as an unhashed event. - hashes = event.get("hashes") + hashes = getattr(event, "hashes", None) # nb it might be a frozendict or a dict if not isinstance(hashes, collections.abc.Mapping): raise SynapseError( diff --git a/synapse/event_auth.py b/synapse/event_auth.py
index 70c556566e..eb9cd2f5eb 100644 --- a/synapse/event_auth.py +++ b/synapse/event_auth.py
@@ -418,7 +418,9 @@ def get_send_level( def _can_send_event(event: EventBase, auth_events: StateMap[EventBase]) -> bool: power_levels_event = _get_power_level_event(auth_events) - send_level = get_send_level(event.type, event.get("state_key"), power_levels_event) + send_level = get_send_level( + event.type, getattr(event, "state_key", None), power_levels_event + ) user_level = get_user_power_level(event.user_id, auth_events) if user_level < send_level: diff --git a/synapse/events/__init__.py b/synapse/events/__init__.py
index c8b52cbc7a..c04905dfed 100644 --- a/synapse/events/__init__.py +++ b/synapse/events/__init__.py
@@ -16,12 +16,15 @@ import abc import os -from typing import Dict, Optional, Tuple, Type +import zlib +from typing import Dict, List, Optional, Tuple, Type, Union -from unpaddedbase64 import encode_base64 +import attr +from unpaddedbase64 import decode_base64, encode_base64 from synapse.api.room_versions import EventFormatVersions, RoomVersion, RoomVersions from synapse.types import JsonDict, RoomStreamToken +from synapse.util import json_decoder, json_encoder from synapse.util.caches import intern_dict from synapse.util.frozenutils import freeze from synapse.util.stringutils import strtobool @@ -37,6 +40,26 @@ from synapse.util.stringutils import strtobool USE_FROZEN_DICTS = strtobool(os.environ.get("SYNAPSE_USE_FROZEN_DICTS", "0")) +_PRESET_ZDICT = b"""{"auth_events":[],"prev_events":[],"type":"m.room.member",m.room.message"room_id":,"sender":,"content":{"msgtype":"m.text","body":""room_version":"creator":"depth":"prev_state":"state_key":""origin":"origin_server_ts":"hashes":{"sha256":"signatures":,"unsigned":{"age_ts":"ed25519""" + + +def _encode_dict(d: JsonDict) -> bytes: + json_bytes = json_encoder.encode(d).encode("utf-8") + c = zlib.compressobj(1, zdict=_PRESET_ZDICT) + result_bytes = c.compress(json_bytes) + result_bytes += c.flush() + return result_bytes + + +def _decode_dict(b: bytes) -> JsonDict: + d = zlib.decompressobj(zdict=_PRESET_ZDICT) + + result_bytes = d.decompress(b) + result_bytes += d.flush() + + return json_decoder.decode(result_bytes.decode("utf-8")) + + class DictProperty: """An object property which delegates to the `_dict` within its parent object.""" @@ -205,7 +228,81 @@ class _EventInternalMetadata: return self._dict.get("redacted", False) +@attr.s(slots=True, auto_attribs=True) +class _Signatures: + _signatures_bytes: bytes + + @staticmethod + def from_dict(signature_dict: JsonDict) -> "_Signatures": + return _Signatures(_encode_dict(signature_dict)) + + def get_dict(self) -> JsonDict: + return _decode_dict(self._signatures_bytes) + + def get(self, server_name): + return self.get_dict().get(server_name) + + def update(self, other: Union[JsonDict, "_Signatures"]): + if isinstance(other, _Signatures): + other_dict = _decode_dict(other._signatures_bytes) + else: + other_dict = other + + signatures = self.get_dict() + signatures.update(other_dict) + self._signatures_bytes = _encode_dict(signatures) + + +class _SmallListV1(str): + __slots__ = [] + + def get(self): + return self.split(",") + + @staticmethod + def create(event_ids): + return _SmallListV1(",".join(event_ids)) + + +class _SmallListV2_V3(bytes): + __slots__ = [] + + def get(self, url_safe): + i = 0 + while i * 32 < len(self): + bit = self[i * 32 : (i + 1) * 32] + i += 1 + yield "$" + encode_base64(bit, urlsafe=url_safe) + + @staticmethod + def create(event_ids): + return _SmallListV2_V3( + b"".join(decode_base64(event_id[1:]) for event_id in event_ids) + ) + + class EventBase(metaclass=abc.ABCMeta): + __slots__ = [ + "room_version", + "signatures", + "unsigned", + "rejected_reason", + "_encoded_dict", + "_auth_event_ids", + "depth", + "_content", + "_hashes", + "origin", + "origin_server_ts", + "_prev_event_ids", + "redacts", + "room_id", + "sender", + "type", + "state_key", + "internal_metadata", + ] + @property @abc.abstractmethod def format_version(self) -> int: @@ -224,33 +321,45 @@ class EventBase(metaclass=abc.ABCMeta): assert room_version.event_format == self.format_version self.room_version = room_version - self.signatures = signatures + self.signatures = _Signatures.from_dict(signatures) self.unsigned = unsigned self.rejected_reason = rejected_reason - self._dict = event_dict + self._encoded_dict = _encode_dict(event_dict) + + self.depth = event_dict["depth"] + self.origin = event_dict["origin"] + self.origin_server_ts = event_dict["origin_server_ts"] + self.redacts = event_dict.get("redacts") + self.room_id = event_dict["room_id"] + self.sender = event_dict["sender"] + self.type = event_dict["type"] + if "state_key" in event_dict: + self.state_key = event_dict["state_key"] self.internal_metadata = _EventInternalMetadata(internal_metadata_dict) - auth_events = DictProperty("auth_events") - depth = DictProperty("depth") - content = DictProperty("content") - hashes = DictProperty("hashes") - origin = DictProperty("origin") - origin_server_ts = DictProperty("origin_server_ts") - prev_events = DictProperty("prev_events") - redacts = DefaultDictProperty("redacts", None) - room_id = DictProperty("room_id") - sender = DictProperty("sender") - state_key = DictProperty("state_key") - type = DictProperty("type") - user_id = DictProperty("sender") + @property + def content(self) -> JsonDict: + return self.get_dict()["content"] + + @property + def hashes(self) -> JsonDict: + return self.get_dict()["hashes"] + + @property + def prev_events(self) -> List[str]: + return list(self._prev_events) @property def event_id(self) -> str: raise NotImplementedError() @property + def user_id(self) -> str: + return self.sender + + @property def membership(self): return self.content["membership"] @@ -258,17 +367,13 @@ class EventBase(metaclass=abc.ABCMeta): return hasattr(self, "state_key") and self.state_key is not None def get_dict(self) -> JsonDict: - d = dict(self._dict) - d.update({"signatures": self.signatures, "unsigned": dict(self.unsigned)}) + d = _decode_dict(self._encoded_dict) + d.update( + {"signatures": self.signatures.get_dict(), "unsigned": dict(self.unsigned)} + ) return d - def get(self, key, default=None): - return self._dict.get(key, default) - - def get_internal_metadata_dict(self): - return self.internal_metadata.get_dict() - def get_pdu_json(self, time_now=None) -> JsonDict: pdu_json = self.get_dict() @@ -285,41 +390,11 @@ class EventBase(metaclass=abc.ABCMeta): def __set__(self, instance, value): raise AttributeError("Unrecognized attribute %s" % (instance,)) - def __getitem__(self, field): - return self._dict[field] - - def __contains__(self, field): - return field in self._dict - - def items(self): - return list(self._dict.items()) - - def keys(self): - return self._dict.keys() - - def prev_event_ids(self): - """Returns the list of prev event IDs. The order matches the order - specified in the event, though there is no meaning to it. - - Returns: - list[str]: The list of event IDs of this event's prev_events - """ - return [e for e, _ in self.prev_events] - - def auth_event_ids(self): - """Returns the list of auth event IDs. The order matches the order - specified in the event, though there is no meaning to it. - - Returns: - list[str]: The list of event IDs of this event's auth_events - """ - return [e for e, _ in self.auth_events] - def freeze(self): """'Freeze' the event dict, so it cannot be modified by accident""" # this will be a no-op if the event dict is already frozen. - self._dict = freeze(self._dict) + # self._dict = freeze(self._dict) class FrozenEvent(EventBase): @@ -355,6 +430,12 @@ class FrozenEvent(EventBase): frozen_dict = event_dict self._event_id = event_dict["event_id"] + self._auth_event_ids = _SmallListV1.create( + e for e, _ in event_dict["auth_events"] + ) + self._prev_event_ids = _SmallListV1.create( + e for e, _ in event_dict["prev_events"] + ) super().__init__( frozen_dict, @@ -369,18 +450,26 @@ class FrozenEvent(EventBase): def event_id(self) -> str: return self._event_id + def auth_event_ids(self): + return list(self._auth_event_ids.get()) + + def prev_event_ids(self): + return list(self._prev_event_ids.get()) + def __str__(self): return self.__repr__() def __repr__(self): return "<FrozenEvent event_id=%r, type=%r, state_key=%r>" % ( - self.get("event_id", None), - self.get("type", None), - self.get("state_key", None), + self.event_id, + self.type, + getattr(self, "state_key", None), ) class FrozenEventV2(EventBase): + __slots__ = ["_event_id"] + format_version = EventFormatVersions.V2 # All events of this type are V2 def __init__( @@ -415,6 +504,8 @@ class FrozenEventV2(EventBase): frozen_dict = event_dict self._event_id = None + self._auth_event_ids = _SmallListV2_V3.create(event_dict["auth_events"]) + self._prev_event_ids = _SmallListV2_V3.create(event_dict["prev_events"]) super().__init__( frozen_dict, @@ -436,24 +527,6 @@ class FrozenEventV2(EventBase): self._event_id = "$" + encode_base64(compute_event_reference_hash(self)[1]) return self._event_id - def prev_event_ids(self): - """Returns the list of prev event IDs. The order matches the order - specified in the event, though there is no meaning to it. - - Returns: - list[str]: The list of event IDs of this event's prev_events - """ - return self.prev_events - - def auth_event_ids(self): - """Returns the list of auth event IDs. The order matches the order - specified in the event, though there is no meaning to it. - - Returns: - list[str]: The list of event IDs of this event's auth_events - """ - return self.auth_events - def __str__(self): return self.__repr__() @@ -461,14 +534,22 @@ class FrozenEventV2(EventBase): return "<%s event_id=%r, type=%r, state_key=%r>" % ( self.__class__.__name__, self.event_id, - self.get("type", None), - self.get("state_key", None), + self.type, + self.state_key if self.is_state() else None, ) + def auth_event_ids(self): + return list(self._auth_event_ids.get(False)) + + def prev_event_ids(self): + return list(self._prev_event_ids.get(False)) + class FrozenEventV3(FrozenEventV2): """FrozenEventV3, which differs from FrozenEventV2 only in the event_id format""" + __slots__ = ["_event_id"] + format_version = EventFormatVersions.V3 # All events of this type are V3 @property @@ -484,6 +565,12 @@ class FrozenEventV3(FrozenEventV2): ) return self._event_id + def auth_event_ids(self): + return list(self._auth_event_ids.get(True)) + + def prev_event_ids(self): + return list(self._prev_event_ids.get(True)) + def _event_type_from_format_version(format_version: int) -> Type[EventBase]: """Returns the python type to use to construct an Event object for the diff --git a/synapse/events/validator.py b/synapse/events/validator.py
index fa6987d7cb..47a74fd5a3 100644 --- a/synapse/events/validator.py +++ b/synapse/events/validator.py
@@ -38,6 +38,8 @@ class EventValidator: if event.format_version == EventFormatVersions.V1: EventID.from_string(event.event_id) + event_dict = event.get_dict() + required = [ "auth_events", "content", @@ -49,7 +51,7 @@ class EventValidator: ] for k in required: - if not hasattr(event, k): + if k not in event_dict: raise SynapseError(400, "Event does not have key %s" % (k,)) # Check that the following keys have string values diff --git a/synapse/federation/federation_base.py b/synapse/federation/federation_base.py
index a86a0b5f5e..dd631c7794 100644 --- a/synapse/federation/federation_base.py +++ b/synapse/federation/federation_base.py
@@ -90,9 +90,7 @@ class FederationBase: # received event was probably a redacted copy (but we then use our # *actual* redacted copy to be on the safe side.) redacted_event = prune_event(pdu) - if set(redacted_event.keys()) == set(pdu.keys()) and set( - redacted_event.content.keys() - ) == set(pdu.content.keys()): + if set(redacted_event.content.keys()) == set(pdu.content.keys()): logger.info( "Event %s seems to have been redacted; using our redacted " "copy", diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index 393f17c3a3..9919cccb19 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py
@@ -1108,7 +1108,7 @@ class EventCreationHandler: # it's not a self-redaction (to avoid having to look up whether the # user is actually admin or not). is_admin_redaction = False - if event.type == EventTypes.Redaction: + if event.type == EventTypes.Redaction and event.redacts: original_event = await self.store.get_event( event.redacts, redact_behaviour=EventRedactBehaviour.AS_IS, @@ -1195,7 +1195,7 @@ class EventCreationHandler: # TODO: Make sure the signatures actually are correct. event.signatures.update(returned_invite.signatures) - if event.type == EventTypes.Redaction: + if event.type == EventTypes.Redaction and event.redacts: original_event = await self.store.get_event( event.redacts, redact_behaviour=EventRedactBehaviour.AS_IS, @@ -1401,7 +1401,7 @@ class EventCreationHandler: ] for k in immutable_fields: - if getattr(builder, k, None) != original_event.get(k): + if getattr(builder, k, None) != getattr(original_event, k, None): raise Exception( "Third party rules module created an invalid event: " "cannot change field " + k diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index fb4823a5cc..0b805af60c 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py
@@ -475,7 +475,7 @@ class RoomCreationHandler(BaseHandler): ): await self.room_member_handler.update_membership( requester, - UserID.from_string(old_event["state_key"]), + UserID.from_string(old_event.state_key), new_room_id, "ban", ratelimit=False, diff --git a/synapse/notifier.py b/synapse/notifier.py
index b9531007e2..4787ea438e 100644 --- a/synapse/notifier.py +++ b/synapse/notifier.py
@@ -277,7 +277,7 @@ class Notifier: event_pos=event_pos, room_id=event.room_id, event_type=event.type, - state_key=event.get("state_key"), + state_key=getattr(event, "state_key", None), membership=event.content.get("membership"), max_room_stream_token=max_room_stream_token, extra_users=extra_users or [], diff --git a/synapse/push/push_rule_evaluator.py b/synapse/push/push_rule_evaluator.py
index 49ecb38522..fa5985894c 100644 --- a/synapse/push/push_rule_evaluator.py +++ b/synapse/push/push_rule_evaluator.py
@@ -125,7 +125,7 @@ class PushRuleEvaluatorForEvent: self._power_levels = power_levels # Maps strings of e.g. 'content.body' -> event["content"]["body"] - self._value_cache = _flatten_dict(event) + self._value_cache = _flatten_dict(event.get_dict()) def matches( self, condition: Dict[str, Any], user_id: str, display_name: str @@ -271,7 +271,7 @@ def _re_word_boundary(r: str) -> str: def _flatten_dict( - d: Union[EventBase, dict], + d: dict, prefix: Optional[List[str]] = None, result: Optional[Dict[str, str]] = None, ) -> Dict[str, str]: