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