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
13using buzz::QName;
14using buzz::XmlAttr;
15using buzz::XmlElement;
16
17namespace remoting {
18namespace protocol {
19
20namespace {
21
22const char kXmlNsNs[] = "http://www.w3.org/2000/xmlns/";
23const 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.
28bool 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
98TEST(JingleMessageTest, SessionInitiate) {
99  const char* kTestSessionInitiateMessage =
100      "<iq to='user@gmail.com/chromoting016DBB07' type='set' "
101        "from='user@gmail.com/chromiumsy5C6A652D' "
102        "xmlns='jabber:client'>"
103        "<jingle xmlns='urn:xmpp:jingle:1' "
104          "action='session-initiate' sid='2227053353' "
105          "initiator='user@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
139TEST(JingleMessageTest, SessionAccept) {
140  const char* kTestSessionAcceptMessage =
141      "<cli:iq from='user@gmail.com/chromoting016DBB07' "
142        "to='user@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
180TEST(JingleMessageTest, TransportInfo) {
181  const char* kTestTransportInfoMessage =
182      "<cli:iq to='user@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
214TEST(JingleMessageTest, SessionTerminate) {
215  const char* kTestSessionTerminateMessage =
216      "<cli:iq from='user@gmail.com/chromoting016DBB07' "
217      "to='user@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
240TEST(JingleMessageTest, SessionInfo) {
241  const char* kTestSessionTerminateMessage =
242      "<cli:iq from='user@gmail.com/chromoting016DBB07' "
243      "to='user@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
269TEST(JingleMessageReplyTest, ToXml) {
270  const char* kTestIncomingMessage =
271      "<cli:iq from='user@gmail.com/chromoting016DBB07' id='4' "
272      "to='user@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@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@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@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@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@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@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
334TEST(JingleMessageTest, ErrorMessage) {
335  const char* kTestSessionInitiateErrorMessage =
336      "<iq to='user@gmail.com/chromoting016DBB07' type='error' "
337        "from='user@gmail.com/chromiumsy5C6A652D' "
338        "xmlns='jabber:client'>"
339        "<jingle xmlns='urn:xmpp:jingle:1' "
340        "action='session-initiate' sid='2227053353' "
341        "initiator='user@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