Skip to main content

tuwunel_core/matrix/pdu/
format.rs

1pub(super) mod check;
2
3use ruma::{
4	CanonicalJsonObject, CanonicalJsonValue, EventId, RoomId, RoomVersionId,
5	room_version_rules::{EventsReferenceFormatVersion, RoomVersionRules},
6};
7
8use crate::{extract_variant, is_equal_to, matrix::room_version};
9
10/// Converts a stored PDU object to its federation wire representation.
11///
12/// Local transaction metadata is removed and room-version rules select the
13/// fields and event-reference shape sent to peers. When rules are unavailable,
14/// the event ID is removed without rewriting event references.
15#[must_use]
16pub fn into_outgoing_federation(
17	mut pdu_json: CanonicalJsonObject,
18	room_version: &RoomVersionId,
19) -> CanonicalJsonObject {
20	if let Some(unsigned) = pdu_json
21		.get_mut("unsigned")
22		.and_then(|val| val.as_object_mut())
23	{
24		unsigned.remove("transaction_id");
25	}
26
27	let Ok(room_rules) = room_version::rules(room_version) else {
28		pdu_json.remove("event_id");
29		return pdu_json;
30	};
31
32	if !room_rules.event_format.require_event_id {
33		pdu_json.remove("event_id");
34	}
35
36	if !room_rules
37		.event_format
38		.require_room_create_room_id
39		&& pdu_json
40			.get("type")
41			.and_then(CanonicalJsonValue::as_str)
42			.is_some_and(is_equal_to!("m.room.create"))
43	{
44		pdu_json.remove("room_id");
45	}
46
47	if matches!(room_rules.events_reference_format, EventsReferenceFormatVersion::V1) {
48		if let Some(value) = pdu_json.get_mut("auth_events") {
49			mutate_outgoing_reference_format(value);
50		}
51		if let Some(value) = pdu_json.get_mut("prev_events") {
52			mutate_outgoing_reference_format(value);
53		}
54	}
55
56	pdu_json
57}
58
59fn mutate_outgoing_reference_format(value: &mut CanonicalJsonValue) {
60	value
61		.as_array_mut()
62		.into_iter()
63		.flatten()
64		.for_each(|value| {
65			if let Some(event_id) = value.as_str().map(ToOwned::to_owned) {
66				*value = CanonicalJsonValue::Array(vec![
67					CanonicalJsonValue::String(event_id),
68					CanonicalJsonValue::Object([(Default::default(), "".into())].into()),
69				]);
70			}
71		});
72}
73
74/// Normalizes a federation PDU object into the stored representation.
75///
76/// Legacy event-reference tuples are collapsed and omitted room or event IDs
77/// are restored from trusted request context. The supplied rules must match the
78/// event's room version.
79#[must_use]
80pub fn from_incoming_federation(
81	room_id: &RoomId,
82	event_id: &EventId,
83	mut pdu_json: CanonicalJsonObject,
84	room_rules: &RoomVersionRules,
85) -> CanonicalJsonObject {
86	if matches!(room_rules.events_reference_format, EventsReferenceFormatVersion::V1) {
87		if let Some(value) = pdu_json.get_mut("auth_events") {
88			mutate_incoming_reference_format(value);
89		}
90		if let Some(value) = pdu_json.get_mut("prev_events") {
91			mutate_incoming_reference_format(value);
92		}
93	}
94
95	if !room_rules
96		.event_format
97		.require_room_create_room_id
98		&& pdu_json["type"] == "m.room.create"
99	{
100		pdu_json.insert("room_id".into(), CanonicalJsonValue::String(room_id.as_str().into()));
101	}
102
103	if !room_rules.event_format.require_event_id {
104		pdu_json.insert("event_id".into(), CanonicalJsonValue::String(event_id.into()));
105	}
106
107	pdu_json
108}
109
110fn mutate_incoming_reference_format(value: &mut CanonicalJsonValue) {
111	value
112		.as_array_mut()
113		.into_iter()
114		.flat_map(|vec| vec.iter_mut())
115		.for_each(|value| {
116			let event_id = value
117				.as_array()
118				.into_iter()
119				.find_map(|vec| vec.first())
120				.and_then(|val| extract_variant!(val, CanonicalJsonValue::String))
121				.cloned()
122				.unwrap_or_default();
123
124			*value = CanonicalJsonValue::String(event_id);
125		});
126}