1use std::collections::BTreeMap;
5use std::sync::Arc;
6
7use serde_json::{json, Value};
8
9use super::{with_conn_pub, Homeserver};
10use crate::error::MatrixError;
11use crate::fed_rooms as fr;
12use crate::federation::{self as fed, enc};
13
14fn internal<E>(_: E) -> MatrixError {
15 MatrixError::internal()
16}
17
18fn now_rfc3339() -> String {
19 chrono::Utc::now().to_rfc3339()
20}
21
22pub(crate) async fn fed_request(state: &Arc<Homeserver>, dest: &str, method: &str, path: &str, body: Option<Value>) -> Result<(u16, Value), MatrixError> {
24 let transport = state.fed_transport.get().cloned().ok_or_else(|| MatrixError::unknown("federation transport is not configured"))?;
25 let local = crate::store::matrix_server_name();
26 let uri = format!("/_matrix{path}");
27 let (key_id, key) = with_conn_pub(state, |c| fed::active_signing_key(c, fed::now_ms()).map_err(internal)).await?;
28 let auth = fed::build_x_matrix_header(local, dest, &key_id, &key, method, &uri, body.as_ref());
29 transport.request(dest, method, &uri, &auth, body.as_ref()).await.map_err(|e| MatrixError::unknown(format!("federation to {dest}: {e}")))
30}
31
32pub(crate) async fn verify_pdu(state: &Arc<Homeserver>, pdu: &Value) -> Result<bool, MatrixError> {
35 let (signer, key_id) = fr::pdu_signer(pdu).ok_or_else(|| MatrixError::bad_json("pdu is not signed by its sender's server"))?;
36 if crate::store::is_local_server_name(&signer) {
37 let id = pdu.get("event_id").and_then(Value::as_str).unwrap_or_default().to_string();
38 let known = with_conn_pub(state, move |c| Ok(crate::store::get_event(c, &id)?.is_some())).await?;
39 return if known { Ok(true) } else { Err(MatrixError::forbidden("pdu claims to be ours but is unknown")) };
40 }
41 let pk = super::federation::remote_key(state, &signer, &key_id).await?;
42 fr::verify_pdu_with_key(pdu, &signer, &key_id, &pk).map_err(|_| MatrixError::forbidden("bad pdu signature"))
43}
44
45#[cfg(feature = "f3-hash-ids")]
47pub(crate) async fn f3_keys(state: &Arc<Homeserver>, pdus: &[Value]) -> Result<m4a_matrix_core::PublicKeyMap, MatrixError> {
48 let owned = pdus.to_vec();
49 let (mut keys, need) = with_conn_pub(state, move |c| {
50 let mut keys = m4a_matrix_core::PublicKeyMap::new();
51 let refs: Vec<&Value> = owned.iter().collect();
52 let need = crate::f3::missing_keys(c, &refs, &mut keys);
53 Ok((keys, need))
54 })
55 .await?;
56 for (server, key_id) in need {
57 let pk = super::federation::remote_key(state, &server, &key_id).await?;
58 crate::f3::add_key(&mut keys, &server, &key_id, &pk)?;
59 }
60 Ok(keys)
61}
62
63#[cfg(feature = "f3-hash-ids")]
67pub(crate) async fn f3_receive_live(state: &Arc<Homeserver>, origin: &str, pdu: &Value) -> Result<crate::f3::Received, MatrixError> {
68 for _ in 0..3 {
69 let keys = f3_keys(state, std::slice::from_ref(pdu)).await?;
70 let (p, o) = (pdu.clone(), origin.to_string());
71 let res = with_conn_pub(state, move |c| Ok(crate::f3::receive_detail(c, Some(&o), &p, &keys, &now_rfc3339()))).await?;
72 match res {
73 Ok(r) => return Ok(r),
74 Err(crate::f3::RecvErr::Other(e)) => return Err(e),
75 Err(crate::f3::RecvErr::Missing(_)) => f3_fetch_missing(state, origin, pdu).await?,
76 }
77 }
78 Err(MatrixError::forbidden("the event still cites unknown events after fetching"))
79}
80
81#[cfg(feature = "f3-hash-ids")]
83async fn f3_fetch_missing(state: &Arc<Homeserver>, origin: &str, pdu: &Value) -> Result<(), MatrixError> {
84 let room = pdu.get("room_id").and_then(Value::as_str).ok_or_else(|| MatrixError::bad_json("room_id"))?.to_string();
85 let latest = crate::f3::wire_id(pdu)?;
86 let r2 = room.clone();
87 let earliest = with_conn_pub(state, move |c| Ok(crate::f3::extremity_ids(c, &r2))).await?;
88 let body = json!({ "earliest_events": earliest, "latest_events": [latest], "limit": 50, "min_depth": 0 });
89 let (status, resp) = fed_request(state, origin, "POST", &format!("/federation/v1/get_missing_events/{}", enc(&room)), Some(body)).await?;
90 if status != 200 {
91 return Err(map_status(status, "get_missing_events"));
92 }
93 let events = resp.get("events").and_then(Value::as_array).cloned().unwrap_or_default();
94 if events.is_empty() {
95 return Err(MatrixError::forbidden("the origin returned no missing events"));
96 }
97 f3_store_history(state, &room, events).await.map(|_| ())
98}
99
100#[cfg(feature = "f3-hash-ids")]
102pub(crate) async fn f3_store_history(state: &Arc<Homeserver>, room: &str, events: Vec<Value>) -> Result<usize, MatrixError> {
103 let keys = f3_keys(state, &events).await?;
104 let room = room.to_string();
105 with_conn_pub(state, move |c| crate::f3::process_historic(c, &room, &events, &keys, &now_rfc3339())).await
106}
107
108fn backoff_ms(attempts: i64) -> i64 {
109 (1000i64 << attempts.clamp(0, 10)).min(600_000)
110}
111
112struct Row {
113 id: i64,
114 kind: String,
115 room_id: String,
116 event_id: String,
117 payload: Value,
118 attempts: i64,
119}
120
121pub async fn drain_outbox(state: &Arc<Homeserver>) -> usize {
123 if state.federation_enabled.get().is_none() || state.fed_transport.get().is_none() {
124 return 0;
125 }
126 let local = crate::store::matrix_server_name().to_string();
127 let now = fed::now_ms();
128 let l2 = local.clone();
129 let rows = with_conn_pub(state, move |c| {
130 if let Err(e) = fr::export_local_events(c, &l2, now) {
131 tracing::warn!("federation export: {e}");
132 }
133 let mut stmt = c
134 .prepare("SELECT id, destination, kind, room_id, event_id, payload, attempts FROM fed_outbox WHERE next_try_ms <= ?1 ORDER BY id LIMIT 300")
135 .map_err(internal)?;
136 let it = stmt
137 .query_map([now], |r| {
138 Ok((
139 r.get::<_, String>(1)?,
140 Row { id: r.get(0)?, kind: r.get(2)?, room_id: r.get(3)?, event_id: r.get(4)?, payload: serde_json::from_str(&r.get::<_, String>(5)?).unwrap_or(Value::Null), attempts: r.get(6)? },
141 ))
142 })
143 .map_err(internal)?;
144 it.collect::<Result<Vec<_>, _>>().map_err(internal)
145 })
146 .await
147 .unwrap_or_default();
148 let mut by_dest: BTreeMap<String, Vec<Row>> = BTreeMap::new();
149 for (d, r) in rows {
150 by_dest.entry(d).or_default().push(r);
151 }
152 let mut delivered = 0;
153 for (dest, items) in by_dest {
154 let mut batch: Vec<Row> = Vec::new();
155 let mut failed_from: Option<usize> = None;
156 let mut idx = 0;
157 let total = items.len();
158 let mut items = items.into_iter().peekable();
159 while let Some(item) = items.next() {
160 let is_invite = item.kind == "invite";
161 if is_invite {
162 if !batch.is_empty() {
164 let b = std::mem::take(&mut batch);
165 match send_batch(state, &local, &dest, &b).await {
166 Ok(n) => delivered += n,
167 Err(_) => {
168 mark_failed(state, &b).await;
169 mark_failed(state, std::slice::from_ref(&item)).await;
170 failed_from = Some(idx);
171 break;
172 }
173 }
174 }
175 #[allow(unused_mut)]
176 let mut body = json!({ "room_version": "11", "event": item.payload["event"], "room_info": item.payload["room_info"], "invite_room_state": item.payload["state"] });
177 if let Some(snap) = item.payload.get("f3") {
178 body["f3"] = snap.clone();
179 }
180 let path = format!("/federation/v2/invite/{}/{}", enc(&item.room_id), enc(&item.event_id));
181 match fed_request(state, &dest, "PUT", &path, Some(body)).await {
182 Ok((200, _)) | Ok((403, _)) => {
183 delete_rows(state, &[item.id]).await;
184 delivered += 1;
185 }
186 _ => {
187 mark_failed(state, std::slice::from_ref(&item)).await;
188 failed_from = Some(idx);
189 break;
190 }
191 }
192 } else {
193 batch.push(item);
194 if batch.len() >= 20 || items.peek().is_none_or(|n| n.kind == "invite") {
195 let b = std::mem::take(&mut batch);
196 match send_batch(state, &local, &dest, &b).await {
197 Ok(n) => delivered += n,
198 Err(_) => {
199 mark_failed(state, &b).await;
200 failed_from = Some(idx);
201 break;
202 }
203 }
204 }
205 }
206 idx += 1;
207 }
208 let _ = (failed_from, total);
209 }
210 delivered
211}
212
213async fn delete_rows(state: &Arc<Homeserver>, ids: &[i64]) {
214 let ids = ids.to_vec();
215 let _ = with_conn_pub(state, move |c| {
216 for id in ids {
217 c.execute("DELETE FROM fed_outbox WHERE id = ?1", [id]).map_err(internal)?;
218 }
219 Ok(())
220 })
221 .await;
222}
223
224async fn mark_failed(state: &Arc<Homeserver>, rows: &[Row]) {
225 let now = fed::now_ms();
226 let data: Vec<(i64, i64)> = rows.iter().map(|r| (r.id, r.attempts)).collect();
227 let _ = with_conn_pub(state, move |c| {
228 for (id, attempts) in data {
229 c.execute("UPDATE fed_outbox SET attempts = attempts + 1, next_try_ms = ?2 WHERE id = ?1", rusqlite::params![id, now + backoff_ms(attempts)]).map_err(internal)?;
230 }
231 Ok(())
232 })
233 .await;
234}
235
236async fn send_batch(state: &Arc<Homeserver>, local: &str, dest: &str, rows: &[Row]) -> Result<usize, MatrixError> {
237 let (mut pdus, mut edus) = (Vec::new(), Vec::new());
238 for r in rows {
239 if r.kind == "edu" {
240 edus.push(r.payload.clone());
241 } else {
242 pdus.push(r.payload.clone());
243 }
244 }
245 let txn = format!("m4a{}", rows[0].id);
246 let body = json!({ "origin": local, "origin_server_ts": fed::now_ms(), "pdus": pdus, "edus": edus });
247 let (status, _) = fed_request(state, dest, "PUT", &format!("/federation/v1/send/{}", enc(&txn)), Some(body)).await?;
248 if status != 200 {
249 return Err(MatrixError::unknown(format!("destination answered {status}")));
250 }
251 delete_rows(state, &rows.iter().map(|r| r.id).collect::<Vec<_>>()).await;
252 Ok(rows.len())
253}
254
255pub fn spawn_outbox_worker(state: Arc<Homeserver>) {
257 tokio::spawn(async move {
258 loop {
259 drain_outbox(&state).await;
260 let _ = tokio::time::timeout(std::time::Duration::from_secs(3), state.fed_notify.notified()).await;
261 }
262 });
263}
264
265pub(crate) fn room_domain_is_remote(room_id: &str) -> bool {
269 room_id.split_once(':').is_some_and(|(_, d)| !crate::store::is_local_server_name(d))
270}
271
272fn map_status(status: u16, what: &str) -> MatrixError {
273 match status {
274 403 => MatrixError::forbidden(format!("{what}: refused by the remote server")),
275 404 => MatrixError::not_found(format!("{what}: not found on the remote server")),
276 _ => MatrixError::unknown(format!("{what}: remote server answered {status}")),
277 }
278}
279
280pub(crate) async fn federated_join(state: &Arc<Homeserver>, caller: &super::Caller, room_id: &str) -> Result<Vec<i64>, MatrixError> {
282 let dest = room_id.split_once(':').map(|(_, d)| d.to_string()).ok_or_else(|| MatrixError::invalid_param("room id"))?;
283 let local = crate::store::matrix_server_name().to_string();
284 let (status, tmpl) = fed_request(state, &dest, "GET", &format!("/federation/v1/make_join/{}/{}{}", enc(room_id), enc(&caller.mxid), if cfg!(feature = "f3-hash-ids") { "?ver=11" } else { "" }), None).await?;
285 if status != 200 {
286 return Err(map_status(status, "make_join"));
287 }
288 #[cfg(feature = "f3-hash-ids")]
289 if tmpl.get("m4a_f3").and_then(Value::as_bool) == Some(true) {
290 return federated_join_f3(state, caller, room_id, &dest, &tmpl).await;
291 }
292 #[cfg(feature = "f3-hash-ids")]
293 if tmpl.get("room_version").and_then(Value::as_str).is_some() && tmpl.pointer("/event/prev_events").is_some() && tmpl.get("m4a_f3").is_none() {
294 return federated_join_spec(state, caller, room_id, &dest, &tmpl).await;
295 }
296 let mut ev = tmpl.get("event").and_then(Value::as_object).cloned().ok_or_else(|| MatrixError::unknown("make_join: no event"))?;
297 let uid = caller.user_id;
298 let label = with_conn_pub(state, move |c| crate::nick::effective_label(c, uid).map_err(internal)).await?;
299 let event_id = crate::store::new_event_id();
300 ev.insert("event_id".into(), json!(event_id));
301 ev.insert("origin".into(), json!(local));
302 let mut content = ev.get("content").cloned().unwrap_or_else(|| json!({}));
303 if !label.is_empty() {
304 content["displayname"] = json!(label);
305 }
306 content["membership"] = json!("join");
307 ev.insert("content".into(), content);
308 if ev.get("sender").and_then(Value::as_str) != Some(caller.mxid.as_str()) || ev.get("room_id").and_then(Value::as_str) != Some(room_id) {
309 return Err(MatrixError::unknown("make_join: template does not match the request"));
310 }
311 let l2 = local.clone();
312 let pdu = with_conn_pub(state, move |c| fr::finalize_pdu(c, ev, &l2, fed::now_ms()).map_err(internal)).await?;
313 let (status, resp) = fed_request(state, &dest, "PUT", &format!("/federation/v2/send_join/{}/{}", enc(room_id), enc(&event_id)), Some(pdu.clone())).await?;
314 if status != 200 {
315 return Err(map_status(status, "send_join"));
316 }
317 let list = |k: &str| resp.get(k).and_then(Value::as_array).cloned().unwrap_or_default();
318 let (state_pdus, timeline) = (list("state"), list("m4a_timeline"));
319 let info = resp.get("m4a_room").cloned().ok_or_else(|| MatrixError::unknown("send_join: no room info"))?;
320 let mut verified: Vec<(Value, bool)> = Vec::new();
321 for p in state_pdus.iter().chain(timeline.iter()) {
322 if p.get("event_id").and_then(Value::as_str) == Some(event_id.as_str()) {
323 continue; }
325 let ok = verify_pdu(state, p).await?;
326 verified.push((p.clone(), ok));
327 }
328 let room = room_id.to_string();
329 let (mxid, user_id) = (caller.mxid.clone(), caller.user_id);
330 let ids = with_conn_pub(state, move |c| {
331 let now = now_rfc3339();
332 fr::create_replica_room(c, &room, &info, &now)?;
333 for (p, ok) in &verified {
334 fr::ingest_pdu(c, p, *ok, fr::Mode::Trusted, &now)?;
335 }
336 fr::store_own_pdu(c, &pdu, user_id, &mxid, &now)?;
337 let ids = crate::rooms::member_and_invited_ids(c, &room).map_err(internal)?;
338 Ok(ids.into_iter().collect::<Vec<_>>())
339 })
340 .await?;
341 Ok(ids)
342}
343
344#[cfg(feature = "f3-hash-ids")]
347async fn federated_join_f3(state: &Arc<Homeserver>, caller: &super::Caller, room_id: &str, dest: &str, tmpl: &Value) -> Result<Vec<i64>, MatrixError> {
348 let mut ev = tmpl.get("event").cloned().ok_or_else(|| MatrixError::unknown("make_join: no event"))?;
349 if ev.get("sender").and_then(Value::as_str) != Some(caller.mxid.as_str()) || ev.get("room_id").and_then(Value::as_str) != Some(room_id) {
350 return Err(MatrixError::unknown("make_join: template does not match the request"));
351 }
352 let uid = caller.user_id;
353 let label = with_conn_pub(state, move |c| crate::nick::effective_label(c, uid).map_err(internal)).await?;
354 if !label.is_empty() {
355 ev["content"]["displayname"] = json!(label);
356 }
357 let (event_id, pdu) = with_conn_pub(state, move |c| crate::f3::sign_own(c, &ev, fed::now_ms())).await?;
358 let (status, resp) = fed_request(state, dest, "PUT", &format!("/federation/v2/send_join/{}/{}", enc(room_id), enc(&event_id)), Some(pdu)).await?;
359 if status != 200 {
360 return Err(map_status(status, "send_join"));
361 }
362 let snap = resp.get("m4a_f3").cloned().ok_or_else(|| MatrixError::unknown("send_join: no room snapshot"))?;
363 let info = resp.get("m4a_room").cloned().ok_or_else(|| MatrixError::unknown("send_join: no room info"))?;
364 let all = crate::f3::snapshot_events(&snap);
365 let keys = f3_keys(state, &all).await?;
366 let room = room_id.to_string();
367 let shared = matches!(info.get("history_visibility").and_then(Value::as_str), Some("shared") | Some("world_readable"));
368 let ids = with_conn_pub(state, move |c| {
369 crate::f3::import_snapshot(c, &room, &info, &snap, &keys, &now_rfc3339())?;
370 let ids = crate::rooms::member_and_invited_ids(c, &room).map_err(internal)?;
371 Ok(ids.into_iter().collect::<Vec<_>>())
372 })
373 .await?;
374 if shared {
376 let q = format!("/federation/v1/backfill/{}?v={}&limit=100", enc(room_id), enc(&event_id));
377 match fed_request(state, dest, "GET", &q, None).await {
378 Ok((200, resp)) => {
379 let events = resp.get("pdus").and_then(Value::as_array).cloned().unwrap_or_default();
380 if let Err(e) = f3_store_history(state, room_id, events).await {
381 tracing::warn!("backfill after join: {}", e.error);
382 }
383 }
384 other => tracing::warn!("backfill after join: {:?}", other.map(|x| x.0)),
385 }
386 }
387 Ok(ids)
388}
389
390#[cfg(feature = "f3-hash-ids")]
394async fn federated_join_spec(state: &Arc<Homeserver>, caller: &super::Caller, room_id: &str, dest: &str, tmpl: &Value) -> Result<Vec<i64>, MatrixError> {
395 if tmpl.get("room_version").and_then(Value::as_str) != Some("11") {
396 return Err(MatrixError::new(400, "M_INCOMPATIBLE_ROOM_VERSION", "only room version 11 can be joined over federation"));
397 }
398 let t = tmpl.get("event").cloned().unwrap_or_default();
399 if t.get("sender").and_then(Value::as_str) != Some(caller.mxid.as_str()) || t.get("room_id").and_then(Value::as_str) != Some(room_id) {
400 return Err(MatrixError::unknown("make_join: template does not match the request"));
401 }
402 let uid = caller.user_id;
403 let label = with_conn_pub(state, move |c| crate::nick::effective_label(c, uid).map_err(internal)).await?;
404 let mut content = t.get("content").cloned().unwrap_or_else(|| json!({}));
405 content["membership"] = json!("join");
406 if !label.is_empty() {
407 content["displayname"] = json!(label);
408 }
409 let mut ev = json!({ "content": content, "origin_server_ts": fed::now_ms() });
410 for k in ["room_id", "sender", "type", "state_key", "prev_events", "auth_events", "depth"] {
411 if let Some(v) = t.get(k) {
412 ev[k] = v.clone();
413 }
414 }
415 let (event_id, pdu) = with_conn_pub(state, move |c| crate::f3::sign_own(c, &ev, fed::now_ms())).await?;
416 let (status, resp) = fed_request(state, dest, "PUT", &format!("/federation/v2/send_join/{}/{}", enc(room_id), enc(&event_id)), Some(pdu.clone())).await?;
417 if status != 200 {
418 return Err(map_status(status, "send_join"));
419 }
420 let list = |k: &str| resp.get(k).and_then(Value::as_array).cloned().unwrap_or_default();
421 let st = list("state");
422 if resp.get("members_omitted").and_then(Value::as_bool) == Some(true) {
423 return Err(MatrixError::unknown("send_join: partial state is not supported"));
424 }
425 let find = |ty: &str| st.iter().find(|e| e.get("type").and_then(Value::as_str) == Some(ty) && e.get("state_key").and_then(Value::as_str) == Some("")).cloned();
426 let content_of = |ty: &str, k: &str| find(ty).and_then(|e| e.pointer(&format!("/content/{k}")).and_then(Value::as_str).map(str::to_string));
427 let info = json!({
428 "kind": "group",
429 "is_encrypted": find("m.room.encryption").is_some(),
430 "join_rule": content_of("m.room.join_rules", "join_rule").unwrap_or_else(|| "invite".into()),
431 "history_visibility": content_of("m.room.history_visibility", "history_visibility").unwrap_or_else(|| "shared".into()),
432 "room_version": "11",
433 "creator": find("m.room.create").and_then(|e| e.get("sender").and_then(Value::as_str).map(str::to_string)).unwrap_or_default(),
434 });
435 let mut state_v = st.clone();
436 state_v.push(pdu.clone());
437 let snap = json!({ "state": state_v, "extremities": [pdu], "auth_chain": list("auth_chain") });
438 let all = crate::f3::snapshot_events(&snap);
439 let keys = f3_keys(state, &all).await?;
440 let room = room_id.to_string();
441 let shared = matches!(info["history_visibility"].as_str(), Some("shared") | Some("world_readable"));
442 let ids = with_conn_pub(state, move |c| {
443 crate::f3::import_snapshot(c, &room, &info, &snap, &keys, &now_rfc3339())?;
444 let ids = crate::rooms::member_and_invited_ids(c, &room).map_err(internal)?;
445 Ok(ids.into_iter().collect::<Vec<_>>())
446 })
447 .await?;
448 if shared {
449 let q = format!("/federation/v1/backfill/{}?v={}&limit=100", enc(room_id), enc(&event_id));
450 if let Ok((200, resp)) = fed_request(state, dest, "GET", &q, None).await {
451 let events = resp.get("pdus").and_then(Value::as_array).cloned().unwrap_or_default();
452 if let Err(e) = f3_store_history(state, room_id, events).await {
453 tracing::warn!("backfill after join: {}", e.error);
454 }
455 }
456 }
457 Ok(ids)
458}
459
460pub(crate) fn split_by_domain<V: Clone>(map: &BTreeMap<String, V>) -> (BTreeMap<String, V>, BTreeMap<String, BTreeMap<String, V>>) {
464 let (mut local, mut remote): (BTreeMap<String, V>, BTreeMap<String, BTreeMap<String, V>>) = (BTreeMap::new(), BTreeMap::new());
465 for (k, v) in map {
466 if fr::is_remote_mxid(k) {
467 if let Some(d) = fr::domain_of(k) {
468 remote.entry(d.to_string()).or_default().insert(k.clone(), v.clone());
469 }
470 } else {
471 local.insert(k.clone(), v.clone());
472 }
473 }
474 (local, remote)
475}
476
477pub(crate) async fn merge_remote_keys_query(state: &Arc<Homeserver>, remote: BTreeMap<String, BTreeMap<String, Vec<String>>>, out: &mut Value) {
479 for (domain, users) in remote {
480 let body = json!({ "device_keys": users });
481 match fed_request(state, &domain, "POST", "/federation/v1/user/keys/query", Some(body)).await {
482 Ok((200, resp)) => {
483 for k in ["device_keys", "master_keys", "self_signing_keys"] {
484 if let (Some(dst), Some(src)) = (out[k].as_object_mut(), resp.get(k).and_then(Value::as_object)) {
485 for (u, v) in src {
486 dst.insert(u.clone(), v.clone());
487 }
488 }
489 }
490 }
491 _ => {
492 out["failures"][&domain] = json!({ "status": 502, "errcode": "M_UNREACHABLE" });
493 }
494 }
495 }
496}
497
498pub(crate) async fn merge_remote_keys_claim(state: &Arc<Homeserver>, remote: BTreeMap<String, BTreeMap<String, BTreeMap<String, String>>>, out: &mut Value) {
500 for (domain, users) in remote {
501 let body = json!({ "one_time_keys": users });
502 match fed_request(state, &domain, "POST", "/federation/v1/user/keys/claim", Some(body)).await {
503 Ok((200, resp)) => {
504 if let (Some(dst), Some(src)) = (out["one_time_keys"].as_object_mut(), resp.get("one_time_keys").and_then(Value::as_object)) {
505 for (u, v) in src {
506 dst.insert(u.clone(), v.clone());
507 }
508 }
509 }
510 _ => {
511 out["failures"][&domain] = json!({ "status": 502, "errcode": "M_UNREACHABLE" });
512 }
513 }
514 }
515}
516
517pub(crate) fn enqueue_to_device_edu(conn: &rusqlite::Connection, domain: &str, sender: &str, event_type: &str, message_id: &str, messages: &Value) -> Result<(), MatrixError> {
519 let edu = json!({ "edu_type": "m.direct_to_device", "content": { "sender": sender, "type": event_type, "message_id": message_id, "messages": messages } });
520 fr::enqueue(conn, domain, "edu", "", "", &edu, fed::now_ms()).map_err(internal)
521}