| /* |
| * Copyright 2010 The WebRTC Project Authors. All rights reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "webrtc/libjingle/session/sessionmessages.h" |
| |
| #include <stdio.h> |
| #include <string> |
| |
| #include "webrtc/base/logging.h" |
| #include "webrtc/base/scoped_ptr.h" |
| #include "webrtc/base/stringutils.h" |
| #include "webrtc/libjingle/session/parsing.h" |
| #include "webrtc/libjingle/session/sessionclient.h" |
| #include "webrtc/libjingle/xmllite/xmlconstants.h" |
| #include "webrtc/libjingle/xmpp/constants.h" |
| #include "webrtc/p2p/base/constants.h" |
| #include "webrtc/p2p/base/p2ptransport.h" |
| #include "webrtc/p2p/base/sessiondescription.h" |
| #include "webrtc/p2p/base/transport.h" |
| |
| namespace cricket { |
| |
| ActionType ToActionType(const std::string& type) { |
| if (type == GINGLE_ACTION_INITIATE) |
| return ACTION_SESSION_INITIATE; |
| if (type == GINGLE_ACTION_INFO) |
| return ACTION_SESSION_INFO; |
| if (type == GINGLE_ACTION_ACCEPT) |
| return ACTION_SESSION_ACCEPT; |
| if (type == GINGLE_ACTION_REJECT) |
| return ACTION_SESSION_REJECT; |
| if (type == GINGLE_ACTION_TERMINATE) |
| return ACTION_SESSION_TERMINATE; |
| if (type == GINGLE_ACTION_CANDIDATES) |
| return ACTION_TRANSPORT_INFO; |
| if (type == JINGLE_ACTION_SESSION_INITIATE) |
| return ACTION_SESSION_INITIATE; |
| if (type == JINGLE_ACTION_TRANSPORT_INFO) |
| return ACTION_TRANSPORT_INFO; |
| if (type == JINGLE_ACTION_TRANSPORT_ACCEPT) |
| return ACTION_TRANSPORT_ACCEPT; |
| if (type == JINGLE_ACTION_SESSION_INFO) |
| return ACTION_SESSION_INFO; |
| if (type == JINGLE_ACTION_SESSION_ACCEPT) |
| return ACTION_SESSION_ACCEPT; |
| if (type == JINGLE_ACTION_SESSION_TERMINATE) |
| return ACTION_SESSION_TERMINATE; |
| if (type == JINGLE_ACTION_TRANSPORT_INFO) |
| return ACTION_TRANSPORT_INFO; |
| if (type == JINGLE_ACTION_TRANSPORT_ACCEPT) |
| return ACTION_TRANSPORT_ACCEPT; |
| if (type == JINGLE_ACTION_DESCRIPTION_INFO) |
| return ACTION_DESCRIPTION_INFO; |
| if (type == GINGLE_ACTION_UPDATE) |
| return ACTION_DESCRIPTION_INFO; |
| |
| return ACTION_UNKNOWN; |
| } |
| |
| std::string ToJingleString(ActionType type) { |
| switch (type) { |
| case ACTION_SESSION_INITIATE: |
| return JINGLE_ACTION_SESSION_INITIATE; |
| case ACTION_SESSION_INFO: |
| return JINGLE_ACTION_SESSION_INFO; |
| case ACTION_DESCRIPTION_INFO: |
| return JINGLE_ACTION_DESCRIPTION_INFO; |
| case ACTION_SESSION_ACCEPT: |
| return JINGLE_ACTION_SESSION_ACCEPT; |
| // Notice that reject and terminate both go to |
| // "session-terminate", but there is no "session-reject". |
| case ACTION_SESSION_REJECT: |
| case ACTION_SESSION_TERMINATE: |
| return JINGLE_ACTION_SESSION_TERMINATE; |
| case ACTION_TRANSPORT_INFO: |
| return JINGLE_ACTION_TRANSPORT_INFO; |
| case ACTION_TRANSPORT_ACCEPT: |
| return JINGLE_ACTION_TRANSPORT_ACCEPT; |
| default: |
| return ""; |
| } |
| } |
| |
| std::string ToGingleString(ActionType type) { |
| switch (type) { |
| case ACTION_SESSION_INITIATE: |
| return GINGLE_ACTION_INITIATE; |
| case ACTION_SESSION_INFO: |
| return GINGLE_ACTION_INFO; |
| case ACTION_SESSION_ACCEPT: |
| return GINGLE_ACTION_ACCEPT; |
| case ACTION_SESSION_REJECT: |
| return GINGLE_ACTION_REJECT; |
| case ACTION_SESSION_TERMINATE: |
| return GINGLE_ACTION_TERMINATE; |
| case ACTION_TRANSPORT_INFO: |
| return GINGLE_ACTION_CANDIDATES; |
| default: |
| return ""; |
| } |
| } |
| |
| |
| bool IsJingleMessage(const buzz::XmlElement* stanza) { |
| const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE); |
| if (jingle == NULL) |
| return false; |
| |
| return (jingle->HasAttr(buzz::QN_ACTION) && jingle->HasAttr(QN_SID)); |
| } |
| |
| bool IsGingleMessage(const buzz::XmlElement* stanza) { |
| const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION); |
| if (session == NULL) |
| return false; |
| |
| return (session->HasAttr(buzz::QN_TYPE) && |
| session->HasAttr(buzz::QN_ID) && |
| session->HasAttr(QN_INITIATOR)); |
| } |
| |
| bool IsSessionMessage(const buzz::XmlElement* stanza) { |
| return (stanza->Name() == buzz::QN_IQ && |
| stanza->Attr(buzz::QN_TYPE) == buzz::STR_SET && |
| (IsJingleMessage(stanza) || |
| IsGingleMessage(stanza))); |
| } |
| |
| bool ParseGingleSessionMessage(const buzz::XmlElement* session, |
| SessionMessage* msg, |
| ParseError* error) { |
| msg->protocol = PROTOCOL_GINGLE; |
| std::string type_string = session->Attr(buzz::QN_TYPE); |
| msg->type = ToActionType(type_string); |
| msg->sid = session->Attr(buzz::QN_ID); |
| msg->initiator = session->Attr(QN_INITIATOR); |
| msg->action_elem = session; |
| |
| if (msg->type == ACTION_UNKNOWN) |
| return BadParse("unknown action: " + type_string, error); |
| |
| return true; |
| } |
| |
| bool ParseJingleSessionMessage(const buzz::XmlElement* jingle, |
| SessionMessage* msg, |
| ParseError* error) { |
| msg->protocol = PROTOCOL_JINGLE; |
| std::string type_string = jingle->Attr(buzz::QN_ACTION); |
| msg->type = ToActionType(type_string); |
| msg->sid = jingle->Attr(QN_SID); |
| msg->initiator = GetXmlAttr(jingle, QN_INITIATOR, buzz::STR_EMPTY); |
| msg->action_elem = jingle; |
| |
| if (msg->type == ACTION_UNKNOWN) |
| return BadParse("unknown action: " + type_string, error); |
| |
| return true; |
| } |
| |
| bool ParseHybridSessionMessage(const buzz::XmlElement* jingle, |
| SessionMessage* msg, |
| ParseError* error) { |
| if (!ParseJingleSessionMessage(jingle, msg, error)) |
| return false; |
| msg->protocol = PROTOCOL_HYBRID; |
| |
| return true; |
| } |
| |
| bool ParseSessionMessage(const buzz::XmlElement* stanza, |
| SessionMessage* msg, |
| ParseError* error) { |
| msg->id = stanza->Attr(buzz::QN_ID); |
| msg->from = stanza->Attr(buzz::QN_FROM); |
| msg->to = stanza->Attr(buzz::QN_TO); |
| msg->stanza = stanza; |
| |
| const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE); |
| const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION); |
| if (jingle && session) |
| return ParseHybridSessionMessage(jingle, msg, error); |
| if (jingle != NULL) |
| return ParseJingleSessionMessage(jingle, msg, error); |
| if (session != NULL) |
| return ParseGingleSessionMessage(session, msg, error); |
| return false; |
| } |
| |
| buzz::XmlElement* WriteGingleAction(const SessionMessage& msg, |
| const XmlElements& action_elems) { |
| buzz::XmlElement* session = new buzz::XmlElement(QN_GINGLE_SESSION, true); |
| session->AddAttr(buzz::QN_TYPE, ToGingleString(msg.type)); |
| session->AddAttr(buzz::QN_ID, msg.sid); |
| session->AddAttr(QN_INITIATOR, msg.initiator); |
| AddXmlChildren(session, action_elems); |
| return session; |
| } |
| |
| buzz::XmlElement* WriteJingleAction(const SessionMessage& msg, |
| const XmlElements& action_elems) { |
| buzz::XmlElement* jingle = new buzz::XmlElement(QN_JINGLE, true); |
| jingle->AddAttr(buzz::QN_ACTION, ToJingleString(msg.type)); |
| jingle->AddAttr(QN_SID, msg.sid); |
| if (msg.type == ACTION_SESSION_INITIATE) { |
| jingle->AddAttr(QN_INITIATOR, msg.initiator); |
| } |
| AddXmlChildren(jingle, action_elems); |
| return jingle; |
| } |
| |
| void WriteSessionMessage(const SessionMessage& msg, |
| const XmlElements& action_elems, |
| buzz::XmlElement* stanza) { |
| stanza->SetAttr(buzz::QN_TO, msg.to); |
| stanza->SetAttr(buzz::QN_TYPE, buzz::STR_SET); |
| |
| if (msg.protocol == PROTOCOL_GINGLE) { |
| stanza->AddElement(WriteGingleAction(msg, action_elems)); |
| } else { |
| stanza->AddElement(WriteJingleAction(msg, action_elems)); |
| } |
| } |
| |
| |
| TransportParser* GetTransportParser(const TransportParserMap& trans_parsers, |
| const std::string& transport_type) { |
| TransportParserMap::const_iterator map = trans_parsers.find(transport_type); |
| if (map == trans_parsers.end()) { |
| return NULL; |
| } else { |
| return map->second; |
| } |
| } |
| |
| CandidateTranslator* GetCandidateTranslator( |
| const CandidateTranslatorMap& translators, |
| const std::string& content_name) { |
| CandidateTranslatorMap::const_iterator map = translators.find(content_name); |
| if (map == translators.end()) { |
| return NULL; |
| } else { |
| return map->second; |
| } |
| } |
| |
| bool GetParserAndTranslator(const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| const std::string& transport_type, |
| const std::string& content_name, |
| TransportParser** parser, |
| CandidateTranslator** translator, |
| ParseError* error) { |
| *parser = GetTransportParser(trans_parsers, transport_type); |
| if (*parser == NULL) { |
| return BadParse("unknown transport type: " + transport_type, error); |
| } |
| // Not having a translator isn't fatal when parsing. If this is called for an |
| // initiate message, we won't have our proxies set up to do the translation. |
| // Fortunately, for the cases where translation is needed, candidates are |
| // never sent in initiates. |
| *translator = GetCandidateTranslator(translators, content_name); |
| return true; |
| } |
| |
| bool GetParserAndTranslator(const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| const std::string& transport_type, |
| const std::string& content_name, |
| TransportParser** parser, |
| CandidateTranslator** translator, |
| WriteError* error) { |
| *parser = GetTransportParser(trans_parsers, transport_type); |
| if (*parser == NULL) { |
| return BadWrite("unknown transport type: " + transport_type, error); |
| } |
| *translator = GetCandidateTranslator(translators, content_name); |
| if (*translator == NULL) { |
| return BadWrite("unknown content name: " + content_name, error); |
| } |
| return true; |
| } |
| |
| bool ParseGingleCandidate(const buzz::XmlElement* candidate_elem, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| const std::string& content_name, |
| Candidates* candidates, |
| ParseError* error) { |
| TransportParser* trans_parser; |
| CandidateTranslator* translator; |
| if (!GetParserAndTranslator(trans_parsers, translators, |
| NS_GINGLE_P2P, content_name, |
| &trans_parser, &translator, error)) |
| return false; |
| |
| Candidate candidate; |
| if (!trans_parser->ParseGingleCandidate( |
| candidate_elem, translator, &candidate, error)) { |
| return false; |
| } |
| |
| candidates->push_back(candidate); |
| return true; |
| } |
| |
| bool ParseGingleCandidates(const buzz::XmlElement* parent, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| const std::string& content_name, |
| Candidates* candidates, |
| ParseError* error) { |
| for (const buzz::XmlElement* candidate_elem = parent->FirstElement(); |
| candidate_elem != NULL; |
| candidate_elem = candidate_elem->NextElement()) { |
| if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) { |
| if (!ParseGingleCandidate(candidate_elem, trans_parsers, translators, |
| content_name, candidates, error)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool ParseGingleTransportInfos(const buzz::XmlElement* action_elem, |
| const ContentInfos& contents, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| TransportInfos* tinfos, |
| ParseError* error) { |
| bool has_audio = FindContentInfoByName(contents, CN_AUDIO) != NULL; |
| bool has_video = FindContentInfoByName(contents, CN_VIDEO) != NULL; |
| |
| // If we don't have media, no need to separate the candidates. |
| if (!has_audio && !has_video) { |
| TransportInfo tinfo(CN_OTHER, |
| TransportDescription(NS_GINGLE_P2P, std::string(), std::string())); |
| if (!ParseGingleCandidates(action_elem, trans_parsers, translators, |
| CN_OTHER, &tinfo.description.candidates, |
| error)) { |
| return false; |
| } |
| |
| tinfos->push_back(tinfo); |
| return true; |
| } |
| |
| // If we have media, separate the candidates. |
| TransportInfo audio_tinfo( |
| CN_AUDIO, |
| TransportDescription(NS_GINGLE_P2P, std::string(), std::string())); |
| TransportInfo video_tinfo( |
| CN_VIDEO, |
| TransportDescription(NS_GINGLE_P2P, std::string(), std::string())); |
| for (const buzz::XmlElement* candidate_elem = action_elem->FirstElement(); |
| candidate_elem != NULL; |
| candidate_elem = candidate_elem->NextElement()) { |
| if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) { |
| const std::string& channel_name = candidate_elem->Attr(buzz::QN_NAME); |
| if (has_audio && |
| (channel_name == GICE_CHANNEL_NAME_RTP || |
| channel_name == GICE_CHANNEL_NAME_RTCP)) { |
| if (!ParseGingleCandidate( |
| candidate_elem, trans_parsers, |
| translators, CN_AUDIO, |
| &audio_tinfo.description.candidates, error)) { |
| return false; |
| } |
| } else if (has_video && |
| (channel_name == GICE_CHANNEL_NAME_VIDEO_RTP || |
| channel_name == GICE_CHANNEL_NAME_VIDEO_RTCP)) { |
| if (!ParseGingleCandidate( |
| candidate_elem, trans_parsers, |
| translators, CN_VIDEO, |
| &video_tinfo.description.candidates, error)) { |
| return false; |
| } |
| } else { |
| return BadParse("Unknown channel name: " + channel_name, error); |
| } |
| } |
| } |
| |
| if (has_audio) { |
| tinfos->push_back(audio_tinfo); |
| } |
| if (has_video) { |
| tinfos->push_back(video_tinfo); |
| } |
| return true; |
| } |
| |
| bool ParseJingleTransportInfo(const buzz::XmlElement* trans_elem, |
| const std::string& content_name, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| TransportInfo* tinfo, |
| ParseError* error) { |
| TransportParser* trans_parser; |
| CandidateTranslator* translator; |
| if (!GetParserAndTranslator(trans_parsers, translators, |
| trans_elem->Name().Namespace(), content_name, |
| &trans_parser, &translator, error)) |
| return false; |
| |
| TransportDescription tdesc; |
| if (!trans_parser->ParseTransportDescription(trans_elem, translator, |
| &tdesc, error)) |
| return false; |
| |
| *tinfo = TransportInfo(content_name, tdesc); |
| return true; |
| } |
| |
| bool ParseJingleTransportInfos(const buzz::XmlElement* jingle, |
| const ContentInfos& contents, |
| const TransportParserMap trans_parsers, |
| const CandidateTranslatorMap& translators, |
| TransportInfos* tinfos, |
| ParseError* error) { |
| for (const buzz::XmlElement* pair_elem |
| = jingle->FirstNamed(QN_JINGLE_CONTENT); |
| pair_elem != NULL; |
| pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) { |
| std::string content_name; |
| if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME, |
| &content_name, error)) |
| return false; |
| |
| const ContentInfo* content = FindContentInfoByName(contents, content_name); |
| if (!content) |
| return BadParse("Unknown content name: " + content_name, error); |
| |
| const buzz::XmlElement* trans_elem; |
| if (!RequireXmlChild(pair_elem, LN_TRANSPORT, &trans_elem, error)) |
| return false; |
| |
| TransportInfo tinfo; |
| if (!ParseJingleTransportInfo(trans_elem, content->name, |
| trans_parsers, translators, |
| &tinfo, error)) |
| return false; |
| |
| tinfos->push_back(tinfo); |
| } |
| |
| return true; |
| } |
| |
| buzz::XmlElement* NewTransportElement(const std::string& name) { |
| return new buzz::XmlElement(buzz::QName(name, LN_TRANSPORT), true); |
| } |
| |
| bool WriteGingleCandidates(const Candidates& candidates, |
| const TransportParserMap& trans_parsers, |
| const std::string& transport_type, |
| const CandidateTranslatorMap& translators, |
| const std::string& content_name, |
| XmlElements* elems, |
| WriteError* error) { |
| TransportParser* trans_parser; |
| CandidateTranslator* translator; |
| if (!GetParserAndTranslator(trans_parsers, translators, |
| transport_type, content_name, |
| &trans_parser, &translator, error)) |
| return false; |
| |
| for (size_t i = 0; i < candidates.size(); ++i) { |
| rtc::scoped_ptr<buzz::XmlElement> element; |
| if (!trans_parser->WriteGingleCandidate(candidates[i], translator, |
| element.accept(), error)) { |
| return false; |
| } |
| |
| elems->push_back(element.release()); |
| } |
| |
| return true; |
| } |
| |
| bool WriteGingleTransportInfos(const TransportInfos& tinfos, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| XmlElements* elems, |
| WriteError* error) { |
| for (TransportInfos::const_iterator tinfo = tinfos.begin(); |
| tinfo != tinfos.end(); ++tinfo) { |
| if (!WriteGingleCandidates(tinfo->description.candidates, |
| trans_parsers, tinfo->description.transport_type, |
| translators, tinfo->content_name, |
| elems, error)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool WriteJingleTransportInfo(const TransportInfo& tinfo, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| XmlElements* elems, |
| WriteError* error) { |
| std::string transport_type = tinfo.description.transport_type; |
| TransportParser* trans_parser; |
| CandidateTranslator* translator; |
| if (!GetParserAndTranslator(trans_parsers, translators, |
| transport_type, tinfo.content_name, |
| &trans_parser, &translator, error)) |
| return false; |
| |
| buzz::XmlElement* trans_elem; |
| if (!trans_parser->WriteTransportDescription(tinfo.description, translator, |
| &trans_elem, error)) { |
| return false; |
| } |
| |
| elems->push_back(trans_elem); |
| return true; |
| } |
| |
| void WriteJingleContent(const std::string name, |
| const XmlElements& child_elems, |
| XmlElements* elems) { |
| buzz::XmlElement* content_elem = new buzz::XmlElement(QN_JINGLE_CONTENT); |
| content_elem->SetAttr(QN_JINGLE_CONTENT_NAME, name); |
| content_elem->SetAttr(QN_CREATOR, LN_INITIATOR); |
| AddXmlChildren(content_elem, child_elems); |
| |
| elems->push_back(content_elem); |
| } |
| |
| bool WriteJingleTransportInfos(const TransportInfos& tinfos, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| XmlElements* elems, |
| WriteError* error) { |
| for (TransportInfos::const_iterator tinfo = tinfos.begin(); |
| tinfo != tinfos.end(); ++tinfo) { |
| XmlElements content_child_elems; |
| if (!WriteJingleTransportInfo(*tinfo, trans_parsers, translators, |
| &content_child_elems, error)) |
| |
| return false; |
| |
| WriteJingleContent(tinfo->content_name, content_child_elems, elems); |
| } |
| |
| return true; |
| } |
| |
| ContentParser* GetContentParser(const ContentParserMap& content_parsers, |
| const std::string& type) { |
| ContentParserMap::const_iterator map = content_parsers.find(type); |
| if (map == content_parsers.end()) { |
| return NULL; |
| } else { |
| return map->second; |
| } |
| } |
| |
| bool ParseContentInfo(SignalingProtocol protocol, |
| const std::string& name, |
| const std::string& type, |
| const buzz::XmlElement* elem, |
| const ContentParserMap& parsers, |
| ContentInfos* contents, |
| ParseError* error) { |
| ContentParser* parser = GetContentParser(parsers, type); |
| if (parser == NULL) |
| return BadParse("unknown application content: " + type, error); |
| |
| ContentDescription* desc; |
| if (!parser->ParseContent(protocol, elem, &desc, error)) |
| return false; |
| |
| contents->push_back(ContentInfo(name, type, desc)); |
| return true; |
| } |
| |
| bool ParseContentType(const buzz::XmlElement* parent_elem, |
| std::string* content_type, |
| const buzz::XmlElement** content_elem, |
| ParseError* error) { |
| if (!RequireXmlChild(parent_elem, LN_DESCRIPTION, content_elem, error)) |
| return false; |
| |
| *content_type = (*content_elem)->Name().Namespace(); |
| return true; |
| } |
| |
| bool ParseGingleContentInfos(const buzz::XmlElement* session, |
| const ContentParserMap& content_parsers, |
| ContentInfos* contents, |
| ParseError* error) { |
| std::string content_type; |
| const buzz::XmlElement* content_elem; |
| if (!ParseContentType(session, &content_type, &content_elem, error)) |
| return false; |
| |
| if (content_type == NS_GINGLE_VIDEO) { |
| // A parser parsing audio or video content should look at the |
| // namespace and only parse the codecs relevant to that namespace. |
| // We use this to control which codecs get parsed: first audio, |
| // then video. |
| rtc::scoped_ptr<buzz::XmlElement> audio_elem( |
| new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT)); |
| CopyXmlChildren(content_elem, audio_elem.get()); |
| if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP, |
| audio_elem.get(), content_parsers, |
| contents, error)) |
| return false; |
| |
| if (!ParseContentInfo(PROTOCOL_GINGLE, CN_VIDEO, NS_JINGLE_RTP, |
| content_elem, content_parsers, |
| contents, error)) |
| return false; |
| } else if (content_type == NS_GINGLE_AUDIO) { |
| if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP, |
| content_elem, content_parsers, |
| contents, error)) |
| return false; |
| } else { |
| if (!ParseContentInfo(PROTOCOL_GINGLE, CN_OTHER, content_type, |
| content_elem, content_parsers, |
| contents, error)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool ParseJingleContentInfos(const buzz::XmlElement* jingle, |
| const ContentParserMap& content_parsers, |
| ContentInfos* contents, |
| ParseError* error) { |
| for (const buzz::XmlElement* pair_elem |
| = jingle->FirstNamed(QN_JINGLE_CONTENT); |
| pair_elem != NULL; |
| pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) { |
| std::string content_name; |
| if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME, |
| &content_name, error)) |
| return false; |
| |
| std::string content_type; |
| const buzz::XmlElement* content_elem; |
| if (!ParseContentType(pair_elem, &content_type, &content_elem, error)) |
| return false; |
| |
| if (!ParseContentInfo(PROTOCOL_JINGLE, content_name, content_type, |
| content_elem, content_parsers, |
| contents, error)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool ParseJingleGroupInfos(const buzz::XmlElement* jingle, |
| ContentGroups* groups, |
| ParseError* error) { |
| for (const buzz::XmlElement* pair_elem |
| = jingle->FirstNamed(QN_JINGLE_DRAFT_GROUP); |
| pair_elem != NULL; |
| pair_elem = pair_elem->NextNamed(QN_JINGLE_DRAFT_GROUP)) { |
| std::string group_name; |
| if (!RequireXmlAttr(pair_elem, QN_JINGLE_DRAFT_GROUP_TYPE, |
| &group_name, error)) |
| return false; |
| |
| ContentGroup group(group_name); |
| for (const buzz::XmlElement* child_elem |
| = pair_elem->FirstNamed(QN_JINGLE_CONTENT); |
| child_elem != NULL; |
| child_elem = child_elem->NextNamed(QN_JINGLE_CONTENT)) { |
| std::string content_name; |
| if (!RequireXmlAttr(child_elem, QN_JINGLE_CONTENT_NAME, |
| &content_name, error)) |
| return false; |
| group.AddContentName(content_name); |
| } |
| groups->push_back(group); |
| } |
| return true; |
| } |
| |
| buzz::XmlElement* WriteContentInfo(SignalingProtocol protocol, |
| const ContentInfo& content, |
| const ContentParserMap& parsers, |
| WriteError* error) { |
| ContentParser* parser = GetContentParser(parsers, content.type); |
| if (parser == NULL) { |
| BadWrite("unknown content type: " + content.type, error); |
| return NULL; |
| } |
| |
| buzz::XmlElement* elem = NULL; |
| if (!parser->WriteContent(protocol, content.description, &elem, error)) |
| return NULL; |
| |
| return elem; |
| } |
| |
| bool IsWritable(SignalingProtocol protocol, |
| const ContentInfo& content, |
| const ContentParserMap& parsers) { |
| ContentParser* parser = GetContentParser(parsers, content.type); |
| if (parser == NULL) { |
| return false; |
| } |
| |
| return parser->IsWritable(protocol, content.description); |
| } |
| |
| bool WriteGingleContentInfos(const ContentInfos& contents, |
| const ContentParserMap& parsers, |
| XmlElements* elems, |
| WriteError* error) { |
| if (contents.size() == 1 || |
| (contents.size() == 2 && |
| !IsWritable(PROTOCOL_GINGLE, contents.at(1), parsers))) { |
| if (contents.front().rejected) { |
| return BadWrite("Gingle protocol may not reject individual contents.", |
| error); |
| } |
| buzz::XmlElement* elem = WriteContentInfo( |
| PROTOCOL_GINGLE, contents.front(), parsers, error); |
| if (!elem) |
| return false; |
| |
| elems->push_back(elem); |
| } else if (contents.size() >= 2 && |
| contents.at(0).type == NS_JINGLE_RTP && |
| contents.at(1).type == NS_JINGLE_RTP) { |
| // Special-case audio + video contents so that they are "merged" |
| // into one "video" content. |
| if (contents.at(0).rejected || contents.at(1).rejected) { |
| return BadWrite("Gingle protocol may not reject individual contents.", |
| error); |
| } |
| buzz::XmlElement* audio = WriteContentInfo( |
| PROTOCOL_GINGLE, contents.at(0), parsers, error); |
| if (!audio) |
| return false; |
| |
| buzz::XmlElement* video = WriteContentInfo( |
| PROTOCOL_GINGLE, contents.at(1), parsers, error); |
| if (!video) { |
| delete audio; |
| return false; |
| } |
| |
| CopyXmlChildren(audio, video); |
| elems->push_back(video); |
| delete audio; |
| } else { |
| return BadWrite("Gingle protocol may only have one content.", error); |
| } |
| |
| return true; |
| } |
| |
| const TransportInfo* GetTransportInfoByContentName( |
| const TransportInfos& tinfos, const std::string& content_name) { |
| for (TransportInfos::const_iterator tinfo = tinfos.begin(); |
| tinfo != tinfos.end(); ++tinfo) { |
| if (content_name == tinfo->content_name) { |
| return &*tinfo; |
| } |
| } |
| return NULL; |
| } |
| |
| bool WriteJingleContents(const ContentInfos& contents, |
| const ContentParserMap& content_parsers, |
| const TransportInfos& tinfos, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| XmlElements* elems, |
| WriteError* error) { |
| for (ContentInfos::const_iterator content = contents.begin(); |
| content != contents.end(); ++content) { |
| if (content->rejected) { |
| continue; |
| } |
| const TransportInfo* tinfo = |
| GetTransportInfoByContentName(tinfos, content->name); |
| if (!tinfo) |
| return BadWrite("No transport for content: " + content->name, error); |
| |
| XmlElements pair_elems; |
| buzz::XmlElement* elem = WriteContentInfo( |
| PROTOCOL_JINGLE, *content, content_parsers, error); |
| if (!elem) |
| return false; |
| pair_elems.push_back(elem); |
| |
| if (!WriteJingleTransportInfo(*tinfo, trans_parsers, translators, |
| &pair_elems, error)) |
| return false; |
| |
| WriteJingleContent(content->name, pair_elems, elems); |
| } |
| return true; |
| } |
| |
| bool WriteJingleContentInfos(const ContentInfos& contents, |
| const ContentParserMap& content_parsers, |
| XmlElements* elems, |
| WriteError* error) { |
| for (ContentInfos::const_iterator content = contents.begin(); |
| content != contents.end(); ++content) { |
| if (content->rejected) { |
| continue; |
| } |
| XmlElements content_child_elems; |
| buzz::XmlElement* elem = WriteContentInfo( |
| PROTOCOL_JINGLE, *content, content_parsers, error); |
| if (!elem) |
| return false; |
| content_child_elems.push_back(elem); |
| WriteJingleContent(content->name, content_child_elems, elems); |
| } |
| return true; |
| } |
| |
| bool WriteJingleGroupInfo(const ContentInfos& contents, |
| const ContentGroups& groups, |
| XmlElements* elems, |
| WriteError* error) { |
| if (!groups.empty()) { |
| buzz::XmlElement* pair_elem = new buzz::XmlElement(QN_JINGLE_DRAFT_GROUP); |
| pair_elem->SetAttr(QN_JINGLE_DRAFT_GROUP_TYPE, GROUP_TYPE_BUNDLE); |
| |
| XmlElements pair_elems; |
| for (ContentInfos::const_iterator content = contents.begin(); |
| content != contents.end(); ++content) { |
| buzz::XmlElement* child_elem = |
| new buzz::XmlElement(QN_JINGLE_CONTENT, false); |
| child_elem->SetAttr(QN_JINGLE_CONTENT_NAME, content->name); |
| pair_elems.push_back(child_elem); |
| } |
| AddXmlChildren(pair_elem, pair_elems); |
| elems->push_back(pair_elem); |
| } |
| return true; |
| } |
| |
| bool ParseContentType(SignalingProtocol protocol, |
| const buzz::XmlElement* action_elem, |
| std::string* content_type, |
| ParseError* error) { |
| const buzz::XmlElement* content_elem; |
| if (protocol == PROTOCOL_GINGLE) { |
| if (!ParseContentType(action_elem, content_type, &content_elem, error)) |
| return false; |
| |
| // Internally, we only use NS_JINGLE_RTP. |
| if (*content_type == NS_GINGLE_AUDIO || |
| *content_type == NS_GINGLE_VIDEO) |
| *content_type = NS_JINGLE_RTP; |
| } else { |
| const buzz::XmlElement* pair_elem |
| = action_elem->FirstNamed(QN_JINGLE_CONTENT); |
| if (pair_elem == NULL) |
| return BadParse("No contents found", error); |
| |
| if (!ParseContentType(pair_elem, content_type, &content_elem, error)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool ParseContentMessage( |
| SignalingProtocol protocol, |
| const buzz::XmlElement* action_elem, |
| bool expect_transports, |
| const ContentParserMap& content_parsers, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| SessionInitiate* init, |
| ParseError* error) { |
| init->owns_contents = true; |
| if (protocol == PROTOCOL_GINGLE) { |
| if (!ParseGingleContentInfos(action_elem, content_parsers, |
| &init->contents, error)) |
| return false; |
| |
| if (expect_transports && |
| !ParseGingleTransportInfos(action_elem, init->contents, |
| trans_parsers, translators, |
| &init->transports, error)) |
| return false; |
| } else { |
| if (!ParseJingleContentInfos(action_elem, content_parsers, |
| &init->contents, error)) |
| return false; |
| if (!ParseJingleGroupInfos(action_elem, &init->groups, error)) |
| return false; |
| |
| if (expect_transports && |
| !ParseJingleTransportInfos(action_elem, init->contents, |
| trans_parsers, translators, |
| &init->transports, error)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool WriteContentMessage( |
| SignalingProtocol protocol, |
| const ContentInfos& contents, |
| const TransportInfos& tinfos, |
| const ContentParserMap& content_parsers, |
| const TransportParserMap& transport_parsers, |
| const CandidateTranslatorMap& translators, |
| const ContentGroups& groups, |
| XmlElements* elems, |
| WriteError* error) { |
| if (protocol == PROTOCOL_GINGLE) { |
| if (!WriteGingleContentInfos(contents, content_parsers, elems, error)) |
| return false; |
| |
| if (!WriteGingleTransportInfos(tinfos, transport_parsers, translators, |
| elems, error)) |
| return false; |
| } else { |
| if (!WriteJingleContents(contents, content_parsers, |
| tinfos, transport_parsers, translators, |
| elems, error)) |
| return false; |
| if (!WriteJingleGroupInfo(contents, groups, elems, error)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ParseSessionInitiate(SignalingProtocol protocol, |
| const buzz::XmlElement* action_elem, |
| const ContentParserMap& content_parsers, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| SessionInitiate* init, |
| ParseError* error) { |
| bool expect_transports = true; |
| return ParseContentMessage(protocol, action_elem, expect_transports, |
| content_parsers, trans_parsers, translators, |
| init, error); |
| } |
| |
| |
| bool WriteSessionInitiate(SignalingProtocol protocol, |
| const ContentInfos& contents, |
| const TransportInfos& tinfos, |
| const ContentParserMap& content_parsers, |
| const TransportParserMap& transport_parsers, |
| const CandidateTranslatorMap& translators, |
| const ContentGroups& groups, |
| XmlElements* elems, |
| WriteError* error) { |
| return WriteContentMessage(protocol, contents, tinfos, |
| content_parsers, transport_parsers, translators, |
| groups, |
| elems, error); |
| } |
| |
| bool ParseSessionAccept(SignalingProtocol protocol, |
| const buzz::XmlElement* action_elem, |
| const ContentParserMap& content_parsers, |
| const TransportParserMap& transport_parsers, |
| const CandidateTranslatorMap& translators, |
| SessionAccept* accept, |
| ParseError* error) { |
| bool expect_transports = true; |
| return ParseContentMessage(protocol, action_elem, expect_transports, |
| content_parsers, transport_parsers, translators, |
| accept, error); |
| } |
| |
| bool WriteSessionAccept(SignalingProtocol protocol, |
| const ContentInfos& contents, |
| const TransportInfos& tinfos, |
| const ContentParserMap& content_parsers, |
| const TransportParserMap& transport_parsers, |
| const CandidateTranslatorMap& translators, |
| const ContentGroups& groups, |
| XmlElements* elems, |
| WriteError* error) { |
| return WriteContentMessage(protocol, contents, tinfos, |
| content_parsers, transport_parsers, translators, |
| groups, |
| elems, error); |
| } |
| |
| bool ParseSessionTerminate(SignalingProtocol protocol, |
| const buzz::XmlElement* action_elem, |
| SessionTerminate* term, |
| ParseError* error) { |
| if (protocol == PROTOCOL_GINGLE) { |
| const buzz::XmlElement* reason_elem = action_elem->FirstElement(); |
| if (reason_elem != NULL) { |
| term->reason = reason_elem->Name().LocalPart(); |
| const buzz::XmlElement *debug_elem = reason_elem->FirstElement(); |
| if (debug_elem != NULL) { |
| term->debug_reason = debug_elem->Name().LocalPart(); |
| } |
| } |
| return true; |
| } else { |
| const buzz::XmlElement* reason_elem = |
| action_elem->FirstNamed(QN_JINGLE_REASON); |
| if (reason_elem) { |
| reason_elem = reason_elem->FirstElement(); |
| if (reason_elem) { |
| term->reason = reason_elem->Name().LocalPart(); |
| } |
| } |
| return true; |
| } |
| } |
| |
| void WriteSessionTerminate(SignalingProtocol protocol, |
| const SessionTerminate& term, |
| XmlElements* elems) { |
| if (protocol == PROTOCOL_GINGLE) { |
| elems->push_back(new buzz::XmlElement(buzz::QName(NS_GINGLE, term.reason))); |
| } else { |
| if (!term.reason.empty()) { |
| buzz::XmlElement* reason_elem = new buzz::XmlElement(QN_JINGLE_REASON); |
| reason_elem->AddElement(new buzz::XmlElement( |
| buzz::QName(NS_JINGLE, term.reason))); |
| elems->push_back(reason_elem); |
| } |
| } |
| } |
| |
| bool ParseDescriptionInfo(SignalingProtocol protocol, |
| const buzz::XmlElement* action_elem, |
| const ContentParserMap& content_parsers, |
| const TransportParserMap& transport_parsers, |
| const CandidateTranslatorMap& translators, |
| DescriptionInfo* description_info, |
| ParseError* error) { |
| bool expect_transports = false; |
| return ParseContentMessage(protocol, action_elem, expect_transports, |
| content_parsers, transport_parsers, translators, |
| description_info, error); |
| } |
| |
| bool WriteDescriptionInfo(SignalingProtocol protocol, |
| const ContentInfos& contents, |
| const ContentParserMap& content_parsers, |
| XmlElements* elems, |
| WriteError* error) { |
| if (protocol == PROTOCOL_GINGLE) { |
| return WriteGingleContentInfos(contents, content_parsers, elems, error); |
| } else { |
| return WriteJingleContentInfos(contents, content_parsers, elems, error); |
| } |
| } |
| |
| bool ParseTransportInfos(SignalingProtocol protocol, |
| const buzz::XmlElement* action_elem, |
| const ContentInfos& contents, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| TransportInfos* tinfos, |
| ParseError* error) { |
| if (protocol == PROTOCOL_GINGLE) { |
| return ParseGingleTransportInfos( |
| action_elem, contents, trans_parsers, translators, tinfos, error); |
| } else { |
| return ParseJingleTransportInfos( |
| action_elem, contents, trans_parsers, translators, tinfos, error); |
| } |
| } |
| |
| bool WriteTransportInfos(SignalingProtocol protocol, |
| const TransportInfos& tinfos, |
| const TransportParserMap& trans_parsers, |
| const CandidateTranslatorMap& translators, |
| XmlElements* elems, |
| WriteError* error) { |
| if (protocol == PROTOCOL_GINGLE) { |
| return WriteGingleTransportInfos(tinfos, trans_parsers, translators, |
| elems, error); |
| } else { |
| return WriteJingleTransportInfos(tinfos, trans_parsers, translators, |
| elems, error); |
| } |
| } |
| |
| bool GetUriTarget(const std::string& prefix, const std::string& str, |
| std::string* after) { |
| size_t pos = str.find(prefix); |
| if (pos == std::string::npos) |
| return false; |
| |
| *after = str.substr(pos + prefix.size(), std::string::npos); |
| return true; |
| } |
| |
| bool FindSessionRedirect(const buzz::XmlElement* stanza, |
| SessionRedirect* redirect) { |
| const buzz::XmlElement* error_elem = GetXmlChild(stanza, LN_ERROR); |
| if (error_elem == NULL) |
| return false; |
| |
| const buzz::XmlElement* redirect_elem = |
| error_elem->FirstNamed(QN_GINGLE_REDIRECT); |
| if (redirect_elem == NULL) |
| redirect_elem = error_elem->FirstNamed(buzz::QN_STANZA_REDIRECT); |
| if (redirect_elem == NULL) |
| return false; |
| |
| if (!GetUriTarget(STR_REDIRECT_PREFIX, redirect_elem->BodyText(), |
| &redirect->target)) |
| return false; |
| |
| return true; |
| } |
| |
| } // namespace cricket |