// Copyright (c) 2012 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'/>"
"<audio transport='stream' version='2' codec='verbatim'/>"
"<initial-resolution width='640' height='480'/>"
"<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'>i"
"<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'/>"
"<audio transport='stream' version='2' codec='verbatim'/>"
"<initial-resolution height='480' width='640'/>"
"<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(JingleMessageTest, SessionInfo) {
const char* kTestSessionTerminateMessage =
"<cli:iq from='user@gmail.com/chromoting016DBB07' "
"to='user@gmail.com/chromiumsy5C6A652D' type='set' "
"xmlns:cli='jabber:client'><jingle action='session-info' "
"sid='2227053353' xmlns='urn:xmpp:jingle:1'><test-info>TestMessage"
"</test-info></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_INFO);
ASSERT_TRUE(message.info.get() != NULL);
EXPECT_TRUE(message.info->Name() ==
buzz::QName("urn:xmpp:jingle:1", "test-info"));
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;
}
}
TEST(JingleMessageTest, ErrorMessage) {
const char* kTestSessionInitiateErrorMessage =
"<iq to='user@gmail.com/chromoting016DBB07' type='error' "
"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'/>"
"<audio transport='stream' version='2' codec='verbatim'/>"
"<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>"
"<error code='501' type='cancel'>"
"<feature-not-implemented "
"xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
"</error>"
"</iq>";
scoped_ptr<XmlElement> source_message(
XmlElement::ForStr(kTestSessionInitiateErrorMessage));
ASSERT_TRUE(source_message.get());
EXPECT_FALSE(JingleMessage::IsJingleMessage(source_message.get()));
JingleMessage message;
std::string error;
EXPECT_FALSE(message.ParseXml(source_message.get(), &error));
EXPECT_FALSE(error.empty());
}
} // namespace protocol
} // namespace remoting