use std::collections::BTreeMap; use ruma::{ MilliSecondsSinceUnixEpoch, events::{AnyTimelineEvent, room::member::MembershipState}, serde::Raw, }; use serde_json::value::{RawValue as RawJsonValue, to_raw_value}; use super::{Pdu, sticky}; use crate::Event; type Unsigned = BTreeMap>; impl Pdu { /// Sets the `unsigned` field of the PDU using a mix of the provided /// optional data, and information available in the PDU itself. The /// `unsigned` field will always be inserted if it is not attached to the /// PDU, however no operations will be performed if `unsigned` is present /// but malformed and unparsable. /// /// `age` is always inserted and is calculated based on the time of the /// function call. `membership`, `prev_content`, and `redacted_by` are /// inserted if not `None`. If `user_id` is `None`, `transaction_id` will be /// removed. The sticky event TTL field is always inserted and calculated /// (per `age`) if the PDU is sticky. /// /// This function panics if any value cannot be serialised, which should not /// happen provided `membership`, `prev_content`, and `redacted_because` are /// well-formed. pub fn set_unsigned( &mut self, user_id: Option<&ruma::UserId>, membership: Option, prev_content: Option>, redacted_because: Option>, ) -> crate::Result { // See: https://spec.matrix.org/v1.19/client-server-api/#definition-clientevent_unsigneddata let mut unsigned = self .unsigned() .map_or_else(|| Ok(Unsigned::new()), |u| serde_json::from_str::(u.get()))?; let now = MilliSecondsSinceUnixEpoch::now().get(); let now_i: ruma::Int = now.into(); unsigned.insert( "age".to_owned(), to_raw_value(&now_i.saturating_sub(self.origin_server_ts.into()))?, ); if let Some(membership) = membership { unsigned.insert("membership".to_owned(), to_raw_value(&membership)?); } if let Some(prev_content) = prev_content { unsigned.insert("prev_content".to_owned(), to_raw_value(&prev_content)?); // TODO(nex): prev_sender is still inserted in append.rs because // only the prev *content* is passed to this function. // I think ideally passing the actual direct `UnsignedContext` to // this function instead of unpacking its parameters would be the // proper solution, especially since every callsite has one } if let Some(redacted_because) = redacted_because { unsigned.remove("org.continuwuity.redacted_by"); unsigned.insert("redacted_because".to_owned(), to_raw_value(&redacted_because)?); } // Remove transaction_id unless the user is the sender if user_id.is_none_or(|u| u != self.sender()) { unsigned.remove("transaction_id"); } if let Some(expires_at) = self .sticky .as_deref() .and_then(|sticky| sticky::expires_at(self.origin_server_ts, sticky, u64::from(now))) { unsigned.insert( sticky::TTL_UNSIGNED_KEY.to_owned(), to_raw_value(&expires_at.saturating_sub(u64::from(now)))?, ); } self.unsigned = Some(to_raw_value(&unsigned)?); Ok(unsigned) } }