1use std::cmp::Ordering;
2
3use axum::extract::State;
4use futures::{FutureExt, future::try_join};
5use ruma::{
6 UInt, UserId,
7 api::client::backup::{
8 add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session,
9 create_backup_version, delete_backup_keys, delete_backup_keys_for_room,
10 delete_backup_keys_for_session, delete_backup_version, get_backup_info, get_backup_keys,
11 get_backup_keys_for_room, get_backup_keys_for_session, get_latest_backup_info,
12 update_backup_version,
13 },
14 serde::Raw,
15};
16use serde::Deserialize;
17use serde_json::value::RawValue as RawJsonValue;
18use tuwunel_core::{Err, Result, err};
19use tuwunel_service::Services;
20
21use crate::Ruma;
22
23#[derive(Deserialize)]
27#[expect(unused)]
28struct AlgorithmShape {
29 algorithm: Box<RawJsonValue>,
30 auth_data: Box<RawJsonValue>,
31}
32
33fn validate_algorithm_shape<T>(raw: &Raw<T>) -> Result {
34 raw.deserialize_as_unchecked::<AlgorithmShape>()
35 .map_err(Into::into)
36 .map(drop)
37}
38
39pub(crate) async fn create_backup_version_route(
43 State(services): State<crate::State>,
44 body: Ruma<create_backup_version::v3::Request>,
45) -> Result<create_backup_version::v3::Response> {
46 validate_algorithm_shape(&body.algorithm)
47 .map_err(|e| err!(Request(BadJson("Invalid backup metadata: {e}"))))?;
48
49 let version = services
50 .key_backups
51 .create_backup(body.sender_user(), &body.algorithm)?;
52
53 Ok(create_backup_version::v3::Response { version })
54}
55
56pub(crate) async fn update_backup_version_route(
61 State(services): State<crate::State>,
62 body: Ruma<update_backup_version::v3::Request>,
63) -> Result<update_backup_version::v3::Response> {
64 validate_algorithm_shape(&body.algorithm)
65 .map_err(|e| err!(Request(BadJson("Invalid backup metadata: {e}"))))?;
66
67 services
68 .key_backups
69 .update_backup(body.sender_user(), &body.version, &body.algorithm)
70 .await?;
71
72 Ok(update_backup_version::v3::Response {})
73}
74
75pub(crate) async fn get_latest_backup_info_route(
79 State(services): State<crate::State>,
80 body: Ruma<get_latest_backup_info::v3::Request>,
81) -> Result<get_latest_backup_info::v3::Response> {
82 let (version, algorithm) = services
83 .key_backups
84 .get_latest_backup(body.sender_user())
85 .await
86 .map_err(|_| err!(Request(NotFound("Key backup does not exist."))))?;
87
88 validate_algorithm_shape(&algorithm)
89 .map_err(|e| err!(Request(NotFound("Key backup does not exist: {e}"))))?;
90
91 let (count, etag) = get_count_etag(&services, body.sender_user(), &version).await?;
92
93 Ok(get_latest_backup_info::v3::Response { algorithm, count, etag, version })
94}
95
96pub(crate) async fn get_backup_info_route(
100 State(services): State<crate::State>,
101 body: Ruma<get_backup_info::v3::Request>,
102) -> Result<get_backup_info::v3::Response> {
103 let algorithm = services
104 .key_backups
105 .get_backup(body.sender_user(), &body.version)
106 .await
107 .map_err(|_| {
108 err!(Request(NotFound("Key backup does not exist at version {:?}", body.version)))
109 })?;
110
111 validate_algorithm_shape(&algorithm).map_err(|e| {
112 err!(Request(NotFound(
113 "Key backup does not exist at version {:?}: {e}",
114 body.version
115 )))
116 })?;
117
118 let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
119
120 Ok(get_backup_info::v3::Response {
121 algorithm,
122 count,
123 etag,
124 version: body.version.clone(),
125 })
126}
127
128pub(crate) async fn delete_backup_version_route(
135 State(services): State<crate::State>,
136 body: Ruma<delete_backup_version::v3::Request>,
137) -> Result<delete_backup_version::v3::Response> {
138 services
139 .key_backups
140 .delete_backup(body.sender_user(), &body.version)
141 .await;
142
143 Ok(delete_backup_version::v3::Response {})
144}
145
146pub(crate) async fn add_backup_keys_route(
155 State(services): State<crate::State>,
156 body: Ruma<add_backup_keys::v3::Request>,
157) -> Result<add_backup_keys::v3::Response> {
158 if services
159 .key_backups
160 .get_latest_backup_version(body.sender_user())
161 .await
162 .is_ok_and(|version| version != body.version)
163 {
164 return Err!(Request(InvalidParam(
165 "You may only manipulate the most recently created version of the backup."
166 )));
167 }
168
169 for (room_id, room) in &body.rooms {
170 for (session_id, key_data) in &room.sessions {
171 services
172 .key_backups
173 .add_key(body.sender_user(), &body.version, room_id, session_id, key_data)
174 .await?;
175 }
176 }
177
178 let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
179
180 Ok(add_backup_keys::v3::Response { count, etag })
181}
182
183pub(crate) async fn add_backup_keys_for_room_route(
192 State(services): State<crate::State>,
193 body: Ruma<add_backup_keys_for_room::v3::Request>,
194) -> Result<add_backup_keys_for_room::v3::Response> {
195 if services
196 .key_backups
197 .get_latest_backup_version(body.sender_user())
198 .await
199 .is_ok_and(|version| version != body.version)
200 {
201 return Err!(Request(InvalidParam(
202 "You may only manipulate the most recently created version of the backup."
203 )));
204 }
205
206 for (session_id, key_data) in &body.sessions {
207 services
208 .key_backups
209 .add_key(body.sender_user(), &body.version, &body.room_id, session_id, key_data)
210 .await?;
211 }
212
213 let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
214
215 Ok(add_backup_keys_for_room::v3::Response { count, etag })
216}
217
218pub(crate) async fn add_backup_keys_for_session_route(
227 State(services): State<crate::State>,
228 body: Ruma<add_backup_keys_for_session::v3::Request>,
229) -> Result<add_backup_keys_for_session::v3::Response> {
230 if services
231 .key_backups
232 .get_latest_backup_version(body.sender_user())
233 .await
234 .is_ok_and(|version| version != body.version)
235 {
236 return Err!(Request(InvalidParam(
237 "You may only manipulate the most recently created version of the backup."
238 )));
239 }
240
241 let mut ok_to_replace = true;
243 if let Some(old_key) = &services
244 .key_backups
245 .get_session(body.sender_user(), &body.version, &body.room_id, &body.session_id)
246 .await
247 .ok()
248 {
249 let old_is_verified = old_key
250 .get_field::<bool>("is_verified")?
251 .unwrap_or_default();
252
253 let new_is_verified = body
254 .session_data
255 .get_field::<bool>("is_verified")?
256 .ok_or_else(|| err!(Request(BadJson("`is_verified` field should exist"))))?;
257
258 if old_is_verified != new_is_verified {
260 if old_is_verified {
261 ok_to_replace = false;
262 }
263 } else {
264 let old_first_message_index = old_key
267 .get_field::<UInt>("first_message_index")?
268 .unwrap_or(UInt::MAX);
269
270 let new_first_message_index = body
271 .session_data
272 .get_field::<UInt>("first_message_index")?
273 .ok_or_else(|| {
274 err!(Request(BadJson("`first_message_index` field should exist")))
275 })?;
276
277 ok_to_replace = match new_first_message_index.cmp(&old_first_message_index) {
278 | Ordering::Less => true,
279 | Ordering::Greater => false,
280 | Ordering::Equal => {
281 let old_forwarded_count = old_key
284 .get_field::<UInt>("forwarded_count")?
285 .unwrap_or(UInt::MAX);
286
287 let new_forwarded_count = body
288 .session_data
289 .get_field::<UInt>("forwarded_count")?
290 .ok_or_else(|| {
291 err!(Request(BadJson("`forwarded_count` field should exist")))
292 })?;
293
294 new_forwarded_count < old_forwarded_count
295 },
296 };
297 }
298 }
299
300 if ok_to_replace {
301 services
302 .key_backups
303 .add_key(
304 body.sender_user(),
305 &body.version,
306 &body.room_id,
307 &body.session_id,
308 &body.session_data,
309 )
310 .await?;
311 }
312
313 let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
314
315 Ok(add_backup_keys_for_session::v3::Response { count, etag })
316}
317
318pub(crate) async fn get_backup_keys_route(
322 State(services): State<crate::State>,
323 body: Ruma<get_backup_keys::v3::Request>,
324) -> Result<get_backup_keys::v3::Response> {
325 let rooms = services
326 .key_backups
327 .get_all(body.sender_user(), &body.version)
328 .await;
329
330 Ok(get_backup_keys::v3::Response { rooms })
331}
332
333pub(crate) async fn get_backup_keys_for_room_route(
337 State(services): State<crate::State>,
338 body: Ruma<get_backup_keys_for_room::v3::Request>,
339) -> Result<get_backup_keys_for_room::v3::Response> {
340 let sessions = services
341 .key_backups
342 .get_room(body.sender_user(), &body.version, &body.room_id)
343 .await;
344
345 Ok(get_backup_keys_for_room::v3::Response { sessions })
346}
347
348pub(crate) async fn get_backup_keys_for_session_route(
352 State(services): State<crate::State>,
353 body: Ruma<get_backup_keys_for_session::v3::Request>,
354) -> Result<get_backup_keys_for_session::v3::Response> {
355 let key_data = services
356 .key_backups
357 .get_session(body.sender_user(), &body.version, &body.room_id, &body.session_id)
358 .await
359 .map_err(|_| {
360 err!(Request(NotFound(debug_error!("Backup key not found for this user's session."))))
361 })?;
362
363 Ok(get_backup_keys_for_session::v3::Response { key_data })
364}
365
366pub(crate) async fn delete_backup_keys_route(
370 State(services): State<crate::State>,
371 body: Ruma<delete_backup_keys::v3::Request>,
372) -> Result<delete_backup_keys::v3::Response> {
373 services
374 .key_backups
375 .delete_all_keys(body.sender_user(), &body.version)
376 .await;
377
378 let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
379
380 Ok(delete_backup_keys::v3::Response { count, etag })
381}
382
383pub(crate) async fn delete_backup_keys_for_room_route(
387 State(services): State<crate::State>,
388 body: Ruma<delete_backup_keys_for_room::v3::Request>,
389) -> Result<delete_backup_keys_for_room::v3::Response> {
390 services
391 .key_backups
392 .delete_room_keys(body.sender_user(), &body.version, &body.room_id)
393 .await;
394
395 let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
396
397 Ok(delete_backup_keys_for_room::v3::Response { count, etag })
398}
399
400pub(crate) async fn delete_backup_keys_for_session_route(
404 State(services): State<crate::State>,
405 body: Ruma<delete_backup_keys_for_session::v3::Request>,
406) -> Result<delete_backup_keys_for_session::v3::Response> {
407 services
408 .key_backups
409 .delete_room_key(body.sender_user(), &body.version, &body.room_id, &body.session_id)
410 .await;
411
412 let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
413
414 Ok(delete_backup_keys_for_session::v3::Response { count, etag })
415}
416
417async fn get_count_etag(
418 services: &Services,
419 sender_user: &UserId,
420 version: &str,
421) -> Result<(UInt, String)> {
422 let count = services
423 .key_backups
424 .count_keys(sender_user, version)
425 .map(TryInto::try_into);
426
427 let etag = services
428 .key_backups
429 .get_etag(sender_user, version)
430 .map(Ok);
431
432 Ok(try_join(count, etag).await?)
433}