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());
json.insert("event_id".into(), event_id);
Self::from_val(&json)
}
pub fn from_val(json: &CanonicalJsonObject) -> Result<Self> {
serde_json::to_value(json)
.and_then(serde_json::from_value)
.map_err(Into::into)

View File

@@ -1,10 +1,10 @@
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, RoomVersionId,
CanonicalJsonObject, CanonicalJsonValue, EventId, RoomId, RoomVersionId,
room_version_rules::{EventsReferenceFormatVersion, RoomVersionRules},
};
use crate::{
Result, err, extract_variant, is_equal_to,
Result, extract_variant, is_equal_to,
matrix::{PduEvent, room_version},
};
@@ -69,6 +69,7 @@ fn mutate_outgoing_reference_format(value: &mut CanonicalJsonValue) {
}
pub fn from_incoming_federation(
room_id: &RoomId,
event_id: &EventId,
pdu_json: &mut CanonicalJsonObject,
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)?)
.map_err(|e| err!(Request(BadJson(debug_warn!("Event is not a valid PDU: {e}")))))
if !room_rules.event_format.require_event_id {
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) {

View File

@@ -17,11 +17,8 @@ use ruma::{
};
use tuwunel_core::{
Err, Result, debug, debug_error, debug_info, debug_warn, err, error, implement, info,
matrix::{
event::{gen_event_id, gen_event_id_canonical_json},
room_version,
},
pdu::{PduBuilder, PduEvent},
matrix::{event::gen_event_id_canonical_json, room_version},
pdu::{PduBuilder, format::from_incoming_federation},
state_res, trace,
utils::{self, IterStream, ReadyExt},
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 =
serde_json::from_str(make_join_response.event.get()).map_err(|e| {
err!(BadServerResponse(warn!(
@@ -221,27 +220,10 @@ pub async fn join_remote(
.expect("event is valid, we just created it"),
);
// We keep the "event_id" in the pdu only in v1 or
// v2 rooms
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
let event_id = self
.services
.server_keys
.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()));
.gen_id_hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// It has enough fields to be called a proper event now
let mut join_event = join_event_stub;
@@ -342,8 +324,8 @@ pub async fn join_remote(
.await;
info!("Parsing join event");
let parsed_join_pdu = PduEvent::from_id_val(&event_id, join_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid join event PDU: {e:?}")))?;
let parsed_join_pdu =
from_incoming_federation(room_id, &event_id, &mut join_event, &room_version_rules)?;
info!("Acquiring server signing keys for response events");
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:?}"))
.ready_filter_map(Result::ok)
.fold(HashMap::new(), async |mut state, (event_id, mut value)| {
let pdu = if value["type"] == "m.room.create" {
if !value.contains_key("room_id") {
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) => {
.ready_filter_map(|(event_id, mut value)| {
from_incoming_federation(room_id, &event_id, &mut value, &room_version_rules)
.inspect_err(|e| {
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
.timeline
.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:?}"))
.ready_filter_map(Result::ok)
.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());
value.insert("room_id".into(), room_id);
}
@@ -438,7 +413,7 @@ pub async fn join_remote(
debug!("Running send_join auth check");
state_res::auth_check(
&room_version::rules(&room_version_id)?,
&room_version_rules,
&parsed_join_pdu,
&async |event_id| self.services.timeline.get_pdu(&event_id).await,
&async |event_type, state_key| {
@@ -719,27 +694,10 @@ pub async fn join_local(
.expect("event is valid, we just created it"),
);
// We keep the "event_id" in the pdu only in v1 or
// v2 rooms
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
let event_id = self
.services
.server_keys
.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()));
.gen_id_hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// It has enough fields to be called a proper event now
let join_event = join_event_stub;

View File

@@ -2,7 +2,7 @@ use std::collections::HashSet;
use futures::{FutureExt, StreamExt, TryFutureExt, pin_mut};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, RoomVersionId, UserId,
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, UserId,
api::federation,
events::{
StateEventType,
@@ -11,7 +11,6 @@ use ruma::{
};
use tuwunel_core::{
Err, Result, debug_info, debug_warn, err, implement,
matrix::event::gen_event_id,
pdu::PduBuilder,
utils::{self, FutureBoolExt, future::ReadyEqExt},
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
match room_version_id {
| 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
let event_id = self
.services
.server_keys
.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()));
.gen_id_hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
// It has enough fields to be called a proper event now
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
// 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)?;

View File

@@ -3,16 +3,16 @@ use std::cmp;
use futures::{StreamExt, TryStreamExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, MilliSecondsSinceUnixEpoch, OwnedEventId,
OwnedRoomId, RoomId, RoomVersionId, UserId,
OwnedRoomId, RoomId, UserId,
events::{StateEventType, TimelineEventType, room::create::RoomCreateEventContent},
room_version_rules::RoomIdFormatVersion,
uint,
};
use serde_json::value::to_raw_value;
use tuwunel_core::{
Err, Error, Result, err, implement,
Error, Result, err, implement,
matrix::{
event::{Event, StateKey, TypeExt, gen_event_id},
event::{Event, StateKey, TypeExt},
pdu::{EventHash, PduBuilder, PduEvent},
room_version,
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}"))))
})?;
// 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
if matches!(version_rules.room_id_format, RoomIdFormatVersion::V2)
if !version_rules
.event_format
.require_room_create_room_id
&& pdu.kind == TimelineEventType::RoomCreate
{
pdu_json.remove("room_id");
}
if let Err(e) = self
pdu.event_id = self
.services
.server_keys
.hash_and_sign_event(&mut pdu_json, &room_version)
{
use ruma::signatures::Error::PduSize;
return match e {
| Error::Signatures(PduSize) => {
Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)")))
},
| _ => 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()));
.gen_id_hash_and_sign_event(&mut pdu_json, &room_version)
.map_err(|e| {
use Error::Signatures;
use ruma::signatures::Error::PduSize;
match e {
| Signatures(PduSize) => {
err!(Request(TooLarge("PDU exceeds 65535 bytes")))
},
| _ => err!(Request(Unknown(warn!("Signing event failed: {e}")))),
}
})?;
// Room id is event id for V12+
if matches!(version_rules.room_id_format, RoomIdFormatVersion::V2)