// 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/me2me_native_messaging_host.h" #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/stl_util.h" #include "base/strings/stringize_macros.h" #include "base/values.h" #include "google_apis/gaia/gaia_oauth_client.h" #include "net/base/file_stream.h" #include "net/base/net_util.h" #include "remoting/base/auto_thread_task_runner.h" #include "remoting/host/native_messaging/native_messaging_channel.h" #include "remoting/host/pin_hash.h" #include "remoting/host/setup/test_util.h" #include "remoting/protocol/pairing_registry.h" #include "remoting/protocol/protocol_mock_objects.h" #include "testing/gtest/include/gtest/gtest.h" using remoting::protocol::MockPairingRegistryDelegate; using remoting::protocol::PairingRegistry; using remoting::protocol::SynchronousPairingRegistry; namespace { void VerifyHelloResponse(scoped_ptr 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(scoped_ptr 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(scoped_ptr 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(scoped_ptr response) { ASSERT_TRUE(response); std::string value; EXPECT_TRUE(response->GetString("type", &value)); EXPECT_EQ("generateKeyPairResponse", value); EXPECT_TRUE(response->GetString("privateKey", &value)); EXPECT_TRUE(response->GetString("publicKey", &value)); } void VerifyGetDaemonConfigResponse(scoped_ptr 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( scoped_ptr 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("setByPolicy", &set_by_policy)); EXPECT_TRUE(supported); EXPECT_TRUE(allowed); EXPECT_TRUE(set_by_policy); } void VerifyStopDaemonResponse(scoped_ptr response) { ASSERT_TRUE(response); std::string value; EXPECT_TRUE(response->GetString("type", &value)); EXPECT_EQ("stopDaemonResponse", value); EXPECT_TRUE(response->GetString("result", &value)); EXPECT_EQ("OK", value); } void VerifyGetDaemonStateResponse(scoped_ptr response) { ASSERT_TRUE(response); std::string value; EXPECT_TRUE(response->GetString("type", &value)); EXPECT_EQ("getDaemonStateResponse", value); EXPECT_TRUE(response->GetString("state", &value)); EXPECT_EQ("STARTED", value); } void VerifyUpdateDaemonConfigResponse( scoped_ptr response) { ASSERT_TRUE(response); std::string value; EXPECT_TRUE(response->GetString("type", &value)); EXPECT_EQ("updateDaemonConfigResponse", value); EXPECT_TRUE(response->GetString("result", &value)); EXPECT_EQ("OK", value); } void VerifyStartDaemonResponse(scoped_ptr response) { ASSERT_TRUE(response); std::string value; EXPECT_TRUE(response->GetString("type", &value)); EXPECT_EQ("startDaemonResponse", value); EXPECT_TRUE(response->GetString("result", &value)); EXPECT_EQ("OK", value); } } // namespace namespace remoting { class MockDaemonControllerDelegate : public DaemonController::Delegate { public: MockDaemonControllerDelegate(); virtual ~MockDaemonControllerDelegate(); // DaemonController::Delegate interface. virtual DaemonController::State GetState() OVERRIDE; virtual scoped_ptr GetConfig() OVERRIDE; virtual void SetConfigAndStart( scoped_ptr config, bool consent, const DaemonController::CompletionCallback& done) OVERRIDE; virtual void UpdateConfig( scoped_ptr config, const DaemonController::CompletionCallback& done) OVERRIDE; virtual void Stop(const DaemonController::CompletionCallback& done) OVERRIDE; virtual void SetWindow(void* window_handle) OVERRIDE; virtual std::string GetVersion() OVERRIDE; virtual DaemonController::UsageStatsConsent GetUsageStatsConsent() OVERRIDE; private: DISALLOW_COPY_AND_ASSIGN(MockDaemonControllerDelegate); }; MockDaemonControllerDelegate::MockDaemonControllerDelegate() {} MockDaemonControllerDelegate::~MockDaemonControllerDelegate() {} DaemonController::State MockDaemonControllerDelegate::GetState() { return DaemonController::STATE_STARTED; } scoped_ptr MockDaemonControllerDelegate::GetConfig() { return scoped_ptr(new base::DictionaryValue()); } void MockDaemonControllerDelegate::SetConfigAndStart( scoped_ptr config, bool consent, const DaemonController::CompletionCallback& done) { // Verify parameters passed in. if (consent && config && config->HasKey("start")) { done.Run(DaemonController::RESULT_OK); } else { done.Run(DaemonController::RESULT_FAILED); } } void MockDaemonControllerDelegate::UpdateConfig( scoped_ptr config, const DaemonController::CompletionCallback& done) { if (config && config->HasKey("update")) { done.Run(DaemonController::RESULT_OK); } else { done.Run(DaemonController::RESULT_FAILED); } } void MockDaemonControllerDelegate::Stop( const DaemonController::CompletionCallback& done) { done.Run(DaemonController::RESULT_OK); } void MockDaemonControllerDelegate::SetWindow(void* window_handle) {} std::string MockDaemonControllerDelegate::GetVersion() { // Unused - Me2MeNativeMessagingHost returns the compiled-in version string // instead of calling this method. NOTREACHED(); return std::string(); } DaemonController::UsageStatsConsent MockDaemonControllerDelegate::GetUsageStatsConsent() { DaemonController::UsageStatsConsent consent; consent.supported = true; consent.allowed = true; consent.set_by_policy = true; return consent; } class Me2MeNativeMessagingHostTest : public testing::Test { public: Me2MeNativeMessagingHostTest(); virtual ~Me2MeNativeMessagingHostTest(); virtual void SetUp() OVERRIDE; virtual void TearDown() OVERRIDE; scoped_ptr 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 MockDaemonControllerDelegate, which is owned by // |channel_|. MockDaemonControllerDelegate* daemon_controller_delegate_; private: void StartHost(); void StopHost(); void ExitTest(); // Each test creates two unidirectional pipes: "input" and "output". // Me2MeNativeMessagingHost 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] -> Me2MeNativeMessagingHost -> [output] -> unittest base::PlatformFile input_write_handle_; base::PlatformFile output_read_handle_; // Message loop of the test thread. scoped_ptr test_message_loop_; scoped_ptr test_run_loop_; scoped_ptr host_thread_; scoped_ptr host_run_loop_; // Task runner of the host thread. scoped_refptr host_task_runner_; scoped_ptr host_; DISALLOW_COPY_AND_ASSIGN(Me2MeNativeMessagingHostTest); }; Me2MeNativeMessagingHostTest::Me2MeNativeMessagingHostTest() {} Me2MeNativeMessagingHostTest::~Me2MeNativeMessagingHostTest() {} void Me2MeNativeMessagingHostTest::SetUp() { base::PlatformFile input_read_handle; base::PlatformFile output_write_handle; ASSERT_TRUE(MakePipe(&input_read_handle, &input_write_handle_)); ASSERT_TRUE(MakePipe(&output_read_handle_, &output_write_handle)); test_message_loop_.reset(new base::MessageLoop()); test_run_loop_.reset(new base::RunLoop()); // Run the host on a dedicated thread. host_thread_.reset(new base::Thread("host_thread")); host_thread_->Start(); // Arrange to run |test_message_loop_| until no components depend on it. host_task_runner_ = new AutoThreadTaskRunner( host_thread_->message_loop_proxy(), base::Bind(&Me2MeNativeMessagingHostTest::ExitTest, base::Unretained(this))); host_task_runner_->PostTask( FROM_HERE, base::Bind(&Me2MeNativeMessagingHostTest::StartHost, base::Unretained(this))); // Wait until the host finishes starting. test_run_loop_->Run(); } void Me2MeNativeMessagingHostTest::StartHost() { DCHECK(host_task_runner_->RunsTasksOnCurrentThread()); base::PlatformFile input_read_handle; base::PlatformFile output_write_handle; ASSERT_TRUE(MakePipe(&input_read_handle, &input_write_handle_)); ASSERT_TRUE(MakePipe(&output_read_handle_, &output_write_handle)); daemon_controller_delegate_ = new MockDaemonControllerDelegate(); scoped_refptr daemon_controller( new DaemonController( scoped_ptr(daemon_controller_delegate_))); scoped_refptr pairing_registry = new SynchronousPairingRegistry(scoped_ptr( new MockPairingRegistryDelegate())); scoped_ptr channel( new NativeMessagingChannel(input_read_handle, output_write_handle)); host_.reset(new Me2MeNativeMessagingHost(channel.Pass(), daemon_controller, pairing_registry, scoped_ptr())); host_->Start(base::Bind(&Me2MeNativeMessagingHostTest::StopHost, base::Unretained(this))); // Notify the test that the host has finished starting up. test_message_loop_->message_loop_proxy()->PostTask( FROM_HERE, test_run_loop_->QuitClosure()); } void Me2MeNativeMessagingHostTest::StopHost() { DCHECK(host_task_runner_->RunsTasksOnCurrentThread()); host_.reset(); // Wait till all shutdown tasks have completed. base::RunLoop().RunUntilIdle(); // Trigger a test shutdown via ExitTest(). host_task_runner_ = NULL; } void Me2MeNativeMessagingHostTest::ExitTest() { if (!test_message_loop_->message_loop_proxy()->RunsTasksOnCurrentThread()) { test_message_loop_->message_loop_proxy()->PostTask( FROM_HERE, base::Bind(&Me2MeNativeMessagingHostTest::ExitTest, base::Unretained(this))); return; } test_run_loop_->Quit(); } void Me2MeNativeMessagingHostTest::TearDown() { // Closing the write-end of the input will send an EOF to the native // messaging reader. This will trigger a host shutdown. base::ClosePlatformFile(input_write_handle_); // Start a new RunLoop and Wait until the host finishes shutting down. test_run_loop_.reset(new base::RunLoop()); test_run_loop_->Run(); // Verify there are no more message in the output pipe. scoped_ptr response = ReadMessageFromOutputPipe(); EXPECT_FALSE(response); // The It2MeMe2MeNativeMessagingHost dtor closes the handles that are passed // to it. So the only handle left to close is |output_read_handle_|. base::ClosePlatformFile(output_read_handle_); } scoped_ptr Me2MeNativeMessagingHostTest::ReadMessageFromOutputPipe() { uint32 length; int read_result = base::ReadPlatformFileAtCurrentPos( output_read_handle_, reinterpret_cast(&length), sizeof(length)); if (read_result != sizeof(length)) { return scoped_ptr(); } std::string message_json(length, '\0'); read_result = base::ReadPlatformFileAtCurrentPos( output_read_handle_, string_as_array(&message_json), length); if (read_result != static_cast(length)) { return scoped_ptr(); } scoped_ptr message(base::JSONReader::Read(message_json)); if (!message || !message->IsType(base::Value::TYPE_DICTIONARY)) { return scoped_ptr(); } return scoped_ptr( static_cast(message.release())); } void Me2MeNativeMessagingHostTest::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(&length), sizeof(length)); base::WritePlatformFileAtCurrentPos(input_write_handle_, message_json.data(), length); } void Me2MeNativeMessagingHostTest::TestBadRequest(const base::Value& message) { base::DictionaryValue good_message; good_message.SetString("type", "hello"); // This test currently relies on synchronous processing of hello messages and // message parameters verification. WriteMessageToInputPipe(good_message); WriteMessageToInputPipe(message); WriteMessageToInputPipe(good_message); // Read from output pipe, and verify responses. scoped_ptr response = ReadMessageFromOutputPipe(); VerifyHelloResponse(response.Pass()); response = ReadMessageFromOutputPipe(); EXPECT_FALSE(response); } // TODO (weitaosu): crbug.com/323306. Re-enable these tests. // Test all valid request-types. TEST_F(Me2MeNativeMessagingHostTest, All) { int next_id = 0; base::DictionaryValue message; message.SetInteger("id", next_id++); message.SetString("type", "hello"); WriteMessageToInputPipe(message); message.SetInteger("id", next_id++); message.SetString("type", "getHostName"); WriteMessageToInputPipe(message); message.SetInteger("id", next_id++); message.SetString("type", "getPinHash"); message.SetString("hostId", "my_host"); message.SetString("pin", "1234"); WriteMessageToInputPipe(message); message.Clear(); message.SetInteger("id", next_id++); message.SetString("type", "generateKeyPair"); WriteMessageToInputPipe(message); message.SetInteger("id", next_id++); message.SetString("type", "getDaemonConfig"); WriteMessageToInputPipe(message); message.SetInteger("id", next_id++); message.SetString("type", "getUsageStatsConsent"); WriteMessageToInputPipe(message); message.SetInteger("id", next_id++); message.SetString("type", "stopDaemon"); WriteMessageToInputPipe(message); message.SetInteger("id", next_id++); 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.SetInteger("id", next_id++); message.SetString("type", "updateDaemonConfig"); WriteMessageToInputPipe(message); config.Clear(); config.SetBoolean("start", true); message.Set("config", config.DeepCopy()); message.SetBoolean("consent", true); message.SetInteger("id", next_id++); message.SetString("type", "startDaemon"); WriteMessageToInputPipe(message); void (*verify_routines[])(scoped_ptr) = { &VerifyHelloResponse, &VerifyGetHostNameResponse, &VerifyGetPinHashResponse, &VerifyGenerateKeyPairResponse, &VerifyGetDaemonConfigResponse, &VerifyGetUsageStatsConsentResponse, &VerifyStopDaemonResponse, &VerifyGetDaemonStateResponse, &VerifyUpdateDaemonConfigResponse, &VerifyStartDaemonResponse, }; ASSERT_EQ(arraysize(verify_routines), static_cast(next_id)); // Read all responses from output pipe, and verify them. for (int i = 0; i < next_id; ++i) { scoped_ptr response = ReadMessageFromOutputPipe(); // Make sure that id is available and is in the range. int id; ASSERT_TRUE(response->GetInteger("id", &id)); ASSERT_TRUE(0 <= id && id < next_id); // Call the verification routine corresponding to the message id. ASSERT_TRUE(verify_routines[id]); verify_routines[id](response.Pass()); // Clear the pointer so that the routine cannot be called the second time. verify_routines[id] = NULL; } } // Verify that response ID matches request ID. TEST_F(Me2MeNativeMessagingHostTest, Id) { base::DictionaryValue message; message.SetString("type", "hello"); WriteMessageToInputPipe(message); message.SetString("id", "42"); WriteMessageToInputPipe(message); scoped_ptr 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(Me2MeNativeMessagingHostTest, WrongFormat) { base::ListValue message; TestBadRequest(message); } // Verify requests with no type are rejected. TEST_F(Me2MeNativeMessagingHostTest, MissingType) { base::DictionaryValue message; TestBadRequest(message); } // Verify rejection if type is unrecognized. TEST_F(Me2MeNativeMessagingHostTest, InvalidType) { base::DictionaryValue message; message.SetString("type", "xxx"); TestBadRequest(message); } // Verify rejection if getPinHash request has no hostId. TEST_F(Me2MeNativeMessagingHostTest, GetPinHashNoHostId) { base::DictionaryValue message; message.SetString("type", "getPinHash"); message.SetString("pin", "1234"); TestBadRequest(message); } // Verify rejection if getPinHash request has no pin. TEST_F(Me2MeNativeMessagingHostTest, 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(Me2MeNativeMessagingHostTest, UpdateDaemonConfigInvalidConfig) { base::DictionaryValue message; message.SetString("type", "updateDaemonConfig"); message.SetString("config", "xxx"); TestBadRequest(message); } // Verify rejection if startDaemon request has invalid config. TEST_F(Me2MeNativeMessagingHostTest, 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(Me2MeNativeMessagingHostTest, StartDaemonNoConsent) { base::DictionaryValue message; message.SetString("type", "startDaemon"); message.Set("config", base::DictionaryValue().DeepCopy()); TestBadRequest(message); } } // namespace remoting