summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorsergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-19 23:31:43 +0000
committersergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-19 23:31:43 +0000
commitda930e1b7adfb28f6895d95004c25984af627080 (patch)
tree5f0de3a58c50101498c70c99ec9622cf3ffab960 /remoting
parentdd5ad5f4fd6f7693d04f11331ad3c7d6a2a6c1b3 (diff)
downloadchromium_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.cc6
-rw-r--r--remoting/protocol/content_description.h4
-rw-r--r--remoting/protocol/jingle_messages.cc385
-rw-r--r--remoting/protocol/jingle_messages.h94
-rw-r--r--remoting/protocol/jingle_messages_unittest.cc289
-rw-r--r--remoting/protocol/jingle_session.cc4
-rw-r--r--remoting/protocol/jingle_session.h2
-rw-r--r--remoting/protocol/jingle_session_manager.cc4
-rw-r--r--remoting/protocol/jingle_stream_connector.cc6
-rw-r--r--remoting/remoting.gyp3
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',