summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--remoting/base/constants.cc2
-rw-r--r--remoting/base/constants.h3
-rw-r--r--remoting/host/chromoting_host.cc15
-rw-r--r--remoting/host/heartbeat_sender.cc107
-rw-r--r--remoting/host/heartbeat_sender.h59
-rw-r--r--remoting/host/heartbeat_sender_unittest.cc148
-rw-r--r--remoting/host/host_key_pair.cc92
-rw-r--r--remoting/host/host_key_pair.h46
-rw-r--r--remoting/host/host_key_pair_unittest.cc71
-rw-r--r--remoting/host/json_host_config.cc1
-rw-r--r--remoting/host/json_host_config_unittest.cc2
-rw-r--r--remoting/host/test_key_pair.h30
-rw-r--r--remoting/jingle_glue/iq_request.h5
-rw-r--r--remoting/jingle_glue/jingle_channel.cc3
-rw-r--r--remoting/jingle_glue/jingle_client.cc21
-rw-r--r--remoting/jingle_glue/jingle_client.h11
-rw-r--r--remoting/jingle_glue/jingle_thread.h2
-rw-r--r--remoting/remoting.gyp7
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',