Skip to main content

tuwunel_matrix/pdu/
unsigned.rs

1use std::{
2	borrow::Cow,
3	collections::{BTreeMap, btree_map::Entry},
4	fmt::{Formatter, Result as FmtResult},
5	iter::from_fn,
6};
7
8use ruma::{
9	MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, UserId,
10	events::{AnySyncMessageLikeEvent, room::member::MembershipState},
11	serde::Raw,
12};
13use serde::{
14	Deserialize, Deserializer, Serialize, Serializer,
15	de::{DeserializeSeed, Error as DeError, IgnoredAny, MapAccess, Visitor},
16	ser::SerializeMap,
17};
18use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue, to_raw_value};
19use tuwunel_core::{Result, err, implement, result::LogErr, utils::BoolExt, warn};
20
21use super::{Pdu, Unsigned};
22
23/// The unstable MSC4140 key naming the delayed event an event was sent from.
24///
25/// It is stored in the sent event's unsigned data, and only the event's sender
26/// is served it.
27pub const DELAY_ID: &str = "org.matrix.msc4140.delay_id";
28
29/// Unsigned keys served to the event's own sender and to nobody else.
30///
31/// Every serving path strips these unless the requester sent the event, and
32/// federation strips them always. A redaction keeps them for the sender.
33pub const PRIVATE: [&str; 2] = ["transaction_id", DELAY_ID];
34
35type BorrowedObject<'a> = BTreeMap<Cow<'a, str>, &'a RawJsonValue>;
36type JsonEntry<'a> = (JsonString<'a>, &'a RawJsonValue);
37
38struct RelationBundle<'a> {
39	unsigned: BorrowedObject<'a>,
40	relations: BorrowedObject<'a>,
41}
42
43struct ThreadBundleFields<'a> {
44	unsigned: BorrowedObject<'a>,
45	relations: BorrowedObject<'a>,
46	thread: BorrowedObject<'a>,
47	latest_event: BorrowedObject<'a>,
48	latest_event_unsigned: BorrowedObject<'a>,
49}
50
51struct RawObjectPatch<'a, 'raw, T> {
52	object: &'a BorrowedObject<'raw>,
53	field: &'static str,
54	value: T,
55}
56
57struct RawObjectRemove<'a, 'raw> {
58	object: &'a BorrowedObject<'raw>,
59	field: &'a str,
60}
61
62struct BorrowedField<'a>(&'a str);
63
64struct UniqueObject;
65
66struct JsonString<'a>(Cow<'a, str>);
67
68struct JsonStringVisitor;
69
70#[derive(Serialize)]
71struct ReferenceBundle<'a> {
72	chunk: ReferenceChunk<'a>,
73}
74
75struct ReferenceChunk<'a>(&'a [OwnedEventId]);
76
77#[derive(Serialize)]
78struct ReferenceEvent<'a> {
79	event_id: &'a OwnedEventId,
80}
81
82#[derive(Deserialize)]
83struct Identity {
84	event_id: OwnedEventId,
85	sender: OwnedUserId,
86}
87
88/// Returns the event without its private fields unless served to its sender.
89///
90/// The by-value form of [`Pdu::remove_private_unless_sender`], for chains that
91/// own the event.
92#[implement(Pdu)]
93#[must_use]
94pub fn without_private_unless_sender(mut self, user_id: Option<&UserId>) -> Self {
95	self.remove_private_unless_sender(user_id);
96
97	self
98}
99
100/// Readies the event for serving to one requester.
101///
102/// Removes the private fields unless the requester sent the event, and adds the
103/// event's current age. A failed age is logged and costs one optional field.
104#[implement(Pdu)]
105#[inline]
106#[must_use]
107pub fn served_to(mut self, user_id: Option<&UserId>) -> Self {
108	self.remove_private_unless_sender(user_id);
109	self.add_age().log_err().ok();
110
111	self
112}
113
114/// Removes the private unsigned fields unless the event is served to its sender.
115///
116/// The transaction ID is the sending client's own idempotency value, which it
117/// matches its echo against, and the delay ID names the delayed event the
118/// sender scheduled, so the sender keeps both and nobody else sees them. A
119/// `None` requester is nobody in particular and is treated as somebody else.
120#[implement(Pdu)]
121pub fn remove_private_unless_sender(&mut self, user_id: Option<&UserId>) {
122	if user_id.is_none_or(|user_id| self.sender != *user_id) {
123		self.remove_private();
124	}
125}
126
127/// Removes the fields private to the sender from unsigned event metadata.
128///
129/// Other unsigned properties are retained and the object is re-encoded. An
130/// event without unsigned data, or without the keys, is left unchanged. Unsigned
131/// data that holds a key but cannot be parsed is logged and dropped whole, so
132/// the event is still served.
133#[implement(Pdu)]
134pub fn remove_private(&mut self) {
135	let Some(unsigned) = &self.unsigned else {
136		return;
137	};
138
139	let raw = unsigned.json().get();
140
141	if !PRIVATE.iter().any(|key| raw.contains(key)) {
142		return;
143	}
144
145	self.unsigned = without_private(raw)
146		.inspect_err(|error| {
147			// Only a corrupt or old remote row has this shape; repair it with `query raw put`.
148			// Stock release builds strip `debug_warn!`, and nothing else would show it.
149			warn!(event_id = %self.event_id, %error, "Omitting unreadable unsigned data.");
150		})
151		.ok();
152}
153
154fn without_private(raw: &str) -> Result<Unsigned> {
155	let mut unsigned: BTreeMap<&str, &RawJsonValue> = serde_json::from_str(raw)?;
156
157	unsigned.retain(|key, _| !PRIVATE.contains(key));
158
159	raw_as(&unsigned)
160}
161
162/// State-section serving strips the stored `prev_content`/`prev_sender`
163/// pair, dropping `unsigned` entirely when emptied; timeline serving keeps
164/// the trio.
165#[implement(Pdu)]
166pub fn remove_prev_state(&mut self) -> Result {
167	use BTreeMap as Map;
168
169	let Some(unsigned) = &self.unsigned else {
170		return Ok(());
171	};
172
173	let raw = unsigned.json().get();
174	let prev_keys = raw.contains("\"prev_content\"") || raw.contains("\"prev_sender\"");
175	if !prev_keys && raw != "{}" {
176		return Ok(());
177	}
178
179	let mut unsigned: Map<&str, Raw<JsonValue>> = serde_json::from_str(raw)
180		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
181
182	unsigned.remove("prev_content");
183	unsigned.remove("prev_sender");
184	self.unsigned = unsigned
185		.is_empty()
186		.is_false()
187		.then(|| to_raw_value(&unsigned))
188		.transpose()?
189		.map(Into::into);
190
191	Ok(())
192}
193
194/// Adds the event's current age to unsigned metadata.
195///
196/// Age is the saturating millisecond difference between the current time and
197/// `origin_server_ts`. Future timestamps can therefore produce a negative
198/// value.
199#[implement(Pdu)]
200pub fn add_age(&mut self) -> Result {
201	use BTreeMap as Map;
202
203	let mut unsigned: Map<&str, Raw<JsonValue>> = self
204		.unsigned
205		.as_ref()
206		.map(Unsigned::json)
207		.map(RawJsonValue::get)
208		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
209		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
210
211	// deliberately allowing for the possibility of negative age
212	let now: i128 = MilliSecondsSinceUnixEpoch::now().get().into();
213	let then: i128 = self.origin_server_ts.into();
214	let this_age = now.saturating_sub(then);
215
216	unsigned.insert("age", raw_of(&this_age)?);
217	self.unsigned = Some(to_raw_value(&unsigned)?.into());
218
219	Ok(())
220}
221
222/// MSC4115: annotate the served event with the requesting user's room
223/// membership at the time of the event.
224#[implement(Pdu)]
225pub fn add_membership(&mut self, membership: &MembershipState) -> Result {
226	use BTreeMap as Map;
227
228	let mut unsigned: Map<&str, Raw<JsonValue>> = self
229		.unsigned
230		.as_ref()
231		.map(Unsigned::json)
232		.map(RawJsonValue::get)
233		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
234		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
235
236	unsigned.insert("membership", raw_of(membership)?);
237	self.unsigned = Some(to_raw_value(&unsigned)?.into());
238
239	Ok(())
240}
241
242/// Adds or replaces a named bundled relation in unsigned metadata.
243///
244/// The related PDU is serialized under `unsigned.m.relations`; `None` stores an
245/// empty object for the named relation. Existing unsigned properties are
246/// retained.
247#[implement(Pdu)]
248pub fn add_relation(&mut self, name: &str, pdu: Option<&Pdu>) -> Result {
249	use serde_json::Map;
250
251	let mut unsigned: Map<String, JsonValue> = self
252		.unsigned
253		.as_ref()
254		.map(Unsigned::json)
255		.map(RawJsonValue::get)
256		.map_or_else(|| Ok(Map::new()), serde_json::from_str)
257		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
258
259	let pdu = pdu
260		.map(serde_json::to_value)
261		.transpose()?
262		.unwrap_or_else(|| JsonValue::Object(Map::new()));
263
264	unsigned
265		.entry("m.relations")
266		.or_insert(JsonValue::Object(Map::new()))
267		.as_object_mut()
268		.map(|object| object.insert(name.to_owned(), pdu));
269
270	self.unsigned = Some(to_raw_value(&unsigned)?.into());
271
272	Ok(())
273}
274
275/// MSC3816: overwrite `unsigned.m.relations.m.thread.current_user_participated`
276/// with a per-requester value. No-op when the event carries no thread bundle.
277#[implement(Pdu)]
278pub fn set_thread_participated(&mut self, participated: bool) -> Result {
279	use serde_json::Map;
280
281	let Some(unsigned) = self.unsigned.as_ref() else {
282		return Ok(());
283	};
284
285	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
286		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
287
288	let updated = unsigned
289		.get_mut("m.relations")
290		.and_then(JsonValue::as_object_mut)
291		.and_then(|relations| relations.get_mut("m.thread"))
292		.and_then(JsonValue::as_object_mut)
293		.map(|thread| {
294			thread.insert("current_user_participated".to_owned(), participated.into());
295		})
296		.is_some();
297
298	if updated {
299		self.unsigned = Some(to_raw_value(&unsigned)?.into());
300	}
301
302	Ok(())
303}
304
305/// Identifies the sender and event ID of a bundled thread preview.
306///
307/// MSC4025 uses the sender for the erasure check and the event ID to load the
308/// event. The thread writer derives both fields from one validated event.
309#[implement(Pdu)]
310#[inline]
311pub fn thread_latest_event(&self) -> Result<Option<(OwnedEventId, OwnedUserId)>> {
312	self.unsigned
313		.as_ref()
314		.map(Unsigned::json)
315		.map(thread_latest)
316		.transpose()?
317		.flatten()
318		.map(thread_identity)
319		.transpose()
320}
321
322/// Whether `unsigned.m.relations` contains an `m.thread` bundle.
323///
324/// This decodes object keys, so escaped spellings cannot bypass read-time
325/// privacy handling.
326#[implement(Pdu)]
327pub fn has_thread_bundle(&self) -> Result<bool> {
328	self.unsigned
329		.as_ref()
330		.map(Unsigned::json)
331		.map(thread_bundle)
332		.transpose()
333		.map(|thread| thread.flatten().is_some())
334}
335
336/// MSC4025: overwrite `unsigned.m.relations.m.thread.latest_event`, serving
337/// the pruned form of an erased sender's thread activity. No-op when the
338/// event carries no thread bundle.
339#[implement(Pdu)]
340pub fn set_thread_latest_event(&mut self, latest: &Raw<AnySyncMessageLikeEvent>) -> Result {
341	use serde_json::Map;
342
343	let Some(unsigned) = self.unsigned.as_ref() else {
344		return Ok(());
345	};
346
347	let latest = serde_json::to_value(latest)?;
348
349	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
350		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
351
352	let updated = unsigned
353		.get_mut("m.relations")
354		.and_then(JsonValue::as_object_mut)
355		.and_then(|relations| relations.get_mut("m.thread"))
356		.and_then(JsonValue::as_object_mut)
357		.map(|thread| {
358			thread.insert("latest_event".to_owned(), latest);
359		})
360		.is_some();
361
362	if updated {
363		self.unsigned = Some(to_raw_value(&unsigned)?.into());
364	}
365
366	Ok(())
367}
368
369/// Removes a thread preview transaction ID unless served to its own sender.
370///
371/// Stored thread bundles predate write-side sanitization, so this rewrites the
372/// nested event at serve time while retaining its other unsigned properties.
373/// A missing or invalid sender returns an error so callers can fail closed.
374#[implement(Pdu)]
375pub fn remove_thread_latest_transaction_id_unless_sender(&mut self, user_id: &UserId) -> Result {
376	if let Some(unsigned) = self
377		.unsigned
378		.as_ref()
379		.map(Unsigned::json)
380		.map(|raw| thread_without_transaction_id(raw, user_id))
381		.transpose()?
382		.flatten()
383	{
384		self.unsigned = Some(unsigned);
385	}
386
387	Ok(())
388}
389
390fn thread_without_transaction_id(
391	raw: &RawJsonValue,
392	user_id: &UserId,
393) -> Result<Option<Unsigned>> {
394	thread_transaction_sender(raw)?
395		.map(|sender| serde_json::from_str(sender.get()))
396		.transpose()
397		.map_err(|error| err!(Database("Invalid sender in thread latest event: {error}")))?
398		.filter(|JsonString(sender)| sender != user_id.as_str())
399		.map(|JsonString(sender)| {
400			UserId::parse(sender.as_ref())
401				.map_err(|error| err!(Database("Invalid sender in thread latest event: {error}")))
402				.and_then(|_| ThreadBundleFields::parse(raw)?.without_transaction_id())
403		})
404		.transpose()
405}
406
407fn thread_transaction_sender(raw: &RawJsonValue) -> Result<Option<&RawJsonValue>> {
408	thread_latest(raw)?
409		.map(transaction_sender)
410		.transpose()
411		.map(Option::flatten)
412}
413
414fn transaction_sender(latest_event: &RawJsonValue) -> Result<Option<&RawJsonValue>> {
415	let sender = || {
416		raw_field(latest_event, "sender", "thread latest event")?.ok_or_else(|| {
417			err!(Database("Thread latest event with transaction ID has no sender"))
418		})
419	};
420
421	raw_field(latest_event, "unsigned", "thread latest event")?
422		.map(|unsigned| raw_field(unsigned, "transaction_id", "thread latest event unsigned"))
423		.transpose()?
424		.flatten()
425		.map(|_| sender())
426		.transpose()
427}
428
429fn thread_latest(raw: &RawJsonValue) -> Result<Option<&RawJsonValue>> {
430	thread_bundle(raw)?
431		.map(|thread| raw_field(thread, "latest_event", "thread bundle"))
432		.transpose()
433		.map(Option::flatten)
434}
435
436fn thread_bundle(raw: &RawJsonValue) -> Result<Option<&RawJsonValue>> {
437	raw_field(raw, "m.relations", "unsigned")?
438		.map(|relations| raw_field(relations, "m.thread", "bundled relations"))
439		.transpose()
440		.map(Option::flatten)
441}
442
443fn thread_identity(raw: &RawJsonValue) -> Result<(OwnedEventId, OwnedUserId)> {
444	serde_json::from_str(raw.get())
445		.map(|Identity { event_id, sender }| (event_id, sender))
446		.map_err(|error| err!(Database("Invalid thread latest event in PDU event: {error}")))
447}
448
449/// Excise `m.thread` from `unsigned.m.relations`, retaining unrelated data.
450///
451/// Empty relation and unsigned objects are removed with the bundle.
452#[implement(Pdu)]
453pub fn remove_thread_bundle(&mut self) -> Result {
454	remove_relation_bundle(self, "m.thread")
455		.or_else(|_| remove_relation_bundle_canonical(self, "m.thread"))
456}
457
458/// Canonicalize malformed or ambiguous relation objects while removing only
459/// the requested relation. This allocation is reserved for the error path of
460/// the borrowed surgical rewrite.
461fn remove_relation_bundle_canonical(pdu: &mut Pdu, relation_type: &str) -> Result {
462	let Some(raw) = pdu.unsigned.as_ref() else {
463		return Ok(());
464	};
465
466	let mut unsigned: JsonValue = serde_json::from_str(raw.json().get())?;
467	let unsigned = unsigned
468		.as_object_mut()
469		.ok_or_else(|| err!(Database("Invalid unsigned object in PDU event")))?;
470
471	let remove_relations = match unsigned.get_mut("m.relations") {
472		| None => return Ok(()),
473		| Some(JsonValue::Object(relations)) => {
474			relations.remove(relation_type);
475			relations.is_empty()
476		},
477		| Some(_) => true,
478	};
479
480	if remove_relations {
481		unsigned.remove("m.relations");
482	}
483
484	pdu.unsigned = if unsigned.is_empty() {
485		None
486	} else {
487		Some(raw_as(unsigned)?)
488	};
489
490	Ok(())
491}
492
493fn remove_relation_bundle(pdu: &mut Pdu, relation_type: &str) -> Result {
494	if let Some(unsigned) = pdu
495		.unsigned
496		.as_ref()
497		.map(Unsigned::json)
498		.map(|raw| RelationBundle::parse(raw, relation_type))
499		.transpose()?
500		.flatten()
501		.map(|bundle| bundle.without(relation_type))
502		.transpose()?
503	{
504		pdu.unsigned = unsigned;
505	}
506
507	Ok(())
508}
509
510#[implement(RelationBundle, generics = "<'a>", params = "<'a>")]
511fn parse(raw: &'a RawJsonValue, relation_type: &str) -> Result<Option<Self>> {
512	let unsigned = raw_object(raw, "unsigned")?;
513
514	unsigned
515		.get("m.relations")
516		.copied()
517		.map(|relations| raw_object(relations, "bundled relations"))
518		.transpose()
519		.map(|relations| {
520			relations
521				.filter(|relations| relations.contains_key(relation_type))
522				.map(|relations| Self { unsigned, relations })
523		})
524}
525
526#[implement(RelationBundle, generics = "<'a>", params = "<'a>")]
527fn without(&self, relation_type: &str) -> Result<Option<Unsigned>> {
528	match self.relations.len() {
529		| 1 => self
530			.unsigned
531			.len()
532			.ne(&1)
533			.then(|| raw_as(&RawObjectRemove::new(&self.unsigned, "m.relations")))
534			.transpose(),
535		| _ => {
536			let relations = RawObjectRemove::new(&self.relations, relation_type);
537			let unsigned = RawObjectPatch::new(&self.unsigned, "m.relations", relations);
538
539			raw_as(&unsigned).map(Some)
540		},
541	}
542}
543
544/// MSC3856: overwrite `unsigned.m.relations.m.thread.count` with a
545/// per-requester value excluding ignored senders' replies. No-op when the
546/// event carries no thread bundle.
547#[implement(Pdu)]
548pub fn set_thread_count(&mut self, count: usize) -> Result {
549	use serde_json::Map;
550
551	let Some(unsigned) = self.unsigned.as_ref() else {
552		return Ok(());
553	};
554
555	let mut unsigned: Map<String, JsonValue> = serde_json::from_str(unsigned.json().get())
556		.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
557
558	let updated = unsigned
559		.get_mut("m.relations")
560		.and_then(JsonValue::as_object_mut)
561		.and_then(|relations| relations.get_mut("m.thread"))
562		.and_then(JsonValue::as_object_mut)
563		.map(|thread| {
564			thread.insert("count".to_owned(), count.into());
565		})
566		.is_some();
567
568	if updated {
569		self.unsigned = Some(to_raw_value(&unsigned)?.into());
570	}
571
572	Ok(())
573}
574
575/// MSC3925: fold the newest `m.replace` edit into
576/// `unsigned.m.relations.m.replace` as the full replacement event, preserving
577/// an existing bundle such as `m.thread` and creating `unsigned` when absent.
578#[implement(Pdu)]
579pub fn set_replacement_bundle(&mut self, replacement: &Raw<AnySyncMessageLikeEvent>) -> Result {
580	use BTreeMap as Map;
581
582	type Object = Map<String, Raw<JsonValue>>;
583
584	let parse = |raw: &RawJsonValue| -> Result<Object> {
585		serde_json::from_str(raw.get())
586			.map_err(|e| err!(Database("Invalid object in pdu unsigned: {e}")))
587	};
588
589	let mut unsigned: Object = self
590		.unsigned
591		.as_ref()
592		.map(|unsigned| parse(unsigned.json()))
593		.transpose()?
594		.unwrap_or_default();
595
596	let mut relations: Object = unsigned
597		.get("m.relations")
598		.map(|relations| parse(relations.json()))
599		.transpose()?
600		.unwrap_or_default();
601
602	relations.insert("m.replace".to_owned(), replacement.cast_ref().clone());
603	unsigned.insert("m.relations".to_owned(), to_raw_value(&relations)?.into());
604	self.unsigned = Some(to_raw_value(&unsigned)?.into());
605
606	Ok(())
607}
608
609/// Inverse of `set_replacement_bundle`: excise `m.replace` from
610/// `unsigned.m.relations`, dropping `m.relations` when the excision empties
611/// it and `unsigned` when that leaves nothing.
612#[implement(Pdu)]
613pub fn remove_replacement_bundle(&mut self) -> Result {
614	remove_relation_bundle(self, "m.replace")
615}
616
617/// MSC2675/MSC3267: fold reference relations into
618/// `unsigned.m.relations.m.reference` as `{ chunk: [{ event_id }, ...] }`,
619/// preserving an existing bundle such as `m.thread` or `m.replace` and creating
620/// `unsigned` when absent.
621#[implement(Pdu)]
622pub fn set_reference_bundle(&mut self, event_ids: &[OwnedEventId]) -> Result {
623	let unsigned = self
624		.unsigned
625		.as_ref()
626		.map(|unsigned| raw_object(unsigned.json(), "unsigned"))
627		.transpose()?
628		.unwrap_or_default();
629
630	let relations = unsigned
631		.get("m.relations")
632		.copied()
633		.map(|relations| raw_object(relations, "bundled relations"))
634		.transpose()?
635		.unwrap_or_default();
636
637	let reference = ReferenceBundle { chunk: ReferenceChunk(event_ids) };
638	let relations = RawObjectPatch::new(&relations, "m.reference", reference);
639	let unsigned = RawObjectPatch::new(&unsigned, "m.relations", relations);
640	self.unsigned = Some(raw_as(&unsigned)?);
641
642	Ok(())
643}
644
645impl<'de> Deserialize<'de> for JsonString<'de> {
646	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
647	where
648		D: Deserializer<'de>,
649	{
650		deserializer.deserialize_str(JsonStringVisitor)
651	}
652}
653
654impl<'de> Visitor<'de> for JsonStringVisitor {
655	type Value = JsonString<'de>;
656
657	fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
658		formatter.write_str("a JSON string")
659	}
660
661	fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E> {
662		Ok(JsonString(Cow::Borrowed(value)))
663	}
664
665	fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
666	where
667		E: DeError,
668	{
669		Ok(JsonString(Cow::Owned(value.to_owned())))
670	}
671
672	fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
673		Ok(JsonString(Cow::Owned(value)))
674	}
675}
676
677impl<'de> DeserializeSeed<'de> for BorrowedField<'_> {
678	type Value = Option<&'de RawJsonValue>;
679
680	fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
681	where
682		D: Deserializer<'de>,
683	{
684		deserializer.deserialize_map(self)
685	}
686}
687
688impl<'de> Visitor<'de> for BorrowedField<'_> {
689	type Value = Option<&'de RawJsonValue>;
690
691	fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
692		write!(formatter, "an object containing {}", self.0)
693	}
694
695	fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
696	where
697		A: MapAccess<'de>,
698	{
699		let mut value = None; // MapAccess key/value reads share the cursor.
700
701		while let Some(JsonString(field)) = map.next_key()? {
702			value = match field.as_ref() {
703				| field if field != self.0 => map.next_value().map(|_: IgnoredAny| value),
704				| _ if value.is_some() =>
705					Err(A::Error::custom(format_args!("duplicate field `{field}`"))),
706				| _ => map.next_value().map(Some),
707			}?;
708		}
709
710		Ok(value)
711	}
712}
713
714impl<'de> DeserializeSeed<'de> for UniqueObject {
715	type Value = BorrowedObject<'de>;
716
717	fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
718	where
719		D: Deserializer<'de>,
720	{
721		deserializer.deserialize_map(self)
722	}
723}
724
725impl<'de> Visitor<'de> for UniqueObject {
726	type Value = BorrowedObject<'de>;
727
728	fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
729		formatter.write_str("an object without duplicate fields")
730	}
731
732	fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
733	where
734		A: MapAccess<'de>,
735	{
736		from_fn(|| map.next_entry().transpose()).try_fold(
737			BorrowedObject::new(),
738			|mut object, entry: Result<JsonEntry<'de>, A::Error>| {
739				let (JsonString(field), value) = entry?;
740
741				match object.entry(field) {
742					| Entry::Occupied(entry) =>
743						Err(A::Error::custom(format_args!("duplicate field `{}`", entry.key()))),
744					| Entry::Vacant(entry) => {
745						entry.insert(value);
746
747						Ok(object)
748					},
749				}
750			},
751		)
752	}
753}
754
755#[implement(ThreadBundleFields, generics = "<'a>", params = "<'a>")]
756fn parse(raw: &'a RawJsonValue) -> Result<Self> {
757	let child = |object: &BorrowedObject<'a>, field, name| {
758		object
759			.get(field)
760			.ok_or_else(|| {
761				err!(Database("Thread transaction probe disagreed with bundle parser"))
762			})
763			.and_then(|raw| raw_object(raw, name))
764	};
765
766	let unsigned = raw_object(raw, "unsigned")?;
767	let relations = child(&unsigned, "m.relations", "bundled relations")?;
768	let thread = child(&relations, "m.thread", "thread bundle")?;
769	let latest_event = child(&thread, "latest_event", "thread latest event")?;
770	let latest_event_unsigned = child(&latest_event, "unsigned", "thread latest event unsigned")?;
771
772	Ok(Self {
773		unsigned,
774		relations,
775		thread,
776		latest_event,
777		latest_event_unsigned,
778	})
779}
780
781#[implement(ThreadBundleFields, generics = "<'a>", params = "<'a>")]
782fn without_transaction_id<U, const N: usize>(&self) -> Result<Raw<U, N>> {
783	let latest_event_unsigned =
784		RawObjectRemove::new(&self.latest_event_unsigned, "transaction_id");
785
786	let latest_event = RawObjectPatch::new(&self.latest_event, "unsigned", latest_event_unsigned);
787	let thread = RawObjectPatch::new(&self.thread, "latest_event", latest_event);
788	let relations = RawObjectPatch::new(&self.relations, "m.thread", thread);
789	let unsigned = RawObjectPatch::new(&self.unsigned, "m.relations", relations);
790
791	raw_as(&unsigned)
792}
793
794impl<'a, 'raw, T> RawObjectPatch<'a, 'raw, T> {
795	fn new(object: &'a BorrowedObject<'raw>, field: &'static str, value: T) -> Self {
796		Self { object, field, value }
797	}
798}
799
800impl<T: Serialize> Serialize for RawObjectPatch<'_, '_, T> {
801	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
802	where
803		S: Serializer,
804	{
805		let present = self.object.contains_key(self.field);
806		let len = self
807			.object
808			.len()
809			.saturating_add(usize::from(!present));
810
811		let map = self.object.iter().try_fold(
812			serializer.serialize_map(Some(len))?,
813			|mut map, (field, value)| {
814				match field.as_ref() == self.field {
815					| true => map.serialize_entry(field, &self.value),
816					| false => map.serialize_entry(field, value),
817				}?;
818
819				Ok(map)
820			},
821		)?;
822
823		present
824			.is_false()
825			.then_some(self.field)
826			.into_iter()
827			.try_fold(map, |mut map, field| {
828				map.serialize_entry(field, &self.value)?;
829
830				Ok(map)
831			})
832			.and_then(SerializeMap::end)
833	}
834}
835
836impl<'a, 'raw> RawObjectRemove<'a, 'raw> {
837	fn new(object: &'a BorrowedObject<'raw>, field: &'a str) -> Self { Self { object, field } }
838}
839
840impl Serialize for RawObjectRemove<'_, '_> {
841	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
842	where
843		S: Serializer,
844	{
845		self.object
846			.iter()
847			.filter(|(field, _)| field.as_ref() != self.field)
848			.try_fold(
849				serializer.serialize_map(Some(self.object.len().saturating_sub(1)))?,
850				|mut map, (field, value)| {
851					map.serialize_entry(field, value)?;
852
853					Ok(map)
854				},
855			)
856			.and_then(SerializeMap::end)
857	}
858}
859
860impl Serialize for ReferenceChunk<'_> {
861	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
862	where
863		S: Serializer,
864	{
865		serializer.collect_seq(
866			self.0
867				.iter()
868				.map(|event_id| ReferenceEvent { event_id }),
869		)
870	}
871}
872
873fn raw_object<'a>(raw: &'a RawJsonValue, name: &str) -> Result<BorrowedObject<'a>> {
874	deserialize_raw(raw, UniqueObject, name)
875}
876
877fn raw_field<'a>(
878	raw: &'a RawJsonValue,
879	field: &str,
880	name: &str,
881) -> Result<Option<&'a RawJsonValue>> {
882	deserialize_raw(raw, BorrowedField(field), name)
883}
884
885fn deserialize_raw<'a, D>(raw: &'a RawJsonValue, seed: D, name: &str) -> Result<D::Value>
886where
887	D: DeserializeSeed<'a>,
888{
889	let mut deserializer = serde_json::Deserializer::from_str(raw.get());
890
891	seed.deserialize(&mut deserializer)
892		.and_then(|value| deserializer.end().map(|()| value))
893		.map_err(|error| err!(Database("Invalid {name} object in PDU event: {error}")))
894}
895
896/// Serializes `value` into raw JSON labeled as `U`.
897///
898/// `Raw<T, N>` has identical layout for every phantom `T` at fixed `N`.
899/// Callers must ensure that the serialized JSON is valid for `U`.
900#[inline]
901fn raw_as<T, U, const N: usize>(value: &T) -> Result<Raw<U, N>>
902where
903	T: Serialize,
904{
905	Raw::<T, N>::new(value)
906		.map(|raw| raw.cast_ref_unchecked::<U>().clone())
907		.map_err(Into::into)
908}
909
910#[inline]
911fn raw_of<T: Serialize>(value: &T) -> Result<Raw<JsonValue>> { raw_as(value) }