1use ruma_common::{OwnedDeviceId, OwnedTransactionId, serde::StringEnum};
6use ruma_macros::EventContent;
7use serde::{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, Deserialize, Serialize)]
49#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
50#[serde(tag = "action", rename_all = "snake_case")]
51pub enum RequestAction {
52 Request(SecretRequestAction),
54
55 RequestCancellation,
57
58 #[doc(hidden)]
59 #[serde(untagged)]
60 _Custom(CustomRequestAction),
61}
62
63impl RequestAction {
64 pub fn action(&self) -> &str {
66 match self {
67 Self::Request(_) => "request",
68 Self::RequestCancellation => "request_cancellation",
69 Self::_Custom(custom) => &custom.action,
70 }
71 }
72}
73
74impl From<SecretRequestAction> for RequestAction {
75 fn from(value: SecretRequestAction) -> Self {
76 Self::Request(value)
77 }
78}
79
80#[derive(Clone, Debug, Serialize, Deserialize)]
82#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
83pub struct SecretRequestAction {
84 pub name: SecretName,
86}
87
88impl SecretRequestAction {
89 pub fn new(name: SecretName) -> Self {
91 Self { name }
92 }
93}
94
95#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
97#[derive(Clone, StringEnum)]
98#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
99pub enum SecretName {
100 #[ruma_enum(rename = "m.cross_signing.master")]
102 CrossSigningMasterKey,
103
104 #[ruma_enum(rename = "m.cross_signing.user_signing")]
106 CrossSigningUserSigningKey,
107
108 #[ruma_enum(rename = "m.cross_signing.self_signing")]
110 CrossSigningSelfSigningKey,
111
112 #[ruma_enum(rename = "m.megolm_backup.v1")]
114 RecoveryKey,
115
116 #[doc(hidden)]
117 _Custom(PrivOwnedStr),
118}
119
120impl From<SecretName> for GlobalAccountDataEventType {
121 fn from(value: SecretName) -> Self {
122 GlobalAccountDataEventType::from(value.as_str())
123 }
124}
125
126#[doc(hidden)]
128#[derive(Clone, Debug, Serialize, Deserialize)]
129pub struct CustomRequestAction {
130 action: String,
132}
133
134#[cfg(test)]
135mod tests {
136 use assert_matches2::assert_matches;
137 use ruma_common::canonical_json::assert_to_canonical_json_eq;
138 use serde_json::{from_value as from_json_value, json};
139
140 use super::{
141 RequestAction, SecretName, SecretRequestAction, ToDeviceSecretRequestEventContent,
142 };
143
144 #[test]
145 fn secret_request_serialization() {
146 let content = ToDeviceSecretRequestEventContent::new(
147 RequestAction::Request(SecretRequestAction::new("org.example.some.secret".into())),
148 "ABCDEFG".into(),
149 "randomly_generated_id_9573".into(),
150 );
151
152 assert_to_canonical_json_eq!(
153 content,
154 json!({
155 "name": "org.example.some.secret",
156 "action": "request",
157 "requesting_device_id": "ABCDEFG",
158 "request_id": "randomly_generated_id_9573",
159 }),
160 );
161 }
162
163 #[test]
164 fn secret_request_recovery_key_serialization() {
165 let content = ToDeviceSecretRequestEventContent::new(
166 RequestAction::Request(SecretRequestAction::new(SecretName::RecoveryKey)),
167 "XYZxyz".into(),
168 "this_is_a_request_id".into(),
169 );
170
171 assert_to_canonical_json_eq!(
172 content,
173 json!({
174 "name": "m.megolm_backup.v1",
175 "action": "request",
176 "requesting_device_id": "XYZxyz",
177 "request_id": "this_is_a_request_id",
178 }),
179 );
180 }
181
182 #[test]
183 fn secret_request_cancellation_serialization() {
184 let content = ToDeviceSecretRequestEventContent::new(
185 RequestAction::RequestCancellation,
186 "ABCDEFG".into(),
187 "randomly_generated_id_9573".into(),
188 );
189
190 assert_to_canonical_json_eq!(
191 content,
192 json!({
193 "action": "request_cancellation",
194 "requesting_device_id": "ABCDEFG",
195 "request_id": "randomly_generated_id_9573",
196 }),
197 );
198 }
199
200 #[test]
201 fn secret_request_deserialization() {
202 let json = json!({
203 "name": "org.example.some.secret",
204 "action": "request",
205 "requesting_device_id": "ABCDEFG",
206 "request_id": "randomly_generated_id_9573"
207 });
208
209 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
210 assert_eq!(content.requesting_device_id, "ABCDEFG");
211 assert_eq!(content.request_id, "randomly_generated_id_9573");
212 assert_matches!(content.action, RequestAction::Request(secret));
213 assert_eq!(secret.name.as_str(), "org.example.some.secret");
214 }
215
216 #[test]
217 fn secret_request_cancellation_deserialization() {
218 let json = json!({
219 "action": "request_cancellation",
220 "requesting_device_id": "ABCDEFG",
221 "request_id": "randomly_generated_id_9573"
222 });
223
224 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
225 assert_eq!(content.requesting_device_id, "ABCDEFG");
226 assert_eq!(content.request_id, "randomly_generated_id_9573");
227 assert_matches!(content.action, RequestAction::RequestCancellation);
228 }
229
230 #[test]
231 fn secret_request_recovery_key_deserialization() {
232 let json = json!({
233 "name": "m.megolm_backup.v1",
234 "action": "request",
235 "requesting_device_id": "XYZxyz",
236 "request_id": "this_is_a_request_id"
237 });
238
239 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
240 assert_eq!(content.requesting_device_id, "XYZxyz");
241 assert_eq!(content.request_id, "this_is_a_request_id");
242 assert_matches!(content.action, RequestAction::Request(secret));
243 assert_eq!(secret.name, SecretName::RecoveryKey);
244 }
245
246 #[test]
247 fn secret_custom_action_serialization_roundtrip() {
248 let json = json!({
249 "action": "my_custom_action",
250 "requesting_device_id": "XYZxyz",
251 "request_id": "this_is_a_request_id"
252 });
253
254 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json.clone()).unwrap();
255 assert_eq!(content.requesting_device_id, "XYZxyz");
256 assert_eq!(content.request_id, "this_is_a_request_id");
257 assert_eq!(content.action.action(), "my_custom_action");
258
259 assert_to_canonical_json_eq!(content, json);
260 }
261}