1use ruma_common::{serde::StringEnum, OwnedDeviceId, OwnedTransactionId};
6use ruma_macros::EventContent;
7use serde::{ser::SerializeStruct, Deserialize, Serialize};
8
9use crate::{GlobalAccountDataEventType, PrivOwnedStr};
10
11#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
17#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
18#[ruma_event(type = "m.secret.request", kind = ToDevice)]
19pub struct ToDeviceSecretRequestEventContent {
20 #[serde(flatten)]
22 pub action: RequestAction,
23
24 pub requesting_device_id: OwnedDeviceId,
26
27 pub request_id: OwnedTransactionId,
33}
34
35impl ToDeviceSecretRequestEventContent {
36 pub fn new(
39 action: RequestAction,
40 requesting_device_id: OwnedDeviceId,
41 request_id: OwnedTransactionId,
42 ) -> Self {
43 Self { action, requesting_device_id, request_id }
44 }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
49#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
50#[serde(try_from = "RequestActionJsonRepr")]
51pub enum RequestAction {
52 Request(SecretName),
54
55 RequestCancellation,
57
58 #[doc(hidden)]
59 _Custom(PrivOwnedStr),
60}
61
62impl Serialize for RequestAction {
63 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
64 where
65 S: serde::Serializer,
66 {
67 let mut st = serializer.serialize_struct("request_action", 2)?;
68
69 match self {
70 Self::Request(name) => {
71 st.serialize_field("name", name)?;
72 st.serialize_field("action", "request")?;
73 st.end()
74 }
75 Self::RequestCancellation => {
76 st.serialize_field("action", "request_cancellation")?;
77 st.end()
78 }
79 RequestAction::_Custom(custom) => {
80 st.serialize_field("action", &custom.0)?;
81 st.end()
82 }
83 }
84 }
85}
86
87#[derive(Deserialize)]
88struct RequestActionJsonRepr {
89 action: String,
90 name: Option<SecretName>,
91}
92
93impl TryFrom<RequestActionJsonRepr> for RequestAction {
94 type Error = &'static str;
95
96 fn try_from(value: RequestActionJsonRepr) -> Result<Self, Self::Error> {
97 match value.action.as_str() {
98 "request" => {
99 if let Some(name) = value.name {
100 Ok(RequestAction::Request(name))
101 } else {
102 Err("A secret name is required when the action is \"request\".")
103 }
104 }
105 "request_cancellation" => Ok(RequestAction::RequestCancellation),
106 _ => Ok(RequestAction::_Custom(PrivOwnedStr(value.action.into()))),
107 }
108 }
109}
110
111#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
113#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
114#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
115pub enum SecretName {
116 #[ruma_enum(rename = "m.cross_signing.master")]
118 CrossSigningMasterKey,
119
120 #[ruma_enum(rename = "m.cross_signing.user_signing")]
122 CrossSigningUserSigningKey,
123
124 #[ruma_enum(rename = "m.cross_signing.self_signing")]
126 CrossSigningSelfSigningKey,
127
128 #[ruma_enum(rename = "m.megolm_backup.v1")]
130 RecoveryKey,
131
132 #[doc(hidden)]
133 _Custom(PrivOwnedStr),
134}
135
136impl From<SecretName> for GlobalAccountDataEventType {
137 fn from(value: SecretName) -> Self {
138 GlobalAccountDataEventType::from(value.as_str())
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use assert_matches2::assert_matches;
145 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
146
147 use super::{RequestAction, SecretName, ToDeviceSecretRequestEventContent};
148 use crate::PrivOwnedStr;
149
150 #[test]
151 fn secret_request_serialization() {
152 let content = ToDeviceSecretRequestEventContent::new(
153 RequestAction::Request("org.example.some.secret".into()),
154 "ABCDEFG".into(),
155 "randomly_generated_id_9573".into(),
156 );
157
158 let json = json!({
159 "name": "org.example.some.secret",
160 "action": "request",
161 "requesting_device_id": "ABCDEFG",
162 "request_id": "randomly_generated_id_9573"
163 });
164
165 assert_eq!(to_json_value(&content).unwrap(), json);
166 }
167
168 #[test]
169 fn secret_request_recovery_key_serialization() {
170 let content = ToDeviceSecretRequestEventContent::new(
171 RequestAction::Request(SecretName::RecoveryKey),
172 "XYZxyz".into(),
173 "this_is_a_request_id".into(),
174 );
175
176 let json = json!({
177 "name": "m.megolm_backup.v1",
178 "action": "request",
179 "requesting_device_id": "XYZxyz",
180 "request_id": "this_is_a_request_id"
181 });
182
183 assert_eq!(to_json_value(&content).unwrap(), json);
184 }
185
186 #[test]
187 fn secret_custom_action_serialization() {
188 let content = ToDeviceSecretRequestEventContent::new(
189 RequestAction::_Custom(PrivOwnedStr("my_custom_action".into())),
190 "XYZxyz".into(),
191 "this_is_a_request_id".into(),
192 );
193
194 let json = json!({
195 "action": "my_custom_action",
196 "requesting_device_id": "XYZxyz",
197 "request_id": "this_is_a_request_id"
198 });
199
200 assert_eq!(to_json_value(&content).unwrap(), json);
201 }
202
203 #[test]
204 fn secret_request_cancellation_serialization() {
205 let content = ToDeviceSecretRequestEventContent::new(
206 RequestAction::RequestCancellation,
207 "ABCDEFG".into(),
208 "randomly_generated_id_9573".into(),
209 );
210
211 let json = json!({
212 "action": "request_cancellation",
213 "requesting_device_id": "ABCDEFG",
214 "request_id": "randomly_generated_id_9573"
215 });
216
217 assert_eq!(to_json_value(&content).unwrap(), json);
218 }
219
220 #[test]
221 fn secret_request_deserialization() {
222 let json = json!({
223 "name": "org.example.some.secret",
224 "action": "request",
225 "requesting_device_id": "ABCDEFG",
226 "request_id": "randomly_generated_id_9573"
227 });
228
229 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
230 assert_eq!(content.requesting_device_id, "ABCDEFG");
231 assert_eq!(content.request_id, "randomly_generated_id_9573");
232 assert_matches!(content.action, RequestAction::Request(secret));
233 assert_eq!(secret.as_str(), "org.example.some.secret");
234 }
235
236 #[test]
237 fn secret_request_cancellation_deserialization() {
238 let json = json!({
239 "action": "request_cancellation",
240 "requesting_device_id": "ABCDEFG",
241 "request_id": "randomly_generated_id_9573"
242 });
243
244 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
245 assert_eq!(content.requesting_device_id, "ABCDEFG");
246 assert_eq!(content.request_id, "randomly_generated_id_9573");
247 assert_matches!(content.action, RequestAction::RequestCancellation);
248 }
249
250 #[test]
251 fn secret_request_recovery_key_deserialization() {
252 let json = json!({
253 "name": "m.megolm_backup.v1",
254 "action": "request",
255 "requesting_device_id": "XYZxyz",
256 "request_id": "this_is_a_request_id"
257 });
258
259 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
260 assert_eq!(content.requesting_device_id, "XYZxyz");
261 assert_eq!(content.request_id, "this_is_a_request_id");
262 assert_matches!(content.action, RequestAction::Request(secret));
263 assert_eq!(secret, SecretName::RecoveryKey);
264 }
265
266 #[test]
267 fn secret_custom_action_deserialization() {
268 let json = json!({
269 "action": "my_custom_action",
270 "requesting_device_id": "XYZxyz",
271 "request_id": "this_is_a_request_id"
272 });
273
274 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
275 assert_eq!(content.requesting_device_id, "XYZxyz");
276 assert_eq!(content.request_id, "this_is_a_request_id");
277 assert_eq!(content.action, RequestAction::_Custom(PrivOwnedStr("my_custom_action".into())));
278 }
279}