Skip to main content

arminius_lib_model/
event.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Event related domain model types
6
7use arminius_lib_db::{DatabaseError, ResultExt as _};
8use arminius_lib_events::{
9	KnownRoomVersionId, PartsVerified, RoomEvent, VerificationMetadata, Verified,
10};
11use ruma::{Int, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, UInt};
12use sqlx::types::chrono::{DateTime, Utc};
13
14/// The `String` representation of the event's JSON.
15///
16/// This is to prevent decanonicalizing of the field.
17pub type EventJson = String;
18
19/// Represents a row in the `events` table.
20#[derive(Debug, Clone)]
21pub struct EventRow {
22	/// The unique identifier for the event in the Matrix protocol.
23	pub event_id: OwnedEventId,
24	/// The unique identifier for the room this event belongs to.
25	pub room_id: OwnedRoomId,
26	/// The depth of the event in the room's event graph.
27	pub depth: i64,
28	/// The JSON representation of the event.
29	pub event_json: EventJson,
30	/// The timestamp when the event was received.
31	pub received_ts: i64,
32}
33
34impl EventRow {
35	/// Turns `&self` into [`serde_json::Value`]. If
36	/// [`serde_json::from_str`] fails, a fallback of
37	/// [`serde_json::Value::Null`] is returned.
38	#[must_use]
39	pub fn to_value(&self) -> serde_json::Value {
40		serde_json::from_str(&self.event_json).unwrap_or(serde_json::Value::Null)
41	}
42}
43
44/// Database representation of a persisted event
45#[derive(Debug)]
46pub struct EventVerifiedRow {
47	/// The JSON contents of the event
48	pub event_json: Box<sqlx::types::JsonRawValue>,
49	// TODO: Make this an enum at the DB level
50	/// Room version
51	pub room_version: String,
52	/// Timestamp when verification was attempted.
53	pub attempted_at: DateTime<Utc>,
54	/// Signature verification outcome for the event.
55	pub signature_verification: DbPartsVerified,
56	/// Whether the event was soft-failed.
57	pub soft_failed: bool,
58}
59
60impl EventVerifiedRow {
61	/// Convert to the domain representation
62	pub fn try_into_event(self) -> Result<RoomEvent<Verified>, DatabaseError> {
63		let metadata = VerificationMetadata {
64			attempted_at: MilliSecondsSinceUnixEpoch(
65				UInt::try_from(self.attempted_at.timestamp_millis())
66					.invalid_data("Unrepresentable timestamp")?,
67			),
68			signature_verification: self.signature_verification.into(),
69			room_version: self.room_version.parse().invalid_data("Unknown room version")?,
70			soft_failed: self.soft_failed,
71		};
72		let verified = RoomEvent::verified_from_pdu(&self.event_json, metadata)
73			.invalid_data("Could not load event from database")?;
74		Ok(verified)
75	}
76}
77
78/// Describes how much of a ruma event was actually verified during a
79/// signature verification
80#[derive(Debug, sqlx::Type, Eq, PartialEq, Clone, Copy)]
81#[sqlx(type_name = "PARTS_VERIFIED", rename_all = "snake_case")]
82pub enum DbPartsVerified {
83	/// We have access to the original unredacted event, and could verify
84	/// everything
85	CompleteEvent,
86	/// We could only check the signature of the hash, and not the original
87	/// event because it has been redacted
88	OnlySignatures,
89}
90
91impl From<PartsVerified> for DbPartsVerified {
92	fn from(value: PartsVerified) -> Self {
93		match value {
94			PartsVerified::CompleteEvent => DbPartsVerified::CompleteEvent,
95			PartsVerified::OnlySignatures => DbPartsVerified::OnlySignatures,
96		}
97	}
98}
99
100impl From<DbPartsVerified> for PartsVerified {
101	fn from(value: DbPartsVerified) -> Self {
102		match value {
103			DbPartsVerified::CompleteEvent => PartsVerified::CompleteEvent,
104			DbPartsVerified::OnlySignatures => PartsVerified::OnlySignatures,
105		}
106	}
107}
108
109/// Represents a row in the `verification_metadata` table.
110#[derive(Debug, Clone)]
111pub struct DbVerificationMetadata {
112	/// Timestamp when verification was attempted.
113	pub attempted_at: DateTime<Utc>,
114	/// Signature verification outcome for the event.
115	pub signature_verification: DbPartsVerified,
116	/// Whether the event was soft-failed.
117	pub soft_failed: bool,
118}
119
120impl DbVerificationMetadata {
121	/// Attempt to convert into the domain representation
122	pub fn try_into_domain(
123		self,
124		room_version: KnownRoomVersionId,
125	) -> Result<VerificationMetadata, DatabaseError> {
126		Ok(VerificationMetadata {
127			attempted_at: MilliSecondsSinceUnixEpoch(
128				UInt::try_from(self.attempted_at.timestamp_millis())
129					.invalid_data("Unrepresentable timestamp")?,
130			),
131			signature_verification: self.signature_verification.into(),
132			room_version,
133			soft_failed: self.soft_failed,
134		})
135	}
136}
137
138impl TryFrom<VerificationMetadata> for DbVerificationMetadata {
139	type Error = DatabaseError;
140
141	fn try_from(from: VerificationMetadata) -> Result<DbVerificationMetadata, DatabaseError> {
142		Ok(DbVerificationMetadata {
143			attempted_at: DateTime::from_timestamp_millis(i64::from(Int::from(
144				from.attempted_at.get(),
145			)))
146			.ok_or(DatabaseError::InvalidData("Unrepresentable timestamp".to_owned()))?,
147			signature_verification: from.signature_verification.into(),
148			soft_failed: from.soft_failed,
149		})
150	}
151}