1use std::collections::BTreeMap;
6
7use ruma_common::{serde::Base64, OwnedTransactionId};
8use ruma_macros::EventContent;
9use serde::{Deserialize, Serialize};
10use serde_json::Value as JsonValue;
11
12use super::{
13 HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString,
14};
15use crate::relation::Reference;
16
17#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
21#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
22#[ruma_event(type = "m.key.verification.accept", kind = ToDevice)]
23pub struct ToDeviceKeyVerificationAcceptEventContent {
24 pub transaction_id: OwnedTransactionId,
28
29 #[serde(flatten)]
31 pub method: AcceptMethod,
32}
33
34impl ToDeviceKeyVerificationAcceptEventContent {
35 pub fn new(transaction_id: OwnedTransactionId, method: AcceptMethod) -> Self {
38 Self { transaction_id, method }
39 }
40}
41
42#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
46#[ruma_event(type = "m.key.verification.accept", kind = MessageLike)]
47#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
48pub struct KeyVerificationAcceptEventContent {
49 #[serde(flatten)]
51 pub method: AcceptMethod,
52
53 #[serde(rename = "m.relates_to")]
55 pub relates_to: Reference,
56}
57
58impl KeyVerificationAcceptEventContent {
59 pub fn new(method: AcceptMethod, relates_to: Reference) -> Self {
62 Self { method, relates_to }
63 }
64}
65
66#[derive(Clone, Debug, Deserialize, Serialize)]
68#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
69#[serde(untagged)]
70pub enum AcceptMethod {
71 SasV1(SasV1Content),
73
74 #[doc(hidden)]
76 _Custom(_CustomContent),
77}
78
79#[doc(hidden)]
81#[derive(Clone, Debug, Deserialize, Serialize)]
82#[allow(clippy::exhaustive_structs)]
83pub struct _CustomContent {
84 pub method: String,
86
87 #[serde(flatten)]
89 pub data: BTreeMap<String, JsonValue>,
90}
91
92#[derive(Clone, Debug, Deserialize, Serialize)]
94#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
95#[serde(rename = "m.sas.v1", tag = "method")]
96pub struct SasV1Content {
97 pub key_agreement_protocol: KeyAgreementProtocol,
100
101 pub hash: HashAlgorithm,
104
105 pub message_authentication_code: MessageAuthenticationCode,
108
109 pub short_authentication_string: Vec<ShortAuthenticationString>,
115
116 pub commitment: Base64,
120}
121
122#[derive(Debug)]
124#[allow(clippy::exhaustive_structs)]
125pub struct SasV1ContentInit {
126 pub key_agreement_protocol: KeyAgreementProtocol,
129
130 pub hash: HashAlgorithm,
133
134 pub message_authentication_code: MessageAuthenticationCode,
136
137 pub short_authentication_string: Vec<ShortAuthenticationString>,
143
144 pub commitment: Base64,
148}
149
150impl From<SasV1ContentInit> for SasV1Content {
151 fn from(init: SasV1ContentInit) -> Self {
153 SasV1Content {
154 hash: init.hash,
155 key_agreement_protocol: init.key_agreement_protocol,
156 message_authentication_code: init.message_authentication_code,
157 short_authentication_string: init.short_authentication_string,
158 commitment: init.commitment,
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use std::collections::BTreeMap;
166
167 use assert_matches2::assert_matches;
168 use ruma_common::{
169 event_id,
170 serde::{Base64, Raw},
171 };
172 use serde_json::{
173 from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
174 };
175
176 use super::{
177 AcceptMethod, HashAlgorithm, KeyAgreementProtocol, KeyVerificationAcceptEventContent,
178 MessageAuthenticationCode, SasV1Content, ShortAuthenticationString,
179 ToDeviceKeyVerificationAcceptEventContent, _CustomContent,
180 };
181 use crate::{relation::Reference, ToDeviceEvent};
182
183 #[test]
184 fn serialization() {
185 let key_verification_accept_content = ToDeviceKeyVerificationAcceptEventContent {
186 transaction_id: "456".into(),
187 method: AcceptMethod::SasV1(SasV1Content {
188 hash: HashAlgorithm::Sha256,
189 key_agreement_protocol: KeyAgreementProtocol::Curve25519,
190 message_authentication_code: MessageAuthenticationCode::HkdfHmacSha256V2,
191 short_authentication_string: vec![ShortAuthenticationString::Decimal],
192 commitment: Base64::new(b"hello".to_vec()),
193 }),
194 };
195
196 let json_data = json!({
197 "transaction_id": "456",
198 "method": "m.sas.v1",
199 "commitment": "aGVsbG8",
200 "key_agreement_protocol": "curve25519",
201 "hash": "sha256",
202 "message_authentication_code": "hkdf-hmac-sha256.v2",
203 "short_authentication_string": ["decimal"]
204 });
205
206 assert_eq!(to_json_value(&key_verification_accept_content).unwrap(), json_data);
207
208 let json_data = json!({
209 "transaction_id": "456",
210 "method": "m.sas.custom",
211 "test": "field",
212 });
213
214 let key_verification_accept_content = ToDeviceKeyVerificationAcceptEventContent {
215 transaction_id: "456".into(),
216 method: AcceptMethod::_Custom(_CustomContent {
217 method: "m.sas.custom".to_owned(),
218 data: vec![("test".to_owned(), JsonValue::from("field"))]
219 .into_iter()
220 .collect::<BTreeMap<String, JsonValue>>(),
221 }),
222 };
223
224 assert_eq!(to_json_value(&key_verification_accept_content).unwrap(), json_data);
225 }
226
227 #[test]
228 fn in_room_serialization() {
229 let event_id = event_id!("$1598361704261elfgc:localhost");
230
231 let key_verification_accept_content = KeyVerificationAcceptEventContent {
232 relates_to: Reference { event_id: event_id.to_owned() },
233 method: AcceptMethod::SasV1(SasV1Content {
234 hash: HashAlgorithm::Sha256,
235 key_agreement_protocol: KeyAgreementProtocol::Curve25519,
236 message_authentication_code: MessageAuthenticationCode::HkdfHmacSha256V2,
237 short_authentication_string: vec![ShortAuthenticationString::Decimal],
238 commitment: Base64::new(b"hello".to_vec()),
239 }),
240 };
241
242 let json_data = json!({
243 "method": "m.sas.v1",
244 "commitment": "aGVsbG8",
245 "key_agreement_protocol": "curve25519",
246 "hash": "sha256",
247 "message_authentication_code": "hkdf-hmac-sha256.v2",
248 "short_authentication_string": ["decimal"],
249 "m.relates_to": {
250 "rel_type": "m.reference",
251 "event_id": event_id,
252 }
253 });
254
255 assert_eq!(to_json_value(&key_verification_accept_content).unwrap(), json_data);
256 }
257
258 #[test]
259 fn deserialization() {
260 let json = json!({
261 "transaction_id": "456",
262 "commitment": "aGVsbG8",
263 "method": "m.sas.v1",
264 "hash": "sha256",
265 "key_agreement_protocol": "curve25519",
266 "message_authentication_code": "hkdf-hmac-sha256.v2",
267 "short_authentication_string": ["decimal"]
268 });
269
270 let content = from_json_value::<ToDeviceKeyVerificationAcceptEventContent>(json).unwrap();
272 assert_eq!(content.transaction_id, "456");
273
274 assert_matches!(content.method, AcceptMethod::SasV1(sas));
275 assert_eq!(sas.commitment.encode(), "aGVsbG8");
276 assert_eq!(sas.hash, HashAlgorithm::Sha256);
277 assert_eq!(sas.key_agreement_protocol, KeyAgreementProtocol::Curve25519);
278 assert_eq!(sas.message_authentication_code, MessageAuthenticationCode::HkdfHmacSha256V2);
279 assert_eq!(sas.short_authentication_string, vec![ShortAuthenticationString::Decimal]);
280
281 let json = json!({
282 "content": {
283 "commitment": "aGVsbG8",
284 "transaction_id": "456",
285 "method": "m.sas.v1",
286 "key_agreement_protocol": "curve25519",
287 "hash": "sha256",
288 "message_authentication_code": "hkdf-hmac-sha256.v2",
289 "short_authentication_string": ["decimal"]
290 },
291 "type": "m.key.verification.accept",
292 "sender": "@example:localhost",
293 });
294
295 let ev = from_json_value::<ToDeviceEvent<ToDeviceKeyVerificationAcceptEventContent>>(json)
296 .unwrap();
297 assert_eq!(ev.content.transaction_id, "456");
298 assert_eq!(ev.sender, "@example:localhost");
299
300 assert_matches!(ev.content.method, AcceptMethod::SasV1(sas));
301 assert_eq!(sas.commitment.encode(), "aGVsbG8");
302 assert_eq!(sas.hash, HashAlgorithm::Sha256);
303 assert_eq!(sas.key_agreement_protocol, KeyAgreementProtocol::Curve25519);
304 assert_eq!(sas.message_authentication_code, MessageAuthenticationCode::HkdfHmacSha256V2);
305 assert_eq!(sas.short_authentication_string, vec![ShortAuthenticationString::Decimal]);
306
307 let json = json!({
308 "content": {
309 "from_device": "123",
310 "transaction_id": "456",
311 "method": "m.sas.custom",
312 "test": "field",
313 },
314 "type": "m.key.verification.accept",
315 "sender": "@example:localhost",
316 });
317
318 let ev = from_json_value::<ToDeviceEvent<ToDeviceKeyVerificationAcceptEventContent>>(json)
319 .unwrap();
320 assert_eq!(ev.content.transaction_id, "456");
321 assert_eq!(ev.sender, "@example:localhost");
322
323 assert_matches!(ev.content.method, AcceptMethod::_Custom(custom));
324 assert_eq!(custom.method, "m.sas.custom");
325 assert_eq!(custom.data.get("test"), Some(&JsonValue::from("field")));
326 }
327
328 #[test]
329 fn in_room_deserialization() {
330 let json = json!({
331 "commitment": "aGVsbG8",
332 "method": "m.sas.v1",
333 "hash": "sha256",
334 "key_agreement_protocol": "curve25519",
335 "message_authentication_code": "hkdf-hmac-sha256.v2",
336 "short_authentication_string": ["decimal"],
337 "m.relates_to": {
338 "rel_type": "m.reference",
339 "event_id": "$1598361704261elfgc:localhost",
340 }
341 });
342
343 let content = from_json_value::<KeyVerificationAcceptEventContent>(json).unwrap();
345 assert_eq!(content.relates_to.event_id, "$1598361704261elfgc:localhost");
346
347 assert_matches!(content.method, AcceptMethod::SasV1(sas));
348 assert_eq!(sas.commitment.encode(), "aGVsbG8");
349 assert_eq!(sas.hash, HashAlgorithm::Sha256);
350 assert_eq!(sas.key_agreement_protocol, KeyAgreementProtocol::Curve25519);
351 assert_eq!(sas.message_authentication_code, MessageAuthenticationCode::HkdfHmacSha256V2);
352 assert_eq!(sas.short_authentication_string, vec![ShortAuthenticationString::Decimal]);
353 }
354
355 #[test]
356 fn in_room_serialization_roundtrip() {
357 let event_id = event_id!("$1598361704261elfgc:localhost");
358
359 let content = KeyVerificationAcceptEventContent {
360 relates_to: Reference { event_id: event_id.to_owned() },
361 method: AcceptMethod::SasV1(SasV1Content {
362 hash: HashAlgorithm::Sha256,
363 key_agreement_protocol: KeyAgreementProtocol::Curve25519,
364 message_authentication_code: MessageAuthenticationCode::HkdfHmacSha256V2,
365 short_authentication_string: vec![ShortAuthenticationString::Decimal],
366 commitment: Base64::new(b"hello".to_vec()),
367 }),
368 };
369
370 let json_content = Raw::new(&content).unwrap();
371 let deser_content = json_content.deserialize().unwrap();
372
373 assert_matches!(deser_content.method, AcceptMethod::SasV1(_));
374 assert_eq!(deser_content.relates_to.event_id, event_id);
375 }
376}