blob: bc8846a0e3833bef62fa6f3fd8ec1b554d4f1577 [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"
[email protected]eaf92532013-06-11 07:39:198#include "base/strings/string_number_conversions.h"
[email protected]da930e12011-08-19 23:31:439#include "remoting/base/constants.h"
10#include "remoting/protocol/content_description.h"
[email protected]a5284ef2012-08-15 01:43:0011#include "remoting/protocol/name_value_map.h"
niklaseaf726f12014-09-08 18:43:0212#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
[email protected]da930e12011-08-19 23:31:4313
14using buzz::QName;
15using buzz::XmlElement;
16
17namespace remoting {
18namespace protocol {
19
sergeyub4b09272015-04-23 18:59:1220namespace {
21
[email protected]da930e12011-08-19 23:31:4322const char kJabberNamespace[] = "jabber:client";
23const char kJingleNamespace[] = "urn:xmpp:jingle:1";
[email protected]da930e12011-08-19 23:31:4324
sergeyub4b09272015-04-23 18:59:1225// Namespace for transport messages when using standard ICE.
26const char kIceTransportNamespace[] = "google:remoting:ice";
[email protected]da930e12011-08-19 23:31:4327
sergeyu41c0b662016-01-28 23:34:5928const char kWebrtcTransportNamespace[] = "google:remoting:webrtc";
29
[email protected]da930e12011-08-19 23:31:4330const char kEmptyNamespace[] = "";
31const char kXmlNamespace[] = "http://www.w3.org/XML/1998/namespace";
32
[email protected]da930e12011-08-19 23:31:4333const int kPortMin = 1000;
34const int kPortMax = 65535;
35
[email protected]da9ccfb2012-01-28 00:34:4036const NameMapElement<JingleMessage::ActionType> kActionTypes[] = {
[email protected]e3f03d02011-09-27 20:10:5137 { JingleMessage::SESSION_INITIATE, "session-initiate" },
38 { JingleMessage::SESSION_ACCEPT, "session-accept" },
39 { JingleMessage::SESSION_TERMINATE, "session-terminate" },
[email protected]1bc9c7c2011-12-14 00:13:3940 { JingleMessage::SESSION_INFO, "session-info" },
[email protected]e3f03d02011-09-27 20:10:5141 { JingleMessage::TRANSPORT_INFO, "transport-info" },
42};
43
[email protected]da9ccfb2012-01-28 00:34:4044const NameMapElement<JingleMessage::Reason> kReasons[] = {
[email protected]e3f03d02011-09-27 20:10:5145 { JingleMessage::SUCCESS, "success" },
46 { JingleMessage::DECLINE, "decline" },
[email protected]a6b74c212012-03-27 02:38:1147 { JingleMessage::CANCEL, "cancel" },
sergeyuec77d8542015-11-03 22:31:0048 { JingleMessage::EXPIRED, "expired" },
[email protected]1f3dfe82011-11-15 03:45:4649 { JingleMessage::GENERAL_ERROR, "general-error" },
sergeyuec77d8542015-11-03 22:31:0050 { JingleMessage::FAILED_APPLICATION, "failed-application" },
[email protected]e3f03d02011-09-27 20:10:5151 { JingleMessage::INCOMPATIBLE_PARAMETERS, "incompatible-parameters" },
52};
53
sergeyub4b09272015-04-23 18:59:1254bool ParseIceCredentials(const buzz::XmlElement* element,
sergeyu0b6c1c8d2015-10-28 20:48:4355 IceTransportInfo::IceCredentials* credentials) {
sergeyub4b09272015-04-23 18:59:1256 DCHECK(element->Name() == QName(kIceTransportNamespace, "credentials"));
57
58 const std::string& channel = element->Attr(QName(kEmptyNamespace, "channel"));
59 const std::string& ufrag =
60 element->Attr(QName(kEmptyNamespace, "ufrag"));
61 const std::string& password =
62 element->Attr(QName(kEmptyNamespace, "password"));
63
64 if (channel.empty() || ufrag.empty() || password.empty()) {
65 return false;
66 }
67
68 credentials->channel = channel;
69 credentials->ufrag = ufrag;
70 credentials->password = password;
71
72 return true;
73}
74
75bool ParseIceCandidate(const buzz::XmlElement* element,
sergeyu0b6c1c8d2015-10-28 20:48:4376 IceTransportInfo::NamedCandidate* candidate) {
sergeyub4b09272015-04-23 18:59:1277 DCHECK(element->Name() == QName(kIceTransportNamespace, "candidate"));
78
79 const std::string& name = element->Attr(QName(kEmptyNamespace, "name"));
80 const std::string& foundation =
81 element->Attr(QName(kEmptyNamespace, "foundation"));
82 const std::string& address = element->Attr(QName(kEmptyNamespace, "address"));
83 const std::string& port_str = element->Attr(QName(kEmptyNamespace, "port"));
84 const std::string& type = element->Attr(QName(kEmptyNamespace, "type"));
85 const std::string& protocol =
86 element->Attr(QName(kEmptyNamespace, "protocol"));
87 const std::string& priority_str =
88 element->Attr(QName(kEmptyNamespace, "priority"));
89 const std::string& generation_str =
90 element->Attr(QName(kEmptyNamespace, "generation"));
91
92 int port;
93 unsigned priority;
94 int generation;
95 if (name.empty() || foundation.empty() || address.empty() ||
96 !base::StringToInt(port_str, &port) || port < kPortMin ||
97 port > kPortMax || type.empty() || protocol.empty() ||
98 !base::StringToUint(priority_str, &priority) ||
99 !base::StringToInt(generation_str, &generation)) {
100 return false;
101 }
102
103 candidate->name = name;
104
105 candidate->candidate.set_foundation(foundation);
106 candidate->candidate.set_address(rtc::SocketAddress(address, port));
107 candidate->candidate.set_type(type);
108 candidate->candidate.set_protocol(protocol);
109 candidate->candidate.set_priority(priority);
110 candidate->candidate.set_generation(generation);
111
112 return true;
113}
114
sergeyub4b09272015-04-23 18:59:12115XmlElement* FormatIceCredentials(
sergeyu0b6c1c8d2015-10-28 20:48:43116 const IceTransportInfo::IceCredentials& credentials) {
[email protected]da930e12011-08-19 23:31:43117 XmlElement* result =
sergeyub4b09272015-04-23 18:59:12118 new XmlElement(QName(kIceTransportNamespace, "credentials"));
119 result->SetAttr(QName(kEmptyNamespace, "channel"), credentials.channel);
120 result->SetAttr(QName(kEmptyNamespace, "ufrag"), credentials.ufrag);
121 result->SetAttr(QName(kEmptyNamespace, "password"), credentials.password);
122 return result;
123}
124
sergeyu0b6c1c8d2015-10-28 20:48:43125XmlElement* FormatIceCandidate(
126 const IceTransportInfo::NamedCandidate& candidate) {
sergeyub4b09272015-04-23 18:59:12127 XmlElement* result =
128 new XmlElement(QName(kIceTransportNamespace, "candidate"));
129 result->SetAttr(QName(kEmptyNamespace, "name"), candidate.name);
130 result->SetAttr(QName(kEmptyNamespace, "foundation"),
131 candidate.candidate.foundation());
132 result->SetAttr(QName(kEmptyNamespace, "address"),
133 candidate.candidate.address().ipaddr().ToString());
134 result->SetAttr(QName(kEmptyNamespace, "port"),
riceaac430822015-09-20 06:25:42135 base::UintToString(candidate.candidate.address().port()));
sergeyub4b09272015-04-23 18:59:12136 result->SetAttr(QName(kEmptyNamespace, "type"), candidate.candidate.type());
137 result->SetAttr(QName(kEmptyNamespace, "protocol"),
138 candidate.candidate.protocol());
139 result->SetAttr(QName(kEmptyNamespace, "priority"),
riceaac430822015-09-20 06:25:42140 base::UintToString(candidate.candidate.priority()));
sergeyub4b09272015-04-23 18:59:12141 result->SetAttr(QName(kEmptyNamespace, "generation"),
riceaac430822015-09-20 06:25:42142 base::UintToString(candidate.candidate.generation()));
sergeyub4b09272015-04-23 18:59:12143 return result;
144}
145
[email protected]da930e12011-08-19 23:31:43146} // namespace
147
sergeyu0b6c1c8d2015-10-28 20:48:43148IceTransportInfo::NamedCandidate::NamedCandidate(
[email protected]8bb746372012-04-26 04:20:12149 const std::string& name,
150 const cricket::Candidate& candidate)
151 : name(name),
152 candidate(candidate) {
153}
154
sergeyu0b6c1c8d2015-10-28 20:48:43155IceTransportInfo::IceCredentials::IceCredentials(std::string channel,
sergeyub4b09272015-04-23 18:59:12156 std::string ufrag,
157 std::string password)
158 : channel(channel), ufrag(ufrag), password(password) {
159}
160
[email protected]da930e12011-08-19 23:31:43161// static
162bool JingleMessage::IsJingleMessage(const buzz::XmlElement* stanza) {
[email protected]007b3f82013-04-09 08:46:45163 return stanza->Name() == QName(kJabberNamespace, "iq") &&
164 stanza->Attr(QName(std::string(), "type")) == "set" &&
sergeyuc5f104b2015-01-09 19:33:24165 stanza->FirstNamed(QName(kJingleNamespace, "jingle")) != nullptr;
[email protected]da930e12011-08-19 23:31:43166}
167
[email protected]137e7cd2012-02-26 00:28:07168// static
169std::string JingleMessage::GetActionName(ActionType action) {
[email protected]a5284ef2012-08-15 01:43:00170 return ValueToName(kActionTypes, action);
[email protected]137e7cd2012-02-26 00:28:07171}
172
sergeyu562defa2015-07-27 21:48:04173JingleMessage::JingleMessage() {}
[email protected]da930e12011-08-19 23:31:43174
sergeyub4b09272015-04-23 18:59:12175JingleMessage::JingleMessage(const std::string& to,
176 ActionType action,
177 const std::string& sid)
sergeyu562defa2015-07-27 21:48:04178 : to(to), action(action), sid(sid) {}
[email protected]da930e12011-08-19 23:31:43179
sergeyu562defa2015-07-27 21:48:04180JingleMessage::~JingleMessage() {}
[email protected]da930e12011-08-19 23:31:43181
182bool JingleMessage::ParseXml(const buzz::XmlElement* stanza,
183 std::string* error) {
[email protected]09100e32012-01-03 20:21:21184 if (!IsJingleMessage(stanza)) {
185 *error = "Not a jingle message";
186 return false;
187 }
188
[email protected]da930e12011-08-19 23:31:43189 const XmlElement* jingle_tag =
190 stanza->FirstNamed(QName(kJingleNamespace, "jingle"));
[email protected]a5284ef2012-08-15 01:43:00191 if (!jingle_tag) {
[email protected]da930e12011-08-19 23:31:43192 *error = "Not a jingle message";
193 return false;
194 }
195
196 from = stanza->Attr(QName(kEmptyNamespace, "from"));
197 to = stanza->Attr(QName(kEmptyNamespace, "to"));
[email protected]4c3d809e72013-06-21 14:40:40198 initiator = jingle_tag->Attr(QName(kEmptyNamespace, "initiator"));
[email protected]da930e12011-08-19 23:31:43199
200 std::string action_str = jingle_tag->Attr(QName(kEmptyNamespace, "action"));
201 if (action_str.empty()) {
202 *error = "action attribute is missing";
203 return false;
[email protected]e3f03d02011-09-27 20:10:51204 }
[email protected]a5284ef2012-08-15 01:43:00205 if (!NameToValue(kActionTypes, action_str, &action)) {
[email protected]da930e12011-08-19 23:31:43206 *error = "Unknown action " + action_str;
207 return false;
208 }
209
210 sid = jingle_tag->Attr(QName(kEmptyNamespace, "sid"));
211 if (sid.empty()) {
212 *error = "sid attribute is missing";
213 return false;
214 }
215
[email protected]1bc9c7c2011-12-14 00:13:39216 if (action == SESSION_INFO) {
217 // session-info messages may contain arbitrary information not
218 // defined by the Jingle protocol. We don't need to parse it.
219 const XmlElement* child = jingle_tag->FirstElement();
220 if (child) {
221 // session-info is allowed to be empty.
222 info.reset(new XmlElement(*child));
223 } else {
sergeyuc5f104b2015-01-09 19:33:24224 info.reset(nullptr);
[email protected]1bc9c7c2011-12-14 00:13:39225 }
226 return true;
227 }
228
[email protected]e3f03d02011-09-27 20:10:51229 const XmlElement* reason_tag =
230 jingle_tag->FirstNamed(QName(kJingleNamespace, "reason"));
231 if (reason_tag && reason_tag->FirstElement()) {
[email protected]a5284ef2012-08-15 01:43:00232 if (!NameToValue(kReasons, reason_tag->FirstElement()->Name().LocalPart(),
233 &reason)) {
234 reason = UNKNOWN_REASON;
235 }
[email protected]da930e12011-08-19 23:31:43236 }
237
[email protected]e3f03d02011-09-27 20:10:51238 if (action == SESSION_TERMINATE)
239 return true;
240
[email protected]da930e12011-08-19 23:31:43241 const XmlElement* content_tag =
242 jingle_tag->FirstNamed(QName(kJingleNamespace, "content"));
243 if (!content_tag) {
244 *error = "content tag is missing";
245 return false;
246 }
247
248 std::string content_name = content_tag->Attr(QName(kEmptyNamespace, "name"));
249 if (content_name != ContentDescription::kChromotingContentName) {
250 *error = "Unexpected content name: " + content_name;
251 return false;
252 }
253
sergeyu76391f02015-12-02 21:50:19254 const XmlElement* webrtc_transport_tag = content_tag->FirstNamed(
255 QName("google:remoting:webrtc", "transport"));
256 if (webrtc_transport_tag) {
257 transport_info.reset(new buzz::XmlElement(*webrtc_transport_tag));
258 }
259
sergeyuc5f104b2015-01-09 19:33:24260 description.reset(nullptr);
[email protected]da930e12011-08-19 23:31:43261 if (action == SESSION_INITIATE || action == SESSION_ACCEPT) {
262 const XmlElement* description_tag = content_tag->FirstNamed(
263 QName(kChromotingXmlNamespace, "description"));
264 if (!description_tag) {
265 *error = "Missing chromoting content description";
266 return false;
267 }
268
sergeyu76391f02015-12-02 21:50:19269 description = ContentDescription::ParseXml(description_tag,
270 webrtc_transport_tag != nullptr);
[email protected]da930e12011-08-19 23:31:43271 if (!description.get()) {
272 *error = "Failed to parse content description";
273 return false;
274 }
275 }
276
sergeyu76391f02015-12-02 21:50:19277 if (!webrtc_transport_tag) {
278 const XmlElement* ice_transport_tag = content_tag->FirstNamed(
279 QName(kIceTransportNamespace, "transport"));
280 if (ice_transport_tag) {
281 transport_info.reset(new buzz::XmlElement(*ice_transport_tag));
282 }
[email protected]da930e12011-08-19 23:31:43283 }
284
285 return true;
286}
287
dcheng0765c492016-04-06 22:41:53288std::unique_ptr<buzz::XmlElement> JingleMessage::ToXml() const {
289 std::unique_ptr<XmlElement> root(
[email protected]da930e12011-08-19 23:31:43290 new XmlElement(QName("jabber:client", "iq"), true));
291
292 DCHECK(!to.empty());
293 root->AddAttr(QName(kEmptyNamespace, "to"), to);
294 if (!from.empty())
295 root->AddAttr(QName(kEmptyNamespace, "from"), from);
296 root->SetAttr(QName(kEmptyNamespace, "type"), "set");
297
298 XmlElement* jingle_tag =
299 new XmlElement(QName(kJingleNamespace, "jingle"), true);
300 root->AddElement(jingle_tag);
301 jingle_tag->AddAttr(QName(kEmptyNamespace, "sid"), sid);
302
[email protected]a5284ef2012-08-15 01:43:00303 const char* action_attr = ValueToName(kActionTypes, action);
[email protected]e3f03d02011-09-27 20:10:51304 if (!action_attr)
305 LOG(FATAL) << "Invalid action value " << action;
[email protected]da930e12011-08-19 23:31:43306 jingle_tag->AddAttr(QName(kEmptyNamespace, "action"), action_attr);
307
[email protected]1bc9c7c2011-12-14 00:13:39308 if (action == SESSION_INFO) {
309 if (info.get())
310 jingle_tag->AddElement(new XmlElement(*info.get()));
sergeyuaa6fa2342015-12-22 23:26:48311 return root;
[email protected]1bc9c7c2011-12-14 00:13:39312 }
313
[email protected]da930e12011-08-19 23:31:43314 if (action == SESSION_INITIATE)
[email protected]4c3d809e72013-06-21 14:40:40315 jingle_tag->AddAttr(QName(kEmptyNamespace, "initiator"), initiator);
[email protected]da930e12011-08-19 23:31:43316
[email protected]e3f03d02011-09-27 20:10:51317 if (reason != UNKNOWN_REASON) {
[email protected]da930e12011-08-19 23:31:43318 XmlElement* reason_tag = new XmlElement(QName(kJingleNamespace, "reason"));
319 jingle_tag->AddElement(reason_tag);
[email protected]e3f03d02011-09-27 20:10:51320 const char* reason_string =
[email protected]a5284ef2012-08-15 01:43:00321 ValueToName(kReasons, reason);
322 if (!reason_string)
[email protected]e3f03d02011-09-27 20:10:51323 LOG(FATAL) << "Invalid reason: " << reason;
324 reason_tag->AddElement(new XmlElement(
325 QName(kJingleNamespace, reason_string)));
326 }
[email protected]da930e12011-08-19 23:31:43327
[email protected]e3f03d02011-09-27 20:10:51328 if (action != SESSION_TERMINATE) {
[email protected]da930e12011-08-19 23:31:43329 XmlElement* content_tag =
330 new XmlElement(QName(kJingleNamespace, "content"));
331 jingle_tag->AddElement(content_tag);
332
333 content_tag->AddAttr(QName(kEmptyNamespace, "name"),
334 ContentDescription::kChromotingContentName);
335 content_tag->AddAttr(QName(kEmptyNamespace, "creator"), "initiator");
336
sergeyu41c0b662016-01-28 23:34:59337 if (description)
[email protected]da930e12011-08-19 23:31:43338 content_tag->AddElement(description->ToXml());
339
sergeyu41c0b662016-01-28 23:34:59340 if (transport_info) {
sergeyu0b6c1c8d2015-10-28 20:48:43341 content_tag->AddElement(new XmlElement(*transport_info));
sergeyu41c0b662016-01-28 23:34:59342 } else if (description && description->config()->webrtc_supported()) {
343 content_tag->AddElement(
344 new XmlElement(QName(kWebrtcTransportNamespace, "transport")));
345 }
[email protected]da930e12011-08-19 23:31:43346 }
347
sergeyuaa6fa2342015-12-22 23:26:48348 return root;
[email protected]da930e12011-08-19 23:31:43349}
350
351JingleMessageReply::JingleMessageReply()
352 : type(REPLY_RESULT),
353 error_type(NONE) {
354}
355
356JingleMessageReply::JingleMessageReply(ErrorType error)
[email protected]b0fffb02012-02-17 21:59:43357 : type(error != NONE ? REPLY_ERROR : REPLY_RESULT),
[email protected]da930e12011-08-19 23:31:43358 error_type(error) {
359}
360
361JingleMessageReply::JingleMessageReply(ErrorType error,
362 const std::string& text_value)
363 : type(REPLY_ERROR),
364 error_type(error),
365 text(text_value) {
366}
367
368JingleMessageReply::~JingleMessageReply() { }
369
dcheng0765c492016-04-06 22:41:53370std::unique_ptr<buzz::XmlElement> JingleMessageReply::ToXml(
[email protected]da930e12011-08-19 23:31:43371 const buzz::XmlElement* request_stanza) const {
dcheng0765c492016-04-06 22:41:53372 std::unique_ptr<XmlElement> iq(
[email protected]cff27642012-02-23 12:06:19373 new XmlElement(QName(kJabberNamespace, "iq"), true));
[email protected]da930e12011-08-19 23:31:43374 iq->SetAttr(QName(kEmptyNamespace, "to"),
375 request_stanza->Attr(QName(kEmptyNamespace, "from")));
376 iq->SetAttr(QName(kEmptyNamespace, "id"),
377 request_stanza->Attr(QName(kEmptyNamespace, "id")));
378
379 if (type == REPLY_RESULT) {
380 iq->SetAttr(QName(kEmptyNamespace, "type"), "result");
sergeyuaa6fa2342015-12-22 23:26:48381 return iq;
[email protected]da930e12011-08-19 23:31:43382 }
383
384 DCHECK_EQ(type, REPLY_ERROR);
385
386 iq->SetAttr(QName(kEmptyNamespace, "type"), "error");
387
388 for (const buzz::XmlElement* child = request_stanza->FirstElement();
sergeyuc5f104b2015-01-09 19:33:24389 child != nullptr; child = child->NextElement()) {
[email protected]da930e12011-08-19 23:31:43390 iq->AddElement(new buzz::XmlElement(*child));
391 }
392
393 buzz::XmlElement* error =
394 new buzz::XmlElement(QName(kJabberNamespace, "error"));
395 iq->AddElement(error);
396
397 std::string type;
398 std::string error_text;
[email protected]007b3f82013-04-09 08:46:45399 QName name;
[email protected]da930e12011-08-19 23:31:43400 switch (error_type) {
401 case BAD_REQUEST:
402 type = "modify";
403 name = QName(kJabberNamespace, "bad-request");
404 break;
405 case NOT_IMPLEMENTED:
406 type = "cancel";
407 name = QName(kJabberNamespace, "feature-bad-request");
408 break;
409 case INVALID_SID:
410 type = "modify";
411 name = QName(kJabberNamespace, "item-not-found");
412 error_text = "Invalid SID";
413 break;
414 case UNEXPECTED_REQUEST:
415 type = "modify";
416 name = QName(kJabberNamespace, "unexpected-request");
417 break;
[email protected]1bc9c7c2011-12-14 00:13:39418 case UNSUPPORTED_INFO:
419 type = "modify";
420 name = QName(kJabberNamespace, "feature-not-implemented");
421 break;
[email protected]da930e12011-08-19 23:31:43422 default:
423 NOTREACHED();
424 }
425
426 if (!text.empty())
427 error_text = text;
428
429 error->SetAttr(QName(kEmptyNamespace, "type"), type);
430
431 // If the error name is not in the standard namespace, we have
432 // to first add some error from that namespace.
433 if (name.Namespace() != kJabberNamespace) {
434 error->AddElement(
435 new buzz::XmlElement(QName(kJabberNamespace, "undefined-condition")));
436 }
437 error->AddElement(new buzz::XmlElement(name));
438
439 if (!error_text.empty()) {
440 // It's okay to always use English here. This text is for
441 // debugging purposes only.
442 buzz::XmlElement* text_elem =
443 new buzz::XmlElement(QName(kJabberNamespace, "text"));
444 text_elem->SetAttr(QName(kXmlNamespace, "lang"), "en");
445 text_elem->SetBodyText(error_text);
446 error->AddElement(text_elem);
447 }
448
sergeyuaa6fa2342015-12-22 23:26:48449 return iq;
[email protected]da930e12011-08-19 23:31:43450}
451
sergeyu0b6c1c8d2015-10-28 20:48:43452IceTransportInfo::IceTransportInfo() {}
453IceTransportInfo::~IceTransportInfo() {}
454
455bool IceTransportInfo::ParseXml(
456 const buzz::XmlElement* element) {
457 if (element->Name() != QName(kIceTransportNamespace, "transport"))
458 return false;
459
460 ice_credentials.clear();
461 candidates.clear();
462
463 QName qn_credentials(kIceTransportNamespace, "credentials");
464 for (const XmlElement* credentials_tag = element->FirstNamed(qn_credentials);
465 credentials_tag;
466 credentials_tag = credentials_tag->NextNamed(qn_credentials)) {
467 IceTransportInfo::IceCredentials credentials;
468 if (!ParseIceCredentials(credentials_tag, &credentials))
469 return false;
470 ice_credentials.push_back(credentials);
471 }
472
473 QName qn_candidate(kIceTransportNamespace, "candidate");
474 for (const XmlElement* candidate_tag = element->FirstNamed(qn_candidate);
475 candidate_tag; candidate_tag = candidate_tag->NextNamed(qn_candidate)) {
476 IceTransportInfo::NamedCandidate candidate;
477 if (!ParseIceCandidate(candidate_tag, &candidate))
478 return false;
479 candidates.push_back(candidate);
480 }
481
482 return true;
483}
484
dcheng0765c492016-04-06 22:41:53485std::unique_ptr<buzz::XmlElement> IceTransportInfo::ToXml() const {
486 std::unique_ptr<buzz::XmlElement> result(
sergeyu0b6c1c8d2015-10-28 20:48:43487 new XmlElement(QName(kIceTransportNamespace, "transport"), true));
488 for (const IceCredentials& credentials : ice_credentials) {
489 result->AddElement(FormatIceCredentials(credentials));
490 }
491 for (const NamedCandidate& candidate : candidates) {
492 result->AddElement(FormatIceCandidate(candidate));
493 }
sergeyuaa6fa2342015-12-22 23:26:48494 return result;
sergeyu0b6c1c8d2015-10-28 20:48:43495}
496
[email protected]da930e12011-08-19 23:31:43497} // namespace protocol
498} // namespace remoting