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.
5 #include "remoting/host/it2me/it2me_native_messaging_host.h"
7 #include "base/basictypes.h"
8 #include "base/compiler_specific.h"
9 #include "base/json/json_reader.h"
10 #include "base/json/json_writer.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/run_loop.h"
13 #include "base/stl_util.h"
14 #include "base/strings/stringize_macros.h"
15 #include "base/values.h"
16 #include "net/base/file_stream.h"
17 #include "net/base/net_util.h"
18 #include "remoting/base/auto_thread_task_runner.h"
19 #include "remoting/host/chromoting_host_context.h"
20 #include "remoting/host/native_messaging/log_message_handler.h"
21 #include "remoting/host/native_messaging/native_messaging_pipe.h"
22 #include "remoting/host/native_messaging/pipe_messaging_channel.h"
23 #include "remoting/host/policy_watcher.h"
24 #include "remoting/host/setup/test_util.h"
25 #include "testing/gtest/include/gtest/gtest.h"
31 const char kTestAccessCode
[] = "888888";
32 const int kTestAccessCodeLifetimeInSeconds
= 666;
33 const char kTestClientUsername
[] = "some_user@gmail.com";
35 void VerifyId(scoped_ptr
<base::DictionaryValue
> response
, int expected_value
) {
36 ASSERT_TRUE(response
);
39 EXPECT_TRUE(response
->GetInteger("id", &value
));
40 EXPECT_EQ(expected_value
, value
);
43 void VerifyStringProperty(scoped_ptr
<base::DictionaryValue
> response
,
44 const std::string
& name
,
45 const std::string
& expected_value
) {
46 ASSERT_TRUE(response
);
49 EXPECT_TRUE(response
->GetString(name
, &value
));
50 EXPECT_EQ(expected_value
, value
);
53 // Verity the values of the "type" and "id" properties
54 void VerifyCommonProperties(scoped_ptr
<base::DictionaryValue
> response
,
55 const std::string
& type
,
57 ASSERT_TRUE(response
);
59 std::string string_value
;
60 EXPECT_TRUE(response
->GetString("type", &string_value
));
61 EXPECT_EQ(type
, string_value
);
64 EXPECT_TRUE(response
->GetInteger("id", &int_value
));
65 EXPECT_EQ(id
, int_value
);
70 class MockIt2MeHost
: public It2MeHost
{
72 MockIt2MeHost(scoped_ptr
<ChromotingHostContext
> context
,
73 scoped_ptr
<PolicyWatcher
> policy_watcher
,
74 base::WeakPtr
<It2MeHost::Observer
> observer
,
75 const XmppSignalStrategy::XmppServerConfig
& xmpp_server_config
,
76 const std::string
& directory_bot_jid
)
77 : It2MeHost(context
.Pass(),
78 policy_watcher
.Pass(),
84 // It2MeHost overrides
85 void Connect() override
;
86 void Disconnect() override
;
87 void RequestNatPolicy() override
;
90 ~MockIt2MeHost() override
{}
92 void RunSetState(It2MeHostState state
);
94 DISALLOW_COPY_AND_ASSIGN(MockIt2MeHost
);
97 void MockIt2MeHost::Connect() {
98 if (!host_context()->ui_task_runner()->BelongsToCurrentThread()) {
99 DCHECK(task_runner()->BelongsToCurrentThread());
100 host_context()->ui_task_runner()->PostTask(
101 FROM_HERE
, base::Bind(&MockIt2MeHost::Connect
, this));
105 RunSetState(kStarting
);
106 RunSetState(kRequestedAccessCode
);
108 std::string
access_code(kTestAccessCode
);
109 base::TimeDelta lifetime
=
110 base::TimeDelta::FromSeconds(kTestAccessCodeLifetimeInSeconds
);
111 task_runner()->PostTask(FROM_HERE
,
112 base::Bind(&It2MeHost::Observer::OnStoreAccessCode
,
117 RunSetState(kReceivedAccessCode
);
119 std::string
client_username(kTestClientUsername
);
120 task_runner()->PostTask(
122 base::Bind(&It2MeHost::Observer::OnClientAuthenticated
,
126 RunSetState(kConnected
);
129 void MockIt2MeHost::Disconnect() {
130 if (!host_context()->network_task_runner()->BelongsToCurrentThread()) {
131 DCHECK(task_runner()->BelongsToCurrentThread());
132 host_context()->network_task_runner()->PostTask(
133 FROM_HERE
, base::Bind(&MockIt2MeHost::Disconnect
, this));
137 RunSetState(kDisconnecting
);
138 RunSetState(kDisconnected
);
141 void MockIt2MeHost::RequestNatPolicy() {}
143 void MockIt2MeHost::RunSetState(It2MeHostState state
) {
144 if (!host_context()->network_task_runner()->BelongsToCurrentThread()) {
145 host_context()->network_task_runner()->PostTask(
146 FROM_HERE
, base::Bind(&It2MeHost::SetStateForTesting
, this, state
, ""));
148 SetStateForTesting(state
, "");
152 class MockIt2MeHostFactory
: public It2MeHostFactory
{
154 MockIt2MeHostFactory() : It2MeHostFactory() {}
155 scoped_refptr
<It2MeHost
> CreateIt2MeHost(
156 scoped_ptr
<ChromotingHostContext
> context
,
157 base::WeakPtr
<It2MeHost::Observer
> observer
,
158 const XmppSignalStrategy::XmppServerConfig
& xmpp_server_config
,
159 const std::string
& directory_bot_jid
) override
{
160 return new MockIt2MeHost(context
.Pass(), nullptr, observer
,
161 xmpp_server_config
, directory_bot_jid
);
165 DISALLOW_COPY_AND_ASSIGN(MockIt2MeHostFactory
);
166 }; // MockIt2MeHostFactory
168 class It2MeNativeMessagingHostTest
: public testing::Test
{
170 It2MeNativeMessagingHostTest() {}
171 ~It2MeNativeMessagingHostTest() override
{}
173 void SetUp() override
;
174 void TearDown() override
;
177 scoped_ptr
<base::DictionaryValue
> ReadMessageFromOutputPipe();
178 void WriteMessageToInputPipe(const base::Value
& message
);
180 void VerifyHelloResponse(int request_id
);
181 void VerifyErrorResponse();
182 void VerifyConnectResponses(int request_id
);
183 void VerifyDisconnectResponses(int request_id
);
185 // The Host process should shut down when it receives a malformed request.
186 // This is tested by sending a known-good request, followed by |message|,
187 // followed by the known-good request again. The response file should only
188 // contain a single response from the first good request.
189 void TestBadRequest(const base::Value
& message
, bool expect_error_response
);
196 // Each test creates two unidirectional pipes: "input" and "output".
197 // It2MeNativeMessagingHost reads from input_read_file and writes to
198 // output_write_file. The unittest supplies data to input_write_handle, and
199 // verifies output from output_read_handle.
201 // unittest -> [input] -> It2MeNativeMessagingHost -> [output] -> unittest
202 base::File input_write_file_
;
203 base::File output_read_file_
;
205 // Message loop of the test thread.
206 scoped_ptr
<base::MessageLoop
> test_message_loop_
;
207 scoped_ptr
<base::RunLoop
> test_run_loop_
;
209 scoped_ptr
<base::Thread
> host_thread_
;
210 scoped_ptr
<base::RunLoop
> host_run_loop_
;
212 // Task runner of the host thread.
213 scoped_refptr
<AutoThreadTaskRunner
> host_task_runner_
;
214 scoped_ptr
<remoting::NativeMessagingPipe
> pipe_
;
216 DISALLOW_COPY_AND_ASSIGN(It2MeNativeMessagingHostTest
);
219 void It2MeNativeMessagingHostTest::SetUp() {
220 test_message_loop_
.reset(new base::MessageLoop());
221 test_run_loop_
.reset(new base::RunLoop());
223 // Run the host on a dedicated thread.
224 host_thread_
.reset(new base::Thread("host_thread"));
225 host_thread_
->Start();
227 host_task_runner_
= new AutoThreadTaskRunner(
228 host_thread_
->task_runner(),
229 base::Bind(&It2MeNativeMessagingHostTest::ExitTest
,
230 base::Unretained(this)));
232 host_task_runner_
->PostTask(
234 base::Bind(&It2MeNativeMessagingHostTest::StartHost
,
235 base::Unretained(this)));
237 // Wait until the host finishes starting.
238 test_run_loop_
->Run();
241 void It2MeNativeMessagingHostTest::TearDown() {
242 // Release reference to AutoThreadTaskRunner, so the host thread can be shut
244 host_task_runner_
= nullptr;
246 // Closing the write-end of the input will send an EOF to the native
247 // messaging reader. This will trigger a host shutdown.
248 input_write_file_
.Close();
250 // Start a new RunLoop and Wait until the host finishes shutting down.
251 test_run_loop_
.reset(new base::RunLoop());
252 test_run_loop_
->Run();
254 // Verify there are no more message in the output pipe.
255 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
256 EXPECT_FALSE(response
);
258 // The It2MeNativeMessagingHost dtor closes the handles that are passed to it.
259 // So the only handle left to close is |output_read_file_|.
260 output_read_file_
.Close();
263 scoped_ptr
<base::DictionaryValue
>
264 It2MeNativeMessagingHostTest::ReadMessageFromOutputPipe() {
267 int read_result
= output_read_file_
.ReadAtCurrentPos(
268 reinterpret_cast<char*>(&length
), sizeof(length
));
269 if (read_result
!= sizeof(length
)) {
270 // The output pipe has been closed, return an empty message.
274 std::string
message_json(length
, '\0');
275 read_result
= output_read_file_
.ReadAtCurrentPos(
276 string_as_array(&message_json
), length
);
277 if (read_result
!= static_cast<int>(length
)) {
278 LOG(ERROR
) << "Message size (" << read_result
279 << ") doesn't match the header (" << length
<< ").";
283 scoped_ptr
<base::Value
> message
= base::JSONReader::Read(message_json
);
284 if (!message
|| !message
->IsType(base::Value::TYPE_DICTIONARY
)) {
285 LOG(ERROR
) << "Malformed message:" << message_json
;
289 scoped_ptr
<base::DictionaryValue
> result
= make_scoped_ptr(
290 static_cast<base::DictionaryValue
*>(message
.release()));
292 // If this is a debug message log, ignore it, otherwise return it.
293 if (!result
->GetString("type", &type
) ||
294 type
!= LogMessageHandler::kDebugMessageTypeName
) {
300 void It2MeNativeMessagingHostTest::WriteMessageToInputPipe(
301 const base::Value
& message
) {
302 std::string message_json
;
303 base::JSONWriter::Write(message
, &message_json
);
305 uint32 length
= message_json
.length();
306 input_write_file_
.WriteAtCurrentPos(reinterpret_cast<char*>(&length
),
308 input_write_file_
.WriteAtCurrentPos(message_json
.data(), length
);
311 void It2MeNativeMessagingHostTest::VerifyHelloResponse(int request_id
) {
312 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
313 VerifyCommonProperties(response
.Pass(), "helloResponse", request_id
);
316 void It2MeNativeMessagingHostTest::VerifyErrorResponse() {
317 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
318 VerifyStringProperty(response
.Pass(), "type", "error");
321 void It2MeNativeMessagingHostTest::VerifyConnectResponses(int request_id
) {
322 bool connect_response_received
= false;
323 bool starting_received
= false;
324 bool requestedAccessCode_received
= false;
325 bool receivedAccessCode_received
= false;
326 bool connected_received
= false;
328 // We expect a total of 5 messages: 1 connectResponse and 4 hostStateChanged.
329 for (int i
= 0; i
< 5; ++i
) {
330 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
331 ASSERT_TRUE(response
);
334 ASSERT_TRUE(response
->GetString("type", &type
));
336 if (type
== "connectResponse") {
337 EXPECT_FALSE(connect_response_received
);
338 connect_response_received
= true;
339 VerifyId(response
.Pass(), request_id
);
340 } else if (type
== "hostStateChanged") {
342 ASSERT_TRUE(response
->GetString("state", &state
));
345 if (state
== It2MeNativeMessagingHost::HostStateToString(kStarting
)) {
346 EXPECT_FALSE(starting_received
);
347 starting_received
= true;
348 } else if (state
== It2MeNativeMessagingHost::HostStateToString(
349 kRequestedAccessCode
)) {
350 EXPECT_FALSE(requestedAccessCode_received
);
351 requestedAccessCode_received
= true;
352 } else if (state
== It2MeNativeMessagingHost::HostStateToString(
353 kReceivedAccessCode
)) {
354 EXPECT_FALSE(receivedAccessCode_received
);
355 receivedAccessCode_received
= true;
357 EXPECT_TRUE(response
->GetString("accessCode", &value
));
358 EXPECT_EQ(kTestAccessCode
, value
);
360 int accessCodeLifetime
;
362 response
->GetInteger("accessCodeLifetime", &accessCodeLifetime
));
363 EXPECT_EQ(kTestAccessCodeLifetimeInSeconds
, accessCodeLifetime
);
365 It2MeNativeMessagingHost::HostStateToString(kConnected
)) {
366 EXPECT_FALSE(connected_received
);
367 connected_received
= true;
369 EXPECT_TRUE(response
->GetString("client", &value
));
370 EXPECT_EQ(kTestClientUsername
, value
);
372 ADD_FAILURE() << "Unexpected host state: " << state
;
375 ADD_FAILURE() << "Unexpected message type: " << type
;
380 void It2MeNativeMessagingHostTest::VerifyDisconnectResponses(int request_id
) {
381 bool disconnect_response_received
= false;
382 bool disconnecting_received
= false;
383 bool disconnected_received
= false;
385 // We expect a total of 3 messages: 1 connectResponse and 2 hostStateChanged.
386 for (int i
= 0; i
< 3; ++i
) {
387 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
388 ASSERT_TRUE(response
);
391 ASSERT_TRUE(response
->GetString("type", &type
));
393 if (type
== "disconnectResponse") {
394 EXPECT_FALSE(disconnect_response_received
);
395 disconnect_response_received
= true;
396 VerifyId(response
.Pass(), request_id
);
397 } else if (type
== "hostStateChanged") {
399 ASSERT_TRUE(response
->GetString("state", &state
));
401 It2MeNativeMessagingHost::HostStateToString(kDisconnecting
)) {
402 EXPECT_FALSE(disconnecting_received
);
403 disconnecting_received
= true;
405 It2MeNativeMessagingHost::HostStateToString(kDisconnected
)) {
406 EXPECT_FALSE(disconnected_received
);
407 disconnected_received
= true;
409 ADD_FAILURE() << "Unexpected host state: " << state
;
412 ADD_FAILURE() << "Unexpected message type: " << type
;
417 void It2MeNativeMessagingHostTest::TestBadRequest(const base::Value
& message
,
418 bool expect_error_response
) {
419 base::DictionaryValue good_message
;
420 good_message
.SetString("type", "hello");
421 good_message
.SetInteger("id", 1);
423 WriteMessageToInputPipe(good_message
);
424 WriteMessageToInputPipe(message
);
425 WriteMessageToInputPipe(good_message
);
427 VerifyHelloResponse(1);
429 if (expect_error_response
)
430 VerifyErrorResponse();
432 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
433 EXPECT_FALSE(response
);
436 void It2MeNativeMessagingHostTest::StartHost() {
437 DCHECK(host_task_runner_
->RunsTasksOnCurrentThread());
439 base::File input_read_file
;
440 base::File output_write_file
;
442 ASSERT_TRUE(MakePipe(&input_read_file
, &input_write_file_
));
443 ASSERT_TRUE(MakePipe(&output_read_file_
, &output_write_file
));
445 pipe_
.reset(new NativeMessagingPipe());
447 scoped_ptr
<extensions::NativeMessagingChannel
> channel(
448 new PipeMessagingChannel(input_read_file
.Pass(),
449 output_write_file
.Pass()));
451 // Creating a native messaging host with a mock It2MeHostFactory.
452 scoped_ptr
<extensions::NativeMessageHost
> it2me_host(
453 new It2MeNativeMessagingHost(
454 ChromotingHostContext::Create(host_task_runner_
),
455 make_scoped_ptr(new MockIt2MeHostFactory())));
456 it2me_host
->Start(pipe_
.get());
458 pipe_
->Start(it2me_host
.Pass(), channel
.Pass());
460 // Notify the test that the host has finished starting up.
461 test_message_loop_
->task_runner()->PostTask(
462 FROM_HERE
, test_run_loop_
->QuitClosure());
465 void It2MeNativeMessagingHostTest::ExitTest() {
466 if (!test_message_loop_
->task_runner()->RunsTasksOnCurrentThread()) {
467 test_message_loop_
->task_runner()->PostTask(
469 base::Bind(&It2MeNativeMessagingHostTest::ExitTest
,
470 base::Unretained(this)));
473 test_run_loop_
->Quit();
476 void It2MeNativeMessagingHostTest::TestConnect() {
477 base::DictionaryValue connect_message
;
480 // Send the "connect" request.
481 connect_message
.SetInteger("id", ++next_id
);
482 connect_message
.SetString("type", "connect");
483 connect_message
.SetString("xmppServerAddress", "talk.google.com:5222");
484 connect_message
.SetBoolean("xmppServerUseTls", true);
485 connect_message
.SetString("directoryBotJid", "remoting@bot.talk.google.com");
486 connect_message
.SetString("userName", "chromo.pyauto@gmail.com");
487 connect_message
.SetString("authServiceWithToken", "oauth2:sometoken");
488 WriteMessageToInputPipe(connect_message
);
490 VerifyConnectResponses(next_id
);
492 base::DictionaryValue disconnect_message
;
493 disconnect_message
.SetInteger("id", ++next_id
);
494 disconnect_message
.SetString("type", "disconnect");
495 WriteMessageToInputPipe(disconnect_message
);
497 VerifyDisconnectResponses(next_id
);
500 // Test hello request.
501 TEST_F(It2MeNativeMessagingHostTest
, Hello
) {
503 base::DictionaryValue message
;
504 message
.SetInteger("id", ++next_id
);
505 message
.SetString("type", "hello");
506 WriteMessageToInputPipe(message
);
508 VerifyHelloResponse(next_id
);
511 // Verify that response ID matches request ID.
512 TEST_F(It2MeNativeMessagingHostTest
, Id
) {
513 base::DictionaryValue message
;
514 message
.SetString("type", "hello");
515 WriteMessageToInputPipe(message
);
516 message
.SetString("id", "42");
517 WriteMessageToInputPipe(message
);
519 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
520 EXPECT_TRUE(response
);
522 EXPECT_FALSE(response
->GetString("id", &value
));
524 response
= ReadMessageFromOutputPipe();
525 EXPECT_TRUE(response
);
526 EXPECT_TRUE(response
->GetString("id", &value
));
527 EXPECT_EQ("42", value
);
530 TEST_F(It2MeNativeMessagingHostTest
, Connect
) {
531 // A new It2MeHost instance is created for every it2me session. The native
532 // messaging host, on the other hand, is long lived. This test verifies
533 // multiple It2Me host startup and shutdowns.
534 for (int i
= 0; i
< 3; ++i
)
538 // Verify non-Dictionary requests are rejected.
539 TEST_F(It2MeNativeMessagingHostTest
, WrongFormat
) {
540 base::ListValue message
;
541 // No "error" response will be sent for non-Dictionary messages.
542 TestBadRequest(message
, false);
545 // Verify requests with no type are rejected.
546 TEST_F(It2MeNativeMessagingHostTest
, MissingType
) {
547 base::DictionaryValue message
;
548 TestBadRequest(message
, true);
551 // Verify rejection if type is unrecognized.
552 TEST_F(It2MeNativeMessagingHostTest
, InvalidType
) {
553 base::DictionaryValue message
;
554 message
.SetString("type", "xxx");
555 TestBadRequest(message
, true);
558 } // namespace remoting