Home | History | Annotate | Download | only in proxy
      1 // Copyright 2014 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 "base/message_loop/message_loop.h"
      6 #include "ppapi/c/pp_errors.h"
      7 #include "ppapi/c/ppb_file_io.h"
      8 #include "ppapi/c/ppb_file_ref.h"
      9 #include "ppapi/c/ppb_file_system.h"
     10 #include "ppapi/proxy/file_system_resource.h"
     11 #include "ppapi/proxy/locking_resource_releaser.h"
     12 #include "ppapi/proxy/plugin_message_filter.h"
     13 #include "ppapi/proxy/ppapi_message_utils.h"
     14 #include "ppapi/proxy/ppapi_messages.h"
     15 #include "ppapi/proxy/ppapi_proxy_test.h"
     16 #include "ppapi/shared_impl/proxy_lock.h"
     17 #include "ppapi/shared_impl/scoped_pp_var.h"
     18 #include "ppapi/shared_impl/var.h"
     19 #include "ppapi/thunk/enter.h"
     20 #include "ppapi/thunk/ppb_file_system_api.h"
     21 #include "ppapi/thunk/thunk.h"
     22 
     23 using ppapi::proxy::ResourceMessageTestSink;
     24 using ppapi::thunk::EnterResource;
     25 using ppapi::thunk::PPB_FileSystem_API;
     26 
     27 namespace ppapi {
     28 namespace proxy {
     29 
     30 namespace {
     31 
     32 const int64_t kExpectedFileSystemSize = 100;
     33 const int64_t kQuotaRequestAmount1 = 10;
     34 const int64_t kQuotaRequestAmount2 = 20;
     35 
     36 class MockCompletionCallback {
     37  public:
     38   MockCompletionCallback() : called_(false) {}
     39 
     40   bool called() { return called_; }
     41   int32_t result() { return result_; }
     42 
     43   static void Callback(void* user_data, int32_t result) {
     44     MockCompletionCallback* that =
     45         reinterpret_cast<MockCompletionCallback*>(user_data);
     46     that->called_ = true;
     47     that->result_ = result;
     48   }
     49 
     50  private:
     51   bool called_;
     52   int32_t result_;
     53 };
     54 
     55 class MockRequestQuotaCallback {
     56  public:
     57   MockRequestQuotaCallback() : called_(false) {}
     58 
     59   bool called() { return called_; }
     60   int64_t result() { return result_; }
     61 
     62   void Reset() { called_ = false; }
     63 
     64   void Callback(int64_t result) {
     65     ASSERT_FALSE(called_);
     66     called_ = true;
     67     result_ = result;
     68   }
     69 
     70  private:
     71   bool called_;
     72   int64_t result_;
     73 };
     74 
     75 class FileSystemResourceTest : public PluginProxyTest {
     76  public:
     77   const PPB_FileSystem_1_0* file_system_iface;
     78   const PPB_FileRef_1_1* file_ref_iface;
     79   const PPB_FileIO_1_1* file_io_iface;
     80 
     81   FileSystemResourceTest()
     82       : file_system_iface(thunk::GetPPB_FileSystem_1_0_Thunk()),
     83         file_ref_iface(thunk::GetPPB_FileRef_1_1_Thunk()),
     84         file_io_iface(thunk::GetPPB_FileIO_1_1_Thunk()) {
     85   }
     86 
     87   void SendReply(const ResourceMessageCallParams& params,
     88                  int32_t result,
     89                  const IPC::Message& nested_message) {
     90     ResourceMessageReplyParams reply_params(params.pp_resource(),
     91                                             params.sequence());
     92     reply_params.set_result(result);
     93     PluginMessageFilter::DispatchResourceReplyForTest(
     94         reply_params, nested_message);
     95   }
     96 
     97   void SendOpenReply(const ResourceMessageCallParams& params, int32_t result) {
     98     SendReply(params, result, PpapiPluginMsg_FileSystem_OpenReply());
     99   }
    100 
    101   // Opens the given file system.
    102   void OpenFileSystem(PP_Resource file_system) {
    103     MockCompletionCallback cb;
    104     int32_t result = file_system_iface->Open(
    105         file_system,
    106         kExpectedFileSystemSize,
    107         PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb));
    108     ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    109 
    110     // Should have sent two new "open" messages to the browser and renderer.
    111     ResourceMessageTestSink::ResourceCallVector open_messages =
    112         sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID);
    113     ASSERT_EQ(2U, open_messages.size());
    114     sink().ClearMessages();
    115 
    116     // The resource is expecting two replies.
    117     SendOpenReply(open_messages[0].first, PP_OK);
    118     SendOpenReply(open_messages[1].first, PP_OK);
    119 
    120     ASSERT_TRUE(cb.called());
    121     ASSERT_EQ(PP_OK, cb.result());
    122   }
    123 
    124   // Opens the given file in the given file system. Since there is no host,
    125   // the file handle will be invalid.
    126   void OpenFile(PP_Resource file_io,
    127                 PP_Resource file_ref,
    128                 PP_Resource file_system) {
    129     MockCompletionCallback cb;
    130     int32_t result = file_io_iface->Open(
    131         file_io,
    132         file_ref,
    133         PP_FILEOPENFLAG_WRITE,
    134         PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb));
    135     ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    136 
    137     // Should have sent an "open" message.
    138     ResourceMessageCallParams params;
    139     IPC::Message msg;
    140     ASSERT_TRUE(sink().GetFirstResourceCallMatching(
    141         PpapiHostMsg_FileIO_Open::ID, &params, &msg));
    142     sink().ClearMessages();
    143 
    144     // Send a success reply.
    145     ResourceMessageReplyParams reply_params(params.pp_resource(),
    146                                             params.sequence());
    147     reply_params.set_result(PP_OK);
    148     PluginMessageFilter::DispatchResourceReplyForTest(
    149         reply_params,
    150         PpapiPluginMsg_FileIO_OpenReply(file_system,
    151                                         0 /* max_written_offset */));
    152   }
    153 };
    154 
    155 }  // namespace
    156 
    157 // Test that Open fails if either host returns failure. The other tests exercise
    158 // the case where both hosts return PP_OK.
    159 TEST_F(FileSystemResourceTest, OpenFailure) {
    160   // Fail if the first reply doesn't return PP_OK.
    161   {
    162     LockingResourceReleaser file_system(
    163         file_system_iface->Create(pp_instance(),
    164                                   PP_FILESYSTEMTYPE_LOCALTEMPORARY));
    165 
    166     MockCompletionCallback cb;
    167     int32_t result = file_system_iface->Open(
    168         file_system.get(),
    169         kExpectedFileSystemSize,
    170         PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb));
    171     ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    172 
    173     ResourceMessageTestSink::ResourceCallVector open_messages =
    174         sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID);
    175     ASSERT_EQ(2U, open_messages.size());
    176     sink().ClearMessages();
    177 
    178     SendOpenReply(open_messages[0].first, PP_ERROR_FAILED);
    179     SendOpenReply(open_messages[1].first, PP_OK);
    180 
    181     ASSERT_TRUE(cb.called());
    182     ASSERT_EQ(PP_ERROR_FAILED, cb.result());
    183   }
    184   // Fail if the second reply doesn't return PP_OK.
    185   {
    186     LockingResourceReleaser file_system(
    187         file_system_iface->Create(pp_instance(),
    188                                   PP_FILESYSTEMTYPE_LOCALTEMPORARY));
    189 
    190     MockCompletionCallback cb;
    191     int32_t result = file_system_iface->Open(
    192         file_system.get(),
    193         kExpectedFileSystemSize,
    194         PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb));
    195     ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    196 
    197     ResourceMessageTestSink::ResourceCallVector open_messages =
    198         sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID);
    199     ASSERT_EQ(2U, open_messages.size());
    200     sink().ClearMessages();
    201 
    202     SendOpenReply(open_messages[0].first, PP_OK);
    203     SendOpenReply(open_messages[1].first, PP_ERROR_FAILED);
    204 
    205     ASSERT_TRUE(cb.called());
    206     ASSERT_EQ(PP_ERROR_FAILED, cb.result());
    207   }
    208 }
    209 
    210 TEST_F(FileSystemResourceTest, RequestQuota) {
    211   LockingResourceReleaser file_system(
    212       file_system_iface->Create(pp_instance(),
    213                                 PP_FILESYSTEMTYPE_LOCALTEMPORARY));
    214 
    215   OpenFileSystem(file_system.get());
    216 
    217   // Create and open two files in the file system. FileIOResource calls
    218   // FileSystemResource::OpenQuotaFile on success.
    219   LockingResourceReleaser file_ref1(
    220       file_ref_iface->Create(file_system.get(), "/file1"));
    221   LockingResourceReleaser file_io1(file_io_iface->Create(pp_instance()));
    222   OpenFile(file_io1.get(), file_ref1.get(), file_system.get());
    223   LockingResourceReleaser file_ref2(
    224       file_ref_iface->Create(file_system.get(), "/file2"));
    225   LockingResourceReleaser file_io2(file_io_iface->Create(pp_instance()));
    226   OpenFile(file_io2.get(), file_ref2.get(), file_system.get());
    227 
    228   EnterResource<PPB_FileSystem_API> enter(file_system.get(), true);
    229   ASSERT_FALSE(enter.failed());
    230   PPB_FileSystem_API* file_system_api = enter.object();
    231 
    232   MockRequestQuotaCallback cb1;
    233   int64_t result = file_system_api->RequestQuota(
    234       kQuotaRequestAmount1,
    235       base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1)));
    236   ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    237 
    238   // Should have sent a "reserve quota" message, with the amount of the request
    239   // and a map of all currently open files to their max written offsets.
    240   ResourceMessageCallParams params;
    241   IPC::Message msg;
    242   ASSERT_TRUE(sink().GetFirstResourceCallMatching(
    243       PpapiHostMsg_FileSystem_ReserveQuota::ID, &params, &msg));
    244   sink().ClearMessages();
    245 
    246   int64_t amount = 0;
    247   FileGrowthMap file_growths;
    248   ASSERT_TRUE(UnpackMessage<PpapiHostMsg_FileSystem_ReserveQuota>(
    249       msg, &amount, &file_growths));
    250   ASSERT_EQ(kQuotaRequestAmount1, amount);
    251   ASSERT_EQ(2U, file_growths.size());
    252   ASSERT_EQ(0, file_growths[file_io1.get()].max_written_offset);
    253   ASSERT_EQ(0, file_growths[file_io2.get()].max_written_offset);
    254 
    255   // Make another request while the "reserve quota" message is pending.
    256   MockRequestQuotaCallback cb2;
    257   result = file_system_api->RequestQuota(
    258       kQuotaRequestAmount2,
    259       base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb2)));
    260   ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    261   // No new "reserve quota" message should be sent while one is pending.
    262   ASSERT_FALSE(sink().GetFirstResourceCallMatching(
    263       PpapiHostMsg_FileSystem_ReserveQuota::ID, &params, &msg));
    264   {
    265     ProxyAutoUnlock unlock_to_prevent_deadlock;
    266     // Reply with quota reservation amount sufficient to cover both requests.
    267     // Both callbacks should be called with the requests granted.
    268     SendReply(params,
    269               PP_OK,
    270               PpapiPluginMsg_FileSystem_ReserveQuotaReply(
    271                   kQuotaRequestAmount1 + kQuotaRequestAmount2,
    272                   FileGrowthMapToFileSizeMapForTesting(file_growths)));
    273   }
    274   ASSERT_TRUE(cb1.called());
    275   ASSERT_EQ(kQuotaRequestAmount1, cb1.result());
    276   ASSERT_TRUE(cb2.called());
    277   ASSERT_EQ(kQuotaRequestAmount2, cb2.result());
    278   cb1.Reset();
    279   cb2.Reset();
    280 
    281   // All requests should fail when insufficient quota is returned to satisfy
    282   // the first request.
    283   result = file_system_api->RequestQuota(
    284       kQuotaRequestAmount1,
    285       base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1)));
    286   ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    287   result = file_system_api->RequestQuota(
    288       kQuotaRequestAmount2,
    289       base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb2)));
    290   ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    291 
    292   ASSERT_TRUE(sink().GetFirstResourceCallMatching(
    293       PpapiHostMsg_FileSystem_ReserveQuota::ID, &params, &msg));
    294   sink().ClearMessages();
    295   {
    296     ProxyAutoUnlock unlock_to_prevent_deadlock;
    297     // Reply with quota reservation amount insufficient to cover the first
    298     // request.
    299     SendReply(params,
    300               PP_OK,
    301               PpapiPluginMsg_FileSystem_ReserveQuotaReply(
    302                   kQuotaRequestAmount1 - 1,
    303                   FileGrowthMapToFileSizeMapForTesting(file_growths)));
    304   }
    305   ASSERT_TRUE(cb1.called());
    306   ASSERT_EQ(0, cb1.result());
    307   ASSERT_TRUE(cb2.called());
    308   ASSERT_EQ(0, cb2.result());
    309   cb1.Reset();
    310   cb2.Reset();
    311 
    312   // A new request should be made if the quota reservation is enough to satisfy
    313   // at least one request.
    314   result = file_system_api->RequestQuota(
    315       kQuotaRequestAmount1,
    316       base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1)));
    317   ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    318   result = file_system_api->RequestQuota(
    319       kQuotaRequestAmount2,
    320       base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb2)));
    321   ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
    322 
    323   ASSERT_TRUE(sink().GetFirstResourceCallMatching(
    324       PpapiHostMsg_FileSystem_ReserveQuota::ID, &params, &msg));
    325   sink().ClearMessages();
    326   {
    327     ProxyAutoUnlock unlock_to_prevent_deadlock;
    328     // Reply with quota reservation amount sufficient only to cover the first
    329     // request.
    330     SendReply(params,
    331               PP_OK,
    332               PpapiPluginMsg_FileSystem_ReserveQuotaReply(
    333                   kQuotaRequestAmount1,
    334                   FileGrowthMapToFileSizeMapForTesting(file_growths)));
    335   }
    336   ASSERT_TRUE(cb1.called());
    337   ASSERT_EQ(kQuotaRequestAmount1, cb1.result());
    338   ASSERT_FALSE(cb2.called());
    339 
    340   // Another request message should have been sent.
    341   ASSERT_TRUE(sink().GetFirstResourceCallMatching(
    342       PpapiHostMsg_FileSystem_ReserveQuota::ID, &params, &msg));
    343   sink().ClearMessages();
    344   {
    345     ProxyAutoUnlock unlock_to_prevent_deadlock;
    346     // Reply with quota reservation amount sufficient to cover the second
    347     // request and some extra.
    348     SendReply(params,
    349               PP_OK,
    350               PpapiPluginMsg_FileSystem_ReserveQuotaReply(
    351                   kQuotaRequestAmount1 + kQuotaRequestAmount2,
    352                   FileGrowthMapToFileSizeMapForTesting(file_growths)));
    353   }
    354 
    355   ASSERT_TRUE(cb2.called());
    356   ASSERT_EQ(kQuotaRequestAmount2, cb2.result());
    357   cb1.Reset();
    358   cb2.Reset();
    359 
    360   // There is kQuotaRequestAmount1 of quota left, and a request for it should
    361   // succeed immediately.
    362   result = file_system_api->RequestQuota(
    363       kQuotaRequestAmount1,
    364       base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1)));
    365   ASSERT_EQ(kQuotaRequestAmount1, result);
    366 }
    367 
    368 }  // namespace proxy
    369 }  // namespace ppapi
    370