1use ruma::{
15 CanonicalJsonObject, CanonicalJsonValue, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId,
16 OwnedServerName, OwnedUserId,
17 canonical_json::{self},
18 events::{
19 AnyMessageLikeEventContent, AnyStateEventContent, RawExt as _, TimelineEventType,
20 room::{create::RoomCreateEventContent, member::MembershipState},
21 },
22 room_version_rules::EventIdFormatVersion,
23 serde::Raw,
24};
25use serde::{Deserialize, Serialize};
26
27use crate::{
28 EventFormatOptions, EventHash, Pdu, PduCommon, V1Pdu, V3Pdu, V12Pdu,
29 conversions::raw_json,
30 errors::{
31 EventContextualizationError, EventIdGenerationError, MismatchedEventRefsError, SigningError,
32 },
33 pdu::V6Pdu,
34 types::{
35 EventRefs, KnownEventsReferenceFormatVersion, KnownRoomVersionId, PduFormat, StateKey,
36 VerificationMetadata,
37 },
38};
39
40#[derive(Debug, Clone, Deserialize, Serialize)]
44pub struct Draft {
45 pub timeline_event_type: TimelineEventType,
47 pub content: Box<serde_json::value::RawValue>,
49 pub state_key: Option<StateKey>,
53 pub(crate) sender: OwnedUserId,
55 pub(crate) redacts: Option<OwnedEventId>,
57}
58
59impl Draft {
60 pub fn new(
70 event_type: TimelineEventType,
71 content: Box<serde_json::value::RawValue>,
72 state_key: Option<StateKey>,
73 sender: OwnedUserId,
74 redacts: Option<OwnedEventId>,
75 ) -> Result<Self, serde_json::Error> {
76 Self::build(event_type, content, state_key, sender, redacts, false)
77 }
78
79 pub(crate) fn from_signed(
97 event_type: TimelineEventType,
98 content: Box<serde_json::value::RawValue>,
99 state_key: Option<StateKey>,
100 sender: OwnedUserId,
101 redacts: Option<OwnedEventId>,
102 ) -> Result<Self, serde_json::Error> {
103 Self::build(event_type, content, state_key, sender, redacts, true)
104 }
105
106 fn build(
112 event_type: TimelineEventType,
113 content: Box<serde_json::value::RawValue>,
114 state_key: Option<StateKey>,
115 sender: OwnedUserId,
116 redacts: Option<OwnedEventId>,
117 allow_redacted_content: bool,
118 ) -> Result<Self, serde_json::Error> {
119 if state_key.is_some() {
123 Raw::<AnyStateEventContent>::from_json(content.clone())
124 .deserialize_with_type(&event_type.to_string())?;
125 } else {
126 let is_empty_content =
145 serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(content.get())
146 .is_ok_and(|map| map.is_empty());
147 if !(allow_redacted_content && is_empty_content) {
148 Raw::<AnyMessageLikeEventContent>::from_json(content.clone())
149 .deserialize_with_type(&event_type.to_string())?;
150 }
151 }
152
153 Ok(Self { timeline_event_type: event_type, content, state_key, sender, redacts })
154 }
155
156 pub fn redact(
167 &mut self,
168 redacted_content: Box<serde_json::value::RawValue>,
169 ) -> Result<(), serde_json::Error> {
170 if self.timeline_event_type == TimelineEventType::RoomCreate {
173 serde_json::from_str::<RoomCreateEventContent>(redacted_content.get())?;
174 }
175 self.content = redacted_content;
176 Ok(())
177 }
178
179 pub(crate) fn content_to_map(&self) -> serde_json::Map<String, serde_json::Value> {
188 #[expect(
189 clippy::expect_used,
190 reason = "The Draft struct guarantees that the draft field is always correct"
191 )]
192 serde_json::from_str(self.content.get())
193 .expect("We guarantee that the content field here is correct")
194 }
195}
196
197#[cfg(any(feature = "test_utils", test))]
203impl PartialEq for Draft {
204 fn eq(&self, other: &Self) -> bool {
205 if self.timeline_event_type != other.timeline_event_type
206 || self.sender != other.sender
207 || self.state_key != other.state_key
208 || self.redacts != other.redacts
209 {
210 return false;
211 }
212 let v1 = serde_json::from_str::<serde_json::Value>(self.content.get());
213 let v2 = serde_json::from_str::<serde_json::Value>(other.content.get());
214 match (v1, v2) {
215 (Ok(v1), Ok(v2)) => v1 == v2,
216 (Ok(_), Err(_)) | (Err(_), Ok(_)) => false,
217 (Err(_), Err(_)) => self.content.get() == other.content.get(),
218 }
219 }
220}
221
222#[cfg(any(feature = "test_utils", test))]
223impl Eq for Draft {}
224
225#[cfg_attr(any(feature = "test_utils", test), derive(Eq, PartialEq))]
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct Contextualized {
236 pub(crate) draft: Draft,
238 pub(crate) depth: u64,
240 pub(crate) room_version: KnownRoomVersionId,
242 pub(crate) auth_events: EventRefs,
244 pub(crate) prev_events: EventRefs,
246 pub(crate) prev_state: Option<EventRefs>,
251 pub(crate) membership: Option<MembershipState>,
256 pub(crate) origin: Option<OwnedServerName>,
261 pub(crate) origin_server_ts: MilliSecondsSinceUnixEpoch,
263 pub(crate) event_id: OwnedEventId,
265 pub(crate) room_id: OwnedRoomId,
267 pub(crate) hashes: EventHash,
269}
270
271impl Contextualized {
272 #[expect(clippy::too_many_arguments, reason = "Matches the contents of the struct")]
277 pub(crate) fn new(
278 draft: Draft,
279 depth: u64,
280 room_version: KnownRoomVersionId,
281 auth_events: EventRefs,
282 prev_events: EventRefs,
283 prev_state: Option<EventRefs>,
284 membership: Option<MembershipState>,
285 origin: Option<OwnedServerName>,
286 origin_server_ts: MilliSecondsSinceUnixEpoch,
287 event_id: Option<OwnedEventId>,
288 room_id: Option<OwnedRoomId>,
289 hashes: Option<EventHash>,
290 ) -> Result<Self, EventContextualizationError> {
291 match room_version.events_reference_format() {
292 KnownEventsReferenceFormatVersion::V1 => {
293 if !matches!(auth_events, EventRefs::Hashed(_) | EventRefs::Empty)
294 || !matches!(prev_events, EventRefs::Hashed(_) | EventRefs::Empty)
295 {
296 return Err(MismatchedEventRefsError::ExpectedV1GotV2.into());
297 }
298 }
299 KnownEventsReferenceFormatVersion::V2 => {
300 if !matches!(auth_events, EventRefs::Plain(_) | EventRefs::Empty)
301 || !matches!(prev_events, EventRefs::Plain(_) | EventRefs::Empty)
302 {
303 return Err(MismatchedEventRefsError::ExpectedV2GotV1.into());
304 }
305 }
306 }
307 if auth_events.len() > 10 {
308 return Err(EventContextualizationError::TooManyAuthEvents);
309 }
310 if prev_events.len() > 20 {
311 return Err(EventContextualizationError::TooManyPrevEvents);
312 }
313
314 let origin = room_version
317 .rules()
318 .redaction
319 .keep_origin_membership_prev_state
320 .then_some(origin)
321 .flatten();
322 let membership = room_version
323 .rules()
324 .redaction
325 .keep_origin_membership_prev_state
326 .then_some(membership)
327 .flatten();
328 let prev_state = room_version
329 .rules()
330 .redaction
331 .keep_origin_membership_prev_state
332 .then_some(prev_state)
333 .flatten();
334
335 let mut temporary_contextualized = Self {
338 draft,
339 depth,
340 room_version,
341 auth_events,
342 prev_events,
343 membership,
344 origin: origin.clone(),
345 origin_server_ts,
346 prev_state,
347 event_id: ruma::owned_event_id!("$event"),
348 room_id: room_id.clone().unwrap_or_else(|| ruma::owned_room_id!("!dummy")),
349 hashes: EventHash::new(""),
350 };
351
352 temporary_contextualized.hashes = match hashes {
353 None => temporary_contextualized.generate_hashes()?,
354 Some(hashes) => hashes,
355 };
356
357 let event_id = match room_version {
358 KnownRoomVersionId::V1 | KnownRoomVersionId::V2 => event_id.map_or_else(
359 || {
360 temporary_contextualized.generate_event_id(Some(&origin.unwrap_or_else(|| {
361 temporary_contextualized.draft.sender.server_name().to_owned()
362 })))
363 },
364 Ok,
365 )?,
366 KnownRoomVersionId::V3
367 | KnownRoomVersionId::V4
368 | KnownRoomVersionId::V5
369 | KnownRoomVersionId::V6
370 | KnownRoomVersionId::V7
371 | KnownRoomVersionId::V8
372 | KnownRoomVersionId::V9
373 | KnownRoomVersionId::V10
374 | KnownRoomVersionId::V11
375 | KnownRoomVersionId::V12 => temporary_contextualized
376 .generate_event_id(Some(temporary_contextualized.draft.sender.server_name()))?,
377 };
378
379 #[expect(
380 clippy::expect_used,
381 reason = "We know that event IDs will have a $ prefix, and that the room ID will be the correct format"
382 )]
383 let room_id = if let Some(room_id) = room_id {
384 room_id
385 } else {
386 ruma::RoomId::parse(format!(
387 "!{}",
388 event_id.to_string().strip_prefix("$").expect("We know event IDs have this prefix")
389 ))
390 .expect("We know this must be correct")
391 };
392
393 Ok(Self { event_id, room_id, ..temporary_contextualized })
394 }
395
396 pub(crate) fn from_signed_for_resign(signed: Signed) -> Result<Self, SigningError> {
411 if signed.contextualized.draft.timeline_event_type != TimelineEventType::RoomMember {
412 return Err(SigningError::InvalidContent(
413 "from_signed_for_resign only reconstructs m.room.member events".to_owned(),
414 ));
415 }
416 Ok(signed.contextualized)
417 }
418
419 #[must_use]
425 fn build_minimal_event_object(&self) -> serde_json::Map<String, serde_json::Value> {
426 let room_version_rules = &self.room_version.rules();
427 let event_format = EventFormatOptions::from_room_version(room_version_rules);
428 let mut content_map = self.draft.content_to_map();
429 let redacts_value =
430 self.draft.redacts.as_ref().map(|id| serde_json::Value::String(id.to_string()));
431 let redacts_is_content_field = event_format.content_field_redacts;
432 if let Some(ref value) = redacts_value
433 && redacts_is_content_field
434 {
435 content_map.insert("redacts".to_owned(), value.clone());
436 }
437
438 let mut event = serde_json::Map::new();
439 event.insert(
440 "type".to_owned(),
441 serde_json::Value::String(self.draft.timeline_event_type.to_string()),
442 );
443 event.insert("content".to_owned(), serde_json::Value::Object(content_map));
444
445 let is_room_create = !event_format.require_room_create_room_id
446 && matches!(self.draft.timeline_event_type, TimelineEventType::RoomCreate);
447 if !is_room_create {
448 event.insert("room_id".to_owned(), serde_json::Value::String(self.room_id.to_string()));
449 }
450 event.insert("sender".to_owned(), serde_json::Value::String(self.draft.sender.to_string()));
451
452 if let Some(membership) = &self.membership {
453 event
454 .insert("membership".to_owned(), serde_json::Value::String(membership.to_string()));
455 }
456
457 if let Some(origin) = &self.origin {
458 event.insert("origin".to_owned(), serde_json::Value::String(origin.to_string()));
459 }
460
461 event.insert(
462 "origin_server_ts".to_owned(),
463 serde_json::Value::Number(serde_json::Number::from(u64::from(self.origin_server_ts.0))),
464 );
465
466 if let Some(state_key) = &self.draft.state_key {
467 event.insert("state_key".to_owned(), serde_json::Value::String(state_key.to_owned()));
468 }
469 if let Some(value) = redacts_value
470 && !redacts_is_content_field
471 {
472 event.insert("redacts".to_owned(), value);
473 }
474
475 event.insert("auth_events".to_owned(), self.auth_events.pdu_serialize());
476 event.insert("prev_events".to_owned(), self.prev_events.pdu_serialize());
477 if let Some(prev_state) = &self.prev_state {
478 event.insert("prev_state".to_owned(), prev_state.pdu_serialize());
479 }
480 event.insert("depth".to_owned(), self.depth.into());
481 event
482 }
483
484 pub(crate) fn build_canonical_event_object(&self) -> Result<CanonicalJsonObject, SigningError> {
490 let value = serde_json::Value::Object(self.build_minimal_event_object());
491 let canonical = canonical_json::to_canonical_value(value)
492 .map_err(|error| SigningError::JsonCanonicalization(error.to_string()))?;
493 let CanonicalJsonValue::Object(object) = canonical else {
494 panic!("When canonicalizing a JSON object, the result should also be an object");
495 };
496 Ok(object)
497 }
498
499 fn generate_hashes(&self) -> Result<EventHash, EventIdGenerationError> {
509 let canonical = self
510 .build_canonical_event_object()
511 .map_err(|err| EventIdGenerationError::InvalidEvent(err.to_string()))?;
512 let hashes = ruma::signatures::content_hash(&canonical)
513 .map_err(|err| EventIdGenerationError::InvalidEvent(err.to_string()))?;
514 Ok(EventHash::new(hashes.to_string()))
515 }
516
517 fn generate_event_id(
525 &self,
526 server_name: Option<&ruma::ServerName>,
527 ) -> Result<OwnedEventId, EventIdGenerationError> {
528 let mut canonical = self
529 .build_canonical_event_object()
530 .map_err(|err| EventIdGenerationError::InvalidEvent(err.to_string()))?;
531 canonical.insert("hashes".to_owned(), self.hashes.to_canonical_json_value());
532
533 let reference_hash =
534 ruma::signatures::reference_hash(&canonical, &self.room_version.rules())
535 .map_err(|err| EventIdGenerationError::InvalidEvent(err.to_string()))?;
536 let event_id_str = match self.room_version.rules().event_id_format {
537 EventIdFormatVersion::V1 => {
538 let server_name = server_name.ok_or(EventIdGenerationError::V1MissingServerName)?;
539 format!("${reference_hash}:{server_name}")
540 }
541 _ => format!("${reference_hash}"),
542 };
543 #[expect(
544 clippy::expect_used,
545 reason = "This parse is guaranteed to succeed, because we construct the string ourselves"
546 )]
547 let expected_event_id =
548 ruma::EventId::parse(event_id_str).expect("Generated event ID is invalid");
549 Ok(expected_event_id)
550 }
551}
552
553#[derive(Debug, Clone, Serialize, Deserialize)]
558#[cfg_attr(any(feature = "test_utils", test), derive(Eq, PartialEq))]
559pub struct Signed {
560 pub(crate) contextualized: Contextualized,
562 pub(crate) signatures: ruma::ServerSignatures,
564}
565
566impl Signed {
567 #[must_use]
570 pub fn build_event_value(&self) -> serde_json::Value {
571 let mut object = self.contextualized.build_minimal_event_object();
572 if self.contextualized.room_version.rules().event_id_format == EventIdFormatVersion::V1 {
573 object.insert("event_id".to_owned(), self.contextualized.event_id.to_string().into());
574 }
575
576 #[expect(
577 clippy::expect_used,
578 reason = "The object being serialized is a map from strings to strings, which should not be fallible"
579 )]
580 object.insert(
581 "signatures".to_owned(),
582 serde_json::to_value(&self.signatures)
583 .expect("The serialization of ruma signatures cannot actually fail"),
584 );
585 object.insert("hashes".to_owned(), self.contextualized.hashes.to_json_value());
586
587 serde_json::Value::Object(object)
588 }
589
590 pub fn build_event_object(&self) -> Result<CanonicalJsonObject, SigningError> {
593 let value = self.build_event_value();
594
595 let canonical = canonical_json::to_canonical_value(value)
596 .map_err(|error| SigningError::JsonCanonicalization(error.to_string()))?;
597
598 let CanonicalJsonValue::Object(object) = canonical else {
599 panic!("Expected an object");
600 };
601 Ok(object)
602 }
603
604 #[must_use]
606 pub fn build_pdu(&self) -> Pdu {
607 let room_version = self.contextualized.room_version;
608 let pdu_format = PduFormat::from(room_version);
609 let value = self.build_event_value();
610
611 let raw = raw_json(&value);
612
613 let common = PduCommon {
614 event_id: self.contextualized.event_id.clone(),
615 room_id: self.contextualized.room_id.clone(),
616 sender: self.contextualized.draft.sender.clone(),
617 event_type: self.contextualized.draft.timeline_event_type.clone(),
618 content: self.contextualized.draft.content.clone(),
619 state_key: self.contextualized.draft.state_key.clone(),
620 origin_server_ts: self.contextualized.origin_server_ts,
621 redacts: self.contextualized.draft.redacts.clone(),
622 rejected: false,
623 raw,
624 };
625
626 match pdu_format {
627 PduFormat::V1 => Pdu::V1(V1Pdu {
628 common,
629 prev_events: Vec::<OwnedEventId>::from(&self.contextualized.prev_events),
630 auth_events: Vec::<OwnedEventId>::from(&self.contextualized.auth_events),
631 depth: self.contextualized.depth,
632 }),
633 PduFormat::V3 => Pdu::V3(V3Pdu {
634 common,
635 prev_events: Vec::<OwnedEventId>::from(&self.contextualized.prev_events),
636 auth_events: Vec::<OwnedEventId>::from(&self.contextualized.auth_events),
637 depth: self.contextualized.depth,
638 }),
639 PduFormat::V6 => Pdu::V6(V6Pdu {
640 common,
641 prev_events: Vec::<OwnedEventId>::from(&self.contextualized.prev_events),
642 auth_events: Vec::<OwnedEventId>::from(&self.contextualized.auth_events),
643 depth: self.contextualized.depth,
644 }),
645 PduFormat::V12 => Pdu::V12(V12Pdu {
646 common,
647 prev_events: Vec::<OwnedEventId>::from(&self.contextualized.prev_events),
648 auth_events: Vec::<OwnedEventId>::from(&self.contextualized.auth_events),
649 depth: self.contextualized.depth,
650 }),
651 }
652 }
653}
654
655#[cfg_attr(any(feature = "test_utils", test), derive(Eq, PartialEq))]
662#[derive(Clone, Debug, Serialize, Deserialize)]
663pub struct Verified {
664 pub(crate) signed: Signed,
666 pub(crate) metadata: VerificationMetadata,
668}