Index: remoting/host/setup/native_messaging_host_unittest.cc |
diff --git a/remoting/host/setup/native_messaging_host_unittest.cc b/remoting/host/setup/native_messaging_host_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b6fc4abf156bf62a412043e33b8fa63995dbc1bb |
--- /dev/null |
+++ b/remoting/host/setup/native_messaging_host_unittest.cc |
@@ -0,0 +1,517 @@ |
+// Copyright 2013 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/setup/native_messaging_host.h" |
+ |
+#include "base/compiler_specific.h" |
+#include "base/json/json_reader.h" |
+#include "base/json/json_writer.h" |
+#include "base/message_loop.h" |
+#include "base/run_loop.h" |
+#include "base/stl_util.h" |
+#include "base/strings/stringize_macros.h" |
+#include "base/values.h" |
+#include "net/base/file_stream.h" |
+#include "net/base/net_util.h" |
+#include "remoting/host/pin_hash.h" |
+#include "remoting/host/setup/test_util.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace { |
+ |
+void VerifyHelloResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("helloResponse", value); |
+ EXPECT_TRUE(response->GetString("version", &value)); |
+ EXPECT_EQ(STRINGIZE(VERSION), value); |
+} |
+ |
+void VerifyGetHostNameResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("getHostNameResponse", value); |
+ EXPECT_TRUE(response->GetString("hostname", &value)); |
+ EXPECT_EQ(net::GetHostName(), value); |
+} |
+ |
+void VerifyGetPinHashResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("getPinHashResponse", value); |
+ EXPECT_TRUE(response->GetString("hash", &value)); |
+ EXPECT_EQ(remoting::MakeHostPinHash("my_host", "1234"), value); |
+} |
+ |
+void VerifyGenerateKeyPairResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("generateKeyPairResponse", value); |
+ EXPECT_TRUE(response->GetString("private_key", &value)); |
+ EXPECT_TRUE(response->GetString("public_key", &value)); |
+} |
+ |
+void VerifyGetDaemonConfigResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("getDaemonConfigResponse", value); |
+ const base::DictionaryValue* config = NULL; |
+ EXPECT_TRUE(response->GetDictionary("config", &config)); |
+ EXPECT_TRUE(base::DictionaryValue().Equals(config)); |
+} |
+ |
+void VerifyGetUsageStatsConsentResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("getUsageStatsConsentResponse", value); |
+ bool supported, allowed, set_by_policy; |
+ EXPECT_TRUE(response->GetBoolean("supported", &supported)); |
+ EXPECT_TRUE(response->GetBoolean("allowed", &allowed)); |
+ EXPECT_TRUE(response->GetBoolean("set_by_policy", &set_by_policy)); |
+ EXPECT_TRUE(supported); |
+ EXPECT_TRUE(allowed); |
+ EXPECT_TRUE(set_by_policy); |
+} |
+ |
+void VerifyStopDaemonResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("stopDaemonResponse", value); |
+ int result; |
+ EXPECT_TRUE(response->GetInteger("result", &result)); |
+ EXPECT_EQ(0, result); |
+} |
+ |
+void VerifyGetDaemonStateResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("getDaemonStateResponse", value); |
+ int result; |
+ EXPECT_TRUE(response->GetInteger("state", &result)); |
+ EXPECT_EQ(4, result); |
+} |
+ |
+void VerifyUpdateDaemonConfigResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("updateDaemonConfigResponse", value); |
+ int result; |
+ EXPECT_TRUE(response->GetInteger("result", &result)); |
+ EXPECT_EQ(0, result); |
+} |
+ |
+void VerifyStartDaemonResponse(const base::DictionaryValue* response) { |
+ ASSERT_TRUE(response); |
+ std::string value; |
+ EXPECT_TRUE(response->GetString("type", &value)); |
+ EXPECT_EQ("startDaemonResponse", value); |
+ int result; |
+ EXPECT_TRUE(response->GetInteger("result", &result)); |
+ EXPECT_EQ(0, result); |
+} |
+ |
+} // namespace |
+ |
+namespace remoting { |
+ |
+class MockDaemonController : public DaemonController { |
+ public: |
+ MockDaemonController(); |
+ virtual ~MockDaemonController(); |
+ |
+ virtual State GetState() OVERRIDE; |
+ virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; |
+ virtual void SetConfigAndStart(scoped_ptr<base::DictionaryValue> config, |
+ bool consent, |
+ const CompletionCallback& callback) OVERRIDE; |
+ virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config, |
+ const CompletionCallback& callback) OVERRIDE; |
+ virtual void Stop(const CompletionCallback& callback) OVERRIDE; |
+ virtual void SetWindow(void* window_handle) OVERRIDE; |
+ virtual void GetVersion(const GetVersionCallback& callback) OVERRIDE; |
+ virtual void GetUsageStatsConsent( |
+ const GetUsageStatsConsentCallback& callback) OVERRIDE; |
+ |
+ // Returns a record of functions called, so that unittests can verify these |
+ // were called in the proper sequence. |
+ std::string call_log() { return call_log_; } |
+ |
+ private: |
+ std::string call_log_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(MockDaemonController); |
+}; |
+ |
+MockDaemonController::MockDaemonController() {} |
+ |
+MockDaemonController::~MockDaemonController() {} |
+ |
+DaemonController::State MockDaemonController::GetState() { |
+ call_log_ += "GetState:"; |
+ return DaemonController::STATE_STARTED; |
+} |
+ |
+void MockDaemonController::GetConfig(const GetConfigCallback& callback) { |
+ call_log_ += "GetConfig:"; |
+ scoped_ptr<base::DictionaryValue> config(new base::DictionaryValue()); |
+ callback.Run(config.Pass()); |
+} |
+ |
+void MockDaemonController::SetConfigAndStart( |
+ scoped_ptr<base::DictionaryValue> config, bool consent, |
+ const CompletionCallback& callback) { |
+ call_log_ += "SetConfigAndStart:"; |
+ |
+ // Verify parameters passed in. |
+ if (consent && config && config->HasKey("start")) { |
+ callback.Run(DaemonController::RESULT_OK); |
+ } else { |
+ callback.Run(DaemonController::RESULT_FAILED); |
+ } |
+} |
+ |
+void MockDaemonController::UpdateConfig( |
+ scoped_ptr<base::DictionaryValue> config, |
+ const CompletionCallback& callback) { |
+ call_log_ += "UpdateConfig:"; |
+ if (config && config->HasKey("update")) { |
+ callback.Run(DaemonController::RESULT_OK); |
+ } else { |
+ callback.Run(DaemonController::RESULT_FAILED); |
+ } |
+} |
+ |
+void MockDaemonController::Stop(const CompletionCallback& callback) { |
+ call_log_ += "Stop:"; |
+ callback.Run(DaemonController::RESULT_OK); |
+} |
+ |
+void MockDaemonController::SetWindow(void* window_handle) {} |
+ |
+void MockDaemonController::GetVersion(const GetVersionCallback& callback) { |
+ // Unused - NativeMessagingHost returns the compiled-in version string |
+ // instead of calling this method. |
+} |
+ |
+void MockDaemonController::GetUsageStatsConsent( |
+ const GetUsageStatsConsentCallback& callback) { |
+ call_log_ += "GetUsageStatsConsent:"; |
+ callback.Run(true, true, true); |
+} |
+ |
+class NativeMessagingHostTest : public testing::Test { |
+ public: |
+ NativeMessagingHostTest(); |
+ virtual ~NativeMessagingHostTest(); |
+ |
+ virtual void SetUp() OVERRIDE; |
+ virtual void TearDown() OVERRIDE; |
+ |
+ void Run(); |
+ |
+ scoped_ptr<base::DictionaryValue> ReadMessageFromOutputPipe(); |
+ |
+ void WriteMessageToInputPipe(const base::Value& message); |
+ |
+ // The Host process should shut down when it receives a malformed request. |
+ // This is tested by sending a known-good request, followed by |message|, |
+ // followed by the known-good request again. The response file should only |
+ // contain a single response from the first good request. |
+ void TestBadRequest(const base::Value& message); |
+ |
+ protected: |
+ // Reference to the MockDaemonController, which is owned by |host_|. |
+ MockDaemonController* daemon_controller_; |
+ std::string call_log_; |
+ |
+ private: |
+ // Each test creates two unidirectional pipes: "input" and "output". |
+ // NativeMessagingHost reads from input_read_handle and writes to |
+ // output_write_handle. The unittest supplies data to input_write_handle, and |
+ // verifies output from output_read_handle. |
+ // |
+ // unittest -> [input] -> NativeMessagingHost -> [output] -> unittest |
+ base::PlatformFile input_read_handle_; |
+ base::PlatformFile input_write_handle_; |
+ base::PlatformFile output_read_handle_; |
+ base::PlatformFile output_write_handle_; |
+ |
+ base::MessageLoop message_loop_; |
+ base::RunLoop run_loop_; |
+ scoped_ptr<remoting::NativeMessagingHost> host_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(NativeMessagingHostTest); |
+}; |
+ |
+NativeMessagingHostTest::NativeMessagingHostTest() |
+ : message_loop_(base::MessageLoop::TYPE_IO) {} |
+ |
+NativeMessagingHostTest::~NativeMessagingHostTest() {} |
+ |
+void NativeMessagingHostTest::SetUp() { |
+ ASSERT_TRUE(MakePipe(&input_read_handle_, &input_write_handle_)); |
+ ASSERT_TRUE(MakePipe(&output_read_handle_, &output_write_handle_)); |
+ |
+ daemon_controller_ = new MockDaemonController(); |
+ scoped_ptr<DaemonController> daemon_controller(daemon_controller_); |
+ host_.reset(new NativeMessagingHost(daemon_controller.Pass(), |
+ input_read_handle_, output_write_handle_, |
+ message_loop_.message_loop_proxy(), |
+ run_loop_.QuitClosure())); |
+} |
+ |
+void NativeMessagingHostTest::TearDown() { |
+ // The NativeMessagingHost dtor closes the handles that are passed to it. |
+ // |input_write_handle_| gets closed just before starting the host. So the |
+ // only handle left to close is |output_read_handle_|. |
+ base::ClosePlatformFile(output_read_handle_); |
+} |
+ |
+void NativeMessagingHostTest::Run() { |
+ // Close the write-end of input, so that the host sees EOF after reading |
+ // messages and won't block waiting for more input. |
+ base::ClosePlatformFile(input_write_handle_); |
+ host_->Start(); |
+ run_loop_.Run(); |
+ |
+ // Destroy |host_| so that it closes its end of the output pipe, so that |
+ // TestBadRequest() will see EOF and won't block waiting for more data. |
+ // Since |host_| owns |daemon_controller_|, capture its call log first. |
+ call_log_ = daemon_controller_->call_log(); |
+ host_.reset(NULL); |
+} |
+ |
+scoped_ptr<base::DictionaryValue> |
+NativeMessagingHostTest::ReadMessageFromOutputPipe() { |
+ uint32 length; |
+ int read_result = base::ReadPlatformFileAtCurrentPos( |
+ output_read_handle_, reinterpret_cast<char*>(&length), sizeof(length)); |
+ if (read_result != sizeof(length)) { |
+ return scoped_ptr<base::DictionaryValue>(); |
+ } |
+ |
+ std::string message_json(length, '\0'); |
+ read_result = base::ReadPlatformFileAtCurrentPos( |
+ output_read_handle_, string_as_array(&message_json), length); |
+ if (read_result != static_cast<int>(length)) { |
+ return scoped_ptr<base::DictionaryValue>(); |
+ } |
+ |
+ scoped_ptr<base::Value> message(base::JSONReader::Read(message_json)); |
+ if (!message || !message->IsType(base::Value::TYPE_DICTIONARY)) { |
+ return scoped_ptr<base::DictionaryValue>(); |
+ } |
+ |
+ return scoped_ptr<base::DictionaryValue>( |
+ static_cast<base::DictionaryValue*>(message.release())); |
+} |
+ |
+void NativeMessagingHostTest::WriteMessageToInputPipe( |
+ const base::Value& message) { |
+ std::string message_json; |
+ base::JSONWriter::Write(&message, &message_json); |
+ |
+ uint32 length = message_json.length(); |
+ base::WritePlatformFileAtCurrentPos(input_write_handle_, |
+ reinterpret_cast<char*>(&length), |
+ sizeof(length)); |
+ base::WritePlatformFileAtCurrentPos(input_write_handle_, message_json.data(), |
+ length); |
+} |
+ |
+void NativeMessagingHostTest::TestBadRequest(const base::Value& message) { |
+ base::DictionaryValue good_message; |
+ good_message.SetString("type", "hello"); |
+ |
+ WriteMessageToInputPipe(good_message); |
+ WriteMessageToInputPipe(message); |
+ WriteMessageToInputPipe(good_message); |
+ |
+ Run(); |
+ |
+ // Read from output pipe, and verify responses. |
+ scoped_ptr<base::DictionaryValue> response = |
+ ReadMessageFromOutputPipe(); |
+ VerifyHelloResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ EXPECT_FALSE(response); |
+} |
+ |
+// Test all valid request-types. |
+TEST_F(NativeMessagingHostTest, All) { |
+ base::DictionaryValue message; |
+ message.SetString("type", "hello"); |
+ WriteMessageToInputPipe(message); |
+ |
+ message.SetString("type", "getHostName"); |
+ WriteMessageToInputPipe(message); |
+ |
+ message.SetString("type", "getPinHash"); |
+ message.SetString("hostId", "my_host"); |
+ message.SetString("pin", "1234"); |
+ WriteMessageToInputPipe(message); |
+ |
+ message.Clear(); |
+ message.SetString("type", "generateKeyPair"); |
+ WriteMessageToInputPipe(message); |
+ |
+ message.SetString("type", "getDaemonConfig"); |
+ WriteMessageToInputPipe(message); |
+ |
+ message.SetString("type", "getUsageStatsConsent"); |
+ WriteMessageToInputPipe(message); |
+ |
+ message.SetString("type", "stopDaemon"); |
+ WriteMessageToInputPipe(message); |
+ |
+ message.SetString("type", "getDaemonState"); |
+ WriteMessageToInputPipe(message); |
+ |
+ // Following messages require a "config" dictionary. |
+ base::DictionaryValue config; |
+ config.SetBoolean("update", true); |
+ message.Set("config", config.DeepCopy()); |
+ message.SetString("type", "updateDaemonConfig"); |
+ WriteMessageToInputPipe(message); |
+ |
+ config.Clear(); |
+ config.SetBoolean("start", true); |
+ message.Set("config", config.DeepCopy()); |
+ message.SetBoolean("consent", true); |
+ message.SetString("type", "startDaemon"); |
+ WriteMessageToInputPipe(message); |
+ |
+ Run(); |
+ |
+ // Read from output pipe, and verify responses. |
+ scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe(); |
+ VerifyHelloResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ VerifyGetHostNameResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ VerifyGetPinHashResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ VerifyGenerateKeyPairResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ VerifyGetDaemonConfigResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ VerifyGetUsageStatsConsentResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ VerifyStopDaemonResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ VerifyGetDaemonStateResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ VerifyUpdateDaemonConfigResponse(response.get()); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ VerifyStartDaemonResponse(response.get()); |
+ |
+ // Verify that DaemonController methods were called in the correct sequence. |
+ // This detects cases where NativeMessagingHost might call a wrong method |
+ // that takes the same parameters and writes out the same response. |
+ EXPECT_EQ("GetConfig:GetUsageStatsConsent:Stop:GetState:UpdateConfig:" |
+ "SetConfigAndStart:", call_log_); |
+} |
+ |
+// Verify that response ID matches request ID. |
+TEST_F(NativeMessagingHostTest, Id) { |
+ base::DictionaryValue message; |
+ message.SetString("type", "hello"); |
+ WriteMessageToInputPipe(message); |
+ message.SetString("id", "42"); |
+ WriteMessageToInputPipe(message); |
+ |
+ Run(); |
+ |
+ scoped_ptr<base::DictionaryValue> response = |
+ ReadMessageFromOutputPipe(); |
+ EXPECT_TRUE(response); |
+ std::string value; |
+ EXPECT_FALSE(response->GetString("id", &value)); |
+ |
+ response = ReadMessageFromOutputPipe(); |
+ EXPECT_TRUE(response); |
+ EXPECT_TRUE(response->GetString("id", &value)); |
+ EXPECT_EQ("42", value); |
+} |
+ |
+// Verify non-Dictionary requests are rejected. |
+TEST_F(NativeMessagingHostTest, WrongFormat) { |
+ base::ListValue message; |
+ TestBadRequest(message); |
+} |
+ |
+// Verify requests with no type are rejected. |
+TEST_F(NativeMessagingHostTest, MissingType) { |
+ base::DictionaryValue message; |
+ TestBadRequest(message); |
+} |
+ |
+// Verify rejection if type is unrecognized. |
+TEST_F(NativeMessagingHostTest, InvalidType) { |
+ base::DictionaryValue message; |
+ message.SetString("type", "xxx"); |
+ TestBadRequest(message); |
+} |
+ |
+// Verify rejection if getPinHash request has no hostId. |
+TEST_F(NativeMessagingHostTest, GetPinHashNoHostId) { |
+ base::DictionaryValue message; |
+ message.SetString("type", "getPinHash"); |
+ message.SetString("pin", "1234"); |
+ TestBadRequest(message); |
+} |
+ |
+// Verify rejection if getPinHash request has no pin. |
+TEST_F(NativeMessagingHostTest, GetPinHashNoPin) { |
+ base::DictionaryValue message; |
+ message.SetString("type", "getPinHash"); |
+ message.SetString("hostId", "my_host"); |
+ TestBadRequest(message); |
+} |
+ |
+// Verify rejection if updateDaemonConfig request has invalid config. |
+TEST_F(NativeMessagingHostTest, UpdateDaemonConfigInvalidConfig) { |
+ base::DictionaryValue message; |
+ message.SetString("type", "updateDaemonConfig"); |
+ message.SetString("config", "xxx"); |
+ TestBadRequest(message); |
+} |
+ |
+// Verify rejection if startDaemon request has invalid config. |
+TEST_F(NativeMessagingHostTest, StartDaemonInvalidConfig) { |
+ base::DictionaryValue message; |
+ message.SetString("type", "startDaemon"); |
+ message.SetString("config", "xxx"); |
+ message.SetBoolean("consent", true); |
+ TestBadRequest(message); |
+} |
+ |
+// Verify rejection if startDaemon request has no "consent" parameter. |
+TEST_F(NativeMessagingHostTest, StartDaemonNoConsent) { |
+ base::DictionaryValue message; |
+ message.SetString("type", "startDaemon"); |
+ message.Set("config", base::DictionaryValue().DeepCopy()); |
+ TestBadRequest(message); |
+} |
+ |
+} // namespace remoting |