diff options
-rw-r--r-- | remoting/base/constants.cc | 2 | ||||
-rw-r--r-- | remoting/base/constants.h | 3 | ||||
-rw-r--r-- | remoting/host/chromoting_host.cc | 15 | ||||
-rw-r--r-- | remoting/host/heartbeat_sender.cc | 107 | ||||
-rw-r--r-- | remoting/host/heartbeat_sender.h | 59 | ||||
-rw-r--r-- | remoting/host/heartbeat_sender_unittest.cc | 148 | ||||
-rw-r--r-- | remoting/host/host_key_pair.cc | 92 | ||||
-rw-r--r-- | remoting/host/host_key_pair.h | 46 | ||||
-rw-r--r-- | remoting/host/host_key_pair_unittest.cc | 71 | ||||
-rw-r--r-- | remoting/host/json_host_config.cc | 1 | ||||
-rw-r--r-- | remoting/host/json_host_config_unittest.cc | 2 | ||||
-rw-r--r-- | remoting/host/test_key_pair.h | 30 | ||||
-rw-r--r-- | remoting/jingle_glue/iq_request.h | 5 | ||||
-rw-r--r-- | remoting/jingle_glue/jingle_channel.cc | 3 | ||||
-rw-r--r-- | remoting/jingle_glue/jingle_client.cc | 21 | ||||
-rw-r--r-- | remoting/jingle_glue/jingle_client.h | 11 | ||||
-rw-r--r-- | remoting/jingle_glue/jingle_thread.h | 2 | ||||
-rw-r--r-- | remoting/remoting.gyp | 7 |
18 files changed, 573 insertions, 52 deletions
diff --git a/remoting/base/constants.cc b/remoting/base/constants.cc index 0449dc5..2a9c0a4 100644 --- a/remoting/base/constants.cc +++ b/remoting/base/constants.cc @@ -11,4 +11,6 @@ const char kChromotingBotJid[] = "remoting@bot.talk.google.com"; // TODO(sergeyu): Use chromoting's own service name here instead of sync. const char kChromotingTokenServiceName[] = "chromiumsync"; +const char kChromotingXmlNamespace[] = "google:remoting"; + } // namespace remoting diff --git a/remoting/base/constants.h b/remoting/base/constants.h index d124172..c992f11 100644 --- a/remoting/base/constants.h +++ b/remoting/base/constants.h @@ -12,6 +12,9 @@ extern const char kChromotingBotJid[]; // Service name used for authentication. extern const char kChromotingTokenServiceName[]; +// Namespace used for chromoting XMPP stanzas. +extern const char kChromotingXmlNamespace[]; + } // namespace remoting #endif // REMOTING_BASE_CONSTANTS_H_ diff --git a/remoting/host/chromoting_host.cc b/remoting/host/chromoting_host.cc index 7795de1..063ee83 100644 --- a/remoting/host/chromoting_host.cc +++ b/remoting/host/chromoting_host.cc @@ -139,12 +139,13 @@ void ChromotingHost::OnStateChange(JingleClient* jingle_client, LOG(INFO) << "Host connected as " << jingle_client->GetFullJid() << "." << std::endl; - // Start heartbeating after we connected - heartbeat_sender_ = new HeartbeatSender(); - heartbeat_sender_->Start(config_, jingle_client_.get()); + // Start heartbeating after we've connected. + heartbeat_sender_->Start(); } else if (state == JingleClient::CLOSED) { LOG(INFO) << "Host disconnected from talk network." << std::endl; - heartbeat_sender_ = NULL; + + // Stop heartbeating. + heartbeat_sender_->Stop(); // TODO(sergeyu): We should try reconnecting here instead of terminating // the host. @@ -219,6 +220,12 @@ void ChromotingHost::DoStart(Task* shutdown_task) { jingle_client_ = new JingleClient(context_->jingle_thread()); jingle_client_->Init(xmpp_login, xmpp_auth_token, kChromotingTokenServiceName, this); + + heartbeat_sender_ = new HeartbeatSender(); + if (!heartbeat_sender_->Init(config_, jingle_client_.get())) { + LOG(ERROR) << "Failed to initialize HeartbeatSender."; + return; + } } void ChromotingHost::DoShutdown() { diff --git a/remoting/host/heartbeat_sender.cc b/remoting/host/heartbeat_sender.cc index fa234e3..b62b268a 100644 --- a/remoting/host/heartbeat_sender.cc +++ b/remoting/host/heartbeat_sender.cc @@ -6,70 +6,103 @@ #include "base/logging.h" #include "base/message_loop.h" +#include "base/string_number_conversions.h" +#include "base/time.h" #include "remoting/base/constants.h" #include "remoting/host/host_config.h" #include "remoting/jingle_glue/iq_request.h" #include "remoting/jingle_glue/jingle_client.h" #include "remoting/jingle_glue/jingle_thread.h" -#include "talk/xmpp/constants.h" -#include "talk/xmllite/xmlelement.h" +#include "third_party/libjingle/source/talk/xmllite/xmlelement.h" +#include "third_party/libjingle/source/talk/xmpp/constants.h" namespace remoting { namespace { -const char * const kChromotingNamespace = "google:remoting"; -const buzz::QName kHeartbeatQuery(true, kChromotingNamespace, "heartbeat"); -const buzz::QName kHostIdAttr(true, kChromotingNamespace, "hostid"); +const char kHeartbeatQueryTag[] = "heartbeat"; +const char kHostIdAttr[] = "hostid"; +const char kHeartbeatSignatureTag[] = "signature"; +const char kSignatureTimeAttr[] = "time"; // TODO(sergeyu): Make this configurable by the cloud. const int64 kHeartbeatPeriodMs = 5 * 60 * 1000; // 5 minutes. } HeartbeatSender::HeartbeatSender() - : started_(false) { + : state_(CREATED) { } -void HeartbeatSender::Start(MutableHostConfig* config, JingleClient* jingle_client) { - DCHECK(jingle_client); - DCHECK(!started_); +HeartbeatSender::~HeartbeatSender() { + DCHECK(state_ == CREATED || state_ == INITIALIZED || state_ == STOPPED); +} - started_ = true; +bool HeartbeatSender::Init(MutableHostConfig* config, + JingleClient* jingle_client) { + DCHECK(jingle_client); + DCHECK(state_ == CREATED); jingle_client_ = jingle_client; config_ = config; if (!config_->GetString(kHostIdConfigPath, &host_id_)) { LOG(ERROR) << "host_id is not defined in the config."; - return; + return false; } - jingle_client_->message_loop()->PostTask( - FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoStart)); + if (!key_pair_.Load(config)) { + return false; + } + + state_ = INITIALIZED; + + return true; } -void HeartbeatSender::DoStart() { - DCHECK(MessageLoop::current() == jingle_client_->message_loop()); +void HeartbeatSender::Start() { + if (MessageLoop::current() != jingle_client_->message_loop()) { + jingle_client_->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::Start)); + return; + } + + DCHECK(state_ == INITIALIZED); + state_ = STARTED; - request_.reset(new IqRequest(jingle_client_)); + request_.reset(jingle_client_->CreateIqRequest()); request_->set_callback(NewCallback(this, &HeartbeatSender::ProcessResponse)); jingle_client_->message_loop()->PostTask( FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoSendStanza)); } +void HeartbeatSender::Stop() { + if (MessageLoop::current() != jingle_client_->message_loop()) { + jingle_client_->message_loop()->PostTask( + FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::Stop)); + return; + } + + DCHECK(state_ == STARTED); + state_ = STOPPED; + request_.reset(NULL); +} + void HeartbeatSender::DoSendStanza() { - DCHECK(MessageLoop::current() == jingle_client_->message_loop()); + if (state_ == STARTED) { + // |jingle_client_| may be already destroyed if |state_| is set to + // |STOPPED|, so don't touch it here unless we are in |STARTED| state. + DCHECK(MessageLoop::current() == jingle_client_->message_loop()); - LOG(INFO) << "Sending heartbeat stanza to " << kChromotingBotJid; + LOG(INFO) << "Sending heartbeat stanza to " << kChromotingBotJid; - buzz::XmlElement* stanza = new buzz::XmlElement(kHeartbeatQuery); - stanza->AddAttr(kHostIdAttr, host_id_); - request_->SendIq(buzz::STR_SET, kChromotingBotJid, stanza); + request_->SendIq(buzz::STR_SET, kChromotingBotJid, + CreateHeartbeatMessage()); - // Schedule next heartbeat. - jingle_client_->message_loop()->PostDelayedTask( - FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoSendStanza), - kHeartbeatPeriodMs); + // Schedule next heartbeat. + jingle_client_->message_loop()->PostDelayedTask( + FROM_HERE, NewRunnableMethod(this, &HeartbeatSender::DoSendStanza), + kHeartbeatPeriodMs); + } } void HeartbeatSender::ProcessResponse(const buzz::XmlElement* response) { @@ -79,4 +112,28 @@ void HeartbeatSender::ProcessResponse(const buzz::XmlElement* response) { } } +buzz::XmlElement* HeartbeatSender::CreateHeartbeatMessage() { + buzz::XmlElement* query = new buzz::XmlElement( + buzz::QName(kChromotingXmlNamespace, kHeartbeatQueryTag)); + query->AddAttr(buzz::QName(kChromotingXmlNamespace, kHostIdAttr), host_id_); + query->AddElement(CreateSignature()); + return query; +} + +buzz::XmlElement* HeartbeatSender::CreateSignature() { + buzz::XmlElement* signature_tag = new buzz::XmlElement( + buzz::QName(kChromotingXmlNamespace, kHeartbeatSignatureTag)); + + int64 time = static_cast<int64>(base::Time::Now().ToDoubleT()); + std::string time_str(base::Int64ToString(time)); + signature_tag->AddAttr( + buzz::QName(kChromotingXmlNamespace, kSignatureTimeAttr), time_str); + + std::string message = jingle_client_->GetFullJid() + ' ' + time_str; + std::string signature(key_pair_.GetSignature(message)); + signature_tag->AddText(signature); + + return signature_tag; +} + } // namespace remoting diff --git a/remoting/host/heartbeat_sender.h b/remoting/host/heartbeat_sender.h index 08665c6..07e2568e 100644 --- a/remoting/host/heartbeat_sender.h +++ b/remoting/host/heartbeat_sender.h @@ -9,34 +9,81 @@ #include "base/scoped_ptr.h" #include "base/ref_counted.h" +#include "remoting/host/host_key_pair.h" #include "remoting/jingle_glue/iq_request.h" +#include "testing/gtest/include/gtest/gtest_prod.h" namespace remoting { class IqRequest; +class HostKeyPair; class JingleClient; class MutableHostConfig; -// HeartbeatSender periodically sends hertbeats to the chromoting bot. -// TODO(sergeyu): Write unittest for this class. +// HeartbeatSender periodically sends heartbeat stanzas to the Chromoting Bot. +// Each heartbeat stanza looks as follows: +// +// <iq type="set" to="remoting@bot.talk.google.com" +// from="user@gmail.com/chromoting123123" id="5" xmlns="jabber:client"> +// <rem:heartbeat rem:hostid="a1ddb11e-8aef-11df-bccf-18a905b9cb5a" +// xmlns:rem="google:remoting"> +// <rem:signature rem:time="1279061748">.signature.</rem:signature> +// </rem:heartbeat> +// </iq> +// +// The time attribute of the signature is the decimal time when the message +// was sent in second since the epoch (01/01/1970). The signature is a BASE64 +// encoded SHA-1/RSA signature created with the host's private key. The message +// being signed is the full Jid concatenated with the time value, separated by +// space. For example, for the heartbeat stanza above the message that is being +// signed is "user@gmail.com/chromoting123123 1279061748". +// TODO(sergeyu): Is it enough to sign JID and nothing else? class HeartbeatSender : public base::RefCountedThreadSafe<HeartbeatSender> { public: HeartbeatSender(); + virtual ~HeartbeatSender(); - // Starts heart-beating for |jingle_client|. - void Start(MutableHostConfig* config, JingleClient* jingle_client); + // Initializes heart-beating for |jingle_client| with the specified + // config. Returns false if the config is invalid (e.g. private key + // cannot be parsed). + bool Init(MutableHostConfig* config, JingleClient* jingle_client); + + // Starts heart-beating. Must be called after init. + void Start(); + + // Stops heart-beating. Must be called before corresponding JingleClient + // is destroyed. This object will not be deleted until Stop() is called, + // and it may (and will) crash after JingleClient is destroyed. Heartbeating + // cannot be restarted after it has been stopped, A new sender must be created + // instead. + void Stop(); private: - void DoStart(); + FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, DoSendStanza); + FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, CreateHeartbeatMessage); + + enum State { + CREATED, + INITIALIZED, + STARTED, + STOPPED, + }; + void DoSendStanza(); + // Helper methods used by DoSendStanza() to generate heartbeat stanzas. + // Caller owns the result. + buzz::XmlElement* CreateHeartbeatMessage(); + buzz::XmlElement* CreateSignature(); + void ProcessResponse(const buzz::XmlElement* response); - bool started_; + State state_; scoped_refptr<MutableHostConfig> config_; JingleClient* jingle_client_; scoped_ptr<IqRequest> request_; std::string host_id_; + HostKeyPair key_pair_; }; } // namespace remoting diff --git a/remoting/host/heartbeat_sender_unittest.cc b/remoting/host/heartbeat_sender_unittest.cc new file mode 100644 index 0000000..6ac2037 --- /dev/null +++ b/remoting/host/heartbeat_sender_unittest.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2010 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 "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/ref_counted.h" +#include "base/scoped_temp_dir.h" +#include "base/string_number_conversions.h" +#include "media/base/data_buffer.h" +#include "remoting/base/constants.h" +#include "remoting/host/heartbeat_sender.h" +#include "remoting/host/host_key_pair.h" +#include "remoting/host/json_host_config.h" +#include "remoting/host/test_key_pair.h" +#include "remoting/jingle_glue/iq_request.h" +#include "remoting/jingle_glue/jingle_client.h" +#include "remoting/jingle_glue/jingle_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libjingle/source/talk/xmllite/xmlelement.h" +#include "third_party/libjingle/source/talk/xmpp/constants.h" + +using testing::_; +using testing::DeleteArg; +using testing::DoAll; +using testing::NotNull; +using testing::Return; + +namespace remoting { + +namespace { +const char kHostId[] = "0"; +const char kTestJid[] = "user@gmail.com/chromoting123"; +const int64 kTestTime = 123123123; +} // namespace + +class MockJingleClient : public JingleClient { + public: + explicit MockJingleClient(JingleThread* thread) : JingleClient(thread) { } + MOCK_METHOD0(CreateIqRequest, IqRequest*()); +}; + +class MockIqRequest : public IqRequest { + public: + explicit MockIqRequest(JingleClient* jingle_client) + : IqRequest(jingle_client) { + } + MOCK_METHOD3(SendIq, void(const std::string& type, + const std::string& addressee, + buzz::XmlElement* iq_body)); +}; + +class HeartbeatSenderTest : public testing::Test { + protected: + class TestConfigUpdater : + public base::RefCountedThreadSafe<TestConfigUpdater> { + public: + void DoUpdate(scoped_refptr<JsonHostConfig> target) { + target->SetString(kHostIdConfigPath, kHostId); + target->SetString(kPrivateKeyConfigPath, kTestHostKeyPair); + } + }; + + virtual void SetUp() { + ASSERT_TRUE(test_dir_.CreateUniqueTempDir()); + FilePath config_path = test_dir_.path().AppendASCII("test_config.json"); + config_ = new JsonHostConfig( + config_path, base::MessageLoopProxy::CreateForCurrentThread()); + scoped_refptr<TestConfigUpdater> config_updater(new TestConfigUpdater()); + config_->Update( + NewRunnableMethod(config_updater.get(), &TestConfigUpdater::DoUpdate, + config_)); + + jingle_thread_.message_loop_ = &message_loop_; + + jingle_client_ = new MockJingleClient(&jingle_thread_); + jingle_client_->full_jid_ = kTestJid; + } + + JingleThread jingle_thread_; + scoped_refptr<MockJingleClient> jingle_client_; + MessageLoop message_loop_; + ScopedTempDir test_dir_; + scoped_refptr<JsonHostConfig> config_; +}; + +TEST_F(HeartbeatSenderTest, DoSendStanza) { + // This test calls Start() followed by Stop(), and makes sure an Iq + // stanza is being send. + + // |iq_request| is freed by HeartbeatSender. + MockIqRequest* iq_request = new MockIqRequest(jingle_client_); + + scoped_refptr<HeartbeatSender> heartbeat_sender = new HeartbeatSender(); + ASSERT_TRUE(heartbeat_sender->Init(config_, jingle_client_)); + + EXPECT_CALL(*jingle_client_, CreateIqRequest()) + .WillOnce(Return(iq_request)); + + EXPECT_CALL(*iq_request, SendIq(buzz::STR_SET, kChromotingBotJid, NotNull())) + .WillOnce(DoAll(DeleteArg<2>(), Return())); + + heartbeat_sender->Start(); + message_loop_.RunAllPending(); + + heartbeat_sender->Stop(); + message_loop_.RunAllPending(); +} + +TEST_F(HeartbeatSenderTest, CreateHeartbeatMessage) { + // This test validates format of the heartbeat stanza. + + scoped_refptr<HeartbeatSender> heartbeat_sender = new HeartbeatSender(); + ASSERT_TRUE(heartbeat_sender->Init(config_, jingle_client_)); + + int64 start_time = static_cast<int64>(base::Time::Now().ToDoubleT()); + + scoped_ptr<buzz::XmlElement> stanza( + heartbeat_sender->CreateHeartbeatMessage()); + ASSERT_TRUE(stanza.get() != NULL); + + EXPECT_TRUE(buzz::QName(kChromotingXmlNamespace, "heartbeat") == + stanza->Name()); + EXPECT_EQ(std::string(kHostId), + stanza->Attr(buzz::QName(kChromotingXmlNamespace, "hostid"))); + + buzz::QName signature_tag(kChromotingXmlNamespace, "signature"); + buzz::XmlElement* signature = stanza->FirstNamed(signature_tag); + ASSERT_TRUE(signature != NULL); + EXPECT_TRUE(stanza->NextNamed(signature_tag) == NULL); + + std::string time_str = + signature->Attr(buzz::QName(kChromotingXmlNamespace, "time")); + int64 time; + EXPECT_TRUE(base::StringToInt64(time_str, &time)); + int64 now = static_cast<int64>(base::Time::Now().ToDoubleT()); + EXPECT_LE(start_time, time); + EXPECT_GE(now, time); + + HostKeyPair key_pair; + key_pair.LoadFromString(kTestHostKeyPair); + std::string expected_signature = + key_pair.GetSignature(std::string(kTestJid) + ' ' + time_str); + EXPECT_EQ(expected_signature, signature->BodyText()); +} + +} // namespace remoting diff --git a/remoting/host/host_key_pair.cc b/remoting/host/host_key_pair.cc new file mode 100644 index 0000000..f2ec6987 --- /dev/null +++ b/remoting/host/host_key_pair.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2010 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/host/host_key_pair.h" + +#include <string> +#include <vector> + +#include "base/base64.h" +#include "base/crypto/rsa_private_key.h" +#include "base/crypto/signature_creator.h" +#include "base/logging.h" +#include "base/task.h" +#include "remoting/host/host_config.h" + +namespace remoting { + +HostKeyPair::HostKeyPair() { } + +HostKeyPair::~HostKeyPair() { } + +void HostKeyPair::Generate() { + key_.reset(base::RSAPrivateKey::Create(2048)); +} + +bool HostKeyPair::LoadFromString(const std::string& key_base64) { + std::string key_str; + if (!base::Base64Decode(key_base64, &key_str)) { + LOG(ERROR) << "Failed to decode private key."; + return false; + } + + std::vector<uint8> key_buf(key_str.begin(), key_str.end()); + key_.reset(base::RSAPrivateKey::CreateFromPrivateKeyInfo(key_buf)); + if (key_.get() == NULL) { + LOG(ERROR) << "Invalid private key."; + return false; + } + + return true; +} + +bool HostKeyPair::Load(HostConfig* host_config) { + std::string key_base64; + if (!host_config->GetString(kPrivateKeyConfigPath, &key_base64)) { + LOG(ERROR) << "Private key wasn't found in the config file."; + return false; + } + return LoadFromString(key_base64); +} + +void HostKeyPair::Save(MutableHostConfig* host_config) { + // Check that the key initialized. + DCHECK(key_.get() != NULL); + + host_config->Update( + NewRunnableMethod(this, &HostKeyPair::DoSave, host_config)); +} + +void HostKeyPair::DoSave(MutableHostConfig* host_config) const { + std::vector<uint8> key_buf; + key_->ExportPrivateKey(&key_buf); + std::string key_str(key_buf.begin(), key_buf.end()); + std::string key_base64; + base::Base64Encode(key_str, &key_base64); + host_config->SetString(kPrivateKeyConfigPath, key_base64); +} + +std::string HostKeyPair::GetPublicKey() const { + std::vector<uint8> public_key; + key_->ExportPublicKey(&public_key); + std::string public_key_str(public_key.begin(), public_key.end()); + std::string public_key_base64; + base::Base64Encode(public_key_str, &public_key_base64); + return public_key_base64; +} + +std::string HostKeyPair::GetSignature(const std::string& message) const { + scoped_ptr<base::SignatureCreator> signature_creator( + base::SignatureCreator::Create(key_.get())); + signature_creator->Update(reinterpret_cast<const uint8*>(message.c_str()), + message.length()); + std::vector<uint8> signature_buf; + signature_creator->Final(&signature_buf); + std::string signature_str(signature_buf.begin(), signature_buf.end()); + std::string signature_base64; + base::Base64Encode(signature_str, &signature_base64); + return signature_base64; +} + +} // namespace remoting diff --git a/remoting/host/host_key_pair.h b/remoting/host/host_key_pair.h new file mode 100644 index 0000000..92ee4aa --- /dev/null +++ b/remoting/host/host_key_pair.h @@ -0,0 +1,46 @@ +// Copyright (c) 2010 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_HOST_HOST_KEY_PAIR_H_ +#define REMOTING_HOST_HOST_KEY_PAIR_H_ + +#include <string> + +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/task.h" + +namespace base { +class RSAPrivateKey; +} // namespace base + +namespace remoting { + +class HostConfig; +class MutableHostConfig; + +class HostKeyPair { + public: + HostKeyPair(); + ~HostKeyPair(); + + void Generate(); + bool LoadFromString(const std::string& key_base64); + bool Load(HostConfig* host_config); + void Save(MutableHostConfig* host_config); + + std::string GetPublicKey() const; + std::string GetSignature(const std::string& message) const; + + private: + void DoSave(MutableHostConfig* host_config) const; + + scoped_ptr<base::RSAPrivateKey> key_; +}; + +} // namespace remoting + +DISABLE_RUNNABLE_METHOD_REFCOUNT(remoting::HostKeyPair); + +#endif // REMOTING_HOST_HOST_KEY_PAIR_H_ diff --git a/remoting/host/host_key_pair_unittest.cc b/remoting/host/host_key_pair_unittest.cc new file mode 100644 index 0000000..e5af756 --- /dev/null +++ b/remoting/host/host_key_pair_unittest.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2010 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 <string> + +#include "base/base64.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/ref_counted.h" +#include "base/scoped_temp_dir.h" +#include "base/string_util.h" +#include "remoting/host/host_key_pair.h" +#include "remoting/host/json_host_config.h" +#include "remoting/host/test_key_pair.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +namespace { +const char kTestMessage[] = "Test Message"; + +// |kTestMessage| signed with the key from |kTestHostKeyPair|. +const char kExpectedSignature[] = +"LfUyXU2AiKM4rpWivUR3bLiQiRt1W3iIenNfJEB8RWyoEfnvSBoD52x8q9yFvtLFDEMPWyIrwM+N2" +"LuaWBKG1c0R7h+twBgvpExzZneJl+lbGMRx9ba8m/KAFrUWA/NRzOen2NHCuPybOEasgrPgGWBrmf" +"gDcvyW8QiGuKLopGj/4c5CQT4yE8JjsyU3Qqo2ZPK4neJYQhOmAlg+Q5dAPLpzWMj5HQyOVHJaSXZ" +"Y8vl/LiKvbdofYLeYNVKAE4q5mfpQMrsysPYpbxBV60AhFyrvtC040MFGcflKQRZNiZwMXVb7DclC" +"BPgvK7rI5Y0ERtVm+yNmH7vCivfyAnDUYA=="; +} + + +class HostKeyPairTest : public testing::Test { + protected: + virtual void SetUp() { + ASSERT_TRUE(test_dir_.CreateUniqueTempDir()); + FilePath config_path = test_dir_.path().AppendASCII("test_config.json"); + config_ = new JsonHostConfig( + config_path, base::MessageLoopProxy::CreateForCurrentThread()); + } + + MessageLoop message_loop_; + ScopedTempDir test_dir_; + scoped_refptr<JsonHostConfig> config_; +}; + +TEST_F(HostKeyPairTest, SaveLoad) { + // Save a key to a config, then load it back, and verify that we + // generate the same signature with both keys. + HostKeyPair exported_key; + exported_key.LoadFromString(kTestHostKeyPair); + exported_key.Save(config_); + + HostKeyPair imported_key; + imported_key.Load(config_); + + ASSERT_EQ(exported_key.GetSignature(kTestMessage), + imported_key.GetSignature(kTestMessage)); +} + +TEST_F(HostKeyPairTest, Signatures) { + // Sign a message and check that we get expected signature. + HostKeyPair key_pair; + key_pair.LoadFromString(kTestHostKeyPair); + + std::string signature_base64 = key_pair.GetSignature(kTestMessage); + ASSERT_EQ(signature_base64, std::string(kExpectedSignature)); +} + +} // namespace remoting diff --git a/remoting/host/json_host_config.cc b/remoting/host/json_host_config.cc index cd868ce..897402c 100644 --- a/remoting/host/json_host_config.cc +++ b/remoting/host/json_host_config.cc @@ -17,6 +17,7 @@ JsonHostConfig::JsonHostConfig( const FilePath& filename, base::MessageLoopProxy* file_message_loop_proxy) : filename_(filename), + values_(new DictionaryValue()), message_loop_proxy_(file_message_loop_proxy) { } diff --git a/remoting/host/json_host_config_unittest.cc b/remoting/host/json_host_config_unittest.cc index 534b4c5..bd1bc14 100644 --- a/remoting/host/json_host_config_unittest.cc +++ b/remoting/host/json_host_config_unittest.cc @@ -14,7 +14,7 @@ namespace remoting { namespace { -const char *kTestConfig = +const char* kTestConfig = "{\n" " \"xmpp_login\" : \"test@gmail.com\",\n" " \"xmpp_auth_token\" : \"TEST_AUTH_TOKEN\",\n" diff --git a/remoting/host/test_key_pair.h b/remoting/host/test_key_pair.h new file mode 100644 index 0000000..cb46e4c --- /dev/null +++ b/remoting/host/test_key_pair.h @@ -0,0 +1,30 @@ +// Copyright (c) 2010 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. + +namespace remoting { +// An RSA keypair used in unittests. +const char kTestHostKeyPair[] = +"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDeeT/wgtzQkGoeA5LRQI4iXibQZ" +"R3YUiJn/oVz0P+KLJSCWL4Uhjl3CytKg0nzuxgaJ9UOHxrl91k1XnLnwJytqkjl8kgWle+JJbiDx3" +"+3WBxmSjPWmmaPNuBebpyTD1N3TJEPRwe7vfYiOz56jzBPSm3gqIeRvJGIC+Ou+Q0OV+ISTu2Jjtj" +"MP3NOLfXjoZfoovPkZAB3/cjwjlBbd+0XWz+b3cAe5zfzcZkDvw6L4htwl/+MlRk7XX1U2dEoaI+x" +"/kZkigfJdfw7k5RCTmygp1A94rUXBaR5m7p+5eVChTXYjqmh0PrEwVr88D/F58zByma5ZNGFyyj1u" +"nXTNUUZAgMBAAECggEAKFvUppwG1Ost5Q3+52kPn9p2rh154ZFc3oLE0PLcOMPHmTHiIbUL3bWv/8" +"97bfTF9ZC+TNaFuaw6ibz9mV9OzQ50NQrT2w0OLFIke/uIBsm8NS5G4yqVamupHlhwnRkNqjnAowX" +"euEdIzIL03aT2PZwh/Lx2A5Ey0XxerJJPtQcTp1UTSdRafAN+gSvS25DbLUUoAhG4J3auaeD3rg/2" +"REeCeqaRrHbD33kB8Sw/u/dZ6sKE2IKeFIzNZBzihYa62Q1YWcFfYpPe9b+9CFM85kKgRUZMJtxQl" +"aSDpi5eK5+EvyQdCQY/eRvhZLOuqD5EBiNZiYUfcWDk582WkFUX9QKBgQD85odRTpZRW6T5adok7I" +"TexReoUGxqSIyPQfAbdTVXP+yRr0a8WLZSL3wYh7i4qR5B7Rg2K7XZxj/RWzI5cTicvJ7MaD7/N1n" +"X25bPQwgZKXSVJ0vfde4MHzu7w8cltjWz7crO/7qy6kyOi13jI43fixMhoALJHZJIN7A8m10MxwKB" +"gQDhM0HhSYNSI5UEavELyKEyXTj4b6XNJ2SGFsM1gHDJeKarSzPkxEPhxt5S8Hfr5e2wmXl27gd6u" +"52pizriA/H98dvneeXvcYETLbysBKFnWdaJ6P4d45MTEVWnNTWFpbnDMH2GaqJ9WpwxOYrh4QM1nm" +"4Q/kfkpwDzrQpHu5Z/HwKBgQCbpwcB8+xwhoczOwMYVrowof29ikIv6Bca1OqC+9Cosp9XyxkuMyu" +"DydYHAwGeJfiJevO744s5TdtZb1eqIxVE4pKaHE8ppVeWk4BucEM7uVgXtOft0ReCPnb1Sbn4a/0B" +"kQmR/bNbCvmzgfN3KRrYyzArMeCFB75Q1HWT18udYQKBgQCFX7Qe10j9NrGk4ilMj1BY4blB+e26a" +"SAyAf0vDJi/2cLsJCfhzxNc/kjh2iVMAusY8mxrsWGgoMkphpojDa8edUWF/D3f59tIHohlYICvEx" +"fqqaEG1qdeXZ/a3bqLIrG4FlnhZ/pAP4/N34SPbpJWCTSqv86YbwgzSUKTZVgmhQKBgAZbvdZM8x8" +"Et5ho7YqrrBWTwnvMLw4G7qftiMco15JcMAXhfGhmlDI8OUq7MlXVCUWwdZtRYcAyWJcqhWQ3yrXv" +"8cWi9hxwNGPuof5PchDDuR+cwe8gZvpzwfFDFTgJHIBe64CkFd9Oeb5CvEffbrP04mpWvjVeMcjjz" +"ZA6a+IB"; +} // namespace remoting diff --git a/remoting/jingle_glue/iq_request.h b/remoting/jingle_glue/iq_request.h index 9d6dcad..6933b17 100644 --- a/remoting/jingle_glue/iq_request.h +++ b/remoting/jingle_glue/iq_request.h @@ -21,7 +21,6 @@ class JingleClient; // set_callback(). If multiple IQ stanzas are send with SendIq() then only reply // to the last one will be received. // The class must be used on the jingle thread only. -// TODO(sergeyu): Implement unittests for this class. class IqRequest : private buzz::XmppIqHandler { public: typedef Callback1<const buzz::XmlElement*>::Type ReplyCallback; @@ -32,8 +31,8 @@ class IqRequest : private buzz::XmppIqHandler { // Sends stanza of type |type| to |addressee|. |iq_body| contains body of // the stanza. Ownership of |iq_body| is transfered to IqRequest. Must // be called on the jingle thread. - void SendIq(const std::string& type, const std::string& addressee, - buzz::XmlElement* iq_body); + virtual void SendIq(const std::string& type, const std::string& addressee, + buzz::XmlElement* iq_body); // Sets callback that is called when reply stanza is received. Callback // is called on the jingle thread. diff --git a/remoting/jingle_glue/jingle_channel.cc b/remoting/jingle_glue/jingle_channel.cc index 77fcd1b..0ff634c 100644 --- a/remoting/jingle_glue/jingle_channel.cc +++ b/remoting/jingle_glue/jingle_channel.cc @@ -45,6 +45,7 @@ void JingleChannel::Init(JingleThread* thread, thread_ = thread; stream_.reset(stream); stream_->SignalEvent.connect(&event_handler_, &EventHandler::OnStreamEvent); + jid_ = jid; // Initialize |state_|. switch (stream->GetState()) { @@ -63,8 +64,6 @@ void JingleChannel::Init(JingleThread* thread, default: NOTREACHED(); } - - jid_ = jid; } void JingleChannel::Write(scoped_refptr<DataBuffer> data) { diff --git a/remoting/jingle_glue/jingle_client.cc b/remoting/jingle_glue/jingle_client.cc index 5287d77..a50ba71 100644 --- a/remoting/jingle_glue/jingle_client.cc +++ b/remoting/jingle_glue/jingle_client.cc @@ -2,10 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(ajwong): Check the initialization sentinels. Can we base it off of -// state_ instead of a member variable? Also, we assign and read from a few of -// the member variables on two threads. We need to audit this for thread -// safety. +// TODO(ajwong): We assign and read from a few of the member variables on +// two threads. We need to audit this for thread safety. #include "remoting/jingle_glue/jingle_client.h" @@ -13,6 +11,7 @@ #include "base/waitable_event.h" #include "base/message_loop.h" #include "remoting/jingle_glue/gaia_token_pre_xmpp_auth.h" +#include "remoting/jingle_glue/iq_request.h" #include "remoting/jingle_glue/jingle_thread.h" #include "remoting/jingle_glue/relay_port_allocator.h" #include "remoting/jingle_glue/xmpp_socket_adapter.h" @@ -32,13 +31,13 @@ namespace remoting { JingleClient::JingleClient(JingleThread* thread) : client_(NULL), thread_(thread), - state_(START), + state_(CREATED), callback_(NULL) { } JingleClient::~JingleClient() { // JingleClient can be destroyed only after it's closed. - DCHECK(state_ == CLOSED); + DCHECK(state_ == CLOSED || state_ == CREATED); } void JingleClient::Init( @@ -46,13 +45,13 @@ void JingleClient::Init( const std::string& auth_token_service, Callback* callback) { DCHECK_NE(username, ""); DCHECK(callback != NULL); - DCHECK(callback_ == NULL); // Init() can be called only once. + DCHECK(state_ == CREATED); callback_ = callback; - message_loop()->PostTask( FROM_HERE, NewRunnableMethod(this, &JingleClient::DoInitialize, username, auth_token, auth_token_service)); + state_ = INITIALIZED; } JingleChannel* JingleClient::Connect(const std::string& host_jid, @@ -160,6 +159,10 @@ std::string JingleClient::GetFullJid() { return full_jid_; } +IqRequest* JingleClient::CreateIqRequest() { + return new IqRequest(this); +} + MessageLoop* JingleClient::message_loop() { return thread_->message_loop(); } @@ -167,7 +170,7 @@ MessageLoop* JingleClient::message_loop() { void JingleClient::OnConnectionStateChanged(buzz::XmppEngine::State state) { switch (state) { case buzz::XmppEngine::STATE_START: - UpdateState(START); + UpdateState(INITIALIZED); break; case buzz::XmppEngine::STATE_OPENING: UpdateState(CONNECTING); diff --git a/remoting/jingle_glue/jingle_client.h b/remoting/jingle_glue/jingle_client.h index aa94a00..bac15d8 100644 --- a/remoting/jingle_glue/jingle_client.h +++ b/remoting/jingle_glue/jingle_client.h @@ -30,11 +30,14 @@ class Session; namespace remoting { +class IqRequest; + class JingleClient : public base::RefCountedThreadSafe<JingleClient>, public sigslot::has_slots<> { public: enum State { - START, // Initial state. + CREATED, // Initial state. + INITIALIZED, CONNECTING, CONNECTED, CLOSED, @@ -88,6 +91,10 @@ class JingleClient : public base::RefCountedThreadSafe<JingleClient>, // known yet, i.e. authentication hasn't finished. std::string GetFullJid(); + // Creates new IqRequest for this client. Ownership for of the created object + // is transfered to the caller. + virtual IqRequest* CreateIqRequest(); + // Current state of the client. State state() { return state_; } @@ -98,6 +105,8 @@ class JingleClient : public base::RefCountedThreadSafe<JingleClient>, MessageLoop* message_loop(); private: + friend class HeartbeatSenderTest; + void OnConnectionStateChanged(buzz::XmppEngine::State state); void OnIncomingTunnel(cricket::TunnelSessionClient* client, buzz::Jid jid, diff --git a/remoting/jingle_glue/jingle_thread.h b/remoting/jingle_glue/jingle_thread.h index b30d07f..2aa23f3 100644 --- a/remoting/jingle_glue/jingle_thread.h +++ b/remoting/jingle_glue/jingle_thread.h @@ -54,6 +54,8 @@ class JingleThread : public talk_base::Thread, TaskPump* task_pump() { return task_pump_; } private: + friend class HeartbeatSenderTest; + virtual void OnMessage(talk_base::Message* msg); void PumpAuxiliaryLoops(); diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 8b2dec6..3e3283f 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -106,7 +106,7 @@ }], ], # end of 'conditions' }, # end of target 'chromoting_plugin' - + { 'target_name': 'chromoting_base', 'type': '<(library)', @@ -188,6 +188,8 @@ 'host/heartbeat_sender.h', 'host/host_config.cc', 'host/host_config.h', + 'host/host_key_pair.cc', + 'host/host_key_pair.h', 'host/json_host_config.cc', 'host/json_host_config.h', ], @@ -365,9 +367,12 @@ 'host/client_connection_unittest.cc', 'host/differ_unittest.cc', 'host/differ_block_unittest.cc', + 'host/heartbeat_sender_unittest.cc', + 'host/host_key_pair_unittest.cc', 'host/json_host_config_unittest.cc', 'host/mock_objects.h', 'host/session_manager_unittest.cc', + 'host/test_key_pair.h', 'jingle_glue/jingle_thread_unittest.cc', 'jingle_glue/jingle_channel_unittest.cc', 'jingle_glue/iq_request_unittest.cc', |