use std::collections::BTreeMap;
use ruma_events_macros::EventContent;
use ruma_identifiers::TransactionId;
use ruma_serde::Base64;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use super::{
HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, Relation,
ShortAuthenticationString,
};
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.key.verification.accept", kind = ToDevice)]
pub struct ToDeviceKeyVerificationAcceptEventContent {
pub transaction_id: Box<TransactionId>,
#[serde(flatten)]
pub method: AcceptMethod,
}
impl ToDeviceKeyVerificationAcceptEventContent {
pub fn new(transaction_id: Box<TransactionId>, method: AcceptMethod) -> Self {
Self { transaction_id, method }
}
}
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
#[ruma_event(type = "m.key.verification.accept", kind = Message)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct KeyVerificationAcceptEventContent {
#[serde(flatten)]
pub method: AcceptMethod,
#[serde(rename = "m.relates_to")]
pub relates_to: Relation,
}
impl KeyVerificationAcceptEventContent {
pub fn new(method: AcceptMethod, relates_to: Relation) -> Self {
Self { method, relates_to }
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(untagged)]
pub enum AcceptMethod {
SasV1(SasV1Content),
#[doc(hidden)]
_Custom(_CustomContent),
}
#[doc(hidden)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[allow(clippy::exhaustive_structs)]
pub struct _CustomContent {
pub method: String,
#[serde(flatten)]
pub data: BTreeMap<String, JsonValue>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(rename = "m.sas.v1", tag = "method")]
pub struct SasV1Content {
pub key_agreement_protocol: KeyAgreementProtocol,
pub hash: HashAlgorithm,
pub message_authentication_code: MessageAuthenticationCode,
pub short_authentication_string: Vec<ShortAuthenticationString>,
pub commitment: Base64,
}
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct SasV1ContentInit {
pub key_agreement_protocol: KeyAgreementProtocol,
pub hash: HashAlgorithm,
pub message_authentication_code: MessageAuthenticationCode,
pub short_authentication_string: Vec<ShortAuthenticationString>,
pub commitment: Base64,
}
impl From<SasV1ContentInit> for SasV1Content {
fn from(init: SasV1ContentInit) -> Self {
SasV1Content {
hash: init.hash,
key_agreement_protocol: init.key_agreement_protocol,
message_authentication_code: init.message_authentication_code,
short_authentication_string: init.short_authentication_string,
commitment: init.commitment,
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use matches::assert_matches;
use ruma_identifiers::{event_id, user_id};
use ruma_serde::Base64;
use serde_json::{
from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
};
use super::{
AcceptMethod, HashAlgorithm, KeyAgreementProtocol, KeyVerificationAcceptEventContent,
MessageAuthenticationCode, SasV1Content, ShortAuthenticationString,
ToDeviceKeyVerificationAcceptEventContent, _CustomContent,
};
use crate::{key::verification::Relation, ToDeviceEvent};
#[test]
fn serialization() {
let key_verification_accept_content = ToDeviceKeyVerificationAcceptEventContent {
transaction_id: "456".into(),
method: AcceptMethod::SasV1(SasV1Content {
hash: HashAlgorithm::Sha256,
key_agreement_protocol: KeyAgreementProtocol::Curve25519,
message_authentication_code: MessageAuthenticationCode::HkdfHmacSha256,
short_authentication_string: vec![ShortAuthenticationString::Decimal],
commitment: Base64::new(b"hello".to_vec()),
}),
};
let sender = user_id!("@example:localhost").to_owned();
let json_data = json!({
"content": {
"transaction_id": "456",
"method": "m.sas.v1",
"commitment": "aGVsbG8",
"key_agreement_protocol": "curve25519",
"hash": "sha256",
"message_authentication_code": "hkdf-hmac-sha256",
"short_authentication_string": ["decimal"]
},
"sender": sender,
"type": "m.key.verification.accept"
});
let key_verification_accept =
ToDeviceEvent { sender, content: key_verification_accept_content };
assert_eq!(to_json_value(&key_verification_accept).unwrap(), json_data);
let sender = user_id!("@example:localhost").to_owned();
let json_data = json!({
"content": {
"transaction_id": "456",
"method": "m.sas.custom",
"test": "field",
},
"sender": sender,
"type": "m.key.verification.accept"
});
let key_verification_accept_content = ToDeviceKeyVerificationAcceptEventContent {
transaction_id: "456".into(),
method: AcceptMethod::_Custom(_CustomContent {
method: "m.sas.custom".to_owned(),
data: vec![("test".to_owned(), JsonValue::from("field"))]
.into_iter()
.collect::<BTreeMap<String, JsonValue>>(),
}),
};
let key_verification_accept =
ToDeviceEvent { sender, content: key_verification_accept_content };
assert_eq!(to_json_value(&key_verification_accept).unwrap(), json_data);
}
#[test]
fn in_room_serialization() {
let event_id = event_id!("$1598361704261elfgc:localhost");
let key_verification_accept_content = KeyVerificationAcceptEventContent {
relates_to: Relation { event_id: event_id.to_owned() },
method: AcceptMethod::SasV1(SasV1Content {
hash: HashAlgorithm::Sha256,
key_agreement_protocol: KeyAgreementProtocol::Curve25519,
message_authentication_code: MessageAuthenticationCode::HkdfHmacSha256,
short_authentication_string: vec![ShortAuthenticationString::Decimal],
commitment: Base64::new(b"hello".to_vec()),
}),
};
let json_data = json!({
"method": "m.sas.v1",
"commitment": "aGVsbG8",
"key_agreement_protocol": "curve25519",
"hash": "sha256",
"message_authentication_code": "hkdf-hmac-sha256",
"short_authentication_string": ["decimal"],
"m.relates_to": {
"rel_type": "m.reference",
"event_id": event_id,
}
});
assert_eq!(to_json_value(&key_verification_accept_content).unwrap(), json_data);
}
#[test]
fn deserialization() {
let json = json!({
"transaction_id": "456",
"commitment": "aGVsbG8",
"method": "m.sas.v1",
"hash": "sha256",
"key_agreement_protocol": "curve25519",
"message_authentication_code": "hkdf-hmac-sha256",
"short_authentication_string": ["decimal"]
});
assert_matches!(
from_json_value::<ToDeviceKeyVerificationAcceptEventContent>(json).unwrap(),
ToDeviceKeyVerificationAcceptEventContent {
transaction_id,
method: AcceptMethod::SasV1(SasV1Content {
commitment,
hash,
key_agreement_protocol,
message_authentication_code,
short_authentication_string,
})
} if commitment.encode() == "aGVsbG8"
&& transaction_id == "456"
&& hash == HashAlgorithm::Sha256
&& key_agreement_protocol == KeyAgreementProtocol::Curve25519
&& message_authentication_code == MessageAuthenticationCode::HkdfHmacSha256
&& short_authentication_string == vec![ShortAuthenticationString::Decimal]
);
let sender = user_id!("@example:localhost");
let json = json!({
"content": {
"commitment": "aGVsbG8",
"transaction_id": "456",
"method": "m.sas.v1",
"key_agreement_protocol": "curve25519",
"hash": "sha256",
"message_authentication_code": "hkdf-hmac-sha256",
"short_authentication_string": ["decimal"]
},
"type": "m.key.verification.accept",
"sender": sender,
});
assert_matches!(
from_json_value::<ToDeviceEvent<ToDeviceKeyVerificationAcceptEventContent>>(json).unwrap(),
ToDeviceEvent {
sender,
content: ToDeviceKeyVerificationAcceptEventContent {
transaction_id,
method: AcceptMethod::SasV1(SasV1Content {
commitment,
hash,
key_agreement_protocol,
message_authentication_code,
short_authentication_string,
})
}
} if commitment.encode() == "aGVsbG8"
&& sender == user_id!("@example:localhost")
&& transaction_id == "456"
&& hash == HashAlgorithm::Sha256
&& key_agreement_protocol == KeyAgreementProtocol::Curve25519
&& message_authentication_code == MessageAuthenticationCode::HkdfHmacSha256
&& short_authentication_string == vec![ShortAuthenticationString::Decimal]
);
let sender = user_id!("@example:localhost");
let json = json!({
"content": {
"from_device": "123",
"transaction_id": "456",
"method": "m.sas.custom",
"test": "field",
},
"type": "m.key.verification.accept",
"sender": sender
});
assert_matches!(
from_json_value::<ToDeviceEvent<ToDeviceKeyVerificationAcceptEventContent>>(json).unwrap(),
ToDeviceEvent {
sender,
content: ToDeviceKeyVerificationAcceptEventContent {
transaction_id,
method: AcceptMethod::_Custom(_CustomContent {
method,
data,
})
}
} if transaction_id == "456"
&& sender == user_id!("@example:localhost")
&& method == "m.sas.custom"
&& data.get("test").unwrap() == &JsonValue::from("field")
);
}
#[test]
fn in_room_deserialization() {
let id = event_id!("$1598361704261elfgc:localhost");
let json = json!({
"commitment": "aGVsbG8",
"method": "m.sas.v1",
"hash": "sha256",
"key_agreement_protocol": "curve25519",
"message_authentication_code": "hkdf-hmac-sha256",
"short_authentication_string": ["decimal"],
"m.relates_to": {
"rel_type": "m.reference",
"event_id": id,
}
});
assert_matches!(
from_json_value::<KeyVerificationAcceptEventContent>(json).unwrap(),
KeyVerificationAcceptEventContent {
relates_to: Relation {
event_id
},
method: AcceptMethod::SasV1(SasV1Content {
commitment,
hash,
key_agreement_protocol,
message_authentication_code,
short_authentication_string,
})
} if commitment.encode() == "aGVsbG8"
&& *event_id == *id
&& hash == HashAlgorithm::Sha256
&& key_agreement_protocol == KeyAgreementProtocol::Curve25519
&& message_authentication_code == MessageAuthenticationCode::HkdfHmacSha256
&& short_authentication_string == vec![ShortAuthenticationString::Decimal]
);
}
}