OLD | NEW |
(Empty) | |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "remoting/host/setup/native_messaging_host.h" |
| 6 |
| 7 #include "base/compiler_specific.h" |
| 8 #include "base/json/json_reader.h" |
| 9 #include "base/json/json_writer.h" |
| 10 #include "base/message_loop.h" |
| 11 #include "base/run_loop.h" |
| 12 #include "base/stl_util.h" |
| 13 #include "base/strings/stringize_macros.h" |
| 14 #include "base/values.h" |
| 15 #include "net/base/file_stream.h" |
| 16 #include "net/base/net_util.h" |
| 17 #include "remoting/host/pin_hash.h" |
| 18 #include "remoting/host/setup/test_util.h" |
| 19 #include "testing/gtest/include/gtest/gtest.h" |
| 20 |
| 21 namespace { |
| 22 |
| 23 void VerifyHelloResponse(const base::DictionaryValue* response) { |
| 24 ASSERT_TRUE(response); |
| 25 std::string value; |
| 26 EXPECT_TRUE(response->GetString("type", &value)); |
| 27 EXPECT_EQ("helloResponse", value); |
| 28 EXPECT_TRUE(response->GetString("version", &value)); |
| 29 EXPECT_EQ(STRINGIZE(VERSION), value); |
| 30 } |
| 31 |
| 32 void VerifyGetHostNameResponse(const base::DictionaryValue* response) { |
| 33 ASSERT_TRUE(response); |
| 34 std::string value; |
| 35 EXPECT_TRUE(response->GetString("type", &value)); |
| 36 EXPECT_EQ("getHostNameResponse", value); |
| 37 EXPECT_TRUE(response->GetString("hostname", &value)); |
| 38 EXPECT_EQ(net::GetHostName(), value); |
| 39 } |
| 40 |
| 41 void VerifyGetPinHashResponse(const base::DictionaryValue* response) { |
| 42 ASSERT_TRUE(response); |
| 43 std::string value; |
| 44 EXPECT_TRUE(response->GetString("type", &value)); |
| 45 EXPECT_EQ("getPinHashResponse", value); |
| 46 EXPECT_TRUE(response->GetString("hash", &value)); |
| 47 EXPECT_EQ(remoting::MakeHostPinHash("my_host", "1234"), value); |
| 48 } |
| 49 |
| 50 void VerifyGenerateKeyPairResponse(const base::DictionaryValue* response) { |
| 51 ASSERT_TRUE(response); |
| 52 std::string value; |
| 53 EXPECT_TRUE(response->GetString("type", &value)); |
| 54 EXPECT_EQ("generateKeyPairResponse", value); |
| 55 EXPECT_TRUE(response->GetString("private_key", &value)); |
| 56 EXPECT_TRUE(response->GetString("public_key", &value)); |
| 57 } |
| 58 |
| 59 void VerifyGetDaemonConfigResponse(const base::DictionaryValue* response) { |
| 60 ASSERT_TRUE(response); |
| 61 std::string value; |
| 62 EXPECT_TRUE(response->GetString("type", &value)); |
| 63 EXPECT_EQ("getDaemonConfigResponse", value); |
| 64 const base::DictionaryValue* config = NULL; |
| 65 EXPECT_TRUE(response->GetDictionary("config", &config)); |
| 66 EXPECT_TRUE(base::DictionaryValue().Equals(config)); |
| 67 } |
| 68 |
| 69 void VerifyGetUsageStatsConsentResponse(const base::DictionaryValue* response) { |
| 70 ASSERT_TRUE(response); |
| 71 std::string value; |
| 72 EXPECT_TRUE(response->GetString("type", &value)); |
| 73 EXPECT_EQ("getUsageStatsConsentResponse", value); |
| 74 bool supported, allowed, set_by_policy; |
| 75 EXPECT_TRUE(response->GetBoolean("supported", &supported)); |
| 76 EXPECT_TRUE(response->GetBoolean("allowed", &allowed)); |
| 77 EXPECT_TRUE(response->GetBoolean("set_by_policy", &set_by_policy)); |
| 78 EXPECT_TRUE(supported); |
| 79 EXPECT_TRUE(allowed); |
| 80 EXPECT_TRUE(set_by_policy); |
| 81 } |
| 82 |
| 83 void VerifyStopDaemonResponse(const base::DictionaryValue* response) { |
| 84 ASSERT_TRUE(response); |
| 85 std::string value; |
| 86 EXPECT_TRUE(response->GetString("type", &value)); |
| 87 EXPECT_EQ("stopDaemonResponse", value); |
| 88 int result; |
| 89 EXPECT_TRUE(response->GetInteger("result", &result)); |
| 90 EXPECT_EQ(0, result); |
| 91 } |
| 92 |
| 93 void VerifyGetDaemonStateResponse(const base::DictionaryValue* response) { |
| 94 ASSERT_TRUE(response); |
| 95 std::string value; |
| 96 EXPECT_TRUE(response->GetString("type", &value)); |
| 97 EXPECT_EQ("getDaemonStateResponse", value); |
| 98 int result; |
| 99 EXPECT_TRUE(response->GetInteger("state", &result)); |
| 100 EXPECT_EQ(4, result); |
| 101 } |
| 102 |
| 103 void VerifyUpdateDaemonConfigResponse(const base::DictionaryValue* response) { |
| 104 ASSERT_TRUE(response); |
| 105 std::string value; |
| 106 EXPECT_TRUE(response->GetString("type", &value)); |
| 107 EXPECT_EQ("updateDaemonConfigResponse", value); |
| 108 int result; |
| 109 EXPECT_TRUE(response->GetInteger("result", &result)); |
| 110 EXPECT_EQ(0, result); |
| 111 } |
| 112 |
| 113 void VerifyStartDaemonResponse(const base::DictionaryValue* response) { |
| 114 ASSERT_TRUE(response); |
| 115 std::string value; |
| 116 EXPECT_TRUE(response->GetString("type", &value)); |
| 117 EXPECT_EQ("startDaemonResponse", value); |
| 118 int result; |
| 119 EXPECT_TRUE(response->GetInteger("result", &result)); |
| 120 EXPECT_EQ(0, result); |
| 121 } |
| 122 |
| 123 } // namespace |
| 124 |
| 125 namespace remoting { |
| 126 |
| 127 class MockDaemonController : public DaemonController { |
| 128 public: |
| 129 MockDaemonController(); |
| 130 virtual ~MockDaemonController(); |
| 131 |
| 132 virtual State GetState() OVERRIDE; |
| 133 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; |
| 134 virtual void SetConfigAndStart(scoped_ptr<base::DictionaryValue> config, |
| 135 bool consent, |
| 136 const CompletionCallback& callback) OVERRIDE; |
| 137 virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config, |
| 138 const CompletionCallback& callback) OVERRIDE; |
| 139 virtual void Stop(const CompletionCallback& callback) OVERRIDE; |
| 140 virtual void SetWindow(void* window_handle) OVERRIDE; |
| 141 virtual void GetVersion(const GetVersionCallback& callback) OVERRIDE; |
| 142 virtual void GetUsageStatsConsent( |
| 143 const GetUsageStatsConsentCallback& callback) OVERRIDE; |
| 144 |
| 145 // Returns a record of functions called, so that unittests can verify these |
| 146 // were called in the proper sequence. |
| 147 std::string call_log() { return call_log_; } |
| 148 |
| 149 private: |
| 150 std::string call_log_; |
| 151 |
| 152 DISALLOW_COPY_AND_ASSIGN(MockDaemonController); |
| 153 }; |
| 154 |
| 155 MockDaemonController::MockDaemonController() {} |
| 156 |
| 157 MockDaemonController::~MockDaemonController() {} |
| 158 |
| 159 DaemonController::State MockDaemonController::GetState() { |
| 160 call_log_ += "GetState:"; |
| 161 return DaemonController::STATE_STARTED; |
| 162 } |
| 163 |
| 164 void MockDaemonController::GetConfig(const GetConfigCallback& callback) { |
| 165 call_log_ += "GetConfig:"; |
| 166 scoped_ptr<base::DictionaryValue> config(new base::DictionaryValue()); |
| 167 callback.Run(config.Pass()); |
| 168 } |
| 169 |
| 170 void MockDaemonController::SetConfigAndStart( |
| 171 scoped_ptr<base::DictionaryValue> config, bool consent, |
| 172 const CompletionCallback& callback) { |
| 173 call_log_ += "SetConfigAndStart:"; |
| 174 |
| 175 // Verify parameters passed in. |
| 176 if (consent && config && config->HasKey("start")) { |
| 177 callback.Run(DaemonController::RESULT_OK); |
| 178 } else { |
| 179 callback.Run(DaemonController::RESULT_FAILED); |
| 180 } |
| 181 } |
| 182 |
| 183 void MockDaemonController::UpdateConfig( |
| 184 scoped_ptr<base::DictionaryValue> config, |
| 185 const CompletionCallback& callback) { |
| 186 call_log_ += "UpdateConfig:"; |
| 187 if (config && config->HasKey("update")) { |
| 188 callback.Run(DaemonController::RESULT_OK); |
| 189 } else { |
| 190 callback.Run(DaemonController::RESULT_FAILED); |
| 191 } |
| 192 } |
| 193 |
| 194 void MockDaemonController::Stop(const CompletionCallback& callback) { |
| 195 call_log_ += "Stop:"; |
| 196 callback.Run(DaemonController::RESULT_OK); |
| 197 } |
| 198 |
| 199 void MockDaemonController::SetWindow(void* window_handle) {} |
| 200 |
| 201 void MockDaemonController::GetVersion(const GetVersionCallback& callback) { |
| 202 // Unused - NativeMessagingHost returns the compiled-in version string |
| 203 // instead of calling this method. |
| 204 } |
| 205 |
| 206 void MockDaemonController::GetUsageStatsConsent( |
| 207 const GetUsageStatsConsentCallback& callback) { |
| 208 call_log_ += "GetUsageStatsConsent:"; |
| 209 callback.Run(true, true, true); |
| 210 } |
| 211 |
| 212 class NativeMessagingHostTest : public testing::Test { |
| 213 public: |
| 214 NativeMessagingHostTest(); |
| 215 virtual ~NativeMessagingHostTest(); |
| 216 |
| 217 virtual void SetUp() OVERRIDE; |
| 218 virtual void TearDown() OVERRIDE; |
| 219 |
| 220 void Run(); |
| 221 |
| 222 scoped_ptr<base::DictionaryValue> ReadMessageFromOutputPipe(); |
| 223 |
| 224 void WriteMessageToInputPipe(const base::Value& message); |
| 225 |
| 226 // The Host process should shut down when it receives a malformed request. |
| 227 // This is tested by sending a known-good request, followed by |message|, |
| 228 // followed by the known-good request again. The response file should only |
| 229 // contain a single response from the first good request. |
| 230 void TestBadRequest(const base::Value& message); |
| 231 |
| 232 protected: |
| 233 // Reference to the MockDaemonController, which is owned by |host_|. |
| 234 MockDaemonController* daemon_controller_; |
| 235 std::string call_log_; |
| 236 |
| 237 private: |
| 238 // Each test creates two unidirectional pipes: "input" and "output". |
| 239 // NativeMessagingHost reads from input_read_handle and writes to |
| 240 // output_write_handle. The unittest supplies data to input_write_handle, and |
| 241 // verifies output from output_read_handle. |
| 242 // |
| 243 // unittest -> [input] -> NativeMessagingHost -> [output] -> unittest |
| 244 base::PlatformFile input_read_handle_; |
| 245 base::PlatformFile input_write_handle_; |
| 246 base::PlatformFile output_read_handle_; |
| 247 base::PlatformFile output_write_handle_; |
| 248 |
| 249 base::MessageLoop message_loop_; |
| 250 base::RunLoop run_loop_; |
| 251 scoped_ptr<remoting::NativeMessagingHost> host_; |
| 252 |
| 253 DISALLOW_COPY_AND_ASSIGN(NativeMessagingHostTest); |
| 254 }; |
| 255 |
| 256 NativeMessagingHostTest::NativeMessagingHostTest() |
| 257 : message_loop_(base::MessageLoop::TYPE_IO) {} |
| 258 |
| 259 NativeMessagingHostTest::~NativeMessagingHostTest() {} |
| 260 |
| 261 void NativeMessagingHostTest::SetUp() { |
| 262 ASSERT_TRUE(MakePipe(&input_read_handle_, &input_write_handle_)); |
| 263 ASSERT_TRUE(MakePipe(&output_read_handle_, &output_write_handle_)); |
| 264 |
| 265 daemon_controller_ = new MockDaemonController(); |
| 266 scoped_ptr<DaemonController> daemon_controller(daemon_controller_); |
| 267 host_.reset(new NativeMessagingHost(daemon_controller.Pass(), |
| 268 input_read_handle_, output_write_handle_, |
| 269 message_loop_.message_loop_proxy(), |
| 270 run_loop_.QuitClosure())); |
| 271 } |
| 272 |
| 273 void NativeMessagingHostTest::TearDown() { |
| 274 // The NativeMessagingHost dtor closes the handles that are passed to it. |
| 275 // |input_write_handle_| gets closed just before starting the host. So the |
| 276 // only handle left to close is |output_read_handle_|. |
| 277 base::ClosePlatformFile(output_read_handle_); |
| 278 } |
| 279 |
| 280 void NativeMessagingHostTest::Run() { |
| 281 // Close the write-end of input, so that the host sees EOF after reading |
| 282 // messages and won't block waiting for more input. |
| 283 base::ClosePlatformFile(input_write_handle_); |
| 284 host_->Start(); |
| 285 run_loop_.Run(); |
| 286 |
| 287 // Destroy |host_| so that it closes its end of the output pipe, so that |
| 288 // TestBadRequest() will see EOF and won't block waiting for more data. |
| 289 // Since |host_| owns |daemon_controller_|, capture its call log first. |
| 290 call_log_ = daemon_controller_->call_log(); |
| 291 host_.reset(NULL); |
| 292 } |
| 293 |
| 294 scoped_ptr<base::DictionaryValue> |
| 295 NativeMessagingHostTest::ReadMessageFromOutputPipe() { |
| 296 uint32 length; |
| 297 int read_result = base::ReadPlatformFileAtCurrentPos( |
| 298 output_read_handle_, reinterpret_cast<char*>(&length), sizeof(length)); |
| 299 if (read_result != sizeof(length)) { |
| 300 return scoped_ptr<base::DictionaryValue>(); |
| 301 } |
| 302 |
| 303 std::string message_json(length, '\0'); |
| 304 read_result = base::ReadPlatformFileAtCurrentPos( |
| 305 output_read_handle_, string_as_array(&message_json), length); |
| 306 if (read_result != static_cast<int>(length)) { |
| 307 return scoped_ptr<base::DictionaryValue>(); |
| 308 } |
| 309 |
| 310 scoped_ptr<base::Value> message(base::JSONReader::Read(message_json)); |
| 311 if (!message || !message->IsType(base::Value::TYPE_DICTIONARY)) { |
| 312 return scoped_ptr<base::DictionaryValue>(); |
| 313 } |
| 314 |
| 315 return scoped_ptr<base::DictionaryValue>( |
| 316 static_cast<base::DictionaryValue*>(message.release())); |
| 317 } |
| 318 |
| 319 void NativeMessagingHostTest::WriteMessageToInputPipe( |
| 320 const base::Value& message) { |
| 321 std::string message_json; |
| 322 base::JSONWriter::Write(&message, &message_json); |
| 323 |
| 324 uint32 length = message_json.length(); |
| 325 base::WritePlatformFileAtCurrentPos(input_write_handle_, |
| 326 reinterpret_cast<char*>(&length), |
| 327 sizeof(length)); |
| 328 base::WritePlatformFileAtCurrentPos(input_write_handle_, message_json.data(), |
| 329 length); |
| 330 } |
| 331 |
| 332 void NativeMessagingHostTest::TestBadRequest(const base::Value& message) { |
| 333 base::DictionaryValue good_message; |
| 334 good_message.SetString("type", "hello"); |
| 335 |
| 336 WriteMessageToInputPipe(good_message); |
| 337 WriteMessageToInputPipe(message); |
| 338 WriteMessageToInputPipe(good_message); |
| 339 |
| 340 Run(); |
| 341 |
| 342 // Read from output pipe, and verify responses. |
| 343 scoped_ptr<base::DictionaryValue> response = |
| 344 ReadMessageFromOutputPipe(); |
| 345 VerifyHelloResponse(response.get()); |
| 346 |
| 347 response = ReadMessageFromOutputPipe(); |
| 348 EXPECT_FALSE(response); |
| 349 } |
| 350 |
| 351 // Test all valid request-types. |
| 352 TEST_F(NativeMessagingHostTest, All) { |
| 353 base::DictionaryValue message; |
| 354 message.SetString("type", "hello"); |
| 355 WriteMessageToInputPipe(message); |
| 356 |
| 357 message.SetString("type", "getHostName"); |
| 358 WriteMessageToInputPipe(message); |
| 359 |
| 360 message.SetString("type", "getPinHash"); |
| 361 message.SetString("hostId", "my_host"); |
| 362 message.SetString("pin", "1234"); |
| 363 WriteMessageToInputPipe(message); |
| 364 |
| 365 message.Clear(); |
| 366 message.SetString("type", "generateKeyPair"); |
| 367 WriteMessageToInputPipe(message); |
| 368 |
| 369 message.SetString("type", "getDaemonConfig"); |
| 370 WriteMessageToInputPipe(message); |
| 371 |
| 372 message.SetString("type", "getUsageStatsConsent"); |
| 373 WriteMessageToInputPipe(message); |
| 374 |
| 375 message.SetString("type", "stopDaemon"); |
| 376 WriteMessageToInputPipe(message); |
| 377 |
| 378 message.SetString("type", "getDaemonState"); |
| 379 WriteMessageToInputPipe(message); |
| 380 |
| 381 // Following messages require a "config" dictionary. |
| 382 base::DictionaryValue config; |
| 383 config.SetBoolean("update", true); |
| 384 message.Set("config", config.DeepCopy()); |
| 385 message.SetString("type", "updateDaemonConfig"); |
| 386 WriteMessageToInputPipe(message); |
| 387 |
| 388 config.Clear(); |
| 389 config.SetBoolean("start", true); |
| 390 message.Set("config", config.DeepCopy()); |
| 391 message.SetBoolean("consent", true); |
| 392 message.SetString("type", "startDaemon"); |
| 393 WriteMessageToInputPipe(message); |
| 394 |
| 395 Run(); |
| 396 |
| 397 // Read from output pipe, and verify responses. |
| 398 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe(); |
| 399 VerifyHelloResponse(response.get()); |
| 400 |
| 401 response = ReadMessageFromOutputPipe(); |
| 402 VerifyGetHostNameResponse(response.get()); |
| 403 |
| 404 response = ReadMessageFromOutputPipe(); |
| 405 VerifyGetPinHashResponse(response.get()); |
| 406 |
| 407 response = ReadMessageFromOutputPipe(); |
| 408 VerifyGenerateKeyPairResponse(response.get()); |
| 409 |
| 410 response = ReadMessageFromOutputPipe(); |
| 411 VerifyGetDaemonConfigResponse(response.get()); |
| 412 |
| 413 response = ReadMessageFromOutputPipe(); |
| 414 VerifyGetUsageStatsConsentResponse(response.get()); |
| 415 |
| 416 response = ReadMessageFromOutputPipe(); |
| 417 VerifyStopDaemonResponse(response.get()); |
| 418 |
| 419 response = ReadMessageFromOutputPipe(); |
| 420 VerifyGetDaemonStateResponse(response.get()); |
| 421 |
| 422 response = ReadMessageFromOutputPipe(); |
| 423 VerifyUpdateDaemonConfigResponse(response.get()); |
| 424 |
| 425 response = ReadMessageFromOutputPipe(); |
| 426 VerifyStartDaemonResponse(response.get()); |
| 427 |
| 428 // Verify that DaemonController methods were called in the correct sequence. |
| 429 // This detects cases where NativeMessagingHost might call a wrong method |
| 430 // that takes the same parameters and writes out the same response. |
| 431 EXPECT_EQ("GetConfig:GetUsageStatsConsent:Stop:GetState:UpdateConfig:" |
| 432 "SetConfigAndStart:", call_log_); |
| 433 } |
| 434 |
| 435 // Verify that response ID matches request ID. |
| 436 TEST_F(NativeMessagingHostTest, Id) { |
| 437 base::DictionaryValue message; |
| 438 message.SetString("type", "hello"); |
| 439 WriteMessageToInputPipe(message); |
| 440 message.SetString("id", "42"); |
| 441 WriteMessageToInputPipe(message); |
| 442 |
| 443 Run(); |
| 444 |
| 445 scoped_ptr<base::DictionaryValue> response = |
| 446 ReadMessageFromOutputPipe(); |
| 447 EXPECT_TRUE(response); |
| 448 std::string value; |
| 449 EXPECT_FALSE(response->GetString("id", &value)); |
| 450 |
| 451 response = ReadMessageFromOutputPipe(); |
| 452 EXPECT_TRUE(response); |
| 453 EXPECT_TRUE(response->GetString("id", &value)); |
| 454 EXPECT_EQ("42", value); |
| 455 } |
| 456 |
| 457 // Verify non-Dictionary requests are rejected. |
| 458 TEST_F(NativeMessagingHostTest, WrongFormat) { |
| 459 base::ListValue message; |
| 460 TestBadRequest(message); |
| 461 } |
| 462 |
| 463 // Verify requests with no type are rejected. |
| 464 TEST_F(NativeMessagingHostTest, MissingType) { |
| 465 base::DictionaryValue message; |
| 466 TestBadRequest(message); |
| 467 } |
| 468 |
| 469 // Verify rejection if type is unrecognized. |
| 470 TEST_F(NativeMessagingHostTest, InvalidType) { |
| 471 base::DictionaryValue message; |
| 472 message.SetString("type", "xxx"); |
| 473 TestBadRequest(message); |
| 474 } |
| 475 |
| 476 // Verify rejection if getPinHash request has no hostId. |
| 477 TEST_F(NativeMessagingHostTest, GetPinHashNoHostId) { |
| 478 base::DictionaryValue message; |
| 479 message.SetString("type", "getPinHash"); |
| 480 message.SetString("pin", "1234"); |
| 481 TestBadRequest(message); |
| 482 } |
| 483 |
| 484 // Verify rejection if getPinHash request has no pin. |
| 485 TEST_F(NativeMessagingHostTest, GetPinHashNoPin) { |
| 486 base::DictionaryValue message; |
| 487 message.SetString("type", "getPinHash"); |
| 488 message.SetString("hostId", "my_host"); |
| 489 TestBadRequest(message); |
| 490 } |
| 491 |
| 492 // Verify rejection if updateDaemonConfig request has invalid config. |
| 493 TEST_F(NativeMessagingHostTest, UpdateDaemonConfigInvalidConfig) { |
| 494 base::DictionaryValue message; |
| 495 message.SetString("type", "updateDaemonConfig"); |
| 496 message.SetString("config", "xxx"); |
| 497 TestBadRequest(message); |
| 498 } |
| 499 |
| 500 // Verify rejection if startDaemon request has invalid config. |
| 501 TEST_F(NativeMessagingHostTest, StartDaemonInvalidConfig) { |
| 502 base::DictionaryValue message; |
| 503 message.SetString("type", "startDaemon"); |
| 504 message.SetString("config", "xxx"); |
| 505 message.SetBoolean("consent", true); |
| 506 TestBadRequest(message); |
| 507 } |
| 508 |
| 509 // Verify rejection if startDaemon request has no "consent" parameter. |
| 510 TEST_F(NativeMessagingHostTest, StartDaemonNoConsent) { |
| 511 base::DictionaryValue message; |
| 512 message.SetString("type", "startDaemon"); |
| 513 message.Set("config", base::DictionaryValue().DeepCopy()); |
| 514 TestBadRequest(message); |
| 515 } |
| 516 |
| 517 } // namespace remoting |
OLD | NEW |