// 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