use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;
use jid::Jid;
use crate::core::{FromXml, IntoXml};
use crate::iq::IqSetPayload;
use crate::jingle_grouping::Group;
use crate::jingle_ibb::Transport as IbbTransport;
use crate::jingle_ice_udp::Transport as IceUdpTransport;
use crate::jingle_rtp::Description as RtpDescription;
use crate::jingle_s5b::Transport as Socks5Transport;
use crate::ns::JINGLE;
use crate::util::error::Error;
use crate::Element;
generate_attribute!(
Action, "action", {
ContentAccept => "content-accept",
ContentAdd => "content-add",
ContentModify => "content-modify",
ContentReject => "content-reject",
ContentRemove => "content-remove",
DescriptionInfo => "description-info",
SecurityInfo => "security-info",
SessionAccept => "session-accept",
SessionInfo => "session-info",
SessionInitiate => "session-initiate",
SessionTerminate => "session-terminate",
TransportAccept => "transport-accept",
TransportInfo => "transport-info",
TransportReject => "transport-reject",
TransportReplace => "transport-replace",
}
);
generate_attribute!(
Creator, "creator", {
Initiator => "initiator",
Responder => "responder",
}
);
generate_attribute!(
Senders, "senders", {
Both => "both",
Initiator => "initiator",
None => "none",
Responder => "responder",
}, Default = Both
);
generate_attribute!(
Disposition, "disposition", {
Inline => "inline",
Attachment => "attachment",
FormData => "form-data",
Signal => "signal",
Alert => "alert",
Icon => "icon",
Render => "render",
RecipientListHistory => "recipient-list-history",
Session => "session",
Aib => "aib",
EarlySession => "early-session",
RecipientList => "recipient-list",
Notification => "notification",
ByReference => "by-reference",
InfoPackage => "info-package",
RecordingSession => "recording-session",
}, Default = Session
);
generate_id!(
ContentId
);
#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
#[xml()]
pub enum Description {
#[xml(transparent)]
Rtp(RtpDescription),
#[xml(element(name = "description"))]
Unknown(Element),
}
impl From<RtpDescription> for Description {
fn from(desc: RtpDescription) -> Description {
Description::Rtp(desc)
}
}
#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
#[xml()]
pub enum Transport {
#[xml(transparent)]
IceUdp(IceUdpTransport),
#[xml(transparent)]
Ibb(IbbTransport),
#[xml(transparent)]
Socks5(Socks5Transport),
#[xml(element(name = "transport"))]
Unknown(Element),
}
impl From<IceUdpTransport> for Transport {
fn from(transport: IceUdpTransport) -> Transport {
Transport::IceUdp(transport)
}
}
impl From<IbbTransport> for Transport {
fn from(transport: IbbTransport) -> Transport {
Transport::Ibb(transport)
}
}
impl From<Socks5Transport> for Transport {
fn from(transport: Socks5Transport) -> Transport {
Transport::Socks5(transport)
}
}
#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
#[xml(namespace = JINGLE, name = "content")]
pub struct Content {
#[xml(attribute)]
pub creator: Creator,
#[xml(attribute(default))]
pub disposition: Disposition,
#[xml(attribute)]
pub name: ContentId,
#[xml(attribute(default))]
pub senders: Senders,
#[xml(child)]
pub description: Option<Description>,
#[xml(child)]
pub transport: Option<Transport>,
#[xml(element(namespace = JINGLE, name = "security", default))]
pub security: Option<Element>,
}
impl Content {
pub fn new(creator: Creator, name: ContentId) -> Content {
Content {
creator,
name,
disposition: Disposition::Session,
senders: Senders::Both,
description: None,
transport: None,
security: None,
}
}
pub fn with_disposition(mut self, disposition: Disposition) -> Content {
self.disposition = disposition;
self
}
pub fn with_senders(mut self, senders: Senders) -> Content {
self.senders = senders;
self
}
pub fn with_description<D: Into<Description>>(mut self, description: D) -> Content {
self.description = Some(description.into());
self
}
pub fn with_transport<T: Into<Transport>>(mut self, transport: T) -> Content {
self.transport = Some(transport.into());
self
}
pub fn with_security(mut self, security: Element) -> Content {
self.security = Some(security);
self
}
}
#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
#[xml(namespace = JINGLE)]
pub enum Reason {
#[xml(name = "alternative-session")]
AlternativeSession(
#[xml(child(namespace = JINGLE, name = "sid", extract(text), default))] Option<String>,
),
#[xml(name = "busy")]
Busy,
#[xml(name = "cancel")]
Cancel,
#[xml(name = "connectivity-error")]
ConnectivityError,
#[xml(name = "decline")]
Decline,
#[xml(name = "expired")]
Expired,
#[xml(name = "failed-application")]
FailedApplication,
#[xml(name = "failed-transport")]
FailedTransport,
#[xml(name = "general-error")]
GeneralError,
#[xml(name = "gone")]
Gone,
#[xml(name = "incompatible-parameters")]
IncompatibleParameters,
#[xml(name = "media-error")]
MediaError,
#[xml(name = "security-error")]
SecurityError,
#[xml(name = "success")]
Success,
#[xml(name = "timeout")]
Timeout,
#[xml(name = "unsupported-applications")]
UnsupportedApplications,
#[xml(name = "unsupported-transports")]
UnsupportedTransports,
}
impl FromStr for Reason {
type Err = Error;
fn from_str(s: &str) -> Result<Reason, Error> {
Ok(match s {
"alternative-session" => Reason::AlternativeSession(None),
"busy" => Reason::Busy,
"cancel" => Reason::Cancel,
"connectivity-error" => Reason::ConnectivityError,
"decline" => Reason::Decline,
"expired" => Reason::Expired,
"failed-application" => Reason::FailedApplication,
"failed-transport" => Reason::FailedTransport,
"general-error" => Reason::GeneralError,
"gone" => Reason::Gone,
"incompatible-parameters" => Reason::IncompatibleParameters,
"media-error" => Reason::MediaError,
"security-error" => Reason::SecurityError,
"success" => Reason::Success,
"timeout" => Reason::Timeout,
"unsupported-applications" => Reason::UnsupportedApplications,
"unsupported-transports" => Reason::UnsupportedTransports,
_ => return Err(Error::ParseError("Unknown reason.")),
})
}
}
type Lang = String;
#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
#[xml(namespace = JINGLE, name = "reason")]
pub struct ReasonElement {
#[xml(child)]
pub reason: Reason,
#[xml(children(namespace = JINGLE, name = "text", extract(attribute(name = "xml:lang", default), text)))]
pub texts: BTreeMap<Lang, String>,
}
impl fmt::Display for ReasonElement {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", Element::from(self.reason.clone()).name())?;
if let Some(text) = self.texts.get("en") {
write!(fmt, ": {}", text)?;
} else if let Some(text) = self.texts.get("") {
write!(fmt, ": {}", text)?;
}
Ok(())
}
}
generate_id!(
SessionId
);
#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
#[xml(namespace = JINGLE, name = "jingle")]
pub struct Jingle {
#[xml(attribute)]
pub action: Action,
#[xml(attribute)]
pub initiator: Option<Jid>,
#[xml(attribute)]
pub responder: Option<Jid>,
#[xml(attribute)]
pub sid: SessionId,
#[xml(children)]
pub contents: Vec<Content>,
#[xml(child)]
pub reason: Option<ReasonElement>,
#[xml(child)]
pub group: Option<Group>,
#[xml(elements)]
pub other: Vec<Element>,
}
impl IqSetPayload for Jingle {}
impl Jingle {
pub fn new(action: Action, sid: SessionId) -> Jingle {
Jingle {
action,
sid,
initiator: None,
responder: None,
contents: Vec::new(),
reason: None,
group: None,
other: Vec::new(),
}
}
pub fn with_initiator(mut self, initiator: Jid) -> Jingle {
self.initiator = Some(initiator);
self
}
pub fn with_responder(mut self, responder: Jid) -> Jingle {
self.responder = Some(responder);
self
}
pub fn add_content(mut self, content: Content) -> Jingle {
self.contents.push(content);
self
}
pub fn set_reason(mut self, reason: ReasonElement) -> Jingle {
self.reason = Some(reason);
self
}
pub fn set_group(mut self, group: Group) -> Jingle {
self.group = Some(group);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(Action, 1);
assert_size!(Creator, 1);
assert_size!(Senders, 1);
assert_size!(Disposition, 1);
assert_size!(ContentId, 12);
assert_size!(Content, 216);
assert_size!(Reason, 12);
assert_size!(ReasonElement, 24);
assert_size!(SessionId, 12);
assert_size!(Jingle, 112);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(Action, 1);
assert_size!(Creator, 1);
assert_size!(Senders, 1);
assert_size!(Disposition, 1);
assert_size!(ContentId, 24);
#[cfg(feature = "nightly")]
assert_size!(Content, 448);
#[cfg(feature = "stable")]
assert_size!(Content, 440);
assert_size!(Reason, 24);
assert_size!(ReasonElement, 48);
assert_size!(SessionId, 24);
assert_size!(Jingle, 224);
}
#[test]
fn test_simple() {
let elem: Element =
"<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'/>"
.parse()
.unwrap();
let jingle = Jingle::try_from(elem).unwrap();
assert_eq!(jingle.action, Action::SessionInitiate);
assert_eq!(jingle.sid, SessionId(String::from("coucou")));
}
#[test]
fn test_invalid_jingle() {
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1'/>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(
message,
"Required attribute field 'action' on Jingle element missing."
);
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-info'/>"
.parse()
.unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(
message,
"Required attribute field 'sid' on Jingle element missing."
);
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='coucou' sid='coucou'/>"
.parse()
.unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown value for 'action' attribute.");
}
#[test]
fn test_content() {
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();
let jingle = Jingle::try_from(elem).unwrap();
assert_eq!(jingle.contents[0].creator, Creator::Initiator);
assert_eq!(jingle.contents[0].name, ContentId(String::from("coucou")));
assert_eq!(jingle.contents[0].senders, Senders::Both);
assert_eq!(jingle.contents[0].disposition, Disposition::Session);
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();
let jingle = Jingle::try_from(elem).unwrap();
assert_eq!(jingle.contents[0].senders, Senders::Both);
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();
let jingle = Jingle::try_from(elem).unwrap();
assert_eq!(jingle.contents[0].disposition, Disposition::EarlySession);
}
#[test]
fn test_invalid_content() {
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content/></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(
message,
"Required attribute field 'creator' on Content element missing."
);
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator'/></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(
message,
"Required attribute field 'name' on Content element missing."
);
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='coucou' name='coucou'/></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown value for 'creator' attribute.");
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='coucou'/></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown value for 'senders' attribute.");
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders=''/></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown value for 'senders' attribute.");
}
#[test]
fn test_reason() {
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/></reason></jingle>".parse().unwrap();
let jingle = Jingle::try_from(elem).unwrap();
let reason = jingle.reason.unwrap();
assert_eq!(reason.reason, Reason::Success);
assert_eq!(reason.texts, BTreeMap::new());
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/><text>coucou</text></reason></jingle>".parse().unwrap();
let jingle = Jingle::try_from(elem).unwrap();
let reason = jingle.reason.unwrap();
assert_eq!(reason.reason, Reason::Success);
assert_eq!(reason.texts.get(""), Some(&String::from("coucou")));
}
#[test]
fn test_invalid_reason() {
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason/></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(
message,
"Missing child field 'reason' in ReasonElement element."
);
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a/></reason></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown child in ReasonElement element.");
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a xmlns='http://www.w3.org/1999/xhtml'/></reason></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(message, "Unknown child in ReasonElement element.");
let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason><success/></reason></jingle>".parse().unwrap();
let error = Jingle::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(
message,
"Jingle element must not have more than one child in field 'reason'."
);
}
#[test]
fn test_serialize_jingle() {
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>"
.parse()
.unwrap();
let jingle = Jingle {
action: Action::SessionInitiate,
initiator: None,
responder: None,
sid: SessionId(String::from("a73sjjvkla37jfea")),
contents: vec![Content {
creator: Creator::Initiator,
disposition: Disposition::default(),
name: ContentId(String::from("this-is-a-stub")),
senders: Senders::default(),
description: Some(Description::Unknown(
Element::builder("description", "urn:xmpp:jingle:apps:stub:0").build(),
)),
transport: Some(Transport::Unknown(
Element::builder("transport", "urn:xmpp:jingle:transports:stub:0").build(),
)),
security: None,
}],
reason: None,
group: None,
other: vec![],
};
let serialized: Element = jingle.into();
assert_eq!(serialized, reference);
}
}