blob: a1b87ff6e25c9348705b8d58132ab14b9c24fb82 [file] [log] [blame]
[email protected]09100e32012-01-03 20:21:211// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]da930e12011-08-19 23:31:432// 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 "base/string_number_conversions.h"
9#include "remoting/base/constants.h"
10#include "remoting/protocol/content_description.h"
11#include "third_party/libjingle/source/talk/p2p/base/candidate.h"
12#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
13
14using buzz::QName;
15using buzz::XmlElement;
16
17namespace remoting {
18namespace protocol {
19
20const char kJabberNamespace[] = "jabber:client";
21const char kJingleNamespace[] = "urn:xmpp:jingle:1";
22const char kP2PTransportNamespace[] = "http://www.google.com/transport/p2p";
23
24namespace {
25
26const char kEmptyNamespace[] = "";
27const char kXmlNamespace[] = "http://www.w3.org/XML/1998/namespace";
28
[email protected]da930e12011-08-19 23:31:4329const int kPortMin = 1000;
30const int kPortMax = 65535;
31
[email protected]e3f03d02011-09-27 20:10:5132template <typename T>
33struct NameMapElement {
34 const T value;
35 const char* const name;
36};
37
38template <typename T>
39const char* ValueToName(const NameMapElement<T> map[], size_t map_size,
40 T value) {
41 for (size_t i = 0; i < map_size; ++i) {
42 if (map[i].value == value)
43 return map[i].name;
44 }
45 return NULL;
46}
47
48template <typename T>
49T NameToValue(const NameMapElement<T> map[], size_t map_size,
50 const std::string& name, T default_value) {
51 for (size_t i = 0; i < map_size; ++i) {
52 if (map[i].name == name)
53 return map[i].value;
54 }
55 return default_value;
56}
57
[email protected]da9ccfb2012-01-28 00:34:4058const NameMapElement<JingleMessage::ActionType> kActionTypes[] = {
[email protected]e3f03d02011-09-27 20:10:5159 { JingleMessage::SESSION_INITIATE, "session-initiate" },
60 { JingleMessage::SESSION_ACCEPT, "session-accept" },
61 { JingleMessage::SESSION_TERMINATE, "session-terminate" },
[email protected]1bc9c7c2011-12-14 00:13:3962 { JingleMessage::SESSION_INFO, "session-info" },
[email protected]e3f03d02011-09-27 20:10:5163 { JingleMessage::TRANSPORT_INFO, "transport-info" },
64};
65
[email protected]da9ccfb2012-01-28 00:34:4066const NameMapElement<JingleMessage::Reason> kReasons[] = {
[email protected]e3f03d02011-09-27 20:10:5167 { JingleMessage::SUCCESS, "success" },
68 { JingleMessage::DECLINE, "decline" },
[email protected]1f3dfe82011-11-15 03:45:4669 { JingleMessage::GENERAL_ERROR, "general-error" },
[email protected]e3f03d02011-09-27 20:10:5170 { JingleMessage::INCOMPATIBLE_PARAMETERS, "incompatible-parameters" },
71};
72
[email protected]da930e12011-08-19 23:31:4373bool ParseCandidate(const buzz::XmlElement* element,
74 cricket::Candidate* candidate) {
75 DCHECK(element->Name() == QName(kP2PTransportNamespace, "candidate"));
76
77 const std::string& name = element->Attr(QName(kEmptyNamespace, "name"));
78 const std::string& address = element->Attr(QName(kEmptyNamespace, "address"));
79 const std::string& port_str = element->Attr(QName(kEmptyNamespace, "port"));
80 const std::string& type = element->Attr(QName(kEmptyNamespace, "type"));
81 const std::string& protocol =
82 element->Attr(QName(kEmptyNamespace, "protocol"));
83 const std::string& username =
84 element->Attr(QName(kEmptyNamespace, "username"));
85 const std::string& password =
86 element->Attr(QName(kEmptyNamespace, "password"));
87 const std::string& preference_str =
88 element->Attr(QName(kEmptyNamespace, "preference"));
89 const std::string& generation_str =
90 element->Attr(QName(kEmptyNamespace, "generation"));
91
92 int port;
93 double preference;
94 int generation;
95 if (name.empty() || address.empty() || !base::StringToInt(port_str, &port) ||
96 port < kPortMin || port > kPortMax || type.empty() || protocol.empty() ||
97 username.empty() || password.empty() ||
98 !base::StringToDouble(preference_str, &preference) ||
99 !base::StringToInt(generation_str, &generation)) {
100 return false;
101 }
102
103 candidate->set_name(name);
104 candidate->set_address(talk_base::SocketAddress(address, port));
105 candidate->set_type(type);
106 candidate->set_protocol(protocol);
107 candidate->set_username(username);
108 candidate->set_password(password);
109 candidate->set_preference(static_cast<float>(preference));
110 candidate->set_generation(generation);
111
112 return true;
113}
114
115XmlElement* FormatCandidate(const cricket::Candidate& candidate) {
116 XmlElement* result =
117 new XmlElement(QName(kP2PTransportNamespace, "candidate"));
118 result->SetAttr(QName(kEmptyNamespace, "name"), candidate.name());
119 result->SetAttr(QName(kEmptyNamespace, "address"),
120 candidate.address().IPAsString());
121 result->SetAttr(QName(kEmptyNamespace, "port"),
122 base::IntToString(candidate.address().port()));
123 result->SetAttr(QName(kEmptyNamespace, "type"), candidate.type());
124 result->SetAttr(QName(kEmptyNamespace, "protocol"), candidate.protocol());
125 result->SetAttr(QName(kEmptyNamespace, "username"), candidate.username());
126 result->SetAttr(QName(kEmptyNamespace, "password"), candidate.password());
127 result->SetAttr(QName(kEmptyNamespace, "preference"),
128 base::DoubleToString(candidate.preference()));
129 result->SetAttr(QName(kEmptyNamespace, "generation"),
130 base::IntToString(candidate.generation()));
131 return result;
132}
133
134} // namespace
135
136// static
137bool JingleMessage::IsJingleMessage(const buzz::XmlElement* stanza) {
[email protected]09100e32012-01-03 20:21:21138 return
139 stanza->Name() == QName(kJabberNamespace, "iq") &&
140 stanza->Attr(QName("", "type")) == "set" &&
141 stanza->FirstNamed(QName(kJingleNamespace, "jingle")) != NULL;
[email protected]da930e12011-08-19 23:31:43142}
143
[email protected]137e7cd2012-02-26 00:28:07144// static
145std::string JingleMessage::GetActionName(ActionType action) {
146 return ValueToName(kActionTypes, arraysize(kActionTypes), action);
147}
148
[email protected]da930e12011-08-19 23:31:43149JingleMessage::JingleMessage()
150 : action(UNKNOWN_ACTION),
[email protected]e3f03d02011-09-27 20:10:51151 reason(UNKNOWN_REASON) {
[email protected]da930e12011-08-19 23:31:43152}
153
154JingleMessage::JingleMessage(
155 const std::string& to_value,
156 ActionType action_value,
157 const std::string& sid_value)
158 : to(to_value),
159 action(action_value),
[email protected]aff936c2011-09-30 00:41:01160 sid(sid_value),
161 reason(UNKNOWN_REASON) {
[email protected]da930e12011-08-19 23:31:43162}
163
164JingleMessage::~JingleMessage() {
165}
166
167bool JingleMessage::ParseXml(const buzz::XmlElement* stanza,
168 std::string* error) {
[email protected]09100e32012-01-03 20:21:21169 if (!IsJingleMessage(stanza)) {
170 *error = "Not a jingle message";
171 return false;
172 }
173
[email protected]da930e12011-08-19 23:31:43174 const XmlElement* jingle_tag =
175 stanza->FirstNamed(QName(kJingleNamespace, "jingle"));
176 if (jingle_tag == NULL) {
177 *error = "Not a jingle message";
178 return false;
179 }
180
181 from = stanza->Attr(QName(kEmptyNamespace, "from"));
182 to = stanza->Attr(QName(kEmptyNamespace, "to"));
183
184 std::string action_str = jingle_tag->Attr(QName(kEmptyNamespace, "action"));
185 if (action_str.empty()) {
186 *error = "action attribute is missing";
187 return false;
[email protected]e3f03d02011-09-27 20:10:51188 }
189 action = NameToValue(
190 kActionTypes, arraysize(kActionTypes), action_str, UNKNOWN_ACTION);
191 if (action == UNKNOWN_ACTION) {
[email protected]da930e12011-08-19 23:31:43192 *error = "Unknown action " + action_str;
193 return false;
194 }
195
196 sid = jingle_tag->Attr(QName(kEmptyNamespace, "sid"));
197 if (sid.empty()) {
198 *error = "sid attribute is missing";
199 return false;
200 }
201
[email protected]1bc9c7c2011-12-14 00:13:39202 if (action == SESSION_INFO) {
203 // session-info messages may contain arbitrary information not
204 // defined by the Jingle protocol. We don't need to parse it.
205 const XmlElement* child = jingle_tag->FirstElement();
206 if (child) {
207 // session-info is allowed to be empty.
208 info.reset(new XmlElement(*child));
209 } else {
210 info.reset(NULL);
211 }
212 return true;
213 }
214
[email protected]e3f03d02011-09-27 20:10:51215 const XmlElement* reason_tag =
216 jingle_tag->FirstNamed(QName(kJingleNamespace, "reason"));
217 if (reason_tag && reason_tag->FirstElement()) {
218 reason = NameToValue(
219 kReasons, arraysize(kReasons),
220 reason_tag->FirstElement()->Name().LocalPart(), UNKNOWN_REASON);
[email protected]da930e12011-08-19 23:31:43221 }
222
[email protected]e3f03d02011-09-27 20:10:51223 if (action == SESSION_TERMINATE)
224 return true;
225
[email protected]da930e12011-08-19 23:31:43226 const XmlElement* content_tag =
227 jingle_tag->FirstNamed(QName(kJingleNamespace, "content"));
228 if (!content_tag) {
229 *error = "content tag is missing";
230 return false;
231 }
232
233 std::string content_name = content_tag->Attr(QName(kEmptyNamespace, "name"));
234 if (content_name != ContentDescription::kChromotingContentName) {
235 *error = "Unexpected content name: " + content_name;
236 return false;
237 }
238
239 description.reset(NULL);
240 if (action == SESSION_INITIATE || action == SESSION_ACCEPT) {
241 const XmlElement* description_tag = content_tag->FirstNamed(
242 QName(kChromotingXmlNamespace, "description"));
243 if (!description_tag) {
244 *error = "Missing chromoting content description";
245 return false;
246 }
247
248 description.reset(ContentDescription::ParseXml(description_tag));
249 if (!description.get()) {
250 *error = "Failed to parse content description";
251 return false;
252 }
253 }
254
255 candidates.clear();
256 const XmlElement* transport_tag = content_tag->FirstNamed(
257 QName(kP2PTransportNamespace, "transport"));
258 if (transport_tag) {
259 QName qn_candidate(kP2PTransportNamespace, "candidate");
260 for (const XmlElement* candidate_tag =
261 transport_tag->FirstNamed(qn_candidate);
262 candidate_tag != NULL;
263 candidate_tag = candidate_tag->NextNamed(qn_candidate)) {
264 cricket::Candidate candidate;
265 if (!ParseCandidate(candidate_tag, &candidate)) {
266 *error = "Failed to parse candidates";
267 return false;
268 }
269 candidates.push_back(candidate);
270 }
271 }
272
273 return true;
274}
275
[email protected]137e7cd2012-02-26 00:28:07276scoped_ptr<buzz::XmlElement> JingleMessage::ToXml() const {
[email protected]da930e12011-08-19 23:31:43277 scoped_ptr<XmlElement> root(
278 new XmlElement(QName("jabber:client", "iq"), true));
279
280 DCHECK(!to.empty());
281 root->AddAttr(QName(kEmptyNamespace, "to"), to);
282 if (!from.empty())
283 root->AddAttr(QName(kEmptyNamespace, "from"), from);
284 root->SetAttr(QName(kEmptyNamespace, "type"), "set");
285
286 XmlElement* jingle_tag =
287 new XmlElement(QName(kJingleNamespace, "jingle"), true);
288 root->AddElement(jingle_tag);
289 jingle_tag->AddAttr(QName(kEmptyNamespace, "sid"), sid);
290
[email protected]e3f03d02011-09-27 20:10:51291 const char* action_attr = ValueToName(
292 kActionTypes, arraysize(kActionTypes), action);
293 if (!action_attr)
294 LOG(FATAL) << "Invalid action value " << action;
[email protected]da930e12011-08-19 23:31:43295 jingle_tag->AddAttr(QName(kEmptyNamespace, "action"), action_attr);
296
[email protected]1bc9c7c2011-12-14 00:13:39297 if (action == SESSION_INFO) {
298 if (info.get())
299 jingle_tag->AddElement(new XmlElement(*info.get()));
[email protected]cff27642012-02-23 12:06:19300 return root.Pass();
[email protected]1bc9c7c2011-12-14 00:13:39301 }
302
[email protected]da930e12011-08-19 23:31:43303 if (action == SESSION_INITIATE)
304 jingle_tag->AddAttr(QName(kEmptyNamespace, "initiator"), from);
305
[email protected]e3f03d02011-09-27 20:10:51306 if (reason != UNKNOWN_REASON) {
[email protected]da930e12011-08-19 23:31:43307 XmlElement* reason_tag = new XmlElement(QName(kJingleNamespace, "reason"));
308 jingle_tag->AddElement(reason_tag);
[email protected]e3f03d02011-09-27 20:10:51309 const char* reason_string =
310 ValueToName(kReasons, arraysize(kReasons), reason);
311 if (reason_string == NULL)
312 LOG(FATAL) << "Invalid reason: " << reason;
313 reason_tag->AddElement(new XmlElement(
314 QName(kJingleNamespace, reason_string)));
315 }
[email protected]da930e12011-08-19 23:31:43316
[email protected]e3f03d02011-09-27 20:10:51317 if (action != SESSION_TERMINATE) {
[email protected]da930e12011-08-19 23:31:43318 XmlElement* content_tag =
319 new XmlElement(QName(kJingleNamespace, "content"));
320 jingle_tag->AddElement(content_tag);
321
322 content_tag->AddAttr(QName(kEmptyNamespace, "name"),
323 ContentDescription::kChromotingContentName);
324 content_tag->AddAttr(QName(kEmptyNamespace, "creator"), "initiator");
325
326 if (description.get())
327 content_tag->AddElement(description->ToXml());
328
329 XmlElement* transport_tag =
330 new XmlElement(QName(kP2PTransportNamespace, "transport"), true);
331 content_tag->AddElement(transport_tag);
332 for (std::list<cricket::Candidate>::const_iterator it = candidates.begin();
333 it != candidates.end(); ++it) {
334 transport_tag->AddElement(FormatCandidate(*it));
335 }
336 }
337
[email protected]cff27642012-02-23 12:06:19338 return root.Pass();
[email protected]da930e12011-08-19 23:31:43339}
340
341JingleMessageReply::JingleMessageReply()
342 : type(REPLY_RESULT),
343 error_type(NONE) {
344}
345
346JingleMessageReply::JingleMessageReply(ErrorType error)
[email protected]b0fffb02012-02-17 21:59:43347 : type(error != NONE ? REPLY_ERROR : REPLY_RESULT),
[email protected]da930e12011-08-19 23:31:43348 error_type(error) {
349}
350
351JingleMessageReply::JingleMessageReply(ErrorType error,
352 const std::string& text_value)
353 : type(REPLY_ERROR),
354 error_type(error),
355 text(text_value) {
356}
357
358JingleMessageReply::~JingleMessageReply() { }
359
[email protected]cff27642012-02-23 12:06:19360scoped_ptr<buzz::XmlElement> JingleMessageReply::ToXml(
[email protected]da930e12011-08-19 23:31:43361 const buzz::XmlElement* request_stanza) const {
[email protected]cff27642012-02-23 12:06:19362 scoped_ptr<XmlElement> iq(
363 new XmlElement(QName(kJabberNamespace, "iq"), true));
[email protected]da930e12011-08-19 23:31:43364 iq->SetAttr(QName(kEmptyNamespace, "to"),
365 request_stanza->Attr(QName(kEmptyNamespace, "from")));
366 iq->SetAttr(QName(kEmptyNamespace, "id"),
367 request_stanza->Attr(QName(kEmptyNamespace, "id")));
368
369 if (type == REPLY_RESULT) {
370 iq->SetAttr(QName(kEmptyNamespace, "type"), "result");
[email protected]cff27642012-02-23 12:06:19371 return iq.Pass();
[email protected]da930e12011-08-19 23:31:43372 }
373
374 DCHECK_EQ(type, REPLY_ERROR);
375
376 iq->SetAttr(QName(kEmptyNamespace, "type"), "error");
377
378 for (const buzz::XmlElement* child = request_stanza->FirstElement();
379 child != NULL; child = child->NextElement()) {
380 iq->AddElement(new buzz::XmlElement(*child));
381 }
382
383 buzz::XmlElement* error =
384 new buzz::XmlElement(QName(kJabberNamespace, "error"));
385 iq->AddElement(error);
386
387 std::string type;
388 std::string error_text;
[email protected]0116cf52011-11-01 02:44:32389 QName name("");
[email protected]da930e12011-08-19 23:31:43390 switch (error_type) {
391 case BAD_REQUEST:
392 type = "modify";
393 name = QName(kJabberNamespace, "bad-request");
394 break;
395 case NOT_IMPLEMENTED:
396 type = "cancel";
397 name = QName(kJabberNamespace, "feature-bad-request");
398 break;
399 case INVALID_SID:
400 type = "modify";
401 name = QName(kJabberNamespace, "item-not-found");
402 error_text = "Invalid SID";
403 break;
404 case UNEXPECTED_REQUEST:
405 type = "modify";
406 name = QName(kJabberNamespace, "unexpected-request");
407 break;
[email protected]1bc9c7c2011-12-14 00:13:39408 case UNSUPPORTED_INFO:
409 type = "modify";
410 name = QName(kJabberNamespace, "feature-not-implemented");
411 break;
[email protected]da930e12011-08-19 23:31:43412 default:
413 NOTREACHED();
414 }
415
416 if (!text.empty())
417 error_text = text;
418
419 error->SetAttr(QName(kEmptyNamespace, "type"), type);
420
421 // If the error name is not in the standard namespace, we have
422 // to first add some error from that namespace.
423 if (name.Namespace() != kJabberNamespace) {
424 error->AddElement(
425 new buzz::XmlElement(QName(kJabberNamespace, "undefined-condition")));
426 }
427 error->AddElement(new buzz::XmlElement(name));
428
429 if (!error_text.empty()) {
430 // It's okay to always use English here. This text is for
431 // debugging purposes only.
432 buzz::XmlElement* text_elem =
433 new buzz::XmlElement(QName(kJabberNamespace, "text"));
434 text_elem->SetAttr(QName(kXmlNamespace, "lang"), "en");
435 text_elem->SetBodyText(error_text);
436 error->AddElement(text_elem);
437 }
438
[email protected]cff27642012-02-23 12:06:19439 return iq.Pass();
[email protected]da930e12011-08-19 23:31:43440}
441
442} // namespace protocol
443} // namespace remoting