1use ruma_common::{OwnedDeviceId, OwnedTransactionId, serde::StringEnum};
6use ruma_macros::EventContent;
7use serde::{Deserialize, Serialize, ser::SerializeStruct};
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, 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 ruma_common::canonical_json::assert_to_canonical_json_eq;
146 use serde_json::{from_value as from_json_value, json};
147
148 use super::{RequestAction, SecretName, ToDeviceSecretRequestEventContent};
149 use crate::PrivOwnedStr;
150
151 #[test]
152 fn secret_request_serialization() {
153 let content = ToDeviceSecretRequestEventContent::new(
154 RequestAction::Request("org.example.some.secret".into()),
155 "ABCDEFG".into(),
156 "randomly_generated_id_9573".into(),
157 );
158
159 assert_to_canonical_json_eq!(
160 content,
161 json!({
162 "name": "org.example.some.secret",
163 "action": "request",
164 "requesting_device_id": "ABCDEFG",
165 "request_id": "randomly_generated_id_9573",
166 }),
167 );
168 }
169
170 #[test]
171 fn secret_request_recovery_key_serialization() {
172 let content = ToDeviceSecretRequestEventContent::new(
173 RequestAction::Request(SecretName::RecoveryKey),
174 "XYZxyz".into(),
175 "this_is_a_request_id".into(),
176 );
177
178 assert_to_canonical_json_eq!(
179 content,
180 json!({
181 "name": "m.megolm_backup.v1",
182 "action": "request",
183 "requesting_device_id": "XYZxyz",
184 "request_id": "this_is_a_request_id",
185 }),
186 );
187 }
188
189 #[test]
190 fn secret_custom_action_serialization() {
191 let content = ToDeviceSecretRequestEventContent::new(
192 RequestAction::_Custom(PrivOwnedStr("my_custom_action".into())),
193 "XYZxyz".into(),
194 "this_is_a_request_id".into(),
195 );
196
197 assert_to_canonical_json_eq!(
198 content,
199 json!({
200 "action": "my_custom_action",
201 "requesting_device_id": "XYZxyz",
202 "request_id": "this_is_a_request_id",
203 }),
204 );
205 }
206
207 #[test]
208 fn secret_request_cancellation_serialization() {
209 let content = ToDeviceSecretRequestEventContent::new(
210 RequestAction::RequestCancellation,
211 "ABCDEFG".into(),
212 "randomly_generated_id_9573".into(),
213 );
214
215 assert_to_canonical_json_eq!(
216 content,
217 json!({
218 "action": "request_cancellation",
219 "requesting_device_id": "ABCDEFG",
220 "request_id": "randomly_generated_id_9573",
221 }),
222 );
223 }
224
225 #[test]
226 fn secret_request_deserialization() {
227 let json = json!({
228 "name": "org.example.some.secret",
229 "action": "request",
230 "requesting_device_id": "ABCDEFG",
231 "request_id": "randomly_generated_id_9573"
232 });
233
234 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
235 assert_eq!(content.requesting_device_id, "ABCDEFG");
236 assert_eq!(content.request_id, "randomly_generated_id_9573");
237 assert_matches!(content.action, RequestAction::Request(secret));
238 assert_eq!(secret.as_str(), "org.example.some.secret");
239 }
240
241 #[test]
242 fn secret_request_cancellation_deserialization() {
243 let json = json!({
244 "action": "request_cancellation",
245 "requesting_device_id": "ABCDEFG",
246 "request_id": "randomly_generated_id_9573"
247 });
248
249 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
250 assert_eq!(content.requesting_device_id, "ABCDEFG");
251 assert_eq!(content.request_id, "randomly_generated_id_9573");
252 assert_matches!(content.action, RequestAction::RequestCancellation);
253 }
254
255 #[test]
256 fn secret_request_recovery_key_deserialization() {
257 let json = json!({
258 "name": "m.megolm_backup.v1",
259 "action": "request",
260 "requesting_device_id": "XYZxyz",
261 "request_id": "this_is_a_request_id"
262 });
263
264 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
265 assert_eq!(content.requesting_device_id, "XYZxyz");
266 assert_eq!(content.request_id, "this_is_a_request_id");
267 assert_matches!(content.action, RequestAction::Request(secret));
268 assert_eq!(secret, SecretName::RecoveryKey);
269 }
270
271 #[test]
272 fn secret_custom_action_deserialization() {
273 let json = json!({
274 "action": "my_custom_action",
275 "requesting_device_id": "XYZxyz",
276 "request_id": "this_is_a_request_id"
277 });
278
279 let content = from_json_value::<ToDeviceSecretRequestEventContent>(json).unwrap();
280 assert_eq!(content.requesting_device_id, "XYZxyz");
281 assert_eq!(content.request_id, "this_is_a_request_id");
282 assert_eq!(content.action, RequestAction::_Custom(PrivOwnedStr("my_custom_action".into())));
283 }
284}