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