diff --git a/src/room.rs b/src/room.rs index 222a9f23..7cb5b772 100644 --- a/src/room.rs +++ b/src/room.rs @@ -11,7 +11,7 @@ pub mod aliases; pub mod avatar; pub mod canonical_alias; pub mod create; -// pub mod encrypted; +pub mod encrypted; pub mod encryption; pub mod guest_access; pub mod history_visibility; diff --git a/src/room/encrypted.rs b/src/room/encrypted.rs index 6fe007cb..8087b815 100644 --- a/src/room/encrypted.rs +++ b/src/room/encrypted.rs @@ -1,21 +1,41 @@ //! Types for the *m.room.encrypted* event. +use std::{convert::TryFrom, str::FromStr}; + use js_int::UInt; -use ruma_identifiers::DeviceId; -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use ruma_identifiers::{DeviceId, EventId, RoomId, UserId}; +use serde::{de::Error, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{from_value, Value}; -use crate::Algorithm; +use crate::{Algorithm, Event, EventType, InvalidEvent, RoomEvent, StateEvent}; -room_event! { - /// This event type is used when sending encrypted events. - /// - /// This type is to be used within a room. For a to-device event, use `EncryptedEventContent` - /// directly. - pub struct EncryptedEvent(EncryptedEventContent) {} +/// This event type is used when sending encrypted events. +/// +/// This type is to be used within a room. For a to-device event, use `EncryptedEventContent` +/// directly. +#[derive(Clone, Debug, PartialEq)] +pub struct EncryptedEvent { + /// The event's content. + pub content: EncryptedEventContent, + + /// The unique identifier for the event. + pub event_id: EventId, + + /// Timestamp (milliseconds since the UNIX epoch) on originating homeserver when this + /// event was sent. + pub origin_server_ts: UInt, + + /// The unique identifier for the room associated with this event. + pub room_id: Option, + + /// The unique identifier for the user who sent this event. + pub sender: UserId, + + /// Additional key-value pairs not signed by the homeserver. + pub unsigned: Option, } -/// The payload of an *m.room.encrypted* event. +/// The payload for `EncryptedEvent`. #[derive(Clone, Debug, PartialEq)] pub enum EncryptedEventContent { /// An event encrypted with *m.olm.v1.curve25519-aes-sha2*. @@ -30,7 +50,224 @@ pub enum EncryptedEventContent { __Nonexhaustive, } -/// The payload of an *m.room.encrypted* event using the *m.olm.v1.curve25519-aes-sha2* algorithm. +impl FromStr for EncryptedEvent { + type Err = InvalidEvent; + + /// Attempt to create `Self` from parsing a string of JSON data. + fn from_str(json: &str) -> Result { + let raw = serde_json::from_str::(json)?; + + let content = match raw.content { + raw::EncryptedEventContent::OlmV1Curve25519AesSha2(content) => { + EncryptedEventContent::OlmV1Curve25519AesSha2(content) + } + raw::EncryptedEventContent::MegolmV1AesSha2(content) => { + EncryptedEventContent::MegolmV1AesSha2(content) + } + raw::EncryptedEventContent::__Nonexhaustive => { + panic!("__Nonexhaustive enum variant is not intended for use."); + } + }; + + Ok(Self { + content: content, + event_id: raw.event_id, + origin_server_ts: raw.origin_server_ts, + room_id: raw.room_id, + sender: raw.sender, + unsigned: raw.unsigned, + }) + } +} + +impl<'a> TryFrom<&'a str> for EncryptedEvent { + type Error = InvalidEvent; + + /// Attempt to create `Self` from parsing a string of JSON data. + fn try_from(json: &'a str) -> Result { + FromStr::from_str(json) + } +} + +impl Serialize for EncryptedEvent { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut len = 6; + + if self.room_id.is_some() { + len += 1; + } + + if self.unsigned.is_some() { + len += 1; + } + + let mut state = serializer.serialize_struct("EncryptedEvent", len)?; + + state.serialize_field("content", &self.content)?; + state.serialize_field("event_id", &self.event_id)?; + state.serialize_field("origin_server_ts", &self.origin_server_ts)?; + + if self.room_id.is_some() { + state.serialize_field("room_id", &self.room_id)?; + } + + if self.unsigned.is_some() { + state.serialize_field("unsigned", &self.unsigned)?; + } + + state.serialize_field("sender", &self.sender)?; + state.serialize_field("type", &self.event_type())?; + + state.end() + } +} + +impl_room_event!( + EncryptedEvent, + EncryptedEventContent, + EventType::RoomEncrypted +); + +impl FromStr for EncryptedEventContent { + type Err = InvalidEvent; + + /// Attempt to create `Self` from parsing a string of JSON data. + fn from_str(json: &str) -> Result { + let raw = serde_json::from_str::(json)?; + + match raw { + raw::EncryptedEventContent::OlmV1Curve25519AesSha2(content) => { + Ok(EncryptedEventContent::OlmV1Curve25519AesSha2(content)) + } + raw::EncryptedEventContent::MegolmV1AesSha2(content) => { + Ok(EncryptedEventContent::MegolmV1AesSha2(content)) + } + raw::EncryptedEventContent::__Nonexhaustive => { + panic!("__Nonexhaustive enum variant is not intended for use."); + } + } + } +} + +impl<'a> TryFrom<&'a str> for EncryptedEventContent { + type Error = InvalidEvent; + + /// Attempt to create `Self` from parsing a string of JSON data. + fn try_from(json: &'a str) -> Result { + FromStr::from_str(json) + } +} + +mod raw { + use super::*; + + /// This event type is used when sending encrypted events. + /// + /// This type is to be used within a room. For a to-device event, use `EncryptedEventContent` + /// directly. + #[derive(Clone, Debug, Deserialize, PartialEq)] + pub struct EncryptedEvent { + /// The event's content. + pub content: EncryptedEventContent, + + /// The unique identifier for the event. + pub event_id: EventId, + + /// Timestamp (milliseconds since the UNIX epoch) on originating homeserver when this + /// event was sent. + pub origin_server_ts: UInt, + + /// The unique identifier for the room associated with this event. + pub room_id: Option, + + /// The unique identifier for the user who sent this event. + pub sender: UserId, + + /// Additional key-value pairs not signed by the homeserver. + pub unsigned: Option, + } + + /// The payload for `EncryptedEvent`. + #[derive(Clone, Debug, PartialEq)] + pub enum EncryptedEventContent { + /// An event encrypted with *m.olm.v1.curve25519-aes-sha2*. + OlmV1Curve25519AesSha2(OlmV1Curve25519AesSha2Content), + + /// An event encrypted with *m.megolm.v1.aes-sha2*. + MegolmV1AesSha2(MegolmV1AesSha2Content), + + /// Additional variants may be added in the future and will not be considered breaking + /// changes to ruma-events. + #[doc(hidden)] + __Nonexhaustive, + } + + impl Serialize for EncryptedEventContent { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + EncryptedEventContent::OlmV1Curve25519AesSha2(ref content) => { + content.serialize(serializer) + } + EncryptedEventContent::MegolmV1AesSha2(ref content) => { + content.serialize(serializer) + } + _ => panic!("Attempted to serialize __Nonexhaustive variant."), + } + } + } + + impl<'de> Deserialize<'de> for EncryptedEventContent { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value: Value = Deserialize::deserialize(deserializer)?; + + let method_value = match value.get("algorithm") { + Some(value) => value.clone(), + None => return Err(D::Error::missing_field("algorithm")), + }; + + let method = match from_value::(method_value.clone()) { + Ok(method) => method, + Err(error) => return Err(D::Error::custom(error.to_string())), + }; + + match method { + Algorithm::OlmV1Curve25519AesSha2 => { + let content = match from_value::(value) { + Ok(content) => content, + Err(error) => return Err(D::Error::custom(error.to_string())), + }; + + Ok(EncryptedEventContent::OlmV1Curve25519AesSha2(content)) + } + Algorithm::MegolmV1AesSha2 => { + let content = match from_value::(value) { + Ok(content) => content, + Err(error) => return Err(D::Error::custom(error.to_string())), + }; + + Ok(EncryptedEventContent::MegolmV1AesSha2(content)) + } + Algorithm::Custom(_) => Err(D::Error::custom( + "Custom algorithms are not supported by `EncryptedEventContent`.", + )), + Algorithm::__Nonexhaustive => Err(D::Error::custom( + "Attempted to deserialize __Nonexhaustive variant.", + )), + } + } + } +} + +/// The payload for `EncryptedEvent` using the *m.olm.v1.curve25519-aes-sha2* algorithm. #[derive(Clone, Debug, Serialize, PartialEq, Deserialize)] pub struct OlmV1Curve25519AesSha2Content { /// The encryption algorithm used to encrypt this event. @@ -56,7 +293,7 @@ pub struct CiphertextInfo { pub message_type: UInt, } -/// The payload of an *m.room.encrypted* event using the *m.megolm.v1.aes-sha2* algorithm. +/// The payload for `EncryptedEvent` using the *m.megolm.v1.aes-sha2* algorithm. #[derive(Clone, Debug, Serialize, PartialEq, Deserialize)] pub struct MegolmV1AesSha2Content { /// The encryption algorithm used to encrypt this event. @@ -90,52 +327,9 @@ impl Serialize for EncryptedEventContent { } } -impl<'de> Deserialize<'de> for EncryptedEventContent { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let value: Value = Deserialize::deserialize(deserializer)?; - - let method_value = match value.get("algorithm") { - Some(value) => value.clone(), - None => return Err(D::Error::missing_field("algorithm")), - }; - - let method = match from_value::(method_value.clone()) { - Ok(method) => method, - Err(error) => return Err(D::Error::custom(error.to_string())), - }; - - match method { - Algorithm::OlmV1Curve25519AesSha2 => { - let content = match from_value::(value) { - Ok(content) => content, - Err(error) => return Err(D::Error::custom(error.to_string())), - }; - - Ok(EncryptedEventContent::OlmV1Curve25519AesSha2(content)) - } - Algorithm::MegolmV1AesSha2 => { - let content = match from_value::(value) { - Ok(content) => content, - Err(error) => return Err(D::Error::custom(error.to_string())), - }; - - Ok(EncryptedEventContent::MegolmV1AesSha2(content)) - } - Algorithm::Custom(_) => Err(D::Error::custom( - "Custom algorithms are not supported by `EncryptedEventContent`.", - )), - Algorithm::__Nonexhaustive => Err(D::Error::custom( - "Attempted to deserialize __Nonexhaustive variant.", - )), - } - } -} #[cfg(test)] mod tests { - use serde_json::{from_str, to_string}; + use serde_json::to_string; use super::{Algorithm, EncryptedEventContent, MegolmV1AesSha2Content}; @@ -168,9 +362,8 @@ mod tests { }); assert_eq!( - from_str::( - r#"{"algorithm":"m.megolm.v1.aes-sha2","ciphertext":"ciphertext","sender_key":"sender_key","device_id":"device_id","session_id":"session_id"}"# - ) + r#"{"algorithm":"m.megolm.v1.aes-sha2","ciphertext":"ciphertext","sender_key":"sender_key","device_id":"device_id","session_id":"session_id"}"# + .parse::() .unwrap(), key_verification_start_content ); @@ -179,7 +372,7 @@ mod tests { #[test] fn deserialization_failure() { assert!( - from_str::(r#"{"algorithm":"m.megolm.v1.aes-sha2"}"#).is_err() + r#"{"algorithm":"m.megolm.v1.aes-sha2"}"#.parse::().is_err() ); } }