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