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/native_messaging_pipe.h"
21 #include "remoting/host/native_messaging/pipe_messaging_channel.h"
22 #include "remoting/host/policy_hack/policy_watcher.h"
23 #include "remoting/host/setup/test_util.h"
24 #include "testing/gtest/include/gtest/gtest.h"
30 const char kTestAccessCode
[] = "888888";
31 const int kTestAccessCodeLifetimeInSeconds
= 666;
32 const char kTestClientUsername
[] = "some_user@gmail.com";
34 void VerifyId(scoped_ptr
<base::DictionaryValue
> response
, int expected_value
) {
35 ASSERT_TRUE(response
);
38 EXPECT_TRUE(response
->GetInteger("id", &value
));
39 EXPECT_EQ(expected_value
, value
);
42 void VerifyStringProperty(scoped_ptr
<base::DictionaryValue
> response
,
43 const std::string
& name
,
44 const std::string
& expected_value
) {
45 ASSERT_TRUE(response
);
48 EXPECT_TRUE(response
->GetString(name
, &value
));
49 EXPECT_EQ(expected_value
, value
);
52 // Verity the values of the "type" and "id" properties
53 void VerifyCommonProperties(scoped_ptr
<base::DictionaryValue
> response
,
54 const std::string
& type
,
56 ASSERT_TRUE(response
);
58 std::string string_value
;
59 EXPECT_TRUE(response
->GetString("type", &string_value
));
60 EXPECT_EQ(type
, string_value
);
63 EXPECT_TRUE(response
->GetInteger("id", &int_value
));
64 EXPECT_EQ(id
, int_value
);
69 class MockIt2MeHost
: public It2MeHost
{
71 MockIt2MeHost(scoped_ptr
<ChromotingHostContext
> context
,
72 scoped_ptr
<policy_hack::PolicyWatcher
> policy_watcher
,
73 base::WeakPtr
<It2MeHost::Observer
> observer
,
74 const XmppSignalStrategy::XmppServerConfig
& xmpp_server_config
,
75 const std::string
& directory_bot_jid
)
76 : It2MeHost(context
.Pass(),
77 policy_watcher
.Pass(),
82 // It2MeHost overrides
83 void Connect() override
;
84 void Disconnect() override
;
85 void RequestNatPolicy() override
;
88 ~MockIt2MeHost() override
{}
90 void RunSetState(It2MeHostState state
);
92 DISALLOW_COPY_AND_ASSIGN(MockIt2MeHost
);
95 void MockIt2MeHost::Connect() {
96 if (!host_context()->ui_task_runner()->BelongsToCurrentThread()) {
97 DCHECK(task_runner()->BelongsToCurrentThread());
98 host_context()->ui_task_runner()->PostTask(
99 FROM_HERE
, base::Bind(&MockIt2MeHost::Connect
, this));
103 RunSetState(kStarting
);
104 RunSetState(kRequestedAccessCode
);
106 std::string
access_code(kTestAccessCode
);
107 base::TimeDelta lifetime
=
108 base::TimeDelta::FromSeconds(kTestAccessCodeLifetimeInSeconds
);
109 task_runner()->PostTask(FROM_HERE
,
110 base::Bind(&It2MeHost::Observer::OnStoreAccessCode
,
115 RunSetState(kReceivedAccessCode
);
117 std::string
client_username(kTestClientUsername
);
118 task_runner()->PostTask(
120 base::Bind(&It2MeHost::Observer::OnClientAuthenticated
,
124 RunSetState(kConnected
);
127 void MockIt2MeHost::Disconnect() {
128 if (!host_context()->network_task_runner()->BelongsToCurrentThread()) {
129 DCHECK(task_runner()->BelongsToCurrentThread());
130 host_context()->network_task_runner()->PostTask(
131 FROM_HERE
, base::Bind(&MockIt2MeHost::Disconnect
, this));
135 RunSetState(kDisconnecting
);
136 RunSetState(kDisconnected
);
139 void MockIt2MeHost::RequestNatPolicy() {}
141 void MockIt2MeHost::RunSetState(It2MeHostState state
) {
142 if (!host_context()->network_task_runner()->BelongsToCurrentThread()) {
143 host_context()->network_task_runner()->PostTask(
144 FROM_HERE
, base::Bind(&It2MeHost::SetStateForTesting
, this, state
));
146 SetStateForTesting(state
);
150 class MockIt2MeHostFactory
: public It2MeHostFactory
{
152 MockIt2MeHostFactory() : It2MeHostFactory() {}
153 scoped_refptr
<It2MeHost
> CreateIt2MeHost(
154 scoped_ptr
<ChromotingHostContext
> context
,
155 base::WeakPtr
<It2MeHost::Observer
> observer
,
156 const XmppSignalStrategy::XmppServerConfig
& xmpp_server_config
,
157 const std::string
& directory_bot_jid
) override
{
158 return new MockIt2MeHost(context
.Pass(), nullptr, observer
,
159 xmpp_server_config
, directory_bot_jid
);
163 DISALLOW_COPY_AND_ASSIGN(MockIt2MeHostFactory
);
164 }; // MockIt2MeHostFactory
166 class It2MeNativeMessagingHostTest
: public testing::Test
{
168 It2MeNativeMessagingHostTest() {}
169 ~It2MeNativeMessagingHostTest() override
{}
171 void SetUp() override
;
172 void TearDown() override
;
175 scoped_ptr
<base::DictionaryValue
> ReadMessageFromOutputPipe();
176 void WriteMessageToInputPipe(const base::Value
& message
);
178 void VerifyHelloResponse(int request_id
);
179 void VerifyErrorResponse();
180 void VerifyConnectResponses(int request_id
);
181 void VerifyDisconnectResponses(int request_id
);
183 // The Host process should shut down when it receives a malformed request.
184 // This is tested by sending a known-good request, followed by |message|,
185 // followed by the known-good request again. The response file should only
186 // contain a single response from the first good request.
187 void TestBadRequest(const base::Value
& message
, bool expect_error_response
);
195 // Each test creates two unidirectional pipes: "input" and "output".
196 // It2MeNativeMessagingHost reads from input_read_file and writes to
197 // output_write_file. The unittest supplies data to input_write_handle, and
198 // verifies output from output_read_handle.
200 // unittest -> [input] -> It2MeNativeMessagingHost -> [output] -> unittest
201 base::File input_write_file_
;
202 base::File output_read_file_
;
204 // Message loop of the test thread.
205 scoped_ptr
<base::MessageLoop
> test_message_loop_
;
206 scoped_ptr
<base::RunLoop
> test_run_loop_
;
208 scoped_ptr
<base::Thread
> host_thread_
;
209 scoped_ptr
<base::RunLoop
> host_run_loop_
;
211 // Task runner of the host thread.
212 scoped_refptr
<AutoThreadTaskRunner
> host_task_runner_
;
213 scoped_ptr
<remoting::NativeMessagingPipe
> pipe_
;
215 DISALLOW_COPY_AND_ASSIGN(It2MeNativeMessagingHostTest
);
218 void It2MeNativeMessagingHostTest::SetUp() {
219 test_message_loop_
.reset(new base::MessageLoop());
220 test_run_loop_
.reset(new base::RunLoop());
222 // Run the host on a dedicated thread.
223 host_thread_
.reset(new base::Thread("host_thread"));
224 host_thread_
->Start();
226 host_task_runner_
= new AutoThreadTaskRunner(
227 host_thread_
->message_loop_proxy(),
228 base::Bind(&It2MeNativeMessagingHostTest::ExitTest
,
229 base::Unretained(this)));
231 host_task_runner_
->PostTask(
233 base::Bind(&It2MeNativeMessagingHostTest::StartHost
,
234 base::Unretained(this)));
236 // Wait until the host finishes starting.
237 test_run_loop_
->Run();
240 void It2MeNativeMessagingHostTest::TearDown() {
241 // Closing the write-end of the input will send an EOF to the native
242 // messaging reader. This will trigger a host shutdown.
243 input_write_file_
.Close();
245 // Start a new RunLoop and Wait until the host finishes shutting down.
246 test_run_loop_
.reset(new base::RunLoop());
247 test_run_loop_
->Run();
249 // Verify there are no more message in the output pipe.
250 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
251 EXPECT_FALSE(response
);
253 // The It2MeNativeMessagingHost dtor closes the handles that are passed to it.
254 // So the only handle left to close is |output_read_file_|.
255 output_read_file_
.Close();
258 scoped_ptr
<base::DictionaryValue
>
259 It2MeNativeMessagingHostTest::ReadMessageFromOutputPipe() {
261 int read_result
= output_read_file_
.ReadAtCurrentPos(
262 reinterpret_cast<char*>(&length
), sizeof(length
));
263 if (read_result
!= sizeof(length
)) {
264 // The output pipe has been closed, return an empty message.
268 std::string
message_json(length
, '\0');
269 read_result
= output_read_file_
.ReadAtCurrentPos(
270 string_as_array(&message_json
), length
);
271 if (read_result
!= static_cast<int>(length
)) {
272 LOG(ERROR
) << "Message size (" << read_result
273 << ") doesn't match the header (" << length
<< ").";
277 scoped_ptr
<base::Value
> message(base::JSONReader::Read(message_json
));
278 if (!message
|| !message
->IsType(base::Value::TYPE_DICTIONARY
)) {
279 LOG(ERROR
) << "Malformed message:" << message_json
;
283 return make_scoped_ptr(
284 static_cast<base::DictionaryValue
*>(message
.release()));
287 void It2MeNativeMessagingHostTest::WriteMessageToInputPipe(
288 const base::Value
& message
) {
289 std::string message_json
;
290 base::JSONWriter::Write(&message
, &message_json
);
292 uint32 length
= message_json
.length();
293 input_write_file_
.WriteAtCurrentPos(reinterpret_cast<char*>(&length
),
295 input_write_file_
.WriteAtCurrentPos(message_json
.data(), length
);
298 void It2MeNativeMessagingHostTest::VerifyHelloResponse(int request_id
) {
299 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
300 VerifyCommonProperties(response
.Pass(), "helloResponse", request_id
);
303 void It2MeNativeMessagingHostTest::VerifyErrorResponse() {
304 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
305 VerifyStringProperty(response
.Pass(), "type", "error");
308 void It2MeNativeMessagingHostTest::VerifyConnectResponses(int request_id
) {
309 bool connect_response_received
= false;
310 bool starting_received
= false;
311 bool requestedAccessCode_received
= false;
312 bool receivedAccessCode_received
= false;
313 bool connected_received
= false;
315 // We expect a total of 5 messages: 1 connectResponse and 4 hostStateChanged.
316 for (int i
= 0; i
< 5; ++i
) {
317 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
318 ASSERT_TRUE(response
);
321 ASSERT_TRUE(response
->GetString("type", &type
));
323 if (type
== "connectResponse") {
324 EXPECT_FALSE(connect_response_received
);
325 connect_response_received
= true;
326 VerifyId(response
.Pass(), request_id
);
327 } else if (type
== "hostStateChanged") {
329 ASSERT_TRUE(response
->GetString("state", &state
));
332 if (state
== It2MeNativeMessagingHost::HostStateToString(kStarting
)) {
333 EXPECT_FALSE(starting_received
);
334 starting_received
= true;
335 } else if (state
== It2MeNativeMessagingHost::HostStateToString(
336 kRequestedAccessCode
)) {
337 EXPECT_FALSE(requestedAccessCode_received
);
338 requestedAccessCode_received
= true;
339 } else if (state
== It2MeNativeMessagingHost::HostStateToString(
340 kReceivedAccessCode
)) {
341 EXPECT_FALSE(receivedAccessCode_received
);
342 receivedAccessCode_received
= true;
344 EXPECT_TRUE(response
->GetString("accessCode", &value
));
345 EXPECT_EQ(kTestAccessCode
, value
);
347 int accessCodeLifetime
;
349 response
->GetInteger("accessCodeLifetime", &accessCodeLifetime
));
350 EXPECT_EQ(kTestAccessCodeLifetimeInSeconds
, accessCodeLifetime
);
352 It2MeNativeMessagingHost::HostStateToString(kConnected
)) {
353 EXPECT_FALSE(connected_received
);
354 connected_received
= true;
356 EXPECT_TRUE(response
->GetString("client", &value
));
357 EXPECT_EQ(kTestClientUsername
, value
);
359 ADD_FAILURE() << "Unexpected host state: " << state
;
362 ADD_FAILURE() << "Unexpected message type: " << type
;
367 void It2MeNativeMessagingHostTest::VerifyDisconnectResponses(int request_id
) {
368 bool disconnect_response_received
= false;
369 bool disconnecting_received
= false;
370 bool disconnected_received
= false;
372 // We expect a total of 3 messages: 1 connectResponse and 2 hostStateChanged.
373 for (int i
= 0; i
< 3; ++i
) {
374 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
375 ASSERT_TRUE(response
);
378 ASSERT_TRUE(response
->GetString("type", &type
));
380 if (type
== "disconnectResponse") {
381 EXPECT_FALSE(disconnect_response_received
);
382 disconnect_response_received
= true;
383 VerifyId(response
.Pass(), request_id
);
384 } else if (type
== "hostStateChanged") {
386 ASSERT_TRUE(response
->GetString("state", &state
));
388 It2MeNativeMessagingHost::HostStateToString(kDisconnecting
)) {
389 EXPECT_FALSE(disconnecting_received
);
390 disconnecting_received
= true;
392 It2MeNativeMessagingHost::HostStateToString(kDisconnected
)) {
393 EXPECT_FALSE(disconnected_received
);
394 disconnected_received
= true;
396 ADD_FAILURE() << "Unexpected host state: " << state
;
399 ADD_FAILURE() << "Unexpected message type: " << type
;
404 void It2MeNativeMessagingHostTest::TestBadRequest(const base::Value
& message
,
405 bool expect_error_response
) {
406 base::DictionaryValue good_message
;
407 good_message
.SetString("type", "hello");
408 good_message
.SetInteger("id", 1);
410 WriteMessageToInputPipe(good_message
);
411 WriteMessageToInputPipe(message
);
412 WriteMessageToInputPipe(good_message
);
414 VerifyHelloResponse(1);
416 if (expect_error_response
)
417 VerifyErrorResponse();
419 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
420 EXPECT_FALSE(response
);
423 void It2MeNativeMessagingHostTest::StartHost() {
424 DCHECK(host_task_runner_
->RunsTasksOnCurrentThread());
426 base::File input_read_file
;
427 base::File output_write_file
;
429 ASSERT_TRUE(MakePipe(&input_read_file
, &input_write_file_
));
430 ASSERT_TRUE(MakePipe(&output_read_file_
, &output_write_file
));
432 pipe_
.reset(new NativeMessagingPipe());
434 scoped_ptr
<extensions::NativeMessagingChannel
> channel(
435 new PipeMessagingChannel(input_read_file
.Pass(),
436 output_write_file
.Pass()));
438 // Creating a native messaging host with a mock It2MeHostFactory.
439 scoped_ptr
<extensions::NativeMessageHost
> it2me_host(
440 new It2MeNativeMessagingHost(
441 ChromotingHostContext::Create(host_task_runner_
),
442 make_scoped_ptr(new MockIt2MeHostFactory())));
443 it2me_host
->Start(pipe_
.get());
445 pipe_
->Start(it2me_host
.Pass(),
447 base::Bind(&It2MeNativeMessagingHostTest::StopHost
,
448 base::Unretained(this)));
450 // Notify the test that the host has finished starting up.
451 test_message_loop_
->message_loop_proxy()->PostTask(
452 FROM_HERE
, test_run_loop_
->QuitClosure());
455 void It2MeNativeMessagingHostTest::StopHost() {
456 DCHECK(host_task_runner_
->RunsTasksOnCurrentThread());
460 // Wait till all shutdown tasks have completed.
461 base::RunLoop().RunUntilIdle();
463 // Trigger a test shutdown via ExitTest().
464 host_task_runner_
= NULL
;
467 void It2MeNativeMessagingHostTest::ExitTest() {
468 if (!test_message_loop_
->message_loop_proxy()->RunsTasksOnCurrentThread()) {
469 test_message_loop_
->message_loop_proxy()->PostTask(
471 base::Bind(&It2MeNativeMessagingHostTest::ExitTest
,
472 base::Unretained(this)));
475 test_run_loop_
->Quit();
478 void It2MeNativeMessagingHostTest::TestConnect() {
479 base::DictionaryValue connect_message
;
482 // Send the "connect" request.
483 connect_message
.SetInteger("id", ++next_id
);
484 connect_message
.SetString("type", "connect");
485 connect_message
.SetString("xmppServerAddress", "talk.google.com:5222");
486 connect_message
.SetBoolean("xmppServerUseTls", true);
487 connect_message
.SetString("directoryBotJid", "remoting@bot.talk.google.com");
488 connect_message
.SetString("userName", "chromo.pyauto@gmail.com");
489 connect_message
.SetString("authServiceWithToken", "oauth2:sometoken");
490 WriteMessageToInputPipe(connect_message
);
492 VerifyConnectResponses(next_id
);
494 base::DictionaryValue disconnect_message
;
495 disconnect_message
.SetInteger("id", ++next_id
);
496 disconnect_message
.SetString("type", "disconnect");
497 WriteMessageToInputPipe(disconnect_message
);
499 VerifyDisconnectResponses(next_id
);
502 // Test hello request.
503 TEST_F(It2MeNativeMessagingHostTest
, Hello
) {
505 base::DictionaryValue message
;
506 message
.SetInteger("id", ++next_id
);
507 message
.SetString("type", "hello");
508 WriteMessageToInputPipe(message
);
510 VerifyHelloResponse(next_id
);
513 // Verify that response ID matches request ID.
514 TEST_F(It2MeNativeMessagingHostTest
, Id
) {
515 base::DictionaryValue message
;
516 message
.SetString("type", "hello");
517 WriteMessageToInputPipe(message
);
518 message
.SetString("id", "42");
519 WriteMessageToInputPipe(message
);
521 scoped_ptr
<base::DictionaryValue
> response
= ReadMessageFromOutputPipe();
522 EXPECT_TRUE(response
);
524 EXPECT_FALSE(response
->GetString("id", &value
));
526 response
= ReadMessageFromOutputPipe();
527 EXPECT_TRUE(response
);
528 EXPECT_TRUE(response
->GetString("id", &value
));
529 EXPECT_EQ("42", value
);
532 TEST_F(It2MeNativeMessagingHostTest
, Connect
) {
533 // A new It2MeHost instance is created for every it2me session. The native
534 // messaging host, on the other hand, is long lived. This test verifies
535 // multiple It2Me host startup and shutdowns.
536 for (int i
= 0; i
< 3; ++i
)
540 // Verify non-Dictionary requests are rejected.
541 TEST_F(It2MeNativeMessagingHostTest
, WrongFormat
) {
542 base::ListValue message
;
543 // No "error" response will be sent for non-Dictionary messages.
544 TestBadRequest(message
, false);
547 // Verify requests with no type are rejected.
548 TEST_F(It2MeNativeMessagingHostTest
, MissingType
) {
549 base::DictionaryValue message
;
550 TestBadRequest(message
, true);
553 // Verify rejection if type is unrecognized.
554 TEST_F(It2MeNativeMessagingHostTest
, InvalidType
) {
555 base::DictionaryValue message
;
556 message
.SetString("type", "xxx");
557 TestBadRequest(message
, true);
560 } // namespace remoting