blob: 8c8c1640bfd0cf223720c7b8c84a6c8c4a81331b [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/protocol/jingle_messages.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "remoting/base/constants.h"
#include "remoting/protocol/content_description.h"
#include "remoting/protocol/name_value_map.h"
#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
using buzz::QName;
using buzz::XmlElement;
namespace remoting {
namespace protocol {
namespace {
const char kJabberNamespace[] = "jabber:client";
const char kJingleNamespace[] = "urn:xmpp:jingle:1";
// Namespace for transport messages when using standard ICE.
const char kIceTransportNamespace[] = "google:remoting:ice";
const char kWebrtcTransportNamespace[] = "google:remoting:webrtc";
const char kEmptyNamespace[] = "";
const char kXmlNamespace[] = "http://www.w3.org/XML/1998/namespace";
const int kPortMin = 1000;
const int kPortMax = 65535;
const NameMapElement<SignalingAddress::Channel> kChannelTypes[] = {
{SignalingAddress::Channel::LCS, "lcs"},
{SignalingAddress::Channel::XMPP, "xmpp"},
};
const NameMapElement<JingleMessage::ActionType> kActionTypes[] = {
{ JingleMessage::SESSION_INITIATE, "session-initiate" },
{ JingleMessage::SESSION_ACCEPT, "session-accept" },
{ JingleMessage::SESSION_TERMINATE, "session-terminate" },
{ JingleMessage::SESSION_INFO, "session-info" },
{ JingleMessage::TRANSPORT_INFO, "transport-info" },
};
const NameMapElement<JingleMessage::Reason> kReasons[] = {
{ JingleMessage::SUCCESS, "success" },
{ JingleMessage::DECLINE, "decline" },
{ JingleMessage::CANCEL, "cancel" },
{ JingleMessage::EXPIRED, "expired" },
{ JingleMessage::GENERAL_ERROR, "general-error" },
{ JingleMessage::FAILED_APPLICATION, "failed-application" },
{ JingleMessage::INCOMPATIBLE_PARAMETERS, "incompatible-parameters" },
};
bool ParseIceCredentials(const buzz::XmlElement* element,
IceTransportInfo::IceCredentials* credentials) {
DCHECK(element->Name() == QName(kIceTransportNamespace, "credentials"));
const std::string& channel = element->Attr(QName(kEmptyNamespace, "channel"));
const std::string& ufrag =
element->Attr(QName(kEmptyNamespace, "ufrag"));
const std::string& password =
element->Attr(QName(kEmptyNamespace, "password"));
if (channel.empty() || ufrag.empty() || password.empty()) {
return false;
}
credentials->channel = channel;
credentials->ufrag = ufrag;
credentials->password = password;
return true;
}
bool ParseIceCandidate(const buzz::XmlElement* element,
IceTransportInfo::NamedCandidate* candidate) {
DCHECK(element->Name() == QName(kIceTransportNamespace, "candidate"));
const std::string& name = element->Attr(QName(kEmptyNamespace, "name"));
const std::string& foundation =
element->Attr(QName(kEmptyNamespace, "foundation"));
const std::string& address = element->Attr(QName(kEmptyNamespace, "address"));
const std::string& port_str = element->Attr(QName(kEmptyNamespace, "port"));
const std::string& type = element->Attr(QName(kEmptyNamespace, "type"));
const std::string& protocol =
element->Attr(QName(kEmptyNamespace, "protocol"));
const std::string& priority_str =
element->Attr(QName(kEmptyNamespace, "priority"));
const std::string& generation_str =
element->Attr(QName(kEmptyNamespace, "generation"));
int port;
unsigned priority;
int generation;
if (name.empty() || foundation.empty() || address.empty() ||
!base::StringToInt(port_str, &port) || port < kPortMin ||
port > kPortMax || type.empty() || protocol.empty() ||
!base::StringToUint(priority_str, &priority) ||
!base::StringToInt(generation_str, &generation)) {
return false;
}
candidate->name = name;
candidate->candidate.set_foundation(foundation);
candidate->candidate.set_address(rtc::SocketAddress(address, port));
candidate->candidate.set_type(type);
candidate->candidate.set_protocol(protocol);
candidate->candidate.set_priority(priority);
candidate->candidate.set_generation(generation);
return true;
}
XmlElement* FormatIceCredentials(
const IceTransportInfo::IceCredentials& credentials) {
XmlElement* result =
new XmlElement(QName(kIceTransportNamespace, "credentials"));
result->SetAttr(QName(kEmptyNamespace, "channel"), credentials.channel);
result->SetAttr(QName(kEmptyNamespace, "ufrag"), credentials.ufrag);
result->SetAttr(QName(kEmptyNamespace, "password"), credentials.password);
return result;
}
XmlElement* FormatIceCandidate(
const IceTransportInfo::NamedCandidate& candidate) {
XmlElement* result =
new XmlElement(QName(kIceTransportNamespace, "candidate"));
result->SetAttr(QName(kEmptyNamespace, "name"), candidate.name);
result->SetAttr(QName(kEmptyNamespace, "foundation"),
candidate.candidate.foundation());
result->SetAttr(QName(kEmptyNamespace, "address"),
candidate.candidate.address().ipaddr().ToString());
result->SetAttr(QName(kEmptyNamespace, "port"),
base::UintToString(candidate.candidate.address().port()));
result->SetAttr(QName(kEmptyNamespace, "type"), candidate.candidate.type());
result->SetAttr(QName(kEmptyNamespace, "protocol"),
candidate.candidate.protocol());
result->SetAttr(QName(kEmptyNamespace, "priority"),
base::UintToString(candidate.candidate.priority()));
result->SetAttr(QName(kEmptyNamespace, "generation"),
base::UintToString(candidate.candidate.generation()));
return result;
}
// Represents the XML attrbute names for the various address fields in the
// iq stanza.
enum class Field { JID, CHANNEL, ENDPOINT_ID };
buzz::QName GetQNameByField(Field attr, bool from) {
std::string attribute_name;
switch (attr) {
case Field::JID:
attribute_name = (from) ? "from" : "to";
break;
case Field::ENDPOINT_ID:
attribute_name = (from) ? "from-endpoint-id" : "to-endpoint-id";
break;
case Field::CHANNEL:
attribute_name = (from) ? "from-channel" : "to-channel";
break;
default:
NOTREACHED();
}
return QName(kEmptyNamespace, attribute_name);
}
SignalingAddress ParseAddress(
const buzz::XmlElement* iq, bool from, std::string* error) {
SignalingAddress empty_instance;
std::string jid(iq->Attr(GetQNameByField(Field::JID, from)));
const XmlElement* jingle = iq->FirstNamed(QName(kJingleNamespace, "jingle"));
if (!jingle) {
return SignalingAddress(jid);
}
std::string type(iq->Attr(QName(std::string(), "type")));
// For error IQs, flips the |from| flag as the jingle node represents the
// original request.
if (type == "error") {
from = !from;
}
std::string endpoint_id(
jingle->Attr(GetQNameByField(Field::ENDPOINT_ID, from)));
std::string channel_str(jingle->Attr(GetQNameByField(Field::CHANNEL, from)));
SignalingAddress::Channel channel;
if (channel_str.empty()) {
channel = SignalingAddress::Channel::XMPP;
} else if (!NameToValue(kChannelTypes, channel_str, &channel)) {
*error = "Unknown channel: " + channel_str;
return empty_instance;
}
bool isLcs = (channel == SignalingAddress::Channel::LCS);
if (isLcs == endpoint_id.empty()) {
*error = (isLcs ? "Missing |endpoint-id| for LCS channel"
: "|endpoint_id| should be empty for XMPP channel");
return empty_instance;
}
return SignalingAddress(jid, endpoint_id, channel);
}
void SetAddress(buzz::XmlElement* iq,
buzz::XmlElement* jingle,
const SignalingAddress& address,
bool from) {
if (address.empty()) {
return;
}
// Always set the JID.
iq->SetAttr(GetQNameByField(Field::JID, from), address.jid);
// Do not tamper the routing-info in the jingle tag for error IQ's, as
// it corresponds to the original message.
std::string type(iq->Attr(QName(std::string(), "type")));
if (type == "error") {
return;
}
// Start from a fresh slate regardless of the previous address format.
jingle->ClearAttr(GetQNameByField(Field::CHANNEL, from));
jingle->ClearAttr(GetQNameByField(Field::ENDPOINT_ID, from));
// Only set the channel and endpoint_id in the LCS channel.
if (address.channel == SignalingAddress::Channel::LCS) {
jingle->AddAttr(
GetQNameByField(Field::ENDPOINT_ID, from), address.endpoint_id);
jingle->AddAttr(
GetQNameByField(Field::CHANNEL, from),
ValueToName(kChannelTypes, address.channel));
}
}
} // namespace
IceTransportInfo::NamedCandidate::NamedCandidate(
const std::string& name,
const cricket::Candidate& candidate)
: name(name),
candidate(candidate) {
}
IceTransportInfo::IceCredentials::IceCredentials(std::string channel,
std::string ufrag,
std::string password)
: channel(channel), ufrag(ufrag), password(password) {
}
SignalingAddress::SignalingAddress()
: channel(SignalingAddress::Channel::XMPP) {}
SignalingAddress::SignalingAddress(const std::string& jid)
: jid(jid), channel(SignalingAddress::Channel::XMPP) {}
SignalingAddress::SignalingAddress(const std::string& jid,
const std::string& endpoint_id,
Channel channel)
: jid(jid), endpoint_id(endpoint_id), channel(channel) {}
bool SignalingAddress::operator==(const SignalingAddress& other) {
return (other.endpoint_id == endpoint_id) && (other.jid == jid) &&
(other.channel == channel);
}
bool SignalingAddress::operator!=(const SignalingAddress& other) {
return !(*this == other);
}
// static
bool JingleMessage::IsJingleMessage(const buzz::XmlElement* stanza) {
return stanza->Name() == QName(kJabberNamespace, "iq") &&
stanza->Attr(QName(std::string(), "type")) == "set" &&
stanza->FirstNamed(QName(kJingleNamespace, "jingle")) != nullptr;
}
// static
std::string JingleMessage::GetActionName(ActionType action) {
return ValueToName(kActionTypes, action);
}
JingleMessage::JingleMessage() {}
JingleMessage::JingleMessage(const SignalingAddress& to,
ActionType action,
const std::string& sid)
: to(to), action(action), sid(sid) {}
JingleMessage::~JingleMessage() {}
bool JingleMessage::ParseXml(const buzz::XmlElement* stanza,
std::string* error) {
if (!IsJingleMessage(stanza)) {
*error = "Not a jingle message";
return false;
}
const XmlElement* jingle_tag =
stanza->FirstNamed(QName(kJingleNamespace, "jingle"));
if (!jingle_tag) {
*error = "Not a jingle message";
return false;
}
from = ParseAddress(stanza, true, error);
if (!error->empty()) {
return false;
}
to = ParseAddress(stanza, false, error);
if (!error->empty()) {
return false;
}
initiator = jingle_tag->Attr(QName(kEmptyNamespace, "initiator"));
std::string action_str = jingle_tag->Attr(QName(kEmptyNamespace, "action"));
if (action_str.empty()) {
*error = "action attribute is missing";
return false;
}
if (!NameToValue(kActionTypes, action_str, &action)) {
*error = "Unknown action " + action_str;
return false;
}
sid = jingle_tag->Attr(QName(kEmptyNamespace, "sid"));
if (sid.empty()) {
*error = "sid attribute is missing";
return false;
}
if (action == SESSION_INFO) {
// session-info messages may contain arbitrary information not
// defined by the Jingle protocol. We don't need to parse it.
const XmlElement* child = jingle_tag->FirstElement();
if (child) {
// session-info is allowed to be empty.
info.reset(new XmlElement(*child));
} else {
info.reset(nullptr);
}
return true;
}
const XmlElement* reason_tag =
jingle_tag->FirstNamed(QName(kJingleNamespace, "reason"));
if (reason_tag && reason_tag->FirstElement()) {
if (!NameToValue(kReasons, reason_tag->FirstElement()->Name().LocalPart(),
&reason)) {
reason = UNKNOWN_REASON;
}
}
const XmlElement* error_code_tag =
jingle_tag->FirstNamed(QName(kChromotingXmlNamespace, "error-code"));
if (error_code_tag && !error_code_tag->BodyText().empty()) {
if (!ParseErrorCode(error_code_tag->BodyText(), &error_code)) {
LOG(WARNING) << "Unknown error-code received "
<< error_code_tag->BodyText();
error_code = UNKNOWN_ERROR;
}
}
if (action == SESSION_TERMINATE)
return true;
const XmlElement* content_tag =
jingle_tag->FirstNamed(QName(kJingleNamespace, "content"));
if (!content_tag) {
*error = "content tag is missing";
return false;
}
std::string content_name = content_tag->Attr(QName(kEmptyNamespace, "name"));
if (content_name != ContentDescription::kChromotingContentName) {
*error = "Unexpected content name: " + content_name;
return false;
}
const XmlElement* webrtc_transport_tag = content_tag->FirstNamed(
QName(kWebrtcTransportNamespace, "transport"));
if (webrtc_transport_tag) {
transport_info.reset(new buzz::XmlElement(*webrtc_transport_tag));
}
description.reset(nullptr);
if (action == SESSION_INITIATE || action == SESSION_ACCEPT) {
const XmlElement* description_tag = content_tag->FirstNamed(
QName(kChromotingXmlNamespace, "description"));
if (!description_tag) {
*error = "Missing chromoting content description";
return false;
}
description = ContentDescription::ParseXml(description_tag,
webrtc_transport_tag != nullptr);
if (!description.get()) {
*error = "Failed to parse content description";
return false;
}
}
if (!webrtc_transport_tag) {
const XmlElement* ice_transport_tag = content_tag->FirstNamed(
QName(kIceTransportNamespace, "transport"));
if (ice_transport_tag) {
transport_info.reset(new buzz::XmlElement(*ice_transport_tag));
}
}
return true;
}
std::unique_ptr<buzz::XmlElement> JingleMessage::ToXml() const {
std::unique_ptr<XmlElement> root(
new XmlElement(QName("jabber:client", "iq"), true));
DCHECK(!to.empty());
root->SetAttr(QName(kEmptyNamespace, "type"), "set");
XmlElement* jingle_tag =
new XmlElement(QName(kJingleNamespace, "jingle"), true);
root->AddElement(jingle_tag);
jingle_tag->AddAttr(QName(kEmptyNamespace, "sid"), sid);
SetAddress(root.get(), jingle_tag, to, false);
SetAddress(root.get(), jingle_tag, from, true);
const char* action_attr = ValueToName(kActionTypes, action);
if (!action_attr)
LOG(FATAL) << "Invalid action value " << action;
jingle_tag->AddAttr(QName(kEmptyNamespace, "action"), action_attr);
if (action == SESSION_INFO) {
if (info.get())
jingle_tag->AddElement(new XmlElement(*info.get()));
return root;
}
if (action == SESSION_INITIATE)
jingle_tag->AddAttr(QName(kEmptyNamespace, "initiator"), initiator);
if (reason != UNKNOWN_REASON) {
XmlElement* reason_tag = new XmlElement(QName(kJingleNamespace, "reason"));
jingle_tag->AddElement(reason_tag);
reason_tag->AddElement(new XmlElement(
QName(kJingleNamespace, ValueToName(kReasons, reason))));
if (error_code != UNKNOWN_ERROR) {
XmlElement* error_code_tag =
new XmlElement(QName(kChromotingXmlNamespace, "error-code"));
jingle_tag->AddElement(error_code_tag);
error_code_tag->SetBodyText(ErrorCodeToString(error_code));
}
}
if (action != SESSION_TERMINATE) {
XmlElement* content_tag =
new XmlElement(QName(kJingleNamespace, "content"));
jingle_tag->AddElement(content_tag);
content_tag->AddAttr(QName(kEmptyNamespace, "name"),
ContentDescription::kChromotingContentName);
content_tag->AddAttr(QName(kEmptyNamespace, "creator"), "initiator");
if (description)
content_tag->AddElement(description->ToXml());
if (transport_info) {
content_tag->AddElement(new XmlElement(*transport_info));
} else if (description && description->config()->webrtc_supported()) {
content_tag->AddElement(
new XmlElement(QName(kWebrtcTransportNamespace, "transport")));
}
}
return root;
}
JingleMessageReply::JingleMessageReply()
: type(REPLY_RESULT),
error_type(NONE) {
}
JingleMessageReply::JingleMessageReply(ErrorType error)
: type(error != NONE ? REPLY_ERROR : REPLY_RESULT),
error_type(error) {
}
JingleMessageReply::JingleMessageReply(ErrorType error,
const std::string& text_value)
: type(REPLY_ERROR),
error_type(error),
text(text_value) {
}
JingleMessageReply::~JingleMessageReply() { }
std::unique_ptr<buzz::XmlElement> JingleMessageReply::ToXml(
const buzz::XmlElement* request_stanza) const {
std::unique_ptr<XmlElement> iq(
new XmlElement(QName(kJabberNamespace, "iq"), true));
iq->SetAttr(QName(kEmptyNamespace, "id"),
request_stanza->Attr(QName(kEmptyNamespace, "id")));
SignalingAddress original_from;
std::string error_message;
original_from = ParseAddress(request_stanza, true, &error_message);
DCHECK(error_message.empty());
if (type == REPLY_RESULT) {
iq->SetAttr(QName(kEmptyNamespace, "type"), "result");
XmlElement* jingle =
new XmlElement(QName(kJingleNamespace, "jingle"), true);
iq->AddElement(jingle);
SetAddress(iq.get(), jingle, original_from, false);
return iq;
}
DCHECK_EQ(type, REPLY_ERROR);
iq->SetAttr(QName(kEmptyNamespace, "type"), "error");
SetAddress(iq.get(), nullptr, original_from, false);
for (const buzz::XmlElement* child = request_stanza->FirstElement();
child != nullptr; child = child->NextElement()) {
iq->AddElement(new buzz::XmlElement(*child));
}
buzz::XmlElement* error =
new buzz::XmlElement(QName(kJabberNamespace, "error"));
iq->AddElement(error);
std::string type;
std::string error_text;
QName name;
switch (error_type) {
case BAD_REQUEST:
type = "modify";
name = QName(kJabberNamespace, "bad-request");
break;
case NOT_IMPLEMENTED:
type = "cancel";
name = QName(kJabberNamespace, "feature-bad-request");
break;
case INVALID_SID:
type = "modify";
name = QName(kJabberNamespace, "item-not-found");
error_text = "Invalid SID";
break;
case UNEXPECTED_REQUEST:
type = "modify";
name = QName(kJabberNamespace, "unexpected-request");
break;
case UNSUPPORTED_INFO:
type = "modify";
name = QName(kJabberNamespace, "feature-not-implemented");
break;
default:
NOTREACHED();
}
if (!text.empty())
error_text = text;
error->SetAttr(QName(kEmptyNamespace, "type"), type);
// If the error name is not in the standard namespace, we have
// to first add some error from that namespace.
if (name.Namespace() != kJabberNamespace) {
error->AddElement(
new buzz::XmlElement(QName(kJabberNamespace, "undefined-condition")));
}
error->AddElement(new buzz::XmlElement(name));
if (!error_text.empty()) {
// It's okay to always use English here. This text is for
// debugging purposes only.
buzz::XmlElement* text_elem =
new buzz::XmlElement(QName(kJabberNamespace, "text"));
text_elem->SetAttr(QName(kXmlNamespace, "lang"), "en");
text_elem->SetBodyText(error_text);
error->AddElement(text_elem);
}
return iq;
}
IceTransportInfo::IceTransportInfo() {}
IceTransportInfo::~IceTransportInfo() {}
bool IceTransportInfo::ParseXml(
const buzz::XmlElement* element) {
if (element->Name() != QName(kIceTransportNamespace, "transport"))
return false;
ice_credentials.clear();
candidates.clear();
QName qn_credentials(kIceTransportNamespace, "credentials");
for (const XmlElement* credentials_tag = element->FirstNamed(qn_credentials);
credentials_tag;
credentials_tag = credentials_tag->NextNamed(qn_credentials)) {
IceTransportInfo::IceCredentials credentials;
if (!ParseIceCredentials(credentials_tag, &credentials))
return false;
ice_credentials.push_back(credentials);
}
QName qn_candidate(kIceTransportNamespace, "candidate");
for (const XmlElement* candidate_tag = element->FirstNamed(qn_candidate);
candidate_tag; candidate_tag = candidate_tag->NextNamed(qn_candidate)) {
IceTransportInfo::NamedCandidate candidate;
if (!ParseIceCandidate(candidate_tag, &candidate))
return false;
candidates.push_back(candidate);
}
return true;
}
std::unique_ptr<buzz::XmlElement> IceTransportInfo::ToXml() const {
std::unique_ptr<buzz::XmlElement> result(
new XmlElement(QName(kIceTransportNamespace, "transport"), true));
for (const IceCredentials& credentials : ice_credentials) {
result->AddElement(FormatIceCredentials(credentials));
}
for (const NamedCandidate& candidate : candidates) {
result->AddElement(FormatIceCandidate(candidate));
}
return result;
}
} // namespace protocol
} // namespace remoting