1// Copyright (c) 2009 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 "chrome/browser/importer/firefox_importer_unittest_utils.h"
6
7#include "base/base_switches.h"
8#include "base/command_line.h"
9#include "base/file_path.h"
10#include "base/message_loop.h"
11#include "base/test/test_timeouts.h"
12#include "chrome/browser/importer/firefox_importer_utils.h"
13#include "ipc/ipc_channel.h"
14#include "ipc/ipc_descriptors.h"
15#include "ipc/ipc_message.h"
16#include "ipc/ipc_switches.h"
17#include "testing/multiprocess_func_list.h"
18
19#define IPC_MESSAGE_IMPL
20#include "chrome/browser/importer/firefox_importer_unittest_messages_internal.h"
21
22namespace {
23
24// Name of IPC Channel to use for Server<-> Child Communications.
25const char kTestChannelID[] = "T1";
26
27// Launch the child process:
28// |nss_path| - path to the NSS directory holding the decryption libraries.
29// |channel| - IPC Channel to use for communication.
30// |handle| - On return, the process handle to use to communicate with the
31// child.
32bool LaunchNSSDecrypterChildProcess(const FilePath& nss_path,
33    const IPC::Channel& channel, base::ProcessHandle* handle) {
34  CommandLine cl(*CommandLine::ForCurrentProcess());
35  cl.AppendSwitchASCII(switches::kTestChildProcess, "NSSDecrypterChildProcess");
36
37  // Set env variable needed for FF encryption libs to load.
38  // See "chrome/browser/importer/nss_decryptor_mac.mm" for an explanation of
39  // why we need this.
40  base::environment_vector env;
41  std::pair<std::string, std::string> dyld_override;
42  dyld_override.first = "DYLD_FALLBACK_LIBRARY_PATH";
43  dyld_override.second = nss_path.value();
44  env.push_back(dyld_override);
45
46  base::file_handle_mapping_vector fds_to_map;
47  const int ipcfd = channel.GetClientFileDescriptor();
48  if (ipcfd > -1) {
49    fds_to_map.push_back(std::pair<int,int>(ipcfd, kPrimaryIPCChannel + 3));
50  } else {
51    return false;
52  }
53
54  bool debug_on_start = CommandLine::ForCurrentProcess()->HasSwitch(
55                            switches::kDebugChildren);
56  return base::LaunchApp(cl.argv(), env, fds_to_map, debug_on_start, handle);
57}
58
59}  // namespace
60
61//----------------------- Server --------------------
62
63// Class to communicate on the server side of the IPC Channel.
64// Method calls are sent over IPC and replies are read back into class
65// variables.
66// This class needs to be called on a single thread.
67class FFDecryptorServerChannelListener : public IPC::Channel::Listener {
68 public:
69  FFDecryptorServerChannelListener()
70      : got_result(false), sender_(NULL) {}
71
72  void SetSender(IPC::Message::Sender* sender) {
73    sender_ = sender;
74  }
75
76  void OnInitDecryptorResponse(bool result) {
77    DCHECK(!got_result);
78    result_bool = result;
79    got_result = true;
80    MessageLoop::current()->Quit();
81  }
82
83  void OnDecryptedTextResonse(const string16& decrypted_text) {
84    DCHECK(!got_result);
85    result_string = decrypted_text;
86    got_result = true;
87    MessageLoop::current()->Quit();
88  }
89
90  void QuitClient() {
91    if (sender_)
92      sender_->Send(new Msg_Decryptor_Quit());
93  }
94
95  virtual bool OnMessageReceived(const IPC::Message& msg) {
96    bool handled = true;
97    IPC_BEGIN_MESSAGE_MAP(FFDecryptorServerChannelListener, msg)
98      IPC_MESSAGE_HANDLER(Msg_Decryptor_InitReturnCode, OnInitDecryptorResponse)
99      IPC_MESSAGE_HANDLER(Msg_Decryptor_Response, OnDecryptedTextResonse)
100      IPC_MESSAGE_UNHANDLED(handled = false)
101    IPC_END_MESSAGE_MAP()
102    return handled;
103  }
104
105  // If an error occured, just kill the message Loop.
106  virtual void OnChannelError() {
107    got_result = false;
108    MessageLoop::current()->Quit();
109  }
110
111  // Results of IPC calls.
112  string16 result_string;
113  bool result_bool;
114  // True if IPC call succeeded and data in above variables is valid.
115  bool got_result;
116
117 private:
118  IPC::Message::Sender* sender_;  // weak
119};
120
121FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy()
122    : child_process_(0) {
123}
124
125bool FFUnitTestDecryptorProxy::Setup(const FilePath& nss_path) {
126  // Create a new message loop and spawn the child process.
127  message_loop_.reset(new MessageLoopForIO());
128
129  listener_.reset(new FFDecryptorServerChannelListener());
130  channel_.reset(new IPC::Channel(kTestChannelID,
131                                  IPC::Channel::MODE_SERVER,
132                                  listener_.get()));
133  CHECK(channel_->Connect());
134  listener_->SetSender(channel_.get());
135
136  // Spawn child and set up sync IPC connection.
137  bool ret = LaunchNSSDecrypterChildProcess(nss_path,
138                                            *(channel_.get()),
139                                            &child_process_);
140  return ret && (child_process_ != 0);
141}
142
143FFUnitTestDecryptorProxy::~FFUnitTestDecryptorProxy() {
144  listener_->QuitClient();
145  channel_->Close();
146
147  if (child_process_) {
148    base::WaitForSingleProcess(child_process_, 5000);
149    base::CloseProcessHandle(child_process_);
150  }
151}
152
153// A message_loop task that quits the message loop when invoked, setting cancel
154// causes the task to do nothing when invoked.
155class CancellableQuitMsgLoop : public base::RefCounted<CancellableQuitMsgLoop> {
156 public:
157  CancellableQuitMsgLoop() : cancelled_(false) {}
158  void QuitNow() {
159    if (!cancelled_)
160      MessageLoop::current()->Quit();
161  }
162  bool cancelled_;
163};
164
165// Spin until either a client response arrives or a timeout occurs.
166bool FFUnitTestDecryptorProxy::WaitForClientResponse() {
167  // What we're trying to do here is to wait for an RPC message to go over the
168  // wire and the client to reply.  If the client does not replyy by a given
169  // timeout we kill the message loop.
170  // The way we do this is to post a CancellableQuitMsgLoop for 3 seconds in
171  // the future and cancel it if an RPC message comes back earlier.
172  // This relies on the IPC listener class to quit the message loop itself when
173  // a message comes in.
174  scoped_refptr<CancellableQuitMsgLoop> quit_task(
175      new CancellableQuitMsgLoop());
176  MessageLoop::current()->PostDelayedTask(
177      FROM_HERE,
178      NewRunnableMethod(quit_task.get(), &CancellableQuitMsgLoop::QuitNow),
179      TestTimeouts::action_max_timeout_ms());
180
181  message_loop_->Run();
182  bool ret = !quit_task->cancelled_;
183  quit_task->cancelled_ = false;
184  return ret;
185}
186
187bool FFUnitTestDecryptorProxy::DecryptorInit(const FilePath& dll_path,
188                                             const FilePath& db_path) {
189  channel_->Send(new Msg_Decryptor_Init(dll_path, db_path));
190  bool ok = WaitForClientResponse();
191  if (ok && listener_->got_result) {
192    listener_->got_result = false;
193    return listener_->result_bool;
194  }
195  return false;
196}
197
198string16 FFUnitTestDecryptorProxy::Decrypt(const std::string& crypt) {
199  channel_->Send(new Msg_Decrypt(crypt));
200  bool ok = WaitForClientResponse();
201  if (ok && listener_->got_result) {
202    listener_->got_result = false;
203    return listener_->result_string;
204  }
205  return string16();
206}
207
208//---------------------------- Child Process -----------------------
209
210// Class to listen on the client side of the ipc channel, it calls through
211// to the NSSDecryptor and sends back a reply.
212class FFDecryptorClientChannelListener : public IPC::Channel::Listener {
213 public:
214  FFDecryptorClientChannelListener()
215      : sender_(NULL) {}
216
217  void SetSender(IPC::Message::Sender* sender) {
218    sender_ = sender;
219  }
220
221  void OnDecryptor_Init(FilePath dll_path, FilePath db_path) {
222    bool ret = decryptor_.Init(dll_path, db_path);
223    sender_->Send(new Msg_Decryptor_InitReturnCode(ret));
224  }
225
226  void OnDecrypt(std::string crypt) {
227    string16 unencrypted_str = decryptor_.Decrypt(crypt);
228    sender_->Send(new Msg_Decryptor_Response(unencrypted_str));
229  }
230
231  void OnQuitRequest() {
232    MessageLoop::current()->Quit();
233  }
234
235  virtual bool OnMessageReceived(const IPC::Message& msg) {
236    bool handled = true;
237    IPC_BEGIN_MESSAGE_MAP(FFDecryptorClientChannelListener, msg)
238      IPC_MESSAGE_HANDLER(Msg_Decryptor_Init, OnDecryptor_Init)
239      IPC_MESSAGE_HANDLER(Msg_Decrypt, OnDecrypt)
240      IPC_MESSAGE_HANDLER(Msg_Decryptor_Quit, OnQuitRequest)
241      IPC_MESSAGE_UNHANDLED(handled = false)
242    IPC_END_MESSAGE_MAP()
243    return handled;
244  }
245
246  virtual void OnChannelError() {
247    MessageLoop::current()->Quit();
248  }
249
250 private:
251  NSSDecryptor decryptor_;
252  IPC::Message::Sender* sender_;
253};
254
255// Entry function in child process.
256MULTIPROCESS_TEST_MAIN(NSSDecrypterChildProcess) {
257  MessageLoopForIO main_message_loop;
258  FFDecryptorClientChannelListener listener;
259
260  IPC::Channel channel(kTestChannelID, IPC::Channel::MODE_CLIENT, &listener);
261  CHECK(channel.Connect());
262  listener.SetSender(&channel);
263
264  // run message loop
265  MessageLoop::current()->Run();
266
267  return 0;
268}
269