ruma_events/call/member/member_data.rs
1//! Types for MatrixRTC `m.call.member` state event content data ([MSC3401])
2//!
3//! [MSC3401]: https://github.com/matrix-org/matrix-spec-proposals/pull/3401
4
5use std::time::Duration;
6
7use as_variant::as_variant;
8use ruma_common::{DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId};
9use ruma_macros::StringEnum;
10use serde::{Deserialize, Serialize};
11use tracing::warn;
12
13use super::focus::{ActiveFocus, ActiveLivekitFocus, Focus};
14use crate::PrivOwnedStr;
15
16/// The data object that contains the information for one membership.
17///
18/// It can be a legacy or a normal MatrixRTC Session membership.
19///
20/// The legacy format contains time information to compute if it is expired or not.
21/// SessionMembershipData does not have the concept of timestamp based expiration anymore.
22/// The state event will reliably be set to empty when the user disconnects.
23#[derive(Clone, Debug)]
24#[cfg_attr(test, derive(PartialEq))]
25#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
26pub enum MembershipData<'a> {
27 /// The legacy format (using an array of memberships for each device -> one event per user)
28 Legacy(&'a LegacyMembershipData),
29 /// One event per device. `SessionMembershipData` contains all the information required to
30 /// represent the current membership state of one device.
31 Session(&'a SessionMembershipData),
32}
33
34impl MembershipData<'_> {
35 /// The application this RTC membership participates in (the session type, can be `m.call`...)
36 pub fn application(&self) -> &Application {
37 match self {
38 MembershipData::Legacy(data) => &data.application,
39 MembershipData::Session(data) => &data.application,
40 }
41 }
42
43 /// The device id of this membership.
44 pub fn device_id(&self) -> &DeviceId {
45 match self {
46 MembershipData::Legacy(data) => &data.device_id,
47 MembershipData::Session(data) => &data.device_id,
48 }
49 }
50
51 /// The active focus is a FocusType specific object that describes how this user
52 /// is currently connected.
53 ///
54 /// It can use the foci_preferred list to choose one of the available (preferred)
55 /// foci or specific information on how to connect to this user.
56 ///
57 /// Every user needs to converge to use the same focus_active type.
58 pub fn focus_active(&self) -> &ActiveFocus {
59 match self {
60 MembershipData::Legacy(_) => &ActiveFocus::Livekit(ActiveLivekitFocus {
61 focus_selection: super::focus::FocusSelection::OldestMembership,
62 }),
63 MembershipData::Session(data) => &data.focus_active,
64 }
65 }
66
67 /// The list of available/preferred options this user provides to connect to the call.
68 pub fn foci_preferred(&self) -> &Vec<Focus> {
69 match self {
70 MembershipData::Legacy(data) => &data.foci_active,
71 MembershipData::Session(data) => &data.foci_preferred,
72 }
73 }
74
75 /// The application of the membership is "m.call" and the scope is "m.room".
76 pub fn is_room_call(&self) -> bool {
77 as_variant!(self.application(), Application::Call)
78 .is_some_and(|call| call.scope == CallScope::Room)
79 }
80
81 /// The application of the membership is "m.call".
82 pub fn is_call(&self) -> bool {
83 as_variant!(self.application(), Application::Call).is_some()
84 }
85
86 /// Gets the created_ts of the event.
87 ///
88 /// This is the `origin_server_ts` for session data.
89 /// For legacy events this can either be the origin server ts or a copy from the
90 /// `origin_server_ts` since we expect legacy events to get updated (when a new device
91 /// joins/leaves).
92 pub fn created_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
93 match self {
94 MembershipData::Legacy(data) => data.created_ts,
95 MembershipData::Session(data) => data.created_ts,
96 }
97 }
98
99 /// Checks if the event is expired.
100 ///
101 /// Defaults to using `created_ts` of the [`MembershipData`].
102 /// If no `origin_server_ts` is provided and the event does not contain `created_ts`
103 /// the event will be considered as not expired.
104 /// In this case, a warning will be logged.
105 ///
106 /// This method needs to be called periodically to check if the event is still valid.
107 ///
108 /// # Arguments
109 ///
110 /// * `origin_server_ts` - a fallback if [`MembershipData::created_ts`] is not present
111 pub fn is_expired(&self, origin_server_ts: Option<MilliSecondsSinceUnixEpoch>) -> bool {
112 if let Some(expire_ts) = self.expires_ts(origin_server_ts) {
113 MilliSecondsSinceUnixEpoch::now() > expire_ts
114 } else {
115 // This should not be reached since we only allow events that have copied over
116 // the origin server ts. `set_created_ts_if_none`
117 warn!("Encountered a Call Member state event where the expire_ts could not be constructed.");
118 false
119 }
120 }
121
122 /// The unix timestamp at which the event will expire.
123 /// This allows to determine at what time the return value of
124 /// [`MembershipData::is_expired`] will change.
125 ///
126 /// Defaults to using `created_ts` of the [`MembershipData`].
127 /// If no `origin_server_ts` is provided and the event does not contain `created_ts`
128 /// the event will be considered as not expired.
129 /// In this case, a warning will be logged.
130 ///
131 /// # Arguments
132 ///
133 /// * `origin_server_ts` - a fallback if [`MembershipData::created_ts`] is not present
134 pub fn expires_ts(
135 &self,
136 origin_server_ts: Option<MilliSecondsSinceUnixEpoch>,
137 ) -> Option<MilliSecondsSinceUnixEpoch> {
138 let expires = match &self {
139 MembershipData::Legacy(data) => data.expires,
140 MembershipData::Session(data) => data.expires,
141 };
142 let ev_created_ts = self.created_ts().or(origin_server_ts)?.to_system_time();
143 ev_created_ts.and_then(|t| MilliSecondsSinceUnixEpoch::from_system_time(t + expires))
144 }
145}
146
147/// A membership describes one of the sessions this user currently partakes.
148///
149/// The application defines the type of the session.
150#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
151#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
152pub struct LegacyMembershipData {
153 /// The type of the MatrixRTC session the membership belongs to.
154 ///
155 /// e.g. call, spacial, document...
156 #[serde(flatten)]
157 pub application: Application,
158
159 /// The device id of this membership.
160 ///
161 /// The same user can join with their phone/computer.
162 pub device_id: OwnedDeviceId,
163
164 /// The duration in milliseconds relative to the time this membership joined
165 /// during which the membership is valid.
166 ///
167 /// The time a member has joined is defined as:
168 /// `MIN(content.created_ts, event.origin_server_ts)`
169 #[serde(with = "ruma_common::serde::duration::ms")]
170 pub expires: Duration,
171
172 /// Stores a copy of the `origin_server_ts` of the initial session event.
173 ///
174 /// If the membership is updated this field will be used to track the
175 /// original `origin_server_ts`.
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub created_ts: Option<MilliSecondsSinceUnixEpoch>,
178
179 /// A list of the foci in use for this membership.
180 pub foci_active: Vec<Focus>,
181
182 /// The id of the membership.
183 ///
184 /// This is required to guarantee uniqueness of the event.
185 /// Sending the same state event twice to synapse makes the HS drop the second one and return
186 /// 200.
187 #[serde(rename = "membershipID")]
188 pub membership_id: String,
189}
190
191/// Initial set of fields of [`LegacyMembershipData`].
192#[derive(Debug)]
193#[allow(clippy::exhaustive_structs)]
194pub struct LegacyMembershipDataInit {
195 /// The type of the MatrixRTC session the membership belongs to.
196 ///
197 /// e.g. call, spacial, document...
198 pub application: Application,
199
200 /// The device id of this membership.
201 ///
202 /// The same user can join with their phone/computer.
203 pub device_id: OwnedDeviceId,
204
205 /// The duration in milliseconds relative to the time this membership joined
206 /// during which the membership is valid.
207 ///
208 /// The time a member has joined is defined as:
209 /// `MIN(content.created_ts, event.origin_server_ts)`
210 pub expires: Duration,
211
212 /// A list of the focuses (foci) in use for this membership.
213 pub foci_active: Vec<Focus>,
214
215 /// The id of the membership.
216 ///
217 /// This is required to guarantee uniqueness of the event.
218 /// Sending the same state event twice to synapse makes the HS drop the second one and return
219 /// 200.
220 pub membership_id: String,
221}
222
223impl From<LegacyMembershipDataInit> for LegacyMembershipData {
224 fn from(init: LegacyMembershipDataInit) -> Self {
225 let LegacyMembershipDataInit {
226 application,
227 device_id,
228 expires,
229 foci_active,
230 membership_id,
231 } = init;
232 Self { application, device_id, expires, created_ts: None, foci_active, membership_id }
233 }
234}
235
236/// Stores all the information for a MatrixRTC membership. (one for each device)
237#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
238#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
239pub struct SessionMembershipData {
240 /// The type of the MatrixRTC session the membership belongs to.
241 ///
242 /// e.g. call, spacial, document...
243 #[serde(flatten)]
244 pub application: Application,
245
246 /// The device id of this membership.
247 ///
248 /// The same user can join with their phone/computer.
249 pub device_id: OwnedDeviceId,
250
251 /// A list of the foci that this membership proposes to use.
252 pub foci_preferred: Vec<Focus>,
253
254 /// Data required to determine the currently used focus by this member.
255 pub focus_active: ActiveFocus,
256
257 /// Stores a copy of the `origin_server_ts` of the initial session event.
258 ///
259 /// If the membership is updated this field will be used to track the
260 /// original `origin_server_ts`.
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub created_ts: Option<MilliSecondsSinceUnixEpoch>,
263
264 /// The duration in milliseconds relative to the time this membership joined
265 /// during which the membership is valid.
266 ///
267 /// The time a member has joined is defined as:
268 /// `MIN(content.created_ts, event.origin_server_ts)`
269 #[serde(with = "ruma_common::serde::duration::ms")]
270 pub expires: Duration,
271}
272
273/// The type of the MatrixRTC session.
274///
275/// This is not the application/client used by the user but the
276/// type of MatrixRTC session e.g. calling (`m.call`), third-room, whiteboard could be
277/// possible applications.
278#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
279#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
280#[serde(tag = "application")]
281pub enum Application {
282 /// The rtc application (session type) for VoIP call.
283 #[serde(rename = "m.call")]
284 Call(CallApplicationContent),
285}
286
287/// Call specific parameters of a `m.call.member` event.
288#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
289#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
290pub struct CallApplicationContent {
291 /// An identifier for calls.
292 ///
293 /// All members using the same `call_id` will end up in the same call.
294 ///
295 /// Does not need to be a uuid.
296 ///
297 /// `""` is used for room scoped calls.
298 pub call_id: String,
299
300 /// Who owns/joins/controls (can modify) the call.
301 pub scope: CallScope,
302}
303
304impl CallApplicationContent {
305 /// Initialize a [`CallApplicationContent`].
306 ///
307 /// # Arguments
308 ///
309 /// * `call_id` - An identifier for calls. All members using the same `call_id` will end up in
310 /// the same call. Does not need to be a uuid. `""` is used for room scoped calls.
311 /// * `scope` - Who owns/joins/controls (can modify) the call.
312 pub fn new(call_id: String, scope: CallScope) -> Self {
313 Self { call_id, scope }
314 }
315}
316
317/// The call scope defines different call ownership models.
318#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
319#[derive(Clone, PartialEq, StringEnum)]
320#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
321#[ruma_enum(rename_all = "m.snake_case")]
322pub enum CallScope {
323 /// A call which every user of a room can join and create.
324 ///
325 /// There is no particular name associated with it.
326 ///
327 /// There can only be one per room.
328 Room,
329
330 /// A user call is owned by a user.
331 ///
332 /// Each user can create one there can be multiple per room. They are started and ended by the
333 /// owning user.
334 User,
335
336 #[doc(hidden)]
337 _Custom(PrivOwnedStr),
338}