Support v1/v2 conditions for join/leave, creation and other operations. (#12)

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk
2025-08-31 10:15:49 +00:00
parent 685946faed
commit 3a78ba2b16
6 changed files with 68 additions and 120 deletions

View File

@@ -103,6 +103,10 @@ impl Pdu {
let event_id = CanonicalJsonValue::String(event_id.into()); let event_id = CanonicalJsonValue::String(event_id.into());
json.insert("event_id".into(), event_id); json.insert("event_id".into(), event_id);
Self::from_val(&json)
}
pub fn from_val(json: &CanonicalJsonObject) -> Result<Self> {
serde_json::to_value(json) serde_json::to_value(json)
.and_then(serde_json::from_value) .and_then(serde_json::from_value)
.map_err(Into::into) .map_err(Into::into)

View File

@@ -1,10 +1,10 @@
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, RoomVersionId, CanonicalJsonObject, CanonicalJsonValue, EventId, RoomId, RoomVersionId,
room_version_rules::{EventsReferenceFormatVersion, RoomVersionRules}, room_version_rules::{EventsReferenceFormatVersion, RoomVersionRules},
}; };
use crate::{ use crate::{
Result, err, extract_variant, is_equal_to, Result, extract_variant, is_equal_to,
matrix::{PduEvent, room_version}, matrix::{PduEvent, room_version},
}; };
@@ -69,6 +69,7 @@ fn mutate_outgoing_reference_format(value: &mut CanonicalJsonValue) {
} }
pub fn from_incoming_federation( pub fn from_incoming_federation(
room_id: &RoomId,
event_id: &EventId, event_id: &EventId,
pdu_json: &mut CanonicalJsonObject, pdu_json: &mut CanonicalJsonObject,
room_rules: &RoomVersionRules, room_rules: &RoomVersionRules,
@@ -82,10 +83,19 @@ pub fn from_incoming_federation(
} }
} }
pdu_json.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.into())); if !room_rules
.event_format
.require_room_create_room_id
&& pdu_json["type"] == "m.room.create"
{
pdu_json.insert("room_id".into(), CanonicalJsonValue::String(room_id.as_str().into()));
}
serde_json::from_value::<PduEvent>(serde_json::to_value(&pdu_json)?) if !room_rules.event_format.require_event_id {
.map_err(|e| err!(Request(BadJson(debug_warn!("Event is not a valid PDU: {e}"))))) pdu_json.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.into()));
}
PduEvent::from_val(pdu_json)
} }
fn mutate_incoming_reference_format(value: &mut CanonicalJsonValue) { fn mutate_incoming_reference_format(value: &mut CanonicalJsonValue) {

View File

@@ -17,11 +17,8 @@ use ruma::{
}; };
use tuwunel_core::{ use tuwunel_core::{
Err, Result, debug, debug_error, debug_info, debug_warn, err, error, implement, info, Err, Result, debug, debug_error, debug_info, debug_warn, err, error, implement, info,
matrix::{ matrix::{event::gen_event_id_canonical_json, room_version},
event::{gen_event_id, gen_event_id_canonical_json}, pdu::{PduBuilder, format::from_incoming_federation},
room_version,
},
pdu::{PduBuilder, PduEvent},
state_res, trace, state_res, trace,
utils::{self, IterStream, ReadyExt}, utils::{self, IterStream, ReadyExt},
warn, warn,
@@ -152,6 +149,8 @@ pub async fn join_remote(
)); ));
} }
let room_version_rules = room_version::rules(&room_version_id)?;
let mut join_event_stub: CanonicalJsonObject = let mut join_event_stub: CanonicalJsonObject =
serde_json::from_str(make_join_response.event.get()).map_err(|e| { serde_json::from_str(make_join_response.event.get()).map_err(|e| {
err!(BadServerResponse(warn!( err!(BadServerResponse(warn!(
@@ -221,27 +220,10 @@ pub async fn join_remote(
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
); );
// We keep the "event_id" in the pdu only in v1 or let event_id = self
// v2 rooms .services
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
join_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
self.services
.server_keys .server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?; .gen_id_hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now // It has enough fields to be called a proper event now
let mut join_event = join_event_stub; let mut join_event = join_event_stub;
@@ -342,8 +324,8 @@ pub async fn join_remote(
.await; .await;
info!("Parsing join event"); info!("Parsing join event");
let parsed_join_pdu = PduEvent::from_id_val(&event_id, join_event.clone()) let parsed_join_pdu =
.map_err(|e| err!(BadServerResponse("Invalid join event PDU: {e:?}")))?; from_incoming_federation(room_id, &event_id, &mut join_event, &room_version_rules)?;
info!("Acquiring server signing keys for response events"); info!("Acquiring server signing keys for response events");
let resp_events = &send_join_response.room_state; let resp_events = &send_join_response.room_state;
@@ -368,26 +350,15 @@ pub async fn join_remote(
}) })
.inspect_err(|e| debug_error!("Invalid send_join state event: {e:?}")) .inspect_err(|e| debug_error!("Invalid send_join state event: {e:?}"))
.ready_filter_map(Result::ok) .ready_filter_map(Result::ok)
.fold(HashMap::new(), async |mut state, (event_id, mut value)| { .ready_filter_map(|(event_id, mut value)| {
let pdu = if value["type"] == "m.room.create" { from_incoming_federation(room_id, &event_id, &mut value, &room_version_rules)
if !value.contains_key("room_id") { .inspect_err(|e| {
let room_id = CanonicalJsonValue::String(room_id.as_str().into());
value.insert("room_id".into(), room_id);
}
PduEvent::from_rid_val(room_id, &event_id, value.clone())
} else {
PduEvent::from_id_val(&event_id, value.clone())
};
let pdu = match pdu {
| Ok(pdu) => pdu,
| Err(e) => {
debug_warn!("Invalid PDU in send_join response: {e:?}: {value:#?}"); debug_warn!("Invalid PDU in send_join response: {e:?}: {value:#?}");
return state; })
}, .map(move |pdu| (event_id, pdu, value))
}; .ok()
})
.fold(HashMap::new(), async |mut state, (event_id, pdu, value)| {
self.services self.services
.timeline .timeline
.add_pdu_outlier(&event_id, &value); .add_pdu_outlier(&event_id, &value);
@@ -423,7 +394,11 @@ pub async fn join_remote(
.inspect_err(|e| debug_error!("Invalid send_join auth_chain event: {e:?}")) .inspect_err(|e| debug_error!("Invalid send_join auth_chain event: {e:?}"))
.ready_filter_map(Result::ok) .ready_filter_map(Result::ok)
.ready_for_each(|(event_id, mut value)| { .ready_for_each(|(event_id, mut value)| {
if !value.contains_key("room_id") { if !room_version_rules
.event_format
.require_room_create_room_id
&& value["type"] == "m.room.create"
{
let room_id = CanonicalJsonValue::String(room_id.as_str().into()); let room_id = CanonicalJsonValue::String(room_id.as_str().into());
value.insert("room_id".into(), room_id); value.insert("room_id".into(), room_id);
} }
@@ -438,7 +413,7 @@ pub async fn join_remote(
debug!("Running send_join auth check"); debug!("Running send_join auth check");
state_res::auth_check( state_res::auth_check(
&room_version::rules(&room_version_id)?, &room_version_rules,
&parsed_join_pdu, &parsed_join_pdu,
&async |event_id| self.services.timeline.get_pdu(&event_id).await, &async |event_id| self.services.timeline.get_pdu(&event_id).await,
&async |event_type, state_key| { &async |event_type, state_key| {
@@ -719,27 +694,10 @@ pub async fn join_local(
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
); );
// We keep the "event_id" in the pdu only in v1 or let event_id = self
// v2 rooms .services
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
join_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
self.services
.server_keys .server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?; .gen_id_hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now // It has enough fields to be called a proper event now
let join_event = join_event_stub; let join_event = join_event_stub;

View File

@@ -2,7 +2,7 @@ use std::collections::HashSet;
use futures::{FutureExt, StreamExt, TryFutureExt, pin_mut}; use futures::{FutureExt, StreamExt, TryFutureExt, pin_mut};
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, RoomVersionId, UserId, CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, UserId,
api::federation, api::federation,
events::{ events::{
StateEventType, StateEventType,
@@ -11,7 +11,6 @@ use ruma::{
}; };
use tuwunel_core::{ use tuwunel_core::{
Err, Result, debug_info, debug_warn, err, implement, Err, Result, debug_info, debug_warn, err, implement,
matrix::event::gen_event_id,
pdu::PduBuilder, pdu::PduBuilder,
utils::{self, FutureBoolExt, future::ReadyEqExt}, utils::{self, FutureBoolExt, future::ReadyEqExt},
warn, warn,
@@ -296,26 +295,10 @@ pub async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result {
), ),
); );
// room v3 and above removed the "event_id" field from remote PDU format let event_id = self
match room_version_id { .services
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
leave_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
self.services
.server_keys .server_keys
.hash_and_sign_event(&mut leave_event_stub, &room_version_id)?; .gen_id_hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&leave_event_stub, &room_version_id)?;
// Add event_id back
leave_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now // It has enough fields to be called a proper event now
let leave_event = leave_event_stub; let leave_event = leave_event_stub;

View File

@@ -70,7 +70,7 @@ pub(super) async fn handle_outlier_pdu(
// Now that we have checked the signature and hashes we can make mutations and // Now that we have checked the signature and hashes we can make mutations and
// convert to our PduEvent type. // convert to our PduEvent type.
let event = from_incoming_federation(event_id, &mut pdu_json, &room_rules)?; let event = from_incoming_federation(room_id, event_id, &mut pdu_json, &room_rules)?;
check_room_id(room_id, &event)?; check_room_id(room_id, &event)?;

View File

@@ -3,16 +3,16 @@ use std::cmp;
use futures::{StreamExt, TryStreamExt}; use futures::{StreamExt, TryStreamExt};
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, MilliSecondsSinceUnixEpoch, OwnedEventId, CanonicalJsonObject, CanonicalJsonValue, MilliSecondsSinceUnixEpoch, OwnedEventId,
OwnedRoomId, RoomId, RoomVersionId, UserId, OwnedRoomId, RoomId, UserId,
events::{StateEventType, TimelineEventType, room::create::RoomCreateEventContent}, events::{StateEventType, TimelineEventType, room::create::RoomCreateEventContent},
room_version_rules::RoomIdFormatVersion, room_version_rules::RoomIdFormatVersion,
uint, uint,
}; };
use serde_json::value::to_raw_value; use serde_json::value::to_raw_value;
use tuwunel_core::{ use tuwunel_core::{
Err, Error, Result, err, implement, Error, Result, err, implement,
matrix::{ matrix::{
event::{Event, StateKey, TypeExt, gen_event_id}, event::{Event, StateKey, TypeExt},
pdu::{EventHash, PduBuilder, PduEvent}, pdu::{EventHash, PduBuilder, PduEvent},
room_version, room_version,
state_res::{self}, state_res::{self},
@@ -175,36 +175,29 @@ pub async fn create_hash_and_sign_event(
err!(Request(BadJson(warn!("Failed to convert PDU to canonical JSON: {e}")))) err!(Request(BadJson(warn!("Failed to convert PDU to canonical JSON: {e}"))))
})?; })?;
// room v3 and above removed the "event_id" field from remote PDU format
if !matches!(room_version, RoomVersionId::V1 | RoomVersionId::V2) {
pdu_json.remove("event_id");
}
// room v12 and above removed the placeholder "room_id" field from m.room.create // room v12 and above removed the placeholder "room_id" field from m.room.create
if matches!(version_rules.room_id_format, RoomIdFormatVersion::V2) if !version_rules
.event_format
.require_room_create_room_id
&& pdu.kind == TimelineEventType::RoomCreate && pdu.kind == TimelineEventType::RoomCreate
{ {
pdu_json.remove("room_id"); pdu_json.remove("room_id");
} }
if let Err(e) = self pdu.event_id = self
.services .services
.server_keys .server_keys
.hash_and_sign_event(&mut pdu_json, &room_version) .gen_id_hash_and_sign_event(&mut pdu_json, &room_version)
{ .map_err(|e| {
use ruma::signatures::Error::PduSize; use Error::Signatures;
use ruma::signatures::Error::PduSize;
return match e { match e {
| Error::Signatures(PduSize) => { | Signatures(PduSize) => {
Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)"))) err!(Request(TooLarge("PDU exceeds 65535 bytes")))
}, },
| _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))), | _ => err!(Request(Unknown(warn!("Signing event failed: {e}")))),
}; }
} })?;
// Generate event id
pdu.event_id = gen_event_id(&pdu_json, &room_version)?;
pdu_json.insert("event_id".into(), CanonicalJsonValue::String(pdu.event_id.clone().into()));
// Room id is event id for V12+ // Room id is event id for V12+
if matches!(version_rules.room_id_format, RoomIdFormatVersion::V2) if matches!(version_rules.room_id_format, RoomIdFormatVersion::V2)