Skip to main content

tuwunel_core/matrix/pdu/
unsigned.rs

1use std::collections::BTreeMap;
2
3use ruma::{
4	MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId,
5	events::{AnySyncMessageLikeEvent, room::member::MembershipState},
6	serde::Raw,
7};
8use serde::{Deserialize, Serialize};
9use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue, to_raw_value};
10
11use super::{Pdu, Unsigned};
12use crate::{Result, err, implement, utils::BoolExt};
13
14/// Removes the local transaction ID from unsigned event metadata.
15///
16/// Other unsigned properties are retained and the object is re-encoded. An
17/// event without unsigned data is left unchanged.
18#[implement(Pdu)]
19pub fn remove_transaction_id(&mut self) -> Result {
20	use BTreeMap as Map;
21
22	let Some(unsigned) = &self.unsigned else {
23		return Ok(());
24	};
25
26	let mut unsigned: Map<&str, Raw<JsonValue>> = serde_json::from_str(unsigned.json().get())
27		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
28
29	unsigned.remove("transaction_id");
30	self.unsigned = to_raw_value(&unsigned)
31		.map(Into::into)
32		.map(Some)
33		.expect("unsigned is valid");
34
35	Ok(())
36}
37
38/// State-section serving strips the stored `prev_content`/`prev_sender`
39/// pair, dropping `unsigned` entirely when emptied; timeline serving keeps
40/// the trio.
41#[implement(Pdu)]
42pub fn remove_prev_state(&mut self) -> Result {
43	use BTreeMap as Map;
44
45	let Some(unsigned) = &self.unsigned else {
46		return Ok(());
47	};
48
49	let raw = unsigned.json().get();
50	let prev_keys = raw.contains("\"prev_content\"") || raw.contains("\"prev_sender\"");
51	if !prev_keys && raw != "{}" {
52		return Ok(());
53	}
54
55	let mut unsigned: Map<&str, Raw<JsonValue>> = serde_json::from_str(raw)
56		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
57
58	unsigned.remove("prev_content");
59	unsigned.remove("prev_sender");
60	self.unsigned = unsigned
61		.is_empty()
62		.is_false()
63		.then(|| to_raw_value(&unsigned))
64		.transpose()?
65		.map(Into::into);
66
67	Ok(())
68}
69
70/// Adds the event's current age to unsigned metadata.
71///
72/// Age is the saturating millisecond difference between the current time and
73/// `origin_server_ts`. Future timestamps can therefore produce a negative
74/// value.
75#[implement(Pdu)]
76pub fn add_age(&mut self) -> Result {
77	use BTreeMap as Map;
78
79	let mut unsigned: Map<&str, Raw<JsonValue>> = self
80		.unsigned
81		.as_ref()
82		.map(Unsigned::json)
83		.map(RawJsonValue::get)
84		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
85		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
86
87	// deliberately allowing for the possibility of negative age
88	let now: i128 = MilliSecondsSinceUnixEpoch::now().get().into();
89	let then: i128 = self.origin_server_ts.into();
90	let this_age = now.saturating_sub(then);
91
92	unsigned.insert("age", raw_of(&this_age)?);
93	self.unsigned = Some(to_raw_value(&unsigned)?.into());
94
95	Ok(())
96}
97
98/// MSC4115: annotate the served event with the requesting user's room
99/// membership at the time of the event.
100#[implement(Pdu)]
101pub fn add_membership(&mut self, membership: &MembershipState) -> Result {
102	use BTreeMap as Map;
103
104	let mut unsigned: Map<&str, Raw<JsonValue>> = self
105		.unsigned
106		.as_ref()
107		.map(Unsigned::json)
108		.map(RawJsonValue::get)
109		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
110		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
111
112	unsigned.insert("membership", raw_of(membership)?);
113	self.unsigned = Some(to_raw_value(&unsigned)?.into());
114
115	Ok(())
116}
117
118/// Adds or replaces a named bundled relation in unsigned metadata.
119///
120/// The related PDU is serialized under `unsigned.m.relations`; `None` stores an
121/// empty object for the named relation. Existing unsigned properties are
122/// retained.
123#[implement(Pdu)]
124pub fn add_relation(&mut self, name: &str, pdu: Option<&Pdu>) -> Result {
125	use serde_json::Map;
126
127	let mut unsigned: Map<String, JsonValue> = self
128		.unsigned
129		.as_ref()
130		.map(Unsigned::json)
131		.map(RawJsonValue::get)
132		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
133		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
134
135	let pdu = pdu
136		.map(serde_json::to_value)
137		.transpose()?
138		.unwrap_or_else(|| JsonValue::Object(Map::new()));
139
140	unsigned
141		.entry("m.relations")
142		.or_insert(JsonValue::Object(Map::new()))
143		.as_object_mut()
144		.map(|object| object.insert(name.to_owned(), pdu));
145
146	self.unsigned = Some(to_raw_value(&unsigned)?.into());
147
148	Ok(())
149}
150
151/// MSC3816: overwrite `unsigned.m.relations.m.thread.current_user_participated`
152/// with a per-requester value. No-op when the event carries no thread bundle.
153#[implement(Pdu)]
154pub fn set_thread_participated(&mut self, participated: bool) -> Result {
155	use serde_json::Map;
156
157	let Some(unsigned) = self.unsigned.as_ref() else {
158		return Ok(());
159	};
160
161	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
162		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
163
164	let updated = unsigned
165		.get_mut("m.relations")
166		.and_then(JsonValue::as_object_mut)
167		.and_then(|relations| relations.get_mut("m.thread"))
168		.and_then(JsonValue::as_object_mut)
169		.map(|thread| {
170			thread.insert("current_user_participated".to_owned(), participated.into());
171		})
172		.is_some();
173
174	if updated {
175		self.unsigned = Some(to_raw_value(&unsigned)?.into());
176	}
177
178	Ok(())
179}
180
181/// MSC4025: identify the bundled `m.thread` `latest_event` without parsing
182/// the whole bundle: the `sender` keys the erasure gate, the `event_id` loads
183/// the event on a hit.
184#[implement(Pdu)]
185#[must_use]
186pub fn thread_latest_event(&self) -> Option<(OwnedEventId, OwnedUserId)> {
187	#[derive(Deserialize)]
188	struct Relations {
189		#[serde(rename = "m.thread")]
190		thread: Option<Thread>,
191	}
192
193	#[derive(Deserialize)]
194	struct Thread {
195		latest_event: Option<Identity>,
196	}
197
198	#[derive(Deserialize)]
199	struct Identity {
200		event_id: OwnedEventId,
201		sender: OwnedUserId,
202	}
203
204	let relations: Relations = self
205		.unsigned
206		.as_ref()?
207		.get_field("m.relations")
208		.ok()
209		.flatten()?;
210
211	let identity = relations.thread?.latest_event?;
212
213	Some((identity.event_id, identity.sender))
214}
215
216/// MSC4025: overwrite `unsigned.m.relations.m.thread.latest_event`, serving
217/// the pruned form of an erased sender's thread activity. No-op when the
218/// event carries no thread bundle.
219#[implement(Pdu)]
220pub fn set_thread_latest_event(&mut self, latest: &Raw<AnySyncMessageLikeEvent>) -> Result {
221	use serde_json::Map;
222
223	let Some(unsigned) = self.unsigned.as_ref() else {
224		return Ok(());
225	};
226
227	let latest = serde_json::to_value(latest)?;
228
229	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
230		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
231
232	let updated = unsigned
233		.get_mut("m.relations")
234		.and_then(JsonValue::as_object_mut)
235		.and_then(|relations| relations.get_mut("m.thread"))
236		.and_then(JsonValue::as_object_mut)
237		.map(|thread| {
238			thread.insert("latest_event".to_owned(), latest);
239		})
240		.is_some();
241
242	if updated {
243		self.unsigned = Some(to_raw_value(&unsigned)?.into());
244	}
245
246	Ok(())
247}
248
249/// MSC3856: overwrite `unsigned.m.relations.m.thread.count` with a
250/// per-requester value excluding ignored senders' replies. No-op when the
251/// event carries no thread bundle.
252#[implement(Pdu)]
253pub fn set_thread_count(&mut self, count: usize) -> Result {
254	use serde_json::Map;
255
256	let Some(unsigned) = self.unsigned.as_ref() else {
257		return Ok(());
258	};
259
260	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
261		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
262
263	let updated = unsigned
264		.get_mut("m.relations")
265		.and_then(JsonValue::as_object_mut)
266		.and_then(|relations| relations.get_mut("m.thread"))
267		.and_then(JsonValue::as_object_mut)
268		.map(|thread| {
269			thread.insert("count".to_owned(), count.into());
270		})
271		.is_some();
272
273	if updated {
274		self.unsigned = Some(to_raw_value(&unsigned)?.into());
275	}
276
277	Ok(())
278}
279
280/// MSC3925: fold the newest `m.replace` edit into
281/// `unsigned.m.relations.m.replace` as the full replacement event, preserving
282/// an existing bundle such as `m.thread` and creating `unsigned` when absent.
283#[implement(Pdu)]
284pub fn set_replacement_bundle(&mut self, replacement: &Raw<AnySyncMessageLikeEvent>) -> Result {
285	use BTreeMap as Map;
286
287	type Object = Map<String, Raw<JsonValue>>;
288
289	let parse = |raw: &RawJsonValue| -> Result<Object> {
290		serde_json::from_str(raw.get())
291			.map_err(|e| err!(Database("Invalid object in pdu unsigned: {e}")))
292	};
293
294	let mut unsigned: Object = self
295		.unsigned
296		.as_ref()
297		.map(|unsigned| parse(unsigned.json()))
298		.transpose()?
299		.unwrap_or_default();
300
301	let mut relations: Object = unsigned
302		.get("m.relations")
303		.map(|relations| parse(relations.json()))
304		.transpose()?
305		.unwrap_or_default();
306
307	relations.insert("m.replace".to_owned(), replacement.cast_ref().clone());
308	unsigned.insert("m.relations".to_owned(), to_raw_value(&relations)?.into());
309	self.unsigned = Some(to_raw_value(&unsigned)?.into());
310
311	Ok(())
312}
313
314/// Inverse of `set_replacement_bundle`: excise `m.replace` from
315/// `unsigned.m.relations`, dropping `m.relations` when the excision empties
316/// it and `unsigned` when that leaves nothing.
317#[implement(Pdu)]
318pub fn remove_replacement_bundle(&mut self) -> Result {
319	use BTreeMap as Map;
320
321	type Object = Map<String, Raw<JsonValue>>;
322
323	let Some(unsigned) = &self.unsigned else {
324		return Ok(());
325	};
326
327	if !unsigned.json().get().contains("\"m.replace\"") {
328		return Ok(());
329	}
330
331	let parse = |raw: &RawJsonValue| -> Result<Object> {
332		serde_json::from_str(raw.get())
333			.map_err(|e| err!(SerdeDe("Invalid object in pdu unsigned: {e}")))
334	};
335
336	let mut unsigned: Object = parse(unsigned.json())?;
337
338	let Some(relations) = unsigned.get("m.relations") else {
339		return Ok(());
340	};
341
342	let mut relations: Object = parse(relations.json())?;
343
344	if relations.remove("m.replace").is_none() {
345		return Ok(());
346	}
347
348	match relations.is_empty() {
349		| true => unsigned.remove("m.relations"),
350		| false => unsigned.insert("m.relations".to_owned(), to_raw_value(&relations)?.into()),
351	};
352
353	self.unsigned = unsigned
354		.is_empty()
355		.is_false()
356		.then(|| to_raw_value(&unsigned))
357		.transpose()?
358		.map(Into::into);
359
360	Ok(())
361}
362
363/// MSC2675/MSC3267: fold reference relations into
364/// `unsigned.m.relations.m.reference` as `{ chunk: [{ event_id }, ...] }`,
365/// preserving an existing bundle such as `m.thread` or `m.replace` and creating
366/// `unsigned` when absent.
367#[implement(Pdu)]
368pub fn set_reference_bundle(&mut self, event_ids: &[OwnedEventId]) -> Result {
369	use BTreeMap as Map;
370
371	type Object = Map<String, Raw<JsonValue>>;
372
373	let parse = |raw: &RawJsonValue| -> Result<Object> {
374		serde_json::from_str(raw.get())
375			.map_err(|e| err!(Database("Invalid object in pdu unsigned: {e}")))
376	};
377
378	let mut unsigned: Object = self
379		.unsigned
380		.as_ref()
381		.map(|unsigned| parse(unsigned.json()))
382		.transpose()?
383		.unwrap_or_default();
384
385	let mut relations: Object = unsigned
386		.get("m.relations")
387		.map(|relations| parse(relations.json()))
388		.transpose()?
389		.unwrap_or_default();
390
391	let chunk: Vec<JsonValue> = event_ids
392		.iter()
393		.map(|event_id| serde_json::json!({ "event_id": event_id }))
394		.collect();
395
396	let reference = serde_json::json!({ "chunk": chunk });
397
398	relations.insert("m.reference".to_owned(), to_raw_value(&reference)?.into());
399	unsigned.insert("m.relations".to_owned(), to_raw_value(&relations)?.into());
400	self.unsigned = Some(to_raw_value(&unsigned)?.into());
401
402	Ok(())
403}
404
405#[inline]
406fn raw_of<T: Serialize>(value: &T) -> Result<Raw<JsonValue>> {
407	Ok(Raw::from_raw_value(&to_raw_value(value)?))
408}