Skip to content

Commit a63c043

Browse files
committed
Update decryption error status events
1 parent baa97a4 commit a63c043

File tree

5 files changed

+84
-30
lines changed

5 files changed

+84
-30
lines changed

mautrix/bridge/matrix.py

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
MemberStateEventContent,
3434
MessageEvent,
3535
MessageEventContent,
36+
MessageStatus,
3637
MessageStatusReason,
3738
MessageType,
3839
PresenceEvent,
@@ -93,6 +94,44 @@
9394
)
9495

9596

97+
class UnencryptedMessageError(DecryptionError):
98+
def __init__(self) -> None:
99+
super().__init__("unencrypted message")
100+
101+
@property
102+
def human_message(self) -> str:
103+
return "the message is not encrypted"
104+
105+
106+
class EncryptionUnsupportedError(DecryptionError):
107+
def __init__(self) -> None:
108+
super().__init__("encryption is not supported")
109+
110+
@property
111+
def human_message(self) -> str:
112+
return "the bridge is not configured to support encryption"
113+
114+
115+
class DeviceUntrustedError(DecryptionError):
116+
def __init__(self, trust: TrustState) -> None:
117+
explanation = {
118+
TrustState.BLACKLISTED: "device is blacklisted",
119+
TrustState.UNVERIFIED: "unverified",
120+
TrustState.UNKNOWN_DEVICE: "device info not found",
121+
TrustState.FORWARDED: "keys were forwarded from an unknown device",
122+
TrustState.CROSS_SIGNED_UNTRUSTED: (
123+
"cross-signing keys changed after setting up the bridge"
124+
),
125+
}.get(trust)
126+
base = "your device is not trusted"
127+
self.message = f"{base} ({explanation})" if explanation else base
128+
super().__init__(self.message)
129+
130+
@property
131+
def human_message(self) -> str:
132+
return self.message
133+
134+
96135
class BaseMatrixHandler:
97136
log: TraceLogger = logging.getLogger("mau.mx")
98137
az: AppService
@@ -506,23 +545,23 @@ def is_command(self, message: MessageEventContent) -> tuple[bool, str]:
506545
async def _send_crypto_status_error(
507546
self,
508547
evt: Event,
509-
err: Exception | str | None = None,
548+
err: DecryptionError | None = None,
510549
retry_num: int = 0,
511550
is_final: bool = True,
512551
edit: EventID | None = None,
513552
wait_for: int | None = None,
514553
) -> EventID | None:
515554
msg = str(err)
516-
if isinstance(err, SessionNotFound):
517-
msg = "the bridge hasn't received the decryption keys"
555+
if isinstance(err, (SessionNotFound, UnencryptedMessageError)):
556+
msg = err.human_message
518557
self._send_message_checkpoint(
519558
evt, MessageSendCheckpointStep.DECRYPTED, msg, permanent=is_final, retry_num=retry_num
520559
)
521560

