1use xso::{AsXml, FromXml};
8
9use crate::iq::IqSetPayload;
10use crate::jingle_grouping::Group;
11use crate::jingle_ibb::Transport as IbbTransport;
12use crate::jingle_ice_udp::Transport as IceUdpTransport;
13use crate::jingle_rtp::Description as RtpDescription;
14use crate::jingle_s5b::Transport as Socks5Transport;
15use crate::ns;
16use alloc::{collections::BTreeMap, fmt};
17use jid::Jid;
18use minidom::Element;
19use xso::error::Error;
20
21generate_attribute!(
22 Action, "action", {
24 ContentAccept => "content-accept",
26
27 ContentAdd => "content-add",
29
30 ContentModify => "content-modify",
32
33 ContentReject => "content-reject",
35
36 ContentRemove => "content-remove",
38
39 DescriptionInfo => "description-info",
41
42 SecurityInfo => "security-info",
44
45 SessionAccept => "session-accept",
47
48 SessionInfo => "session-info",
50
51 SessionInitiate => "session-initiate",
53
54 SessionTerminate => "session-terminate",
56
57 TransportAccept => "transport-accept",
59
60 TransportInfo => "transport-info",
62
63 TransportReject => "transport-reject",
65
66 TransportReplace => "transport-replace",
68 }
69);
70
71generate_attribute!(
72 Creator, "creator", {
74 Initiator => "initiator",
76
77 Responder => "responder",
79 }
80);
81
82generate_attribute!(
83 Senders, "senders", {
85 Both => "both",
87
88 Initiator => "initiator",
90
91 None => "none",
93
94 Responder => "responder",
96 }, Default = Both
97);
98
99generate_attribute!(
100 Disposition, "disposition", {
107 Inline => "inline",
109
110 Attachment => "attachment",
112
113 FormData => "form-data",
115
116 Signal => "signal",
118
119 Alert => "alert",
121
122 Icon => "icon",
124
125 Render => "render",
127
128 RecipientListHistory => "recipient-list-history",
131
132 Session => "session",
135
136 Aib => "aib",
138
139 EarlySession => "early-session",
142
143 RecipientList => "recipient-list",
146
147 Notification => "notification",
151
152 ByReference => "by-reference",
155
156 InfoPackage => "info-package",
158
159 RecordingSession => "recording-session",
163 }, Default = Session
164);
165
166generate_id!(
167 ContentId
170);
171
172#[derive(AsXml, Debug, Clone, PartialEq)]
174#[xml()]
175pub enum Description {
176 #[xml(transparent)]
178 Rtp(RtpDescription),
179
180 #[xml(transparent)]
183 Unknown(Element),
184}
185
186impl TryFrom<Element> for Description {
187 type Error = Error;
188
189 fn try_from(elem: Element) -> Result<Description, Error> {
190 Ok(if elem.is("description", ns::JINGLE_RTP) {
191 Description::Rtp(RtpDescription::try_from(elem)?)
192 } else if elem.name() == "description" {
193 Description::Unknown(elem)
194 } else {
195 return Err(Error::Other("Invalid description."));
196 })
197 }
198}
199
200impl ::xso::FromXml for Description {
201 type Builder = ::xso::minidom_compat::FromEventsViaElement<Description>;
202
203 fn from_events(
204 qname: ::xso::exports::rxml::QName,
205 attrs: ::xso::exports::rxml::AttrMap,
206 _ctx: &::xso::Context<'_>,
207 ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
208 if qname.1 != "description" {
209 return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
210 }
211 Self::Builder::new(qname, attrs)
212 }
213}
214
215impl From<RtpDescription> for Description {
216 fn from(desc: RtpDescription) -> Description {
217 Description::Rtp(desc)
218 }
219}
220
221#[derive(AsXml, Debug, Clone, PartialEq)]
223#[xml()]
224pub enum Transport {
225 #[xml(transparent)]
227 IceUdp(IceUdpTransport),
228
229 #[xml(transparent)]
231 Ibb(IbbTransport),
232
233 #[xml(transparent)]
235 Socks5(Socks5Transport),
236
237 #[xml(transparent)]
240 Unknown(Element),
241}
242
243impl TryFrom<Element> for Transport {
244 type Error = Error;
245
246 fn try_from(elem: Element) -> Result<Transport, Error> {
247 Ok(if elem.is("transport", ns::JINGLE_ICE_UDP) {
248 Transport::IceUdp(IceUdpTransport::try_from(elem)?)
249 } else if elem.is("transport", ns::JINGLE_IBB) {
250 Transport::Ibb(IbbTransport::try_from(elem)?)
251 } else if elem.is("transport", ns::JINGLE_S5B) {
252 Transport::Socks5(Socks5Transport::try_from(elem)?)
253 } else if elem.name() == "transport" {
254 Transport::Unknown(elem)
255 } else {
256 return Err(Error::Other("Invalid transport."));
257 })
258 }
259}
260
261impl ::xso::FromXml for Transport {
262 type Builder = ::xso::minidom_compat::FromEventsViaElement<Transport>;
263
264 fn from_events(
265 qname: ::xso::exports::rxml::QName,
266 attrs: ::xso::exports::rxml::AttrMap,
267 _ctx: &::xso::Context<'_>,
268 ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
269 if qname.1 != "transport" {
270 return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
271 }
272 Self::Builder::new(qname, attrs)
273 }
274}
275
276impl From<IceUdpTransport> for Transport {
277 fn from(transport: IceUdpTransport) -> Transport {
278 Transport::IceUdp(transport)
279 }
280}
281
282impl From<IbbTransport> for Transport {
283 fn from(transport: IbbTransport) -> Transport {
284 Transport::Ibb(transport)
285 }
286}
287
288impl From<Socks5Transport> for Transport {
289 fn from(transport: Socks5Transport) -> Transport {
290 Transport::Socks5(transport)
291 }
292}
293
294#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
296#[xml(namespace = ns::JINGLE, name = "security")]
297pub struct Security;
298
299#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
302#[xml(namespace = ns::JINGLE, name = "content")]
303pub struct Content {
304 #[xml(attribute)]
306 pub creator: Creator,
307
308 #[xml(attribute(default))]
310 pub disposition: Disposition,
311
312 #[xml(attribute)]
314 pub name: ContentId,
315
316 #[xml(attribute(default))]
318 pub senders: Senders,
319
320 #[xml(child(default))]
322 pub description: Option<Description>,
323
324 #[xml(child(default))]
326 pub transport: Option<Transport>,
327
328 #[xml(child(default))]
330 pub security: Option<Security>,
331}
332
333impl Content {
334 pub fn new(creator: Creator, name: ContentId) -> Content {
336 Content {
337 creator,
338 name,
339 disposition: Disposition::Session,
340 senders: Senders::Both,
341 description: None,
342 transport: None,
343 security: None,
344 }
345 }
346
347 pub fn with_disposition(mut self, disposition: Disposition) -> Content {
349 self.disposition = disposition;
350 self
351 }
352
353 pub fn with_senders(mut self, senders: Senders) -> Content {
355 self.senders = senders;
356 self
357 }
358
359 pub fn with_description<D: Into<Description>>(mut self, description: D) -> Content {
361 self.description = Some(description.into());
362 self
363 }
364
365 pub fn with_transport<T: Into<Transport>>(mut self, transport: T) -> Content {
367 self.transport = Some(transport.into());
368 self
369 }
370
371 pub fn with_security(mut self, security: Security) -> Content {
373 self.security = Some(security);
374 self
375 }
376}
377
378#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
380#[xml(namespace = ns::JINGLE)]
381pub enum Reason {
382 #[xml(name = "alternative-session")]
387 AlternativeSession {
388 #[xml(extract(namespace = ns::JINGLE, name = "sid", default, fields(text(type_ = String))))]
390 sid: Option<String>,
391 },
392
393 #[xml(name = "busy")]
395 Busy,
396
397 #[xml(name = "cancel")]
399 Cancel,
400
401 #[xml(name = "connectivity-error")]
403 ConnectivityError,
404
405 #[xml(name = "decline")]
407 Decline,
408
409 #[xml(name = "expired")]
412 Expired,
413
414 #[xml(name = "failed-application")]
417 FailedApplication,
418
419 #[xml(name = "failed-transport")]
422 FailedTransport,
423
424 #[xml(name = "general-error")]
426 GeneralError,
427
428 #[xml(name = "gone")]
430 Gone,
431
432 #[xml(name = "incompatible-parameters")]
435 IncompatibleParameters,
436
437 #[xml(name = "media-error")]
439 MediaError,
440
441 #[xml(name = "security-error")]
443 SecurityError,
444
445 #[xml(name = "success")]
448 Success,
449
450 #[xml(name = "timeout")]
453 Timeout,
454
455 #[xml(name = "unsupported-applications")]
457 UnsupportedApplications,
458
459 #[xml(name = "unsupported-transports")]
461 UnsupportedTransports,
462}
463
464type Lang = String;
465
466#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
468#[xml(namespace = ns::JINGLE, name = "reason")]
469pub struct ReasonElement {
470 #[xml(child)]
472 pub reason: Reason,
473
474 #[xml(extract(n = .., namespace = ns::JINGLE, name = "text", fields(
476 attribute(type_ = String, name = "xml:lang", default),
477 text(type_ = String),
478 )))]
479 pub texts: BTreeMap<Lang, String>,
480}
481
482impl fmt::Display for ReasonElement {
483 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
484 write!(fmt, "{}", Element::from(self.reason.clone()).name())?;
485 if let Some(text) = self.texts.get("en") {
486 write!(fmt, ": {}", text)?;
487 } else if let Some(text) = self.texts.get("") {
488 write!(fmt, ": {}", text)?;
489 }
490 Ok(())
491 }
492}
493
494generate_id!(
495 SessionId
497);
498
499#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
501#[xml(namespace = ns::JINGLE, name = "jingle")]
502pub struct Jingle {
503 #[xml(attribute)]
505 pub action: Action,
506
507 #[xml(attribute(default))]
509 pub initiator: Option<Jid>,
510
511 #[xml(attribute(default))]
513 pub responder: Option<Jid>,
514
515 #[xml(attribute)]
517 pub sid: SessionId,
518
519 #[xml(child(n = ..))]
521 pub contents: Vec<Content>,
522
523 #[xml(child(default))]
525 pub reason: Option<ReasonElement>,
526
527 #[xml(child(default))]
529 pub group: Option<Group>,
530
531 #[xml(child(n = ..))]
533 pub other: Vec<Element>,
534}
535
536impl IqSetPayload for Jingle {}
537
538impl Jingle {
539 pub fn new(action: Action, sid: SessionId) -> Jingle {
541 Jingle {
542 action,
543 sid,
544 initiator: None,
545 responder: None,
546 contents: Vec::new(),
547 reason: None,
548 group: None,
549 other: Vec::new(),
550 }
551 }
552
553 pub fn with_initiator(mut self, initiator: Jid) -> Jingle {
555 self.initiator = Some(initiator);
556 self
557 }
558
559 pub fn with_responder(mut self, responder: Jid) -> Jingle {
561 self.responder = Some(responder);
562 self
563 }
564
565 pub fn add_content(mut self, content: Content) -> Jingle {
567 self.contents.push(content);
568 self
569 }
570
571 pub fn set_reason(mut self, reason: ReasonElement) -> Jingle {
573 self.reason = Some(reason);
574 self
575 }
576
577 pub fn set_group(mut self, group: Group) -> Jingle {
579 self.group = Some(group);
580 self
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587 use xso::error::FromElementError;
588
589 #[cfg(target_pointer_width = "32")]
590 #[test]
591 fn test_size() {
592 assert_size!(Action, 1);
593 assert_size!(Creator, 1);
594 assert_size!(Senders, 1);
595 assert_size!(Disposition, 1);
596 assert_size!(ContentId, 12);
597 assert_size!(Content, 156);
598 assert_size!(Reason, 12);
599 assert_size!(ReasonElement, 24);
600 assert_size!(SessionId, 12);
601 assert_size!(Jingle, 112);
602 }
603
604 #[cfg(target_pointer_width = "64")]
605 #[test]
606 fn test_size() {
607 assert_size!(Action, 1);
608 assert_size!(Creator, 1);
609 assert_size!(Senders, 1);
610 assert_size!(Disposition, 1);
611 assert_size!(ContentId, 24);
612 assert_size!(Content, 312);
613 assert_size!(Reason, 24);
614 assert_size!(ReasonElement, 48);
615 assert_size!(SessionId, 24);
616 assert_size!(Jingle, 224);
617 }
618
619 #[test]
620 fn test_simple() {
621 let elem: Element =
622 "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'/>"
623 .parse()
624 .unwrap();
625 let jingle = Jingle::try_from(elem).unwrap();
626 assert_eq!(jingle.action, Action::SessionInitiate);
627 assert_eq!(jingle.sid, SessionId(String::from("coucou")));
628 }
629
630 #[test]
631 fn test_invalid_jingle() {
632 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1'/>".parse().unwrap();
633 let error = Jingle::try_from(elem).unwrap_err();
634 let message = match error {
635 FromElementError::Invalid(Error::Other(string)) => string,
636 _ => panic!(),
637 };
638 assert_eq!(
639 message,
640 "Required attribute field 'action' on Jingle element missing."
641 );
642
643 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-info'/>"
644 .parse()
645 .unwrap();
646 let error = Jingle::try_from(elem).unwrap_err();
647 let message = match error {
648 FromElementError::Invalid(Error::Other(string)) => string,
649 _ => panic!(),
650 };
651 assert_eq!(
652 message,
653 "Required attribute field 'sid' on Jingle element missing."
654 );
655
656 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='coucou' sid='coucou'/>"
657 .parse()
658 .unwrap();
659 let error = Jingle::try_from(elem).unwrap_err();
660 let message = match error {
661 FromElementError::Invalid(Error::TextParseError(string)) => string,
662 _ => panic!(),
663 };
664 assert_eq!(message.to_string(), "Unknown value for 'action' attribute.");
665 }
666
667 #[test]
668 fn test_content() {
669 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou'><description/><transport xmlns='urn:xmpp:jingle:transports:stub:0'/></content></jingle>".parse().unwrap();
670 let jingle = Jingle::try_from(elem).unwrap();
671 assert_eq!(jingle.contents[0].creator, Creator::Initiator);
672 assert_eq!(jingle.contents[0].name, ContentId(String::from("coucou")));
673 assert_eq!(jingle.contents[0].senders, Senders::Both);
674 assert_eq!(jingle.contents[0].disposition, Disposition::Session);
675
676 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='both'><description/><transport xmlns='urn:xmpp:jingle:transports:stub:0'/></content></jingle>".parse().unwrap();
677 let jingle = Jingle::try_from(elem).unwrap();
678 assert_eq!(jingle.contents[0].senders, Senders::Both);
679
680 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' disposition='early-session'><description/><transport xmlns='urn:xmpp:jingle:transports:stub:0'/></content></jingle>".parse().unwrap();
681 let jingle = Jingle::try_from(elem).unwrap();
682 assert_eq!(jingle.contents[0].disposition, Disposition::EarlySession);
683 }
684
685 #[test]
686 fn test_invalid_content() {
687 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content/></jingle>".parse().unwrap();
688 let error = Jingle::try_from(elem).unwrap_err();
689 let message = match error {
690 FromElementError::Invalid(Error::Other(string)) => string,
691 _ => panic!(),
692 };
693 assert_eq!(
694 message,
695 "Required attribute field 'creator' on Content element missing."
696 );
697
698 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator'/></jingle>".parse().unwrap();
699 let error = Jingle::try_from(elem).unwrap_err();
700 let message = match error {
701 FromElementError::Invalid(Error::Other(string)) => string,
702 _ => panic!(),
703 };
704 assert_eq!(
705 message,
706 "Required attribute field 'name' on Content element missing."
707 );
708
709 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='coucou' name='coucou'/></jingle>".parse().unwrap();
710 let error = Jingle::try_from(elem).unwrap_err();
711 let message = match error {
712 FromElementError::Invalid(Error::TextParseError(string)) => string,
713 other => panic!("unexpected result: {:?}", other),
714 };
715 assert_eq!(
716 message.to_string(),
717 "Unknown value for 'creator' attribute."
718 );
719
720 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='coucou'/></jingle>".parse().unwrap();
721 let error = Jingle::try_from(elem).unwrap_err();
722 let message = match error {
723 FromElementError::Invalid(Error::TextParseError(string)) => string,
724 _ => panic!(),
725 };
726 assert_eq!(
727 message.to_string(),
728 "Unknown value for 'senders' attribute."
729 );
730
731 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders=''/></jingle>".parse().unwrap();
732 let error = Jingle::try_from(elem).unwrap_err();
733 let message = match error {
734 FromElementError::Invalid(Error::TextParseError(string)) => string,
735 _ => panic!(),
736 };
737 assert_eq!(
738 message.to_string(),
739 "Unknown value for 'senders' attribute."
740 );
741 }
742
743 #[test]
744 fn test_reason() {
745 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/></reason></jingle>".parse().unwrap();
746 let jingle = Jingle::try_from(elem).unwrap();
747 let reason = jingle.reason.unwrap();
748 assert_eq!(reason.reason, Reason::Success);
749 assert_eq!(reason.texts, BTreeMap::new());
750
751 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/><text>coucou</text></reason></jingle>".parse().unwrap();
752 let jingle = Jingle::try_from(elem).unwrap();
753 let reason = jingle.reason.unwrap();
754 assert_eq!(reason.reason, Reason::Success);
755 assert_eq!(reason.texts.get(""), Some(&String::from("coucou")));
756 }
757
758 #[test]
759 fn test_missing_reason_text() {
760 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason/></jingle>".parse().unwrap();
761 let error = Jingle::try_from(elem).unwrap_err();
762 let message = match error {
763 FromElementError::Invalid(Error::Other(string)) => string,
764 _ => panic!(),
765 };
766 assert_eq!(
767 message,
768 "Missing child field 'reason' in ReasonElement element."
769 );
770 }
771
772 #[test]
773 #[cfg_attr(feature = "disable-validation", should_panic = "Result::unwrap_err")]
774 fn test_invalid_child_in_reason() {
775 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/><a/></reason></jingle>".parse().unwrap();
776 let error = Jingle::try_from(elem).unwrap_err();
777 let message = match error {
778 FromElementError::Invalid(Error::Other(string)) => string,
779 _ => panic!(),
780 };
781 assert_eq!(message, "Unknown child in ReasonElement element.");
782 }
783
784 #[test]
785 fn test_multiple_reason_children() {
786 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason/></jingle>".parse().unwrap();
787 let error = Jingle::try_from(elem).unwrap_err();
788 let message = match error {
789 FromElementError::Invalid(Error::Other(string)) => string,
790 _ => panic!(),
791 };
792 assert_eq!(
793 message,
794 "Jingle element must not have more than one child in field 'reason'."
795 );
796
797 }
809
810 #[test]
811 fn test_serialize_jingle() {
812 let reference: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='a73sjjvkla37jfea'><content xmlns='urn:xmpp:jingle:1' creator='initiator' name='this-is-a-stub'><description xmlns='urn:xmpp:jingle:apps:stub:0'/><transport xmlns='urn:xmpp:jingle:transports:stub:0'/></content></jingle>"
813 .parse()
814 .unwrap();
815
816 let jingle = Jingle {
817 action: Action::SessionInitiate,
818 initiator: None,
819 responder: None,
820 sid: SessionId(String::from("a73sjjvkla37jfea")),
821 contents: vec![Content {
822 creator: Creator::Initiator,
823 disposition: Disposition::default(),
824 name: ContentId(String::from("this-is-a-stub")),
825 senders: Senders::default(),
826 description: Some(Description::Unknown(
827 Element::builder("description", "urn:xmpp:jingle:apps:stub:0").build(),
828 )),
829 transport: Some(Transport::Unknown(
830 Element::builder("transport", "urn:xmpp:jingle:transports:stub:0").build(),
831 )),
832 security: None,
833 }],
834 reason: None,
835 group: None,
836 other: vec![],
837 };
838 let serialized: Element = jingle.into();
839 assert_eq!(serialized, reference);
840 }
841}