1use std::{
21 collections::{BTreeMap, HashMap, HashSet},
22 time::Duration,
23};
24
25use rand::Rng;
26use ruma::{
27 MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedRoomId, OwnedUserId,
28 events::secret::request::SecretName,
29};
30use serde::{Deserialize, Serialize};
31use vodozemac::{Curve25519PublicKey, base64_encode};
32use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
33
34use super::{DehydrationError, GossipRequest};
35#[cfg(feature = "experimental-push-secrets")]
36use crate::types::events::secret_push::SecretPushContent;
37use crate::{
38 Account, Device, DeviceData, GossippedSecret, Session, UserIdentity, UserIdentityData,
39 olm::{
40 InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity,
41 SenderData,
42 },
43 types::{
44 EventEncryptionAlgorithm,
45 events::{
46 room_key_bundle::RoomKeyBundleContent,
47 room_key_withheld::{RoomKeyWithheldContent, RoomKeyWithheldEvent},
48 },
49 },
50};
51
52#[derive(Default, Debug)]
58#[allow(missing_docs)]
59pub struct PendingChanges {
60 pub account: Option<Account>,
61}
62
63impl PendingChanges {
64 pub fn is_empty(&self) -> bool {
66 self.account.is_none()
67 }
68}
69
70#[derive(Default, Debug)]
73#[allow(missing_docs)]
74pub struct Changes {
75 pub private_identity: Option<PrivateCrossSigningIdentity>,
76 pub backup_version: Option<String>,
77 pub backup_decryption_key: Option<BackupDecryptionKey>,
78 pub dehydrated_device_pickle_key: Option<DehydratedDeviceKey>,
79 pub sessions: Vec<Session>,
80 pub message_hashes: Vec<OlmMessageHash>,
81 pub inbound_group_sessions: Vec<InboundGroupSession>,
82 pub outbound_group_sessions: Vec<OutboundGroupSession>,
83 pub key_requests: Vec<GossipRequest>,
84 pub identities: IdentityChanges,
85 pub devices: DeviceChanges,
86 pub withheld_session_info: BTreeMap<OwnedRoomId, BTreeMap<String, RoomKeyWithheldEntry>>,
88 pub room_settings: HashMap<OwnedRoomId, RoomSettings>,
89 pub secrets: Vec<SecretsInboxItem>,
90 pub next_batch_token: Option<String>,
91
92 pub received_room_key_bundles: Vec<StoredRoomKeyBundleData>,
95
96 pub room_key_backups_fully_downloaded: HashSet<OwnedRoomId>,
99
100 pub rooms_pending_key_bundle: HashMap<OwnedRoomId, Option<RoomPendingKeyBundleDetails>>,
103}
104
105#[derive(Clone)]
108pub struct SecretsInboxItem {
109 pub secret_name: SecretName,
111 pub secret: Zeroizing<String>,
113}
114
115#[cfg(not(tarpaulin_include))]
116impl std::fmt::Debug for SecretsInboxItem {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 f.debug_tuple("SecretsInboxItem").field(&self.secret_name).finish()
119 }
120}
121
122impl From<GossippedSecret> for SecretsInboxItem {
123 fn from(secret: GossippedSecret) -> Self {
124 Self { secret_name: secret.secret_name, secret: secret.event.content.secret.clone().into() }
125 }
126}
127
128#[cfg(feature = "experimental-push-secrets")]
129impl From<SecretPushContent> for SecretsInboxItem {
130 fn from(secret: SecretPushContent) -> Self {
131 Self { secret_name: secret.name.clone(), secret: secret.secret.clone().into() }
132 }
133}
134
135#[derive(Clone, Debug, Serialize, Deserialize)]
139pub struct StoredRoomKeyBundleData {
140 pub sender_user: OwnedUserId,
142
143 pub sender_key: Curve25519PublicKey,
145
146 pub sender_data: SenderData,
149
150 pub bundle_data: RoomKeyBundleContent,
152}
153
154#[derive(Clone, Debug, Serialize, Deserialize)]
156pub struct TrackedUser {
157 pub user_id: OwnedUserId,
159 pub dirty: bool,
163}
164
165impl Changes {
166 pub fn is_empty(&self) -> bool {
168 self.private_identity.is_none()
169 && self.backup_version.is_none()
170 && self.backup_decryption_key.is_none()
171 && self.dehydrated_device_pickle_key.is_none()
172 && self.sessions.is_empty()
173 && self.message_hashes.is_empty()
174 && self.inbound_group_sessions.is_empty()
175 && self.outbound_group_sessions.is_empty()
176 && self.key_requests.is_empty()
177 && self.identities.is_empty()
178 && self.devices.is_empty()
179 && self.withheld_session_info.is_empty()
180 && self.room_settings.is_empty()
181 && self.secrets.is_empty()
182 && self.next_batch_token.is_none()
183 && self.received_room_key_bundles.is_empty()
184 }
185}
186
187#[derive(Debug, Clone, Default)]
198#[allow(missing_docs)]
199pub struct IdentityChanges {
200 pub new: Vec<UserIdentityData>,
201 pub changed: Vec<UserIdentityData>,
202 pub unchanged: Vec<UserIdentityData>,
203}
204
205impl IdentityChanges {
206 pub(super) fn is_empty(&self) -> bool {
207 self.new.is_empty() && self.changed.is_empty()
208 }
209
210 pub(super) fn into_maps(
213 self,
214 ) -> (
215 BTreeMap<OwnedUserId, UserIdentityData>,
216 BTreeMap<OwnedUserId, UserIdentityData>,
217 BTreeMap<OwnedUserId, UserIdentityData>,
218 ) {
219 let new: BTreeMap<_, _> = self
220 .new
221 .into_iter()
222 .map(|identity| (identity.user_id().to_owned(), identity))
223 .collect();
224
225 let changed: BTreeMap<_, _> = self
226 .changed
227 .into_iter()
228 .map(|identity| (identity.user_id().to_owned(), identity))
229 .collect();
230
231 let unchanged: BTreeMap<_, _> = self
232 .unchanged
233 .into_iter()
234 .map(|identity| (identity.user_id().to_owned(), identity))
235 .collect();
236
237 (new, changed, unchanged)
238 }
239}
240
241#[derive(Debug, Clone, Default)]
242#[allow(missing_docs)]
243pub struct DeviceChanges {
244 pub new: Vec<DeviceData>,
245 pub changed: Vec<DeviceData>,
246 pub deleted: Vec<DeviceData>,
247}
248
249#[derive(Clone, Debug, Default)]
252pub struct DeviceUpdates {
253 pub new: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Device>>,
259 pub changed: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Device>>,
261}
262
263#[derive(Clone, Debug, Default)]
266pub struct IdentityUpdates {
267 pub new: BTreeMap<OwnedUserId, UserIdentity>,
273 pub changed: BTreeMap<OwnedUserId, UserIdentity>,
275 pub unchanged: BTreeMap<OwnedUserId, UserIdentity>,
277}
278
279#[derive(Clone, Zeroize, ZeroizeOnDrop, Deserialize, Serialize)]
289#[serde(transparent)]
290pub struct BackupDecryptionKey {
291 pub(crate) inner: Box<[u8; BackupDecryptionKey::KEY_SIZE]>,
292}
293
294impl BackupDecryptionKey {
295 pub const KEY_SIZE: usize = 32;
297
298 #[allow(clippy::new_without_default)]
300 pub fn new() -> Self {
301 let mut rng = rand::rng();
302
303 let mut key = Box::new([0u8; Self::KEY_SIZE]);
304 rng.fill_bytes(key.as_mut_slice());
305
306 Self { inner: key }
307 }
308
309 pub fn to_base64(&self) -> String {
311 base64_encode(self.inner.as_slice())
312 }
313}
314
315#[cfg(not(tarpaulin_include))]
316impl std::fmt::Debug for BackupDecryptionKey {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 f.debug_tuple("BackupDecryptionKey").field(&"...").finish()
319 }
320}
321
322#[derive(Clone, Zeroize, ZeroizeOnDrop, Deserialize, Serialize)]
327#[serde(transparent)]
328pub struct DehydratedDeviceKey {
329 pub(crate) inner: Box<[u8; DehydratedDeviceKey::KEY_SIZE]>,
330}
331
332impl DehydratedDeviceKey {
333 pub const KEY_SIZE: usize = 32;
335
336 #[allow(clippy::new_without_default)]
338 pub fn new() -> Self {
339 let mut rng = rand::rng();
340
341 let mut key = Box::new([0u8; Self::KEY_SIZE]);
342 rng.fill_bytes(key.as_mut_slice());
343
344 Self { inner: key }
345 }
346
347 pub fn from_slice(slice: &[u8]) -> Result<Self, DehydrationError> {
351 if slice.len() == 32 {
352 let mut key = Box::new([0u8; 32]);
353 key.copy_from_slice(slice);
354 Ok(DehydratedDeviceKey { inner: key })
355 } else {
356 Err(DehydrationError::PickleKeyLength(slice.len()))
357 }
358 }
359
360 pub fn from_bytes(raw_key: &[u8; 32]) -> Self {
362 let mut inner = Box::new([0u8; Self::KEY_SIZE]);
363 inner.copy_from_slice(raw_key);
364
365 Self { inner }
366 }
367
368 pub fn to_base64(&self) -> String {
370 base64_encode(self.inner.as_slice())
371 }
372}
373
374impl From<&[u8; 32]> for DehydratedDeviceKey {
375 fn from(value: &[u8; 32]) -> Self {
376 DehydratedDeviceKey { inner: Box::new(*value) }
377 }
378}
379
380impl From<DehydratedDeviceKey> for Vec<u8> {
381 fn from(key: DehydratedDeviceKey) -> Self {
382 key.inner.to_vec()
383 }
384}
385
386#[cfg(not(tarpaulin_include))]
387impl std::fmt::Debug for DehydratedDeviceKey {
388 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389 f.debug_tuple("DehydratedDeviceKey").field(&"...").finish()
390 }
391}
392
393impl DeviceChanges {
394 pub fn extend(&mut self, other: DeviceChanges) {
396 self.new.extend(other.new);
397 self.changed.extend(other.changed);
398 self.deleted.extend(other.deleted);
399 }
400
401 pub fn is_empty(&self) -> bool {
403 self.new.is_empty() && self.changed.is_empty() && self.deleted.is_empty()
404 }
405}
406
407#[derive(Debug, Clone, Default)]
409pub struct RoomKeyCounts {
410 pub total: usize,
412 pub backed_up: usize,
414}
415
416#[derive(Default, Clone, Debug)]
418pub struct BackupKeys {
419 pub decryption_key: Option<BackupDecryptionKey>,
421 pub backup_version: Option<String>,
423}
424
425#[derive(Default, Zeroize, ZeroizeOnDrop)]
428pub struct CrossSigningKeyExport {
429 pub master_key: Option<String>,
431 pub self_signing_key: Option<String>,
433 pub user_signing_key: Option<String>,
435}
436
437#[cfg(not(tarpaulin_include))]
438impl std::fmt::Debug for CrossSigningKeyExport {
439 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 f.debug_struct("CrossSigningKeyExport")
441 .field("master_key", &self.master_key.is_some())
442 .field("self_signing_key", &self.self_signing_key.is_some())
443 .field("user_signing_key", &self.user_signing_key.is_some())
444 .finish_non_exhaustive()
445 }
446}
447
448#[derive(Clone, Copy, Debug, PartialEq, Eq)]
451pub(crate) enum UserKeyQueryResult {
452 WasPending,
453 WasNotPending,
454
455 TimeoutExpired,
457}
458
459#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
461pub struct RoomSettings {
462 pub algorithm: EventEncryptionAlgorithm,
464
465 #[cfg(feature = "experimental-encrypted-state-events")]
467 #[serde(default)]
468 pub encrypt_state_events: bool,
469
470 pub only_allow_trusted_devices: bool,
473
474 pub session_rotation_period: Option<Duration>,
477
478 pub session_rotation_period_messages: Option<usize>,
481}
482
483impl Default for RoomSettings {
484 fn default() -> Self {
485 Self {
486 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
487 #[cfg(feature = "experimental-encrypted-state-events")]
488 encrypt_state_events: false,
489 only_allow_trusted_devices: false,
490 session_rotation_period: None,
491 session_rotation_period_messages: None,
492 }
493 }
494}
495
496#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
498pub struct RoomKeyInfo {
499 pub algorithm: EventEncryptionAlgorithm,
504
505 pub room_id: OwnedRoomId,
507
508 pub sender_key: Curve25519PublicKey,
510
511 pub session_id: String,
513}
514
515impl From<&InboundGroupSession> for RoomKeyInfo {
516 fn from(group_session: &InboundGroupSession) -> Self {
517 RoomKeyInfo {
518 algorithm: group_session.algorithm().clone(),
519 room_id: group_session.room_id().to_owned(),
520 sender_key: group_session.sender_key(),
521 session_id: group_session.session_id().to_owned(),
522 }
523 }
524}
525
526#[derive(Clone, Debug, Deserialize, Serialize)]
528pub struct RoomKeyWithheldInfo {
529 pub room_id: OwnedRoomId,
531
532 pub session_id: String,
534
535 pub withheld_event: RoomKeyWithheldEntry,
540}
541
542#[derive(Clone, Debug, Serialize, Deserialize)]
545pub struct RoomKeyWithheldEntry {
546 pub sender: OwnedUserId,
551 pub content: RoomKeyWithheldContent,
554}
555
556impl From<RoomKeyWithheldEvent> for RoomKeyWithheldEntry {
557 fn from(value: RoomKeyWithheldEvent) -> Self {
558 Self { sender: value.sender, content: value.content }
559 }
560}
561
562#[derive(Debug, Clone)]
570pub struct RoomKeyBundleInfo {
571 pub sender: OwnedUserId,
573
574 pub sender_key: Curve25519PublicKey,
576
577 pub room_id: OwnedRoomId,
579}
580
581impl From<&StoredRoomKeyBundleData> for RoomKeyBundleInfo {
582 fn from(value: &StoredRoomKeyBundleData) -> Self {
583 let StoredRoomKeyBundleData { sender_user, sender_data: _, bundle_data, sender_key } =
584 value;
585 let sender_key = *sender_key;
586
587 Self { sender: sender_user.clone(), room_id: bundle_data.room_id.clone(), sender_key }
588 }
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize)]
594pub struct RoomPendingKeyBundleDetails {
595 pub room_id: OwnedRoomId,
599
600 pub invite_accepted_at: MilliSecondsSinceUnixEpoch,
603
604 pub inviter: OwnedUserId,
606}