palpo_core/federation/
transaction.rs

1/// Endpoints for exchanging transaction messages between homeservers.
2
3/// `PUT /_matrix/federation/*/send/{txn_id}`
4///
5/// Send live activity messages to another server.
6/// `/v1/` ([spec])
7///
8/// [spec]: https://spec.matrix.org/latest/server-server-api/#put_matrixfederationv1sendtxnid
9use std::collections::BTreeMap;
10
11use reqwest::Url;
12use salvo::prelude::*;
13use serde::{Deserialize, Serialize, de};
14
15use crate::device::{DeviceListUpdateContent, DirectDeviceContent};
16use crate::encryption::CrossSigningKey;
17use crate::events::receipt::{Receipt, ReceiptContent};
18use crate::events::typing::TypingContent;
19use crate::identifiers::*;
20use crate::presence::PresenceContent;
21use crate::sending::{SendRequest, SendResult};
22use crate::serde::{JsonValue, RawJsonValue, from_raw_json_value};
23use crate::{OwnedServerName, UnixMillis};
24
25// const METADATA: Metadata = metadata! {
26//     method: PUT,
27//     rate_limited: false,
28//     authentication: ServerSignatures,
29//     history: {
30//         1.0 => "/_matrix/federation/v1/send/:transaction_id",
31//     }
32// };
33
34pub fn send_messages_request(origin: &str, txn_id: &str, body: SendMessageReqBody) -> SendResult<SendRequest> {
35    let url = Url::parse(&format!("{origin}/_matrix/federation/v1/send/{txn_id}"))?;
36    crate::sending::put(url).stuff(body)
37}
38
39/// Request type for the `send_transaction_message` endpoint.
40
41#[derive(ToSchema, Deserialize, Serialize, Debug)]
42pub struct SendMessageReqBody {
43    // /// A transaction ID unique between sending and receiving homeservers.
44    // #[salvo(parameter(parameter_in = Path))]
45    // pub transaction_id: OwnedTransactionId,
46    /// The server_name of the homeserver sending this transaction.
47    pub origin: OwnedServerName,
48
49    /// POSIX timestamp in milliseconds on the originating homeserver when this transaction
50    /// started.
51    pub origin_server_ts: UnixMillis,
52
53    /// List of persistent updates to rooms.
54    ///
55    /// Must not be more than 50 items.
56    ///
57    /// With the `unstable-unspecified` feature, sending `pdus` is optional.
58    /// See [matrix-spec#705](https://github.com/matrix-org/matrix-spec/issues/705).
59    #[cfg_attr(
60        feature = "unstable-unspecified",
61        serde(default, skip_serializing_if = "<[_]>::is_empty")
62    )]
63    #[salvo(schema(value_type = Vec<Object>))]
64    pub pdus: Vec<Box<RawJsonValue>>,
65
66    /// List of ephemeral messages.
67    ///
68    /// Must not be more than 100 items.
69    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
70    pub edus: Vec<Edu>,
71}
72crate::json_body_modifier!(SendMessageReqBody);
73
74/// Response type for the `send_transaction_message` endpoint.
75#[derive(ToSchema, Serialize, Deserialize, Debug)]
76
77pub struct SendMessageResBody {
78    /// Map of event IDs and response for each PDU given in the request.
79    ///
80    /// With the `unstable-msc3618` feature, returning `pdus` is optional.
81    /// See [MSC3618](https://github.com/matrix-org/matrix-spec-proposals/pull/3618).
82    #[serde(default, with = "crate::serde::pdu_process_response")]
83    pub pdus: BTreeMap<OwnedEventId, Result<(), String>>,
84}
85crate::json_body_modifier!(SendMessageResBody);
86
87impl SendMessageResBody {
88    /// Creates a new `Response` with the given PDUs.
89    pub fn new(pdus: BTreeMap<OwnedEventId, Result<(), String>>) -> Self {
90        Self { pdus }
91    }
92}
93
94/// Type for passing ephemeral data to homeservers.
95#[derive(ToSchema, Clone, Debug, Serialize)]
96#[serde(tag = "edu_type", content = "content")]
97pub enum Edu {
98    /// An EDU representing presence updates for users of the sending homeserver.
99    #[serde(rename = "m.presence")]
100    Presence(PresenceContent),
101
102    /// An EDU representing receipt updates for users of the sending homeserver.
103    #[serde(rename = "m.receipt")]
104    Receipt(ReceiptContent),
105
106    /// A typing notification EDU for a user in a room.
107    #[serde(rename = "m.typing")]
108    Typing(TypingContent),
109
110    /// An EDU that lets servers push details to each other when one of their users adds
111    /// a new device to their account, required for E2E encryption to correctly target the
112    /// current set of devices for a given user.
113    #[serde(rename = "m.device_list_update")]
114    DeviceListUpdate(DeviceListUpdateContent),
115
116    /// An EDU that lets servers push send events directly to a specific device on a
117    /// remote server - for instance, to maintain an Olm E2E encrypted message channel
118    /// between a local and remote device.
119    #[serde(rename = "m.direct_to_device")]
120    DirectToDevice(DirectDeviceContent),
121
122    /// An EDU that lets servers push details to each other when one of their users updates their
123    /// cross-signing keys.
124    #[serde(rename = "m.signing_key_update")]
125    #[salvo(schema(value_type = Object))]
126    SigningKeyUpdate(SigningKeyUpdateContent),
127
128    #[doc(hidden)]
129    #[salvo(schema(value_type = Object))]
130    _Custom(JsonValue),
131}
132
133#[derive(Debug, Deserialize)]
134struct EduDeHelper {
135    /// The message type field
136    edu_type: String,
137    content: Box<RawJsonValue>,
138}
139
140impl<'de> Deserialize<'de> for Edu {
141    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142    where
143        D: de::Deserializer<'de>,
144    {
145        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
146        let EduDeHelper { edu_type, content } = from_raw_json_value(&json)?;
147
148        Ok(match edu_type.as_ref() {
149            "m.presence" => Self::Presence(from_raw_json_value(&content)?),
150            "m.receipt" => Self::Receipt(from_raw_json_value(&content)?),
151            "m.typing" => Self::Typing(from_raw_json_value(&content)?),
152            "m.device_list_update" => Self::DeviceListUpdate(from_raw_json_value(&content)?),
153            "m.direct_to_device" => Self::DirectToDevice(from_raw_json_value(&content)?),
154            "m.signing_key_update" => Self::SigningKeyUpdate(from_raw_json_value(&content)?),
155            _ => Self::_Custom(from_raw_json_value(&content)?),
156        })
157    }
158}
159
160/// Mapping between user and `ReceiptData`.
161#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
162pub struct ReceiptMap {
163    /// Read receipts for users in the room.
164    #[serde(rename = "m.read")]
165    pub read: BTreeMap<OwnedUserId, ReceiptData>,
166}
167
168impl ReceiptMap {
169    /// Creates a new `ReceiptMap`.
170    pub fn new(read: BTreeMap<OwnedUserId, ReceiptData>) -> Self {
171        Self { read }
172    }
173}
174
175/// Metadata about the event that was last read and when.
176#[derive(Clone, Debug, Deserialize, Serialize)]
177pub struct ReceiptData {
178    /// Metadata for the read receipt.
179    pub data: Receipt,
180
181    /// The extremity event ID the user has read up to.
182    pub event_ids: Vec<OwnedEventId>,
183}
184
185impl ReceiptData {
186    /// Creates a new `ReceiptData`.
187    pub fn new(data: Receipt, event_ids: Vec<OwnedEventId>) -> Self {
188        Self { data, event_ids }
189    }
190}
191
192/// The content for an `m.signing_key_update` EDU.
193#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
194pub struct SigningKeyUpdateContent {
195    /// The user ID whose cross-signing keys have changed.
196    pub user_id: OwnedUserId,
197
198    /// The user's master key, if it was updated.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub master_key: Option<CrossSigningKey>,
201
202    /// The users's self-signing key, if it was updated.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub self_signing_key: Option<CrossSigningKey>,
205}
206
207impl SigningKeyUpdateContent {
208    /// Creates a new `SigningKeyUpdateContent`.
209    pub fn new(user_id: OwnedUserId) -> Self {
210        Self {
211            user_id,
212            master_key: None,
213            self_signing_key: None,
214        }
215    }
216}
217
218// #[cfg(test)]
219// mod tests {
220//     use crate::events::ToDeviceEventType;
221//     use crate::{room_id, user_id};
222//     use assert_matches2::assert_matches;
223//     use serde_json::json;
224
225//     use super::{DeviceListUpdateContent, Edu, ReceiptContent};
226
227//     #[test]
228//     fn device_list_update_edu() {
229//         let json = json!({
230//             "content": {
231//                 "deleted": false,
232//                 "device_display_name": "Mobile",
233//                 "device_id": "QBUAZIFURK",
234//                 "keys": {
235//                     "algorithms": [
236//                         "m.olm.v1.curve25519-aes-sha2",
237//                         "m.megolm.v1.aes-sha2"
238//                     ],
239//                     "device_id": "JLAFKJWSCS",
240//                     "keys": {
241//                         "curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI",
242//                         "ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"
243//                     },
244//                     "signatures": {
245//                         "@alice:example.com": {
246//                             "ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA"
247//                         }
248//                     },
249//                     "user_id": "@alice:example.com"
250//                 },
251//                 "stream_id": 6,
252//                 "user_id": "@john:example.com"
253//             },
254//             "edu_type": "m.device_list_update"
255//         });
256
257//         let edu = serde_json::from_value::<Edu>(json.clone()).unwrap();
258//         assert_matches!(
259//             &edu,
260//             Edu::DeviceListUpdate(DeviceListUpdateContent {
261//                 user_id,
262//                 device_id,
263//                 device_display_name,
264//                 stream_id,
265//                 prev_id,
266//                 deleted,
267//                 keys,
268//             })
269//         );
270
271//         assert_eq!(user_id, "@john:example.com");
272//         assert_eq!(device_id, "QBUAZIFURK");
273//         assert_eq!(device_display_name.as_deref(), Some("Mobile"));
274//         assert_eq!(*stream_id, u6);
275//         assert_eq!(*prev_id, vec![]);
276//         assert_eq!(*deleted, Some(false));
277//         assert_matches!(keys, Some(_));
278
279//         assert_eq!(serde_json::to_value(&edu).unwrap(), json);
280//     }
281
282//     #[test]
283//     fn minimal_device_list_update_edu() {
284//         let json = json!({
285//             "content": {
286//                 "device_id": "QBUAZIFURK",
287//                 "stream_id": 6,
288//                 "user_id": "@john:example.com"
289//             },
290//             "edu_type": "m.device_list_update"
291//         });
292
293//         let edu = serde_json::from_value::<Edu>(json.clone()).unwrap();
294//         assert_matches!(
295//             &edu,
296//             Edu::DeviceListUpdate(DeviceListUpdateContent {
297//                 user_id,
298//                 device_id,
299//                 device_display_name,
300//                 stream_id,
301//                 prev_id,
302//                 deleted,
303//                 keys,
304//             })
305//         );
306
307//         assert_eq!(user_id, "@john:example.com");
308//         assert_eq!(device_id, "QBUAZIFURK");
309//         assert_eq!(*device_display_name, None);
310//         assert_eq!(*stream_id, u6);
311//         assert_eq!(*prev_id, vec![]);
312//         assert_eq!(*deleted, None);
313//         assert_matches!(keys, None);
314
315//         assert_eq!(serde_json::to_value(&edu).unwrap(), json);
316//     }
317
318//     #[test]
319//     fn receipt_edu() {
320//         let json = json!({
321//             "content": {
322//                 "!some_room:example.org": {
323//                     "m.read": {
324//                         "@john:matrix.org": {
325//                             "data": {
326//                                 "ts": 1_533_358
327//                             },
328//                             "event_ids": [
329//                                 "$read_this_event:matrix.org"
330//                             ]
331//                         }
332//                     }
333//                 }
334//             },
335//             "edu_type": "m.receipt"
336//         });
337
338//         let edu = serde_json::from_value::<Edu>(json.clone()).unwrap();
339//         assert_matches!(&edu, Edu::Receipt(ReceiptContent { receipts }));
340//         assert!(receipts.get(room_id!("!some_room:example.org")).is_some());
341
342//         assert_eq!(serde_json::to_value(&edu).unwrap(), json);
343//     }
344
345//     #[test]
346//     fn typing_edu() {
347//         let json = json!({
348//             "content": {
349//                 "room_id": "!somewhere:matrix.org",
350//                 "typing": true,
351//                 "user_id": "@john:matrix.org"
352//             },
353//             "edu_type": "m.typing"
354//         });
355
356//         let edu = serde_json::from_value::<Edu>(json.clone()).unwrap();
357//         assert_matches!(&edu, Edu::Typing(content));
358//         assert_eq!(content.room_id, "!somewhere:matrix.org");
359//         assert_eq!(content.user_id, "@john:matrix.org");
360//         assert!(content.typing);
361
362//         assert_eq!(serde_json::to_value(&edu).unwrap(), json);
363//     }
364
365//     #[test]
366//     fn direct_to_device_edu() {
367//         let json = json!({
368//             "content": {
369//                 "message_id": "hiezohf6Hoo7kaev",
370//                 "messages": {
371//                     "@alice:example.org": {
372//                         "IWHQUZUIAH": {
373//                             "algorithm": "m.megolm.v1.aes-sha2",
374//                             "room_id": "!Cuyf34gef24t:localhost",
375//                             "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
376//                             "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8LlfJL7qNBEY..."
377//                         }
378//                     }
379//                 },
380//                 "sender": "@john:example.com",
381//                 "type": "m.room_key_request"
382//             },
383//             "edu_type": "m.direct_to_device"
384//         });
385
386//         let edu = serde_json::from_value::<Edu>(json.clone()).unwrap();
387//         assert_matches!(&edu, Edu::DirectToDevice(content));
388//         assert_eq!(content.sender, "@john:example.com");
389//         assert_eq!(content.ev_type, ToDeviceEventType::RoomKeyRequest);
390//         assert_eq!(content.message_id, "hiezohf6Hoo7kaev");
391//         assert!(content.messages.get(user_id!("@alice:example.org")).is_some());
392
393//         assert_eq!(serde_json::to_value(&edu).unwrap(), json);
394//     }
395
396//     #[test]
397//     fn signing_key_update_edu() {
398//         let json = json!({
399//             "content": {
400//                 "master_key": {
401//                     "keys": {
402//                         "ed25519:alice+base64+public+key": "alice+base64+public+key",
403//                         "ed25519:base64+master+public+key": "base64+master+public+key"
404//                     },
405//                     "signatures": {
406//                         "@alice:example.com": {
407//                             "ed25519:alice+base64+master+key": "signature+of+key"
408//                         }
409//                     },
410//                     "usage": [
411//                         "master"
412//                     ],
413//                     "user_id": "@alice:example.com"
414//                 },
415//                 "self_signing_key": {
416//                     "keys": {
417//                         "ed25519:alice+base64+public+key": "alice+base64+public+key",
418//                         "ed25519:base64+self+signing+public+key": "base64+self+signing+master+public+key"
419//                     },
420//                     "signatures": {
421//                         "@alice:example.com": {
422//                             "ed25519:alice+base64+master+key": "signature+of+key",
423//                             "ed25519:base64+master+public+key": "signature+of+self+signing+key"
424//                         }
425//                     },
426//                     "usage": [
427//                         "self_signing"
428//                     ],
429//                     "user_id": "@alice:example.com"
430//                   },
431//                 "user_id": "@alice:example.com"
432//             },
433//             "edu_type": "m.signing_key_update"
434//         });
435
436//         let edu = serde_json::from_value::<Edu>(json.clone()).unwrap();
437//         assert_matches!(&edu, Edu::SigningKeyUpdate(content));
438//         assert_eq!(content.user_id, "@alice:example.com");
439//         assert!(content.master_key.is_some());
440//         assert!(content.self_signing_key.is_some());
441
442//         assert_eq!(serde_json::to_value(&edu).unwrap(), json);
443//     }
444// }