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