Home | History | Annotate | Download | only in importer
      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