use std::collections::BTreeMap;
use ruma::{OwnedDeviceId, OwnedRoomId, OwnedTransactionId, RoomId};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use vodozemac::Curve25519PublicKey;
use super::{EventType, ToDeviceEvent};
use crate::types::{deserialize_curve_key, serialize_curve_key, EventEncryptionAlgorithm};
pub type RoomKeyRequestEvent = ToDeviceEvent<RoomKeyRequestContent>;
impl Clone for RoomKeyRequestEvent {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
content: self.content.clone(),
other: self.other.clone(),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct RoomKeyRequestContent {
#[serde(flatten)]
pub action: Action,
pub requesting_device_id: OwnedDeviceId,
pub request_id: OwnedTransactionId,
}
impl RoomKeyRequestContent {
pub fn new_request(
info: RequestedKeyInfo,
requesting_device_id: OwnedDeviceId,
request_id: OwnedTransactionId,
) -> Self {
Self { action: Action::Request(info), requesting_device_id, request_id }
}
}
impl EventType for RoomKeyRequestContent {
const EVENT_TYPE: &'static str = "m.room_key_request";
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(tag = "action", content = "body")]
pub enum Action {
#[serde(rename = "request_cancellation")]
Cancellation,
#[serde(rename = "request")]
Request(RequestedKeyInfo),
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(try_from = "RequestedKeyInfoHelper")]
pub enum RequestedKeyInfo {
MegolmV1AesSha2(MegolmV1AesSha2Content),
#[cfg(feature = "experimental-algorithms")]
MegolmV2AesSha2(MegolmV2AesSha2Content),
Unknown(UnknownRoomKeyRequest),
}
impl RequestedKeyInfo {
pub fn algorithm(&self) -> EventEncryptionAlgorithm {
match self {
RequestedKeyInfo::MegolmV1AesSha2(_) => EventEncryptionAlgorithm::MegolmV1AesSha2,
#[cfg(feature = "experimental-algorithms")]
RequestedKeyInfo::MegolmV2AesSha2(_) => EventEncryptionAlgorithm::MegolmV2AesSha2,
RequestedKeyInfo::Unknown(c) => c.algorithm.to_owned(),
}
}
}
impl From<SupportedKeyInfo> for RequestedKeyInfo {
fn from(s: SupportedKeyInfo) -> Self {
match s {
SupportedKeyInfo::MegolmV1AesSha2(c) => Self::MegolmV1AesSha2(c),
#[cfg(feature = "experimental-algorithms")]
SupportedKeyInfo::MegolmV2AesSha2(c) => Self::MegolmV2AesSha2(c),
}
}
}
impl From<MegolmV1AesSha2Content> for RequestedKeyInfo {
fn from(c: MegolmV1AesSha2Content) -> Self {
Self::MegolmV1AesSha2(c)
}
}
#[cfg(feature = "experimental-algorithms")]
impl From<MegolmV2AesSha2Content> for RequestedKeyInfo {
fn from(c: MegolmV2AesSha2Content) -> Self {
Self::MegolmV2AesSha2(c)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(try_from = "RequestedKeyInfo", into = "RequestedKeyInfo")]
pub enum SupportedKeyInfo {
MegolmV1AesSha2(MegolmV1AesSha2Content),
#[cfg(feature = "experimental-algorithms")]
MegolmV2AesSha2(MegolmV2AesSha2Content),
}
impl SupportedKeyInfo {
pub fn algorithm(&self) -> EventEncryptionAlgorithm {
match self {
Self::MegolmV1AesSha2(_) => EventEncryptionAlgorithm::MegolmV1AesSha2,
#[cfg(feature = "experimental-algorithms")]
Self::MegolmV2AesSha2(_) => EventEncryptionAlgorithm::MegolmV2AesSha2,
}
}
pub fn room_id(&self) -> &RoomId {
match self {
Self::MegolmV1AesSha2(c) => &c.room_id,
#[cfg(feature = "experimental-algorithms")]
Self::MegolmV2AesSha2(c) => &c.room_id,
}
}
pub fn session_id(&self) -> &str {
match self {
Self::MegolmV1AesSha2(c) => &c.session_id,
#[cfg(feature = "experimental-algorithms")]
Self::MegolmV2AesSha2(c) => &c.session_id,
}
}
}
impl TryFrom<RequestedKeyInfo> for SupportedKeyInfo {
type Error = &'static str;
fn try_from(value: RequestedKeyInfo) -> Result<Self, Self::Error> {
match value {
RequestedKeyInfo::MegolmV1AesSha2(c) => Ok(Self::MegolmV1AesSha2(c)),
#[cfg(feature = "experimental-algorithms")]
RequestedKeyInfo::MegolmV2AesSha2(c) => Ok(Self::MegolmV2AesSha2(c)),
RequestedKeyInfo::Unknown(_) => Err("Unsupported algorithm for a room key request"),
}
}
}
impl From<MegolmV1AesSha2Content> for SupportedKeyInfo {
fn from(c: MegolmV1AesSha2Content) -> Self {
Self::MegolmV1AesSha2(c)
}
}
#[cfg(feature = "experimental-algorithms")]
impl From<MegolmV2AesSha2Content> for SupportedKeyInfo {
fn from(c: MegolmV2AesSha2Content) -> Self {
Self::MegolmV2AesSha2(c)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct MegolmV1AesSha2Content {
pub room_id: OwnedRoomId,
#[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
pub sender_key: Curve25519PublicKey,
pub session_id: String,
}
#[cfg(feature = "experimental-algorithms")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct MegolmV2AesSha2Content {
pub room_id: OwnedRoomId,
pub session_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct UnknownRoomKeyRequest {
pub algorithm: EventEncryptionAlgorithm,
#[serde(flatten)]
other: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct RequestedKeyInfoHelper {
pub algorithm: EventEncryptionAlgorithm,
#[serde(flatten)]
other: Value,
}
impl Serialize for RequestedKeyInfo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let other = match self {
Self::MegolmV1AesSha2(r) => {
serde_json::to_value(r).map_err(serde::ser::Error::custom)?
}
#[cfg(feature = "experimental-algorithms")]
Self::MegolmV2AesSha2(r) => {
serde_json::to_value(r).map_err(serde::ser::Error::custom)?
}
Self::Unknown(r) => {
serde_json::to_value(r.other.clone()).map_err(serde::ser::Error::custom)?
}
};
let helper = RequestedKeyInfoHelper { algorithm: self.algorithm(), other };
helper.serialize(serializer)
}
}
impl TryFrom<RequestedKeyInfoHelper> for RequestedKeyInfo {
type Error = serde_json::Error;
fn try_from(value: RequestedKeyInfoHelper) -> Result<Self, Self::Error> {
Ok(match value.algorithm {
EventEncryptionAlgorithm::MegolmV1AesSha2 => {
Self::MegolmV1AesSha2(serde_json::from_value(value.other)?)
}
#[cfg(feature = "experimental-algorithms")]
EventEncryptionAlgorithm::MegolmV2AesSha2 => {
Self::MegolmV2AesSha2(serde_json::from_value(value.other)?)
}
_ => Self::Unknown(UnknownRoomKeyRequest {
algorithm: value.algorithm,
other: serde_json::from_value(value.other)?,
}),
})
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use serde_json::{json, Value};
use super::{Action, RequestedKeyInfo, RoomKeyRequestEvent};
pub fn json() -> Value {
json!({
"sender": "@alice:example.org",
"content": {
"action": "request",
"body": {
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": "!Cuyf34gef24t:localhost",
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
},
"request_id": "1495474790150.19",
"requesting_device_id": "RJYKSTBOIE"
},
"type": "m.room_key_request"
})
}
pub fn json_cancellation() -> Value {
json!({
"sender": "@alice:example.org",
"content": {
"action": "request_cancellation",
"request_id": "1495474790150.19",
"requesting_device_id": "RJYKSTBOIE"
},
"type": "m.room_key_request"
})
}
#[cfg(feature = "experimental-algorithms")]
pub fn json_megolm_v2() -> Value {
json!({
"sender": "@alice:example.org",
"content": {
"action": "request",
"body": {
"algorithm": "m.megolm.v2.aes-sha2",
"room_id": "!Cuyf34gef24t:localhost",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
},
"request_id": "1495474790150.19",
"requesting_device_id": "RJYKSTBOIE"
},
"type": "m.room_key_request"
})
}
#[test]
fn deserialization() -> Result<(), serde_json::Error> {
let json = json();
let event: RoomKeyRequestEvent = serde_json::from_value(json.clone())?;
assert_matches!(
event.content.action,
Action::Request(RequestedKeyInfo::MegolmV1AesSha2(_))
);
let serialized = serde_json::to_value(event)?;
assert_eq!(json, serialized);
let json = json_cancellation();
let event: RoomKeyRequestEvent = serde_json::from_value(json.clone())?;
assert_matches!(event.content.action, Action::Cancellation);
let serialized = serde_json::to_value(event)?;
assert_eq!(json, serialized);
Ok(())
}
#[test]
#[cfg(feature = "experimental-algorithms")]
fn deserialization_megolm_v2() -> Result<(), serde_json::Error> {
let json = json_megolm_v2();
let event: RoomKeyRequestEvent = serde_json::from_value(json.clone())?;
assert_matches!(
event.content.action,
Action::Request(RequestedKeyInfo::MegolmV2AesSha2(_))
);
let serialized = serde_json::to_value(event)?;
assert_eq!(json, serialized);
Ok(())
}
}