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.
5 #include "remoting/protocol/content_description.h"
7 #include "base/logging.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "remoting/base/constants.h"
10 #include "remoting/protocol/authenticator.h"
11 #include "remoting/protocol/name_value_map.h"
12 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
15 using buzz::XmlElement
;
20 const char ContentDescription::kChromotingContentName
[] = "chromoting";
24 const char kDefaultNs
[] = "";
26 // Following constants are used to format session description in XML.
27 const char kDescriptionTag
[] = "description";
28 const char kStandardIceTag
[] = "standard-ice";
29 const char kControlTag
[] = "control";
30 const char kEventTag
[] = "event";
31 const char kVideoTag
[] = "video";
32 const char kAudioTag
[] = "audio";
33 const char kVp9ExperimentTag
[] = "vp9-experiment";
34 const char kDeprecatedResolutionTag
[] = "initial-resolution";
36 const char kTransportAttr
[] = "transport";
37 const char kVersionAttr
[] = "version";
38 const char kCodecAttr
[] = "codec";
39 const char kDeprecatedWidthAttr
[] = "width";
40 const char kDeprecatedHeightAttr
[] = "height";
42 const NameMapElement
<ChannelConfig::TransportType
> kTransports
[] = {
43 { ChannelConfig::TRANSPORT_STREAM
, "stream" },
44 { ChannelConfig::TRANSPORT_MUX_STREAM
, "mux-stream" },
45 { ChannelConfig::TRANSPORT_DATAGRAM
, "datagram" },
46 { ChannelConfig::TRANSPORT_NONE
, "none" },
49 const NameMapElement
<ChannelConfig::Codec
> kCodecs
[] = {
50 { ChannelConfig::CODEC_VERBATIM
, "verbatim" },
51 { ChannelConfig::CODEC_VP8
, "vp8" },
52 { ChannelConfig::CODEC_VP9
, "vp9" },
53 { ChannelConfig::CODEC_ZIP
, "zip" },
54 { ChannelConfig::CODEC_OPUS
, "opus" },
55 { ChannelConfig::CODEC_SPEEX
, "speex" },
58 // Format a channel configuration tag for chromotocol session description,
59 // e.g. for video channel:
60 // <video transport="stream" version="1" codec="vp8" />
61 XmlElement
* FormatChannelConfig(const ChannelConfig
& config
,
62 const std::string
& tag_name
) {
63 XmlElement
* result
= new XmlElement(
64 QName(kChromotingXmlNamespace
, tag_name
));
66 result
->AddAttr(QName(kDefaultNs
, kTransportAttr
),
67 ValueToName(kTransports
, config
.transport
));
69 if (config
.transport
!= ChannelConfig::TRANSPORT_NONE
) {
70 result
->AddAttr(QName(kDefaultNs
, kVersionAttr
),
71 base::IntToString(config
.version
));
73 if (config
.codec
!= ChannelConfig::CODEC_UNDEFINED
) {
74 result
->AddAttr(QName(kDefaultNs
, kCodecAttr
),
75 ValueToName(kCodecs
, config
.codec
));
82 // Returns false if the element is invalid.
83 bool ParseChannelConfig(const XmlElement
* element
, bool codec_required
,
84 ChannelConfig
* config
) {
86 kTransports
, element
->Attr(QName(kDefaultNs
, kTransportAttr
)),
87 &config
->transport
)) {
91 // Version is not required when transport="none".
92 if (config
->transport
!= ChannelConfig::TRANSPORT_NONE
) {
93 if (!base::StringToInt(element
->Attr(QName(kDefaultNs
, kVersionAttr
)),
98 // Codec is not required when transport="none".
100 if (!NameToValue(kCodecs
, element
->Attr(QName(kDefaultNs
, kCodecAttr
)),
105 config
->codec
= ChannelConfig::CODEC_UNDEFINED
;
109 config
->codec
= ChannelConfig::CODEC_UNDEFINED
;
117 ContentDescription::ContentDescription(
118 scoped_ptr
<CandidateSessionConfig
> config
,
119 scoped_ptr
<buzz::XmlElement
> authenticator_message
)
120 : candidate_config_(config
.Pass()),
121 authenticator_message_(authenticator_message
.Pass()) {
124 ContentDescription::~ContentDescription() { }
126 // ToXml() creates content description for chromoting session. The
127 // description looks as follows:
128 // <description xmlns="google:remoting">
130 // <control transport="stream" version="1" />
131 // <event transport="datagram" version="1" />
132 // <video transport="stream" codec="vp8" version="1" />
133 // <audio transport="stream" codec="opus" version="1" />
135 // Message created by Authenticator implementation.
139 XmlElement
* ContentDescription::ToXml() const {
140 XmlElement
* root
= new XmlElement(
141 QName(kChromotingXmlNamespace
, kDescriptionTag
), true);
143 if (config()->standard_ice()) {
145 new buzz::XmlElement(QName(kChromotingXmlNamespace
, kStandardIceTag
)));
148 for (const ChannelConfig
& channel_config
: config()->control_configs()) {
149 root
->AddElement(FormatChannelConfig(channel_config
, kControlTag
));
152 for (const ChannelConfig
& channel_config
: config()->event_configs()) {
153 root
->AddElement(FormatChannelConfig(channel_config
, kEventTag
));
156 for (const ChannelConfig
& channel_config
: config()->video_configs()) {
157 root
->AddElement(FormatChannelConfig(channel_config
, kVideoTag
));
160 for (const ChannelConfig
& channel_config
: config()->audio_configs()) {
161 root
->AddElement(FormatChannelConfig(channel_config
, kAudioTag
));
164 // Older endpoints require an initial-resolution tag, but otherwise ignore it.
165 XmlElement
* resolution_tag
= new XmlElement(
166 QName(kChromotingXmlNamespace
, kDeprecatedResolutionTag
));
167 resolution_tag
->AddAttr(QName(kDefaultNs
, kDeprecatedWidthAttr
), "640");
168 resolution_tag
->AddAttr(QName(kDefaultNs
, kDeprecatedHeightAttr
), "480");
169 root
->AddElement(resolution_tag
);
171 if (authenticator_message_
.get()) {
172 DCHECK(Authenticator::IsAuthenticatorMessage(authenticator_message_
.get()));
173 root
->AddElement(new XmlElement(*authenticator_message_
));
176 if (config()->vp9_experiment_enabled()) {
178 new XmlElement(QName(kChromotingXmlNamespace
, kVp9ExperimentTag
)));
185 // Adds the channel configs corresponding to |tag_name|,
186 // found in |element|, to |configs|.
187 bool ContentDescription::ParseChannelConfigs(
188 const XmlElement
* const element
,
189 const char tag_name
[],
192 std::list
<ChannelConfig
>* const configs
) {
193 QName
tag(kChromotingXmlNamespace
, tag_name
);
194 const XmlElement
* child
= element
->FirstNamed(tag
);
196 ChannelConfig channel_config
;
197 if (ParseChannelConfig(child
, codec_required
, &channel_config
)) {
198 configs
->push_back(channel_config
);
200 child
= child
->NextNamed(tag
);
202 if (optional
&& configs
->empty()) {
203 // If there's no mention of the tag, implicitly assume disabled channel.
204 configs
->push_back(ChannelConfig::None());
210 scoped_ptr
<ContentDescription
> ContentDescription::ParseXml(
211 const XmlElement
* element
) {
212 if (element
->Name() != QName(kChromotingXmlNamespace
, kDescriptionTag
)) {
213 LOG(ERROR
) << "Invalid description: " << element
->Str();
216 scoped_ptr
<CandidateSessionConfig
> config(
217 CandidateSessionConfig::CreateEmpty());
219 config
->set_standard_ice(
220 element
->FirstNamed(QName(kChromotingXmlNamespace
, kStandardIceTag
)) !=
223 if (!ParseChannelConfigs(element
, kControlTag
, false, false,
224 config
->mutable_control_configs()) ||
225 !ParseChannelConfigs(element
, kEventTag
, false, false,
226 config
->mutable_event_configs()) ||
227 !ParseChannelConfigs(element
, kVideoTag
, true, false,
228 config
->mutable_video_configs()) ||
229 !ParseChannelConfigs(element
, kAudioTag
, true, true,
230 config
->mutable_audio_configs())) {
234 // Check if VP9 experiment is enabled.
235 if (element
->FirstNamed(QName(kChromotingXmlNamespace
, kVp9ExperimentTag
))) {
236 config
->set_vp9_experiment_enabled(true);
239 scoped_ptr
<XmlElement
> authenticator_message
;
240 const XmlElement
* child
= Authenticator::FindAuthenticatorMessage(element
);
242 authenticator_message
.reset(new XmlElement(*child
));
244 return make_scoped_ptr(
245 new ContentDescription(config
.Pass(), authenticator_message
.Pass()));
248 } // namespace protocol
249 } // namespace remoting