Skip to main content

tuwunel_core/matrix/pdu/
unsigned.rs

1use std::collections::BTreeMap;
2
3use ruma::{
4	MilliSecondsSinceUnixEpoch, OwnedEventId,
5	events::{AnySyncMessageLikeEvent, room::member::MembershipState},
6	serde::Raw,
7};
8use serde::Serialize;
9use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue, to_raw_value};
10
11use super::{Pdu, Unsigned};
12use crate::{Result, err, implement};
13
14#[implement(Pdu)]
15pub fn remove_transaction_id(&mut self) -> Result {
16	use BTreeMap as Map;
17
18	let Some(unsigned) = &self.unsigned else {
19		return Ok(());
20	};
21
22	let mut unsigned: Map<&str, Raw<JsonValue>> = serde_json::from_str(unsigned.json().get())
23		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
24
25	unsigned.remove("transaction_id");
26	self.unsigned = to_raw_value(&unsigned)
27		.map(Into::into)
28		.map(Some)
29		.expect("unsigned is valid");
30
31	Ok(())
32}
33
34#[implement(Pdu)]
35pub fn add_age(&mut self) -> Result {
36	use BTreeMap as Map;
37
38	let mut unsigned: Map<&str, Raw<JsonValue>> = self
39		.unsigned
40		.as_ref()
41		.map(Unsigned::json)
42		.map(RawJsonValue::get)
43		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
44		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
45
46	// deliberately allowing for the possibility of negative age
47	let now: i128 = MilliSecondsSinceUnixEpoch::now().get().into();
48	let then: i128 = self.origin_server_ts.into();
49	let this_age = now.saturating_sub(then);
50
51	unsigned.insert("age", raw_of(&this_age)?);
52	self.unsigned = Some(to_raw_value(&unsigned)?.into());
53
54	Ok(())
55}
56
57/// MSC4115: annotate the served event with the requesting user's room
58/// membership at the time of the event.
59#[implement(Pdu)]
60pub fn add_membership(&mut self, membership: &MembershipState) -> Result {
61	use BTreeMap as Map;
62
63	let mut unsigned: Map<&str, Raw<JsonValue>> = self
64		.unsigned
65		.as_ref()
66		.map(Unsigned::json)
67		.map(RawJsonValue::get)
68		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
69		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
70
71	unsigned.insert("membership", raw_of(membership)?);
72	self.unsigned = Some(to_raw_value(&unsigned)?.into());
73
74	Ok(())
75}
76
77#[implement(Pdu)]
78pub fn add_relation(&mut self, name: &str, pdu: Option<&Pdu>) -> Result {
79	use serde_json::Map;
80
81	let mut unsigned: Map<String, JsonValue> = self
82		.unsigned
83		.as_ref()
84		.map(Unsigned::json)
85		.map(RawJsonValue::get)
86		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
87		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
88
89	let pdu = pdu
90		.map(serde_json::to_value)
91		.transpose()?
92		.unwrap_or_else(|| JsonValue::Object(Map::new()));
93
94	unsigned
95		.entry("m.relations")
96		.or_insert(JsonValue::Object(Map::new()))
97		.as_object_mut()
98		.map(|object| object.insert(name.to_owned(), pdu));
99
100	self.unsigned = Some(to_raw_value(&unsigned)?.into());
101
102	Ok(())
103}
104
105/// MSC3816: overwrite `unsigned.m.relations.m.thread.current_user_participated`
106/// with a per-requester value. No-op when the event carries no thread bundle.
107#[implement(Pdu)]
108pub fn set_thread_participated(&mut self, participated: bool) -> Result {
109	use serde_json::Map;
110
111	let Some(unsigned) = self.unsigned.as_ref() else {
112		return Ok(());
113	};
114
115	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
116		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
117
118	let updated = unsigned
119		.get_mut("m.relations")
120		.and_then(JsonValue::as_object_mut)
121		.and_then(|relations| relations.get_mut("m.thread"))
122		.and_then(JsonValue::as_object_mut)
123		.map(|thread| {
124			thread.insert("current_user_participated".to_owned(), participated.into());
125		})
126		.is_some();
127
128	if updated {
129		self.unsigned = Some(to_raw_value(&unsigned)?.into());
130	}
131
132	Ok(())
133}
134
135/// MSC3925: fold the newest `m.replace` edit into
136/// `unsigned.m.relations.m.replace` as the full replacement event, preserving
137/// an existing bundle such as `m.thread` and creating `unsigned` when absent.
138#[implement(Pdu)]
139pub fn set_replacement_bundle(&mut self, replacement: &Raw<AnySyncMessageLikeEvent>) -> Result {
140	use BTreeMap as Map;
141
142	type Object = Map<String, Raw<JsonValue>>;
143
144	let parse = |raw: &RawJsonValue| -> Result<Object> {
145		serde_json::from_str(raw.get())
146			.map_err(|e| err!(Database("Invalid object in pdu unsigned: {e}")))
147	};
148
149	let mut unsigned: Object = self
150		.unsigned
151		.as_ref()
152		.map(|unsigned| parse(unsigned.json()))
153		.transpose()?
154		.unwrap_or_default();
155
156	let mut relations: Object = unsigned
157		.get("m.relations")
158		.map(|relations| parse(relations.json()))
159		.transpose()?
160		.unwrap_or_default();
161
162	relations.insert("m.replace".to_owned(), replacement.cast_ref().clone());
163	unsigned.insert("m.relations".to_owned(), to_raw_value(&relations)?.into());
164	self.unsigned = Some(to_raw_value(&unsigned)?.into());
165
166	Ok(())
167}
168
169/// MSC2675/MSC3267: fold reference relations into
170/// `unsigned.m.relations.m.reference` as `{ chunk: [{ event_id }, ...] }`,
171/// preserving an existing bundle such as `m.thread` or `m.replace` and creating
172/// `unsigned` when absent.
173#[implement(Pdu)]
174pub fn set_reference_bundle(&mut self, event_ids: &[OwnedEventId]) -> Result {
175	use BTreeMap as Map;
176
177	type Object = Map<String, Raw<JsonValue>>;
178
179	let parse = |raw: &RawJsonValue| -> Result<Object> {
180		serde_json::from_str(raw.get())
181			.map_err(|e| err!(Database("Invalid object in pdu unsigned: {e}")))
182	};
183
184	let mut unsigned: Object = self
185		.unsigned
186		.as_ref()
187		.map(|unsigned| parse(unsigned.json()))
188		.transpose()?
189		.unwrap_or_default();
190
191	let mut relations: Object = unsigned
192		.get("m.relations")
193		.map(|relations| parse(relations.json()))
194		.transpose()?
195		.unwrap_or_default();
196
197	let chunk: Vec<JsonValue> = event_ids
198		.iter()
199		.map(|event_id| serde_json::json!({ "event_id": event_id }))
200		.collect();
201
202	let reference = serde_json::json!({ "chunk": chunk });
203
204	relations.insert("m.reference".to_owned(), to_raw_value(&reference)?.into());
205	unsigned.insert("m.relations".to_owned(), to_raw_value(&relations)?.into());
206	self.unsigned = Some(to_raw_value(&unsigned)?.into());
207
208	Ok(())
209}
210
211#[inline]
212fn raw_of<T: Serialize>(value: &T) -> Result<Raw<JsonValue>> {
213	Ok(Raw::from_raw_value(&to_raw_value(value)?))
214}