use std::{
collections::{BTreeMap, HashMap},
time::Duration,
};
use ruma::{OwnedDeviceId, OwnedRoomId, OwnedUserId};
use serde::{Deserialize, Serialize};
use vodozemac::{base64_encode, Curve25519PublicKey};
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::{DehydrationError, GossipRequest};
use crate::{
olm::{
InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity,
SenderData,
},
types::{
events::{
room_key_bundle::RoomKeyBundleContent,
room_key_withheld::{RoomKeyWithheldContent, RoomKeyWithheldEvent},
},
EventEncryptionAlgorithm,
},
Account, Device, DeviceData, GossippedSecret, Session, UserIdentity, UserIdentityData,
};
#[derive(Default, Debug)]
#[allow(missing_docs)]
pub struct PendingChanges {
pub account: Option<Account>,
}
impl PendingChanges {
pub fn is_empty(&self) -> bool {
self.account.is_none()
}
}
#[derive(Default, Debug)]
#[allow(missing_docs)]
pub struct Changes {
pub private_identity: Option<PrivateCrossSigningIdentity>,
pub backup_version: Option<String>,
pub backup_decryption_key: Option<BackupDecryptionKey>,
pub dehydrated_device_pickle_key: Option<DehydratedDeviceKey>,
pub sessions: Vec<Session>,
pub message_hashes: Vec<OlmMessageHash>,
pub inbound_group_sessions: Vec<InboundGroupSession>,
pub outbound_group_sessions: Vec<OutboundGroupSession>,
pub key_requests: Vec<GossipRequest>,
pub identities: IdentityChanges,
pub devices: DeviceChanges,
pub withheld_session_info: BTreeMap<OwnedRoomId, BTreeMap<String, RoomKeyWithheldEntry>>,
pub room_settings: HashMap<OwnedRoomId, RoomSettings>,
pub secrets: Vec<GossippedSecret>,
pub next_batch_token: Option<String>,
pub received_room_key_bundles: Vec<StoredRoomKeyBundleData>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StoredRoomKeyBundleData {
pub sender_user: OwnedUserId,
pub sender_key: Curve25519PublicKey,
pub sender_data: SenderData,
pub bundle_data: RoomKeyBundleContent,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TrackedUser {
pub user_id: OwnedUserId,
pub dirty: bool,
}
impl Changes {
pub fn is_empty(&self) -> bool {
self.private_identity.is_none()
&& self.backup_version.is_none()
&& self.backup_decryption_key.is_none()
&& self.dehydrated_device_pickle_key.is_none()
&& self.sessions.is_empty()
&& self.message_hashes.is_empty()
&& self.inbound_group_sessions.is_empty()
&& self.outbound_group_sessions.is_empty()
&& self.key_requests.is_empty()
&& self.identities.is_empty()
&& self.devices.is_empty()
&& self.withheld_session_info.is_empty()
&& self.room_settings.is_empty()
&& self.secrets.is_empty()
&& self.next_batch_token.is_none()
&& self.received_room_key_bundles.is_empty()
}
}
#[derive(Debug, Clone, Default)]
#[allow(missing_docs)]
pub struct IdentityChanges {
pub new: Vec<UserIdentityData>,
pub changed: Vec<UserIdentityData>,
pub unchanged: Vec<UserIdentityData>,
}
impl IdentityChanges {
pub(super) fn is_empty(&self) -> bool {
self.new.is_empty() && self.changed.is_empty()
}
pub(super) fn into_maps(
self,
) -> (
BTreeMap<OwnedUserId, UserIdentityData>,
BTreeMap<OwnedUserId, UserIdentityData>,
BTreeMap<OwnedUserId, UserIdentityData>,
) {
let new: BTreeMap<_, _> = self
.new
.into_iter()
.map(|identity| (identity.user_id().to_owned(), identity))
.collect();
let changed: BTreeMap<_, _> = self
.changed
.into_iter()
.map(|identity| (identity.user_id().to_owned(), identity))
.collect();
let unchanged: BTreeMap<_, _> = self
.unchanged
.into_iter()
.map(|identity| (identity.user_id().to_owned(), identity))
.collect();
(new, changed, unchanged)
}
}
#[derive(Debug, Clone, Default)]
#[allow(missing_docs)]
pub struct DeviceChanges {
pub new: Vec<DeviceData>,
pub changed: Vec<DeviceData>,
pub deleted: Vec<DeviceData>,
}
#[derive(Clone, Debug, Default)]
pub struct DeviceUpdates {
pub new: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Device>>,
pub changed: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Device>>,
}
#[derive(Clone, Debug, Default)]
pub struct IdentityUpdates {
pub new: BTreeMap<OwnedUserId, UserIdentity>,
pub changed: BTreeMap<OwnedUserId, UserIdentity>,
pub unchanged: BTreeMap<OwnedUserId, UserIdentity>,
}
#[derive(Clone, Zeroize, ZeroizeOnDrop, Deserialize, Serialize)]
#[serde(transparent)]
pub struct BackupDecryptionKey {
pub(crate) inner: Box<[u8; BackupDecryptionKey::KEY_SIZE]>,
}
impl BackupDecryptionKey {
pub const KEY_SIZE: usize = 32;
pub fn new() -> Result<Self, rand::Error> {
let mut rng = rand::thread_rng();
let mut key = Box::new([0u8; Self::KEY_SIZE]);
rand::Fill::try_fill(key.as_mut_slice(), &mut rng)?;
Ok(Self { inner: key })
}
pub fn to_base64(&self) -> String {
base64_encode(self.inner.as_slice())
}
}
#[cfg(not(tarpaulin_include))]
impl std::fmt::Debug for BackupDecryptionKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("BackupDecryptionKey").field(&"...").finish()
}
}
#[derive(Clone, Zeroize, ZeroizeOnDrop, Deserialize, Serialize)]
#[serde(transparent)]
pub struct DehydratedDeviceKey {
pub(crate) inner: Box<[u8; DehydratedDeviceKey::KEY_SIZE]>,
}
impl DehydratedDeviceKey {
pub const KEY_SIZE: usize = 32;
pub fn new() -> Result<Self, rand::Error> {
let mut rng = rand::thread_rng();
let mut key = Box::new([0u8; Self::KEY_SIZE]);
rand::Fill::try_fill(key.as_mut_slice(), &mut rng)?;
Ok(Self { inner: key })
}
pub fn from_slice(slice: &[u8]) -> Result<Self, DehydrationError> {
if slice.len() == 32 {
let mut key = Box::new([0u8; 32]);
key.copy_from_slice(slice);
Ok(DehydratedDeviceKey { inner: key })
} else {
Err(DehydrationError::PickleKeyLength(slice.len()))
}
}
pub fn from_bytes(raw_key: &[u8; 32]) -> Self {
let mut inner = Box::new([0u8; Self::KEY_SIZE]);
inner.copy_from_slice(raw_key);
Self { inner }
}
pub fn to_base64(&self) -> String {
base64_encode(self.inner.as_slice())
}
}
impl From<&[u8; 32]> for DehydratedDeviceKey {
fn from(value: &[u8; 32]) -> Self {
DehydratedDeviceKey { inner: Box::new(*value) }
}
}
impl From<DehydratedDeviceKey> for Vec<u8> {
fn from(key: DehydratedDeviceKey) -> Self {
key.inner.to_vec()
}
}
#[cfg(not(tarpaulin_include))]
impl std::fmt::Debug for DehydratedDeviceKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("DehydratedDeviceKey").field(&"...").finish()
}
}
impl DeviceChanges {
pub fn extend(&mut self, other: DeviceChanges) {
self.new.extend(other.new);
self.changed.extend(other.changed);
self.deleted.extend(other.deleted);
}
pub fn is_empty(&self) -> bool {
self.new.is_empty() && self.changed.is_empty() && self.deleted.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct RoomKeyCounts {
pub total: usize,
pub backed_up: usize,
}
#[derive(Default, Clone, Debug)]
pub struct BackupKeys {
pub decryption_key: Option<BackupDecryptionKey>,
pub backup_version: Option<String>,
}
#[derive(Default, Zeroize, ZeroizeOnDrop)]
pub struct CrossSigningKeyExport {
pub master_key: Option<String>,
pub self_signing_key: Option<String>,
pub user_signing_key: Option<String>,
}
#[cfg(not(tarpaulin_include))]
impl std::fmt::Debug for CrossSigningKeyExport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CrossSigningKeyExport")
.field("master_key", &self.master_key.is_some())
.field("self_signing_key", &self.self_signing_key.is_some())
.field("user_signing_key", &self.user_signing_key.is_some())
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum UserKeyQueryResult {
WasPending,
WasNotPending,
TimeoutExpired,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct RoomSettings {
pub algorithm: EventEncryptionAlgorithm,
#[cfg(feature = "experimental-encrypted-state-events")]
#[serde(default)]
pub encrypt_state_events: bool,
pub only_allow_trusted_devices: bool,
pub session_rotation_period: Option<Duration>,
pub session_rotation_period_messages: Option<usize>,
}
impl Default for RoomSettings {
fn default() -> Self {
Self {
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
#[cfg(feature = "experimental-encrypted-state-events")]
encrypt_state_events: false,
only_allow_trusted_devices: false,
session_rotation_period: None,
session_rotation_period_messages: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct RoomKeyInfo {
pub algorithm: EventEncryptionAlgorithm,
pub room_id: OwnedRoomId,
pub sender_key: Curve25519PublicKey,
pub session_id: String,
}
impl From<&InboundGroupSession> for RoomKeyInfo {
fn from(group_session: &InboundGroupSession) -> Self {
RoomKeyInfo {
algorithm: group_session.algorithm().clone(),
room_id: group_session.room_id().to_owned(),
sender_key: group_session.sender_key(),
session_id: group_session.session_id().to_owned(),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoomKeyWithheldInfo {
pub room_id: OwnedRoomId,
pub session_id: String,
pub withheld_event: RoomKeyWithheldEntry,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RoomKeyWithheldEntry {
pub sender: OwnedUserId,
pub content: RoomKeyWithheldContent,
}
impl From<RoomKeyWithheldEvent> for RoomKeyWithheldEntry {
fn from(value: RoomKeyWithheldEvent) -> Self {
Self { sender: value.sender, content: value.content }
}
}
#[derive(Debug, Clone)]
pub struct RoomKeyBundleInfo {
pub sender: OwnedUserId,
pub sender_key: Curve25519PublicKey,
pub room_id: OwnedRoomId,
}
impl From<&StoredRoomKeyBundleData> for RoomKeyBundleInfo {
fn from(value: &StoredRoomKeyBundleData) -> Self {
let StoredRoomKeyBundleData { sender_user, sender_data: _, bundle_data, sender_key } =
value;
let sender_key = *sender_key;
Self { sender: sender_user.clone(), room_id: bundle_data.room_id.clone(), sender_key }
}
}