Skip to main content

tuwunel_matrix/pdu/
format.rs

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