Commit 76d9000d authored by sergeyu's avatar sergeyu Committed by Commit bot

Remove dependency on XMPP implementation in WebRTC

Now XMPP is implemented directly in remoting/signaling instead of
relying on WebRTC. The new code is mostly just a translation of
the XMPP implementation we use in the webapp from JavaScript to
C++ and uses the same layout and class names, except that the main
class is called XmppSignalStrategy instead of XmppConnection.

BUG=459756, 443806, 417336

Review URL: https://codereview.chromium.org/958703003

Cr-Commit-Position: refs/heads/master@{#319712}
parent b48f1883
......@@ -235,6 +235,7 @@
'../jingle/jingle.gyp:jingle_glue',
'../jingle/jingle.gyp:notifier',
'../net/net.gyp:net',
'../third_party/expat/expat.gyp:expat',
'../third_party/libjingle/libjingle.gyp:libjingle',
'remoting_base',
],
......
......@@ -105,6 +105,7 @@
'../native_client_sdk/native_client_sdk_untrusted.gyp:nacl_io_untrusted',
'../net/net_nacl.gyp:net_nacl',
'../third_party/boringssl/boringssl_nacl.gyp:boringssl_nacl',
'../third_party/expat/expat_nacl.gyp:expat_nacl',
'../third_party/khronos/khronos.gyp:khronos_headers',
'../third_party/libjingle/libjingle_nacl.gyp:libjingle_nacl',
'../third_party/libvpx/libvpx_nacl.gyp:libvpx_nacl',
......
......@@ -203,6 +203,10 @@
'signaling/signal_strategy.h',
'signaling/xmpp_signal_strategy.cc',
'signaling/xmpp_signal_strategy.h',
'signaling/xmpp_stream_parser.cc',
'signaling/xmpp_stream_parser.h',
'signaling/xmpp_login_handler.cc',
'signaling/xmpp_login_handler.h',
],
'remoting_client_sources': [
......
......@@ -237,6 +237,9 @@
'signaling/log_to_server_unittest.cc',
'signaling/server_log_entry_unittest.cc',
'signaling/server_log_entry_unittest.h',
'signaling/xmpp_login_handler_unittest.cc',
'signaling/xmpp_stream_parser_unittest.cc',
'signaling/xmpp_signal_strategy_unittest.cc',
'test/access_token_fetcher_unittest.cc',
'test/app_remoting_test_driver_environment_unittest.cc',
'test/remote_host_info_fetcher_unittest.cc',
......
......@@ -34,6 +34,7 @@ class SignalStrategy {
OK,
AUTHENTICATION_FAILED,
NETWORK_ERROR,
PROTOCOL_ERROR,
};
// Callback interface for signaling event. Event handlers are not
......
// Copyright 2015 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/signaling/xmpp_login_handler.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/logging.h"
#include "remoting/signaling/xmpp_stream_parser.h"
#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
// Undefine SendMessage and ERROR defined in Windows headers.
#ifdef SendMessage
#undef SendMessage
#endif
#ifdef ERROR
#undef ERROR
#endif
namespace remoting {
const char kOAuthMechanism[] = "X-OAUTH2";
buzz::StaticQName kXmppIqName = {"jabber:client", "iq"};
char kXmppBindNs[] = "urn:ietf:params:xml:ns:xmpp-bind";
buzz::StaticQName kXmppBindName = {kXmppBindNs, "bind"};
buzz::StaticQName kXmppJidName = {kXmppBindNs, "jid"};
buzz::StaticQName kJabberFeaturesName = {"http://etherx.jabber.org/streams",
"features"};
char kXmppTlsNs[] = "urn:ietf:params:xml:ns:xmpp-tls";
buzz::StaticQName kStartTlsName = {kXmppTlsNs, "starttls"};
buzz::StaticQName kTlsProceedName = {kXmppTlsNs, "proceed"};
char kXmppSaslNs[] = "urn:ietf:params:xml:ns:xmpp-sasl";
buzz::StaticQName kSaslMechanismsName = {kXmppSaslNs, "mechanisms"};
buzz::StaticQName kSaslMechanismName = {kXmppSaslNs, "mechanism"};
buzz::StaticQName kSaslSuccessName = {kXmppSaslNs, "success"};
XmppLoginHandler::XmppLoginHandler(const std::string& server,
const std::string& username,
const std::string& auth_token,
TlsMode tls_mode,
Delegate* delegate)
: server_(server),
username_(username),
auth_token_(auth_token),
tls_mode_(tls_mode),
delegate_(delegate),
state_(State::INIT) {
}
XmppLoginHandler::~XmppLoginHandler() {
}
void XmppLoginHandler::Start() {
switch (tls_mode_) {
case TlsMode::NO_TLS:
state_ = State::WAIT_STREAM_HEADER_AFTER_TLS;
StartAuthHandshake();
break;
case TlsMode::WITH_HANDSHAKE:
state_ = State::WAIT_STREAM_HEADER;
StartStream("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
break;
case TlsMode::WITHOUT_HANDSHAKE:
// If <starttls> handshake is not required then start TLS right away.
state_ = State::STARTING_TLS;
delegate_->StartTls();
break;
}
}
void XmppLoginHandler::OnDataReceived(const std::string& data) {
DCHECK(state_ != State::INIT && state_ != State::DONE &&
state_ != State::ERROR);
stream_parser_->AppendData(data);
}
void XmppLoginHandler::OnStanza(scoped_ptr<buzz::XmlElement> stanza) {
switch (state_) {
case State::WAIT_STREAM_HEADER: {
if (stanza->Name() == kJabberFeaturesName &&
stanza->FirstNamed(kStartTlsName) != nullptr) {
state_ = State::WAIT_STARTTLS_RESPONSE;
} else {
LOG(ERROR) << "Server doesn't support TLS.";
OnError(SignalStrategy::PROTOCOL_ERROR);
}
break;
}
case State::WAIT_STARTTLS_RESPONSE: {
if (stanza->Name() == kTlsProceedName) {
state_ = State::STARTING_TLS;
delegate_->StartTls();
} else {
LOG(ERROR) << "Failed to start TLS: " << stanza->Str();
OnError(SignalStrategy::PROTOCOL_ERROR);
}
break;
}
case State::WAIT_STREAM_HEADER_AFTER_TLS: {
buzz::XmlElement* mechanisms_element =
stanza->FirstNamed(kSaslMechanismsName);
bool oauth_supported = false;
if (mechanisms_element) {
for (buzz::XmlElement* element =
mechanisms_element->FirstNamed(kSaslMechanismName);
element; element = element->NextNamed(kSaslMechanismName)) {
if (element->BodyText() == kOAuthMechanism) {
oauth_supported = true;
break;
}
}
}
if (!oauth_supported) {
LOG(ERROR) << kOAuthMechanism
<< " auth mechanism is not supported by the server.";
OnError(SignalStrategy::PROTOCOL_ERROR);
return;
}
state_ = State::WAIT_AUTH_RESULT;
break;
}
case State::WAIT_AUTH_RESULT: {
if (stanza->Name() == kSaslSuccessName) {
state_ = State::WAIT_STREAM_HEADER_AFTER_AUTH;
StartStream(
"<iq type=\"set\" id=\"0\">"
"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
"<resource>chromoting</resource>"
"</bind>"
"</iq>"
"<iq type=\"set\" id=\"1\">"
"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
"</iq>");
} else {
OnError(SignalStrategy::AUTHENTICATION_FAILED);
}
break;
}
case State::WAIT_STREAM_HEADER_AFTER_AUTH:
if (stanza->Name() == kJabberFeaturesName &&
stanza->FirstNamed(kXmppBindName) != nullptr) {
state_ = State::WAIT_BIND_RESULT;
} else {
LOG(ERROR) << "Server doesn't support bind after authentication.";
OnError(SignalStrategy::PROTOCOL_ERROR);
}
break;
case State::WAIT_BIND_RESULT: {
buzz::XmlElement* bind = stanza->FirstNamed(kXmppBindName);
buzz::XmlElement* jid = bind ? bind->FirstNamed(kXmppJidName) : nullptr;
if (stanza->Attr(buzz::QName("", "id")) != "0" ||
stanza->Attr(buzz::QName("", "type")) != "result" || !jid) {
LOG(ERROR) << "Received unexpected response to bind: " << stanza->Str();
OnError(SignalStrategy::PROTOCOL_ERROR);
return;
}
jid_ = jid->BodyText();
state_ = State::WAIT_SESSION_IQ_RESULT;
break;
}
case State::WAIT_SESSION_IQ_RESULT:
if (stanza->Name() != kXmppIqName ||
stanza->Attr(buzz::QName("", "id")) != "1" ||
stanza->Attr(buzz::QName("", "type")) != "result") {
LOG(ERROR) << "Failed to start session: " << stanza->Str();
OnError(SignalStrategy::PROTOCOL_ERROR);
return;
}
state_ = State::DONE;
delegate_->OnHandshakeDone(jid_, stream_parser_.Pass());
break;
default:
NOTREACHED();
break;
}
}
void XmppLoginHandler::OnTlsStarted() {
DCHECK(state_ == State::STARTING_TLS);
state_ = State::WAIT_STREAM_HEADER_AFTER_TLS;
StartAuthHandshake();
}
void XmppLoginHandler::StartAuthHandshake() {
DCHECK(state_ == State::WAIT_STREAM_HEADER_AFTER_TLS);
std::string cookie;
base::Base64Encode(
std::string("\0", 1) + username_ + std::string("\0", 1) + auth_token_,
&cookie);
StartStream(
"<auth xmlns=\"" + std::string(kXmppSaslNs) + "\" "
"mechanism=\"" + "X-OAUTH2" + "\" "
"auth:service=\"oauth2\" "
"auth:allow-generated-jid=\"true\" "
"auth:client-uses-full-bind-result=\"true\" "
"auth:allow-non-google-login=\"true\" "
"xmlns:auth=\"http://www.google.com/talk/protocol/auth\">" +
cookie +
"</auth>");
};
void XmppLoginHandler::OnParserError() {
OnError(SignalStrategy::PROTOCOL_ERROR);
}
void XmppLoginHandler::StartStream(const std::string& first_message) {
delegate_->SendMessage("<stream:stream to=\"" + server_ +
"\" version=\"1.0\" xmlns=\"jabber:client\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\">" +
first_message);
stream_parser_.reset(new XmppStreamParser());
stream_parser_->SetCallbacks(
base::Bind(&XmppLoginHandler::OnStanza, base::Unretained(this)),
base::Bind(&XmppLoginHandler::OnParserError, base::Unretained(this)));
}
void XmppLoginHandler::OnError(SignalStrategy::Error error) {
if (state_ != State::ERROR) {
state_ = State::ERROR;
delegate_->OnLoginHandlerError(error);
}
}
} // namespace remoting
// Copyright 2015 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.
#ifndef REMOTING_SIGNALING_XMPP_LOGIN_HANDLER_H_
#define REMOTING_SIGNALING_XMPP_LOGIN_HANDLER_H_
#include <string>
#include "base/memory/scoped_ptr.h"
#include "remoting/signaling/signal_strategy.h"
// Undefine SendMessage and ERROR defined in Windows headers.
#ifdef SendMessage
#undef SendMessage
#endif
#ifdef ERROR
#undef ERROR
#endif
namespace remoting {
class XmppStreamParser;
// XmppLoginHandler handles authentication handshake for XmppSignalStrategy. It
// receives incoming data using onDataReceived(), calls Delegate::SendMessage()
// to send outgoing messages and calls Delegate::OnHandshakeDone() after
// authentication is finished successfully or Delegate::OnError() on error.
//
// See RFC3920 for description of XMPP and authentication handshake.
class XmppLoginHandler {
public:
class Delegate {
public:
Delegate() {}
virtual void SendMessage(const std::string& message) = 0;
virtual void StartTls() = 0;
virtual void OnHandshakeDone(const std::string& jid,
scoped_ptr<XmppStreamParser> parser) = 0;
virtual void OnLoginHandlerError(SignalStrategy::Error error) = 0;
protected:
virtual ~Delegate() {}
};
enum class TlsMode {
NO_TLS,
WITH_HANDSHAKE,
WITHOUT_HANDSHAKE,
};
XmppLoginHandler(const std::string& server,
const std::string& username,
const std::string& auth_token,
TlsMode tls_mode,
Delegate* delegate);
~XmppLoginHandler();
void Start();
void OnDataReceived(const std::string& data);
void OnTlsStarted();
private:
// States the handshake goes through. States are iterated from INIT to DONE
// sequentially, except for ERROR state which may be accepted at any point.
//
// Following messages are sent/received in each state:
// INIT
// client -> server: Stream header
// client -> server: <starttls>
// WAIT_STREAM_HEADER
// client <- server: Stream header with list of supported features which
// should include starttls.
// WAIT_STARTTLS_RESPONSE
// client <- server: <proceed>
// STARTING_TLS
// TLS handshake
// client -> server: Stream header
// client -> server: <auth> message with the OAuth2 token.
// WAIT_STREAM_HEADER_AFTER_TLS
// client <- server: Stream header with list of supported authentication
// methods which is expected to include X-OAUTH2
// WAIT_AUTH_RESULT
// client <- server: <success> or <failure>
// client -> server: Stream header
// client -> server: <bind>
// client -> server: <iq><session/></iq> to start the session
// WAIT_STREAM_HEADER_AFTER_AUTH
// client <- server: Stream header with list of features that should
// include <bind>.
// WAIT_BIND_RESULT
// client <- server: <bind> result with JID.
// WAIT_SESSION_IQ_RESULT
// client <- server: result for <iq><session/></iq>
// DONE
enum class State {
INIT,
WAIT_STREAM_HEADER,
WAIT_STARTTLS_RESPONSE,
STARTING_TLS,
WAIT_STREAM_HEADER_AFTER_TLS,
WAIT_AUTH_RESULT,
WAIT_STREAM_HEADER_AFTER_AUTH,
WAIT_BIND_RESULT,
WAIT_SESSION_IQ_RESULT,
DONE,
ERROR,
};
// Callbacks for XmppStreamParser.
void OnStanza(scoped_ptr<buzz::XmlElement> stanza);
void OnParserError();
// Starts authentication handshake in WAIT_STREAM_HEADER_AFTER_TLS state.
void StartAuthHandshake();
// Helper used to send stream header.
void StartStream(const std::string& first_message);
// Report the |error| to the delegate and changes |state_| to ERROR,
void OnError(SignalStrategy::Error error);
std::string server_;
std::string username_;
std::string auth_token_;
TlsMode tls_mode_;
Delegate* delegate_;
State state_;
std::string jid_;
scoped_ptr<XmppStreamParser> stream_parser_;
DISALLOW_COPY_AND_ASSIGN(XmppLoginHandler);
};
} // namespace remoting
#endif // REMOTING_SIGNALING_XMPP_LOGIN_HANDLER_H_
// Copyright 2015 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/signaling/xmpp_login_handler.h"
#include "base/base64.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "remoting/signaling/xmpp_stream_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
#ifdef SendMessage
#undef SendMessage
#endif
#ifdef ERROR
#undef ERROR
#endif
namespace remoting {
char kTestUsername[] = "testUsername@gmail.com";
char kTestToken[] = "testToken";
class XmppLoginHandlerTest : public testing::Test,
public XmppLoginHandler::Delegate {
public:
XmppLoginHandlerTest()
: start_tls_called_(false), error_(SignalStrategy::OK) {}
void TearDown() override {
login_handler_.reset();
parser_.reset();
base::RunLoop().RunUntilIdle();
}
void SendMessage(const std::string& message) override {
sent_data_ += message;
}
void StartTls() override {
start_tls_called_ = true;
}
void OnHandshakeDone(const std::string& jid,
scoped_ptr<XmppStreamParser> parser) override {
jid_ = jid;
parser_ = parser.Pass();
}
void OnLoginHandlerError(SignalStrategy::Error error) override {
EXPECT_NE(error, SignalStrategy::OK);
error_ = error;
}
protected:
void HandshakeBase();
base::MessageLoop message_loop_;
scoped_ptr<XmppLoginHandler> login_handler_;
std::string sent_data_;
bool start_tls_called_;
std::string jid_;
scoped_ptr<XmppStreamParser> parser_;
SignalStrategy::Error error_;
};
void XmppLoginHandlerTest::HandshakeBase() {
login_handler_.reset(
new XmppLoginHandler("google.com", kTestUsername, kTestToken,
XmppLoginHandler::TlsMode::WITHOUT_HANDSHAKE, this));
login_handler_->Start();
EXPECT_TRUE(start_tls_called_);
login_handler_->OnTlsStarted();
std::string cookie;
base::Base64Encode(
std::string("\0", 1) + kTestUsername + std::string("\0", 1) + kTestToken,
&cookie);
EXPECT_EQ(
sent_data_,
"<stream:stream to=\"google.com\" version=\"1.0\" "
"xmlns=\"jabber:client\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\">"
"<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"X-OAUTH2\" "
"auth:service=\"oauth2\" auth:allow-generated-jid=\"true\" "
"auth:client-uses-full-bind-result=\"true\" "
"auth:allow-non-google-login=\"true\" "
"xmlns:auth=\"http://www.google.com/talk/protocol/auth\">" + cookie +
"</auth>");
sent_data_.clear();
login_handler_->OnDataReceived(
"<stream:stream from=\"google.com\" id=\"DCDDE5171CB2154A\" "
"version=\"1.0\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\" "
"xmlns=\"jabber:client\">"
"<stream:features>"
"<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"
"<mechanism>X-OAUTH2</mechanism>"
"<mechanism>X-GOOGLE-TOKEN</mechanism>"
"<mechanism>PLAIN</mechanism>"
"</mechanisms>"
"</stream:features>");
}
TEST_F(XmppLoginHandlerTest, SuccessfulAuth) {
HandshakeBase();
login_handler_->OnDataReceived(
"<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>");
EXPECT_EQ(
sent_data_,
"<stream:stream to=\"google.com\" version=\"1.0\" "
"xmlns=\"jabber:client\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\">"
"<iq type=\"set\" id=\"0\">"
"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
"<resource>chromoting</resource>"
"</bind>"
"</iq>"
"<iq type=\"set\" id=\"1\">"
"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
"</iq>");
sent_data_.clear();
login_handler_->OnDataReceived(
"<stream:stream from=\"google.com\" id=\"104FA10576E2AA80\" "
"version=\"1.0\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\" "
"xmlns=\"jabber:client\">"
"<stream:features>"
"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"
"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
"</stream:features>"
"<iq id=\"0\" type=\"result\">"
"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
"<jid>" + std::string(kTestUsername) + "/chromoting52B4920E</jid>"
"</bind>"
"</iq>"
"<iq type=\"result\" id=\"1\"/>");
EXPECT_EQ(jid_, std::string(kTestUsername) + "/chromoting52B4920E");
EXPECT_TRUE(parser_);
}
TEST_F(XmppLoginHandlerTest, StartTlsHandshake) {
login_handler_.reset(
new XmppLoginHandler("google.com", kTestUsername, kTestToken,
XmppLoginHandler::TlsMode::WITH_HANDSHAKE, this));
login_handler_->Start();
EXPECT_FALSE(start_tls_called_);
EXPECT_EQ(sent_data_,
"<stream:stream to=\"google.com\" version=\"1.0\" "
"xmlns=\"jabber:client\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\">"
"<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
sent_data_.clear();
login_handler_->OnDataReceived(
"<stream:stream from=\"google.com\" id=\"78A87C70559EF28A\" "
"version=\"1.0\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\" "
"xmlns=\"jabber:client\">"
"<stream:features>"
"<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">"
"<required/>"
"</starttls>"
"<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"
"<mechanism>X-OAUTH2</mechanism>"
"<mechanism>X-GOOGLE-TOKEN</mechanism>"
"</mechanisms>"
"</stream:features>");
login_handler_->OnDataReceived(
"<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
EXPECT_TRUE(start_tls_called_);
}
TEST_F(XmppLoginHandlerTest, AuthError) {
HandshakeBase();
login_handler_->OnDataReceived(
"<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"
"<not-authorized/></failure>");
EXPECT_EQ(error_, SignalStrategy::AUTHENTICATION_FAILED);
}
TEST_F(XmppLoginHandlerTest, NoTls) {
login_handler_.reset(
new XmppLoginHandler("google.com", kTestUsername, kTestToken,
XmppLoginHandler::TlsMode::NO_TLS, this));
login_handler_->Start();
EXPECT_FALSE(start_tls_called_);
std::string cookie;
base::Base64Encode(
std::string("\0", 1) + kTestUsername + std::string("\0", 1) + kTestToken,
&cookie);
EXPECT_EQ(
sent_data_,
"<stream:stream to=\"google.com\" version=\"1.0\" "
"xmlns=\"jabber:client\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\">"
"<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"X-OAUTH2\" "
"auth:service=\"oauth2\" auth:allow-generated-jid=\"true\" "
"auth:client-uses-full-bind-result=\"true\" "
"auth:allow-non-google-login=\"true\" "
"xmlns:auth=\"http://www.google.com/talk/protocol/auth\">" + cookie +
"</auth>");
}
TEST_F(XmppLoginHandlerTest, StreamParseError) {
HandshakeBase();
login_handler_->OnDataReceived("BAD DATA");
EXPECT_EQ(error_, SignalStrategy::PROTOCOL_ERROR);
}
} // namespace remoting
This diff is collapsed.
......@@ -2,42 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The XmppSignalStrategy encapsulates all the logic to perform the signaling
// STUN/ICE for jingle via a direct XMPP connection.
//
// This class is not threadsafe.
#ifndef REMOTING_SIGNALING_XMPP_SIGNAL_STRATEGY_H_
#define REMOTING_SIGNALING_XMPP_SIGNAL_STRATEGY_H_
#include "remoting/signaling/signal_strategy.h"
#include <vector>
#include "base/compiler_specific.h"
#include "base/observer_list.h"
#include "base/threading/non_thread_safe.h"
#include "base/timer/timer.h"
#include "third_party/webrtc/base/sigslot.h"
#include "third_party/webrtc/libjingle/xmpp/xmppclient.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
namespace net {
class ClientSocketFactory;
class URLRequestContextGetter;
} // namespace net
namespace rtc {
class TaskRunner;
} // namespace rtc
namespace remoting {
class JingleThread;
class XmppSignalStrategy : public base::NonThreadSafe,
public SignalStrategy,
public buzz::XmppStanzaHandler,
public sigslot::has_slots<> {
// XmppSignalStrategy implements SignalStrategy using direct XMPP connection.
class XmppSignalStrategy : public SignalStrategy {
public:
// XMPP Server configuration for XmppSignalStrategy.
struct XmppServerConfig {
......@@ -69,41 +51,16 @@ class XmppSignalStrategy : public base::NonThreadSafe,
bool SendStanza(scoped_ptr<buzz::XmlElement> stanza) override;
std::string GetNextId() override;
// buzz::XmppStanzaHandler interface.
bool HandleStanza(const buzz::XmlElement* stanza) override;
// This method is used to update the auth info (for example when the OAuth
// access token is renewed). It is OK to call this even when we are in the
// CONNECTED state. It will be used on the next Connect() call.
void SetAuthInfo(const std::string& username,
const std::string& auth_token);
// Use this method to override the default resource name used (optional).
// This will be used on the next Connect() call.
void SetResourceName(const std::string& resource_name);
private:
static buzz::PreXmppAuth* CreatePreXmppAuth(
const buzz::XmppClientSettings& settings);
void OnConnectionStateChanged(buzz::XmppEngine::State state);
void SetState(State new_state);
void SendKeepAlive();
net::ClientSocketFactory* socket_factory_;
scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
std::string resource_name_;
scoped_ptr<rtc::TaskRunner> task_runner_;
buzz::XmppClient* xmpp_client_;
XmppServerConfig xmpp_server_config_;
State state_;
Error error_;
ObserverList<Listener, true> listeners_;
class Core;
base::RepeatingTimer<XmppSignalStrategy> keep_alive_timer_;
scoped_ptr<Core> core_;
DISALLOW_COPY_AND_ASSIGN(XmppSignalStrategy);
};
......
// Copyright 2015 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/signaling/xmpp_signal_strategy.h"
#include "base/base64.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "net/socket/socket_test_util.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
namespace remoting {
namespace {
class XmppSocketDataProvider: public net::SocketDataProvider {
public:
net::MockRead GetNextRead() override {
return net::MockRead(net::ASYNC, net::ERR_IO_PENDING);
}
net::MockWriteResult OnWrite(const std::string& data) override {
written_data_.append(data);
return net::MockWriteResult(net::SYNCHRONOUS, data.size());
}
void Reset() override {}
void ReceiveData(const std::string& text) {
socket()->OnReadComplete(
net::MockRead(net::ASYNC, text.data(), text.size()));
}
void Close() {
ReceiveData(std::string());
}
void SimulateNetworkError() {
socket()->OnReadComplete(
net::MockRead(net::ASYNC, net::ERR_CONNECTION_RESET));
}
std::string GetAndClearWrittenData() {
std::string data;
data.swap(written_data_);
return data;
}
private:
std::string written_data_;
};
} // namespace
const char kTestUsername[] = "test_username@example.com";
const char kTestAuthToken[] = "test_auth_token";
class XmppSignalStrategyTest : public testing::Test,
public SignalStrategy::Listener {
public:
XmppSignalStrategyTest() : message_loop_(base::MessageLoop::TYPE_IO) {}
void SetUp() override {
scoped_ptr<net::TestURLRequestContext> context(
new net::TestURLRequestContext());
request_context_getter_ = new net::TestURLRequestContextGetter(
message_loop_.task_runner(), context.Pass());
XmppSignalStrategy::XmppServerConfig config;
config.host = "talk.google.com";
config.port = 443;
config.username = kTestUsername;
config.auth_token = kTestAuthToken;
signal_strategy_.reset(new XmppSignalStrategy(
&client_socket_factory_, request_context_getter_, config));
signal_strategy_->AddListener(this);
}
void TearDown() override {
signal_strategy_->RemoveListener(this);
signal_strategy_.reset();
base::RunLoop().RunUntilIdle();
}
void OnSignalStrategyStateChange(SignalStrategy::State state) override {
state_history_.push_back(state);
}
bool OnSignalStrategyIncomingStanza(const buzz::XmlElement* stanza) override {
received_messages_.push_back(
make_scoped_ptr(new buzz::XmlElement(*stanza)));
return true;
}
void Connect(bool success);
protected:
base::MessageLoop message_loop_;
scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
net::MockClientSocketFactory client_socket_factory_;
scoped_ptr<XmppSocketDataProvider> socket_data_provider_;
scoped_ptr<net::SSLSocketDataProvider> ssl_socket_data_provider_;
scoped_ptr<XmppSignalStrategy> signal_strategy_;
std::vector<SignalStrategy::State> state_history_;
ScopedVector<buzz::XmlElement> received_messages_;
};
void XmppSignalStrategyTest::Connect(bool success) {
EXPECT_EQ(SignalStrategy::DISCONNECTED, signal_strategy_->GetState());
state_history_.clear();
socket_data_provider_.reset(new XmppSocketDataProvider());
socket_data_provider_->set_connect_data(
net::MockConnect(net::ASYNC, net::OK));
client_socket_factory_.AddSocketDataProvider(socket_data_provider_.get());
ssl_socket_data_provider_.reset(
new net::SSLSocketDataProvider(net::ASYNC, net::OK));
client_socket_factory_.AddSSLSocketDataProvider(
ssl_socket_data_provider_.get());
signal_strategy_->Connect();
EXPECT_EQ(SignalStrategy::CONNECTING, signal_strategy_->GetState());
EXPECT_EQ(1U, state_history_.size());
EXPECT_EQ(SignalStrategy::CONNECTING, state_history_[0]);
// No data written before TLS.
EXPECT_EQ("", socket_data_provider_->GetAndClearWrittenData());
base::RunLoop().RunUntilIdle();
socket_data_provider_->ReceiveData(
"<stream:stream from=\"google.com\" id=\"DCDDE5171CB2154A\" "
"version=\"1.0\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\" "
"xmlns=\"jabber:client\">"
"<stream:features>"
"<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"
"<mechanism>X-OAUTH2</mechanism>"
"<mechanism>X-GOOGLE-TOKEN</mechanism>"
"<mechanism>PLAIN</mechanism>"
"</mechanisms>"
"</stream:features>");
base::RunLoop().RunUntilIdle();
std::string cookie;
base::Base64Encode(std::string("\0", 1) + kTestUsername +
std::string("\0", 1) + kTestAuthToken,
&cookie);
// Expect auth message.
EXPECT_EQ(
"<stream:stream to=\"google.com\" version=\"1.0\" "
"xmlns=\"jabber:client\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\">"
"<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"X-OAUTH2\" "
"auth:service=\"oauth2\" auth:allow-generated-jid=\"true\" "
"auth:client-uses-full-bind-result=\"true\" "
"auth:allow-non-google-login=\"true\" "
"xmlns:auth=\"http://www.google.com/talk/protocol/auth\">" + cookie +
"</auth>", socket_data_provider_->GetAndClearWrittenData());
if (!success) {
socket_data_provider_->ReceiveData(
"<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"
"<not-authorized/></failure>");
EXPECT_EQ(2U, state_history_.size());
EXPECT_EQ(SignalStrategy::DISCONNECTED, state_history_[1]);
EXPECT_EQ(SignalStrategy::AUTHENTICATION_FAILED,
signal_strategy_->GetError());
return;
}
socket_data_provider_->ReceiveData(
"<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>");
base::RunLoop().RunUntilIdle();
EXPECT_EQ(
"<stream:stream to=\"google.com\" version=\"1.0\" "
"xmlns=\"jabber:client\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\">"
"<iq type=\"set\" id=\"0\">"
"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
"<resource>chromoting</resource>"
"</bind>"
"</iq>"
"<iq type=\"set\" id=\"1\">"
"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
"</iq>",
socket_data_provider_->GetAndClearWrittenData());
socket_data_provider_->ReceiveData(
"<stream:stream from=\"google.com\" id=\"104FA10576E2AA80\" "
"version=\"1.0\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\" "
"xmlns=\"jabber:client\">"
"<stream:features>"
"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"
"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
"</stream:features>"
"<iq id=\"0\" type=\"result\">"
"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
"<jid>" + std::string(kTestUsername) + "/chromoting52B4920E</jid>"
"</bind>"
"</iq>"
"<iq type=\"result\" id=\"1\"/>");
EXPECT_EQ(2U, state_history_.size());
EXPECT_EQ(SignalStrategy::CONNECTED, state_history_[1]);
}
TEST_F(XmppSignalStrategyTest, SendAndReceive) {
Connect(true);
EXPECT_TRUE(signal_strategy_->SendStanza(make_scoped_ptr(
new buzz::XmlElement(buzz::QName(std::string(), "hello")))));
EXPECT_EQ("<hello/>", socket_data_provider_->GetAndClearWrittenData());
socket_data_provider_->ReceiveData("<hi xmlns=\"hello\"/>");
EXPECT_EQ(1U, received_messages_.size());
EXPECT_EQ("<hi xmlns=\"hello\"/>", received_messages_[0]->Str());
}
TEST_F(XmppSignalStrategyTest, AuthError) {
Connect(false);
}
TEST_F(XmppSignalStrategyTest, ConnectionClosed) {
Connect(true);
socket_data_provider_->Close();
EXPECT_EQ(3U, state_history_.size());
EXPECT_EQ(SignalStrategy::DISCONNECTED, state_history_[2]);
EXPECT_EQ(SignalStrategy::DISCONNECTED, signal_strategy_->GetState());
EXPECT_EQ(SignalStrategy::OK, signal_strategy_->GetError());
// Can't send messages anymore.
EXPECT_FALSE(signal_strategy_->SendStanza(make_scoped_ptr(
new buzz::XmlElement(buzz::QName(std::string(), "hello")))));
// Try connecting again.
Connect(true);
}
TEST_F(XmppSignalStrategyTest, NetworkError) {
Connect(true);
socket_data_provider_->SimulateNetworkError();
EXPECT_EQ(3U, state_history_.size());
EXPECT_EQ(SignalStrategy::DISCONNECTED, state_history_[2]);
EXPECT_EQ(SignalStrategy::NETWORK_ERROR, signal_strategy_->GetError());
// Can't send messages anymore.
EXPECT_FALSE(signal_strategy_->SendStanza(make_scoped_ptr(
new buzz::XmlElement(buzz::QName(std::string(), "hello")))));
// Try connecting again.
Connect(true);
}
} // namespace remoting
// Copyright 2015 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/signaling/xmpp_stream_parser.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "third_party/webrtc/libjingle/xmllite/xmlbuilder.h"
#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
#include "third_party/webrtc/libjingle/xmllite/xmlparser.h"
namespace remoting {
class XmppStreamParser::Core : public buzz::XmlParseHandler {
public:
typedef base::Callback<void(scoped_ptr<buzz::XmlElement> stanza)>
OnStanzaCallback;
Core();
~Core() override;
void SetCallbacks(const OnStanzaCallback& on_stanza_callback,
const base::Closure& on_error_callback);
void AppendData(const std::string& data);
private:
// buzz::XmlParseHandler interface.
void StartElement(buzz::XmlParseContext* context,
const char* name,
const char** atts) override;
void EndElement(buzz::XmlParseContext* context, const char* name) override;
void CharacterData(buzz::XmlParseContext* context,
const char* text,
int len) override;
void Error(buzz::XmlParseContext* context, XML_Error error_code) override;
void ProcessError();
OnStanzaCallback on_stanza_callback_;
base::Closure on_error_callback_;
buzz::XmlParser parser_;
int depth_;
buzz::XmlBuilder builder_;
bool error_;
DISALLOW_COPY_AND_ASSIGN(Core);
};
XmppStreamParser::Core::Core()
: parser_(this),
depth_(0),
error_(false) {
}
XmppStreamParser::Core::~Core() {
}
void XmppStreamParser::Core::SetCallbacks(
const OnStanzaCallback& on_stanza_callback,
const base::Closure& on_error_callback) {
on_stanza_callback_ = on_stanza_callback;
on_error_callback_ = on_error_callback;
}
void XmppStreamParser::Core::AppendData(const std::string& data) {
if (error_)
return;
parser_.Parse(data.data(), data.size(), false);
}
void XmppStreamParser::Core::StartElement(buzz::XmlParseContext* context,
const char* name,
const char** atts) {
DCHECK(!error_);
++depth_;
if (depth_ == 1) {
scoped_ptr<buzz::XmlElement> header(
buzz::XmlBuilder::BuildElement(context, name, atts));
if (!header) {
LOG(ERROR) << "Failed to parse XMPP stream header.";
ProcessError();
}
return;
}
builder_.StartElement(context, name, atts);
}
void XmppStreamParser::Core::EndElement(buzz::XmlParseContext* context,
const char* name) {
DCHECK(!error_);
--depth_;
if (depth_ == 0) {
LOG(ERROR) << "XMPP stream ended unexpectedly.";
ProcessError();
return;
}
builder_.EndElement(context, name);
if (depth_ == 1) {
if (!on_stanza_callback_.is_null())
on_stanza_callback_.Run(make_scoped_ptr(builder_.CreateElement()));
}
}
void XmppStreamParser::Core::CharacterData(buzz::XmlParseContext* context,
const char* text,
int len) {
DCHECK(!error_);
// Ignore data between stanzas.
if (depth_ <= 1) {
// Only whitespace is allowed outside of the stanzas.
bool all_spaces = true;
for (char c: std::string(text, len)) {
if (c != ' ') {
all_spaces = false;
break;
}
}
if (!all_spaces) {
LOG(ERROR) << "Received unexpected string: " << std::string(text,
text + len);
ProcessError();
}
} else if (depth_ > 1) {
builder_.CharacterData(context, text, len);
}
}
void XmppStreamParser::Core::Error(buzz::XmlParseContext* context,
XML_Error error_code) {
LOG(ERROR) << "XMPP parser error: " << error_code;
ProcessError();
}
void XmppStreamParser::Core::ProcessError() {
error_ = true;
if (!on_error_callback_.is_null())
on_error_callback_.Run();
}
XmppStreamParser::XmppStreamParser() : core_(new Core()) {
}
XmppStreamParser::~XmppStreamParser() {
// Set null callbacks and delete |core_| asynchronously to make sure it's not
// deleted from a callback.
core_->SetCallbacks(OnStanzaCallback(), base::Closure());
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, core_.release());
}
void XmppStreamParser::SetCallbacks(const OnStanzaCallback& on_stanza_callback,
const base::Closure& on_error_callback) {
core_->SetCallbacks(on_stanza_callback, on_error_callback);
}
void XmppStreamParser::AppendData(const std::string& data) {
core_->AppendData(data);
}
} // namespace remoting
// Copyright 2015 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.
#ifndef REMOTING_SIGNALING_XMPP_STREAM_PARSER_H_
#define REMOTING_SIGNALING_XMPP_STREAM_PARSER_H_
#include <string>
#include "base/callback.h"
namespace buzz {
class XmlElement;
} // namespace buzz
namespace remoting {
// XmppStreamParser is used to parse XMPP stream. Data is fed to the parser
// using appendData() method and it calls |on_stanza_callback\ and
// |on_error_callback| specified using SetCallbacks().
class XmppStreamParser {
public:
typedef base::Callback<void(scoped_ptr<buzz::XmlElement> stanza)>
OnStanzaCallback;
XmppStreamParser();
~XmppStreamParser();
void SetCallbacks(const OnStanzaCallback& on_stanza_callback,
const base::Closure& on_error_callback);
void AppendData(const std::string& data);
private:
class Core;
scoped_ptr<Core> core_;
DISALLOW_COPY_AND_ASSIGN(XmppStreamParser);
};
} // namespace remoting
#endif // REMOTING_SIGNALING_XMPP_STREAM_PARSER_H_
// Copyright 2015 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/signaling/xmpp_stream_parser.h"
#include "base/bind.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
namespace remoting {
class XmppStreamParserTest : public testing::Test {
public:
XmppStreamParserTest() : error_(false) {}
void SetUp() override {
parser_.reset(new remoting::XmppStreamParser());
parser_->SetCallbacks(
base::Bind(&XmppStreamParserTest::OnStanza, base::Unretained(this)),
base::Bind(&XmppStreamParserTest::OnError, base::Unretained(this)));
}
void TearDown() override {
parser_.reset();
base::RunLoop().RunUntilIdle();
}
void OnStanza(scoped_ptr<buzz::XmlElement> stanza) {
received_stanzas_.push_back(stanza.Pass());
}
void OnError() {
error_ = true;
}
protected:
base::MessageLoop message_loop_;
scoped_ptr<XmppStreamParser> parser_;
ScopedVector<buzz::XmlElement> received_stanzas_;
bool error_;
};
TEST_F(XmppStreamParserTest, ParseXmppStream) {
parser_->AppendData("<stream><iq>text</iq>");
EXPECT_EQ(received_stanzas_[0]->Str(), "<iq>text</iq>");
};
TEST_F(XmppStreamParserTest, HandleMultipleIncomingStanzas) {
parser_->AppendData("<stream><iq>text</iq><iq>more text</iq>");
EXPECT_EQ(received_stanzas_[0]->Str(), "<iq>text</iq>");
EXPECT_EQ(received_stanzas_[1]->Str(), "<iq>more text</iq>");
};
TEST_F(XmppStreamParserTest, IgnoreWhitespaceBetweenStanzas) {
parser_->AppendData("<stream> <iq>text</iq>");
EXPECT_EQ(received_stanzas_[0]->Str(), "<iq>text</iq>");
};
TEST_F(XmppStreamParserTest, AssembleMessagesFromChunks) {
parser_->AppendData("<stream><i");
parser_->AppendData("q>");
// Split one UTF-8 sequence into two chunks
std::string data = "😃";
parser_->AppendData(data.substr(0, 2));
parser_->AppendData(data.substr(2));
parser_->AppendData("</iq>");
EXPECT_EQ(received_stanzas_[0]->Str(), "<iq>😃</iq>");
};
TEST_F(XmppStreamParserTest, StopParsingOnErrors) {
parser_->AppendData("<stream><invalidtag p!='a'></invalidtag><iq>text</iq>");
EXPECT_TRUE(error_);
EXPECT_TRUE(received_stanzas_.empty());
};
TEST_F(XmppStreamParserTest, FailOnInvalidStreamHeader) {
parser_->AppendData("<stream p!='a'>");
EXPECT_TRUE(error_);
};
TEST_F(XmppStreamParserTest, FailOnLooseText) {
parser_->AppendData("stream<");
EXPECT_TRUE(error_);
};
} // namespace remoting
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment