Implement Improved Signalling for 1:1 VoIP
According to MSC2746
This commit is contained in:
		
							parent
							
								
									67d6df3dd2
								
							
						
					
					
						commit
						906d576a27
					
				| @ -10,9 +10,9 @@ Breaking changes: | ||||
| * Add `user_id` field to `PushConditionRoomCtx` | ||||
| * Remove `PartialEq` implementation on `NotificationPowerLevels` | ||||
| * Remove `PartialEq` implementation for `events::call::SessionDescription` | ||||
| * Split `events::call::SessionDescription` into `AnswerSessionDescription` | ||||
|   and `OfferSessionDescription` | ||||
|   * Remove `SessionDescriptionType` | ||||
| * Use new `events::call::AnswerSessionDescription` for `CallAnswerEventContent`  | ||||
|   and `OfferSessionDescription` for `CallInviteEventContent` | ||||
| * Use new `VoipVersionId` and `VoipId` types for `events::call` events | ||||
| 
 | ||||
| Improvements: | ||||
| 
 | ||||
| @ -20,6 +20,7 @@ Improvements: | ||||
| * Change `events::relation::BundledAnnotation` to a struct instead of an enum | ||||
|   * Remove `BundledReaction` | ||||
| * Add unstable support for polls (MSC3381) | ||||
| * Add unstable support for Improved Signalling for 1:1 VoIP (MSC2746) | ||||
| 
 | ||||
| # 0.9.2 | ||||
| 
 | ||||
|  | ||||
| @ -36,6 +36,7 @@ unstable-msc2448 = [] | ||||
| unstable-msc2675 = [] | ||||
| unstable-msc2676 = [] | ||||
| unstable-msc2677 = [] | ||||
| unstable-msc2746 = [] | ||||
| unstable-msc3245 = ["unstable-msc3246"] | ||||
| unstable-msc3246 = ["unstable-msc3551", "thiserror"] | ||||
| unstable-msc3381 = ["unstable-msc1767"] | ||||
|  | ||||
| @ -6,9 +6,74 @@ pub mod answer; | ||||
| pub mod candidates; | ||||
| pub mod hangup; | ||||
| pub mod invite; | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| pub mod negotiate; | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| pub mod reject; | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| pub mod select_answer; | ||||
| 
 | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| use crate::{serde::StringEnum, PrivOwnedStr}; | ||||
| 
 | ||||
| /// A VoIP session description.
 | ||||
| ///
 | ||||
| /// This is the same type as WebRTC's [`RTCSessionDescriptionInit`].
 | ||||
| ///
 | ||||
| /// [`RTCSessionDescriptionInit`]: (https://www.w3.org/TR/webrtc/#dom-rtcsessiondescriptioninit):
 | ||||
| #[derive(Clone, Debug, Deserialize, Serialize)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| pub struct SessionDescription { | ||||
|     /// The type of session description.
 | ||||
|     #[serde(rename = "type")] | ||||
|     pub session_type: SessionDescriptionType, | ||||
| 
 | ||||
|     /// The SDP text of the session description.
 | ||||
|     ///
 | ||||
|     /// With the `unstable-msc2746` feature, this field is unused if the type is `rollback` and
 | ||||
|     /// defaults to an empty string.
 | ||||
|     #[cfg_attr(feature = "unstable-msc2746", serde(default))] | ||||
|     pub sdp: String, | ||||
| } | ||||
| 
 | ||||
