mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-07-16 18:28:51 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9aacbf2650 | ||
|
|
b1a3bcfb88 | ||
|
|
3c2274b9e7 |
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# Roadmap for PDU versioning
|
||||||
|
|
||||||
|
Currently, PDUs are one monolithic `PduEvent` struct which applies a one-size-fits-all approach to PDUs, however as we
|
||||||
|
support a wide range of room versions, this only just about currently works, and will cause more and more problems as
|
||||||
|
time progresses. This branch will split the `PduEvent` struct into multiple versioned structs that represent the proper
|
||||||
|
format for appropriate room versions, and will drop the `Event` trait in favour of Ruma's `ruma::state_res::Event`
|
||||||
|
trait.
|
||||||
|
|
||||||
|
This will require a lot of work, and since I forget everything the moment I look out the window, here be a list (waow):
|
||||||
|
|
||||||
|
- [x] Split `PduEvent` into multiple versioned structs, implementing `ruma::state_res::Event` for each of them.
|
||||||
|
- [ ] Refactor out all references to `PduEvent`.
|
||||||
|
- [ ] Refactor out all references to `conduwuit_core::event::Event` trait in favour of `ruma::state_res::Event`.
|
||||||
|
- [ ] Centralise PDU struct creation in a way that allows us to greater control the deserialization process.
|
||||||
|
- Developers can't be trusted to read documentation so we can't store the PDU metadata (things like event ID and
|
||||||
|
rejection status) in the PDU struct itself, as this may end up being leaked to consumers. Instead, PDU metadata
|
||||||
|
should be stored seprately from the PDU struct itself, but needs to be strongly associated with it (i.e. fetched
|
||||||
|
at the same time, always returned together, etc). This may take a form similar to
|
||||||
|
`StoredPdu<T: Event> { pdu: T, metadata: PduMetadata }`.
|
||||||
|
- [ ] Stop storing generated data in the `unsigned` field, but instead calculate it on demand(?).
|
||||||
|
- Storing generated data in the `unsigned` field results in:
|
||||||
|
- History visibility cannot be enforced on `prev_content` or `redacted_because`.
|
||||||
|
- `age` is manually calculated but only sometimes.
|
||||||
|
- `membership` is not provided at all.
|
||||||
|
- `prev_content` can leak redacted state event content ([#1103]).
|
||||||
|
- `prev_content` cannot easily be populated for backfilled events, leaving them contextless.
|
||||||
|
- It is easy to accidentally leak `transaction_id`.
|
||||||
|
|
||||||
|
Once `PduEvent` is gone, we can then do the following lovely things:
|
||||||
|
|
||||||
|
- Drop our local state resolution logic and event authorisation logic and instead use Ruma's, which naturally receives
|
||||||
|
more love and attention. Our current `Event` trait and `PduEvent` implementations are incompatible with what Ruma
|
||||||
|
needs, and do not store the appropriate information required.
|
||||||
|
- There's something else but I forgot just trust me
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use assign::assign;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Pdu, Result, debug_info, debug_warn, err,
|
Err, Pdu, Result, debug_info, debug_warn, err,
|
||||||
matrix::{event::gen_event_id, pdu::PartialPdu},
|
matrix::{event::gen_event_id, pdu::PartialPdu},
|
||||||
|
pdu::{AnyPDU, metadata::PduMetadata, new_pdu},
|
||||||
utils::{self, FutureBoolExt, future::ReadyEqExt},
|
utils::{self, FutureBoolExt, future::ReadyEqExt},
|
||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
@@ -216,7 +218,7 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
|||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
mut servers: HashSet<OwnedServerName, S>,
|
mut servers: HashSet<OwnedServerName, S>,
|
||||||
) -> Result<Pdu> {
|
) -> Result<AnyPDU> {
|
||||||
let mut make_leave_response_and_server =
|
let mut make_leave_response_and_server =
|
||||||
Err!(BadServerResponse("No remote server available to assist in leaving {room_id}."));
|
Err!(BadServerResponse("No remote server available to assist in leaving {room_id}."));
|
||||||
|
|
||||||
@@ -406,9 +408,14 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
|||||||
.outlier
|
.outlier
|
||||||
.add_pdu_outlier(&event_id, &leave_event);
|
.add_pdu_outlier(&event_id, &leave_event);
|
||||||
|
|
||||||
let leave_pdu = Pdu::from_id_val(&event_id, leave_event).map_err(|e| {
|
let leave_pdu = new_pdu(
|
||||||
err!(BadServerResponse("Invalid leave PDU received during federated leave: {e:?}"))
|
room_version_rules.into(),
|
||||||
})?;
|
PduMetadata::new(event_id),
|
||||||
|
serde_json::to_value(leave_event)?,
|
||||||
|
)?;
|
||||||
|
// let leave_pdu = Pdu::from_id_val(&event_id, leave_event).map_err(|e| {
|
||||||
|
// err!(BadServerResponse("Invalid leave PDU received during federated leave:
|
||||||
|
// {e:?}")) })?;
|
||||||
|
|
||||||
Ok(leave_pdu)
|
Ok(leave_pdu)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,8 +143,6 @@ fn as_mut_pdu(&mut self) -> &mut Pdu { unimplemented!("not a mutable Pdu") }
|
|||||||
|
|
||||||
fn into_pdu(self) -> Pdu;
|
fn into_pdu(self) -> Pdu;
|
||||||
|
|
||||||
fn is_owned(&self) -> bool;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Canonical properties
|
// Canonical properties
|
||||||
//
|
//
|
||||||
@@ -182,12 +180,12 @@ fn as_mut_pdu(&mut self) -> &mut Pdu { unimplemented!("not a mutable Pdu") }
|
|||||||
fn state_key(&self) -> Option<&str>;
|
fn state_key(&self) -> Option<&str>;
|
||||||
|
|
||||||
/// The event type.
|
/// The event type.
|
||||||
|
// #[deprecated]
|
||||||
fn kind(&self) -> &TimelineEventType;
|
fn kind(&self) -> &TimelineEventType;
|
||||||
|
|
||||||
/// Metadata container; peer-trusted only.
|
/// Metadata container; peer-trusted only.
|
||||||
fn unsigned(&self) -> Option<&RawJsonValue>;
|
fn unsigned(&self) -> Option<&RawJsonValue>;
|
||||||
|
|
||||||
//#[deprecated]
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn event_type(&self) -> &TimelineEventType { self.kind() }
|
fn event_type(&self) -> &TimelineEventType { self.kind() }
|
||||||
}
|
}
|
||||||
|
|||||||
+195
-12
@@ -1,19 +1,25 @@
|
|||||||
mod count;
|
mod count;
|
||||||
|
pub mod hashes;
|
||||||
mod id;
|
mod id;
|
||||||
|
pub mod metadata;
|
||||||
mod partial;
|
mod partial;
|
||||||
mod raw_id;
|
mod raw_id;
|
||||||
mod redact;
|
mod redact;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
mod unsigned;
|
mod unsigned;
|
||||||
|
pub mod v2;
|
||||||
|
pub mod v3;
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use assign::assign;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId,
|
CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId,
|
||||||
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, UInt, UserId, events::TimelineEventType,
|
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerSignatures, UInt, UserId, event_id,
|
||||||
|
events::TimelineEventType, room_version_rules::RoomVersionRules, state_res::Event,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
@@ -23,10 +29,193 @@
|
|||||||
partial::PartialPdu,
|
partial::PartialPdu,
|
||||||
raw_id::*,
|
raw_id::*,
|
||||||
};
|
};
|
||||||
use super::{Event, StateKey};
|
use super::StateKey;
|
||||||
use crate::Result;
|
use crate::{
|
||||||
|
Err, Result,
|
||||||
|
pdu::{hashes::Hashes, metadata::PduMetadata},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum EventFormatVersion {
|
||||||
|
/// V1 represents the PDU format for rooms v1 and v2.
|
||||||
|
V1,
|
||||||
|
/// V2 represents the PDU format for rooms v3-v11.
|
||||||
|
V2,
|
||||||
|
/// V3 represents the PDU format for rooms v12+.
|
||||||
|
V3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RoomVersionRules> for EventFormatVersion {
|
||||||
|
fn from(rules: RoomVersionRules) -> Self {
|
||||||
|
// TODO: this is hacky because ruma doesn't actually define event versions in an
|
||||||
|
// enum, but does define consts with the appropriate rules.
|
||||||
|
if rules.event_format.require_event_id {
|
||||||
|
Self::V1
|
||||||
|
} else if rules.event_format.require_room_create_room_id {
|
||||||
|
Self::V2
|
||||||
|
} else {
|
||||||
|
Self::V3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait that allows conversion from a generic PDU to a concrete PDU type.
|
||||||
|
pub trait ConcretePDU: ruma::state_res::Event<Id = OwnedEventId> {
|
||||||
|
/// The concrete type for this PDU (`v2::PDU`, `v3::PDU`, etc)
|
||||||
|
type Concrete;
|
||||||
|
|
||||||
|
/// Get a reference to the concrete PDU type.
|
||||||
|
fn to_concrete(&self) -> &Self::Concrete;
|
||||||
|
|
||||||
|
/// Get the value of the concrete PDU type, consuming.
|
||||||
|
fn into_concrete(self) -> Self::Concrete;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_pdu<E, C, P, JSON>(
|
||||||
|
format: EventFormatVersion,
|
||||||
|
metadata: PduMetadata,
|
||||||
|
json: serde_json::Value,
|
||||||
|
) -> Result<E>
|
||||||
|
where
|
||||||
|
E: ConcretePDU<Concrete = C>,
|
||||||
|
JSON: DeserializeOwned,
|
||||||
|
{
|
||||||
|
match format {
|
||||||
|
| EventFormatVersion::V2 =>
|
||||||
|
Ok(assign!(v2::PDU::try_from(json)?, {internal_metadata: metadata})),
|
||||||
|
| EventFormatVersion::V3 =>
|
||||||
|
Ok(assign!(v3::PDU::try_from(json)?, {internal_metadata: metadata})),
|
||||||
|
| _ => Err!("Unsupported event format: {format:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder type that specifies every field present on any PDU type, allowing
|
||||||
|
/// it to be used to create any PDU version with the appropriate function.
|
||||||
|
///
|
||||||
|
/// See `create_pdu`.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct CommonPDUBuilder {
|
||||||
|
pub auth_events: Option<Vec<OwnedEventId>>,
|
||||||
|
pub content: Option<Box<RawJsonValue>>,
|
||||||
|
pub depth: Option<UInt>,
|
||||||
|
pub hashes: Option<Hashes>,
|
||||||
|
pub origin_server_ts: Option<MilliSecondsSinceUnixEpoch>,
|
||||||
|
pub prev_events: Option<Vec<OwnedEventId>>,
|
||||||
|
pub redacts: Option<OwnedEventId>,
|
||||||
|
pub room_id: Option<OwnedRoomId>,
|
||||||
|
pub sender: Option<OwnedUserId>,
|
||||||
|
pub signatures: Option<ServerSignatures>,
|
||||||
|
pub state_key: Option<String>,
|
||||||
|
pub event_type: Option<TimelineEventType>,
|
||||||
|
pub unsigned: Option<Box<RawJsonValue>>,
|
||||||
|
pub internal_metadata: Option<PduMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new PDU based on the given version and builder.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use conduwuit_core::pdu::{CommonPDUBuilder, create_pdu}
|
||||||
|
///
|
||||||
|
/// let room_version_rules = RoomVersionRules::V10; // This usually comes dynamically.
|
||||||
|
/// let builder = CommonPDUBuilder {
|
||||||
|
/// auth_events: Some(vec![event_id!("$auth_event_id").to_owned()]),
|
||||||
|
/// content: Some(Box::new(serde_json::json!({"key": "value"}))),
|
||||||
|
/// depth: Some(1),
|
||||||
|
/// origin_server_ts: Some(MilliSecondsSinceUnixEpoch(123456789)),
|
||||||
|
/// prev_events: Some(vec![event_id!("$prev_event_id").to_owned()]),
|
||||||
|
/// room_id: Some(room_id!("!room_id").to_owned()),
|
||||||
|
/// sender: Some(user_id!("@sender:example.com").to_owned()),
|
||||||
|
/// event_type: Some(TimelineEventType::RoomMessage),
|
||||||
|
/// ..Default::default()
|
||||||
|
/// };
|
||||||
|
/// let pdu = create_pdu(
|
||||||
|
/// room_version_rules.into(),
|
||||||
|
/// builder,
|
||||||
|
/// ).expect("PDU creation must succeed");
|
||||||
|
/// ```
|
||||||
|
pub fn create_pdu<C>(version: EventFormatVersion, builder: CommonPDUBuilder) -> Result<C> {
|
||||||
|
match version {
|
||||||
|
| EventFormatVersion::V2 => Ok(create_v2_pdu(builder)),
|
||||||
|
| EventFormatVersion::V3 => Ok(create_v3_pdu(builder)),
|
||||||
|
| _ => Err!("Unsupported event format: {version:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_v2_pdu<C>(builder: CommonPDUBuilder) -> v2::PDU {
|
||||||
|
v2::PDU {
|
||||||
|
auth_events: builder
|
||||||
|
.auth_events
|
||||||
|
.expect("auth_events must be provided for a V2 PDU"),
|
||||||
|
content: builder
|
||||||
|
.content
|
||||||
|
.expect("content must be provided for a V2 PDU"),
|
||||||
|
depth: builder.depth.expect("depth must be provided for a V2 PDU"),
|
||||||
|
hashes: builder.hashes.unwrap_or_default(), /* We allow default here since we might not
|
||||||
|
* have hashed yet */
|
||||||
|
origin_server_ts: builder
|
||||||
|
.origin_server_ts
|
||||||
|
.expect("origin_server_ts must be provided for a V2 PDU"),
|
||||||
|
prev_events: builder
|
||||||
|
.prev_events
|
||||||
|
.expect("prev_events must be provided for a V2 PDU"),
|
||||||
|
redacts: builder.redacts,
|
||||||
|
room_id: builder
|
||||||
|
.room_id
|
||||||
|
.expect("room_id must be provided for a V2 PDU"),
|
||||||
|
sender: builder
|
||||||
|
.sender
|
||||||
|
.expect("sender must be provided for a V2 PDU"),
|
||||||
|
signatures: builder.signatures.unwrap_or_default(), /* We allow default here since we
|
||||||
|
* might not have signed yet */
|
||||||
|
state_key: builder.state_key,
|
||||||
|
event_type: builder
|
||||||
|
.event_type
|
||||||
|
.expect("event_type must be provided for a V2 PDU"),
|
||||||
|
unsigned: builder.unsigned,
|
||||||
|
internal_metadata: builder
|
||||||
|
.internal_metadata
|
||||||
|
.unwrap_or_else(|| PduMetadata::new(event_id!("$unknown").to_owned())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_v3_pdu(builder: CommonPDUBuilder) -> v3::PDU {
|
||||||
|
v3::PDU {
|
||||||
|
auth_events: builder
|
||||||
|
.auth_events
|
||||||
|
.expect("auth_events must be provided for a V3 PDU"),
|
||||||
|
content: builder
|
||||||
|
.content
|
||||||
|
.expect("content must be provided for a V3 PDU"),
|
||||||
|
depth: builder.depth.expect("depth must be provided for a V3 PDU"),
|
||||||
|
hashes: builder.hashes.unwrap_or_default(), /* We allow default here since we might not
|
||||||
|
* have hashed yet */
|
||||||
|
origin_server_ts: builder
|
||||||
|
.origin_server_ts
|
||||||
|
.expect("origin_server_ts must be provided for a V3 PDU"),
|
||||||
|
prev_events: builder
|
||||||
|
.prev_events
|
||||||
|
.expect("prev_events must be provided for a V3 PDU"),
|
||||||
|
room_id: builder.room_id,
|
||||||
|
sender: builder
|
||||||
|
.sender
|
||||||
|
.expect("sender must be provided for a V3 PDU"),
|
||||||
|
signatures: builder.signatures.unwrap_or_default(), /* We allow default here since we
|
||||||
|
* might not have signed yet */
|
||||||
|
state_key: builder.state_key,
|
||||||
|
event_type: builder
|
||||||
|
.event_type
|
||||||
|
.expect("event_type must be provided for a V3 PDU"),
|
||||||
|
unsigned: builder.unsigned,
|
||||||
|
internal_metadata: builder
|
||||||
|
.internal_metadata
|
||||||
|
.unwrap_or_else(|| PduMetadata::new(event_id!("$unknown").to_owned())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Persistent Data Unit (Event)
|
/// Persistent Data Unit (Event)
|
||||||
|
#[deprecated(note = "Use `v2::PDU` or `v3::PDU` or `ruma::state_res::Event` instead")]
|
||||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||||
pub struct Pdu {
|
pub struct Pdu {
|
||||||
pub event_id: OwnedEventId,
|
pub event_id: OwnedEventId,
|
||||||
@@ -85,7 +274,7 @@ pub fn from_id_val(event_id: &EventId, mut json: CanonicalJsonObject) -> Result<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event for Pdu {
|
impl crate::Event for Pdu {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
|
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
|
||||||
self.auth_events.iter().map(AsRef::as_ref)
|
self.auth_events.iter().map(AsRef::as_ref)
|
||||||
@@ -151,12 +340,9 @@ fn as_pdu(&self) -> &Pdu { self }
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_pdu(self) -> Pdu { self }
|
fn into_pdu(self) -> Pdu { self }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_owned(&self) -> bool { true }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event for &Pdu {
|
impl crate::Event for &Pdu {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
|
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
|
||||||
self.auth_events.iter().map(AsRef::as_ref)
|
self.auth_events.iter().map(AsRef::as_ref)
|
||||||
@@ -219,9 +405,6 @@ fn as_pdu(&self) -> &Pdu { self }
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_pdu(self) -> Pdu { self.clone() }
|
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
|
/// Prevent derived equality which wouldn't limit itself to event_id
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// The content hash map for a PDU.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
|
||||||
|
pub struct Hashes {
|
||||||
|
/// The SHA256 content hash.
|
||||||
|
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||||
|
pub sha256: String,
|
||||||
|
|
||||||
|
/// Any other hashes present, if any.
|
||||||
|
#[serde(flatten, default, skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
pub other: HashMap<String, String>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use ruma::{EventId, 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PduMetadata {
|
||||||
|
/// Creates a new PduMetadata instance with the minimum required fields.
|
||||||
|
pub fn new(event_id: OwnedEventId) -> Self { Self { event_id, rejected: false } }
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
use ruma::{
|
||||||
|
CanonicalJsonObject, 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<OwnedEventId>,
|
||||||
|
/// This event's content.
|
||||||
|
pub content: Box<RawJsonValue>,
|
||||||
|
/// 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<OwnedEventId>,
|
||||||
|
/// For redaction events, the ID of the event being redacted.
|
||||||
|
///
|
||||||
|
/// Not used in v11.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub redacts: Option<OwnedEventId>,
|
||||||
|
/// 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.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub state_key: Option<String>,
|
||||||
|
/// 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.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub unsigned: Option<Box<RawJsonValue>>,
|
||||||
|
|
||||||
|
/// Internal metadata that is important to the PDU but should not be
|
||||||
|
/// serialized into the final event.
|
||||||
|
///
|
||||||
|
/// This field MUST NOT be sent to clients or federation.
|
||||||
|
pub internal_metadata: PduMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PDU {
|
||||||
|
#[must_use]
|
||||||
|
pub fn internal_metadata(&self) -> &PduMetadata { &self.internal_metadata }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::pdu::ConcretePDU for PDU {
|
||||||
|
type Concrete = PDU;
|
||||||
|
|
||||||
|
fn to_concrete(&self) -> &Self::Concrete { &self }
|
||||||
|
|
||||||
|
fn into_concrete(self) -> Self::Concrete { self }
|
||||||
|
}
|
||||||
|
|
||||||
|
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<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||||
|
Box::new(self.prev_events.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||||
|
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 crate::Event for PDU {
|
||||||
|
fn as_pdu(&self) -> &Pdu { todo!() }
|
||||||
|
|
||||||
|
fn into_pdu(self) -> Pdu { todo!() }
|
||||||
|
|
||||||
|
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + 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<Item = &EventId> + 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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&RawJsonValue> for PDU {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &RawJsonValue) -> Result<Self, Self::Error> {
|
||||||
|
serde_json::from_str(value.get()).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Box<RawJsonValue>> for PDU {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(value: Box<RawJsonValue>) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_from(value.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<serde_json::Value> for PDU {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
|
||||||
|
serde_json::from_value(value).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<CanonicalJsonObject> for PDU {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(value: CanonicalJsonObject) -> Result<Self, Self::Error> {
|
||||||
|
serde_json::from_value(serde_json::to_value(value)?).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
use ruma::{
|
||||||
|
CanonicalJsonObject, 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<OwnedEventId>,
|
||||||
|
/// This event's content.
|
||||||
|
pub content: Box<RawJsonValue>,
|
||||||
|
/// 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<OwnedEventId>,
|
||||||
|
/// The room in which this event resides.
|
||||||
|
/// May be None if the event is a create event.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub room_id: Option<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.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub state_key: Option<String>,
|
||||||
|
/// 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.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub unsigned: Option<Box<RawJsonValue>>,
|
||||||
|
|
||||||
|
/// Internal metadata that is important to the PDU but should not be
|
||||||
|
/// serialized into the final event.
|
||||||
|
///
|
||||||
|
/// This field MUST NOT be sent to clients or federation.
|
||||||
|
pub internal_metadata: PduMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PDU {
|
||||||
|
#[must_use]
|
||||||
|
pub fn internal_metadata(&self) -> &PduMetadata { &self.internal_metadata }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::pdu::ConcretePDU<PDU> for PDU {
|
||||||
|
fn to_concrete(&self) -> &PDU { &self }
|
||||||
|
|
||||||
|
fn into_concrete(self) -> PDU { self }
|
||||||
|
}
|
||||||
|
|
||||||
|
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<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||||
|
Box::new(self.prev_events.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn auth_events(&self) -> Box<dyn DoubleEndedIterator<Item = &Self::Id> + '_> {
|
||||||
|
Box::new(self.auth_events.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redacts(&self) -> Option<&Self::Id> { None }
|
||||||
|
|
||||||
|
fn rejected(&self) -> bool { self.internal_metadata().rejected }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::Event for PDU {
|
||||||
|
fn as_pdu(&self) -> &Pdu { todo!() }
|
||||||
|
|
||||||
|
fn into_pdu(self) -> Pdu { todo!() }
|
||||||
|
|
||||||
|
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + 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<Item = &EventId> + 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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&RawJsonValue> for PDU {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &RawJsonValue) -> Result<Self, Self::Error> {
|
||||||
|
serde_json::from_str(value.get()).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Box<RawJsonValue>> for PDU {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(value: Box<RawJsonValue>) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_from(value.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<serde_json::Value> for PDU {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
|
||||||
|
serde_json::from_value(value).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<CanonicalJsonObject> for PDU {
|
||||||
|
type Error = crate::Error;
|
||||||
|
|
||||||
|
fn try_from(value: CanonicalJsonObject) -> Result<Self, Self::Error> {
|
||||||
|
serde_json::from_value(serde_json::to_value(value)?).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{cmp, collections::HashMap};
|
use std::{cmp, collections::HashMap};
|
||||||
|
|
||||||
use conduwuit::{smallstr::SmallString, trace};
|
use conduwuit::{pdu::AnyPDU, smallstr::SmallString, trace};
|
||||||
use conduwuit_core::{
|
use conduwuit_core::{
|
||||||
Err, Error, Result, err,
|
Err, Error, Result, err,
|
||||||
matrix::{
|
matrix::{
|
||||||
@@ -84,7 +84,7 @@ pub async fn create_event(
|
|||||||
sender: &UserId,
|
sender: &UserId,
|
||||||
room_id: Option<&RoomId>,
|
room_id: Option<&RoomId>,
|
||||||
_mutex_lock: &RoomMutexGuard,
|
_mutex_lock: &RoomMutexGuard,
|
||||||
) -> Result<(PduEvent, RoomVersionRules)> {
|
) -> Result<AnyPDU> {
|
||||||
let PartialPdu {
|
let PartialPdu {
|
||||||
event_type,
|
event_type,
|
||||||
content,
|
content,
|
||||||
@@ -282,7 +282,7 @@ pub async fn create_hash_and_sign_event(
|
|||||||
sender: &UserId,
|
sender: &UserId,
|
||||||
room_id: Option<&RoomId>,
|
room_id: Option<&RoomId>,
|
||||||
mutex_lock: &RoomMutexGuard,
|
mutex_lock: &RoomMutexGuard,
|
||||||
) -> Result<(PduEvent, CanonicalJsonObject)> {
|
) -> Result<AnyPDU> {
|
||||||
if !self.services.globals.user_is_local(sender) {
|
if !self.services.globals.user_is_local(sender) {
|
||||||
return Err!(Request(Forbidden("Sender must be a local user")));
|
return Err!(Request(Forbidden("Sender must be a local user")));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user