diff options
author | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-19 23:31:43 +0000 |
---|---|---|
committer | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-19 23:31:43 +0000 |
commit | da930e1b7adfb28f6895d95004c25984af627080 (patch) | |
tree | 5f0de3a58c50101498c70c99ec9622cf3ffab960 /remoting | |
parent | dd5ad5f4fd6f7693d04f11331ad3c7d6a2a6c1b3 (diff) | |
download | chromium_src-da930e1b7adfb28f6895d95004c25984af627080.zip chromium_src-da930e1b7adfb28f6895d95004c25984af627080.tar.gz chromium_src-da930e1b7adfb28f6895d95004c25984af627080.tar.bz2 |
Parsers and formatters for jingle messages.
BUG=51198
TEST=Unittests
Review URL: http://codereview.chromium.org/7677042
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@97535 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/protocol/content_description.cc | 6 | ||||
-rw-r--r-- | remoting/protocol/content_description.h | 4 | ||||
-rw-r--r-- | remoting/protocol/jingle_messages.cc | 385 | ||||
-rw-r--r-- | remoting/protocol/jingle_messages.h | 94 | ||||
-rw-r--r-- | remoting/protocol/jingle_messages_unittest.cc | 289 | ||||
-rw-r--r-- | remoting/protocol/jingle_session.cc | 4 | ||||
-rw-r--r-- | remoting/protocol/jingle_session.h | 2 | ||||
-rw-r--r-- | remoting/protocol/jingle_session_manager.cc | 4 | ||||
-rw-r--r-- | remoting/protocol/jingle_stream_connector.cc | 6 | ||||
-rw-r--r-- | remoting/remoting.gyp | 3 |
10 files changed, 783 insertions, 14 deletions
diff --git a/remoting/protocol/content_description.cc b/remoting/protocol/content_description.cc index d9b9477..fcc1fd4 100644 --- a/remoting/protocol/content_description.cc +++ b/remoting/protocol/content_description.cc @@ -17,12 +17,12 @@ using buzz::XmlElement; namespace remoting { namespace protocol { +const char ContentDescription::kChromotingContentName[] = "chromoting"; + namespace { const char kDefaultNs[] = ""; -const char kChromotingContentName[] = "chromoting"; - // Following constants are used to format session description in XML. const char kDescriptionTag[] = "description"; const char kControlTag[] = "control"; @@ -235,7 +235,7 @@ XmlElement* ContentDescription::ToXml() const { } // static -cricket::ContentDescription* ContentDescription::ParseXml( +ContentDescription* ContentDescription::ParseXml( const XmlElement* element) { if (element->Name() == QName(kChromotingXmlNamespace, kDescriptionTag)) { scoped_ptr<CandidateSessionConfig> config( diff --git a/remoting/protocol/content_description.h b/remoting/protocol/content_description.h index b102480..5e4053f 100644 --- a/remoting/protocol/content_description.h +++ b/remoting/protocol/content_description.h @@ -26,6 +26,8 @@ namespace protocol { // interface does not need to depend on libjingle. class ContentDescription : public cricket::ContentDescription { public: + static const char kChromotingContentName[]; + ContentDescription(const CandidateSessionConfig* config, const std::string& auth_token, const std::string& certificate); @@ -40,7 +42,7 @@ class ContentDescription : public cricket::ContentDescription { buzz::XmlElement* ToXml() const; - static cricket::ContentDescription* ParseXml(const buzz::XmlElement* element); + static ContentDescription* ParseXml(const buzz::XmlElement* element); private: scoped_ptr<const CandidateSessionConfig> candidate_config_; diff --git a/remoting/protocol/jingle_messages.cc b/remoting/protocol/jingle_messages.cc new file mode 100644 index 0000000..44cc551 --- /dev/null +++ b/remoting/protocol/jingle_messages.cc @@ -0,0 +1,385 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/protocol/jingle_messages.h" + +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "remoting/base/constants.h" +#include "remoting/protocol/content_description.h" +#include "third_party/libjingle/source/talk/p2p/base/candidate.h" +#include "third_party/libjingle/source/talk/xmllite/xmlelement.h" + +using buzz::QName; +using buzz::XmlElement; + +namespace remoting { +namespace protocol { + +const char kJabberNamespace[] = "jabber:client"; +const char kJingleNamespace[] = "urn:xmpp:jingle:1"; +const char kP2PTransportNamespace[] = "http://www.google.com/transport/p2p"; + +namespace { + +const char kEmptyNamespace[] = ""; +const char kXmlNamespace[] = "http://www.w3.org/XML/1998/namespace"; + +const char kSessionInitiateAction[] = "session-initiate"; +const char kSessionAcceptAction[] = "session-accept"; +const char kSessionTerminateAction[] = "session-terminate"; +const char kSessionRejectAction[] = "session-return"; +const char kTransportInfoAction[] = "transport-info"; + +const int kPortMin = 1000; +const int kPortMax = 65535; + +bool ParseCandidate(const buzz::XmlElement* element, + cricket::Candidate* candidate) { + DCHECK(element->Name() == QName(kP2PTransportNamespace, "candidate")); + + const std::string& name = element->Attr(QName(kEmptyNamespace, "name")); + const std::string& address = element->Attr(QName(kEmptyNamespace, "address")); + const std::string& port_str = element->Attr(QName(kEmptyNamespace, "port")); + const std::string& type = element->Attr(QName(kEmptyNamespace, "type")); + const std::string& protocol = + element->Attr(QName(kEmptyNamespace, "protocol")); + const std::string& username = + element->Attr(QName(kEmptyNamespace, "username")); + const std::string& password = + element->Attr(QName(kEmptyNamespace, "password")); + const std::string& preference_str = + element->Attr(QName(kEmptyNamespace, "preference")); + const std::string& generation_str = + element->Attr(QName(kEmptyNamespace, "generation")); + + int port; + double preference; + int generation; + if (name.empty() || address.empty() || !base::StringToInt(port_str, &port) || + port < kPortMin || port > kPortMax || type.empty() || protocol.empty() || + username.empty() || password.empty() || + !base::StringToDouble(preference_str, &preference) || + !base::StringToInt(generation_str, &generation)) { + return false; + } + + candidate->set_name(name); + candidate->set_address(talk_base::SocketAddress(address, port)); + candidate->set_type(type); + candidate->set_protocol(protocol); + candidate->set_username(username); + candidate->set_password(password); + candidate->set_preference(static_cast<float>(preference)); + candidate->set_generation(generation); + + return true; +} + +XmlElement* FormatCandidate(const cricket::Candidate& candidate) { + XmlElement* result = + new XmlElement(QName(kP2PTransportNamespace, "candidate")); + result->SetAttr(QName(kEmptyNamespace, "name"), candidate.name()); + result->SetAttr(QName(kEmptyNamespace, "address"), + candidate.address().IPAsString()); + result->SetAttr(QName(kEmptyNamespace, "port"), + base::IntToString(candidate.address().port())); + result->SetAttr(QName(kEmptyNamespace, "type"), candidate.type()); + result->SetAttr(QName(kEmptyNamespace, "protocol"), candidate.protocol()); + result->SetAttr(QName(kEmptyNamespace, "username"), candidate.username()); + result->SetAttr(QName(kEmptyNamespace, "password"), candidate.password()); + result->SetAttr(QName(kEmptyNamespace, "preference"), + base::DoubleToString(candidate.preference())); + result->SetAttr(QName(kEmptyNamespace, "generation"), + base::IntToString(candidate.generation())); + return result; +} + +} // namespace + +// static +bool JingleMessage::IsJingleMessage(const buzz::XmlElement* stanza) { + return stanza->FirstNamed(QName(kJingleNamespace, "jingle")) != NULL; +} + +JingleMessage::JingleMessage() + : action(UNKNOWN_ACTION), + termination_reason(kJingleNamespace, "success") { +} + +JingleMessage::JingleMessage( + const std::string& to_value, + ActionType action_value, + const std::string& sid_value) + : to(to_value), + action(action_value), + sid(sid_value) { +} + +JingleMessage::~JingleMessage() { +} + +bool JingleMessage::ParseXml(const buzz::XmlElement* stanza, + std::string* error) { + const XmlElement* jingle_tag = + stanza->FirstNamed(QName(kJingleNamespace, "jingle")); + if (jingle_tag == NULL) { + *error = "Not a jingle message"; + return false; + } + + from = stanza->Attr(QName(kEmptyNamespace, "from")); + to = stanza->Attr(QName(kEmptyNamespace, "to")); + + std::string action_str = jingle_tag->Attr(QName(kEmptyNamespace, "action")); + if (action_str.empty()) { + *error = "action attribute is missing"; + return false; + } else if (action_str == kSessionInitiateAction) { + action = SESSION_INITIATE; + } else if (action_str == kSessionAcceptAction) { + action = SESSION_ACCEPT; + } else if (action_str == kSessionTerminateAction) { + action = SESSION_TERMINATE; + } else if (action_str == kSessionRejectAction) { + action = SESSION_REJECT; + } else if (action_str == kTransportInfoAction) { + action = TRANSPORT_INFO; + } else { + *error = "Unknown action " + action_str; + return false; + } + + sid = jingle_tag->Attr(QName(kEmptyNamespace, "sid")); + if (sid.empty()) { + *error = "sid attribute is missing"; + return false; + } + + if (action == SESSION_TERMINATE || action == SESSION_REJECT) { + const XmlElement* reason_tag = + jingle_tag->FirstNamed(QName(kJingleNamespace, "reason")); + if (reason_tag && reason_tag->FirstElement()) + termination_reason = reason_tag->FirstElement()->Name(); + return true; + } + + const XmlElement* content_tag = + jingle_tag->FirstNamed(QName(kJingleNamespace, "content")); + if (!content_tag) { + *error = "content tag is missing"; + return false; + } + + std::string content_name = content_tag->Attr(QName(kEmptyNamespace, "name")); + if (content_name != ContentDescription::kChromotingContentName) { + *error = "Unexpected content name: " + content_name; + return false; + } + + description.reset(NULL); + if (action == SESSION_INITIATE || action == SESSION_ACCEPT) { + const XmlElement* description_tag = content_tag->FirstNamed( + QName(kChromotingXmlNamespace, "description")); + if (!description_tag) { + *error = "Missing chromoting content description"; + return false; + } + + description.reset(ContentDescription::ParseXml(description_tag)); + if (!description.get()) { + *error = "Failed to parse content description"; + return false; + } + } + + candidates.clear(); + const XmlElement* transport_tag = content_tag->FirstNamed( + QName(kP2PTransportNamespace, "transport")); + if (transport_tag) { + QName qn_candidate(kP2PTransportNamespace, "candidate"); + for (const XmlElement* candidate_tag = + transport_tag->FirstNamed(qn_candidate); + candidate_tag != NULL; + candidate_tag = candidate_tag->NextNamed(qn_candidate)) { + cricket::Candidate candidate; + if (!ParseCandidate(candidate_tag, &candidate)) { + *error = "Failed to parse candidates"; + return false; + } + candidates.push_back(candidate); + } + } + + return true; +} + +buzz::XmlElement* JingleMessage::ToXml() { + scoped_ptr<XmlElement> root( + new XmlElement(QName("jabber:client", "iq"), true)); + + DCHECK(!to.empty()); + root->AddAttr(QName(kEmptyNamespace, "to"), to); + if (!from.empty()) + root->AddAttr(QName(kEmptyNamespace, "from"), from); + root->SetAttr(QName(kEmptyNamespace, "type"), "set"); + + XmlElement* jingle_tag = + new XmlElement(QName(kJingleNamespace, "jingle"), true); + root->AddElement(jingle_tag); + jingle_tag->AddAttr(QName(kEmptyNamespace, "sid"), sid); + + std::string action_attr; + switch (action) { + case SESSION_INITIATE: + action_attr = kSessionInitiateAction; + break; + case SESSION_ACCEPT: + action_attr = kSessionAcceptAction; + break; + case SESSION_TERMINATE: + action_attr = kSessionTerminateAction; + break; + case SESSION_REJECT: + action_attr = kSessionRejectAction; + break; + case TRANSPORT_INFO: + action_attr = kTransportInfoAction; + break; + default: + NOTREACHED(); + break; + } + jingle_tag->AddAttr(QName(kEmptyNamespace, "action"), action_attr); + + if (action == SESSION_INITIATE) + jingle_tag->AddAttr(QName(kEmptyNamespace, "initiator"), from); + + if (action == SESSION_TERMINATE || action == SESSION_REJECT) { + XmlElement* reason_tag = new XmlElement(QName(kJingleNamespace, "reason")); + jingle_tag->AddElement(reason_tag); + + reason_tag->AddElement(new XmlElement(termination_reason)); + } else { + XmlElement* content_tag = + new XmlElement(QName(kJingleNamespace, "content")); + jingle_tag->AddElement(content_tag); + + content_tag->AddAttr(QName(kEmptyNamespace, "name"), + ContentDescription::kChromotingContentName); + content_tag->AddAttr(QName(kEmptyNamespace, "creator"), "initiator"); + + if (description.get()) + content_tag->AddElement(description->ToXml()); + + XmlElement* transport_tag = + new XmlElement(QName(kP2PTransportNamespace, "transport"), true); + content_tag->AddElement(transport_tag); + for (std::list<cricket::Candidate>::const_iterator it = candidates.begin(); + it != candidates.end(); ++it) { + transport_tag->AddElement(FormatCandidate(*it)); + } + } + + return root.release(); +} + +JingleMessageReply::JingleMessageReply() + : type(REPLY_RESULT), + error_type(NONE) { +} + +JingleMessageReply::JingleMessageReply(ErrorType error) + : type(REPLY_ERROR), + error_type(error) { +} + +JingleMessageReply::JingleMessageReply(ErrorType error, + const std::string& text_value) + : type(REPLY_ERROR), + error_type(error), + text(text_value) { +} + +JingleMessageReply::~JingleMessageReply() { } + +buzz::XmlElement* JingleMessageReply::ToXml( + const buzz::XmlElement* request_stanza) const { + XmlElement* iq = new XmlElement(QName(kJabberNamespace, "iq"), true); + iq->SetAttr(QName(kEmptyNamespace, "to"), + request_stanza->Attr(QName(kEmptyNamespace, "from"))); + iq->SetAttr(QName(kEmptyNamespace, "id"), + request_stanza->Attr(QName(kEmptyNamespace, "id"))); + + if (type == REPLY_RESULT) { + iq->SetAttr(QName(kEmptyNamespace, "type"), "result"); + return iq; + } + + DCHECK_EQ(type, REPLY_ERROR); + + iq->SetAttr(QName(kEmptyNamespace, "type"), "error"); + + for (const buzz::XmlElement* child = request_stanza->FirstElement(); + child != NULL; child = child->NextElement()) { + iq->AddElement(new buzz::XmlElement(*child)); + } + + buzz::XmlElement* error = + new buzz::XmlElement(QName(kJabberNamespace, "error")); + iq->AddElement(error); + + std::string type; + std::string error_text; + QName name; + switch (error_type) { + case BAD_REQUEST: + type = "modify"; + name = QName(kJabberNamespace, "bad-request"); + break; + case NOT_IMPLEMENTED: + type = "cancel"; + name = QName(kJabberNamespace, "feature-bad-request"); + break; + case INVALID_SID: + type = "modify"; + name = QName(kJabberNamespace, "item-not-found"); + error_text = "Invalid SID"; + break; + case UNEXPECTED_REQUEST: + type = "modify"; + name = QName(kJabberNamespace, "unexpected-request"); + break; + default: + NOTREACHED(); + } + + if (!text.empty()) + error_text = text; + + error->SetAttr(QName(kEmptyNamespace, "type"), type); + + // If the error name is not in the standard namespace, we have + // to first add some error from that namespace. + if (name.Namespace() != kJabberNamespace) { + error->AddElement( + new buzz::XmlElement(QName(kJabberNamespace, "undefined-condition"))); + } + error->AddElement(new buzz::XmlElement(name)); + + if (!error_text.empty()) { + // It's okay to always use English here. This text is for + // debugging purposes only. + buzz::XmlElement* text_elem = + new buzz::XmlElement(QName(kJabberNamespace, "text")); + text_elem->SetAttr(QName(kXmlNamespace, "lang"), "en"); + text_elem->SetBodyText(error_text); + error->AddElement(text_elem); + } + + return iq; +} + +} // namespace protocol +} // namespace remoting diff --git a/remoting/protocol/jingle_messages.h b/remoting/protocol/jingle_messages.h new file mode 100644 index 0000000..0d20cd3 --- /dev/null +++ b/remoting/protocol/jingle_messages.h @@ -0,0 +1,94 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_PROTOCOL_JINGLE_MESSAGES_H_ +#define REMOTING_PROTOCOL_JINGLE_MESSAGES_H_ + +#include <list> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "third_party/libjingle/source/talk/xmllite/xmlelement.h" + +namespace cricket { +class Candidate; +} // namespace cricket + +namespace remoting { +namespace protocol { + +class ContentDescription; + +extern const char kJabberNamespace[]; +extern const char kJingleNamespace[]; +extern const char kP2PTransportNamespace[]; + +struct JingleMessage { + enum ActionType { + UNKNOWN_ACTION, + SESSION_INITIATE, + SESSION_ACCEPT, + SESSION_REJECT, + SESSION_TERMINATE, + TRANSPORT_INFO, + }; + + JingleMessage(); + JingleMessage(const std::string& to_value, + ActionType action_value, + const std::string& sid_value); + ~JingleMessage(); + + // Caller keeps ownership of |stanza|. + static bool IsJingleMessage(const buzz::XmlElement* stanza); + + // Caller keeps ownership of |stanza|. |error| is set to debug error + // message when parsing fails. + bool ParseXml(const buzz::XmlElement* stanza, std::string* error); + + buzz::XmlElement* ToXml(); + + std::string from; + std::string to; + ActionType action; + std::string sid; + + scoped_ptr<ContentDescription> description; + std::list<cricket::Candidate> candidates; + + buzz::QName termination_reason; +}; + +struct JingleMessageReply { + enum ReplyType { + REPLY_RESULT, + REPLY_ERROR, + }; + enum ErrorType { + NONE, + BAD_REQUEST, + NOT_IMPLEMENTED, + INVALID_SID, + UNEXPECTED_REQUEST, + }; + + JingleMessageReply(); + JingleMessageReply(ErrorType error); + JingleMessageReply(ErrorType error, const std::string& text); + ~JingleMessageReply(); + + // Formats reply stanza for the specified |request_stanza|. Id and + // recepient as well as other information needed to generate a valid + // reply are taken from |request_stanza|. + buzz::XmlElement* ToXml(const buzz::XmlElement* request_stanza) const; + + ReplyType type; + ErrorType error_type; + std::string text; +}; + +} // protocol +} // remoting + +#endif // REMOTING_PROTOCOL_JINGLE_MESSAGES_H_ diff --git a/remoting/protocol/jingle_messages_unittest.cc b/remoting/protocol/jingle_messages_unittest.cc new file mode 100644 index 0000000..98810b9 --- /dev/null +++ b/remoting/protocol/jingle_messages_unittest.cc @@ -0,0 +1,289 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/protocol/jingle_messages.h" + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libjingle/source/talk/xmllite/xmlelement.h" +#include "third_party/libjingle/source/talk/xmpp/constants.h" + +using buzz::QName; +using buzz::XmlAttr; +using buzz::XmlElement; + +namespace remoting { +namespace protocol { + +namespace { + +const char kXmlNsNs[] = "http://www.w3.org/2000/xmlns/"; +const char kXmlNs[] = "xmlns"; + +// Compares two XML blobs and returns true if they are +// equivalent. Otherwise |error| is set to error message that +// specifies the first test. +bool VerifyXml(const XmlElement* exp, + const XmlElement* val, + std::string* error) { + if (exp->Name() != val->Name()) { + *error = "<" + exp->Name().Merged() + ">" + " is expected, but " + + "<" + val->Name().Merged() + ">" + " found"; + return false; + } + if (exp->BodyText() != val->BodyText()) { + *error = "<" + exp->Name().LocalPart() + ">" + exp->BodyText() + + "</" + exp->Name().LocalPart() + ">" " is expected, but found " + + "<" + exp->Name().LocalPart() + ">" + val->BodyText() + + "</" + exp->Name().LocalPart() + ">"; + return false; + } + + for (const XmlAttr* exp_attr = exp->FirstAttr(); exp_attr != NULL; + exp_attr = exp_attr->NextAttr()) { + if (exp_attr->Name().Namespace() == kXmlNsNs || + exp_attr->Name() == QName(kXmlNs)) { + continue; // Skip NS attributes. + } + if (val->Attr(exp_attr->Name()) != exp_attr->Value()) { + *error = "In <" + exp->Name().LocalPart() + "> attribute " + + exp_attr->Name().LocalPart() + " is expected to be set to " + + exp_attr->Value(); + return false; + } + } + + for (const XmlAttr* val_attr = val->FirstAttr(); val_attr; + val_attr = val_attr->NextAttr()) { + if (val_attr->Name().Namespace() == kXmlNsNs || + val_attr->Name() == QName(kXmlNs)) { + continue; // Skip NS attributes. + } + if (exp->Attr(val_attr->Name()) != val_attr->Value()) { + *error = "In <" + exp->Name().LocalPart() + "> unexpected attribute " + + val_attr->Name().LocalPart(); + return false; + } + } + + const XmlElement* exp_child = exp->FirstElement(); + const XmlElement* val_child = val->FirstElement(); + while (exp_child && val_child) { + if (!VerifyXml(exp_child, val_child, error)) + return false; + exp_child = exp_child->NextElement(); + val_child = val_child->NextElement(); + } + if (exp_child) { + *error = "<" + exp_child->Name().Merged() + "> is expected, but not found"; + return false; + } + + if (val_child) { + *error = "Unexpected <" + val_child->Name().Merged() + "> found"; + return false; + } + + return true; +} + +} // namespace + +// Each of the tests below try to parse a message, format it again, +// and then verify that the formatted message is the same as the +// original stanza. The sample messages were generated by libjingle. + +TEST(JingleMessageTest, SessionInitiate) { + const char* kTestSessionInitiateMessage = + "<iq to='user@gmail.com/chromoting016DBB07' type='set' " + "from='user@gmail.com/chromiumsy5C6A652D' " + "xmlns='jabber:client'><jingle xmlns='urn:xmpp:jingle:1' " + "action='session-initiate' sid='2227053353' " + "initiator='user@gmail.com/chromiumsy5C6A652D'><content " + "name='chromoting' creator='initiator'><description " + "xmlns='google:remoting'><control transport='stream' version='2'/><event " + "transport='stream' version='2'/><video transport='stream' version='2' " + "codec='vp8'/><initial-resolution width='800' height='600'/>" + "<authentication><auth-token>j7whCMii0Z0AAPwj7whCM/j7whCMii0Z0AAPw=" + "</auth-token></authentication></description><transport " + "xmlns='http://www.google.com/transport/p2p'/></content></jingle>" + "</iq>"; + scoped_ptr<XmlElement> source_message( + XmlElement::ForStr(kTestSessionInitiateMessage)); + ASSERT_TRUE(source_message.get()); + + EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get())); + + JingleMessage message; + std::string error; + EXPECT_TRUE(message.ParseXml(source_message.get(), &error)) << error; + + EXPECT_EQ(message.action, JingleMessage::SESSION_INITIATE); + + scoped_ptr<XmlElement> formatted_message(message.ToXml()); + ASSERT_TRUE(formatted_message.get()); + EXPECT_TRUE(VerifyXml(formatted_message.get(), source_message.get(), &error)) + << error; +} + +TEST(JingleMessageTest, SessionAccept) { + const char* kTestSessionAcceptMessage = + "<cli:iq from='user@gmail.com/chromoting016DBB07' " + "to='user@gmail.com/chromiumsy5C6A652D' type='set' " + "xmlns:cli='jabber:client'><jingle action='session-accept' " + "sid='2227053353' xmlns='urn:xmpp:jingle:1'><content creator='initiator' " + "name='chromoting'><description xmlns='google:remoting'><control " + "transport='stream' version='2'/><event transport='stream' version='2'/>" + "<video codec='vp8' transport='stream' version='2'/><initial-resolution " + "height='2048' width='2048'/><authentication><certificate>" + "MIICpjCCAY6gW0Cert0TANBgkqhkiG9w0BAQUFA=</certificate>" + "</authentication></description><transport xmlns=" + "'http://www.google.com/transport/p2p'/></content></jingle></cli:iq>"; + + scoped_ptr<XmlElement> source_message( + XmlElement::ForStr(kTestSessionAcceptMessage)); + ASSERT_TRUE(source_message.get()); + + EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get())); + + JingleMessage message; + std::string error; + EXPECT_TRUE(message.ParseXml(source_message.get(), &error)) << error; + + EXPECT_EQ(message.action, JingleMessage::SESSION_ACCEPT); + + scoped_ptr<XmlElement> formatted_message(message.ToXml()); + ASSERT_TRUE(formatted_message.get()); + EXPECT_TRUE(VerifyXml(source_message.get(), formatted_message.get(), &error)) + << error; +} + +TEST(JingleMessageTest, TransportInfo) { + const char* kTestTransportInfoMessage = + "<cli:iq to='user@gmail.com/chromoting016DBB07' type='set' " + "xmlns:cli='jabber:client'><jingle xmlns='urn:xmpp:jingle:1' " + "action='transport-info' sid='2227053353'><content name='chromoting' " + "creator='initiator'><transport " + "xmlns='http://www.google.com/transport/p2p'><candidate name='event' " + "address='172.23.164.186' port='57040' preference='1' " + "username='tPUyEAmQrEw3y7hi' protocol='udp' generation='0' " + "password='2iRdhLfawKZC5ydJ' type='local'/><candidate name='video' " + "address='172.23.164.186' port='42171' preference='1' " + "username='EPK3CXo5sTLJSez0' protocol='udp' generation='0' " + "password='eM0VUfUkZ+1Pyi0M' type='local'/></transport></content>" + "</jingle></cli:iq>"; + + scoped_ptr<XmlElement> source_message( + XmlElement::ForStr(kTestTransportInfoMessage)); + ASSERT_TRUE(source_message.get()); + + EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get())); + + JingleMessage message; + std::string error; + EXPECT_TRUE(message.ParseXml(source_message.get(), &error)) << error; + + EXPECT_EQ(message.action, JingleMessage::TRANSPORT_INFO); + EXPECT_EQ(message.candidates.size(), 2U); + + scoped_ptr<XmlElement> formatted_message(message.ToXml()); + ASSERT_TRUE(formatted_message.get()); + EXPECT_TRUE(VerifyXml(source_message.get(), formatted_message.get(), &error)) + << error; +} + +TEST(JingleMessageTest, SessionTerminate) { + const char* kTestSessionTerminateMessage = + "<cli:iq from='user@gmail.com/chromoting016DBB07' " + "to='user@gmail.com/chromiumsy5C6A652D' type='set' " + "xmlns:cli='jabber:client'><jingle action='session-terminate' " + "sid='2227053353' xmlns='urn:xmpp:jingle:1'><reason><success/>" + "</reason></jingle></cli:iq>"; + + scoped_ptr<XmlElement> source_message( + XmlElement::ForStr(kTestSessionTerminateMessage)); + ASSERT_TRUE(source_message.get()); + + EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get())); + + JingleMessage message; + std::string error; + EXPECT_TRUE(message.ParseXml(source_message.get(), &error)) << error; + + EXPECT_EQ(message.action, JingleMessage::SESSION_TERMINATE); + + scoped_ptr<XmlElement> formatted_message(message.ToXml()); + ASSERT_TRUE(formatted_message.get()); + EXPECT_TRUE(VerifyXml(source_message.get(), formatted_message.get(), &error)) + << error; +} + +TEST(JingleMessageReplyTest, ToXml) { + const char* kTestIncomingMessage = + "<cli:iq from='user@gmail.com/chromoting016DBB07' id='4' " + "to='user@gmail.com/chromiumsy5C6A652D' type='set' " + "xmlns:cli='jabber:client'><jingle action='session-terminate' " + "sid='2227053353' xmlns='urn:xmpp:jingle:1'><reason><success/>" + "</reason></jingle></cli:iq>"; + scoped_ptr<XmlElement> incoming_message( + XmlElement::ForStr(kTestIncomingMessage)); + ASSERT_TRUE(incoming_message.get()); + + struct TestCase { + const JingleMessageReply::ErrorType error; + std::string error_text; + std::string expected_text; + } tests[] = { + { JingleMessageReply::BAD_REQUEST, "", "<iq xmlns='jabber:client' " + "to='user@gmail.com/chromoting016DBB07' id='4' type='error'><jingle " + "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>" + "<reason><success/></reason></jingle><error type='modify'><bad-request/>" + "</error></iq>" }, + { JingleMessageReply::BAD_REQUEST, "ErrorText", "<iq xmlns='jabber:client' " + "to='user@gmail.com/chromoting016DBB07' id='4' type='error'><jingle " + "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>" + "<reason><success/></reason></jingle><error type='modify'><bad-request/>" + "<text xml:lang='en'>ErrorText</text></error></iq>" }, + { JingleMessageReply::NOT_IMPLEMENTED, "", "<iq xmlns='jabber:client' " + "to='user@gmail.com/chromoting016DBB07' id='4' type='error'><jingle " + "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>" + "<reason><success/></reason></jingle><error type='cancel'>" + "<feature-bad-request/></error></iq>" }, + { JingleMessageReply::INVALID_SID, "", "<iq xmlns='jabber:client' " + "to='user@gmail.com/chromoting016DBB07' id='4' type='error'><jingle " + "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>" + "<reason><success/></reason></jingle><error type='modify'>" + "<item-not-found/><text xml:lang='en'>Invalid SID</text></error></iq>" }, + { JingleMessageReply::INVALID_SID, "ErrorText", "<iq xmlns='jabber:client' " + "to='user@gmail.com/chromoting016DBB07' id='4' type='error'><jingle " + "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>" + "<reason><success/></reason></jingle><error type='modify'>" + "<item-not-found/><text xml:lang='en'>ErrorText</text></error></iq>" }, + { JingleMessageReply::UNEXPECTED_REQUEST, "", "<iq xmlns='jabber:client' " + "to='user@gmail.com/chromoting016DBB07' id='4' type='error'><jingle " + "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>" + "<reason><success/></reason></jingle><error type='modify'>" + "<unexpected-request/></error></iq>" }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + JingleMessageReply reply_msg; + if (tests[i].error_text.empty()) { + reply_msg = JingleMessageReply(tests[i].error); + } else { + reply_msg = JingleMessageReply(tests[i].error, tests[i].error_text); + } + scoped_ptr<XmlElement> reply(reply_msg.ToXml(incoming_message.get())); + + scoped_ptr<XmlElement> expected(XmlElement::ForStr(tests[i].expected_text)); + ASSERT_TRUE(expected.get()); + + std::string error; + EXPECT_TRUE(VerifyXml(expected.get(), reply.get(), &error)) << error; + } +} + +} // namespace protocol +} // namespace remoting diff --git a/remoting/protocol/jingle_session.cc b/remoting/protocol/jingle_session.cc index ae0539a..1f79b8e 100644 --- a/remoting/protocol/jingle_session.cc +++ b/remoting/protocol/jingle_session.cc @@ -24,11 +24,8 @@ using cricket::BaseSession; namespace remoting { - namespace protocol { -const char JingleSession::kChromotingContentName[] = "chromoting"; - namespace { const char kControlChannelName[] = "control"; @@ -483,5 +480,4 @@ void JingleSession::SetState(State new_state) { } } // namespace protocol - } // namespace remoting diff --git a/remoting/protocol/jingle_session.h b/remoting/protocol/jingle_session.h index 2336d5f..ee0a80e 100644 --- a/remoting/protocol/jingle_session.h +++ b/remoting/protocol/jingle_session.h @@ -26,8 +26,6 @@ class JingleSessionManager; class JingleSession : public protocol::Session, public sigslot::has_slots<> { public: - static const char kChromotingContentName[]; - // Session interface. virtual void SetStateChangeCallback(StateChangeCallback* callback) OVERRIDE; virtual void CreateStreamChannel( diff --git a/remoting/protocol/jingle_session_manager.cc b/remoting/protocol/jingle_session_manager.cc index 1dae59d..9312d54 100644 --- a/remoting/protocol/jingle_session_manager.cc +++ b/remoting/protocol/jingle_session_manager.cc @@ -338,7 +338,7 @@ JingleSessionManager::CreateClientSessionDescription( const std::string& auth_token) { cricket::SessionDescription* desc = new cricket::SessionDescription(); desc->AddContent( - JingleSession::kChromotingContentName, kChromotingXmlNamespace, + ContentDescription::kChromotingContentName, kChromotingXmlNamespace, new ContentDescription(config, auth_token, "")); return desc; } @@ -349,7 +349,7 @@ cricket::SessionDescription* JingleSessionManager::CreateHostSessionDescription( const std::string& certificate) { cricket::SessionDescription* desc = new cricket::SessionDescription(); desc->AddContent( - JingleSession::kChromotingContentName, kChromotingXmlNamespace, + ContentDescription::kChromotingContentName, kChromotingXmlNamespace, new ContentDescription(config, "", certificate)); return desc; } diff --git a/remoting/protocol/jingle_stream_connector.cc b/remoting/protocol/jingle_stream_connector.cc index 228fd3f..57e0fcd 100644 --- a/remoting/protocol/jingle_stream_connector.cc +++ b/remoting/protocol/jingle_stream_connector.cc @@ -15,6 +15,7 @@ #include "net/socket/ssl_client_socket.h" #include "net/socket/ssl_server_socket.h" #include "net/socket/client_socket_factory.h" +#include "remoting/protocol/content_description.h" #include "remoting/protocol/jingle_session.h" namespace remoting { @@ -44,12 +45,13 @@ net::SSLClientSocket* CreateSSLClientSocket( ssl_config.allowed_bad_certs.push_back(cert_and_status); // SSLClientSocket takes ownership of the adapter. - net::HostPortPair host_and_pair(JingleSession::kChromotingContentName, 0); + net::HostPortPair host_and_port( + ContentDescription::kChromotingContentName, 0); net::SSLClientSocketContext context; context.cert_verifier = cert_verifier; net::SSLClientSocket* ssl_socket = net::ClientSocketFactory::GetDefaultFactory()->CreateSSLClientSocket( - socket, host_and_pair, ssl_config, NULL, context); + socket, host_and_port, ssl_config, NULL, context); return ssl_socket; } diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 4ec741e..7fb27f0 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -628,6 +628,8 @@ 'protocol/jingle_channel_connector.h', 'protocol/jingle_datagram_connector.cc', 'protocol/jingle_datagram_connector.h', + 'protocol/jingle_messages.cc', + 'protocol/jingle_messages.h', 'protocol/jingle_session.cc', 'protocol/jingle_session.h', 'protocol/jingle_session_manager.cc', @@ -783,6 +785,7 @@ 'protocol/connection_to_client_unittest.cc', 'protocol/fake_session.cc', 'protocol/fake_session.h', + 'protocol/jingle_messages_unittest.cc', 'protocol/jingle_session_unittest.cc', 'protocol/message_decoder_unittest.cc', 'protocol/message_reader_unittest.cc', |