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
23using ppapi::proxy::ResourceMessageTestSink;
24using ppapi::thunk::EnterResource;
25using ppapi::thunk::PPB_FileSystem_API;
26
27namespace ppapi {
28namespace proxy {
29
30namespace {
31
32const int64_t kExpectedFileSystemSize = 100;
33const int64_t kQuotaRequestAmount1 = 10;
34const int64_t kQuotaRequestAmount2 = 20;
35
36class 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
55class 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
75class 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.
159TEST_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
210TEST_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