522561
if wait_for:
523562
msg += f". The bridge will retry for {wait_for} seconds"
524-
full_msg = f"\u26a0\ufe0f Your message was not bridged: {msg}."
525-
if msg == "encryption is not supported":
563+
full_msg = f"\u26a0 Your message was not bridged: {msg}."
564+
if isinstance(err, EncryptionUnsupportedError):
526565
full_msg = "🔒️ This bridge has not been configured to support encryption"
527566
event_id = None
528567
if self.config.get("bridge.delivery_error_reports", True):
@@ -544,12 +583,12 @@ async def _send_crypto_status_error(
544583
status_content = BeeperMessageStatusEventContent(
545584
network="", # TODO set network properly
546585
relates_to=RelatesTo(rel_type=RelationType.REFERENCE, event_id=evt.event_id),
547-
success=False,
548-
is_certain=True,
549-
can_retry=True,
586+
status=MessageStatus.RETRIABLE if is_final else MessageStatus.PENDING,
550587
reason=MessageStatusReason.UNDECRYPTABLE,
551588
error=msg,
589+
message=err.human_message if err else None,
552590
)
591+
status_content.fill_legacy_booleans()
553592
await self.az.intent.send_message_event(
554593
evt.room_id, EventType.BEEPER_MESSAGE_STATUS, status_content
555594
)
@@ -564,7 +603,7 @@ async def handle_message(self, evt: MessageEvent, was_encrypted: bool = False) -
564603

565604
if not was_encrypted and self.require_e2ee:
566605
self.log.warning(f"Dropping {event_id} from {user_id} as it's not encrypted!")
567-
await self._send_crypto_status_error(evt, "unencrypted message", 0)
606+
await self._send_crypto_status_error(evt, UnencryptedMessageError(), 0)
568607
return
569608

570609
sender = await self.bridge.get_user(user_id)
@@ -714,19 +753,6 @@ async def try_handle_sync_event(self, evt: Event) -> None:
714753
except Exception:
715754
self.log.exception("Error handling manually received Matrix event")
716755

717-
@staticmethod
718-
def _device_unverified_explanation(trust: TrustState) -> str:
719-
explanation = {
720-
TrustState.BLACKLISTED: "device is blacklisted",
721-
TrustState.UNKNOWN_DEVICE: "device info not found",
722-
TrustState.FORWARDED: "keys were forwarded from an unknown device",
723-
TrustState.CROSS_SIGNED_UNTRUSTED: (
724-
"cross-signing keys changed after setting up the bridge"
725-
),
726-
}.get(trust)
727-
base = "your device is not trusted"
728-
return f"{base} ({explanation})" if explanation else base
729-
730756
async def _post_decrypt(
731757
self, evt: Event, retry_num: int = 0, error_event_id: EventID | None = None
732758
) -> None:
@@ -739,7 +765,7 @@ async def _post_decrypt(
739765
await self._send_crypto_status_error(
740766
evt,
741767
retry_num=retry_num,
742-
err=self._device_unverified_explanation(trust_state),
768+
err=DeviceUntrustedError(trust_state),
743769
edit=error_event_id,
744770
)
745771
return
@@ -758,7 +784,7 @@ async def handle_encrypted(self, evt: EncryptedEvent) -> None:
758784
evt.event_id,
759785
evt.sender,
760786
)
761-
await self._send_crypto_status_error(evt, "encryption is not supported")
787+
await self._send_crypto_status_error(evt, EncryptionUnsupportedError())
762788
return
763789
try:
764790
decrypted = await self.e2ee.decrypt(evt, wait_session_timeout=3)

mautrix/errors/crypto.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ class SessionShareError(CryptoError):
2727

2828

2929
class DecryptionError(CryptoError):
30-
pass
30+
@property
31+
def human_message(self) -> str:
32+
return "the bridge failed to decrypt the message"
3133

3234

3335
class MatchingSessionDecryptionError(DecryptionError):
@@ -42,6 +44,10 @@ def __init__(self, session_id: SessionID, sender_key: IdentityKey | None = None)
4244
self.session_id = session_id
4345
self._sender_key = sender_key
4446

47+
@property
48+
def human_message(self) -> str:
49+
return "the bridge hasn't received the decryption keys"
50+
4551
@property
4652
def sender_key(self) -> IdentityKey | None:
4753
"""

mautrix/types/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
MemberStateEventContent,
8787
MessageEvent,
8888
MessageEventContent,
89+
MessageStatus,
8990
MessageStatusReason,
9091
MessageType,
9192
MessageUnsigned,
@@ -277,6 +278,7 @@
277278
"MemberStateEventContent",
278279
"MessageEvent",
279280
"MessageEventContent",
281+
"MessageStatus",
280282
"MessageStatusReason",
281283
"MessageType",
282284
"MessageUnsigned",

mautrix/types/event/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
)
1212
from .base import BaseEvent, BaseRoomEvent, BaseUnsigned, GenericEvent
1313
from .batch import BatchSendEvent, BatchSendStateEvent
14-
from .beeper import BeeperMessageStatusEvent, BeeperMessageStatusEventContent, MessageStatusReason
14+
from .beeper import (
15+
BeeperMessageStatusEvent,
16+
BeeperMessageStatusEventContent,
17+
MessageStatus,
18+
MessageStatusReason,
19+
)
1520
from .encrypted import (
1621
EncryptedEvent,
1722
EncryptedEventContent,

mautrix/types/event/beeper.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,36 @@ def checkpoint_status(self):
3232
return MessageSendCheckpointStatus.PERM_FAILURE
3333

3434

35-
@dataclass
35+
class MessageStatus(SerializableEnum):
36+
SUCCESS = "SUCCESS"
37+
PENDING = "PENDNIG"
38+
RETRIABLE = "FAIL_RETRIABLE"
39+
FAIL = "FAIL_PERMANENT"
40+
41+
42+
@dataclass(kw_only=True)
3643
class BeeperMessageStatusEventContent(SerializableAttrs):
37-
network: str
38-
success: bool
3944
relates_to: RelatesTo = field(json="m.relates_to")
45+
network: str = ""
46+
status: Optional[MessageStatus] = None
4047

4148
reason: Optional[MessageStatusReason] = None
4249
error: Optional[str] = None
4350
message: Optional[str] = None
51+
52+
success: Optional[bool] = None
53+
still_working: Optional[bool] = None
4454
can_retry: Optional[bool] = None
4555
is_certain: Optional[bool] = None
4656

47-
still_working: Optional[bool] = None
4857
last_retry: Optional[EventID] = None
4958

59+
def fill_legacy_booleans(self) -> None:
60+
self.success = self.status == MessageStatus.SUCCESS
61+
if not self.success:
62+
self.still_working = self.status == MessageStatus.PENDING
63+
self.can_retry = self.status in (MessageStatus.PENDING, MessageStatus.RETRIABLE)
64+
5065

5166
@dataclass
5267
class BeeperMessageStatusEvent(BaseRoomEvent, SerializableAttrs):

0 commit comments

Comments
 (0)