Skip to main content

tuwunel_matrix/
pdu.rs

1//! Persistent data unit storage and federation-format utilities.
2//!
3//! The module contains the stored event representation, sequence identifiers,
4//! builders, and validation helpers. Its wire-format adapters account for
5//! room-version rules.
6
7mod builder;
8mod count;
9mod format;
10mod hashes;
11mod id;
12mod raw_id;
13#[cfg(test)]
14mod tests;
15mod unsigned;
16
17use std::cmp::Ordering;
18
19use ruma::{
20	CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId,
21	OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, UInt, UserId,
22	canonical_json::redact_in_place,
23	events::TimelineEventType,
24	room_version_rules::{RedactionRules, RoomVersionRules},
25	serde::Raw,
26};
27use serde::{Deserialize, Serialize};
28use serde_json::value::RawValue as RawJsonValue;
29use smallvec::SmallVec;
30use tuwunel_core::{Err, Result, err};
31
32pub use self::{
33	Count as PduCount, Id as PduId, Pdu as PduEvent, RawId as RawPduId,
34	builder::{Builder, Builder as PduBuilder},
35	count::Count,
36	format::{
37		check::{check_room_id, check_rules},
38		from_incoming_federation, into_outgoing_federation,
39	},
40	hashes::EventHashes as EventHash,
41	id::Id,
42	raw_id::*,
43	unsigned::{DELAY_ID, PRIVATE as PRIVATE_UNSIGNED},
44};
45use super::{Event, ShortRoomId, StateKey};
46
47/// Stores a Matrix persistent data unit in typed form.
48///
49/// The representation retains canonical event fields used by state resolution,
50/// storage, and client serialization. Federation adapters normalize
51/// version-specific wire shapes.
52#[derive(Clone, Deserialize, Serialize, Debug)]
53pub struct Pdu {
54	/// Matrix event type.
55	///
56	/// The value is serialized under the top-level `type` field.
57	#[serde(rename = "type")]
58	pub kind: TimelineEventType,
59
60	/// Raw canonical event content.
61	///
62	/// Content remains encoded until a caller requests a typed or JSON value.
63	pub content: Content,
64
65	/// Matrix identifier assigned to this event.
66	///
67	/// Stored PDUs always carry an owned event ID, including room versions that
68	/// derive it outside the federation wire object.
69	pub event_id: OwnedEventId,
70
71	/// Matrix room containing this event.
72	///
73	/// Stored PDUs carry the room ID even when a room version omits it from a
74	/// creation event's federation representation.
75	pub room_id: OwnedRoomId,
76
77	/// Matrix user who sent the event.
78	///
79	/// The sender participates in authorization and client-visible event
80	/// output.
81	pub sender: OwnedUserId,
82
83	/// State key when this is a state event.
84	///
85	/// Message-like events store `None` and omit the field during
86	/// serialization.
87	#[serde(skip_serializing_if = "Option::is_none")]
88	pub state_key: Option<StateKey>,
89
90	/// Event targeted by a redaction when carried at the top level.
91	///
92	/// Newer room versions can place this value in event content instead. An
93	/// absent target is omitted during serialization.
94	#[serde(skip_serializing_if = "Option::is_none")]
95	pub redacts: Option<OwnedEventId>,
96
97	/// Events declared as direct predecessors of this event.
98	///
99	/// The sequence is stored inline for the common single-predecessor case.
100	pub prev_events: PrevEvents,
101
102	/// Events used to authorize this event.
103	///
104	/// These identifiers form the event's explicit authorization dependency
105	/// set.
106	pub auth_events: AuthEvents,
107
108	/// Millisecond timestamp supplied by the originating server.
109	///
110	/// The value is preserved for ordering metadata and client serialization.
111	pub origin_server_ts: UInt,
112
113	/// Event depth in the room directed acyclic graph.
114	///
115	/// Depth is originating-server-provided graph metadata and is distinct from
116	/// the local timeline sequence.
117	pub depth: UInt,
118
119	/// Content hash declared by the event.
120	///
121	/// Federation validation compares the declaration with the computed content
122	/// hash to detect content changes.
123	pub hashes: EventHash,
124
125	/// Server that originated an event carrying a top-level `origin` field.
126	///
127	/// Legacy event formats retain this value for local and remote events. The
128	/// field is absent when it was not carried by the event format.
129	#[serde(skip_serializing_if = "Option::is_none")]
130	pub origin: Option<OwnedServerName>,
131
132	/// Unsigned metadata excluded from event hashing and signing.
133	///
134	/// Stored values are local annotations such as transaction IDs, age, prior
135	/// state, and bundled relations. The field is omitted when absent.
136	#[serde(default, skip_serializing_if = "Option::is_none")]
137	pub unsigned: Option<Unsigned>,
138
139	//TODO: https://spec.matrix.org/v1.14/rooms/v11/#rejected-events
140	/// Whether state resolution rejected this event in test fixtures.
141	///
142	/// Production builds derive rejection state outside the serialized PDU.
143	#[cfg(test)]
144	#[serde(default, skip_serializing)]
145	pub rejected: bool,
146}
147
148/// Inline storage for the common single-entry `prev_events` case.
149///
150/// Events with additional predecessors spill to the heap, avoiding larger
151/// inline storage on every event.
152pub type PrevEvents = SmallVec<[OwnedEventId; 1]>;
153
154/// Inline storage for the typical three-entry `auth_events` case.
155///
156/// Restricted rooms can require many more entries, so this remains a spilling
157/// `SmallVec` rather than a fixed-capacity `ArrayVec`.
158pub type AuthEvents = SmallVec<[OwnedEventId; 3]>;
159
160/// Raw event-content storage with 112 bytes of inline capacity.
161///
162/// The capacity follows an allocator-profile mode in the 96 to 112 byte range
163/// and targets a 128 byte total size with `SmallVec` metadata.
164pub type Content = Raw<CanonicalJsonObject, 112>;
165
166/// Raw `unsigned` storage with 112 bytes of inline capacity.
167///
168/// The enclosing field is usually `None` or contains a small local annotation,
169/// such as `transaction_id`, `age`, or `membership`. Those values remain inline
170/// at the `Content` size class, while larger state-event `prev_content` and
171/// bundled `m.relations` values spill to the heap.
172pub type Unsigned = Raw<CanonicalJsonObject, 112>;
173
174/// The [maximum size allowed] for a PDU.
175/// [maximum size allowed]: <https://spec.matrix.org/latest/client-server-api/#size-limits>
176pub const MAX_PDU_BYTES: usize = 65_535;
177
178pub use tuwunel_core::matrix::MAX_PREV_EVENTS;
179
180/// The [maximum length allowed] for the `auth_events` array of a PDU.
181/// [maximum length allowed]: <https://spec.matrix.org/latest/rooms/v1/#event-format>
182pub const MAX_AUTH_EVENTS: usize = 10;
183
184impl Pdu {
185	/// Inserts room and event IDs before deserializing a canonical PDU object.
186	///
187	/// Existing values under those keys are replaced. The resulting typed PDU
188	/// owns both supplied identifiers.
189	pub fn from_object_and_roomid_and_eventid(
190		room_id: &RoomId,
191		event_id: &EventId,
192		mut json: CanonicalJsonObject,
193	) -> Result<Self> {
194		let room_id = CanonicalJsonValue::String(room_id.into());
195		json.insert("room_id".into(), room_id);
196		Self::from_object_and_eventid(event_id, json)
197	}
198
199	/// Inserts an event ID before deserializing a canonical PDU object.
200	///
201	/// Any existing `event_id` value is replaced. Other object fields pass
202	/// through to normal PDU deserialization.
203	pub fn from_object_and_eventid(
204		event_id: &EventId,
205		mut json: CanonicalJsonObject,
206	) -> Result<Self> {
207		let event_id = CanonicalJsonValue::String(event_id.into());
208		json.insert("event_id".into(), event_id);
209		Self::from_object(json)
210	}
211
212	/// Normalizes federation wire fields and validates PDU format and room ID.
213	///
214	/// Version-specific wire fields are converted to the stored representation
215	/// before these checks. Signature, content-hash, and authorization
216	/// validation remain the caller's responsibility.
217	///
218	/// # Panics
219	///
220	/// Panics if the object lacks `type` while the selected rules do not
221	/// require a create-event room ID.
222	pub fn from_object_federation(
223		room_id: &RoomId,
224		event_id: &EventId,
225		json: CanonicalJsonObject,
226		rules: &RoomVersionRules,
227	) -> Result<(Self, CanonicalJsonObject)> {
228		let json = from_incoming_federation(room_id, event_id, json, rules);
229		let pdu = Self::from_object_checked(json.clone(), rules)?;
230		check_room_id(&pdu, room_id)?;
231
232		if rules
233			.authorization
234			.room_create_event_id_as_room_id
235			&& pdu.kind == TimelineEventType::RoomCreate
236			&& room_id.as_event_id().ok().as_deref() != Some(event_id)
237		{
238			return Err!(Request(InvalidParam(
239				"Create event {event_id} is not the create event of {room_id}"
240			)));
241		}
242
243		Ok((pdu, json))
244	}
245
246	/// Validates a canonical PDU object before deserializing it.
247	///
248	/// Checks use the supplied room-version event-format rules. Successful
249	/// validation returns the typed stored representation.
250	pub fn from_object_checked(
251		json: CanonicalJsonObject,
252		rules: &RoomVersionRules,
253	) -> Result<Self> {
254		check_rules(&json, &rules.event_format)?;
255		Self::from_object(json)
256	}
257
258	/// Deserializes a canonical JSON object into a stored PDU.
259	///
260	/// The object is wrapped as a canonical JSON value before typed
261	/// deserialization. No room-version format checks are performed.
262	pub fn from_object(json: CanonicalJsonObject) -> Result<Self> {
263		let json = CanonicalJsonValue::Object(json);
264		Self::from_value(json)
265	}
266
267	/// Deserializes raw JSON through a canonical JSON value.
268	///
269	/// Canonical conversion normalizes integer and object representation before
270	/// the PDU fields are decoded. No room-version format checks are
271	/// performed.
272	///
273	/// # Panics
274	///
275	/// Panics if the raw JSON contains a value outside canonical JSON, such as
276	/// a floating-point value or an integer outside the canonical range.
277	pub fn from_raw_value(json: &RawJsonValue) -> Result<Self> {
278		let json: CanonicalJsonValue = json.into();
279		Self::from_value(json)
280	}
281
282	/// Deserializes a canonical JSON value into a stored PDU.
283	///
284	/// The input must contain the fields required by `Pdu`. No room-version
285	/// format checks are performed before deserialization.
286	pub fn from_value(json: CanonicalJsonValue) -> Result<Self> {
287		serde_json::from_value(json.into()).map_err(Into::into)
288	}
289
290	/// Deserializes raw JSON directly into a stored PDU.
291	///
292	/// This path uses `Pdu`'s Serde representation without first canonicalizing
293	/// the input. Callers that require canonical validation should use a
294	/// checked constructor.
295	pub fn from_raw_json(json: &RawJsonValue) -> Result<Self> {
296		Self::deserialize(json).map_err(Into::into)
297	}
298
299	/// MSC4025: a pruned clone per the redaction rules, carrying no
300	/// `redacted_because`; no redaction event exists for a serve-time
301	/// erasure.
302	pub fn redacted(&self, rules: &RedactionRules) -> Result<Self> {
303		let mut object = self.to_canonical_object();
304
305		redact_in_place(&mut object, rules, None)
306			.map_err(|e| err!("Failed to redact event: {e}"))?;
307
308		Self::from_object(object)
309	}
310}
311
312impl Event for Pdu
313where
314	Self: Send + Sync + 'static,
315{
316	#[inline]
317	fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
318		self.auth_events.iter().map(AsRef::as_ref)
319	}
320
321	#[inline]
322	fn auth_events_into(
323		self,
324	) -> impl IntoIterator<IntoIter = impl Iterator<Item = OwnedEventId>> + Send {
325		self.auth_events.into_iter()
326	}
327
328	#[inline]
329	fn content(&self) -> &RawJsonValue { self.content.json() }
330
331	#[inline]
332	fn event_id(&self) -> &EventId { &self.event_id }
333
334	#[inline]
335	fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
336		MilliSecondsSinceUnixEpoch(self.origin_server_ts)
337	}
338
339	#[inline]
340	fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
341		self.prev_events.iter().map(AsRef::as_ref)
342	}
343
344	#[inline]
345	fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() }
346
347	#[cfg(test)]
348	#[inline]
349	fn rejected(&self) -> bool { self.rejected }
350
351	#[cfg(not(test))]
352	#[inline]
353	fn rejected(&self) -> bool { false }
354
355	#[inline]
356	fn room_id(&self) -> &RoomId { &self.room_id }
357
358	#[inline]
359	fn sender(&self) -> &UserId { &self.sender }
360
361	#[inline]
362	fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
363
364	#[inline]
365	fn kind(&self) -> &TimelineEventType { &self.kind }
366
367	#[inline]
368	fn unsigned(&self) -> Option<&RawJsonValue> { self.unsigned.as_ref().map(Unsigned::json) }
369
370	#[inline]
371	fn as_mut_pdu(&mut self) -> &mut Pdu { self }
372
373	#[inline]
374	fn as_pdu(&self) -> &Pdu { self }
375
376	#[inline]
377	fn into_pdu(self) -> Pdu { self }
378
379	#[inline]
380	fn is_owned(&self) -> bool { true }
381}
382
383impl Event for &Pdu
384where
385	Self: Send,
386{
387	#[inline]
388	fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
389		self.auth_events.iter().map(AsRef::as_ref)
390	}
391
392	#[inline]
393	fn auth_events_into(
394		self,
395	) -> impl IntoIterator<IntoIter = impl Iterator<Item = OwnedEventId>> + Send {
396		self.auth_events.iter().map(ToOwned::to_owned)
397	}
398
399	#[inline]
400	fn content(&self) -> &RawJsonValue { self.content.json() }
401
402	#[inline]
403	fn event_id(&self) -> &EventId { &self.event_id }
404
405	#[inline]
406	fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
407		MilliSecondsSinceUnixEpoch(self.origin_server_ts)
408	}
409
410	#[inline]
411	fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
412		self.prev_events.iter().map(AsRef::as_ref)
413	}
414
415	#[inline]
416	fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() }
417
418	#[cfg(test)]
419	#[inline]
420	fn rejected(&self) -> bool { self.rejected }
421
422	#[cfg(not(test))]
423	#[inline]
424	fn rejected(&self) -> bool { false }
425
426	#[inline]
427	fn room_id(&self) -> &RoomId { &self.room_id }
428
429	#[inline]
430	fn sender(&self) -> &UserId { &self.sender }
431
432	#[inline]
433	fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
434
435	#[inline]
436	fn kind(&self) -> &TimelineEventType { &self.kind }
437
438	#[inline]
439	fn unsigned(&self) -> Option<&RawJsonValue> { self.unsigned.as_ref().map(Unsigned::json) }
440
441	#[inline]
442	fn as_pdu(&self) -> &Pdu { self }
443
444	#[inline]
445	fn into_pdu(self) -> Pdu { self.clone() }
446
447	#[inline]
448	fn is_owned(&self) -> bool { false }
449}
450
451/// Prevent derived equality which wouldn't limit itself to event_id
452impl Eq for Pdu {}
453
454/// Equality determined by the Pdu's ID, not the memory representations.
455impl PartialEq for Pdu {
456	fn eq(&self, other: &Self) -> bool { self.event_id == other.event_id }
457}
458
459/// Ordering determined by the Pdu's ID, not the memory representations.
460impl Ord for Pdu {
461	fn cmp(&self, other: &Self) -> Ordering { self.event_id.cmp(&other.event_id) }
462}
463
464/// Ordering determined by the Pdu's ID, not the memory representations.
465impl PartialOrd for Pdu {
466	fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
467}