Encapsulate incoming pdu formatting and checks within constructor.

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk
2026-03-11 13:42:17 +00:00
parent a554280559
commit e9864bc4e7
13 changed files with 110 additions and 79 deletions

View File

@@ -553,9 +553,11 @@ pub(super) async fn force_set_room_state_from_server(
};
let pdu = if value["type"] == "m.room.create" {
PduEvent::from_rid_val(&room_id, &event_id, value.clone()).map_err(invalid_pdu_err)?
PduEvent::from_object_and_roomid_and_eventid(&room_id, &event_id, value.clone())
.map_err(invalid_pdu_err)?
} else {
PduEvent::from_id_val(&event_id, value.clone()).map_err(invalid_pdu_err)?
PduEvent::from_object_and_eventid(&event_id, value.clone())
.map_err(invalid_pdu_err)?
};
if !value.contains_key("room_id") {

View File

@@ -1,6 +1,6 @@
mod builder;
mod count;
pub mod format;
mod format;
mod hashes;
mod id;
mod raw_id;
@@ -13,6 +13,7 @@ use std::cmp::Ordering;
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId,
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, UInt, UserId, events::TimelineEventType,
room_version_rules::RoomVersionRules,
};
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;
@@ -22,7 +23,10 @@ pub use self::{
Count as PduCount, Id as PduId, Pdu as PduEvent, RawId as RawPduId,
builder::{Builder, Builder as PduBuilder},
count::Count,
format::check::check_pdu_format,
format::{
check::{check_room_id, check_rules},
from_incoming_federation, into_outgoing_federation,
},
hashes::EventHashes as EventHash,
id::Id,
raw_id::*,
@@ -99,28 +103,61 @@ pub const MAX_PREV_EVENTS: usize = 20;
pub const MAX_AUTH_EVENTS: usize = 10;
impl Pdu {
pub fn from_rid_val(
pub fn from_object_and_roomid_and_eventid(
room_id: &RoomId,
event_id: &EventId,
mut json: CanonicalJsonObject,
) -> Result<Self> {
let room_id = CanonicalJsonValue::String(room_id.into());
json.insert("room_id".into(), room_id);
Self::from_id_val(event_id, json)
Self::from_object_and_eventid(event_id, json)
}
pub fn from_id_val(event_id: &EventId, mut json: CanonicalJsonObject) -> Result<Self> {
pub fn from_object_and_eventid(
event_id: &EventId,
mut json: CanonicalJsonObject,
) -> Result<Self> {
let event_id = CanonicalJsonValue::String(event_id.into());
json.insert("event_id".into(), event_id);
Self::from_val(&json)
Self::from_object(json)
}
pub fn from_val(json: &CanonicalJsonObject) -> Result<Self> {
serde_json::to_value(json)
.and_then(serde_json::from_value)
.map_err(Into::into)
pub fn from_object_federation(
room_id: &RoomId,
event_id: &EventId,
json: CanonicalJsonObject,
rules: &RoomVersionRules,
) -> Result<(Self, CanonicalJsonObject)> {
let json = from_incoming_federation(room_id, event_id, json, rules);
let pdu = Self::from_object_checked(json.clone(), rules)?;
check_room_id(&pdu, room_id)?;
Ok((pdu, json))
}
pub fn from_object_checked(
json: CanonicalJsonObject,
rules: &RoomVersionRules,
) -> Result<Self> {
check_rules(&json, &rules.event_format)?;
Self::from_object(json)
}
pub fn from_object(json: CanonicalJsonObject) -> Result<Self> {
let json = CanonicalJsonValue::Object(json);
Self::from_value(json)
}
pub fn from_raw_value(json: &RawJsonValue) -> Result<Self> {
let json: CanonicalJsonValue = json.into();
Self::from_value(json)
}
pub fn from_value(json: CanonicalJsonValue) -> Result<Self> {
serde_json::from_value(json.into()).map_err(Into::into)
}
pub fn from_raw_json(json: &RawJsonValue) -> Result<Self> {
Self::deserialize(json).map_err(Into::into)
}
}

View File

@@ -5,11 +5,9 @@ use ruma::{
room_version_rules::{EventsReferenceFormatVersion, RoomVersionRules},
};
use crate::{
Result, extract_variant, is_equal_to,
matrix::{PduEvent, room_version},
};
use crate::{extract_variant, is_equal_to, matrix::room_version};
#[must_use]
pub fn into_outgoing_federation(
mut pdu_json: CanonicalJsonObject,
room_version: &RoomVersionId,
@@ -68,12 +66,13 @@ fn mutate_outgoing_reference_format(value: &mut CanonicalJsonValue) {
});
}
#[must_use]
pub fn from_incoming_federation(
room_id: &RoomId,
event_id: &EventId,
pdu_json: &mut CanonicalJsonObject,
mut pdu_json: CanonicalJsonObject,
room_rules: &RoomVersionRules,
) -> Result<PduEvent> {
) -> CanonicalJsonObject {
if matches!(room_rules.events_reference_format, EventsReferenceFormatVersion::V1) {
if let Some(value) = pdu_json.get_mut("auth_events") {
mutate_incoming_reference_format(value);
@@ -95,9 +94,7 @@ pub fn from_incoming_federation(
pdu_json.insert("event_id".into(), CanonicalJsonValue::String(event_id.into()));
}
check::check_pdu_format(pdu_json, &room_rules.event_format)?;
PduEvent::from_val(pdu_json)
pdu_json
}
fn mutate_incoming_reference_format(value: &mut CanonicalJsonValue) {

View File

@@ -4,10 +4,21 @@ use ruma::{
};
use serde_json::to_string as to_json_string;
use crate::{
Err, Result, err,
matrix::pdu::{MAX_AUTH_EVENTS, MAX_PDU_BYTES, MAX_PREV_EVENTS},
};
use super::super::{MAX_AUTH_EVENTS, MAX_PDU_BYTES, MAX_PREV_EVENTS, Pdu};
use crate::{Err, Result, err};
pub fn check_room_id(pdu: &Pdu, room_id: &RoomId) -> Result {
if pdu.room_id != room_id {
return Err!(Request(InvalidParam(error!(
pdu_event_id = ?pdu.event_id,
pdu_room_id = ?pdu.room_id,
?room_id,
"Event in wrong room",
))));
}
Ok(())
}
/// Check that the given canonicalized PDU respects the event format of the room
/// version and the [size limits] from the Matrix specification.
@@ -31,7 +42,7 @@ use crate::{
///
/// [size limits]: https://spec.matrix.org/latest/client-server-api/#size-limits
/// [checks performed on receipt of a PDU]: https://spec.matrix.org/latest/server-server-api/#checks-performed-on-receipt-of-a-pdu
pub fn check_pdu_format(pdu: &CanonicalJsonObject, rules: &EventFormatRules) -> Result {
pub fn check_rules(pdu: &CanonicalJsonObject, rules: &EventFormatRules) -> Result {
// Check the PDU size, it must occur on the full PDU with signatures.
let json = to_json_string(&pdu)
.map_err(|e| err!(Request(BadJson("Failed to serialize canonical JSON: {e}"))))?;
@@ -200,7 +211,7 @@ mod tests {
};
use serde_json::{from_value as from_json_value, json};
use super::check_pdu_format;
use super::check_rules as check_pdu_format;
/// Construct a PDU valid for the event format of room v1.
fn pdu_v1() -> CanonicalJsonObject {

View File

@@ -35,7 +35,7 @@ pub async fn format_pdu_into(
.as_ref()
.or(room_version)
{
pdu_json = pdu::format::into_outgoing_federation(pdu_json, room_version);
pdu_json = pdu::into_outgoing_federation(pdu_json, room_version);
} else {
pdu_json.remove("event_id");
}

View File

@@ -29,7 +29,7 @@ use serde_json::value::RawValue as RawJsonValue;
use tuwunel_core::{
Err, Result, at, debug, debug_error, debug_info, debug_warn, err, error, implement, info,
matrix::{event::gen_event_id_canonical_json, room_version},
pdu::{PduBuilder, check_pdu_format, format::from_incoming_federation},
pdu::{Pdu, PduBuilder, check_rules},
trace,
utils::{self, BoolExt, IterStream, ReadyExt, future::TryExtExt, math::Expected, shuffle},
warn,
@@ -310,8 +310,8 @@ pub async fn join_remote(
%shortroomid,
"Initialized room. Parsing join event..."
);
let parsed_join_pdu =
from_incoming_federation(room_id, &event_id, &mut join_event, &room_version_rules)?;
let (parsed_join_pdu, join_event) =
Pdu::from_object_federation(room_id, &event_id, join_event, &room_version_rules)?;
let resp_state = &response.state;
let resp_auth = &response.auth_chain;
@@ -337,12 +337,12 @@ pub async fn join_remote(
})
.inspect_err(|e| debug_error!("Invalid send_join state event: {e:?}"))
.ready_filter_map(Result::ok)
.ready_filter_map(|(event_id, mut value)| {
from_incoming_federation(room_id, &event_id, &mut value, &room_version_rules)
.ready_filter_map(|(event_id, value)| {
Pdu::from_object_federation(room_id, &event_id, value, &room_version_rules)
.inspect_err(|e| {
debug_warn!("Invalid PDU in send_join response: {e:?}: {value:#?}");
debug_warn!("Invalid PDU {event_id:?} in send_join response: {e:?}");
})
.map(move |pdu| (event_id, pdu, value))
.map(move |(pdu, value)| (event_id, pdu, value))
.ok()
})
.fold(HashMap::new(), async |mut state, (event_id, pdu, value)| {
@@ -757,7 +757,7 @@ async fn create_join_event(
.server_keys
.gen_id_hash_and_sign_event(&mut event, room_version_id)?;
check_pdu_format(&event, &room_version_rules.event_format)?;
check_rules(&event, &room_version_rules.event_format)?;
Ok((event, event_id, join_authorized_via_users_server))
}

View File

@@ -308,7 +308,7 @@ async fn knock_room_helper_local(
info!("Parsing knock event");
let parsed_knock_pdu = PduEvent::from_id_val(&event_id, knock_event.clone())
let parsed_knock_pdu = PduEvent::from_object_and_eventid(&event_id, knock_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
info!("Updating membership locally to knock state with provided stripped state events");
@@ -480,7 +480,7 @@ async fn knock_room_helper_remote(
.await;
info!("Parsing knock event");
let parsed_knock_pdu = PduEvent::from_id_val(&event_id, knock_event.clone())
let parsed_knock_pdu = PduEvent::from_object_and_eventid(&event_id, knock_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
info!("Going through send_knock response knock state events");

View File

@@ -16,7 +16,7 @@ use ruma::{
};
use tuwunel_core::{
Err, Result, debug_info, debug_warn, err, implement,
matrix::{PduCount, pdu::check_pdu_format, room_version},
matrix::{PduCount, pdu::check_rules, room_version},
pdu::PduBuilder,
utils::{
self, FutureBoolExt,
@@ -353,7 +353,7 @@ async fn remote_leave(
.server_keys
.gen_id_hash_and_sign_event(&mut event, &room_version_id)?;
check_pdu_format(&event, &room_version_rules.event_format)?;
check_rules(&event, &room_version_rules.event_format)?;
self.services
.federation

View File

@@ -7,11 +7,13 @@ use ruma::{
};
use tuwunel_core::{
Result, debug_warn, err, implement,
matrix::{Event, PduEvent, pdu::MAX_PREV_EVENTS},
matrix::{
Event, PduEvent,
pdu::{MAX_PREV_EVENTS, check_room_id},
},
utils::stream::IterStream,
};
use super::check_room_id;
use crate::rooms::state_res;
#[implement(super::Service)]
@@ -69,7 +71,7 @@ where
continue;
};
check_room_id(room_id, &pdu)?;
check_room_id(&pdu, room_id)?;
let limit = self.services.server.config.max_fetch_prev_events;
if amount > limit {

View File

@@ -5,13 +5,11 @@ use ruma::{
use tuwunel_core::{
Err, Result, debug, debug_info, err, implement,
matrix::{Event, PduEvent, event::TypeExt, room_version},
pdu::{check_pdu_format, format::from_incoming_federation},
ref_at, trace,
utils::{future::TryExtExt, stream::IterStream},
warn,
};
use super::check_room_id;
use crate::rooms::state_res;
#[implement(super::Service)]
@@ -41,7 +39,7 @@ pub(super) async fn handle_outlier_pdu(
// anywhere?: https://matrix.org/docs/spec/rooms/v6#canonical-json
// 2. Check signatures, otherwise drop
// 3. check content hash, redact if doesn't match
let mut pdu_json = match self
let pdu_json = match self
.services
.server_keys
.verify_event(&pdu_json, Some(room_version))
@@ -57,7 +55,8 @@ pub(super) async fn handle_outlier_pdu(
)));
};
let Ok(obj) = ruma::canonical_json::redact(pdu_json, &rules.redaction, None) else {
let Ok(pdu_json) = ruma::canonical_json::redact(pdu_json, &rules.redaction, None)
else {
return Err!(Request(InvalidParam("Redaction failed")));
};
@@ -68,7 +67,7 @@ pub(super) async fn handle_outlier_pdu(
)));
}
obj
pdu_json
},
| Err(e) => {
return Err!(Request(InvalidParam(debug_error!(
@@ -77,15 +76,11 @@ pub(super) async fn handle_outlier_pdu(
},
};
let room_rules = room_version::rules(room_version)?;
check_pdu_format(&pdu_json, &room_rules.event_format)?;
// Now that we have checked the signature and hashes we can make mutations and
// convert to our PduEvent type.
let event = from_incoming_federation(room_id, event_id, &mut pdu_json, &room_rules)?;
check_room_id(room_id, &event)?;
let room_rules = room_version::rules(room_version)?;
let (event, pdu_json) =
PduEvent::from_object_federation(room_id, event_id, pdu_json, &room_rules)?;
if !auth_events_known {
// 4. fetch any missing auth events doing all checks listed here starting at 1.

View File

@@ -19,10 +19,10 @@ use std::{
};
use async_trait::async_trait;
use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId};
use ruma::{EventId, OwnedEventId, OwnedRoomId};
use tuwunel_core::{
Err, Result, implement,
matrix::{Event, PduEvent},
Result, implement,
matrix::PduEvent,
utils::{MutexMap, bytes::pretty, continue_exponential_backoff},
};
@@ -146,16 +146,3 @@ async fn event_exists(&self, event_id: &EventId) -> bool {
async fn event_fetch(&self, event_id: &EventId) -> Result<PduEvent> {
self.services.timeline.get_pdu(event_id).await
}
fn check_room_id<Pdu: Event>(room_id: &RoomId, pdu: &Pdu) -> Result {
if pdu.room_id() != room_id {
return Err!(Request(InvalidParam(error!(
pdu_event_id = ?pdu.event_id(),
pdu_room_id = ?pdu.room_id(),
?room_id,
"Found event from room in room",
))));
}
Ok(())
}

View File

@@ -7,7 +7,7 @@ use ruma::{
};
use tuwunel_core::{
Err, Result, debug, debug_info, err, implement, is_equal_to,
matrix::{Event, EventTypeExt, PduEvent, StateKey, pdu::check_pdu_format, room_version},
matrix::{Event, EventTypeExt, PduEvent, StateKey, pdu::check_rules, room_version},
trace,
utils::stream::{BroadbandExt, ReadyExt},
warn,
@@ -33,7 +33,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu(
origin: &ServerName,
room_id: &RoomId,
incoming_pdu: PduEvent,
val: CanonicalJsonObject,
pdu_json: CanonicalJsonObject,
room_version: &RoomVersionId,
recursion_level: usize,
create_event_id: &EventId,
@@ -64,7 +64,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu(
let room_rules = room_version::rules(room_version)?;
trace!(format = ?room_rules.event_format, "Checking format");
check_pdu_format(&val, &room_rules.event_format)?;
check_rules(&pdu_json, &room_rules.event_format)?;
// 10. Fetch missing state and auth chain events by calling /state_ids at
// backwards extremities doing all the checks in this list starting at 1.
@@ -280,7 +280,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu(
.timeline
.append_incoming_pdu(
&incoming_pdu,
val,
pdu_json,
extremities,
state_ids_compressed,
soft_fail,

View File

@@ -13,7 +13,7 @@ use tuwunel_core::{
Error, Result, err, implement,
matrix::{
event::{Event, StateKey, TypeExt},
pdu::{EventHash, PduBuilder, PduEvent, PrevEvents, check_pdu_format},
pdu::{EventHash, PduBuilder, PduEvent, PrevEvents, check_rules},
room_version,
},
utils::{
@@ -195,7 +195,7 @@ pub async fn create_hash_and_sign_event(
pdu_json.insert("room_id".into(), CanonicalJsonValue::String(pdu.room_id.clone().into()));
}
check_pdu_format(&pdu_json, &version_rules.event_format)?;
check_rules(&pdu_json, &version_rules.event_format)?;
// Generate short event id
let _shorteventid = self