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/jingle_messages.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/content_description.h"
     11 #include "remoting/protocol/name_value_map.h"
     12 #include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
     13 
     14 using buzz::QName;
     15 using buzz::XmlElement;
     16 
     17 namespace remoting {
     18 namespace protocol {
     19 
     20 const char kJabberNamespace[] = "jabber:client";
     21 const char kJingleNamespace[] = "urn:xmpp:jingle:1";
     22 const char kP2PTransportNamespace[] = "http://www.google.com/transport/p2p";
     23 
     24 namespace {
     25 
     26 const char kEmptyNamespace[] = "";
     27 const char kXmlNamespace[] = "http://www.w3.org/XML/1998/namespace";
     28 
     29 const int kPortMin = 1000;
     30 const int kPortMax = 65535;
     31 
     32 const NameMapElement<JingleMessage::ActionType> kActionTypes[] = {
     33   { JingleMessage::SESSION_INITIATE, "session-initiate" },
     34   { JingleMessage::SESSION_ACCEPT, "session-accept" },
     35   { JingleMessage::SESSION_TERMINATE, "session-terminate" },
     36   { JingleMessage::SESSION_INFO, "session-info" },
     37   { JingleMessage::TRANSPORT_INFO, "transport-info" },
     38 };
     39 
     40 const NameMapElement<JingleMessage::Reason> kReasons[] = {
     41   { JingleMessage::SUCCESS, "success" },
     42   { JingleMessage::DECLINE, "decline" },
     43   { JingleMessage::CANCEL, "cancel" },
     44   { JingleMessage::GENERAL_ERROR, "general-error" },
     45   { JingleMessage::INCOMPATIBLE_PARAMETERS, "incompatible-parameters" },
     46 };
     47 
     48 bool ParseCandidate(const buzz::XmlElement* element,
     49                     JingleMessage::NamedCandidate* candidate) {
     50   DCHECK(element->Name() == QName(kP2PTransportNamespace, "candidate"));
     51 
     52   const std::string& name = element->Attr(QName(kEmptyNamespace, "name"));
     53   const std::string& address = element->Attr(QName(kEmptyNamespace, "address"));
     54   const std::string& port_str = element->Attr(QName(kEmptyNamespace, "port"));
     55   const std::string& type = element->Attr(QName(kEmptyNamespace, "type"));
     56   const std::string& protocol =
     57       element->Attr(QName(kEmptyNamespace, "protocol"));
     58   const std::string& username =
     59       element->Attr(QName(kEmptyNamespace, "username"));
     60   const std::string& password =
     61       element->Attr(QName(kEmptyNamespace, "password"));
     62   const std::string& preference_str =
     63       element->Attr(QName(kEmptyNamespace, "preference"));
     64   const std::string& generation_str =
     65       element->Attr(QName(kEmptyNamespace, "generation"));
     66 
     67   int port;
     68   double preference;
     69   int generation;
     70   if (name.empty() || address.empty() || !base::StringToInt(port_str, &port) ||
     71       port < kPortMin || port > kPortMax || type.empty() || protocol.empty() ||
     72       username.empty() || password.empty() ||
     73       !base::StringToDouble(preference_str, &preference) ||
     74       !base::StringToInt(generation_str, &generation)) {
     75     return false;
     76   }
     77 
     78   candidate->name = name;
     79 
     80   candidate->candidate.set_address(talk_base::SocketAddress(address, port));
     81   candidate->candidate.set_type(type);
     82   candidate->candidate.set_protocol(protocol);
     83   candidate->candidate.set_username(username);
     84   candidate->candidate.set_password(password);
     85   candidate->candidate.set_preference(static_cast<float>(preference));
     86   candidate->candidate.set_generation(generation);
     87 
     88   return true;
     89 }
     90 
     91 XmlElement* FormatCandidate(const JingleMessage::NamedCandidate& candidate) {
     92   XmlElement* result =
     93       new XmlElement(QName(kP2PTransportNamespace, "candidate"));
     94   result->SetAttr(QName(kEmptyNamespace, "name"), candidate.name);
     95   result->SetAttr(QName(kEmptyNamespace, "address"),
     96                   candidate.candidate.address().ipaddr().ToString());
     97   result->SetAttr(QName(kEmptyNamespace, "port"),
     98                   base::IntToString(candidate.candidate.address().port()));
     99   result->SetAttr(QName(kEmptyNamespace, "type"), candidate.candidate.type());
    100   result->SetAttr(QName(kEmptyNamespace, "protocol"),
    101                   candidate.candidate.protocol());
    102   result->SetAttr(QName(kEmptyNamespace, "username"),
    103                   candidate.candidate.username());
    104   result->SetAttr(QName(kEmptyNamespace, "password"),
    105                   candidate.candidate.password());
    106   result->SetAttr(QName(kEmptyNamespace, "preference"),
    107                   base::DoubleToString(candidate.candidate.preference()));
    108   result->SetAttr(QName(kEmptyNamespace, "generation"),
    109                   base::IntToString(candidate.candidate.generation()));
    110   return result;
    111 }
    112 
    113 }  // namespace
    114 
    115 JingleMessage::NamedCandidate::NamedCandidate() {
    116 }
    117 
    118 JingleMessage::NamedCandidate::NamedCandidate(
    119     const std::string& name,
    120     const cricket::Candidate& candidate)
    121     : name(name),
    122       candidate(candidate) {
    123 }
    124 
    125 // static
    126 bool JingleMessage::IsJingleMessage(const buzz::XmlElement* stanza) {
    127   return stanza->Name() == QName(kJabberNamespace, "iq") &&
    128          stanza->Attr(QName(std::string(), "type")) == "set" &&
    129          stanza->FirstNamed(QName(kJingleNamespace, "jingle")) != NULL;
    130 }
    131 
    132 // static
    133 std::string JingleMessage::GetActionName(ActionType action) {
    134   return ValueToName(kActionTypes, action);
    135 }
    136 
    137 JingleMessage::JingleMessage()
    138     : action(UNKNOWN_ACTION),
    139       reason(UNKNOWN_REASON) {
    140 }
    141 
    142 JingleMessage::JingleMessage(
    143     const std::string& to_value,
    144     ActionType action_value,
    145     const std::string& sid_value)
    146     : to(to_value),
    147       action(action_value),
    148       sid(sid_value),
    149       reason(UNKNOWN_REASON) {
    150 }
    151 
    152 JingleMessage::~JingleMessage() {
    153 }
    154 
    155 bool JingleMessage::ParseXml(const buzz::XmlElement* stanza,
    156                              std::string* error) {
    157   if (!IsJingleMessage(stanza)) {
    158     *error = "Not a jingle message";
    159     return false;
    160   }
    161 
    162   const XmlElement* jingle_tag =
    163       stanza->FirstNamed(QName(kJingleNamespace, "jingle"));
    164   if (!jingle_tag) {
    165     *error = "Not a jingle message";
    166     return false;
    167   }
    168 
    169   from = stanza->Attr(QName(kEmptyNamespace, "from"));
    170   to = stanza->Attr(QName(kEmptyNamespace, "to"));
    171   initiator = jingle_tag->Attr(QName(kEmptyNamespace, "initiator"));
    172 
    173   std::string action_str = jingle_tag->Attr(QName(kEmptyNamespace, "action"));
    174   if (action_str.empty()) {
    175     *error = "action attribute is missing";
    176     return false;
    177   }
    178   if (!NameToValue(kActionTypes, action_str, &action)) {
    179     *error = "Unknown action " + action_str;
    180     return false;
    181   }
    182 
    183   sid = jingle_tag->Attr(QName(kEmptyNamespace, "sid"));
    184   if (sid.empty()) {
    185     *error = "sid attribute is missing";
    186     return false;
    187   }
    188 
    189   if (action == SESSION_INFO) {
    190     // session-info messages may contain arbitrary information not
    191     // defined by the Jingle protocol. We don't need to parse it.
    192     const XmlElement* child = jingle_tag->FirstElement();
    193     if (child) {
    194       // session-info is allowed to be empty.
    195       info.reset(new XmlElement(*child));
    196     } else {
    197       info.reset(NULL);
    198     }
    199     return true;
    200   }
    201 
    202   const XmlElement* reason_tag =
    203       jingle_tag->FirstNamed(QName(kJingleNamespace, "reason"));
    204   if (reason_tag && reason_tag->FirstElement()) {
    205     if (!NameToValue(kReasons, reason_tag->FirstElement()->Name().LocalPart(),
    206                      &reason)) {
    207       reason = UNKNOWN_REASON;
    208     }
    209   }
    210 
    211   if (action == SESSION_TERMINATE)
    212     return true;
    213 
    214   const XmlElement* content_tag =
    215       jingle_tag->FirstNamed(QName(kJingleNamespace, "content"));
    216   if (!content_tag) {
    217     *error = "content tag is missing";
    218     return false;
    219   }
    220 
    221   std::string content_name = content_tag->Attr(QName(kEmptyNamespace, "name"));
    222   if (content_name != ContentDescription::kChromotingContentName) {
    223     *error = "Unexpected content name: " + content_name;
    224     return false;
    225   }
    226 
    227   description.reset(NULL);
    228   if (action == SESSION_INITIATE || action == SESSION_ACCEPT) {
    229     const XmlElement* description_tag = content_tag->FirstNamed(
    230         QName(kChromotingXmlNamespace, "description"));
    231     if (!description_tag) {
    232       *error = "Missing chromoting content description";
    233       return false;
    234     }
    235 
    236     description = ContentDescription::ParseXml(description_tag);
    237     if (!description.get()) {
    238       *error = "Failed to parse content description";
    239       return false;
    240     }
    241   }
    242 
    243   candidates.clear();
    244   const XmlElement* transport_tag = content_tag->FirstNamed(
    245       QName(kP2PTransportNamespace, "transport"));
    246   if (transport_tag) {
    247     QName qn_candidate(kP2PTransportNamespace, "candidate");
    248     for (const XmlElement* candidate_tag =
    249              transport_tag->FirstNamed(qn_candidate);
    250          candidate_tag != NULL;
    251          candidate_tag = candidate_tag->NextNamed(qn_candidate)) {
    252       NamedCandidate candidate;
    253       if (!ParseCandidate(candidate_tag, &candidate)) {
    254         *error = "Failed to parse candidates";
    255         return false;
    256       }
    257       candidates.push_back(candidate);
    258     }
    259   }
    260 
    261   return true;
    262 }
    263 
    264 scoped_ptr<buzz::XmlElement> JingleMessage::ToXml() const {
    265   scoped_ptr<XmlElement> root(
    266       new XmlElement(QName("jabber:client", "iq"), true));
    267 
    268   DCHECK(!to.empty());
    269   root->AddAttr(QName(kEmptyNamespace, "to"), to);
    270   if (!from.empty())
    271     root->AddAttr(QName(kEmptyNamespace, "from"), from);
    272   root->SetAttr(QName(kEmptyNamespace, "type"), "set");
    273 
    274   XmlElement* jingle_tag =
    275       new XmlElement(QName(kJingleNamespace, "jingle"), true);
    276   root->AddElement(jingle_tag);
    277   jingle_tag->AddAttr(QName(kEmptyNamespace, "sid"), sid);
    278 
    279   const char* action_attr = ValueToName(kActionTypes, action);
    280   if (!action_attr)
    281     LOG(FATAL) << "Invalid action value " << action;
    282   jingle_tag->AddAttr(QName(kEmptyNamespace, "action"), action_attr);
    283 
    284   if (action == SESSION_INFO) {
    285     if (info.get())
    286       jingle_tag->AddElement(new XmlElement(*info.get()));
    287     return root.Pass();
    288   }
    289 
    290   if (action == SESSION_INITIATE)
    291     jingle_tag->AddAttr(QName(kEmptyNamespace, "initiator"), initiator);
    292 
    293   if (reason != UNKNOWN_REASON) {
    294     XmlElement* reason_tag = new XmlElement(QName(kJingleNamespace, "reason"));
    295     jingle_tag->AddElement(reason_tag);
    296     const char* reason_string =
    297         ValueToName(kReasons, reason);
    298     if (!reason_string)
    299       LOG(FATAL) << "Invalid reason: " << reason;
    300     reason_tag->AddElement(new XmlElement(
    301         QName(kJingleNamespace, reason_string)));
    302   }
    303 
    304   if (action != SESSION_TERMINATE) {
    305     XmlElement* content_tag =
    306         new XmlElement(QName(kJingleNamespace, "content"));
    307     jingle_tag->AddElement(content_tag);
    308 
    309     content_tag->AddAttr(QName(kEmptyNamespace, "name"),
    310                          ContentDescription::kChromotingContentName);
    311     content_tag->AddAttr(QName(kEmptyNamespace, "creator"), "initiator");
    312 
    313     if (description.get())
    314       content_tag->AddElement(description->ToXml());
    315 
    316     XmlElement* transport_tag =
    317         new XmlElement(QName(kP2PTransportNamespace, "transport"), true);
    318     content_tag->AddElement(transport_tag);
    319     for (std::list<NamedCandidate>::const_iterator it = candidates.begin();
    320          it != candidates.end(); ++it) {
    321       transport_tag->AddElement(FormatCandidate(*it));
    322     }
    323   }
    324 
    325   return root.Pass();
    326 }
    327 
    328 JingleMessageReply::JingleMessageReply()
    329     : type(REPLY_RESULT),
    330       error_type(NONE) {
    331 }
    332 
    333 JingleMessageReply::JingleMessageReply(ErrorType error)
    334     : type(error != NONE ? REPLY_ERROR : REPLY_RESULT),
    335       error_type(error) {
    336 }
    337 
    338 JingleMessageReply::JingleMessageReply(ErrorType error,
    339                                        const std::string& text_value)
    340     : type(REPLY_ERROR),
    341       error_type(error),
    342       text(text_value) {
    343 }
    344 
    345 JingleMessageReply::~JingleMessageReply() { }
    346 
    347 scoped_ptr<buzz::XmlElement> JingleMessageReply::ToXml(
    348     const buzz::XmlElement* request_stanza) const {
    349   scoped_ptr<XmlElement> iq(
    350       new XmlElement(QName(kJabberNamespace, "iq"), true));
    351   iq->SetAttr(QName(kEmptyNamespace, "to"),
    352               request_stanza->Attr(QName(kEmptyNamespace, "from")));
    353   iq->SetAttr(QName(kEmptyNamespace, "id"),
    354               request_stanza->Attr(QName(kEmptyNamespace, "id")));
    355 
    356   if (type == REPLY_RESULT) {
    357     iq->SetAttr(QName(kEmptyNamespace, "type"), "result");
    358     return iq.Pass();
    359   }
    360 
    361   DCHECK_EQ(type, REPLY_ERROR);
    362 
    363   iq->SetAttr(QName(kEmptyNamespace, "type"), "error");
    364 
    365   for (const buzz::XmlElement* child = request_stanza->FirstElement();
    366        child != NULL; child = child->NextElement()) {
    367     iq->AddElement(new buzz::XmlElement(*child));
    368   }
    369 
    370   buzz::XmlElement* error =
    371       new buzz::XmlElement(QName(kJabberNamespace, "error"));
    372   iq->AddElement(error);
    373 
    374   std::string type;
    375   std::string error_text;
    376   QName name;
    377   switch (error_type) {
    378     case BAD_REQUEST:
    379       type = "modify";
    380       name = QName(kJabberNamespace, "bad-request");
    381       break;
    382     case NOT_IMPLEMENTED:
    383       type = "cancel";
    384       name = QName(kJabberNamespace, "feature-bad-request");
    385       break;
    386     case INVALID_SID:
    387       type = "modify";
    388       name = QName(kJabberNamespace, "item-not-found");
    389       error_text = "Invalid SID";
    390       break;
    391     case UNEXPECTED_REQUEST:
    392       type = "modify";
    393       name = QName(kJabberNamespace, "unexpected-request");
    394       break;
    395     case UNSUPPORTED_INFO:
    396       type = "modify";
    397       name = QName(kJabberNamespace, "feature-not-implemented");
    398       break;
    399     default:
    400       NOTREACHED();
    401   }
    402 
    403   if (!text.empty())
    404     error_text = text;
    405 
    406   error->SetAttr(QName(kEmptyNamespace, "type"), type);
    407 
    408   // If the error name is not in the standard namespace, we have
    409   // to first add some error from that namespace.
    410   if (name.Namespace() != kJabberNamespace) {
    411     error->AddElement(
    412         new buzz::XmlElement(QName(kJabberNamespace, "undefined-condition")));
    413   }
    414   error->AddElement(new buzz::XmlElement(name));
    415 
    416   if (!error_text.empty()) {
    417     // It's okay to always use English here. This text is for
    418     // debugging purposes only.
    419     buzz::XmlElement* text_elem =
    420             new buzz::XmlElement(QName(kJabberNamespace, "text"));
    421     text_elem->SetAttr(QName(kXmlNamespace, "lang"), "en");
    422     text_elem->SetBodyText(error_text);
    423     error->AddElement(text_elem);
    424   }
    425 
    426   return iq.Pass();
    427 }
    428 
    429 }  // namespace protocol
    430 }  // namespace remoting
    431