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` | * Add `user_id` field to `PushConditionRoomCtx` | ||||||
| * Remove `PartialEq` implementation on `NotificationPowerLevels` | * Remove `PartialEq` implementation on `NotificationPowerLevels` | ||||||
| * Remove `PartialEq` implementation for `events::call::SessionDescription` | * Remove `PartialEq` implementation for `events::call::SessionDescription` | ||||||
| * Split `events::call::SessionDescription` into `AnswerSessionDescription` | * Use new `events::call::AnswerSessionDescription` for `CallAnswerEventContent`  | ||||||
|   and `OfferSessionDescription` |   and `OfferSessionDescription` for `CallInviteEventContent` | ||||||
|   * Remove `SessionDescriptionType` | * Use new `VoipVersionId` and `VoipId` types for `events::call` events | ||||||
| 
 | 
 | ||||||
| Improvements: | Improvements: | ||||||
| 
 | 
 | ||||||
| @ -20,6 +20,7 @@ Improvements: | |||||||
| * Change `events::relation::BundledAnnotation` to a struct instead of an enum | * Change `events::relation::BundledAnnotation` to a struct instead of an enum | ||||||
|   * Remove `BundledReaction` |   * Remove `BundledReaction` | ||||||
| * Add unstable support for polls (MSC3381) | * Add unstable support for polls (MSC3381) | ||||||
|  | * Add unstable support for Improved Signalling for 1:1 VoIP (MSC2746) | ||||||
| 
 | 
 | ||||||
| # 0.9.2 | # 0.9.2 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ unstable-msc2448 = [] | |||||||
| unstable-msc2675 = [] | unstable-msc2675 = [] | ||||||
| unstable-msc2676 = [] | unstable-msc2676 = [] | ||||||
| unstable-msc2677 = [] | unstable-msc2677 = [] | ||||||
|  | unstable-msc2746 = [] | ||||||
| unstable-msc3245 = ["unstable-msc3246"] | unstable-msc3245 = ["unstable-msc3246"] | ||||||
| unstable-msc3246 = ["unstable-msc3551", "thiserror"] | unstable-msc3246 = ["unstable-msc3551", "thiserror"] | ||||||
| unstable-msc3381 = ["unstable-msc1767"] | unstable-msc3381 = ["unstable-msc1767"] | ||||||
|  | |||||||
| @ -6,9 +6,74 @@ pub mod answer; | |||||||
| pub mod candidates; | pub mod candidates; | ||||||
| pub mod hangup; | pub mod hangup; | ||||||
| pub mod invite; | 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 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.
 | /// A VoIP answer session description.
 | ||||||
| #[derive(Clone, Debug, Deserialize, Serialize)] | #[derive(Clone, Debug, Deserialize, Serialize)] | ||||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||||
| @ -40,3 +105,30 @@ impl OfferSessionDescription { | |||||||
|         Self { sdp } |         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
 | //! [`m.call.answer`]: https://spec.matrix.org/v1.2/client-server-api/#mcallanswer
 | ||||||
| 
 | 
 | ||||||
| use js_int::UInt; |  | ||||||
| use ruma_macros::EventContent; | use ruma_macros::EventContent; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| use super::AnswerSessionDescription; | use super::AnswerSessionDescription; | ||||||
|  | #[cfg(feature = "unstable-msc2746")] | ||||||
|  | use super::CallCapabilities; | ||||||
|  | use crate::{OwnedVoipId, VoipVersionId}; | ||||||
| 
 | 
 | ||||||
| /// The content of an `m.call.answer` event.
 | /// The content of an `m.call.answer` event.
 | ||||||
| ///
 | ///
 | ||||||
| @ -18,16 +20,56 @@ pub struct CallAnswerEventContent { | |||||||
|     /// The VoIP session description object.
 |     /// The VoIP session description object.
 | ||||||
|     pub answer: AnswerSessionDescription, |     pub answer: AnswerSessionDescription, | ||||||
| 
 | 
 | ||||||
|     /// The ID of the call this event relates to.
 |     /// 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 version of the VoIP specification this messages adheres to.
 |     /// 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 { | impl CallAnswerEventContent { | ||||||
|     /// Creates an `AnswerEventContent` with the given answer, call ID and VoIP version.
 |     /// Creates an `CallAnswerEventContent` with the given answer, call ID and VoIP version.
 | ||||||
|     pub fn new(answer: AnswerSessionDescription, call_id: String, version: UInt) -> Self { |     pub fn new( | ||||||
|         Self { answer, call_id, version } |         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 ruma_macros::EventContent; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
|  | use crate::{OwnedVoipId, VoipVersionId}; | ||||||
|  | 
 | ||||||
| /// The content of an `m.call.candidates` event.
 | /// 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
 | /// 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)] | #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||||
| #[ruma_event(type = "m.call.candidates", kind = MessageLike)] | #[ruma_event(type = "m.call.candidates", kind = MessageLike)] | ||||||
| pub struct CallCandidatesEventContent { | pub struct CallCandidatesEventContent { | ||||||
|     /// The ID of the call this event relates to.
 |     /// A unique identifier for the call.
 | ||||||
|     pub call_id: String, |     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.
 |     /// 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>, |     pub candidates: Vec<Candidate>, | ||||||
| 
 | 
 | ||||||
|     /// The version of the VoIP specification this messages adheres to.
 |     /// The version of the VoIP specification this messages adheres to.
 | ||||||
|     pub version: UInt, |     pub version: VoipVersionId, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl CallCandidatesEventContent { | 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.
 |     /// version.
 | ||||||
|     pub fn new(call_id: String, candidates: Vec<Candidate>, version: UInt) -> Self { |     pub fn new(call_id: OwnedVoipId, candidates: Vec<Candidate>, version: VoipVersionId) -> Self { | ||||||
|         Self { call_id, candidates, version } |         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
 | //! [`m.call.hangup`]: https://spec.matrix.org/v1.2/client-server-api/#mcallhangup
 | ||||||
| 
 | 
 | ||||||
| use js_int::UInt; |  | ||||||
| use ruma_macros::EventContent; | use ruma_macros::EventContent; | ||||||
|  | #[cfg(feature = "unstable-msc2746")] | ||||||
|  | use serde::Serializer; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| use crate::{serde::StringEnum, PrivOwnedStr}; | use crate::{serde::StringEnum, OwnedVoipId, PrivOwnedStr, VoipVersionId}; | ||||||
| 
 | 
 | ||||||
| /// The content of an `m.call.hangup` event.
 | /// 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
 | /// Sent by either party to signal their termination of the call.
 | ||||||
| /// call has has been established or before to abort 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)] | #[derive(Clone, Debug, Deserialize, Serialize, EventContent)] | ||||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||||
| #[ruma_event(type = "m.call.hangup", kind = MessageLike)] | #[ruma_event(type = "m.call.hangup", kind = MessageLike)] | ||||||
| pub struct CallHangupEventContent { | pub struct CallHangupEventContent { | ||||||
|     /// The ID of the call this event relates to.
 |     /// 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.
 | ||||||
|  |     ///
 | ||||||
|  |     /// 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.
 |     /// The version of the VoIP specification this messages adheres to.
 | ||||||
|     pub version: UInt, |     pub version: VoipVersionId, | ||||||
| 
 | 
 | ||||||
|     /// Optional error reason for the hangup.
 |     /// 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>, |     pub reason: Option<Reason>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl CallHangupEventContent { | impl CallHangupEventContent { | ||||||
|     /// Creates a new `HangupEventContent` with the given call ID and VoIP version.
 |     /// Creates a new `CallHangupEventContent` with the given call ID and VoIP version.
 | ||||||
|     pub fn new(call_id: String, version: UInt) -> Self { |     pub fn new(call_id: OwnedVoipId, version: VoipVersionId) -> Self { | ||||||
|         Self { call_id, version, reason: None } |         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.
 |     /// Party did not answer in time.
 | ||||||
|     InviteTimeout, |     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)] |     #[doc(hidden)] | ||||||
|     _Custom(PrivOwnedStr), |     _Custom(PrivOwnedStr), | ||||||
| } | } | ||||||
| @ -59,4 +130,31 @@ impl Reason { | |||||||
|     pub fn as_str(&self) -> &str { |     pub fn as_str(&self) -> &str { | ||||||
|         self.as_ref() |         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 ruma_macros::EventContent; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
|  | #[cfg(feature = "unstable-msc2746")] | ||||||
|  | use super::CallCapabilities; | ||||||
| use super::OfferSessionDescription; | use super::OfferSessionDescription; | ||||||
|  | #[cfg(feature = "unstable-msc2746")] | ||||||
|  | use crate::OwnedUserId; | ||||||
|  | use crate::{OwnedVoipId, VoipVersionId}; | ||||||
| 
 | 
 | ||||||
| /// The content of an `m.call.invite` event.
 | /// The content of an `m.call.invite` event.
 | ||||||
| ///
 | ///
 | ||||||
| @ -16,7 +21,12 @@ use super::OfferSessionDescription; | |||||||
| #[ruma_event(type = "m.call.invite", kind = MessageLike)] | #[ruma_event(type = "m.call.invite", kind = MessageLike)] | ||||||
| pub struct CallInviteEventContent { | pub struct CallInviteEventContent { | ||||||
|     /// A unique identifier for the call.
 |     /// 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.
 |     /// The time in milliseconds that the invite is valid for.
 | ||||||
|     ///
 |     ///
 | ||||||
| @ -28,17 +38,70 @@ pub struct CallInviteEventContent { | |||||||
|     pub offer: OfferSessionDescription, |     pub offer: OfferSessionDescription, | ||||||
| 
 | 
 | ||||||
|     /// The version of the VoIP specification this messages adheres to.
 |     /// 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 { | 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( |     pub fn new( | ||||||
|         call_id: String, |         call_id: OwnedVoipId, | ||||||
|         lifetime: UInt, |         lifetime: UInt, | ||||||
|         offer: OfferSessionDescription, |         offer: OfferSessionDescription, | ||||||
|         version: UInt, |         version: VoipVersionId, | ||||||
|     ) -> Self { |     ) -> 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.invite" => super::call::invite, | ||||||
|         "m.call.hangup" => super::call::hangup, |         "m.call.hangup" => super::call::hangup, | ||||||
|         "m.call.candidates" => super::call::candidates, |         "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")] |         #[cfg(feature = "unstable-msc1767")] | ||||||
|         "m.emote" => super::emote, |         "m.emote" => super::emote, | ||||||
|         #[cfg(feature = "unstable-msc3551")] |         #[cfg(feature = "unstable-msc3551")] | ||||||
| @ -346,6 +352,8 @@ impl AnyMessageLikeEventContent { | |||||||
|             } |             } | ||||||
|             #[cfg(feature = "unstable-msc3381")] |             #[cfg(feature = "unstable-msc3381")] | ||||||
|             Self::PollStart(_) => None, |             Self::PollStart(_) => None, | ||||||
|  |             #[cfg(feature = "unstable-msc2746")] | ||||||
|  |             Self::CallNegotiate(_) | Self::CallReject(_) | Self::CallSelectAnswer(_) => None, | ||||||
|             Self::CallAnswer(_) |             Self::CallAnswer(_) | ||||||
|             | Self::CallInvite(_) |             | Self::CallInvite(_) | ||||||
|             | Self::CallHangup(_) |             | Self::CallHangup(_) | ||||||
|  | |||||||
| @ -32,10 +32,13 @@ pub use self::{ | |||||||
|     signatures::{DeviceSignatures, EntitySignatures, ServerSignatures, Signatures}, |     signatures::{DeviceSignatures, EntitySignatures, ServerSignatures, Signatures}, | ||||||
|     transaction_id::{OwnedTransactionId, TransactionId}, |     transaction_id::{OwnedTransactionId, TransactionId}, | ||||||
|     user_id::{OwnedUserId, UserId}, |     user_id::{OwnedUserId, UserId}, | ||||||
|  |     voip_id::{OwnedVoipId, VoipId}, | ||||||
|  |     voip_version_id::VoipVersionId, | ||||||
| }; | }; | ||||||
| #[doc(inline)] | #[doc(inline)] | ||||||
| pub use ruma_identifiers_validation::error::{ | pub use ruma_identifiers_validation::error::{ | ||||||
|     Error as IdParseError, MatrixIdError, MatrixToError, MatrixUriError, MxcUriError, |     Error as IdParseError, MatrixIdError, MatrixToError, MatrixUriError, MxcUriError, | ||||||
|  |     VoipVersionIdError, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub mod matrix_uri; | pub mod matrix_uri; | ||||||
| @ -58,6 +61,8 @@ mod server_name; | |||||||
| mod session_id; | mod session_id; | ||||||
| mod signatures; | mod signatures; | ||||||
| mod transaction_id; | mod transaction_id; | ||||||
|  | mod voip_id; | ||||||
|  | mod voip_version_id; | ||||||
| 
 | 
 | ||||||
| /// Generates a random identifier localpart.
 | /// Generates a random identifier localpart.
 | ||||||
| #[cfg(feature = "rand")] | #[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 assert_matches::assert_matches; | ||||||
| use js_int::{uint, UInt}; | use js_int::uint; | ||||||
| use ruma_common::{ | 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 serde_json::{from_value as from_json_value, json}; | ||||||
| 
 | 
 | ||||||
| use ruma_common::events::{ |  | ||||||
|     call::{answer::CallAnswerEventContent, AnswerSessionDescription}, |  | ||||||
|     AnyMessageLikeEvent, OriginalMessageLikeEvent, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| #[test] | #[test] | ||||||
| fn ui() { | fn ui() { | ||||||
|     let t = trybuild::TestCases::new(); |     let t = trybuild::TestCases::new(); | ||||||
| @ -27,7 +23,7 @@ fn deserialize_message_event() { | |||||||
|                 "sdp": "Hello" |                 "sdp": "Hello" | ||||||
|             }, |             }, | ||||||
|             "call_id": "foofoo", |             "call_id": "foofoo", | ||||||
|             "version": 1 |             "version": 0 | ||||||
|         }, |         }, | ||||||
|         "event_id": "$h29iv0s8:example.com", |         "event_id": "$h29iv0s8:example.com", | ||||||
|         "origin_server_ts": 1, |         "origin_server_ts": 1, | ||||||
| @ -36,29 +32,19 @@ fn deserialize_message_event() { | |||||||
|         "type": "m.call.answer" |         "type": "m.call.answer" | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     assert_matches!( |     let message_event = assert_matches!( | ||||||
|         from_json_value::<AnyMessageLikeEvent>(json_data) |         from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(), | ||||||
|             .unwrap(), |         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(message_event)) => message_event | ||||||
|         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() |  | ||||||
|     ); |     ); | ||||||
|  | 
 | ||||||
|  |     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::{ | use ruma_common::{ | ||||||
|     event_id, |     event_id, | ||||||
|     events::{ |     events::{ | ||||||
|         call::{answer::CallAnswerEventContent, AnswerSessionDescription}, |  | ||||||
|         room::{ImageInfo, MediaSource, ThumbnailInfo}, |         room::{ImageInfo, MediaSource, ThumbnailInfo}, | ||||||
|         sticker::StickerEventContent, |         sticker::StickerEventContent, | ||||||
|         AnyMessageLikeEvent, AnyMessageLikeEventContent, AnySyncMessageLikeEvent, MessageLikeEvent, |         AnyMessageLikeEvent, AnyMessageLikeEventContent, AnySyncMessageLikeEvent, MessageLikeEvent, | ||||||
| @ -12,7 +11,7 @@ use ruma_common::{ | |||||||
|     }, |     }, | ||||||
|     mxc_uri, room_id, |     mxc_uri, room_id, | ||||||
|     serde::Raw, |     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}; | 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" |             "sdp": "Hello" | ||||||
|         }, |         }, | ||||||
|         "call_id": "foofoo", |         "call_id": "foofoo", | ||||||
|         "version": 1 |         "version": 0 | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     assert_matches!( |     let content = assert_matches!( | ||||||
|         from_json_value::<Raw<AnyMessageLikeEventContent>>(json_data) |         from_json_value::<Raw<AnyMessageLikeEventContent>>(json_data) | ||||||
|             .unwrap() |             .unwrap() | ||||||
|             .deserialize_content(MessageLikeEventType::CallAnswer) |             .deserialize_content(MessageLikeEventType::CallAnswer) | ||||||
|             .unwrap(), |             .unwrap(), | ||||||
|         AnyMessageLikeEventContent::CallAnswer(CallAnswerEventContent { |         AnyMessageLikeEventContent::CallAnswer(content) => content | ||||||
|             answer: AnswerSessionDescription { |  | ||||||
|                 sdp, |  | ||||||
|                 .. |  | ||||||
|             }, |  | ||||||
|             call_id, |  | ||||||
|             version, |  | ||||||
|             .. |  | ||||||
|         }) if sdp == "Hello" && call_id == "foofoo" && version == UInt::new(1).unwrap() |  | ||||||
|     ); |     ); | ||||||
|  | 
 | ||||||
|  |     assert_eq!(content.answer.sdp, "Hello"); | ||||||
|  |     assert_eq!(content.call_id, "foofoo"); | ||||||
|  |     assert_eq!(content.version, VoipVersionId::V0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
| @ -156,7 +151,7 @@ fn deserialize_message_call_answer() { | |||||||
|                 "sdp": "Hello" |                 "sdp": "Hello" | ||||||
|             }, |             }, | ||||||
|             "call_id": "foofoo", |             "call_id": "foofoo", | ||||||
|             "version": 1 |             "version": 0 | ||||||
|         }, |         }, | ||||||
|         "event_id": "$h29iv0s8:example.com", |         "event_id": "$h29iv0s8:example.com", | ||||||
|         "origin_server_ts": 1, |         "origin_server_ts": 1, | ||||||
| @ -165,30 +160,20 @@ fn deserialize_message_call_answer() { | |||||||
|         "type": "m.call.answer" |         "type": "m.call.answer" | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     assert_matches!( |     let message_event = assert_matches!( | ||||||
|         from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(), |         from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(), | ||||||
|         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(OriginalMessageLikeEvent { |         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(message_event)) => message_event | ||||||
|             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() |  | ||||||
|     ); |     ); | ||||||
|  |     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] | #[test] | ||||||
| @ -280,7 +265,7 @@ fn deserialize_message_then_convert_to_full() { | |||||||
|                 "sdp": "Hello" |                 "sdp": "Hello" | ||||||
|             }, |             }, | ||||||
|             "call_id": "foofoo", |             "call_id": "foofoo", | ||||||
|             "version": 1 |             "version": 0 | ||||||
|         }, |         }, | ||||||
|         "event_id": "$h29iv0s8:example.com", |         "event_id": "$h29iv0s8:example.com", | ||||||
|         "origin_server_ts": 1, |         "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(); |     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()), |         sync_ev.into_full_event(rid.to_owned()), | ||||||
|         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(OriginalMessageLikeEvent { |         AnyMessageLikeEvent::CallAnswer(MessageLikeEvent::Original(message_event)) => message_event | ||||||
|             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() |  | ||||||
|     ); |     ); | ||||||
|  |     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")] | #![cfg(feature = "events")] | ||||||
| 
 | 
 | ||||||
| mod audio; | mod audio; | ||||||
|  | mod call; | ||||||
| mod enums; | mod enums; | ||||||
| mod ephemeral_event; | mod ephemeral_event; | ||||||
| mod event; | mod event; | ||||||
|  | |||||||
| @ -15,4 +15,5 @@ all-features = true | |||||||
| compat = [] | compat = [] | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
|  | js_int = "0.2.0" | ||||||
| thiserror = "1.0.26" | thiserror = "1.0.26" | ||||||
|  | |||||||
| @ -30,6 +30,10 @@ pub enum Error { | |||||||
|     #[error("invalid Matrix Content URI: {0}")] |     #[error("invalid Matrix Content URI: {0}")] | ||||||
|     InvalidMxcUri(#[from] MxcUriError), |     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.
 |     /// 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")] |     #[error("server name is not a valid IP address or domain name")] | ||||||
|     InvalidServerName, |     InvalidServerName, | ||||||
| @ -149,6 +153,15 @@ pub enum MatrixUriError { | |||||||
|     UnknownQueryItem, |     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)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use std::mem::size_of; |     use std::mem::size_of; | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ pub mod room_version_id; | |||||||
| pub mod server_name; | pub mod server_name; | ||||||
| pub mod session_id; | pub mod session_id; | ||||||
| pub mod user_id; | pub mod user_id; | ||||||
|  | pub mod voip_version_id; | ||||||
| 
 | 
 | ||||||
| pub use error::Error; | 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-client-api/unstable-msc2677", | ||||||
|     "ruma-common/unstable-msc2677", |     "ruma-common/unstable-msc2677", | ||||||
| ] | ] | ||||||
|  | unstable-msc2746 = ["ruma-common/unstable-msc2746"] | ||||||
| unstable-msc2870 = ["ruma-signatures/unstable-msc2870"] | unstable-msc2870 = ["ruma-signatures/unstable-msc2870"] | ||||||
| unstable-msc3245 = ["ruma-common/unstable-msc3245"] | unstable-msc3245 = ["ruma-common/unstable-msc3245"] | ||||||
| unstable-msc3246 = ["ruma-common/unstable-msc3246"] | unstable-msc3246 = ["ruma-common/unstable-msc3246"] | ||||||
| @ -163,6 +164,7 @@ __ci = [ | |||||||
|     "unstable-msc2675", |     "unstable-msc2675", | ||||||
|     "unstable-msc2676", |     "unstable-msc2676", | ||||||
|     "unstable-msc2677", |     "unstable-msc2677", | ||||||
|  |     "unstable-msc2746", | ||||||
|     "unstable-msc2870", |     "unstable-msc2870", | ||||||
|     "unstable-msc3245", |     "unstable-msc3245", | ||||||
|     "unstable-msc3246", |     "unstable-msc3246", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user