From 3c2274b9e70906158709f36ec693e40a427d6d53 Mon Sep 17 00:00:00 2001 From: timedout Date: Sun, 12 Jul 2026 00:07:52 +0100 Subject: [PATCH] feat: Create versioned PDU types --- src/core/matrix/event.rs | 4 +- src/core/matrix/pdu.rs | 10 +-- src/core/matrix/pdu/hashes.rs | 14 ++++ src/core/matrix/pdu/metadata.rs | 11 +++ src/core/matrix/pdu/v2.rs | 127 ++++++++++++++++++++++++++++ src/core/matrix/pdu/v3.rs | 142 ++++++++++++++++++++++++++++++++ 6 files changed, 299 insertions(+), 9 deletions(-) create mode 100644 src/core/matrix/pdu/hashes.rs create mode 100644 src/core/matrix/pdu/metadata.rs create mode 100644 src/core/matrix/pdu/v2.rs create mode 100644 src/core/matrix/pdu/v3.rs diff --git a/src/core/matrix/event.rs b/src/core/matrix/event.rs index 5af35634c..fadf91f52 100644 --- a/src/core/matrix/event.rs +++ b/src/core/matrix/event.rs @@ -143,8 +143,6 @@ pub trait Event: Clone + Debug { fn into_pdu(self) -> Pdu; - fn is_owned(&self) -> bool; - // // Canonical properties // @@ -182,12 +180,12 @@ pub trait Event: Clone + Debug { fn state_key(&self) -> Option<&str>; /// The event type. + // #[deprecated] fn kind(&self) -> &TimelineEventType; /// Metadata container; peer-trusted only. fn unsigned(&self) -> Option<&RawJsonValue>; - //#[deprecated] #[inline] fn event_type(&self) -> &TimelineEventType { self.kind() } } diff --git a/src/core/matrix/pdu.rs b/src/core/matrix/pdu.rs index b05389988..2ebebb54a 100644 --- a/src/core/matrix/pdu.rs +++ b/src/core/matrix/pdu.rs @@ -1,11 +1,15 @@ mod count; +pub mod hashes; mod id; +pub mod metadata; mod partial; mod raw_id; mod redact; #[cfg(test)] mod tests; mod unsigned; +pub mod v2; +pub mod v3; use std::cmp::Ordering; @@ -151,9 +155,6 @@ impl Event for Pdu { #[inline] fn into_pdu(self) -> Pdu { self } - - #[inline] - fn is_owned(&self) -> bool { true } } impl Event for &Pdu { @@ -219,9 +220,6 @@ impl Event for &Pdu { #[inline] fn into_pdu(self) -> Pdu { self.clone() } - - #[inline] - fn is_owned(&self) -> bool { false } } /// Prevent derived equality which wouldn't limit itself to event_id diff --git a/src/core/matrix/pdu/hashes.rs b/src/core/matrix/pdu/hashes.rs new file mode 100644 index 000000000..aa523ff90 --- /dev/null +++ b/src/core/matrix/pdu/hashes.rs @@ -0,0 +1,14 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +/// The content hash map for a PDU. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Hashes { + /// The SHA256 content hash. + pub sha256: String, + + /// Any other hashes present, if any. + #[serde(flatten, default, skip_serializing_if = "HashMap::is_empty")] + pub other: HashMap, +} diff --git a/src/core/matrix/pdu/metadata.rs b/src/core/matrix/pdu/metadata.rs new file mode 100644 index 000000000..29da2dc2c --- /dev/null +++ b/src/core/matrix/pdu/metadata.rs @@ -0,0 +1,11 @@ +use ruma::OwnedEventId; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PduMetadata { + /// This event's calculated event ID. + pub event_id: OwnedEventId, + + /// Indicates whether the event has been rejected under auth rules. + pub rejected: bool, +} diff --git a/src/core/matrix/pdu/v2.rs b/src/core/matrix/pdu/v2.rs new file mode 100644 index 000000000..bc790e721 --- /dev/null +++ b/src/core/matrix/pdu/v2.rs @@ -0,0 +1,127 @@ +use ruma::{ + EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, + ServerSignatures, UInt, UserId, events::TimelineEventType, +}; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue as RawJsonValue; + +use crate::{ + Pdu, + pdu::{hashes::Hashes, metadata::PduMetadata}, +}; + +/// Represents a persistent data unit (PDU) version 2, introduced in room +/// version 3, up until room version 11. +/// +/// Spec: https://spec.matrix.org/v1.19/rooms/v3/#event-format +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PDU { + /// The event IDs which authorise this event. Between 0 and 10 entries. + pub auth_events: Vec, + /// This event's content. + pub content: Box, + /// The depth of this event. + pub depth: UInt, + /// The content hashes for this event. + pub hashes: Hashes, + /// Timestamp in milliseconds on origin homeserver when this event was + /// created. + pub origin_server_ts: MilliSecondsSinceUnixEpoch, + /// The event IDs which immediately precede this event. Between 0 and 20 + /// entries. + pub prev_events: Vec, + /// For redaction events, the ID of the event being redacted. + /// + /// Not used in v11. + pub redacts: Option, + /// The room in which this event resides. + pub room_id: OwnedRoomId, + /// The user who sent this event. + pub sender: OwnedUserId, + /// The signatures on this event. + pub signatures: ServerSignatures, + /// The state key for this PDU, if any. + pub state_key: Option, + /// The type of this PDU. + #[serde(rename = "type")] + pub event_type: TimelineEventType, + /// Additional data added by the origin server but not covered by the + /// signatures. + /// + /// This should be `None` when receiving from and transmitting over + /// federation. + pub unsigned: Option>, + + pub internal_metadata: PduMetadata, +} + +impl PDU { + #[must_use] + pub fn internal_metadata(&self) -> &PduMetadata { &self.internal_metadata } +} + +impl ruma::state_res::Event for PDU { + type Id = OwnedEventId; + + fn event_id(&self) -> &Self::Id { &self.internal_metadata.event_id } + + fn room_id(&self) -> Option<&RoomId> { Some(&self.room_id) } + + fn sender(&self) -> &UserId { &self.sender } + + fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { self.origin_server_ts } + + fn event_type(&self) -> &TimelineEventType { &self.event_type } + + fn content(&self) -> &RawJsonValue { self.content.as_ref() } + + fn state_key(&self) -> Option<&str> { self.state_key.as_deref() } + + fn prev_events(&self) -> Box + '_> { + Box::new(self.prev_events.iter()) + } + + fn auth_events(&self) -> Box + '_> { + Box::new(self.auth_events.iter()) + } + + fn redacts(&self) -> Option<&Self::Id> { self.redacts.as_ref() } + + fn rejected(&self) -> bool { self.internal_metadata().rejected } +} + +impl super::Event for PDU { + fn as_pdu(&self) -> &Pdu { todo!() } + + fn into_pdu(self) -> Pdu { todo!() } + + fn auth_events(&self) -> impl DoubleEndedIterator + Clone + Send + '_ { + self.auth_events.iter().map(AsRef::as_ref) + } + + fn content(&self) -> &RawJsonValue { self.content.as_ref() } + + fn event_id(&self) -> &EventId { &self.internal_metadata.event_id } + + fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { self.origin_server_ts } + + fn prev_events(&self) -> impl DoubleEndedIterator + Clone + Send + '_ { + self.prev_events.iter().map(AsRef::as_ref) + } + + fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() } + + fn room_id(&self) -> Option<&RoomId> { Some(&self.room_id) } + + fn room_id_or_hash(&self) -> OwnedRoomId { self.room_id.clone() } + + fn sender(&self) -> &UserId { &self.sender } + + fn state_key(&self) -> Option<&str> { self.state_key.as_deref() } + + fn kind(&self) -> &TimelineEventType { self.event_type() } + + fn unsigned(&self) -> Option<&RawJsonValue> { self.unsigned.as_deref() } + + fn event_type(&self) -> &TimelineEventType { &self.event_type } +} diff --git a/src/core/matrix/pdu/v3.rs b/src/core/matrix/pdu/v3.rs new file mode 100644 index 000000000..48a594342 --- /dev/null +++ b/src/core/matrix/pdu/v3.rs @@ -0,0 +1,142 @@ +use ruma::{ + EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, + ServerSignatures, UInt, UserId, + events::{StateEventType, TimelineEventType}, +}; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue as RawJsonValue; + +use crate::{ + Pdu, + pdu::{hashes::Hashes, metadata::PduMetadata}, +}; + +/// Represents a persistent data unit (PDU) version 3, introduced in room +/// version 12. +/// +/// Spec: https://spec.matrix.org/v1.19/rooms/v12/#event-format +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PDU { + /// The event IDs which authorise this event. Between 0 and 10 entries. + pub auth_events: Vec, + /// This event's content. + pub content: Box, + /// The depth of this event. + pub depth: UInt, + /// The content hashes for this event. + pub hashes: Hashes, + /// Timestamp in milliseconds on origin homeserver when this event was + /// created. + pub origin_server_ts: MilliSecondsSinceUnixEpoch, + /// The event IDs which immediately precede this event. Between 0 and 20 + /// entries. + pub prev_events: Vec, + /// The room in which this event resides. + /// May be None if the event is a create event. + pub room_id: Option, + /// The user who sent this event. + pub sender: OwnedUserId, + /// The signatures on this event. + pub signatures: ServerSignatures, + /// The state key for this PDU, if any. + pub state_key: Option, + /// The type of this PDU. + #[serde(rename = "type")] + pub event_type: TimelineEventType, + /// Additional data added by the origin server but not covered by the + /// signatures. + /// + /// This should be `None` when receiving from and transmitting over + /// federation. + pub unsigned: Option>, + + pub internal_metadata: PduMetadata, +} + +impl PDU { + #[must_use] + pub fn internal_metadata(&self) -> &PduMetadata { &self.internal_metadata } +} + +impl ruma::state_res::Event for PDU { + type Id = OwnedEventId; + + fn event_id(&self) -> &Self::Id { &self.internal_metadata.event_id } + + fn room_id(&self) -> Option<&RoomId> { self.room_id.as_deref() } + + fn sender(&self) -> &UserId { &self.sender } + + fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { self.origin_server_ts } + + fn event_type(&self) -> &TimelineEventType { &self.event_type } + + fn content(&self) -> &RawJsonValue { self.content.as_ref() } + + fn state_key(&self) -> Option<&str> { self.state_key.as_deref() } + + fn prev_events(&self) -> Box + '_> { + Box::new(self.prev_events.iter()) + } + + fn auth_events(&self) -> Box + '_> { + Box::new(self.auth_events.iter()) + } + + fn redacts(&self) -> Option<&Self::Id> { None } + + fn rejected(&self) -> bool { self.internal_metadata().rejected } +} + +impl super::Event for PDU { + fn as_pdu(&self) -> &Pdu { todo!() } + + fn into_pdu(self) -> Pdu { todo!() } + + fn auth_events(&self) -> impl DoubleEndedIterator + Clone + Send + '_ { + self.auth_events.iter().map(AsRef::as_ref) + } + + fn content(&self) -> &RawJsonValue { self.content.as_ref() } + + fn event_id(&self) -> &EventId { &self.internal_metadata.event_id } + + fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { self.origin_server_ts } + + fn prev_events(&self) -> impl DoubleEndedIterator + Clone + Send + '_ { + self.prev_events.iter().map(AsRef::as_ref) + } + + fn redacts(&self) -> Option<&EventId> { None } + + fn room_id(&self) -> Option<&RoomId> { self.room_id.as_deref() } + + fn room_id_or_hash(&self) -> OwnedRoomId { + if self.event_type == StateEventType::RoomCreate.into() + && self.state_key().is_some_and(str::is_empty) + { + // Calculate the room ID for a create event. + RoomId::new_v2( + self.internal_metadata() + .event_id + .as_str() + .strip_prefix("$") + .expect("event ID must start with $ sigil"), + ) + .expect("must be able to create a room ID from create event reference hash") + } else { + // Otherwise, all other events have a room ID field. + self.room_id.clone().unwrap() + } + } + + fn sender(&self) -> &UserId { &self.sender } + + fn state_key(&self) -> Option<&str> { self.state_key.as_deref() } + + fn kind(&self) -> &TimelineEventType { self.event_type() } + + fn unsigned(&self) -> Option<&RawJsonValue> { self.unsigned.as_deref() } + + fn event_type(&self) -> &TimelineEventType { &self.event_type } +}