1pub mod add_backup_keys;
4pub mod add_backup_keys_for_room;
5pub mod add_backup_keys_for_session;
6pub mod create_backup_version;
7pub mod delete_backup_keys;
8pub mod delete_backup_keys_for_room;
9pub mod delete_backup_keys_for_session;
10pub mod delete_backup_version;
11pub mod get_backup_info;
12pub mod get_backup_keys;
13pub mod get_backup_keys_for_room;
14pub mod get_backup_keys_for_session;
15pub mod get_latest_backup_info;
16pub mod update_backup_version;
17
18use std::{borrow::Cow, collections::BTreeMap};
19
20use js_int::UInt;
21use ruma_common::{
22 CrossSigningOrDeviceSignatures,
23 serde::{Base64, JsonObject, Raw},
24};
25use serde::{Deserialize, Serialize};
26use serde_json::Value as JsonValue;
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
30#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
31pub struct RoomKeyBackup {
32 pub sessions: BTreeMap<String, Raw<KeyBackupData>>,
34}
35
36impl RoomKeyBackup {
37 pub fn new(sessions: BTreeMap<String, Raw<KeyBackupData>>) -> Self {
39 Self { sessions }
40 }
41}
42
43#[derive(Clone, Debug, Serialize, Deserialize)]
45#[serde(tag = "algorithm", content = "auth_data")]
46#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
47pub enum BackupAlgorithm {
48 #[serde(rename = "m.megolm_backup.v1.curve25519-aes-sha2")]
50 MegolmBackupV1Curve25519AesSha2(MegolmBackupV1Curve25519AesSha2AuthData),
51
52 #[doc(hidden)]
53 #[serde(untagged)]
54 _Custom(CustomBackupAlgorithm),
55}
56
57impl BackupAlgorithm {
58 pub fn algorithm(&self) -> &str {
60 match self {
61 Self::MegolmBackupV1Curve25519AesSha2(_) => "m.megolm_backup.v1.curve25519-aes-sha2",
62 Self::_Custom(c) => &c.algorithm,
63 }
64 }
65
66 pub fn auth_data(&self) -> Cow<'_, JsonObject> {
71 fn serialize<T: Serialize>(obj: &T) -> JsonObject {
72 match serde_json::to_value(obj).expect("backup data serialization to succeed") {
73 JsonValue::Object(obj) => obj,
74 _ => panic!("all backup data types must serialize to objects"),
75 }
76 }
77
78 match self {
79 Self::MegolmBackupV1Curve25519AesSha2(d) => Cow::Owned(serialize(d)),
80 Self::_Custom(c) => Cow::Borrowed(&c.auth_data),
81 }
82 }
83}
84
85impl From<MegolmBackupV1Curve25519AesSha2AuthData> for BackupAlgorithm {
86 fn from(value: MegolmBackupV1Curve25519AesSha2AuthData) -> Self {
87 Self::MegolmBackupV1Curve25519AesSha2(value)
88 }
89}
90
91#[derive(Clone, Debug, Serialize, Deserialize)]
93#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
94pub struct MegolmBackupV1Curve25519AesSha2AuthData {
95 pub public_key: Base64,
97
98 pub signatures: CrossSigningOrDeviceSignatures,
100}
101
102impl MegolmBackupV1Curve25519AesSha2AuthData {
103 pub fn new(public_key: Base64) -> Self {
105 Self { public_key, signatures: Default::default() }
106 }
107}
108
109#[doc(hidden)]
111#[derive(Clone, Debug, Deserialize, Serialize)]
112pub struct CustomBackupAlgorithm {
113 algorithm: String,
115
116 auth_data: JsonObject,
118}
119
120#[derive(Clone, Debug, Serialize, Deserialize)]
125#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
126pub struct KeyBackupData {
127 pub first_message_index: UInt,
129
130 pub forwarded_count: UInt,
132
133 pub is_verified: bool,
135
136 pub session_data: EncryptedSessionData,
138}
139
140#[derive(Debug)]
145#[allow(clippy::exhaustive_structs)]
146pub struct KeyBackupDataInit {
147 pub first_message_index: UInt,
149
150 pub forwarded_count: UInt,
152
153 pub is_verified: bool,
155
156 pub session_data: EncryptedSessionData,
158}
159
160impl From<KeyBackupDataInit> for KeyBackupData {
161 fn from(init: KeyBackupDataInit) -> Self {
162 let KeyBackupDataInit { first_message_index, forwarded_count, is_verified, session_data } =
163 init;
164 Self { first_message_index, forwarded_count, is_verified, session_data }
165 }
166}
167
168#[derive(Clone, Debug, Serialize, Deserialize)]
173#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
174pub struct EncryptedSessionData {
175 pub ephemeral: Base64,
177
178 pub ciphertext: Base64,
180
181 pub mac: Base64,
183}
184
185#[derive(Debug)]
190#[allow(clippy::exhaustive_structs)]
191pub struct EncryptedSessionDataInit {
192 pub ephemeral: Base64,
194
195 pub ciphertext: Base64,
197
198 pub mac: Base64,
200}
201
202impl From<EncryptedSessionDataInit> for EncryptedSessionData {
203 fn from(init: EncryptedSessionDataInit) -> Self {
204 let EncryptedSessionDataInit { ephemeral, ciphertext, mac } = init;
205 Self { ephemeral, ciphertext, mac }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use std::borrow::Cow;
212
213 use assert_matches2::assert_matches;
214 use ruma_common::{
215 SigningKeyAlgorithm, SigningKeyId, canonical_json::assert_to_canonical_json_eq,
216 owned_user_id, serde::Base64,
217 };
218 use serde_json::{Value as JsonValue, from_value as from_json_value, json};
219
220 use super::{BackupAlgorithm, MegolmBackupV1Curve25519AesSha2AuthData};
221
222 #[test]
223 fn megolm_v1_backup_algorithm_serialize_roundtrip() {
224 let json = json!({
225 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
226 "auth_data": {
227 "public_key": "YWJjZGVm",
228 "signatures": {
229 "@alice:example.org": {
230 "ed25519:DEVICEID": "signature",
231 },
232 },
233 },
234 });
235
236 let mut backup_algorithm =
237 MegolmBackupV1Curve25519AesSha2AuthData::new(Base64::new(b"abcdef".to_vec()));
238 backup_algorithm.signatures.insert_signature(
239 owned_user_id!("@alice:example.org"),
240 SigningKeyId::from_parts(SigningKeyAlgorithm::Ed25519, "DEVICEID".into()),
241 "signature".to_owned(),
242 );
243 assert_to_canonical_json_eq!(BackupAlgorithm::from(backup_algorithm), json.clone());
244
245 assert_matches!(
246 from_json_value(json),
247 Ok(BackupAlgorithm::MegolmBackupV1Curve25519AesSha2(auth_data))
248 );
249 assert_eq!(auth_data.public_key.as_bytes(), b"abcdef");
250 assert_matches!(
251 auth_data.signatures.get(&owned_user_id!("@alice:example.org")),
252 Some(user_signatures)
253 );
254
255 let mut user_signatures_iter = user_signatures.iter();
256 assert_matches!(user_signatures_iter.next(), Some((key_id, signature)));
257 assert_eq!(key_id, "ed25519:DEVICEID");
258 assert_eq!(signature, "signature");
259 assert_matches!(user_signatures_iter.next(), None);
260 }
261
262 #[test]
263 fn custom_backup_algorithm_serialize_roundtrip() {
264 let json = json!({
265 "algorithm": "local.dev.unknown_algorithm",
266 "auth_data": {
267 "foo": "bar",
268 "signatures": {
269 "ed25519:DEVICEID": "signature",
270 },
271 },
272 });
273
274 assert_matches!(from_json_value::<BackupAlgorithm>(json.clone()), Ok(backup_algorithm));
275 assert_eq!(backup_algorithm.algorithm(), "local.dev.unknown_algorithm");
276 assert_matches!(backup_algorithm.auth_data(), Cow::Borrowed(auth_data));
277
278 assert_matches!(auth_data.get("foo"), Some(JsonValue::String(foo)));
279 assert_eq!(foo, "bar");
280 assert_matches!(auth_data.get("signatures"), Some(JsonValue::Object(signatures)));
281 assert_matches!(signatures.get("ed25519:DEVICEID"), Some(JsonValue::String(signature)));
282 assert_eq!(signature, "signature");
283
284 assert_to_canonical_json_eq!(backup_algorithm, json);
285 }
286}