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/base64.h"
8 #include "base/logging.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "remoting/base/constants.h"
11 #include "remoting/protocol/authenticator.h"
12 #include "remoting/protocol/name_value_map.h"
13 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
16 using buzz::XmlElement
;
21 const char ContentDescription::kChromotingContentName
[] = "chromoting";
25 const char kDefaultNs
[] = "";
27 // Following constants are used to format session description in XML.
28 const char kDescriptionTag
[] = "description";
29 const char kStandardIceTag
[] = "standard-ice";
30 const char kControlTag
[] = "control";
31 const char kEventTag
[] = "event";
32 const char kVideoTag
[] = "video";
33 const char kAudioTag
[] = "audio";
34 const char kVp9ExperimentTag
[] = "vp9-experiment";
35 const char kDeprecatedResolutionTag
[] = "initial-resolution";
36 const char kQuicConfigTag
[] = "quic-config";
38 const char kTransportAttr
[] = "transport";
39 const char kVersionAttr
[] = "version";
40 const char kCodecAttr
[] = "codec";
41 const char kDeprecatedWidthAttr
[] = "width";
42 const char kDeprecatedHeightAttr
[] = "height";
44 const NameMapElement
<ChannelConfig::TransportType
> kTransports
[] = {
45 { ChannelConfig::TRANSPORT_STREAM
, "stream" },
46 { ChannelConfig::TRANSPORT_MUX_STREAM
, "mux-stream" },
47 { ChannelConfig::TRANSPORT_QUIC_STREAM
, "quic-stream" },
48 { ChannelConfig::TRANSPORT_DATAGRAM
, "datagram" },
49 { ChannelConfig::TRANSPORT_NONE
, "none" },
52 const NameMapElement
<ChannelConfig::Codec
> kCodecs
[] = {
53 { ChannelConfig::CODEC_VERBATIM
, "verbatim" },
54 { ChannelConfig::CODEC_VP8
, "vp8" },
55 { ChannelConfig::CODEC_VP9
, "vp9" },
56 { ChannelConfig::CODEC_ZIP
, "zip" },
57 { ChannelConfig::CODEC_OPUS
, "opus" },
58 { ChannelConfig::CODEC_SPEEX
, "speex" },
61 // Format a channel configuration tag for chromotocol session description,
62 // e.g. for video channel:
63 // <video transport="stream" version="1" codec="vp8" />
64 XmlElement
* FormatChannelConfig(const ChannelConfig
& config
,
65 const std::string
& tag_name
) {
66 XmlElement
* result
= new XmlElement(
67 QName(kChromotingXmlNamespace
, tag_name
));
69 result
->AddAttr(QName(kDefaultNs
, kTransportAttr
),
70 ValueToName(kTransports
, config
.transport
));
72 if (config
.transport
!= ChannelConfig::TRANSPORT_NONE
) {
73 result
->AddAttr(QName(kDefaultNs
, kVersionAttr
),
74 base::IntToString(config
.version
));
76 if (config
.codec
!= ChannelConfig::CODEC_UNDEFINED
) {
77 result
->AddAttr(QName(kDefaultNs
, kCodecAttr
),
78 ValueToName(kCodecs
, config
.codec
));
85 // Returns false if the element is invalid.
86 bool ParseChannelConfig(const XmlElement
* element
, bool codec_required
,
87 ChannelConfig
* config
) {
89 kTransports
, element
->Attr(QName(kDefaultNs
, kTransportAttr
)),
90 &config
->transport
)) {
94 // Version is not required when transport="none".
95 if (config
->transport
!= ChannelConfig::TRANSPORT_NONE
) {
96 if (!base::StringToInt(element
->Attr(QName(kDefaultNs
, kVersionAttr
)),
101 // Codec is not required when transport="none".
102 if (codec_required
) {
103 if (!NameToValue(kCodecs
, element
->Attr(QName(kDefaultNs
, kCodecAttr
)),
108 config
->codec
= ChannelConfig::CODEC_UNDEFINED
;
112 config
->codec
= ChannelConfig::CODEC_UNDEFINED
;
120 ContentDescription::ContentDescription(
121 scoped_ptr
<CandidateSessionConfig
> config
,
122 scoped_ptr
<buzz::XmlElement
> authenticator_message
,
123 const std::string
& quic_config_message
)
124 : candidate_config_(config
.Pass()),
125 authenticator_message_(authenticator_message
.Pass()),
126 quic_config_message_(quic_config_message
) {
129 ContentDescription::~ContentDescription() { }
131 // ToXml() creates content description for chromoting session. The
132 // description looks as follows:
133 // <description xmlns="google:remoting">
135 // <control transport="stream" version="1" />
136 // <event transport="datagram" version="1" />
137 // <video transport="stream" codec="vp8" version="1" />
138 // <audio transport="stream" codec="opus" version="1" />
140 // Message created by Authenticator implementation.
144 XmlElement
* ContentDescription::ToXml() const {
145 XmlElement
* root
= new XmlElement(
146 QName(kChromotingXmlNamespace
, kDescriptionTag
), true);
148 if (config()->standard_ice()) {
150 new buzz::XmlElement(QName(kChromotingXmlNamespace
, kStandardIceTag
)));
153 for (const ChannelConfig
& channel_config
: config()->control_configs()) {
154 root
->AddElement(FormatChannelConfig(channel_config
, kControlTag
));
157 for (const ChannelConfig
& channel_config
: config()->event_configs()) {
158 root
->AddElement(FormatChannelConfig(channel_config
, kEventTag
));
161 for (const ChannelConfig
& channel_config
: config()->video_configs()) {
162 root
->AddElement(FormatChannelConfig(channel_config
, kVideoTag
));
165 for (const ChannelConfig
& channel_config
: config()->audio_configs()) {
166 root
->AddElement(FormatChannelConfig(channel_config
, kAudioTag
));
169 // Older endpoints require an initial-resolution tag, but otherwise ignore it.
170 XmlElement
* resolution_tag
= new XmlElement(
171 QName(kChromotingXmlNamespace
, kDeprecatedResolutionTag
));
172 resolution_tag
->AddAttr(QName(kDefaultNs
, kDeprecatedWidthAttr
), "640");
173 resolution_tag
->AddAttr(QName(kDefaultNs
, kDeprecatedHeightAttr
), "480");
174 root
->AddElement(resolution_tag
);
176 if (authenticator_message_
) {
177 DCHECK(Authenticator::IsAuthenticatorMessage(authenticator_message_
.get()));
178 root
->AddElement(new XmlElement(*authenticator_message_
));
181 if (!quic_config_message_
.empty()) {
182 XmlElement
* quic_config_tag
=
183 new XmlElement(QName(kChromotingXmlNamespace
, kQuicConfigTag
));
184 root
->AddElement(quic_config_tag
);
185 std::string config_base64
;
186 base::Base64Encode(quic_config_message_
, &config_base64
);
187 quic_config_tag
->SetBodyText(config_base64
);
190 if (config()->vp9_experiment_enabled()) {
192 new XmlElement(QName(kChromotingXmlNamespace
, kVp9ExperimentTag
)));
199 // Adds the channel configs corresponding to |tag_name|,
200 // found in |element|, to |configs|.
201 bool ContentDescription::ParseChannelConfigs(
202 const XmlElement
* const element
,
203 const char tag_name
[],
206 std::list
<ChannelConfig
>* const configs
) {
207 QName
tag(kChromotingXmlNamespace
, tag_name
);
208 const XmlElement
* child
= element
->FirstNamed(tag
);
210 ChannelConfig channel_config
;
211 if (ParseChannelConfig(child
, codec_required
, &channel_config
)) {
212 configs
->push_back(channel_config
);
214 child
= child
->NextNamed(tag
);
216 if (optional
&& configs
->empty()) {
217 // If there's no mention of the tag, implicitly assume disabled channel.
218 configs
->push_back(ChannelConfig::None());
224 scoped_ptr
<ContentDescription
> ContentDescription::ParseXml(
225 const XmlElement
* element
) {
226 if (element
->Name() != QName(kChromotingXmlNamespace
, kDescriptionTag
)) {
227 LOG(ERROR
) << "Invalid description: " << element
->Str();
230 scoped_ptr
<CandidateSessionConfig
> config(
231 CandidateSessionConfig::CreateEmpty());
233 config
->set_standard_ice(
234 element
->FirstNamed(QName(kChromotingXmlNamespace
, kStandardIceTag
)) !=
237 if (!ParseChannelConfigs(element
, kControlTag
, false, false,
238 config
->mutable_control_configs()) ||
239 !ParseChannelConfigs(element
, kEventTag
, false, false,
240 config
->mutable_event_configs()) ||
241 !ParseChannelConfigs(element
, kVideoTag
, true, false,
242 config
->mutable_video_configs()) ||
243 !ParseChannelConfigs(element
, kAudioTag
, true, true,
244 config
->mutable_audio_configs())) {
248 // Check if VP9 experiment is enabled.
249 if (element
->FirstNamed(QName(kChromotingXmlNamespace
, kVp9ExperimentTag
))) {
250 config
->set_vp9_experiment_enabled(true);
253 scoped_ptr
<XmlElement
> authenticator_message
;
254 const XmlElement
* child
= Authenticator::FindAuthenticatorMessage(element
);
256 authenticator_message
.reset(new XmlElement(*child
));
258 std::string quic_config_message
;
259 const XmlElement
* quic_config_tag
=
260 element
->FirstNamed(QName(kChromotingXmlNamespace
, kQuicConfigTag
));
261 if (quic_config_tag
) {
262 if (!base::Base64Decode(quic_config_tag
->BodyText(),
263 &quic_config_message
)) {
264 LOG(ERROR
) << "Failed to parse QUIC config.";
269 return make_scoped_ptr(new ContentDescription(
270 config
.Pass(), authenticator_message
.Pass(), quic_config_message
));
273 } // namespace protocol
274 } // namespace remoting