Home | History | Annotate | Download | only in protocol
      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 
     14 using buzz::QName;
     15 using buzz::XmlElement;
     16 
     17 namespace remoting {
     18 namespace protocol {
     19 
     20 const char ContentDescription::kChromotingContentName[] = "chromoting";
     21 
     22 namespace {
     23 
     24 const char kDefaultNs[] = "";
     25 
     26 // Following constants are used to format session description in XML.
     27 const char kDescriptionTag[] = "description";
     28 const char kControlTag[] = "control";
     29 const char kEventTag[] = "event";
     30 const char kVideoTag[] = "video";
     31 const char kAudioTag[] = "audio";
     32 const char kDeprecatedResolutionTag[] = "initial-resolution";
     33 
     34 const char kTransportAttr[] = "transport";
     35 const char kVersionAttr[] = "version";
     36 const char kCodecAttr[] = "codec";
     37 const char kDeprecatedWidthAttr[] = "width";
     38 const char kDeprecatedHeightAttr[] = "height";
     39 
     40 const 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 
     47 const 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" />
     59 XmlElement* 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.
     81 bool 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 
    115 ContentDescription::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 
    122 ContentDescription::~ContentDescription() { }
    123 
    124 ContentDescription* 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 //
    144 XmlElement* 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|.
    189 bool 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
    213 scoped_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