| impl SessionDescription { | ||||
|     /// Creates a new `SessionDescription` with the given session type and SDP text.
 | ||||
|     pub fn new(session_type: SessionDescriptionType, sdp: String) -> Self { | ||||
|         Self { session_type, sdp } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// The type of VoIP session description.
 | ||||
| ///
 | ||||
| /// This is the same type as WebRTC's [`RTCSdpType`].
 | ||||
| ///
 | ||||
| /// [`RTCSdpType`]: (https://www.w3.org/TR/webrtc/#dom-rtcsdptype):
 | ||||
| #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))] | ||||
| #[derive(Clone, Debug, PartialEq, Eq, StringEnum)] | ||||
| #[ruma_enum(rename_all = "lowercase")] | ||||
| #[non_exhaustive] | ||||
| pub enum SessionDescriptionType { | ||||
|     /// The description must be treated as an SDP final answer, and the offer-answer exchange must
 | ||||
|     /// be considered complete.
 | ||||
|     Answer, | ||||
| 
 | ||||
|     /// The description must be treated as an SDP offer.
 | ||||
|     Offer, | ||||
| 
 | ||||
|     /// The description must be treated as an SDP answer, but not final.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     PrAnswer, | ||||
| 
 | ||||
|     /// The description must be treated as cancelling the current SDP negotiation and moving the
 | ||||
|     /// SDP offer back to what it was in the previous stable state.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     Rollback, | ||||
| 
 | ||||
|     #[doc(hidden)] | ||||
|     _Custom(PrivOwnedStr), | ||||
| } | ||||
| 
 | ||||
| /// A VoIP answer session description.
 | ||||
| #[derive(Clone, Debug, Deserialize, Serialize)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| @ -40,3 +105,30 @@ impl OfferSessionDescription { | ||||
|         Self { sdp } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// The capabilities of a client in a VoIP call.
 | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| #[derive(Clone, Debug, Default, Serialize, Deserialize)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| pub struct CallCapabilities { | ||||
|     /// Whether this client supports [DTMF].
 | ||||
|     ///
 | ||||
|     /// Defaults to `false`.
 | ||||
|     ///
 | ||||
|     /// [DTMF]: https://w3c.github.io/webrtc-pc/#peer-to-peer-dtmf
 | ||||
|     #[serde(rename = "m.call.dtmf", default)] | ||||
|     pub dtmf: bool, | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| impl CallCapabilities { | ||||
|     /// Creates a default `CallCapabilities`.
 | ||||
|     pub fn new() -> Self { | ||||
|         Self::default() | ||||
|     } | ||||
| 
 | ||||
|     /// Whether this `CallCapabilities` only contains default values.
 | ||||
|     pub fn is_default(&self) -> bool { | ||||
|         !self.dtmf | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,11 +2,13 @@ | ||||
| //!
 | ||||
| //! [`m.call.answer`]: https://spec.matrix.org/v1.2/client-server-api/#mcallanswer
 | ||||
| 
 | ||||
| use js_int::UInt; | ||||
| use ruma_macros::EventContent; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| use super::AnswerSessionDescription; | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| use super::CallCapabilities; | ||||
| use crate::{OwnedVoipId, VoipVersionId}; | ||||
| 
 | ||||
| /// The content of an `m.call.answer` event.
 | ||||
| ///
 | ||||
| @ -18,16 +20,56 @@ pub struct CallAnswerEventContent { | ||||
|     /// The VoIP session description object.
 | ||||
|     pub answer: AnswerSessionDescription, | ||||
| 
 | ||||
|     /// The ID of the call this event relates to.
 | ||||
|     pub call_id: String, | ||||
|     /// A unique identifier for the call.
 | ||||
|     pub call_id: OwnedVoipId, | ||||
| 
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     /// **Required in VoIP version 1.** A unique ID for this session for the duration of the call.
 | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub party_id: Option<OwnedVoipId>, | ||||
| 
 | ||||
|     /// The version of the VoIP specification this messages adheres to.
 | ||||
|     pub version: UInt, | ||||
|     pub version: VoipVersionId, | ||||
| 
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     /// **Added in VoIP version 1.** The VoIP capabilities of the client.
 | ||||
|     #[serde(default, skip_serializing_if = "CallCapabilities::is_default")] | ||||
|     pub capabilities: CallCapabilities, | ||||
| } | ||||
| 
 | ||||
| impl CallAnswerEventContent { | ||||
|     /// Creates an `AnswerEventContent` with the given answer, call ID and VoIP version.
 | ||||
|     pub fn new(answer: AnswerSessionDescription, call_id: String, version: UInt) -> Self { | ||||
|         Self { answer, call_id, version } | ||||
|     /// Creates an `CallAnswerEventContent` with the given answer, call ID and VoIP version.
 | ||||
|     pub fn new( | ||||
|         answer: AnswerSessionDescription, | ||||
|         call_id: OwnedVoipId, | ||||
|         version: VoipVersionId, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
|             answer, | ||||
|             call_id, | ||||
|             #[cfg(feature = "unstable-msc2746")] | ||||
|             party_id: None, | ||||
|             version, | ||||
|             #[cfg(feature = "unstable-msc2746")] | ||||
|             capabilities: Default::default(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a VoIP version 0 `CallAnswerEventContent` with all the required
 | ||||
|     /// fields.
 | ||||
|     pub fn version_0(answer: AnswerSessionDescription, call_id: OwnedVoipId) -> Self { | ||||
|         Self::new(answer, call_id, VoipVersionId::V0) | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a VoIP version 1 `CallAnswerEventContent` with all the required
 | ||||
|     /// fields.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     pub fn version_1( | ||||
|         answer: AnswerSessionDescription, | ||||
|         call_id: OwnedVoipId, | ||||
|         party_id: OwnedVoipId, | ||||
|         capabilities: CallCapabilities, | ||||
|     ) -> Self { | ||||
|         Self { answer, call_id, party_id: Some(party_id), version: VoipVersionId::V1, capabilities } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -6,6 +6,8 @@ use js_int::UInt; | ||||
| use ruma_macros::EventContent; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| use crate::{OwnedVoipId, VoipVersionId}; | ||||
| 
 | ||||
| /// The content of an `m.call.candidates` event.
 | ||||
| ///
 | ||||
| /// This event is sent by callers after sending an invite and by the callee after answering. Its
 | ||||
| @ -14,21 +16,56 @@ use serde::{Deserialize, Serialize}; | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| #[ruma_event(type = "m.call.candidates", kind = MessageLike)] | ||||
| pub struct CallCandidatesEventContent { | ||||
|     /// The ID of the call this event relates to.
 | ||||
|     pub call_id: String, | ||||
|     /// A unique identifier for the call.
 | ||||
|     pub call_id: OwnedVoipId, | ||||
| 
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     /// **Required in VoIP version 1.** The unique ID for this session for the duration of the
 | ||||
|     /// call.
 | ||||
|     ///
 | ||||
|     /// Must be the same as the one sent by the previous invite or answer from
 | ||||
|     /// this session.
 | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub party_id: Option<OwnedVoipId>, | ||||
| 
 | ||||
|     /// A list of candidates.
 | ||||
|     ///
 | ||||
|     /// With the `unstable-msc2746` feature, in VoIP version 1, this list should end with a
 | ||||
|     /// `Candidate` with an empty `candidate` field when no more candidates will be sent.
 | ||||
|     pub candidates: Vec<Candidate>, | ||||
| 
 | ||||
|     /// The version of the VoIP specification this messages adheres to.
 | ||||
|     pub version: UInt, | ||||
|     pub version: VoipVersionId, | ||||
| } | ||||
| 
 | ||||
| impl CallCandidatesEventContent { | ||||
|     /// Creates a new `CandidatesEventContent` with the given call id, candidate list and VoIP
 | ||||
|     /// Creates a new `CallCandidatesEventContent` with the given call id, candidate list and VoIP
 | ||||
|     /// version.
 | ||||
|     pub fn new(call_id: String, candidates: Vec<Candidate>, version: UInt) -> Self { | ||||
|         Self { call_id, candidates, version } | ||||
|     pub fn new(call_id: OwnedVoipId, candidates: Vec<Candidate>, version: VoipVersionId) -> Self { | ||||
|         Self { | ||||
|             call_id, | ||||
|             candidates, | ||||
|             version, | ||||
|             #[cfg(feature = "unstable-msc2746")] | ||||
|             party_id: None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a VoIP version 0 `CallCandidatesEventContent` with all the
 | ||||
|     /// required fields.
 | ||||
|     pub fn version_0(call_id: OwnedVoipId, candidates: Vec<Candidate>) -> Self { | ||||
|         Self::new(call_id, candidates, VoipVersionId::V0) | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a VoIP version 1 `CallCandidatesEventContent` with all the
 | ||||
|     /// required fields.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     pub fn version_1( | ||||
|         call_id: OwnedVoipId, | ||||
|         party_id: OwnedVoipId, | ||||
|         candidates: Vec<Candidate>, | ||||
|     ) -> Self { | ||||
|         Self { call_id, party_id: Some(party_id), candidates, version: VoipVersionId::V1 } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -2,35 +2,80 @@ | ||||
| //!
 | ||||
| //! [`m.call.hangup`]: https://spec.matrix.org/v1.2/client-server-api/#mcallhangup
 | ||||
| 
 | ||||
| use js_int::UInt; | ||||
| use ruma_macros::EventContent; | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| use serde::Serializer; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| use crate::{serde::StringEnum, PrivOwnedStr}; | ||||
| use crate::{serde::StringEnum, OwnedVoipId, PrivOwnedStr, VoipVersionId}; | ||||
| 
 | ||||
| /// The content of an `m.call.hangup` event.
 | ||||
| ///
 | ||||
| /// Sent by either party to signal their termination of the call. This can be sent either once the
 | ||||
| /// call has has been established or before to abort the call.
 | ||||
| /// Sent by either party to signal their termination of the call.
 | ||||
| ///
 | ||||
| /// In VoIP version 0, this can be sent either once the call has been established or before to abort
 | ||||
| /// the call.
 | ||||
| ///
 | ||||
| /// With the `unstable-msc2746` feature, and if the call is using VoIP version 1, this should only
 | ||||
| /// be sent by the caller after sending the invite or by the callee after answering the invite. To
 | ||||
| /// reject an invite, send an [`m.call.reject`] event.
 | ||||
| ///
 | ||||
| /// [`m.call.reject`]: super::reject::CallRejectEventContent
 | ||||
| #[derive(Clone, Debug, Deserialize, Serialize, EventContent)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| #[ruma_event(type = "m.call.hangup", kind = MessageLike)] | ||||
| pub struct CallHangupEventContent { | ||||
|     /// The ID of the call this event relates to.
 | ||||
|     pub call_id: String, | ||||
|     /// A unique identifier for the call.
 | ||||
|     pub call_id: OwnedVoipId, | ||||
| 
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     /// **Required in VoIP version 1.** A unique ID for this session for the duration of the call.
 | ||||
|     ///
 | ||||
|     /// Must be the same as the one sent by the previous invite or answer from
 | ||||
|     /// this session.
 | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub party_id: Option<OwnedVoipId>, | ||||
| 
 | ||||
|     /// The version of the VoIP specification this messages adheres to.
 | ||||
|     pub version: UInt, | ||||
|     pub version: VoipVersionId, | ||||
| 
 | ||||
|     /// Optional error reason for the hangup.
 | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     ///
 | ||||
|     /// With the `unstable-msc2746` feature, this field defaults to `Some(Reason::UserHangup)`.
 | ||||
|     #[cfg_attr(not(feature = "unstable-msc2746"), serde(skip_serializing_if = "Option::is_none"))] | ||||
|     #[cfg_attr(
 | ||||
|         feature = "unstable-msc2746", | ||||
|         serde( | ||||
|             default = "Reason::option_with_default", | ||||
|             serialize_with = "Reason::serialize_option_with_default" | ||||
|         ) | ||||
|     )] | ||||
|     pub reason: Option<Reason>, | ||||
| } | ||||
| 
 | ||||
| impl CallHangupEventContent { | ||||
|     /// Creates a new `HangupEventContent` with the given call ID and VoIP version.
 | ||||
|     pub fn new(call_id: String, version: UInt) -> Self { | ||||
|         Self { call_id, version, reason: None } | ||||
|     /// Creates a new `CallHangupEventContent` with the given call ID and VoIP version.
 | ||||
|     pub fn new(call_id: OwnedVoipId, version: VoipVersionId) -> Self { | ||||
|         Self { | ||||
|             call_id, | ||||
|             #[cfg(feature = "unstable-msc2746")] | ||||
|             party_id: None, | ||||
|             version, | ||||
|             reason: Default::default(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a VoIP version 0 `CallHangupEventContent` with all the required
 | ||||
|     /// fields.
 | ||||
|     pub fn version_0(call_id: OwnedVoipId) -> Self { | ||||
|         Self::new(call_id, VoipVersionId::V0) | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a VoIP version 1 `CallHangupEventContent` with all the required
 | ||||
|     /// fields.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     pub fn version_1(call_id: OwnedVoipId, party_id: OwnedVoipId, reason: Reason) -> Self { | ||||
|         Self { call_id, party_id: Some(party_id), version: VoipVersionId::V1, reason: Some(reason) } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -50,6 +95,32 @@ pub enum Reason { | ||||
|     /// Party did not answer in time.
 | ||||
|     InviteTimeout, | ||||
| 
 | ||||
|     /// The connection failed after some media was exchanged.
 | ||||
|     ///
 | ||||
|     /// Note that, in the case of an ICE renegotiation, a client should be sure to send
 | ||||
|     /// `ice_timeout` rather than `ice_failed` if media had previously been received successfully,
 | ||||
|     /// even if the ICE renegotiation itself failed.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     IceTimeout, | ||||
| 
 | ||||
|     /// The user chose to end the call.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     UserHangup, | ||||
| 
 | ||||
|     /// The client was unable to start capturing media in such a way as it is unable to continue
 | ||||
|     /// the call.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     UserMediaFailed, | ||||
| 
 | ||||
|     /// The user is busy.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     UserBusy, | ||||
| 
 | ||||
|     /// Some other failure occurred that meant the client was unable to continue the call rather
 | ||||
|     /// than the user choosing to end it.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     UnknownError, | ||||
| 
 | ||||
|     #[doc(hidden)] | ||||
|     _Custom(PrivOwnedStr), | ||||
| } | ||||
| @ -59,4 +130,31 @@ impl Reason { | ||||
|     pub fn as_str(&self) -> &str { | ||||
|         self.as_ref() | ||||
|     } | ||||
| 
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     fn serialize_option_with_default<S>( | ||||
|         reason: &Option<Reason>, | ||||
|         serializer: S, | ||||
|     ) -> Result<S::Ok, S::Error> | ||||
|     where | ||||
|         S: Serializer, | ||||
|     { | ||||
|         if let Some(reason) = &reason { | ||||
|             reason.serialize(serializer) | ||||
|         } else { | ||||
|             Self::default().serialize(serializer) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     fn option_with_default() -> Option<Self> { | ||||
|         Some(Self::default()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| impl Default for Reason { | ||||
|     fn default() -> Self { | ||||
|         Self::UserHangup | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -6,7 +6,12 @@ use js_int::UInt; | ||||
| use ruma_macros::EventContent; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| use super::CallCapabilities; | ||||
| use super::OfferSessionDescription; | ||||
| #[cfg(feature = "unstable-msc2746")] | ||||
| use crate::OwnedUserId; | ||||
| use crate::{OwnedVoipId, VoipVersionId}; | ||||
| 
 | ||||
| /// The content of an `m.call.invite` event.
 | ||||
| ///
 | ||||
| @ -16,7 +21,12 @@ use super::OfferSessionDescription; | ||||
| #[ruma_event(type = "m.call.invite", kind = MessageLike)] | ||||
| pub struct CallInviteEventContent { | ||||
|     /// A unique identifier for the call.
 | ||||
|     pub call_id: String, | ||||
|     pub call_id: OwnedVoipId, | ||||
| 
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     /// **Required in VoIP version 1.** A unique ID for this session for the duration of the call.
 | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub party_id: Option<OwnedVoipId>, | ||||
| 
 | ||||
|     /// The time in milliseconds that the invite is valid for.
 | ||||
|     ///
 | ||||
| @ -28,17 +38,70 @@ pub struct CallInviteEventContent { | ||||
|     pub offer: OfferSessionDescription, | ||||
| 
 | ||||
|     /// The version of the VoIP specification this messages adheres to.
 | ||||
|     pub version: UInt, | ||||
|     pub version: VoipVersionId, | ||||
| 
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     /// **Added in VoIP version 1.** The VoIP capabilities of the client.
 | ||||
|     #[serde(default, skip_serializing_if = "CallCapabilities::is_default")] | ||||
|     pub capabilities: CallCapabilities, | ||||
| 
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     /// **Added in VoIP version 1.** The intended target of the invite, if any.
 | ||||
|     ///
 | ||||
|     /// If this is `None`, the invite is intended for any member of the room, except the sender.
 | ||||
|     ///
 | ||||
|     /// The invite should be ignored if the invitee is set and doesn't match the user's ID.
 | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub invitee: Option<OwnedUserId>, | ||||
| } | ||||
| 
 | ||||
| impl CallInviteEventContent { | ||||
|     /// Creates a new `InviteEventContent` with the given call ID, lifetime and VoIP version.
 | ||||
|     /// Creates a new `CallInviteEventContent` with the given call ID, lifetime, offer and VoIP
 | ||||
|     /// version.
 | ||||
|     pub fn new( | ||||
|         call_id: String, | ||||
|         call_id: OwnedVoipId, | ||||
|         lifetime: UInt, | ||||
|         offer: OfferSessionDescription, | ||||
|         version: UInt, | ||||
|         version: VoipVersionId, | ||||
|     ) -> Self { | ||||
|         Self { call_id, lifetime, offer, version } | ||||
|         Self { | ||||
|             call_id, | ||||
|             #[cfg(feature = "unstable-msc2746")] | ||||
|             party_id: None, | ||||
|             lifetime, | ||||
|             offer, | ||||
|             version, | ||||
|             #[cfg(feature = "unstable-msc2746")] | ||||
|             capabilities: Default::default(), | ||||
|             #[cfg(feature = "unstable-msc2746")] | ||||
|             invitee: None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a version 0 `CallInviteEventContent` with all the required
 | ||||
|     /// fields.
 | ||||
|     pub fn version_0(call_id: OwnedVoipId, lifetime: UInt, offer: OfferSessionDescription) -> Self { | ||||
|         Self::new(call_id, lifetime, offer, VoipVersionId::V0) | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a version 1 `CallInviteEventContent` with all the required
 | ||||
|     /// fields.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     pub fn version_1( | ||||
|         call_id: OwnedVoipId, | ||||
|         party_id: OwnedVoipId, | ||||
|         lifetime: UInt, | ||||
|         offer: OfferSessionDescription, | ||||
|         capabilities: CallCapabilities, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
|             call_id, | ||||
|             party_id: Some(party_id), | ||||
|             lifetime, | ||||
|             offer, | ||||
|             version: VoipVersionId::V1, | ||||
|             capabilities, | ||||
|             invitee: None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										50
									
								
								crates/ruma-common/src/events/call/negotiate.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								crates/ruma-common/src/events/call/negotiate.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| //! Types for the `m.call.negotiate` event [MSC2746].
 | ||||
| //!
 | ||||
| //! [MSC2746]: https://github.com/matrix-org/matrix-spec-proposals/pull/2746
 | ||||
| 
 | ||||
| use js_int::UInt; | ||||
| use ruma_macros::EventContent; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| use super::SessionDescription; | ||||
| use crate::OwnedVoipId; | ||||
| 
 | ||||
| /// **Added in VoIP version 1.** The content of an `m.call.negotiate` event.
 | ||||
| ///
 | ||||
| /// This event is sent by either party after the call is established to renegotiate it. It can be
 | ||||
| /// used for media pause, hold/resume, ICE restarts and voice/video call up/downgrading.
 | ||||
| ///
 | ||||
| /// First an event must be sent with an `offer` session description, which is replied to with an
 | ||||
| /// event with an `answer` session description.
 | ||||
| #[derive(Clone, Debug, Deserialize, Serialize, EventContent)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| #[ruma_event(type = "m.call.negotiate", kind = MessageLike)] | ||||
| pub struct CallNegotiateEventContent { | ||||
|     /// The ID of the call this event relates to.
 | ||||
|     pub call_id: OwnedVoipId, | ||||
| 
 | ||||
|     /// The unique ID for this session for the duration of the call.
 | ||||
|     ///
 | ||||
|     /// Must be the same as the one sent by the previous invite or answer from
 | ||||
|     /// this session.
 | ||||
|     pub party_id: OwnedVoipId, | ||||
| 
 | ||||
|     /// The time in milliseconds that the negotiation is valid for.
 | ||||
|     pub lifetime: UInt, | ||||
| 
 | ||||
|     /// The session description of the negotiation.
 | ||||
|     pub description: SessionDescription, | ||||
| } | ||||
| 
 | ||||
| impl CallNegotiateEventContent { | ||||
|     /// Creates a `CallNegotiateEventContent` with the given call ID, party ID, lifetime and
 | ||||
|     /// description.
 | ||||
|     pub fn new( | ||||
|         call_id: OwnedVoipId, | ||||
|         party_id: OwnedVoipId, | ||||
|         lifetime: UInt, | ||||
|         description: SessionDescription, | ||||
|     ) -> Self { | ||||
|         Self { call_id, party_id, lifetime, description } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								crates/ruma-common/src/events/call/reject.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								crates/ruma-common/src/events/call/reject.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| //! Types for the `m.call.reject` event [MSC2746].
 | ||||
| //!
 | ||||
| //! [MSC2746]: https://github.com/matrix-org/matrix-spec-proposals/pull/2746
 | ||||
| 
 | ||||
| use ruma_macros::EventContent; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| use crate::{OwnedVoipId, VoipVersionId}; | ||||
| 
 | ||||
| /// **Added in VoIP version 1.** The content of an `m.call.reject` event.
 | ||||
| ///
 | ||||
| /// Starting from VoIP version 1, this event is sent by the callee to reject an invite.
 | ||||
| #[derive(Clone, Debug, Deserialize, Serialize, EventContent)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| #[ruma_event(type = "m.call.reject", kind = MessageLike)] | ||||
| pub struct CallRejectEventContent { | ||||
|     /// The ID of the call this event relates to.
 | ||||
|     pub call_id: OwnedVoipId, | ||||
| 
 | ||||
|     /// A unique ID for this session for the duration of the call.
 | ||||
|     pub party_id: OwnedVoipId, | ||||
| 
 | ||||
|     /// The version of the VoIP specification this messages adheres to.
 | ||||
|     ///
 | ||||
|     /// Cannot be older than `VoipVersionId::V1`.
 | ||||
|     pub version: VoipVersionId, | ||||
| } | ||||
| 
 | ||||
| impl CallRejectEventContent { | ||||
|     /// Creates a `CallRejectEventContent` with the given call ID, VoIP version and party ID.
 | ||||
|     pub fn new(call_id: OwnedVoipId, party_id: OwnedVoipId, version: VoipVersionId) -> Self { | ||||
|         Self { call_id, party_id, version } | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a version 1 `CallRejectEventContent` with all the required
 | ||||
|     /// fields.
 | ||||
|     pub fn version_1(call_id: OwnedVoipId, party_id: OwnedVoipId) -> Self { | ||||
|         Self::new(call_id, party_id, VoipVersionId::V1) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										55
									
								
								crates/ruma-common/src/events/call/select_answer.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								crates/ruma-common/src/events/call/select_answer.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| //! Types for the `m.call.select_answer` event [MSC2746].
 | ||||
| //!
 | ||||
| //! [MSC2746]: https://github.com/matrix-org/matrix-spec-proposals/pull/2746
 | ||||
| 
 | ||||
| use ruma_macros::EventContent; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| use crate::{OwnedVoipId, VoipVersionId}; | ||||
| 
 | ||||
| /// **Added in VoIP version 1.** The content of an `m.call.select_answer` event.
 | ||||
| ///
 | ||||
| /// This event is sent by the caller when it has chosen an answer.
 | ||||
| #[derive(Clone, Debug, Deserialize, Serialize, EventContent)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| #[ruma_event(type = "m.call.select_answer", kind = MessageLike)] | ||||
| pub struct CallSelectAnswerEventContent { | ||||
|     /// The ID of the call this event relates to.
 | ||||
|     pub call_id: OwnedVoipId, | ||||
| 
 | ||||
|     /// A unique ID for this session for the duration of the call.
 | ||||
|     ///
 | ||||
|     /// Must be the same as the one sent by the previous invite from this session.
 | ||||
|     pub party_id: OwnedVoipId, | ||||
| 
 | ||||
|     /// The party ID of the selected answer to the previously sent invite.
 | ||||
|     pub selected_party_id: OwnedVoipId, | ||||
| 
 | ||||
|     /// The version of the VoIP specification this messages adheres to.
 | ||||
|     ///
 | ||||
|     /// Cannot be older than `VoipVersionId::V1`.
 | ||||
|     pub version: VoipVersionId, | ||||
| } | ||||
| 
 | ||||
| impl CallSelectAnswerEventContent { | ||||
|     /// Creates a `CallSelectAnswerEventContent` with the given call ID, VoIP version, party ID and
 | ||||
|     /// selected party ID.
 | ||||
|     pub fn new( | ||||
|         call_id: OwnedVoipId, | ||||
|         party_id: OwnedVoipId, | ||||
|         selected_party_id: OwnedVoipId, | ||||
|         version: VoipVersionId, | ||||
|     ) -> Self { | ||||
|         Self { call_id, party_id, selected_party_id, version } | ||||
|     } | ||||
| 
 | ||||
|     /// Convenience method to create a version 1 `CallSelectAnswerEventContent` with all the
 | ||||
|     /// required fields.
 | ||||
|     pub fn version_1( | ||||
|         call_id: OwnedVoipId, | ||||
|         party_id: OwnedVoipId, | ||||
|         selected_party_id: OwnedVoipId, | ||||
|     ) -> Self { | ||||
|         Self::new(call_id, party_id, selected_party_id, VoipVersionId::V1) | ||||
|     } | ||||
| } | ||||
| @ -43,6 +43,12 @@ event_enum! { | ||||
|         "m.call.invite" => super::call::invite, | ||||
|         "m.call.hangup" => super::call::hangup, | ||||
|         "m.call.candidates" => super::call::candidates, | ||||
|         #[cfg(feature = "unstable-msc2746")] | ||||
|         "m.call.negotiate" => super::call::negotiate, | ||||
|         #[cfg(feature = "unstable-msc2746")] | ||||
|         "m.call.reject" => super::call::reject, | ||||
|         #[cfg(feature = "unstable-msc2746")] | ||||
|         "m.call.select_answer" => super::call::select_answer, | ||||
|         #[cfg(feature = "unstable-msc1767")] | ||||
|         "m.emote" => super::emote, | ||||
|         #[cfg(feature = "unstable-msc3551")] | ||||
| @ -346,6 +352,8 @@ impl AnyMessageLikeEventContent { | ||||
|             } | ||||
|             #[cfg(feature = "unstable-msc3381")] | ||||
|             Self::PollStart(_) => None, | ||||
|             #[cfg(feature = "unstable-msc2746")] | ||||
|             Self::CallNegotiate(_) | Self::CallReject(_) | Self::CallSelectAnswer(_) => None, | ||||
|             Self::CallAnswer(_) | ||||
|             | Self::CallInvite(_) | ||||
|             | Self::CallHangup(_) | ||||
|  | ||||
| @ -32,10 +32,13 @@ pub use self::{ | ||||
|     signatures::{DeviceSignatures, EntitySignatures, ServerSignatures, Signatures}, | ||||
|     transaction_id::{OwnedTransactionId, TransactionId}, | ||||
|     user_id::{OwnedUserId, UserId}, | ||||
|     voip_id::{OwnedVoipId, VoipId}, | ||||
|     voip_version_id::VoipVersionId, | ||||
| }; | ||||
| #[doc(inline)] | ||||
| pub use ruma_identifiers_validation::error::{ | ||||
|     Error as IdParseError, MatrixIdError, MatrixToError, MatrixUriError, MxcUriError, | ||||
|     VoipVersionIdError, | ||||
| }; | ||||
| 
 | ||||
| pub mod matrix_uri; | ||||
| @ -58,6 +61,8 @@ mod server_name; | ||||
| mod session_id; | ||||
| mod signatures; | ||||
| mod transaction_id; | ||||
| mod voip_id; | ||||
| mod voip_version_id; | ||||
| 
 | ||||
| /// Generates a random identifier localpart.
 | ||||
| #[cfg(feature = "rand")] | ||||
|  | ||||
							
								
								
									
										38
									
								
								crates/ruma-common/src/identifiers/voip_id.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								crates/ruma-common/src/identifiers/voip_id.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| //! VoIP identifier.
 | ||||
| 
 | ||||
| use ruma_macros::IdZst; | ||||
| 
 | ||||
| /// A VoIP identifier.
 | ||||
| ///
 | ||||
| /// VoIP IDs in Matrix are opaque strings. This type is provided simply for its semantic
 | ||||
| /// value.
 | ||||
| ///
 | ||||
| /// You can create one from a string (using `VoipId::parse()`) but the recommended way is to
 | ||||
| /// use `VoipId::new()` to generate a random one. If that function is not available for you,
 | ||||
| /// you need to activate this crate's `rand` Cargo feature.
 | ||||
| #[repr(transparent)] | ||||
| #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)] | ||||
| pub struct VoipId(str); | ||||
| 
 | ||||
| impl VoipId { | ||||
|     /// Creates a random VoIP identifier.
 | ||||
|     ///
 | ||||
|     /// This will currently be a UUID without hyphens, but no guarantees are made about the
 | ||||
|     /// structure of client secrets generated from this function.
 | ||||
|     #[cfg(feature = "rand")] | ||||
|     #[allow(clippy::new_ret_no_self)] | ||||
|     pub fn new() -> OwnedVoipId { | ||||
|         let id = uuid::Uuid::new_v4(); | ||||
|         VoipId::from_borrowed(&id.simple().to_string()).to_owned() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::VoipId; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn try_from() { | ||||
|         assert!(<&VoipId>::try_from("this_-_a_valid_secret_1337").is_ok()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										224
									
								
								crates/ruma-common/src/identifiers/voip_version_id.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								crates/ruma-common/src/identifiers/voip_version_id.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,224 @@ | ||||
| //! Matrix VoIP version identifier.
 | ||||
| 
 | ||||
| use std::fmt; | ||||
| 
 | ||||
| use js_int::UInt; | ||||
| use ruma_macros::DisplayAsRefStr; | ||||
| use serde::{ | ||||
|     de::{self, Visitor}, | ||||
|     Deserialize, Deserializer, Serialize, | ||||
| }; | ||||
| 
 | ||||
| use crate::{IdParseError, PrivOwnedStr}; | ||||
| 
 | ||||
| /// A Matrix VoIP version ID.
 | ||||
| ///
 | ||||
| /// A `VoipVersionId` representing VoIP version 0 can be converted or deserialized from a `UInt`,
 | ||||
| /// and can be converted or serialized back into a `UInt` as needed.
 | ||||
| ///
 | ||||
| /// Custom room versions or ones that were introduced into the specification after this code was
 | ||||
| /// written are represented by a hidden enum variant. They can be converted or deserialized from a
 | ||||
| /// string slice, and can be converted or serialized back into a string as needed.
 | ||||
| ///
 | ||||
| /// ```
 | ||||
| /// # use ruma_common::VoipVersionId;
 | ||||
| /// assert_eq!(VoipVersionId::try_from("1").unwrap().as_ref(), "1");
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// For simplicity, version 0 has a string representation, but trying to construct a `VoipVersionId`
 | ||||
| /// from a `"0"` string will not result in the `V0` variant.
 | ||||
| #[derive(Clone, Debug, PartialEq, Eq, Hash, DisplayAsRefStr)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| pub enum VoipVersionId { | ||||
|     /// A version 0 VoIP call.
 | ||||
|     V0, | ||||
| 
 | ||||
|     /// A version 1 VoIP call.
 | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     V1, | ||||
| 
 | ||||
|     #[doc(hidden)] | ||||
|     _Custom(PrivOwnedStr), | ||||
| } | ||||
| 
 | ||||
| impl VoipVersionId { | ||||
|     /// Creates a string slice from this `VoipVersionId`.
 | ||||
|     pub fn as_str(&self) -> &str { | ||||
|         match &self { | ||||
|             Self::V0 => "0", | ||||
|             #[cfg(feature = "unstable-msc2746")] | ||||
|             Self::V1 => "1", | ||||
|             Self::_Custom(PrivOwnedStr(s)) => s, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Creates a byte slice from this `VoipVersionId`.
 | ||||
|     pub fn as_bytes(&self) -> &[u8] { | ||||
|         self.as_str().as_bytes() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<VoipVersionId> for String { | ||||
|     fn from(id: VoipVersionId) -> Self { | ||||
|         match id { | ||||
|             VoipVersionId::_Custom(PrivOwnedStr(version)) => version.into(), | ||||
|             _ => id.as_str().to_owned(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl AsRef<str> for VoipVersionId { | ||||
|     fn as_ref(&self) -> &str { | ||||
|         self.as_str() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'de> Deserialize<'de> for VoipVersionId { | ||||
|     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||||
|     where | ||||
|         D: Deserializer<'de>, | ||||
|     { | ||||
|         struct CallVersionVisitor; | ||||
| 
 | ||||
|         impl<'de> Visitor<'de> for CallVersionVisitor { | ||||
|             type Value = VoipVersionId; | ||||
| 
 | ||||
|             fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|                 formatter.write_str("0 or string") | ||||
|             } | ||||
| 
 | ||||
|             fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> | ||||
|             where | ||||
|                 E: de::Error, | ||||
|             { | ||||
|                 Ok(value.into()) | ||||
|             } | ||||
| 
 | ||||
|             fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> | ||||
|             where | ||||
|                 E: de::Error, | ||||
|             { | ||||
|                 let uint = UInt::try_from(value).map_err(de::Error::custom)?; | ||||
|                 Self::Value::try_from(uint).map_err(de::Error::custom) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         deserializer.deserialize_any(CallVersionVisitor) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Serialize for VoipVersionId { | ||||
|     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||||
|     where | ||||
|         S: serde::Serializer, | ||||
|     { | ||||
|         match self { | ||||
|             Self::V0 => serializer.serialize_u64(0), | ||||
|             _ => serializer.serialize_str(self.as_str()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<UInt> for VoipVersionId { | ||||
|     type Error = IdParseError; | ||||
| 
 | ||||
|     fn try_from(u: UInt) -> Result<Self, Self::Error> { | ||||
|         ruma_identifiers_validation::voip_version_id::validate(u)?; | ||||
|         Ok(Self::V0) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn from<T>(s: T) -> VoipVersionId | ||||
| where | ||||
|     T: AsRef<str> + Into<Box<str>>, | ||||
| { | ||||
|     match s.as_ref() { | ||||
|         #[cfg(feature = "unstable-msc2746")] | ||||
|         "1" => VoipVersionId::V1, | ||||
|         _ => VoipVersionId::_Custom(PrivOwnedStr(s.into())), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<&str> for VoipVersionId { | ||||
|     fn from(s: &str) -> Self { | ||||
|         from(s) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<String> for VoipVersionId { | ||||
|     fn from(s: String) -> Self { | ||||
|         from(s) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use assert_matches::assert_matches; | ||||
|     use js_int::uint; | ||||
|     use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; | ||||
| 
 | ||||
|     use super::VoipVersionId; | ||||
|     use crate::IdParseError; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn valid_version_0() { | ||||
|         assert_eq!(VoipVersionId::try_from(uint!(0)), Ok(VoipVersionId::V0)); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn invalid_uint_version() { | ||||
|         assert_matches!( | ||||
|             VoipVersionId::try_from(uint!(1)), | ||||
|             Err(IdParseError::InvalidVoipVersionId(_)) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     fn valid_version_1() { | ||||
|         assert_eq!(VoipVersionId::try_from("1"), Ok(VoipVersionId::V1)); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn valid_custom_string_version() { | ||||
|         let version = assert_matches!( | ||||
|             VoipVersionId::try_from("io.ruma.2"), | ||||
|             Ok(version) => version | ||||
|         ); | ||||
|         assert_eq!(version.as_ref(), "io.ruma.2"); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn serialize_version_0() { | ||||
|         assert_eq!(to_json_value(&VoipVersionId::V0).unwrap(), json!(0)); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn deserialize_version_0() { | ||||
|         assert_eq!(from_json_value::<VoipVersionId>(json!(0)).unwrap(), VoipVersionId::V0); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     fn serialize_version_1() { | ||||
|         assert_eq!(to_json_value(&VoipVersionId::V1).unwrap(), json!("1")); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[cfg(feature = "unstable-msc2746")] | ||||
|     fn deserialize_version_1() { | ||||
|         assert_eq!(from_json_value::<VoipVersionId>(json!("1")).unwrap(), VoipVersionId::V1); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn serialize_custom_string() { | ||||
|         let version = VoipVersionId::try_from("io.ruma.1").unwrap(); | ||||
|         assert_eq!(to_json_value(&version).unwrap(), json!("io.ruma.1")); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn deserialize_custom_string() { | ||||
|         let version = VoipVersionId::try_from("io.ruma.1").unwrap(); | ||||
|         assert_eq!(from_json_value::<VoipVersionId>(json!("io.ruma.1")).unwrap(), version); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										587
									
								
								crates/ruma-common/tests/events/call.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										587
									
								
								crates/ruma-common/tests/events/call.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,587 @@ | ||||
| #![cfg(feature = "unstable-msc2746")] | ||||
| 
 | ||||
| use assert_matches::assert_matches; | ||||
| use assign::assign; | ||||
| use js_int::uint; | ||||
| use ruma_common::{ | ||||
|     event_id, | ||||
|     events::{ | ||||
|         call::{ | ||||
|             answer::CallAnswerEventContent, | ||||
|             candidates::{CallCandidatesEventContent, Candidate}, | ||||
|             hangup::{CallHangupEventContent, Reason}, | ||||
|             invite::CallInviteEventContent, | ||||
|             negotiate::CallNegotiateEventContent, | ||||
|             reject::CallRejectEventContent, | ||||
|             select_answer::CallSelectAnswerEventContent, | ||||
|             AnswerSessionDescription, CallCapabilities, OfferSessionDescription, | ||||
|             SessionDescription, SessionDescriptionType, | ||||
|         }, | ||||
|         AnyMessageLikeEvent, MessageLikeEvent, MessageLikeUnsigned, OriginalMessageLikeEvent, | ||||
|     }, | ||||
|     room_id, user_id, MilliSecondsSinceUnixEpoch, VoipVersionId, | ||||
| }; | ||||
| use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; | ||||
| 
 | ||||
| #[test] | ||||
| fn invite_content_serialization() { | ||||
|     let event_content = CallInviteEventContent::version_0( | ||||
|         "abcdef".into(), | ||||
|         uint!(30000), | ||||
|         OfferSessionDescription::new("not a real sdp".to_owned()), | ||||
|     ); | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event_content).unwrap(), | ||||
|         json!({ | ||||
|             "call_id": "abcdef", | ||||
|             "lifetime": 30000, | ||||
|             "version": 0, | ||||
|             "offer": { | ||||
|                 "type": "offer", | ||||
|                 "sdp": "not a real sdp", | ||||
|             }, | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn invite_event_serialization() { | ||||
|     let event = OriginalMessageLikeEvent { | ||||
|         content: CallInviteEventContent::version_1( | ||||
|             "abcdef".into(), | ||||
|             "9876".into(), | ||||
|             uint!(60000), | ||||
|             OfferSessionDescription::new("not a real sdp".to_owned()), | ||||
|             CallCapabilities::new(), | ||||
|         ), | ||||
|         event_id: event_id!("$event:notareal.hs").to_owned(), | ||||
|         sender: user_id!("@user:notareal.hs").to_owned(), | ||||
|         origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)), | ||||
|         room_id: room_id!("!roomid:notareal.hs").to_owned(), | ||||
|         unsigned: MessageLikeUnsigned::default(), | ||||
|     }; | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event).unwrap(), | ||||
|         json!({ | ||||
|             "content": { | ||||
|                 "call_id": "abcdef", | ||||
|                 "party_id": "9876", | ||||
|                 "lifetime": 60000, | ||||
|                 "version": "1", | ||||
|                 "offer": { | ||||
|                     "type": "offer", | ||||
|                     "sdp": "not a real sdp", | ||||
|                 }, | ||||
|             }, | ||||
|             "event_id": "$event:notareal.hs", | ||||
|             "origin_server_ts": 134_829_848, | ||||
|             "room_id": "!roomid:notareal.hs", | ||||
|             "sender": "@user:notareal.hs", | ||||
|             "type": "m.call.invite", | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn invite_event_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "content": { | ||||
|             "call_id": "abcdef", | ||||
|             "party_id": "9876", | ||||
|             "lifetime": 60000, | ||||
|             "version": "1", | ||||
|             "offer": { | ||||
|                 "type": "offer", | ||||
|                 "sdp": "not a real sdp", | ||||
|             }, | ||||
|         }, | ||||
|         "event_id": "$event:notareal.hs", | ||||
|         "origin_server_ts": 134_829_848, | ||||
|         "room_id": "!roomid:notareal.hs", | ||||
|         "sender": "@user:notareal.hs", | ||||
|         "type": "m.call.invite", | ||||
|     }); | ||||
| 
 | ||||
|     let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(); | ||||
|     let message_event = assert_matches!( | ||||
|         event, | ||||
|         AnyMessageLikeEvent::CallInvite(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.call_id, "abcdef"); | ||||
|     assert_eq!(content.party_id.unwrap(), "9876"); | ||||
|     assert_eq!(content.lifetime, uint!(60000)); | ||||
|     assert_eq!(content.version, VoipVersionId::V1); | ||||
|     assert_eq!(content.offer.sdp, "not a real sdp"); | ||||
|     assert!(!content.capabilities.dtmf); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn answer_content_serialization() { | ||||
|     let event_content = CallAnswerEventContent::version_0( | ||||
|         AnswerSessionDescription::new("not a real sdp".to_owned()), | ||||
|         "abcdef".into(), | ||||
|     ); | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event_content).unwrap(), | ||||
|         json!({ | ||||
|             "call_id": "abcdef", | ||||
|             "version": 0, | ||||
|             "answer": { | ||||
|                 "type": "answer", | ||||
|                 "sdp": "not a real sdp", | ||||
|             }, | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn answer_event_serialization() { | ||||
|     let event = OriginalMessageLikeEvent { | ||||
|         content: CallAnswerEventContent::version_1( | ||||
|             AnswerSessionDescription::new("not a real sdp".to_owned()), | ||||
|             "abcdef".into(), | ||||
|             "9876".into(), | ||||
|             assign!(CallCapabilities::new(), { dtmf: true }), | ||||
|         ), | ||||
|         event_id: event_id!("$event:notareal.hs").to_owned(), | ||||
|         sender: user_id!("@user:notareal.hs").to_owned(), | ||||
|         origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)), | ||||
|         room_id: room_id!("!roomid:notareal.hs").to_owned(), | ||||
|         unsigned: MessageLikeUnsigned::default(), | ||||
|     }; | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event).unwrap(), | ||||
|         json!({ | ||||
|             "content": { | ||||
|                 "call_id": "abcdef", | ||||
|                 "party_id": "9876", | ||||
|                 "version": "1", | ||||
|                 "answer": { | ||||
|                     "type": "answer", | ||||
|                     "sdp": "not a real sdp", | ||||
|                 }, | ||||
|                 "capabilities": { | ||||
|                     "m.call.dtmf": true, | ||||
|                 }, | ||||
|             }, | ||||
|             "event_id": "$event:notareal.hs", | ||||
|             "origin_server_ts": 134_829_848, | ||||
|             "room_id": "!roomid:notareal.hs", | ||||
|             "sender": "@user:notareal.hs", | ||||
|             "type": "m.call.answer", | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn answer_event_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "content": { | ||||
|             "call_id": "abcdef", | ||||
|             "party_id": "9876", | ||||
|             "version": "org.matrix.1b", | ||||
|             "answer": { | ||||
|                 "type": "answer", | ||||
|                 "sdp": "not a real sdp", | ||||
|             }, | ||||
|             "capabilities": { | ||||
|                 "m.call.dtmf": true, | ||||
|             }, | ||||
|         }, | ||||
|         "event_id": "$event:notareal.hs", | ||||
|         "origin_server_ts": 134_829_848, | ||||
|         "room_id": "!roomid:notareal.hs", | ||||
|         "sender": "@user:notareal.hs", | ||||
|         "type": "m.call.answer", | ||||
|     }); | ||||
| 
 | ||||
|     let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(); | ||||
|     let message_event = assert_matches!( | ||||
|         event, | ||||
|         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.call_id, "abcdef"); | ||||
|     assert_eq!(content.party_id.unwrap(), "9876"); | ||||
|     assert_eq!(content.version.as_ref(), "org.matrix.1b"); | ||||
|     assert_eq!(content.answer.sdp, "not a real sdp"); | ||||
|     assert!(content.capabilities.dtmf); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn candidates_content_serialization() { | ||||
|     let event_content = CallCandidatesEventContent::version_0( | ||||
|         "abcdef".into(), | ||||
|         vec![Candidate::new("not a real candidate".to_owned(), "0".to_owned(), uint!(0))], | ||||
|     ); | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event_content).unwrap(), | ||||
|         json!({ | ||||
|             "call_id": "abcdef", | ||||
|             "version": 0, | ||||
|             "candidates": [ | ||||
|                 { | ||||
|                     "candidate": "not a real candidate", | ||||
|                     "sdpMid": "0", | ||||
|                     "sdpMLineIndex": 0, | ||||
|                 }, | ||||
|             ], | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn candidates_event_serialization() { | ||||
|     let event = OriginalMessageLikeEvent { | ||||
|         content: CallCandidatesEventContent::version_1( | ||||
|             "abcdef".into(), | ||||
|             "9876".into(), | ||||
|             vec![ | ||||
|                 Candidate::new("not a real candidate".to_owned(), "0".to_owned(), uint!(0)), | ||||
|                 Candidate::new("another fake candidate".to_owned(), "0".to_owned(), uint!(1)), | ||||
|             ], | ||||
|         ), | ||||
|         event_id: event_id!("$event:notareal.hs").to_owned(), | ||||
|         sender: user_id!("@user:notareal.hs").to_owned(), | ||||
|         origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)), | ||||
|         room_id: room_id!("!roomid:notareal.hs").to_owned(), | ||||
|         unsigned: MessageLikeUnsigned::default(), | ||||
|     }; | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event).unwrap(), | ||||
|         json!({ | ||||
|             "content": { | ||||
|                 "call_id": "abcdef", | ||||
|                 "party_id": "9876", | ||||
|                 "version": "1", | ||||
|                 "candidates": [ | ||||
|                     { | ||||
|                         "candidate": "not a real candidate", | ||||
|                         "sdpMid": "0", | ||||
|                         "sdpMLineIndex": 0, | ||||
|                     }, | ||||
|                     { | ||||
|                         "candidate": "another fake candidate", | ||||
|                         "sdpMid": "0", | ||||
|                         "sdpMLineIndex": 1, | ||||
|                     }, | ||||
|                 ], | ||||
|             }, | ||||
|             "event_id": "$event:notareal.hs", | ||||
|             "origin_server_ts": 134_829_848, | ||||
|             "room_id": "!roomid:notareal.hs", | ||||
|             "sender": "@user:notareal.hs", | ||||
|             "type": "m.call.candidates", | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn candidates_event_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "content": { | ||||
|             "call_id": "abcdef", | ||||
|             "party_id": "9876", | ||||
|             "version": "1", | ||||
|             "candidates": [ | ||||
|                 { | ||||
|                     "candidate": "not a real candidate", | ||||
|                     "sdpMid": "0", | ||||
|                     "sdpMLineIndex": 0, | ||||
|                 }, | ||||
|                 { | ||||
|                     "candidate": "another fake candidate", | ||||
|                     "sdpMid": "0", | ||||
|                     "sdpMLineIndex": 1, | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|         "event_id": "$event:notareal.hs", | ||||
|         "origin_server_ts": 134_829_848, | ||||
|         "room_id": "!roomid:notareal.hs", | ||||
|         "sender": "@user:notareal.hs", | ||||
|         "type": "m.call.candidates", | ||||
|     }); | ||||
| 
 | ||||
|     let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(); | ||||
|     let message_event = assert_matches!( | ||||
|         event, | ||||
|         AnyMessageLikeEvent::CallCandidates(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.call_id, "abcdef"); | ||||
|     assert_eq!(content.party_id.unwrap(), "9876"); | ||||
|     assert_eq!(content.version, VoipVersionId::V1); | ||||
|     assert_eq!(content.candidates.len(), 2); | ||||
|     assert_eq!(content.candidates[0].candidate, "not a real candidate"); | ||||
|     assert_eq!(content.candidates[0].sdp_mid, "0"); | ||||
|     assert_eq!(content.candidates[0].sdp_m_line_index, uint!(0)); | ||||
|     assert_eq!(content.candidates[1].candidate, "another fake candidate"); | ||||
|     assert_eq!(content.candidates[1].sdp_mid, "0"); | ||||
|     assert_eq!(content.candidates[1].sdp_m_line_index, uint!(1)); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn hangup_content_serialization() { | ||||
|     let event_content = CallHangupEventContent::version_0("abcdef".into()); | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event_content).unwrap(), | ||||
|         json!({ | ||||
|             "call_id": "abcdef", | ||||
|             "version": 0, | ||||
|             "reason": "user_hangup", | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn hangup_event_serialization() { | ||||
|     let event = OriginalMessageLikeEvent { | ||||
|         content: CallHangupEventContent::version_1( | ||||
|             "abcdef".into(), | ||||
|             "9876".into(), | ||||
|             Reason::IceFailed, | ||||
|         ), | ||||
|         event_id: event_id!("$event:notareal.hs").to_owned(), | ||||
|         sender: user_id!("@user:notareal.hs").to_owned(), | ||||
|         origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)), | ||||
|         room_id: room_id!("!roomid:notareal.hs").to_owned(), | ||||
|         unsigned: MessageLikeUnsigned::default(), | ||||
|     }; | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event).unwrap(), | ||||
|         json!({ | ||||
|             "content": { | ||||
|                 "call_id": "abcdef", | ||||
|                 "party_id": "9876", | ||||
|                 "version": "1", | ||||
|                 "reason": "ice_failed", | ||||
|             }, | ||||
|             "event_id": "$event:notareal.hs", | ||||
|             "origin_server_ts": 134_829_848, | ||||
|             "room_id": "!roomid:notareal.hs", | ||||
|             "sender": "@user:notareal.hs", | ||||
|             "type": "m.call.hangup", | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn hangup_event_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "content": { | ||||
|             "call_id": "abcdef", | ||||
|             "party_id": "9876", | ||||
|             "version": "1", | ||||
|         }, | ||||
|         "event_id": "$event:notareal.hs", | ||||
|         "origin_server_ts": 134_829_848, | ||||
|         "room_id": "!roomid:notareal.hs", | ||||
|         "sender": "@user:notareal.hs", | ||||
|         "type": "m.call.hangup", | ||||
|     }); | ||||
| 
 | ||||
|     let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(); | ||||
|     let message_event = assert_matches!( | ||||
|         event, | ||||
|         AnyMessageLikeEvent::CallHangup(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.call_id, "abcdef"); | ||||
|     assert_eq!(content.party_id.unwrap(), "9876"); | ||||
|     assert_eq!(content.version, VoipVersionId::V1); | ||||
|     assert_eq!(content.reason, Some(Reason::UserHangup)); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn negotiate_event_serialization() { | ||||
|     let event = OriginalMessageLikeEvent { | ||||
|         content: CallNegotiateEventContent::new( | ||||
|             "abcdef".into(), | ||||
|             "9876".into(), | ||||
|             uint!(30000), | ||||
|             SessionDescription::new(SessionDescriptionType::Offer, "not a real sdp".to_owned()), | ||||
|         ), | ||||
|         event_id: event_id!("$event:notareal.hs").to_owned(), | ||||
|         sender: user_id!("@user:notareal.hs").to_owned(), | ||||
|         origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)), | ||||
|         room_id: room_id!("!roomid:notareal.hs").to_owned(), | ||||
|         unsigned: MessageLikeUnsigned::default(), | ||||
|     }; | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event).unwrap(), | ||||
|         json!({ | ||||
|             "content": { | ||||
|                 "call_id": "abcdef", | ||||
|                 "party_id": "9876", | ||||
|                 "lifetime": 30000, | ||||
|                 "description": { | ||||
|                     "type": "offer", | ||||
|                     "sdp": "not a real sdp", | ||||
|                 } | ||||
|             }, | ||||
|             "event_id": "$event:notareal.hs", | ||||
|             "origin_server_ts": 134_829_848, | ||||
|             "room_id": "!roomid:notareal.hs", | ||||
|             "sender": "@user:notareal.hs", | ||||
|             "type": "m.call.negotiate", | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn negotiate_event_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "content": { | ||||
|             "call_id": "abcdef", | ||||
|             "party_id": "9876", | ||||
|             "lifetime": 30000, | ||||
|             "description": { | ||||
|                 "type": "pranswer", | ||||
|                 "sdp": "not a real sdp", | ||||
|             } | ||||
|         }, | ||||
|         "event_id": "$event:notareal.hs", | ||||
|         "origin_server_ts": 134_829_848, | ||||
|         "room_id": "!roomid:notareal.hs", | ||||
|         "sender": "@user:notareal.hs", | ||||
|         "type": "m.call.negotiate", | ||||
|     }); | ||||
| 
 | ||||
|     let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(); | ||||
|     let message_event = assert_matches!( | ||||
|         event, | ||||
|         AnyMessageLikeEvent::CallNegotiate(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.call_id, "abcdef"); | ||||
|     assert_eq!(content.party_id, "9876"); | ||||
|     assert_eq!(content.lifetime, uint!(30000)); | ||||
|     assert_eq!(content.description.session_type, SessionDescriptionType::PrAnswer); | ||||
|     assert_eq!(content.description.sdp, "not a real sdp") | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn reject_event_serialization() { | ||||
|     let event = OriginalMessageLikeEvent { | ||||
|         content: CallRejectEventContent::version_1("abcdef".into(), "9876".into()), | ||||
|         event_id: event_id!("$event:notareal.hs").to_owned(), | ||||
|         sender: user_id!("@user:notareal.hs").to_owned(), | ||||
|         origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)), | ||||
|         room_id: room_id!("!roomid:notareal.hs").to_owned(), | ||||
|         unsigned: MessageLikeUnsigned::default(), | ||||
|     }; | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event).unwrap(), | ||||
|         json!({ | ||||
|             "content": { | ||||
|                 "call_id": "abcdef", | ||||
|                 "party_id": "9876", | ||||
|                 "version": "1", | ||||
|             }, | ||||
|             "event_id": "$event:notareal.hs", | ||||
|             "origin_server_ts": 134_829_848, | ||||
|             "room_id": "!roomid:notareal.hs", | ||||
|             "sender": "@user:notareal.hs", | ||||
|             "type": "m.call.reject", | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn reject_event_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "content": { | ||||
|             "call_id": "abcdef", | ||||
|             "party_id": "9876", | ||||
|             "version": "1", | ||||
|         }, | ||||
|         "event_id": "$event:notareal.hs", | ||||
|         "origin_server_ts": 134_829_848, | ||||
|         "room_id": "!roomid:notareal.hs", | ||||
|         "sender": "@user:notareal.hs", | ||||
|         "type": "m.call.reject", | ||||
|     }); | ||||
| 
 | ||||
|     let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(); | ||||
|     let message_event = assert_matches!( | ||||
|         event, | ||||
|         AnyMessageLikeEvent::CallReject(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.call_id, "abcdef"); | ||||
|     assert_eq!(content.party_id, "9876"); | ||||
|     assert_eq!(content.version, VoipVersionId::V1); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn select_answer_event_serialization() { | ||||
|     let event = OriginalMessageLikeEvent { | ||||
|         content: CallSelectAnswerEventContent::version_1( | ||||
|             "abcdef".into(), | ||||
|             "9876".into(), | ||||
|             "6336".into(), | ||||
|         ), | ||||
|         event_id: event_id!("$event:notareal.hs").to_owned(), | ||||
|         sender: user_id!("@user:notareal.hs").to_owned(), | ||||
|         origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)), | ||||
|         room_id: room_id!("!roomid:notareal.hs").to_owned(), | ||||
|         unsigned: MessageLikeUnsigned::default(), | ||||
|     }; | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&event).unwrap(), | ||||
|         json!({ | ||||
|             "content": { | ||||
|                 "call_id": "abcdef", | ||||
|                 "party_id": "9876", | ||||
|                 "selected_party_id": "6336", | ||||
|                 "version": "1", | ||||
|             }, | ||||
|             "event_id": "$event:notareal.hs", | ||||
|             "origin_server_ts": 134_829_848, | ||||
|             "room_id": "!roomid:notareal.hs", | ||||
|             "sender": "@user:notareal.hs", | ||||
|             "type": "m.call.select_answer", | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn select_answer_event_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "content": { | ||||
|             "call_id": "abcdef", | ||||
|             "party_id": "9876", | ||||
|             "selected_party_id": "6336", | ||||
|             "version": "1", | ||||
|         }, | ||||
|         "event_id": "$event:notareal.hs", | ||||
|         "origin_server_ts": 134_829_848, | ||||
|         "room_id": "!roomid:notareal.hs", | ||||
|         "sender": "@user:notareal.hs", | ||||
|         "type": "m.call.select_answer", | ||||
|     }); | ||||
| 
 | ||||
|     let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(); | ||||
|     let message_event = assert_matches!( | ||||
|         event, | ||||
|         AnyMessageLikeEvent::CallSelectAnswer(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.call_id, "abcdef"); | ||||
|     assert_eq!(content.party_id, "9876"); | ||||
|     assert_eq!(content.selected_party_id, "6336"); | ||||
|     assert_eq!(content.version, VoipVersionId::V1); | ||||
| } | ||||
| @ -1,15 +1,11 @@ | ||||
| use assert_matches::assert_matches; | ||||
| use js_int::{uint, UInt}; | ||||
| use js_int::uint; | ||||
| use ruma_common::{ | ||||
|     event_id, events::MessageLikeEvent, room_id, user_id, MilliSecondsSinceUnixEpoch, | ||||
|     events::{AnyMessageLikeEvent, MessageLikeEvent}, | ||||
|     MilliSecondsSinceUnixEpoch, VoipVersionId, | ||||
| }; | ||||
| use serde_json::{from_value as from_json_value, json}; | ||||
| 
 | ||||
| use ruma_common::events::{ | ||||
|     call::{answer::CallAnswerEventContent, AnswerSessionDescription}, | ||||
|     AnyMessageLikeEvent, OriginalMessageLikeEvent, | ||||
| }; | ||||
| 
 | ||||
| #[test] | ||||
| fn ui() { | ||||
|     let t = trybuild::TestCases::new(); | ||||
| @ -27,7 +23,7 @@ fn deserialize_message_event() { | ||||
|                 "sdp": "Hello" | ||||
|             }, | ||||
|             "call_id": "foofoo", | ||||
|             "version": 1 | ||||
|             "version": 0 | ||||
|         }, | ||||
|         "event_id": "$h29iv0s8:example.com", | ||||
|         "origin_server_ts": 1, | ||||
| @ -36,29 +32,19 @@ fn deserialize_message_event() { | ||||
|         "type": "m.call.answer" | ||||
|     }); | ||||
| 
 | ||||
|     assert_matches!( | ||||
|         from_json_value::<AnyMessageLikeEvent>(json_data) | ||||
|             .unwrap(), | ||||
|         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(OriginalMessageLikeEvent { | ||||
|             content: CallAnswerEventContent { | ||||
|                 answer: AnswerSessionDescription { | ||||
|                     sdp, | ||||
|                     .. | ||||
|                 }, | ||||
|                 call_id, | ||||
|                 version, | ||||
|                 .. | ||||
|             }, | ||||
|             event_id, | ||||
|             origin_server_ts, | ||||
|             room_id, | ||||
|             sender, | ||||
|             unsigned, | ||||
|         })) if sdp == "Hello" && call_id == "foofoo" && version == UInt::new(1).unwrap() | ||||
|             && event_id == event_id!("$h29iv0s8:example.com") | ||||
|             && origin_server_ts == MilliSecondsSinceUnixEpoch(uint!(1)) | ||||
|             && room_id == room_id!("!roomid:room.com") | ||||
|             && sender == user_id!("@carl:example.com") | ||||
|             && unsigned.is_empty() | ||||
|     let message_event = assert_matches!( | ||||
|         from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(), | ||||
|         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
| 
 | ||||
|     assert_eq!(message_event.event_id, "$h29iv0s8:example.com"); | ||||
|     assert_eq!(message_event.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1))); | ||||
|     assert_eq!(message_event.room_id, "!roomid:room.com"); | ||||
|     assert_eq!(message_event.sender, "@carl:example.com"); | ||||
|     assert!(message_event.unsigned.is_empty()); | ||||
| 
 | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.answer.sdp, "Hello"); | ||||
|     assert_eq!(content.call_id, "foofoo"); | ||||
|     assert_eq!(content.version, VoipVersionId::V0); | ||||
| } | ||||
|  | ||||
| @ -4,7 +4,6 @@ use js_int::{uint, UInt}; | ||||
| use ruma_common::{ | ||||
|     event_id, | ||||
|     events::{ | ||||
|         call::{answer::CallAnswerEventContent, AnswerSessionDescription}, | ||||
|         room::{ImageInfo, MediaSource, ThumbnailInfo}, | ||||
|         sticker::StickerEventContent, | ||||
|         AnyMessageLikeEvent, AnyMessageLikeEventContent, AnySyncMessageLikeEvent, MessageLikeEvent, | ||||
| @ -12,7 +11,7 @@ use ruma_common::{ | ||||
|     }, | ||||
|     mxc_uri, room_id, | ||||
|     serde::Raw, | ||||
|     user_id, MilliSecondsSinceUnixEpoch, | ||||
|     user_id, MilliSecondsSinceUnixEpoch, VoipVersionId, | ||||
| }; | ||||
| use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; | ||||
| 
 | ||||
| @ -127,24 +126,20 @@ fn deserialize_message_call_answer_content() { | ||||
|             "sdp": "Hello" | ||||
|         }, | ||||
|         "call_id": "foofoo", | ||||
|         "version": 1 | ||||
|         "version": 0 | ||||
|     }); | ||||
| 
 | ||||
|     assert_matches!( | ||||
|     let content = assert_matches!( | ||||
|         from_json_value::<Raw<AnyMessageLikeEventContent>>(json_data) | ||||
|             .unwrap() | ||||
|             .deserialize_content(MessageLikeEventType::CallAnswer) | ||||
|             .unwrap(), | ||||
|         AnyMessageLikeEventContent::CallAnswer(CallAnswerEventContent { | ||||
|             answer: AnswerSessionDescription { | ||||
|                 sdp, | ||||
|                 .. | ||||
|             }, | ||||
|             call_id, | ||||
|             version, | ||||
|             .. | ||||
|         }) if sdp == "Hello" && call_id == "foofoo" && version == UInt::new(1).unwrap() | ||||
|         AnyMessageLikeEventContent::CallAnswer(content) => content | ||||
|     ); | ||||
| 
 | ||||
|     assert_eq!(content.answer.sdp, "Hello"); | ||||
|     assert_eq!(content.call_id, "foofoo"); | ||||
|     assert_eq!(content.version, VoipVersionId::V0); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| @ -156,7 +151,7 @@ fn deserialize_message_call_answer() { | ||||
|                 "sdp": "Hello" | ||||
|             }, | ||||
|             "call_id": "foofoo", | ||||
|             "version": 1 | ||||
|             "version": 0 | ||||
|         }, | ||||
|         "event_id": "$h29iv0s8:example.com", | ||||
|         "origin_server_ts": 1, | ||||
| @ -165,30 +160,20 @@ fn deserialize_message_call_answer() { | ||||
|         "type": "m.call.answer" | ||||
|     }); | ||||
| 
 | ||||
|     assert_matches!( | ||||
|     let message_event = assert_matches!( | ||||
|         from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(), | ||||
|         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(OriginalMessageLikeEvent { | ||||
|             content: CallAnswerEventContent { | ||||
|                 answer: AnswerSessionDescription { | ||||
|                     sdp, | ||||
|                     .. | ||||
|                 }, | ||||
|                 call_id, | ||||
|                 version, | ||||
|                 .. | ||||
|             }, | ||||
|             event_id, | ||||
|             origin_server_ts, | ||||
|             room_id, | ||||
|             sender, | ||||
|             unsigned, | ||||
|         })) if sdp == "Hello" && call_id == "foofoo" && version == UInt::new(1).unwrap() | ||||
|             && event_id == event_id!("$h29iv0s8:example.com") | ||||
|             && origin_server_ts == MilliSecondsSinceUnixEpoch(uint!(1)) | ||||
|             && room_id == room_id!("!roomid:room.com") | ||||
|             && sender == user_id!("@carl:example.com") | ||||
|             && unsigned.is_empty() | ||||
|         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
|     assert_eq!(message_event.event_id, "$h29iv0s8:example.com"); | ||||
|     assert_eq!(message_event.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1))); | ||||
|     assert_eq!(message_event.room_id, "!roomid:room.com"); | ||||
|     assert_eq!(message_event.sender, "@carl:example.com"); | ||||
|     assert!(message_event.unsigned.is_empty()); | ||||
| 
 | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.answer.sdp, "Hello"); | ||||
|     assert_eq!(content.call_id, "foofoo"); | ||||
|     assert_eq!(content.version, VoipVersionId::V0); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| @ -280,7 +265,7 @@ fn deserialize_message_then_convert_to_full() { | ||||
|                 "sdp": "Hello" | ||||
|             }, | ||||
|             "call_id": "foofoo", | ||||
|             "version": 1 | ||||
|             "version": 0 | ||||
|         }, | ||||
|         "event_id": "$h29iv0s8:example.com", | ||||
|         "origin_server_ts": 1, | ||||
| @ -290,30 +275,18 @@ fn deserialize_message_then_convert_to_full() { | ||||
| 
 | ||||
|     let sync_ev: AnySyncMessageLikeEvent = from_json_value(json_data).unwrap(); | ||||
| 
 | ||||
|     assert_matches!( | ||||
|     let message_event = assert_matches!( | ||||
|         sync_ev.into_full_event(rid.to_owned()), | ||||
|         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(OriginalMessageLikeEvent { | ||||
|             content: CallAnswerEventContent { | ||||
|                 answer: AnswerSessionDescription { | ||||
|                     sdp, | ||||
|                     .. | ||||
|                 }, | ||||
|                 call_id, | ||||
|                 version, | ||||
|                 .. | ||||
|             }, | ||||
|             event_id, | ||||
|             origin_server_ts, | ||||
|             room_id, | ||||
|             sender, | ||||
|             unsigned, | ||||
|         })) if sdp == "Hello" | ||||
|             && call_id == "foofoo" | ||||
|             && version == uint!(1) | ||||
|             && event_id == "$h29iv0s8:example.com" | ||||
|             && origin_server_ts == MilliSecondsSinceUnixEpoch(uint!(1)) | ||||
|             && room_id == "!roomid:room.com" | ||||
|             && sender == "@carl:example.com" | ||||
|             && unsigned.is_empty() | ||||
|         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(message_event)) => message_event | ||||
|     ); | ||||
|     assert_eq!(message_event.event_id, "$h29iv0s8:example.com"); | ||||
|     assert_eq!(message_event.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1))); | ||||
|     assert_eq!(message_event.room_id, "!roomid:room.com"); | ||||
|     assert_eq!(message_event.sender, "@carl:example.com"); | ||||
|     assert!(message_event.unsigned.is_empty()); | ||||
| 
 | ||||
|     let content = message_event.content; | ||||
|     assert_eq!(content.answer.sdp, "Hello"); | ||||
|     assert_eq!(content.call_id, "foofoo"); | ||||
|     assert_eq!(content.version, VoipVersionId::V0); | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| #![cfg(feature = "events")] | ||||
| 
 | ||||
| mod audio; | ||||
| mod call; | ||||
| mod enums; | ||||
| mod ephemeral_event; | ||||
| mod event; | ||||
|  | ||||
| @ -15,4 +15,5 @@ all-features = true | ||||
| compat = [] | ||||
| 
 | ||||
| [dependencies] | ||||
| js_int = "0.2.0" | ||||
| thiserror = "1.0.26" | ||||
|  | ||||
| @ -30,6 +30,10 @@ pub enum Error { | ||||
|     #[error("invalid Matrix Content URI: {0}")] | ||||
|     InvalidMxcUri(#[from] MxcUriError), | ||||
| 
 | ||||
|     /// The value isn't a valid VoIP version Id.
 | ||||
|     #[error("invalid VoIP version ID: {0}")] | ||||
|     InvalidVoipVersionId(#[from] VoipVersionIdError), | ||||
| 
 | ||||
|     /// The server name part of the the ID string is not a valid server name.
 | ||||
|     #[error("server name is not a valid IP address or domain name")] | ||||
|     InvalidServerName, | ||||
| @ -149,6 +153,15 @@ pub enum MatrixUriError { | ||||
|     UnknownQueryItem, | ||||
| } | ||||
| 
 | ||||
| /// An error occurred while validating a `VoipVersionId`.
 | ||||
| #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, thiserror::Error)] | ||||
| #[non_exhaustive] | ||||
| pub enum VoipVersionIdError { | ||||
|     /// The value of the `UInt` is not 0.
 | ||||
|     #[error("UInt value is not 0")] | ||||
|     WrongUintValue, | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use std::mem::size_of; | ||||
|  | ||||
| @ -15,6 +15,7 @@ pub mod room_version_id; | ||||
| pub mod server_name; | ||||
| pub mod session_id; | ||||
| pub mod user_id; | ||||
| pub mod voip_version_id; | ||||
| 
 | ||||
| pub use error::Error; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										11
									
								
								crates/ruma-identifiers-validation/src/voip_version_id.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								crates/ruma-identifiers-validation/src/voip_version_id.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| use js_int::{uint, UInt}; | ||||
| 
 | ||||
| use crate::{error::VoipVersionIdError, Error}; | ||||
| 
 | ||||
| pub fn validate(u: UInt) -> Result<(), Error> { | ||||
|     if u != uint!(0) { | ||||
|         return Err(VoipVersionIdError::WrongUintValue.into()); | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| @ -134,6 +134,7 @@ unstable-msc2677 = [ | ||||
|     "ruma-client-api/unstable-msc2677", | ||||
|     "ruma-common/unstable-msc2677", | ||||
| ] | ||||
| unstable-msc2746 = ["ruma-common/unstable-msc2746"] | ||||
| unstable-msc2870 = ["ruma-signatures/unstable-msc2870"] | ||||
| unstable-msc3245 = ["ruma-common/unstable-msc3245"] | ||||
| unstable-msc3246 = ["ruma-common/unstable-msc3246"] | ||||
| @ -163,6 +164,7 @@ __ci = [ | ||||
|     "unstable-msc2675", | ||||
|     "unstable-msc2676", | ||||
|     "unstable-msc2677", | ||||
|     "unstable-msc2746", | ||||
|     "unstable-msc2870", | ||||
|     "unstable-msc3245", | ||||
|     "unstable-msc3246", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user