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 22 namespace { 23 24 // Name of IPC Channel to use for Server<-> Child Communications. 25 const 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. 32 bool 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. 67 class 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 121 FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy() 122 : child_process_(0) { 123 } 124 125 bool 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 143 FFUnitTestDecryptorProxy::~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. 155 class 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. 166 bool 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 187 bool 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 198 string16 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. 212 class 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. 256 MULTIPROCESS_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