ruma_client_api/keys/
upload_signatures.rs
1pub mod v3 {
6 use std::collections::BTreeMap;
11
12 use ruma_common::{
13 api::{request, response, Metadata},
14 encryption::{CrossSigningKey, DeviceKeys},
15 metadata,
16 serde::{Raw, StringEnum},
17 OwnedDeviceId, OwnedUserId,
18 };
19 use serde::{Deserialize, Serialize};
20 use serde_json::value::RawValue as RawJsonValue;
21
22 pub use super::iter::SignedKeysIter;
23 use crate::PrivOwnedStr;
24
25 const METADATA: Metadata = metadata! {
26 method: POST,
27 rate_limited: false,
28 authentication: AccessToken,
29 history: {
30 unstable => "/_matrix/client/unstable/keys/signatures/upload",
31 1.1 => "/_matrix/client/v3/keys/signatures/upload",
32 }
33 };
34
35 #[request(error = crate::Error)]
37 pub struct Request {
38 #[ruma_api(body)]
40 pub signed_keys: BTreeMap<OwnedUserId, SignedKeys>,
41 }
42
43 #[response(error = crate::Error)]
45 #[derive(Default)]
46 pub struct Response {
47 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
49 pub failures: BTreeMap<OwnedUserId, BTreeMap<String, Failure>>,
50 }
51
52 impl Request {
53 pub fn new(signed_keys: BTreeMap<OwnedUserId, SignedKeys>) -> Self {
55 Self { signed_keys }
56 }
57 }
58
59 impl Response {
60 pub fn new() -> Self {
62 Self::default()
63 }
64 }
65
66 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
68 #[serde(transparent)]
69 pub struct SignedKeys(BTreeMap<Box<str>, Box<RawJsonValue>>);
70
71 impl SignedKeys {
72 pub fn new() -> Self {
74 Self::default()
75 }
76
77 pub fn add_device_keys(&mut self, device_id: OwnedDeviceId, device_keys: Raw<DeviceKeys>) {
79 self.0.insert(device_id.as_str().into(), device_keys.into_json());
80 }
81
82 pub fn add_cross_signing_keys(
84 &mut self,
85 cross_signing_key_id: Box<str>,
86 cross_signing_keys: Raw<CrossSigningKey>,
87 ) {
88 self.0.insert(cross_signing_key_id, cross_signing_keys.into_json());
89 }
90
91 pub fn iter(&self) -> SignedKeysIter<'_> {
93 SignedKeysIter(self.0.iter())
94 }
95 }
96
97 #[derive(Clone, Debug, Deserialize, Serialize)]
99 pub struct Failure {
100 errcode: FailureErrorCode,
102
103 #[cfg_attr(feature = "compat-upload-signatures", serde(alias = "message"))]
105 error: String,
106 }
107
108 #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
110 #[derive(Clone, PartialEq, Eq, StringEnum)]
111 #[non_exhaustive]
112 #[ruma_enum(rename_all = "M_MATRIX_ERROR_CASE")]
113 pub enum FailureErrorCode {
114 InvalidSignature,
116
117 #[doc(hidden)]
118 _Custom(PrivOwnedStr),
119 }
120
121 #[cfg(all(test, feature = "client"))]
122 mod tests {
123 use super::ResponseBody;
124
125 #[cfg(feature = "compat-upload-signatures")]
126 #[test]
127 fn deserialize_synapse_response() {
128 use ruma_common::user_id;
129
130 use super::FailureErrorCode;
131
132 const JSON: &str = r#"{
133 "failures": {
134 "@richvdh:sw1v.org": {
135 "EOZDSWJVGZ": {
136 "status": 400,
137 "errcode": "M_INVALID_SIGNATURE",
138 "message": "400: Invalid signature"
139 }
140 }
141 }
142 }"#;
143
144 let parsed: ResponseBody = serde_json::from_str(JSON).unwrap();
145 let failure = &parsed.failures[user_id!("@richvdh:sw1v.org")]["EOZDSWJVGZ"];
146 assert_eq!(failure.errcode, FailureErrorCode::InvalidSignature);
147 assert_eq!(failure.error, "400: Invalid signature");
148 }
149
150 #[test]
151 fn deserialize_empty_response() {
152 const JSON: &str = r#"{}"#;
153
154 let _parsed: ResponseBody = serde_json::from_str(JSON)
155 .expect("We should be able to deserialize an empty keys/signatures/upload");
156 }
157 }
158}
159
160mod iter;