// Copyright (c) 2017 Emmanuel Gil Peyrot // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::convert::TryFrom; use std::str::FromStr; use minidom::Element; use error::Error; use ns; #[derive(Debug, Clone, PartialEq)] pub enum Action { ContentAccept, ContentAdd, ContentModify, ContentReject, ContentRemove, DescriptionInfo, SecurityInfo, SessionAccept, SessionInfo, SessionInitiate, SessionTerminate, TransportAccept, TransportInfo, TransportReject, TransportReplace, } impl FromStr for Action { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "content-accept" => Action::ContentAccept, "content-add" => Action::ContentAdd, "content-modify" => Action::ContentModify, "content-reject" => Action::ContentReject, "content-remove" => Action::ContentRemove, "description-info" => Action::DescriptionInfo, "security-info" => Action::SecurityInfo, "session-accept" => Action::SessionAccept, "session-info" => Action::SessionInfo, "session-initiate" => Action::SessionInitiate, "session-terminate" => Action::SessionTerminate, "transport-accept" => Action::TransportAccept, "transport-info" => Action::TransportInfo, "transport-reject" => Action::TransportReject, "transport-replace" => Action::TransportReplace, _ => return Err(Error::ParseError("Unknown action.")), }) } } impl From for String { fn from(action: Action) -> String { String::from(match action { Action::ContentAccept => "content-accept", Action::ContentAdd => "content-add", Action::ContentModify => "content-modify", Action::ContentReject => "content-reject", Action::ContentRemove => "content-remove", Action::DescriptionInfo => "description-info", Action::SecurityInfo => "security-info", Action::SessionAccept => "session-accept", Action::SessionInfo => "session-info", Action::SessionInitiate => "session-initiate", Action::SessionTerminate => "session-terminate", Action::TransportAccept => "transport-accept", Action::TransportInfo => "transport-info", Action::TransportReject => "transport-reject", Action::TransportReplace => "transport-replace", }) } } // TODO: use a real JID type. type Jid = String; #[derive(Debug, Clone, PartialEq)] pub enum Creator { Initiator, Responder, } impl FromStr for Creator { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "initiator" => Creator::Initiator, "responder" => Creator::Responder, _ => return Err(Error::ParseError("Unknown creator.")), }) } } impl From for String { fn from(creator: Creator) -> String { String::from(match creator { Creator::Initiator => "initiator", Creator::Responder => "responder", }) } } #[derive(Debug, Clone, PartialEq)] pub enum Senders { Both, Initiator, None_, Responder, } impl FromStr for Senders { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "both" => Senders::Both, "initiator" => Senders::Initiator, "none" => Senders::None_, "responder" => Senders::Responder, _ => return Err(Error::ParseError("Unknown senders.")), }) } } impl From for String { fn from(senders: Senders) -> String { String::from(match senders { Senders::Both => "both", Senders::Initiator => "initiator", Senders::None_ => "none", Senders::Responder => "responder", }) } } #[derive(Debug, Clone)] pub struct Content { pub creator: Creator, pub disposition: String, pub name: String, pub senders: Senders, pub description: (String, Element), pub transport: (String, Element), pub security: Option<(String, Element)>, } #[derive(Debug, Clone, PartialEq)] pub enum Reason { AlternativeSession, //(String), Busy, Cancel, ConnectivityError, Decline, Expired, FailedApplication, FailedTransport, GeneralError, Gone, IncompatibleParameters, MediaError, SecurityError, Success, Timeout, UnsupportedApplications, UnsupportedTransports, } impl FromStr for Reason { type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "alternative-session" => Reason::AlternativeSession, "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.")), }) } } impl<'a> Into for &'a Reason { fn into(self) -> Element { Element::builder(match *self { Reason::AlternativeSession => "alternative-session", Reason::Busy => "busy", Reason::Cancel => "cancel", Reason::ConnectivityError => "connectivity-error", Reason::Decline => "decline", Reason::Expired => "expired", Reason::FailedApplication => "failed-application", Reason::FailedTransport => "failed-transport", Reason::GeneralError => "general-error", Reason::Gone => "gone", Reason::IncompatibleParameters => "incompatible-parameters", Reason::MediaError => "media-error", Reason::SecurityError => "security-error", Reason::Success => "success", Reason::Timeout => "timeout", Reason::UnsupportedApplications => "unsupported-applications", Reason::UnsupportedTransports => "unsupported-transports", }).build() } } #[derive(Debug, Clone)] pub struct ReasonElement { pub reason: Reason, pub text: Option, } #[derive(Debug, Clone)] pub struct Jingle { pub action: Action, pub initiator: Option, pub responder: Option, pub sid: String, pub contents: Vec, pub reason: Option, pub other: Vec, } impl<'a> TryFrom<&'a Element> for Jingle { type Error = Error; fn try_from(root: &'a Element) -> Result { if !root.is("jingle", ns::JINGLE) { return Err(Error::ParseError("This is not a Jingle element.")); } let mut contents: Vec = vec!(); let action = root.attr("action") .ok_or(Error::ParseError("Jingle must have an 'action' attribute."))? .parse()?; let initiator = root.attr("initiator") .and_then(|initiator| initiator.parse().ok()); let responder = root.attr("responder") .and_then(|responder| responder.parse().ok()); let sid = root.attr("sid") .ok_or(Error::ParseError("Jingle must have a 'sid' attribute."))?; let mut reason_element = None; let mut other = vec!(); for child in root.children() { if child.is("content", ns::JINGLE) { let creator = child.attr("creator") .ok_or(Error::ParseError("Content must have a 'creator' attribute."))? .parse()?; let disposition = child.attr("disposition") .unwrap_or("session"); let name = child.attr("name") .ok_or(Error::ParseError("Content must have a 'name' attribute."))?; let senders = child.attr("senders") .unwrap_or("both") .parse()?; let mut description = None; let mut transport = None; let mut security = None; for stuff in child.children() { if stuff.name() == "description" { if description.is_some() { return Err(Error::ParseError("Content must not have more than one description.")); } let namespace = stuff.ns() .and_then(|ns| ns.parse().ok()) // TODO: is this even reachable? .ok_or(Error::ParseError("Invalid namespace on description element."))?; description = Some(( namespace, stuff.clone(), )); } else if stuff.name() == "transport" { if transport.is_some() { return Err(Error::ParseError("Content must not have more than one transport.")); } let namespace = stuff.ns() .and_then(|ns| ns.parse().ok()) // TODO: is this even reachable? .ok_or(Error::ParseError("Invalid namespace on transport element."))?; transport = Some(( namespace, stuff.clone(), )); } else if stuff.name() == "security" { if security.is_some() { return Err(Error::ParseError("Content must not have more than one security.")); } let namespace = stuff.ns() .and_then(|ns| ns.parse().ok()) // TODO: is this even reachable? .ok_or(Error::ParseError("Invalid namespace on security element."))?; security = Some(( namespace, stuff.clone(), )); } } if description.is_none() { return Err(Error::ParseError("Content must have one description.")); } if transport.is_none() { return Err(Error::ParseError("Content must have one transport.")); } let description = description.unwrap().to_owned(); let transport = transport.unwrap().to_owned(); contents.push(Content { creator: creator, disposition: disposition.to_owned(), name: name.to_owned(), senders: senders, description: description, transport: transport, security: security, }); } else if child.is("reason", ns::JINGLE) { if reason_element.is_some() { return Err(Error::ParseError("Jingle must not have more than one reason.")); } let mut reason = None; let mut text = None; for stuff in child.children() { if stuff.ns() != Some(ns::JINGLE) { return Err(Error::ParseError("Reason contains a foreign element.")); } let name = stuff.name(); if name == "text" { if text.is_some() { return Err(Error::ParseError("Reason must not have more than one text.")); } text = Some(stuff.text()); } else { reason = Some(name.parse()?); } } if reason.is_none() { return Err(Error::ParseError("Reason doesn’t contain a valid reason.")); } reason_element = Some(ReasonElement { reason: reason.unwrap(), text: text, }); } else { other.push(child.clone()); } } Ok(Jingle { action: action, initiator: initiator, responder: responder, sid: sid.to_owned(), contents: contents, reason: reason_element, other: other, }) } } impl<'a> Into for &'a Content { fn into(self) -> Element { let mut root = Element::builder("content") .ns(ns::JINGLE) .attr("creator", String::from(self.creator.clone())) .attr("disposition", self.disposition.clone()) .attr("name", self.name.clone()) .attr("senders", String::from(self.senders.clone())) .build(); root.append_child(self.description.1.clone()); root.append_child(self.transport.1.clone()); if let Some(security) = self.security.clone() { root.append_child(security.1.clone()); } root } } impl<'a> Into for &'a Jingle { fn into(self) -> Element { let mut root = Element::builder("jingle") .ns(ns::JINGLE) .attr("action", String::from(self.action.clone())) .attr("initiator", self.initiator.clone()) .attr("responder", self.responder.clone()) .attr("sid", self.sid.clone()) .build(); for content in self.contents.clone() { let content_elem = (&content).into(); root.append_child(content_elem); } if let Some(ref reason) = self.reason { let reason2: Element = (&reason.reason).into(); let reason_elem = Element::builder("reason") .append(reason2) .append(reason.text.clone()) .build(); root.append_child(reason_elem); } root } } impl Into for Jingle { fn into(self) -> Element { (&self).into() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_simple() { let elem: Element = "".parse().unwrap(); let jingle = Jingle::try_from(&elem).unwrap(); assert_eq!(jingle.action, Action::SessionInitiate); assert_eq!(jingle.sid, "coucou"); } #[test] fn test_invalid_jingle() { let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Jingle must have an 'action' attribute."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Jingle must have a 'sid' attribute."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Unknown action."); } #[test] fn test_content() { let elem: Element = "".parse().unwrap(); let jingle = Jingle::try_from(&elem).unwrap(); assert_eq!(jingle.contents[0].creator, Creator::Initiator); assert_eq!(jingle.contents[0].name, "coucou"); assert_eq!(jingle.contents[0].senders, Senders::Both); assert_eq!(jingle.contents[0].disposition, "session"); let elem: Element = "".parse().unwrap(); let jingle = Jingle::try_from(&elem).unwrap(); assert_eq!(jingle.contents[0].senders, Senders::Both); let elem: Element = "".parse().unwrap(); let jingle = Jingle::try_from(&elem).unwrap(); assert_eq!(jingle.contents[0].disposition, "early-session"); } #[test] fn test_invalid_content() { let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Content must have a 'creator' attribute."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Content must have a 'name' attribute."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Unknown creator."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Unknown senders."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Unknown senders."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Content must have one description."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Content must have one transport."); } #[test] fn test_reason() { let elem: Element = "".parse().unwrap(); let jingle = Jingle::try_from(&elem).unwrap(); let reason = jingle.reason.unwrap(); assert_eq!(reason.reason, Reason::Success); assert_eq!(reason.text, None); let elem: Element = "coucou".parse().unwrap(); let jingle = Jingle::try_from(&elem).unwrap(); let reason = jingle.reason.unwrap(); assert_eq!(reason.reason, Reason::Success); assert_eq!(reason.text, Some(String::from("coucou"))); } #[test] fn test_invalid_reason() { let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Reason doesn’t contain a valid reason."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Unknown reason."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Reason contains a foreign element."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Jingle must not have more than one reason."); let elem: Element = "".parse().unwrap(); let error = Jingle::try_from(&elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Reason must not have more than one text."); } }