From c1cbd8cf40271e1abe3cb2b1f2a83eeb31938b1b Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Tue, 7 Apr 2026 15:04:05 +0100 Subject: [PATCH] WIP --- rust/src/events/mod.rs | 56 +++++++++++++++++++++++------ rust/src/events/utils.rs | 78 ++++++---------------------------------- 2 files changed, 56 insertions(+), 78 deletions(-) diff --git a/rust/src/events/mod.rs b/rust/src/events/mod.rs index bf6eb78367..62e79771b0 100644 --- a/rust/src/events/mod.rs +++ b/rust/src/events/mod.rs @@ -21,6 +21,7 @@ //! Classes for representing Events. use std::{ + borrow::Cow, collections::HashMap, str::FromStr, sync::{Arc, RwLock}, @@ -595,8 +596,6 @@ struct EventCommonFields { type_: Box, signatures: Signatures, - room_id: Option>, - #[serde(default)] unsigned: JsonObjectMutable, @@ -616,7 +615,7 @@ struct Event { #[pymethods] impl Event { #[new] - fn new<'a, 'py>( + fn new_from_py<'a, 'py>( py: Python<'py>, event_dict: &'a Bound<'py, PyAny>, room_version: &'a Bound<'py, PyAny>, @@ -648,7 +647,7 @@ impl Event { let event_format_v3_v4_plus: EventFormatV3V4Container = depythonize(event_dict)?; - event_format_v3_v4_plus.check_valid(room_version)?; + event_format_v3_v4_plus.validate(room_version)?; let internal_metadata = Py::new(py, EventInternalMetadata::new(internal_metadata_dict)?)?; @@ -701,10 +700,9 @@ impl Event { } #[getter] - fn room_id(&self) -> Option<&str> { + fn room_id(&self) -> PyResult> { match &self.inner { - EventFormatEnum::V3V4Plus(format) => format.common_fields.room_id.as_deref(), - // ... + EventFormatEnum::V3V4Plus(format) => Ok(format.room_id(&self.event_id)?), } } @@ -923,6 +921,7 @@ enum EventFormatEnum { struct EventFormatV3V4Plus { auth_events: Vec, prev_events: Vec, + room_id: Option>, } #[derive(Serialize, Deserialize)] @@ -934,8 +933,28 @@ struct EventFormatV3V4Container { } impl EventFormatV3V4Container { - fn check_valid(&self, room_version: &RoomVersion) -> Result<(), Error> { - if self.common_fields.room_id.is_none() { + fn room_id(&self, event_id: &EventID) -> Result, Error> { + if let Some(room_id) = self.specific_fields.room_id.as_deref() { + return Ok(room_id.into()); + } + + // Ensure that this is a create event, as all other events must + // have a room ID. + if &*self.common_fields.type_ != "m.room.create" { + bail!("Event '{event_id}' has no room ID"); + } + + // The room ID is derived from the event ID by replacing the + // leading '$' with a '!'. + let mut room_id = String::with_capacity(event_id.len()); + room_id.push('!'); + room_id.push_str(&event_id[1..]); + + Ok(room_id.into()) + } + + fn validate(&self, room_version: &RoomVersion) -> Result<(), Error> { + if self.specific_fields.room_id.is_none() { // Only create events in event formats v4 plus can have a missing room_id. if room_version.event_format != EventFormatVersions::ROOM_V4_PLUS { bail!("room_id is required for event formats v3 and below"); @@ -953,6 +972,8 @@ impl EventFormatV3V4Container { #[cfg(test)] mod tests { + use signed_json::json; + use super::*; #[test] @@ -966,7 +987,7 @@ mod tests { assert_eq!(&*event.common_fields.type_, "m.room.create"); assert_eq!( - event.common_fields.room_id.as_deref(), + event.specific_fields.room_id.as_deref(), Some("!qVoJSympOqdUQRUfiC:localhost:8800") ); @@ -996,4 +1017,19 @@ mod tests { serde_json::from_str::(json).unwrap() ); } + + #[test] + fn test_room_id_for_create_event() { + let json = r#"{"auth_events":[],"prev_events":[],"type":"m.room.create","sender":"@erikj:jki.re","content":{"room_version":"12","predecessor":{"room_id":"!VuNGkDTdbMOOxSmuDa:jki.re"}},"depth":1,"state_key":"","origin_server_ts":1775568141481,"hashes":{"sha256":"qBX+glsKvogXFrvsEN0eh13pO2kpuE6o/b4yREPtOqw"},"signatures":{"jki.re":{"ed25519:auto":"n/4gHQRagk3r1r24L/7a+oaMMf9cysVfQRYdjpDZcf4ppkVym33rhTW18Vy4zMa1L5nsWLkxsBvbrRRDYUOhBQ"}},"unsigned":{"age_ts":1775568141481}}"#; + let event_value: serde_json::Value = serde_json::from_str(json).unwrap(); + + let event: EventFormatV3V4Container = serde_json::from_str(json).unwrap(); + + let event_id = calculate_event_id(&event_value, &RoomVersion::V12).unwrap(); + + assert_eq!( + &*event.room_id(&event_id).unwrap(), + "!BeXKh925K_M46DwsuJFR0EyBpE1P7CFUDGuWW4xw55Y" + ); + } } diff --git a/rust/src/events/utils.rs b/rust/src/events/utils.rs index 2bb87ee2d0..1578092776 100644 --- a/rust/src/events/utils.rs +++ b/rust/src/events/utils.rs @@ -112,7 +112,7 @@ pub fn redact(event: &Value, room_version: &RoomVersion) -> anyhow::Result anyhow::Result { add_content_field(membership_field::MEMBERSHIP); - if use_restricted_join_rule_fix(room_version) { + if room_version.restricted_join_rule_fix { add_content_field(membership_field::JOIN_AUTHORISED_VIA_USERS_SERVER); } - if use_updated_redaction_rules(room_version) { + if room_version.updated_redaction_rules { // Preserve the signed field under third_party_invite. if let Some(third_party_invite) = event_content.get(membership_field::THIRD_PARTY_INVITE) @@ -163,7 +163,7 @@ pub fn redact(event: &Value, room_version: &RoomVersion) -> anyhow::Result { - if use_updated_redaction_rules(room_version) { + if room_version.updated_redaction_rules { // MSC2176 rules state that create events cannot have their `content` redacted. if let Some(event_content_object) = event_content.as_object() { for (field, _value) in event_content_object { @@ -171,18 +171,18 @@ pub fn redact(event: &Value, room_version: &RoomVersion) -> anyhow::Result { add_content_field(join_rules_field::JOIN_RULE); - if use_restricted_join_rule(room_version) { + if room_version.restricted_join_rule { add_content_field(join_rules_field::ALLOW); } } @@ -195,17 +195,17 @@ pub fn redact(event: &Value, room_version: &RoomVersion) -> anyhow::Result { + M_ROOM_ALIASES if room_version.special_case_aliases_auth => { add_content_field(aliases_field::ALIASES); } M_ROOM_HISTORY_VISIBILITY => { add_content_field(history_visibility_field::HISTORY_VISIBILITY) } - M_ROOM_REDACTION if use_updated_redaction_rules(room_version) => { + M_ROOM_REDACTION if room_version.updated_redaction_rules => { add_content_field(redaction_field::REDACTS); } _ => (), @@ -261,64 +261,6 @@ pub fn redact_internal(event: &Value, room_version: &RoomVersion) -> anyhow::Res Ok(redacted) } -/// Whether the `redacts` field has been moved from the top level to inside `content` for the given -/// room version. The top-level `origin`, `membership`, and `prev_state` properties are no longer -/// protected from redaction. The `m.room.create` event now keeps the entire content property. The -/// `m.room.redaction` event keeps the `redacts` property under `content`. The -/// `m.room.power_levels` event keeps the `invite` property under `content`. -/// -/// Added in room version 11. -/// -pub fn use_updated_redaction_rules(room_version: &RoomVersion) -> bool { - room_version.updated_redaction_rules -} - -/// Whether `m.room.aliases` has significant meaning. -/// This means that the `aliases` property under `content` is no longer kept for the `m.room.aliases` -/// event type. -/// -/// Removed in room version 6. -/// -fn use_special_case_aliases_auth(room_version_id: &RoomVersion) -> bool { - room_version_id.special_case_aliases_auth -} - -/// `m.room.join_rules` events now keep `allow` in addition to other keys in `content` when being -/// redacted. -/// -/// Added in room version 8. -/// -fn use_restricted_join_rule(room_version_id: &RoomVersion) -> bool { - room_version_id.restricted_join_rule -} - -/// `m.room.member` events now keep `join_authorised_via_users_server` in addition to other keys in -/// `content` when being redacted. -/// -/// Added in room version 9. -/// -fn use_restricted_join_rule_fix(room_version_id: &RoomVersion) -> bool { - room_version_id.restricted_join_rule_fix -} - -/// The content of a `m.room.create` event no longer has a `creator` property, which previously was -/// always equivalent to the `sender` of the event. -/// -/// Added in room version 11. -/// -fn use_implicit_room_creator(room_version_id: &RoomVersion) -> bool { - room_version_id.implicit_room_creator -} - -/// Whether room ids are hashes, or still the older localpart:domain style. -/// Returns true if the room_id is a hash for the given room version. -/// -/// Added in room version 12. -/// -pub fn use_room_ids_as_hashes(room_version_id: &RoomVersion) -> bool { - room_version_id.msc4291_room_ids_as_hashes -} - #[cfg(test)] mod tests { use serde_json::json;