Home | History | Annotate | Download | only in protocol
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "remoting/protocol/jingle_messages.h"
      6 
      7 #include "base/logging.h"
      8 #include "testing/gmock/include/gmock/gmock.h"
      9 #include "testing/gtest/include/gtest/gtest.h"
     10 #include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
     11 #include "third_party/libjingle/source/talk/xmpp/constants.h"
     12 
     13 using buzz::QName;
     14 using buzz::XmlAttr;
     15 using buzz::XmlElement;
     16 
     17 namespace remoting {
     18 namespace protocol {
     19 
     20 namespace {
     21 
     22 const char kXmlNsNs[] = "http://www.w3.org/2000/xmlns/";
     23 const char kXmlNs[] = "xmlns";
     24 
     25 // Compares two XML blobs and returns true if they are
     26 // equivalent. Otherwise |error| is set to error message that
     27 // specifies the first test.
     28 bool VerifyXml(const XmlElement* exp,
     29                const XmlElement* val,
     30                std::string* error) {
     31   if (exp->Name() != val->Name()) {
     32     *error = "<" + exp->Name().Merged() + ">" + " is expected, but " +
     33         "<" + val->Name().Merged() + ">"  + " found";
     34     return false;
     35   }
     36   if (exp->BodyText() != val->BodyText()) {
     37     *error = "<" + exp->Name().LocalPart() + ">" + exp->BodyText() +
     38         "</" + exp->Name().LocalPart() + ">" " is expected, but found " +
     39         "<" + exp->Name().LocalPart() + ">" + val->BodyText() +
     40         "</" + exp->Name().LocalPart() + ">";
     41     return false;
     42   }
     43 
     44   for (const XmlAttr* exp_attr = exp->FirstAttr(); exp_attr != NULL;
     45        exp_attr = exp_attr->NextAttr()) {
     46     if (exp_attr->Name().Namespace() == kXmlNsNs ||
     47         exp_attr->Name() == QName(kXmlNs)) {
     48       continue; // Skip NS attributes.
     49     }
     50     if (val->Attr(exp_attr->Name()) != exp_attr->Value()) {
     51       *error = "In <" + exp->Name().LocalPart() + "> attribute " +
     52           exp_attr->Name().LocalPart() + " is expected to be set to " +
     53           exp_attr->Value();
     54       return false;
     55     }
     56   }
     57 
     58   for (const XmlAttr* val_attr = val->FirstAttr(); val_attr;
     59        val_attr = val_attr->NextAttr()) {
     60     if (val_attr->Name().Namespace() == kXmlNsNs ||
     61         val_attr->Name() == QName(kXmlNs)) {
     62       continue; // Skip NS attributes.
     63     }
     64     if (exp->Attr(val_attr->Name()) != val_attr->Value()) {
     65       *error = "In <" + exp->Name().LocalPart() + "> unexpected attribute " +
     66           val_attr->Name().LocalPart();
     67       return false;
     68     }
     69   }
     70 
     71   const XmlElement* exp_child = exp->FirstElement();
     72   const XmlElement* val_child = val->FirstElement();
     73   while (exp_child && val_child) {
     74     if (!VerifyXml(exp_child, val_child, error))
     75       return false;
     76     exp_child = exp_child->NextElement();
     77     val_child = val_child->NextElement();
     78   }
     79   if (exp_child) {
     80     *error = "<" + exp_child->Name().Merged() + "> is expected, but not found";
     81     return false;
     82   }
     83 
     84   if (val_child) {
     85     *error = "Unexpected <" + val_child->Name().Merged() + "> found";
     86     return false;
     87   }
     88 
     89   return true;
     90 }
     91 
     92 }  // namespace
     93 
     94 // Each of the tests below try to parse a message, format it again,
     95 // and then verify that the formatted message is the same as the
     96 // original stanza. The sample messages were generated by libjingle.
     97 
     98 TEST(JingleMessageTest, SessionInitiate) {
     99   const char* kTestSessionInitiateMessage =
    100       "<iq to='user (at) gmail.com/chromoting016DBB07' type='set' "
    101         "from='user (at) gmail.com/chromiumsy5C6A652D' "
    102         "xmlns='jabber:client'>"
    103         "<jingle xmlns='urn:xmpp:jingle:1' "
    104           "action='session-initiate' sid='2227053353' "
    105           "initiator='user (at) gmail.com/chromiumsy5C6A652D'>"
    106           "<content name='chromoting' creator='initiator'>"
    107             "<description xmlns='google:remoting'>"
    108               "<control transport='stream' version='2'/>"
    109               "<event transport='stream' version='2'/>"
    110               "<video transport='stream' version='2' codec='vp8'/>"
    111               "<audio transport='stream' version='2' codec='verbatim'/>"
    112               "<initial-resolution width='640' height='480'/>"
    113               "<authentication><auth-token>"
    114                 "j7whCMii0Z0AAPwj7whCM/j7whCMii0Z0AAPw="
    115               "</auth-token></authentication>"
    116           "</description>"
    117           "<transport xmlns='http://www.google.com/transport/p2p'/>"
    118           "</content>"
    119         "</jingle>"
    120       "</iq>";
    121   scoped_ptr<XmlElement> source_message(
    122       XmlElement::ForStr(kTestSessionInitiateMessage));
    123   ASSERT_TRUE(source_message.get());
    124 
    125   EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get()));
    126 
    127   JingleMessage message;
    128   std::string error;
    129   EXPECT_TRUE(message.ParseXml(source_message.get(), &error)) << error;
    130 
    131   EXPECT_EQ(message.action, JingleMessage::SESSION_INITIATE);
    132 
    133   scoped_ptr<XmlElement> formatted_message(message.ToXml());
    134   ASSERT_TRUE(formatted_message.get());
    135   EXPECT_TRUE(VerifyXml(formatted_message.get(), source_message.get(), &error))
    136       << error;
    137 }
    138 
    139 TEST(JingleMessageTest, SessionAccept) {
    140   const char* kTestSessionAcceptMessage =
    141       "<cli:iq from='user (at) gmail.com/chromoting016DBB07' "
    142         "to='user (at) gmail.com/chromiumsy5C6A652D' type='set' "
    143         "xmlns:cli='jabber:client'>"
    144         "<jingle action='session-accept' sid='2227053353' "
    145           "xmlns='urn:xmpp:jingle:1'>i"
    146           "<content creator='initiator' name='chromoting'>"
    147             "<description xmlns='google:remoting'>"
    148               "<control transport='stream' version='2'/>"
    149               "<event transport='stream' version='2'/>"
    150               "<video codec='vp8' transport='stream' version='2'/>"
    151               "<audio transport='stream' version='2' codec='verbatim'/>"
    152               "<initial-resolution height='480' width='640'/>"
    153               "<authentication><certificate>"
    154                 "MIICpjCCAY6gW0Cert0TANBgkqhkiG9w0BAQUFA="
    155               "</certificate></authentication>"
    156             "</description>"
    157             "<transport xmlns='http://www.google.com/transport/p2p'/>"
    158           "</content>"
    159         "</jingle>"
    160       "</cli:iq>";
    161 
    162   scoped_ptr<XmlElement> source_message(
    163       XmlElement::ForStr(kTestSessionAcceptMessage));
    164   ASSERT_TRUE(source_message.get());
    165 
    166   EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get()));
    167 
    168   JingleMessage message;
    169   std::string error;
    170   EXPECT_TRUE(message.ParseXml(source_message.get(), &error)) << error;
    171 
    172   EXPECT_EQ(message.action, JingleMessage::SESSION_ACCEPT);
    173 
    174   scoped_ptr<XmlElement> formatted_message(message.ToXml());
    175   ASSERT_TRUE(formatted_message.get());
    176   EXPECT_TRUE(VerifyXml(source_message.get(), formatted_message.get(), &error))
    177       << error;
    178 }
    179 
    180 TEST(JingleMessageTest, TransportInfo) {
    181   const char* kTestTransportInfoMessage =
    182       "<cli:iq to='user (at) gmail.com/chromoting016DBB07' type='set' "
    183       "xmlns:cli='jabber:client'><jingle xmlns='urn:xmpp:jingle:1' "
    184       "action='transport-info' sid='2227053353'><content name='chromoting' "
    185       "creator='initiator'><transport "
    186       "xmlns='http://www.google.com/transport/p2p'><candidate name='event' "
    187       "address='172.23.164.186' port='57040' preference='1' "
    188       "username='tPUyEAmQrEw3y7hi' protocol='udp' generation='0' "
    189       "password='2iRdhLfawKZC5ydJ' type='local'/><candidate name='video' "
    190       "address='172.23.164.186' port='42171' preference='1' "
    191       "username='EPK3CXo5sTLJSez0' protocol='udp' generation='0' "
    192       "password='eM0VUfUkZ+1Pyi0M' type='local'/></transport></content>"
    193       "</jingle></cli:iq>";
    194 
    195   scoped_ptr<XmlElement> source_message(
    196       XmlElement::ForStr(kTestTransportInfoMessage));
    197   ASSERT_TRUE(source_message.get());
    198 
    199   EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get()));
    200 
    201   JingleMessage message;
    202   std::string error;
    203   EXPECT_TRUE(message.ParseXml(source_message.get(), &error)) << error;
    204 
    205   EXPECT_EQ(message.action, JingleMessage::TRANSPORT_INFO);
    206   EXPECT_EQ(message.candidates.size(), 2U);
    207 
    208   scoped_ptr<XmlElement> formatted_message(message.ToXml());
    209   ASSERT_TRUE(formatted_message.get());
    210   EXPECT_TRUE(VerifyXml(source_message.get(), formatted_message.get(), &error))
    211       << error;
    212 }
    213 
    214 TEST(JingleMessageTest, SessionTerminate) {
    215   const char* kTestSessionTerminateMessage =
    216       "<cli:iq from='user (at) gmail.com/chromoting016DBB07' "
    217       "to='user (at) gmail.com/chromiumsy5C6A652D' type='set' "
    218       "xmlns:cli='jabber:client'><jingle action='session-terminate' "
    219       "sid='2227053353' xmlns='urn:xmpp:jingle:1'><reason><success/>"
    220       "</reason></jingle></cli:iq>";
    221 
    222   scoped_ptr<XmlElement> source_message(
    223       XmlElement::ForStr(kTestSessionTerminateMessage));
    224   ASSERT_TRUE(source_message.get());
    225 
    226   EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get()));
    227 
    228   JingleMessage message;
    229   std::string error;
    230   EXPECT_TRUE(message.ParseXml(source_message.get(), &error)) << error;
    231 
    232   EXPECT_EQ(message.action, JingleMessage::SESSION_TERMINATE);
    233 
    234   scoped_ptr<XmlElement> formatted_message(message.ToXml());
    235   ASSERT_TRUE(formatted_message.get());
    236   EXPECT_TRUE(VerifyXml(source_message.get(), formatted_message.get(), &error))
    237       << error;
    238 }
    239 
    240 TEST(JingleMessageTest, SessionInfo) {
    241   const char* kTestSessionTerminateMessage =
    242       "<cli:iq from='user (at) gmail.com/chromoting016DBB07' "
    243       "to='user (at) gmail.com/chromiumsy5C6A652D' type='set' "
    244       "xmlns:cli='jabber:client'><jingle action='session-info' "
    245       "sid='2227053353' xmlns='urn:xmpp:jingle:1'><test-info>TestMessage"
    246       "</test-info></jingle></cli:iq>";
    247 
    248   scoped_ptr<XmlElement> source_message(
    249       XmlElement::ForStr(kTestSessionTerminateMessage));
    250   ASSERT_TRUE(source_message.get());
    251 
    252   EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get()));
    253 
    254   JingleMessage message;
    255   std::string error;
    256   EXPECT_TRUE(message.ParseXml(source_message.get(), &error)) << error;
    257 
    258   EXPECT_EQ(message.action, JingleMessage::SESSION_INFO);
    259   ASSERT_TRUE(message.info.get() != NULL);
    260   EXPECT_TRUE(message.info->Name() ==
    261               buzz::QName("urn:xmpp:jingle:1", "test-info"));
    262 
    263   scoped_ptr<XmlElement> formatted_message(message.ToXml());
    264   ASSERT_TRUE(formatted_message.get());
    265   EXPECT_TRUE(VerifyXml(source_message.get(), formatted_message.get(), &error))
    266       << error;
    267 }
    268 
    269 TEST(JingleMessageReplyTest, ToXml) {
    270   const char* kTestIncomingMessage =
    271       "<cli:iq from='user (at) gmail.com/chromoting016DBB07' id='4' "
    272       "to='user (at) gmail.com/chromiumsy5C6A652D' type='set' "
    273       "xmlns:cli='jabber:client'><jingle action='session-terminate' "
    274       "sid='2227053353' xmlns='urn:xmpp:jingle:1'><reason><success/>"
    275       "</reason></jingle></cli:iq>";
    276   scoped_ptr<XmlElement> incoming_message(
    277       XmlElement::ForStr(kTestIncomingMessage));
    278   ASSERT_TRUE(incoming_message.get());
    279 
    280   struct TestCase {
    281     const JingleMessageReply::ErrorType error;
    282     std::string error_text;
    283     std::string expected_text;
    284   } tests[] = {
    285     { JingleMessageReply::BAD_REQUEST, "", "<iq xmlns='jabber:client' "
    286       "to='user (at) gmail.com/chromoting016DBB07' id='4' type='error'><jingle "
    287       "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>"
    288       "<reason><success/></reason></jingle><error type='modify'><bad-request/>"
    289       "</error></iq>" },
    290     { JingleMessageReply::BAD_REQUEST, "ErrorText", "<iq xmlns='jabber:client' "
    291       "to='user (at) gmail.com/chromoting016DBB07' id='4' type='error'><jingle "
    292       "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>"
    293       "<reason><success/></reason></jingle><error type='modify'><bad-request/>"
    294       "<text xml:lang='en'>ErrorText</text></error></iq>" },
    295     { JingleMessageReply::NOT_IMPLEMENTED, "", "<iq xmlns='jabber:client' "
    296       "to='user (at) gmail.com/chromoting016DBB07' id='4' type='error'><jingle "
    297       "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>"
    298       "<reason><success/></reason></jingle><error type='cancel'>"
    299       "<feature-bad-request/></error></iq>" },
    300     { JingleMessageReply::INVALID_SID, "",  "<iq xmlns='jabber:client' "
    301       "to='user (at) gmail.com/chromoting016DBB07' id='4' type='error'><jingle "
    302       "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>"
    303       "<reason><success/></reason></jingle><error type='modify'>"
    304       "<item-not-found/><text xml:lang='en'>Invalid SID</text></error></iq>" },
    305     { JingleMessageReply::INVALID_SID, "ErrorText", "<iq xmlns='jabber:client' "
    306       "to='user (at) gmail.com/chromoting016DBB07' id='4' type='error'><jingle "
    307       "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>"
    308       "<reason><success/></reason></jingle><error type='modify'>"
    309       "<item-not-found/><text xml:lang='en'>ErrorText</text></error></iq>" },
    310     { JingleMessageReply::UNEXPECTED_REQUEST, "", "<iq xmlns='jabber:client' "
    311       "to='user (at) gmail.com/chromoting016DBB07' id='4' type='error'><jingle "
    312       "action='session-terminate' sid='2227053353' xmlns='urn:xmpp:jingle:1'>"
    313       "<reason><success/></reason></jingle><error type='modify'>"
    314       "<unexpected-request/></error></iq>" },
    315   };
    316 
    317   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    318     JingleMessageReply reply_msg;
    319     if (tests[i].error_text.empty()) {
    320       reply_msg = JingleMessageReply(tests[i].error);
    321     } else {
    322       reply_msg = JingleMessageReply(tests[i].error, tests[i].error_text);
    323     }
    324     scoped_ptr<XmlElement> reply(reply_msg.ToXml(incoming_message.get()));
    325 
    326     scoped_ptr<XmlElement> expected(XmlElement::ForStr(tests[i].expected_text));
    327     ASSERT_TRUE(expected.get());
    328 
    329     std::string error;
    330     EXPECT_TRUE(VerifyXml(expected.get(), reply.get(), &error)) << error;
    331   }
    332 }
    333 
    334 TEST(JingleMessageTest, ErrorMessage) {
    335   const char* kTestSessionInitiateErrorMessage =
    336       "<iq to='user (at) gmail.com/chromoting016DBB07' type='error' "
    337         "from='user (at) gmail.com/chromiumsy5C6A652D' "
    338         "xmlns='jabber:client'>"
    339         "<jingle xmlns='urn:xmpp:jingle:1' "
    340         "action='session-initiate' sid='2227053353' "
    341         "initiator='user (at) gmail.com/chromiumsy5C6A652D'>"
    342           "<content name='chromoting' creator='initiator'>"
    343             "<description xmlns='google:remoting'>"
    344               "<control transport='stream' version='2'/>"
    345               "<event transport='stream' version='2'/>"
    346               "<video transport='stream' version='2' codec='vp8'/>"
    347               "<audio transport='stream' version='2' codec='verbatim'/>"
    348               "<initial-resolution width='800' height='600'/>"
    349               "<authentication><auth-token>"
    350                 "j7whCMii0Z0AAPwj7whCM/j7whCMii0Z0AAPw="
    351               "</auth-token></authentication>"
    352             "</description>"
    353             "<transport xmlns='http://www.google.com/transport/p2p'/>"
    354           "</content>"
    355         "</jingle>"
    356         "<error code='501' type='cancel'>"
    357           "<feature-not-implemented "
    358             "xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
    359         "</error>"
    360       "</iq>";
    361   scoped_ptr<XmlElement> source_message(
    362       XmlElement::ForStr(kTestSessionInitiateErrorMessage));
    363   ASSERT_TRUE(source_message.get());
    364 
    365   EXPECT_FALSE(JingleMessage::IsJingleMessage(source_message.get()));
    366 
    367   JingleMessage message;
    368   std::string error;
    369   EXPECT_FALSE(message.ParseXml(source_message.get(), &error));
    370   EXPECT_FALSE(error.empty());
    371 }
    372 
    373 }  // namespace protocol
    374 }  // namespace remoting
    375