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/xmpp/constants.h" 11 #include "third_party/webrtc/libjingle/xmllite/xmlelement.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