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/content_description.h"
6
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"
13
14using buzz::QName;
15using buzz::XmlElement;
16
17namespace remoting {
18namespace protocol {
19
20const char ContentDescription::kChromotingContentName[] = "chromoting";
21
22namespace {
23
24const char kDefaultNs[] = "";
25
26// Following constants are used to format session description in XML.
27const char kDescriptionTag[] = "description";
28const char kControlTag[] = "control";
29const char kEventTag[] = "event";
30const char kVideoTag[] = "video";
31const char kAudioTag[] = "audio";
32const char kDeprecatedResolutionTag[] = "initial-resolution";
33
34const char kTransportAttr[] = "transport";
35const char kVersionAttr[] = "version";
36const char kCodecAttr[] = "codec";
37const char kDeprecatedWidthAttr[] = "width";
38const char kDeprecatedHeightAttr[] = "height";
39
40const NameMapElement<ChannelConfig::TransportType> kTransports[] = {
41  { ChannelConfig::TRANSPORT_STREAM, "stream" },
42  { ChannelConfig::TRANSPORT_MUX_STREAM, "mux-stream" },
43  { ChannelConfig::TRANSPORT_DATAGRAM, "datagram" },
44  { ChannelConfig::TRANSPORT_NONE, "none" },
45};
46
47const NameMapElement<ChannelConfig::Codec> kCodecs[] = {
48  { ChannelConfig::CODEC_VERBATIM, "verbatim" },
49  { ChannelConfig::CODEC_VP8, "vp8" },
50  { ChannelConfig::CODEC_VP9, "vp9" },
51  { ChannelConfig::CODEC_ZIP, "zip" },
52  { ChannelConfig::CODEC_OPUS, "opus" },
53  { ChannelConfig::CODEC_SPEEX, "speex" },
54};
55
56// Format a channel configuration tag for chromotocol session description,
57// e.g. for video channel:
58//    <video transport="stream" version="1" codec="vp8" />
59XmlElement* FormatChannelConfig(const ChannelConfig& config,
60                                const std::string& tag_name) {
61  XmlElement* result = new XmlElement(
62      QName(kChromotingXmlNamespace, tag_name));
63
64  result->AddAttr(QName(kDefaultNs, kTransportAttr),
65                  ValueToName(kTransports, config.transport));
66
67  if (config.transport != ChannelConfig::TRANSPORT_NONE) {
68    result->AddAttr(QName(kDefaultNs, kVersionAttr),
69                    base::IntToString(config.version));
70
71    if (config.codec != ChannelConfig::CODEC_UNDEFINED) {
72      result->AddAttr(QName(kDefaultNs, kCodecAttr),
73                      ValueToName(kCodecs, config.codec));
74    }
75  }
76
77  return result;
78}
79
80// Returns false if the element is invalid.
81bool ParseChannelConfig(const XmlElement* element, bool codec_required,
82                        ChannelConfig* config) {
83  if (!NameToValue(
84          kTransports, element->Attr(QName(kDefaultNs, kTransportAttr)),
85          &config->transport)) {
86    return false;
87  }
88
89  // Version is not required when transport="none".
90  if (config->transport != ChannelConfig::TRANSPORT_NONE) {
91    if (!base::StringToInt(element->Attr(QName(kDefaultNs, kVersionAttr)),
92                           &config->version)) {
93      return false;
94    }
95
96    // Codec is not required when transport="none".
97    if (codec_required) {
98      if (!NameToValue(kCodecs, element->Attr(QName(kDefaultNs, kCodecAttr)),
99                       &config->codec)) {
100        return false;
101      }
102    } else {
103      config->codec = ChannelConfig::CODEC_UNDEFINED;
104    }
105  } else {
106    config->version = 0;
107    config->codec = ChannelConfig::CODEC_UNDEFINED;
108  }
109
110  return true;
111}
112
113}  // namespace
114
115ContentDescription::ContentDescription(
116    scoped_ptr<CandidateSessionConfig> config,
117    scoped_ptr<buzz::XmlElement> authenticator_message)
118    : candidate_config_(config.Pass()),
119      authenticator_message_(authenticator_message.Pass()) {
120}
121
122ContentDescription::~ContentDescription() { }
123
124ContentDescription* ContentDescription::Copy() const {
125  if (!candidate_config_.get() || !authenticator_message_.get()) {
126    return NULL;
127  }
128  scoped_ptr<XmlElement> message(new XmlElement(*authenticator_message_));
129  return new ContentDescription(candidate_config_->Clone(), message.Pass());
130}
131
132// ToXml() creates content description for chromoting session. The
133// description looks as follows:
134//   <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" />
139//     <authentication>
140//      Message created by Authenticator implementation.
141//     </authentication>
142//   </description>
143//
144XmlElement* ContentDescription::ToXml() const {
145  XmlElement* root = new XmlElement(
146      QName(kChromotingXmlNamespace, kDescriptionTag), true);
147
148  std::list<ChannelConfig>::const_iterator it;
149
150  for (it = config()->control_configs().begin();
151       it != config()->control_configs().end(); ++it) {
152    root->AddElement(FormatChannelConfig(*it, kControlTag));
153  }
154
155  for (it = config()->event_configs().begin();
156       it != config()->event_configs().end(); ++it) {
157    root->AddElement(FormatChannelConfig(*it, kEventTag));
158  }
159
160  for (it = config()->video_configs().begin();
161       it != config()->video_configs().end(); ++it) {
162    root->AddElement(FormatChannelConfig(*it, kVideoTag));
163  }
164
165  for (it = config()->audio_configs().begin();
166       it != config()->audio_configs().end(); ++it) {
167    ChannelConfig config = *it;
168    root->AddElement(FormatChannelConfig(config, kAudioTag));
169  }
170
171  // Older endpoints require an initial-resolution tag, but otherwise ignore it.
172  XmlElement* resolution_tag = new XmlElement(
173      QName(kChromotingXmlNamespace, kDeprecatedResolutionTag));
174  resolution_tag->AddAttr(QName(kDefaultNs, kDeprecatedWidthAttr), "640");
175  resolution_tag->AddAttr(QName(kDefaultNs, kDeprecatedHeightAttr), "480");
176  root->AddElement(resolution_tag);
177
178  if (authenticator_message_.get()) {
179    DCHECK(Authenticator::IsAuthenticatorMessage(authenticator_message_.get()));
180    root->AddElement(new XmlElement(*authenticator_message_));
181  }
182
183  return root;
184}
185
186// static
187// Adds the channel configs corresponding to |tag_name|,
188// found in |element|, to |configs|.
189bool ContentDescription::ParseChannelConfigs(
190    const XmlElement* const element,
191    const char tag_name[],
192    bool codec_required,
193    bool optional,
194    std::list<ChannelConfig>* const configs) {
195
196  QName tag(kChromotingXmlNamespace, tag_name);
197  const XmlElement* child = element->FirstNamed(tag);
198  while (child) {
199    ChannelConfig channel_config;
200    if (ParseChannelConfig(child, codec_required, &channel_config)) {
201      configs->push_back(channel_config);
202    }
203    child = child->NextNamed(tag);
204  }
205  if (optional && configs->empty()) {
206      // If there's no mention of the tag, implicitly assume disabled channel.
207      configs->push_back(ChannelConfig::None());
208  }
209  return true;
210}
211
212// static
213scoped_ptr<ContentDescription> ContentDescription::ParseXml(
214    const XmlElement* element) {
215  if (element->Name() != QName(kChromotingXmlNamespace, kDescriptionTag)) {
216    LOG(ERROR) << "Invalid description: " << element->Str();
217    return scoped_ptr<ContentDescription>();
218  }
219  scoped_ptr<CandidateSessionConfig> config(
220      CandidateSessionConfig::CreateEmpty());
221  if (!ParseChannelConfigs(element, kControlTag, false, false,
222                           config->mutable_control_configs()) ||
223      !ParseChannelConfigs(element, kEventTag, false, false,
224                           config->mutable_event_configs()) ||
225      !ParseChannelConfigs(element, kVideoTag, true, false,
226                           config->mutable_video_configs()) ||
227      !ParseChannelConfigs(element, kAudioTag, true, true,
228                           config->mutable_audio_configs())) {
229    return scoped_ptr<ContentDescription>();
230  }
231
232  scoped_ptr<XmlElement> authenticator_message;
233  const XmlElement* child = Authenticator::FindAuthenticatorMessage(element);
234  if (child)
235    authenticator_message.reset(new XmlElement(*child));
236
237  return scoped_ptr<ContentDescription>(
238      new ContentDescription(config.Pass(), authenticator_message.Pass()));
239}
240
241}  // namespace protocol
242}  // namespace remoting
243