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